mwxlib 0.99.0__py3-none-any.whl → 1.7.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mwx/framework.py CHANGED
@@ -1,15 +1,15 @@
1
1
  #! python3
2
2
  """mwxlib framework.
3
3
  """
4
- __version__ = "0.99.0"
4
+ __version__ = "1.7.13"
5
5
  __author__ = "Kazuya O'moto <komoto@jeol.co.jp>"
6
6
 
7
7
  from contextlib import contextmanager
8
+ from datetime import datetime
8
9
  from functools import wraps, partial
9
10
  from importlib import reload
10
11
  import traceback
11
12
  import builtins
12
- import datetime
13
13
  import textwrap
14
14
  import time
15
15
  import os
@@ -28,31 +28,26 @@ def deb(target=None, loop=True, locals=None, **kwargs):
28
28
  """Dive into the process.
29
29
 
30
30
  Args:
31
- target : Object or module (default None).
32
- If None, the target is set to `__main__`.
33
- loop : If True, the app and the mainloop will be created.
34
- Otherwise, neither the app nor the mainloop will be created.
35
- locals : Additional context of the shell
31
+ target: Object or module (default None).
32
+ If None, the target is set to `__main__`.
33
+ loop: If True, the app and the mainloop will be created.
34
+ locals: Additional context of the shell
36
35
 
37
- **kwargs: Nautilus arguments
36
+ **kwargs: ShellFrame and Nautilus arguments
38
37
 
39
- - introText : introductory of the shell
40
- - startupScript : startup script file (default None)
41
- - execStartupScript : True => Execute the startup script.
42
- - ensureClose : True => EVT_CLOSE will close the window.
43
- False => EVT_CLOSE will hide the window.
38
+ - session: file name of the session. Defaults to None.
39
+ - standalone: True => EVT_CLOSE will close the window.
40
+ False => EVT_CLOSE will hide the window.
41
+ - introText: introductory of the shell
42
+ - startupScript: startup script file (default None)
43
+ - execStartupScript: True => Execute the startup script.
44
44
 
45
45
  Note:
46
46
  This will execute the startup script $(PYTHONSTARTUP).
47
47
  """
48
- quote_unqoute = """
49
- Anything one man can imagine, other man can make real.
50
- --- Jules Verne (1828--1905)
51
- """
52
- kwargs.setdefault("introText",
53
- "mwx {}".format(__version__) + quote_unqoute)
48
+ kwargs.setdefault("introText", f"mwx {__version__}\n")
54
49
  kwargs.setdefault("execStartupScript", True)
55
- kwargs.setdefault("ensureClose", True)
50
+ kwargs.setdefault("standalone", True)
56
51
 
57
52
  app = wx.GetApp() or wx.App()
58
53
  frame = ShellFrame(None, target, **kwargs)
@@ -60,10 +55,9 @@ def deb(target=None, loop=True, locals=None, **kwargs):
60
55
  frame.rootshell.SetFocus()
61
56
  if locals:
62
57
  frame.rootshell.locals.update(locals)
63
- if not loop:
64
- return frame
65
- if not app.GetMainLoop():
66
- return app.MainLoop()
58
+ if loop and not app.GetMainLoop():
59
+ app.MainLoop()
60
+ return frame
67
61
 
68
62
 
69
63
  def postcall(f):
@@ -79,12 +73,12 @@ def postcall(f):
79
73
  @contextmanager
80
74
  def save_focus_excursion():
81
75
  """Save window focus excursion."""
82
- wnd = wx.Window.FindFocus() # original focus
76
+ wnd = wx.Window.FindFocus() # original focus
83
77
  try:
84
78
  yield wnd
85
79
  finally:
86
80
  if wnd and wnd.IsShownOnScreen():
87
- wnd.SetFocus() # restore focus
81
+ wnd.SetFocus() # restore focus
88
82
 
89
83
 
90
84
  _speckeys = {
@@ -170,22 +164,6 @@ _speckeys = {
170
164
 
171
165
  _speckeys_wxkmap = dict((v, k) for k, v in _speckeys.items())
172
166
 
173
- def getKeyState(key):
174
- """Returns state of speckey (cf. wx.GetKeyState)."""
175
- try:
176
- return wx.GetKeyState(_speckeys_wxkmap[key])
177
- except KeyError:
178
- pass
179
-
180
-
181
- def setKeyState(key, state):
182
- """Makes you feel like having pressed/released speckey."""
183
- vk = wx.UIActionSimulator()
184
- if state:
185
- vk.KeyDown(_speckeys_wxkmap[key])
186
- else:
187
- vk.KeyUp(_speckeys_wxkmap[key])
188
-
189
167
 
190
168
  def hotkey(evt):
191
169
  """Interpret evt.KeyCode as hotkey:str and overwrite evt.key.
@@ -196,11 +174,11 @@ def hotkey(evt):
196
174
  mod = ""
197
175
  for k, v in ((wx.WXK_WINDOWS_LEFT, 'Lwin-'),
198
176
  (wx.WXK_WINDOWS_RIGHT, 'Rwin-'),
199
- ## (wx.WXK_CONTROL, 'C-'),
200
- ## (wx.WXK_ALT, 'M-'),
201
- ## (wx.WXK_SHIFT, 'S-')
177
+ # (wx.WXK_CONTROL, 'C-'),
178
+ # (wx.WXK_ALT, 'M-'),
179
+ # (wx.WXK_SHIFT, 'S-')
202
180
  ):
203
- if key != k and wx.GetKeyState(k): # Note: lazy-eval state
181
+ if key != k and wx.GetKeyState(k): # Note: lazy-eval state
204
182
  mod += v
205
183
 
206
184
  if key != wx.WXK_CONTROL and evt.controlDown: mod += "C-"
@@ -213,10 +191,10 @@ def hotkey(evt):
213
191
 
214
192
 
215
193
  def regulate_key(key):
216
- return (key.replace("ctrl-", "C-") # modifier keys abbreviation
194
+ return (key.replace("ctrl-", "C-") # modifier keys abbreviation
217
195
  .replace("alt-", "M-")
218
196
  .replace("shift-", "S-")
219
- .replace("M-C-", "C-M-") # modifier key regulation C-M-S-
197
+ .replace("M-C-", "C-M-") # modifier key regulation C-M-S-
220
198
  .replace("S-M-", "M-S-")
221
199
  .replace("S-C-", "C-S-"))
222
200
 
@@ -231,10 +209,29 @@ class KeyCtrlInterfaceMixin:
231
209
  spec-map : 'C-c'
232
210
  esc-map : 'escape'
233
211
  """
212
+ message = print # override this in subclass
213
+
234
214
  @postcall
235
215
  def post_message(self, *args, **kwargs):
236
216
  return self.message(*args, **kwargs)
237
-
217
+
218
+ @staticmethod
219
+ def getKeyState(key):
220
+ """Return state of speckey (cf. wx.GetKeyState)."""
221
+ try:
222
+ return wx.GetKeyState(_speckeys_wxkmap[key])
223
+ except KeyError:
224
+ pass
225
+
226
+ @staticmethod
227
+ def setKeyState(key, state):
228
+ """Makes you feel like having pressed/released speckey."""
229
+ vk = wx.UIActionSimulator()
230
+ if state:
231
+ vk.KeyDown(_speckeys_wxkmap[key])
232
+ else:
233
+ vk.KeyUp(_speckeys_wxkmap[key])
234
+
238
235
  def make_keymap(self, keymap):
239
236
  """Make a basis of extension map in the handler.
240
237
  """
@@ -242,7 +239,7 @@ class KeyCtrlInterfaceMixin:
242
239
 
243
240
  def _Pass(evt):
244
241
  self.message("{} {}".format(keymap, evt.key))
245
- _Pass.__name__ = str('pass')
242
+ _Pass.__name__ = "pass"
246
243
 
247
244
  state = self.handler.default_state
248
245
  event = keymap + ' pressed'
@@ -262,36 +259,40 @@ class KeyCtrlInterfaceMixin:
262
259
  '*[LR]win pressed' : [ keymap, _Pass ],
263
260
  },
264
261
  })
265
-
262
+
266
263
  def pre_command_hook(self, evt):
267
- """Enter extension mode.
268
- Check text selection for [C-c/C-x].
269
- """
264
+ ## """Called when entering extension mode (internal use only)."""
265
+ ## Check text selection for [C-c/C-x].
270
266
  wnd = wx.Window.FindFocus()
271
267
  if isinstance(wnd, wx.TextEntry) and wnd.StringSelection\
272
- or isinstance(wnd, stc.StyledTextCtrl) and wnd.SelectedText:
273
- ## or any other of pre-selection-p?
268
+ or isinstance(wnd, stc.StyledTextCtrl) and wnd.SelectedText:
274
269
  self.handler('quit', evt)
275
270
  else:
276
271
  self.message(evt.key + '-')
277
272
  evt.Skip()
278
-
273
+ pre_command_hook.__name__ = "enter"
274
+
279
275
  def post_command_hook(self, evt):
276
+ ## """Called when exiting extension mode (internal use only)."""
280
277
  keymap = self.handler.previous_state
281
278
  if keymap:
282
279
  self.message("{} {}".format(keymap, evt.key))
283
280
  else:
284
281
  self.message(evt.key)
282
+ ## Check if the event has reached a top-level window; Don't skip text event.
283
+ if isinstance(self, wx.TopLevelWindow):
284
+ return
285
285
  evt.Skip()
286
-
287
- def define_key(self, keymap, action=None, *args, **kwargs):
286
+ post_command_hook.__name__ = "exit"
287
+
288
+ def define_key(self, keymap, action=None, /, *args, **kwargs):
288
289
  """Define [map key (pressed)] action.
289
290
 
290
291
  If no action, it invalidates the key and returns @decor(binder).
291
292
  The key must be in C-M-S order (ctrl + alt(meta) + shift).
292
293
 
293
294
  Note:
294
- kwargs `doc` and `alias` are reserved as kw-only-args.
295
+ The funcall kwargs `doc` and `alias` are reserved as kw-only-args.
295
296
  """
296
297
  assert isinstance(keymap, str)
297
298
  assert callable(action) or action is None
@@ -306,28 +307,22 @@ class KeyCtrlInterfaceMixin:
306
307
 
307
308
  if map not in self.handler:
308
309
  warn(f"New map to define_key {keymap!r} in {self}.")
309
- self.make_keymap(map) # make new keymap
310
-
311
- transaction = self.handler[map].get(key, [state])
312
- if len(transaction) > 1:
313
- warn(f"Duplicate define_key {keymap!r} in {self}.")
310
+ self.make_keymap(map) # make new keymap
314
311
 
315
312
  if action is None:
316
- self.handler[map].pop(key, None) # cf. undefine_key
313
+ self.handler[map].pop(key, None) # cf. undefine_key
317
314
  return lambda f: self.define_key(keymap, f, *args, **kwargs)
318
315
 
319
316
  F = _F(action, *args, **kwargs)
317
+
320
318
  @wraps(F)
321
319
  def f(*v, **kw):
322
- self.message(f.__name__)
320
+ self.message(f.__name__) # echo message
323
321
  return F(*v, **kw)
324
322
 
325
- if map != state:
326
- self.handler.update({map: {key: [state, self.post_command_hook, f]}})
327
- else:
328
- self.handler.update({map: {key: [state, f]}})
323
+ self.handler.update({map: {key: [state, f]}})
329
324
  return action
330
-
325
+
331
326
  @ignore(UserWarning)
332
327
  def undefine_key(self, keymap):
333
328
  """Delete [map key (pressed)] context."""
@@ -338,9 +333,7 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
338
333
  """Mouse/Key event interface mixin.
339
334
  """
