1 // --------------------------------------------------------------------
2 // IpePresenter for Qt
3 // --------------------------------------------------------------------
4 /*
5 
6     This file is part of the extensible drawing editor Ipe.
7     Copyright (c) 1993-2020 Otfried Cheong
8 
9     Ipe is free software; you can redistribute it and/or modify it
10     under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 3 of the License, or
12     (at your option) any later version.
13 
14     As a special exception, you have permission to link Ipe with the
15     CGAL library and distribute executables, as long as you follow the
16     requirements of the Gnu General Public License in regard to all of
17     the software in the executable aside from CGAL.
18 
19     Ipe is distributed in the hope that it will be useful, but WITHOUT
20     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21     or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
22     License for more details.
23 
24     You should have received a copy of the GNU General Public License
25     along with Ipe; if not, you can find it at
26     "http://www.gnu.org/copyleft/gpl.html", or write to the Free
27     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 
29 */
30 
31 #include "ipepresenter_qt.h"
32 
33 #include "ipethumbs.h"
34 
35 #include <QtWidgets/QApplication>
36 #include <QtWidgets/QHBoxLayout>
37 #include <QtWidgets/QLabel>
38 #include <QtWidgets/QMenuBar>
39 #include <QtWidgets/QMessageBox>
40 #include <QtWidgets/QSplitter>
41 #include <QtWidgets/QVBoxLayout>
42 #include <QtWidgets/QWidget>
43 #include <QtWidgets/QInputDialog>
44 #include <QDesktopServices>
45 #include <QUrl>
46 
47 using namespace ipe;
48 
49 // --------------------------------------------------------------------
50 
QIpe(const String & str)51 inline QString QIpe(const String &str)
52 {
53   return QString::fromUtf8(str.z());
54 }
55 
56 // --------------------------------------------------------------------
57 
IpeAction(int cmd,const QString & text,const char * shortcut,MainWindow * parent)58 IpeAction::IpeAction(int cmd, const QString &text, const char *shortcut,
59                      MainWindow *parent):
60   QAction(text, parent), iCommand{cmd}
61 {
62   if (shortcut)
63     setShortcut(QKeySequence(shortcut));
__anonc21bba620102() 64   connect(this, &QAction::triggered, [=] () { parent->cmd(iCommand); });
65 }
66 
67 // --------------------------------------------------------------------
68 
BeamerView(Qt::WindowFlags f)69 BeamerView::BeamerView(Qt::WindowFlags f) : QMainWindow(nullptr, f)
70 {
71   iView = new PdfView(this);
72   iView->setBackground(Color(0, 0, 0));
73   setCentralWidget(iView);
74 }
75 
76 // --------------------------------------------------------------------
77 
MainWindow(BeamerView * bv,Qt::WindowFlags f)78 MainWindow::MainWindow(BeamerView* bv, Qt::WindowFlags f) :
79   QMainWindow(nullptr, f), iScreen(bv)
80 {
81   QWidget *centralwidget = new QWidget(this);
82   QHBoxLayout *horizontalLayout = new QHBoxLayout(centralwidget);
83 
84   QSplitter *splitV = new QSplitter(centralwidget);
85   splitV->setOrientation(Qt::Horizontal);
86 
87   iCurrent = new PdfView(splitV);
88 
89   QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
90   sizePolicy.setHorizontalStretch(0);
91   sizePolicy.setVerticalStretch(0);
92   sizePolicy.setHeightForWidth(iCurrent->sizePolicy().hasHeightForWidth());
93   iCurrent->setSizePolicy(sizePolicy);
94   iCurrent->setMinimumSize(QSize(600, 0));
95   splitV->addWidget(iCurrent);
96 
97   QSplitter *splitH = new QSplitter(splitV);
98   splitH->setOrientation(Qt::Vertical);
99 
100   QWidget *clockNotes = new QWidget(splitH);
101   QVBoxLayout *clockNotesLayout = new QVBoxLayout(clockNotes);
102   clockNotesLayout->setContentsMargins(0, 0, 0, 0);
103 
104   iClock = new TimeLabel(clockNotes);
105   QFont clockFont;
106   clockFont.setPointSize(28);
107   iClock->setFont(clockFont);
108 
109   clockNotesLayout->addWidget(iClock);
110 
111   QLabel *notesLabel = new QLabel(clockNotes);
112   clockNotesLayout->addWidget(notesLabel);
113 
114   iNotes = new QPlainTextEdit(clockNotes);
115   iNotes->setReadOnly(true);
116   QFont notesFont;
117   notesFont.setFamily(QStringLiteral("Monospace"));
118   iNotes->setFont(notesFont);
119 
120   clockNotesLayout->addWidget(iNotes);
121 
122   splitH->addWidget(clockNotes);
123 
124   QWidget *nextView = new QWidget(splitH);
125   QVBoxLayout *nextLayout = new QVBoxLayout(nextView);
126   nextLayout->setContentsMargins(0, 0, 0, 0);
127   QLabel *nextLabel = new QLabel(nextView);
128   QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Fixed);
129   sizePolicy1.setHorizontalStretch(0);
130   sizePolicy1.setVerticalStretch(0);
131   sizePolicy1.setHeightForWidth(nextLabel->sizePolicy().hasHeightForWidth());
132   nextLabel->setSizePolicy(sizePolicy1);
133 
134   nextLayout->addWidget(nextLabel);
135 
136   iNext = new PdfView(nextView);
137   QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Expanding);
138   sizePolicy2.setHorizontalStretch(0);
139   sizePolicy2.setVerticalStretch(0);
140   sizePolicy2.setHeightForWidth(iNext->sizePolicy().hasHeightForWidth());
141   iNext->setSizePolicy(sizePolicy2);
142 
143   nextLayout->addWidget(iNext);
144 
145   splitH->addWidget(nextView);
146   splitV->addWidget(splitH);
147 
148   horizontalLayout->addWidget(splitV);
149 
150   setCentralWidget(centralwidget);
151 
152   iClock->setText("00:00:00");
153   notesLabel->setText("Notes:");
154   nextLabel->setText("Next view:");
155 
156   QMenuBar *menubar = new QMenuBar(this);
157   setMenuBar(menubar);
158 
159   iViewMenu = menuBar()->addMenu(tr("&View"));
160   iTimeMenu = menuBar()->addMenu(tr("&Time"));
161   iMoveMenu = menuBar()->addMenu(tr("&Navigate"));
162   iHelpMenu = menuBar()->addMenu(tr("&Help"));
163 
164   iShowPresentationAction = new IpeAction(EShowPresentation, "Show presentation", "F5", this);
165   iShowPresentationAction->setCheckable(true);
166   iViewMenu->addAction(iShowPresentationAction);
167 
168   iFullScreenAction = new IpeAction(EFullScreen, "Full screen", "F11", this);
169   iFullScreenAction->setCheckable(true);
170   iViewMenu->addAction(iFullScreenAction);
171 
172   iBlackoutAction = new IpeAction(EBlackout, "Blackout", "B", this);
173   iBlackoutAction->setCheckable(true);
174   iViewMenu->addAction(iBlackoutAction);
175 
176   connect(iViewMenu, &QMenu::aboutToShow,
177 	  [=] () {
178 	    iShowPresentationAction->setChecked(iScreen->isVisible());
179 	    iFullScreenAction->setChecked((iScreen->windowState() & Qt::WindowFullScreen) != 0);
180 	    iBlackoutAction->setChecked(iScreen->pdfView()->blackout());
181 	  });
182 
183   iTimeMenu->addAction(new IpeAction(ESetTime, "Set time", "", this));
184   iTimeMenu->addAction(new IpeAction(EResetTime, "Reset time", "R", this));
185 
186   IpeAction *countDown = new IpeAction(ETimeCountdown, "Count down", "/", this);
187   countDown->setCheckable(true);
188   iTimeMenu->addAction(countDown);
189   IpeAction* countTime = new IpeAction(EToggleTimeCounting, "Count time", "T", this);
190   countTime->setCheckable(true);
191   iTimeMenu->addAction(countTime);
192 
193   IpeAction *next = new IpeAction(ENextView, "Next view", nullptr, this);
194   IpeAction *prev = new IpeAction(EPreviousView, "Previous view", nullptr, this);
195   QList<QKeySequence> nextKeys { QKeySequence("Right"), QKeySequence("Down"), QKeySequence("PgDown"),};
196   QList<QKeySequence> prevKeys { QKeySequence("Left"), QKeySequence("Up"), QKeySequence("PgUp") };
197   next->setShortcuts(nextKeys);
198   prev->setShortcuts(prevKeys);
199   iMoveMenu->addAction(next);
200   iMoveMenu->addAction(prev);
201   iMoveMenu->addAction(new IpeAction(ENextPage, "Next page", "N", this));
202   iMoveMenu->addAction(new IpeAction(EPreviousPage, "Previous page", "P", this));
203   iMoveMenu->addAction(new IpeAction(EFirstView, "First view", "Home", this));
204   iMoveMenu->addAction(new IpeAction(ELastView, "Last view", "End", this));
205   iMoveMenu->addAction(new IpeAction(EJumpTo, "Jump to...", "J", this));
206   iMoveMenu->addAction(new IpeAction(ESelectPage, "Select page...", "S", this));
207 
208   iHelpMenu->addAction(new IpeAction(EAbout, "About IpePresenter", nullptr, this));
209 
210   connect(iScreen->pdfView(), &PdfView::sizeChanged,
211 	  [=] () { fitBox(mediaBox(-1), iScreen->pdfView()); });
212   connect(iCurrent, &PdfView::sizeChanged,
213 	  [=] () { fitBox(mediaBox(-1), iCurrent); });
214   connect(iNext, &PdfView::sizeChanged,
215 	  [=] () { fitBox(mediaBox(-2), iNext); });
216 
217   connect(iCurrent, &PdfView::mouseButton,
218 	  [=] (int button, const Vector &pos) {
219 	    const PdfDict *action = findLink(pos);
220 	    if (action) {
221 	      interpretAction(action);
222 	      setView();
223 	    } else
224 	      cmd(button);
225 	  });
226 
227   connect(iScreen->pdfView(), &PdfView::mouseButton, this, &MainWindow::cmd);
228 }
229 
230 // --------------------------------------------------------------------
231 
cmd(int c)232 void MainWindow::cmd(int c)
233 {
234   // ipeDebug("Command %d", c);
235   switch (c) {
236   case EOpen:
237     break;
238   case EQuit:
239     QApplication::exit();
240     break;
241     //
242   case EShowPresentation:
243     if (iScreen->isVisible())
244       iScreen->hide();
245     else
246       iScreen->show();
247     break;
248   case EFullScreen:
249     iScreen->setWindowState(iScreen->windowState() ^ Qt::WindowFullScreen);
250     break;
251   case EBlackout:
252     iScreen->pdfView()->setBlackout(!iScreen->pdfView()->blackout());
253     iScreen->pdfView()->updatePdf();
254     break;
255     //
256   case EToggleTimeCounting:
257     iClock->toggleCounting();
258     break;
259   case ETimeCountdown:
260     iClock->toggleCountdown();
261     break;
262   case ESetTime:
263     iClock->setTime();
264     break;
265   case EResetTime:
266     iClock->resetTime();
267     break;
268     //
269   case ELeftMouse:
270   case ENextView:
271     nextView(+1);
272     setView();
273     break;
274   case EOtherMouse:
275   case EPreviousView:
276     nextView(-1);
277     setView();
278     break;
279   case ENextPage:
280     nextPage(+1);
281     setView();
282     break;
283   case EPreviousPage:
284     nextPage(-1);
285     setView();
286     break;
287   case EFirstView:
288     firstView();
289     setView();
290     break;
291   case ELastView:
292     lastView();
293     setView();
294     break;
295   case EJumpTo:
296     jumpTo();
297     break;
298   case ESelectPage:
299     selectPage();
300     break;
301   case EAbout:
302     aboutIpePresenter();
303     break;
304   default:
305     // unknown action
306     return;
307   }
308 }
309 
310 // --------------------------------------------------------------------
311 
load(const char * fn)312 bool MainWindow::load(const char* fn)
313 {
314   bool result = Presenter::load(fn);
315   if (result) {
316     setPdf();
317     setView();
318   }
319   return result;
320 }
321 
jumpTo()322 void MainWindow::jumpTo()
323 {
324   auto str = QInputDialog::getText(this, tr("Jump to page"), tr("Enter page label:"));
325   if (!str.isEmpty()) {
326     jumpToPage(String(str.trimmed().toUtf8()));
327     setView();
328   }
329 }
330 
setPdf()331 void MainWindow::setPdf()
332 {
333   iScreen->pdfView()->setPdf(iPdf.get(), iFonts.get());
334   iCurrent->setPdf(iPdf.get(), iFonts.get());
335   iNext->setPdf(iPdf.get(), iFonts.get());
336 }
337 
setView()338 void MainWindow::setView()
339 {
340   setViewPage(iScreen->pdfView(), iPdfPageNo);
341   setViewPage(iCurrent, iPdfPageNo);
342   setViewPage(iNext, iPdfPageNo < iPdf->countPages() - 1 ? iPdfPageNo + 1 : iPdfPageNo);
343 
344   setWindowTitle(QIpe(currentLabel()));
345   iNotes->setPlainText(QIpe(iAnnotations[iPdfPageNo]));
346 }
347 
selectPage()348 void MainWindow::selectPage()
349 {
350   constexpr int iconWidth = 250;
351   std::vector<String> labels;
352   for (int i = 0; i < iPdf->countPages(); ++i)
353     labels.push_back(pageLabel(i));
354 
355   if (iPageIcons.empty()) {
356     PdfThumbnail r(iPdf.get(), iconWidth);
357     for (int i = 0; i < iPdf->countPages(); ++i) {
358       Buffer b = r.render(iPdf->page(i));
359       QImage bits((const uchar *) b.data(), r.width(), r.height(), QImage::Format_RGB32);
360       // need to copy bits since buffer b is temporary
361       iPageIcons.push_back(QPixmap::fromImage(bits.copy()));
362     }
363   }
364 
365   QDialog *d = new QDialog();
366   d->setWindowTitle("IpePresenter: Select page");
367 
368   QLayout *lo = new QVBoxLayout;
369 
370   PageSelector *p = new PageSelector(d);
371   p->fill(iPageIcons, labels);
372   p->setCurrentRow(iPdfPageNo);
373 
374   lo->addWidget(p);
375   d->setLayout(lo);
376 
377   QWidget::connect(p, SIGNAL(selectionMade()), d, SLOT(accept()));
378 
379   d->setWindowState(Qt::WindowMaximized);
380 
381   int result = d->exec();
382   int sel = p->selectedIndex();
383   delete d;
384 
385   if (result == QDialog::Accepted) {
386     iPdfPageNo = sel;
387     setView();
388   }
389 }
390 
browseLaunch(bool launch,String dest)391 void MainWindow::browseLaunch(bool launch, String dest)
392 {
393   QDesktopServices::openUrl(QUrl(dest.z()));
394 }
395 
396 // --------------------------------------------------------------------
397 
closeEvent(QCloseEvent * event)398 void MainWindow::closeEvent(QCloseEvent *event)
399 {
400   iScreen->close();
401   QMainWindow::closeEvent(event);
402 }
403 
404 // --------------------------------------------------------------------
405 
406 static const char * const aboutText =
407   "<qt><h1>IpePresenter %d.%d.%d</h1>"
408   "<p>Copyright (c) 2020 Otfried Cheong</p>"
409   "<p>A presentation tool for giving PDF presentations "
410   "created in Ipe or using beamer.</p>"
411   "<p>Originally invented by Dmitriy Morozov, "
412   "IpePresenter is now developed together with Ipe and released under the GNU Public License.</p>"
413   "<p>See the <a href=\"http://ipepresenter.otfried.org\">IpePresenter homepage</a>"
414   " for further information.</p>"
415   "<p>You can \"like\" IpePresenter and follow IpePresenter announcements on "
416   "<a href=\"http://www.facebook.com/drawing.editor.Ipe7\">Facebook</a>.</p>"
417   "<p>If you are an IpePresenter fan and want to show others, have a look at the "
418   "<a href=\"https://www.shirtee.com/en/store/ipe\">Ipe T-shirts</a>.</p>"
419   "<h3>Platinum and gold sponsors</h3>"
420   "<ul><li>Hee-Kap Ahn</li>"
421   "<li>Günter Rote</li>"
422   "<li>SCALGO</li>"
423   "<li>Martin Ziegler</li></ul>"
424   "<p>If you enjoy IpePresenter, feel free to treat the author on a cup of coffee at "
425   "<a href=\"https://ko-fi.com/ipe7author\">Ko-fi</a>.</p>"
426   "<p>You can also become a member of the exclusive community of "
427   "<a href=\"http://patreon.com/otfried\">Ipe patrons</a>. "
428   "For the price of a cup of coffee per month you can make a meaningful contribution "
429   "to the continuing development of IpePresenter and Ipe.</p>"
430   "</qt>";
431 
aboutIpePresenter()432 void MainWindow::aboutIpePresenter()
433 {
434   std::vector<char> buf(strlen(aboutText) + 100);
435   sprintf(&buf[0], aboutText,
436 	  IPELIB_VERSION / 10000,
437 	  (IPELIB_VERSION / 100) % 100,
438 	  IPELIB_VERSION % 100);
439 
440   QMessageBox msgBox(this);
441   msgBox.setWindowTitle("About IpePresenter");
442   msgBox.setInformativeText(&buf[0]);
443   msgBox.setStandardButtons(QMessageBox::Ok);
444   msgBox.exec();
445 }
446 
showType3Warning(const char * s)447 void MainWindow::showType3Warning(const char *s)
448 {
449   QMessageBox msgBox(this);
450   msgBox.setWindowTitle("Type3 font detected");
451   msgBox.setInformativeText(s);
452   msgBox.setStandardButtons(QMessageBox::Ok);
453   msgBox.exec();
454 }
455 
456 // --------------------------------------------------------------------
457 
usage()458 static void usage()
459 {
460   fprintf(stderr, "Usage: ipepresenter <filename>\n");
461   exit(1);
462 }
463 
main(int argc,char * argv[])464 int main(int argc, char *argv[])
465 {
466   Platform::initLib(IPELIB_VERSION);
467   QApplication a(argc, argv);
468 
469   if (argc != 2)
470     usage();
471 
472   const char *load = argv[1];
473 
474   BeamerView *bv = new BeamerView();
475   MainWindow *mw = new MainWindow(bv);
476 
477   if (!mw->load(load))
478     exit(2);
479 
480   mw->show();
481 
482   QObject::connect(&a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()));
483   return a.exec();
484 }
485 
486 // --------------------------------------------------------------------
487