1 /*********
2 *
3 * This file is part of BibleTime's source code, http://www.bibletime.info/.
4 *
5 * Copyright 1999-2016 by the BibleTime developers.
6 * The BibleTime source code is licensed under the GNU General Public License version 2.0.
7 *
8 **********/
9 
10 #include "bibletime.h"
11 
12 #include <cstdlib>
13 #include <exception>
14 #include <QAction>
15 #include <QApplication>
16 #include <QCloseEvent>
17 #include <QDebug>
18 #include <QInputDialog>
19 #include <QMdiSubWindow>
20 #include <QSplashScreen>
21 #include <QSplitter>
22 #include "backend/config/btconfig.h"
23 #include "backend/drivers/cswordbiblemoduleinfo.h"
24 #include "backend/drivers/cswordbookmoduleinfo.h"
25 #include "backend/drivers/cswordcommentarymoduleinfo.h"
26 #include "backend/drivers/cswordlexiconmoduleinfo.h"
27 #include "backend/drivers/cswordmoduleinfo.h"
28 #include "backend/keys/cswordldkey.h"
29 #include "backend/keys/cswordversekey.h"
30 #include "bibletimeapp.h"
31 #include "frontend/btaboutmoduledialog.h"
32 #include "frontend/cmdiarea.h"
33 #include "frontend/display/btfindwidget.h"
34 #include "frontend/displaywindow/btactioncollection.h"
35 #include "frontend/displaywindow/cbiblereadwindow.h"
36 #include "frontend/displaywindow/cbookreadwindow.h"
37 #include "frontend/displaywindow/ccommentaryreadwindow.h"
38 #include "frontend/displaywindow/cdisplaywindow.h"
39 #include "frontend/displaywindow/chtmlwritewindow.h"
40 #include "frontend/displaywindow/clexiconreadwindow.h"
41 #include "frontend/displaywindow/cplainwritewindow.h"
42 #include "frontend/displaywindow/creadwindow.h"
43 #include "frontend/keychooser/ckeychooser.h"
44 #include "frontend/messagedialog.h"
45 #include "frontend/searchdialog/csearchdialog.h"
46 #include "util/btassert.h"
47 #include "util/cresmgr.h"
48 #include "util/directory.h"
49 
50 
51 BibleTime *BibleTime::m_instance = nullptr;
52 
BibleTime(QWidget * parent,Qt::WindowFlags flags)53 BibleTime::BibleTime(QWidget *parent, Qt::WindowFlags flags)
54     : QMainWindow(parent, flags)
55 {
56     namespace DU = util::directory;
57 
58     BT_ASSERT(!m_instance);
59     m_instance = this;
60 
61     QSplashScreen *splash = nullptr;
62     QString splashHtml;
63 
64     if (btConfig().value<bool>("GUI/showSplashScreen", true)) {
65         splashHtml = "<div style='background:transparent;color:white;font-weight:bold'>%1"
66                      "</div>";
67 
68         static const char splash1[] = "startuplogo.png";
69         static const char splash2[] = "startuplogo_christmas.png";
70         static const char splash3[] = "startuplogo_easter.jpg";
71         static const char * const splashes[3] = {
72             splash1, splash2, splash3
73         };
74         QString splashImage = DU::getPicsDir().canonicalPath().append("/")
75                                               .append(splashes[rand() % 3]);
76         QPixmap pm;
77         if (!pm.load(splashImage)) {
78             qWarning("Can't load startuplogo! Check your installation.");
79         }
80         splash = new QSplashScreen(this, pm);
81         splash->setAttribute(Qt::WA_DeleteOnClose);
82         splash->finish(this);
83         splash->showMessage(splashHtml.arg(tr("Initializing the SWORD engine...")),
84                             Qt::AlignCenter);
85         splash->show();
86         qApp->processEvents();
87     }
88     initBackends();
89 
90     if (splash != nullptr) {
91         splash->showMessage(splashHtml.arg(tr("Creating BibleTime's user interface...")),
92                             Qt::AlignCenter);
93         qApp->processEvents();
94     }
95     initView();
96 
97     if (splash != nullptr) {
98         splash->showMessage(splashHtml.arg(tr("Initializing menu- and toolbars...")),
99                             Qt::AlignCenter);
100         qApp->processEvents();
101     }
102     initActions();
103     initMenubar();
104     initToolbars();
105     initConnections();
106 
107     setWindowTitle("BibleTime " BT_VERSION);
108     setWindowIcon(CResMgr::mainWindow::icon());
109     retranslateUi();
110 }
111 
~BibleTime()112 BibleTime::~BibleTime() {
113     //  delete m_dcopInterface;
114     // The backend is deleted by the BibleTimeApp instance
115 #ifndef NDEBUG
116     deleteDebugWindow();
117 #endif
118     saveProfile();
119 }
120 
121 namespace {
122 
createReadInstance(QList<CSwordModuleInfo * > const modules,CMDIArea * const parent)123 CReadWindow * createReadInstance(QList<CSwordModuleInfo *> const modules,
124                                  CMDIArea * const parent)
125 {
126     switch (modules.first()->type()) {
127         case CSwordModuleInfo::Bible:
128             return new CBibleReadWindow(modules, parent);
129         case CSwordModuleInfo::Commentary:
130             return new CCommentaryReadWindow(modules, parent);
131         case CSwordModuleInfo::Lexicon:
132             return new CLexiconReadWindow(modules, parent);
133         case CSwordModuleInfo::GenericBook:
134             return new CBookReadWindow(modules, parent);
135         default:
136             qFatal("unknown module type");
137             std::terminate();
138     }
139 }
140 
141 } // anonymous namespace
142 
143 /** Creates a new presenter in the MDI area according to the type of the module. */
createReadDisplayWindow(QList<CSwordModuleInfo * > modules,const QString & key)144 CDisplayWindow* BibleTime::createReadDisplayWindow(QList<CSwordModuleInfo*> modules, const QString& key) {
145     qApp->setOverrideCursor( QCursor(Qt::WaitCursor) );
146     // qDebug() << "BibleTime::createReadDisplayWindow(QList<CSwordModuleInfo*> modules, const QString& key)";
147     CDisplayWindow * const displayWindow = createReadInstance(modules, m_mdi);
148     if ( displayWindow ) {
149         displayWindow->init();
150         m_mdi->addSubWindow(displayWindow);
151         displayWindow->show();
152         //   if (!key.isEmpty())
153         displayWindow->lookupKey(key);
154     }
155     // We have to process pending events here, otherwise displayWindow is not fully painted
156     qApp->processEvents();
157     // Now all events, including mouse clicks for the displayWindow have been handled
158     // and we can let the user click the same module again
159     //m_bookshelfPage->unfreezeModules(modules);
160     qApp->restoreOverrideCursor();
161     return displayWindow;
162 }
163 
164 
165 /** Creates a new presenter in the MDI area according to the type of the module. */
createReadDisplayWindow(CSwordModuleInfo * module,const QString & key)166 CDisplayWindow* BibleTime::createReadDisplayWindow(CSwordModuleInfo* module, const QString& key) {
167     return createReadDisplayWindow(QList<CSwordModuleInfo*>() << module, key);
168 }
169 
createWriteDisplayWindow(CSwordModuleInfo * module,const QString & key,CPlainWriteWindow::WriteWindowType type)170 CDisplayWindow * BibleTime::createWriteDisplayWindow(CSwordModuleInfo * module, const QString & key, CPlainWriteWindow::WriteWindowType type) {
171     qApp->setOverrideCursor( QCursor(Qt::WaitCursor) );
172 
173     CDisplayWindow * const displayWindow =
174         (type == CPlainWriteWindow::HTMLWindow)
175         ? new CHTMLWriteWindow(QList<CSwordModuleInfo *>() << module, m_mdi)
176         : new CPlainWriteWindow(QList<CSwordModuleInfo *>() << module, m_mdi);
177     displayWindow->init();
178     m_mdi->addSubWindow(displayWindow);
179     if (m_mdi->subWindowList().isEmpty())
180         displayWindow->showMaximized();
181     else
182         displayWindow->show();
183     displayWindow->lookupKey(key);
184 
185     qApp->restoreOverrideCursor();
186     return displayWindow;
187 }
188 
moduleEditPlain(CSwordModuleInfo * module)189 CDisplayWindow* BibleTime::moduleEditPlain(CSwordModuleInfo *module) {
190     /// \todo Refactor this.
191     return createWriteDisplayWindow(module,
192                                     QString::null,
193                                     CPlainWriteWindow::PlainTextWindow);
194 }
195 
moduleEditHtml(CSwordModuleInfo * module)196 CDisplayWindow* BibleTime::moduleEditHtml(CSwordModuleInfo *module) {
197     /// \todo Refactor this.
198     return createWriteDisplayWindow(module,
199                                     QString::null,
200                                     CPlainWriteWindow::HTMLWindow);
201 }
202 
203 
searchInModule(CSwordModuleInfo * module)204 void BibleTime::searchInModule(CSwordModuleInfo *module) {
205     /// \todo Refactor this.
206     BtConstModuleList modules;
207     modules.append(module);
208     Search::CSearchDialog::openDialog(modules, QString::null);
209 }
210 
moduleUnlock(CSwordModuleInfo * module,QWidget * parent)211 bool BibleTime::moduleUnlock(CSwordModuleInfo *module, QWidget *parent) {
212     /// \todo Write a proper unlocking dialog with integrated error messages.
213     QString unlockKey;
214     bool ok;
215     for (;;) {
216         unlockKey = QInputDialog::getText(
217             parent, tr("Unlock Work"), tr("Enter the unlock key for %1.").arg(module->name()),
218             QLineEdit::Normal, module->config(CSwordModuleInfo::CipherKey), &ok
219         );
220         if (!ok) return false;
221         module->unlock(unlockKey);
222 
223         /// \todo refactor this module reload
224         /* There is currently a deficiency in sword 1.6.1 in that backend->setCipherKey() does
225          * not work correctly for modules from which data was already fetched. Therefore we have to
226          * reload the modules.
227          */
228         {
229             const QString moduleName(module->name());
230             CSwordBackend *backend = CSwordBackend::instance();
231             backend->reloadModules(CSwordBackend::OtherChange);
232             module = backend->findModuleByName(moduleName);
233             BT_ASSERT(module);
234         }
235 
236         if (!module->isLocked()) break;
237         message::showWarning(parent, tr("Warning: Invalid unlock key!"),
238                              tr("The unlock key you provided did not properly unlock this "
239                                 "module. Please try again."));
240     }
241     return true;
242 }
243 
slotModuleUnlock(CSwordModuleInfo * module)244 void BibleTime::slotModuleUnlock(CSwordModuleInfo *module) {
245     moduleUnlock(module, this);
246 }
247 
moduleAbout(CSwordModuleInfo * module)248 void BibleTime::moduleAbout(CSwordModuleInfo *module) {
249     BTAboutModuleDialog *dialog = new BTAboutModuleDialog(module, this);
250     dialog->setAttribute(Qt::WA_DeleteOnClose); // Destroy dialog when closed
251     dialog->show();
252     dialog->raise();
253 }
254 
255 /** Refreshes all presenters.*/
refreshDisplayWindows() const256 void BibleTime::refreshDisplayWindows() const {
257     Q_FOREACH(QMdiSubWindow const * const subWindow, m_mdi->subWindowList())
258         if (CDisplayWindow * const window =
259                 dynamic_cast<CDisplayWindow*>(subWindow->widget()))
260             window->reload(CSwordBackend::OtherChange);
261 }
262 
closeEvent(QCloseEvent * event)263 void BibleTime::closeEvent(QCloseEvent *event) {
264     /*
265       Sequentially queries all open subwindows whether its fine to close them. If some sub-
266       window returns false, the querying is stopped and the close event is ignored. If all
267       subwindows return true, the close event is accepted.
268     */
269     Q_FOREACH (QMdiSubWindow * const subWindow, m_mdi->subWindowList()) {
270         if (CDisplayWindow * const window = dynamic_cast<CDisplayWindow*>(subWindow->widget())) {
271             if (!window->queryClose()) {
272                 event->ignore();
273                 return;
274             }
275         }
276     }
277     event->accept();
278 }
279 
processCommandline(bool ignoreSession,const QString & bibleKey)280 void BibleTime::processCommandline(bool ignoreSession, const QString &bibleKey) {
281     if (btConfig().value<bool>("state/crashedTwoTimes", false)) {
282         return;
283     }
284 
285     // Restore workspace if not not ignoring session data:
286     if (!ignoreSession)
287         reloadProfile();
288 
289     if (btConfig().value<bool>("state/crashedLastTime", false)) {
290         return;
291     }
292 
293     if (!bibleKey.isNull()) {
294         CSwordModuleInfo* bible = btConfig().getDefaultSwordModuleByType("standardBible");
295         if (bibleKey == "random") {
296             CSwordVerseKey vk(nullptr);
297             const int maxIndex = 31100;
298             int newIndex = rand() % maxIndex;
299             vk.setPosition(sword::TOP);
300             vk.setIndex(newIndex);
301             createReadDisplayWindow(bible, vk.key());
302         } else {
303             createReadDisplayWindow(bible, bibleKey);
304         }
305 
306         /*
307           We are sure only one window is open - it should be displayed
308           fullscreen in the working area:
309         */
310         m_mdi->myTileVertical();
311     }
312 
313     if (btConfig().value<bool>("state/crashedLastTime", false)) {
314         btConfig().setValue("state/crashedTwoTimes", true);
315     }
316     else {
317         btConfig().setValue("state/crashedLastTime", true);
318     }
319     btConfig().sync();
320 }
321 
event(QEvent * event)322 bool BibleTime::event(QEvent* event) {
323     if (event->type() == QEvent::Close)
324         Search::CSearchDialog::closeDialog();
325     return QMainWindow::event(event);
326 }
327 
getCurrentModule()328 const CSwordModuleInfo* BibleTime::getCurrentModule() {
329     QMdiSubWindow* activeSubWindow = m_mdi->activeSubWindow();
330     if (!activeSubWindow)
331         return nullptr;
332     CDisplayWindow* w = dynamic_cast<CDisplayWindow*>(activeSubWindow->widget());
333     if (!w)
334         return nullptr;
335     return w->modules().first();
336 }
337 
openFindWidget()338 void BibleTime::openFindWidget()
339 {
340     m_findWidget->setVisible(true);
341     m_findWidget->showAndSelect();
342 }
343