340
335
  handler = property(lambda self: self.__handler)
341
-
342
- message = print # override this in subclass
343
-
336
+
344
337
  def __init__(self):
345
338
  self.__key = ''
346
339
  self.__button = ''
@@ -396,17 +389,17 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
396
389
 
397
390
  self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, lambda v: _N('capture_lost', v))
398
391
  self.Bind(wx.EVT_MOUSE_CAPTURE_CHANGED, lambda v: _N('capture_changed', v))
399
-
400
- def on_hotkey_press(self, evt): #<wx._core.KeyEvent>
392
+
393
+ def on_hotkey_press(self, evt): #<wx._core.KeyEvent>
401
394
  """Called when a key is pressed."""
402
- if evt.EventObject is not self:
403
- evt.Skip()
404
- return
395
+ # if evt.EventObject is not self:
396
+ # evt.Skip()
397
+ # return
405
398
  key = hotkey(evt)
406
399
  self.__key = regulate_key(key + '-')
407
400
  if self.handler('{} pressed'.format(key), evt) is None:
408
401
  evt.Skip()
409
-
402
+
410
403
  def on_hotkey_down(self, evt): #<wx._core.KeyEvent>
411
404
  """Called when a key is pressed while dragging.
412
405
  Specifically called when the mouse is being captured.
@@ -415,15 +408,15 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
415
408
  self.on_hotkey_press(evt)
416
409
  else:
417
410
  evt.Skip()
418
-
419
- def on_hotkey_up(self, evt): #<wx._core.KeyEvent>
411
+
412
+ def on_hotkey_up(self, evt): #<wx._core.KeyEvent>
420
413
  """Called when a key is released."""
421
414
  key = hotkey(evt)
422
415
  self.__key = ''
423
416
  if self.handler('{} released'.format(key), evt) is None:
424
417
  evt.Skip()
425
-
426
- def on_mousewheel(self, evt): #<wx._core.MouseEvent>
418
+
419
+ def on_mousewheel(self, evt): #<wx._core.MouseEvent>
427
420
  """Called on mouse wheel events.
428
421
  Trigger event: 'key+wheel[up|down|right|left] pressed'
429
422
  """
@@ -434,8 +427,8 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
434
427
  evt.key = self.__key + "wheel{}".format(p)
435
428
  if self.handler('{} pressed'.format(evt.key), evt) is None:
436
429
  evt.Skip()
437
-
438
- def on_motion(self, evt): #<wx._core.MouseEvent>
430
+
431
+ def on_motion(self, evt): #<wx._core.MouseEvent>
439
432
  """Called on mouse motion events.
440
433
  Trigger event: 'key+[LMR]drag begin/motion/end'
441
434
  """
@@ -449,8 +442,8 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
449
442
  else:
450
443
  self.handler('motion', evt)
451
444
  evt.Skip()
452
-
453
- def _mouse_handler(self, event, evt): #<wx._core.MouseEvent>
445
+
446
+ def _mouse_handler(self, event, evt): #<wx._core.MouseEvent>
454
447
  """Called on mouse button events.
455
448
  Trigger event: 'key+[LMRX]button pressed/released/dblclick'
456
449
  """
@@ -462,7 +455,7 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
462
455
  kbtn = self.__key + self.__button
463
456
  self.handler('{}drag end'.format(kbtn), evt)
464
457
 
465
- k = evt.GetButton() #{1:L, 2:M, 3:R, 4:X1, 5:X2}
458
+ k = evt.GetButton() # {1:L, 2:M, 3:R, 4:X1, 5:X2}
466
459
  if action == 'pressed' and k in (1,2,3):
467
460
  self.__button = 'LMR'[k-1]
468
461
  else:
@@ -470,22 +463,22 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
470
463
  if self.handler(event, evt) is None:
471
464
  evt.Skip()
472
465
  try:
473
- self.SetFocusIgnoringChildren() # let the panel accept keys
466
+ self.SetFocusIgnoringChildren() # let the panel accept keys
474
467
  except AttributeError:
475
468
  pass
476
-
477
- def _normal_handler(self, event, evt): #<wx._core.Event>
469
+
470
+ def _normal_handler(self, event, evt): #<wx._core.Event>
478
471
  if self.handler(event, evt) is None:
479
472
  evt.Skip()
480
473
 
481
474
 
482
475
  ## --------------------------------
483
- ## wx Framework and Designer
476
+ ## wx Framework and Designer.
484
477
  ## --------------------------------
485
478
 
486
479
  def ID_(id):
487
- ## Free ID - どこで使っているか検索できるように
488
- ## do not use [ID_LOWEST(4999):ID_HIGHEST(5999)]
480
+ ## Free ID - どこで使っているか検索できるように.
481
+ ## Do not use [ID_LOWEST(4999):ID_HIGHEST(5999)].
489
482
  id += wx.ID_HIGHEST
490
483
  assert not wx.ID_LOWEST <= id <= wx.ID_HIGHEST
491
484
  return id
@@ -504,7 +497,7 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
504
497
  )
505
498
 
506
499
  Args:
507
- items : wx objects (with some packing parameters)
500
+ items: wx objects (with some packing parameters)
508
501
 
509
502
  - (obj, 1) -> sized with ratio 1 (parallel to `orient`)
510
503
  - (obj, 1, wx.EXPAND) -> expanded with ratio 1 (perpendicular to `orient`)
@@ -513,9 +506,9 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
513
506
  - (-1,-1) -> padding space
514
507
  - None -> phantom
515
508
 
516
- orient : HORIZONTAL or VERTICAL
517
- label : StaticBox label
518
- style : Sizer option (proportion, flag, border)
509
+ orient: HORIZONTAL or VERTICAL
510
+ label: StaticBox label
511
+ style: Sizer option (proportion, flag, border)
519
512
 
520
513
  - flag-expansion -> EXPAND, SHAPED
521
514
  - flag-border -> TOP, BOTTOM, LEFT, RIGHT, ALL
@@ -523,7 +516,7 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
523
516
  ALIGN_CENTER_VERTICAL, ALIGN_CENTER_HORIZONTAL
524
517
  """
525
518
  if style is None:
526
- style = (0, wx.EXPAND | wx.ALL, 0) # DEFALT_STYLE (prop, flag, border)
519
+ style = (0, wx.EXPAND | wx.ALL, 0) # DEFALT_STYLE (prop, flag, border)
527
520
  if label is None:
528
521
  sizer = wx.BoxSizer(orient)
529
522
  else:
@@ -533,14 +526,14 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
533
526
  if not isinstance(item, (wx.Object, list, tuple, type(None))):
534
527
  warn(f"pack items must be a wx.Object, tuple or None, not {type(item)}")
535
528
  if item is None:
536
- item = (0, 0), 0, 0, 0 # null space
529
+ item = (0, 0), 0, 0, 0 # null space
537
530
  elif not item:
538
- item = (0, 0) # padding space
531
+ item = (0, 0) # padding space
539
532
  try:
540
533
  try:
541
534
  sizer.Add(item, *style)
542
535
  except TypeError:
543
- sizer.Add(*item) # using item-specific style
536
+ sizer.Add(*item) # using item-specific style
544
537
  except TypeError as e:
545
538
  traceback.print_exc()
546
539
  bmp = wx.StaticBitmap(self, bitmap=wx.ArtProvider.GetBitmap(wx.ART_ERROR))
@@ -554,8 +547,8 @@ class Menu(wx.Menu):
554
547
  """Construct the menu.
555
548
 
556
549
  Args:
557
- menulist : list of MenuItem args
558
- owner : window object to bind handlers
550
+ owner: window object to bind handlers
551
+ menulist: list of MenuItem args
559
552
 
560
553
  (id, text, hint, style, icon, ... Menu.Append arguments
561
554
  action, updater, highlight) ... Menu Event handlers
@@ -576,10 +569,10 @@ class Menu(wx.Menu):
576
569
  continue
577
570
  id = item[0]
578
571
  handlers = [x for x in item if callable(x)]
579
- icons = [x for x in item if isinstance(x, wx.Bitmap)]
572
+ icons = [x for x in item if isinstance(x, wx.Bitmap)]
580
573
  argv = [x for x in item if x not in handlers and x not in icons]
581
574
  if isinstance(id, int):
582
- menu_item = wx.MenuItem(self, *argv) # <- menu_item.Id
575
+ menu_item = wx.MenuItem(self, *argv) # <- menu_item.Id
583
576
  if icons:
584
577
  menu_item.SetBitmaps(*icons)
585
578
  self.Append(menu_item)
@@ -590,16 +583,16 @@ class Menu(wx.Menu):
590
583
  except IndexError:
591
584
  pass
592
585
  else:
593
- subitems = list(argv.pop()) # extract the last element as submenu
586
+ subitems = list(argv.pop()) # extract the last element as submenu
594
587
  submenu = Menu(owner, subitems)
595
588
  submenu_item = wx.MenuItem(self, wx.ID_ANY, *argv)
596
589
  submenu_item.SetSubMenu(submenu)
597
590
  if icons:
598
591
  submenu_item.SetBitmaps(*icons)
599
592
  self.Append(submenu_item)
600
- self.Enable(submenu_item.Id, len(subitems)) # Disable an empty menu.
601
- submenu.Id = submenu_item.Id # <- ID_ANY (dummy to check empty sbumenu)
602
-
593
+ self.Enable(submenu_item.Id, len(subitems)) # Disable an empty menu.
594
+ submenu.Id = submenu_item.Id # <- ID_ANY (dummy to check empty sbumenu)
595
+
603
596
  def _unbind(self):
604
597
  for item in self.MenuItems:
605
598
  if item.Id != wx.ID_SEPARATOR:
@@ -608,17 +601,16 @@ class Menu(wx.Menu):
608
601
  self.owner.Unbind(wx.EVT_MENU_HIGHLIGHT, item)
609
602
  if item.SubMenu:
610
603
  item.SubMenu._unbind()
611
-
604
+
612
605
  def Destroy(self):
613
- try:
606
+ if self.owner and not self.owner.IsBeingDeleted():
614
607
  self._unbind()
615
- finally:
616
- return wx.Menu.Destroy(self)
617
-
608
+ return wx.Menu.Destroy(self)
609
+
618
610
  @staticmethod
619
- def Popup(parent, menulist, *args, **kwargs):
620
- menu = Menu(parent, menulist)
621
- parent.PopupMenu(menu, *args, **kwargs)
611
+ def Popup(owner, menulist, *args, **kwargs):
612
+ menu = Menu(owner, menulist)
613
+ owner.PopupMenu(menu, *args, **kwargs)
622
614
  menu.Destroy()
623
615
 
624
616
 
@@ -637,7 +629,7 @@ class MenuBar(wx.MenuBar, TreeList):
637
629
  def __init__(self, *args, **kwargs):
638
630
  wx.MenuBar.__init__(self, *args, **kwargs)
639
631
  TreeList.__init__(self)
640
-
632
+
641
633
  def getmenu(self, key, root=None):
642
634
  if '/' in key:
643
635
  a, b = key.split('/', 1)
@@ -646,7 +638,7 @@ class MenuBar(wx.MenuBar, TreeList):
646
638
  if root is None:
647
639
  return next((menu for menu, label in self.Menus if menu.Title == key), None)
648
640
  return next((item.SubMenu for item in root.MenuItems if item.ItemLabel == key), None)
649
-
641
+
650
642
  def update(self, key):
651
643
  """Update items of the menu that has specified key:root/branch.
652
644
  Call when the menulist is changed.
@@ -661,19 +653,19 @@ class MenuBar(wx.MenuBar, TreeList):
661
653
  return
662
654
 
663
655
  menu._unbind()
