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