summaryrefslogtreecommitdiff
path: root/src/frontend/bookshelfmanager/installpage/btsourcewidget.cpp
blob: 81e84fb878988985a2a7db8ffe385191fe30f4e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/*********
*
* This file is part of BibleTime's source code, http://www.bibletime.info/.
*
* Copyright 1999-2008 by the BibleTime developers.
* The BibleTime source code is licensed under the GNU General Public License version 2.0.
*
**********/

#include "frontend/bookshelfmanager/installpage/btsourcewidget.h"

#include <QApplication>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressDialog>
#include <QPushButton>
#include <QString>
#include <QTabBar>
#include <QTabWidget>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QWidget>
#include "frontend/bookshelfmanager/btinstallmgr.h"
#include "frontend/bookshelfmanager/btmodulemanagerdialog.h"
#include "frontend/bookshelfmanager/cswordsetupinstallsourcesdialog.h"
#include "frontend/bookshelfmanager/installpage/btinstallmodulechooserdialog.h"
#include "frontend/bookshelfmanager/installpage/btinstallpage.h"
#include "frontend/bookshelfmanager/installpage/btinstallprogressdialog.h"
#include "frontend/bookshelfmanager/installpage/btsourcearea.h"
#include "frontend/bookshelfmanager/instbackend.h"
#include "util/dialogutil.h"


/**
* Tab Widget that holds source widgets
*/
BtSourceWidget::BtSourceWidget(BtInstallPage* parent)
        : QTabWidget(parent),
        m_page(parent) {
    qDebug() << "BtSourceWidget::BtSourceWidget start";
    initSources();
    // send queued event because "Delete" is initiated from tab which should be deleted
    connect(this, SIGNAL(sigInitSources()), SLOT(initSources()), Qt::QueuedConnection);
    /// \todo choose the page from config

}

BtSourceArea* BtSourceWidget::area() {
    return dynamic_cast<BtSourceArea*>(currentWidget());
}

QString BtSourceWidget::currentSourceName() {
    qDebug() << "BtSourceWidget::currentSourceName: " << m_sourceNameList.at(currentIndex());
    return m_sourceNameList.at(currentIndex());
}

void BtSourceWidget::initSourceConnections() {
    qDebug() << "void BtSourceWidget::initSourceConnections() start";
    if (area()) {
        connect(area()->m_refreshButton, SIGNAL(clicked()), SLOT(slotRefresh()));
        //connect(area()->m_editButton, SIGNAL(clicked()), SLOT(slotEdit()));
        connect(area()->m_deleteButton, SIGNAL(clicked()), SLOT(slotDelete()), Qt::QueuedConnection);
        connect(area()->m_addButton, SIGNAL(clicked()), SLOT(slotAdd()));
        connect(area(), SIGNAL(signalSelectionChanged(QString, int)), SLOT(slotModuleSelectionChanged(QString, int)) );
    }
    qDebug() << "void BtSourceWidget::initSourceConnections() end";
}

void BtSourceWidget::slotEdit() {
    qDebug() << "BtSourceWidget::slotEdit";
    /// \todo open the source editor dialog

    // if the source was changed, init the sources

}

void BtSourceWidget::slotDelete() {
    qDebug() << "void BtSourceWidget::slotDelete() start";
    // ask for confirmation
    int ret = util::showWarning(this, tr("Delete Source?"),
                                tr("Do you really want to delete this source?"),
                                QMessageBox::Yes | QMessageBox::No);

    if (ret == QMessageBox::Yes) {
        instbackend::deleteSource(currentSourceName());
        initSources();
    }
}

void BtSourceWidget::slotAdd() {

    boost::scoped_ptr<CSwordSetupInstallSourcesDialog> dlg( new CSwordSetupInstallSourcesDialog() );
    sword::InstallSource newSource(""); //empty, invalid Source

    if (dlg->exec() == QDialog::Accepted) {
        if (!dlg->wasRemoteListAdded()) {
            newSource = dlg->getSource();
            if ( !((QString)newSource.type.c_str()).isEmpty() ) { // we have a valid source to add
                instbackend::addSource(newSource);
            }
        }
        initSources();
    }
}