664
- for item in menu.MenuItems: # delete all items
656
+ for item in menu.MenuItems: # delete all items
665
657
  menu.Delete(item)
666
658
 
667
- menu2 = Menu(self.Parent, self[key]) # new menu2 to swap menu
659
+ menu2 = Menu(self.Parent, self[key]) # new menu2 to swap menu
668
660
  for item in menu2.MenuItems:
669
- menu.Append(menu2.Remove(item)) # 重複しないようにいったん切り離して追加する
661
+ menu.Append(menu2.Remove(item)) # 重複しないようにいったん切り離して追加する
670
662
 
671
663
  if hasattr(menu, 'Id'):
672
- self.Enable(menu.Id, menu.MenuItemCount > 0) # Disable empty submenu.
664
+ self.Enable(menu.Id, menu.MenuItemCount > 0) # Disable empty submenu.
673
665
 
674
666
  for j, (key, values) in enumerate(self):
675
- self.EnableTop(j, bool(values)) # Disable empty main menu.
676
-
667
+ self.EnableTop(j, bool(values)) # Disable empty main menu.
668
+
677
669
  def reset(self):
678
670
  """Recreates the menubar if the Parent was attached.
679
671
  Call when the menulist is changed.
@@ -682,41 +674,41 @@ class MenuBar(wx.MenuBar, TreeList):
682
674
  warn(f"No parents bound to {self}.")
683
675
  return
684
676
 
685
- for j in range(self.GetMenuCount()): # remove and del all top-level menu
677
+ for j in range(self.GetMenuCount()): # remove and del all top-level menu
686
678
  menu = self.Remove(0)
687
679
  menu.Destroy()
688
680
 
689
681
  for j, (key, values) in enumerate(self):
690
682
  menu = Menu(self.Parent, values)
691
683
  self.Append(menu, key)
692
- self.EnableTop(j, bool(values)) # Disable empty main menu.
684
+ self.EnableTop(j, bool(values)) # Disable empty main menu.
693
685
 
694
686
 
695
687
  class StatusBar(wx.StatusBar):
696
688
  """Construct the statusbar with read/write interfaces.
697
689
 
698
690
  Attributes:
699
- field : list of field widths
700
- pane : index of status text field
691
+ field: list of field widths
692
+ pane: index of status text field
701
693
  """
702
694
  def __init__(self, *args, **kwargs):
703
695
  wx.StatusBar.__init__(self, *args, **kwargs)
704
-
696
+
705
697
  def __call__(self, *args, **kwargs):
706
698
  text = ' '.join(str(v) for v in args)
707
699
  if self:
708
700
  return self.write(text, **kwargs)
709
-
701
+
710
702
  def resize(self, field):
711
703
  self.SetFieldsCount(len(field))
712
- self.SetStatusWidths(list(field)) # oldver requires list type
713
-
704
+ self.SetStatusWidths(list(field)) # oldver requires list type
705
+
714
706
  def write(self, text, pane=0):
715
707
  if text and text[0] == '\b':
716
708
  text = self.read(pane) + text[1:]
717
709
  self.SetStatusText(text, pane % self.GetFieldsCount())
718
710
  return text
719
-
711
+
720
712
  def read(self, pane=0):
721
713
  return self.GetStatusText(pane % self.GetFieldsCount())
722
714
 
@@ -725,23 +717,18 @@ class Frame(wx.Frame, KeyCtrlInterfaceMixin):
725
717
  """Frame extension class.
726
718
 
727
719
  Attributes:
728
- menubar : MenuBar
729
- statusbar : StatusBar
730
- shellframe : mini-frame of the shell
720
+ menubar: MenuBar
721
+ statusbar: StatusBar
722
+ shellframe: mini-frame of the shell
731
723
  """
732
724
  handler = property(lambda self: self.__handler)
733
-
725
+
734
726
  message = property(lambda self: self.statusbar)
735
-
736
- def post_command_hook(self, evt):
737
- ## (override) Don't skip events as a TopLevelWindow.
738
- pass
739
- post_command_hook.__name__ = str('noskip')
740
-
727
+
741
728
  def __init__(self, *args, **kwargs):
742
729
  wx.Frame.__init__(self, *args, **kwargs)
743
730
 
744
- self.shellframe = ShellFrame(None, target=self)
731
+ self.shellframe = ShellFrame(None, target=self, session='')
745
732
 
746
733
  self.menubar = MenuBar()
747
734
  self.menubar["File"] = [
@@ -773,7 +760,7 @@ class Frame(wx.Frame, KeyCtrlInterfaceMixin):
773
760
  ## AcceleratorTable mimic
774
761
  def hook_char(evt):
775
762
  """Called when key down."""
776
- if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
763
+ if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
777
764
  evt.Skip()
778
765
  else:
779
766
  if self.handler('{} pressed'.format(hotkey(evt)), evt) is None:
@@ -788,39 +775,32 @@ class Frame(wx.Frame, KeyCtrlInterfaceMixin):
788
775
  },
789
776
  )
790
777
  self.make_keymap('C-x')
791
-
778
+
792
779
  def About(self):
793
780
  wx.MessageBox(__import__("__main__").__doc__ or "no information",
794
781
  "About this software")
795
-
782
+
796
783
  def Destroy(self):
797
- try:
798
- self.timer.Stop()
799
- self.shellframe.Destroy() # shellframe is not my child
800
- finally:
801
- return wx.Frame.Destroy(self)
784
+ self.timer.Stop()
785
+ self.shellframe.Destroy() # shellframe is not my child
786
+ return wx.Frame.Destroy(self)
802
787
 
803
788
 
804
789
  class MiniFrame(wx.MiniFrame, KeyCtrlInterfaceMixin):
805
790
  """MiniFrame extension class.
806
791
 
807
792
  Attributes:
808
- menubar : MenuBar
809
- statusbar : StatusBar (not shown by default)
793
+ menubar: MenuBar
794
+ statusbar: StatusBar (not shown by default)
810
795
  """
811
796
  handler = property(lambda self: self.__handler)
812
-
797
+
813
798
  message = property(lambda self: self.statusbar)
814
-
815
- def post_command_hook(self, evt):
816
- ## (override) Don't skip events as a TopLevelWindow.
817
- pass
818
- post_command_hook.__name__ = str('noskip')
819
-
799
+
820
800
  def __init__(self, *args, **kwargs):
821
801
  wx.MiniFrame.__init__(self, *args, **kwargs)
822
802
 
823
- ## To disable, self.SetMenuBar(None)
803
+ ## To disable, call self.SetMenuBar(None).
824
804
  self.menubar = MenuBar()
825
805
  self.SetMenuBar(self.menubar)
826
806
 
@@ -828,17 +808,17 @@ class MiniFrame(wx.MiniFrame, KeyCtrlInterfaceMixin):
828
808
  self.statusbar.Show(0)
829
809
  self.SetStatusBar(self.statusbar)
830
810
 
831
- ## AcceleratorTable mimic
811
+ ## AcceleratorTable mimic.
832
812
  def hook_char(evt):
833
813
  """Called when key down."""
834
- if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
814
+ if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
835
815
  evt.Skip()
836
816
  else:
837
817
  if self.handler('{} pressed'.format(hotkey(evt)), evt) is None:
838
818
  evt.Skip()
839
819
  self.Bind(wx.EVT_CHAR_HOOK, hook_char)
840
820
 
841
- ## To default close >>> self.Unbind(wx.EVT_CLOSE)
821
+ ## To default close >>> self.Unbind(wx.EVT_CLOSE).
842
822
  self.Bind(wx.EVT_CLOSE, lambda v: self.Show(0))
843
823
 
844
824
  self.__handler = FSM({ # DNA<MiniFrame>
@@ -849,9 +829,6 @@ class MiniFrame(wx.MiniFrame, KeyCtrlInterfaceMixin):
849
829
  },
850
830
  )
851
831
  self.make_keymap('C-x')
852
-
853
- def Destroy(self):
854
- return wx.MiniFrame.Destroy(self)
855
832
 
856
833
 
857
834
  class AuiNotebook(aui.AuiNotebook):
@@ -870,52 +847,48 @@ class AuiNotebook(aui.AuiNotebook):
870
847
  self.Name = name
871
848
 
872
849
  def tab_menu(evt):
873
- tabs = evt.EventObject #<AuiTabCtrl>
874
- page = tabs.Pages[evt.Selection] # GetPage for split notebook.
850
+ tabs = evt.EventObject #<AuiTabCtrl>
851
+ page = tabs.Pages[evt.Selection] # GetPage for split notebook.
875
852
  try:
876
853
  Menu.Popup(self, page.window.menu)
877
854
  except AttributeError:
878
855
  pass
879
856
  self.Bind(aui.EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, tab_menu)
880
-
881
- @property
882
- def all_pages(self):
883
- """Returns all window pages."""
884
- return [self.GetPage(i) for i in range(self.PageCount)]
885
-
857
+
886
858
  @property
887
- def all_tabs(self):
888
- """Returns all AuiTabCtrl objects."""
859
+ def _all_tabs(self):
860
+ """Return all AuiTabCtrl objects (internal use only)."""
889
861
  return [x for x in self.Children if isinstance(x, aui.AuiTabCtrl)]
890
-
862
+
891
863
  @property
892
- def all_panes(self):
893
- """Returns all AuiPaneInfo excluding `dummy` one."""
864
+ def _all_panes(self):
865
+ """Return all AuiPaneInfo excluding `dummy` one (internal use only)."""
894
866
  return list(self._mgr.AllPanes)[1:]
895
-
867
+
896
868
  def get_pages(self, type=None):
897
869
  """Yields pages of the specified window type."""
898
- for win in self.all_pages:
870
+ for i in range(self.PageCount):
871
+ win = self.GetPage(i)
899
872
  if type is None or isinstance(win, type):
900
873
  yield win
901
-
874
+
902
875
  def swap_page(self, win):
903
876
  """Replace the page with the specified page w/o focusing."""
904
877
  j = self.GetPageIndex(win)
905
878
  if j != -1:
906
- wnd = wx.Window.FindFocus() # original focus
879
+ wnd = wx.Window.FindFocus() # original focus
907
880
  org = self.CurrentPage
908
881
  if j != self.Selection:
909
- self.Selection = j # the focus moves if shown
910
- self.CurrentPage.SetFocus() # reset focus
911
- if wnd and wnd is not org: # restore focus other window
882
+ self.Selection = j # the focus moves if shown
883
+ self.CurrentPage.SetFocus() # reset focus
884
+ if wnd and wnd is not org: # restore focus other window
912
885
  wnd.SetFocus()
913
-
886
+
914
887
  def get_caption(self, win):
915
888
  """Get caption of tab/page for specifiend window."""
916
889
  tab, page = self.find_tab(win)
917
890
  return page.caption
918
-
891
+
919
892
  def set_caption(self, win, caption):
920
893
  """Set caption of tab/page for specifiend window.
921
894
  Returns True if the caption has changed.
@@ -925,41 +898,43 @@ class AuiNotebook(aui.AuiNotebook):
925
898
  page.caption = caption
926
899
  tab.Refresh()
927
900
  return True
928
-
901
+
929
902
  def find_tab(self, win):
930
- """Returns AuiTabCtrl and AuiNotebookPage for the window.
903
+ """Return AuiTabCtrl and AuiNotebookPage for the window.
931
904
 
932
905
  cf. aui.AuiNotebook.FindTab -> bool, tab, idx
933
906
  Note:
934
907
  Argument `win` can also be page.window.Name (not page.caption).
