1 // --------------------------------------------------------------------
2 // AppUi
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.h"
32 #include "tools.h"
33
34 #include "ipelua.h"
35
36 #include "ipecairopainter.h"
37
38 using namespace ipe;
39 using namespace ipelua;
40
41 // --------------------------------------------------------------------------------
42
43 const char * const AppUiBase::selectorNames[] =
44 { "stroke", "fill", "pen", "dashstyle", "textsize", "markshape", "symbolsize",
45 "opacity", "gridsize", "anglesize", "view", "page", "viewmarked", "pagemarked" };
46
AppUiBase(lua_State * L0,int model)47 AppUiBase::AppUiBase(lua_State *L0, int model)
48 {
49 L = L0;
50 iModel = model;
51 isInkMode = false;
52
53 iMouseIn = 0; // points
54 iMouseFactor = 1.0; // scale 1:1
55 iCoordinatesFormat = "%g%s, %g%s";
56 lua_getglobal(L, "prefs");
57 lua_getfield(L, -1, "coordinates_format");
58 if (lua_isstring(L, -1)) {
59 iCoordinatesFormat = lua_tolstring(L, -1, nullptr);
60 }
61 lua_pop(L, 1); // coordinates_format
62
63 lua_getfield(L, -1, "width_notes_bookmarks");
64 if (lua_isnumber(L, -1)) {
65 iWidthNotesBookmarks = lua_tointegerx(L, -1, nullptr);
66 }
67 lua_pop(L, 1); // width_notes_bookmarks
68
69 iUiScale = 100;
70 lua_getfield(L, -1, "ui_scaling");
71 if (lua_isnumber(L, -1))
72 iUiScale = lua_tointegerx(L, -1, nullptr);
73 lua_pop(L, 1); // ui_scaling
74
75 iToolbarScale = 100;
76 lua_getfield(L, -1, "toolbar_scaling");
77 if (lua_isnumber(L, -1))
78 iToolbarScale = lua_tointegerx(L, -1, nullptr);
79 lua_pop(L, 1); // toolbar_scaling
80
81 // win_ui_gap separates the input elements vertically, so the
82 // can be touched with a finger
83 iUiGap = 0;
84 lua_getfield(L, -1, "win_ui_gap");
85 if (lua_isnumber(L, -1))
86 iUiGap = lua_tointegerx(L, -1, nullptr);
87 lua_pop(L, 1); // win_ui_gap
88
89 // win_mini_edit leaves only the most important buttons on the Edit
90 // toolbar
91 lua_getfield(L, -1, "win_mini_edit");
92 isMiniEdit = lua_toboolean(L, -1);
93 lua_pop(L, 1); // win_mini_edit
94
95 // win_left_panels_float makes the panels on the left float on top
96 // of the canvas
97 lua_getfield(L, -1, "win_left_panels_float");
98 iLeftDockFloats = lua_toboolean(L, -1);
99 lua_pop(L, 1); // win_left_panels_float
100
101 iScalings.push_back(1);
102 lua_getfield(L, -1, "scale_factors");
103 if (lua_istable(L, -1)) {
104 int n = lua_rawlen(L, -1);
105 for (int i = 1; i <= n; ++i) {
106 lua_rawgeti(L, -1, i);
107 if (lua_isnumber(L, -1))
108 iScalings.push_back(lua_tointegerx(L, -1, nullptr));
109 lua_pop(L, 1); // number
110 }
111 }
112 lua_pop(L, 2); // prefs, scale_factors
113 }
114
~AppUiBase()115 AppUiBase::~AppUiBase()
116 {
117 ipeDebug("AppUiBase C++ destructor");
118 luaL_unref(L, LUA_REGISTRYINDEX, iModel);
119 // collect this model
120 lua_gc(L, LUA_GCCOLLECT, 0);
121 }
122
dpi() const123 int AppUiBase::dpi() const
124 {
125 return 96;
126 }
127
128 // --------------------------------------------------------------------
129
buildMenus()130 void AppUiBase::buildMenus()
131 {
132 addRootMenu(EFileMenu, "&File");
133 addRootMenu(EEditMenu, "&Edit");
134 addRootMenu(EPropertiesMenu, "P&roperties");
135 addRootMenu(ESnapMenu, "&Snap");
136 addRootMenu(EModeMenu, "&Mode");
137 addRootMenu(EZoomMenu, "&Zoom");
138 addRootMenu(ELayerMenu, "&Layers");
139 addRootMenu(EViewMenu, "&Views");
140 addRootMenu(EPageMenu, "&Pages");
141 addRootMenu(EIpeletMenu, "&Ipelets");
142 addRootMenu(EHelpMenu, "&Help");
143
144 addItem(EFileMenu, "New Window", "new_window");
145 addItem(EFileMenu, "New", "new");
146 addItem(EFileMenu, "Open", "open");
147 addItem(EFileMenu, "Save", "save");
148 addItem(EFileMenu, "Save as", "save_as");
149 addItem(EFileMenu);
150 startSubMenu(EFileMenu, "Recent files", ESubmenuRecentFiles);
151 iRecentFileMenu = endSubMenu();
152 addItem(EFileMenu);
153 addItem(EFileMenu, "Export as PNG", "export_png");
154 addItem(EFileMenu, "Export as EPS", "export_eps");
155 addItem(EFileMenu, "Export as SVG", "export_svg");
156 addItem(EFileMenu);
157 addItem(EFileMenu, "Insert image", "insert_image");
158 addItem(EFileMenu);
159 addItem(EFileMenu, "Automatically run Latex", "*auto_latex");
160 addItem(EFileMenu, "Run Latex", "run_latex");
161 addItem(EFileMenu);
162 addItem(EFileMenu, "Close", "close");
163
164 addItem(EEditMenu, "Undo", "undo");
165 addItem(EEditMenu, "Redo", "redo");
166 addItem(EEditMenu);
167 addItem(EEditMenu, "Cut", "cut");
168 addItem(EEditMenu, "Copy", "copy");
169 addItem(EEditMenu, "Paste", "paste");
170 addItem(EEditMenu, "Paste with layer", "paste_with_layer");
171 addItem(EEditMenu, "Paste at cursor", "paste_at_cursor");
172 addItem(EEditMenu, "Delete", "delete");
173 addItem(EEditMenu);
174 addItem(EEditMenu, "Group", "group");
175 addItem(EEditMenu, "Ungroup", "ungroup");
176 addItem(EEditMenu, "Front", "front");
177 addItem(EEditMenu, "Back", "back");
178 addItem(EEditMenu, "Forward", "forward");
179 addItem(EEditMenu, "Backward", "backward");
180 addItem(EEditMenu, "Just before", "before");
181 addItem(EEditMenu, "Just behind", "behind");
182 addItem(EEditMenu, "Duplicate", "duplicate");
183 addItem(EEditMenu, "Select all", "select_all");
184 addItem(EEditMenu);
185 addItem(EEditMenu, "Pick properties", "pick_properties");
186 addItem(EEditMenu, "Apply properties", "apply_properties");
187 addItem(EEditMenu);
188 addItem(EEditMenu, "Insert text box", "insert_text_box");
189 addItem(EEditMenu, "Change text width", "change_width");
190 addItem(EEditMenu, "Edit object", "edit");
191 addItem(EEditMenu, "Edit object as XML", "edit_as_xml");
192 addItem(EEditMenu);
193 addItem(EEditMenu, "Edit group", "edit_group");
194 addItem(EEditMenu, "End group edit", "end_group_edit");
195 addItem(EEditMenu);
196 addItem(EEditMenu, "Document properties", "document_properties");
197 addItem(EEditMenu, "Style sheets", "style_sheets");
198 addItem(EEditMenu, "Update style sheets", "update_style_sheets");
199 addItem(EEditMenu, "Check symbolic attributes", "check_style");
200
201 startSubMenu(EPropertiesMenu, "Pinned");
202 addSubItem("none", "pinned|none");
203 addSubItem("horizontal", "pinned|horizontal");
204 addSubItem("vertical", "pinned|vertical");
205 addSubItem("fixed", "pinned|fixed");
206 endSubMenu();
207
208 startSubMenu(EPropertiesMenu, "Transformations");
209 addSubItem("translations", "transformations|translations");
210 addSubItem("rigid motions", "transformations|rigid");
211 addSubItem("affine", "transformations|affine");
212 endSubMenu();
213
214 addItem(EPropertiesMenu);
215
216 startSubMenu(EPropertiesMenu, "Minipage style", ESubmenuTextStyle);
217 iTextStyleMenu = endSubMenu();
218 startSubMenu(EPropertiesMenu, "Label style", ESubmenuLabelStyle);
219 iLabelStyleMenu = endSubMenu();
220
221 startSubMenu(EPropertiesMenu, "Horizontal alignment");
222 addSubItem("left", "horizontalalignment|left");
223 addSubItem("center", "horizontalalignment|hcenter");
224 addSubItem("right", "horizontalalignment|right");
225 endSubMenu();
226
227 startSubMenu(EPropertiesMenu, "Vertical alignment");
228 addSubItem("bottom", "verticalalignment|bottom");
229 addSubItem("baseline", "verticalalignment|baseline");
230 addSubItem("center", "verticalalignment|vcenter");
231 addSubItem("top", "verticalalignment|top");
232 endSubMenu();
233
234 startSubMenu(EPropertiesMenu, "Transformable text");
235 addSubItem("Yes", "transformabletext|true");
236 addSubItem("No", "transformabletext|false");
237 endSubMenu();
238
239 startSubMenu(EPropertiesMenu, "Spline type");
240 addSubItem("bspline", "splinetype|bspline");
241 addSubItem("cardinal", "splinetype|cardinal");
242 addSubItem("spiro", "splinetype|spiro");
243 endSubMenu();
244
245 addItem(EModeMenu, "Select objects (with Shift: non-destructive)",
246 "mode_select");
247 addItem(EModeMenu, "Translate objects (with Shift: horizontal/vertical)",
248 "mode_translate");
249 addItem(EModeMenu, "Rotate objects", "mode_rotate");
250 addItem(EModeMenu, "Stretch objects (with Shift: scale objects)",
251 "mode_stretch");
252 addItem(EModeMenu, "Shear objects", "mode_shear");
253 addItem(EModeMenu, "Move graph nodes", "mode_graph");
254 addItem(EModeMenu, "Pan the canvas", "mode_pan");
255 addItem(EModeMenu, "Shred objects", "mode_shredder");
256 addItem(EModeMenu, "Laser pointer", "mode_laser");
257 addItem(EModeMenu);
258 addItem(EModeMenu, "Text labels", "mode_label");
259 addItem(EModeMenu, "Mathematical symbols", "mode_math");
260 addItem(EModeMenu, "Paragraphs", "mode_paragraph");
261 addItem(EModeMenu, "Marks", "mode_marks");
262 addItem(EModeMenu, "Axis-parallel rectangles (with Shift: squares)",
263 "mode_rectangles1");
264 addItem(EModeMenu, "Axis-parallel rectangles, by center (with Shift: squares)",
265 "mode_rectangles2");
266 addItem(EModeMenu, "Rectangles (with Shift: squares)", "mode_rectangles3");
267 addItem(EModeMenu, "Parallelograms (with Shift: axis-parallel)",
268 "mode_parallelogram");
269 addItem(EModeMenu, "Lines and polylines", "mode_lines");
270 addItem(EModeMenu, "Polygons", "mode_polygons");
271 addItem(EModeMenu, "Splines", "mode_splines");
272 addItem(EModeMenu, "Splinegons", "mode_splinegons");
273 addItem(EModeMenu, "Circular arcs (by center, right and left point)",
274 "mode_arc1");
275 addItem(EModeMenu, "Circular arcs (by center, left and right point)",
276 "mode_arc2");
277 addItem(EModeMenu, "Circular arcs (by 3 points)", "mode_arc3");
278 addItem(EModeMenu, "Circles (by center and radius)", "mode_circle1");
279 addItem(EModeMenu, "Circles (by diameter)", "mode_circle2");
280 addItem(EModeMenu, "Circles (by 3 points)", "mode_circle3");
281 addItem(EModeMenu, "Ink", "mode_ink");
282
283 // @ means the action can be used while drawing
284 // * means the action is checkable (on/off)
285 // Checkable actions work differently in Qt and Win32/Cocoa:
286 // Qt already toggles the state
287 // In Win32/Cocoa the action needs to toggle the state.
288
289 addItem(ESnapMenu, "Snap to vertex", "@*snapvtx");
290 addItem(ESnapMenu, "Snap to control point", "@*snapctl");
291 addItem(ESnapMenu, "Snap to boundary", "@*snapbd");
292 addItem(ESnapMenu, "Snap to intersection", "@*snapint");
293 addItem(ESnapMenu, "Snap to grid", "@*snapgrid");
294 addItem(ESnapMenu, "Snap to custom grid", "@*snapcustom");
295 addItem(ESnapMenu, "Angular snap", "@*snapangle");
296 addItem(ESnapMenu, "Automatic snap", "@*snapauto");
297 addItem(ESnapMenu);
298 startSubMenu(ESnapMenu, "Grid size", ESubmenuGridSize);
299 iGridSizeMenu = endSubMenu();
300 startSubMenu(ESnapMenu, "Radial angle", ESubmenuAngleSize);
301 iAngleSizeMenu = endSubMenu();
302 addItem(ESnapMenu);
303 addItem(ESnapMenu, "Set origin", "@set_origin");
304 addItem(ESnapMenu, "Set origin && snap", "@set_origin_snap");
305 addItem(ESnapMenu, "Show axes", "@*show_axes");
306 addItem(ESnapMenu, "Set direction", "@set_direction");
307 addItem(ESnapMenu, "Set tangent direction", "@set_tangent_direction");
308 addItem(ESnapMenu, "Reset direction", "@reset_direction");
309 addItem(ESnapMenu, "Set line", "@set_line");
310 addItem(ESnapMenu, "Set line && snap", "@set_line_snap");
311
312 addItem(EZoomMenu, "Fullscreen", "@*fullscreen");
313 addItem(EZoomMenu, "Grid visible", "@*grid_visible");
314 addItem(EZoomMenu, "Pretty display", "@*pretty_display");
315
316 startSubMenu(EZoomMenu, "Coordinates");
317 addSubItem("points", "@coordinates|points");
318 addSubItem("mm", "@coordinates|mm");
319 addSubItem("m", "@coordinates|m");
320 addSubItem("inch", "@coordinates|inch");
321 endSubMenu();
322
323 startSubMenu(EZoomMenu, "Coordinate scale");
324 for (int s : iScalings) {
325 char display[32];
326 char action[32];
327 if (s < 0)
328 sprintf(display, "%d:1", -s);
329 else
330 sprintf(display, "1:%d", s);
331 sprintf(action, "@scaling|%d", s);
332 addSubItem(display, action);
333 }
334 endSubMenu();
335
336 addItem(EZoomMenu);
337 addItem(EZoomMenu, "Zoom in", "@zoom_in");
338 addItem(EZoomMenu, "Zoom out", "@zoom_out");
339 addItem(EZoomMenu, "Normal size", "@normal_size");
340 addItem(EZoomMenu, "Fit page", "@fit_page");
341 addItem(EZoomMenu, "Fit width", "@fit_width");
342 addItem(EZoomMenu, "Fit page top", "@fit_top");
343 addItem(EZoomMenu, "Fit objects", "@fit_objects");
344 addItem(EZoomMenu, "Fit selection", "@fit_selection");
345 addItem(EZoomMenu);
346 addItem(EZoomMenu, "Pan here", "@pan_here");
347
348 addItem(ELayerMenu, "New layer", "new_layer");
349 addItem(ELayerMenu, "Rename active layer", "rename_active_layer");
350 addItem(ELayerMenu);
351 addItem(ELayerMenu, "Select all in active layer",
352 "select_in_active_layer");
353 startSubMenu(ELayerMenu, "Select all in layer", ESubmenuSelectLayer);
354 iSelectLayerMenu = endSubMenu();
355 addItem(ELayerMenu, "Move to active layer", "move_to_active_layer");
356 startSubMenu(ELayerMenu, "Move to layer", ESubmenuMoveLayer);
357 iMoveToLayerMenu = endSubMenu();
358
359 addItem(EViewMenu, "Next view", "next_view");
360 addItem(EViewMenu, "Previous view", "previous_view");
361 addItem(EViewMenu, "First view", "first_view");
362 addItem(EViewMenu, "Last view", "last_view");
363 addItem(EViewMenu);
364 addItem(EViewMenu, "New layer, new view", "new_layer_view");
365 addItem(EViewMenu, "New view", "new_view");
366 addItem(EViewMenu, "Delete view", "delete_view");
367 addItem(EViewMenu);
368 addItem(EViewMenu, "Mark views from this view", "mark_from_view");
369 addItem(EViewMenu, "Unmark views from this view", "unmark_from_view");
370 addItem(EViewMenu);
371 addItem(EViewMenu, "Jump to view", "jump_view");
372 addItem(EViewMenu, "Edit view", "edit_view");
373 addItem(EViewMenu, "View sorter", "view_sorter");
374
375 addItem(EPageMenu, "Next page", "next_page");
376 addItem(EPageMenu, "Previous page", "previous_page");
377 addItem(EPageMenu, "First page", "first_page");
378 addItem(EPageMenu, "Last page", "last_page");
379 addItem(EPageMenu);
380 addItem(EPageMenu, "New page", "new_page");
381 addItem(EPageMenu, "Cut page", "cut_page");
382 addItem(EPageMenu, "Copy page", "copy_page");
383 addItem(EPageMenu, "Paste page", "paste_page");
384 addItem(EPageMenu, "Delete page", "delete_page");
385 addItem(EPageMenu);
386 addItem(EPageMenu, "Jump to page", "jump_page");
387 addItem(EPageMenu, "Edit title && sections", "edit_title");
388 addItem(EPageMenu, "Edit notes", "edit_notes");
389 addItem(EPageMenu, "Page sorter", "page_sorter");
390 addItem(EPageMenu);
391 #ifndef IPEUI_QT
392 // In Qt these are created using "toggleViewAction()"
393 addItem(EPageMenu, "Notes", "@*toggle_notes");
394 addItem(EPageMenu, "Bookmarks", "@*toggle_bookmarks");
395 #endif
396
397 addItem(EHelpMenu, "Ipe &manual", "manual");
398 addItem(EHelpMenu, "Preferences", "preferences");
399 addItem(EHelpMenu, "Onscreen keyboard", "@keyboard");
400 addItem(EHelpMenu, "Show &configuration", "show_configuration");
401 addItem(EHelpMenu, "Show &libraries", "show_libraries");
402 addItem(EHelpMenu, "&Ipelet information", "about_ipelets");
403 addItem(EHelpMenu, "Enable online Latex-compilation", "cloud_latex");
404
405 lua_getglobal(L, "prefs");
406 lua_getfield(L, -1, "developer");
407 if (lua_toboolean(L, -1)) {
408 startSubMenu(EHelpMenu, "Developer");
409 addSubItem("Reload ipelets", "developer_reload_ipelets");
410 addSubItem("List shortcuts", "developer_list_shortcuts");
411 endSubMenu();
412 }
413 lua_pop(L, 2); // developer, prefs
414
415 #ifndef IPEUI_COCOA
416 addItem(EHelpMenu, "&About Ipe", "about");
417 #endif
418
419 // build Ipelet menu
420 lua_getglobal(L, "ipelets");
421 int n = lua_rawlen(L, -1);
422 for (int i = 1; i <= n; ++i) {
423 lua_rawgeti(L, -1, i);
424 lua_getfield(L, -1, "label");
425 if (!lua_isstring(L, -1)) {
426 lua_pop(L, 2); // label, ipelet
427 continue;
428 }
429 String label(lua_tolstring(L, -1, nullptr));
430 lua_pop(L, 1);
431 lua_getfield(L, -1, "name");
432 String name(lua_tolstring(L, -1, nullptr));
433 lua_pop(L, 1);
434 lua_getfield(L, -1, "methods");
435 char buf[20];
436 if (lua_isnil(L, -1)) {
437 String action("ipelet_1_");
438 action += name;
439 addItem(EIpeletMenu, label.z(), action.z());
440 } else {
441 int m = lua_rawlen(L, -1);
442 startSubMenu(EIpeletMenu, label.z());
443 for (int j = 1; j <= m; ++j) {
444 lua_rawgeti(L, -1, j);
445 lua_getfield(L, -1, "label");
446 sprintf(buf, "ipelet_%d_", j);
447 String action(buf);
448 action += name;
449 addSubItem(lua_tolstring(L, -1, nullptr), action.z());
450 lua_pop(L, 2); // sublabel, method
451 }
452 endSubMenu();
453 }
454 lua_pop(L, 2); // methods, ipelet
455 }
456 lua_pop(L, 1);
457 }
458
459 // --------------------------------------------------------------------
460
canvasObserverWheelMoved(double xDegrees,double yDegrees,int kind)461 void AppUiBase::canvasObserverWheelMoved(double xDegrees, double yDegrees,
462 int kind)
463 {
464 if (xDegrees != 0.0 || yDegrees != 0.0) {
465 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
466 lua_getfield(L, -1, "wheel_zoom");
467 lua_insert(L, -2); // model
468 lua_pushnumber(L, xDegrees);
469 lua_pushnumber(L, yDegrees);
470 lua_pushinteger(L, kind);
471 luacall(L, 4, 0);
472 } else
473 // result of a zoom gesture (Windows only, currently)
474 setZoom(iCanvas->zoom());
475 }
476
canvasObserverToolChanged(bool hasTool)477 void AppUiBase::canvasObserverToolChanged(bool hasTool)
478 {
479 setActionsEnabled(!hasTool || isInkMode);
480 }
481
adjust(double & x,int mode,double factor)482 static void adjust(double &x, int mode, double factor)
483 {
484 if (ipe::abs(x) < 1e-12)
485 x = 0.0;
486 x *= factor;
487 switch (mode) {
488 case 1: // mm
489 x = (x / 72.0) * 25.4;
490 break;
491 case 2: // m
492 x = (x / 72000.0) * 25.4;
493 break;
494 case 3: // in
495 x /= 72;
496 break;
497 default:
498 break;
499 }
500 }
501
502 static const char * const mouse_units[] = { "", " mm", " m", " in" };
503
canvasObserverPositionChanged()504 void AppUiBase::canvasObserverPositionChanged()
505 {
506 Vector v = iCanvas->CanvasBase::pos();
507 const Snap &snap = iCanvas->snap();
508 if (snap.iWithAxes) {
509 v = v - snap.iOrigin;
510 v = Linear(-snap.iDir) * v;
511 }
512 adjust(v.x, iMouseIn, iMouseFactor);
513 adjust(v.y, iMouseIn, iMouseFactor);
514 const char *units = mouse_units[iMouseIn];
515 char s[256];
516 sprintf(s, iCoordinatesFormat.z(), v.x, units, v.y, units);
517 setMouseIndicator(s);
518 }
519
canvasObserverMouseAction(int button)520 void AppUiBase::canvasObserverMouseAction(int button)
521 {
522 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
523 lua_getfield(L, -1, "mouseButtonAction");
524 lua_insert(L, -2); // model
525 push_button(L, button);
526 luacall(L, 3, 0);
527 }
528
canvasObserverSizeChanged()529 void AppUiBase::canvasObserverSizeChanged()
530 {
531 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
532 lua_getfield(L, -1, "sizeChanged");
533 lua_insert(L, -2); // model
534 luacall(L, 1, 0);
535 }
536
537 // --------------------------------------------------------------------
538
actionInfo(lua_State * L) const539 int AppUiBase::actionInfo(lua_State *L) const
540 {
541 return 0; // only Windows will override this
542 }
543
call_selector(lua_State * L,int model,String name)544 static void call_selector(lua_State *L, int model, String name)
545 {
546 // calls model selector
547 lua_rawgeti(L, LUA_REGISTRYINDEX, model);
548 lua_getfield(L, -1, "selector");
549 lua_pushvalue(L, -2); // model
550 lua_remove(L, -3);
551 push_string(L, name);
552 }
553
luaSelector(String name,String value)554 void AppUiBase::luaSelector(String name, String value)
555 {
556 call_selector(L, iModel, name);
557 if (value == "true")
558 lua_pushboolean(L, true);
559 else if (value == "false")
560 lua_pushboolean(L, false);
561 else
562 push_string(L, value);
563 luacall(L, 3, 0);
564 }
565
luaAbsoluteButton(const char * s)566 void AppUiBase::luaAbsoluteButton(const char *s)
567 {
568 // calls model selector
569 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
570 lua_getfield(L, -1, "absoluteButton");
571 lua_insert(L, -2); // method, model
572 lua_pushstring(L, s);
573 luacall(L, 2, 0);
574 }
575
576 // --------------------------------------------------------------------
577
luaAction(String name)578 void AppUiBase::luaAction(String name)
579 {
580 if (isInkMode && iCanvas->tool() != nullptr)
581 return; // refuse any action while drawing ink
582 if (name.left(12) == "coordinates|") {
583 if (name.right(2) == "mm")
584 iMouseIn = 1;
585 else if (name.right(1) == "m")
586 iMouseIn = 2;
587 else if (name.right(4) == "inch")
588 iMouseIn = 3;
589 else
590 iMouseIn = 0;
591 } else if (name.left(8) == "scaling|") {
592 Lex lex(name.substr(8));
593 int s = lex.getInt();
594 if (s < 0)
595 iMouseFactor = 1.0 / -s;
596 else
597 iMouseFactor = s;
598 } else if (name.find('|') >= 0) {
599 // calls model selector
600 int i = name.find('|');
601 luaSelector(name.left(i), name.substr(i+1));
602 } else {
603 // calls model action
604 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
605 lua_getfield(L, -1, "action");
606 lua_insert(L, -2); // before model
607 push_string(L, name);
608 luacall(L, 2, 0);
609 }
610 }
611
luaShowPathStylePopup(Vector v)612 void AppUiBase::luaShowPathStylePopup(Vector v)
613 {
614 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
615 lua_getfield(L, -1, "showPathStylePopup");
616 lua_insert(L, -2); // before model
617 push_vector(L, v);
618 luacall(L, 2, 0);
619 }
620
luaShowLayerBoxPopup(Vector v,String layer)621 void AppUiBase::luaShowLayerBoxPopup(Vector v, String layer)
622 {
623 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
624 lua_getfield(L, -1, "showLayerBoxPopup");
625 lua_insert(L, -2); // before model
626 push_vector(L, v);
627 push_string(L, layer);
628 luacall(L, 3, 0);
629 }
630
luaLayerAction(String name,String layer)631 void AppUiBase::luaLayerAction(String name, String layer)
632 {
633 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
634 lua_getfield(L, -1, "layerAction");
635 lua_insert(L, -2); // before model
636 push_string(L, name);
637 push_string(L, layer);
638 luacall(L, 3, 0);
639 }
640
luaBookmarkSelected(int index)641 void AppUiBase::luaBookmarkSelected(int index)
642 {
643 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
644 lua_getfield(L, -1, "bookmark");
645 lua_insert(L, -2); // method, model
646 lua_pushnumber(L, index + 1);
647 luacall(L, 2, 0);
648 }
649
luaRecentFileSelected(String name)650 void AppUiBase::luaRecentFileSelected(String name)
651 {
652 lua_rawgeti(L, LUA_REGISTRYINDEX, iModel);
653 lua_getfield(L, -1, "recent_file");
654 lua_insert(L, -2); // method, model
655 push_string(L, name);
656 luacall(L, 2, 0);
657 }
658
659 // --------------------------------------------------------------------
660
stripMark(Attribute mark)661 static String stripMark(Attribute mark)
662 {
663 String s = mark.string();
664 if (s.left(5) == "mark/") {
665 int i = s.rfind('(');
666 return s.substr(5, i > 0 ? i-5 : -1);
667 } else
668 return String();
669 }
670
showInCombo(const Cascade * sheet,Kind kind,int sel,const char * deflt)671 void AppUiBase::showInCombo(const Cascade *sheet, Kind kind,
672 int sel, const char *deflt)
673 {
674 AttributeSeq seq;
675 sheet->allNames(kind, seq);
676 if (!seq.size() && deflt != nullptr) {
677 addCombo(sel, deflt);
678 iComboContents[sel].push_back(deflt);
679 }
680 if (kind != EGridSize && kind != EAngleSize && kind != EDashStyle
681 && kind != EOpacity) {
682 addCombo(sel, IPEABSOLUTE);
683 iComboContents[sel].push_back(IPEABSOLUTE);
684 }
685 for (const auto & att : seq) {
686 String s = att.string();
687 addCombo(sel, s);
688 iComboContents[sel].push_back(s);
689 }
690 }
691
showMarksInCombo(const Cascade * sheet)692 void AppUiBase::showMarksInCombo(const Cascade *sheet)
693 {
694 AttributeSeq seq;
695 sheet->allNames(ESymbol, seq);
696 for (const auto & att : seq) {
697 String s = stripMark(att);
698 if (!s.empty()) {
699 addCombo(EUiMarkShape, s);
700 iComboContents[EUiMarkShape].push_back(s);
701 }
702 }
703 }
704
setupSymbolicNames(const Cascade * sheet)705 void AppUiBase::setupSymbolicNames(const Cascade *sheet)
706 {
707 resetCombos();
708 for (int i = 0; i < EUiView; ++i)
709 iComboContents[i].clear();
710 AttributeSeq seq, absColor;
711 sheet->allNames(EColor, seq);
712 for (const auto & att : seq)
713 absColor.push_back(sheet->find(EColor, att));
714 addComboColors(seq, absColor);
715 showInCombo(sheet, EPen, EUiPen);
716 showInCombo(sheet, ETextSize, EUiTextSize);
717 showInCombo(sheet, ESymbolSize, EUiSymbolSize);
718 showInCombo(sheet, EDashStyle, EUiDashStyle);
719 showInCombo(sheet, EOpacity, EUiOpacity);
720 showMarksInCombo(sheet);
721 showInCombo(sheet, EGridSize, EUiGridSize, "16pt");
722 showInCombo(sheet, EAngleSize, EUiAngleSize, "45 deg");
723 }
724
setGridAngleSize(Attribute abs_grid,Attribute abs_angle)725 void AppUiBase::setGridAngleSize(Attribute abs_grid, Attribute abs_angle)
726 {
727 AttributeSeq seq;
728 iCascade->allNames(EGridSize, seq);
729 if (!seq.size())
730 setComboCurrent(EUiGridSize, 0);
731 for (int i = 0; i < size(seq); ++i) {
732 if (iCascade->find(EGridSize, seq[i]) == abs_grid) {
733 setComboCurrent(EUiGridSize, i);
734 break;
735 }
736 }
737 seq.clear();
738 iCascade->allNames(EAngleSize, seq);
739 if (!seq.size())
740 setComboCurrent(EUiAngleSize, 0);
741 for (int i = 0; i < size(seq); ++i) {
742 if (iCascade->find(EAngleSize, seq[i]) == abs_angle) {
743 setComboCurrent(EUiAngleSize, i);
744 break;
745 }
746 }
747 }
748
749 // --------------------------------------------------------------------
750
setAttribute(int sel,Attribute a)751 void AppUiBase::setAttribute(int sel, Attribute a)
752 {
753 String s = a.isSymbolic() ? a.string() : IPEABSOLUTE;
754 for (int i = 0; i < int(iComboContents[sel].size()); ++i) {
755 if (iComboContents[sel][i] == s) {
756 setComboCurrent(sel, i);
757 return;
758 }
759 }
760 }
761
setAttributes(const AllAttributes & all,Cascade * sheet)762 void AppUiBase::setAttributes(const AllAttributes &all, Cascade *sheet)
763 {
764 iAll = all;
765 iCascade = sheet;
766
767 setPathView(all, sheet);
768
769 setAttribute(EUiStroke, iAll.iStroke);
770 setAttribute(EUiFill, iAll.iFill);
771 Color stroke = iCascade->find(EColor, iAll.iStroke).color();
772 Color fill = iCascade->find(EColor, iAll.iFill).color();
773 setButtonColor(EUiStroke, stroke);
774 setButtonColor(EUiFill, fill);
775 setAttribute(EUiPen, iAll.iPen);
776 setAttribute(EUiTextSize, iAll.iTextSize);
777 setAttribute(EUiSymbolSize, iAll.iSymbolSize);
778 setAttribute(EUiDashStyle, iAll.iDashStyle);
779 setAttribute(EUiOpacity, iAll.iOpacity);
780
781 String s = stripMark(iAll.iMarkShape);
782 for (int i = 0; i < int(iComboContents[EUiMarkShape].size()); ++i) {
783 if (iComboContents[EUiMarkShape][i] == s) {
784 setComboCurrent(EUiMarkShape, i);
785 break;
786 }
787 }
788
789 setCheckMark("horizontalalignment", Attribute(iAll.iHorizontalAlignment));
790 setCheckMark("verticalalignment", Attribute(iAll.iVerticalAlignment));
791 setCheckMark("splinetype", Attribute(iAll.iSplineType));
792 setCheckMark("pinned", Attribute(iAll.iPinned));
793 setCheckMark("transformabletext",
794 Attribute::Boolean(iAll.iTransformableText));
795 setCheckMark("transformations", Attribute(iAll.iTransformations));
796 setCheckMark("linejoin", Attribute(iAll.iLineJoin));
797 setCheckMark("linecap", Attribute(iAll.iLineCap));
798 setCheckMark("fillrule", Attribute(iAll.iFillRule));
799 }
800
ipeIcon(String action)801 int AppUiBase::ipeIcon(String action)
802 {
803 if (!ipeIcons) {
804 String fname = ipeIconDirectory() + "icons.ipe";
805 if (!Platform::fileExists(fname))
806 return -1;
807 ipeIconsDark.reset(Document::loadWithErrorReport(fname.z()));
808 ipeIcons.reset(new Document(*ipeIconsDark));
809 ipeIcons->cascade()->remove(0);
810 }
811 return ipeIcons->findPage(action);
812 }
813
readImage(lua_State * L,String fn)814 int AppUiBase::readImage(lua_State *L, String fn)
815 {
816 // check if it is perhaps a JPEG file
817 FILE *f = Platform::fopen(fn.z(), "rb");
818 bool jpeg = false;
819 if (f != nullptr) {
820 jpeg = (std::fgetc(f) == 0xff && std::fgetc(f) == 0xd8);
821 fclose(f);
822 }
823
824 ipeDebug("Dropping file %s (jpeg: %d)", fn.z(), jpeg);
825
826 Vector dpi;
827 const char *errmsg;
828 Bitmap bm = jpeg ? Bitmap::readJpeg(fn.z(), dpi, errmsg) : Bitmap::readPNG(fn.z(), dpi, errmsg);
829
830 if (bm.isNull())
831 return 0;
832
833 ipe::Rect r(Vector::ZERO, Vector(bm.width(), bm.height()));
834 Image *img = new Image(r, bm);
835 push_object(L, img);
836 return 1;
837 }
838
839 // --------------------------------------------------------------------
840
841