void BtSourceWidget::slotRefresh() {
    qDebug() << "void BtSourceWidget::slotRefresh() start";
    // (re)build the module cache for the source

    QString sourceName = currentSourceName();

    // quick enough, make it modal so that we don't need to take care of anything else
    m_progressDialog = new QProgressDialog("", tr("Cancel"), 0 , 100, this);
    m_progressDialog->setWindowTitle(tr("Refreshing Source"));
    m_progressDialog->setMinimumDuration(0);

    /// \todo get rid of the backend code, BtInstallMgr and progressdialog could handle this
    //write method BtInstallMgr::slotRefreshCanceled()
    connect(m_progressDialog, SIGNAL(canceled()), SLOT(slotRefreshCanceled()));

    // BACKEND CODE **********************************************************
    // would this be possible: instbackend::refreshSource( arguments );
    qDebug() << "void BtSourceWidget::slotRefresh 1";
    BtInstallMgr iMgr;
    m_currentInstallMgr = &iMgr; //for the progress dialog
    sword::InstallSource is = instbackend::source(sourceName);
    bool success = false;
    qDebug() << "void BtSourceWidget::slotRefresh 2";
    // connect this directly to the dialog setValue(int) if possible
    connect(&iMgr, SIGNAL(percentCompleted(const int, const int)), SLOT(slotRefreshCompleted(const int, const int)));

    if (instbackend::isRemote(is)) {
        m_progressDialog->show();
        qApp->processEvents();
        this->slotRefreshCompleted(0, 0);
        m_progressDialog->setLabelText(tr("Connecting..."));
        m_progressDialog->setValue(0);
        qApp->processEvents();
        qDebug() << "void BtSourceWidget::slotRefresh 3";
        bool successful = iMgr.refreshRemoteSource( &is );
        if (!successful ) { //make sure the sources were updated sucessfully
            success = true;
            m_progressDialog->setValue(100); //make sure the dialog closes
        }
        else {
            qWarning("InstallMgr: refreshRemoteSources returned an error.");
            success = false;
        }
    }
    else {
        // Local source, update the list
        success = true;
    }

    delete m_progressDialog;
    m_progressDialog = 0;

    // rebuild the view tree and refresh the view
    if (success) {
        qDebug() << "void BtSourceWidget::slotRefresh 4";
        area()->createModuleTree();
    }
}

/// \todo try to move this to BtInstallMgr
void BtSourceWidget::slotRefreshCanceled() {
    qDebug() << "BtSourceWidget::slotRefreshCanceled";
    Q_ASSERT(m_currentInstallMgr);
    if (m_currentInstallMgr) {
        m_currentInstallMgr->terminate();
    }
    qApp->processEvents();
}

/// \todo try to move this to progress dialog
void BtSourceWidget::slotRefreshCompleted(const int, const int current) {
    qDebug() << "BtSourceWidget::slotRefreshCompleted";
    if (m_progressDialog) {
        if (m_progressDialog->labelText() != tr("Refreshing...")) {
            m_progressDialog->setLabelText(tr("Refreshing..."));
        }
        m_progressDialog->setValue(current);
    }
    qApp->processEvents();
}

