1 // --------------------------------------------------------------------
2 // AppUi 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 "appui_qt.h"
32 #include "ipecanvas_qt.h"
33 #include "controls_qt.h"
34
35 #include "ipelua.h"
36
37 #include "ipethumbs.h"
38
39 #include <cstdio>
40 #include <cstdlib>
41
42 #include <QMenuBar>
43 #include <QKeySequence>
44 #include <QCloseEvent>
45 #include <QStatusBar>
46 #include <QGridLayout>
47 #include <QButtonGroup>
48 #include <QMessageBox>
49 #include <QSignalMapper>
50 #include <QToolTip>
51 #include <QDialogButtonBox>
52 #include <QClipboard>
53 #include <QMimeData>
54 #include <QWindow>
55 #include <QScreen>
56
57 #include <sys/types.h>
58 #include <sys/stat.h>
59
60 using namespace ipe;
61 using namespace ipelua;
62
63 // --------------------------------------------------------------------
64
adapt_size(const QSize & size,int factor)65 inline QSize adapt_size(const QSize &size, int factor)
66 {
67 return QSize(factor * size.width() / 100, factor * size.height() / 100);
68 }
69
prefsIcon(String name)70 QIcon AppUi::prefsIcon(String name)
71 {
72 if (name == "ipe")
73 return QIcon(QIpe(ipeIconDirectory() + "icon_128x128.png"));
74
75 String svgdir = Platform::latexDirectory() + "/icons/";
76 if (!Platform::fileExists(svgdir) && mkdir(svgdir.z(), 0700) != 0)
77 return QIcon();
78
79 String svgname = svgdir + name + ".svg";
80 int pno = ipeIcon(name);
81 if (pno >= 0) {
82 bool dark = QGuiApplication::palette().text().color().value() > 128;
83 Document *doc = dark ? ipeIconsDark.get() : ipeIcons.get();
84 Thumbnail thumbs(doc, 22);
85 thumbs.setNoCrop(true);
86 thumbs.saveRender(Thumbnail::ESVG, svgname.z(), doc->page(pno), 0, 1.0);
87 return QIcon(QIpe(svgname));
88 } else
89 return QIcon();
90 }
91
prefsPixmap(String name)92 QPixmap AppUi::prefsPixmap(String name)
93 {
94 QIcon icon = prefsIcon(name);
95 int size = uiscale(24);
96 return icon.pixmap(QSize(size, size));
97 }
98
prefsColorIcon(Color color)99 QIcon AppUi::prefsColorIcon(Color color)
100 {
101 int size = uiscale(16);
102 QPixmap pixmap(size, size);
103 pixmap.fill(QIpe(color));
104 return QIcon(pixmap);
105 }
106
107 // --------------------------------------------------------------------
108
findAction(const char * name) const109 QAction *AppUi::findAction(const char *name) const
110 {
111 std::map<String, QAction*>::const_iterator it = iActions.find(name);
112 if (it != iActions.end())
113 return it->second;
114 else
115 return nullptr;
116 }
117
addItem(QMenu * m,const QString & title,const char * name)118 void AppUi::addItem(QMenu *m, const QString &title, const char *name)
119 {
120 if (name[0] == '@') {
121 // canUseWhileDrawing = true;
122 name = name + 1;
123 }
124 bool checkable = (m == iMenu[EModeMenu]) ||
125 (String(name).find('|') >= 0);
126 if (name[0] == '*') {
127 checkable = true;
128 name = name + 1;
129 }
130 QAction *a = new QAction(title, this);
131 a->setIconVisibleInMenu(false);
132 if (checkable)
133 a->setCheckable(true);
134 lua_getglobal(L, "shortcuts");
135 lua_getfield(L, -1, name);
136 if (lua_isstring(L, -1)) {
137 QString s = lua_tostring(L, -1);
138 a->setShortcut(QKeySequence(s));
139 QString tt = title + " [" + s + "]";
140 a->setToolTip(tt);
141 }
142 a->setIcon(prefsIcon(name));
143 lua_pop(L, 2);
144 m->addAction(a);
145 if (m == iMenu[EModeMenu]) {
146 a->setActionGroup(iModeActionGroup);
147 iObjectTools->addAction(a);
148 }
149 connect(a, SIGNAL(triggered()), iActionMap, SLOT(map()));
150 iActionMap->setMapping(a, name);
151 iActions[name] = a;
152 }
153
addSnap(const char * name)154 void AppUi::addSnap(const char *name)
155 {
156 QAction *a = findAction(name);
157 assert(a);
158 a->setCheckable(true);
159 iSnapTools->addAction(a);
160 }
161
addEdit(const char * name)162 void AppUi::addEdit(const char *name)
163 {
164 QAction *a = findAction(name);
165 assert(a);
166 iEditTools->addAction(a);
167 }
168
addRootMenu(int id,const char * name)169 void AppUi::addRootMenu(int id, const char *name)
170 {
171 iMenu[id] = menuBar()->addMenu(name);
172 }
173
174 // if title and name == 0, add separator (default)
addItem(int id,const char * title,const char * name)175 void AppUi::addItem(int id, const char *title, const char *name)
176 {
177 if (title == nullptr)
178 iMenu[id]->addSeparator();
179 else
180 addItem(iMenu[id], QString::fromUtf8(title), name);
181 }
182
183 static QMenu *submenu = nullptr;
184 static int submenuId = 0;
185
startSubMenu(int id,const char * name,int tag)186 void AppUi::startSubMenu(int id, const char *name, int tag)
187 {
188 submenuId = id;
189 submenu = new QMenu(name);
190 }
191
addSubItem(const char * title,const char * name)192 void AppUi::addSubItem(const char *title, const char *name)
193 {
194 addItem(submenu, title, name);
195 }
196
endSubMenu()197 MENUHANDLE AppUi::endSubMenu()
198 {
199 iMenu[submenuId]->addMenu(submenu);
200 return submenu;
201 }
202
getDockSide(lua_State * L,const char * name,Qt::DockWidgetArea deflt)203 Qt::DockWidgetArea getDockSide(lua_State *L, const char *name,
204 Qt::DockWidgetArea deflt)
205 {
206 Qt::DockWidgetArea r = deflt;
207 lua_getglobal(L, "prefs");
208 lua_getfield(L, -1, "tools_placement");
209 if (lua_istable(L, -1)) {
210 lua_getfield(L, -1, name);
211 const char *s = lua_tolstring(L, -1, nullptr);
212 if (!strcmp(s, "left"))
213 r = Qt::LeftDockWidgetArea;
214 else if (!strcmp(s, "right"))
215 r = Qt::RightDockWidgetArea;
216 lua_pop(L, 1); // left or right
217 }
218 lua_pop(L, 2); // prefs, tools_placement
219 return r;
220 }
221
set_toolbar_size(QToolBar * bar,int factor)222 static void set_toolbar_size(QToolBar *bar, int factor)
223 {
224 QSize size = bar->iconSize();
225 QSize size1 = adapt_size(size, factor);
226 bar->setIconSize(size1);
227 /*
228 ipeDebug("toolbar %s has size %d x %d, changed to %d x %d",
229 bar->windowTitle().toUtf8().constData(), size.width(), size.height(),
230 size1.width(), size1.height());
231 */
232 }
233
234 // --------------------------------------------------------------------
235
AppUi(lua_State * L0,int model,Qt::WindowFlags f)236 AppUi::AppUi(lua_State *L0, int model, Qt::WindowFlags f)
237 : QMainWindow(nullptr, f), AppUiBase(L0, model)
238 {
239 qApp->setWindowIcon(prefsIcon("ipe"));
240 QMainWindow::setAttribute(Qt::WA_DeleteOnClose);
241 qApp->setAttribute(Qt::AA_UseHighDpiPixmaps);
242 setDockOptions(AnimatedDocks); // do not allow stacking properties and layers
243
244 Canvas *canvas = new Canvas(this);
245 iCanvas = canvas;
246 setCentralWidget(canvas);
247
248 iSnapTools = addToolBar("Snap");
249 iEditTools = addToolBar("Edit");
250 addToolBarBreak();
251 iObjectTools = addToolBar("Objects");
252
253 set_toolbar_size(iEditTools, iToolbarScale);
254 set_toolbar_size(iSnapTools, iToolbarScale);
255 set_toolbar_size(iObjectTools, iToolbarScale);
256
257 iActionMap = new QSignalMapper(this);
258 connect(iActionMap, SIGNAL(mapped(const QString &)),
259 SLOT(qAction(const QString &)));
260
261 iModeActionGroup = new QActionGroup(this);
262 iModeActionGroup->setExclusive(true);
263 QActionGroup *cg = new QActionGroup(this);
264 cg->setExclusive(true);
265 QActionGroup *cs = new QActionGroup(this);
266 cs->setExclusive(true);
267
268 buildMenus();
269
270 findAction("coordinates|points")->setActionGroup(cg);
271 findAction("coordinates|mm")->setActionGroup(cg);
272 findAction("coordinates|m")->setActionGroup(cg);
273 findAction("coordinates|inch")->setActionGroup(cg);
274
275 for (uint i = 0; i < iScalings.size(); ++i) {
276 char action[32];
277 sprintf(action, "scaling|%d", iScalings[i]);
278 findAction(action)->setActionGroup(cs);
279 }
280
281 connect(iRecentFileMenu, SIGNAL(triggered(QAction *)),
282 SLOT(recentFileAction(QAction *)));
283 connect(iSelectLayerMenu, SIGNAL(triggered(QAction *)),
284 SLOT(selectLayerAction(QAction *)));
285 connect(iMoveToLayerMenu, SIGNAL(triggered(QAction *)),
286 SLOT(moveToLayerAction(QAction *)));
287 connect(iTextStyleMenu, SIGNAL(triggered(QAction *)),
288 SLOT(textStyleAction(QAction *)));
289 connect(iLabelStyleMenu, SIGNAL(triggered(QAction *)),
290 SLOT(labelStyleAction(QAction *)));
291 connect(iGridSizeMenu, SIGNAL(triggered(QAction *)),
292 SLOT(gridSizeAction(QAction *)));
293 connect(iAngleSizeMenu, SIGNAL(triggered(QAction *)),
294 SLOT(angleSizeAction(QAction *)));
295
296 QSignalMapper *comboMap = new QSignalMapper(this);
297
298 iSelector[EUiGridSize] = new QComboBox();
299 iSelector[EUiAngleSize] = new QComboBox();
300 connect(iSelector[EUiGridSize], SIGNAL(activated(int)),
301 comboMap, SLOT(map()));
302 connect(iSelector[EUiAngleSize], SIGNAL(activated(int)),
303 comboMap, SLOT(map()));
304 comboMap->setMapping(iSelector[EUiGridSize], EUiGridSize);
305 comboMap->setMapping(iSelector[EUiAngleSize], EUiAngleSize);
306
307 addSnap("snapvtx");
308 addSnap("snapctl");
309 addSnap("snapbd");
310 addSnap("snapint");
311 addSnap("snapgrid");
312 iSnapTools->addWidget(iSelector[EUiGridSize]);
313 addSnap("snapangle");
314 iSnapTools->addWidget(iSelector[EUiAngleSize]);
315 addSnap("snapcustom");
316 addSnap("snapauto");
317
318 addEdit("copy");
319 addEdit("cut");
320 addEdit("paste");
321 addEdit("delete");
322 addEdit("undo");
323 addEdit("redo");
324 addEdit("zoom_in");
325 addEdit("zoom_out");
326 addEdit("fit_objects");
327 addEdit("fit_page");
328 addEdit("fit_width");
329 addEdit("keyboard");
330 iShiftKey = new QAction("shift_key", this);
331 iShiftKey->setCheckable(true);
332 iShiftKey->setIcon(prefsIcon("shift_key"));
333 iEditTools->addAction(iShiftKey);
334 iEditTools->addAction(findAction("grid_visible"));
335 iAbortButton = new QAction("stop", this);
336 iAbortButton->setIcon(prefsIcon("stop"));
337 iEditTools->addAction(iAbortButton);
338 connect(iShiftKey, SIGNAL(triggered()), SLOT(toolbarModifiersChanged()));
339 connect(iAbortButton, SIGNAL(triggered()), SLOT(abortDrawing()));
340
341 iPropertiesTools = new QDockWidget("Properties", this);
342 addDockWidget(getDockSide(L, "properties", Qt::LeftDockWidgetArea),
343 iPropertiesTools);
344 iPropertiesTools->setAllowedAreas(Qt::LeftDockWidgetArea|
345 Qt::RightDockWidgetArea);
346
347 iBookmarkTools = new QDockWidget("Bookmarks", this);
348 addDockWidget(getDockSide(L, "bookmarks", Qt::RightDockWidgetArea),
349 iBookmarkTools);
350 iBookmarkTools->setAllowedAreas(Qt::LeftDockWidgetArea|
351 Qt::RightDockWidgetArea);
352 iMenu[EPageMenu]->addAction(iBookmarkTools->toggleViewAction());
353
354 iNotesTools = new QDockWidget("Notes", this);
355 addDockWidget(getDockSide(L, "notes", Qt::RightDockWidgetArea),
356 iNotesTools);
357 iNotesTools->setAllowedAreas(Qt::LeftDockWidgetArea|
358 Qt::RightDockWidgetArea);
359 iMenu[EPageMenu]->addAction(iNotesTools->toggleViewAction());
360
361 iLayerTools = new QDockWidget("Layers", this);
362 addDockWidget(getDockSide(L, "layers", Qt::LeftDockWidgetArea),
363 iLayerTools);
364 iLayerTools->setAllowedAreas(Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea);
365
366 // object names are used for saving toolbar state
367 iSnapTools->setObjectName(QLatin1String("SnapTools"));
368 iObjectTools->setObjectName(QLatin1String("ObjectTools"));
369 iPropertiesTools->setObjectName(QLatin1String("PropertiesTools"));
370 iLayerTools->setObjectName(QLatin1String("LayerTools"));
371 iNotesTools->setObjectName(QLatin1String("NotesTools"));
372 iBookmarkTools->setObjectName(QLatin1String("BookmarkTools"));
373
374 QWidget *wg = new QFrame();
375 QGridLayout *lo = new QGridLayout();
376 wg->setLayout(lo);
377 int m = uiscale(2);
378 lo->setSpacing(1);
379 lo->setContentsMargins(m, m, m, m); // l t r b
380 // wg->setMaximumWidth(150);
381 lo->setSizeConstraint(QLayout::SetFixedSize);
382 QButtonGroup *bg = new QButtonGroup(wg);
383 bg->setExclusive(false);
384 connect(bg, SIGNAL(buttonClicked(int)), this, SLOT(absoluteButton(int)));
385 iButton[EUiMarkShape] = nullptr; // no such buttons
386 iButton[EUiDashStyle] = nullptr;
387 /* enum { EUiStroke, EUiFill, EUiPen, EUiDashStyle,
388 EUiTextSize, EUiMarkShape, EUiSymbolSize, EUiOpacity ... }
389 layout:
390 0: stroke
391 1: fill
392 2: pen \ pen button
393 3: dashstyle /
394 4: pathview
395 5: textsize
396 6: markshape \ symbolsize button
397 7: symbolsize /
398 8: opacity
399 */
400 for (int i = 0; i <= EUiOpacity; ++i) {
401 if (i != EUiDashStyle && i != EUiMarkShape && i != EUiOpacity) {
402 iButton[i] = new QToolButton();
403 iButton[i]->setFocusPolicy(Qt::NoFocus);
404 iButton[i]->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding,
405 QSizePolicy::ToolButton));
406 bg->addButton(iButton[i], i);
407 if (i == EUiPen)
408 lo->addWidget(iButton[i], i, 0, 2, 1);
409 else if (i == EUiSymbolSize)
410 lo->addWidget(iButton[i], i, 0, 2, 1);
411 else
412 lo->addWidget(iButton[i], i >= EUiTextSize ? i+1 : i, 0);
413 iButton[i]->setIconSize(adapt_size(iButton[i]->iconSize(), iUiScale));
414 }
415 iSelector[i] = new QComboBox();
416 if (i == EUiOpacity)
417 lo->addWidget(iSelector[i], i+1, 0, 1, 2);
418 else
419 lo->addWidget(iSelector[i], i >= EUiTextSize ? i+1 : i, 1);
420 connect(iSelector[i], SIGNAL(activated(int)), comboMap, SLOT(map()));
421 comboMap->setMapping(iSelector[i], i);
422 }
423 iButton[EUiStroke]->setIcon(prefsColorIcon(Color(1000, 0, 0)));
424 iButton[EUiFill]->setIcon(prefsColorIcon(Color(1000, 1000, 0)));
425 iButton[EUiPen]->setIcon(prefsIcon("pen"));
426 iButton[EUiTextSize]->setIcon(prefsIcon("mode_label"));
427 iButton[EUiSymbolSize]->setIcon(prefsIcon("mode_marks"));
428
429 iButton[EUiStroke]->setToolTip("Absolute stroke color");
430 iButton[EUiFill]->setToolTip("Absolute fill color");
431 iButton[EUiPen]->setToolTip("Absolute pen width");
432 iButton[EUiTextSize]->setToolTip("Absolute text size");
433 iButton[EUiSymbolSize]->setToolTip("Absolute symbol size");
434
435 iSelector[EUiStroke]->setToolTip("Symbolic stroke color");
436 iSelector[EUiFill]->setToolTip("Symbolic fill color");
437 iSelector[EUiPen]->setToolTip("Symbolic pen width");
438 iSelector[EUiTextSize]->setToolTip("Symbolic text size");
439 iSelector[EUiMarkShape]->setToolTip("Mark shape");
440 iSelector[EUiSymbolSize]->setToolTip("Symbolic symbol size");
441 iSelector[EUiDashStyle]->setToolTip("Dash style");
442 iSelector[EUiOpacity]->setToolTip("Opacity");
443
444 iSelector[EUiGridSize]->setToolTip("Grid size");
445 iSelector[EUiAngleSize]->setToolTip("Angle for angular snap");
446
447 connect(comboMap, SIGNAL(mapped(int)), this, SLOT(comboSelector(int)));
448
449 iPathView = new PathView(uiscale(100));
450 connect(iPathView, SIGNAL(activated(String)), SLOT(action(String)));
451 connect(iPathView, SIGNAL(showPathStylePopup(Vector)),
452 SLOT(showPathStylePopup(Vector)));
453 lo->addWidget(iPathView, 4, 1);
454 iPropertiesTools->setWidget(wg);
455
456 iModeIndicator = new QLabel();
457 iModeIndicator->setPixmap(prefsPixmap("mode_select"));
458 lo->addWidget(iModeIndicator, 4, 0);
459
460 QHBoxLayout *hol = new QHBoxLayout();
461 iViewNumber = new QToolButton();
462 iPageNumber = new QToolButton();
463 iViewNumber->setFocusPolicy(Qt::NoFocus);
464 iPageNumber->setFocusPolicy(Qt::NoFocus);
465 iViewNumber->setText("View 1/1");
466 iViewNumber->setToolTip("Current view number");
467 iPageNumber->setText("Page 1/1");
468 iPageNumber->setToolTip("Current page number");
469 iViewMarked = new QCheckBox();
470 iPageMarked = new QCheckBox();
471 iViewMarked->setFocusPolicy(Qt::NoFocus);
472 iPageMarked->setFocusPolicy(Qt::NoFocus);
473 bg->addButton(iViewNumber, EUiView);
474 bg->addButton(iPageNumber, EUiPage);
475 bg->addButton(iViewMarked, EUiViewMarked);
476 bg->addButton(iPageMarked, EUiPageMarked);
477 hol->setSpacing(0);
478 hol->addWidget(iViewMarked);
479 hol->addWidget(iViewNumber);
480 hol->addStretch(1);
481 hol->addWidget(iPageMarked);
482 hol->addWidget(iPageNumber);
483 lo->addLayout(hol, EUiOpacity + 2, 0, 1, -1);
484
485 iPageNotes = new QTextEdit();
486 iPageNotes->setAcceptRichText(false);
487 iPageNotes->setReadOnly(true);
488 iPageNotes->setFocusPolicy(Qt::NoFocus);
489 iNotesTools->setWidget(iPageNotes);
490
491 iBookmarks = new QListWidget();
492 iBookmarks->setToolTip("Bookmarks of this document");
493 iBookmarks->setFocusPolicy(Qt::NoFocus);
494 connect(iBookmarks, SIGNAL(itemActivated(QListWidgetItem *)),
495 this, SLOT(bookmarkSelected(QListWidgetItem *)));
496 iBookmarkTools->setWidget(iBookmarks);
497
498 iLayerList = new LayerBox();
499 iLayerList->setToolTip("Layers of this page");
500 iLayerTools->setWidget(iLayerList);
501 connect(iLayerList, SIGNAL(activated(String, String)),
502 this, SLOT(layerAction(String, String)));
503 connect(iLayerList, SIGNAL(showLayerBoxPopup(Vector, String)),
504 SLOT(showLayerBoxPopup(Vector, String)));
505
506 connect(iSelectLayerMenu, SIGNAL(aboutToShow()),
507 this, SLOT(aboutToShowSelectLayerMenu()));
508 connect(iMoveToLayerMenu, SIGNAL(aboutToShow()),
509 this, SLOT(aboutToShowMoveToLayerMenu()));
510 connect(iTextStyleMenu, SIGNAL(aboutToShow()),
511 this, SLOT(aboutToShowTextStyleMenu()));
512 connect(iLabelStyleMenu, SIGNAL(aboutToShow()),
513 this, SLOT(aboutToShowLabelStyleMenu()));
514 connect(iGridSizeMenu, SIGNAL(aboutToShow()),
515 this, SLOT(aboutToShowGridSizeMenu()));
516 connect(iAngleSizeMenu, SIGNAL(aboutToShow()),
517 this, SLOT(aboutToShowAngleSizeMenu()));
518
519 iSnapIndicator = new QLabel(statusBar());
520 statusBar()->addPermanentWidget(iSnapIndicator, 0);
521 QFont font = iSnapIndicator->font();
522 font.setFamily("Monospace");
523 iSnapIndicator->setFont(font);
524
525 iMouse = new QLabel(statusBar());
526 findAction("coordinates|points")->setChecked(true);
527 findAction("scaling|1")->setChecked(true);
528 statusBar()->addPermanentWidget(iMouse, 0);
529 iMouse->setFont(font);
530
531 iResolution = new QLabel(statusBar());
532 statusBar()->addPermanentWidget(iResolution, 0);
533
534 iCanvas->setObserver(this);
535 }
536
~AppUi()537 AppUi::~AppUi()
538 {
539 ipeDebug("AppUi C++ destructor");
540 }
541
542 // --------------------------------------------------------------------
543
setRecentFileMenu(const std::vector<String> & names)544 void AppUi::setRecentFileMenu(const std::vector<String> & names)
545 {
546 iRecentFileMenu->clear();
547 for (const auto & name : names) {
548 QAction *a = new QAction(QIpe(name), iRecentFileMenu);
549 iRecentFileMenu->addAction(a);
550 }
551 }
552
recentFileAction(QAction * a)553 void AppUi::recentFileAction(QAction *a)
554 {
555 luaRecentFileSelected(IpeQ(a->text()));
556 }
557
populateLayerMenu(LayerBox * layerList,QMenu * menu)558 static void populateLayerMenu(LayerBox *layerList, QMenu *menu)
559 {
560 menu->clear();
561 for (int i = 0; i < layerList->count(); ++i) {
562 LayerItem *item = dynamic_cast<LayerItem *>(layerList->item(i));
563 if (item) {
564 QAction *a = new QAction(QIpe(item->ipeLayerName), menu);
565 menu->addAction(a);
566 }
567 }
568
569 }
570
aboutToShowSelectLayerMenu()571 void AppUi::aboutToShowSelectLayerMenu()
572 {
573 populateLayerMenu(iLayerList, iSelectLayerMenu);
574 }
575
selectLayerAction(QAction * a)576 void AppUi::selectLayerAction(QAction *a)
577 {
578 action(String("selectinlayer-") + IpeQ(a->text()));
579 }
580
aboutToShowMoveToLayerMenu()581 void AppUi::aboutToShowMoveToLayerMenu()
582 {
583 populateLayerMenu(iLayerList, iMoveToLayerMenu);
584 }
585
moveToLayerAction(QAction * a)586 void AppUi::moveToLayerAction(QAction *a)
587 {
588 action(String("movetolayer-") + IpeQ(a->text()));
589 }
590
aboutToShowStyleMenu(Kind kind,MENUHANDLE menu,String current)591 void AppUi::aboutToShowStyleMenu(Kind kind, MENUHANDLE menu, String current)
592 {
593 AttributeSeq seq;
594 iCascade->allNames(kind, seq);
595 menu->clear();
596 for (uint i = 0; i < seq.size(); ++i) {
597 String s = seq[i].string();
598 QAction *a = new QAction(QIpe(s), menu);
599 a->setCheckable(true);
600 if (s == current)
601 a->setChecked(true);
602 menu->addAction(a);
603 }
604 }
605
aboutToShowTextStyleMenu()606 void AppUi::aboutToShowTextStyleMenu()
607 {
608 aboutToShowStyleMenu(ETextStyle, iTextStyleMenu, iAll.iTextStyle.string());
609 }
610
aboutToShowLabelStyleMenu()611 void AppUi::aboutToShowLabelStyleMenu()
612 {
613 aboutToShowStyleMenu(ELabelStyle, iLabelStyleMenu, iAll.iLabelStyle.string());
614 }
615
aboutToShowGridSizeMenu()616 void AppUi::aboutToShowGridSizeMenu()
617 {
618 iGridSizeMenu->clear();
619 for (auto &name : iComboContents[EUiGridSize]) {
620 QString s = QIpe(name);
621 QAction *a = new QAction(s, iGridSizeMenu);
622 a->setCheckable(true);
623 if (s == iSelector[EUiGridSize]->currentText())
624 a->setChecked(true);
625 iGridSizeMenu->addAction(a);
626 }
627 }
628
aboutToShowAngleSizeMenu()629 void AppUi::aboutToShowAngleSizeMenu()
630 {
631 iAngleSizeMenu->clear();
632 for (auto &name : iComboContents[EUiAngleSize]) {
633 QString s = QIpe(name);
634 QAction *a = new QAction(s, iAngleSizeMenu);
635 a->setCheckable(true);
636 if (s == iSelector[EUiAngleSize]->currentText())
637 a->setChecked(true);
638 iAngleSizeMenu->addAction(a);
639 }
640 }
641
textStyleAction(QAction * a)642 void AppUi::textStyleAction(QAction *a)
643 {
644 action(String("textstyle|") + IpeQ(a->text()));
645 }
646
labelStyleAction(QAction * a)647 void AppUi::labelStyleAction(QAction *a)
648 {
649 action(String("labelstyle|") + IpeQ(a->text()));
650 }
651
gridSizeAction(QAction * a)652 void AppUi::gridSizeAction(QAction *a)
653 {
654 action(String("gridsize|") + IpeQ(a->text()));
655 }
656
angleSizeAction(QAction * a)657 void AppUi::angleSizeAction(QAction *a)
658 {
659 action(String("anglesize|") + IpeQ(a->text()));
660 }
661
toolbarModifiersChanged()662 void AppUi::toolbarModifiersChanged()
663 {
664 if (iCanvas) {
665 int mod = 0;
666 if (iShiftKey->isChecked())
667 mod |= CanvasBase::EShift;
668 iCanvas->setAdditionalModifiers(mod);
669 }
670 }
671
abortDrawing()672 void AppUi::abortDrawing()
673 {
674 action("stop");
675 }
676
677 // --------------------------------------------------------------------
678
resetCombos()679 void AppUi::resetCombos()
680 {
681 for (int i = 0; i < EUiView; ++i)
682 iSelector[i]->clear();
683 }
684
addComboColors(AttributeSeq & sym,AttributeSeq & abs)685 void AppUi::addComboColors(AttributeSeq &sym, AttributeSeq &abs)
686 {
687 iSelector[EUiStroke]->addItem(IPEABSOLUTE);
688 iSelector[EUiFill]->addItem(IPEABSOLUTE);
689 iComboContents[EUiStroke].push_back(IPEABSOLUTE);
690 iComboContents[EUiFill].push_back(IPEABSOLUTE);
691 for (uint i = 0; i < sym.size(); ++i) {
692 Color color = abs[i].color();
693 String s = sym[i].string();
694 QIcon icon = prefsColorIcon(color);
695 iSelector[EUiStroke]->addItem(icon, QIpe(s));
696 iSelector[EUiFill]->addItem(icon, QIpe(s));
697 iComboContents[EUiStroke].push_back(s);
698 iComboContents[EUiFill].push_back(s);
699 }
700 }
701
addCombo(int sel,String s)702 void AppUi::addCombo(int sel, String s)
703 {
704 iSelector[sel]->addItem(QIpe(s));
705 }
706
setComboCurrent(int sel,int idx)707 void AppUi::setComboCurrent(int sel, int idx)
708 {
709 iSelector[sel]->setCurrentIndex(idx);
710 }
711
setButtonColor(int sel,Color color)712 void AppUi::setButtonColor(int sel, Color color)
713 {
714 iButton[sel]->setIcon(prefsColorIcon(color));
715 }
716
setPathView(const AllAttributes & all,Cascade * sheet)717 void AppUi::setPathView(const AllAttributes &all, Cascade *sheet)
718 {
719 iPathView->set(all, sheet);
720 }
721
setCheckMark(String name,Attribute a)722 void AppUi::setCheckMark(String name, Attribute a)
723 {
724 String sa = name + "|";
725 int na = sa.size();
726 String sb = sa + a.string();
727 for (std::map<String, QAction *>::iterator it = iActions.begin();
728 it != iActions.end(); ++it) {
729 if (it->first.left(na) == sa)
730 it->second->setChecked(it->first == sb);
731 }
732 }
733
setNumbers(String vno,bool vm,String pno,bool pm)734 void AppUi::setNumbers(String vno, bool vm, String pno, bool pm)
735 {
736 if (vno.empty()) {
737 iViewNumber->hide();
738 iViewMarked->hide();
739 } else {
740 iViewNumber->setText(QIpe(vno));
741 iViewNumber->show();
742 iViewMarked->setCheckState(vm ? Qt::Checked : Qt::Unchecked);
743 iViewMarked->show();
744 }
745 if (pno.empty()) {
746 iPageNumber->hide();
747 iPageMarked->hide();
748 } else {
749 iPageNumber->show();
750 iPageMarked->show();
751 iPageNumber->setText(QIpe(pno));
752 iPageMarked->setCheckState(pm ? Qt::Checked : Qt::Unchecked);
753 }
754 }
755
setNotes(String notes)756 void AppUi::setNotes(String notes)
757 {
758 iPageNotes->setPlainText(QIpe(notes));
759 }
760
setLayers(const Page * page,int view)761 void AppUi::setLayers(const Page *page, int view)
762 {
763 iLayerList->set(page, view);
764 }
765
setBookmarks(int no,const String * s)766 void AppUi::setBookmarks(int no, const String *s)
767 {
768 iBookmarks->clear();
769 for (int i = 0; i < no; ++i) {
770 QListWidgetItem *item = new QListWidgetItem(QIpe(s[i]));
771 if (s[i][0] == ' ')
772 item->setTextColor(Qt::blue);
773 iBookmarks->addItem(item);
774 }
775 }
776
bookmarkSelected(QListWidgetItem * item)777 void AppUi::bookmarkSelected(QListWidgetItem *item)
778 {
779 int index = iBookmarks->row(item);
780 luaBookmarkSelected(index);
781 }
782
setToolVisible(int m,bool vis)783 void AppUi::setToolVisible(int m, bool vis)
784 {
785 QWidget *tool = nullptr;
786 switch (m) {
787 case 0: tool = iPropertiesTools; break;
788 case 1: tool = iBookmarkTools; break;
789 case 2: tool = iNotesTools; break;
790 case 3: tool = iLayerTools; break;
791 default: break;
792 }
793 if (vis)
794 tool->show();
795 else
796 tool->hide();
797 }
798
setZoom(double zoom)799 void AppUi::setZoom(double zoom)
800 {
801 QString s;
802 s.sprintf("(%dppi)", int(72.0 * zoom));
803 iResolution->setText(s);
804 iCanvas->setZoom(zoom);
805 }
806
807 // --------------------------------------------------------------------
808
enableActions(QMenu * menu,bool mode)809 static void enableActions(QMenu *menu, bool mode)
810 {
811 menu->setEnabled(mode);
812 QListIterator<QAction *> it(menu->actions());
813 while (it.hasNext())
814 it.next()->setEnabled(mode);
815 }
816
setActionsEnabled(bool mode)817 void AppUi::setActionsEnabled(bool mode)
818 {
819 enableActions(iMenu[EFileMenu], mode);
820 enableActions(iMenu[EEditMenu], mode);
821 enableActions(iMenu[EModeMenu], mode);
822 enableActions(iMenu[EPropertiesMenu], mode);
823 enableActions(iMenu[ELayerMenu], mode);
824 enableActions(iMenu[EViewMenu], mode);
825 enableActions(iMenu[EPageMenu], mode);
826 enableActions(iMenu[EIpeletMenu], mode);
827
828 iModeActionGroup->setEnabled(mode);
829 iPropertiesTools->setEnabled(mode);
830 iLayerTools->setEnabled(mode);
831 iBookmarkTools->setEnabled(mode);
832 }
833
834 // --------------------------------------------------------------------
835
836 //! Window has been closed
closeEvent(QCloseEvent * ce)837 void AppUi::closeEvent(QCloseEvent* ce)
838 {
839 // calls model
840 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
841 lua_getfield(L, -1, "closeEvent");
842 lua_pushvalue(L, -2); // model
843 lua_remove(L, -3);
844 lua_call(L, 1, 1);
845 bool result = lua_toboolean(L, -1);
846 if (result)
847 ce->accept();
848 else
849 ce->ignore();
850 }
851
852 // --------------------------------------------------------------------
853
854 // Determine if action is checked
855 // Used for snapXXX, grid_visible, viewmarked, and pagemarked
actionState(const char * name)856 bool AppUi::actionState(const char *name)
857 {
858 if (!strcmp(name, "viewmarked"))
859 return (iViewMarked->checkState() == Qt::Checked);
860 if (!strcmp(name, "pagemarked"))
861 return (iPageMarked->checkState() == Qt::Checked);
862
863 QAction *a = findAction(name);
864 if (a)
865 return a->isChecked();
866 else
867 return 0;
868 }
869
870 // Check/uncheck an action
871 // Used for snapXXX, grid_visible, to initialize mode_select
setActionState(const char * name,bool value)872 void AppUi::setActionState(const char *name, bool value)
873 {
874 QAction *a = findAction(name);
875 if (a) a->setChecked(value);
876 }
877
absoluteButton(int id)878 void AppUi::absoluteButton(int id)
879 {
880 luaAbsoluteButton(selectorNames[id]);
881 }
882
selector(int id,String value)883 void AppUi::selector(int id, String value)
884 {
885 luaSelector(String(selectorNames[id]), value);
886 }
887
comboSelector(int id)888 void AppUi::comboSelector(int id)
889 {
890 luaSelector(String(selectorNames[id]), IpeQ(iSelector[id]->currentText()));
891 }
892
893 // --------------------------------------------------------------------
894
895 static const char * const aboutText =
896 "<qt><h1>Ipe %d.%d.%d</h1>"
897 "<p>Copyright (c) 1993-%d Otfried Cheong</p>"
898 "<p>The extensible drawing editor Ipe creates figures in PDF format, "
899 "using LaTeX to format the text in the figures.</p>"
900 "<p>Ipe is released under the GNU Public License.</p>"
901 "<p>See the <a href=\"http://ipe.otfried.org\">Ipe homepage</a>"
902 " for further information.</p>"
903 "<p>You can \"like\" Ipe and follow Ipe announcements on "
904 "<a href=\"http://www.facebook.com/drawing.editor.Ipe7\">Facebook</a>.</p>"
905 "<p>If you are an Ipe fan and want to show others, have a look at the "
906 "<a href=\"https://www.shirtee.com/en/store/ipe\">Ipe T-shirts</a>.</p>"
907 "<h3>Platinum and gold sponsors</h3>"
908 "<ul><li>Hee-Kap Ahn</li>"
909 "<li>Günter Rote</li>"
910 "<li>SCALGO</li>"
911 "<li>Martin Ziegler</li></ul>"
912 "<p>If you enjoy Ipe, feel free to treat the author on a cup of coffee at "
913 "<a href=\"https://ko-fi.com/ipe7author\">Ko-fi</a>.</p>"
914 "<p>You can also become a member of the exclusive community of "
915 "<a href=\"http://patreon.com/otfried\">Ipe patrons</a>. "
916 "For the price of a cup of coffee per month you can make a meaningful contribution "
917 "to the continuing development of Ipe.</p>"
918 "</qt>";
919
aboutIpe()920 void AppUi::aboutIpe()
921 {
922 std::vector<char> buf(strlen(aboutText) + 100);
923 sprintf(&buf[0], aboutText,
924 IPELIB_VERSION / 10000,
925 (IPELIB_VERSION / 100) % 100,
926 IPELIB_VERSION % 100,
927 COPYRIGHT_YEAR);
928
929 QMessageBox msgBox(this);
930 msgBox.setWindowTitle("About Ipe");
931 msgBox.setWindowIcon(prefsIcon("ipe"));
932 msgBox.setInformativeText(&buf[0]);
933 msgBox.setIconPixmap(prefsPixmap("ipe"));
934 msgBox.setStandardButtons(QMessageBox::Ok);
935 msgBox.exec();
936 }
937
qAction(const QString & name)938 void AppUi::qAction(const QString &name)
939 {
940 action(IpeQ(name));
941 }
942
action(String name)943 void AppUi::action(String name)
944 {
945 if (name == "fullscreen") {
946 setWindowState(windowState() ^ Qt::WindowFullScreen);
947 } else if (name == "about") {
948 aboutIpe();
949 } else {
950 if (name.left(5) == "mode_")
951 iModeIndicator->setPixmap(prefsPixmap(name));
952 luaAction(name);
953 }
954 }
955
956 // --------------------------------------------------------------------
957
pageSorter(lua_State * L,Document * doc,int pno,int width,int height,int thumbWidth)958 int AppUi::pageSorter(lua_State *L, Document *doc, int pno,
959 int width, int height, int thumbWidth)
960 {
961 QDialog *d = new QDialog();
962 if (pno >= 0)
963 d->setWindowTitle("Ipe View Sorter");
964 else
965 d->setWindowTitle("Ipe Page Sorter");
966
967 QLayout *lo = new QVBoxLayout;
968 PageSorter *p = new PageSorter(doc, pno, thumbWidth);
969 QDialogButtonBox *buttonBox =
970 new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
971 lo->addWidget(p);
972 lo->addWidget(buttonBox);
973 d->setLayout(lo);
974
975 d->connect(buttonBox, SIGNAL(accepted()), SLOT(accept()));
976 d->connect(buttonBox, SIGNAL(rejected()), SLOT(reject()));
977
978 d->resize(width, height);
979
980 if (d->exec() == QDialog::Rejected) {
981 delete d;
982 return 0;
983 }
984
985 lua_createtable(L, p->count(), 0);
986 for (int i = 1; i <= p->count(); ++i) {
987 lua_pushinteger(L, p->pageAt(i-1) + 1);
988 lua_rawseti(L, -2, i);
989 }
990 int m = p->iMarks.size();
991 lua_createtable(L, m, 0);
992 for (int i = 1; i <= m; ++i) {
993 lua_pushboolean(L, p->iMarks[i-1]);
994 lua_rawseti(L, -2, i);
995 }
996 delete d;
997 return 2;
998 }
999
1000 // --------------------------------------------------------------------
1001
showPathStylePopup(Vector v)1002 void AppUi::showPathStylePopup(Vector v)
1003 {
1004 luaShowPathStylePopup(v);
1005 }
1006
showLayerBoxPopup(Vector v,String layer)1007 void AppUi::showLayerBoxPopup(Vector v, String layer)
1008 {
1009 luaShowLayerBoxPopup(v, layer);
1010 }
1011
layerAction(String name,String layer)1012 void AppUi::layerAction(String name, String layer)
1013 {
1014 luaLayerAction(name, layer);
1015 }
1016
1017 // --------------------------------------------------------------------
1018
windowId()1019 WINID AppUi::windowId()
1020 {
1021 return this;
1022 }
1023
closeWindow()1024 void AppUi::closeWindow()
1025 {
1026 close();
1027 }
1028
setWindowCaption(bool mod,const char * s)1029 void AppUi::setWindowCaption(bool mod, const char *s)
1030 {
1031 setWindowModified(mod);
1032 setWindowTitle(QString::fromUtf8(s));
1033 }
1034
setMouseIndicator(const char * s)1035 void AppUi::setMouseIndicator(const char *s)
1036 {
1037 iMouse->setText(s);
1038 }
1039
setSnapIndicator(const char * s)1040 void AppUi::setSnapIndicator(const char *s)
1041 {
1042 iSnapIndicator->setText(s);
1043 }
1044
explain(const char * s,int t)1045 void AppUi::explain(const char *s, int t)
1046 {
1047 statusBar()->showMessage(QString::fromUtf8(s), t);
1048 }
1049
showWindow(int width,int height,int x,int y)1050 void AppUi::showWindow(int width, int height, int x, int y)
1051 {
1052 if (width > 0 && height > 0)
1053 resize(width, height);
1054 if (x >= 0 && y >= 0)
1055 move(x, y);
1056 show();
1057 }
1058
setFullScreen(int mode)1059 void AppUi::setFullScreen(int mode)
1060 {
1061 Qt::WindowStates state = windowState() & ~(Qt::WindowFullScreen | Qt::WindowMaximized);
1062 switch (mode) {
1063 case 1:
1064 state |= Qt::WindowMaximized;
1065 break;
1066 case 2:
1067 state |= Qt::WindowFullScreen;
1068 break;
1069 default:
1070 break;
1071 }
1072 setWindowState(state);
1073 }
1074
setClipboard(lua_State * L)1075 int AppUi::setClipboard(lua_State *L)
1076 {
1077 QString data = QString::fromUtf8(luaL_checkstring(L, 2));
1078 QClipboard *cb = QApplication::clipboard();
1079 cb->setText(data);
1080 return 0;
1081 }
1082
getImage(lua_State * L,QImage & img)1083 static int getImage(lua_State *L, QImage &img)
1084 {
1085 QImage im1 = img.convertToFormat(QImage::Format_ARGB32);
1086 int w = im1.width();
1087 int h = im1.height();
1088 Buffer data(w * h * sizeof(uint32_t));
1089 uint32_t *d = (uint32_t *) data.data();
1090 for (int y = 0; y < h; ++y) {
1091 uint32_t *p = (uint32_t *) im1.scanLine(y);
1092 for (int x = 0; x < w; ++x) {
1093 *d++ = *p++;
1094 }
1095 }
1096
1097 Bitmap bitmap(w, h, Bitmap::ENative, data);
1098 Rect r(Vector::ZERO, Vector(w, h));
1099 Image *im = new Image(r, bitmap);
1100 push_object(L, im);
1101 return 1;
1102 }
1103
clipboard(lua_State * L)1104 int AppUi::clipboard(lua_State *L)
1105 {
1106 bool allow_bitmap = lua_toboolean(L, 2);
1107 const QClipboard *cb = QApplication::clipboard();
1108 if (allow_bitmap) {
1109 const QMimeData *md = cb->mimeData();
1110 if (md->hasUrls()) {
1111 auto urls = md->urls();
1112 if (urls.size() == 1 && urls[0].isLocalFile()) {
1113 if (readImage(L, IpeQ(urls[0].toLocalFile())))
1114 return 1;
1115 }
1116 }
1117 QImage img = cb->image();
1118 if (!img.isNull()) {
1119 return getImage(L, img);
1120 }
1121 }
1122 QString data = cb->text();
1123 lua_pushstring(L, data.toUtf8());
1124 return 1;
1125 }
1126
createAppUi(lua_State * L0,int model)1127 AppUiBase *createAppUi(lua_State *L0, int model)
1128 {
1129 return new AppUi(L0, model);
1130 }
1131
1132 // --------------------------------------------------------------------
1133