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