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