935
908
  """
936
- for tabs in self.all_tabs: #<aui.AuiTabCtrl>
937
- for page in tabs.Pages: #<aui.AuiNotebookPage>
909
+ for tab in self._all_tabs: #<aui.AuiTabCtrl>
910
+ for page in tab.Pages: #<aui.AuiNotebookPage>
938
911
  ## if page.window is win or page.caption == win:
939
912
  if page.window is win or page.window.Name == win:
940
- return tabs, page
941
-
942
- def move_tab(self, win, tabs):
943
- """Move the window page to the specified tabs."""
913
+ return tab, page
914
+
915
+ def move_tab(self, win, tab):
916
+ """Move the window page to the specified tab."""
917
+ if isinstance(tab, int):
918
+ tab = self._all_tabs[tab]
944
919
  try:
945
920
  tc1, nb1 = self.find_tab(win)
946
921
  win = nb1.window
947
- except Exception: # object not found
922
+ except Exception: # object not found
948
923
  return
949
- page = wx.aui.AuiNotebookPage(nb1) # copy-ctor
924
+ page = wx.aui.AuiNotebookPage(nb1) # copy-ctor
950
925
  tc1.RemovePage(win) # Accessing nb1 will crash at this point.
951
- tabs.AddPage(win, page) # Add a page with the copied info.
926
+ tab.AddPage(win, page) # Add a page with the copied info.
952
927
  if tc1.PageCount == 0:
953
928
  ## Delete an empty tab and the corresponding pane.
954
- j = self.all_tabs.index(tc1)
955
- pane = self.all_panes[j]
929
+ j = self._all_tabs.index(tc1)
930
+ pane = self._all_panes[j]
956
931
  tc1.Destroy()
957
932
  self._mgr.DetachPane(pane.window)
958
933
  self._mgr.Update()
959
-
934
+
960
935
  ## Methods to save / load the perspectives.
961
936
  ## *** Inspired by wx.lib.agw.aui.AuiNotebook ***
962
-
937
+
963
938
  def savePerspective(self):
964
939
  """Saves the entire user interface layout into an encoded string,
965
940
  which can then be stored by the application.
@@ -968,17 +943,16 @@ class AuiNotebook(aui.AuiNotebook):
968
943
  Perspectives are saved according to page.window.Name.
969
944
  User should give it (not page.caption) a unique name.
970
945
  """
971
- for j, pane in enumerate(self.all_panes):
946
+ for j, pane in enumerate(self._all_panes):
972
947
  pane.name = f"pane{j+1}"
973
948
  spec = ""
974
- for j, tabs in enumerate(self.all_tabs):
975
- k = next(k for k, page in enumerate(tabs.Pages)
976
- if page.window.Shown) # get active window
949
+ for j, tabs in enumerate(self._all_tabs):
950
+ k = next(k for k, page in enumerate(tabs.Pages) if page.window.Shown)
977
951
  ## names = [page.caption for page in tabs.Pages]
978
952
  names = [page.window.Name for page in tabs.Pages]
979
953
  spec += f"pane{j+1}={names};{k}|"
980
954
  return spec + '@' + self._mgr.SavePerspective()
981
-
955
+
982
956
  def loadPerspective(self, spec):
983
957
  """Loads a saved perspective.
984
958
 
@@ -990,29 +964,26 @@ class AuiNotebook(aui.AuiNotebook):
990
964
  tabinfo = re.findall(r"pane\w+?=(.*?);(.*?)\|", tabs)
991
965
  try:
992
966
  self.Parent.Freeze()
993
- ## Collapse all tabs to main tabctrl
994
- maintab = self.all_tabs[0]
995
- for win in self.all_pages:
996
- self.move_tab(win, maintab)
967
+ ## Collapse all tabctrls to main tabctrl.
968
+ for win in self.get_pages():
969
+ self.move_tab(win, 0)
997
970
 
998
- ## Create a new tab using Split method.
971
+ ## Create a new tabctrl using Split method.
999
972
  ## Note: The normal way of creating panes with `_mgr` crashes.
1000
-
1001
- all_names = [win.Name for win in self.all_pages]
973
+ all_names = [win.Name for win in self.get_pages()]
1002
974
  for names, k in tabinfo[1:]:
1003
975
  names, k = eval(names), int(k)
1004
- i = all_names.index(names[0]) # Assuming 0:tab is included.
976
+ i = all_names.index(names[0]) # Assuming 0:tab is included.
1005
977
  self.Split(i, wx.LEFT)
1006
- newtab = self.all_tabs[-1]
1007
978
  for name in names[1:]:
1008
- self.move_tab(name, newtab)
1009
- self.Selection = all_names.index(names[k]) # new tabs active window
979
+ self.move_tab(name, -1)
980
+ self.Selection = all_names.index(names[k]) # new tabctrl active window
1010
981
  else:
1011
982
  names, k = tabinfo[0]
1012
983
  names, k = eval(names), int(k)
1013
- self.Selection = all_names.index(names[k]) # main tabs active window
984
+ self.Selection = all_names.index(names[k]) # main tabctrl active window
1014
985
 
1015
- for j, pane in enumerate(self.all_panes):
986
+ for j, pane in enumerate(self._all_panes):
1016
987
  pane.name = f"pane{j+1}"
1017
988
  self._mgr.LoadPerspective(frames)
1018
989
  self._mgr.Update()
@@ -1028,34 +999,36 @@ class FileDropLoader(wx.DropTarget):
1028
999
  def __init__(self, target):
1029
1000
  wx.DropTarget.__init__(self)
1030
1001
 
1031
- self.editor = target
1002
+ self.target = target
1032
1003
  self.textdo = wx.TextDataObject()
1033
1004
  self.filedo = wx.FileDataObject()
1034
- self.DataObject = wx.DataObjectComposite()
1035
- self.DataObject.Add(self.textdo)
1036
- self.DataObject.Add(self.filedo, True)
1037
-
1005
+ self.do = wx.DataObjectComposite()
1006
+ self.do.Add(self.textdo)
1007
+ self.do.Add(self.filedo)
1008
+ self.SetDataObject(self.do)
1009
+
1010
+ def OnDragOver(self, x, y, result):
1011
+ index, flags = self.target.HitTest((x, y))
1012
+ if index != -1:
1013
+ self.target.Selection = index
1014
+ result = wx.DragCopy
1015
+ else:
1016
+ result = wx.DragNone
1017
+ return result
1018
+
1038
1019
  def OnData(self, x, y, result):
1039
- editor = self.editor
1020
+ editor = self.target.Parent.current_editor
1040
1021
  self.GetData()
1041
- if self.textdo.TextLength > 1:
1042
- f = self.textdo.Text.strip()
1043
- res = editor.load_file(f)
1044
- if res:
1045
- editor.buffer.SetFocus()
1046
- result = wx.DragCopy
1047
- elif res is None:
1048
- editor.post_message("Load canceled.")
1049
- result = wx.DragCancel
1050
- else:
1051
- editor.post_message(f"Loading {f!r} failed.")
1052
- result = wx.DragNone
1053
- self.textdo.Text = ''
1022
+ if self.textdo.Text:
1023
+ fn = self.textdo.Text.strip()
1024
+ res = editor.parent.handler("text_dropped", fn)
1025
+ if res is None or not any(res):
1026
+ editor.load_file(fn)
1027
+ result = wx.DragCopy
1028
+ self.textdo.SetText("")
1054
1029
  else:
1055
- for f in self.filedo.Filenames:
1056
- if editor.load_file(f):
1057
- editor.buffer.SetFocus()
1058
- editor.post_message(f"Loaded {f!r} successfully.")
1030
+ for fn in self.filedo.Filenames:
1031
+ editor.load_file(fn)
1059
1032
  self.filedo.SetData(wx.DF_FILENAME, None)
1060
1033
  return result
1061
1034
 
@@ -1064,26 +1037,28 @@ class ShellFrame(MiniFrame):
1064
1037
  """MiniFrame of the Shell.
1065
1038
 
1066
1039
  Args:
1067
- target : target object of the rootshell.
1068
- If None, it will be `__main__`.
1069
- debrc : session file for deb run command.
1070
- SESSION_FILE will be overwritten.
1071
- ensureClose : A flag for the shell standalone.
1072
- If True, EVT_CLOSE will close the window.
1073
- Otherwise it will be only hidden.
1074
- **kwargs : Nautilus arguments
1040
+ target: target object of the rootshell.
1041
+ If None, it will be __main__.
1042
+ session: file name of the session. Defaults to None.
1043
+ If None, no session will be created or saved.
1044
+ If `''`, the default session (.debrc) will be loaded.
1045
+ standalone: flag for the shell standalone.
1046
+ If True, EVT_CLOSE will close the window.
1047
+ Otherwise the window will be only hidden.
1048
+ **kwargs: Nautilus arguments
1075
1049
 
1076
1050
  Attributes:
1077
- console : Notebook of shells
1078
- ghost : Notebook of editors/buffers
1079
- watcher : Notebook of global/locals watcher
1080
- Scratch : Book of scratch (tooltip)
1081
- Help : Book of help
1082
- Log : Book of logging
1083
- monitor : wxmon.EventMonitor object
1084
- inspector : wxwit.Inspector object
1085
- debugger : wxpdb.Debugger object
1086
- ginfo/linfo : globals/locals list
1051
+ console: Notebook of shells
1052
+ ghost: Notebook of editors/buffers
1053
+ watcher: Notebook of global/locals watcher
1054
+ Scratch: Book of scratch (tooltip)
1055
+ Help: Book of help
1056
+ Log: Book of logging
1057
+ monitor: wxmon.EventMonitor object
1058
+ inspector: wxwit.Inspector object
1059
+ debugger: wxpdb.Debugger object
1060
+ ginfo: globals list
1061
+ linfo: locals list
1087
1062
 
1088
1063
  Built-in utility::
1089
1064
 
@@ -1102,23 +1077,19 @@ class ShellFrame(MiniFrame):
1102
1077
  @highlight : Highlight the widget.
1103
1078
  @filling : Inspection using ``wx.lib.filling.Filling``.
1104
1079
  """
1105
- rootshell = property(lambda self: self.__shell) #: the root shell
1106
-
1107
- def __init__(self, parent, target=None, debrc=None, ensureClose=False,
1108
- title=None, size=(1280,720), style=wx.DEFAULT_FRAME_STYLE,
1109
- **kwargs):
1110
- MiniFrame.__init__(self, parent, size=size, style=style)
1080
+ rootshell = property(lambda self: self.__shell) # the root shell
1081
+
1082
+ def __init__(self, parent, target=None, session=None, standalone=False, **kwargs):
1083
+ MiniFrame.__init__(self, parent, size=(1280,720), style=wx.DEFAULT_FRAME_STYLE)
1111
1084
 
1112
1085
  self.statusbar.resize((-1,120))
1113
1086
  self.statusbar.Show(1)
1114
1087
 
1115
- self.__standalone = bool(ensureClose)
1088
+ self.__standalone = bool(standalone)
1116
1089
 
1117
- ## Initialize self-specific builtins.
1118
- ## Note: This should be called before creating root shell.
1119
1090
  self.Init()
1120
1091
 
1121
- from .nutshell import Nautilus, EditorBook
1092
+ from .nutshell import Nautilus, EditorBook, Stylus
1122
1093
  from .bookshelf import EditorTreeCtrl
1123
1094
 
