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