// init the tabbar, setup the module tree for the current source
void BtSourceWidget::initSources() {
    qDebug() << "void BtSourceWidget::initSources() start";

    //first clear all sources
    //int i = count();
    for (int i = count() - 1; i >= 0; i--) {
        BtSourceArea* a = dynamic_cast<BtSourceArea*>(widget(i));
        a->prepareRemove();
    }
    for (int i = count() - 1; i >= 0; i--) {
        qDebug() << "remove tab" << tabText(i);
        QWidget* w = widget(i);
        removeTab(i);
        delete w;
        qDebug() << "deleted";
    }
    m_sourceNameList.clear();

    // ***** Use the backend to get the list of sources *****
    instbackend::initPassiveFtpMode();
    QStringList sourceList = instbackend::sourceList();
    qDebug() << "got the source list from backend:" << sourceList;
    // Add a default entry, the Crosswire main repository
    if (!sourceList.count()) {
        /// \todo Open a dialog which asks whether to get list from server and add sources
        sword::InstallSource is("FTP");   //default return value
        is.caption = "CrossWire Bible Society";
        is.source = "ftp.crosswire.org";
        is.directory = "/pub/sword/raw";
        // passive ftp is not needed here, it's the default

        instbackend::addSource(is);

        sourceList = instbackend::sourceList();
        //Q_ASSERT( sourceList.count() > 0 );
    }
    qDebug() << "void BtSourceWidget::initSources 1";
    // Add the sources to the widget
    foreach (QString sourceName, sourceList) {
        addSource(sourceName);
    }
    // connect this after the tabs have been created,
    // otherwise the signal is caught too early.
    QObject::connect(this, SIGNAL(currentChanged(int)), this, SLOT(slotTabSelected(int)));
    qDebug() << "void BtSourceWidget::initSources end";
    /// \todo select the current source from the config
    // It's important to choose something because the tree is not initialized until now
    setCurrentIndex(0);
    slotTabSelected(0); // setting the index wasn't enough if there were only 1 tab

    if (sourceList.count() == 0) {
        QHBoxLayout* l = new QHBoxLayout(this);
        QLabel* message = new QLabel(QString("<i>") + tr("No sources were found in the SWORD configuration and BibleTime couldn't create a default source. Check your SWORD configuration and that the configuration path is writable. Then restart the Bookshelf Manager.") + QString("</i>"), this);
        message->setWordWrap(true);
        l->addWidget(message);
    }
}

void BtSourceWidget::addSource(const QString& sourceName) {
    qDebug() << "void BtSourceWidget::addSource(const QString& sourceName) start, with name" << sourceName;
    // The source has already been added to the backend.

    QString type;
    QString server;
    QString path;
    sword::InstallSource is = instbackend::source(sourceName);
    if (instbackend::isRemote(is)) {
        type = tr("Remote:");
        server = is.source.c_str();
        path = is.directory.c_str();
    }
    else { // local source
        type = tr("Local:");
        QFileInfo fi( is.directory.c_str() );
        path = is.directory.c_str();
        if (!(fi.isDir() )) {
            path = path + QString(" ") + tr("Not a directory!"); /// \todo change this
        }
        if (!fi.isReadable()) {
            path = path + QString(" ") + tr("Not readable!"); /// \todo change this
        }
    }

    // Here the tab UI is created and added to the tab widget
    BtSourceArea* area = new BtSourceArea(sourceName);
    int tabNumber = this->addTab(area, sourceName);

    /// \todo add "remote/local", server, path etc.
    QString toolTip(QString("<p style='white-space:pre'>") + sourceName + QString("<br/><b>") + type + QString("</b> ") + server + path + QString("</p>"));
    tabBar()->setTabToolTip(tabNumber, toolTip);

    //select the new tab
    setCurrentIndex(tabNumber);
    m_sourceNameList.append(sourceName);
    initSourceConnections();
    qDebug() << "BtSourceWidget::addSource end";
}

//
void BtSourceWidget::slotModuleSelectionChanged(QString sourceName, int selectedCount) {
    /// \todo editing sources should update the map also
    qDebug() << "BtSourceWidget::slotModuleSelectionChanged start";

    int overallCount = 0;
    m_selectedModulesCountMap.insert(sourceName, selectedCount);
    foreach (int count, m_selectedModulesCountMap) {
        qDebug() << "add" << count << "to overall count of selected modules";
        overallCount += count;
    }

    if (overallCount > 0) {
        m_page->setInstallEnabled(true);
    }
    else {
        m_page->setInstallEnabled(false);
    }
}

void BtSourceWidget::slotTabSelected(int /*index*/) {
    BtSourceArea* area = dynamic_cast<BtSourceArea*>(currentWidget());
    if (area) area->initTreeFirstTime();
}

