Strip Mode is now optional
[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         stripModeAction = new QAction(tr("Strip Mode"), QKeySequence(), this);
150         stripModeAction->setToggleAction(true);
151         rotateRightAction = new QAction(Icons::get(ICON_ROTRIGHT), tr("Rotate right"), QKeySequence(), this);
152         rotateLeftAction = new QAction(Icons::get(ICON_ROTLEFT), tr("Rotate left"), QKeySequence(), this);
153         rotateResetAction = new QAction(tr("No rotation"), QKeySequence(), this);
154         togglePreserveRotationAction = new QAction(tr("Preserve rotation"), QKeySequence(), this);
155         togglePreserveRotationAction->setToggleAction(true);
156         showInfoAction = new QAction(Icons::get(ICON_INFO), tr("Info"), ALT+Key_I, this);
157         exitFullScreenAction = new QAction(QString::null, Key_Escape, this);
158         toggleStatusbarAction = new QAction(tr("Statusbar"), QKeySequence(), this);
159         toggleStatusbarAction->setToggleAction(true);
160         toggleThumbnailsAction = new QAction(Icons::get(ICON_THUMBNAILS), tr("Thumbnails"), ALT+Key_T, this);
161         toggleThumbnailsAction->setToggleAction(true);
162         toggleToolbarAction = new QAction(tr("Toolbar"), QKeySequence(), this);
163         toggleToolbarAction->setToggleAction(true);
164
165         connect(openArchiveAction, SIGNAL(activated()), this, SLOT(browseArchive()));
166         connect(openDirAction, SIGNAL(activated()), this, SLOT(browseDirectory()));
167         connect(openNextAction, SIGNAL(activated()), this, SLOT(openNext()));
168         connect(openPrevAction, SIGNAL(activated()), this, SLOT(openPrevious()));
169         connect(showInfoAction, SIGNAL(activated()), this, SLOT(showInfo()));
170         connect(exitFullScreenAction, SIGNAL(activated()), this, SLOT(exitFullscreen()));
171         connect(nextPageAction, SIGNAL(activated()), this, SLOT(nextPage()));
172         connect(forwardPageAction, SIGNAL(activated()), this, SLOT(forwardPages()));
173         connect(firstPageAction, SIGNAL(activated()), this, SLOT(firstPage()));
174         connect(lastPageAction, SIGNAL(activated()), this, SLOT(lastPage()));
175         connect(backwardPageAction, SIGNAL(activated()), this, SLOT(backwardPages())); 
176         connect(quitAction, SIGNAL(activated()), this, SLOT(close()));
177 }
178
179 void ComicMainWindow::setupComicImageView()
180 {
181         view = new ComicImageView(this, cfg->pageSize(), cfg->background());
182         setCentralWidget(view);
183         view->setFocus();
184         view->setSmallCursor(cfg->smallCursor());
185         connect(cfg, SIGNAL(backgroundChanged(const QColor&)), view, SLOT(setBackground(const QColor&)));
186         connect(cfg, SIGNAL(cursorChanged(bool)), view, SLOT(setSmallCursor(bool)));
187         connect(fullScreenAction, SIGNAL(activated()), this, SLOT(toggleFullScreen()));
188         connect(pageTopAction, SIGNAL(activated()), view, SLOT(scrollToTop()));
189         connect(pageBottomAction, SIGNAL(activated()), view, SLOT(scrollToBottom()));
190         connect(scrollRightAction, SIGNAL(activated()), view, SLOT(scrollRight()));
191         connect(scrollLeftAction, SIGNAL(activated()), view, SLOT(scrollLeft()));
192         connect(scrollRightFastAction, SIGNAL(activated()), view, SLOT(scrollRightFast()));
193         connect(scrollLeftFastAction, SIGNAL(activated()), view, SLOT(scrollLeftFast()));
194         connect(scrollUpAction, SIGNAL(activated()), view, SLOT(scrollUp()));
195         connect(scrollDownAction, SIGNAL(activated()), view, SLOT(scrollDown()));       
196         connect(scrollUpFastAction, SIGNAL(activated()), view, SLOT(scrollUpFast()));        
197         connect(scrollDownFastAction, SIGNAL(activated()), view, SLOT(scrollDownFast()));
198         connect(fitWidthAction, SIGNAL(activated()), view, SLOT(setSizeFitWidth()));        
199         connect(fitHeightAction, SIGNAL(activated()), view, SLOT(setSizeFitHeight()));        
200         connect(wholePageAction, SIGNAL(activated()), view, SLOT(setSizeWholePage()));        
201         connect(originalSizeAction, SIGNAL(activated()), view, SLOT(setSizeOriginal()));        
202         connect(bestFitAction, SIGNAL(activated()), view, SLOT(setSizeBestFit()));        
203         connect(mangaModeAction, SIGNAL(toggled(bool)), this, SLOT(toggleJapaneseMode(bool)));        
204         connect(twoPagesAction, SIGNAL(toggled(bool)), this, SLOT(toggleTwoPages(bool)));
205         connect(stripModeAction, SIGNAL(toggled(bool)), this, SLOT(toggleStripMode(bool)));
206         connect(prevPageAction, SIGNAL(activated()), this, SLOT(prevPage()));       
207         connect(rotateRightAction, SIGNAL(activated()), view, SLOT(rotateRight()));        
208         connect(rotateLeftAction, SIGNAL(activated()), view, SLOT(rotateLeft()));
209         connect(rotateResetAction, SIGNAL(activated()), view, SLOT(resetRotation()));
210         connect(jumpDownAction, SIGNAL(activated()), view, SLOT(jumpDown()));
211         connect(jumpUpAction, SIGNAL(activated()), view, SLOT(jumpUp()));
212         if (cfg->continuousScrolling())
213         {
214                 connect(view, SIGNAL(bottomReached()), this, SLOT(nextPage()));
215                 connect(view, SIGNAL(topReached()), this, SLOT(prevPageBottom()));
216         }
217         connect(view, SIGNAL(doubleClick()), this, SLOT(nextPage()));
218         view->enableScrollbars(cfg->scrollbarsVisible());
219         QAction *which = originalSizeAction;
220         switch (cfg->pageSize())
221         {
222                 case FitWidth:  which = fitWidthAction; break;
223                 case FitHeight: which = fitHeightAction; break;
224                 case BestFit:   which = bestFitAction; break;
225                 case WholePage: which = wholePageAction; break;
226                 case Original:  which = originalSizeAction; break;
227         }
228         which->setOn(true);
229 }
230
231 void ComicMainWindow::setupThumbnailsWindow()
232 {
233         thumbswin = new ThumbnailsWindow(QDockWindow::InDock, this);
234         moveDockWindow(thumbswin, Qt::DockLeft); //initial position of thumbnails window
235         connect(thumbswin, SIGNAL(requestedPage(int, bool)), this, SLOT(jumpToPage(int, bool)));
236         connect(thumbswin, SIGNAL(visibilityChanged(bool)), this, SLOT(thumbnailsVisibilityChanged(bool)));
237         connect(thumbswin, SIGNAL(visibilityChanged(bool)), toggleThumbnailsAction, SLOT(setOn(bool)));
238         connect(toggleThumbnailsAction, SIGNAL(toggled(bool)), thumbswin, SLOT(setShown(bool)));       
239 }
240
241 void ComicMainWindow::setupToolbar()
242 {
243         toolbar = new QToolBar(tr("Toolbar"), this);
244         openArchiveAction->addTo(toolbar);
245         openDirAction->addTo(toolbar);
246         toolbar->addSeparator();
247         showInfoAction->addTo(toolbar);
248         toggleThumbnailsAction->addTo(toolbar);
249         toolbar->addSeparator();
250         twoPagesAction->addTo(toolbar);
251         stripModeAction->addTo(toolbar);
252         mangaModeAction->addTo(toolbar);
253         toolbar->addSeparator();
254         prevPageAction->addTo(toolbar);
255         nextPageAction->addTo(toolbar);
256         backwardPageAction->addTo(toolbar);
257         forwardPageAction->addTo(toolbar);
258         pageTopAction->addTo(toolbar);
259         pageBottomAction->addTo(toolbar);
260         toolbar->addSeparator();
261         originalSizeAction->addTo(toolbar);
262         fitWidthAction->addTo(toolbar);
263         fitHeightAction->addTo(toolbar);
264         wholePageAction->addTo(toolbar);
265         bestFitAction->addTo(toolbar);
266         toolbar->addSeparator();
267         rotateRightAction->addTo(toolbar);
268         rotateLeftAction->addTo(toolbar);
269         connect(toggleToolbarAction, SIGNAL(toggled(bool)), toolbar, SLOT(setShown(bool)));
270         connect(toolbar, SIGNAL(visibilityChanged(bool)), this, SLOT(toolbarVisibilityChanged(bool)));
271 }
272
273 void ComicMainWindow::setupFileMenu()
274 {
275         file_menu = new QPopupMenu(this);
276         openArchiveAction->addTo(file_menu);
277         openDirAction->addTo(file_menu);
278         openNextAction->addTo(file_menu);
279         openPrevAction->addTo(file_menu);
280         recent_menu = new QPopupMenu(this);
281         file_menu->insertItem(tr("Recently opened"), recent_menu);
282         connect(recent_menu, SIGNAL(activated(int)), this, SLOT(recentSelected(int)));
283         file_menu->insertSeparator();
284         //create_id = file_menu->insertItem(tr("Create archive"), this, SLOT(createArchive()));
285         //file_menu->insertSeparator();
286         showInfoAction->addTo(file_menu);
287         file_menu->insertSeparator();
288         close_id = file_menu->insertItem(tr("Close"), this, SLOT(closeSink()));
289         file_menu->insertSeparator();
290         quitAction->addTo(file_menu);
291         menuBar()->insertItem(tr("&File"), file_menu);   
292 }
293
294 void ComicMainWindow::setupEditMenu()
295 {
296         edit_menu = new QPopupMenu(this);
297         gimp_id = edit_menu->insertItem(tr("Open with Gimp"), this, SLOT(openWithGimp()));
298         //edit_menu->insertItem(tr("Open with Kolour Paint"), this, SLOT(openWithGimp()));
299         //edit_menu->insertItem(tr("Open with ImageMagick"), this, SLOT(openWithGimp()));
300         edit_menu->insertSeparator();
301         reload_id = edit_menu->insertItem(tr("Reload page"), this, SLOT(reloadPage()));
302         menuBar()->insertItem(tr("&Edit"), edit_menu);
303 }
304
305 void ComicMainWindow::setupViewMenu()
306 {
307         view_menu = new QPopupMenu(this);
308         view_menu->setCheckable(true);
309         originalSizeAction->addTo(view_menu);
310         fitWidthAction->addTo(view_menu);
311         fitHeightAction->addTo(view_menu);
312         wholePageAction->addTo(view_menu);
313         bestFitAction->addTo(view_menu);
314         view_menu->insertSeparator();
315         rotateRightAction->addTo(view_menu);
316         rotateLeftAction->addTo(view_menu);
317         rotateResetAction->addTo(view_menu);
318         togglePreserveRotationAction->addTo(view_menu);
319         view_menu->insertSeparator();
320         twoPagesAction->addTo(view_menu);
321         stripModeAction->addTo(view_menu);
322         mangaModeAction->addTo(view_menu);
323         toggleThumbnailsAction->addTo(view_menu);
324         menuBar()->insertItem(tr("&View"), view_menu);     
325 }
326
327 void ComicMainWindow::setupNavigationMenu()
328 {
329         navi_menu = new QPopupMenu(this);
330         nextPageAction->addTo(navi_menu);
331         prevPageAction->addTo(navi_menu);
332         navi_menu->insertSeparator();
333         forwardPageAction->addTo(navi_menu);
334         backwardPageAction->addTo(navi_menu);
335         navi_menu->insertSeparator();
336         jumpto_id = navi_menu->insertItem(tr("Jump to page..."), this, SLOT(showJumpToPage()));
337         firstPageAction->addTo(navi_menu);
338         lastPageAction->addTo(navi_menu);
339         navi_menu->insertSeparator();
340         pageTopAction->addTo(navi_menu);
341         pageBottomAction->addTo(navi_menu);
342         navi_menu->insertSeparator();
343         contscr_id = navi_menu->insertItem(tr("Continuous scrolling"), this, SLOT(toggleContinousScroll()));
344         twoPagesAction->setOn(cfg->twoPagesMode());
345         stripModeAction->setOn(cfg->stripMode());
346         mangaModeAction->setOn(cfg->japaneseMode());
347         navi_menu->setItemChecked(contscr_id, cfg->continuousScrolling());
348         menuBar()->insertItem(tr("&Navigation"), navi_menu);
349 }
350
351 void ComicMainWindow::setupBookmarksMenu()
352 {
353         bookmarks_menu = new QPopupMenu(this);
354         bookmarks = new Bookmarks(bookmarks_menu);
355         menuBar()->insertItem(tr("&Bookmarks"), bookmarks_menu);
356         setbookmark_id = bookmarks_menu->insertItem(Icons::get(ICON_BOOKMARK), tr("Set bookmark for this comicbook"), this, SLOT(setBookmark()));
357         rmvbookmark_id = bookmarks_menu->insertItem(tr("Remove bookmark for this comicbook"), this, SLOT(removeBookmark()));
358         bookmarks_menu->insertItem(tr("Manage bookmarks"), this, SLOT(openBookmarksManager()));
359         bookmarks_menu->insertSeparator();
360         bookmarks->load();
361         connect(bookmarks_menu, SIGNAL(activated(int)), this, SLOT(bookmarkSelected(int)));
362 }
363
364 void ComicMainWindow::setupSettingsMenu()
365 {
366         settings_menu = new QPopupMenu(this);
367         scrv_id = settings_menu->insertItem(tr("Scrollbars"), this, SLOT(toggleScrollbars()));
368         settings_menu->setItemChecked(scrv_id, cfg->scrollbarsVisible());
369         toggleToolbarAction->addTo(settings_menu);
370         toggleStatusbarAction->addTo(settings_menu);
371         settings_menu->insertSeparator();
372         fullScreenAction->addTo(settings_menu);
373         settings_menu->insertSeparator();
374         settings_menu->insertItem(Icons::get(ICON_SETTINGS), tr("Configure QComicBook"), this, SLOT(showConfigDialog()));
375         menuBar()->insertItem(tr("&Settings"), settings_menu);
376 }
377
378 void ComicMainWindow::setupHelpMenu()
379 {
380         QPopupMenu *help_menu = new QPopupMenu(this);
381         help_menu->insertItem(tr("System information"), this, SLOT(showSysInfo()));
382         help_menu->insertSeparator();
383         help_menu->insertItem(tr("Index"), this, SLOT(showHelp()));
384         help_menu->insertItem(tr("About"), this, SLOT(showAbout()));
385         menuBar()->insertItem(tr("&Help"), help_menu);
386 }
387
388 void ComicMainWindow::setupStatusbar()
389 {
390         statusbar = new StatusBar(this);      
391         connect(toggleStatusbarAction, SIGNAL(toggled(bool)), statusbar, SLOT(setShown(bool)));
392         toggleStatusbarAction->setOn(cfg->showStatusbar());
393         statusbar->setShown(cfg->showStatusbar());
394 }
395
396 void ComicMainWindow::setupContextMenu()
397 {
398         QPopupMenu *cmenu = view->contextMenu();
399         pageinfo = new QLabel(cmenu);
400         pageinfo->setMargin(3);
401         pageinfo->setAlignment(Qt::AlignHCenter);
402         pageinfo->setFrameStyle(QFrame::Box | QFrame::Raised);
403         nextPageAction->addTo(cmenu);
404         prevPageAction->addTo(cmenu);
405         cmenu->insertSeparator();
406         fitWidthAction->addTo(cmenu);
407         fitHeightAction->addTo(cmenu);
408         wholePageAction->addTo(cmenu);
409         originalSizeAction->addTo(cmenu);
410         bestFitAction->addTo(cmenu);
411         cmenu->insertSeparator();
412         rotateRightAction->addTo(cmenu);
413         rotateLeftAction->addTo(cmenu);
414         rotateResetAction->addTo(cmenu);
415         cmenu->insertSeparator();
416         twoPagesAction->addTo(cmenu);
417         stripModeAction->addTo(cmenu);
418         mangaModeAction->addTo(cmenu);
419         cmenu->insertSeparator();
420         fullScreenAction->addTo(cmenu);
421         cmenu->insertSeparator();
422         cmenu->insertItem(pageinfo);
423 }
424
425 void ComicMainWindow::enableComicBookActions(bool f)
426 {
427         //
428         // file menu
429         const bool x = f && sink && sink->supportsNext();
430         file_menu->setItemEnabled(close_id, f);
431         file_menu->setItemEnabled(create_id, f);
432         showInfoAction->setEnabled(f);
433         openNextAction->setEnabled(x);
434         openPrevAction->setEnabled(x);
435
436         //
437         // edit menu
438         if (edit_menu)
439         {
440                 edit_menu->setItemEnabled(gimp_id, f);
441                 edit_menu->setItemEnabled(reload_id, f);
442         }
443         
444         //
445         // view menu
446         rotateRightAction->setEnabled(f);
447         rotateLeftAction->setEnabled(f);
448         rotateResetAction->setEnabled(f);
449
450         //
451         // navigation menu
452         lastPageAction->setEnabled(f);
453         firstPageAction->setEnabled(f);
454         navi_menu->setItemEnabled(jumpto_id, f);
455         nextPageAction->setEnabled(f);
456         prevPageAction->setEnabled(f);
457         backwardPageAction->setEnabled(f);
458         forwardPageAction->setEnabled(f);
459         pageTopAction->setEnabled(f);
460         pageBottomAction->setEnabled(f);
461
462         //
463         // bookmarks menu
464         bookmarks_menu->setItemEnabled(setbookmark_id, f);
465         bookmarks_menu->setItemEnabled(rmvbookmark_id, f);
466 }
467
468 void ComicMainWindow::dragEnterEvent(QDragEnterEvent *e)
469 {
470         e->accept(QUriDrag::canDecode(e));
471 }
472
473 void ComicMainWindow::dropEvent(QDropEvent *e)
474 {
475         QStringList files;
476         if (QUriDrag::decodeLocalFiles(e, files))
477                 open(files[0], 0);
478 }
479
480 void ComicMainWindow::keyPressEvent(QKeyEvent *e)
481 {
482         if ((e->key()>=Qt::Key_1) && (e->key()<=Qt::Key_9))
483         {
484                 e->accept();
485                 showJumpToPage(e->text());
486         }
487         else
488                 QMainWindow::keyPressEvent(e);
489 }
490
491 void ComicMainWindow::closeEvent(QCloseEvent *e)
492 {
493         return (!cfg->confirmExit() || confirmExit()) ? e->accept() : e->ignore();
494 }
495
496 bool ComicMainWindow::confirmExit()
497 {
498         return QMessageBox::question(this, tr("Leave QComicBook?"), tr("Do you really want to quit QComicBook?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
499 }
500
501 void ComicMainWindow::thumbnailsVisibilityChanged(bool f)
502 {
503         if (f && sink)
504         {
505                 int max = sink->numOfImages();
506                 for (int i=0; i<max; i++)
507                         if (!thumbswin->view()->isLoaded(i))
508                                 sink->requestThumbnail(i);
509         }
510 }
511
512 void ComicMainWindow::toolbarVisibilityChanged(bool f)
513 {
514         toggleToolbarAction->setOn(f);
515 }
516
517 void ComicMainWindow::toggleScrollbars()
518 {
519         bool f = settings_menu->isItemChecked(scrv_id);
520         settings_menu->setItemChecked(scrv_id, !f);
521         view->enableScrollbars(!f);
522 }
523
524 void ComicMainWindow::toggleContinousScroll()
525 {
526         bool f = navi_menu->isItemChecked(contscr_id);
527         navi_menu->setItemChecked(contscr_id, !f);
528         if (f)
529         {
530                 view->disconnect(SIGNAL(bottomReached()), this);
531                 view->disconnect(SIGNAL(topReached()), this);
532         }
533         else
534         {       connect(view, SIGNAL(bottomReached()), this, SLOT(nextPage()));
535                 connect(view, SIGNAL(topReached()), this, SLOT(prevPageBottom()));
536         }
537 }
538
539 void ComicMainWindow::toggleTwoPages(bool f)
540 {
541         twoPagesAction->setOn(f);
542         jumpToPage(currpage, true);
543 }
544
545 void ComicMainWindow::toggleStripMode(bool f)
546 {
547         stripModeAction->setOn(f);
548         jumpToPage(currpage, true);
549 }
550
551 void ComicMainWindow::toggleJapaneseMode(bool f)
552 {
553         mangaModeAction->setOn(f);
554         if (twoPagesAction->isOn())
555                 jumpToPage(currpage, true);
556 }
557
558 void ComicMainWindow::reloadPage()
559 {
560         if (sink)
561                 jumpToPage(currpage, true);
562 }
563
564 void ComicMainWindow::updateCaption()
565 {
566         QString c = "QComicBook";
567         if (sink)
568                 c += " - " + sink->getName();
569         setCaption(c);
570 }
571
572 void ComicMainWindow::setRecentFilesMenu(const History &hist)
573 {
574         QStringList list = hist.getAll();
575         recent_menu->clear();
576         for (QStringList::const_iterator it = list.begin(); it != list.end(); it++)
577                 recent_menu->insertItem(*it);
578 }
579
580 void ComicMainWindow::recentSelected(int id)
581 {
582         const QString &fname = recent_menu->text(id);
583         if (fname != QString::null)
584         {
585                 QFileInfo finfo(fname);
586                 if (!finfo.exists())
587                 {
588                         recentfiles->remove(fname);
589                         recent_menu->removeItem(id);
590                 }
591                 open(fname, 0);
592         }
593 }
594
595 void ComicMainWindow::sinkReady(const QString &path)
596 {
597         statusbar->setShown(toggleStatusbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideStatusbar())); //applies back user's statusbar&toolbar preferences
598         //toolbar->setShown(toggleToolbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideToolbar()));
599
600         recentfiles->append(path);
601         setRecentFilesMenu(*recentfiles);
602
603         enableComicBookActions(true);
604         updateCaption();
605         statusbar->setName(sink->getFullName());
606
607         thumbswin->view()->setPages(sink->numOfImages());
608
609         //
610         // request thumbnails for all pages
611         if (thumbswin->isVisible())
612                 sink->requestThumbnails(0, sink->numOfImages());
613
614         jumpToPage(currpage, true);
615
616         const bool hasdesc = (sink->getDescription().count() > 0);
617         showInfoAction->setDisabled(!hasdesc);
618
619         if (hasdesc && cfg->autoInfo())
620                 showInfo();
621 }
622
623 void ComicMainWindow::sinkError(int code)
624 {
625         statusbar->setShown(toggleStatusbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideStatusbar())); //applies back user's statusbar&toolbar preferences
626         //toolbar->setShown(toggleToolbarAction->isOn() && !(isFullScreen() && cfg->fullScreenHideToolbar()));
627
628         QString msg;
629         switch (code)
630         {
631                 case SINKERR_EMPTY: msg = tr("no images found"); break;
632                 case SINKERR_UNKNOWNFILE: msg = tr("unknown archive"); break;
633                 case SINKERR_ACCESS: msg = tr("can't access directory"); break;
634                 case SINKERR_NOTFOUND: msg = tr("file/directory not found"); break;
635                 case SINKERR_NOTSUPPORTED: msg = tr("archive not supported"); break;
636                 case SINKERR_ARCHEXIT: msg = tr("archive extractor exited with error"); break;
637                 default: break;
638         }
639         QMessageBox::critical(this, "QComicBook error", "Error opening comicbook: " + msg, 
640                         QMessageBox::Ok, QMessageBox::NoButton);
641         closeSink();
642 }
643
644 void ComicMainWindow::browseDirectory()
645 {
646         const QString dir = QFileDialog::getExistingDirectory(lastdir, this, 
647                         NULL, tr("Choose a directory") );
648         if (!dir.isEmpty())
649                 open(dir, 0);
650 }
651
652 void ComicMainWindow::browseArchive()
653 {
654         const QString file = QFileDialog::getOpenFileName(lastdir,
655                         "Archives (" + ImgArchiveSink::supportedOpenExtensions() + ");;All files (*)",
656                         this, NULL, tr("Choose a file") );
657         if (!file.isEmpty())
658                 open(file, 0);
659 }
660
661 void ComicMainWindow::open(const QString &path, int page)
662 {
663         const QFileInfo f(path);
664         const QString fullname = f.absFilePath();
665
666         if (sink && sink->getFullName() == fullname) //trying to open same dir?
667                 return;
668
669         lastdir = f.dirPath(true);
670         currpage = page;
671
672         closeSink();
673
674         ImlibImage::setCacheSize(cfg->cacheSize()*1024*1024);
675
676         sink = ImgSinkFactory::instance().createImgSink(path);
677         sink->thumbnailLoader().setReciever(thumbswin);
678         sink->thumbnailLoader().setUseCache(cfg->cacheThumbnails());
679
680         connect(sink, SIGNAL(sinkReady(const QString&)), this, SLOT(sinkReady(const QString&)));
681         connect(sink, SIGNAL(sinkError(int)), this, SLOT(sinkError(int)));
682         connect(sink, SIGNAL(progress(int, int)), statusbar, SLOT(setProgress(int, int)));
683
684         statusbar->setShown(true); //ensures status bar is visible when opening regardless of user settings
685
686         sink->open(fullname);
687 }
688
689 void ComicMainWindow::openNext()
690 {
691         if (sink && sink->supportsNext())
692         {
693                 QString fname = sink->getNext();
694                 if (!fname.isEmpty())
695                         open(fname, 0);
696         }
697 }
698
699 void ComicMainWindow::openPrevious()
700 {
701         if (sink && sink->supportsNext())
702         {
703                 QString fname = sink->getPrevious();
704                 if (!fname.isEmpty())
705                         open(fname, 0);
706         }
707 }
708
709 void ComicMainWindow::toggleFullScreen()
710 {
711         if (isFullScreen())
712         {
713                 exitFullscreen();
714         }
715         else
716         {
717                 if (cfg->fullScreenHideMenu())
718                         menuBar()->hide();
719                 if (cfg->fullScreenHideStatusbar())
720                         statusbar->hide();
721                 /*if (cfg->fullScreenHideToolbar())
722                         toolbar->hide();*/
723                 showFullScreen();
724         }
725 }
726
727 void ComicMainWindow::exitFullscreen()
728 {
729         if (isFullScreen())
730         {
731                 menuBar()->show();
732                 if (toggleStatusbarAction->isOn())
733                         statusbar->show();
734                 /*if (toggleToolbarAction->isOn())
735                         toolbar->show();*/
736                 showNormal();
737         }
738 }
739
740 void ComicMainWindow::nextPage()
741 {
742         if (sink)
743         {
744                 int savedpage = currpage;
745                 if (nextpage != -1)
746                 {
747                         jumpToPage(nextpage);
748                 }
749                 else
750                 {
751                         if (!spreading && twoPagesAction->isOn() && cfg->twoPagesStep())
752                         {
753                                 if (currpage < sink->numOfImages() - 2) //do not change pages if last two pages are visible
754                                         jumpToPage(currpage + 2);
755                         }
756                         else
757                         {
758                                 jumpToPage(currpage + 1);
759                         }
760                 }
761                 if (currpage != savedpage)
762                         prevpage = savedpage;
763         }
764 }
765
766 void ComicMainWindow::prevPage()
767 {
768         if (prevpage != -1)
769                 jumpToPage(prevpage);
770         else
771                 if (!spreading && twoPagesAction->isOn() && cfg->twoPagesStep())
772                         jumpToPage(currpage - 2);
773                 else
774                         jumpToPage(currpage - 1);
775 }
776
777 void ComicMainWindow::prevPageBottom()
778 {
779         if (currpage > 0)
780         {
781                 prevPage();
782                 view->scrollToBottom();
783         }
784 }
785
786 void ComicMainWindow::firstPage()
787 {
788         jumpToPage(0);
789 }
790
791 void ComicMainWindow::lastPage()
792 {
793         if (sink)
794                 jumpToPage(sink->numOfImages() - (twoPagesAction->isOn() && cfg->twoPagesStep() ? 2 : 1));
795 }
796
797 void ComicMainWindow::forwardPages()
798 {
799         jumpToPage(currpage + 5*(nextpage-currpage));
800 }
801
802 void ComicMainWindow::backwardPages()
803 {
804         jumpToPage(currpage - 5*(nextpage-currpage));
805 }
806
807 void ComicMainWindow::jumpToPage(int n, bool force)
808 {
809         if (!sink)
810                 return;
811         if (n >= sink->numOfImages())
812                 n = sink->numOfImages()-1;
813         if (n < 0)
814                 n = 0;
815         if ((n != currpage) || force)
816         {
817                 prevpage = -1;
818                 int result1, result2;
819                 ImlibImage *img1, *img2;
820                 ImlibImageList list[2];
821                 int page_off = 0;
822                 const bool preserveangle = togglePreserveRotationAction->isOn();
823
824                 img1 = sink->getImage(currpage = n, result1);
825
826                 if (img1->getFormat() == STRIP_FORMAT && stripModeAction->isOn())
827                 {
828                         nextpage = buildStripList(n, img1, list, twoPagesAction->isOn());
829                         jumpToStrip(n, list, twoPagesAction->isOn());
830                         return;
831                 }
832
833                 if (twoPagesAction->isOn())
834                 {
835                         img2 = sink->getImage(currpage + (++page_off), result2);
836
837                         if (result2 == 0 && img2->getFormat() == STRIP_FORMAT && stripModeAction->isOn()) {
838                                 list[0].append(img1);
839                                 nextpage = buildStripList(currpage+page_off, img2, &(list[1]), false);
840                                 jumpToStrip(n, list, true);
841                                 return;
842                         }
843
844                         if (img1->getFormat() == SPREAD_FORMAT)
845                         {
846                                 result2 = 1;
847                                 spreading = true;
848                         }
849                         else
850                         {
851                                 spreading = false;
852                         }
853
854                         // Ignore spreads as second pages
855                         // Showing the next page or not showing any page
856                         // should be an option because some sinks have the spread
857                         // between the left and right page, while others have the
858                         // spread but not the single pages, and there is no way simple way
859                         // to autodetect it.
860
861                         // For the moment we just use the "stop at spread"
862 #if 0
863                         // Skip spreads:
864                         while (result2 == 0 && img2->getFormat() == SPREAD_FORMAT)
865                         {
866                                 delete img2;
867                                 img2 = sink->getImage(currpage + (++page_off), result2);
868                                 spreading = true;
869                         }
870 #else
871                         // Stop at spreads:
872                         if (result2 == 0 && img2->getFormat() == SPREAD_FORMAT)
873                         {
874                                 result2 = 1;
875                                 spreading = true;
876                         }
877 #endif
878
879                         if (result2 == 0)
880                         {
881                                 if (mangaModeAction->isOn())
882                                 {
883                                         view->setImage(img2, img1, preserveangle);
884                                         statusbar->setImageInfo(img2, img1);
885                                 }
886                                 else
887                                 {
888                                         view->setImage(img1, img2, preserveangle);
889                                         statusbar->setImageInfo(img1, img2);
890                                 }
891                         }
892                         else
893                         {
894                                 view->setImage(img1, preserveangle);
895                                 statusbar->setImageInfo(img1);
896                                 if (cfg->preloadPages())
897                                         sink->preload(currpage + 1);
898                         }
899                 }
900                 else
901                 {
902                         view->setImage(img1, preserveangle);
903                         statusbar->setImageInfo(img1);
904                 }
905                 if (mangaModeAction->isOn())
906                         view->ensureVisible(view->imageWidth(), 0);
907                 const QString page = tr("Page") + " " + QString::number(currpage + 1) + "/" + QString::number(sink->numOfImages());
908                 pageinfo->setText(page);
909                 statusbar->setPage(currpage + 1, sink->numOfImages());
910                 thumbswin->view()->scrollToPage(currpage);
911                 if (cfg->preloadPages())
912                 {
913                         if (!twoPagesAction->isOn())
914                         {
915                                 sink->preload(currpage + 1);
916                         }
917                         else
918                         {
919                                 sink->preload(currpage + 3);
920                                 sink->preload(currpage + 2);
921                         }
922                 }
923                 nextpage = currpage + (++page_off);
924         }
925 }
926
927 int ComicMainWindow::buildStripList(int n, ImlibImage *img1, ImlibImageList *list, bool twoPages)
928 {
929 #define STRIP_MAX 7
930         ImlibImage *img2;
931         int page_off = 0;
932         int result2;
933
934         list[0].append(img1);
935
936         img2 = sink->getImage(n + (++page_off), result2) ;
937         while (result2 == 0 && img2->getFormat() == STRIP_FORMAT && list[0].count() < STRIP_MAX) {
938                 list[0].append(img2) ;
939                 img2 = sink->getImage(n + (++page_off), result2) ;
940         }
941
942         // If we are displaying two pages, start filling the second side
943         if (twoPages && result2 == 0)
944         {
945                 list[1].append(img2) ;
946                 // ... which is a strip collection if the first strip is a strip
947                 if (img2->getFormat() == STRIP_FORMAT)
948                 {
949                         img2 = sink->getImage(n + (++page_off), result2) ;
950                         while (result2 == 0 && img2->getFormat() == STRIP_FORMAT && list[1].count() < STRIP_MAX) {
951                                 list[1].append(img2) ;
952                                 img2 = sink->getImage(n + (++page_off), result2) ;
953                         }
954                 }
955
956         }
957         return (n + page_off);
958 }
959
960 void ComicMainWindow::jumpToStrip(int n, ImlibImageList *list, bool twoPages)
961 {
962         const bool preserveangle = togglePreserveRotationAction->isOn();
963
964         // Temporary
965         ImlibImage *img1, *img2;
966
967         img1 = list[0].at(0);
968
969         if (twoPages)
970         {
971                 img2 = list[1].at(0);
972                 if (mangaModeAction->isOn())
973                 {
974                         view->setImage(list[1], list[0], preserveangle);
975                         statusbar->setImageInfo(img2, img1);
976                 }
977                 else
978                 {
979                         view->setImage(list[0], list[1], preserveangle);
980                         statusbar->setImageInfo(img1, img2);
981                 }
982         }
983         else
984         {
985                 view->setImage(list[0], preserveangle);
986                 statusbar->setImageInfo(img1);
987         }
988         if (mangaModeAction->isOn())
989                 view->ensureVisible(view->imageWidth(), 0);
990         const QString page = tr("Page") + " " + QString::number(currpage + 1) + "/" + QString::number(sink->numOfImages());
991         pageinfo->setText(page);
992         statusbar->setPage(currpage + 1, sink->numOfImages());
993         thumbswin->view()->scrollToPage(currpage);
994 }
995
996 void ComicMainWindow::showInfo()
997 {
998         if (sink)
999         {
1000                 ComicBookInfo *i = new ComicBookInfo(this, *sink, cfg->infoFont());
1001                 i->show();
1002         }
1003 }
1004
1005 void ComicMainWindow::showSysInfo()
1006 {
1007         SupportedArchivesWindow *win = new SupportedArchivesWindow(this);
1008         win->show();
1009 }
1010
1011 void ComicMainWindow::createArchive()
1012 {
1013         if (sink)
1014         {
1015                 ArchiverDialog *win = new ArchiverDialog(this, sink);
1016                 win->exec();
1017                 delete win;
1018         }
1019 }
1020
1021 void ComicMainWindow::showAbout()
1022 {
1023         AboutDialog *win = new AboutDialog(this, "About QComicBook",
1024                         "QComicBook " VERSION " - comic book viewer for GNU/Linux\n"
1025                         "(c)by Pawel Stolowski 2005-2006\n"
1026                         "released under terms of GNU General Public License\n\n"
1027                         "http://linux.bydg.org/~yogin\n"
1028                         "pawel.stolowski@wp.pl", QPixmap(DATADIR "qcomicbook-splash.png"));
1029         win->show();
1030 }
1031
1032 void ComicMainWindow::showHelp()
1033 {
1034         const QString helpdir = HelpBrowser::getLocaleHelpDir(DATADIR "/help");
1035         if (!cfg->useInternalBrowser() && cfg->externalBrowser() != QString::null)
1036         {
1037                 QProcess *proc = new QProcess(this);
1038                 proc->addArgument(cfg->externalBrowser());
1039                 proc->addArgument(helpdir + "/index.html");
1040                 connect(proc, SIGNAL(processExited()), proc, SLOT(deleteLater()));
1041                 if (!proc->start())
1042                 {
1043                         proc->deleteLater();
1044                         cfg->useInternalBrowser(true);
1045                 }
1046         }
1047         if (cfg->useInternalBrowser())
1048         {
1049                 HelpBrowser *help = new HelpBrowser(tr("QComicBook Help"), helpdir);
1050                 help->show();
1051         }
1052 }
1053
1054 void ComicMainWindow::showConfigDialog()
1055 {
1056         ComicBookCfgDialog *d = new ComicBookCfgDialog(this, cfg);
1057         d->show();
1058 }
1059
1060 void ComicMainWindow::showJumpToPage(const QString &number)
1061 {
1062         if (sink)
1063         {
1064                 JumpToPageWindow *win = new JumpToPageWindow(this, number.toInt(), sink->numOfImages());
1065                 connect(win, SIGNAL(pageSelected(int)), this, SLOT(jumpToPage(int)));
1066                 win->show();
1067         }
1068 }
1069
1070 void ComicMainWindow::closeSink()
1071 {
1072         enableComicBookActions(false);
1073
1074         if (sink)
1075         {
1076                 /*if (typeid(*sink) == typeid(ImgArchiveSink) && cfg->editSupport() && sink->hasModifiedFiles()) 
1077                 {
1078                         if (QMessageBox::warning(this, tr("Create archive?"),
1079                                 tr("Warning! Some files were modified in this comic book\nDo you want to create new archive file?"),
1080                                 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1081                         {
1082                                 ArchiverDialog *win = new ArchiverDialog(this, sink);
1083                                 win->exec();
1084                                 delete win;
1085                         }
1086
1087                 }*/
1088                 sink->deleteLater();
1089                 sink = NULL;
1090         }
1091         view->clear();
1092         thumbswin->view()->clear();
1093         updateCaption();
1094         statusbar->clear();
1095 }
1096
1097 void ComicMainWindow::setBookmark()
1098 {
1099         if (sink)
1100                 bookmarks->set(sink->getFullName(), currpage);
1101 }
1102
1103 void ComicMainWindow::removeBookmark()
1104 {
1105         if (sink && bookmarks->exists(sink->getFullName()) && QMessageBox::question(this, tr("Removing bookmark"),
1106                                 tr("Do you really want to remove bookmark\nfor this comic book?"),
1107                                 QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1108                 bookmarks->remove(sink->getFullName());
1109 }
1110
1111 void ComicMainWindow::openBookmarksManager()
1112 {
1113         BookmarkManager *win = new BookmarkManager(this, bookmarks);
1114         win->show();
1115 }
1116
1117 void ComicMainWindow::openWithGimp()
1118 {
1119         if (!sink)
1120                 return;
1121         QProcess *proc = new QProcess(this);
1122         proc->addArgument("gimp-remote");
1123         proc->addArgument(sink->getFullFileName(currpage));
1124         connect(proc, SIGNAL(processExited()), proc, SLOT(deleteLater()));
1125         if (!proc->start())
1126         {
1127                 proc->deleteLater();
1128         }
1129 }
1130
1131 void ComicMainWindow::bookmarkSelected(int id)
1132 {
1133         Bookmark b;
1134         if (bookmarks->get(id, b))
1135         {
1136                 if (b.getName() != QString::null)
1137                 {
1138                         QString fname = b.getName();
1139                         if (sink && fname == sink->getFullName()) //is this comicbook already opened?
1140                         {
1141                                 jumpToPage(b.getPage(), true); //if so, just jump to bookmarked page
1142                                 return;
1143                         }
1144
1145                         QFileInfo finfo(fname);
1146                         if (!finfo.exists())
1147                         {
1148                                 if (QMessageBox::question(this, tr("Comic book not found"),
1149                                                         tr("Selected bookmark points to\nnon-existing comic book\nDo you want to remove it?"),
1150                                                         QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
1151                                         bookmarks->remove(id);
1152                                 return;
1153                         }
1154                         open(fname, b.getPage());
1155                 }
1156         }
1157 }
1158
1159 void ComicMainWindow::saveSettings()
1160 {
1161         cfg->geometry(frameGeometry());
1162         cfg->saveDockLayout(this);
1163         cfg->scrollbarsVisible(settings_menu->isItemChecked(scrv_id));
1164         cfg->twoPagesMode(twoPagesAction->isOn());
1165         cfg->stripMode(stripModeAction->isOn());
1166         cfg->japaneseMode(mangaModeAction->isOn());
1167         cfg->continuousScrolling(navi_menu->isItemChecked(contscr_id));
1168         cfg->lastDir(lastdir);
1169         cfg->recentlyOpened(*recentfiles);
1170         cfg->pageSize(view->getSize());
1171         cfg->showStatusbar(toggleStatusbarAction->isOn());
1172
1173         bookmarks->save();        
1174 }
1175