1 //C-  -*- C++ -*-
2 //C- -------------------------------------------------------------------
3 //C- DjView4
4 //C- Copyright (c) 2006-  Leon Bottou
5 //C-
6 //C- This software is subject to, and may be distributed under, the
7 //C- GNU General Public License, either version 2 of the license,
8 //C- or (at your option) any later version. The license should have
9 //C- accompanied the software or you may obtain a copy of the license
10 //C- from the Free Software Foundation at http://www.fsf.org .
11 //C-
12 //C- This program is distributed in the hope that it will be useful,
13 //C- but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 //C- GNU General Public License for more details.
16 //C-  ------------------------------------------------------------------
17 
18 #if AUTOCONF
19 # include "config.h"
20 #else
21 # define HAVE_STRING_H 1
22 # define HAVE_STRERROR 1
23 # ifndef WIN32
24 #  define HAVE_UNISTD_H 1
25 # endif
26 #endif
27 
28 #if HAVE_STRING_H
29 # include <string.h>
30 #endif
31 #if HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 #include <errno.h>
35 
36 #include <libdjvu/miniexp.h>
37 #include <libdjvu/ddjvuapi.h>
38 
39 #include <QAction>
40 #include <QActionGroup>
41 #include <QApplication>
42 #include <QBoxLayout>
43 #include <QClipboard>
44 #include <QCloseEvent>
45 #include <QComboBox>
46 #include <QCoreApplication>
47 #include <QDebug>
48 #include <QDialog>
49 #include <QDir>
50 #include <QDockWidget>
51 #include <QDragEnterEvent>
52 #include <QDragMoveEvent>
53 #include <QDropEvent>
54 #include <QEvent>
55 #include <QFile>
56 #include <QFileDialog>
57 #include <QFileInfo>
58 #include <QFontMetrics>
59 #include <QBoxLayout>
60 #include <QFont>
61 #include <QFrame>
62 #include <QIcon>
63 #include <QImageWriter>
64 #include <QInputDialog>
65 #include <QLabel>
66 #include <QLineEdit>
67 #include <QList>
68 #include <QMainWindow>
69 #include <QMenu>
70 #include <QMenuBar>
71 #include <QMessageBox>
72 #include <QMimeData>
73 #include <QObject>
74 #include <QPair>
75 #include <QPalette>
76 #include <QProcess>
77 #include <QRegExp>
78 #include <QRegExpValidator>
79 #include <QScrollBar>
80 #include <QSettings>
81 #include <QShortcut>
82 #include <QSlider>
83 #include <QStackedLayout>
84 #include <QStatusBar>
85 #include <QString>
86 #include <QStyle>
87 #include <QStyleOptionComboBox>
88 #include <QTextDocument>
89 #include <QTextStream>
90 #include <QTimer>
91 #include <QTabWidget>
92 #include <QToolBar>
93 #include <QUrl>
94 #include <QWhatsThis>
95 #if QT_VERSION >= 0x40200
96 # include <QDesktopServices>
97 #endif
98 #if QT_VERSION >= 0x50000
99 # include <QUrlQuery>
100 #endif
101 
102 #if QT_VERSION >= 0x50E00
103 # define tr8 tr
104 # define swidth(m,s) (m).horizontalAdvance(s)
105 # define zero(T) T()
106 #else
107 # define tr8 trUtf8
108 # define swidth(m,s) (m).width(s)
109 # define zero(T) 0
110 #endif
111 
112 #include "qdjvu.h"
113 #include "qdjvunet.h"
114 #include "qdjvuwidget.h"
115 #include "qdjview.h"
116 #include "qdjviewprefs.h"
117 #include "qdjviewdialogs.h"
118 #include "qdjviewsidebar.h"
119 
120 
121 
122 // ----------------------------------------
123 // UTILITIES
124 
125 typedef QList<QPair<QString, QString> > QueryItems;
126 
127 static QueryItems
urlQueryItems(const QUrl & url,bool fullydecoded=false)128 urlQueryItems(const QUrl &url, bool fullydecoded=false)
129 {
130 #if QT_VERSION >= 0x50000
131   if (fullydecoded)
132     return QUrlQuery(url).queryItems(QUrl::FullyDecoded);
133   return QUrlQuery(url).queryItems();
134 #else
135   Q_UNUSED(fullydecoded);
136   return url.queryItems();
137 #endif
138 }
139 
140 static void
addQueryItem(QueryItems & items,const QString & k,const QString & v)141 addQueryItem(QueryItems &items, const QString &k, const QString &v)
142 {
143   items.append(qMakePair(k,v));
144 }
145 
146 static bool
hasQueryItem(const QueryItems & items,QString key,bool afterDjvuOpts=true)147 hasQueryItem(const QueryItems &items, QString key, bool afterDjvuOpts=true)
148 {
149   key = key.toLower();
150   QPair<QString,QString> pair;
151   foreach(pair, items)
152     if (pair.first.toLower() == "djvuopts")
153       afterDjvuOpts = true;
154     else if (afterDjvuOpts && pair.first.toLower() == key)
155       return true;
156   return false;
157 }
158 
159 static void
urlSetQueryItems(QUrl & url,const QueryItems & items)160 urlSetQueryItems(QUrl &url, const QueryItems &items)
161 {
162 #if QT_VERSION >= 0x50000
163   QUrlQuery qitems;
164   qitems.setQueryItems(items);
165   url.setQuery(qitems);
166 #else
167   url.setQueryItems(items);
168 #endif
169 }
170 
171 static void
growPageCombo(QComboBox * p,QString contents=QString ())172 growPageCombo(QComboBox *p, QString contents = QString())
173 {
174   QFontMetrics metrics(p->font());
175   QSize csize = metrics.size(Qt::TextSingleLine, contents);
176   QStyleOptionComboBox options;
177   options.initFrom(p);
178   options.editable = true;
179   QSize osize = p->style()->sizeFromContents(QStyle::CT_ComboBox,&options,csize,p);
180   p->setMinimumWidth(qMax(p->minimumWidth(), osize.width()));
181 }
182 
183 
184 
185 // ----------------------------------------
186 // QDJVIEW
187 
188 
189 
190 /*! \class QDjView
191   \brief The main viewer interface.
192 
193   Class \a QDjView defines the djvu viewer graphical user interface. It is
194   composed of a main window with menubar, toolbar, statusbar and a dockable
195   sidebar.  The center is occupied by a \a QDjVuWidget. */
196 
197 
198 
199 // ----------------------------------------
200 // FILL USER INTERFACE COMPONENTS
201 
202 
203 void
fillPageCombo(QComboBox * pageCombo)204 QDjView::fillPageCombo(QComboBox *pageCombo)
205 {
206   pageCombo->clear();
207   int n = documentPages.size();
208   for (int j=0; j<n; j++)
209     {
210       QString name = pageName(j);
211       pageCombo->addItem(name, QVariant(j));
212     }
213 }
214 
215 
216 void
fillZoomCombo(QComboBox * zoomCombo)217 QDjView::fillZoomCombo(QComboBox *zoomCombo)
218 {
219   zoomCombo->clear();
220   zoomCombo->addItem(tr("FitWidth","zoomCombo"), QDjVuWidget::ZOOM_FITWIDTH);
221   zoomCombo->addItem(tr("FitPage","zoomCombo"), QDjVuWidget::ZOOM_FITPAGE);
222   zoomCombo->addItem(tr("Stretch","zoomCombo"), QDjVuWidget::ZOOM_STRETCH);
223   zoomCombo->addItem(tr("1:1","zoomCombo"), QDjVuWidget::ZOOM_ONE2ONE);
224   zoomCombo->addItem(tr("300%","zoomCombo"), 300);
225   zoomCombo->addItem(tr("200%","zoomCombo"), 200);
226   zoomCombo->addItem(tr("150%","zoomCombo"), 150);
227   zoomCombo->addItem(tr("100%","zoomCombo"), 100);
228   zoomCombo->addItem(tr("75%","zoomCombo"), 75);
229   zoomCombo->addItem(tr("50%","zoomCombo"), 50);
230 }
231 
232 
233 void
fillModeCombo(QComboBox * modeCombo)234 QDjView::fillModeCombo(QComboBox *modeCombo)
235 {
236   modeCombo->clear();
237   modeCombo->addItem(tr("Color","modeCombo"), QDjVuWidget::DISPLAY_COLOR);
238   modeCombo->addItem(tr("Stencil","modeCombo"), QDjVuWidget::DISPLAY_STENCIL);
239   modeCombo->addItem(tr("Foreground","modeCombo"), QDjVuWidget::DISPLAY_FG);
240   modeCombo->addItem(tr("Background","modeCombo"), QDjVuWidget::DISPLAY_BG);
241   modeCombo->addItem(tr("Hidden Text","modeCombo"), QDjVuWidget::DISPLAY_TEXT);
242 }
243 
244 
245 void
fillToolBar(QToolBar * toolBar)246 QDjView::fillToolBar(QToolBar *toolBar)
247 {
248   // Hide toolbar
249   bool wasHidden = toolBar->isHidden();
250   toolBar->hide();
251   toolBar->clear();
252   // Hide combo boxes
253   modeCombo->hide();
254   pageCombo->hide();
255   zoomCombo->hide();
256   // Use options to compose toolbar
257   if (viewerMode >= STANDALONE)
258     {
259       if (tools & QDjViewPrefs::TOOL_NEW)
260         toolBar->addAction(actionNew);
261       if (tools & QDjViewPrefs::TOOL_OPEN)
262         toolBar->addAction(actionOpen);
263     }
264   if (tools & QDjViewPrefs::TOOL_SAVE)
265     {
266       toolBar->addAction(actionSave);
267     }
268   if (tools & QDjViewPrefs::TOOL_PRINT)
269     {
270       toolBar->addAction(actionPrint);
271     }
272   if (tools & QDjViewPrefs::TOOL_FIND)
273     {
274       toolBar->addAction(actionFind);
275     }
276   if (tools & QDjViewPrefs::TOOL_SELECT)
277     {
278       toolBar->addAction(actionSelect);
279     }
280   if (tools & QDjViewPrefs::TOOL_LAYOUT)
281     {
282       toolBar->addAction(actionLayoutContinuous);
283       toolBar->addAction(actionLayoutSideBySide);
284     }
285   if ((tools & QDjViewPrefs::TOOL_MODECOMBO) ||
286       (tools & QDjViewPrefs::TOOL_MODEBUTTONS) )
287     {
288       modeCombo->show();
289       toolBar->addWidget(modeCombo);
290     }
291   if (tools & QDjViewPrefs::TOOL_ZOOMCOMBO)
292     {
293       zoomCombo->show();
294       toolBar->addWidget(zoomCombo);
295     }
296   if (tools & QDjViewPrefs::TOOL_ZOOMBUTTONS)
297     {
298       toolBar->addAction(actionZoomIn);
299       toolBar->addAction(actionZoomOut);
300     }
301   if (tools & QDjViewPrefs::TOOL_ROTATE)
302     {
303       toolBar->addAction(actionRotateRight);
304       toolBar->addAction(actionRotateLeft);
305     }
306   if (tools & QDjViewPrefs::TOOL_PAGECOMBO)
307     {
308       pageCombo->show();
309       toolBar->addWidget(pageCombo);
310     }
311   if (tools & QDjViewPrefs::TOOL_FIRSTLAST)
312     {
313       toolBar->addAction(actionNavFirst);
314     }
315   if (tools & QDjViewPrefs::TOOL_PREVNEXT)
316     {
317       toolBar->addAction(actionNavPrev);
318       toolBar->addAction(actionNavNext);
319     }
320   if (tools & QDjViewPrefs::TOOL_FIRSTLAST)
321     {
322       toolBar->addAction(actionNavLast);
323     }
324   if (tools & QDjViewPrefs::TOOL_BACKFORW)
325     {
326       toolBar->addAction(actionBack);
327       toolBar->addAction(actionForw);
328     }
329   if (tools & QDjViewPrefs::TOOL_WHATSTHIS)
330     {
331       toolBar->addAction(actionWhatsThis);
332     }
333   // Allowed areas
334   Qt::ToolBarAreas areas = zero(Qt::ToolBarAreas);
335   if (tools & QDjViewPrefs::TOOLBAR_TOP)
336     areas |= Qt::TopToolBarArea;
337   if (tools & QDjViewPrefs::TOOLBAR_BOTTOM)
338     areas |= Qt::BottomToolBarArea;
339   if (!areas)
340     areas = Qt::TopToolBarArea | Qt::BottomToolBarArea;
341   toolBar->setAllowedAreas(areas);
342   if (! (toolBarArea(toolBar) & areas))
343     {
344       removeToolBar(toolBar);
345       if (areas & Qt::TopToolBarArea)
346         addToolBar(Qt::TopToolBarArea, toolBar);
347       else
348         addToolBar(Qt::BottomToolBarArea, toolBar);
349     }
350   // Done
351   toolBar->setVisible(!wasHidden);
352   toolsCached = tools;
353 }
354 
355 
356 
357 
358 // ----------------------------------------
359 // ACTIONS
360 
361 
362 QAction *
makeAction(QString text)363 QDjView::makeAction(QString text)
364 {
365   QAction *action = new QAction(text, this);
366   allActions.append(action);
367   return action;
368 }
369 
370 QAction *
makeAction(QString text,Qt::ShortcutContext scontext)371 QDjView::makeAction(QString text, Qt::ShortcutContext scontext)
372 {
373   QAction *action = new QAction(text, widget);
374   allActions.append(action);
375   action->setShortcutContext(scontext);
376   return action;
377 }
378 
379 QAction *
makeAction(QString text,bool value)380 QDjView::makeAction(QString text, bool value)
381 {
382   QAction *action = new QAction(text, this);
383   allActions.append(action);
384   action->setCheckable(true);
385   action->setChecked(value);
386   return action;
387 }
388 
389 static inline QAction *
operator <<(QAction * action,QIcon icon)390 operator<<(QAction *action, QIcon icon)
391 {
392   action->setIcon(icon);
393   return action;
394 }
395 
396 static inline QAction *
operator <<(QAction * action,QActionGroup & group)397 operator<<(QAction *action, QActionGroup &group)
398 {
399   action->setActionGroup(&group);
400   return action;
401 }
402 
403 static inline QAction *
operator <<(QAction * action,QKeySequence shortcut)404 operator<<(QAction *action, QKeySequence shortcut)
405 {
406   QList<QKeySequence> shortcuts = action->shortcuts();
407   shortcuts.prepend(shortcut);
408   action->setShortcuts(shortcuts);
409   return action;
410 }
411 
412 static inline QAction *
operator <<(QAction * action,QString string)413 operator<<(QAction *action, QString string)
414 {
415   if (action->text().isEmpty())
416     action->setText(string);
417   else if (action->statusTip().isEmpty())
418     action->setStatusTip(string);
419   else if (action->whatsThis().isEmpty())
420     action->setWhatsThis(string);
421   return action;
422 }
423 
424 struct Trigger {
425   QObject *object;
426   const char *slot;
TriggerTrigger427   Trigger(QObject *object, const char *slot)
428     : object(object), slot(slot) { }
429 };
430 
431 static inline QAction *
operator <<(QAction * action,Trigger trigger)432 operator<<(QAction *action, Trigger trigger)
433 {
434   QObject::connect(action, SIGNAL(triggered(bool)),
435                    trigger.object, trigger.slot);
436   return action;
437 }
438 
439 static inline QAction *
operator <<(QAction * action,QVariant variant)440 operator<<(QAction *action, QVariant variant)
441 {
442   action->setData(variant);
443   return action;
444 }
445 
446 void
createActions()447 QDjView::createActions()
448 {
449   // Create action groups
450   zoomActionGroup = new QActionGroup(this);
451   modeActionGroup = new QActionGroup(this);
452   rotationActionGroup  = new QActionGroup(this);
453 
454   // Create all actions
455   actionNew = makeAction(tr("&New", "File|"))
456     << QKeySequence(tr("Ctrl+N", "File|New"))
457     << QIcon(":/images/icon_new.png")
458     << tr("Create a new DjView window.")
459     << Trigger(this, SLOT(performNew()));
460 
461   actionOpen = makeAction(tr("&Open", "File|"))
462     << QKeySequence(tr("Ctrl+O", "File|Open"))
463     << QIcon(":/images/icon_open.png")
464     << tr("Open a DjVu document.")
465     << Trigger(this, SLOT(performOpen()));
466 
467   actionOpenLocation = makeAction(tr("Open &Location...", "File|"))
468     << tr("Open a remote DjVu document.")
469     << QIcon(":/images/icon_web.png")
470     << Trigger(this, SLOT(performOpenLocation()));
471 
472   actionClose = makeAction(tr("&Close", "File|"))
473     << QKeySequence(tr("Ctrl+W", "File|Close"))
474     << QIcon(":/images/icon_close.png")
475     << tr("Close this window.")
476     << Trigger(this, SLOT(close()));
477 
478   actionQuit = makeAction(tr("&Quit", "File|"))
479     << QKeySequence(tr("Ctrl+Q", "File|Quit"))
480     << QIcon(":/images/icon_quit.png")
481     << tr("Close all windows and quit the application.")
482     << Trigger(QCoreApplication::instance(), SLOT(closeAllWindows()));
483 
484   actionSave = makeAction(tr("Save &as...", "File|"))
485     << QKeySequence(tr("Ctrl+S", "File|SaveAs"))
486     << QIcon(":/images/icon_save.png")
487     << tr("Save the DjVu document.")
488     << Trigger(this, SLOT(saveAs()));
489 
490   actionExport = makeAction(tr("&Export as...", "File|"))
491     << QKeySequence(tr("Ctrl+E", "File|ExportAs"))
492     << QIcon(":/images/icon_save.png")
493     << tr("Export DjVu page or document to other formats.")
494     << Trigger(this, SLOT(exportAs()));
495 
496   actionPrint = makeAction(tr("&Print...", "File|"))
497     << QKeySequence(tr("Ctrl+P", "File|Print"))
498     << QIcon(":/images/icon_print.png")
499     << tr("Print the DjVu document.")
500     << Trigger(this, SLOT(print()));
501 
502   actionFind = makeAction(tr("&Find...", "Edit|"))
503     << QKeySequence(tr("Ctrl+F", "Edit|Find"))
504     << QIcon(":/images/icon_find.png")
505     << tr("Find text in the document.")
506     << Trigger(this, SLOT(showFind()));
507 
508   actionFindNext = makeAction(tr("Find &Next", "Edit|"))
509     << QKeySequence(tr("F3", "Edit|Find Next"))
510     << tr("Find next occurrence of search text in the document.")
511     << Trigger(findWidget, SLOT(findNext()));
512 
513   actionFindPrev = makeAction(tr("Find &Previous", "Edit|"))
514     << QKeySequence(tr("Shift+F3", "Edit|Find Previous"))
515     << tr("Find previous occurrence of search text in the document.")
516     << Trigger(findWidget, SLOT(findPrev()));
517 
518   actionSelect = makeAction(tr("&Select", "Edit|"), false)
519     << QKeySequence(tr("F2", "Edit|Select"))
520     << QIcon(":/images/icon_select.png")
521     << tr("Select a rectangle in the document.")
522     << Trigger(this, SLOT(performSelect(bool)));
523 
524   actionZoomIn = makeAction(tr("Zoom &In", "Zoom|"), Qt::WidgetWithChildrenShortcut)
525     << QIcon(":/images/icon_zoomin.png")
526     << tr("Increase the magnification.")
527     << QKeySequence("+")
528     << Trigger(widget, SLOT(zoomIn()));
529 
530   actionZoomOut = makeAction(tr("Zoom &Out", "Zoom|"), Qt::WidgetWithChildrenShortcut)
531     << QIcon(":/images/icon_zoomout.png")
532     << tr("Decrease the magnification.")
533     << QKeySequence("-")
534     << Trigger(widget, SLOT(zoomOut()));
535 
536   actionZoomFitWidth = makeAction(tr("Fit &Width", "Zoom|"), false)
537     << tr("Set magnification to fit page width.")
538     << QVariant(QDjVuWidget::ZOOM_FITWIDTH)
539     << Trigger(this, SLOT(performZoom()))
540     << *zoomActionGroup;
541 
542   actionZoomFitPage = makeAction(tr("Fit &Page", "Zoom|"), false)
543     << tr("Set magnification to fit page.")
544     << QVariant(QDjVuWidget::ZOOM_FITPAGE)
545     << Trigger(this, SLOT(performZoom()))
546     << *zoomActionGroup;
547 
548   actionZoomOneToOne = makeAction(tr("One &to one", "Zoom|"), false)
549     << tr("Set full resolution magnification.")
550     << QVariant(QDjVuWidget::ZOOM_ONE2ONE)
551     << Trigger(this, SLOT(performZoom()))
552     << *zoomActionGroup;
553 
554   actionZoom300 = makeAction(tr("&300%", "Zoom|"), false)
555     << tr("Magnify 300%")
556     << QVariant(300)
557     << Trigger(this, SLOT(performZoom()))
558     << *zoomActionGroup;
559 
560   actionZoom200 = makeAction(tr("&200%", "Zoom|"), false)
561     << tr("Magnify 20%")
562     << QVariant(200)
563     << Trigger(this, SLOT(performZoom()))
564     << *zoomActionGroup;
565 
566   actionZoom150 = makeAction(tr("&150%", "Zoom|"), false)
567     << tr("Magnify 150%")
568     << QVariant(200)
569     << Trigger(this, SLOT(performZoom()))
570     << *zoomActionGroup;
571 
572   actionZoom100 = makeAction(tr("&100%", "Zoom|"), false)
573     << tr("Magnify 100%")
574     << QVariant(100)
575     << Trigger(this, SLOT(performZoom()))
576     << *zoomActionGroup;
577 
578   actionZoom75 = makeAction(tr("&75%", "Zoom|"), false)
579     << tr("Magnify 75%")
580     << QVariant(75)
581     << Trigger(this, SLOT(performZoom()))
582     << *zoomActionGroup;
583 
584   actionZoom50 = makeAction(tr("&50%", "Zoom|"), false)
585     << tr("Magnify 50%")
586     << QVariant(50)
587     << Trigger(this, SLOT(performZoom()))
588     << *zoomActionGroup;
589 
590   actionNavFirst = makeAction(tr("&First Page", "Go|"), Qt::WidgetWithChildrenShortcut)
591     << QIcon(":/images/icon_first.png")
592     << tr("Jump to first document page.")
593     << QKeySequence("Ctrl+Home")
594     << Trigger(widget, SLOT(firstPage()))
595     << Trigger(this, SLOT(updateActionsLater()));
596 
597   actionNavNext = makeAction(tr("&Next Page", "Go|"), Qt::WidgetWithChildrenShortcut)
598     << QIcon(":/images/icon_next.png")
599     << tr("Jump to next document page.")
600     << QKeySequence("PgDown")
601     << Trigger(widget, SLOT(nextPage()))
602     << Trigger(this, SLOT(updateActionsLater()));
603 
604   actionNavPrev = makeAction(tr("&Previous Page", "Go|"), Qt::WidgetWithChildrenShortcut)
605     << QIcon(":/images/icon_prev.png")
606     << tr("Jump to previous document page.")
607     << QKeySequence("PgUp")
608     << Trigger(widget, SLOT(prevPage()))
609     << Trigger(this, SLOT(updateActionsLater()));
610 
611   actionNavLast = makeAction(tr("&Last Page", "Go|"), Qt::WidgetWithChildrenShortcut)
612     << QIcon(":/images/icon_last.png")
613     << tr("Jump to last document page.")
614     << QKeySequence("Ctrl+End")
615     << Trigger(widget, SLOT(lastPage()))
616     << Trigger(this, SLOT(updateActionsLater()));
617 
618   actionBack = makeAction(tr("&Backward", "Go|"))
619     << QIcon(":/images/icon_back.png")
620     << tr("Backward in history.")
621     << QKeySequence("Alt+Left")
622     << Trigger(this, SLOT(performUndo()))
623     << Trigger(this, SLOT(updateActionsLater()));
624 
625   actionForw = makeAction(tr("&Forward", "Go|"))
626     << QIcon(":/images/icon_forw.png")
627     << tr("Forward in history.")
628     << QKeySequence("Alt+Right")
629     << Trigger(this, SLOT(performRedo()))
630     << Trigger(this, SLOT(updateActionsLater()));
631 
632   actionRotateLeft = makeAction(tr("Rotate &Left", "Rotate|"), Qt::WidgetWithChildrenShortcut)
633     << QIcon(":/images/icon_rotateleft.png")
634     << tr("Rotate page image counter-clockwise.")
635     << QKeySequence("[")
636     << Trigger(widget, SLOT(rotateLeft()))
637     << Trigger(this, SLOT(updateActionsLater()));
638 
639   actionRotateRight = makeAction(tr("Rotate &Right", "Rotate|"), Qt::WidgetWithChildrenShortcut)
640     << QIcon(":/images/icon_rotateright.png")
641     << tr("Rotate page image clockwise.")
642     << QKeySequence("]")
643     << Trigger(widget, SLOT(rotateRight()))
644     << Trigger(this, SLOT(updateActionsLater()));
645 
646   actionRotate0 = makeAction(tr8("Rotate &0\302\260", "Rotate|"), false)
647     << tr("Set natural page orientation.")
648     << QVariant(0)
649     << Trigger(this, SLOT(performRotation()))
650     << *rotationActionGroup;
651 
652   actionRotate90 = makeAction(tr8("Rotate &90\302\260", "Rotate|"), false)
653     << tr("Turn page on its left side.")
654     << QVariant(1)
655     << Trigger(this, SLOT(performRotation()))
656     << *rotationActionGroup;
657 
658   actionRotate180 = makeAction(tr8("Rotate &180\302\260", "Rotate|"), false)
659     << tr("Turn page upside-down.")
660     << QVariant(2)
661     << Trigger(this, SLOT(performRotation()))
662     << *rotationActionGroup;
663 
664   actionRotate270 = makeAction(tr8("Rotate &270\302\260", "Rotate|"), false)
665     << tr("Turn page on its right side.")
666     << QVariant(3)
667     << Trigger(this, SLOT(performRotation()))
668     << *rotationActionGroup;
669 
670   actionInformation = makeAction(tr("&Information...", "Edit|"))
671     << QKeySequence(tr("Ctrl+I", "Edit|Information"))
672     << tr("Show information about the document encoding and structure.")
673     << Trigger(this, SLOT(performInformation()));
674 
675   actionMetadata = makeAction(tr("&Metadata...", "Edit|"))
676 #ifndef Q_OS_DARWIN
677     << QKeySequence(tr("Ctrl+M", "Edit|Metadata"))
678 #endif
679     << tr("Show the document and page meta data.")
680     << Trigger(this, SLOT(performMetadata()));
681 
682   actionWhatsThis = QWhatsThis::createAction(this);
683 
684   actionAbout = makeAction(tr("&About DjView..."))
685 #ifndef Q_OS_DARWIN
686     << QIcon(":/images/djview.png")
687 #endif
688     << tr("Show information about this program.")
689     << Trigger(this, SLOT(performAbout()));
690 
691   actionDisplayColor = makeAction(tr("&Color", "Display|"), false)
692     << tr("Display everything.")
693     << Trigger(widget, SLOT(displayModeColor()))
694     << Trigger(this, SLOT(updateActionsLater()))
695     << *modeActionGroup;
696 
697   actionDisplayBW = makeAction(tr("&Stencil", "Display|"), false)
698     << tr("Only display the document bitonal stencil.")
699     << Trigger(widget, SLOT(displayModeStencil()))
700     << Trigger(this, SLOT(updateActionsLater()))
701     << *modeActionGroup;
702 
703   actionDisplayForeground
704     = makeAction(tr("&Foreground", "Display|"), false)
705     << tr("Only display the foreground layer.")
706     << Trigger(widget, SLOT(displayModeForeground()))
707     << Trigger(this, SLOT(updateActionsLater()))
708     << *modeActionGroup;
709 
710   actionDisplayBackground
711     = makeAction(tr("&Background", "Display|"), false)
712     << tr("Only display the background layer.")
713     << Trigger(widget, SLOT(displayModeBackground()))
714     << Trigger(this, SLOT(updateActionsLater()))
715     << *modeActionGroup;
716 
717   actionDisplayHiddenText
718     = makeAction(tr("&Hidden Text", "Display|"), false)
719     << tr("Overlay a representation of the hidden text layer.")
720     << Trigger(widget, SLOT(displayModeText()))
721     << Trigger(this, SLOT(updateActionsLater()))
722     << *modeActionGroup;
723 
724   actionInvertLuminance
725     = makeAction(tr("I&nvert Luminance", "View|"), true)
726     << tr("Invert image luminance while preserving hue.")
727     << Trigger(widget, SLOT(setInvertLuminance(bool)))
728     << Trigger(this, SLOT(updateActionsLater()));
729 
730   actionPreferences = makeAction(tr("Prefere&nces...", "Settings|"))
731     << QIcon(":/images/icon_prefs.png")
732     << tr("Show the preferences dialog.")
733     << Trigger(this, SLOT(performPreferences()));
734 
735   actionViewSideBar = makeAction(tr("Show &Sidebar", "Settings|"), true)
736     << QKeySequence(tr("F9", "Settings|Show sidebar"))
737 #ifdef Q_OS_DARWIN
738     << QKeySequence(tr("Alt+Ctrl+S", "Settings|Show sidebar"))
739 #endif
740     << tr("Show/hide the side bar.")
741     << Trigger(this, SLOT(showSideBar(bool)));
742 
743   actionViewToolBar = toolBar->toggleViewAction()
744     << tr("Show &Toolbar", "Settings|")
745     << QKeySequence(tr("F10", "Settings|Show toolbar"))
746 #ifdef Q_OS_DARWIN
747     << QKeySequence(tr("Alt+Ctrl+T", "Settings|Show toolbar"))
748 #endif
749     << tr("Show/hide the standard tool bar.")
750     << Trigger(this, SLOT(updateActionsLater()));
751 
752   actionViewStatusBar = makeAction(tr("Show Stat&usbar", "Settings|"), true)
753     << tr("Show/hide the status bar.")
754 #ifdef Q_OS_DARWIN
755     << QKeySequence(tr("Alt+Ctrl+/", "Settings|Show toolbar"))
756 #endif
757     << Trigger(statusBar,SLOT(setVisible(bool)))
758     << Trigger(this, SLOT(updateActionsLater()));
759 
760   actionViewFullScreen
761     = makeAction(tr("&Full Screen","View|"), false)
762     << QKeySequence(tr("F11","View|FullScreen"))
763 #ifdef Q_OS_DARWIN
764     << QKeySequence(tr("Meta+Ctrl+F","View|FullScreen"))
765 #endif
766     << QIcon(":/images/icon_fullscreen.png")
767     << tr("Toggle full screen mode.")
768     << Trigger(this, SLOT(performViewFullScreen(bool)));
769 
770   actionViewSlideShow
771     = makeAction(tr("&Slide Show","View|"), false)
772     << QKeySequence(tr("Shift+F11","View|Slideshow"))
773 #ifdef Q_OS_DARWIN
774     << QKeySequence(tr("Shift+Ctrl+F", "Settings|Show toolbar"))
775 #endif
776     << QIcon(":/images/icon_slideshow.png")
777     << tr("Toggle slide show mode.")
778     << Trigger(this, SLOT(performViewSlideShow(bool)));
779 
780   actionLayoutContinuous = makeAction(tr("&Continuous", "Layout|"), false)
781     << QIcon(":/images/icon_continuous.png")
782     << QKeySequence(tr("F4", "Layout|Continuous"))
783     << tr("Toggle continuous layout mode.")
784     << Trigger(widget, SLOT(setContinuous(bool)))
785     << Trigger(this, SLOT(updateActionsLater()));
786 
787   actionLayoutSideBySide = makeAction(tr("Side &by Side", "Layout|"), false)
788     << QIcon(":/images/icon_sidebyside.png")
789     << QKeySequence(tr("F5", "Layout|SideBySide"))
790     << tr("Toggle side-by-side layout mode.")
791     << Trigger(widget, SLOT(setSideBySide(bool)))
792     << Trigger(this, SLOT(updateActionsLater()));
793 
794   actionLayoutCoverPage = makeAction(tr("Co&ver Page", "Layout|"), false)
795 #ifdef Q_OS_DARWIN
796     << QIcon(":/images/icon_coverpage.png")
797 #endif
798     << QKeySequence(tr("F6", "Layout|CoverPage"))
799     << tr("Show the cover page alone in side-by-side mode.")
800     << Trigger(widget, SLOT(setCoverPage(bool)))
801     << Trigger(this, SLOT(updateActionsLater()));
802 
803   actionLayoutRightToLeft = makeAction(tr("&Right to Left", "Layout|"), false)
804 #ifdef Q_OS_DARWIN
805     << QIcon(":/images/icon_righttoleft.png")
806 #endif
807     << QKeySequence(tr("Shift+F6", "Layout|RightToLeft"))
808     << tr("Show pages right-to-left in side-by-side mode.")
809     << Trigger(widget, SLOT(setRightToLeft(bool)))
810     << Trigger(this, SLOT(updateActionsLater()));
811 
812   actionCopyUrl = makeAction(tr("Copy &URL", "Edit|"))
813     << tr("Save an URL for the current page into the clipboard.")
814     << QKeySequence(tr("Ctrl+C", "Edit|CopyURL"))
815     << Trigger(this, SLOT(performCopyUrl()));
816 
817   actionCopyOutline = makeAction(tr("Copy &Outline", "Edit|"))
818     << tr("Save the djvused code for the outline into the clipboard.")
819     << Trigger(this, SLOT(performCopyOutline()));
820 
821   actionCopyAnnotation = makeAction(tr("Copy &Annotations", "Edit|"))
822     << tr("Save the djvused code for the page annotations into the clipboard.")
823     << Trigger(this, SLOT(performCopyAnnotation()));
824 
825   actionAbout->setMenuRole(QAction::AboutRole);
826   actionQuit->setMenuRole(QAction::QuitRole);
827   actionPreferences->setMenuRole(QAction::PreferencesRole);
828 }
829 
830 
831 void
createMenus()832 QDjView::createMenus()
833 {
834   // Layout main menu
835   QMenu *fileMenu = menuBar->addMenu(tr("&File", "File|"));
836   if (viewerMode >= STANDALONE)
837     {
838       fileMenu->addAction(actionNew);
839       fileMenu->addAction(actionOpen);
840       fileMenu->addAction(actionOpenLocation);
841       recentMenu = fileMenu->addMenu(tr("Open &Recent"));
842       recentMenu->menuAction()->setIcon(QIcon(":/images/icon_open.png"));
843       connect(recentMenu, SIGNAL(aboutToShow()), this, SLOT(fillRecent()));
844       fileMenu->addSeparator();
845     }
846   fileMenu->addAction(actionSave);
847   fileMenu->addAction(actionExport);
848   fileMenu->addAction(actionPrint);
849   if (viewerMode >= STANDALONE)
850     {
851       fileMenu->addSeparator();
852       fileMenu->addAction(actionClose);
853       fileMenu->addAction(actionQuit);
854     }
855   QMenu *editMenu = menuBar->addMenu(tr("&Edit", "Edit|"));
856   editMenu->addAction(actionSelect);
857   editMenu->addAction(actionCopyUrl);
858   editMenu->addAction(actionCopyOutline);
859   editMenu->addAction(actionCopyAnnotation);
860   editMenu->addSeparator();
861   editMenu->addAction(actionFind);
862   editMenu->addAction(actionFindNext);
863   editMenu->addAction(actionFindPrev);
864   QMenu *viewMenu = menuBar->addMenu(tr("&View", "View|"));
865   QMenu *zoomMenu = viewMenu->addMenu(tr("&Zoom","View|Zoom"));
866   zoomMenu->addAction(actionZoomIn);
867   zoomMenu->addAction(actionZoomOut);
868   zoomMenu->addSeparator();
869   zoomMenu->addAction(actionZoomOneToOne);
870   zoomMenu->addAction(actionZoomFitWidth);
871   zoomMenu->addAction(actionZoomFitPage);
872   zoomMenu->addSeparator();
873   zoomMenu->addAction(actionZoom300);
874   zoomMenu->addAction(actionZoom200);
875   zoomMenu->addAction(actionZoom150);
876   zoomMenu->addAction(actionZoom100);
877   zoomMenu->addAction(actionZoom75);
878   zoomMenu->addAction(actionZoom50);
879   QMenu *rotationMenu = viewMenu->addMenu(tr("&Rotate","View|Rotate"));
880   rotationMenu->addAction(actionRotateLeft);
881   rotationMenu->addAction(actionRotateRight);
882   rotationMenu->addSeparator();
883   rotationMenu->addAction(actionRotate0);
884   rotationMenu->addAction(actionRotate90);
885   rotationMenu->addAction(actionRotate180);
886   rotationMenu->addAction(actionRotate270);
887   QMenu *modeMenu = viewMenu->addMenu(tr("&Display","View|Display"));
888   modeMenu->addAction(actionDisplayColor);
889   modeMenu->addAction(actionDisplayBW);
890   modeMenu->addAction(actionDisplayForeground);
891   modeMenu->addAction(actionDisplayBackground);
892   modeMenu->addAction(actionDisplayHiddenText);
893   viewMenu->addSeparator();
894   viewMenu->addAction(actionLayoutContinuous);
895   viewMenu->addAction(actionLayoutSideBySide);
896   viewMenu->addAction(actionLayoutCoverPage);
897   viewMenu->addAction(actionLayoutRightToLeft);
898   viewMenu->addSeparator();
899   viewMenu->addAction(actionInvertLuminance);
900   viewMenu->addSeparator();
901   viewMenu->addAction(actionInformation);
902   viewMenu->addAction(actionMetadata);
903   if (viewerMode >= STANDALONE)
904     viewMenu->addSeparator();
905   if (viewerMode >= STANDALONE)
906     viewMenu->addAction(actionViewFullScreen);
907   if (viewerMode >= STANDALONE)
908     viewMenu->addAction(actionViewSlideShow);
909   QMenu *gotoMenu = menuBar->addMenu(tr("&Go", "Go|"));
910   gotoMenu->addAction(actionNavFirst);
911   gotoMenu->addAction(actionNavPrev);
912   gotoMenu->addAction(actionNavNext);
913   gotoMenu->addAction(actionNavLast);
914   gotoMenu->addSeparator();
915   gotoMenu->addAction(actionBack);
916   gotoMenu->addAction(actionForw);
917   QMenu *settingsMenu = menuBar->addMenu(tr("&Settings", "Settings|"));
918   settingsMenu->addAction(actionViewSideBar);
919   settingsMenu->addAction(actionViewToolBar);
920   settingsMenu->addAction(actionViewStatusBar);
921   settingsMenu->addSeparator();
922   settingsMenu->addAction(actionPreferences);
923   QMenu *helpMenu = menuBar->addMenu(tr("&Help", "Help|"));
924   helpMenu->addAction(actionWhatsThis);
925   helpMenu->addSeparator();
926   helpMenu->addAction(actionAbout);
927 
928   // Layout context menu
929   gotoMenu = contextMenu->addMenu(tr("&Go", "Go|"));
930   gotoMenu->addAction(actionNavFirst);
931   gotoMenu->addAction(actionNavPrev);
932   gotoMenu->addAction(actionNavNext);
933   gotoMenu->addAction(actionNavLast);
934   zoomMenu = contextMenu->addMenu(tr("&Zoom","View|Zoom"));
935   zoomMenu->addAction(actionZoomIn);
936   zoomMenu->addAction(actionZoomOut);
937   zoomMenu->addSeparator();
938   zoomMenu->addAction(actionZoomOneToOne);
939   zoomMenu->addAction(actionZoomFitWidth);
940   zoomMenu->addAction(actionZoomFitPage);
941   zoomMenu->addSeparator();
942   zoomMenu->addAction(actionZoom300);
943   zoomMenu->addAction(actionZoom200);
944   zoomMenu->addAction(actionZoom150);
945   zoomMenu->addAction(actionZoom100);
946   zoomMenu->addAction(actionZoom75);
947   zoomMenu->addAction(actionZoom50);
948   rotationMenu = contextMenu->addMenu(tr("&Rotate","View|Rotate"));
949   rotationMenu->addAction(actionRotateLeft);
950   rotationMenu->addAction(actionRotateRight);
951   rotationMenu->addSeparator();
952   rotationMenu->addAction(actionRotate0);
953   rotationMenu->addAction(actionRotate90);
954   rotationMenu->addAction(actionRotate180);
955   rotationMenu->addAction(actionRotate270);
956   modeMenu = contextMenu->addMenu(tr("&Display","View|Display"));
957   modeMenu->addAction(actionDisplayColor);
958   modeMenu->addAction(actionDisplayBW);
959   modeMenu->addAction(actionDisplayForeground);
960   modeMenu->addAction(actionDisplayBackground);
961   modeMenu->addAction(actionDisplayHiddenText);
962   contextMenu->addSeparator();
963   contextMenu->addAction(actionLayoutContinuous);
964   contextMenu->addAction(actionLayoutSideBySide);
965   contextMenu->addAction(actionLayoutCoverPage);
966   contextMenu->addSeparator();
967   contextMenu->addAction(actionFind);
968   contextMenu->addAction(actionInformation);
969   contextMenu->addAction(actionMetadata);
970   contextMenu->addSeparator();
971   contextMenu->addAction(actionSave);
972   contextMenu->addAction(actionExport);
973   contextMenu->addAction(actionPrint);
974   contextMenu->addSeparator();
975   contextMenu->addAction(actionViewSideBar);
976   contextMenu->addAction(actionViewToolBar);
977   contextMenu->addAction(actionViewFullScreen);
978   contextMenu->addAction(actionViewSlideShow);
979   contextMenu->addSeparator();
980   contextMenu->addAction(actionPreferences);
981   contextMenu->addSeparator();
982   contextMenu->addAction(actionWhatsThis);
983   contextMenu->addAction(actionAbout);
984 }
985 
986 
987 /*! Update graphical interface components.
988   This is called whenever something changes
989   using function \a QDjView::updateActionsLater().
990   It updates the all gui compnents. */
991 
992 void
updateActions()993 QDjView::updateActions()
994 {
995   // Rebuild toolbar if necessary
996   if (tools != toolsCached)
997     fillToolBar(toolBar);
998 
999   // Enable all actions
1000   foreach(QObject *object, allActions)
1001     if (QAction *action = qobject_cast<QAction*>(object))
1002       action->setEnabled(true);
1003 
1004   // Some actions are explicitly disabled
1005   actionSave->setEnabled(savingAllowed || prefs->restrictOverride);
1006   actionExport->setEnabled(savingAllowed || prefs->restrictOverride);
1007   actionPrint->setEnabled(printingAllowed || prefs->restrictOverride);
1008 
1009   // Some actions are only available in standalone mode
1010   actionNew->setVisible(viewerMode >= STANDALONE);
1011   actionOpen->setVisible(viewerMode >= STANDALONE);
1012   actionOpenLocation->setVisible(viewerMode >= STANDALONE);
1013   actionClose->setVisible(viewerMode >= STANDALONE);
1014   actionQuit->setVisible(viewerMode >= STANDALONE);
1015   actionViewFullScreen->setVisible(viewerMode >= STANDALONE);
1016   actionViewSlideShow->setVisible(viewerMode >= STANDALONE);
1017   setAcceptDrops(viewerMode >= STANDALONE);
1018 
1019   // Zoom combo and actions
1020   int zoom = widget->zoom();
1021   actionZoomIn->setEnabled(zoom < QDjVuWidget::ZOOM_MAX);
1022   actionZoomOut->setEnabled(zoom < 0 || zoom > QDjVuWidget::ZOOM_MIN);
1023   actionZoomFitPage->setChecked(zoom == QDjVuWidget::ZOOM_FITPAGE);
1024   actionZoomFitWidth->setChecked(zoom == QDjVuWidget::ZOOM_FITWIDTH);
1025   actionZoomOneToOne->setChecked(zoom == QDjVuWidget::ZOOM_ONE2ONE);
1026   actionZoom300->setChecked(zoom == 300);
1027   actionZoom200->setChecked(zoom == 200);
1028   actionZoom150->setChecked(zoom == 150);
1029   actionZoom100->setChecked(zoom == 100);
1030   actionZoom75->setChecked(zoom == 75);
1031   actionZoom50->setChecked(zoom == 50);
1032   zoomCombo->setEnabled(!!document);
1033   int zoomIndex = zoomCombo->findData(QVariant(zoom));
1034   zoomCombo->clearEditText();
1035   zoomCombo->setCurrentIndex(zoomIndex);
1036   if (zoomIndex < 0 &&
1037       zoom >= QDjVuWidget::ZOOM_MIN &&
1038       zoom <= QDjVuWidget::ZOOM_MAX)
1039     zoomCombo->setEditText(QString("%1%").arg(zoom));
1040   else if (zoomIndex >= 0)
1041     zoomCombo->setEditText(zoomCombo->itemText(zoomIndex));
1042 
1043   // Mode combo and actions
1044   QDjVuWidget::DisplayMode mode = widget->displayMode();
1045   actionDisplayColor->setChecked(mode == QDjVuWidget::DISPLAY_COLOR);
1046   actionDisplayBW->setChecked(mode == QDjVuWidget::DISPLAY_STENCIL);
1047   actionDisplayBackground->setChecked(mode == QDjVuWidget::DISPLAY_BG);
1048   actionDisplayForeground->setChecked(mode == QDjVuWidget::DISPLAY_FG);
1049   actionDisplayHiddenText->setChecked(mode == QDjVuWidget::DISPLAY_TEXT);
1050   modeCombo->setCurrentIndex(modeCombo->findData(QVariant(mode)));
1051   modeCombo->setEnabled(!!document);
1052 
1053   // Image inversion
1054   actionInvertLuminance->setChecked(widget->invertLuminance());
1055 
1056   // Rotations
1057   int rotation = widget->rotation();
1058   actionRotate0->setChecked(rotation == 0);
1059   actionRotate90->setChecked(rotation == 1);
1060   actionRotate180->setChecked(rotation == 2);
1061   actionRotate270->setChecked(rotation == 3);
1062 
1063   // Page combo and actions
1064   int pagenum = documentPages.size();
1065   int pageno = widget->page();
1066   pageCombo->clearEditText();
1067   pageCombo->setCurrentIndex(pageCombo->findData(QVariant(pageno)));
1068   if (pageno >= 0 && pagenum > 0  && !pageCombo->hasFocus())
1069     {
1070       QString sNumber = QString("%1 / %2").arg(pageno+1).arg(pagenum);
1071       QString sName = pageName(pageno,true);
1072       growPageCombo(pageCombo, sNumber);
1073       pageCombo->setEditText((sName.isEmpty()) ? sNumber : sName);
1074       pageCombo->lineEdit()->setCursorPosition(0);
1075     }
1076   pageCombo->setEnabled(pagenum > 0);
1077   actionNavFirst->setEnabled(pagenum>0 && pageno>0);
1078   actionNavPrev->setEnabled(pagenum>0 && pageno>0);
1079   actionNavNext->setEnabled(pagenum>0 && pageno<pagenum-1);
1080   actionNavLast->setEnabled(pagenum>0 && pageno<pagenum-1);
1081 
1082   // Layout actions
1083   actionLayoutContinuous->setChecked(widget->continuous());
1084   actionLayoutSideBySide->setChecked(widget->sideBySide());
1085   actionLayoutCoverPage->setEnabled(widget->sideBySide());
1086   actionLayoutCoverPage->setChecked(widget->coverPage());
1087   actionLayoutRightToLeft->setEnabled(widget->sideBySide());
1088   actionLayoutRightToLeft->setChecked(widget->rightToLeft());
1089 
1090   // UndoRedo
1091   undoTimer->stop();
1092   undoTimer->start(250);
1093   actionBack->setEnabled(undoList.size() > 0);
1094   actionForw->setEnabled(redoList.size() > 0);
1095 
1096   // Misc
1097   textLabel->setVisible(prefs->showTextLabel);
1098   textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored);
1099   actionCopyOutline->setVisible(prefs->advancedFeatures);
1100   actionCopyAnnotation->setVisible(prefs->advancedFeatures);
1101   actionCopyUrl->setEnabled(pagenum > 0);
1102   actionCopyOutline->setEnabled(pagenum > 0);
1103   actionCopyAnnotation->setEnabled(pagenum > 0);
1104   actionViewSideBar->setChecked(hiddenSideBarTabs() == 0);
1105   actionViewSlideShow->setChecked(viewerMode == STANDALONE_SLIDESHOW);
1106   actionViewFullScreen->setChecked(viewerMode == STANDALONE_FULLSCREEN);
1107 
1108   // Disable almost everything when document==0
1109   if (! document)
1110     {
1111       foreach(QObject *object, allActions)
1112         if (QAction *action = qobject_cast<QAction*>(object))
1113           if (action != actionNew &&
1114               action != actionOpen &&
1115               action != actionOpenLocation &&
1116               action != actionClose &&
1117               action != actionQuit &&
1118               action != actionPreferences &&
1119               action != actionViewToolBar &&
1120               action != actionViewStatusBar &&
1121               action != actionViewSideBar &&
1122               action != actionViewFullScreen &&
1123               action != actionWhatsThis &&
1124               action != actionAbout )
1125             action->setEnabled(false);
1126     }
1127 
1128   // Finished
1129   updateActionsScheduled = false;
1130 
1131   // Reset slide show timeout
1132   slideShowTimeout(true);
1133 
1134   // Notify plugin
1135   if (viewerMode < STANDALONE)
1136     emit pluginOnChange();
1137 }
1138 
1139 
1140 
1141 // ----------------------------------------
1142 // WHATSTHIS HELP
1143 
1144 
1145 void
createWhatsThis()1146 QDjView::createWhatsThis()
1147 {
1148 #define HELP(x,y) { QString s(x); Help h(s); h y; }
1149 
1150   struct Help {
1151     QString s;
1152     Help(QString s) : s(s) { }
1153     Help& operator>>(QWidget *w) { w->setWhatsThis(s); return *this; }
1154     Help& operator>>(QAction *a) { a->setWhatsThis(s); return *this; }
1155   };
1156 
1157   QString mc, ms, ml;
1158 #ifdef Q_OS_DARWIN
1159   mc = tr("Control Left Mouse Button");
1160 #else
1161   mc = tr("Right Mouse Button");
1162 #endif
1163   ms = prefs->modifiersToString(prefs->modifiersForSelect);
1164   ms = ms.replace("+"," ");
1165   ml = prefs->modifiersToString(prefs->modifiersForLens);
1166   ml = ml.replace("+"," ");
1167 
1168   HELP(tr("<html><b>Selecting a rectangle.</b><br/> "
1169           "Once a rectangular area is selected, a popup menu "
1170           "lets you copy the corresponding text or image. "
1171           "Instead of using this tool, you can also hold %1 "
1172           "and use the Left Mouse Button."
1173           "</html>").arg(ms),
1174        >> actionSelect );
1175 
1176   HELP(tr("<html><b>Zooming.</b><br/> "
1177           "Choose a zoom level for viewing the document. "
1178           "Zoom level 100% displays the document for a 100 dpi screen. "
1179           "Zoom levels <tt>Fit Page</tt> and <tt>Fit Width</tt> ensure "
1180           "that the full page or the page width fit in the window. "
1181           "</html>"),
1182        >> actionZoomIn >> actionZoomOut
1183        >> actionZoomFitPage >> actionZoomFitWidth
1184        >> actionZoom300 >> actionZoom200 >> actionZoom150
1185        >> actionZoom75 >> actionZoom50
1186        >> zoomCombo );
1187 
1188   HELP(tr("<html><b>Rotating the pages.</b><br/> "
1189           "Choose to display pages in portrait or landscape mode. "
1190           "You can also turn them upside down.</html>"),
1191        >> actionRotateLeft >> actionRotateRight
1192        >> actionRotate0 >> actionRotate90
1193        >> actionRotate180 >> actionRotate270 );
1194 
1195   HELP(tr("<html><b>Display mode.</b><br/> "
1196           "DjVu images compose a background layer and a foreground layer "
1197           "using a stencil. The display mode specifies with layers "
1198           "should be displayed.</html>"),
1199             >> actionDisplayColor >> actionDisplayBW
1200             >> actionDisplayForeground >> actionDisplayBackground
1201             >> modeCombo );
1202 
1203   HELP(tr("<html><b>Navigating the document.</b><br/> "
1204           "The page selector lets you jump to any page by name "
1205           "and can be activated at any time by pressing Ctrl+G. "
1206           "The navigation buttons jump to the first page, the previous "
1207           "page, the next page, or the last page. </html>"),
1208        >> actionNavFirst >> actionNavPrev
1209        >> actionNavNext >> actionNavLast
1210        >> pageCombo );
1211 
1212   HELP(tr("<html><b>Document and page information.</b><br> "
1213           "Display a dialog window for viewing "
1214           "encoding information pertaining to the document "
1215           "or to a specific page.</html>"),
1216        >> actionInformation );
1217 
1218   HELP(tr("<html><b>Document and page metadata.</b><br> "
1219           "Display a dialog window for viewing metadata "
1220           "pertaining to the document "
1221           "or to a specific page.</html>"),
1222        >> actionMetadata );
1223 
1224   HELP(tr("<html><b>Continuous layout.</b><br/> "
1225           "Display all the document pages arranged vertically "
1226           "inside the scrollable document viewing area.</html>"),
1227        >> actionLayoutContinuous );
1228 
1229   HELP(tr("<html><b>Side by side layout.</b><br/> "
1230           "Display pairs of pages side by side "
1231           "inside the scrollable document viewing area.</html>"),
1232        >> actionLayoutSideBySide );
1233 
1234   HELP(tr("<html><b>Page information.</b><br/> "
1235           "Display information about the page located under the cursor: "
1236           "the sequential page number, the page size in pixels, "
1237           "and the page resolution in dots per inch. </html>"),
1238        >> pageLabel );
1239 
1240   HELP(tr("<html><b>Cursor information.</b><br/> "
1241           "Display the position of the mouse cursor "
1242           "expressed in page coordinates. </html>"),
1243        >> mouseLabel );
1244 
1245   HELP(tr("<html><b>Document viewing area.</b><br/> "
1246           "This is the main display area for the DjVu document. <ul>"
1247           "<li>Arrows and page keys to navigate the document.</li>"
1248           "<li>Space and BackSpace to read the document.</li>"
1249           "<li>Keys <tt>+</tt> <tt>-</tt> <tt>[</tt> <tt>]</tt> "
1250           "to zoom or rotate the document.</li>"
1251           "<li>Left Mouse Button for panning and selecting links.</li>"
1252           "<li>%3 for displaying the contextual menu.</li>"
1253           "<li>%1 Left Mouse Button for selecting text or images.</li>"
1254           "<li>%2 for popping the magnification lens.</li>"
1255           "</ul></html>").arg(ms).arg(ml).arg(mc),
1256        >> widget );
1257 
1258   HELP(tr("<html><b>Document viewing area.</b><br/> "
1259           "This is the main display area for the DjVu document. "
1260           "But you must first open a DjVu document to see anything."
1261           "</html>"),
1262        >> splash );
1263 
1264 #undef HELP
1265 }
1266 
1267 
1268 
1269 // ----------------------------------------
1270 // APPLY PREFERENCES
1271 
1272 
1273 QDjViewPrefs::Saved *
getSavedConfig(ViewerMode mode)1274 QDjView::getSavedConfig(ViewerMode mode)
1275 {
1276   if (mode == STANDALONE_FULLSCREEN)
1277     return &fsSavedFullScreen;
1278   else if (mode == STANDALONE_SLIDESHOW)
1279     return &fsSavedSlideShow;
1280   return &fsSavedNormal;
1281 }
1282 
1283 
1284 QDjViewPrefs::Saved *
getSavedPrefs(void)1285 QDjView::getSavedPrefs(void)
1286 {
1287   if (viewerMode==EMBEDDED_PLUGIN)
1288     return &prefs->forEmbeddedPlugin;
1289   else if (viewerMode==FULLPAGE_PLUGIN)
1290     return &prefs->forFullPagePlugin;
1291   else if (viewerMode==STANDALONE_FULLSCREEN)
1292     return &prefs->forFullScreen;
1293   else if (viewerMode==STANDALONE_SLIDESHOW)
1294     return &prefs->forSlideShow;
1295   return &prefs->forStandalone;
1296 }
1297 
1298 
1299 void
enableContextMenu(bool enable)1300 QDjView::enableContextMenu(bool enable)
1301 {
1302   QMenu *oldContextMenu = widget->contextMenu();
1303   if (!enable || oldContextMenu != contextMenu)
1304     {
1305       // remove context menu
1306       widget->setContextMenu(0);
1307       // disable shortcuts
1308       if (oldContextMenu)
1309         foreach(QAction *action, widget->actions())
1310           widget->removeAction(action);
1311     }
1312   if (enable && oldContextMenu != contextMenu)
1313     {
1314       // setup context menu
1315       widget->setContextMenu(contextMenu);
1316       // enable shortcuts
1317       if (contextMenu)
1318         {
1319           // all explicit shortcuts in context menu
1320           widget->addAction(contextMenu->menuAction());
1321           // things that do not have a context menu entry
1322           widget->addAction(actionFindNext);
1323           widget->addAction(actionFindPrev);
1324         }
1325     }
1326 }
1327 
1328 
1329 void
enableScrollBars(bool enable)1330 QDjView::enableScrollBars(bool enable)
1331 {
1332   Qt::ScrollBarPolicy policy = Qt::ScrollBarAlwaysOff;
1333   if (enable)
1334     policy = Qt::ScrollBarAsNeeded;
1335   widget->setHorizontalScrollBarPolicy(policy);
1336   widget->setVerticalScrollBarPolicy(policy);
1337 }
1338 
1339 
1340 void
applyOptions(bool remember)1341 QDjView::applyOptions(bool remember)
1342 {
1343   menuBar->setVisible(options & QDjViewPrefs::SHOW_MENUBAR);
1344   toolBar->setVisible(options & QDjViewPrefs::SHOW_TOOLBAR);
1345   statusBar->setVisible(options & QDjViewPrefs::SHOW_STATUSBAR);
1346   enableScrollBars(options & QDjViewPrefs::SHOW_SCROLLBARS);
1347   widget->setDisplayFrame(options & QDjViewPrefs::SHOW_FRAME);
1348   widget->setDisplayMapAreas(options & QDjViewPrefs::SHOW_MAPAREAS);
1349   widget->setContinuous(options & QDjViewPrefs::LAYOUT_CONTINUOUS);
1350   widget->setSideBySide(options & QDjViewPrefs::LAYOUT_SIDEBYSIDE);
1351   widget->setCoverPage(options & QDjViewPrefs::LAYOUT_COVERPAGE);
1352   widget->setRightToLeft(options & QDjViewPrefs::LAYOUT_RIGHTTOLEFT);
1353   widget->enableMouse(options & QDjViewPrefs::HANDLE_MOUSE);
1354   widget->enableKeyboard(options & QDjViewPrefs::HANDLE_KEYBOARD);
1355   widget->enableHyperlink(options & QDjViewPrefs::HANDLE_LINKS);
1356   enableContextMenu(options & QDjViewPrefs::HANDLE_CONTEXTMENU);
1357   if (!remember)
1358     showSideBar(options & QDjViewPrefs::SHOW_SIDEBAR);
1359 }
1360 
1361 
1362 void
updateOptions(void)1363 QDjView::updateOptions(void)
1364 {
1365   options = zero(QDjViewPrefs::Options);
1366   if (! menuBar->isHidden())
1367     options |= QDjViewPrefs::SHOW_MENUBAR;
1368   if (! toolBar->isHidden())
1369     options |= QDjViewPrefs::SHOW_TOOLBAR;
1370   if (! statusBar->isHidden())
1371     options |= QDjViewPrefs::SHOW_STATUSBAR;
1372   if (! hiddenSideBarTabs())
1373     options |= QDjViewPrefs::SHOW_SIDEBAR;
1374   if (widget->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)
1375     options |= QDjViewPrefs::SHOW_SCROLLBARS;
1376   if (widget->displayFrame())
1377     options |= QDjViewPrefs::SHOW_FRAME;
1378   if (widget->displayMapAreas())
1379     options |= QDjViewPrefs::SHOW_MAPAREAS;
1380   if (widget->continuous())
1381     options |= QDjViewPrefs::LAYOUT_CONTINUOUS;
1382   if (widget->sideBySide())
1383     options |= QDjViewPrefs::LAYOUT_SIDEBYSIDE;
1384   if (widget->coverPage())
1385     options |= QDjViewPrefs::LAYOUT_COVERPAGE;
1386   if (widget->rightToLeft())
1387     options |= QDjViewPrefs::LAYOUT_RIGHTTOLEFT;
1388   if (widget->mouseEnabled())
1389     options |= QDjViewPrefs::HANDLE_MOUSE;
1390   if (widget->keyboardEnabled())
1391     options |= QDjViewPrefs::HANDLE_KEYBOARD;
1392   if (widget->hyperlinkEnabled())
1393     options |= QDjViewPrefs::HANDLE_LINKS;
1394   if (widget->contextMenu())
1395     options |= QDjViewPrefs::HANDLE_CONTEXTMENU;
1396 }
1397 
1398 
1399 
1400 void
applySaved(Saved * saved)1401 QDjView::applySaved(Saved *saved)
1402 {
1403   // default dock geometry
1404   tabifyDockWidget(thumbnailDock, outlineDock);
1405   tabifyDockWidget(thumbnailDock, findDock);
1406   thumbnailDock->raise();
1407   // main saved states
1408   options = saved->options;
1409   if (saved->state.size() > 0)
1410     restoreState(saved->state);
1411   applyOptions(saved->remember);
1412   widget->setZoom(saved->zoom);
1413 }
1414 
1415 
1416 static const Qt::WindowStates unusualWindowStates =
1417        (Qt::WindowMinimized|Qt::WindowMaximized|Qt::WindowFullScreen);
1418 
1419 void
updateSaved(Saved * saved)1420 QDjView::updateSaved(Saved *saved)
1421 {
1422   updateOptions();
1423   if (saved->remember)
1424     {
1425       // main saved states
1426       saved->zoom = widget->zoom();
1427       saved->state = saveState();
1428       saved->options = options;
1429       // avoid confusing options in standalone mode
1430       if (saved == &prefs->forStandalone &&
1431           !(saved->options & QDjViewPrefs::HANDLE_CONTEXTMENU) &&
1432           !(saved->options & QDjViewPrefs::SHOW_MENUBAR) )
1433         saved->options |= (QDjViewPrefs::SHOW_MENUBAR |
1434                            QDjViewPrefs::SHOW_SCROLLBARS |
1435                            QDjViewPrefs::SHOW_STATUSBAR |
1436                            QDjViewPrefs::SHOW_FRAME |
1437                            QDjViewPrefs::HANDLE_CONTEXTMENU );
1438       // main window size in standalone mode
1439       if (saved == &prefs->forStandalone)
1440         {
1441           Qt::WindowStates wstate = windowState();
1442           prefs->windowMaximized = false;
1443           if (wstate & Qt::WindowMaximized)
1444             prefs->windowMaximized = true;
1445           else if (! (wstate & unusualWindowStates))
1446             prefs->windowSize = size();
1447         }
1448       // non-saved
1449       saved->nsBorderBrush = widget->borderBrush();
1450       saved->nsBorderSize = widget->borderSize();
1451     }
1452 }
1453 
1454 
1455 void
applyPreferences(void)1456 QDjView::applyPreferences(void)
1457 {
1458   // Saved preferences
1459   applySaved(getSavedPrefs());
1460 
1461   // Other preferences
1462   QDjVuNetDocument::setProxy(prefs->proxyUrl);
1463   djvuContext.setCacheSize(prefs->cacheSize);
1464   widget->setPixelCacheSize(prefs->pixelCacheSize);
1465   widget->setModifiersForLens(prefs->modifiersForLens);
1466   widget->setModifiersForSelect(prefs->modifiersForSelect);
1467   widget->setModifiersForLinks(prefs->modifiersForLinks);
1468   widget->setGamma(prefs->gamma);
1469   widget->setWhite(prefs->white);
1470   widget->setScreenDpi(prefs->resolution ? prefs->resolution : physicalDpiY());
1471   widget->setLensSize(prefs->lensSize);
1472   widget->setLensPower(prefs->lensPower);
1473   widget->enableAnimation(prefs->enableAnimations);
1474   widget->setInvertLuminance(prefs->invertLuminance);
1475   widget->setMouseWheelZoom(prefs->mouseWheelZoom);
1476 
1477   // Thumbnail preferences
1478   thumbnailWidget->setSize(prefs->thumbnailSize);
1479   thumbnailWidget->setSmart(prefs->thumbnailSmart);
1480 
1481   // Search preferences
1482   findWidget->setWordOnly(prefs->searchWordsOnly);
1483   findWidget->setCaseSensitive(prefs->searchCaseSensitive);
1484   // Too risky:  findWidget->setRegExpMode(prefs->searchRegExpMode);
1485 
1486   // Special preferences for embedded plugins
1487   if (viewerMode == EMBEDDED_PLUGIN)
1488     setMinimumSize(QSize(8,8));
1489 
1490   // Preload mode change prefs.
1491   fsSavedNormal = prefs->forStandalone;
1492   fsSavedFullScreen = prefs->forFullScreen;
1493   fsSavedSlideShow = prefs->forSlideShow;
1494 }
1495 
1496 
1497 void
updatePreferences(void)1498 QDjView::updatePreferences(void)
1499 {
1500   updateSaved(getSavedPrefs());
1501   prefs->thumbnailSize = thumbnailWidget->size();
1502   prefs->thumbnailSmart = thumbnailWidget->smart();
1503   prefs->searchWordsOnly = findWidget->wordOnly();
1504   prefs->searchCaseSensitive = findWidget->caseSensitive();
1505   prefs->searchRegExpMode = findWidget->regExpMode();
1506 }
1507 
1508 
1509 void
preferencesChanged(void)1510 QDjView::preferencesChanged(void)
1511 {
1512   applyPreferences();
1513   createWhatsThis();
1514   updateActions();
1515 }
1516 
1517 
1518 
1519 // ----------------------------------------
1520 // SESSION MANAGEMENT
1521 
1522 
1523 void
saveSession(QSettings * s)1524 QDjView::saveSession(QSettings *s)
1525 {
1526   Saved saved;
1527   updateSaved(&saved);
1528   s->setValue("name", objectName());
1529   s->setValue("options", prefs->optionsToString(saved.options));
1530   s->setValue("state", saved.state);
1531   s->setValue("tools", prefs->toolsToString(tools));
1532   s->setValue("documentUrl", getDecoratedUrl().toString());
1533 }
1534 
1535 void
restoreSession(QSettings * s)1536 QDjView::restoreSession(QSettings *s)
1537 {
1538   QUrl du = QUrl(s->value("documentUrl").toString());
1539   Tools tools = this->tools;
1540   Saved saved;
1541   updateSaved(&saved);
1542   if (s->contains("options"))
1543     saved.options = prefs->stringToOptions(s->value("options").toString());
1544   if (s->contains("zoom")) // compat
1545     saved.zoom = s->value("zoom").toInt();
1546   if (s->contains("state"))
1547     saved.state = s->value("state").toByteArray();
1548   if (s->contains("tools"))
1549     tools = prefs->stringToTools(s->value("tools").toString());
1550   if (s->contains("name"))
1551     setObjectName(s->value("name").toString());
1552   applySaved(&saved);
1553   updateActionsLater();
1554   if (du.isValid())
1555     open(du);
1556   if (document && s->contains("pageNo")) // compat
1557     goToPage(s->value("pageNo", 0).toInt());
1558 }
1559 
1560 
1561 
1562 
1563 // ----------------------------------------
1564 // QDJVIEW ARGUMENTS
1565 
1566 
1567 static bool
string_is_on(QString val)1568 string_is_on(QString val)
1569 {
1570   QString v = val.toLower();
1571   return v == "yes" || v == "on" || v == "true" || v == "1";
1572 }
1573 
1574 static bool
string_is_off(QString val)1575 string_is_off(QString val)
1576 {
1577   QString v = val.toLower();
1578   return v == "no" || v == "off" || v == "false" || v == "0";
1579 }
1580 
1581 static QString
get_boolean(bool b)1582 get_boolean(bool b)
1583 {
1584   return QString(b ? "yes" : "no");
1585 }
1586 
1587 static bool
parse_boolean(QString key,QString val,QList<QString> & errors,bool & answer)1588 parse_boolean(QString key, QString val,
1589               QList<QString> &errors, bool &answer)
1590 {
1591   answer = false;
1592   if (string_is_off(val))
1593     return true;
1594   answer = true;
1595   if (string_is_on(val) || val.isNull())
1596     return true;
1597   errors << QDjView::tr("Option '%1' requires boolean argument.").arg(key);
1598   return false;
1599 }
1600 
1601 static void
illegal_value(QString key,QString value,QList<QString> & errors)1602 illegal_value(QString key, QString value, QList<QString> &errors)
1603 {
1604   errors << QDjView::tr("Illegal value '%2' for option '%1'.")
1605     .arg(key).arg(value);
1606 }
1607 
1608 static bool
parse_position(QString value,double & x,double & y)1609 parse_position(QString value, double &x, double &y)
1610 {
1611   bool okay0, okay1;
1612   QStringList val = value.split(",");
1613   if (val.size() == 2)
1614     {
1615       x = val[0].toDouble(&okay0);
1616       y = val[1].toDouble(&okay1);
1617       return okay0 && okay1;
1618     }
1619   return false;
1620 }
1621 
1622 static bool
parse_highlight(QString value,int & x,int & y,int & w,int & h,QColor & color)1623 parse_highlight(QString value,
1624                 int &x, int &y, int &w, int &h,
1625                 QColor &color)
1626 {
1627   bool okay0, okay1, okay2, okay3;
1628   QStringList val = value.split(",");
1629   if (val.size() < 4)
1630     return false;
1631   x = val[0].toInt(&okay0);
1632   y = val[1].toInt(&okay1);
1633   w = val[2].toInt(&okay2);
1634   h = val[3].toInt(&okay3);
1635   if (! (okay0 && okay1 && okay2 && okay3))
1636     return false;
1637   if (val.size() < 5)
1638     return true;
1639   QString c = val[4];
1640   if (c[0] != '#')
1641     c = "#" + c;
1642   color.setNamedColor(c);
1643   if (! color.isValid())
1644     return false;
1645   if (val.size() <= 5)
1646     return true;
1647   return false;
1648 }
1649 
1650 template<class T> static inline void
set_reset(QFlags<T> & x,bool plus,bool minus,T y)1651 set_reset(QFlags<T> &x, bool plus, bool minus, T y)
1652 {
1653   if (plus)
1654     x |= y;
1655   else if (minus)
1656     x &= ~y;
1657 }
1658 
1659 
1660 void
parseToolBarOption(QString option,QStringList & errors)1661 QDjView::parseToolBarOption(QString option, QStringList &errors)
1662 {
1663   QString str = option.toLower();
1664   int len = str.size();
1665   bool wantselect = false;
1666   bool wantmode = false;
1667   bool minus = false;
1668   bool plus = false;
1669   int npos = 0;
1670   while (npos < len)
1671     {
1672       int pos = npos;
1673       npos = str.indexOf(QRegExp("[-+,]"), pos);
1674       if (npos < 0)
1675         npos = len;
1676       QString key = str.mid(pos, npos-pos).trimmed();
1677       if (string_is_off(key) && !plus && !minus)
1678         options &= ~QDjViewPrefs::SHOW_TOOLBAR;
1679       else if (string_is_on(key) && !plus && !minus)
1680         options |= QDjViewPrefs::SHOW_TOOLBAR;
1681       else if (key=="bottom" && !plus && !minus) {
1682         options |= QDjViewPrefs::SHOW_TOOLBAR;
1683         tools &= ~QDjViewPrefs::TOOLBAR_TOP;
1684       } else if (key=="top" && !plus && !minus) {
1685         options |= QDjViewPrefs::SHOW_TOOLBAR;
1686         tools &= ~QDjViewPrefs::TOOLBAR_BOTTOM;
1687       } else if (key=="auto" && !plus && !minus)
1688         tools |= QDjViewPrefs::TOOLBAR_AUTOHIDE;
1689       else if (key=="always" && !plus && !minus) {
1690         options |= QDjViewPrefs::SHOW_TOOLBAR;
1691         tools &= ~QDjViewPrefs::TOOLBAR_AUTOHIDE;
1692       } else if (key.contains(QRegExp("^(fore|back|color|bw)(_button)?$")))
1693         wantmode |= plus;
1694       else if (key=="pan" || key=="zoomsel" || key=="textsel")
1695         wantselect |= plus;
1696       else if (key=="modecombo")
1697         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_MODECOMBO);
1698       else if (key=="rescombo" || key=="zoomcombo")
1699         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_ZOOMCOMBO);
1700       else if (key=="zoom")
1701         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_ZOOMBUTTONS);
1702       else if (key=="rotate")
1703         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_ROTATE);
1704       else if (key=="search" || key=="find")
1705         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_FIND);
1706       else if (key=="print")
1707         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_PRINT);
1708       else if (key=="save")
1709         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_SAVE);
1710       else if (key=="pagecombo")
1711         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_PAGECOMBO);
1712       else if (key=="backforw")
1713         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_BACKFORW);
1714       else if (key=="prevnext" || key=="prevnextpage")
1715         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_PREVNEXT);
1716       else if (key=="firstlast" || key=="firstlastpage")
1717         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_FIRSTLAST);
1718       // lizards
1719       else if (key=="doublepage")  // lizards!
1720         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_LAYOUT);
1721       else if (key=="calibrate" || key=="ruler" || key=="lizard")
1722         errors << tr("Toolbar option '%1' is not implemented.").arg(key);
1723       // djview4
1724       else if (key=="select")
1725         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_SELECT);
1726       else if (key=="new")
1727         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_NEW);
1728       else if (key=="open")
1729         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_OPEN);
1730       else if (key=="layout")
1731         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_LAYOUT);
1732       else if (key=="help")
1733         set_reset(tools, plus, minus, QDjViewPrefs::TOOL_WHATSTHIS);
1734       else if (key!="")
1735         errors << tr("Toolbar option '%1' is not recognized.").arg(key);
1736       // handle + or -
1737       if (npos < len)
1738         {
1739           Tools base = QDjViewPrefs::TOOLBAR_AUTOHIDE |
1740             QDjViewPrefs::TOOLBAR_TOP | QDjViewPrefs::TOOLBAR_BOTTOM;
1741           if (str[npos] == '-')
1742             {
1743               if (!minus && !plus)
1744                 tools |= (prefs->tools & ~base);
1745               plus = false;
1746               minus = true;
1747             }
1748           else if (str[npos] == '+')
1749             {
1750               if (!minus && !plus)
1751                 tools &= base;
1752               minus = false;
1753               plus = true;
1754             }
1755           npos += 1;
1756         }
1757     }
1758   if (wantmode)
1759     tools |= QDjViewPrefs::TOOL_MODECOMBO;
1760   if (wantselect)
1761     tools |= QDjViewPrefs::TOOL_SELECT;
1762 }
1763 
1764 
1765 
1766 /*! Parse a qdjview option described by a pair \a key, \a value.
1767   Such options can be provided on the command line
1768   or passed with the document URL as pseudo-CGI arguments.
1769   Return a list of error strings. */
1770 
1771 QStringList
parseArgument(QString key,QString value)1772 QDjView::parseArgument(QString key, QString value)
1773 {
1774   bool okay;
1775   QStringList errors;
1776   key = key.toLower();
1777 
1778   if (key == "fullscreen" || key == "fs")
1779     {
1780       if (viewerMode < STANDALONE)
1781         errors << tr("Option '%1' requires a standalone viewer.").arg(key);
1782       if (parse_boolean(key, value, errors, okay))
1783         setViewerMode(okay ? STANDALONE_FULLSCREEN : STANDALONE);
1784     }
1785   else if (key == "slideshow")
1786     {
1787       if (viewerMode < STANDALONE)
1788         errors << tr("Option '%1' requires a standalone viewer.").arg(key);
1789       int delay = value.toInt(&okay);
1790       if (okay && delay < 0)
1791         okay = false;
1792       if (okay || parse_boolean(key, value, errors, okay))
1793         setViewerMode(okay ? STANDALONE_SLIDESHOW : STANDALONE);
1794       if (delay > 0)
1795         setSlideShowDelay(delay);
1796     }
1797   else if (key == "toolbar")
1798     {
1799       parseToolBarOption(value, errors);
1800       toolBar->setVisible(options & QDjViewPrefs::SHOW_TOOLBAR);
1801     }
1802   else if (key == "page")
1803     {
1804       pendingPage = value;
1805       performPendingLater();
1806     }
1807   else if (key == "pageno")
1808     {
1809       pendingPage = QString("$%1").arg(value.toInt());
1810       performPendingLater();
1811     }
1812   else if (key == "cache")
1813     {
1814       // see QDjVuDocument::setUrl(...)
1815       parse_boolean(key, value, errors, okay);
1816     }
1817   else if (key == "passive" || key == "passivestretch")
1818     {
1819       if (parse_boolean(key, value, errors, okay) && okay)
1820         {
1821           enableScrollBars(false);
1822           enableContextMenu(false);
1823           menuBar->setVisible(false);
1824           statusBar->setVisible(false);
1825           thumbnailDock->setVisible(false);
1826           outlineDock->setVisible(false);
1827           findDock->setVisible(false);
1828           toolBar->setVisible(false);
1829           if (key == "passive")
1830             widget->setZoom(QDjVuWidget::ZOOM_FITPAGE);
1831           else
1832             widget->setZoom(QDjVuWidget::ZOOM_STRETCH);
1833           widget->setContinuous(false);
1834           widget->setSideBySide(false);
1835           widget->enableKeyboard(false);
1836           widget->enableMouse(false);
1837         }
1838     }
1839   else if (key == "scrollbars")
1840     {
1841       if (parse_boolean(key, value, errors, okay))
1842         enableScrollBars(okay);
1843     }
1844   else if (key == "menubar")    // new for djview4
1845     {
1846       if (parse_boolean(key, value, errors, okay))
1847         menuBar->setVisible(okay);
1848     }
1849   else if (key == "sidebar" ||  // new for djview4
1850            key == "navpane" )   // lizards
1851     {
1852       showSideBar(value, errors);
1853     }
1854   else if (key == "statusbar")   // new for djview4
1855     {
1856       if (parse_boolean(key, value, errors, okay))
1857         statusBar->setVisible(okay);
1858     }
1859   else if (key == "continuous")  // new for djview4
1860     {
1861       if (parse_boolean(key, value, errors, okay))
1862         widget->setContinuous(okay);
1863     }
1864   else if (key == "side_by_side" ||
1865            key == "sidebyside")  // new for djview4
1866     {
1867       if (parse_boolean(key, value, errors, okay))
1868         widget->setSideBySide(okay);
1869     }
1870   else if (key == "firstpagealone" ||
1871            key == "first_page_alone" ||
1872            key == "cover_page" ||
1873            key == "coverpage")   // new for djview4
1874     {
1875       if (parse_boolean(key, value, errors, okay))
1876         widget->setCoverPage(okay);
1877     }
1878   else if (key == "right_to_left" ||
1879            key == "righttoleft") // new for djview4
1880     {
1881       if (parse_boolean(key, value, errors, okay))
1882         widget->setRightToLeft(okay);
1883     }
1884   else if (key == "layout")      // lizards
1885     {
1886       bool layout = false;
1887       bool continuous = false;
1888       bool sidebyside = false;
1889       foreach (QString s, value.split(","))
1890         {
1891           if (s == "single")
1892             layout = true, continuous = sidebyside = false;
1893           else if (s == "double")
1894             layout = sidebyside = true;
1895           else if (s == "continuous")
1896             layout = continuous = true;
1897           else if (s == "ltor")
1898             widget->setRightToLeft(false);
1899           else if (s == "rtol")
1900             widget->setRightToLeft(true);
1901           else if (s == "cover")
1902             widget->setCoverPage(true);
1903           else if (s == "nocover")
1904             widget->setCoverPage(false);
1905           else if (s == "gap")
1906             widget->setSeparatorSize(12);
1907           else if (s == "nogap")
1908             widget->setSeparatorSize(0);
1909         }
1910       if (layout)
1911         {
1912           widget->setContinuous(continuous);
1913           widget->setSideBySide(sidebyside);
1914         }
1915     }
1916   else if (key == "frame")
1917     {
1918       if (parse_boolean(key, value, errors, okay))
1919         widget->setDisplayFrame(okay);
1920     }
1921   else if (key == "background")
1922     {
1923       QColor color;
1924       color.setNamedColor((value[0] == '#') ? value : "#" + value);
1925       if (color.isValid())
1926         {
1927           QBrush brush = QBrush(color);
1928           widget->setBorderBrush(brush);
1929           fsSavedNormal.nsBorderBrush = brush;
1930           fsSavedFullScreen.nsBorderBrush = brush;
1931           fsSavedSlideShow.nsBorderBrush = brush;
1932         }
1933       else
1934         illegal_value(key, value, errors);
1935     }
1936   else if (key == "menu")
1937     {
1938       if (parse_boolean(key, value, errors, okay))
1939         enableContextMenu(okay);
1940     }
1941   else if (key == "keyboard")
1942     {
1943       if (parse_boolean(key, value, errors, okay))
1944         widget->enableKeyboard(okay);
1945     }
1946   else if (key == "mouse")
1947     {
1948       if (parse_boolean(key, value, errors, okay))
1949         widget->enableMouse(okay);
1950     }
1951   else if (key == "links")
1952     {
1953       if (parse_boolean(key, value, errors, okay))
1954         widget->enableHyperlink(okay);
1955     }
1956   else if (key == "zoom")
1957     {
1958       int z = value.toInt(&okay);
1959       QString val = value.toLower();
1960       if (val == "one2one")
1961         widget->setZoom(QDjVuWidget::ZOOM_ONE2ONE);
1962       else if (val == "width" || val == "fitwidth")
1963         widget->setZoom(QDjVuWidget::ZOOM_FITWIDTH);
1964       else if (val == "page" || val == "fitpage")
1965         widget->setZoom(QDjVuWidget::ZOOM_FITPAGE);
1966       else if (val == "stretch")
1967         widget->setZoom(QDjVuWidget::ZOOM_STRETCH);
1968       else if (okay && z >= QDjVuWidget::ZOOM_MIN
1969                && z <= QDjVuWidget::ZOOM_MAX)
1970         widget->setZoom(z);
1971       else
1972         illegal_value(key, value, errors);
1973     }
1974   else if (key == "mode")
1975     {
1976       QString val = value.toLower();
1977       if (val == "color")
1978         widget->setDisplayMode(QDjVuWidget::DISPLAY_COLOR);
1979       else if (val == "bw" || val == "mask" || val == "stencil")
1980         widget->setDisplayMode(QDjVuWidget::DISPLAY_STENCIL);
1981       else if (val == "fore" || val == "foreground" || val == "fg")
1982         widget->setDisplayMode(QDjVuWidget::DISPLAY_FG);
1983       else if (val == "back" || val == "background" || val == "bg")
1984         widget->setDisplayMode(QDjVuWidget::DISPLAY_BG);
1985       else if (val == "text" || val == "hiddentext")
1986         widget->setDisplayMode(QDjVuWidget::DISPLAY_TEXT);
1987       else
1988         illegal_value(key, value, errors);
1989     }
1990   else if (key == "hor_align" || key == "halign")
1991     {
1992       QString val = value.toLower();
1993       if (val == "left")
1994         widget->setHorizAlign(QDjVuWidget::ALIGN_LEFT);
1995       else if (val == "center")
1996         widget->setHorizAlign(QDjVuWidget::ALIGN_CENTER);
1997       else if (val == "right")
1998         widget->setHorizAlign(QDjVuWidget::ALIGN_RIGHT);
1999       else
2000         illegal_value(key, value, errors);
2001     }
2002   else if (key == "ver_align" || key == "valign")
2003     {
2004       QString val = value.toLower();
2005       if (val == "top")
2006         widget->setVertAlign(QDjVuWidget::ALIGN_TOP);
2007       else if (val == "center")
2008         widget->setVertAlign(QDjVuWidget::ALIGN_CENTER);
2009       else if (val == "bottom")
2010         widget->setVertAlign(QDjVuWidget::ALIGN_BOTTOM);
2011       else
2012         illegal_value(key, value, errors);
2013     }
2014   else if (key == "highlight")
2015     {
2016       int x,y,w,h;
2017       QColor color;
2018       if (value.isEmpty())
2019         widget->clearHighlights(widget->page());
2020       else if (parse_highlight(value, x, y, w, h, color))
2021         pendingHilite << StringPair(pendingPage, value);
2022       else
2023         illegal_value(key, value, errors);
2024       performPendingLater();
2025     }
2026   else if (key == "rotate")
2027     {
2028       if (value == "0")
2029         widget->setRotation(0);
2030       else if (value == "90")
2031         widget->setRotation(1);
2032       else if (value == "180")
2033         widget->setRotation(2);
2034       else if (value == "270")
2035         widget->setRotation(3);
2036       else
2037         illegal_value(key, value, errors);
2038     }
2039   else if (key == "print")
2040     {
2041       if (parse_boolean(key, value, errors, okay))
2042         printingAllowed = okay;
2043     }
2044   else if (key == "save")
2045     {
2046       if (parse_boolean(key, value, errors, okay))
2047         savingAllowed = okay;
2048     }
2049   else if (key == "notoolbar" ||
2050            key == "noscrollbars" ||
2051            key == "nomenu" )
2052     {
2053       QString msg = tr("Deprecated option '%1'").arg(key);
2054       qWarning("%s",(const char*)msg.toLocal8Bit());
2055       if (key == "notoolbar" && value.isNull())
2056         toolBar->setVisible(false);
2057       else if (key == "noscrollbars" && value.isNull())
2058         enableScrollBars(false);
2059       else if (key == "nomenu" && value.isNull())
2060         enableContextMenu(false);
2061     }
2062   else if (key == "thumbnails")
2063     {
2064       QStringList junk;
2065       if (! showSideBar(value+",thumbnails", junk))
2066         illegal_value(key, value, errors);
2067     }
2068   else if (key == "outline")
2069     {
2070       QStringList junk;
2071       if (! showSideBar(value+",outline", junk))
2072         illegal_value(key, value, errors);
2073     }
2074   else if (key == "find") // new for djview4
2075     {
2076       if (findWidget)
2077         {
2078           findWidget->setText(QString());
2079           findWidget->setRegExpMode(false);
2080           findWidget->setWordOnly(true);
2081           findWidget->setCaseSensitive(false);
2082         }
2083       pendingFind = value;
2084       if (! value.isEmpty())
2085         performPendingLater();
2086     }
2087   else if (key == "showposition")
2088     {
2089       double x=0, y=0;
2090       pendingPosition.clear();
2091       if (! parse_position(value, x, y))
2092         illegal_value(key, value, errors);
2093       else
2094         {
2095           pendingPosition << x << y;
2096           performPendingLater();
2097         }
2098     }
2099   else if (key == "logo" || key == "textsel" || key == "search")
2100     {
2101       QString msg = tr("Option '%1' is not implemented.").arg(key);
2102       qWarning("%s",(const char*)msg.toLocal8Bit());
2103     }
2104   else
2105     {
2106       errors << tr("Option '%1' is not recognized.").arg(key);
2107     }
2108   updateActionsLater();
2109   return errors;
2110 }
2111 
2112 
2113 /*! Parse a \a QDjView option expressed a a string \a key=value.
2114   This is very useful for processing command line options. */
2115 
2116 QStringList
parseArgument(QString keyEqualValue)2117 QDjView::parseArgument(QString keyEqualValue)
2118 {
2119   int n = keyEqualValue.indexOf("=");
2120   if (n < 0)
2121     return parseArgument(keyEqualValue, QString());
2122   else
2123     return parseArgument(keyEqualValue.left(n),
2124                          keyEqualValue.mid(n+1));
2125 }
2126 
2127 
2128 /*! Parse the \a QDjView options passed via
2129   the CGI query arguments of \a url.
2130   This is called by \a QDjView::open(QDjVuDocument, QUrl). */
2131 
2132 void
parseDjVuCgiArguments(QUrl url)2133 QDjView::parseDjVuCgiArguments(QUrl url)
2134 {
2135   QStringList errors;
2136   // parse
2137   bool djvuopts = false;
2138   QPair<QString,QString> pair;
2139   foreach(pair, urlQueryItems(url, true))
2140     {
2141       if (pair.first.toLower() == "djvuopts")
2142         djvuopts = true;
2143       else if (djvuopts)
2144         errors << parseArgument(pair.first, pair.second);
2145     }
2146   // warning for errors
2147   if (djvuopts && errors.size() > 0)
2148     foreach(QString error, errors)
2149       qWarning("%s",(const char*)error.toLocal8Bit());
2150 }
2151 
2152 
2153 /*! Return a copy of the url without the
2154   CGI query arguments corresponding to DjVu options. */
2155 
2156 QUrl
removeDjVuCgiArguments(QUrl url)2157 QDjView::removeDjVuCgiArguments(QUrl url)
2158 {
2159   QList<QPair<QString,QString> > args;
2160   bool djvuopts = false;
2161   QPair<QString,QString> pair;
2162   foreach(pair, urlQueryItems(url))
2163     {
2164       if (pair.first.toLower() == "djvuopts")
2165         djvuopts = true;
2166       else if (!djvuopts)
2167         args << pair;
2168     }
2169   QUrl newurl = url;
2170   urlSetQueryItems(newurl, args);
2171   return newurl;
2172 }
2173 
2174 
2175 /*! Returns the most adequate value
2176   that could be passed to \a parseArgument
2177   for the specified \a key. */
2178 
2179 QString
getArgument(QString key)2180 QDjView::getArgument(QString key)
2181 {
2182   key = key.toLower();
2183   if (key == "pages") // readonly
2184     return QString::number(pageNum());
2185   else if (key == "page")
2186     return pageName(widget->page());
2187   else if (key == "pageno")
2188     return QString::number(widget->page()+1);
2189   else if (key == "fullscreen" || key == "fs")
2190     return get_boolean(viewerMode == STANDALONE_FULLSCREEN);
2191   else if (key == "slideshow")
2192     return get_boolean(viewerMode == STANDALONE_SLIDESHOW);
2193   else if (key == "scrollbars")
2194     return get_boolean(widget->horizontalScrollBarPolicy() !=
2195                        Qt::ScrollBarAlwaysOff );
2196   else if (key == "menubar")
2197     return get_boolean(menuBar->isVisibleTo(this));
2198   else if (key == "statusbar")
2199     return get_boolean(statusBar->isVisibleTo(this));
2200   else if (key == "continuous")
2201     return get_boolean(widget->continuous());
2202   else if (key == "side_by_side" || key == "sidebyside")
2203     return get_boolean(widget->sideBySide());
2204   else if (key == "coverpage" || key == "firstpagealone" ||
2205            key == "first_page_alone" )
2206     return get_boolean(widget->coverPage());
2207   else if (key == "righttoleft" || key == "right_to_left")
2208     return get_boolean(widget->rightToLeft());
2209   else if (key == "frame")
2210     return get_boolean(widget->displayFrame());
2211   else if (key == "keyboard")
2212     return get_boolean(widget->keyboardEnabled());
2213   else if (key == "mouse")
2214     return get_boolean(widget->mouseEnabled());
2215   else if (key == "links")
2216     return get_boolean(widget->hyperlinkEnabled());
2217   else if (key == "menu")
2218     return get_boolean(widget->contextMenu() != 0);
2219   else if (key == "rotate")
2220     return QString::number(widget->rotation() * 90);
2221   else if (key == "print")
2222     return get_boolean(printingAllowed);
2223   else if (key == "save")
2224     return get_boolean(savingAllowed);
2225   else if (key == "background")
2226     {
2227       QBrush brush = widget->borderBrush();
2228       if (brush.style() == Qt::SolidPattern)
2229         return brush.color().name();
2230     }
2231   else if (key == "layout")
2232     {
2233       QStringList l;
2234       if (widget->sideBySide())
2235         l << QString("double");
2236       if (widget->continuous())
2237         l << QString("continuous");
2238       if (l.isEmpty())
2239         l << QString("single");
2240       l << QString(widget->rightToLeft() ? "rtol" : "ltor");
2241       l << QString(widget->coverPage() ? "cover" : "nocover");
2242       l << QString(widget->separatorSize() ? "gap" : "nogap");
2243       return l.join(",");
2244     }
2245   else if (key == "zoom")
2246     {
2247       int z = widget->zoom();
2248       if (z == QDjVuWidget::ZOOM_ONE2ONE)
2249         return QString("one2one");
2250       else if (z == QDjVuWidget::ZOOM_FITWIDTH)
2251         return QString("width");
2252       else if (z == QDjVuWidget::ZOOM_FITPAGE)
2253         return QString("page");
2254       else if (z == QDjVuWidget::ZOOM_STRETCH)
2255         return QString("stretch");
2256       else if (z >= QDjVuWidget::ZOOM_MIN && z <= QDjVuWidget::ZOOM_MAX)
2257         return QString::number(z);
2258     }
2259   else if (key == "mode")
2260     {
2261       QDjVuWidget::DisplayMode m = widget->displayMode();
2262       if (m == QDjVuWidget::DISPLAY_COLOR)
2263         return QString("color");
2264       else if (m == QDjVuWidget::DISPLAY_STENCIL)
2265         return QString("bw");
2266       else if (m == QDjVuWidget::DISPLAY_FG)
2267         return QString("fore");
2268       else if (m == QDjVuWidget::DISPLAY_BG)
2269         return QString("back");
2270       else if (m == QDjVuWidget::DISPLAY_TEXT)
2271         return QString("text");
2272     }
2273   else if (key == "hor_align" || key == "halign")
2274     {
2275       QDjVuWidget::Align a = widget->horizAlign();
2276       if (a == QDjVuWidget::ALIGN_LEFT)
2277         return QString("left");
2278       else if (a == QDjVuWidget::ALIGN_CENTER)
2279         return QString("center");
2280       else if (a == QDjVuWidget::ALIGN_RIGHT)
2281         return QString("right");
2282     }
2283   else if (key == "ver_align" || key == "valign")
2284     {
2285       QDjVuWidget::Align a = widget->vertAlign();
2286       if (a == QDjVuWidget::ALIGN_TOP)
2287         return QString("top");
2288       else if (a == QDjVuWidget::ALIGN_CENTER)
2289         return QString("center");
2290       else if (a == QDjVuWidget::ALIGN_BOTTOM)
2291         return QString("bottom");
2292     }
2293   else if (key == "showposition")
2294     {
2295       QPoint center = widget->rect().center();
2296       QDjVuWidget::Position pos = widget->positionWithClosestAnchor(center);
2297       double ha = pos.hAnchor / 100.0;
2298       double va = pos.vAnchor / 100.0;
2299       return QString("%1,%2").arg(ha).arg(va);
2300     }
2301   else if (key == "passive" || key == "passivestretch")
2302     {
2303       if (widget->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff
2304           && !menuBar->isVisibleTo(this) && !statusBar->isVisibleTo(this)
2305           && !thumbnailDock->isVisibleTo(this) && !outlineDock->isVisibleTo(this)
2306           && !findDock->isVisibleTo(this) && !toolBar->isVisibleTo(this)
2307           && !widget->continuous() && !widget->sideBySide()
2308           && !widget->keyboardEnabled() && !widget->mouseEnabled()
2309           && widget->contextMenu() == 0 )
2310         {
2311           int z = widget->zoom();
2312           if (key == "passive" && z == QDjVuWidget::ZOOM_FITPAGE)
2313             return QString("yes");
2314           if (key == "passivestretch" && z == QDjVuWidget::ZOOM_STRETCH)
2315             return QString("yes");
2316         }
2317       return QString("no");
2318     }
2319   else if (key == "sidebar")
2320     { // without position
2321       QStringList what;
2322       if (thumbnailDock->isVisibleTo(this))
2323         what << "thumbnails";
2324       if (outlineDock->isVisibleTo(this))
2325         what << "outline";
2326       if (findDock->isVisibleTo(this))
2327         what << "find";
2328       if (what.size() > 0)
2329         return QString("yes,") + what.join(",");
2330       else
2331         return QString("no");
2332     }
2333   else if (key == "toolbar")
2334     { // Currently without toolbar content
2335       return get_boolean(toolBar->isVisibleTo(this));
2336     }
2337   else if (key == "highlight")
2338     { // Currently not working
2339       return QString();
2340     }
2341   return QString();
2342 }
2343 
2344 
2345 
2346 
2347 
2348 
2349 
2350 
2351 // ----------------------------------------
2352 // QDJVIEW
2353 
2354 
2355 /*! \enum QDjView::ViewerMode
2356   The viewer mode is selected at creation time.
2357   Mode \a STANDALONE corresponds to a standalone program.
2358   Modes \a FULLPAGE_PLUGIN and \a EMBEDDED_PLUGIN
2359   correspond to a netscape plugin. */
2360 
2361 
~QDjView()2362 QDjView::~QDjView()
2363 {
2364   closeDocument();
2365 }
2366 
2367 
2368 /*! Construct a \a QDjView object using
2369   the specified djvu context and viewer mode. */
2370 
QDjView(QDjVuContext & context,ViewerMode mode,QWidget * parent)2371 QDjView::QDjView(QDjVuContext &context, ViewerMode mode, QWidget *parent)
2372   : QMainWindow(parent),
2373     viewerMode(mode < STANDALONE ? mode : STANDALONE),
2374     djvuContext(context),
2375     document(0),
2376     hasNumericalPageTitle(true),
2377     updateActionsScheduled(false),
2378     performPendingScheduled(false),
2379     printingAllowed(true),
2380     savingAllowed(true)
2381 {
2382   // Main window setup
2383   setWindowTitle(tr("DjView"));
2384   setWindowIcon(QIcon(":/images/djview.png"));
2385 #ifndef Q_OS_DARWIN
2386   if (QApplication::windowIcon().isNull())
2387     QApplication::setWindowIcon(windowIcon());
2388 #endif
2389 
2390   // Determine unique object name
2391   if (mode >= STANDALONE)
2392     {
2393       QWidgetList wl = QApplication::topLevelWidgets();
2394       static int num = 0;
2395       QString name;
2396       while(name.isEmpty())
2397         {
2398           name = QString("djview%1").arg(num++);
2399           foreach(QWidget *w, wl)
2400             if (w->objectName() == name)
2401               name = QString();
2402         }
2403       setObjectName(name);
2404     }
2405 
2406   // Basic preferences
2407   prefs = QDjViewPrefs::instance();
2408   options = QDjViewPrefs::defaultOptions;
2409   tools = prefs->tools;
2410   toolsCached = zero(Tools);
2411 
2412   // Create dialogs
2413   errorDialog = new QDjViewErrorDialog(this);
2414   infoDialog = 0;
2415   metaDialog = 0;
2416 
2417   // Try using GLWidget?
2418   bool useOpenGL = prefs->openGLAccel;
2419   QString envOpenGL = QString::fromLocal8Bit(::getenv("DJVIEW_OPENGL"));
2420   useOpenGL |= string_is_on(envOpenGL);
2421   useOpenGL &= !string_is_off(envOpenGL);
2422   if (viewerMode < STANDALONE)
2423     useOpenGL = false; // GLWidget/XEmbed has focus issues
2424 
2425   // Create djvu widget
2426   QWidget *central = new QWidget(this);
2427   widget = new QDjVuWidget(useOpenGL, central);
2428   widget->setFrameShape(QFrame::NoFrame);
2429   if (viewerMode >= STANDALONE)
2430     widget->setFrameShadow(QFrame::Sunken);
2431 
2432   widget->viewport()->installEventFilter(this);
2433   connect(widget, SIGNAL(errorCondition(int)),
2434           this, SLOT(errorCondition(int)));
2435   connect(widget, SIGNAL(error(QString,QString,int)),
2436           errorDialog, SLOT(error(QString,QString,int)));
2437   connect(widget, SIGNAL(error(QString,QString,int)),
2438           this, SLOT(error(QString,QString,int)));
2439   connect(widget, SIGNAL(info(QString)),
2440           this, SLOT(info(QString)));
2441   connect(widget, SIGNAL(layoutChanged()),
2442           this, SLOT(updateActionsLater()));
2443   connect(widget, SIGNAL(pageChanged(int)),
2444           this, SLOT(updateActionsLater()));
2445   connect(widget, SIGNAL(pointerPosition(const Position&,const PageInfo&)),
2446           this, SLOT(pointerPosition(const Position&,const PageInfo&)));
2447   connect(widget, SIGNAL(pointerEnter(const Position&,miniexp_t)),
2448           this, SLOT(pointerEnter(const Position&,miniexp_t)));
2449   connect(widget, SIGNAL(pointerLeave(const Position&,miniexp_t)),
2450           this, SLOT(pointerLeave(const Position&,miniexp_t)));
2451   connect(widget, SIGNAL(pointerClick(const Position&,miniexp_t)),
2452           this, SLOT(pointerClick(const Position&,miniexp_t)));
2453   connect(widget, SIGNAL(pointerSelect(const QPoint&,const QRect&)),
2454           this, SLOT(pointerSelect(const QPoint&,const QRect&)));
2455 
2456   // Create splash screen
2457   splash = new QLabel(central);
2458   splash->setFrameShadow(QFrame::Sunken);
2459   splash->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
2460   splash->setPixmap(QPixmap(":/images/splash.png"));
2461   QPalette palette = splash->palette();
2462   palette.setColor(QPalette::Background, Qt::white);
2463   splash->setPalette(palette);
2464   splash->setAutoFillBackground(true);
2465 
2466   // Create central layout
2467   layout = new QStackedLayout(central);
2468   layout->addWidget(widget);
2469   layout->addWidget(splash);
2470   layout->setCurrentWidget(splash);
2471   setCentralWidget(central);
2472 
2473   // Create context menu
2474   contextMenu = new QMenu(this);
2475   recentMenu = 0;
2476 
2477   // Create menubar
2478   menuBar = new QMenuBar(this);
2479   setMenuBar(menuBar);
2480 
2481   // Create statusbar
2482   statusBar = new QStatusBar(this);
2483   QFont font = QApplication::font();
2484   font.setPointSize((font.pointSize() * 3 + 3) / 4);
2485   QFontMetrics metric(font);
2486   textLabelTimer = new QTimer(this);
2487   textLabelTimer->setSingleShot(true);
2488   connect(textLabelTimer,SIGNAL(timeout()),this,SLOT(updateTextLabel()));
2489   textLabel = new QLabel(statusBar);
2490   textLabel->setFont(font);
2491   textLabel->setAutoFillBackground(true);
2492   textLabel->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
2493   textLabel->setFrameStyle(QFrame::Panel);
2494   textLabel->setFrameShadow(QFrame::Sunken);
2495   textLabel->setMinimumWidth(swidth(metric,"M")*48);
2496   statusBar->addWidget(textLabel, 1);
2497   pageLabel = new QLabel(statusBar);
2498   pageLabel->setFont(font);
2499   pageLabel->setTextFormat(Qt::PlainText);
2500   pageLabel->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
2501   pageLabel->setFrameStyle(QFrame::Panel);
2502   pageLabel->setFrameShadow(QFrame::Sunken);
2503   pageLabel->setMinimumWidth(swidth(metric," P88/888 8888x8888 888dpi "));
2504   statusBar->addPermanentWidget(pageLabel);
2505   mouseLabel = new QLabel(statusBar);
2506   mouseLabel->setFont(font);
2507   mouseLabel->setTextFormat(Qt::PlainText);
2508   mouseLabel->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
2509   mouseLabel->setFrameStyle(QFrame::Panel);
2510   mouseLabel->setFrameShadow(QFrame::Sunken);
2511   mouseLabel->setMinimumWidth(swidth(metric," x=888 y=888 "));
2512   statusBar->addPermanentWidget(mouseLabel);
2513   setStatusBar(statusBar);
2514 
2515   // Create toolbar
2516   toolBar = new QToolBar(this);
2517   toolBar->setObjectName("toolbar");
2518   toolBar->setAllowedAreas(Qt::TopToolBarArea|Qt::BottomToolBarArea);
2519   addToolBar(toolBar);
2520 
2521   // Create mode combo box
2522   modeCombo = new QComboBox(toolBar);
2523   fillModeCombo(modeCombo);
2524   connect(modeCombo, SIGNAL(activated(int)),
2525           this, SLOT(modeComboActivated(int)) );
2526   connect(modeCombo, SIGNAL(activated(int)),
2527           this, SLOT(updateActionsLater()) );
2528 
2529   // Create zoom combo box
2530   zoomCombo = new QComboBox(toolBar);
2531   zoomCombo->setEditable(true);
2532   zoomCombo->setInsertPolicy(QComboBox::NoInsert);
2533   fillZoomCombo(zoomCombo);
2534   connect(zoomCombo, SIGNAL(activated(int)),
2535           this, SLOT(zoomComboActivated(int)) );
2536   connect(zoomCombo->lineEdit(), SIGNAL(editingFinished()),
2537           this, SLOT(zoomComboEdited()) );
2538 
2539   // Create page combo box
2540   pageCombo = new QComboBox(toolBar);
2541   pageCombo->setEditable(true);
2542   pageCombo->setInsertPolicy(QComboBox::NoInsert);
2543   pageCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
2544   pageCombo->installEventFilter(this);
2545   connect(pageCombo, SIGNAL(activated(int)),
2546           this, SLOT(pageComboActivated(int)));
2547   connect(pageCombo->lineEdit(), SIGNAL(editingFinished()),
2548           this, SLOT(pageComboEdited()) );
2549 
2550   // Create sidebar
2551   thumbnailWidget = new QDjViewThumbnails(this);
2552   thumbnailDock = new QDockWidget(this);
2553   thumbnailDock->setObjectName("thumbnailDock");
2554   thumbnailDock->setWindowTitle(tr("Thumbnails"));
2555   thumbnailDock->setWidget(thumbnailWidget);
2556   thumbnailDock->installEventFilter(this);
2557   addDockWidget(Qt::LeftDockWidgetArea, thumbnailDock);
2558   outlineWidget = new QDjViewOutline(this);
2559   outlineDock = new QDockWidget(this);
2560   outlineDock->setObjectName("outlineDock");
2561   outlineDock->setWindowTitle(tr("Outline"));
2562   outlineDock->setWidget(outlineWidget);
2563   outlineDock->installEventFilter(this);
2564   addDockWidget(Qt::LeftDockWidgetArea, outlineDock);
2565   findWidget = new QDjViewFind(this);
2566   findDock = new QDockWidget(this);
2567   findDock->setObjectName("findDock");
2568   findDock->setWindowTitle(tr("Find"));
2569   findDock->setWidget(findWidget);
2570   findDock->installEventFilter(this);
2571   addDockWidget(Qt::LeftDockWidgetArea, findDock);
2572 
2573   // Create escape shortcut for sidebar
2574   shortcutEscape = new QShortcut(QKeySequence("Esc"), this);
2575   connect(shortcutEscape, SIGNAL(activated()), this, SLOT(performEscape()));
2576 
2577   // Create escape shortcut for activating page dialog
2578   shortcutGoPage = new QShortcut(QKeySequence("Ctrl+G"), this);
2579   connect(shortcutGoPage, SIGNAL(activated()), this, SLOT(performGoPage()));
2580 
2581   // Create MacOS shortcut for minimizing current window
2582 #ifdef Q_OS_DARWIN
2583   QShortcut *shortcutMinimize = new QShortcut(QKeySequence("Ctrl+M"),this);
2584   connect(shortcutMinimize, SIGNAL(activated()), this, SLOT(showMinimized()));
2585 #endif
2586 
2587   // Create misc timers
2588   undoTimer = new QTimer(this);
2589   undoTimer->setSingleShot(true);
2590   connect(undoTimer,SIGNAL(timeout()), this, SLOT(saveUndoData()));
2591   slideShowTimer = new QTimer(this);
2592   slideShowTimer->setSingleShot(true);
2593   connect(slideShowTimer, SIGNAL(timeout()), this, SLOT(slideShowTimeout()));
2594 
2595   // Actions
2596   createActions();
2597   createMenus();
2598   createWhatsThis();
2599   enableContextMenu(true);
2600 
2601   // Remember geometry and fix viewerMode
2602   if (mode >= STANDALONE)
2603     if (! (prefs->windowSize.isNull()))
2604       resize(prefs->windowSize);
2605   if (mode >= STANDALONE)
2606     if (prefs->windowMaximized)
2607       setWindowState(Qt::WindowMaximized);
2608   if (mode > STANDALONE)
2609     setViewerMode(mode);
2610 
2611   // Preferences
2612   connect(prefs, SIGNAL(updated()), this, SLOT(preferencesChanged()));
2613   applyPreferences();
2614   updateActions();
2615 
2616   // Options set so far have default priority
2617   widget->reduceOptionsToPriority(QDjVuWidget::PRIORITY_DEFAULT);
2618 }
2619 
2620 
2621 void
closeDocument()2622 QDjView::closeDocument()
2623 {
2624   // remember recent document
2625   QDjVuDocument *doc = document;
2626   if (doc && viewerMode >= STANDALONE)
2627     {
2628       if (documentPages.size() > 0)
2629         addRecent(getDecoratedUrl());
2630     }
2631   // clear undo data
2632   here.clear();
2633   undoList.clear();
2634   redoList.clear();
2635   // kill one-shot timers
2636   undoTimer->stop();
2637   textLabelTimer->stop();
2638   // close document
2639   layout->setCurrentWidget(splash);
2640   if (doc)
2641     {
2642       doc->ref();
2643       disconnect(doc, 0, this, 0);
2644       disconnect(doc, 0, errorDialog, 0);
2645       printingAllowed = true;
2646       savingAllowed = true;
2647     }
2648   widget->setDocument(0);
2649   documentPages.clear();
2650   documentFileName.clear();
2651   hasNumericalPageTitle = true;
2652   documentUrl.clear();
2653   documentModified = QDateTime();
2654   document = 0;
2655   if (doc)
2656     {
2657       emit documentClosed(doc);
2658       doc->deref();
2659     }
2660 }
2661 
2662 
2663 /*! Open a document represented by a \a QDjVuDocument object. */
2664 
2665 void
open(QDjVuDocument * doc,QUrl url)2666 QDjView::open(QDjVuDocument *doc, QUrl url)
2667 {
2668   closeDocument();
2669   document = doc;
2670   if (url.isValid())
2671     documentUrl = url;
2672   connect(doc,SIGNAL(destroyed(void)), this, SLOT(closeDocument(void)));
2673   connect(doc,SIGNAL(docinfo(void)), this, SLOT(docinfo(void)));
2674   widget->setDocument(document);
2675   disconnect(document, 0, errorDialog, 0);
2676   layout->setCurrentWidget(widget);
2677   updateActions();
2678   docinfo();
2679   if (doc)
2680     emit documentOpened(doc);
2681   // process url options
2682   if (url.isValid())
2683     parseDjVuCgiArguments(url);
2684   widget->reduceOptionsToPriority(QDjVuWidget::PRIORITY_CGI);
2685   // set focus
2686   widget->setFocus();
2687   // set title
2688   setWindowTitle(QString("%1[*] - ").arg(getShortFileName()) + tr("DjView"));
2689 #if QT_VERSION > 0x40400
2690   setWindowFilePath(url.toLocalFile());
2691 #endif
2692 }
2693 
2694 
2695 /*! Open the djvu document stored in file \a filename. */
2696 
2697 bool
open(QString filename)2698 QDjView::open(QString filename)
2699 {
2700   closeDocument();
2701   QDjVuDocument *doc = new QDjVuDocument(true);
2702   connect(doc, SIGNAL(error(QString,QString,int)),
2703           errorDialog, SLOT(error(QString,QString,int)));
2704   doc->setFileName(&djvuContext, filename);
2705   if (!doc->isValid())
2706     {
2707       delete doc;
2708       addToErrorDialog(tr("Cannot open file '%1'.").arg(filename));
2709       raiseErrorDialog(QMessageBox::Critical, tr("Opening DjVu file"));
2710       return false;
2711     }
2712   QFileInfo fileinfo(filename);
2713   QUrl url = QUrl::fromLocalFile(fileinfo.absoluteFilePath());
2714   open(doc, url);
2715   documentFileName = filename;
2716   documentModified = QFileInfo(filename).lastModified();
2717   restoreRecentDocument(url);
2718   return true;
2719 }
2720 
2721 
2722 /* Helper class to open remote documents */
2723 
2724 class QDjView::NetOpen : public QObject
2725 {
2726   Q_OBJECT
2727 private:
2728   QDjView *q;
2729   QDjVuNetDocument *doc;
2730   QUrl url;
2731   bool inNewWindow;
2732   bool maybeInBrowser;
2733   bool startedBrowser;
2734 public:
~NetOpen()2735   ~NetOpen() {
2736     if (doc) {
2737       doc->deref();
2738       if (! startedBrowser) {
2739         q->addToErrorDialog(tr("Cannot open URL '%1'.").arg(url.toString()));
2740         q->raiseErrorDialog(QMessageBox::Critical, tr("Opening DjVu document"));
2741       }
2742     }
2743   }
NetOpen(QDjView * q,QDjVuNetDocument * d,QUrl u,bool n,bool b)2744   NetOpen(QDjView *q, QDjVuNetDocument *d, QUrl u, bool n, bool b)
2745     : QObject(q),
2746       q(q), doc(d), url(u),
2747       inNewWindow(n), maybeInBrowser(b),
2748       startedBrowser(false) {
2749     doc->ref();
2750     connect(doc, SIGNAL(docinfo()),
2751             this, SLOT(docinfo()) );
2752     connect(doc, SIGNAL(gotContentType(QString,bool&)),
2753             this, SLOT(gotContentType(QString,bool&)) );
2754   }
2755 public slots:
docinfo()2756   void docinfo() {
2757     int status = ddjvu_document_decoding_status(*doc);
2758     if (status == DDJVU_JOB_OK) {
2759       disconnect(doc,0,this,0);
2760       if (inNewWindow) {
2761         QDjView *other = q->copyWindow();
2762         other->open(url);
2763         other->show();
2764       } else {
2765         q->open(doc,url);
2766       }
2767       doc = 0;
2768     }
2769     if (status >= DDJVU_JOB_OK)
2770       deleteLater();
2771   }
gotContentType(QString type,bool & okay)2772   void gotContentType(QString type, bool &okay) {
2773     QRegExp re("image/(x[.]|vnd[.-]|)(djvu|dejavu|iw44)(;.*)?");
2774     okay = re.exactMatch(type);
2775     if (maybeInBrowser && !okay) {
2776       startedBrowser = q->startBrowser(url);
2777       if (startedBrowser) {
2778         disconnect(doc,0,this,0);
2779         deleteLater();
2780       } else {
2781         QString msg = tr("Cannot spawn a browser for url '%1'").arg(url.toString());
2782         qWarning("%s",(const char*)msg.toLocal8Bit());
2783       }
2784     }
2785   }
2786 };
2787 
2788 
2789 /*! Open the djvu document available at url \a url. */
2790 
2791 bool
open(QUrl url,bool inNewWindow,bool maybeInBrowser)2792 QDjView::open(QUrl url, bool inNewWindow, bool maybeInBrowser)
2793 {
2794   QDjVuNetDocument *doc = new QDjVuNetDocument(true);
2795   connect(doc, SIGNAL(error(QString,QString,int)),
2796           errorDialog, SLOT(error(QString,QString,int)));
2797   connect(doc, SIGNAL(authRequired(QString,QString&,QString&)),
2798           this, SLOT(authRequired(QString,QString&,QString&)) );
2799   connect(doc, SIGNAL(sslWhiteList(QString,bool&)),
2800           this, SLOT(sslWhiteList(QString,bool&)) );
2801   QUrl docurl = removeDjVuCgiArguments(url);
2802   doc->setUrl(&djvuContext, docurl);
2803   if (! (url.isValid() && doc->isValid()))
2804     {
2805       delete doc;
2806       addToErrorDialog(tr("Cannot open URL '%1'.").arg(url.toString()));
2807       raiseErrorDialog(QMessageBox::Critical, tr("Opening DjVu document"));
2808       return false;
2809     }
2810   new NetOpen(this, doc, url, inNewWindow, maybeInBrowser);
2811   return true;
2812 }
2813 
2814 
2815 void
sslWhiteList(QString why,bool & okay)2816 QDjView::sslWhiteList(QString why, bool &okay)
2817 {
2818 #if QT_VERSION >= 0x50000
2819   QString ewhy = why.toHtmlEscaped();
2820 #else
2821   QString ewhy = Qt::escape(why);
2822 #endif
2823   if (QMessageBox::question(this,
2824         tr("Certificate validation error - DjView", "dialog caption"),
2825         tr("<html> %1  Do you want to continue anyway? </html>").arg(ewhy),
2826         QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
2827     okay = true;
2828   else
2829     closeDocument();
2830 }
2831 
2832 void
authRequired(QString why,QString & user,QString & pass)2833 QDjView::authRequired(QString why, QString &user, QString &pass)
2834 {
2835   QDjViewAuthDialog *dialog = new QDjViewAuthDialog(this);
2836   dialog->setInfo(why);
2837   dialog->setUser(user);
2838   dialog->setPass(pass);
2839   int result = dialog->exec();
2840   user = dialog->user();
2841   pass = dialog->pass();
2842   if (result != QDialog::Accepted)
2843     closeDocument();
2844 }
2845 
2846 
2847 /*! Reload the document afresh. */
2848 
2849 void
reloadDocument()2850 QDjView::reloadDocument()
2851 {
2852   QDjVuDocument *doc = document;
2853   QUrl url = getDecoratedUrl();
2854   if (doc && viewerMode >= STANDALONE && url.isValid())
2855     {
2856       closeDocument();
2857       ddjvu_cache_clear(djvuContext);
2858       // Opening files more efficiently
2859       QFileInfo file = url.toLocalFile();
2860       if (file.exists())
2861         {
2862           open(file.absoluteFilePath());
2863           parseDjVuCgiArguments(url);
2864           return;
2865         }
2866       // Opening the url could do it all in fact.
2867       open(url);
2868     }
2869 }
2870 
2871 
2872 void
maybeReloadDocument()2873 QDjView::maybeReloadDocument()
2874 {
2875   QDjVuDocument *doc = document;
2876   if (doc && !documentModified.isNull() && !documentFileName.isNull())
2877     {
2878       QFileInfo info(documentFileName);
2879       if (info.exists() && info.lastModified() > documentModified)
2880         QTimer::singleShot(0, this, SLOT(reloadDocument()));
2881     }
2882 }
2883 
2884 
2885 /*! Jump to the page numbered \a pageno. */
2886 
2887 void
goToPage(int pageno)2888 QDjView::goToPage(int pageno)
2889 {
2890   int pagenum = documentPages.size();
2891   if (!pagenum || !document)
2892     {
2893       pendingPage = QString("$%1").arg(pageno+1);
2894     }
2895   else
2896     {
2897       if (pageno>=0 && pageno<pagenum)
2898         {
2899           widget->setPage(pageno);
2900         }
2901       else
2902         {
2903           QString msg = tr("Cannot find page numbered: %1").arg(pageno+1);
2904           qWarning("%s",(const char*)msg.toLocal8Bit());
2905         }
2906       updateActionsLater();
2907     }
2908 }
2909 
2910 
2911 /*! Jump to the page named \a name.
2912   Names starting with the "#" character are interpreted
2913   using the same rules as hyperlinks. In particular,
2914   names of the form \a "#+n" and \a "#-n" express
2915   relative displacement from the current page
2916   or the page specified by argument \a from. */
2917 
2918 void
goToPage(QString name,int from)2919 QDjView::goToPage(QString name, int from)
2920 {
2921   int pagenum = documentPages.size();
2922   if (!pagenum || !document)
2923     {
2924       pendingPage = name;
2925     }
2926   else
2927     {
2928       int pageno = pageNumber(name, from);
2929       if (pageno >= 0 && pageno < pagenum)
2930         {
2931           widget->setPage(pageno);
2932         }
2933       else
2934         {
2935           QString msg = tr("Cannot find page named: %1").arg(name);
2936           qWarning("%s",(const char*)msg.toLocal8Bit());
2937         }
2938       updateActionsLater();
2939     }
2940 }
2941 
2942 
2943 /* Jump to the page named \a pagename
2944    and tries to position the specified point in
2945    the center of the window. Arguments \a px and \a py
2946    must be in range 0 to 1 indicating a fraction
2947    of the page width or height. */
2948 
2949 void
goToPosition(QString name,double px,double py)2950 QDjView::goToPosition(QString name, double px, double py)
2951 {
2952   int pagenum = documentPages.size();
2953   if (!pagenum || !document)
2954     {
2955       pendingPosition.clear();
2956       pendingPosition << px << py;
2957       if (! name.isEmpty())
2958         pendingPage = name;
2959     }
2960   else
2961     {
2962       int pageno = (name.isEmpty()) ? widget->page() : pageNumber(name);
2963       if (pageno < 0 || pageno >= pagenum)
2964         {
2965           QString msg = tr("Cannot find page named: %1").arg(name);
2966           qWarning("%s",(const char*)msg.toLocal8Bit());
2967         }
2968       else
2969         {
2970           QDjVuWidget::Position pos;
2971           pos.pageNo = pageno;
2972           pos.inPage = false;
2973           pos.posView = QPoint(0,0);
2974           pos.posPage = QPoint(0,0);
2975           pos.hAnchor = qBound(0, (int)(px*100), 100);
2976           pos.vAnchor = qBound(0, (int)(py*100), 100);
2977           widget->setPosition(pos, widget->rect().center());
2978         }
2979       updateActionsLater();
2980     }
2981 }
2982 
2983 
2984 
2985 /*! Add a message to the error dialog. */
2986 
2987 void
addToErrorDialog(QString message)2988 QDjView::addToErrorDialog(QString message)
2989 {
2990   errorDialog->error(message, __FILE__, __LINE__);
2991 }
2992 
2993 
2994 /*! Show the error dialog. */
2995 
2996 void
raiseErrorDialog(QMessageBox::Icon icon,QString caption)2997 QDjView::raiseErrorDialog(QMessageBox::Icon icon, QString caption)
2998 {
2999   errorDialog->prepare(icon, caption);
3000   errorDialog->show();
3001   errorDialog->raise();
3002   errorDialog->activateWindow();
3003 }
3004 
3005 
3006 /*! Show the error dialog and wait until the user clicks "OK". */
3007 
3008 int
execErrorDialog(QMessageBox::Icon icon,QString caption)3009 QDjView::execErrorDialog(QMessageBox::Icon icon, QString caption)
3010 {
3011   errorDialog->prepare(icon, caption);
3012   return errorDialog->exec();
3013 }
3014 
3015 
3016 /*! Set the content of the page box in the status bar. */
3017 
3018 void
setPageLabelText(QString s)3019 QDjView::setPageLabelText(QString s)
3020 {
3021   QLabel *m = pageLabel;
3022   m->setText(s);
3023   m->setMinimumWidth(qMax(m->minimumWidth(), m->sizeHint().width()));
3024 }
3025 
3026 
3027 /*! Set the content of the mouse box in the status bar. */
3028 
3029 void
setMouseLabelText(QString s)3030 QDjView::setMouseLabelText(QString s)
3031 {
3032   QLabel *m = mouseLabel;
3033   m->setText(s);
3034   m->setMinimumWidth(qMax(m->minimumWidth(), m->sizeHint().width()));
3035 }
3036 
3037 
3038 /*! Display transient message in the status bar.
3039   This also emits signal \a pluginStatusMessage
3040   to mirror the message in the browser status bar. */
3041 
3042 void
statusMessage(QString message)3043 QDjView::statusMessage(QString message)
3044 {
3045   if (message.isEmpty())
3046     statusBar->clearMessage();
3047   else
3048     statusBar->showMessage(message);
3049   emit pluginStatusMessage(message);
3050 }
3051 
3052 
3053 /*! Returns a bitmask of the visible sidebar tabs. */
3054 
3055 int
visibleSideBarTabs()3056 QDjView::visibleSideBarTabs()
3057 {
3058   int tabs = 0;
3059   if (! thumbnailDock->isHidden())
3060     tabs |= 1;
3061   if (! outlineDock->isHidden())
3062     tabs |= 2;
3063   if (! findDock->isHidden())
3064     tabs |= 4;
3065   return tabs;
3066 }
3067 
3068 
3069 /*! Returns a bitmask of the hidden sidebar tabs. */
3070 
3071 int
hiddenSideBarTabs()3072 QDjView::hiddenSideBarTabs()
3073 {
3074   int tabs = 0;
3075   if (thumbnailDock->isHidden())
3076     tabs |= 1;
3077   if (outlineDock->isHidden())
3078     tabs |= 2;
3079   if (findDock->isHidden())
3080     tabs |= 4;
3081   return tabs;
3082 }
3083 
3084 
3085 /*! Change the position and the visibility of
3086     the sidebars specified by bitmask \a tabs. */
3087 
3088 bool
showSideBar(Qt::DockWidgetAreas areas,int tabs)3089 QDjView::showSideBar(Qt::DockWidgetAreas areas, int tabs)
3090 {
3091   QList<QDockWidget*> allDocks;
3092   allDocks << thumbnailDock << outlineDock << findDock;
3093   // find first tab in desired area
3094   QDockWidget *first = 0;
3095   foreach(QDockWidget *w, allDocks)
3096     if (!first && !w->isHidden() && !w->isFloating())
3097       if (dockWidgetArea(w) & areas)
3098         first = w;
3099   // find relevant docks
3100   QList<QDockWidget*> docks;
3101   if (tabs & 1)
3102     docks << thumbnailDock;
3103   if (tabs & 2)
3104     docks << outlineDock;
3105   if (tabs & 4)
3106     docks << findDock;
3107   // hide all
3108   foreach(QDockWidget *w, docks)
3109     w->hide();
3110   if (areas)
3111     {
3112       Qt::DockWidgetArea area = Qt::LeftDockWidgetArea;
3113       if (areas & Qt::LeftDockWidgetArea)
3114         area = Qt::LeftDockWidgetArea;
3115       else if (areas & Qt::RightDockWidgetArea)
3116         area = Qt::RightDockWidgetArea;
3117       else if (areas & Qt::TopDockWidgetArea)
3118         area = Qt::TopDockWidgetArea;
3119       else if (areas & Qt::BottomDockWidgetArea)
3120         area = Qt::BottomDockWidgetArea;
3121       foreach(QDockWidget *w, docks)
3122         {
3123           w->show();
3124           if (w->isFloating())
3125             w->setFloating(false);
3126           if (! (areas & dockWidgetArea(w)))
3127             {
3128               removeDockWidget(w);
3129               addDockWidget(area, w);
3130               if (first)
3131                 tabifyDockWidget(first, w);
3132               else
3133                 first = w;
3134             }
3135           w->show();
3136           w->raise();
3137         }
3138     }
3139   // Okay
3140   return true;
3141 }
3142 
3143 
3144 /*! Change the position and composition of the sidebar.
3145   String \a args contains comma separated keywords:
3146   \a left, \a right, \a top, \a bottom, \a yes, or \a no,
3147   \a outline, \a bookmarks, \a thumbnails, \a search, etc.
3148   Error messages are added to string list \a errors. */
3149 
3150 bool
showSideBar(QString args,QStringList & errors)3151 QDjView::showSideBar(QString args, QStringList &errors)
3152 {
3153   bool yes = true;
3154   bool ret = true;
3155   int tabs = 0;
3156   Qt::DockWidgetAreas areas = zero(Qt::DockWidgetAreas);
3157   QString arg;
3158   foreach(arg, args.split(","))
3159     if (!args.isEmpty())
3160       {
3161         arg = arg.toLower();
3162         if (arg == "no" || arg == "false")
3163           yes = false;
3164         else if (arg == "left")
3165           areas |= Qt::LeftDockWidgetArea;
3166         else if (arg == "right")
3167           areas |= Qt::RightDockWidgetArea;
3168         else if (arg == "top")
3169           areas |= Qt::TopDockWidgetArea;
3170         else if (arg == "bottom")
3171           areas |= Qt::BottomDockWidgetArea;
3172         else if (arg == "thumbnails" || arg == "thumbnail")
3173           tabs |= 1;
3174         else if (arg == "outline" || arg == "bookmarks")
3175           tabs |= 2;
3176         else if (arg == "search" || arg == "find")
3177           tabs |= 4;
3178         else if (arg != "yes" && arg != "true") {
3179           errors << tr("Unrecognized sidebar options '%1'.").arg(arg);
3180           ret = false;
3181         }
3182       }
3183   if (!tabs)
3184     tabs = ~0;
3185   if (yes && !areas)
3186     areas = Qt::AllDockWidgetAreas;
3187   if (showSideBar(areas, tabs))
3188     return ret;
3189   return false;
3190 }
3191 
3192 
3193 /*! Overloaded version of \a showSideBar for convenience.
3194   This version makes great efforts to show docks with
3195   the exact layout they had before being hidden. */
3196 
3197 bool
showSideBar(bool show)3198 QDjView::showSideBar(bool show)
3199 {
3200   if (! show)
3201     {
3202       // save everything about dock geometry
3203       savedDockState = saveState();
3204     }
3205   else if (savedDockState.size() > 0)
3206     {
3207       // hack toolbar name to avoid restoring its state
3208       QString savedToolBarName = toolBar->objectName();
3209       toolBar->setObjectName(QString());
3210       restoreState(savedDockState);
3211       toolBar->setObjectName(savedToolBarName);
3212       savedDockState.clear();
3213     }
3214   // set visibility
3215   thumbnailDock->setVisible(show);
3216   outlineDock->setVisible(show);
3217   findDock->setVisible(show);
3218   if (! show)
3219     widget->setFocus(Qt::OtherFocusReason);
3220   return true;
3221 }
3222 
3223 
3224 /*! Pops up find sidebar */
3225 void
showFind()3226 QDjView::showFind()
3227 {
3228   if (! actionViewSideBar->isChecked())
3229     actionViewSideBar->activate(QAction::Trigger);
3230   findDock->show();
3231   findDock->raise();
3232   findWidget->selectAll();
3233   findWidget->takeFocus(Qt::ShortcutFocusReason);
3234 }
3235 
3236 
3237 bool
warnAboutPrintingRestrictions()3238 QDjView::warnAboutPrintingRestrictions()
3239 {
3240   if (prefs->restrictOverride && !printingAllowed)
3241     if (QMessageBox::warning(this,
3242                              tr("Print - DjView", "dialog caption"),
3243                              tr("<html> This file was served with "
3244                                 "printing restrictions. "
3245                                 "Do you want to print it anyway?</html>"),
3246                              QMessageBox::Yes | QMessageBox::Cancel,
3247                              QMessageBox::Cancel) != QMessageBox::Yes )
3248       return false;
3249   return true;
3250 }
3251 
3252 
3253 bool
warnAboutSavingRestrictions()3254 QDjView::warnAboutSavingRestrictions()
3255 {
3256   if (prefs->restrictOverride && !savingAllowed)
3257     if (QMessageBox::warning(this,
3258                              tr("Save - DjView", "dialog caption"),
3259                              tr("<html> This file was served with "
3260                                 "saving restrictions. "
3261                                 "Do you want to save it anyway?</html>"),
3262                              QMessageBox::Yes | QMessageBox::Cancel,
3263                              QMessageBox::Cancel) != QMessageBox::Yes )
3264       return false;
3265   return true;
3266 }
3267 
3268 
3269 /*! Pops up a print dialog */
3270 void
print()3271 QDjView::print()
3272 {
3273   QDjViewPrintDialog *pd = printDialog;
3274   if (! pd)
3275     {
3276       printDialog = pd = new QDjViewPrintDialog(this);
3277       pd->setAttribute(Qt::WA_DeleteOnClose);
3278       pd->setWindowTitle(tr("Print - DjView", "dialog caption"));
3279     }
3280   if (warnAboutPrintingRestrictions())
3281     {
3282       pd->show();
3283       pd->raise();
3284     }
3285 }
3286 
3287 
3288 
3289 /*! Pops up a save dialog */
3290 void
saveAs()3291 QDjView::saveAs()
3292 {
3293   QDjViewSaveDialog *sd = saveDialog;
3294   if (! sd)
3295     {
3296       saveDialog = sd = new QDjViewSaveDialog(this);
3297       sd->setAttribute(Qt::WA_DeleteOnClose);
3298       sd->setWindowTitle(tr("Save - DjView", "dialog caption"));
3299     }
3300   if (warnAboutSavingRestrictions())
3301     {
3302       sd->show();
3303       sd->raise();
3304     }
3305 }
3306 
3307 
3308 /*! Pops up export dialog */
3309 void
exportAs()3310 QDjView::exportAs()
3311 {
3312   QDjViewExportDialog *sd = exportDialog;
3313   if (! sd)
3314     {
3315       exportDialog = sd = new QDjViewExportDialog(this);
3316       sd->setAttribute(Qt::WA_DeleteOnClose);
3317       sd->setWindowTitle(tr("Export - DjView", "dialog caption"));
3318     }
3319   if (warnAboutSavingRestrictions())
3320     {
3321       sd->show();
3322       sd->raise();
3323     }
3324 }
3325 
3326 
3327 
3328 /*! Start searching string \a find.
3329     String might be terminated with a slash
3330     followed by letters "w" (words only),
3331     "W" (not words only), "c" (case sensitive),
3332     or "C" (case insentitive). */
3333 
3334 void
find(QString find)3335 QDjView::find(QString find)
3336 {
3337   if (! find.isEmpty())
3338     {
3339       QRegExp options("/[wWcCrR]*$");
3340       if (find.contains(options))
3341         {
3342           for (int i=find.lastIndexOf("/"); i<find.size(); i++)
3343             {
3344               int c = find[i].toLatin1();
3345               if (c == 'c')
3346                 findWidget->setCaseSensitive(true);
3347               else if (c == 'C')
3348                 findWidget->setCaseSensitive(false);
3349               else if (c == 'w')
3350                 findWidget->setWordOnly(true);
3351               else if (c == 'W')
3352                 findWidget->setWordOnly(false);
3353               else if (c == 'r')
3354                 findWidget->setRegExpMode(true);
3355               else if (c == 'R')
3356                 findWidget->setRegExpMode(false);
3357             }
3358           find = find.remove(options);
3359         }
3360       findWidget->setText(find);
3361     }
3362   findWidget->findNext();
3363 }
3364 
3365 
3366 
3367 
3368 
3369 
3370 // -----------------------------------
3371 // UTILITIES
3372 
3373 
3374 /*! \fn QDjView::getDjVuWidget()
3375   Return the \a QDjVuWidget object managed by this window. */
3376 
3377 /*! \fn QDjView::getErrorDialog()
3378   Return the error dialog for this window. */
3379 
3380 /*! \fn QDjView::getDocument()
3381   Return the currently displayed \a QDjVuDocument. */
3382 
3383 /*! \fn QDjView::getDocumentFileName()
3384   Return the filename of the currently displayed \a QDjVuDocument. */
3385 
3386 /*! \fn QDjView::getDocumentUrl()
3387   Return the url of the currently displayed \a QDjVuDocument. */
3388 
3389 /*! \fn QDjView::getViewerMode
3390   Return the current viewer mode. */
3391 
3392 
3393 
3394 /*! Return an url that encodes the current view and page. */
3395 
3396 QUrl
getDecoratedUrl()3397 QDjView::getDecoratedUrl()
3398 {
3399   QUrl url = removeDjVuCgiArguments(documentUrl);
3400   QPoint center = widget->rect().center();
3401   QDjVuWidget::Position pos = widget->positionWithClosestAnchor(center);
3402   int pageNo = pos.pageNo;
3403   if (url.isValid() && pageNo>=0 && pageNo<pageNum())
3404     {
3405       QueryItems items = urlQueryItems(url);
3406       addQueryItem(items, "djvuopts", QString());
3407       QList<ddjvu_fileinfo_t> &dp = documentPages;
3408       QString pagestr = QString("%1").arg(pageNo+1);
3409       if (hasNumericalPageTitle && pageNo<documentPages.size())
3410         // Only use page ids when confusions are possible.
3411         // Always using page ids triggers bugs in the celartem plugin :-(
3412         pagestr = QString::fromUtf8(dp[pageNo].id);
3413       addQueryItem(items, "page", pagestr);
3414       int rotation = widget->rotation();
3415       if (rotation)
3416         addQueryItem(items, "rotate", QString::number(90 * rotation));
3417       QString zoom = getArgument("zoom");
3418       if (zoom.isEmpty())
3419         zoom = QString::number(widget->zoomFactor());
3420       addQueryItem(items, "zoom", zoom);
3421       double ha = pos.hAnchor / 100.0;
3422       double va = pos.vAnchor / 100.0;
3423       addQueryItem(items, "showposition", QString("%1,%2").arg(ha).arg(va));
3424       urlSetQueryItems(url, items);
3425     }
3426   return url;
3427 }
3428 
3429 
3430 /*! Return the base name of the current file. */
3431 
3432 QString
getShortFileName()3433 QDjView::getShortFileName()
3434 {
3435   if (! documentFileName.isEmpty())
3436     return QFileInfo(documentFileName).fileName();
3437   else if (! documentUrl.isEmpty())
3438     return documentUrl.path().section('/', -1);
3439   return QString();
3440 }
3441 
3442 
3443 /*! Return the number of pages in the document.
3444   This function returns zero when called before
3445   fully decoding the document header. */
3446 
3447 int
pageNum(void)3448 QDjView::pageNum(void)
3449 {
3450   return documentPages.size();
3451 }
3452 
3453 
3454 /*! Return a name for page \a pageno. */
3455 
3456 QString
pageName(int pageno,bool titleonly)3457 QDjView::pageName(int pageno, bool titleonly)
3458 {
3459   // obtain page title
3460   if (pageno>=0 && pageno<documentPages.size())
3461     if ( documentPages[pageno].title )
3462       return QString::fromUtf8(documentPages[pageno].title);
3463   if (titleonly)
3464     return QString();
3465   // generate a name from the page number
3466   //// if (hasNumericalPageTitle)
3467   ////  return QString("$%1").arg(pageno + 1);
3468   return QString("%1").arg(pageno + 1);
3469 }
3470 
3471 
3472 /*! Return a page number for the page named \a name.
3473   It first searches a page whose ID matches X.
3474   Otherwise, if X has the form +N or -N where N is a number,
3475   this indicates a displacement relative to the current page.
3476   Otherwise, it searches a page with TITLE X starting
3477   from the current page and wrapping around.
3478   Otherwise, if X is numerical and in range, this is the page number.
3479   Otherwise, it searches a page whose NAME matches X.
3480 */
3481 
3482 int
pageNumber(QString name,int from)3483 QDjView::pageNumber(QString name, int from)
3484 {
3485   int pagenum = documentPages.size();
3486   if (pagenum <= 0)
3487     return -1;
3488   // First search an exact page id match
3489   QByteArray utf8Name = name.toUtf8();
3490   for (int i=0; i<pagenum; i++)
3491     if (documentPages[i].id &&
3492         !strcmp(utf8Name, documentPages[i].id))
3493       return i;
3494   // Then interpret the syntaxes +n, -n.
3495   // Also recognizes $n as an ordinal page number (obsolete)
3496   if (from < 0)
3497     from = widget->page();
3498   if (from < pagenum && name.contains(QRegExp("^[-+$]\\d+$")) )
3499     {
3500       int num = name.mid(1).toInt();
3501       if (name[0]=='+')
3502         num = from + 1 + num;
3503       else if (name[0]=='-')
3504         num = from + 1 - num;
3505       return qBound(1, num, pagenum) - 1;
3506     }
3507   // Then search a matching page title starting
3508   // from the current page and wrapping around
3509   for (int i=from; i<pagenum; i++)
3510     if (documentPages[i].title &&
3511         ! strcmp(utf8Name, documentPages[i].title))
3512       return i;
3513   for (int i=0; i<from; i++)
3514     if (documentPages[i].title &&
3515         ! strcmp(utf8Name, documentPages[i].title))
3516       return i;
3517   // Then process a number in range [1..pagenum]
3518   if (name.contains(QRegExp("^\\d+$")))
3519     return qBound(1, name.toInt(), pagenum) - 1;
3520   // Otherwise search page names in the unlikely
3521   // case they are different from the page ids
3522   for (int i=0; i<pagenum; i++)
3523     if (documentPages[i].name &&
3524         !strcmp(utf8Name, documentPages[i].name))
3525       return i;
3526   // Compatibility with unknown viewers:
3527   // If name contains space, remove all spaces, and try again
3528   if (name.contains(" "))
3529     return pageNumber(name.replace(" ", ""));
3530   // Give up
3531   return -1;
3532 }
3533 
3534 
3535 /*! Create another main window with the same
3536   contents, zoom and position as the current one. */
3537 
3538 QDjView*
copyWindow(bool openDocument)3539 QDjView::copyWindow(bool openDocument)
3540 {
3541   // update preferences
3542   updatePreferences();
3543   // create new window
3544   QDjView *other = new QDjView(djvuContext, STANDALONE);
3545   QDjVuWidget *otherWidget = other->widget;
3546   other->setAttribute(Qt::WA_DeleteOnClose);
3547   // copy window geometry
3548   if (! (windowState() & unusualWindowStates))
3549     {
3550       other->resize( size() );
3551       other->toolBar->setVisible(!toolBar->isHidden());
3552       other->statusBar->setVisible(!statusBar->isHidden());
3553       other->restoreState(saveState());
3554     }
3555   // copy essential properties
3556   otherWidget->setDisplayMode( widget->displayMode() );
3557   otherWidget->setContinuous( widget->continuous() );
3558   otherWidget->setSideBySide( widget->sideBySide() );
3559   otherWidget->setCoverPage( widget->coverPage() );
3560   otherWidget->setRotation( widget->rotation() );
3561   otherWidget->setZoom( widget->zoom() );
3562   // copy document
3563   if (document && openDocument)
3564     {
3565       other->open(document);
3566       other->documentFileName = documentFileName;
3567       other->documentUrl = documentUrl;
3568       // copy position
3569       bool saved = otherWidget->animationEnabled();
3570       otherWidget->enableAnimation(false);
3571       otherWidget->setPosition( widget->position() );
3572       otherWidget->enableAnimation(saved);
3573     }
3574   return other;
3575 }
3576 
3577 
3578 /*! Save \a text info the specified file.
3579   When argument \a filename is omitted,
3580   a file dialog is presented to the user. */
3581 
3582 bool
saveTextFile(QString text,QString filename)3583 QDjView::saveTextFile(QString text, QString filename)
3584 {
3585   // obtain filename
3586   if (filename.isEmpty())
3587     {
3588       QString filters;
3589       filters += tr("Text files", "save filter")+" (*.txt);;";
3590       filters += tr("All files", "save filter") + " (*)";
3591       QString caption = tr("Save Text - DjView", "dialog caption");
3592       filename = QFileDialog::getSaveFileName(this, caption, "", filters);
3593       if (filename.isEmpty())
3594         return false;
3595     }
3596   // open file
3597   errno = 0;
3598   QFile file(filename);
3599   if (! file.open(QIODevice::WriteOnly|QIODevice::Truncate))
3600     {
3601       QString message = file.errorString();
3602 #if HAVE_STRERROR
3603       if (file.error() == QFile::OpenError && errno > 0)
3604         message = strerror(errno);
3605 #endif
3606       QMessageBox::critical(this,
3607                             tr("Error - DjView", "dialog caption"),
3608                             tr("Cannot write file '%1'.\n%2.")
3609                             .arg(QFileInfo(filename).fileName())
3610                             .arg(message) );
3611       file.remove();
3612       return false;
3613     }
3614   // save text in current locale encoding
3615   QTextStream(&file) << text;
3616   return true;
3617 }
3618 
3619 
3620 /*! Save \a image info the specified file
3621   using a format derived from the filename suffix.
3622   When argument \a filename is omitted,
3623   a file dialog is presented to the user. */
3624 
3625 bool
saveImageFile(QImage image,QString filename)3626 QDjView::saveImageFile(QImage image, QString filename)
3627 {
3628   // obtain filename with suitable suffix
3629   if (filename.isEmpty())
3630     {
3631       QString filters;
3632       QList<QByteArray> formats = QImageWriter::supportedImageFormats();
3633       foreach(QByteArray format, formats)
3634         filters += tr("%1 files (*.%2);;", "save image filter")
3635         .arg(QString(format).toUpper())
3636         .arg(QString(format).toLower());
3637       filters += tr("All files", "save filter") + " (*)";
3638       QString caption = tr("Save Image - DjView", "dialog caption");
3639       filename = QFileInfo(getShortFileName()).baseName();
3640       if (formats.size())
3641         filename += "." + QString(formats[0]);
3642       filename = QFileDialog::getSaveFileName(this, caption,
3643                                               filename, filters);
3644       if (filename.isEmpty())
3645         return false;
3646     }
3647   // suffix
3648   QString suffix = QFileInfo(filename).suffix();
3649   if (suffix.isEmpty())
3650     {
3651       QMessageBox::critical(this,
3652                             tr("Error - DjView", "dialog caption"),
3653                             tr("Cannot determine file format.\n"
3654                                "Filename '%1' has no suffix.")
3655                             .arg(QFileInfo(filename).fileName()) );
3656       return false;
3657     }
3658   // write
3659   errno = 0;
3660   QFile file(filename);
3661   QImageWriter writer(&file, suffix.toLatin1());
3662   if (! writer.write(image))
3663     {
3664       QString message = file.errorString();
3665       if (writer.error() == QImageWriter::UnsupportedFormatError)
3666         message = tr("Image format %1 not supported.").arg(suffix.toUpper());
3667 #if HAVE_STRERROR
3668       else if (file.error() == QFile::OpenError && errno > 0)
3669         message = strerror(errno);
3670 #endif
3671       QMessageBox::critical(this,
3672                             tr("Error - DjView", "dialog caption"),
3673                             tr("Cannot write file '%1'.\n%2.")
3674                             .arg(QFileInfo(filename).fileName())
3675                             .arg(message) );
3676       file.remove();
3677       return false;
3678     }
3679   return true;
3680 }
3681 
3682 
3683 /*! Start the preferred browser on \a url. */
3684 
3685 bool
startBrowser(QUrl url)3686 QDjView::startBrowser(QUrl url)
3687 {
3688   // Determine browsers to try
3689   QStringList browsers;
3690 #ifdef Q_OS_DARWIN
3691   browsers << "open";
3692 #endif
3693 #ifdef Q_OS_WIN32
3694 # if QT_VERSION < 0x40200
3695   browsers << "firefox.exe";
3696   browsers << "iexplore.exe";
3697 # endif
3698 #endif
3699 #ifdef Q_OS_UNIX
3700   browsers << "x-www-browser" << "firefox" << "konqueror";
3701   const char *varBrowser = ::getenv("BROWSER");
3702   if (varBrowser && varBrowser[0])
3703     browsers = QFile::decodeName(varBrowser).split(":") + browsers;
3704   browsers << "sensible-browser";
3705   const char *varPath = ::getenv("PATH");
3706   QStringList path;
3707   if (varPath && varPath[0])
3708     path = QFile::decodeName(varPath).split(":");
3709   path << ".";
3710 #endif
3711   if (! prefs->browserProgram.isEmpty())
3712     browsers.prepend(prefs->browserProgram);
3713   // Try them
3714   QStringList args(url.toEncoded());
3715   foreach (QString browser, browsers)
3716     {
3717 #ifdef Q_OS_UNIX
3718       int i;
3719       for (i=0; i<path.size(); i++)
3720         if (QFileInfo(QDir(path[i]), browser).exists())
3721           break;
3722       if (i >= path.size())
3723         continue;
3724 #endif
3725       if (QProcess::startDetached(browser, args))
3726         return true;
3727     }
3728   // fallback
3729 #if QT_VERSION >= 0x40200
3730   return QDesktopServices::openUrl(url);
3731 #else
3732   return false;
3733 #endif
3734 }
3735 
3736 
3737 
3738 /*! \fn QDjView::documentClosed(QDjVuDocument *doc)
3739   This signal is emitted when clearing the current document.
3740   Argument \a doc is the previous document. */
3741 
3742 /*! \fn QDjView::documentOpened(QDjVuDocument *doc)
3743   This signal is emitted when opening a new document \a doc. */
3744 
3745 /*! \fn QDjView::documentReady(QDjVuDocument *doc)
3746   This signal is emitted when the document structure
3747   for the current document \a doc is known. */
3748 
3749 /*! \fn QDjView::pluginStatusMessage(QString message = QString())
3750   This signal is emitted when a message is displayed
3751   in the status bar. This is useful for plugins because
3752   the message must be replicated in the browser status bar. */
3753 
3754 /*! \fn QDjView::pluginGetUrl(QUrl url, QString target)
3755   This signal is emitted when the user clicks an external
3756   hyperlink while the viewer is in plugin mode. */
3757 
3758 
3759 
3760 // -----------------------------------
3761 // OVERRIDES
3762 
3763 
3764 void
closeEvent(QCloseEvent * event)3765 QDjView::closeEvent(QCloseEvent *event)
3766 {
3767   // Close document.
3768   closeDocument();
3769   // Save options.
3770   //  after closing the document in order to
3771   //  avoid saving document defined settings.
3772   updatePreferences();
3773   prefs->saveRemembered();
3774   // continue closing the window
3775   event->accept();
3776 }
3777 
3778 
3779 void
dragEnterEvent(QDragEnterEvent * event)3780 QDjView::dragEnterEvent(QDragEnterEvent *event)
3781 {
3782   if (viewerMode >= STANDALONE)
3783     {
3784       const QMimeData *data = event->mimeData();
3785       if (data->hasUrls() && data->urls().size()==1)
3786         event->accept();
3787     }
3788 }
3789 
3790 
3791 void
dragMoveEvent(QDragMoveEvent * event)3792 QDjView::dragMoveEvent(QDragMoveEvent *event)
3793 {
3794 #ifdef Q_OS_WIN
3795   QMainWindow::dragMoveEvent(event);
3796 #else
3797   QRect rect = centralWidget()->geometry();
3798   if (event->answerRect().intersects(rect))
3799     event->accept(rect);
3800   else
3801     event->ignore();
3802 #endif
3803 }
3804 
3805 
3806 void
dropEvent(QDropEvent * event)3807 QDjView::dropEvent(QDropEvent *event)
3808 {
3809   if (viewerMode >= STANDALONE)
3810     {
3811       const QMimeData *data = event->mimeData();
3812       if (data->hasUrls() && data->urls().size()==1)
3813         if (open(data->urls()[0]))
3814           {
3815             event->setDropAction(Qt::CopyAction);
3816             event->accept();
3817           }
3818     }
3819 }
3820 
3821 
3822 bool
eventFilter(QObject * watched,QEvent * event)3823 QDjView::eventFilter(QObject *watched, QEvent *event)
3824 {
3825   switch(event->type())
3826     {
3827     case QEvent::Show:
3828     case QEvent::Hide:
3829       if (qobject_cast<QDockWidget*>(watched))
3830         updateActionsLater();
3831       break;
3832     case QEvent::Leave:
3833       if ((watched == widget->viewport()) ||
3834           (qobject_cast<QDockWidget*>(watched)) )
3835         {
3836           mouseLabel->clear();
3837           textLabel->clear();
3838           statusMessage();
3839           return false;
3840         }
3841       break;
3842     case QEvent::Enter:
3843       if ((watched == widget->viewport()) ||
3844           (qobject_cast<QDockWidget*>(watched)) )
3845         maybeReloadDocument();
3846       break;
3847     case QEvent::StatusTip:
3848       if (qobject_cast<QMenu*>(watched))
3849         return QApplication::sendEvent(this, event);
3850       break;
3851     case QEvent::FocusIn:
3852       if (watched == pageCombo)
3853         if (widget && documentPages.size())
3854           pageCombo->setEditText(pageName(widget->page()));
3855       break;
3856     default:
3857       break;
3858     }
3859   return false;
3860 }
3861 
3862 
3863 // -----------------------------------
3864 // PROTECTED SIGNALS
3865 
3866 
3867 void
info(QString message)3868 QDjView::info(QString message)
3869 {
3870   if (! message.contains(QRegExp("^\\[\\d+")))
3871     statusBar->showMessage(message, 2000);
3872   qWarning("INFO: %s", (const char*)message.toLocal8Bit());
3873 }
3874 
3875 
3876 void
error(QString message,QString filename,int lineno)3877 QDjView::error(QString message, QString filename, int lineno)
3878 {
3879   // just for calling qWarning
3880   filename = filename.section("/", -1);
3881   if (filename.isEmpty())
3882     qWarning("ERROR: %s", (const char*)message.toLocal8Bit());
3883   else
3884     qWarning("ERROR (%s:%d): %s", (const char*)filename.toLocal8Bit(),
3885              lineno, (const char*)message.toLocal8Bit() );
3886 }
3887 
3888 
3889 void
errorCondition(int pageno)3890 QDjView::errorCondition(int pageno)
3891 {
3892   QString message;
3893   if (pageno >= 0)
3894     message = tr("Cannot decode page %1.").arg(pageno+1);
3895   else
3896     message = tr("Cannot decode document.");
3897   addToErrorDialog(message);
3898   raiseErrorDialog(QMessageBox::Warning, tr("Decoding DjVu document"));
3899 }
3900 
3901 
3902 void
docinfo()3903 QDjView::docinfo()
3904 {
3905   if (document && documentPages.size()==0 &&
3906       ddjvu_document_decoding_status(*document)==DDJVU_JOB_OK)
3907     {
3908       // Obtain information about pages.
3909       int n = ddjvu_document_get_pagenum(*document);
3910       int m = ddjvu_document_get_filenum(*document);
3911       for (int i=0; i<m; i++)
3912         {
3913           ddjvu_fileinfo_t info;
3914           if (ddjvu_document_get_fileinfo(*document, i, &info)!=DDJVU_JOB_OK)
3915             qWarning("Internal(docinfo): ddjvu_document_get_fileinfo fails.");
3916           if (info.title && info.name && !strcmp(info.title, info.name))
3917             info.title = 0;  // clear title if equal to name.
3918           if (info.type == 'P')
3919             documentPages << info;
3920         }
3921       if (documentPages.size() != n)
3922         qWarning("Internal(docinfo): inconsistent number of pages.");
3923 
3924       // Check for numerical title
3925       hasNumericalPageTitle = false;
3926       QRegExp allNumbers("\\d+");
3927       for (int i=0; i<n; i++)
3928         if (documentPages[i].title &&
3929             allNumbers.exactMatch(QString::fromUtf8(documentPages[i].title)) )
3930           hasNumericalPageTitle = true;
3931 
3932       // Fill page combo
3933       fillPageCombo(pageCombo);
3934 
3935       // Continue processing a bit later
3936       QTimer::singleShot(0, this, SLOT(docinfoExtra()));
3937     }
3938 }
3939 
3940 void
docinfoExtra()3941 QDjView::docinfoExtra()
3942 {
3943   if (document && documentPages.size()>0)
3944     {
3945       bool saved = widget->animationEnabled();
3946       widget->enableAnimation(false);
3947       performPending();
3948       widget->enableAnimation(saved);
3949       updateActionsLater();
3950       emit documentReady(document);
3951     }
3952 }
3953 
3954 
3955 /*! Set options that could not be set before fully decoding
3956   the document header. For instance, calling \a QDjView::goToPage
3957   before decoding the djvu document header simply stores
3958   the page number in a well known variable. Function \a performPending
3959   peforms the page change as soon as the document information
3960   is available. */
3961 
3962 
3963 void
performPending()3964 QDjView::performPending()
3965 {
3966   if (! documentPages.isEmpty())
3967     {
3968       if (! pendingPosition.isEmpty())
3969         {
3970           if (pendingPosition.size() == 2)
3971             goToPosition(pendingPage, pendingPosition[0], pendingPosition[1]);
3972           pendingPosition.clear();
3973           pendingPage.clear();
3974         }
3975       if (! pendingPage.isNull())
3976         {
3977           goToPage(pendingPage);
3978           pendingPage.clear();
3979         }
3980       if (pendingHilite.size() > 0)
3981         {
3982           StringPair pair;
3983           foreach(pair, pendingHilite)
3984             {
3985               int x, y, w, h;
3986               QColor color = Qt::blue;
3987               int pageno = widget->page();
3988               if (! pair.first.isEmpty())
3989                 pageno = pageNumber(pair.first);
3990               if (pageno < pageNum() && pageno >= 0
3991                     && parse_highlight(pair.second, x, y, w, h, color)
3992                     && w > 0 && h > 0 )
3993                 {
3994                   color.setAlpha(96);
3995                   widget->addHighlight(pageno, x, y, w, h, color);
3996                 }
3997             }
3998           pendingHilite.clear();
3999         }
4000       if (pendingFind.size() > 0)
4001         {
4002           find(pendingFind);
4003           pendingFind.clear();
4004         }
4005     }
4006   performPendingScheduled = false;
4007 }
4008 
4009 
4010 /*! Schedule a call to \a QDjView::performPending(). */
4011 
4012 void
performPendingLater()4013 QDjView::performPendingLater()
4014 {
4015   if (! performPendingScheduled)
4016     {
4017       performPendingScheduled = true;
4018       QTimer::singleShot(0, this, SLOT(performPending()));
4019     }
4020 }
4021 
4022 
4023 void
pointerPosition(const Position & pos,const PageInfo & info)4024 QDjView::pointerPosition(const Position &pos, const PageInfo &info)
4025 {
4026   // setup page label
4027   QString p = "";
4028   QString m = "";
4029   if (pos.pageNo >= 0)
4030     {
4031       p = tr(" P%1/%2 %3x%4 %5dpi ")
4032         .arg(pos.pageNo+1).arg(documentPages.size())
4033         .arg(info.width).arg(info.height).arg(info.dpi);
4034     }
4035   if (pos.inPage || !info.segment.isEmpty())
4036     {
4037       if (info.segment.isEmpty())
4038         m = tr(" x=%1 y=%2 ")
4039           .arg(pos.posPage.x())
4040           .arg(pos.posPage.y());
4041       else
4042         m = tr(" %3x%4+%1+%2 ")
4043           .arg(info.segment.left())
4044           .arg(info.segment.top())
4045           .arg(info.segment.width())
4046           .arg(info.segment.height());
4047     }
4048   setPageLabelText(p);
4049   setMouseLabelText(m);
4050   if (textLabel->isVisibleTo(this))
4051     {
4052       textLabelRect = info.selected;
4053       textLabelTimer->start(25);
4054     }
4055 }
4056 
4057 
4058 void
updateTextLabel()4059 QDjView::updateTextLabel()
4060 {
4061   textLabel->clear();
4062   textLabel->setWordWrap(false);
4063   textLabel->setTextFormat(Qt::PlainText);
4064   if (document && textLabel->isVisibleTo(this))
4065     {
4066       QString text;
4067       QFontMetrics m(textLabel->font());
4068       QString lb = QString::fromUtf8(" \302\253 ");
4069       QString rb = QString::fromUtf8(" \302\273 ");
4070       int w = textLabel->width()-2*textLabel->frameWidth()-swidth(m,lb+rb+"MM");
4071       if (! textLabelRect.isEmpty())
4072         {
4073           text = widget->getTextForRect(textLabelRect);
4074           text = text.replace(QRegExp("\\s+"), " ");
4075           text = m.elidedText(text, Qt::ElideMiddle, w);
4076         }
4077       else
4078         {
4079           QString results[3];
4080           if (widget->getTextForPointer(results))
4081             {
4082               results[0] = results[0].simplified();
4083               results[1] = results[1].simplified();
4084               results[2] = results[2].simplified();
4085               if (results[0].size() || results[2].size())
4086                 results[1] = " [" + results[1] + "] ";
4087               int r1 = swidth(m,results[1]);
4088               int r2 = swidth(m,results[2]);
4089               int r0 = qMax(0, qMax( (w-r1)/2, (w-r1-r2) ));
4090               text = m.elidedText(results[0], Qt::ElideLeft, r0) + results[1];
4091               text = m.elidedText(text+results[2], Qt::ElideRight, w);
4092             }
4093         }
4094       text = text.trimmed();
4095       if (text.size())
4096         textLabel->setText(lb + text + rb);
4097     }
4098 }
4099 
4100 
4101 void
pointerEnter(const Position &,miniexp_t)4102 QDjView::pointerEnter(const Position&, miniexp_t)
4103 {
4104   // Display information message about hyperlink
4105   QString link = widget->linkUrl();
4106   if (link.isEmpty())
4107     return;
4108   QString target = widget->linkTarget();
4109   if (target=="_self" || target=="_page")
4110     target.clear();
4111   QString message;
4112   if (link.startsWith("#") &&
4113       link.contains(QRegExp("^#[-+]\\d+$")) )
4114     {
4115       int n = link.mid(2).toInt();
4116       if (link[1]=='+')
4117         message = (n==1) ? tr("Go: 1 page forward.")
4118           : tr("Go: %n pages forward.", 0, n);
4119       else
4120         message = (n==1) ? tr("Go: 1 page backward.")
4121           : tr("Go: %n pages backward.", 0, n);
4122     }
4123   else if (link.startsWith("#$"))
4124     message = tr("Go: page %1.").arg(link.mid(2));
4125   else if (link.startsWith("#"))
4126     message = tr("Go: page %1.").arg(link.mid(1));
4127   else
4128     message = tr("Go: %1").arg(link);
4129   if (!target.isEmpty())
4130     message = message + tr(" (in other window.)");
4131 
4132   statusMessage(message);
4133 }
4134 
4135 
4136 void
pointerLeave(const Position &,miniexp_t)4137 QDjView::pointerLeave(const Position&, miniexp_t)
4138 {
4139   statusMessage();
4140 }
4141 
4142 
4143 void
pointerClick(const Position & pos,miniexp_t)4144 QDjView::pointerClick(const Position &pos, miniexp_t)
4145 {
4146   goToLink(widget->linkUrl(), widget->linkTarget(), pos.pageNo);
4147 }
4148 
4149 
4150 void
goToLink(QString link,QString target,int fromPage)4151 QDjView::goToLink(QString link, QString target, int fromPage)
4152 {
4153   bool inPlace = target.isEmpty() || target=="_self" || target=="_page";
4154   QUrl url = documentUrl;
4155   // Internal link
4156   if (link.startsWith("#"))
4157     {
4158       QString name = link.mid(1);
4159       if (inPlace)
4160         {
4161           goToPage(name, fromPage);
4162           return;
4163         }
4164       if (viewerMode >= STANDALONE)
4165         {
4166           QDjView *other = copyWindow();
4167           other->goToPage(name, fromPage);
4168           other->show();
4169           return;
4170         }
4171       // Construct url
4172       url = removeDjVuCgiArguments(url);
4173       QueryItems items = urlQueryItems(url);
4174       addQueryItem(items, "djvuopts", QString());
4175       int pageno = pageNumber(name, fromPage);
4176       if (pageno>=0 && pageno<=documentPages.size())
4177         addQueryItem(items, "page", QString::fromUtf8(documentPages[pageno].id));
4178       urlSetQueryItems(url, items);
4179     }
4180   else if (link.startsWith("?"))
4181     {
4182       if (inPlace)
4183         {
4184           foreach(QString opt, link.mid(1).split("&"))
4185             parseArgument(opt);
4186           return;
4187         }
4188       if (viewerMode >= STANDALONE)
4189         {
4190           QDjView *other = copyWindow();
4191           foreach(QString opt, link.mid(1).split("&"))
4192             other->parseArgument(opt);
4193           other->show();
4194           return;
4195         }
4196       // Construct url
4197       QUrl linkUrl = QUrl::fromEncoded("file://d/d" + link.toUtf8());
4198       url = removeDjVuCgiArguments(url);
4199       QueryItems items = urlQueryItems(url);
4200       addQueryItem(items, "djvuopts", "");
4201       QPair<QString,QString> pair;
4202       foreach(pair, urlQueryItems(linkUrl))
4203         addQueryItem(items, pair.first, pair.second);
4204       urlSetQueryItems(url, items);
4205     }
4206   else
4207     {
4208       // Decode url
4209       QUrl linkUrl = QUrl::fromEncoded(link.toUtf8());
4210       // Resolve url
4211       QueryItems empty;
4212       urlSetQueryItems(url, empty);
4213       url = url.resolved(linkUrl);
4214       urlSetQueryItems(url, urlQueryItems(linkUrl));
4215     }
4216   // Check url
4217   if (! url.isValid() || url.isRelative())
4218     {
4219       QString msg = tr("Cannot resolve link '%1'").arg(link);
4220       qWarning("%s", (const char*)msg.toLocal8Bit());
4221       return;
4222     }
4223   // Signal browser
4224   if (viewerMode < STANDALONE)
4225     {
4226       emit pluginGetUrl(url, target);
4227       return;
4228     }
4229   // Standalone only: call open(QUrl,...) with adequate flags
4230   open(url, !inPlace, true);
4231 }
4232 
4233 
4234 void
pointerSelect(const QPoint & pointerPos,const QRect & rect)4235 QDjView::pointerSelect(const QPoint &pointerPos, const QRect &rect)
4236 {
4237   // Collect text
4238   QString text=widget->getTextForRect(rect);
4239   int l = text.size();
4240   int w = rect.width();
4241   int h = rect.height();
4242   QString s = tr("%n characters", 0, l);
4243   if (QApplication::clipboard()->supportsSelection())
4244     QApplication::clipboard()->setText(text, QClipboard::Selection);
4245   // Prepare menu
4246   QMenu *menu = new QMenu(this);
4247   QAction *copyText = menu->addAction(tr("Copy text (%1)").arg(s));
4248   QAction *saveText = menu->addAction(tr("Save text as..."));
4249   copyText->setEnabled(l>0);
4250   saveText->setEnabled(l>0);
4251   copyText->setStatusTip(tr("Copy text into the clipboard."));
4252   saveText->setStatusTip(tr("Save text into a file."));
4253   menu->addSeparator();
4254   QString copyImageString = tr("Copy image (%1x%2 pixels)").arg(w).arg(h);
4255   QAction *copyImage = menu->addAction(copyImageString);
4256   QAction *saveImage = menu->addAction(tr("Save image as..."));
4257   copyImage->setStatusTip(tr("Copy image into the clipboard."));
4258   saveImage->setStatusTip(tr("Save image into a file."));
4259   menu->addSeparator();
4260   QAction *zoom = menu->addAction(tr("Zoom to rectangle"));
4261   zoom->setStatusTip(tr("Zoom the selection to fit the window."));
4262   QAction *copyUrl = 0;
4263   QAction *copyMaparea = 0;
4264   if (prefs->advancedFeatures)
4265     {
4266       menu->addSeparator();
4267       copyUrl = menu->addAction(tr("Copy URL"));
4268       copyUrl->setStatusTip(tr("Save into the clipboard an URL that "
4269                                "highlights the selection."));
4270       copyMaparea = menu->addAction(tr("Copy Maparea"));
4271       copyMaparea->setStatusTip(tr("Save into the clipboard a maparea "
4272                                    "annotation expression for program "
4273                                    "djvused."));
4274     }
4275 
4276   // Make sure that status tips work (hack)
4277   menu->installEventFilter(this);
4278 
4279   // Execute menu
4280   QAction *action = menu->exec(pointerPos-QPoint(5,5), copyText);
4281   if (action == zoom)
4282     widget->zoomRect(rect);
4283   else if (action == copyText)
4284     QApplication::clipboard()->setText(text);
4285   else if (action == saveText)
4286     saveTextFile(text);
4287   else if (action == copyImage)
4288     QApplication::clipboard()->setImage(widget->getImageForRect(rect));
4289   else if (action == saveImage)
4290     saveImageFile(widget->getImageForRect(rect));
4291   else if (action && action == copyMaparea)
4292     {
4293       Position pos = widget->position(pointerPos);
4294       QRect seg = widget->getSegmentForRect(rect, pos.pageNo);
4295       if (! rect.isEmpty())
4296         {
4297           QString s = QString("(maparea \"url\"\n"
4298                               "         \"comment\"\n"
4299                               "         (rect %1 %2 %3 %4))")
4300             .arg(seg.left()).arg(seg.top())
4301             .arg(seg.width()).arg(seg.height());
4302           QApplication::clipboard()->setText(s);
4303         }
4304     }
4305   else if (action && action == copyUrl)
4306     {
4307       QUrl url = getDecoratedUrl();
4308       Position pos = widget->position(pointerPos);
4309       QRect seg = widget->getSegmentForRect(rect, pos.pageNo);
4310       if (url.isValid() && pos.pageNo>=0 && pos.pageNo<pageNum())
4311         {
4312           QueryItems items = urlQueryItems(url);
4313           if (! hasQueryItem(items, "djvuopts"))
4314             addQueryItem(items, "djvuopts", QString());
4315           QList<ddjvu_fileinfo_t> &dp = documentPages;
4316           if (! hasQueryItem(items, "page", false))
4317             if (pos.pageNo>=0 && pos.pageNo<documentPages.size())
4318               addQueryItem(items, "page", QString::fromUtf8(dp[pos.pageNo].id));
4319           if (! rect.isEmpty())
4320             addQueryItem(items, "highlight", QString("%1,%2,%3,%4")
4321                          .arg(seg.left()).arg(seg.top())
4322                          .arg(seg.width()).arg(seg.height()) );
4323           urlSetQueryItems(url, items);
4324           QApplication::clipboard()->setText(url.toString());
4325         }
4326     }
4327 
4328   // Cancel select mode.
4329   updateActionsLater();
4330   if (actionSelect->isChecked())
4331     {
4332       actionSelect->setChecked(false);
4333       performSelect(false);
4334     }
4335 }
4336 
4337 
4338 /*! Schedule a call to \a QDjView::updateActions(). */
4339 
4340 void
updateActionsLater()4341 QDjView::updateActionsLater()
4342 {
4343   if (! updateActionsScheduled)
4344     {
4345       updateActionsScheduled = true;
4346       QTimer::singleShot(0, this, SLOT(updateActions()));
4347     }
4348 }
4349 
4350 
4351 void
modeComboActivated(int index)4352 QDjView::modeComboActivated(int index)
4353 {
4354   int mode = modeCombo->itemData(index).toInt();
4355   widget->setDisplayMode((QDjVuWidget::DisplayMode)mode);
4356   widget->setFocus();
4357 }
4358 
4359 
4360 void
zoomComboActivated(int index)4361 QDjView::zoomComboActivated(int index)
4362 {
4363   int zoom = zoomCombo->itemData(index).toInt();
4364   widget->setZoom(zoom);
4365   updateActionsLater();
4366   widget->setFocus();
4367 }
4368 
4369 
4370 void
zoomComboEdited(void)4371 QDjView::zoomComboEdited(void)
4372 {
4373   bool okay;
4374   QString text = zoomCombo->lineEdit()->text();
4375   int zoom = text.replace(QRegExp("\\s*%?$"),"").trimmed().toInt(&okay);
4376   if (okay && zoom>0)
4377     widget->setZoom(zoom);
4378   updateActionsLater();
4379   if (okay)
4380     widget->setFocus();
4381 }
4382 
4383 
4384 void
pageComboActivated(int index)4385 QDjView::pageComboActivated(int index)
4386 {
4387   goToPage(pageCombo->itemData(index).toInt());
4388   updateActionsLater();
4389   widget->setFocus();
4390 }
4391 
4392 
4393 void
pageComboEdited(void)4394 QDjView::pageComboEdited(void)
4395 {
4396   QString data = pageCombo->lineEdit()->text().trimmed();
4397   int pagenum = documentPages.size();
4398   QRegExp pattern = QRegExp(QString("\\s*(\\d+)\\s*/\\s*%1\\s*").arg(pagenum));
4399   if (pattern.exactMatch(data))
4400     goToPage(qMax(0, pattern.cap(1).toInt() - 1));
4401   else
4402     goToPage(data);
4403   updateActionsLater();
4404   widget->setFocus();
4405 }
4406 
4407 
4408 
4409 
4410 // -----------------------------------
4411 // SIGNALS IMPLEMENTING ACTIONS
4412 
4413 
4414 
4415 void
performAbout(void)4416 QDjView::performAbout(void)
4417 {
4418   QStringList version;
4419 #if DDJVUAPI_VERSION >= 20
4420   version << QString(ddjvu_get_version_string());
4421 #endif
4422 #ifdef QT_VERSION
4423   version << QString("Qt-%1").arg(qVersion());
4424 #endif
4425   QString versioninfo = "";
4426   if (version.size() > 0)
4427     versioninfo = "(" + version.join(", ") + ")";
4428   QString html = tr8("<html>"
4429        "<h2>DjVuLibre DjView %1</h2>%2"
4430        "<p>"
4431        "Viewer for DjVu documents<br>"
4432        "<a href=%3>%3</a><br>"
4433        "Copyright \302\251 2006-- L\303\251on Bottou."
4434        "</p>"
4435        "<p align=justify><small>"
4436        "This program is free software. "
4437        "You can redistribute or modify it under the terms of the "
4438        "GNU General Public License as published "
4439        "by the Free Software Foundation. "
4440        "This program is distributed <i>without any warranty</i>. "
4441        "See the GNU General Public License for more details."
4442        "</small></p>"
4443        "</html>")
4444     .arg(QDjViewPrefs::versionString())
4445     .arg(versioninfo)
4446     .arg("http://djvu.sourceforge.net");
4447 
4448   QMessageBox::about(this, tr("About DjView"), html);
4449 }
4450 
4451 
4452 void
performNew(void)4453 QDjView::performNew(void)
4454 {
4455   if (viewerMode < STANDALONE)
4456     return;
4457   QDjView *other = copyWindow(false);
4458   other->show();
4459 }
4460 
4461 
4462 void
performOpen(void)4463 QDjView::performOpen(void)
4464 {
4465   if (viewerMode < STANDALONE)
4466     return;
4467   QString filters;
4468   filters += tr("DjVu files") + " (*.djvu *.djv);;";
4469   filters += tr("All files") + " (*)";
4470   QString caption = tr("Open - DjView", "dialog caption");
4471   QString dirname = QDir::currentPath();
4472   QDir dir = QFileInfo(documentFileName).absoluteDir();
4473   if (dir.exists() && !documentFileName.isEmpty())
4474     dirname = dir.absolutePath();
4475   QString fname;
4476   fname = QFileDialog::getOpenFileName(this, caption, dirname, filters);
4477   if (! fname.isEmpty())
4478     open(fname);
4479 }
4480 
4481 
4482 void
performOpenLocation(void)4483 QDjView::performOpenLocation(void)
4484 {
4485   if (viewerMode < STANDALONE)
4486     return;
4487   QString caption = tr("Open Location - DjView", "dialog caption");
4488   QString label = tr("Enter the URL of a DjVu document:");
4489   QString text = "http://";
4490   bool ok;
4491   QUrl url  = QInputDialog::getText(this, caption, label,
4492                                     QLineEdit::Normal, text, &ok);
4493   if (ok && url.isValid())
4494 	open(url);
4495 }
4496 
4497 
4498 void
performInformation(void)4499 QDjView::performInformation(void)
4500 {
4501   if (! documentPages.size())
4502     return;
4503   if (! infoDialog)
4504     infoDialog = new QDjViewInfoDialog(this);
4505   infoDialog->setWindowTitle(tr("Information - DjView", "dialog caption"));
4506   infoDialog->setPage(widget->page());
4507   infoDialog->refresh();
4508   infoDialog->raise();
4509   infoDialog->show();
4510 }
4511 
4512 
4513 void
performMetadata(void)4514 QDjView::performMetadata(void)
4515 {
4516   if (! documentPages.size())
4517     return;
4518   if (! metaDialog)
4519     metaDialog = new QDjViewMetaDialog(this);
4520   metaDialog->setWindowTitle(tr("Metadata - DjView", "dialog caption"));
4521   metaDialog->setPage(widget->page());
4522   metaDialog->refresh();
4523   metaDialog->raise();
4524   metaDialog->show();
4525 }
4526 
4527 
4528 void
performPreferences(void)4529 QDjView::performPreferences(void)
4530 {
4531   updatePreferences();
4532   QDjViewPrefsDialog *dialog = QDjViewPrefsDialog::instance();
4533   dialog->load(this);
4534   dialog->show();
4535   dialog->raise();
4536 }
4537 
4538 
4539 void
performRotation(void)4540 QDjView::performRotation(void)
4541 {
4542   QAction *action = qobject_cast<QAction*>(sender());
4543   int rotation = action->data().toInt();
4544   widget->setRotation(rotation);
4545 }
4546 
4547 
4548 void
performZoom(void)4549 QDjView::performZoom(void)
4550 {
4551   QAction *action = qobject_cast<QAction*>(sender());
4552   int zoom = action->data().toInt();
4553   widget->setZoom(zoom);
4554 }
4555 
4556 
4557 void
performSelect(bool checked)4558 QDjView::performSelect(bool checked)
4559 {
4560   if (checked)
4561     widget->setModifiersForSelect(Qt::NoModifier);
4562   else
4563     widget->setModifiersForSelect(prefs->modifiersForSelect);
4564 }
4565 
4566 
4567 bool
setViewerMode(ViewerMode mode)4568 QDjView::setViewerMode(ViewerMode mode)
4569 {
4570   Saved *savedPrefs;
4571   Saved *savedConfig;
4572   if (viewerMode == mode)
4573     return true;
4574   if (viewerMode < STANDALONE || mode < STANDALONE)
4575     return false;
4576   savedPrefs = getSavedPrefs();
4577   savedConfig = getSavedConfig(viewerMode);
4578   savedConfig->remember = true;
4579   updateSaved(savedConfig);
4580   updateSaved(savedPrefs);
4581   Qt::WindowStates wstate = windowState();
4582   wstate &= ~unusualWindowStates;
4583   if (mode > STANDALONE)
4584     wstate |= Qt::WindowFullScreen;
4585   if (mode > STANDALONE)
4586     setUnifiedTitleAndToolBarOnMac(false);
4587   savedConfig = getSavedConfig(mode);
4588   setWindowState(wstate);
4589   applySaved(savedConfig);
4590   widget->setBorderBrush(savedConfig->nsBorderBrush);
4591   widget->setBorderSize(savedConfig->nsBorderSize);
4592   viewerMode = mode;
4593   // slideshow delay
4594   if (mode == STANDALONE_SLIDESHOW)
4595     setSlideShowDelay(prefs->slideShowDelay);
4596   // make sure checkboxes are set right.
4597   updateActionsLater();
4598   return true;
4599 }
4600 
4601 void
setSlideShowDelay(int delay)4602 QDjView::setSlideShowDelay(int delay)
4603 {
4604   slideShowCounter = 0;
4605   slideShowDelay = delay;
4606   slideShowTimeout(true);
4607 }
4608 
4609 void
slideShowTimeout(bool reset)4610 QDjView::slideShowTimeout(bool reset)
4611 {
4612   double ratio = 0;
4613   bool ssmode = (viewerMode == STANDALONE_SLIDESHOW) && (slideShowDelay>0);
4614   bool active = ssmode && (widget->page() < pageNum()-1);
4615   bool newpage = (slideShowCounter >= slideShowDelay-1);
4616   slideShowCounter = (reset||newpage||!active) ? 0 : slideShowCounter+1;
4617   if (newpage && active)
4618     widget->nextPage();
4619   if (active)
4620     ratio = 1.0 - (double)slideShowCounter / slideShowDelay;
4621   widget->setHourGlassRatio(ratio);
4622   slideShowTimer->stop();
4623   if (ssmode)
4624     slideShowTimer->start(1000);
4625 }
4626 
4627 void
performViewFullScreen(bool checked)4628 QDjView::performViewFullScreen(bool checked)
4629 {
4630   if (checked)
4631     setViewerMode(STANDALONE_FULLSCREEN);
4632   else
4633     setViewerMode(STANDALONE);
4634 }
4635 
4636 void
performViewSlideShow(bool checked)4637 QDjView::performViewSlideShow(bool checked)
4638 {
4639   if (checked)
4640     setViewerMode(STANDALONE_SLIDESHOW);
4641   else
4642     setViewerMode(STANDALONE);
4643 }
4644 
4645 
4646 void
performEscape()4647 QDjView::performEscape()
4648 {
4649   if (actionViewSideBar->isChecked())
4650     actionViewSideBar->activate(QAction::Trigger);
4651   else if (viewerMode > STANDALONE && widget->keyboardEnabled())
4652     setViewerMode(STANDALONE);
4653 }
4654 
4655 
4656 void
performGoPage()4657 QDjView::performGoPage()
4658 {
4659   if (toolBar->isVisibleTo(this) || widget->keyboardEnabled())
4660     if (toolsCached & QDjViewPrefs::TOOL_PAGECOMBO)
4661       {
4662         toolBar->show();
4663         pageCombo->setFocus();
4664         QTimer::singleShot(0, pageCombo->lineEdit(), SLOT(selectAll()));
4665       }
4666 }
4667 
4668 
4669 void
restoreRecentDocument(QUrl url)4670 QDjView::restoreRecentDocument(QUrl url)
4671 {
4672   url.setPassword(QString());
4673   QUrl cleanUrl = removeDjVuCgiArguments(url);
4674   QString prefix = cleanUrl.toString(QUrl::RemoveQuery);
4675   foreach (QString recent, prefs->recentFiles)
4676     if (recent.startsWith(prefix))
4677       {
4678         QUrl recentUrl = recent;
4679         QUrl cleanRecentUrl = removeDjVuCgiArguments(recentUrl);
4680         if (cleanUrl == cleanRecentUrl)
4681           {
4682             parseDjVuCgiArguments(recentUrl);
4683             return;
4684           }
4685       }
4686 }
4687 
4688 
4689 void
addRecent(QUrl url)4690 QDjView::addRecent(QUrl url)
4691 {
4692   prefs->loadRecent();
4693   // never remember passwords
4694   url.setPassword(QString());
4695   // remove matching entries
4696   QUrl cleanUrl = removeDjVuCgiArguments(url);
4697   QString prefix = cleanUrl.toString(QUrl::RemoveQuery);
4698   QStringList::iterator it = prefs->recentFiles.begin();
4699   while (it != prefs->recentFiles.end())
4700     {
4701       if (it->startsWith(prefix) &&
4702           cleanUrl == removeDjVuCgiArguments(QUrl(*it)))
4703         it = prefs->recentFiles.erase(it);
4704       else
4705         ++it;
4706     }
4707   // add new url (max 50)
4708   QString name = url.toString();
4709   prefs->recentFiles.prepend(name);
4710   while(prefs->recentFiles.size() > 50)
4711     prefs->recentFiles.pop_back();
4712   // save
4713   prefs->saveRecent();
4714 }
4715 
4716 
4717 void
fillRecent()4718 QDjView::fillRecent()
4719 {
4720   if (recentMenu)
4721     {
4722       recentMenu->clear();
4723       prefs->loadRecent();
4724       int n = qMin(prefs->recentFiles.size(), 8);
4725       for (int i=0; i<n; i++)
4726         {
4727           QUrl url = prefs->recentFiles[i];
4728           QString base = url.path().section("/",-1);
4729           QString name = url.toLocalFile();
4730           if (name.isEmpty())
4731             name = removeDjVuCgiArguments(url).toString();
4732           QFont defaultFont;
4733           QFontMetrics metrics(defaultFont);
4734           name = metrics.elidedText(name, Qt::ElideMiddle, 400);
4735           name = QString("%1 [%2]").arg(base).arg(name);
4736           QAction *action = recentMenu->addAction(name);
4737           action->setData(url);
4738           connect(action,SIGNAL(triggered()), this, SLOT(openRecent()));
4739         }
4740       recentMenu->addSeparator();
4741     }
4742   QAction *action = recentMenu->addAction(tr("&Clear History"));
4743   connect(action, SIGNAL(triggered()), this, SLOT(clearRecent()));
4744 }
4745 
4746 
4747 void
openRecent()4748 QDjView::openRecent()
4749 {
4750   QAction *action = qobject_cast<QAction*>(sender());
4751   if (action && viewerMode >= STANDALONE)
4752     {
4753       QUrl url = action->data().toUrl();
4754       QFileInfo file = url.toLocalFile();
4755       if (file.exists())
4756         open(file.absoluteFilePath());
4757       else
4758         open(url);
4759     }
4760 }
4761 
4762 
4763 void
clearRecent()4764 QDjView::clearRecent()
4765 {
4766   prefs->recentFiles.clear();
4767   prefs->saveRecent();
4768 }
4769 
4770 
UndoRedo()4771 QDjView::UndoRedo::UndoRedo()
4772   : valid(false)
4773 {
4774 }
4775 
4776 
4777 void
clear()4778 QDjView::UndoRedo::clear()
4779 {
4780   valid = false;
4781 }
4782 
4783 
4784 void
set(QDjView * djview)4785 QDjView::UndoRedo::set(QDjView *djview)
4786 {
4787   QDjVuWidget *djvu = djview->getDjVuWidget();
4788   rotation = djvu->rotation();
4789   zoom = djvu->zoom();
4790   hotSpot = djvu->hotSpot();
4791   position = djvu->position(hotSpot);
4792   valid = true;
4793 }
4794 
4795 
4796 void
apply(QDjView * djview)4797 QDjView::UndoRedo::apply(QDjView *djview)
4798 {
4799   if (valid)
4800     {
4801       QDjVuWidget *djvu = djview->getDjVuWidget();
4802       djvu->setZoom(zoom);
4803       djvu->setRotation(rotation);
4804       djvu->setPosition(position, hotSpot);
4805     }
4806 }
4807 
4808 
4809 bool
changed(const QDjVuWidget * djvu) const4810 QDjView::UndoRedo::changed(const QDjVuWidget *djvu) const
4811 {
4812   if (valid)
4813     {
4814       if (zoom != djvu->zoom() ||
4815           rotation != djvu->rotation())
4816         return true;
4817       Position curpos = djvu->position(hotSpot);
4818       if (curpos.pageNo != position.pageNo ||
4819           curpos.inPage != position.inPage)
4820         return true;
4821       if (curpos.inPage)
4822         return (curpos.posPage != position.posPage);
4823       else
4824         return (curpos.posView != position.posView);
4825     }
4826   return false;
4827 }
4828 
4829 
4830 void
saveUndoData()4831 QDjView::saveUndoData()
4832 {
4833   if (QApplication::mouseButtons() == Qt::NoButton
4834       && widget->pageSizeKnown(widget->page()) )
4835     {
4836       if (here.changed(widget))
4837         {
4838           undoList.prepend(here);
4839           while (undoList.size() > 1024)
4840             undoList.removeLast();
4841           redoList.clear();
4842         }
4843       here.set(this);
4844       actionBack->setEnabled(undoList.size() > 0);
4845       actionForw->setEnabled(redoList.size() > 0);
4846     }
4847   else
4848     {
4849       undoTimer->stop();
4850       undoTimer->start(250);
4851     }
4852 }
4853 
4854 
4855 void
performUndo()4856 QDjView::performUndo()
4857 {
4858   if (undoList.size() > 0)
4859     {
4860       UndoRedo target = undoList.takeFirst();
4861       UndoRedo saved;
4862       saved.set(this);
4863       target.apply(this);
4864       here.clear();
4865       redoList.prepend(saved);
4866     }
4867 }
4868 
4869 
4870 void
performRedo()4871 QDjView::performRedo()
4872 {
4873   if (redoList.size() > 0)
4874     {
4875       UndoRedo target = redoList.takeFirst();
4876       UndoRedo saved;
4877       saved.set(this);
4878       target.apply(this);
4879       here.clear();
4880       undoList.prepend(saved);
4881     }
4882 }
4883 
4884 
4885 void
performCopyUrl()4886 QDjView::performCopyUrl()
4887 {
4888   QUrl url = getDecoratedUrl();
4889   if (url.isValid())
4890     QApplication::clipboard()->setText(url.toString());
4891 }
4892 
4893 
4894 #if DDJVUAPI_VERSION < 21
4895 
4896 static QByteArray *qstring_puts_data = 0;
qstring_puts(const char * s)4897 static int qstring_puts(const char *s)
4898 {
4899   if (qstring_puts_data)
4900     (*qstring_puts_data) += s;
4901   return strlen(s);
4902 }
4903 
4904 static QString
miniexp_to_string(miniexp_t expr,int width=40,bool octal=false)4905 miniexp_to_string(miniexp_t expr, int width=40, bool octal=false)
4906 {
4907   QByteArray buffer;
4908   qstring_puts_data = &buffer;
4909   int (*saved_puts)(const char*) = minilisp_puts;
4910   int saved_print_7bits = minilisp_print_7bits;
4911   minilisp_puts = qstring_puts;
4912   minilisp_print_7bits = (octal) ? 1 : 0;
4913   miniexp_pprint(expr, width);
4914   minilisp_print_7bits = saved_print_7bits;
4915   minilisp_puts = saved_puts;
4916   return QString::fromUtf8(buffer.data());
4917 }
4918 
4919 #else
4920 
4921 static int
qstring_puts(miniexp_io_t * io,const char * s)4922 qstring_puts(miniexp_io_t *io, const char *s)
4923 {
4924   QByteArray *bap = (QByteArray*)io->data[1];
4925   if (bap) *bap += s;
4926   return strlen(s);
4927 }
4928 
4929 static QString
miniexp_to_string(miniexp_t expr,int width=40,bool octal=false)4930 miniexp_to_string(miniexp_t expr, int width=40, bool octal=false)
4931 {
4932   QByteArray buffer;
4933   miniexp_io_t io;
4934   miniexp_io_init(&io);
4935   io.fputs = qstring_puts;
4936   io.data[1] = (void*)&buffer;
4937 #ifdef miniexp_io_print7bits
4938   static int flags = miniexp_io_print7bits;
4939   io.p_flags = (octal) ? &flags : 0;
4940 #else
4941   static int flags = 1;
4942   io.p_print7bits = (octal) ? &flags : 0;
4943 #endif
4944   miniexp_pprint_r(&io, expr, width);
4945   return QString::fromUtf8(buffer.data());
4946 }
4947 
4948 #endif
4949 
4950 
4951 void
performCopyOutline()4952 QDjView::performCopyOutline()
4953 {
4954   if (document)
4955     {
4956       QString s;
4957       minivar_t expr = document->getDocumentOutline();
4958       if (miniexp_consp(expr))
4959         s += QString("# This is the existing outline.\n");
4960       else {
4961         s += QString("# This is an outline template with all pages.\n");
4962         expr = miniexp_cons(miniexp_symbol("bookmarks"),miniexp_nil);
4963         for (int i=0; i<documentPages.size(); i++)
4964           {
4965             minivar_t p = miniexp_nil;
4966             QByteArray ref = documentPages[i].id;
4967             p = miniexp_cons(miniexp_string(ref.prepend("#").constData()),p);
4968             QString name = QString("Page %1").arg(pageName(i));
4969             p = miniexp_cons(miniexp_string(name.toUtf8().constData()),p);
4970             expr = miniexp_cons(p,expr);
4971           }
4972         expr = miniexp_reverse(expr);
4973       }
4974       s += QString("# Edit it and store it with command:\n"
4975                    "#   $ djvused foo.djvu -f thisfile -s\n"
4976                    "# The following line is the djvused command\n"
4977                    "# to set the outline and the rest is the outline\n"
4978                    "set-outline\n\n");
4979       s += miniexp_to_string(expr);
4980       // copy to clipboard
4981       QApplication::clipboard()->setText(s);
4982     }
4983 }
4984 
4985 
4986 void
performCopyAnnotation()4987 QDjView::performCopyAnnotation()
4988 {
4989   int pageNo = widget->page();
4990   if (document && pageNo>=0 && pageNo<pageNum())
4991     {
4992       QString s;
4993       miniexp_t expr = document->getPageAnnotations(pageNo);
4994       if (expr == miniexp_nil || expr == miniexp_dummy)
4995         s += QString("# There were no annotations for page %1.\n");
4996       else
4997         s += QString("# These are the annotation for page %1.\n");
4998       s += QString("# Edit this file and store it with command:\n"
4999                    "#   $ djvused foo.djvu -f thisfile -s\n"
5000                    "# Tip: select an area in djview4 and use 'copy maparea'.\n"
5001                    "# The following line is the djvused command to set\n"
5002                    "# the annotation and the rest are the annotations\n"
5003                    "select %2; set-ant\n\n");
5004       s = s.arg(pageNo+1).arg(pageNo+1);
5005       while (miniexp_consp(expr))
5006         {
5007           s += miniexp_to_string(miniexp_car(expr));
5008           expr = miniexp_cdr(expr);
5009         }
5010       // copy to clipboard
5011       QApplication::clipboard()->setText(s);
5012     }
5013 }
5014 
5015 
5016 // ----------------------------------------
5017 // MOC
5018 
5019 #include "qdjview.moc"
5020 
5021 
5022 /* -------------------------------------------------------------
5023    Local Variables:
5024    c++-font-lock-extra-types: ( "\\sw+_t" "[A-Z]\\sw*[a-z]\\sw*" )
5025    End:
5026    ------------------------------------------------------------- */
5027