Handle second-page spreads in two-page mode differently.
[qcomicbook-oblomov] / src / comicmain.cpp
1 /*
2  * This file is a part of QComicBook.
3  *
4  * Copyright (C) 2005-2006 Pawel Stolowski <yogin@linux.bydg.org>
5  *
6  * QComicBook is free software; you can redestribute it and/or modify it
7  * under terms of GNU General Public License by Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY. See GPL for more details.
11  */
12
13 #include "config.h"
14 #include "bookmarks.h"
15 #include "comicmain.h"
16 #include "icons.h"
17 #include "cbicons.h"
18 #include "cbinfo.h"
19 #include "imgview.h"
20 #include "imgarchivesink.h"
21 #include "imgsinkfactory.h"
22 #include "aboutdialog.h"
23 #include "cbsettings.h"
24 #include "history.h"
25 #include "helpbrowser.h"
26 #include "cbconfigdialog.h"
27 #include "statusbar.h"
28 #include "thumbnailswin.h"
29 #include "thumbnailsview.h"
30 #include "thumbnailloader.h"
31 #include "bookmarkmanager.h"
32 #include "miscutil.h"
33 #include "archiverdialog.h"
34 #include "suparchwin.h"
35 #include "imlibimage.h"
36 #include <qimage.h>
37 #include <qmenubar.h>
38 #include <qpopupmenu.h>
39 #include <qstringlist.h>
40 #include <qaction.h>
41 #include <qfiledialog.h>
42 #include <qfileinfo.h>
43 #include <qtoolbar.h>
44 #include <qmessagebox.h>
45 #include <qlabel.h>
46 #include <qframe.h>
47 #include <jumptopagewin.h>
48 #include <qprocess.h>
49 #include <qdragobject.h>
50 #include <typeinfo>
51
52 using namespace QComicBook;
53 using namespace Utility;
54
55 ComicMainWindow::ComicMainWindow(QWidget *parent): QMainWindow(parent, NULL, WType_TopLevel|WDestructiveClose), sink(NULL), currpage(0), edit_menu(NULL)
56 {
57         updateCaption();
58         setIcon(Icons::get(ICON_APPICON).pixmap(QIconSet::Small, true));
59         setMinimumSize(320, 200);
60
61         setAcceptDrops(true); //???
62
63         cfg = &ComicBookSettings::instance();
64
65         setGeometry(cfg->geometry());
66
67         setupActions();
68         setupToolbar();
69         setupStatusbar();
70         setupComicImageView();
71         setupThumbnailsWindow();
72
73         setupFileMenu();  
74         if (cfg->editSupport())
75               setupEditMenu();
76         setupViewMenu();
77         setupNavigationMenu();
78         setupBookmarksMenu();
79         setupSettingsMenu();
80         setupHelpMenu();
81         setupContextMenu();    
82
83         lastdir = cfg->lastDir();
84         recentfiles = new History(10);
85         *recentfiles = cfg->recentlyOpened();
86         setRecentFilesMenu(*recentfiles);
87
88         cfg->restoreDockLayout(this);
89
90         enableComicBookActions(false);
91 }
92
93 ComicMainWindow::~ComicMainWindow()
94 {
95         if (cfg->cacheThumbnails())
96                 ImgDirSink::removeThumbnails(cfg->thumbnailsAge());
97
98         saveSettings();        
99
100         delete recentfiles;
101         delete bookmarks;
102
103         if (sink)
104                 delete sink;
105 }
106
107 void ComicMainWindow::setupActions()
108 {
109         openArchiveAction = new QAction(Icons::get(ICON_OPENARCH), tr("Open archive"), CTRL+Key_O, this);
110         openDirAction = new QAction(Icons::get(ICON_OPENDIR), tr("Open directory"), CTRL+Key_D, this);
111         openNextAction = new QAction(tr("Open next"), CTRL+Key_N, this);
112         openPrevAction = new QAction(tr("Open previous"), CTRL+Key_P, this);
113         fullScreenAction = new QAction(tr("&Fullscreen"), Key_F11, this);
114         nextPageAction = new QAction(Icons::get(ICON_NEXTPAGE), tr("Next page"), Key_PageDown, this);
115         forwardPageAction = new QAction(Icons::get(ICON_FORWARD), tr("5 pages forward"), QKeySequence(), this);
116         backwardPageAction = new QAction(Icons::get (ICON_BACKWARD), tr("5 pages backward"), QKeySequence(), this);
117         jumpDownAction = new QAction(QString::null, Key_Space, this);
118         jumpUpAction = new QAction(QString::null, Key_Backspace, this);
119         prevPageAction = new QAction(Icons::get(ICON_PREVPAGE), tr("&Previous page"), Key_PageUp, this);
120         pageTopAction = new QAction(Icons::get(ICON_PAGETOP), tr("Page top"), Key_Home, this);
121         pageBottomAction = new QAction(Icons::get(ICON_PAGEBOTTOM), tr("Page bottom"), Key_End, this);
122         firstPageAction = new QAction(tr("First page"), CTRL+Key_Home, this);
123         lastPageAction = new QAction(tr("Last page"), CTRL+Key_End, this);
124         scrollRightAction = new QAction(tr("Scroll right"), Key_Right, this);        
125         scrollLeftAction = new QAction(tr("Scroll left"), Key_Left, this);
126         scrollRightFastAction = new QAction(tr("Fast scroll right"), SHIFT+Key_Right, this);
127         scrollLeftFastAction = new QAction(tr("Fast scroll left"), SHIFT+Key_Left, this);
128         scrollUpAction = new QAction(tr("Scroll up"), Key_Up, this);
129         scrollDownAction = new QAction(tr("Scroll down"), Key_Down, this);
130         scrollUpFastAction = new QAction(tr("Fast scroll up"), SHIFT+Key_Up, this);
131         scrollDownFastAction = new QAction(tr("Fast scroll down"), SHIFT+Key_Down, this);
132         quitAction = new QAction(tr("Quit"), CTRL+Key_Q, this);
133
134         QActionGroup *scaleActions = new QActionGroup(this);
135         fitWidthAction = new QAction(Icons::get(ICON_FITWIDTH), tr("Fit width"), ALT+Key_W, scaleActions);                         
136         fitWidthAction->setToggleAction(true);
137         fitHeightAction = new QAction(Icons::get(ICON_FITHEIGHT), tr("Fit height"), ALT+Key_H, scaleActions);
138         fitHeightAction->setToggleAction(true);
139         wholePageAction = new QAction(Icons::get(ICON_WHOLEPAGE), tr("Whole page"), ALT+Key_A, scaleActions);
140         wholePageAction->setToggleAction(true);
141         originalSizeAction = new QAction(Icons::get(ICON_ORGSIZE), tr("Original size"), ALT+Key_O, scaleActions);
142         originalSizeAction->setToggleAction(true);
143         bestFitAction = new QAction(Icons::get(ICON_BESTFIT), tr("Best fit"), ALT+Key_B, scaleActions);
144         bestFitAction->setToggleAction(true);
145         mangaModeAction = new QAction(Icons::get(ICON_JAPANESE), tr("Japanese mode"), CTRL+Key_J, this);
146         mangaModeAction->setToggleAction(true);
147         twoPagesAction = new QAction(Icons::get(ICON_TWOPAGES), tr("Two pages"), CTRL+Key_T, this);
148         twoPagesAction->setToggleAction(true);
149         rotateRightAction = new QAction(Icons::get(ICON_ROTRIGHT), tr("Rotate right"), QKeySequence(), this);
150         rotateLeftAction = new QAction(Icons::get(ICON_ROTLEFT), tr("Rotate left"), QKeySequence(), this);
151         rotateResetAction = new QAction(tr("No rotation"), QKeySequence(), this);
152         togglePreserveRotationAction = new QAction(tr("Preserve rotation"), QKeySequence(), this);
153         togglePreserveRotationAction->setToggleAction(true);
154         showInfoAction = new QAction(Icons::get(ICON_INFO), tr("Info"), ALT+Key_I, this);
155         exitFullScreenAction = new QAction(QString::null, Key_Escape, this);
156         toggleStatusbarAction = new QAction(tr("Statusbar"), QKeySequence(), this);
157         toggleStatusbarAction->setToggleAction(true);
158         toggleThumbnailsAction = new QAction(Icons::get(ICON_THUMBNAILS), tr("Thumbnails"), ALT+Key_T, this);
159         toggleThumbnailsAction->setToggleAction(true);
160         toggleToolbarAction = new QAction(tr("Toolbar"), QKeySequence(), this);
161         toggleToolbarAction->setToggleAction(true);
162
163         connect(openArchiveAction, SIGNAL(activated()), this, SLOT(browseArchive()));
164         connect(openDirAction, SIGNAL(activated()), this, SLOT(browseDirectory()));
165         connect(openNextAction, SIGNAL(activated()), this, SLOT(openNext()));
166         connect(openPrevAction, SIGNAL(activated()), this, SLOT(openPrevious()));
167         connect(showInfoAction, SIGNAL(activated()), this, SLOT(showInfo()));
168         connect(exitFullScreenAction, SIGNAL(activated()), this, SLOT(exitFullscreen()));
169         connect(nextPageAction, SIGNAL(activated()), this, SLOT(nextPage()));
170         connect(forwardPageAction, SIGNAL(activated()), this, SLOT(forwardPages()));
171         connect(firstPageAction, SIGNAL(activated()), this, SLOT(firstPage()));
172         connect(lastPageAction, SIGNAL(activated()), this, SLOT(lastPage()));
173         connect(backwardPageAction, SIGNAL(activated()), this, SLOT(backwardPages())); 
174         connect(quitAction, SIGNAL(activated()), this, SLOT(close()));
175 }
176
177 void ComicMainWindow::setupComicImageView()
178 {
179         view = new ComicImageView(this, cfg->pageSize(), cfg->background());
180         setCentralWidget(view);
181         view->setFocus();
182         view->setSmallCursor(cfg->smallCursor());
183         connect(cfg, SIGNAL(backgroundChanged(const QColor&)), view, SLOT(setBackground(const QColor&)));
184         connect(cfg, SIGNAL(cursorChanged(bool)), view, SLOT(setSmallCursor(bool)));
185         connect(fullScreenAction, SIGNAL(activated()), this, SLOT(toggleFullScreen()));
186         connect(pageTopAction, SIGNAL(activated()), view, SLOT(scrollToTop()));
187         connect(pageBottomAction, SIGNAL(activated()), view, SLOT(scrollToBottom()));
188         connect(scrollRightAction, SIGNAL(activated()), view, SLOT(scrollRight()));
189         connect(scrollLeftAction, SIGNAL(activated()), view, SLOT(scrollLeft()));
190         connect(scrollRightFastAction, SIGNAL(activated()), view, SLOT(scrollRightFast()));
191         connect(scrollLeftFastAction, SIGNAL(activated()), view, SLOT(scrollLeftFast()));
192         connect(scrollUpAction, SIGNAL(activated()), view, SLOT(scrollUp()));
193         connect(scrollDownAction, SIGNAL(activated()), view, SLOT(scrollDown()));       
194         connect(scrollUpFastAction, SIGNAL(activated()), view, SLOT(scrollUpFast()));        
195         connect(scrollDownFastAction, SIGNAL(activated()), view, SLOT(scrollDownFast()));
196         connect(fitWidthAction, SIGNAL(activated()), view, SLOT(setSizeFitWidth()));        
197         connect(fitHeightAction, SIGNAL(activated()), view, SLOT(setSizeFitHeight()));        
198         connect(wholePageAction, SIGNAL(activated()), view, SLOT(setSizeWholePage()));        
199         connect(originalSizeAction, SIGNAL(activated()), view, SLOT(setSizeOriginal()));        
200         connect(bestFitAction, SIGNAL(activated()), view, SLOT(setSizeBestFit()));        
201         connect(mangaModeAction, SIGNAL(toggled(bool)), this, SLOT(toggleJapaneseMode(bool)));        
202         connect(twoPagesAction, SIGNAL(toggled(bool)), this, SLOT(toggleTwoPages(bool)));
203         connect(prevPageAction, SIGNAL(activated()), this, SLOT(prevPage()));       
204         connect(rotateRightAction, SIGNAL(activated()), view, SLOT(rotateRight()));        
205         connect(rotateLeftAction, SIGNAL(activated()), view, SLOT(rotateLeft()));
206         connect(rotateResetAction, SIGNAL(activated()), view, SLOT(resetRotation()));
207         connect(jumpDownAction, SIGNAL(activated()), view, SLOT(jumpDown()));
208         connect(jumpUpAction, SIGNAL(activated()), view, SLOT(jumpUp()));
209         if (cfg->continuousScrolling())
210         {
211                 connect(view, SIGNAL(bottomReached()), this, SLOT(nextPage()));
212                 connect(view, SIGNAL(topReached()), this, SLOT(prevPageBottom()));
213         }
214         connect(view, SIGNAL(doubleClick()), this, SLOT(nextPage()));
215         view->enableScrollbars(cfg->scrollbarsVisible());
216         QAction *which = originalSizeAction;
217         switch (cfg->pageSize())
218         {
219                 case FitWidth:  which = fitWidthAction; break;
220                 case FitHeight: which = fitHeightAction; break;
221                 case BestFit:   which = bestFitAction; break;
222                 case WholePage: which = wholePageAction; break;
223                 case Original:  which = originalSizeAction; break;
224         }
225         which->setOn(true);
226 }
227
228 void ComicMainWindow::setupThumbnailsWindow()
229 {
230         thumbswin = new ThumbnailsWindow(QDockWindow::InDock, this);
231         moveDockWindow(thumbswin, Qt::DockLeft); //initial position of thumbnails window
232         connect(thumbswin, SIGNAL(requestedPage(int, bool)), this, SLOT(jumpToPage(int, bool)));
233         connect(thumbswin, SIGNAL(visibilityChanged(bool)), this, SLOT(thumbnailsVisibilityChanged(bool)));
234         connect(thumbswin, SIGNAL(visibilityChanged(bool)), toggleThumbnailsAction, SLOT(setOn(bool)));
235         connect(toggleThumbnailsAction, SIGNAL(toggled(bool)), thumbswin, SLOT(setShown(bool)));       
236 }
237
238 void ComicMainWindow::setupToolbar()
239 {
240         toolbar = new QToolBar(tr("Toolbar"), this);
241         openArchiveAction->addTo(toolbar);
242         openDirAction->addTo(toolbar);
243         toolbar->addSeparator();
244         showInfoAction->addTo(toolbar);
245         toggleThumbnailsAction->addTo(toolbar);
246         toolbar->addSeparator();
247         twoPagesAction->addTo(toolbar);
248         mangaModeAction->addTo(toolbar);
249         toolbar->addSeparator();
250         prevPageAction->addTo(toolbar);
251         nextPageAction->addTo(toolbar);
252         backwardPageAction->addTo(toolbar);
253         forwardPageAction->addTo(toolbar);
254         pageTopAction->addTo(toolbar);
255         pageBottomAction->addTo(toolbar);
256         toolbar->addSeparator();
257         originalSizeAction->addTo(toolbar);
258         fitWidthAction->addTo(toolbar);
259         fitHeightAction->addTo(toolbar);
260         wholePageAction->addTo(toolbar);
261         bestFitAction->addTo(toolbar);
262         toolbar->addSeparator();
263         rotateRightAction->addTo(toolbar);
264         rotateLeftAction->addTo(toolbar);
265         connect(toggleToolbarAction, SIGNAL(toggled(bool)), toolbar, SLOT(setShown(bool)));
266         connect(toolbar, SIGNAL(visibilityChanged(bool)), this, SLOT(toolbarVisibilityChanged(bool)));
267 }
268
269 void ComicMainWindow::setupFileMenu()
270 {
271         file_menu = new QPopupMenu(this);
272         openArchiveAction->addTo(file_menu);
273         openDirAction->addTo(file_menu);
274         openNextAction->addTo(file_menu);
275         openPrevAction->addTo(file_menu);
276         recent_menu = new QPopupMenu(this);
277         file_menu->insertItem(tr("Recently opened"), recent_menu);
278         connect(recent_menu, SIGNAL(activated(int)), this, SLOT(recentSelected(int)));
279         file_menu->insertSeparator();
280         //create_id = file_menu->insertItem(tr("Create archive"), this, SLOT(createArchive()));
281         //file_menu->insertSeparator();
282         showInfoAction->addTo(file_menu);
283         file_menu->insertSeparator();
284         close_id = file_menu->insertItem(tr("Close"), this, SLOT(closeSink()));
285         file_menu->insertSeparator();
286         quitAction->addTo(file_menu);
287         menuBar()->insertItem(tr("&File"), file_menu);   
288 }
289
290 void ComicMainWindow::setupEditMenu()
291 {
292         edit_menu = new QPopupMenu(this);
293         gimp_id = edit_menu->insertItem(tr("Open with Gimp"), this, SLOT(openWithGimp()));
294         //edit_menu->insertItem(tr("Open with Kolour Paint"), this, SLOT(openWithGimp()));
295         //edit_menu->insertItem(tr("Open with ImageMagick"), this, SLOT(openWithGimp()));
296         edit_menu->insertSeparator();
297         reload_id = edit_menu->insertItem(tr("Reload page"), this, SLOT(reloadPage()));
298         menuBar()->insertItem(tr("&Edit"), edit_menu);
299 }
300
301 void ComicMainWindow::setupViewMenu()
302 {
303         view_menu = new QPopupMenu(this);
304         view_menu->setCheckable(true);
305         originalSizeAction->addTo(view_menu);
306         fitWidthAction->addTo(view_menu);
307         fitHeightAction->addTo(view_menu);
308         wholePageAction->addTo(view_menu);
309         bestFitAction->addTo(view_menu);
310         view_menu->insertSeparator();
311         rotateRightAction->addTo(view_menu);
312         rotateLeftAction->addTo(view_menu);
313         rotateResetAction->addTo(view_menu);
314         togglePreserveRotationAction->addTo(view_menu);
315         view_menu->insertSeparator();
316         twoPagesAction->addTo(view_menu);
317         mangaModeAction->addTo(view_menu);
318         toggleThumbnailsAction->addTo(view_menu);
319         menuBar()->insertItem(tr("&View"), view_menu);     
320 }
321
322 void ComicMainWindow::setupNavigationMenu()
323 {
324         navi_menu = new QPopupMenu(this);
325         nextPageAction->addTo(navi_menu);
326         prevPageAction->addTo(navi_menu);
327         navi_menu->insertSeparator();
328         forwardPageAction->addTo(navi_menu);
329         backwardPageAction->addTo(navi_menu);
330         navi_menu->insertSeparator();
331         jumpto_id = navi_menu->insertItem(tr("Jump to page..."), this, SLOT(showJumpToPage()));
332         firstPageAction->addTo(navi_menu);
333         lastPageAction->addTo(navi_menu);
334         navi_menu->insertSeparator();
335         pageTopAction->addTo(navi_menu);
336         pageBottomAction->addTo(navi_menu);
337         navi_menu->insertSeparator();
338         contscr_id = navi_menu->insertItem(tr("Continuous scrolling"), this, SLOT(toggleContinousScroll()));
339         twoPagesAction->setOn(cfg->twoPagesMode());
340         mangaModeAction->setOn(cfg->japaneseMode());
341         navi_menu->setItemChecked(contscr_id, cfg->continuousScrolling());
342         menuBar()->insertItem(tr("&Navigation"), navi_menu);
343 }
344
345 void ComicMainWindow::setupBookmarksMenu()
346 {
347         bookmarks_menu = new QPopupMenu(this);
348         bookmarks = new Bookmarks(bookmarks_menu);
349         menuBar()->insertItem(tr("&Bookmarks"), bookmarks_menu);
350         setbookmark_id = bookmarks_menu->insertItem(Icons::get(ICON_BOOKMARK), tr("Set bookmark for this comicbook"), this, SLOT(setBookmark()));
351         rmvbookmark_id = bookmarks_menu->insertItem(tr("Remove bookmark for this comicbook"), this, SLOT(removeBookmark()));
352         bookmarks_menu->insertItem(tr("Manage bookmarks"), this, SLOT(openBookmarksManager()));
353         bookmarks_menu->insertSeparator();
354         bookmarks->load();
355         connect(bookmarks_menu, SIGNAL(activated(int)), this, SLOT(bookmarkSelected(int)));
356 }
357
358 void ComicMainWindow::setupSettingsMenu()
359 {
360         settings_menu = new QPopupMenu(this);
361         scrv_id = settings_menu->insertItem(tr("Scrollbars"), this, SLOT(toggleScrollbars()));
362         settings_menu->setItemChecked(scrv_id, cfg->scrollbarsVisible());
363         toggleToolbarAction->addTo(settings_menu);
364         toggleStatusbarAction->addTo(settings_menu);
365         settings_menu->insertSeparator();
366         fullScreenAction->addTo(settings_menu);
367         settings_menu->insertSeparator();
368         settings_menu->insertItem(Icons::get(ICON_SETTINGS), tr("Configure QComicBook"), this, SLOT(showConfigDialog()));
369         menuBar()->insertItem(tr("&Settings"), settings_menu);
370 }
371
372 void ComicMainWindow::setupHelpMenu()
373 {
374         QPopupMenu *help_menu = new QPopupMenu(this);
375         help_menu->insertItem(tr("System information"), this, SLOT(showSysInfo()));
376         help_menu->insertSeparator();
377         help_menu->insertItem(tr("Index"), this, SLOT(showHelp()));
378         help_menu->insertItem(tr("About"), this, SLOT(showAbout()));
379         menuBar()->insertItem(tr("&Help"), help_menu);
380 }
381
382 void ComicMainWindow::setupStatusbar()
383 {
384         statusbar = new StatusBar(this);      
385         connect(toggleStatusbarAction, SIGNAL(toggled(bool)), statusbar, SLOT(setShown(bool)));
386         toggleStatusbarAction->setOn(cfg->showStatusbar());
387         statusbar->setShown(cfg->showStatusbar());
388 }
389
390 void ComicMainWindow::setupContextMenu()
391 {
392         QPopupMenu *cmenu = view->contextMenu();
393         pageinfo = new QLabel(cmenu);
394         pageinfo->setMargin(3);
395         pageinfo->setAlignment(Qt::AlignHCenter);
396         pageinfo->setFrameStyle(QFrame::Box | QFrame::Raised);
397         nextPageAction->addTo(cmenu);
398         prevPageAction->addTo(cmenu);
399         cmenu->insertSeparator();
400         fitWidthAction->addTo(cmenu);
401         fitHeightAction->addTo(cmenu);
402         wholePageAction->addTo(cmenu);
403         originalSizeAction->addTo(cmenu);
404         bestFitAction->addTo(cmenu);
405         cmenu->insertSeparator();
406         rotateRightAction->addTo(cmenu);
407         rotateLeftAction->addTo(cmenu);
408         rotateResetAction->addTo(cmenu);
409         cmenu->insertSeparator();
410         twoPagesAction->addTo(cmenu);
411         mangaModeAction->addTo(cmenu);
412         cmenu->insertSeparator();
413         fullScreenAction->addTo(cmenu);
414         cmenu->insertSeparator();
415         cmenu->insertItem(pageinfo);
416 }
417
418 void ComicMainWindow::enableComicBookActions(bool f)
419 {
420         //
421         // file menu
422         const bool x = f && sink && sink->supportsNext();
423         file_menu->setItemEnabled(close_id, f);
424         file_menu->setItemEnabled(create_id, f);
425         showInfoAction->setEnabled(f);
426         openNextAction->setEnabled(x);
427         openPrevAction->setEnabled(x);
428
429         //
430         // edit menu
431         if (edit_menu)
432         {
433                 edit_menu->setItemEnabled(gimp_id, f);
434                 edit_menu->setItemEnabled(reload_id, f);
435         }
436         
437         //
438         // view menu
439         rotateRightAction->setEnabled(f);
440         rotateLeftAction->setEnabled(f);
441         rotateResetAction->setEnabled(f);
442
443         //
444         // navigation menu
445         lastPageAction->setEnabled(f);
446         firstPageAction->setEnabled(f);
447         navi_menu->setItemEnabled(jumpto_id, f);
448         nextPageAction->setEnabled(f);
449         prevPageAction->setEnabled(f);
450         backwardPageAction->setEnabled(f);
451         forwardPageAction->setEnabled(f);
452         pageTopAction->setEnabled(f);
453         pageBottomAction->setEnabled(f);
454
455         //
456         // bookmarks menu
457         bookmarks_menu->setItemEnabled(setbookmark_id, f);
458         bookmarks_menu->setItemEnabled(rmvbookmark_id, f);
459 }
460
461 void ComicMainWindow::dragEnterEvent(QDragEnterEvent *e)
462 {
463         e->accept(QUriDrag::canDecode(e));
464 }
465
466 void ComicMainWindow::dropEvent(QDropEvent *e)
467 {
468         QStringList files;
469         if (QUriDrag::decodeLocalFiles(e, files))
470                 open(files[0], 0);
471 }
472
473 void ComicMainWindow::keyPressEvent(QKeyEvent *e)
474 {
475         if ((e->key()>=Qt::Key_1) && (e->key()<=Qt::Key_9))
476         {
477                 e->accept();
478                 showJumpToPage(e->text());
479         }
480         else
481                 QMainWindow::keyPressEvent(e);
482 }
483
484 void ComicMainWindow::closeEvent(QCloseEvent *e)
485 {
486         return (!cfg->confirmExit() || confirmExit()) ? e->accept() : e->ignore();
487 }
488
489 bool ComicMainWindow::confirmExit()
490 {
491         return QMessageBox::question(this, tr("Leave QComicBook?"), tr("Do you really want to quit QComicBook?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
492 }
493
494 void ComicMainWindow::thumbnailsVisibilityChanged(bool f)
495 {
496         if (f && sink)
497         {
498                 int max = sink->numOfImages();
499                 for (int i=0; i<max; i++)
500                         if (!thumbswin->view()->isLoaded(i))
501                                 sink->requestThumbnail(i);
502         }
503 }
504
505 void ComicMainWindow::toolbarVisibilityChanged(bool f)
506 {
507         toggleToolbarAction->setOn(f);
508 }
509
510 void ComicMainWindow::toggleScrollbars()
511 {
512         bool f = settings_menu->isItemChecked(scrv_id);
513         settings_menu->setItemChecked(scrv_id, !f);
514         view->enableScrollbars(!f);
515 }
516
517 void ComicMainWindow::toggleContinousScroll()
518 {
519         bool f = navi_menu->isItemChecked(contscr_id);
520         navi_menu->setItemChecked(contscr_id, !f);
521         if (f)
522         {
523                 view->disconnect(SIGNAL(bottomReached()), this);
524                 view->disconnect(SIGNAL(topReached()), this);
525         }
526         else
527         {       connect(view, SIGNAL(bottomReached()), this, SLOT(nextPage()));
528                 connect(view, SIGNAL(topReached()), this, SLOT(prevPageBottom()));
529         }
530 }
531
532 void ComicMainWindow::toggleTwoPages(bool f)
533 {
534         twoPagesAction->setOn(f);
535         jumpToPage(currpage, true);
536 }
537
538 void ComicMainWindow::toggleJapaneseMode(bool f)
539 {
540         mangaModeAction->setOn(f);
541         if (twoPagesAction->isOn())
542                 jumpToPage(currpage, true);
543 }
544
545 void ComicMainWindow::reloadPage()
546 {
547         if (sink)
548                 jumpToPage(currpage, true);
549 }
550
551 void ComicMainWindow::updateCaption()
552 {
553         QString c = "QComicBook";
554         if (sink)
555                 c += " - " + sink->getName();
556         setCaption(c);
557 }
558
559 void ComicMainWindow::setRecentFilesMenu(const History &hist)
560 {
561         QStringList list = hist.getAll();
562         recent_menu->clear();
563         for (QStringList::const_iterator it = list.begin(); it != list.end(); it++)
564                 recent_menu->insertItem(*it);
565 }
566
567 void ComicMainWindow::recentSelected(int id)
568 {
569         const QString &fname = recent_menu->text(id);
570         if (fname != QString::null)
571         {
572                 QFileInfo finfo(fname);
573                 if (!finfo.exists())
574                 {
575                         recentfiles->remove(fname);
576                         recent_menu->removeItem(id);
577                 }
578                 open(fname, 0);
579         }
580 }
581
582 void ComicMainWindow::sinkReady(const QString &path)
583 {
584         statusbar->setShown(toggleStatusbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideStatusbar())); //applies back user's statusbar&toolbar preferences
585         //toolbar->setShown(toggleToolbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideToolbar()));
586
587         recentfiles->append(path);
588         setRecentFilesMenu(*recentfiles);
589
590         enableComicBookActions(true);
591         updateCaption();
592         statusbar->setName(sink->getFullName());
593
594         thumbswin->view()->setPages(sink->numOfImages());
595
596         //
597         // request thumbnails for all pages
598         if (thumbswin->isVisible())
599                 sink->requestThumbnails(0, sink->numOfImages());
600
601         jumpToPage(currpage, true);
602
603         const bool hasdesc = (sink->getDescription().count() > 0);
604         showInfoAction->setDisabled(!hasdesc);
605
606         if (hasdesc && cfg->autoInfo())
607                 showInfo();
608 }
609
610 void ComicMainWindow::sinkError(int code)
611 {
612         statusbar->setShown(toggleStatusbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideStatusbar())); //applies back user's statusbar&toolbar preferences
613         //toolbar->setShown(toggleToolbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideToolbar()));
614
615         QString msg;
616         switch (code)
617         {
618                 case SINKERR_EMPTY: msg = tr("no images found"); break;
619                 case SINKERR_UNKNOWNFILE: msg = tr("unknown archive"); break;
620                 case SINKERR_ACCESS: msg = tr("can't access directory"); break;
621                 case SINKERR_NOTFOUND: msg = tr("file/directory not found"); break;
622                 case SINKERR_NOTSUPPORTED: msg = tr("archive not supported"); break;
623                 case SINKERR_ARCHEXIT: msg = tr("archive extractor exited with error"); break;
624                 default: break;
625         }
626         QMessageBox::critical(this, "QComicBook error", "Error opening comicbook: " + msg, 
627                         QMessageBox::Ok, QMessageBox::NoButton);
628         closeSink();
629 }
630
631 void ComicMainWindow::browseDirectory()
632 {
633         const QString dir = QFileDialog::getExistingDirectory(lastdir, this, 
634                         NULL, tr("Choose a directory") );
635         if (!dir.isEmpty())
636                 open(dir, 0);
637 }
638
639 void ComicMainWindow::browseArchive()
640 {
641         const QString file = QFileDialog::getOpenFileName(lastdir,
642                         "Archives (" + ImgArchiveSink::supportedOpenExtensions() + ");;All files (*)",
643                         this, NULL, tr("Choose a file") );
644         if (!file.isEmpty())
645                 open(file, 0);
646 }
647
648 void ComicMainWindow::open(const QString &path, int page)
649 {
650         const QFileInfo f(path);
651         const QString fullname = f.absFilePath();
652
653         if (sink && sink->getFullName() == fullname) //trying to open same dir?
654                 return;
655
656         lastdir = f.dirPath(true);
657         currpage = page;
658
659         closeSink();
660
661         ImlibImage::setCacheSize(cfg->cacheSize()*1024*1024);
662
663         sink = ImgSinkFactory::instance().createImgSink(path);
664         sink->thumbnailLoader().setReciever(thumbswin);
665         sink->thumbnailLoader().setUseCache(cfg->cacheThumbnails());
666
667         connect(sink, SIGNAL(sinkReady(const QString&)), this, SLOT(sinkReady(const QString&)));
668         connect(sink, SIGNAL(sinkError(int)), this, SLOT(sinkError(int)));
669         connect(sink, SIGNAL(progress(int, int)), statusbar, SLOT(setProgress(int, int)));
670
671         statusbar->setShown(true); //ensures status bar is visible when opening regardless of user settings
672
673         sink->open(fullname);
674 }
675
676 void ComicMainWindow::openNext()
677 {
678         if (sink && sink->supportsNext())
679         {
680                 QString fname = sink->getNext();
681                 if (!fname.isEmpty())
682                         open(fname, 0);
683         }
684 }
685
686 void ComicMainWindow::openPrevious()
687 {
688         if (sink && sink->supportsNext())
689         {
690                 QString fname = sink->getPrevious();
691                 if (!fname.isEmpty())
692                         open(fname, 0);
693         }
694 }
695
696 void ComicMainWindow::toggleFullScreen()
697 {
698         if (isFullScreen())
699         {
700                 exitFullscreen();
701         }
702         else
703         {
704                 if (cfg->fullScreenHideMenu())
705                         menuBar()->hide();
706                 if (cfg->fullScreenHideStatusbar())
707                         statusbar->hide();
708                 /*if (cfg->fullScreenHideToolbar())
709                         toolbar->hide();*/
710                 showFullScreen();
711         }
712 }
713
714 void ComicMainWindow::exitFullscreen()
715 {
716         if (isFullScreen())
717         {
718                 menuBar()->show();
719                 if (toggleStatusbarAction->isOn())
720                         statusbar->show();
721                 /*if (toggleToolbarAction->isOn())
722                         toolbar->show();*/
723                 showNormal();
724         }
725 }
726
727 void ComicMainWindow::nextPage()
728 {
729         if (sink)
730         {
731                 if (!spreading && twoPagesAction->isOn() && cfg->twoPagesStep())
732                 {
733                         if (currpage < sink->numOfImages() - 2) //do not change pages if last two pages are visible
734                                 jumpToPage(currpage + 2);
735                 }
736                 else
737                 {
738                         jumpToPage(currpage + 1);
739                 }
740         }
741 }
742
743 void ComicMainWindow::prevPage()
744 {
745         jumpToPage(currpage - (twoPagesAction->isOn() && cfg->twoPagesStep() ? 2 : 1));
746 }
747
748 void ComicMainWindow::prevPageBottom()
749 {
750         if (currpage > 0)
751         {
752                 jumpToPage(currpage - (twoPagesAction->isOn() && cfg->twoPagesStep() ? 2 : 1));
753                 view->scrollToBottom();
754         }
755 }
756
757 void ComicMainWindow::firstPage()
758 {
759         jumpToPage(0);
760 }
761
762 void ComicMainWindow::lastPage()
763 {
764         if (sink)
765                 jumpToPage(sink->numOfImages() - (twoPagesAction->isOn() && cfg->twoPagesStep() ? 2 : 1));
766 }
767
768 void ComicMainWindow::forwardPages()
769 {
770         jumpToPage(currpage + (twoPagesAction->isOn() && cfg->twoPagesStep() ? 10 : 5));
771 }
772
773 void ComicMainWindow::backwardPages()
774 {
775         jumpToPage(currpage - (twoPagesAction->isOn() && cfg->twoPagesStep() ? 10 : 5));
776 }
777
778 void ComicMainWindow::jumpToPage(int n, bool force)
779 {
780         if (!sink)
781                 return;
782         if (n >= sink->numOfImages())
783                 n = sink->numOfImages()-1;
784         if (n < 0)
785                 n = 0;
786         if ((n != currpage) || force)
787         {
788                 int result1, result2;
789                 ImlibImage *img1, *img2;
790                 int page_off = 0;
791                 const bool preserveangle = togglePreserveRotationAction->isOn();
792
793                 img1 = sink->getImage(currpage = n, result1);
794
795                 if (twoPagesAction->isOn())
796                 {
797                         if (img1->getFormat() == SPREAD_FORMAT)
798                         {
799                                 result2 = 1;
800                                 spreading = true;
801                         }
802                         else
803                         {
804                                 spreading = false;
805                                 img2 = sink->getImage(currpage + (++page_off), result2);
806                         }
807
808                         // Ignore spreads as second pages
809                         // Showing the next page or not showing any page
810                         // should be an option because some sinks have the spread
811                         // between the left and right page, while others have the
812                         // spread but not the single pages, and there is no way simple way
813                         // to autodetect it.
814
815                         // For the moment we just use the "stop at spread"
816 #if 0
817                         // Skip spreads:
818                         while (result2 == 0 && img2->getFormat() == SPREAD_FORMAT)
819                         {
820                                 delete img2;
821                                 img2 = sink->getImage(currpage + (++page_off), result2);
822                                 spreading = true;
823                         }
824 #else
825                         // Stop at spreads:
826                         if (result2 == 0 && img2->getFormat() == SPREAD_FORMAT)
827                         {
828                                 result2 = 1;
829                                 spreading = true;
830                         }
831 #endif
832
833                         if (result2 == 0)
834                         {
835                                 if (mangaModeAction->isOn())
836                                 {
837                                         view->setImage(img2, img1, preserveangle);
838                                         statusbar->setImageInfo(img2, img1);
839                                 }
840                                 else
841                                 {
842                                         view->setImage(img1, img2, preserveangle);
843                                         statusbar->setImageInfo(img1, img2);
844                                 }
845                         }
846                         else
847                         {
848                                 view->setImage(img1, preserveangle);
849                                 statusbar->setImageInfo(img1);
850                                 if (cfg->preloadPages())
851                                         sink->preload(currpage + 1);
852                         }
853                 }
854                 else
855                 {
856                         view->setImage(img1, preserveangle);
857                         statusbar->setImageInfo(img1);
858                 }
859                 if (mangaModeAction->isOn())
860                         view->ensureVisible(view->imageWidth(), 0);
861                 const QString page = tr("Page") + " " + QString::number(currpage + 1) + "/" + QString::number(sink->numOfImages());
862                 pageinfo->setText(page);
863                 statusbar->setPage(currpage + 1, sink->numOfImages());
864                 thumbswin->view()->scrollToPage(currpage);
865                 if (cfg->preloadPages())
866                 {
867                         if (!twoPagesAction->isOn())
868                         {
869                                 sink->preload(currpage + 1);
870                         }
871                         else
872                         {
873                                 sink->preload(currpage + 3);
874                                 sink->preload(currpage + 2);
875                         }
876                 }
877         }
878 }
879
880 void ComicMainWindow::showInfo()
881 {
882         if (sink)
883         {
884                 ComicBookInfo *i = new ComicBookInfo(this, *sink, cfg->infoFont());
885                 i->show();
886         }
887 }
888
889 void ComicMainWindow::showSysInfo()
890 {
891         SupportedArchivesWindow *win = new SupportedArchivesWindow(this);
892         win->show();
893 }
894
895 void ComicMainWindow::createArchive()
896 {
897         if (sink)
898         {
899                 ArchiverDialog *win = new ArchiverDialog(this, sink);
900                 win->exec();
901                 delete win;
902         }
903 }
904
905 void ComicMainWindow::showAbout()
906 {
907         AboutDialog *win = new AboutDialog(this, "About QComicBook",
908                         "QComicBook " VERSION " - comic book viewer for GNU/Linux\n"
909                         "(c)by Pawel Stolowski 2005-2006\n"
910                         "released under terms of GNU General Public License\n\n"
911                         "http://linux.bydg.org/~yogin\n"
912                         "pawel.stolowski@wp.pl", QPixmap(DATADIR "qcomicbook-splash.png"));
913         win->show();
914 }
915
916 void ComicMainWindow::showHelp()
917 {
918         const QString helpdir = HelpBrowser::getLocaleHelpDir(DATADIR "/help");
919         if (!cfg->useInternalBrowser() && cfg->externalBrowser() != QString::null)
920         {
921                 QProcess *proc = new QProcess(this);
922                 proc->addArgument(cfg->externalBrowser());
923                 proc->addArgument(helpdir + "/index.html");
924                 connect(proc, SIGNAL(processExited()), proc, SLOT(deleteLater()));
925                 if (!proc->start())
926                 {
927                         proc->deleteLater();
928                         cfg->useInternalBrowser(true);
929                 }
930         }
931         if (cfg->useInternalBrowser())
932         {
933                 HelpBrowser *help = new HelpBrowser(tr("QComicBook Help"), helpdir);
934                 help->show();
935         }
936 }
937
938 void ComicMainWindow::showConfigDialog()
939 {
940         ComicBookCfgDialog *d = new ComicBookCfgDialog(this, cfg);
941         d->show();
942 }
943
944 void ComicMainWindow::showJumpToPage(const QString &number)
945 {
946         if (sink)
947         {
948                 JumpToPageWindow *win = new JumpToPageWindow(this, number.toInt(), sink->numOfImages());
949                 connect(win, SIGNAL(pageSelected(int)), this, SLOT(jumpToPage(int)));
950                 win->show();
951         }
952 }
953
954 void ComicMainWindow::closeSink()
955 {
956         enableComicBookActions(false);
957
958         if (sink)
959         {
960                 /*if (typeid(*sink) == typeid(ImgArchiveSink) && cfg->editSupport() && sink->hasModifiedFiles()) 
961                 {
962                         if (QMessageBox::warning(this, tr("Create archive?"),
963                                 tr("Warning! Some files were modified in this comic book\nDo you want to create new archive file?"),
964                                 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
965                         {
966                                 ArchiverDialog *win = new ArchiverDialog(this, sink);
967                                 win->exec();
968                                 delete win;
969                         }
970
971                 }*/
972                 sink->deleteLater();
973                 sink = NULL;
974         }
975         view->clear();
976         thumbswin->view()->clear();
977         updateCaption();
978         statusbar->clear();
979 }
980
981 void ComicMainWindow::setBookmark()
982 {
983         if (sink)
984                 bookmarks->set(sink->getFullName(), currpage);
985 }
986
987 void ComicMainWindow::removeBookmark()
988 {
989         if (sink && bookmarks->exists(sink->getFullName()) && QMessageBox::question(this, tr("Removing bookmark"),
990                                 tr("Do you really want to remove bookmark\nfor this comic book?"),
991                                 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
992                 bookmarks->remove(sink->getFullName());
993 }
994
995 void ComicMainWindow::openBookmarksManager()
996 {
997         BookmarkManager *win = new BookmarkManager(this, bookmarks);
998         win->show();
999 }
1000
1001 void ComicMainWindow::openWithGimp()
1002 {
1003         if (!sink)
1004                 return;
1005         QProcess *proc = new QProcess(this);
1006         proc->addArgument("gimp-remote");
1007         proc->addArgument(sink->getFullFileName(currpage));
1008         connect(proc, SIGNAL(processExited()), proc, SLOT(deleteLater()));
1009         if (!proc->start())
1010         {
1011                 proc->deleteLater();
1012         }
1013 }
1014
1015 void ComicMainWindow::bookmarkSelected(int id)
1016 {
1017         Bookmark b;
1018         if (bookmarks->get(id, b))
1019         {
1020                 if (b.getName() != QString::null)
1021                 {
1022                         QString fname = b.getName();
1023                         if (sink && fname == sink->getFullName()) //is this comicbook already opened?
1024                         {
1025                                 jumpToPage(b.getPage(), true); //if so, just jump to bookmarked page
1026                                 return;
1027                         }
1028
1029                         QFileInfo finfo(fname);
1030                         if (!finfo.exists())
1031                         {
1032                                 if (QMessageBox::question(this, tr("Comic book not found"),
1033                                                         tr("Selected bookmark points to\nnon-existing comic book\nDo you want to remove it?"),
1034                                                         QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1035                                         bookmarks->remove(id);
1036                                 return;
1037                         }
1038                         open(fname, b.getPage());
1039                 }
1040         }
1041 }
1042
1043 void ComicMainWindow::saveSettings()
1044 {
1045         cfg->geometry(frameGeometry());
1046         cfg->saveDockLayout(this);
1047         cfg->scrollbarsVisible(settings_menu->isItemChecked(scrv_id));
1048         cfg->twoPagesMode(twoPagesAction->isOn());
1049         cfg->japaneseMode(mangaModeAction->isOn());
1050         cfg->continuousScrolling(navi_menu->isItemChecked(contscr_id));
1051         cfg->lastDir(lastdir);
1052         cfg->recentlyOpened(*recentfiles);
1053         cfg->pageSize(view->getSize());
1054         cfg->showStatusbar(toggleStatusbarAction->isOn());
1055
1056         bookmarks->save();        
1057 }
1058