Improve sequential navigation when reading strips
[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), prevpage(-1), nextpage(-1), spreading(false), stripping(false), 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                 int savedpage = currpage;
732                 if (nextpage != -1)
733                 {
734                         jumpToPage(nextpage);
735                 }
736                 else
737                 {
738                         if (!spreading && twoPagesAction->isOn() && cfg->twoPagesStep())
739                         {
740                                 if (currpage < sink->numOfImages() - 2) //do not change pages if last two pages are visible
741                                         jumpToPage(currpage + 2);
742                         }
743                         else
744                         {
745                                 jumpToPage(currpage + 1);
746                         }
747                 }
748                 if (currpage != savedpage)
749                         prevpage = savedpage;
750         }
751 }
752
753 void ComicMainWindow::prevPage()
754 {
755         if (prevpage != -1)
756                 jumpToPage(prevpage);
757         else
758                 if (!spreading && twoPagesAction->isOn() && cfg->twoPagesStep())
759                         jumpToPage(currpage - 2);
760                 else
761                         jumpToPage(currpage - 1);
762 }
763
764 void ComicMainWindow::prevPageBottom()
765 {
766         if (currpage > 0)
767         {
768                 prevPage();
769                 view->scrollToBottom();
770         }
771 }
772
773 void ComicMainWindow::firstPage()
774 {
775         jumpToPage(0);
776 }
777
778 void ComicMainWindow::lastPage()
779 {
780         if (sink)
781                 jumpToPage(sink->numOfImages() - (twoPagesAction->isOn() && cfg->twoPagesStep() ? 2 : 1));
782 }
783
784 void ComicMainWindow::forwardPages()
785 {
786         jumpToPage(currpage + 5*(nextpage-currpage));
787 }
788
789 void ComicMainWindow::backwardPages()
790 {
791         jumpToPage(currpage - 5*(nextpage-currpage));
792 }
793
794 void ComicMainWindow::jumpToPage(int n, bool force)
795 {
796         if (!sink)
797                 return;
798         if (n >= sink->numOfImages())
799                 n = sink->numOfImages()-1;
800         if (n < 0)
801                 n = 0;
802         if ((n != currpage) || force)
803         {
804                 prevpage = -1;
805                 int result1, result2;
806                 ImlibImage *img1, *img2;
807                 ImlibImageList list[2];
808                 int page_off = 0;
809                 const bool preserveangle = togglePreserveRotationAction->isOn();
810
811                 img1 = sink->getImage(currpage = n, result1);
812
813                 if (img1->getFormat() == STRIP_FORMAT)
814                 {
815                         nextpage = buildStripList(n, img1, list, twoPagesAction->isOn());
816                         jumpToStrip(n, list, twoPagesAction->isOn());
817                         return;
818                 }
819
820                 if (twoPagesAction->isOn())
821                 {
822                         img2 = sink->getImage(currpage + (++page_off), result2);
823
824                         if (result2 == 0 && img2->getFormat() == STRIP_FORMAT) {
825                                 list[0].append(img1);
826                                 nextpage = buildStripList(currpage+page_off, img2, &(list[1]), false);
827                                 jumpToStrip(n, list, true);
828                                 return;
829                         }
830
831                         if (img1->getFormat() == SPREAD_FORMAT)
832                         {
833                                 result2 = 1;
834                                 spreading = true;
835                         }
836                         else
837                         {
838                                 spreading = false;
839                         }
840
841                         // Ignore spreads as second pages
842                         // Showing the next page or not showing any page
843                         // should be an option because some sinks have the spread
844                         // between the left and right page, while others have the
845                         // spread but not the single pages, and there is no way simple way
846                         // to autodetect it.
847
848                         // For the moment we just use the "stop at spread"
849 #if 0
850                         // Skip spreads:
851                         while (result2 == 0 && img2->getFormat() == SPREAD_FORMAT)
852                         {
853                                 delete img2;
854                                 img2 = sink->getImage(currpage + (++page_off), result2);
855                                 spreading = true;
856                         }
857 #else
858                         // Stop at spreads:
859                         if (result2 == 0 && img2->getFormat() == SPREAD_FORMAT)
860                         {
861                                 result2 = 1;
862                                 spreading = true;
863                         }
864 #endif
865
866                         if (result2 == 0)
867                         {
868                                 if (mangaModeAction->isOn())
869                                 {
870                                         view->setImage(img2, img1, preserveangle);
871                                         statusbar->setImageInfo(img2, img1);
872                                 }
873                                 else
874                                 {
875                                         view->setImage(img1, img2, preserveangle);
876                                         statusbar->setImageInfo(img1, img2);
877                                 }
878                         }
879                         else
880                         {
881                                 view->setImage(img1, preserveangle);
882                                 statusbar->setImageInfo(img1);
883                                 if (cfg->preloadPages())
884                                         sink->preload(currpage + 1);
885                         }
886                 }
887                 else
888                 {
889                         view->setImage(img1, preserveangle);
890                         statusbar->setImageInfo(img1);
891                 }
892                 if (mangaModeAction->isOn())
893                         view->ensureVisible(view->imageWidth(), 0);
894                 const QString page = tr("Page") + " " + QString::number(currpage + 1) + "/" + QString::number(sink->numOfImages());
895                 pageinfo->setText(page);
896                 statusbar->setPage(currpage + 1, sink->numOfImages());
897                 thumbswin->view()->scrollToPage(currpage);
898                 if (cfg->preloadPages())
899                 {
900                         if (!twoPagesAction->isOn())
901                         {
902                                 sink->preload(currpage + 1);
903                         }
904                         else
905                         {
906                                 sink->preload(currpage + 3);
907                                 sink->preload(currpage + 2);
908                         }
909                 }
910                 nextpage = currpage + (++page_off);
911         }
912 }
913
914 int ComicMainWindow::buildStripList(int n, ImlibImage *img1, ImlibImageList *list, bool twoPages)
915 {
916 #define STRIP_MAX 7
917         ImlibImage *img2;
918         int page_off = 0;
919         int result2;
920
921         list[0].append(img1);
922
923         img2 = sink->getImage(n + (++page_off), result2) ;
924         while (result2 == 0 && img2->getFormat() == STRIP_FORMAT && list[0].count() < STRIP_MAX) {
925                 list[0].append(img2) ;
926                 img2 = sink->getImage(n + (++page_off), result2) ;
927         }
928
929         // If we are displaying two pages, start filling the second side
930         if (twoPages && result2 == 0)
931         {
932                 list[1].append(img2) ;
933                 // ... which is a strip collection if the first strip is a strip
934                 if (img2->getFormat() == STRIP_FORMAT)
935                 {
936                         img2 = sink->getImage(n + (++page_off), result2) ;
937                         while (result2 == 0 && img2->getFormat() == STRIP_FORMAT && list[1].count() < STRIP_MAX) {
938                                 list[1].append(img2) ;
939                                 img2 = sink->getImage(n + (++page_off), result2) ;
940                         }
941                 }
942
943         }
944         return (n + page_off);
945 }
946
947 void ComicMainWindow::jumpToStrip(int n, ImlibImageList *list, bool twoPages)
948 {
949         const bool preserveangle = togglePreserveRotationAction->isOn();
950
951         // Temporary
952         ImlibImage *img1, *img2;
953
954         img1 = list[0].at(0);
955
956         if (twoPages)
957         {
958                 img2 = list[1].at(0);
959                 if (mangaModeAction->isOn())
960                 {
961                         view->setImage(list[1], list[0], preserveangle);
962                         statusbar->setImageInfo(img2, img1);
963                 }
964                 else
965                 {
966                         view->setImage(list[0], list[1], preserveangle);
967                         statusbar->setImageInfo(img1, img2);
968                 }
969         }
970         else
971         {
972                 view->setImage(list[0], preserveangle);
973                 statusbar->setImageInfo(img1);
974         }
975         if (mangaModeAction->isOn())
976                 view->ensureVisible(view->imageWidth(), 0);
977         const QString page = tr("Page") + " " + QString::number(currpage + 1) + "/" + QString::number(sink->numOfImages());
978         pageinfo->setText(page);
979         statusbar->setPage(currpage + 1, sink->numOfImages());
980         thumbswin->view()->scrollToPage(currpage);
981 }
982
983 void ComicMainWindow::showInfo()
984 {
985         if (sink)
986         {
987                 ComicBookInfo *i = new ComicBookInfo(this, *sink, cfg->infoFont());
988                 i->show();
989         }
990 }
991
992 void ComicMainWindow::showSysInfo()
993 {
994         SupportedArchivesWindow *win = new SupportedArchivesWindow(this);
995         win->show();
996 }
997
998 void ComicMainWindow::createArchive()
999 {
1000         if (sink)
1001         {
1002                 ArchiverDialog *win = new ArchiverDialog(this, sink);
1003                 win->exec();
1004                 delete win;
1005         }
1006 }
1007
1008 void ComicMainWindow::showAbout()
1009 {
1010         AboutDialog *win = new AboutDialog(this, "About QComicBook",
1011                         "QComicBook " VERSION " - comic book viewer for GNU/Linux\n"
1012                         "(c)by Pawel Stolowski 2005-2006\n"
1013                         "released under terms of GNU General Public License\n\n"
1014                         "http://linux.bydg.org/~yogin\n"
1015                         "pawel.stolowski@wp.pl", QPixmap(DATADIR "qcomicbook-splash.png"));
1016         win->show();
1017 }
1018
1019 void ComicMainWindow::showHelp()
1020 {
1021         const QString helpdir = HelpBrowser::getLocaleHelpDir(DATADIR "/help");
1022         if (!cfg->useInternalBrowser() && cfg->externalBrowser() != QString::null)
1023         {
1024                 QProcess *proc = new QProcess(this);
1025                 proc->addArgument(cfg->externalBrowser());
1026                 proc->addArgument(helpdir + "/index.html");
1027                 connect(proc, SIGNAL(processExited()), proc, SLOT(deleteLater()));
1028                 if (!proc->start())
1029                 {
1030                         proc->deleteLater();
1031                         cfg->useInternalBrowser(true);
1032                 }
1033         }
1034         if (cfg->useInternalBrowser())
1035         {
1036                 HelpBrowser *help = new HelpBrowser(tr("QComicBook Help"), helpdir);
1037                 help->show();
1038         }
1039 }
1040
1041 void ComicMainWindow::showConfigDialog()
1042 {
1043         ComicBookCfgDialog *d = new ComicBookCfgDialog(this, cfg);
1044         d->show();
1045 }
1046
1047 void ComicMainWindow::showJumpToPage(const QString &number)
1048 {
1049         if (sink)
1050         {
1051                 JumpToPageWindow *win = new JumpToPageWindow(this, number.toInt(), sink->numOfImages());
1052                 connect(win, SIGNAL(pageSelected(int)), this, SLOT(jumpToPage(int)));
1053                 win->show();
1054         }
1055 }
1056
1057 void ComicMainWindow::closeSink()
1058 {
1059         enableComicBookActions(false);
1060
1061         if (sink)
1062         {
1063                 /*if (typeid(*sink) == typeid(ImgArchiveSink) && cfg->editSupport() && sink->hasModifiedFiles()) 
1064                 {
1065                         if (QMessageBox::warning(this, tr("Create archive?"),
1066                                 tr("Warning! Some files were modified in this comic book\nDo you want to create new archive file?"),
1067                                 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1068                         {
1069                                 ArchiverDialog *win = new ArchiverDialog(this, sink);
1070                                 win->exec();
1071                                 delete win;
1072                         }
1073
1074                 }*/
1075                 sink->deleteLater();
1076                 sink = NULL;
1077         }
1078         view->clear();
1079         thumbswin->view()->clear();
1080         updateCaption();
1081         statusbar->clear();
1082 }
1083
1084 void ComicMainWindow::setBookmark()
1085 {
1086         if (sink)
1087                 bookmarks->set(sink->getFullName(), currpage);
1088 }
1089
1090 void ComicMainWindow::removeBookmark()
1091 {
1092         if (sink && bookmarks->exists(sink->getFullName()) && QMessageBox::question(this, tr("Removing bookmark"),
1093                                 tr("Do you really want to remove bookmark\nfor this comic book?"),
1094                                 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1095                 bookmarks->remove(sink->getFullName());
1096 }
1097
1098 void ComicMainWindow::openBookmarksManager()
1099 {
1100         BookmarkManager *win = new BookmarkManager(this, bookmarks);
1101         win->show();
1102 }
1103
1104 void ComicMainWindow::openWithGimp()
1105 {
1106         if (!sink)
1107                 return;
1108         QProcess *proc = new QProcess(this);
1109         proc->addArgument("gimp-remote");
1110         proc->addArgument(sink->getFullFileName(currpage));
1111         connect(proc, SIGNAL(processExited()), proc, SLOT(deleteLater()));
1112         if (!proc->start())
1113         {
1114                 proc->deleteLater();
1115         }
1116 }
1117
1118 void ComicMainWindow::bookmarkSelected(int id)
1119 {
1120         Bookmark b;
1121         if (bookmarks->get(id, b))
1122         {
1123                 if (b.getName() != QString::null)
1124                 {
1125                         QString fname = b.getName();
1126                         if (sink && fname == sink->getFullName()) //is this comicbook already opened?
1127                         {
1128                                 jumpToPage(b.getPage(), true); //if so, just jump to bookmarked page
1129                                 return;
1130                         }
1131
1132                         QFileInfo finfo(fname);
1133                         if (!finfo.exists())
1134                         {
1135                                 if (QMessageBox::question(this, tr("Comic book not found"),
1136                                                         tr("Selected bookmark points to\nnon-existing comic book\nDo you want to remove it?"),
1137                                                         QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1138                                         bookmarks->remove(id);
1139                                 return;
1140                         }
1141                         open(fname, b.getPage());
1142                 }
1143         }
1144 }
1145
1146 void ComicMainWindow::saveSettings()
1147 {
1148         cfg->geometry(frameGeometry());
1149         cfg->saveDockLayout(this);
1150         cfg->scrollbarsVisible(settings_menu->isItemChecked(scrv_id));
1151         cfg->twoPagesMode(twoPagesAction->isOn());
1152         cfg->japaneseMode(mangaModeAction->isOn());
1153         cfg->continuousScrolling(navi_menu->isItemChecked(contscr_id));
1154         cfg->lastDir(lastdir);
1155         cfg->recentlyOpened(*recentfiles);
1156         cfg->pageSize(view->getSize());
1157         cfg->showStatusbar(toggleStatusbarAction->isOn());
1158
1159         bookmarks->save();        
1160 }
1161