1 struct MyFrame : wxFrame {
2     typedef std::vector<std::pair<wxString, wxString>> MenuString;
3     typedef MenuString::iterator MenuStringIterator;
4     wxMenu *editmenupopup;
5     wxString exepath_;
6     wxFileHistory filehistory;
7     wxTextCtrl *filter, *replaces;
8     wxToolBar *tb;
9     int refreshhack, refreshhackinstances;
10     BlinkTimer bt;
11     wxTaskBarIcon tbi;
12     wxIcon icon;
13     ImageDropdown *idd;
14     wxAuiNotebook *nb;
15     wxAuiManager *aui;
16     wxBitmap line_nw, line_sw;
17     wxBitmap foldicon;
18     bool fromclosebox;
19     MyApp *app;
20     wxFileSystemWatcher *watcher;
21     bool watcherwaitingforuser;
22     double csf, csf_orig;
23     std::vector<std::string> scripts_in_menu;
24 
GetPathMyFrame25     wxString GetPath(const wxString &relpath) {
26         if (!exepath_.Length()) return relpath;
27         return exepath_ + "/" + relpath;
28     }
29 
30     MenuString menustrings;
31 
32     void MyAppend(wxMenu *menu, int tag, const wxString &contents, const wchar_t *help = L"") {
33         wxString item = contents;
34         wxString key = L"";
35         int pos = contents.Find("\t");
36         if (pos >= 0) {
37             item = contents.Mid(0, pos);
38             key = contents.Mid(pos + 1);
39         }
40         key = sys->cfg->Read(item, key);
41         wxString newcontents = item;
42         if (key.Length()) newcontents += "\t" + key;
43         menu->Append(tag, newcontents, help);
44         menustrings.push_back(std::make_pair(item, key));
45     }
46 
MyFrameMyFrame47     MyFrame(wxString exename, MyApp *_app)
48         : wxFrame((wxFrame *)nullptr, wxID_ANY, L"TreeSheets", wxDefaultPosition, wxDefaultSize,
49                   wxDEFAULT_FRAME_STYLE),
50           filter(nullptr),
51           replaces(nullptr),
52           tb(nullptr),
53           nb(nullptr),
54           idd(nullptr),
55           refreshhack(0),
56           refreshhackinstances(0),
57           aui(nullptr),
58           fromclosebox(true),
59           app(_app),
60           watcherwaitingforuser(false),
61           watcher(nullptr) {
62         sys->frame = this;
63 
64         exepath_ = L"/usr/local/share/treesheets";
65         #ifdef __WXMAC__
66         int cut = exepath_.Find("/MacOS");
67         if (cut > 0) { exepath_ = exepath_.SubString(0, cut) + "/Resources"; }
68         #endif
69 
70         class MyLog : public wxLog {
71             void DoLogString(const wxChar *msg, time_t timestamp) { DoLogText(*msg); }
72             void DoLogText(const wxString &msg) {
73                 #ifdef WIN32
74                 OutputDebugString(msg.c_str());
75                 OutputDebugString(L"\n");
76                 #else
77                 fputws(msg.c_str(), stderr);
78                 fputws(L"\n", stderr);
79                 #endif
80             }
81         };
82 
83         wxLog::SetActiveTarget(new MyLog());
84 
85         wxLogMessage(L"%s", wxVERSION_STRING);
86 
87         wxLogMessage(L"locale: %s", std::setlocale(LC_CTYPE, nullptr));
88 
89         app->AddTranslation(GetPath("translations"));
90 
91         csf = GetContentScaleFactor();
92         wxLogMessage(L"content scale: %f", csf);
93         csf_orig = csf;
94         #ifdef __WXMSW__
95             // On Windows, I get csf == 1.25, as indicated in the display properties.
96             // With this factor set, bitmaps display. At their same physical sizes as when
97             // TreeSheets was a non-DPI-aware app, and extra resolution is used.
98         #endif
99         #ifdef __WXMAC__
100             // Typically csf == 2 on a retina mac. But on the mac, unlike Windows, image rendering
101             // *already* does scaling, and no way to turn that behavior off for now?
102             csf = 1.0;
103             // FIXME: This gives us low res images even though the display is capable of better!
104             // Apparently still not fixed: http://trac.wxwidgets.org/ticket/15808
105             // wxBitmap::CreateScaled could be the way to solve this, but it is not obvious
106             // how to use it, since you can't pass this scale to LoadFile etc. Could possibly
107             // blit it over via a MemoryDC?
108         #endif
109         #ifdef __WXGTK__
110             // Currently on Linux csf is always 1, so this is useless.
111             // FIXME: On a high-DPI display we get low res images even though the display is
112             // capable of better!
113         #endif
114 
115         wxInitAllImageHandlers();
116 
117         wxIconBundle icons;
118         wxIcon iconbig;
119         icon.LoadFile(GetPath(L"images/icon16.png"), wxBITMAP_TYPE_PNG);
120         iconbig.LoadFile(GetPath(L"images/icon32.png"), wxBITMAP_TYPE_PNG);
121         if (!icon.IsOk() || !iconbig.IsOk()) {
122             wxMessageBox(_(L"Error loading core data file (TreeSheets not installed correctly?)"),
123                          _(L"Initialization Error"), wxOK, this);
124             exit(1);
125         }
126         #ifdef WIN32
127         int iconsmall = ::GetSystemMetrics(SM_CXSMICON);
128         int iconlarge = ::GetSystemMetrics(SM_CXICON);
129         icon.SetSize(iconsmall, iconsmall);  // this shouldn't be necessary...
130         iconbig.SetSize(iconlarge, iconlarge);
131         #endif
132         icons.AddIcon(icon);
133         icons.AddIcon(iconbig);
134         SetIcons(icons);
135 
136         wxImage foldiconi;
137         line_nw.LoadFile(GetPath(L"images/render/line_nw.png"), wxBITMAP_TYPE_PNG);
138         line_sw.LoadFile(GetPath(L"images/render/line_sw.png"), wxBITMAP_TYPE_PNG);
139         foldiconi.LoadFile(GetPath(L"images/nuvola/fold.png"));
140         foldicon = wxBitmap(foldiconi);
141         ScaleBitmap(foldicon, csf / 3.0, foldicon);
142 
143         if (sys->singletray)
144             tbi.Connect(wxID_ANY, wxEVT_TASKBAR_LEFT_UP,
145                         wxTaskBarIconEventHandler(MyFrame::OnTBIDBLClick), nullptr, this);
146         else
147             tbi.Connect(wxID_ANY, wxEVT_TASKBAR_LEFT_DCLICK,
148                         wxTaskBarIconEventHandler(MyFrame::OnTBIDBLClick), nullptr, this);
149 
150         aui = new wxAuiManager(this);
151 
152         bool mergetbar = false;
153 
154         bool showtbar, showsbar, lefttabs, iconset;
155 
156         sys->cfg->Read(L"showtbar", &showtbar, true);
157         sys->cfg->Read(L"showsbar", &showsbar, true);
158         sys->cfg->Read(L"lefttabs", &lefttabs, true);
159         sys->cfg->Read(L"iconset", &iconset, false);
160 
161         filehistory.Load(*sys->cfg);
162 
163         wxMenu *expmenu = new wxMenu();
164         MyAppend(expmenu, A_EXPXML, _(L"&XML..."),
165                  _(L"Export the current view as XML (which can also be reimported without losing "
166                    L"structure)"));
167         MyAppend(expmenu, A_EXPHTMLT, _(L"&HTML (Tables+Styling)..."),
168                  _(L"Export the current view as HTML using nested tables, that will look somewhat "
169                    L"like the TreeSheet"));
170         MyAppend(expmenu, A_EXPHTMLO, _(L"HTML (&Outline)..."),
171                  _(L"Export the current view as HTML as nested headers, suitable for importing into "
172                    L"Word's outline mode"));
173         MyAppend(expmenu, A_EXPTEXT, _(L"Indented &Text..."),
174                  _(L"Export the current view as tree structured text, using spaces for each "
175                    L"indentation level. "
176                    L"Suitable for importing into mindmanagers and general text programs"));
177         MyAppend(expmenu, A_EXPCSV, _(L"&Comma delimited text (CSV)..."),
178                  _(L"Export the current view as CSV. Good for spreadsheets and databases. Only works "
179                    L"on grids "
180                    L"with no sub-grids (use the Flatten operation first if need be)"));
181         MyAppend(expmenu, A_EXPIMAGE, _(L"&Image..."),
182                  _(L"Export the current view as an image. Useful for faithfull renderings of the "
183                    L"TreeSheet, and "
184                    L"programs that don't accept any of the above options"));
185 
186         wxMenu *impmenu = new wxMenu();
187         MyAppend(impmenu, A_IMPXML, _(L"XML..."));
188         MyAppend(impmenu, A_IMPXMLA, _(L"XML (attributes too, for OPML etc)..."));
189         MyAppend(impmenu, A_IMPTXTI, _(L"Indented text..."));
190         MyAppend(impmenu, A_IMPTXTC, _(L"Comma delimited text (CSV)..."));
191         MyAppend(impmenu, A_IMPTXTS, _(L"Semi-Colon delimited text (CSV)..."));
192         MyAppend(impmenu, A_IMPTXTT, _(L"Tab delimited text..."));
193 
194         wxMenu *recentmenu = new wxMenu();
195         filehistory.UseMenu(recentmenu);
196         filehistory.AddFilesToMenu();
197 
198         wxMenu *filemenu = new wxMenu();
199         MyAppend(filemenu, A_NEW, _(L"&New\tCTRL+n"));
200         MyAppend(filemenu, A_OPEN, _(L"&Open...\tCTRL+o"));
201         MyAppend(filemenu, A_CLOSE, _(L"&Close\tCTRL+w"));
202         filemenu->AppendSubMenu(recentmenu, _(L"&Recent files"));
203         MyAppend(filemenu, A_SAVE, _(L"&Save\tCTRL+s"));
204         MyAppend(filemenu, A_SAVEAS, _(L"Save &As..."));
205         filemenu->AppendSeparator();
206         MyAppend(filemenu, A_PAGESETUP, _(L"Page Setup..."));
207         MyAppend(filemenu, A_PRINTSCALE, _(L"Set Print Scale..."));
208         MyAppend(filemenu, A_PREVIEW, _(L"Print preview..."));
209         MyAppend(filemenu, A_PRINT, _(L"&Print...\tCTRL+p"));
210         filemenu->AppendSeparator();
211         filemenu->AppendSubMenu(expmenu, _(L"Export &view as"));
212         filemenu->AppendSubMenu(impmenu, _(L"Import file from"));
213         filemenu->AppendSeparator();
214         MyAppend(filemenu, A_EXIT, _(L"&Exit\tCTRL+q"));
215 
216         wxMenu *editmenu;
217         loop(twoeditmenus, 2) {
218             wxMenu *sizemenu = new wxMenu();
219             MyAppend(sizemenu, A_INCSIZE, _(L"&Increase text size (SHIFT+mousewheel)\tSHIFT+PGUP"));
220             MyAppend(sizemenu, A_DECSIZE, _(L"&Decrease text size (SHIFT+mousewheel)\tSHIFT+PGDN"));
221             MyAppend(sizemenu, A_RESETSIZE, _(L"&Reset text sizes\tSHIFT+CTRL+s"));
222             MyAppend(sizemenu, A_MINISIZE, _(L"&Shrink text of all sub-grids\tSHIFT+CTRL+m"));
223             sizemenu->AppendSeparator();
224             MyAppend(sizemenu, A_INCWIDTH, _(L"Increase column width (ALT+mousewheel)\tALT+PGUP"));
225             MyAppend(sizemenu, A_DECWIDTH, _(L"Decrease column width (ALT+mousewheel)\tALT+PGDN"));
226             MyAppend(sizemenu, A_INCWIDTHNH,
227                      _(L"Increase column width (no sub grids)\tCTRL+ALT+PGUP"));
228             MyAppend(sizemenu, A_DECWIDTHNH,
229                      _(L"Decrease column width (no sub grids)\tCTRL+ALT+PGDN"));
230             MyAppend(sizemenu, A_RESETWIDTH, _(L"Reset column widths\tSHIFT+CTRL+w"));
231 
232             wxMenu *bordmenu = new wxMenu();
233             MyAppend(bordmenu, A_BORD0, L"&0\tCTRL+SHIFT+0");
234             MyAppend(bordmenu, A_BORD1, L"&1\tCTRL+SHIFT+1");
235             MyAppend(bordmenu, A_BORD2, L"&2\tCTRL+SHIFT+2");
236             MyAppend(bordmenu, A_BORD3, L"&3\tCTRL+SHIFT+3");
237             MyAppend(bordmenu, A_BORD4, L"&4\tCTRL+SHIFT+4");
238             MyAppend(bordmenu, A_BORD5, L"&5\tCTRL+SHIFT+5");
239 
240             wxMenu *selmenu = new wxMenu();
241             MyAppend(selmenu, A_NEXT, _(L"Move to next cell\tTAB"));
242             MyAppend(selmenu, A_PREV, _(L"Move to previous cell\tSHIFT+TAB"));
243             selmenu->AppendSeparator();
244             MyAppend(selmenu, A_SELALL, _(L"Select &all in current grid\tCTRL+a"));
245             selmenu->AppendSeparator();
246             MyAppend(selmenu, A_LEFT, _(L"Move Selection Left\tLEFT"));
247             MyAppend(selmenu, A_RIGHT, _(L"Move Selection Right\tRIGHT"));
248             MyAppend(selmenu, A_UP, _(L"Move Selection Up\tUP"));
249             MyAppend(selmenu, A_DOWN, _(L"Move Selection Down\tDOWN"));
250             selmenu->AppendSeparator();
251             MyAppend(selmenu, A_MLEFT, _(L"Move Cells Left\tCTRL+LEFT"));
252             MyAppend(selmenu, A_MRIGHT, _(L"Move Cells Right\tCTRL+RIGHT"));
253             MyAppend(selmenu, A_MUP, _(L"Move Cells Up\tCTRL+UP"));
254             MyAppend(selmenu, A_MDOWN, _(L"Move Cells Down\tCTRL+DOWN"));
255             selmenu->AppendSeparator();
256             MyAppend(selmenu, A_SLEFT, _(L"Extend Selection Left\tSHIFT+LEFT"));
257             MyAppend(selmenu, A_SRIGHT, _(L"Extend Selection Right\tSHIFT+RIGHT"));
258             MyAppend(selmenu, A_SUP, _(L"Extend Selection Up\tSHIFT+UP"));
259             MyAppend(selmenu, A_SDOWN, _(L"Extend Selection Down\tSHIFT+DOWN"));
260             MyAppend(selmenu, A_SCOLS, _(L"Extend Selection Full Columns"));
261             MyAppend(selmenu, A_SROWS, _(L"Extend Selection Full Rows"));
262             selmenu->AppendSeparator();
263             MyAppend(selmenu, A_CANCELEDIT, _(L"Select &Parent\tESC"));
264             MyAppend(selmenu, A_ENTERGRID, _(L"Select First &Child\tSHIFT+ENTER"));
265             selmenu->AppendSeparator();
266             MyAppend(selmenu, A_LINK, _(L"Go To &Matching Cell\tF6"));
267             MyAppend(selmenu, A_LINKREV, _(L"Go To Matching Cell (Reverse)\tSHIFT+F6"));
268 
269             wxMenu *temenu = new wxMenu();
270             MyAppend(temenu, A_LEFT, _(L"Cursor Left\tLEFT"));
271             MyAppend(temenu, A_RIGHT, _(L"Cursor Right\tRIGHT"));
272             MyAppend(temenu, A_MLEFT, _(L"Word Left\tCTRL+LEFT"));
273             MyAppend(temenu, A_MRIGHT, _(L"Word Right\tCTRL+RIGHT"));
274             temenu->AppendSeparator();
275             MyAppend(temenu, A_SLEFT, _(L"Extend Selection Left\tSHIFT+LEFT"));
276             MyAppend(temenu, A_SRIGHT, _(L"Extend Selection Right\tSHIFT+RIGHT"));
277             MyAppend(temenu, A_SCLEFT, _(L"Extend Selection Word Left\tSHIFT+CTRL+LEFT"));
278             MyAppend(temenu, A_SCRIGHT, _(L"Extend Selection Word Right\tSHIFT+CTRL+RIGHT"));
279             MyAppend(temenu, A_SHOME, _(L"Extend Selection to Start\tSHIFT+HOME"));
280             MyAppend(temenu, A_SEND, _(L"Extend Selection to End\tSHIFT+END"));
281             temenu->AppendSeparator();
282             MyAppend(temenu, A_HOME, _(L"Start of line of text\tHOME"));
283             MyAppend(temenu, A_END, _(L"End of line of text\tEND"));
284             MyAppend(temenu, A_CHOME, _(L"Start of text\tCTRL+HOME"));
285             MyAppend(temenu, A_CEND, _(L"End of text\tCTRL+END"));
286             temenu->AppendSeparator();
287             MyAppend(temenu, A_ENTERCELL, _(L"Enter/exit text edit mode\tENTER"));
288             MyAppend(temenu, A_ENTERCELL, _(L"Enter/exit text edit mode\tF2"));
289             MyAppend(temenu, A_CANCELEDIT, _(L"Cancel text edits\tESC"));
290 
291             wxMenu *stmenu = new wxMenu();
292             MyAppend(stmenu, A_BOLD, _(L"Toggle cell &BOLD\tCTRL+b"));
293             MyAppend(stmenu, A_ITALIC, _(L"Toggle cell &ITALIC\tCTRL+i"));
294             MyAppend(stmenu, A_TT, _(L"Toggle cell &typewriter\tCTRL+ALT+t"));
295             MyAppend(stmenu, A_UNDERL, _(L"Toggle cell &underlined\tCTRL+u"));
296             MyAppend(stmenu, A_STRIKET, _(L"Toggle cell &strikethrough\tCTRL+t"));
297             stmenu->AppendSeparator();
298             MyAppend(stmenu, A_RESETSTYLE, _(L"&Reset text styles\tSHIFT+CTRL+r"));
299             MyAppend(stmenu, A_RESETCOLOR, _(L"Reset &colors\tSHIFT+CTRL+c"));
300 
301             wxMenu *tagmenu = new wxMenu();
302             MyAppend(tagmenu, A_TAGADD, _(L"&Add Cell Text as Tag"));
303             MyAppend(tagmenu, A_TAGREMOVE, _(L"&Remove Cell Text from Tags"));
304             MyAppend(tagmenu, A_NOP, _(L"&Set Cell Text to tag (use CTRL+RMB)"),
305                      _(L"Hold CTRL while pressing right mouse button to quickly set a tag for the "
306                        L"current cell "
307                        L"using a popup menu"));
308 
309             wxMenu *orgmenu = new wxMenu();
310             MyAppend(orgmenu, A_TRANSPOSE, _(L"&Transpose\tSHIFT+CTRL+t"),
311                      _(L"changes the orientation of a grid"));
312             MyAppend(orgmenu, A_SORT, _(L"Sort &Ascending"),
313                      _(L"Make a 1xN selection to indicate which column to sort on, and which rows to "
314                        L"affect"));
315             MyAppend(orgmenu, A_SORTD, _(L"Sort &Descending"),
316                      _(L"Make a 1xN selection to indicate which column to sort on, and which rows to "
317                        L"affect"));
318             MyAppend(orgmenu, A_HSWAP, _(L"Hierarchy &Swap\tF8"),
319                      _(L"Swap all cells with this text at this level (or above) with the parent"));
320             MyAppend(orgmenu, A_HIFY, _(L"&Hierarchify"),
321                      _(L"Convert an NxN grid with repeating elements per column into an 1xN grid "
322                        L"with hierarchy, "
323                        L"useful to convert data from spreadsheets"));
324             MyAppend(orgmenu, A_FLATTEN, _(L"&Flatten"),
325                      _(L"Takes a hierarchy (nested 1xN or Nx1 grids) and converts it into a flat NxN "
326                        L"grid, useful "
327                        L"for export to spreadsheets"));
328 
329             wxMenu *imgmenu = new wxMenu();
330             MyAppend(imgmenu, A_IMAGE, _(L"&Add Image"), _(L"Adds an image to the selected cell"));
331             MyAppend(imgmenu, A_IMAGESCP, _(L"&Scale Image (re-sample pixels)"),
332                      _(L"Change the image size if it is too big, by reducing the amount of "
333                        L"pixels"));
334             MyAppend(imgmenu, A_IMAGESCF, _(L"&Scale Image (display only)"),
335                      _(L"Change the image size if it is too big or too small, by changing the size "
336                        L"shown on screen. Applies to all uses of this image."));
337             MyAppend(imgmenu, A_IMAGESCN, _(L"&Reset Scale (display only)"),
338                      _(L"Change the scale to match DPI of the current display. "
339                        L"Applies to all uses of this image."));
340             MyAppend(imgmenu, A_IMAGER, _(L"&Remove Image(s)"),
341                      _(L"Remove image(s) from the selected cells"));
342 
343             wxMenu *navmenu = new wxMenu();
344             MyAppend(navmenu, A_BROWSE, _(L"Open link in &browser\tF5"),
345                      _(L"Opens up the text from the selected cell in browser (should start be a "
346                        L"valid URL)"));
347             MyAppend(navmenu, A_BROWSEF, _(L"Open &file\tF4"),
348                      _(L"Opens up the text from the selected cell in default application for the "
349                        L"file type"));
350 
351             wxMenu *laymenu = new wxMenu();
352             MyAppend(laymenu, A_V_GS, _(L"Vertical Layout with Grid Style Rendering\tALT+1"));
353             MyAppend(laymenu, A_V_BS, _(L"Vertical Layout with Bubble Style Rendering\tALT+2"));
354             MyAppend(laymenu, A_V_LS, _(L"Vertical Layout with Line Style Rendering\tALT+3"));
355             laymenu->AppendSeparator();
356             MyAppend(laymenu, A_H_GS, _(L"Horizontal Layout with Grid Style Rendering\tALT+4"));
357             MyAppend(laymenu, A_H_BS, _(L"Horizontal Layout with Bubble Style Rendering\tALT+5"));
358             MyAppend(laymenu, A_H_LS, _(L"Horizontal Layout with Line Style Rendering\tALT+6"));
359             laymenu->AppendSeparator();
360             MyAppend(laymenu, A_GS, _(L"Grid Style Rendering\tALT+7"));
361             MyAppend(laymenu, A_BS, _(L"Bubble Style Rendering\tALT+8"));
362             MyAppend(laymenu, A_LS, _(L"Line Style Rendering\tALT+9"));
363             laymenu->AppendSeparator();
364             MyAppend(laymenu, A_TEXTGRID, _(L"Toggle Vertical Layout\tF7"),
365                      _(L"Make a hierarchy layout more vertical (default) or more horizontal"));
366 
367             editmenu = new wxMenu();
368             MyAppend(editmenu, A_CUT, _(L"Cu&t\tCTRL+x"));
369             MyAppend(editmenu, A_COPY, _(L"&Copy\tCTRL+c"));
370             MyAppend(editmenu, A_COPYCT, _(L"Copy As Continuous Text"));
371             MyAppend(editmenu, A_PASTE, _(L"&Paste\tCTRL+v"));
372             MyAppend(editmenu, A_PASTESTYLE, _(L"Paste Style Only\tCTRL+SHIFT+v"),
373                      _(L"only sets the colors and style of the copied cell, and keeps the text"));
374             editmenu->AppendSeparator();
375             MyAppend(editmenu, A_UNDO, _(L"&Undo\tCTRL+z"), _(L"revert the changes, one step at a time"));
376             MyAppend(editmenu, A_REDO, _(L"&Redo\tCTRL+y"),
377                      _(L"redo any undo steps, if you haven't made changes since"));
378             editmenu->AppendSeparator();
379             MyAppend(editmenu, A_DELETE, _(L"&Delete After\tDEL"),
380                      _(L"Deletes the column of cells after the selected grid line, or the row below"));
381             MyAppend(
382                 editmenu, A_BACKSPACE, _(L"Delete Before\tBACK"),
383                 _(L"Deletes the column of cells before the selected grid line, or the row above"));
384             editmenu->AppendSeparator();
385             MyAppend(editmenu, A_NEWGRID,
386                      #ifdef __WXMAC__
387                      _(L"&Insert New Grid\tCTRL+g"),
388                      #else
389                      _(L"&Insert New Grid\tINS"),
390                      #endif
391                      _(L"Adds a grid to the selected cell"));
392             MyAppend(editmenu, A_WRAP, _(L"&Wrap in new parent\tF9"),
393                      _(L"Creates a new level of hierarchy around the current selection"));
394             editmenu->AppendSeparator();
395             // F10 is tied to the OS on both Ubuntu and OS X, and SHIFT+F10 is now right
396             // click on all platforms?
397             MyAppend(editmenu, A_FOLD,
398                      #ifndef WIN32
399                      _(L"Toggle Fold\tCTRL+F10"),
400                      #else
401                      _(L"Toggle Fold\tF10"),
402                      #endif
403                     _("Toggles showing the grid of the selected cell(s)"));
404             MyAppend(editmenu, A_FOLDALL, _(L"Fold All\tCTRL+SHIFT+F10"),
405                 _(L"Folds the grid of the selected cell(s) recursively"));
406             MyAppend(editmenu, A_UNFOLDALL, _(L"Unfold All\tCTRL+ALT+F10"),
407                 _(L"Unfolds the grid of the selected cell(s) recursively"));
408             editmenu->AppendSeparator();
409             editmenu->AppendSubMenu(selmenu, _(L"&Selection..."));
410             editmenu->AppendSubMenu(orgmenu, _(L"&Grid Reorganization..."));
411             editmenu->AppendSubMenu(laymenu, _(L"&Layout && Render Style..."));
412             editmenu->AppendSubMenu(imgmenu, _(L"&Images..."));
413             editmenu->AppendSubMenu(navmenu, _(L"&Browsing..."));
414             editmenu->AppendSubMenu(temenu, _(L"Text &Editing..."));
415             editmenu->AppendSubMenu(sizemenu, _(L"Text Sizing..."));
416             editmenu->AppendSubMenu(stmenu, _(L"Text Style..."));
417             editmenu->AppendSubMenu(bordmenu, _(L"Set Grid Border Width..."));
418             editmenu->AppendSubMenu(tagmenu, _(L"Tag..."));
419 
420             if (!twoeditmenus) editmenupopup = editmenu;
421         }
422 
423         wxMenu *semenu = new wxMenu();
424         MyAppend(semenu, A_SEARCHF, _(L"&Search\tCTRL+f"));
425         MyAppend(semenu, A_SEARCHNEXT, _(L"&Go To Next Search Result\tF3"));
426         MyAppend(semenu, A_REPLACEONCE, _(L"&Replace in Current Selection\tCTRL+h"));
427         MyAppend(semenu, A_REPLACEONCEJ, _(L"&Replace in Current Selection & Jump Next\tCTRL+j"));
428         MyAppend(semenu, A_REPLACEALL, _(L"Replace &All"));
429 
430         wxMenu *scrollmenu = new wxMenu();
431         MyAppend(scrollmenu, A_AUP, _(L"Scroll Up (mousewheel)\tPGUP"));
432         MyAppend(scrollmenu, A_AUP, _(L"Scroll Up (mousewheel)\tALT+UP"));
433         MyAppend(scrollmenu, A_ADOWN, _(L"Scroll Down (mousewheel)\tPGDN"));
434         MyAppend(scrollmenu, A_ADOWN, _(L"Scroll Down (mousewheel)\tALT+DOWN"));
435         MyAppend(scrollmenu, A_ALEFT, _(L"Scroll Left\tALT+LEFT"));
436         MyAppend(scrollmenu, A_ARIGHT, _(L"Scroll Right\tALT+RIGHT"));
437 
438         wxMenu *filtermenu = new wxMenu();
439         MyAppend(filtermenu, A_FILTEROFF, _(L"Turn filter &off"));
440         MyAppend(filtermenu, A_FILTERS, _(L"Show only cells in current search"));
441         MyAppend(filtermenu, A_FILTER5, _(L"Show 5% of last edits"));
442         MyAppend(filtermenu, A_FILTER10, _(L"Show 10% of last edits"));
443         MyAppend(filtermenu, A_FILTER20, _(L"Show 20% of last edits"));
444         MyAppend(filtermenu, A_FILTER50, _(L"Show 50% of last edits"));
445         MyAppend(filtermenu, A_FILTERM, _(L"Show 1% more than the last filter"));
446         MyAppend(filtermenu, A_FILTERL, _(L"Show 1% less than the last filter"));
447 
448         wxMenu *viewmenu = new wxMenu();
449         MyAppend(viewmenu, A_ZOOMIN, _(L"Zoom &In (CTRL+mousewheel)\tCTRL+PGUP"));
450         MyAppend(viewmenu, A_ZOOMOUT, _(L"Zoom &Out (CTRL+mousewheel)\tCTRL+PGDN"));
451         MyAppend(viewmenu, A_NEXTFILE,
452                  #ifndef __WXGTK__
453                  _(L"Switch to &next file/tab\tCTRL+TAB"));
454                  #else
455                  // On Linux, this conflicts with CTRL+I, see Document::Key()
456                  // CTRL+SHIFT+TAB below still works, so that will have to be used to switch tabs.
457                  _(L"Switch to &next file/tab"));
458                  #endif
459         MyAppend(viewmenu, A_PREVFILE, _(L"Switch to &previous file/tab\tSHIFT+CTRL+TAB"));
460         MyAppend(viewmenu, A_FULLSCREEN,
461                  #ifdef __WXMAC__
462                  _(L"Toggle &Fullscreen View\tCTRL+F11"));
463                  #else
464                  _(L"Toggle &Fullscreen View\tF11"));
465                  #endif
466         MyAppend(viewmenu, A_SCALED,
467                  #ifdef __WXMAC__
468                  _(L"Toggle &Scaled Presentation View\tCTRL+F12"));
469                  #else
470                  _(L"Toggle &Scaled Presentation View\tF12"));
471                  #endif
472         viewmenu->AppendSubMenu(scrollmenu, _(L"Scroll Sheet..."));
473         viewmenu->AppendSubMenu(filtermenu, _(L"Filter..."));
474 
475         wxMenu *roundmenu = new wxMenu();
476         roundmenu->AppendRadioItem(A_ROUND0, _(L"Radius &0"));
477         roundmenu->AppendRadioItem(A_ROUND1, _(L"Radius &1"));
478         roundmenu->AppendRadioItem(A_ROUND2, _(L"Radius &2"));
479         roundmenu->AppendRadioItem(A_ROUND3, _(L"Radius &3"));
480         roundmenu->AppendRadioItem(A_ROUND4, _(L"Radius &4"));
481         roundmenu->AppendRadioItem(A_ROUND5, _(L"Radius &5"));
482         roundmenu->AppendRadioItem(A_ROUND6, _(L"Radius &6"));
483         roundmenu->Check(sys->roundness + A_ROUND0, true);
484 
485         wxMenu *optmenu = new wxMenu();
486         MyAppend(optmenu, A_DEFFONT, _(L"Pick Default Font..."));
487         MyAppend(optmenu, A_CUSTKEY, _(L"Change a key binding..."));
488         MyAppend(optmenu, A_CUSTCOL, _(L"Pick Custom &Color..."));
489         MyAppend(optmenu, A_COLCELL, _(L"&Set Custom Color From Cell BG"));
490         MyAppend(optmenu, A_DEFBGCOL, _(L"Pick Document Background..."));
491         optmenu->AppendSeparator();
492         optmenu->AppendCheckItem(A_SHOWSBAR, _(L"Show Statusbar"));
493         optmenu->Check(A_SHOWSBAR, showsbar);
494         optmenu->AppendCheckItem(A_SHOWTBAR, _(L"Show Toolbar"));
495         optmenu->Check(A_SHOWTBAR, showtbar);
496         optmenu->AppendCheckItem(A_LEFTTABS, _(L"File Tabs on the bottom"));
497         optmenu->Check(A_LEFTTABS, lefttabs);
498         optmenu->AppendCheckItem(A_TOTRAY, _(L"Minimize to tray"));
499         optmenu->Check(A_TOTRAY, sys->totray);
500         optmenu->AppendCheckItem(A_MINCLOSE, _(L"Minimize on close"));
501         optmenu->Check(A_MINCLOSE, sys->minclose);
502         optmenu->AppendCheckItem(A_SINGLETRAY, _(L"Single click maximize from tray"));
503         optmenu->Check(A_SINGLETRAY, sys->singletray);
504         optmenu->AppendSeparator();
505         optmenu->AppendCheckItem(A_ZOOMSCR, _(L"Swap mousewheel scrolling and zooming"));
506         optmenu->Check(A_ZOOMSCR, sys->zoomscroll);
507         optmenu->AppendCheckItem(A_THINSELC, _(L"Navigate in between cells with cursor keys"));
508         optmenu->Check(A_THINSELC, sys->thinselc);
509         optmenu->AppendSeparator();
510         optmenu->AppendCheckItem(A_MAKEBAKS, _(L"Create .bak files"));
511         optmenu->Check(A_MAKEBAKS, sys->makebaks);
512         optmenu->AppendCheckItem(A_AUTOSAVE, _(L"Autosave to .tmp"));
513         optmenu->Check(A_AUTOSAVE, sys->autosave);
514         optmenu->AppendCheckItem(
515             A_FSWATCH, _(L"Auto reload documents"),
516             _(L"Reloads when another computer has changed a file (if you have made changes, asks)"));
517         optmenu->Check(A_FSWATCH, sys->fswatch);
518         optmenu->AppendCheckItem(A_AUTOEXPORT, _(L"Automatically export a .html on every save"));
519         optmenu->Check(A_AUTOEXPORT, sys->autohtmlexport);
520         optmenu->AppendSeparator();
521         optmenu->AppendCheckItem(A_CENTERED, _(L"Render document centered"));
522         optmenu->Check(A_CENTERED, sys->centered);
523         optmenu->AppendCheckItem(A_FASTRENDER, _(L"Faster line rendering"));
524         optmenu->Check(A_FASTRENDER, sys->fastrender);
525         optmenu->AppendCheckItem(A_ICONSET, _(L"Black and white toolbar icons"));
526         optmenu->Check(A_ICONSET, iconset);
527         optmenu->AppendSubMenu(roundmenu, _(L"&Roundness of grid borders..."));
528 
529         wxMenu *scriptmenu = new wxMenu();
530         auto scriptpath = GetPath("scripts/");
531         wxString sf = wxFindFirstFile(scriptpath + L"*.lobster");
532         int sidx = 0;
533         while (!sf.empty()) {
534             auto fn = wxFileName::FileName(sf).GetFullName();
535             auto ms = fn.BeforeFirst('.');
536             if (sidx < 26) {
537                 ms += L"\tCTRL+SHIFT+ALT+";
538                 ms += wxChar('A' + sidx);
539             }
540             MyAppend(scriptmenu, A_SCRIPT + sidx, ms);
541             auto ss = fn.utf8_str();
542             scripts_in_menu.push_back(std::string(ss.data(), ss.length()));
543             sf = wxFindNextFile();
544             sidx++;
545         }
546 
547         wxMenu *markmenu = new wxMenu();
548         MyAppend(markmenu, A_MARKDATA, _(L"&Data"));
549         MyAppend(markmenu, A_MARKCODE, _(L"&Operation"));
550         MyAppend(markmenu, A_MARKVARD, _(L"Variable &Assign"));
551         MyAppend(markmenu, A_MARKVARU, _(L"Variable &Read"));
552         MyAppend(markmenu, A_MARKVIEWH, _(L"&Horizontal View"));
553         MyAppend(markmenu, A_MARKVIEWV, _(L"&Vertical View"));
554 
555         wxMenu *langmenu = new wxMenu();
556         MyAppend(langmenu, A_RUN, _(L"&Run"));
557         langmenu->AppendSubMenu(markmenu, _(L"&Mark as"));
558         MyAppend(langmenu, A_CLRVIEW, _(L"&Clear Views"));
559 
560         wxMenu *helpmenu = new wxMenu();
561         MyAppend(helpmenu, A_ABOUT, _(L"&About..."));
562         MyAppend(helpmenu, A_HELPI, _(L"Load interactive &tutorial...\tF1"));
563         MyAppend(helpmenu, A_HELP, _(L"View tutorial &web page..."));
564 
565         wxAcceleratorEntry entries[3];
566         entries[0].Set(wxACCEL_SHIFT, WXK_DELETE, A_CUT);
567         entries[1].Set(wxACCEL_SHIFT, WXK_INSERT, A_PASTE);
568         entries[2].Set(wxACCEL_CTRL, WXK_INSERT, A_COPY);
569         wxAcceleratorTable accel(3, entries);
570         SetAcceleratorTable(accel);
571 
572         if (!mergetbar) {
573             wxMenuBar *menubar = new wxMenuBar();
574             menubar->Append(filemenu, _(L"&File"));
575             menubar->Append(editmenu, _(L"&Edit"));
576             menubar->Append(semenu, _(L"&Search"));
577             menubar->Append(viewmenu, _(L"&View"));
578             menubar->Append(optmenu, _(L"&Options"));
579             menubar->Append(scriptmenu, _(L"Script"));
580             menubar->Append(langmenu, _(L"&Program"));
581             menubar->Append(helpmenu,
582                             #ifdef __WXMAC__
583                             wxApp::s_macHelpMenuTitleName  // so merges with osx provided help
584                             #else
585                             _(L"&Help")
586                             #endif
587                             );
588             #ifdef __WXMAC__
589             // these don't seem to work anymore in the newer wxWidgets, handled in the menu event
590             // handler below instead
591             wxApp::s_macAboutMenuItemId = A_ABOUT;
592             wxApp::s_macExitMenuItemId = A_EXIT;
593             wxApp::s_macPreferencesMenuItemId =
594                 A_DEFFONT;  // we have no prefs, so for now just select the font
595             #endif
596             SetMenuBar(menubar);
597         }
598 
599         wxColour toolbgcol(iconset ? 0xF0ECE8 : 0xD8C7BC);
600 
601         if (showtbar || mergetbar) {
602             tb = CreateToolBar(wxBORDER_NONE | wxTB_HORIZONTAL | wxTB_FLAT | wxTB_NODIVIDER);
603             tb->SetOwnBackgroundColour(toolbgcol);
604 
605             #ifdef __WXMAC__
606             #define SEPARATOR
607             #else
608             #define SEPARATOR tb->AddSeparator()
609             #endif
610 
611             wxString iconpath =
612                 GetPath(iconset ? L"images/webalys/toolbar/" : L"images/nuvola/toolbar/");
613             auto sz = (iconset ? wxSize(18, 18) : wxSize(22, 22)) * csf;
614             tb->SetToolBitmapSize(sz);
615 
616             double sc = iconset ? 1.0 : 22.0 / 48.0;
617 
618             auto AddTBIcon = [&](const wxChar *name, int action, wxString file) {
619                 wxBitmap bm;
620                 if (bm.LoadFile(file, wxBITMAP_TYPE_PNG)) {
621                     auto ns = csf_orig * sc;
622                     ScaleBitmap(bm, ns, bm);
623                     MakeInternallyScaled(bm, tb->GetBackgroundColour(), csf_orig);
624                     tb->AddTool(action, name, bm, bm, wxITEM_NORMAL, name);
625                 }
626             };
627 
628             AddTBIcon(_(L"New (CTRL+n)"), A_NEW, iconpath + L"filenew.png");
629             AddTBIcon(_(L"Open (CTRL+o)"), A_OPEN, iconpath + L"fileopen.png");
630             AddTBIcon(_(L"Save (CTRL+s)"), A_SAVE, iconpath + L"filesave.png");
631             AddTBIcon(_(L"Save As"), A_SAVEAS, iconpath + L"filesaveas.png");
632             SEPARATOR;
633             AddTBIcon(_(L"Undo (CTRL+z)"), A_UNDO, iconpath + L"undo.png");
634             AddTBIcon(_(L"Copy (CTRL+c)"), A_COPY, iconpath + L"editcopy.png");
635             AddTBIcon(_(L"Paste (CTRL+v)"), A_PASTE, iconpath + L"editpaste.png");
636             SEPARATOR;
637             AddTBIcon(_(L"Zoom In (CTRL+mousewheel)"), A_ZOOMIN, iconpath + L"zoomin.png");
638             AddTBIcon(_(L"Zoom Out (CTRL+mousewheel)"), A_ZOOMOUT, iconpath + L"zoomout.png");
639             SEPARATOR;
640             AddTBIcon(_(L"New Grid (INS)"), A_NEWGRID, iconpath + L"newgrid.png");
641             AddTBIcon(_(L"Add Image"), A_IMAGE, iconpath + L"image.png");
642             SEPARATOR;
643             AddTBIcon(_(L"Run"), A_RUN, iconpath + L"run.png");
644             tb->AddSeparator();
645             tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Search ")));
646             tb->AddControl(filter =
647                 new wxTextCtrl(tb, A_SEARCH, "", wxDefaultPosition, wxSize(80, 22) * csf));
648             SEPARATOR;
649             tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Replace ")));
650             tb->AddControl(replaces =
651                 new wxTextCtrl(tb, A_REPLACE, "", wxDefaultPosition, wxSize(60, 22) * csf));
652             tb->AddSeparator();
653             tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Cell ")));
654             tb->AddControl(new ColorDropdown(tb, A_CELLCOLOR, csf, 1));
655             SEPARATOR;
656             tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Text ")));
657             tb->AddControl(new ColorDropdown(tb, A_TEXTCOLOR, csf, 2));
658             SEPARATOR;
659             tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Border ")));
660             tb->AddControl(new ColorDropdown(tb, A_BORDCOLOR, csf, 7));
661             tb->AddSeparator();
662             tb->AddControl(new wxStaticText(tb, wxID_ANY, _(L"Image ")));
663             wxString imagepath = GetPath("images/nuvola/dropdown/");
664             idd = new ImageDropdown(tb, imagepath);
665             tb->AddControl(idd);
666             tb->Realize();
667         }
668 
669         if (showsbar) {
670             wxStatusBar *sb = CreateStatusBar(4);
671             sb->SetOwnBackgroundColour(toolbgcol);
672             SetStatusBarPane(0);
673             int swidths[] = {-1, 200, 120, 100};
674             SetStatusWidths(4, swidths);
675         }
676 
677         nb = new wxAuiNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
678                                wxAUI_NB_TAB_MOVE | wxAUI_NB_SCROLL_BUTTONS |
679                                    wxAUI_NB_WINDOWLIST_BUTTON | wxAUI_NB_CLOSE_ON_ALL_TABS |
680                                    (lefttabs ? wxAUI_NB_BOTTOM : wxAUI_NB_TOP));
681         nb->SetOwnBackgroundColour(toolbgcol);
682 
683         int display_id = wxDisplay::GetFromWindow(this);
684         wxRect disprect = wxDisplay(display_id == wxNOT_FOUND ? 0 : display_id).GetClientArea();
685         const int screenx = disprect.width - disprect.x;
686         const int screeny = disprect.height - disprect.y;
687 
688         const int boundary = 64;
689         const int defx = screenx - 2 * boundary;
690         const int defy = screeny - 2 * boundary;
691         int resx, resy, posx, posy;
692         sys->cfg->Read(L"resx", &resx, defx);
693         sys->cfg->Read(L"resy", &resy, defy);
694         sys->cfg->Read(L"posx", &posx, boundary + disprect.x);
695         sys->cfg->Read(L"posy", &posy, boundary + disprect.y);
696         if (resx > screenx || resy > screeny || posx < disprect.x || posy < disprect.y ||
697             posx + resx > disprect.width + disprect.x ||
698             posy + resy > disprect.height + disprect.y) {
699             // Screen res has been resized since we last ran, set sizes to default to avoid being
700             // off-screen.
701             resx = defx;
702             resy = defy;
703             posx = posy = boundary;
704             posx += disprect.x;
705             posy += disprect.y;
706         }
707         SetSize(resx, resy);
708         SetPosition(wxPoint(posx, posy));
709 
710         bool ismax;
711         sys->cfg->Read(L"maximized", &ismax, true);
712 
713         aui->AddPane(nb, wxCENTER);
714         aui->Update();
715 
716         Show(TRUE);
717 
718         // needs to be after Show() to avoid scrollbars rendered in the wrong place?
719         if (ismax) Maximize(true);
720 
721         SetFileAssoc(exename);
722 
723         wxSafeYield();
724     }
725 
AppOnEventLoopEnterMyFrame726     void AppOnEventLoopEnter()
727     {
728         // Have to do this here, if we do it in the Frame constructor above, it crashes on OS X.
729         watcher = new wxFileSystemWatcher();
730         watcher->SetOwner(this);
731         Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent));
732     }
733 
~MyFrameMyFrame734     ~MyFrame() {
735         filehistory.Save(*sys->cfg);
736         if (!IsIconized()) {
737             sys->cfg->Write(L"maximized", IsMaximized());
738             if (!IsMaximized()) {
739                 sys->cfg->Write(L"resx", GetSize().x);
740                 sys->cfg->Write(L"resy", GetSize().y);
741                 sys->cfg->Write(L"posx", GetPosition().x);
742                 sys->cfg->Write(L"posy", GetPosition().y);
743             }
744         }
745         aui->ClearEventHashTable();
746         aui->UnInit();
747         DELETEP(aui);
748         DELETEP(editmenupopup);
749         DELETEP(watcher);
750     }
751 
752     TSCanvas *NewTab(Document *doc, bool append = false) {
753         TSCanvas *sw = new TSCanvas(this, nb);
754         sw->doc = doc;
755         doc->sw = sw;
756         sw->SetScrollRate(1, 1);
757         if (append)
758             nb->AddPage(sw, L"<unnamed>", true, wxNullBitmap);
759         else
760             nb->InsertPage(0, sw, L"<unnamed>", true, wxNullBitmap);
761         sw->SetDropTarget(new DropTarget(doc->dataobjc));
762         sw->SetFocus();
763         return sw;
764     }
765 
GetCurTabMyFrame766     TSCanvas *GetCurTab() {
767         return nb && nb->GetSelection() >= 0
768             ? (TSCanvas *)nb->GetPage(nb->GetSelection())
769             : nullptr;
770     }
GetTabByFileNameMyFrame771     TSCanvas *GetTabByFileName(const wxString &fn) {
772         if (nb) loop(i, nb->GetPageCount()) {
773                 TSCanvas *p = (TSCanvas *)nb->GetPage(i);
774                 if (p->doc->filename == fn) {
775                     nb->SetSelection(i);
776                     return p;
777                 }
778             }
779         return nullptr;
780     }
781 
OnTabChangeMyFrame782     void OnTabChange(wxAuiNotebookEvent &nbe) {
783         TSCanvas *sw = (TSCanvas *)nb->GetPage(nbe.GetSelection());
784         sw->Status();
785         sys->TabChange(sw->doc);
786     }
787 
TabsResetMyFrame788     void TabsReset() {
789         if (nb) loop(i, nb->GetPageCount()) {
790                 TSCanvas *p = (TSCanvas *)nb->GetPage(i);
791                 p->doc->rootgrid->ResetChildren();
792             }
793     }
794 
OnTabCloseMyFrame795     void OnTabClose(wxAuiNotebookEvent &nbe) {
796         TSCanvas *sw = (TSCanvas *)nb->GetPage(nbe.GetSelection());
797         sys->RememberOpenFiles();
798         if (nb->GetPageCount() <= 1) {
799             nbe.Veto();
800             Close();
801         } else if (sw->doc->CloseDocument()) {
802             nbe.Veto();
803         }
804     }
805 
806     void CycleTabs(int offset = 1) {
807         auto numtabs = (int)nb->GetPageCount();
808         offset = ((offset >= 0) ? 1 : numtabs - 1);  // normalize to non-negative wrt modulo
809         nb->SetSelection((nb->GetSelection() + offset) % numtabs);
810     }
811 
812     void SetPageTitle(const wxString &fn, wxString mods, int page = -1) {
813         if (page < 0) page = nb->GetSelection();
814         if (page < 0) return;
815         if (page == nb->GetSelection()) SetTitle(L"TreeSheets - " + fn + mods);
816         nb->SetPageText(page, (fn.empty() ? L"<unnamed>" : wxFileName(fn).GetName()) + mods);
817     }
818 
819     void TBMenu(wxToolBar *tb, wxMenu *menu, const wxChar *name, int id = 0) {
820         tb->AddTool(id, name, wxNullBitmap, wxEmptyString, wxITEM_DROPDOWN);
821         tb->SetDropdownMenu(id, menu);
822     }
823 
OnMenuMyFrame824     void OnMenu(wxCommandEvent &ce) {
825         wxTextCtrl *tc;
826         if (((tc = filter) && filter == wxWindow::FindFocus()) ||
827             ((tc = replaces) && replaces == wxWindow::FindFocus())) {
828             // FIXME: have to emulate this behavior because menu always captures these events (??)
829             long from, to;
830             tc->GetSelection(&from, &to);
831             switch (ce.GetId()) {
832                 case A_MLEFT:
833                 case A_LEFT:
834                     if (from != to)
835                         tc->SetInsertionPoint(from);
836                     else if (from)
837                         tc->SetInsertionPoint(from - 1);
838                     return;
839                 case A_MRIGHT:
840                 case A_RIGHT:
841                     if (from != to)
842                         tc->SetInsertionPoint(to);
843                     else if (to < tc->GetLineLength(0))
844                         tc->SetInsertionPoint(to + 1);
845                     return;
846 
847                 case A_SHOME: tc->SetSelection(0, to); return;
848                 case A_SEND: tc->SetSelection(from, 1000); return;
849 
850                 case A_SCLEFT:
851                 case A_SLEFT:
852                     if (from) tc->SetSelection(from - 1, to);
853                     return;
854                 case A_SCRIGHT:
855                 case A_SRIGHT:
856                     if (to < tc->GetLineLength(0)) tc->SetSelection(from, to + 1);
857                     return;
858 
859                 case A_BACKSPACE: tc->Remove(from - (from == to), to); return;
860                 case A_DELETE: tc->Remove(from, to + (from == to)); return;
861                 case A_HOME: tc->SetSelection(0, 0); return;
862                 case A_END: tc->SetSelection(1000, 1000); return;
863                 case A_SELALL: tc->SetSelection(0, 1000); return;
864             }
865         }
866         TSCanvas *sw = GetCurTab();
867         wxClientDC dc(sw);
868         sw->DoPrepareDC(dc);
869         sw->doc->ShiftToCenter(dc);
870         auto Check = [&](const wxChar *cfg) {
871             sys->cfg->Write(cfg, ce.IsChecked());
872             sw->Status(_(L"change will take effect next run of TreeSheets"));
873         };
874         switch (ce.GetId()) {
875             case A_NOP: break;
876 
877             case A_ALEFT: sw->CursorScroll(-g_scrollratecursor, 0); break;
878             case A_ARIGHT: sw->CursorScroll(g_scrollratecursor, 0); break;
879             case A_AUP: sw->CursorScroll(0, -g_scrollratecursor); break;
880             case A_ADOWN: sw->CursorScroll(0, g_scrollratecursor); break;
881 
882             case A_ICONSET:
883                 Check(L"iconset");
884                 break;
885             case A_SHOWSBAR:
886                 Check(L"showsbar");
887                 break;
888             case A_SHOWTBAR:
889                 Check(L"showtbar");
890                 break;
891             case A_LEFTTABS:
892                 Check(L"lefttabs");
893                 break;
894             case A_SINGLETRAY:
895                 Check(L"singletray");
896                 break;
897             case A_MAKEBAKS: sys->cfg->Write(L"makebaks", sys->makebaks = ce.IsChecked()); break;
898             case A_TOTRAY: sys->cfg->Write(L"totray", sys->totray = ce.IsChecked()); break;
899             case A_MINCLOSE: sys->cfg->Write(L"minclose", sys->minclose = ce.IsChecked()); break;
900             case A_ZOOMSCR: sys->cfg->Write(L"zoomscroll", sys->zoomscroll = ce.IsChecked()); break;
901             case A_THINSELC: sys->cfg->Write(L"thinselc", sys->thinselc = ce.IsChecked()); break;
902             case A_AUTOSAVE: sys->cfg->Write(L"autosave", sys->autosave = ce.IsChecked()); break;
903             case A_CENTERED:
904                 sys->cfg->Write(L"centered", sys->centered = ce.IsChecked());
905                 Refresh();
906                 break;
907             case A_FSWATCH:
908                 Check(L"fswatch");
909                 sys->fswatch = ce.IsChecked();
910                 break;
911             case A_AUTOEXPORT:
912                 sys->cfg->Write(L"autohtmlexport", sys->autohtmlexport = ce.IsChecked());
913                 break;
914             case A_FASTRENDER:
915                 sys->cfg->Write(L"fastrender", sys->fastrender = ce.IsChecked());
916                 Refresh();
917                 break;
918             case A_FULLSCREEN:
919                 ShowFullScreen(!IsFullScreen());
920                 if (IsFullScreen()) sw->Status(_(L"Press F11 to exit fullscreen mode."));
921                 break;
922             case A_SEARCHF:
923                 if (filter) {
924                     filter->SetFocus();
925                     filter->SetSelection(0, 1000);
926                 } else {
927                     sw->Status(_(L"Please enable (Options -> Show Toolbar) to use search."));
928                 }
929                 break;
930             #ifdef __WXMAC__
931             case wxID_OSX_HIDE: Iconize(true); break;
932             case wxID_OSX_HIDEOTHERS: sw->Status(L"NOT IMPLEMENTED"); break;
933             case wxID_OSX_SHOWALL: Iconize(false); break;
934             case wxID_ABOUT: sw->doc->Action(dc, A_ABOUT); break;
935             case wxID_PREFERENCES: sw->doc->Action(dc, A_DEFFONT); break;
936             case wxID_EXIT:  // FALL THRU:
937             #endif
938             case A_EXIT:
939                 fromclosebox = false;
940                 this->Close();
941                 break;
942             case A_CLOSE: sw->doc->Action(dc, ce.GetId()); break;  // sw dangling pointer on return
943             default:
944                 if (ce.GetId() >= wxID_FILE1 && ce.GetId() <= wxID_FILE9) {
945                     wxString f(filehistory.GetHistoryFile(ce.GetId() - wxID_FILE1));
946                     sw->Status(sys->Open(f));
947                 } else if (ce.GetId() >= A_TAGSET && ce.GetId() < A_SCRIPT) {
948                     sw->Status(sw->doc->TagSet(ce.GetId() - A_TAGSET));
949                 } else if (ce.GetId() >= A_SCRIPT && ce.GetId() < A_MAXACTION) {
950                     auto msg = tssi.ScriptRun(scripts_in_menu[ce.GetId() - A_SCRIPT].c_str());
951                     sw->Status(wxString(msg));
952                 } else {
953                     sw->Status(sw->doc->Action(dc, ce.GetId()));
954                     break;
955                 }
956         }
957     }
958 
OnSearchMyFrame959     void OnSearch(wxCommandEvent &ce) {
960         sys->searchstring = ce.GetString().Lower();
961         Document *doc = GetCurTab()->doc;
962         doc->selected.g = nullptr;
963         if (doc->searchfilter)
964             doc->SetSearchFilter(sys->searchstring.Len() != 0);
965         else
966             doc->Refresh();
967         GetCurTab()->Status();
968     }
969 
ReFocusMyFrame970     void ReFocus() {
971         if (GetCurTab()) GetCurTab()->SetFocus();
972     }
973 
OnCellColorMyFrame974     void OnCellColor(wxCommandEvent &ce) {
975         GetCurTab()->doc->ColorChange(A_CELLCOLOR, ce.GetInt());
976         ReFocus();
977     }
OnTextColorMyFrame978     void OnTextColor(wxCommandEvent &ce) {
979         GetCurTab()->doc->ColorChange(A_TEXTCOLOR, ce.GetInt());
980         ReFocus();
981     }
OnBordColorMyFrame982     void OnBordColor(wxCommandEvent &ce) {
983         GetCurTab()->doc->ColorChange(A_BORDCOLOR, ce.GetInt());
984         ReFocus();
985     }
OnDDImageMyFrame986     void OnDDImage(wxCommandEvent &ce) {
987         GetCurTab()->doc->ImageChange(idd->as[ce.GetInt()], dd_icon_res_scale);
988         ReFocus();
989     }
990 
OnSizingMyFrame991     void OnSizing(wxSizeEvent &se) { se.Skip(); }
OnMaximizeMyFrame992     void OnMaximize(wxMaximizeEvent &me) {
993         ReFocus();
994         me.Skip();
995     }
OnActivateMyFrame996     void OnActivate(wxActivateEvent &ae) {
997         // This causes warnings in the debug log, but without it keyboard entry upon window select
998         // doesn't work.
999         ReFocus();
1000     }
1001 
OnIconizeMyFrame1002     void OnIconize(wxIconizeEvent &me) {
1003         if (me.IsIconized()) {
1004             #ifdef WIN32
1005             if (sys->totray) {
1006                 tbi.SetIcon(icon, L"TreeSheets");
1007                 Show(false);
1008                 Iconize();
1009             }
1010             #endif
1011         } else {
1012             if (GetCurTab()) GetCurTab()->SetFocus();
1013         }
1014     }
1015 
DeIconizeMyFrame1016     void DeIconize() {
1017         if (!IsIconized()) {
1018             RequestUserAttention();
1019             return;
1020         }
1021         Show(true);
1022         Iconize(false);
1023         tbi.RemoveIcon();
1024     }
1025 
OnTBIDBLClickMyFrame1026     void OnTBIDBLClick(wxTaskBarIconEvent &e) { DeIconize(); }
1027 
OnClosingMyFrame1028     void OnClosing(wxCloseEvent &ce) {
1029         bool fcb = fromclosebox;
1030         fromclosebox = true;
1031         if (fcb && sys->minclose) {
1032             ce.Veto();
1033             Iconize();
1034             return;
1035         }
1036         sys->RememberOpenFiles();
1037         if (ce.CanVeto())
1038             while (nb->GetPageCount()) {
1039                 if (GetCurTab()->doc->CloseDocument()) {
1040                     ce.Veto();
1041                     sys->RememberOpenFiles();  // may have closed some, but not all
1042                     return;
1043                 } else {
1044                     nb->DeletePage(nb->GetSelection());
1045                 }
1046             }
1047         bt.Stop();
1048         sys->savechecker.Stop();
1049         Destroy();
1050     }
1051 
1052     #ifdef WIN32
SetRegKeyMyFrame1053     void SetRegKey(wxChar *key, wxString val) {
1054         wxRegKey rk(key);
1055         rk.Create();
1056         rk.SetValue(L"", val);
1057     }
1058     #endif
1059 
SetFileAssocMyFrame1060     void SetFileAssoc(wxString &exename) {
1061         #ifdef WIN32
1062         SetRegKey(L"HKEY_CLASSES_ROOT\\.cts", L"TreeSheets");
1063         SetRegKey(L"HKEY_CLASSES_ROOT\\TreeSheets", L"TreeSheets file");
1064         SetRegKey(L"HKEY_CLASSES_ROOT\\TreeSheets\\Shell\\Open\\Command",
1065                   wxString(L"\"") + exename + L"\" \"%1\"");
1066         SetRegKey(L"HKEY_CLASSES_ROOT\\TreeSheets\\DefaultIcon",
1067                   wxString(L"\"") + exename + L"\",0");
1068         #else
1069         // TODO: do something similar for mac/kde/gnome?
1070         #endif
1071     }
1072 
OnFileSystemEventMyFrame1073     void OnFileSystemEvent(wxFileSystemWatcherEvent &event) {
1074         // 0xF == create/delete/rename/modify
1075         if ((event.GetChangeType() & 0xF) == 0 || watcherwaitingforuser || !nb) return;
1076         const wxString &modfile = event.GetPath().GetFullPath();
1077         loop(i, nb->GetPageCount()) {
1078             Document *doc = ((TSCanvas *)nb->GetPage(i))->doc;
1079             if (modfile == doc->filename) {
1080                 wxDateTime modtime = wxFileName(modfile).GetModificationTime();
1081                 // Compare with last modified to trigger multiple times.
1082                 if (!modtime.IsValid() ||
1083                     !doc->lastmodificationtime.IsValid() ||
1084                     modtime == doc->lastmodificationtime) {
1085                     return;
1086                 }
1087                 if (doc->modified) {
1088                     // TODO: this dialog is problematic since it may be on an unattended
1089                     // computer and more of these events may fire. since the occurrence of this
1090                     // situation is rare, it may be better to just take the most
1091                     // recently changed version (which is the one that has just been modified
1092                     // on disk) this potentially throws away local changes, but this can only
1093                     // happen if the user left changes unsaved, then decided to go edit an older
1094                     // version on another computer.
1095                     // for now, we leave this code active, and guard it with
1096                     // watcherwaitingforuser
1097                     wxString msg = wxString::Format(
1098                         _(L"%s\nhas been modified on disk by another program / computer:\nWould "
1099                           L"you like to discard "
1100                           L"your changes and re-load from disk?"),
1101                         doc->filename);
1102                     watcherwaitingforuser = true;
1103                     int res = wxMessageBox(msg, _(L"File modification conflict!"),
1104                                             wxYES_NO | wxICON_QUESTION, this);
1105                     watcherwaitingforuser = false;
1106                     if (res != wxYES) return;
1107                 }
1108                 auto msg = sys->LoadDB(doc->filename, false, true);
1109                 assert(msg);
1110                 if (*msg) {
1111                     GetCurTab()->Status(msg);
1112                 } else {
1113                     loop(j, nb->GetPageCount()) if (((TSCanvas *)nb->GetPage(j))->doc == doc)
1114                         nb->DeletePage(j);
1115                     ::wxRemoveFile(sys->TmpName(modfile));
1116                     GetCurTab()->Status(
1117                         _(L"File has been re-loaded because of modifications of another program / "
1118                           L"computer"));
1119                 }
1120                 return;
1121             }
1122         }
1123     }
1124 
1125     DECLARE_EVENT_TABLE()
1126 };
1127