Frames

Pynsource GUI bootup sequence explained

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1"""
2Pynsource GUI
3-------------
4
5(c) Andy Bulka
6www.andypatterns.com
7
8Community Edition
9LICENSE: GPL 3
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with this program. If not, see <http://www.gnu.org/licenses/>.
23
24Pro Edition
25LICENCE: Commercial
26
27The pro edition of Pynsource requires a valid paid license. Please
28support firther development of this project by purchasing a license
29http://pynsource.atug.com/buy
30"""
31
32import os
33import sys
34from pydbg import dbg
35from common.messages import *
36import wx
37from gui.settings import PRO_EDITION, ASYNC_BACKGROUND_REFRESH
38from common.printframework import MyPrintout
39from media import images
40from gui.settings import APP_VERSION, APP_ICON_PATH
41from gui.settings_wx import DEFAULT_ASCII_UML_FONT_SIZE
42from generate_code.gen_plantuml import displaymodel_to_plantuml
43from generate_code.gen_plantuml import plant_uml_create_png_and_return_image_url_async
44from common.dialog_dir_path import dialog_path_pyinstaller_push, dialog_path_pyinstaller_pop, set_original_working_dir
45from configobj import ConfigObj # pip install configobj
46from appdirs import AppDirs # pip install appdirs
47from wxasync import AsyncBind, WxAsyncApp, StartCoroutine
48import asyncio
49import aiohttp
50from asyncio.events import get_event_loop
51from app.settings import RefreshPlantUmlEvent, EVT_REFRESH_PLANTUML_EVENT
52from app.settings import CancelRefreshPlantUmlEvent, EVT_CANCEL_REFRESH_PLANTUML_EVENT
53from app.settings import ASYNC
54import datetime
55from contextlib import suppress
56import urllib.request, urllib.parse
57from common.url_to_data import url_to_data
58import json
59from typing import List, Set, Dict, Tuple, Optional
60import webbrowser
61
62
63if PRO_EDITION:
64 import ogl2 as ogl
65 from pro.gui_imageviewer2 import ImageViewer2 as ImageViewer
66else:
67 import wx.lib.ogl as ogl
68 from common.gui_imageviewer import ImageViewer
69
70WINDOW_SIZE
setting the window size of the app
= (1024, 768)
71GUI_LAYOUT = "MULTI_TAB_GUI" # "MULTI_TAB_GUI" or "SPLIT_GUI" or None
72USE_SIZER = False
73ALLOW_INSERT_IMAGE_AND_COMMENT_COMMANDS = True
74
75# if 'wxMac' in wx.PlatformInfo:
76# GUI_LAYOUT = None
77
78from gui.uml_canvas import UmlCanvas
79from gui.wx_log import Log
80from ascii_uml.layout_ascii import model_to_ascii_builder
81
82from app.app import App
83import wx.lib.mixins.inspection # Ctrl-Alt-I
84
85unregistered = not PRO_EDITION
86
87
88class MainApp(WxAsyncApp, wx.lib.mixins.inspection.InspectionMixin):
89# class MainApp(wx.App, wx.lib.mixins.inspection.InspectionMixin):
90 def OnInit(self):
91 self.locale = wx.Locale(wx.LANGUAGE_ENGLISH) # needed for window 10
92 self.Init() # initialize the inspection tool
93 self.log = Log()
94 self.working = False
95 self.printData = None
96 wx.InitAllImageHandlers()
97 self.andyapptitle = "Pynsource"
98
99 self.frame = wx.Frame(
100 None,
101 -1,
102 self.andyapptitle,
103 pos=(50, 50),
104 size=(0, 0),
105 style=wx.DEFAULT_FRAME_STYLE,
106 # style=wx.NO_FULL_REPAINT_ON_RESIZE | wx.DEFAULT_FRAME_STYLE,
107 # style=wx.DEFAULT_FRAME_STYLE & ~ (wx.MAXIMIZE_BOX),
108 # style=wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER | wx.CAPTION | wx.CLIP_CHILDREN | wx.CLOSE_BOX
109 )
110 self.frame.CreateStatusBar()
111
112 """
113 Setting double buffered True solves blurring of shapes as you drag them under wx.ogl and solves the constant
114 flickering of the display in ogl2 mode due to the constant Refresh() thus paints happening.
115 Its supposed to be true by default, however it is actually false by default on windows!. Cannot turn off on Mac.
116 """
117 self.frame.SetDoubleBuffered(True)
118 # print(self.frame.IsDoubleBuffered())
119
120 # ANDY HACK SECTION - STOP BUILDING THE REST OF THE UI AND SHOW THE FRAME NOW!
121 # self.frame.SetPosition((40, 40))
122 # self.frame.SetSize((400, 400))
123 # self.frame.Show()
124 # self.OnHelp(None) # attempt to trigger help window instantly - works! And mouse scrolling works.
125 # return True
126 # END HACK SECTION
127
128 if GUI_LAYOUT == "MULTI_TAB_GUI":
129
130 # self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
131 sizer = wx.BoxSizer(wx.VERTICAL)
132 self.notebook = wx.Notebook(
133 self.frame, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0, # 0 or wx.NB_RIGHT
134 )
135
136 # Page 0
137 self.umlcanvas = UmlCanvas(self.notebook, Log(), self.frame)
138 self.umlcanvas.SetScrollRate(5, 5)
139 self.notebook.AddPage(self.umlcanvas, "UML", True)
140
141 # Page 1
142 self.asciiart = wx.ScrolledWindow(
143 self.notebook,
144 wx.ID_ANY,
145 wx.DefaultPosition,
146 wx.DefaultSize,
147 wx.HSCROLL | wx.VSCROLL,
148 )
149 self.asciiart.SetScrollRate(5, 5)
150 asciiart_sizer = wx.BoxSizer(wx.VERTICAL)
151
152 # txt = u"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, \n" * 10
153 # self.multiText = wx.StaticText(self.asciiart, wx.ID_ANY, txt, wx.DefaultPosition, wx.DefaultSize, 0)
154 # self.multiText.Wrap(600)
155 # asciiart_sizer.Add(self.multiText, 1, wx.ALL, 0)
156
157 self.multiText = wx.TextCtrl(
158 self.asciiart, wx.ID_ANY, ASCII_UML_HELP_MSG, style=wx.TE_MULTILINE | wx.HSCROLL
159 )
160 self.multiText.SetFont(
161 wx.Font(DEFAULT_ASCII_UML_FONT_SIZE, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
162 ) # see http://www.wxpython.org/docs/api/wx.Font-class.html for more fonts
163 asciiart_sizer.Add(self.multiText, 1, wx.EXPAND | wx.ALL, 0)
164 self.asciiart.SetSizer(asciiart_sizer)
165 self.asciiart.Layout()
166 asciiart_sizer.Fit(self.asciiart)
167 self.notebook.AddPage(self.asciiart, "Ascii Art", True)
168
169 # Page 2
170 self.plantuml = ImageViewer(self.notebook)
171 self.notebook.AddPage(self.plantuml, "PlantUML", True)
172
173 sizer.Add(self.notebook, 1, wx.EXPAND | wx.ALL, 0)
174 self.frame.SetSizer(sizer)
175 self.frame.Layout()
176 self.frame.Centre(wx.BOTH)
177 # self.frame.Show()
178 self.notebook.ChangeSelection(0)
179
180 self.multiText.Bind(wx.EVT_MOUSEWHEEL, self.OnWheelZoom_ascii)
181 self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnTabPageChanged) # builds ascii
182
183 elif GUI_LAYOUT == "SPLIT_GUI":
184
185 # Ascii UML text control above the canvas - good for testing canvas coord resiliency
186
187 bSizer1 = wx.BoxSizer(wx.VERTICAL)
188
189 self.notebook = wx.Notebook(self.frame, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize,
190 0)
191 self.m_panel1 = wx.Panel(self.notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize,
192 wx.TAB_TRAVERSAL)
193 bSizer3 = wx.BoxSizer(wx.VERTICAL)
194
195 self.m_scrolledWindow4 = wx.ScrolledWindow(self.m_panel1, wx.ID_ANY, wx.DefaultPosition,
196 wx.DefaultSize, wx.HSCROLL | wx.VSCROLL)
197 self.m_scrolledWindow4.SetScrollRate(5, 5)
198 bSizer5 = wx.BoxSizer(wx.VERTICAL)
199
200 self.m_textCtrl2 = wx.TextCtrl(self.m_scrolledWindow4, wx.ID_ANY, wx.EmptyString,
201 wx.DefaultPosition, wx.DefaultSize, style=wx.TE_MULTILINE | wx.HSCROLL)
202 self.m_textCtrl2.SetFont(wx.Font(DEFAULT_ASCII_UML_FONT_SIZE, wx.MODERN, wx.NORMAL, wx.NORMAL, False))
203 bSizer5.Add(self.m_textCtrl2, 1, wx.ALL | wx.EXPAND, 5)
204
205 self.m_scrolledWindow4.SetSizer(bSizer5)
206 self.m_scrolledWindow4.Layout()
207 bSizer5.Fit(self.m_scrolledWindow4)
208 bSizer3.Add(self.m_scrolledWindow4, 1, wx.EXPAND | wx.ALL, 5)
209
210 self.umlcanvas = UmlCanvas(self.m_panel1, Log(), self.frame)
211 self.umlcanvas.SetScrollRate(5, 5)
212 # self.m_scrolledWindow3 = wx.ScrolledWindow(self.m_panel1, wx.ID_ANY, wx.DefaultPosition,
213 # wx.DefaultSize, wx.HSCROLL | wx.VSCROLL)
214 # self.m_scrolledWindow3.SetScrollRate(5, 5)
215 # bSizer3.Add(self.m_scrolledWindow3, 3, wx.EXPAND | wx.ALL, 5)
216 UML_CANVAS_PROPORTION = 2 # set to 3 to make ascii window smaller and uml canvas bigger
217 bSizer3.Add(self.umlcanvas, UML_CANVAS_PROPORTION, wx.EXPAND | wx.ALL, 5)
218
219 self.m_panel1.SetSizer(bSizer3)
220 self.m_panel1.Layout()
221 bSizer3.Fit(self.m_panel1)
222 self.notebook.AddPage(self.m_panel1, u"a page", False)
223
224 bSizer1.Add(self.notebook, 1, wx.EXPAND | wx.ALL, 5)
225
226 self.frame.SetSizer(bSizer1)
227 self.frame.Layout()
228
229 self.frame.Centre(wx.BOTH)
230 else:
231 self.notebook = None
232
233 self.panel_one = wx.Panel(self.frame, -1)
234 self.umlcanvas = UmlCanvas(self.panel_one, Log(), self.frame)
235 #
236 sizer = wx.BoxSizer(wx.VERTICAL)
237 sizer.Add(self.umlcanvas, 1, wx.EXPAND)
238 self.panel_one.SetSizer(sizer)
239
240 self.panel_two = self.asciiart = wx.Panel(self.frame, -1)
241 self.multiText = wx.TextCtrl(
242 self.panel_two, -1, ASCII_UML_HELP_MSG, style=wx.TE_MULTILINE | wx.HSCROLL
243 )
244 self.multiText.SetFont(
245 wx.Font(DEFAULT_ASCII_UML_FONT_SIZE, wx.MODERN, wx.NORMAL, wx.NORMAL, False)
246 ) # see http://www.wxpython.org/docs/api/wx.Font-class.html for more fonts
247
248 sizer = wx.BoxSizer(wx.VERTICAL)
249 sizer.Add(self.multiText, 1, wx.EXPAND)
250 self.panel_two.SetSizer(sizer)
251
252 self.panel_two.Hide()
253
254 self.sizer = wx.BoxSizer(wx.VERTICAL)
255 self.sizer.Add(self.panel_one, 1, wx.EXPAND)
256 self.sizer.Add(self.panel_two, 1, wx.EXPAND)
257 self.frame.SetSizer(self.sizer)
258
259 ogl.OGLInitialize() # creates some pens and brushes that the OGL library uses.
260
261 # Set the frame to a good size for showing stuff
262 self.frame.SetSize(WINDOW_SIZE)
263 self.umlcanvas.SetFocus()
264 self.SetTopWindow(self.frame)
265
266 self.frame.Show(
267 True
268 ) # in wxpython2.8 this causes umlcanvas to grow too, but not in wxpython3 - till much later
269 self.frame.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
270
271 self.popupmenu = None
272 self.umlcanvas.Bind(
273 wx.EVT_RIGHT_DOWN, self.OnRightButtonMenu
274 ) # WARNING: takes over all righclick events - need to event.skip() to let through things to UmlShapeHandler
275 self.Bind(wx.EVT_SIZE, self.OnResizeFrame)
276
277 self.umlcanvas.InitSizeAndObjs() # Now that frame is visible and calculated, there should be sensible world coords to use
278
279 self.InitConfig()
280
281 class Context(object):
282 """ Everything everybody needs to know """
283
284 pass
285
286 context = Context()
287 # init the context
288 context.wxapp = self
289 context.config = self.config
290 context.umlcanvas = self.umlcanvas
291 context.displaymodel = self.umlcanvas.displaymodel
292 context.snapshot_mgr = self.umlcanvas.snapshot_mgr
293 context.coordmapper = self.umlcanvas.coordmapper
294 context.layouter = self.umlcanvas.layouter
295 context.overlap_remover = self.umlcanvas.overlap_remover
296 context.frame = self.frame
297 if GUI_LAYOUT == "MULTI_TAB_GUI":
298 context.multiText = self.multiText
299 context.asciiart = self.asciiart
300 self.plantuml.context = context # actually only need to pass config
301 elif GUI_LAYOUT == "SPLIT_GUI":
302 self.multiText = self.m_textCtrl2
303 self.asciiart = self.m_scrolledWindow4
304 else:
305 context.multiText = None
306 context.asciiart = None
307
308 # App knows about everyone.
309 # Everyone (ring classes) should be an adapter
310 # but not strictly necessary unless you want an extra level
311 # of indirection and separation or don't want to constantly
312 # edit the ring classes to match what the app (and other ring
313 # objects) expect - edit an adapter instead.
314 self.app = App(context)
315 self.app.Boot()
316
317 self.InitMenus()
318 self.PostOglViewSwitch() # ensure key bindings kick in under linux
319
320 self.frame.CenterOnScreen()
321
322 # self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate) # start up non-minimised when packaged - doesn't work
323 # self.frame.Iconize(False) # start up non-minimised when packaged
324 # if self.frame.IsIconized():
325 # wx.MessageBox("iconized?")
326 # wx.CallAfter(self.minimized_on_mac_problem)
327
328 if ASYNC:
329 # Custom event bindings
330 # don't specify id, and use self.frame not self
331 AsyncBind(EVT_REFRESH_PLANTUML_EVENT, self.refresh_plantuml_view, self.frame)
332 AsyncBind(EVT_CANCEL_REFRESH_PLANTUML_EVENT,
333 self.cancel_refresh_plantuml_view,
334 self.frame)
335 else:
336 # bind custom event to synchronous handler - works OK
337 self.Bind(EVT_REFRESH_PLANTUML_EVENT, self.refresh_plantuml_view)
338
339 self.determine_cwd()
340 self.set_custom_window_position()
341
342 """
343 Only run bootstrap command at startup to load a sample file - only in dev mode
344 """
345 if getattr(sys, 'frozen', False):
346 # running in a bundle
347 pass
348 # doesn't make a difference calling this via CallAfter ?
349 # though when deployed, need a delay
350 # wx.CallAfter(self.app.run.CmdBootStrap)
351 # fix refresh issue on macbook air when deployed
352 # wx.CallLater(100, self.app.run.CmdRefreshUmlWindow) # risky - may clash with wx.SafeYield()
353 else:
354 # running live in development mode
355 # Load an initial diagram, for fun
356 if "wxGTK" not in wx.PlatformInfo:
357 wx.CallAfter(self.app.run.CmdBootStrap) # causes safe yield warnings on linux?
358 # wx.CallLater(2000, self.app.run.CmdBootStrap)
359
360 wx.CallAfter(self.PostOglViewSwitch) # ensure status bar message appears after loading
361
362 if ASYNC:
363 StartCoroutine(self.check_for_updates, self)
364
365 if ASYNC and ASYNC_BACKGROUND_REFRESH:
366 StartCoroutine(self.mega_refresh_check, self)
367
368 self._set_app_icon()
369
370 # wx.lib.inspection.InspectionTool().Show()
371
372 return True
373
374 def _set_app_icon(self):
375 """
376 Set app icon, though for Mac you need to set up a .plist pointing to a .icns file when
377 bundling using pyinstaller
378 Though... somehow the official wxdemo seems to change mac icon running in devel mode?
379 """
380 try:
381 wd = sys._MEIPASS
382 except AttributeError:
383 wd = os.path.dirname(os.path.realpath(__file__)) # "."
384 path = os.path.join(wd, APP_ICON_PATH)
385 self.frame.SetIcon(wx.Icon(path))
386
387 def determine_cwd(self):
388 """
389 Determine the place where Pynsource is running from, so can access media and html
390 """
391 cwd_info = {}
392
393 cwd_info['getcwd'] = os.getcwd()
394
395 cwd_info['__file__'] = os.path.dirname(os.path.realpath(__file__))
396
397 if "SNAP" in os.environ:
398 cwd_info['SNAP'] = os.environ["SNAP"]
399
400 # print(cwd_info)
401 set_original_working_dir(cwd_info['__file__']) # more accurate
402 self.cwd_info = cwd_info
403
404 def set_custom_window_position(self):
405 """
406 Fix for people who have small or unusual displays
407
408 Edit your pynsource.ini file to add one or both entries:
409
410 WindowSize = 200, 400
411 WindowPosition = 210, 240
412
413 You can comment out those lines with # e.g.
414
415 #WindowSize = 200, 400
416 #WindowPosition = 210, 240
417
418 Returns: -
419 """
420
421 window_size = self.config.get("WindowSize", None)
422 window_pos = self.config.get("WindowPosition", None)
423
424 # def convert_to_tuple(ls: List[str, str]) -> Tuple[int, int]:
425 def convert_to_tuple(ls: List[str]) -> Tuple[int, int]:
426 pos = [int(i) for i in ls]
427 pos = tuple(pos)
428 return pos
429
430 if type(window_size) == list:
431 self.frame.SetSize(convert_to_tuple(window_size))
432
433 if type(window_pos) == list:
434 self.frame.SetPosition(convert_to_tuple(window_pos))
435
436
437# def minimized_on_mac_problem(self):
438 # self.frame.Iconize(False) # start up non-minimised when packaged
439 # if self.frame.IsIconized():
440 # wx.MessageBox("iconized?")
441 # self.frame.Show()
442 #
443 # def OnActivate(self, event):
444 # print "OnActivate"
445 # if event.GetActive():
446 # self.frame.Raise()
447 # event.Skip()
448
449 def InitConfig(self):
450 """Config file. I have two, the main original one, and a second one that uses wx config."""
451
452 """
453 Main config file pynsource.ini
454
455 Mac:
456 ~/Library/Preferences/Pynsource/pynsource.ini
457 e.g.
458 /Users/YOURNAME/Library/Preferences/Pynsource/pynsource.ini
459
460 Windows:
461 C:\\Users\\YOURNAME\\AppData\\Roaming\\Pynsource\\pynsource.ini
462
463 Linux:
464 ~/.Pynsource/pynsource.ini
465 """
466 global PYNSOURCE_CONFIG_DIR
467 if "wxGTK" in wx.PlatformInfo:
468 PYNSOURCE_CONFIG_DIR = "." + PYNSOURCE_CONFIG_DIR
469
470 config_dir = os.path.join(wx.StandardPaths.Get().GetUserConfigDir(), PYNSOURCE_CONFIG_DIR)
471 try:
472 os.makedirs(config_dir)
473 except OSError:
474 pass
475 self.user_config_file = os.path.join(config_dir, PYNSOURCE_CONFIG_FILE)
476 # print("Pynsource (configobj) config location", self.user_config_file)
477
478 self.config = ConfigObj(self.user_config_file)
479 self.config["keyword1"] = 100
480 self.config["keyword2"] = "hi there"
481 # self.config["LastFilesOpened"] = ["blah", "blah2", "blah3"]
482
483 # Set some sensible defaults
484 for key in ["LastDirFileOpen", "LastDirFileImport", "LastDirInsertImage"]:
485 if not self.config.get(key, None):
486 self.config[key] = os.path.expanduser("~/")
487
488 self.config.write()
489 # print("Pynsource config", self.config)
490
491
492 """
493 Second config file based on wx API's - needed cos File History API requres it for persistence
494 on Mac its stored in "~/Library/Preferences/Pynsource Preferences" if you specify it this way:
495 self.configwx = wx.FileConfig(appName=ABOUT_APPNAME, vendorName=VENDOR_NAME)
496 but I want the preferences to be in a subdir, together with my other preferences file, so
497 by specifying localFilename the preference config file is now
498 ~/Library/Preferences/Pynsource/pynsource_wx.ini
499 living nicely with the other config file, in the directory
500 ~/Library/Preferences/Pynsource/
501 """
502 self.user_configwx_file = os.path.join(config_dir, PYNSOURCE_CONFIGWX_FILE)
503 self.configwx = wx.FileConfig(localFilename=self.user_configwx_file)
504
505 keyword3 = self.configwx.Read("keyword3") # just testing
506 # print("Pynsource (wx.FileConfig) config location", self.user_configwx_file)
507 # print(keyword3)
508
509 """Experiments with using wx.StandardPaths"""
510 # std = wx.StandardPaths.Get()
511 #
512 # print("GetConfigDir is", std.GetConfigDir())
513 # print("GetUserDataDir is", std.GetUserDataDir())
514 # print("GetUserConfigDir is", std.GetUserConfigDir())
515 #
516 # results:
517 #
518 # # GetConfigDir is /Library/Preferences
519 # # GetUserDataDir is ~/Library/Application Support/pynsource-gui
520 # # GetUserConfigDir is ~/Library/Preferences
521
522 def OnResizeFrame(self, event): # ANDY interesting - GetVirtualSize grows when resize frame
523 # print("OnResizeFrame event.EventObject", event.EventObject.__class__, event.EventObject == self.umlcanvas, isinstance(event.EventObject, wx.ScrolledWindow))
524
525 # if event.EventObject == self.umlcanvas: # only works in ogl2 mode
526 if isinstance(event.EventObject, wx.ScrolledWindow):
527
528 # Proportionally constrained resize. Nice trick from http://stackoverflow.com/questions/6005960/resizing-a-wxpython-window
529 # hsize = event.GetSize()[0] * 0.75
530 # self.frame.SetSizeHints(minW=-1, minH=hsize, maxH=hsize)
531 # self.frame.SetTitle(str(event.GetSize()))
532
533 # print("GetClientSize()", self.umlcanvas.GetClientSize(), wx.ClientDisplayRect(), wx.DisplaySize())
534
535 # self.umlcanvas.canvas_resizer.frame_calibration() # do I really need this anymore?
536
537 self.umlcanvas.fix_scrollbars()
538
539 if 'wxMac' in wx.PlatformInfo:
540 # Protect against resizing to pure fullscreen, to avoid weird performance degredation
541 # Luckily the Messagebox kills the resize frame attempt!
542 # print(f"wx.DisplaySize()={wx.DisplaySize()}")
543 alarm = self.frame.GetScreenRect()[2] >= wx.DisplaySize()[0] and \
544 self.frame.GetScreenRect()[3] >= wx.DisplaySize()[1]
545 # print(self.frame.GetClientSize(), self.frame.GetScreenRect(), alarm)
546 if alarm:
547 # print("\n",whoscalling2())
548 wx.CallAfter(self.mac_fullscreen_warning)
549
550 event.Skip()
551
552 def mac_fullscreen_warning(self):
553 if 'wxMac' in wx.PlatformInfo:
554 self.MessageBox(PURE_MAC_FULLSCREEN_WARNING)
555
556 def OnWheelZoom_ascii(self, event):
557 # Since our binding of wx.EVT_MOUSEWHEEL to here takes over all wheel events
558 # we have to manually do the normal test scrolling. This event can't propogate via event.skip()
559 #
560 # The native widgets handle the low level
561 # events (or not) their own way and wx makes no guarantees about behaviors
562 # when intercepting them yourself.
563 # --
564 # Robin Dunn
565 #
566 # Update - this doesn't seem true anymore, since my adding event.Skip() at the end
567 # of this routine allow regular mouse wheel scrolling to continue to work.
568
569 # print "MOUSEWHEEEL self.working=", self.working
570 if self.working:
571 return
572 self.working = True
573
574 # print event.ShouldPropagate(), dir(event)
575
576 controlDown = event.CmdDown()
577 if controlDown:
578 direction = event.GetWheelRotation()
579 self.ascii_art_zoom(direction)
580 self.multiText.ShowPosition(0)
581
582 # don't need this explicit scroll code - it happens naturally
583 # else:
584 # if event.GetWheelRotation() < 0:
585 # self.multiText.ScrollLines(4)
586 # else:
587 # self.multiText.ScrollLines(-4)
588
589 self.working = False
590
591 # self.frame.GetEventHandler().ProcessEvent(event)
592 # wx.PostEvent(self.frame, event)
593
594 if not controlDown:
595 event.Skip() # Need this or regular mouse wheel scrolling breaks
596
597 def ascii_art_zoom(self, direction=1, amount=1, reset=False):
598 if unregistered:
599 return
600
601 font = self.multiText.GetFont()
602 # dbg(font.GetPointSize())
603 # dbg(direction)
604 MIN_FONT_SIZE = 4
605 MAX_FONT_SIZE = 65
606
607 if reset:
608 self.multiText.SetFont(wx.Font(DEFAULT_ASCII_UML_FONT_SIZE, wx.MODERN, wx.NORMAL, wx.NORMAL, False))
609 return
610
611 if direction < 0:
612 newfontsize = font.GetPointSize() - amount
613 else:
614 newfontsize = font.GetPointSize() + amount
615
616 if newfontsize > MIN_FONT_SIZE and newfontsize < MAX_FONT_SIZE:
617 self.multiText.SetFont(wx.Font(newfontsize, wx.MODERN, wx.NORMAL, wx.NORMAL, False))
618
619 def OnDumpDisplayModel(self, event):
620 self.app.run.CmdDumpDisplayModel()
621
622 def OnSaveGraphToConsole(self, event):
623 self.app.run.CmdFileSaveWorkspaceToConsole()
624
625 def OnSaveGraph(self, event):
626 self.app.run.CmdFileSaveWorkspace()
627
628 def OnLoadGraphFromText(self, event):
629 self.app.run.CmdFileLoadWorkspaceFromQuickPrompt()
630
631 def OnLoadGraph(self, event):
632 self.app.run.CmdFileLoadWorkspaceViaDialog()
633
634 def OnLoadGraphSample(self, event):
635 self.app.run.CmdFileLoadWorkspaceSampleViaPickList() # CmdFileLoadWorkspaceSampleViaDialog()
636
637 # phoenix hack to get things to appear
638 # self.app.run.CmdRefreshUmlWindow()
639 # self.umlcanvas.mega_refresh()
640
641 def set_app_title(self, title):
642 self.frame.SetTitle(self.andyapptitle + " - " + title)
643
644 def OnTabPageChanged(self, event):
645 if event.GetSelection() == 0: # canvas diagram
646 self.PostOglViewSwitch()
647 elif event.GetSelection() == 1: # ascii art
648 self.RefreshAsciiUmlTab()
649 self.PostAsciiViewSwitch()
650 elif event.GetSelection() == 2: # plantuml
651 # force cos tab hasn't changed yet
652
653 wx.CallAfter(self.plantuml.SetFocus) # otherwise focus remains on tab itself
654 wx.PostEvent(self.frame, RefreshPlantUmlEvent(force=True))
655
656 event.Skip() # allow the tab change to occur, though seems to happen even if omitted
657
658 async def refresh_plantuml_view(self, event):
659 force = getattr(event, "force", False)
660
661 if self.plantuml.working_fetching:
662 return
663
664 # await self._refresh_plantuml_view(force) # original technique doesn't give us task id
665
666 if sys.version_info[1] == 6: # create_task not available in Python 3.6
667 loop = asyncio.get_event_loop()
668 self.plantuml_refresh_task = loop.create_task(self._refresh_plantuml_view(force))
669 else:
670 self.plantuml_refresh_task = asyncio.create_task(self._refresh_plantuml_view(force))
671
672 await self.plantuml_refresh_task
673
674 async def _refresh_plantuml_view(self, force=False):
675 """Build plantuml view from display model, which involves converting to PlantUML text and
676 sending to PlantUML server to render.
677
678 There are actually two steps.
679 1. plant_uml_create_png_and_return_image_url(plant_uml_txt) - contacts plantuml
680 server and gets back a html page with the url of the resulting image inside
681 2. self.plantuml.ViewImage(url=image_url) - contacts the plantuml server to get image
682
683 'force' means do it regardless of what tab is visible
684
685 If there is an entry in
686
687 ~/Library/Preferences/Pynsource/pynsource.ini
688
689 and it contains
690
691 PlantUmlServerUrl = http://localhost:8080/plantuml/uml
692
693 then that will be used as the Plantuml server url, otherwise the url defaults to
694 "http://www.plantuml.com/plantuml/uml"
695
696 For instructions on installing PlantUML server see http://plantuml.com/server or
697 otherwise google for advice.
698
699 """
700 if force or self.viewing_plantuml_tab:
701 if len(self.umlcanvas.displaymodel.graph.nodeSet) == 0:
702 self.plantuml.clear()
703 else:
704 # Generate plantuml from displaymodel graph
705 try:
706 plant_uml_txt = displaymodel_to_plantuml(self.umlcanvas.displaymodel)
707
708 self.plantuml.render_in_progress(True, self.frame)
709 StartCoroutine(self.update_refresh_plantuml_view_clock, self)
710
711 # internet connection errors getting initial plantuml html are
712 # handled internally to this function
713 plantuml_server_local_url = self.config.get("PlantUmlServerUrl", None)
714 image_url = await plant_uml_create_png_and_return_image_url_async(
715 plant_uml_txt,
716 plantuml_server_local_url=plantuml_server_local_url
717 )
718
719 # Simulate failures
720 # import random
721 # if random.randint(1,2) == 1:
722 # image_url = None
723
724 if image_url:
725 await self.plantuml.ViewImage(url=image_url)
726 self.plantuml.plantuml_text = plant_uml_txt
727 self.frame.SetStatusText(STATUS_TEXT_UML_VIEW)
728 else:
729 self.frame.SetStatusText(STATUS_TEXT_UML_VIEW_NO_PLANTUML_RESPONSE)
730 self.plantuml.clear_cos_connection_error()
731 self.plantuml.plantuml_text = plant_uml_txt # fixed - ensure plantuml_text always available
732 finally:
733 self.plantuml.render_in_progress(False, self.frame)
734
735 async def cancel_refresh_plantuml_view(self, event):
736 self.plantuml_refresh_task.cancel()
737 self.plantuml.render_in_progress(False, self.frame)
738
739 async def update_refresh_plantuml_view_clock(self):
740 while self.plantuml.working_fetching:
741 if self.viewing_plantuml_tab:
742 self._update_clock()
743 self.frame.Refresh() # just in case big on screen message changes
744 await asyncio.sleep(0.5)
745
746 def _update_clock(self):
747 time_taken: float = self.plantuml.time_taken_fetching
748 self.frame.SetStatusText(f"{PLANTUML_VIEW_FETCHING_MSG} (time taken: {int(time_taken)} seconds)")
749
750 async def async_callback(self, event):
751 print("development mode", self._running_andy_development_mode())
752 self.frame.SetStatusText("Menu clicked")
753 await asyncio.sleep(1)
754 self.frame.SetStatusText("Working")
755 await asyncio.sleep(1)
756 self.frame.SetStatusText("Completed")
757 await asyncio.sleep(1)
758 self.frame.SetStatusText("")
759
760 def RefreshAsciiUmlTab(self):
761 self.model_to_ascii()
762
763 def PostAsciiViewSwitch(self):
764 self.menuBar.EnableTop(2, False) # disable layout menu
765 wx.CallAfter(self.multiText.SetFocus)
766 wx.CallAfter(self.multiText.SetInsertionPoint, 0)
767 self.frame.SetStatusText("Hold ALT whilst dragging to block select. CMD/Ctrl + Mousewheel to zoom (Pro).")
768
769 def PostOglViewSwitch(self):
770 wx.CallAfter(self.umlcanvas.SetFocus)
771 self.menuBar.EnableTop(2, True) # enable layout menu
772 self.frame.SetStatusText("Click to select. Drag to move. Right click on shapes, lines and workspace. CMD/Ctrl + Mousewheel to zoom (Pro).")
773
774 def InitMenus(self):
775 menuBar = wx.MenuBar()
776 self.menuBar = menuBar
777 menu1 = self.menu1 = wx.Menu()
778 menu2 = wx.Menu()
779 menu3 = wx.Menu()
780 menu3sub = wx.Menu()
781 menu4 = wx.Menu()
782 menu5 = wx.Menu()
783 menu5sub = wx.Menu()
784
785 # and a file history
786 self.filehistory = wx.FileHistory()
787 self.filehistory.UseMenu(self.menu1)
788
789 self.next_menu_id = wx.NewIdRef() # was wx.NewId()
790
791 if getattr(sys, 'frozen', False):
792 pro_image = images.pro.GetBitmap() if unregistered else None
793 else:
794 # set this to images.pro.GetBitmap() if want to see Pro menu images even if registered
795 pro_image = images.pro.GetBitmap() if unregistered else None
796
797 def Add(menu, name, shortcut=None, func=None, func_update=None, image=None):
798
799 if "wxGTK" in wx.PlatformInfo: # ubuntu gtk menus have images disallowed, so add text
800 if image:
801 name = f"{name:<30} (Pro)"
802
803 help_string = name
804 if shortcut:
805 name = "%s\t%s" % (name, shortcut)
806 id = self.next_menu_id
807
808 # Mac tweak, see http://wiki.wxpython.org/Optimizing%20for%20Mac%20OS%20X
809 if "wxMac" in wx.PlatformInfo and name == "&About...":
810 id = wx.ID_ABOUT
811
812 if "wxMSW" in wx.PlatformInfo:
813 # Alternate technique for creating menuitem with image, that works under windows.
814 # Also works on Mac but not Linux because GTK disables images in menu items.
815 # The idea is to create the menuitem first, set the image and THEN add the menu item to the menu.
816 menu_item = wx.MenuItem(menu, id, name, help_string)
817 if image:
818 menu_item.SetBitmap(image)
819 menu_item = menu.Append(menu_item)
820 else:
821 # Original technique, menu images work ok on mac, but not windows or linux
822 menu_item = menu.Append(id, name, help_string)
823 if image:
824 menu_item.SetBitmap(image)
825
826 self.Bind(wx.EVT_MENU, func, id=id)
827 if func_update:
828 self.Bind(wx.EVT_UPDATE_UI, func_update, id=id)
829
830 self.next_menu_id = wx.NewIdRef() # was wx.NewId()
831 return menu_item
832
833 def AddSubMenu(menu, submenu, s):
834 menu.AppendSubMenu(submenu, s)
835
836 Add(menu1, "&New", "Ctrl-N", self.FileNew)
837 Add(menu1, "&Open...", "Ctrl-O", self.OnLoadGraph)
838 Add(menu1, "Open Sample &Diagram...", "Ctrl-U", self.OnLoadGraphSample)
839 Add(menu1, "&Save As...", "Ctrl-S", self.OnSaveGraph, self.uml_only_view_update)
840 menu1.AppendSeparator()
841 Add(menu1, "&Save PlantUML Image...", "", self.OnSavePlantUml, self.save_plantuml_update, image=pro_image)
842 menu1.AppendSeparator()
843 Add(menu1, "&Import Python Code...", "Ctrl-I", self.OnFileImport)
844
845 # New way of wiring things up - see https://wxpython.org/blog/avoiding-window-ids/index.html
846 self.item_python3_mode = menu1.AppendCheckItem(
847 wx.ID_ANY, "Python 3 mode", help="Assume Python syntax 2/3"
848 )
849 self.Bind(wx.EVT_MENU, self.OnPythonMode, self.item_python3_mode)
850 self.item_python3_mode.Check()
851
852 menu1.AppendSeparator()
853 Add(menu1, "&Print / Preview...", "Ctrl-P", self.FilePrint)
854 Add(menu1, "&Page Setup...", "", self.OnPageSetup)
855 menu1.AppendSeparator()
856 Add(menu1, "E&xit", "Alt-X", self.OnButton)
857
858 K_ADD_CLASS = "F3" if "wxGTK" in wx.PlatformInfo else "Ctrl-1"
859 K_ADD_COMMENT = "F4" if "wxGTK" in wx.PlatformInfo else "Ctrl-2"
860 K_ADD_IMAGE = "F5" if "wxGTK" in wx.PlatformInfo else "Ctrl-3"
861
862 Add(menu2, "&Add Class...", K_ADD_CLASS, self.OnInsertClass, self.uml_only_view_update)
863 if ALLOW_INSERT_IMAGE_AND_COMMENT_COMMANDS:
864 Add(menu2, "&Insert Comment...", K_ADD_COMMENT, self.OnInsertComment, self.uml_only_view_update) # was Shift-I
865 Add(menu2, "&Insert Image...", K_ADD_IMAGE, self.OnInsertImage, self.uml_only_view_update)
866 menu2.AppendSeparator()
867 Add(menu2, "&Duplicate", "Ctrl-D", self.OnDuplicate, self.OnDuplicateNode_update)
868 menu2.AppendSeparator()
869 menu_item_delete_class = Add(
870 menu2, "&Delete", "Del", self.OnDeleteNode, self.OnDeleteNode_update
871 )
872 menu2.AppendSeparator()
873 menu_item_delete_class.Enable(
874 True
875 ) # demo one way to enable/disable. But better to do via _update function
876 Add(
877 menu2,
878 "Prop&erties...",
879 "F2",
880 self.OnEditProperties,
881 self.OnEditProperties_update,
882 )
883
884 Add(menu5, "Zoom In", "Ctrl-+", self.OnZoomIn, self.OnPro_update, image=pro_image)
885 Add(menu5, "Zoom Out", "Ctrl--", self.OnZoomOut, self.OnPro_update, image=pro_image)
886 Add(menu5, "Zoom To Fit", "Ctrl-9", self.OnZoomToFit, self.OnPro_update, image=pro_image)
887 Add(menu5, "Reset Zoom", "Ctrl-0", self.OnZoomReset, self.OnPro_update, image=pro_image)
888
889 # Add(menu5, "test - add new code block", "Ctrl-7", self.OnNewCodeBlock)
890 # Add(menu5, "test - add new DividedShape", "Ctrl-6", self.OnNewDividedShape)
891 # menu5.AppendSeparator()
892
893 # if ASYNC:
894 # id = wx.NewIdRef()
895 # menu_item = menu5.Append(id, "test - async call\tCtrl-6")
896 # AsyncBind(wx.EVT_MENU, self.async_callback, menu5, id=id)
897
898 menu5.AppendSeparator()
899
900 Add(menu5, "&Toggle Ascii Art UML", "Ctrl-J", self.OnViewToggleAscii)
901 Add(menu5, "&Toggle PlantUML", "Ctrl-K", self.OnViewTogglePlantUml)
902 menu5.AppendSeparator()
903 Add(menu5, "Colour &Sibling Classes", "Ctrl-F", self.OnColourSiblings, self.uml_only_view_update)
904
905 Add(
906 menu5sub,
907 "Change &Sibling Class Colour Scheme",
908 "Ctrl-T",
909 self.OnColourSiblingsRandom, self.uml_only_view_update,
910 )
911 Add(menu5sub, "&Randomise Class Colour", "Ctrl-G", self.OnCycleColours, self.uml_only_view_update)
912 menu5sub.AppendSeparator()
913 Add(menu5sub, "Reset to &Default Class Colours", "", self.OnCycleColoursDefault, self.uml_only_view_update)
914 AddSubMenu(menu5, menu5sub, "Advanced")
915 menu5.AppendSeparator()
916 Add(menu5, "&Redraw Screen", "Ctrl-R", self.OnRefreshUmlWindow)
917
918 Add(menu3, "&Layout UML", "Ctrl-L", self.OnLayout, self.uml_only_view_update)
919 Add(menu3, "&Layout UML Optimally", "Ctrl-B", self.OnDeepLayout, self.OnPro_and_uml_view_only_update, image=pro_image)
920 menu3.AppendSeparator()
921 Add(menu3, "&Expand Layout", "Ctrl-.", self.app.run.CmdLayoutExpand, self.uml_only_view_update)
922 Add(menu3, "&Contract Layout", "Ctrl-,", self.app.run.CmdLayoutContract, self.uml_only_view_update)
923 menu3.AppendSeparator()
924 Add(menu3, "&Remember Layout", "Ctrl-6", self.OnRememberLayout2, self.uml_only_view_update)
925 Add(menu3, "&Restore Layout", "Ctrl-7", self.OnRestoreLayout2, self.uml_only_view_update)
926
927 Add(menu4, "&Help...", "F1", self.OnHelp)
928 Add(menu4, "&Visit Pynsource Website...", "", self.OnVisitWebsite)
929 Add(menu4, "&Check for Updates...", "", self.OnCheckForUpdates)
930 Add(menu4, "&Report Bug...", "", self.OnReportBug)
931 Add(menu4, "View &Log...", "", self.OnViewLog)
932 if unregistered:
933 menu4.AppendSeparator()
934 if "wxGTK" in wx.PlatformInfo:
935 Add(menu4, "&Purchase Pro Edition... (go Pro)", "", self.OnVisitWebsite)
936 else:
937 Add(menu4, "&Purchase Pro Edition...", "", self.OnVisitWebsite, image=pro_image)
938 Add(menu4, "&Enter License...", "", self.OnEnterLicense)
939 if not "wxMac" in wx.PlatformInfo:
940 menu4.AppendSeparator()
941 helpID = Add(menu4, "&About...", "", self.OnAbout).GetId()
942
943 self.add_last_viewed_files(menu1)
944
945 menuBar.Append(menu1, "&File")
946 menuBar.Append(menu2, "&Edit")
947 menuBar.Append(menu3, "&Layout")
948
949 # Super Stoopid Hack (TM) is to just name the menu "View " instead of "View"
950 # https://github.com/itsayellow/marcam/issues/52
951 # https://github.com/wxWidgets/Phoenix/issues/347
952 menuBar.Append(menu5, "&View ") # the name "View" causes weird mac dock auto items
953
954 menuBar.Append(menu4, "&Help")
955
956 self.frame.SetMenuBar(menuBar)
957
958 def add_last_viewed_files(self, menu):
959 self.filehistory.Load(self.configwx)
960 self.filehistory.UseMenu(menu)
961 self._filehistory_linux_workaround()
962
963 self.Bind(wx.EVT_MENU_RANGE, self.OnFileHistory, id=wx.ID_FILE1, id2=wx.ID_FILE9)
964 # self.frame.Bind(wx.EVT_WINDOW_DESTROY, self.Cleanup) # not working on Linux? Cleanup on wx.EVT_CLOSE instead
965
966 def _filehistory_linux_workaround(self):
967 """
968 Under Linux the paths, when loaded, they turn into horrible full paths.
969
970 ...The paths are shown relative to the current working directory, so
971 if that changes to the same location as the files then they are shown
972 with no path... Robin Dunn https://groups.google.com/forum/#!topic/wxpython-users/ckmTSrF9l54
973
974 Hmm - after loading one previous file from the history, or adding a new file to the history (which
975 happens when you load a previous file), then the history menu repairs itself.
976 So workaround by adding the latest menu item to the history again, to trigger the repair
977 """
978 if self.filehistory.Count > 0:
979 if "wxGTK" in wx.PlatformInfo:
980 path = self.filehistory.GetHistoryFile(0)
981 self.filehistory.AddFileToHistory(path)
982
983 def Cleanup(self, *args):
984 # A little extra cleanup is required for the FileHistory control
985 # print("Cleanup")
986 self.filehistory.Save(self.configwx)
987 del self.filehistory
988 self.configwx.Write("keyword3", "a funny thing happened on the way to the cafe...")
989
990 def OnFileHistory(self, event):
991 # get the file based on the menu ID
992 fileNum = event.GetId() - wx.ID_FILE1
993 path = self.filehistory.GetHistoryFile(fileNum)
994 # self.log.write("You selected %s\n" % path)
995
996 try:
997 self.app.run.CmdFileLoadWorkspaceFromFilepath(path)
998 except FileNotFoundError as e:
999
1000 dlg = wx.MessageDialog(self.frame, f"{e}\n\nRemove it from File History?", "Could not open diagram file", style=wx.YES|wx.NO)
1001 if dlg.ShowModal() == wx.ID_YES:
1002 self.filehistory.RemoveFileFromHistory(fileNum)
1003 else:
1004 # add it back to the history so it will be moved up the list
1005 self.filehistory.AddFileToHistory(path)
1006
1007 def OnNewDividedShape(self, event):
1008 from gui.uml_canvas import DividedShape
1009 # i = ogl.DividedShape(100,200)
1010 i = DividedShape(300, 200, self.umlcanvas)
1011 i.fill = ["AQUAMARINE"]
1012 i.BuildRegions(self.umlcanvas) # hmm, why pass in canvas again?!!
1013 self.umlcanvas.InsertShape(i, 999)
1014 self.umlcanvas.deselect()
1015 self.umlcanvas.Refresh()
1016
1017 def OnNewCodeBlock(self, event):
1018 i = ogl.CodeBlock()
1019 # self.canvas.AddShape(i)
1020 self.umlcanvas.InsertShape(i, 999)
1021 self.umlcanvas.deselect()
1022 self.umlcanvas.Refresh()
1023
1024 def OnZoomIn(self, event):
1025 if self.viewing_uml_tab:
1026 self.umlcanvas.zoom_in()
1027 elif self.viewing_asciiart_tab:
1028 self.ascii_art_zoom(direction=1, amount=2) # little bit stronger zoom when via menu
1029 elif self.viewing_plantuml_tab:
1030 self.plantuml.zoom(zoom_incr=0.1)
1031
1032 def OnZoomOut(self, event):
1033 if self.viewing_uml_tab:
1034 self.umlcanvas.zoom_out()
1035 elif self.viewing_asciiart_tab:
1036 self.ascii_art_zoom(direction=-1, amount=2)
1037 elif self.viewing_plantuml_tab:
1038 self.plantuml.zoom(out=False, zoom_incr=0.1)
1039
1040 def OnZoomToFit(self, event):
1041 if self.viewing_uml_tab:
1042 self.umlcanvas.zoom_to_fit()
1043 elif self.viewing_asciiart_tab:
1044 self.ascii_art_zoom(reset=True)
1045 elif self.viewing_plantuml_tab:
1046 self.plantuml.zoom_to_fit()
1047
1048 def OnZoomReset(self, event):
1049 if self.viewing_uml_tab:
1050 self.umlcanvas.zoom_reset()
1051 elif self.viewing_asciiart_tab:
1052 self.ascii_art_zoom(reset=True)
1053 elif self.viewing_plantuml_tab:
1054 self.plantuml.zoom(reset=True)
1055
1056 def OnPythonMode(self, event):
1057 print("python 3 syntax mode state now:", self.item_python3_mode.IsChecked())
1058
1059 def OnRightButtonMenu(self, event): # Menu
1060 x, y = event.GetPosition()
1061 if PRO_EDITION:
1062 hit_which_shapes = self.umlcanvas.getCurrentShape(event)
1063 else:
1064 # but hittest fails when canvas is scrolled !! Fix.
1065 adjusted_x = x + self.umlcanvas.GetScrollPos(wx.HORIZONTAL)
1066 adjusted_y = y + self.umlcanvas.GetScrollPos(wx.VERTICAL)
1067 # Since our binding of wx.EVT_RIGHT_DOWN to here takes over all right click events
1068 # we have to manually figure out if we have clicked on shape
1069 # then allow natural shape node menu to kick in via UmlShapeHandler (defined above)
1070 hit_which_shapes = [
1071 s
1072 for s in self.umlcanvas.GetDiagram().GetShapeList()
1073 if s.HitTest(adjusted_x, adjusted_y)
1074 ]
1075 # print('hit_which_shapes', hit_which_shapes) # hmm seems to pick up lines, too!
1076 if hit_which_shapes:
1077 event.Skip() # will send the event on to the next handler, I think in the canvas
1078 return
1079
1080 self.umlcanvas.focus_canvas() # bit of accelerator fun, though below menu is not accel.
1081
1082 if self.popupmenu:
1083 self.popupmenu.Destroy() # wx.Menu objects need to be explicitly destroyed (e.g. menu.Destroy()) in this situation. Otherwise, they will rack up the USER Objects count on Windows; eventually crashing a program when USER Objects is maxed out. -- U. Artie Eoff http://wiki.wxpython.org/index.cgi/PopupMenuOnRightClick
1084 self.popupmenu = wx.Menu() # Create a menu
1085
1086 item = self.popupmenu.Append(wx.ID_ANY, "Add Class...")
1087 self.frame.Bind(wx.EVT_MENU, self.OnInsertClass, item)
1088 if ALLOW_INSERT_IMAGE_AND_COMMENT_COMMANDS:
1089 item = self.popupmenu.Append(wx.ID_ANY, "Add Image...")
1090 self.frame.Bind(wx.EVT_MENU, self.OnInsertImage, item)
1091 item = self.popupmenu.Append(wx.ID_ANY, "Add Comment...")
1092 self.frame.Bind(wx.EVT_MENU, self.OnInsertComment, item)
1093
1094 self.popupmenu.AppendSeparator()
1095
1096 # Developer only tools
1097 #
1098 # item = self.popupmenu.Append(wx.ID_ANY, "Load Graph from text...")
1099 # self.frame.Bind(wx.EVT_MENU, self.OnLoadGraphFromText, item)
1100 #
1101 # item = self.popupmenu.Append(wx.ID_ANY, "Dump Graph to console")
1102 # self.frame.Bind(wx.EVT_MENU, self.OnSaveGraphToConsole, item)
1103
1104 self.popupmenu.AppendSeparator()
1105
1106 item = self.popupmenu.Append(wx.ID_ANY, "Load Diagram...")
1107 self.frame.Bind(wx.EVT_MENU, self.OnLoadGraph, item)
1108
1109 item = self.popupmenu.Append(wx.ID_ANY, "Save Diagram...")
1110 self.frame.Bind(wx.EVT_MENU, self.OnSaveGraph, item)
1111
1112 self.popupmenu.AppendSeparator()
1113
1114 item = self.popupmenu.Append(wx.ID_ANY, "Dump Display Model (diagnostic)")
1115 self.frame.Bind(wx.EVT_MENU, self.OnDumpDisplayModel, item)
1116
1117 self.frame.PopupMenu(self.popupmenu, wx.Point(x, y))
1118
1119 def OnReportBug(self, event):
1120 # webbrowser.open("http://code.google.com/p/pynsource/issues/list")
1121 webbrowser.open("https://github.com/abulka/pynsource/issues")
1122
1123 def OnViewLog(self, event):
1124 from common.logger import LOG_FILENAME
1125 webbrowser.open('file://' + LOG_FILENAME)
1126
1127 def OnRememberLayout1(self, event):
1128 self.umlcanvas.CmdRememberLayout1()
1129
1130 def OnRememberLayout2(self, event):
1131 self.umlcanvas.CmdRememberLayout2()
1132
1133 def OnRestoreLayout1(self, event):
1134 self.umlcanvas.CmdRestoreLayout1()
1135 self.umlcanvas.extra_refresh()
1136
1137 def OnRestoreLayout2(self, event):
1138 self.umlcanvas.CmdRestoreLayout2()
1139 self.umlcanvas.extra_refresh()
1140
1141 def OnCycleColours(self, event):
1142 self.app.run.CmdCycleColours()
1143
1144 def OnCycleColoursDefault(self, event):
1145 self.app.run.CmdCycleColours(colour=wx.Brush("WHEAT", wx.SOLID))
1146
1147 def OnColourSiblings(self, event):
1148 self.app.run.CmdColourSiblings()
1149
1150 def OnColourSiblingsRandom(self, event):
1151 self.app.run.CmdColourSiblings(color_range_offset=True)
1152
1153 def OnFileImport(self, event):
1154 mode = 3 if self.item_python3_mode.IsChecked() else 2
1155 self.app.run.CmdFileImportViaDialog(mode=mode)
1156
1157 def OnViewTogglePlantUml(self, event):
1158 if GUI_LAYOUT == "MULTI_TAB_GUI":
1159 if self.notebook.GetSelection() in (0,1):
1160 self.notebook.SetSelection(2)
1161 else:
1162 self.notebook.SetSelection(0)
1163
1164 def OnViewToggleAscii(self, event):
1165 if GUI_LAYOUT == "MULTI_TAB_GUI":
1166 if self.viewing_uml_tab or self.viewing_plantuml_tab:
1167 self.notebook.SetSelection(1)
1168 self.model_to_ascii()
1169 self.PostAsciiViewSwitch()
1170 else:
1171 self.switch_to_ogl_uml_view()
1172 elif GUI_LAYOUT == "SPLIT_GUI":
1173 pass
1174 else:
1175 if self.panel_one.IsShown():
1176 self.panel_one.Hide()
1177 self.panel_two.Show()
1178 self.model_to_ascii()
1179 self.PostAsciiViewSwitch()
1180 self.frame.Layout()
1181 else:
1182 self.switch_to_ogl_uml_view()
1183
1184 def switch_to_ogl_uml_view(self):
1185 if GUI_LAYOUT == "MULTI_TAB_GUI":
1186 if self.notebook.GetSelection() == 1: # ascii uml
1187 self.notebook.SetSelection(0)
1188 self.PostOglViewSwitch()
1189 elif GUI_LAYOUT == "SPLIT_GUI":
1190 pass
1191 else:
1192 self.panel_one.Show()
1193 self.panel_two.Hide()
1194 self.PostOglViewSwitch()
1195 self.frame.Layout()
1196
1197 def model_to_ascii(self):
1198 wx.BeginBusyCursor(cursor=wx.HOURGLASS_CURSOR)
1199 m = model_to_ascii_builder()
1200 try:
1201 # wx.SafeYield()
1202 # print("avoided safe yield in model to ascii")
1203 s = m.main(self.umlcanvas.displaymodel.graph)
1204 self.multiText.SetValue(str(s))
1205 if str(s).strip() == "":
1206 self.multiText.SetValue(ASCII_UML_HELP_MSG)
1207 self.multiText.ShowPosition(0)
1208 finally:
1209 wx.EndBusyCursor()
1210
1211 def FileNew(self, event):
1212 self.app.run.CmdFileNew()
1213
1214 def OnPageSetup(self, evt):
1215 if not self.printData:
1216 self.printData = wx.PrintData()
1217 psdd = wx.PageSetupDialogData(self.printData)
1218 psdd.EnablePrinter(True)
1219 # psdd.CalculatePaperSizeFromId()
1220 dlg = wx.PageSetupDialog(self.frame, psdd)
1221 dlg.ShowModal()
1222
1223 # this makes a copy of the wx.PrintData instead of just saving
1224 # a reference to the one inside the PrintDialogData that will
1225 # be destroyed when the dialog is destroyed
1226 self.printData = wx.PrintData(dlg.GetPageSetupData().GetPrintData())
1227
1228 dlg.Destroy()
1229
1230 def FilePrint(self, event):
1231 if not self.printData:
1232 self.printData = wx.PrintData()
1233 # self.printData = wx.PrintDialogData(self.printData) # ??
1234
1235 # Don't set any of these unless you want to override what
1236 # has been possibly set up in 'self.printData' by an earlier Page Setup.
1237 #
1238 # self.printData.SetPaperId(wx.PAPER_LETTER) # setting paper works
1239 # self.printData.SetPaperId(wx.PAPER_A4)
1240 # self.printData.SetDuplex(wx.DUPLEX_VERTICAL) # setting these doesn't work
1241 # self.printData.SetDuplex(wx.DUPLEX_HORIZONTAL)
1242 # self.printData.SetDuplex(wx.DUPLEX_SIMPLEX) # not duplex
1243 # self.printData.SetOrientation(wx.LANDSCAPE) # setting orientation works
1244
1245 self.printData.SetNoCopies(1) # setting this works, ensure you pick a non thermal printer
1246 self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER)
1247
1248 if self.viewing_plantuml_tab:
1249 printout = MyPrintout(self.plantuml, self.log)
1250 printout2 = MyPrintout(self.plantuml, self.log)
1251
1252 def _fit_diagram_to_paper_for_image(maxX, maxY):
1253 return self.plantuml.maxWidth, self.plantuml.maxHeight
1254
1255 printout.bounds_func = _fit_diagram_to_paper_for_image
1256 printout2.bounds_func = _fit_diagram_to_paper_for_image
1257 else:
1258 printout = MyPrintout(self.umlcanvas, self.log)
1259 printout2 = MyPrintout(self.umlcanvas, self.log)
1260 self.preview = wx.PrintPreview(printout, printout2, self.printData)
1261 if not self.preview.IsOk():
1262 self.log.WriteText("Houston, we have a problem...\n")
1263 return
1264
1265 frame = wx.PreviewFrame(self.preview, self.frame, "This is a print preview")
1266
1267 frame.Initialize()
1268 frame.SetPosition(self.frame.GetPosition())
1269 frame.SetSize(self.frame.GetSize())
1270 frame.Show(True)
1271
1272 def OnAbout(self, event):
1273 # self.MessageBox(ABOUT_MSG.strip() % APP_VERSION)
1274
1275 from wx.adv import AboutDialogInfo, AboutBox
1276 from gui.settings import registered_to
1277
1278 license = "Community Edition" if unregistered else f"PRO Edition - registered to \"{registered_to}\" thank you for your support."
1279 info = AboutDialogInfo()
1280
1281 # image appears on left on all platforms, so make sure the image is small
1282 # info.SetIcon(wx.Icon('media/pynsource.png', wx.BITMAP_TYPE_PNG)) # TODO fix path when deployed, disable for now
1283
1284 info.SetName(ABOUT_APPNAME)
1285 info.SetVersion(str(APP_VERSION))
1286 info.SetWebSite(WEB_PYNSOURCE_HOME_URL, "Home Page")
1287 info.SetDescription(f"{ABOUT_MSG}\n{license}\n")
1288 # info.Description = wordwrap(ABOUT_MSG, 350, wx.ClientDC(self.frame))
1289 info.SetCopyright(ABOUT_AUTHOR)
1290 license = ABOUT_LICENSE if unregistered else ABOUT_LICENSE_PRO
1291 license += "\n\n" + ABOUT_LICENSE_DETAILS
1292 info.SetLicence(license)
1293 info.AddDeveloper(ABOUT_AUTHORS)
1294 # info.AddDocWriter(ABOUT_FEATURES)
1295 # info.AddArtist('Blah')
1296 # info.AddTranslator('Blah')
1297
1298 AboutBox(info)
1299
1300 def OnEnterLicense(self, event):
1301 from dialogs.DialogRego import RegoDialog
1302 from gui.settings import enter_license
1303
1304 dialog = RegoDialog(None)
1305 dialog.m_textCtrl_name.Value = ""
1306 dialog.m_textCtrl_serial.Value = ""
1307
1308 if dialog.ShowModal() == wx.ID_OK:
1309 name = dialog.m_textCtrl_name.Value
1310 serial = dialog.m_textCtrl_serial.Value
1311
1312 result = enter_license(name, serial)
1313 if result:
1314 wx.MessageBox(f"Registration successful - please restart Pynsource.")
1315 else:
1316 wx.MessageBox(f"The registration details you entered are invalid for this version of Pynsource.\n\n{name}\n{serial}")
1317 dialog.Destroy()
1318
1319 def OnVisitWebsite(self, event):
1320 import webbrowser
1321
1322 webbrowser.open(WEB_PYNSOURCE_HOME_URL)
1323
1324 async def mega_refresh_check(self):
1325 print("mrcheck")
1326 while True:
1327 if self.umlcanvas.mega_refresh_flag:
1328 print("mega refresh check find True!")
1329 self.umlcanvas.mega_refresh_flag = False
1330 self.umlcanvas._mega_refresh()
1331 else:
1332 self.umlcanvas.Refresh()
1333 print("plain insurance refresh")
1334 await asyncio.sleep(0.5)
1335
1336 async def check_for_updates(self):
1337 # await asyncio.sleep(15)
1338 # print("hanging back even longer from doing a version check...")
1339 await asyncio.sleep(15)
1340 url = WEB_VERSION_CHECK_URL_TRACKED_DEVEL if self._running_andy_development_mode() else WEB_VERSION_CHECK_URL_TRACKED
1341 status_code = 0
1342 try:
1343 data, status_code = await url_to_data(url)
1344 response_text = data.decode('utf-8')
1345 # info = json.loads(response_text)
1346 info = eval(response_text)
1347 # except (ConnectionError, requests.exceptions.RequestException) as e:
1348 # # log.exception("Trying to render using plantuml server %s str(e)" % plant_uml_server)
1349 # log.error(
1350 # f"Error trying to fetch initial html from plantuml server {plant_uml_server} {str(e)}")
1351 # return None
1352 except asyncio.TimeoutError as e: # there is no string repr of this exception
1353 print("time out getting latest version info")
1354 except aiohttp.client_exceptions.ClientConnectorError as e:
1355 print("connection error (no internet?) getting latest version info")
1356 else:
1357 print(f"checked url for updates: {url}")
1358
1359 if status_code == 200:
1360 self._update_alert(info, alert_even_if_running_latest=False)
1361
1362 def OnCheckForUpdates(self, event):
1363 url = WEB_VERSION_CHECK_URL_TRACKED_DEVEL if self._running_andy_development_mode() else WEB_VERSION_CHECK_URL_TRACKED
1364 s = urllib.request.urlopen(url).read().decode("utf-8")
1365 print(f"manually checked url for updates: {url}")
1366 s = s.replace("\r", "")
1367 info = eval(s)
1368 self._update_alert(info, alert_even_if_running_latest=True)
1369
1370 def _update_alert(self, info, alert_even_if_running_latest):
1371 ver = info["latest_version"]
1372 if ver > APP_VERSION:
1373 msg = WEB_UPDATE_MSG % (ver, info["latest_announcement"].strip())
1374 retCode = wx.MessageBox(
1375 msg.strip(), "Update Check", wx.YES_NO | wx.ICON_QUESTION
1376 ) # MessageBox simpler than MessageDialog
1377 if retCode == wx.YES:
1378 import webbrowser
1379
1380 webbrowser.open(info["download_url"])
1381 elif ver < APP_VERSION:
1382 if alert_even_if_running_latest:
1383 self.MessageBox(
1384 f"You seem to have a pre-release version {APP_VERSION} - congratulations! Stable version is {ver}"
1385 )
1386 else:
1387 print("you have a future version of Pynsource!")
1388 else:
1389 if alert_even_if_running_latest:
1390 self.MessageBox("You already have the latest version: %s" % APP_VERSION)
1391 else:
1392 print("latest version ok")
1393
1394 def _running_andy_development_mode(self):
1395 return os.path.exists("/Users/Andy/Devel/pynsource-rego") or \
1396 os.path.exists("/Users/andy/pynsource-rego") or \
1397 os.path.exists("/home/andy/Devel/pynsource-rego")
1398
1399 # def OnHelpAlt(self, event): # not used
1400 # """manually build a frame with inner html window, no sizer involved"""
1401 # class MyHtmlFrame(wx.Frame):
1402 # def __init__(self, parent, title):
1403 # super(MyHtmlFrame, self).__init__(parent, title=title)
1404 # html = wx.html.HtmlWindow(parent=self)
1405 # if "gtk2" in wx.PlatformInfo:
1406 # html.SetStandardFonts()
1407 # wx.CallAfter(html.LoadPage, os.path.join("dialogs/HelpWindow.html"))
1408 # frm = MyHtmlFrame(parent=self.frame, title="Simple HTML Browser")
1409 # frm.Show()
1410
1411 def OnHelp(self, event):
1412 """Uses a wxformbuilder frame with inner html window"""
1413 from dialogs.HelpWindow import HelpWindow
1414 from wx.html import EVT_HTML_LINK_CLICKED
1415 import webbrowser
1416
1417 class Help(HelpWindow):
1418 def __init__(self, parent):
1419 HelpWindow.__init__(self, parent)
1420
1421 # CMD-W to close Frame by attaching the key bind event to accellerator table
1422 randomId = wx.NewIdRef() # was wx.NewId()
1423 self.Bind(wx.EVT_MENU, self.OnCloseWindow, id=randomId)
1424 accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord("W"), randomId)])
1425 self.SetAcceleratorTable(accel_tbl)
1426
1427 self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyUP) # Close on ESC
1428 self.m_htmlWin1.Bind(EVT_HTML_LINK_CLICKED, self.link_click)
1429
1430 def link_click(self, link):
1431 print("click on", link.GetLinkInfo().Href)
1432 webbrowser.open(link.GetLinkInfo().Href)
1433
1434 def OnKeyUP(self, event): # Close on ESC
1435 keyCode = event.GetKeyCode()
1436 if keyCode == wx.WXK_ESCAPE:
1437 self.Close()
1438 event.Skip()
1439
1440 def OnCancelClick(self, event):
1441 self.Close()
1442
1443 def OnCloseMe(self, event):
1444 self.Close(True)
1445
1446 def OnCloseWindow(self, event):
1447 self.Destroy()
1448
1449 f = Help(parent=self.frame)
1450
1451 dir = dialog_path_pyinstaller_push(self.frame)
1452 try:
1453 f.m_htmlWin1.LoadPage(os.path.join(dir, "HelpWindow.html"))
1454 f.Show(True)
1455 finally:
1456 dialog_path_pyinstaller_pop()
1457
1458 def Enable_if_node_selected(self, event):
1459 selected = [s for s in self.umlcanvas.GetDiagram().GetShapeList() if s.Selected()]
1460 event.Enable(len(selected) > 0 and self.viewing_uml_tab)
1461
1462 @property
1463 def viewing_plantuml_tab(self):
1464 return GUI_LAYOUT == "MULTI_TAB_GUI" and self.notebook.GetSelection() == 2
1465
1466 @property
1467 def viewing_asciiart_tab(self):
1468 return GUI_LAYOUT == "MULTI_TAB_GUI" and self.notebook.GetSelection() == 1
1469
1470 @property
1471 def viewing_uml_tab(self):
1472 if GUI_LAYOUT == "MULTI_TAB_GUI":
1473 if self.notebook:
1474 return self.notebook.GetSelection() == 0
1475 else:
1476 return False
1477 elif GUI_LAYOUT == "SPLIT_GUI":
1478 return True
1479 else:
1480 return self.panel_one.IsShown()
1481
1482 def OnDeleteNode(self, event):
1483 self.app.run.CmdNodeDeleteSelected()
1484
1485 def OnDeleteNode_update(self, event):
1486 self.Enable_if_node_selected(event)
1487
1488 def OnDuplicateNode_update(self, event):
1489 self.Enable_if_node_selected(event)
1490
1491 def uml_only_view_update(self, event):
1492 event.Enable(self.viewing_uml_tab)
1493
1494 def save_plantuml_update(self, event):
1495 event.Enable(self.viewing_plantuml_tab and not unregistered)
1496
1497 def OnPro_update(self, event):
1498 event.Enable(not unregistered)
1499
1500 def OnPro_and_uml_view_only_update(self, event):
1501 event.Enable(not unregistered and self.viewing_uml_tab)
1502
1503 def OnSavePlantUml(self, event):
1504 self.plantuml.OnHandleSaveImage(event)
1505
1506 def OnInsertComment(self, event):
1507 self.app.run.CmdInsertComment()
1508
1509 def OnInsertImage(self, event):
1510 self.app.run.CmdInsertImage()
1511
1512 def OnInsertClass(self, event):
1513 self.app.run.CmdInsertUmlClass()
1514
1515 def OnDuplicate(self, event):
1516 self.app.run.CmdDuplicate()
1517
1518 def OnEditProperties(self, event):
1519 from gui.node_edit_multi_purpose import node_edit_multi_purpose
1520
1521 # not sure why we are looping cos currently cannot select more than one shape
1522 for shape in self.umlcanvas.GetDiagram().GetShapeList():
1523 if shape.Selected():
1524 node_edit_multi_purpose(shape, self.app)
1525 break
1526
1527 def OnEditProperties_update(self, event):
1528 self.Enable_if_node_selected(event)
1529
1530 def OnLayout(self, event):
1531 self.app.run.CmdLayout()
1532
1533 def OnDeepLayout(self, event):
1534 self.app.run.CmdDeepLayout()
1535
1536 def OnRefreshUmlWindow(self, event):
1537 if self.viewing_uml_tab:
1538 self.app.run.CmdRefreshUmlWindow()
1539 elif self.viewing_plantuml_tab:
1540 # self.refresh_plantuml_view()
1541 wx.PostEvent(self.frame, RefreshPlantUmlEvent(force=False))
1542
1543 def MessageBox(self, msg):
1544 dlg = wx.MessageDialog(self.frame, msg, "Message", wx.OK | wx.ICON_INFORMATION)
1545 dlg.ShowModal()
1546 dlg.Destroy()
1547
1548 def OnButton(self, evt):
1549 self.frame.Close(True)
1550
1551 def OnCloseFrame(self, evt):
1552 if hasattr(self, "window") and hasattr(self.window, "ShutdownDemo"):
1553 self.umlcanvas.ShutdownDemo()
1554 self.Cleanup()
1555 evt.Skip()
1556
1557
1558def main():
1559 application = MainApp(0)
1560 # application = MainApp(redirect=True, filename='/tmp/pynsource.log') # to view what's going on
1561 application.MainLoop()
1562
1563def main_async():
1564 # see https://github.com/sirk390/wxasync
1565 application = MainApp(0)
1566 loop = get_event_loop()
1567 loop.run_until_complete(application.MainLoop())
1568
1569 # Let's also cancel all running tasks:
1570 # https://stackoverflow.com/questions/37278647/fire-and-forget-python-async-await
1571 pending = asyncio.Task.all_tasks()
1572 for task in pending:
1573 # print("Cancelling leftover task...", task._coro.cr_code.co_name, task._state)
1574 task.cancel()
1575 # Now we should await task to execute it's cancellation.
1576 # Cancelled task raises asyncio.CancelledError that we can suppress:
1577 with suppress(asyncio.CancelledError):
1578 loop.run_until_complete(task)
1579
1580
1581if __name__ == "__main__":
1582
1583 # Sanity check for paths, ensure there is not any .. and other relative crud in
1584 # our path. You only need that stuff when running a module as a standalone.
1585 # in which case prefix such appends with if __name__ == '__main__':
1586 # Otherwise everything should be assumed to run from trunk/src/ as the root
1587 #
1588 # import sys, pprint
1589 # pprint.pprint(sys.path)
1590
1591 """
1592 Adding this kind of WORKS to allow the app to run ok in a pyinstaller generated .app (and avoids
1593 infinite spawning of the main window - stop this with "pkill Pynsource" in bash). But
1594 gui.settings still being loaded in an infinite loop, bugs occur, and pynsource keeps running
1595 (at least gui.settings does) when terminated, needing a pkill. Very worrisome. Will have
1596 to stop using joblib.
1597 """
1598 # if getattr(sys, 'frozen', False):
1599 # from multiprocessing import freeze_support # allow joblib.Memory to be used in pyinstaller
1600 # freeze_support()
1601
1602 if ASYNC:
1603 main_async()
1604 else:
1605 main()
1606
this is the window size of the resulting desktop app
The Pynsource UML for Python app is open source