void BtSourceWidget::slotInstall() {
    qDebug() << "void BtInstallPage::slotInstall start";

    // check that the destination path is writable, do nothing if not and user doesn't want to continue
    QDir dir = QDir(dynamic_cast<BtInstallPage*>(parent())->selectedInstallPath());
    bool canWrite = true;
    if (dir.isReadable()) {
        const QFileInfo fi( dir.canonicalPath() );
        if (!fi.exists() || !fi.isWritable()) {
            canWrite = false;
        }
    }
    else {
        canWrite = false;
    }
    if (!canWrite) {
        const int result = util::showWarning(this, tr("Warning"), tr("The destination directory is not writable or does not exist. Installation will fail unless this has first been fixed."), QMessageBox::Ignore | QMessageBox::Cancel, QMessageBox::Cancel);
        if (result != QMessageBox::Ignore) {
            return;
        }
    }

    // create the confirmation dialog
    // (module tree dialog, modules taken from all sources)
    QString dlgTitle(tr("Install/Update works?"));
    QString dlgLabel(tr("Do you really want to install these works?") +
                     QString("<br/><br/><small>") +
                     tr("Only one version of a work can be installed at the same time. Select only one if there are items marked with red.") +
                     QString("</small>"));

    // with empty list we avoid creating the module tree inside the dialog code
    QList<CSwordModuleInfo*> emptyList;
    BtInstallModuleChooserDialog* dlg = new BtInstallModuleChooserDialog(this, dlgTitle, dlgLabel, &emptyList);
    //dlg->setGrouping(BTModuleTreeItem::Mod);
    QTreeWidget* treeWidget = dlg->treeWidget();
    QTreeWidgetItem* rootItem = treeWidget->invisibleRootItem();

    QStringList nameList;

    // loop through each tab
    for (int tab = 0; tab < count(); ++tab) {
        BtSourceArea* sArea = dynamic_cast<BtSourceArea*>(widget(tab));
        if (sArea && sArea->selectedModules()->count() > 0) {
            // there are selected modules in the source, create items for these
            /// \todo Use new bookshelf model instead
            /// \bug Valgrind reports memory leak:
            QTreeWidgetItem* sourceItem = new QTreeWidgetItem(rootItem);
            sourceItem->setText(0, m_sourceNameList.at(tab));
            sourceItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsTristate | Qt::ItemIsEnabled);
            foreach (QString mName, sArea->selectedModules()->keys()) {
                dlg->initModuleItem(mName, sourceItem);
            }
            sourceItem->setExpanded(true);
        }
    }

    //user accepts the dialog
    connect(dlg, SIGNAL(modulesChanged(QList<CSwordModuleInfo*>, QTreeWidget*)), SLOT(slotInstallAccepted(QList<CSwordModuleInfo*>, QTreeWidget*)) );
    // user checks/unchecks an item, needed for preventing double items
    QObject::connect(treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), dlg, SLOT(slotItemChecked(QTreeWidgetItem*, int)));
    dlg->exec();
    // The OK signal sent by the dialog is catched with slotInstallAccepted.
}

void BtSourceWidget::slotStopInstall(QTreeWidget* /*treeWidget*/) {
    qDebug() << "BtSourceWidget::slotStopInstall";
    // not needed?
}

void BtSourceWidget::slotInstallAccepted(QList<CSwordModuleInfo*> /*modules*/, QTreeWidget* treeWidget) {
    qDebug() << "BtSourceWidget::slotInstallAccepted";

    /// \todo first remove all modules which will be updated from the module list
    // but what modules? all with the same real name? (there may be _n modules...)

    BtModuleManagerDialog* parentDialog = dynamic_cast<BtModuleManagerDialog*>(dynamic_cast<BtInstallPage*>(parent())->parentDialog());

    BtInstallProgressDialog* dlg = new BtInstallProgressDialog(parentDialog, treeWidget, dynamic_cast<BtInstallPage*>(parent())->selectedInstallPath());

    if (!parentDialog) qDebug() << "error, wrong parent!";

    m_page->setInstallEnabled(false);
    // the progress dialog is now modal, it can be made modeless later.
    dlg->exec();

    qDebug() << "BtSourceWidget::slotInstallAccepted end";
}