1124
1095
  self.__shell = Nautilus(self,
@@ -1133,6 +1104,28 @@ class ShellFrame(MiniFrame):
1133
1104
  self.Bookshelf = EditorTreeCtrl(self, name="Bookshelf",
1134
1105
  style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT)
1135
1106
 
1107
+ ## Set shell and editor styles.
1108
+ self.Scratch.set_attributes(Style=Stylus.py_shell_mode)
1109
+
1110
+ self.Log.set_attributes(ReadOnly=True,
1111
+ Style=Stylus.py_log_mode)
1112
+
1113
+ self.Help.set_attributes(ReadOnly=False,
1114
+ Style=Stylus.py_log_mode)
1115
+
1116
+ self.set_hookable(self.Scratch)
1117
+ self.set_hookable(self.Log)
1118
+
1119
+ @self.Scratch.define_key('C-j')
1120
+ def eval_line(evt):
1121
+ self.Scratch.buffer.eval_line()
1122
+ evt.Skip(False) # Don't skip explicitly.
1123
+
1124
+ @self.Scratch.define_key('M-j')
1125
+ def eval_buffer(evt):
1126
+ self.Scratch.buffer.exec_region()
1127
+ evt.Skip(False) # Don't skip explicitly.
1128
+
1136
1129
  from .wxpdb import Debugger
1137
1130
  from .wxwit import Inspector
1138
1131
  from .wxmon import EventMonitor
@@ -1142,11 +1135,11 @@ class ShellFrame(MiniFrame):
1142
1135
  self.debugger = Debugger(self,
1143
1136
  stdin=self.__shell.interp.stdin,
1144
1137
  stdout=self.__shell.interp.stdout,
1145
- skip=[Debugger.__module__, # Don't enter debugger
1146
- EventMonitor.__module__, # Don't enter event-hook
1138
+ skip=[Debugger.__module__, # Don't enter debugger
1139
+ EventMonitor.__module__, # Don't enter event-hook
1147
1140
  FSM.__module__,
1148
1141
  'wx.core', 'wx.lib.eventwatcher',
1149
- 'fnmatch', 'warnings', 'bdb', 'pdb', #'contextlib',
1142
+ 'fnmatch', 'warnings', 'bdb', 'pdb', 'contextlib',
1150
1143
  ],
1151
1144
  )
1152
1145
  self.inspector = Inspector(self, name="Inspector")
@@ -1157,9 +1150,7 @@ class ShellFrame(MiniFrame):
1157
1150
  self.console = AuiNotebook(self, size=(600,400), name='console')
1158
1151
  self.console.AddPage(self.__shell, "root", bitmap=Icon('core'))
1159
1152
  self.console.TabCtrlHeight = 0
1160
- ## self.console.Name = "console"
1161
1153
 
1162
- ## self.console.Bind(aui.EVT_AUINOTEBOOK_BUTTON, self.OnConsoleCloseBtn)
1163
1154
  self.console.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnConsolePageClose)
1164
1155
  self.console.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnConsolePageChanged)
1165
1156
 
@@ -1170,7 +1161,7 @@ class ShellFrame(MiniFrame):
1170
1161
 
1171
1162
  self.ghost.AddPage(self.Bookshelf, "Bookshelf", bitmap=Icon('book'))
1172
1163
 
1173
- self.ghost.SetDropTarget(FileDropLoader(self.Scratch))
1164
+ self.ghost.SetDropTarget(FileDropLoader(self.ghost))
1174
1165
 
1175
1166
  self.watcher = AuiNotebook(self, size=(600,400), name="watcher")
1176
1167
  self.watcher.AddPage(self.ginfo, "globals")
@@ -1191,7 +1182,7 @@ class ShellFrame(MiniFrame):
1191
1182
  self._mgr.AddPane(self.ghost,
1192
1183
  aui.AuiPaneInfo().Name("ghost")
1193
1184
  .Caption("Ghost in the Shell").Right()
1194
- .MaximizeButton().Show(0))
1185
+ .MaximizeButton().Show(1))
1195
1186
 
1196
1187
  self._mgr.AddPane(self.watcher,
1197
1188
  aui.AuiPaneInfo().Name("watcher")
@@ -1213,6 +1204,8 @@ class ShellFrame(MiniFrame):
1213
1204
 
1214
1205
  self.Bind(wx.EVT_FIND, self.OnFindNext)
1215
1206
  self.Bind(wx.EVT_FIND_NEXT, self.OnFindNext)
1207
+ self.Bind(wx.EVT_FIND_REPLACE, self.OnFindNext)
1208
+ self.Bind(wx.EVT_FIND_REPLACE_ALL, self.OnFindNext)
1216
1209
  self.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
1217
1210
 
1218
1211
  self.indicator = Indicator(self.statusbar, value=1)
@@ -1254,53 +1247,38 @@ class ShellFrame(MiniFrame):
1254
1247
  'trace_end' : [ None, self.on_trace_end ],
1255
1248
  'monitor_begin' : [ None, self.on_monitor_begin ],
1256
1249
  'monitor_end' : [ None, self.on_monitor_end ],
1257
- 'buffer_new' : [ None, ],
1258
1250
  'shell_new' : [ None, ],
1251
+ 'book_new' : [ None, ],
1259
1252
  'add_log' : [ None, self.add_log ],
1260
1253
  'add_help' : [ None, self.add_help ],
1261
1254
  'title_window' : [ None, self.on_title_window ],
1262
- 'buffer_caption_updated' : [ None, self.on_buffer_caption ], # => self.OnActivate
1263
1255
  },
1264
1256
  0 : {
1265
1257
  '* pressed' : (0, fork_debugger),
1266
1258
  '* released' : (0, fork_debugger),
1267
- 'C-g pressed' : (0, self.quit, fork_debugger),
1259
+ 'C-g pressed' : (0, self.Quit, fork_debugger),
1268
1260
  'f1 pressed' : (0, self.About),
1269
- 'C-f pressed' : (0, self.OnFindText),
1270
- 'f3 pressed' : (0, self.OnFindNext),
1271
- 'S-f3 pressed' : (0, self.OnFindPrev),
1272
- 'f11 pressed' : (0, _F(self.toggle_window, self.ghost, alias='toggle_ghost')),
1273
- 'S-f11 pressed' : (0, _F(self.toggle_window, self.watcher, alias='toggle_watcher')),
1261
+ 'C-f pressed' : (0, self.on_search_dialog),
1262
+ 'C-S-f pressed' : (0, self.on_replace_dialog),
1263
+ 'f3 pressed' : (0, self.repeat_forward_search),
1264
+ 'S-f3 pressed' : (0, self.repeat_backward_search),
1265
+ 'f11 pressed' : (0, _F(self.toggle_window, win=self.ghost, alias='toggle_ghost')),
1266
+ 'S-f11 pressed' : (0, _F(self.toggle_window, win=self.watcher, alias='toggle_watcher')),
1274
1267
  'f12 pressed' : (0, _F(self.Close, alias="close")),
1275
1268
  '*f[0-9]* pressed' : (0, ),
1276
1269
  },
1277
1270
  })
1278
1271
 
1279
- self.Scratch.set_attributes(Style=Nautilus.STYLE)
1280
- self.Log.set_attributes(ReadOnly=True)
1281
- self.Help.set_attributes(ReadOnly=False)
1282
-
1283
- self.set_hookable(self.Scratch)
1284
- self.set_hookable(self.Log)
1285
-
1286
- @self.Scratch.define_key('C-j')
1287
- def eval_line(evt):
1288
- self.Scratch.buffer.eval_line()
1289
- evt.Skip(False) # Don't skip explicitly.
1290
-
1291
- @self.Scratch.define_key('M-j')
1292
- def eval_buffer(evt):
1293
- self.Scratch.buffer.exec_region()
1294
- evt.Skip(False) # Don't skip explicitly.
1295
-
1296
- ## Session
1272
+ ## Session files.
1297
1273
  self.SESSION_FILE = get_rootpath(".debrc")
1298
1274
  self.SCRATCH_FILE = get_rootpath("scratch.py")
1299
1275
  self.LOGGING_FILE = get_rootpath("deb-logging.log")
1300
1276
 
1301
- self.load_session(
1302
- os.path.abspath(debrc) if debrc else self.SESSION_FILE)
1303
-
1277
+ if session is not None:
1278
+ self.load_session(session or self.SESSION_FILE)
1279
+ else:
1280
+ self.SESSION_FILE = None
1281
+
1304
1282
  def load_session(self, filename):
1305
1283
  """Load session from file.
1306
1284
  Buffers, pointers, and the entire layout are loaded.
@@ -1313,23 +1291,25 @@ class ShellFrame(MiniFrame):
1313
1291
  except Exception:
1314
1292
  pass
1315
1293
 
1316
- _fload(self.Scratch, self.SCRATCH_FILE) # restore scratch
1294
+ _fload(self.Scratch, self.SCRATCH_FILE) # restore scratch
1317
1295
 
1318
1296
  ## Re-open the *log* file.
1319
- self.add_log("#! Opened: <{}>\r\n".format(datetime.datetime.now()))
1297
+ self.add_log("#! Opened: <{}>\r\n".format(datetime.now()))
1320
1298
 
1321
- fn = os.path.abspath(filename)
1299
+ session = os.path.abspath(filename)
1322
1300
  try:
1323
- with open(fn, encoding='utf-8', newline='') as i:
1301
+ with open(session) as i:
1324
1302
  exec(i.read())
1325
1303
  except FileNotFoundError:
1326
1304
  pass
1327
- self.SESSION_FILE = fn
1305
+ except Exception as e:
1306
+ print("- Failed to load session:", e)
1307
+ self.SESSION_FILE = session
1328
1308
 
1329
1309
  ## Reposition the window if it is not on the desktop.
1330
1310
  if wx.Display.GetFromWindow(self) == -1:
1331
1311
  self.Position = (0, 0)
1332
-
1312
+
1333
1313
  def save_session(self):
1334
1314
  """Save session to file.
1335
1315
  Buffers, pointers, and the entire layout are saved.
@@ -1342,16 +1322,21 @@ class ShellFrame(MiniFrame):
1342
1322
  except Exception:
1343
1323
  pass
1344
1324
 
1345
- _fsave(self.Scratch, self.SCRATCH_FILE) # save scratch
1346
- _fsave(self.Log, self.LOGGING_FILE) # save log
1325
+ _fsave(self.Scratch, self.SCRATCH_FILE) # save scratch
1326
+ _fsave(self.Log, self.LOGGING_FILE) # save log
1327
+
1328
+ if not self.SESSION_FILE:
1329
+ return
1347
1330
 
1348
1331
  with open(self.SESSION_FILE, 'w') as o:
1349
1332
  o.write("#! Session file (This file is generated automatically)\n")
1350
1333
  o.write("self.SetSize({})\n".format(self.Size))
1351
1334
  o.write("self.SetPosition({})\n".format(self.Position))
1352
1335
 
1353
- for book in self.all_editors:
1354
- for buf in book.all_buffers:
1336
+ for book in self.get_all_editors():
1337
+ if book.Name not in ("Scratch", "Log", "Help"): # Save default editors only.
1338
+ continue
1339
+ for buf in book.get_all_buffers():
1355
1340
  if buf.mtdelta is not None:
1356
1341
  o.write("self.{}.load_file({!r}, {})\n"
1357
1342
  .format(book.Name, buf.filename, buf.markline+1))
@@ -1363,12 +1348,16 @@ class ShellFrame(MiniFrame):
1363
1348
  "self._mgr.LoadPerspective({!r})".format(self._mgr.SavePerspective()),
1364
1349
  "self._mgr.Update()\n",
1365
1350
  )))
1366
-
1351
+
1367
1352
  def Init(self):
1353
+ """Initialize self-specific builtins.
1354
+ Note:
1355
+ This should be called before creating root shell.
1356
+ """
1368
1357
  try:
1369
1358
  builtins.dive
1370
1359
  except AttributeError:
1371
- ## Add useful built-in functions and methods
1360
+ ## Add useful built-in functions and methods.
1372
1361
  builtins.apropos = apropos
1373
1362
  builtins.typename = typename
1374
1363
  builtins.reload = reload
@@ -1387,10 +1376,10 @@ class ShellFrame(MiniFrame):
1387
1376
  builtins.profile = self.profile
1388
1377
  builtins.highlight = self.highlight
1389
1378
  builtins.filling = filling
1390
-
1379
+
1391
1380
  def Destroy(self):
1392
1381
  try:
1393
- ## Remove built-in self methods
1382
+ ## Remove built-in self methods.
1394
1383
  del builtins.info
1395
1384
  del builtins.help
1396
1385
  del builtins.load
@@ -1402,16 +1391,21 @@ class ShellFrame(MiniFrame):
1402
1391
  del builtins.highlight
1403
1392
  except AttributeError:
1404
1393
  pass
1405
- try:
1406
- self.timer.Stop()
1407
- self.save_session()
1408
- finally:
1409
- self._mgr.UnInit()
1410
- return MiniFrame.Destroy(self)
1411
-
1394
+
1395
+ self.timer.Stop()
1396
+ self.save_session()
1397
+ self._mgr.UnInit()
1398
+ return MiniFrame.Destroy(self)
1399
+
1400
+ def Close(self):
1401
+ if self.__standalone:
1402
+ MiniFrame.Close(self)
1403
+ else:
1404
+ self.Show(not self.Shown)
1405
+
1412
1406
  def OnClose(self, evt):
1413
1407
  if self.debugger.busy:
1414
- if wx.MessageBox( # Confirm debugger close.
1408
+ if wx.MessageBox( # Confirm closing the debugger.
1415
1409
  "The debugger is running.\n\n"
1416
1410
  "Enter [q]uit to exit before closing.\n"
1417
1411
  "Continue closing?",
@@ -1419,20 +1413,20 @@ class ShellFrame(MiniFrame):
1419
1413
  self.message("The close has been canceled.")
1420
1414
  evt.Veto()
1421
1415
  return
1422
- #? RuntimeError('wrapped C/C++ object ... has been deleted')
1423
- self.quit()
1416
+ ## RuntimeError('wrapped C/C++ object ... has been deleted')
1417
+ self.Quit()
1424
1418
 
1425
1419
  if self.debugger.tracing:
1426
1420
  wx.MessageBox("The debugger ends tracing.\n\n"
1427
1421
  "The trace pointer will be cleared.")
1428
- self.debugger.unwatch() # cf. [pointer_unset] stop_trace
1422
+ self.debugger.unwatch() # cf. [pointer_unset] stop_trace
1429
1423
 
1430
- for book in self.all_editors:
1431
- for buf in book.all_buffers:
1424
+ for book in self.get_all_editors():
1425
+ for buf in book.get_all_buffers():
1432
1426
  if buf.need_buffer_save:
1433
1427
  self.popup_window(book)
1434
1428
  buf.SetFocus()
1435
- if wx.MessageBox( # Confirm close.
1429
+ if wx.MessageBox( # Confirm closing the buffer.
1436
1430
  "You are closing unsaved content.\n\n"
1437
1431
  "Changes to the content will be discarded.\n"
1438
1432
  "Continue closing?",
@@ -1441,58 +1435,60 @@ class ShellFrame(MiniFrame):
1441
1435
  self.message("The close has been canceled.")
1442
1436
  evt.Veto()
1443
1437
  return
1444
- break # Don't ask any more.
1438
+ break # Don't ask any more.
1445
1439
  if self.__standalone:
1446
- evt.Skip() # Close the window
1440
+ evt.Skip() # Close the window
1447
1441
  else:
1448
- self.Show(0) # Don't destroy the window
1449
-
1442
+ self.Show(0) # Don't destroy the window
1443
+
1450
1444
  def OnActivate(self, evt):
1451
1445
  if not evt.Active:
1452
1446
  ## Reset autoload when active focus going outside.
1453
1447
  self.__autoload = True
1454
- elif evt.GetActivationReason() == evt.Reason_Mouse\
1455
- and self.__autoload:
1448
+ ## elif evt.GetActivationReason() == evt.Reason_Mouse and self.__autoload:
1449
+ elif self.__autoload:
1456
1450
  ## Check all buffers that need to be loaded.
1457
- for book in self.all_editors:
1458
- for buf in book.all_buffers:
1451
+ verbose = 1
1452
+ for book in self.get_all_editors():
1453
+ for buf in book.get_all_buffers():
1459
1454
  if buf.need_buffer_load:
1460
- if wx.MessageBox( # Confirm load.
1461
- "The file has been modified externally.\n\n"
1462
- "The contents of the buffer will be overwritten.\n"
1463
- "Continue loading {}/{}?".format(book.Name, buf.name),
1464
- "Load {!r}".format(buf.name),
1465
- style=wx.YES_NO|wx.ICON_INFORMATION) != wx.YES:
1466
- self.__autoload = False # Don't ask any more.
1467
- return
1468
- book.load_file(buf.filename)
1455
+ buf.update_caption()
1456
+ if verbose:
1457
+ with wx.MessageDialog(self, # Confirm load.
1458
+ "The file has been modified externally.\n\n"
1459
+ "The contents of the buffer will be overwritten.\n"
1460
+ "Continue loading {}/{}?".format(book.Name, buf.name),
1461
+ "Load {!r}".format(buf.name),
1462
+ style=wx.YES_NO|wx.CANCEL|wx.HELP|wx.ICON_INFORMATION) as dlg:
1463
+ dlg.SetHelpLabel("Yes to All")
1464
+ ret = dlg.ShowModal()
1465
+ if ret == wx.ID_NO:
1466
+ continue
1467
+ if ret == wx.ID_CANCEL:
1468
+ break # all
1469
+ if ret == wx.ID_HELP: # ID_YESTOALL
1470
+ verbose = 0
1471
+ book.load_file(buf.filename, buf.markline+1)
1472
+ self.__autoload = False
1469
1473
  ## Reinitialize self-specific builtins if other instances are destroyed.
1470
1474
  if evt.Active:
1471
1475
  self.Init()
1472
1476
  evt.Skip()
1473
-
1477
+
1474
1478
  def OnShow(self, evt):
1475
- pane = self._mgr.GetPane(self.watcher)
1476
- if evt.IsShown():
1477
- if pane.IsShown():
1478
- self.inspector.watch()
1479
- self.monitor.watch()
1480
- else:
1481
- if pane.IsDocked():
1482
- self.inspector.unwatch()
1483
- self.monitor.unwatch()
1479
+ for pane in self._mgr.GetAllPanes():
1480
+ ## When the window is hidden, disable docking and keep child panes floating.
1481
+ pane.Dockable(evt.IsShown() or pane.IsDocked())
1484
1482
  evt.Skip()
1485
-
1483
+
1486
1484
  def OnGhostShow(self, evt):
1487
1485
  if evt.IsShown():
1488
1486
  self.inspector.watch()
1489
- self.monitor.watch()
1490
1487
  else:
1491
1488
  self.inspector.unwatch()
1492
- self.monitor.unwatch()
1493
1489
  evt.Skip()
1494
-
1495
- def OnConsolePageChanged(self, evt): #<wx._aui.AuiNotebookEvent>
1490
+
1491
+ def OnConsolePageChanged(self, evt): #<wx._aui.AuiNotebookEvent>
1496
1492
  nb = evt.EventObject
1497
1493
  win = nb.CurrentPage
1498
1494
  if win is self.rootshell:
@@ -1501,24 +1497,12 @@ class ShellFrame(MiniFrame):
1501
1497
  nb.WindowStyle |= aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
1502
1498
  nb.TabCtrlHeight = 0 if nb.PageCount == 1 else -1
1503
1499
  evt.Skip()
1504
-
1505
- ## def OnConsoleCloseBtn(self, evt): #<wx._aui.AuiNotebookEvent>
1506
- ## tabs = evt.EventObject
1507
- ## win = tabs.Pages[evt.Selection].window # GetPage for split notebook.
1508
- ## if win is self.rootshell:
1509
- ## ## self.message("- Don't close the root shell.")
1510
- ## return
1511
- ## elif self.debugger.busy and win is self.debugger.interactive_shell:
1512
- ## wx.MessageBox("The debugger is running.\n\n"
1513
- ## "Enter [q]uit to exit before closing.")
1514
- ## return
1515
- ## evt.Skip()
1516
-
1517
- def OnConsolePageClose(self, evt): #<wx._aui.AuiNotebookEvent>
1500
+
1501
+ def OnConsolePageClose(self, evt): #<wx._aui.AuiNotebookEvent>
1518
1502
  nb = evt.EventObject
1519
- win = nb.all_pages[evt.Selection]
1503
+ win = list(nb.get_pages())[evt.Selection]
1520
1504
  if win is self.rootshell:
1521
- ## self.message("Don't close the root shell.")
1505
+ # self.message("Don't close the root shell.")
1522
1506
  nb.WindowStyle &= ~aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
1523
1507
  evt.Veto()
1524
1508
  elif self.debugger.busy and win is self.debugger.interactive_shell:
@@ -1527,39 +1511,37 @@ class ShellFrame(MiniFrame):
1527
1511
  evt.Veto()
1528
1512
  else:
1529
1513
  evt.Skip()
1530
-
1514
+
1531
1515
  def About(self, evt=None):
1532
1516
  self.add_help(
1533
1517
  '\n\n'.join((
1534
- "#<module 'mwx' from {!r}>".format(__file__),
1535
- "Author: {!r}".format(__author__),
1536
- "Version: {!s}".format(__version__),
1518
+ f"#<module 'mwx' from {__file__!r}>",
1519
+ f"Author: {__author__!r}",
1520
+ f"Version: {__version__!s}",
1537
1521
  self.__class__.__doc__,
1538
1522
  self.rootshell.__class__.__doc__,
1539
-
1540
- ## Thanks to wx.py.shell.
1541
- "#{!r}".format(wx.py),
1542
- "Author: {!r}".format(wx.py.version.__author__),
1543
- "Version: {!s}".format(wx.py.version.VERSION),
1523
+ ## <--- Thanks to <wx.py.shell> --->
1524
+ f"#{wx.py!r}",
1525
+ f"Author: {wx.py.version.__author__!r}",
1526
+ f"Version: {wx.py.version.VERSION!s}",
1544
1527
  wx.py.shell.Shell.__doc__,
1545
1528
  textwrap.indent("*original" + wx.py.shell.HELP_TEXT, ' '*4),
1546
-
1547
- ## Thanks are also due to wx.
1548
- "#{!r}".format(wx),
1549
- "To show the credit, press C-M-Mbutton.\n",
1529
+ ## <--- Thanks are also due to <wx> --->
1530
+ ## f"#{wx!r}".format(wx),
1531
+ ## f"To show the credit, press [C-M-Mbutton].", # cf. wx.InfoMessageBox(None)
1550
1532
  ))
1551
1533
  )
1552
-
1534
+
1553
1535
  def toggle_window(self, win):
1554
1536
  pane = self._mgr.GetPane(win)
1555
1537
  if pane.IsDocked():
1556
1538
  if not self.console.IsShown():
1557
- self._mgr.RestoreMaximizedPane() # いったん表示切替
1558
- self._mgr.Update() # 更新後に best_size 取得
1539
+ self._mgr.RestoreMaximizedPane() # いったん表示切替
1540
+ self._mgr.Update() # 更新後に best_size 取得
1559
1541
  if pane.IsShown():
1560
1542
  pane.best_size = win.Size
1561
1543
  self.popup_window(win, not pane.IsShown())
1562
-
1544
+
1563
1545
  @save_focus_excursion()
1564
1546
  def popup_window(self, win, show=True):
1565
1547
  """Show the notebook page and keep the focus."""
@@ -1567,58 +1549,48 @@ class ShellFrame(MiniFrame):
1567
1549
  nb = pane.window
1568
1550
  if nb is win:
1569
1551
  break
1570
- j = nb.GetPageIndex(win) # find and select page
1552
+ j = nb.GetPageIndex(win) # find and select page
1571
1553
  if j != -1:
1572
1554
  if j != nb.Selection:
1573
- nb.Selection = j # the focus moves
1555
+ nb.Selection = j # the focus moves
1574
1556
  break
1575
1557
  else:
1576
- return # no such pane.window
1558
+ return # no such pane.window
1577
1559
 
1578
1560
  ## Modify the floating position of the pane when displayed.
1579
1561
  ## Note: This is a known bug in wxWidgets 3.17 -- 3.20,
1580
- ## and will be fixed in wxPython 4.2.1.
1562
+ ## and will be fixed in wx ver 4.2.1.
1581
1563
  if wx.Display.GetFromWindow(pane.window) == -1:
1582
1564
  pane.floating_pos = wx.GetMousePosition()
1583
1565
 
1584
1566
  nb.Show(show)
1585
1567
  pane.Show(show)
1586
1568
  self._mgr.Update()
1587
-
1569
+
1588
1570
  ## --------------------------------
1589
- ## Actions for handler
1571
+ ## Actions for handler.
1590
1572
  ## --------------------------------
1591
-
1592
- def quit(self, evt=None):
1573
+
1574
+ def Quit(self, evt=None):
1593
1575
  """Stop debugger and monitor."""
1594
- ## self.inspector.unwatch()
1595
1576
  self.monitor.unwatch()
1596
- self.ginfo.unwatch()
1597
- self.linfo.unwatch()
1598
1577
  self.debugger.unwatch()
1599
- self.debugger.send_input('\n') # terminates the reader of threading pdb
1600
- shell = self.debugger.interactive_shell # reset interp locals
1578
+ self.debugger.send_input('\n') # terminates the reader of threading pdb
1579
+ shell = self.debugger.interactive_shell # reset interp locals
1601
1580
  del shell.locals
1602
1581
  del shell.globals
1603
1582
  self.indicator.Value = 1
1604
1583
  self.message("Quit")
1605
-
1606
- def _load(self, filename, lineno, editor): # for backward compatibility
1607
- """Load file in the session (internal use only)."""
1608
- if isinstance(editor, str):
1609
- editor = getattr(self, editor, None)
1610
- if editor:
1611
- return editor.load_file(filename, lineno)
1612
-
1584
+
1613
1585
  @save_focus_excursion()
1614
1586
  def load(self, filename, lineno=0, show=True):
1615
1587
  """Load file @where the object is defined.
1616
1588
 
1617
1589
  Args:
1618
- filename : target filename:str or object.
1619
- It also supports <'filename:lineno'> format.
1620
- lineno : Set mark to lineno on load.
1621
- show : Show the page.
1590
+ filename: target filename:str or object.
1591
+ It also supports <'filename:lineno'> format.
1592
+ lineno: Set mark to lineno on load.
1593
+ show: Show the page.
1622
1594
  """
1623
1595
  if not isinstance(filename, str):
1624
1596
  filename = where(filename)
@@ -1629,18 +1601,18 @@ class ShellFrame(MiniFrame):
1629
1601
  if m:
1630
1602
  filename, ln = m.groups()
1631
1603
  lineno = int(ln)
1632
- editor = self.find_editor(filename) or self.Log
1604
+ editor = next(self.get_all_editors(filename), self.Log)
1633
1605
  ret = editor.load_file(filename, lineno)
1634
- if ret:
1606
+ if ret and show:
1635
1607
  self.popup_window(editor, show)
1636
1608
  return ret
1637
-
1609
+
1638
1610
  def info(self, obj):
1639
1611
  self.rootshell.info(obj)
1640
-
1612
+
1641
1613
  def help(self, obj):
1642
1614
  self.rootshell.help(obj)
1643
-
1615
+
1644
1616
  def watch(self, obj):
1645
1617
  if isinstance(obj, wx.Object):
1646
1618
  self.monitor.watch(obj)
@@ -1649,10 +1621,12 @@ class ShellFrame(MiniFrame):
1649
1621
  self.linfo.watch(obj.__dict__)
1650
1622
  self.ginfo.watch(None)
1651
1623
  self.popup_window(self.linfo)
1652
-
1624
+ else:
1625
+ raise TypeError("primitive objects cannot be set as watch targets")
1626
+
1653
1627
  def highlight(self, obj, *args, **kwargs):
1654
1628
  self.inspector.highlight(obj, *args, **kwargs)
1655
-
1629
+
1656
1630
  def timeit(self, obj, *args, **kwargs):
1657
1631
  """Measure the duration cpu time (per one execution)."""
1658
1632
  from timeit import timeit
@@ -1670,8 +1644,8 @@ class ShellFrame(MiniFrame):
1670
1644
  except Exception as e:
1671
1645
  print(e)
1672
1646
  else:
1673
- print("- obj is neither a string nor callable")
1674
-
1647
+ print("- obj must be either a string or a callable.")
1648
+
1675
1649
  def profile(self, obj, *args, **kwargs):
1676
1650
  """Profile a single function call."""
1677
1651
  from profile import Profile
@@ -1691,9 +1665,9 @@ class ShellFrame(MiniFrame):
1691
1665
  except TypeError as e:
1692
1666
  print(e)
1693
1667
  else:
1694
- print("- obj must be callable or be a string, bytes or code object")
1695
-
1696
- ## Note: history に余計な文字列が入らないようにする
1668
+ print("- obj must be callable, or a string, bytes, or code object.")
1669
+
1670
+ ## Note: history に余計な文字列が入らないようにする.
1697
1671
  @postcall
1698
1672
  def debug(self, obj, *args, **kwargs):
1699
1673
  shell = self.debugger.interactive_shell
@@ -1720,7 +1694,7 @@ class ShellFrame(MiniFrame):
1720
1694
  style=wx.ICON_ERROR)
1721
1695
  finally:
1722
1696
  self.debugger.interactive_shell = shell
1723
-
1697
+
1724
1698
  def on_debug_begin(self, frame):
1725
1699
  """Called before set_trace."""
1726
1700
  if not self:
@@ -1734,7 +1708,9 @@ class ShellFrame(MiniFrame):
1734
1708
  self.popup_window(self.linfo)
1735
1709
  self.add_log("<-- Beginning of debugger\r\n")
1736
1710
  self.indicator.Value = 2
1737
-
1711
+ if wx.IsBusy():
1712
+ wx.EndBusyCursor()
1713
+
1738
1714
  def on_debug_next(self, frame):
1739
1715
  """Called from cmdloop."""
1740
1716
  if not self:
@@ -1755,7 +1731,7 @@ class ShellFrame(MiniFrame):
1755
1731
  command = re.sub(r"^(.*)", r" \1", command, flags=re.M)
1756
1732
  self.add_log(command)
1757
1733
  self.message("Debugger is busy now (Press [C-g] to quit).")
1758
-
1734
+
1759
1735
  def on_debug_end(self, frame):
1760
1736
  """Called after set_quit."""
1761
1737
  if not self:
@@ -1770,7 +1746,9 @@ class ShellFrame(MiniFrame):
1770
1746
  del shell.locals
1771
1747
  del shell.globals
1772
1748
  self.indicator.Value = 1
1773
-
1749
+ if wx.IsBusy():
1750
+ wx.EndBusyCursor()
1751
+
1774
1752
  def set_hookable(self, editor, traceable=True):
1775
1753
  """Bind pointer to set/unset trace."""
1776
1754
  if traceable:
@@ -1779,100 +1757,103 @@ class ShellFrame(MiniFrame):
1779
1757
  else:
1780
1758
  editor.handler.unbind('pointer_set')
1781
1759
  editor.handler.unbind('pointer_unset')
1782
-
1760
+
1783
1761
  def start_trace(self, line, editor):
1784
1762
  if not self.debugger.busy:
1785
1763
  self.debugger.unwatch()
1786
1764
  self.debugger.editor = editor
1787
1765
  self.debugger.watch((editor.buffer.filename, line+1))
1788
- self.debugger.send_input('') # clear input
1789
-
1766
+ self.debugger.send_input('') # clear input
1767
+
1790
1768
  def stop_trace(self, line, editor):
1791
1769
  if self.debugger.busy:
1792
1770
  return
1793
1771
  if self.debugger.tracing:
1794
1772
  self.debugger.editor = None
1795
1773
  self.debugger.unwatch()
1796
-
1774
+
1797
1775
  def on_trace_begin(self, frame):
1798
1776
  """Called when set-trace."""
1799
1777
  self.message("Debugger has started tracing {!r}.".format(frame))
1800
1778
  self.indicator.Value = 3
1801
-
1779
+
1802
1780
  def on_trace_hook(self, frame):
1803
1781
  """Called when a breakpoint is reached."""
1804
1782
  self.message("Debugger hooked {!r}.".format(frame))
1805
-
1783
+
1806
1784
  def on_trace_end(self, frame):
1807
1785
  """Called when unset-trace."""
1808
1786
  self.message("Debugger has stopped tracing {!r}.".format(frame))
1809
1787
  self.indicator.Value = 1
1810
-
1788
+
1811
1789
  def on_monitor_begin(self, widget):
1812
1790
  """Called when monitor watch."""
1813
1791
  self.inspector.set_colour(widget, 'blue')
1814
1792
  self.message("Started monitoring {!r}.".format(widget))
1815
-
1793
+
1816
1794
  def on_monitor_end(self, widget):
1817
1795
  """Called when monitor unwatch."""
1818
1796
  self.inspector.set_colour(widget, 'black')
1819
1797
  self.message("Stopped monitoring {!r}.".format(widget))
1820
-
1798
+
1821
1799
  def on_title_window(self, obj):
1822
1800
  """Set title to the frame."""
1823
1801
  title = obj if isinstance(obj, str) else repr(obj)
1824
1802
  self.SetTitle("Nautilus - {}".format(title))
1825
-
1826
- def on_buffer_caption(self, buf):
1827
- """Called when the buffer caption is updated."""
1828
- if buf.caption_prefix.startswith('!'):
1829
- v = wx.ActivateEvent(wx.wxEVT_ACTIVATE, True,
1830
- buf.Id, ActivationReason=0)
1831
- self.EventHandler.ProcessEvent(v) # => self.OnActivate
1832
-
1803
+
1833
1804
  def add_log(self, text, noerr=None):
1834
1805
  """Add text to the logging buffer.
1835
- If noerr <bool> is specified, add a line-marker.
1806
+ If noerr:bool is specified, add a line-marker.
1836
1807
  """
1837
1808
  buf = self.Log.default_buffer or self.Log.new_buffer()
1838
1809
  with buf.off_readonly():
1839
- buf.goto_char(buf.TextLength) # line to set an arrow marker
1810
+ buf.goto_char(buf.TextLength) # line to set an arrow marker
1840
1811
  buf.write(text)
1841
1812
  if noerr is not None:
1842
1813
  ## Set a marker on the current line.
1843
- buf.add_marker(buf.cline, 1 if noerr else 2) # 1:white 2:red-arrow
1814
+ buf.add_marker(buf.cline, 1 if noerr else 2) # 1:white 2:red-arrow
1844
1815
  return
1845
1816
 
1846
1817
  ## Logging text every step in case of crash.
1847
- ## with open(self.LOGGING_FILE, 'a', encoding='utf-8', newline='') as o:
1848
- ## o.write(text)
1849
-
1850
- def add_help(self, text):
1851
- """Add text to the help buffer."""
1818
+ # with open(self.LOGGING_FILE, 'a', encoding='utf-8', newline='') as o:
1819
+ # o.write(text)
1820
+
1821
+ def add_help(self, text, title=None):
1822
+ """Add text to the help buffer.
1823
+ If title:str is specified, create a new buffer with that title.
1824
+ """
1852
1825
  buf = self.Help.default_buffer or self.Help.new_buffer()
1826
+ if title is not None:
1827
+ self.Help.find_file(f"*{title}*")
1828
+ buf = self.Help.buffer
1853
1829
  with buf.off_readonly():
1854
- buf.SetText(text)
1830
+ buf.Text = text
1831
+ buf.EmptyUndoBuffer()
1832
+ buf.SetSavePoint()
1855
1833
  ## Overwrite text and popup the window.
1856
1834
  self.popup_window(self.Help)
1857
1835
  self.Help.swap_page(buf)
1858
-
1836
+
1859
1837
  def clone_shell(self, target):
1860
1838
  if not hasattr(target, '__dict__'):
1861
1839
  raise TypeError("primitive objects cannot be targeted")
1862
1840
 
1863
- shell = self.rootshell.__class__(self, target, name="clone",
1864
- style=wx.CLIP_CHILDREN|wx.BORDER_NONE)
1865
- self.handler('shell_new', shell)
1866
- self.Show()
1867
- self.console.AddPage(shell, typename(shell.target))
1841
+ try:
1842
+ shell = next(self.get_all_shells(target))
1843
+ except StopIteration:
1844
+ shell = self.rootshell.__class__(self, target, name="clone",
1845
+ style=wx.CLIP_CHILDREN|wx.BORDER_NONE)
1846
+ self.console.AddPage(shell, typename(shell.target))
1847
+ self.handler('shell_new', shell)
1868
1848
  self.popup_window(shell)
1849
+ self.Show()
1869
1850
  shell.SetFocus()
1870
1851
  return shell
1871
-
1852
+
1872
1853
  def delete_shell(self, shell):
1873
1854
  """Close the current shell."""
1874
1855
  if shell is self.rootshell:
1875
- ## self.message("- Don't close the root shell.")
1856
+ # self.message("- Don't close the root shell.")
1876
1857
  return
1877
1858
  if self.debugger.busy and shell is self.debugger.interactive_shell:
1878
1859
  wx.MessageBox("The debugger is running.\n\n"
@@ -1880,63 +1861,85 @@ class ShellFrame(MiniFrame):
1880
1861
  return
1881
1862
  j = self.console.GetPageIndex(shell)
1882
1863
  if j != -1:
1883
- self.console.DeletePage(j) # Destroy the window
1884
-
1864
+ self.console.DeletePage(j) # Destroy the window
1865
+
1885
1866
  ## --------------------------------
1886
- ## Attributes for notebook pages
1867
+ ## Attributes for notebook pages.
1887
1868
  ## --------------------------------
1888
-
1869
+
1889
1870
  def get_all_pages(self, type=None):
1890
1871
  """Yields all pages of the specified type in the notebooks."""
1891
1872
  yield from self.console.get_pages(type)
1892
1873
  yield from self.ghost.get_pages(type)
1893
-
1894
- @property
1895
- def all_shells(self):
1896
- """Yields all books in the notebooks."""
1897
- return self.console.get_pages(type(self.rootshell))
1898
-
1874
+
1875
+ def get_all_shells(self, target=None):
1876
+ """Yields all shells with specified target.
1877
+
1878
+ If the shell is found, it switches to the corresponding page.
1879
+ If `target` is not provided, it yields all shells in the notebooks.
1880
+ """
1881
+ if target is None:
1882
+ yield from self.console.get_pages(type(self.rootshell))
1883
+ else:
1884
+ for shell in self.console.get_pages(type(self.rootshell)):
1885
+ if shell.target is target:
1886
+ self.console.swap_page(shell)
1887
+ yield shell
1888
+
1889
+ def get_all_editors(self, fn=None):
1890
+ """Yields all editors with specified fn:filename or code.
1891
+
1892
+ If the editor is found, it switches to the corresponding page.
1893
+ If `fn` is not provided, it yields all editors in the notebooks.
1894
+ """
1895
+ if fn is None:
1896
+ yield from self.ghost.get_pages(type(self.Log))
1897
+ else:
1898
+ for book in self.ghost.get_pages(type(self.Log)):
1899
+ buf = book.find_buffer(fn)
1900
+ if buf:
1901
+ book.swap_page(buf)
1902
+ yield book
1903
+
1899
1904
  @property
1900
1905
  def current_shell(self):
1901
1906
  """Currently selected shell or rootshell."""
1902
1907
  return self.console.CurrentPage
1903
-
1904
- @property
1905
- def all_editors(self):
1906
- """Yields all editors in the notebooks."""
1907
- return self.ghost.get_pages(type(self.Log))
1908
-
1908
+
1909
1909
  @property
1910
1910
  def current_editor(self):
1911
1911
  """Currently selected editor or scratch."""
1912
1912
  editor = self.ghost.CurrentPage
1913
1913
  if isinstance(editor, type(self.Log)):
1914
1914
  return editor
1915
- return next((x for x in self.all_editors if x.IsShown()), self.Scratch)
1916
-
1917
- def find_editor(self, fn):
1918
- """Find an editor with the specified fn:filename or code.
1919
- If found, switch to the buffer page.
1915
+ return next((book for book in self.get_all_editors() if book.IsShown()), self.Scratch)
1916
+
1917
+ def create_editor(self, bookname):
1918
+ """Create a new editor (internal use only)..
1919
+ If such an editor already exists, no new editor is created.
1920
1920
  """
1921
- return next(self.find_editors(fn), None)
1922
-
1923
- def find_editors(self, fn):
1924
- """Yields all editors with the specified fn:filename or code."""
1925
- def _f(book):
1926
- buf = book.find_buffer(fn)
1927
- if buf:
1928
- book.swap_page(buf)
1929
- return buf
1930
- return filter(_f, self.all_editors)
1931
-
1921
+ try:
1922
+ return next(book for book in self.get_all_editors() if book.Name == bookname)
1923
+ except StopIteration:
1924
+ with wx.FrozenWindow(self.ghost):
1925
+ editor = self.Log.__class__(self, bookname)
1926
+ self.ghost.AddPage(editor, bookname)
1927
+ self.ghost.move_tab(editor, 0)
1928
+ self.handler('book_new', editor)
1929
+ def _attach():
1930
+ editor.handler.append(self.Bookshelf.context)
1931
+ self.Bookshelf.build_tree(clear=0)
1932
+ wx.CallAfter(_attach)
1933
+ return editor
1934
+
1932
1935
  ## --------------------------------
1933
- ## Find text dialog
1936
+ ## Find / Replace text dialog.
1934
1937
  ## --------------------------------
1935
1938
  ## *** The following code is a modification of <wx.py.frame.Frame> ***
1936
-
1939
+
1937
1940
  __find_target = None
1938
-
1939
- def OnFindText(self, evt):
1941
+
1942
+ def on_search_dialog(self, evt, flags=0):
1940
1943
  if self.findDlg is not None:
1941
1944
  self.findDlg.SetFocus()
1942
1945
  return
@@ -1945,32 +1948,54 @@ class ShellFrame(MiniFrame):
1945
1948
  if not isinstance(wnd, stc.StyledTextCtrl):
1946
1949
  return
1947
1950
  self.__find_target = wnd
1948
- self.findData.FindString = wnd.topic_at_caret
1949
- self.findDlg = wx.FindReplaceDialog(wnd, self.findData, "Find",
1950
- style=wx.FR_NOWHOLEWORD|wx.FR_NOUPDOWN)
1951
+ topic = wnd.topic_at_caret
1952
+ if topic:
1953
+ self.findData.FindString = topic
1954
+ self.findData.Flags |= wx.FR_DOWN
1955
+ self.findDlg = wx.FindReplaceDialog(wnd, self.findData, "Find", flags)
1951
1956
  self.findDlg.Show()
1952
-
1953
- def OnFindNext(self, evt, backward=False): #<wx._core.FindDialogEvent>
1954
- data = self.findData
1955
- down_p = data.Flags & wx.FR_DOWN
1956
- if (backward and down_p) or (not backward and not down_p):
1957
- data.Flags ^= wx.FR_DOWN # toggle up/down flag
1958
-
1959
- wnd = wx.Window.FindFocus()
1960
- if not isinstance(wnd, stc.StyledTextCtrl):
1961
- wnd = self.__find_target
1962
- if not wnd:
1963
- return
1964
- wnd.DoFindNext(data, self.findDlg or wnd)
1965
- if self.findDlg:
1966
- self.OnFindClose(None)
1967
- wnd.EnsureVisible(wnd.cline)
1968
- wnd.EnsureLineMoreOnScreen(wnd.cline)
1969
-
1970
- def OnFindPrev(self, evt):
1957
+
1958
+ def on_replace_dialog(self, evt):
1959
+ self.on_search_dialog(evt, flags=wx.FR_REPLACEDIALOG)
1960
+
1961
+ def repeat_forward_search(self, evt):
1962
+ self.OnFindNext(evt, backward=False)
1963
+
1964
+ def repeat_backward_search(self, evt):
1971
1965
  self.OnFindNext(evt, backward=True)
1972
-
1973
- def OnFindClose(self, evt): #<wx._core.FindDialogEvent>
1966
+
1967
+ def OnFindNext(self, evt, backward=None): #<wx._core.FindDialogEvent>
1968
+ if not self.findData.FindString:
1969
+ self.message("No last search.")
1970
+ return
1971
+ if isinstance(evt, wx.FindDialogEvent):
1972
+ wnd = self.findDlg.Parent
1973
+ else:
1974
+ wnd = evt.EventObject
1975
+ if not isinstance(wnd, stc.StyledTextCtrl):
1976
+ wnd = self.__find_target
1977
+ if backward:
1978
+ self.findData.Flags &= ~wx.FR_DOWN
1979
+ else:
1980
+ self.findData.Flags |= wx.FR_DOWN
1981
+
1982
+ if evt.EventType == wx.EVT_FIND_REPLACE_ALL.typeId: # replace-all
1983
+ n = wnd.DoReplaceAll(self.findData)
1984
+ self.message(f"Replaced {n} strings.")
1985
+ if self.findDlg:
1986
+ self.OnFindClose(None)
1987
+ return
1988
+ elif evt.EventType == wx.EVT_FIND_REPLACE.typeId: # replace
1989
+ loc = wnd.DoReplaceNext(self.findData)
1990
+ else:
1991
+ loc = wnd.DoFindNext(self.findData)
1992
+ if self.findDlg:
1993
+ if not (self.findDlg.WindowStyle & wx.FR_REPLACEDIALOG): # for search-dialog
1994
+ self.OnFindClose(None)
1995
+ if loc < 0:
1996
+ self.message("Unable to find the search text.")
1997
+
1998
+ def OnFindClose(self, evt): #<wx._core.FindDialogEvent>
1974
1999
  self.findDlg.Destroy()
1975
2000
  self.findDlg = None
1976
2001
 
@@ -1982,7 +2007,7 @@ def filling(obj=None, **kwargs):
1982
2007
  rootLabel=typename(obj),
1983
2008
  pos=wx.GetMousePosition(),
1984
2009
  **kwargs)
1985
- frame.filling.text.WrapMode = 0 # no wrap
1986
- frame.filling.text.Zoom = -1 # zoom level of size of fonts
2010
+ frame.filling.text.WrapMode = 0 # no wrap
2011
+ frame.filling.text.Zoom = -1 # zoom level of size of fonts
1987
2012
  frame.Show()
1988
2013
  return frame