mwxlib 1.0.0__py3-none-any.whl → 1.8.0__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.8.0"
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,56 +239,60 @@ 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'
247
246
 
248
247
  assert state is not None, "Don't make keymap for None:state"
249
248
 
250
- self.handler.update({ # DNA<KeyCtrlInterfaceMixin>
249
+ self.handler.update({ # DNA<KeyCtrlInterfaceMixin>
251
250
  state : {
252
- event : [ keymap, self.pre_command_hook ],
251
+ event : [keymap, self.pre_command_hook],
253
252
  },
254
253
  keymap : {
255
- 'quit' : [ state, ],
256
- '* pressed' : [ state, self.post_command_hook ],
257
- '*alt pressed' : [ keymap, _Pass ],
258
- '*ctrl pressed' : [ keymap, _Pass ],
259
- '*shift pressed' : [ keymap, _Pass ],
260
- '*[LR]win pressed' : [ keymap, _Pass ],
254
+ 'quit' : [state, ],
255
+ '* pressed' : [state, self.post_command_hook],
256
+ '*alt pressed' : [keymap, _Pass],
257
+ '*ctrl pressed' : [keymap, _Pass],
258
+ '*shift pressed' : [keymap, _Pass],
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,12 +333,12 @@ 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 = ''
343
340
  self.__isDragging = False
344
- self.__handler = FSM({ # DNA<CtrlInterface>
341
+ self.__handler = FSM({ # DNA<CtrlInterface>
345
342
  None : {
346
343
  },
347
344
  0 : {
@@ -353,15 +350,17 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
353
350
  _N = self._normal_handler
354
351
 
355
352
  def activate(evt):
356
- self.handler('focus_set', evt)
353
+ if self:
354
+ self.handler('focus_set', evt)
357
355
  evt.Skip()
358
356
  self.Bind(wx.EVT_SET_FOCUS, activate)
359
357
 
360
358
  def inactivate(evt):
361
- self.__key = ''
362
- self.__button = ''
363
- self.__isDragging = False
364
- self.handler('focus_kill', evt)
359
+ if self:
360
+ self.__key = ''
361
+ self.__button = ''
362
+ self.__isDragging = False
363
+ self.handler('focus_kill', evt)
365
364
  evt.Skip()
366
365
  self.Bind(wx.EVT_KILL_FOCUS, inactivate)
367
366
 
@@ -392,18 +391,18 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
392
391
 
393
392
  self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, lambda v: _N('capture_lost', v))
394
393
  self.Bind(wx.EVT_MOUSE_CAPTURE_CHANGED, lambda v: _N('capture_changed', v))
395
-
396
- def on_hotkey_press(self, evt): #<wx._core.KeyEvent>
394
+
395
+ def on_hotkey_press(self, evt): # <wx._core.KeyEvent>
397
396
  """Called when a key is pressed."""
398
- if evt.EventObject is not self:
399
- evt.Skip()
400
- return
397
+ # if evt.EventObject is not self:
398
+ # evt.Skip()
399
+ # return
401
400
  key = hotkey(evt)
402
401
  self.__key = regulate_key(key + '-')
403
402
  if self.handler('{} pressed'.format(key), evt) is None:
404
403
  evt.Skip()
405
-
406
- def on_hotkey_down(self, evt): #<wx._core.KeyEvent>
404
+
405
+ def on_hotkey_down(self, evt): # <wx._core.KeyEvent>
407
406
  """Called when a key is pressed while dragging.
408
407
  Specifically called when the mouse is being captured.
409
408
  """
@@ -411,15 +410,15 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
411
410
  self.on_hotkey_press(evt)
412
411
  else:
413
412
  evt.Skip()
414
-
415
- def on_hotkey_up(self, evt): #<wx._core.KeyEvent>
413
+
414
+ def on_hotkey_up(self, evt): # <wx._core.KeyEvent>
416
415
  """Called when a key is released."""
417
416
  key = hotkey(evt)
418
417
  self.__key = ''
419
418
  if self.handler('{} released'.format(key), evt) is None:
420
419
  evt.Skip()
421
-
422
- def on_mousewheel(self, evt): #<wx._core.MouseEvent>
420
+
421
+ def on_mousewheel(self, evt): # <wx._core.MouseEvent>
423
422
  """Called on mouse wheel events.
424
423
  Trigger event: 'key+wheel[up|down|right|left] pressed'
425
424
  """
@@ -430,8 +429,8 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
430
429
  evt.key = self.__key + "wheel{}".format(p)
431
430
  if self.handler('{} pressed'.format(evt.key), evt) is None:
432
431
  evt.Skip()
433
-
434
- def on_motion(self, evt): #<wx._core.MouseEvent>
432
+
433
+ def on_motion(self, evt): # <wx._core.MouseEvent>
435
434
  """Called on mouse motion events.
436
435
  Trigger event: 'key+[LMR]drag begin/motion/end'
437
436
  """
@@ -445,8 +444,8 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
445
444
  else:
446
445
  self.handler('motion', evt)
447
446
  evt.Skip()
448
-
449
- def _mouse_handler(self, event, evt): #<wx._core.MouseEvent>
447
+
448
+ def _mouse_handler(self, event, evt): # <wx._core.MouseEvent>
450
449
  """Called on mouse button events.
451
450
  Trigger event: 'key+[LMRX]button pressed/released/dblclick'
452
451
  """
@@ -458,7 +457,7 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
458
457
  kbtn = self.__key + self.__button
459
458
  self.handler('{}drag end'.format(kbtn), evt)
460
459
 
461
- k = evt.GetButton() #{1:L, 2:M, 3:R, 4:X1, 5:X2}
460
+ k = evt.GetButton() # {1:L, 2:M, 3:R, 4:X1, 5:X2}
462
461
  if action == 'pressed' and k in (1,2,3):
463
462
  self.__button = 'LMR'[k-1]
464
463
  else:
@@ -466,22 +465,22 @@ class CtrlInterface(KeyCtrlInterfaceMixin):
466
465
  if self.handler(event, evt) is None:
467
466
  evt.Skip()
468
467
  try:
469
- self.SetFocusIgnoringChildren() # let the panel accept keys
468
+ self.SetFocusIgnoringChildren() # let the panel accept keys
470
469
  except AttributeError:
471
470
  pass
472
-
473
- def _normal_handler(self, event, evt): #<wx._core.Event>
471
+
472
+ def _normal_handler(self, event, evt): # <wx._core.Event>
474
473
  if self.handler(event, evt) is None:
475
474
  evt.Skip()
476
475
 
477
476
 
478
477
  ## --------------------------------
479
- ## wx Framework and Designer
478
+ ## wx Framework and Designer.
480
479
  ## --------------------------------
481
480
 
482
481
  def ID_(id):
483
- ## Free ID - どこで使っているか検索できるように
484
- ## do not use [ID_LOWEST(4999):ID_HIGHEST(5999)]
482
+ ## Free ID - どこで使っているか検索できるように.
483
+ ## Do not use [ID_LOWEST(4999):ID_HIGHEST(5999)].
485
484
  id += wx.ID_HIGHEST
486
485
  assert not wx.ID_LOWEST <= id <= wx.ID_HIGHEST
487
486
  return id
@@ -500,7 +499,7 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
500
499
  )
501
500
 
502
501
  Args:
503
- items : wx objects (with some packing parameters)
502
+ items: wx objects (with some packing parameters)
504
503
 
505
504
  - (obj, 1) -> sized with ratio 1 (parallel to `orient`)
506
505
  - (obj, 1, wx.EXPAND) -> expanded with ratio 1 (perpendicular to `orient`)
@@ -509,9 +508,9 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
509
508
  - (-1,-1) -> padding space
510
509
  - None -> phantom
511
510
 
512
- orient : HORIZONTAL or VERTICAL
513
- label : StaticBox label
514
- style : Sizer option (proportion, flag, border)
511
+ orient: HORIZONTAL or VERTICAL
512
+ label: StaticBox label
513
+ style: Sizer option (proportion, flag, border)
515
514
 
516
515
  - flag-expansion -> EXPAND, SHAPED
517
516
  - flag-border -> TOP, BOTTOM, LEFT, RIGHT, ALL
@@ -519,7 +518,7 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
519
518
  ALIGN_CENTER_VERTICAL, ALIGN_CENTER_HORIZONTAL
520
519
  """
521
520
  if style is None:
522
- style = (0, wx.EXPAND | wx.ALL, 0) # DEFALT_STYLE (prop, flag, border)
521
+ style = (0, wx.EXPAND | wx.ALL, 0) # DEFALT_STYLE (prop, flag, border)
523
522
  if label is None:
524
523
  sizer = wx.BoxSizer(orient)
525
524
  else:
@@ -529,14 +528,14 @@ def pack(self, items, orient=wx.HORIZONTAL, style=None, label=None):
529
528
  if not isinstance(item, (wx.Object, list, tuple, type(None))):
530
529
  warn(f"pack items must be a wx.Object, tuple or None, not {type(item)}")
531
530
  if item is None:
532
- item = (0, 0), 0, 0, 0 # null space
531
+ item = (0, 0), 0, 0, 0 # null space
533
532
  elif not item:
534
- item = (0, 0) # padding space
533
+ item = (0, 0) # padding space
535
534
  try:
536
535
  try:
537
536
  sizer.Add(item, *style)
538
537
  except TypeError:
539
- sizer.Add(*item) # using item-specific style
538
+ sizer.Add(*item) # using item-specific style
540
539
  except TypeError as e:
541
540
  traceback.print_exc()
542
541
  bmp = wx.StaticBitmap(self, bitmap=wx.ArtProvider.GetBitmap(wx.ART_ERROR))
@@ -550,8 +549,8 @@ class Menu(wx.Menu):
550
549
  """Construct the menu.
551
550
 
552
551
  Args:
553
- menulist : list of MenuItem args
554
- owner : window object to bind handlers
552
+ owner: window object to bind handlers
553
+ menulist: list of MenuItem args
555
554
 
556
555
  (id, text, hint, style, icon, ... Menu.Append arguments
557
556
  action, updater, highlight) ... Menu Event handlers
@@ -572,10 +571,10 @@ class Menu(wx.Menu):
572
571
  continue
573
572
  id = item[0]
574
573
  handlers = [x for x in item if callable(x)]
575
- icons = [x for x in item if isinstance(x, wx.Bitmap)]
574
+ icons = [x for x in item if isinstance(x, wx.Bitmap)]
576
575
  argv = [x for x in item if x not in handlers and x not in icons]
577
576
  if isinstance(id, int):
578
- menu_item = wx.MenuItem(self, *argv) # <- menu_item.Id
577
+ menu_item = wx.MenuItem(self, *argv) # <- menu_item.Id
579
578
  if icons:
580
579
  menu_item.SetBitmaps(*icons)
581
580
  self.Append(menu_item)
@@ -586,16 +585,16 @@ class Menu(wx.Menu):
586
585
  except IndexError:
587
586
  pass
588
587
  else:
589
- subitems = list(argv.pop()) # extract the last element as submenu
588
+ subitems = list(argv.pop()) # extract the last element as submenu
590
589
  submenu = Menu(owner, subitems)
591
590
  submenu_item = wx.MenuItem(self, wx.ID_ANY, *argv)
592
591
  submenu_item.SetSubMenu(submenu)
593
592
  if icons:
594
593
  submenu_item.SetBitmaps(*icons)
595
594
  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
-
595
+ self.Enable(submenu_item.Id, len(subitems)) # Disable an empty menu.
596
+ submenu.Id = submenu_item.Id # <- ID_ANY (dummy to check empty sbumenu)
597
+
599
598
  def _unbind(self):
600
599
  for item in self.MenuItems:
601
600
  if item.Id != wx.ID_SEPARATOR:
@@ -604,17 +603,16 @@ class Menu(wx.Menu):
604
603
  self.owner.Unbind(wx.EVT_MENU_HIGHLIGHT, item)
605
604
  if item.SubMenu:
606
605
  item.SubMenu._unbind()
607
-
606
+
608
607
  def Destroy(self):
609
- try:
608
+ if self.owner and not self.owner.IsBeingDeleted():
610
609
  self._unbind()
611
- finally:
612
- return wx.Menu.Destroy(self)
613
-
610
+ return wx.Menu.Destroy(self)
611
+
614
612
  @staticmethod
615
- def Popup(parent, menulist, *args, **kwargs):
616
- menu = Menu(parent, menulist)
617
- parent.PopupMenu(menu, *args, **kwargs)
613
+ def Popup(owner, menulist, *args, **kwargs):
614
+ menu = Menu(owner, menulist)
615
+ owner.PopupMenu(menu, *args, **kwargs)
618
616
  menu.Destroy()
619
617
 
620
618
 
@@ -633,7 +631,7 @@ class MenuBar(wx.MenuBar, TreeList):
633
631
  def __init__(self, *args, **kwargs):
634
632
  wx.MenuBar.__init__(self, *args, **kwargs)
635
633
  TreeList.__init__(self)
636
-
634
+
637
635
  def getmenu(self, key, root=None):
638
636
  if '/' in key:
639
637
  a, b = key.split('/', 1)
@@ -642,7 +640,7 @@ class MenuBar(wx.MenuBar, TreeList):
642
640
  if root is None:
643
641
  return next((menu for menu, label in self.Menus if menu.Title == key), None)
644
642
  return next((item.SubMenu for item in root.MenuItems if item.ItemLabel == key), None)
645
-
643
+
646
644
  def update(self, key):
647
645
  """Update items of the menu that has specified key:root/branch.
648
646
  Call when the menulist is changed.
@@ -657,19 +655,19 @@ class MenuBar(wx.MenuBar, TreeList):
657
655
  return
658
656
 
659
657
  menu._unbind()
660
- for item in menu.MenuItems: # delete all items
658
+ for item in menu.MenuItems: # delete all items
661
659
  menu.Delete(item)
662
660
 
663
- menu2 = Menu(self.Parent, self[key]) # new menu2 to swap menu
661
+ menu2 = Menu(self.Parent, self[key]) # new menu2 to swap menu
664
662
  for item in menu2.MenuItems:
665
- menu.Append(menu2.Remove(item)) # 重複しないようにいったん切り離して追加する
663
+ menu.Append(menu2.Remove(item)) # 重複しないようにいったん切り離して追加する
666
664
 
667
665
  if hasattr(menu, 'Id'):
668
- self.Enable(menu.Id, menu.MenuItemCount > 0) # Disable empty submenu.
666
+ self.Enable(menu.Id, menu.MenuItemCount > 0) # Disable empty submenu.
669
667
 
670
668
  for j, (key, values) in enumerate(self):
671
- self.EnableTop(j, bool(values)) # Disable empty main menu.
672
-
669
+ self.EnableTop(j, bool(values)) # Disable empty main menu.
670
+
673
671
  def reset(self):
674
672
  """Recreates the menubar if the Parent was attached.
675
673
  Call when the menulist is changed.
@@ -678,41 +676,41 @@ class MenuBar(wx.MenuBar, TreeList):
678
676
  warn(f"No parents bound to {self}.")
679
677
  return
680
678
 
681
- for j in range(self.GetMenuCount()): # remove and del all top-level menu
679
+ for j in range(self.GetMenuCount()): # remove and del all top-level menu
682
680
  menu = self.Remove(0)
683
681
  menu.Destroy()
684
682
 
685
683
  for j, (key, values) in enumerate(self):
686
684
  menu = Menu(self.Parent, values)
687
685
  self.Append(menu, key)
688
- self.EnableTop(j, bool(values)) # Disable empty main menu.
686
+ self.EnableTop(j, bool(values)) # Disable empty main menu.
689
687
 
690
688
 
691
689
  class StatusBar(wx.StatusBar):
692
690
  """Construct the statusbar with read/write interfaces.
693
691
 
694
692
  Attributes:
695
- field : list of field widths
696
- pane : index of status text field
693
+ field: list of field widths
694
+ pane: index of status text field
697
695
  """
698
696
  def __init__(self, *args, **kwargs):
699
697
  wx.StatusBar.__init__(self, *args, **kwargs)
700
-
698
+
701
699
  def __call__(self, *args, **kwargs):
702
700
  text = ' '.join(str(v) for v in args)
703
701
  if self:
704
702
  return self.write(text, **kwargs)
705
-
703
+
706
704
  def resize(self, field):
707
705
  self.SetFieldsCount(len(field))
708
- self.SetStatusWidths(list(field)) # oldver requires list type
709
-
706
+ self.SetStatusWidths(list(field)) # oldver requires list type
707
+
710
708
  def write(self, text, pane=0):
711
709
  if text and text[0] == '\b':
712
710
  text = self.read(pane) + text[1:]
713
711
  self.SetStatusText(text, pane % self.GetFieldsCount())
714
712
  return text
715
-
713
+
716
714
  def read(self, pane=0):
717
715
  return self.GetStatusText(pane % self.GetFieldsCount())
718
716
 
@@ -721,19 +719,14 @@ class Frame(wx.Frame, KeyCtrlInterfaceMixin):
721
719
  """Frame extension class.
722
720
 
723
721
  Attributes:
724
- menubar : MenuBar
725
- statusbar : StatusBar
726
- shellframe : mini-frame of the shell
722
+ menubar: MenuBar
723
+ statusbar: StatusBar
724
+ shellframe: mini-frame of the shell
727
725
  """
728
726
  handler = property(lambda self: self.__handler)
729
-
727
+
730
728
  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
-
729
+
737
730
  def __init__(self, *args, **kwargs):
738
731
  wx.Frame.__init__(self, *args, **kwargs)
739
732
 
@@ -769,14 +762,14 @@ class Frame(wx.Frame, KeyCtrlInterfaceMixin):
769
762
  ## AcceleratorTable mimic
770
763
  def hook_char(evt):
771
764
  """Called when key down."""
772
- if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
765
+ if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
773
766
  evt.Skip()
774
767
  else:
775
768
  if self.handler('{} pressed'.format(hotkey(evt)), evt) is None:
776
769
  evt.Skip()
777
770
  self.Bind(wx.EVT_CHAR_HOOK, hook_char)
778
771
 
779
- self.__handler = FSM({ # DNA<Frame>
772
+ self.__handler = FSM({ # DNA<Frame>
780
773
  None : {
781
774
  },
782
775
  0 : {
@@ -784,39 +777,32 @@ class Frame(wx.Frame, KeyCtrlInterfaceMixin):
784
777
  },
785
778
  )
786
779
  self.make_keymap('C-x')
787
-
780
+
788
781
  def About(self):
789
782
  wx.MessageBox(__import__("__main__").__doc__ or "no information",
790
783
  "About this software")
791
-
784
+
792
785
  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)
786
+ self.timer.Stop()
787
+ self.shellframe.Destroy() # shellframe is not my child
788
+ return wx.Frame.Destroy(self)
798
789
 
799
790
 
800
791
  class MiniFrame(wx.MiniFrame, KeyCtrlInterfaceMixin):
801
792
  """MiniFrame extension class.
802
793
 
803
794
  Attributes:
804
- menubar : MenuBar
805
- statusbar : StatusBar (not shown by default)
795
+ menubar: MenuBar
796
+ statusbar: StatusBar (not shown by default)
806
797
  """
807
798
  handler = property(lambda self: self.__handler)
808
-
799
+
809
800
  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
-
801
+
816
802
  def __init__(self, *args, **kwargs):
817
803
  wx.MiniFrame.__init__(self, *args, **kwargs)
818
804
 
819
- ## To disable, self.SetMenuBar(None)
805
+ ## To disable, call self.SetMenuBar(None).
820
806
  self.menubar = MenuBar()
821
807
  self.SetMenuBar(self.menubar)
822
808
 
@@ -824,20 +810,20 @@ class MiniFrame(wx.MiniFrame, KeyCtrlInterfaceMixin):
824
810
  self.statusbar.Show(0)
825
811
  self.SetStatusBar(self.statusbar)
826
812
 
827
- ## AcceleratorTable mimic
813
+ ## AcceleratorTable mimic.
828
814
  def hook_char(evt):
829
815
  """Called when key down."""
830
- if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
816
+ if isinstance(evt.EventObject, wx.TextEntry): # prior to handler
831
817
  evt.Skip()
832
818
  else:
833
819
  if self.handler('{} pressed'.format(hotkey(evt)), evt) is None:
834
820
  evt.Skip()
835
821
  self.Bind(wx.EVT_CHAR_HOOK, hook_char)
836
822
 
837
- ## To default close >>> self.Unbind(wx.EVT_CLOSE)
823
+ ## To default close >>> self.Unbind(wx.EVT_CLOSE).
838
824
  self.Bind(wx.EVT_CLOSE, lambda v: self.Show(0))
839
825
 
840
- self.__handler = FSM({ # DNA<MiniFrame>
826
+ self.__handler = FSM({ # DNA<MiniFrame>
841
827
  None : {
842
828
  },
843
829
  0 : {
@@ -845,9 +831,6 @@ class MiniFrame(wx.MiniFrame, KeyCtrlInterfaceMixin):
845
831
  },
846
832
  )
847
833
  self.make_keymap('C-x')
848
-
849
- def Destroy(self):
850
- return wx.MiniFrame.Destroy(self)
851
834
 
852
835
 
853
836
  class AuiNotebook(aui.AuiNotebook):
@@ -866,52 +849,48 @@ class AuiNotebook(aui.AuiNotebook):
866
849
  self.Name = name
867
850
 
868
851
  def tab_menu(evt):
869
- tabs = evt.EventObject #<AuiTabCtrl>
870
- page = tabs.Pages[evt.Selection] # GetPage for split notebook.
852
+ tabs = evt.EventObject # <AuiTabCtrl>
853
+ page = tabs.Pages[evt.Selection] # GetPage for split notebook.
871
854
  try:
872
855
  Menu.Popup(self, page.window.menu)
873
856
  except AttributeError:
874
857
  pass
875
858
  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
-
859
+
882
860
  @property
883
- def all_tabs(self):
884
- """Returns all AuiTabCtrl objects."""
861
+ def _all_tabs(self):
862
+ """Return all AuiTabCtrl objects (internal use only)."""
885
863
  return [x for x in self.Children if isinstance(x, aui.AuiTabCtrl)]
886
-
864
+
887
865
  @property
888
- def all_panes(self):
889
- """Returns all AuiPaneInfo excluding `dummy` one."""
866
+ def _all_panes(self):
867
+ """Return all AuiPaneInfo excluding `dummy` one (internal use only)."""
890
868
  return list(self._mgr.AllPanes)[1:]
891
-
869
+
892
870
  def get_pages(self, type=None):
893
871
  """Yields pages of the specified window type."""
894
- for win in self.all_pages:
872
+ for i in range(self.PageCount):
873
+ win = self.GetPage(i)
895
874
  if type is None or isinstance(win, type):
896
875
  yield win
897
-
876
+
898
877
  def swap_page(self, win):
899
878
  """Replace the page with the specified page w/o focusing."""
900
879
  j = self.GetPageIndex(win)
901
880
  if j != -1:
902
- wnd = wx.Window.FindFocus() # original focus
881
+ wnd = wx.Window.FindFocus() # original focus
903
882
  org = self.CurrentPage
904
883
  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
884
+ self.Selection = j # the focus moves if shown
885
+ self.CurrentPage.SetFocus() # reset focus
886
+ if wnd and wnd is not org: # restore focus other window
908
887
  wnd.SetFocus()
909
-
888
+
910
889
  def get_caption(self, win):
911
890
  """Get caption of tab/page for specifiend window."""
912
891
  tab, page = self.find_tab(win)
913
892
  return page.caption
914
-
893
+
915
894
  def set_caption(self, win, caption):
916
895
  """Set caption of tab/page for specifiend window.
917
896
  Returns True if the caption has changed.
@@ -921,41 +900,43 @@ class AuiNotebook(aui.AuiNotebook):
921
900
  page.caption = caption
922
901
  tab.Refresh()
923
902
  return True
924
-
903
+
925
904
  def find_tab(self, win):
926
- """Returns AuiTabCtrl and AuiNotebookPage for the window.
905
+ """Return AuiTabCtrl and AuiNotebookPage for the window.
927
906
 
928
907
  cf. aui.AuiNotebook.FindTab -> bool, tab, idx
929
908
  Note:
930
909
  Argument `win` can also be page.window.Name (not page.caption).
931
910
  """
932
- for tabs in self.all_tabs: #<aui.AuiTabCtrl>
933
- for page in tabs.Pages: #<aui.AuiNotebookPage>
911
+ for tab in self._all_tabs: # <aui.AuiTabCtrl>
912
+ for page in tab.Pages: # <aui.AuiNotebookPage>
934
913
  ## if page.window is win or page.caption == win:
935
914
  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."""
915
+ return tab, page
916
+
917
+ def move_tab(self, win, tab):
918
+ """Move the window page to the specified tab."""
919
+ if isinstance(tab, int):
920
+ tab = self._all_tabs[tab]
940
921
  try:
941
922
  tc1, nb1 = self.find_tab(win)
942
923
  win = nb1.window
943
- except Exception: # object not found
924
+ except Exception: # object not found
944
925
  return
945
- page = wx.aui.AuiNotebookPage(nb1) # copy-ctor
926
+ page = wx.aui.AuiNotebookPage(nb1) # copy-ctor
946
927
  tc1.RemovePage(win) # Accessing nb1 will crash at this point.
947
- tabs.AddPage(win, page) # Add a page with the copied info.
928
+ tab.AddPage(win, page) # Add a page with the copied info.
948
929
  if tc1.PageCount == 0:
949
930
  ## Delete an empty tab and the corresponding pane.
950
- j = self.all_tabs.index(tc1)
951
- pane = self.all_panes[j]
931
+ j = self._all_tabs.index(tc1)
932
+ pane = self._all_panes[j]
952
933
  tc1.Destroy()
953
934
  self._mgr.DetachPane(pane.window)
954
935
  self._mgr.Update()
955
-
936
+
956
937
  ## Methods to save / load the perspectives.
957
938
  ## *** Inspired by wx.lib.agw.aui.AuiNotebook ***
958
-
939
+
959
940
  def savePerspective(self):
960
941
  """Saves the entire user interface layout into an encoded string,
961
942
  which can then be stored by the application.
@@ -964,17 +945,16 @@ class AuiNotebook(aui.AuiNotebook):
964
945
  Perspectives are saved according to page.window.Name.
965
946
  User should give it (not page.caption) a unique name.
966
947
  """
967
- for j, pane in enumerate(self.all_panes):
948
+ for j, pane in enumerate(self._all_panes):
968
949
  pane.name = f"pane{j+1}"
969
950
  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
951
+ for j, tabs in enumerate(self._all_tabs):
952
+ k = next(k for k, page in enumerate(tabs.Pages) if page.window.Shown)
973
953
  ## names = [page.caption for page in tabs.Pages]
974
954
  names = [page.window.Name for page in tabs.Pages]
975
955
  spec += f"pane{j+1}={names};{k}|"
976
956
  return spec + '@' + self._mgr.SavePerspective()
977
-
957
+
978
958
  def loadPerspective(self, spec):
979
959
  """Loads a saved perspective.
980
960
 
@@ -986,34 +966,31 @@ class AuiNotebook(aui.AuiNotebook):
986
966
  tabinfo = re.findall(r"pane\w+?=(.*?);(.*?)\|", tabs)
987
967
  try:
988
968
  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)
969
+ ## Collapse all tabctrls to main tabctrl.
970
+ for win in self.get_pages():
971
+ self.move_tab(win, 0)
993
972
 
994
- ## Create a new tab using Split method.
973
+ ## Create a new tabctrl using Split method.
995
974
  ## Note: The normal way of creating panes with `_mgr` crashes.
996
-
997
- all_names = [win.Name for win in self.all_pages]
975
+ all_names = [win.Name for win in self.get_pages()]
998
976
  for names, k in tabinfo[1:]:
999
977
  names, k = eval(names), int(k)
1000
- i = all_names.index(names[0]) # Assuming 0:tab is included.
978
+ i = all_names.index(names[0]) # Assuming 0:tab is included.
1001
979
  self.Split(i, wx.LEFT)
1002
- newtab = self.all_tabs[-1]
1003
980
  for name in names[1:]:
1004
- self.move_tab(name, newtab)
1005
- self.Selection = all_names.index(names[k]) # new tabs active window
981
+ self.move_tab(name, -1)
982
+ self.Selection = all_names.index(names[k]) # new tabctrl active window
1006
983
  else:
1007
984
  names, k = tabinfo[0]
1008
985
  names, k = eval(names), int(k)
1009
- self.Selection = all_names.index(names[k]) # main tabs active window
986
+ self.Selection = all_names.index(names[k]) # main tabctrl active window
1010
987
 
1011
- for j, pane in enumerate(self.all_panes):
988
+ for j, pane in enumerate(self._all_panes):
1012
989
  pane.name = f"pane{j+1}"
1013
990
  self._mgr.LoadPerspective(frames)
1014
991
  self._mgr.Update()
1015
992
  except Exception as e:
1016
- print("- Failed to load perspective.", e)
993
+ print("- Failed to load perspective;", e)
1017
994
  finally:
1018
995
  self.Parent.Thaw()
1019
996
 
@@ -1027,31 +1004,33 @@ class FileDropLoader(wx.DropTarget):
1027
1004
  self.target = target
1028
1005
  self.textdo = wx.TextDataObject()
1029
1006
  self.filedo = wx.FileDataObject()
1030
- self.DataObject = wx.DataObjectComposite()
1031
- self.DataObject.Add(self.textdo)
1032
- self.DataObject.Add(self.filedo, True)
1033
-
1007
+ self.do = wx.DataObjectComposite()
1008
+ self.do.Add(self.textdo)
1009
+ self.do.Add(self.filedo)
1010
+ self.SetDataObject(self.do)
1011
+
1012
+ def OnDragOver(self, x, y, result):
1013
+ index, flags = self.target.HitTest((x, y))
1014
+ if index != -1:
1015
+ self.target.Selection = index
1016
+ result = wx.DragCopy
1017
+ else:
1018
+ result = wx.DragNone
1019
+ return result
1020
+
1034
1021
  def OnData(self, x, y, result):
1035
- editor = self.target.current_editor
1022
+ editor = self.target.Parent.current_editor
1036
1023
  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 = ''
1024
+ if self.textdo.Text:
1025
+ fn = self.textdo.Text.strip()
1026
+ res = editor.parent.handler("text_dropped", fn)
1027
+ if res is None or not any(res):
1028
+ editor.load_file(fn)
1029
+ result = wx.DragCopy
1030
+ self.textdo.SetText("")
1050
1031
  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.")
1032
+ for fn in self.filedo.Filenames:
1033
+ editor.load_file(fn)
1055
1034
  self.filedo.SetData(wx.DF_FILENAME, None)
1056
1035
  return result
1057
1036
 
@@ -1060,26 +1039,28 @@ class ShellFrame(MiniFrame):
1060
1039
  """MiniFrame of the Shell.
1061
1040
 
1062
1041
  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
1042
+ target: target object of the rootshell.
1043
+ If None, it will be __main__.
1044
+ session: file name of the session. Defaults to None.
1045
+ If None, no session will be created or saved.
1046
+ If `''`, the default session (.debrc) will be loaded.
1047
+ standalone: flag for the shell standalone.
1048
+ If True, EVT_CLOSE will close the window.
1049
+ Otherwise the window will be only hidden.
1050
+ **kwargs: Nautilus arguments
1071
1051
 
1072
1052
  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
1053
+ console: Notebook of shells
1054
+ ghost: Notebook of editors/buffers
1055
+ watcher: Notebook of global/locals watcher
1056
+ Scratch: Book of scratch (tooltip)
1057
+ Help: Book of help
1058
+ Log: Book of logging
1059
+ monitor: wxmon.EventMonitor object
1060
+ inspector: wxwit.Inspector object
1061
+ debugger: wxpdb.Debugger object
1062
+ ginfo: globals list
1063
+ linfo: locals list
1083
1064
 
1084
1065
  Built-in utility::
1085
1066
 
@@ -1098,19 +1079,19 @@ class ShellFrame(MiniFrame):
1098
1079
  @highlight : Highlight the widget.
1099
1080
  @filling : Inspection using ``wx.lib.filling.Filling``.
1100
1081
  """
1101
- rootshell = property(lambda self: self.__shell) #: the root shell
1102
-
1103
- def __init__(self, parent, target=None, session=None, ensureClose=False, **kwargs):
1082
+ rootshell = property(lambda self: self.__shell) # the root shell
1083
+
1084
+ def __init__(self, parent, target=None, session=None, standalone=False, **kwargs):
1104
1085
  MiniFrame.__init__(self, parent, size=(1280,720), style=wx.DEFAULT_FRAME_STYLE)
1105
1086
 
1106
1087
  self.statusbar.resize((-1,120))
1107
1088
  self.statusbar.Show(1)
1108
1089
 
1109
- self.__standalone = bool(ensureClose)
1090
+ self.__standalone = bool(standalone)
1110
1091
 
1111
1092
  self.Init()
1112
1093
 
1113
- from .nutshell import Nautilus, EditorBook
1094
+ from .nutshell import Nautilus, EditorBook, Stylus
1114
1095
  from .bookshelf import EditorTreeCtrl
1115
1096
 
1116
1097
  self.__shell = Nautilus(self,
@@ -1125,6 +1106,28 @@ class ShellFrame(MiniFrame):
1125
1106
  self.Bookshelf = EditorTreeCtrl(self, name="Bookshelf",
1126
1107
  style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT)
1127
1108
 
1109
+ ## Set shell and editor styles.
1110
+ self.Scratch.set_attributes(Style=Stylus.py_shell_mode)
1111
+
1112
+ self.Log.set_attributes(ReadOnly=True,
1113
+ Style=Stylus.py_log_mode)
1114
+
1115
+ self.Help.set_attributes(ReadOnly=False,
1116
+ Style=Stylus.py_log_mode)
1117
+
1118
+ self.set_hookable(self.Scratch)
1119
+ self.set_hookable(self.Log)
1120
+
1121
+ @self.Scratch.define_key('C-j')
1122
+ def eval_line(evt):
1123
+ self.Scratch.buffer.eval_line()
1124
+ evt.Skip(False) # Don't skip explicitly.
1125
+
1126
+ @self.Scratch.define_key('M-j')
1127
+ def eval_buffer(evt):
1128
+ self.Scratch.buffer.exec_region()
1129
+ evt.Skip(False) # Don't skip explicitly.
1130
+
1128
1131
  from .wxpdb import Debugger
1129
1132
  from .wxwit import Inspector
1130
1133
  from .wxmon import EventMonitor
@@ -1134,8 +1137,8 @@ class ShellFrame(MiniFrame):
1134
1137
  self.debugger = Debugger(self,
1135
1138
  stdin=self.__shell.interp.stdin,
1136
1139
  stdout=self.__shell.interp.stdout,
1137
- skip=[Debugger.__module__, # Don't enter debugger
1138
- EventMonitor.__module__, # Don't enter event-hook
1140
+ skip=[Debugger.__module__, # Don't enter debugger
1141
+ EventMonitor.__module__, # Don't enter event-hook
1139
1142
  FSM.__module__,
1140
1143
  'wx.core', 'wx.lib.eventwatcher',
1141
1144
  'fnmatch', 'warnings', 'bdb', 'pdb', 'contextlib',
@@ -1149,9 +1152,7 @@ class ShellFrame(MiniFrame):
1149
1152
  self.console = AuiNotebook(self, size=(600,400), name='console')
1150
1153
  self.console.AddPage(self.__shell, "root", bitmap=Icon('core'))
1151
1154
  self.console.TabCtrlHeight = 0
1152
- ## self.console.Name = "console"
1153
1155
 
1154
- ## self.console.Bind(aui.EVT_AUINOTEBOOK_BUTTON, self.OnConsoleCloseBtn)
1155
1156
  self.console.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnConsolePageClose)
1156
1157
  self.console.Bind(aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnConsolePageChanged)
1157
1158
 
@@ -1162,7 +1163,7 @@ class ShellFrame(MiniFrame):
1162
1163
 
1163
1164
  self.ghost.AddPage(self.Bookshelf, "Bookshelf", bitmap=Icon('book'))
1164
1165
 
1165
- self.ghost.SetDropTarget(FileDropLoader(self))
1166
+ self.ghost.SetDropTarget(FileDropLoader(self.ghost))
1166
1167
 
1167
1168
  self.watcher = AuiNotebook(self, size=(600,400), name="watcher")
1168
1169
  self.watcher.AddPage(self.ginfo, "globals")
@@ -1183,7 +1184,7 @@ class ShellFrame(MiniFrame):
1183
1184
  self._mgr.AddPane(self.ghost,
1184
1185
  aui.AuiPaneInfo().Name("ghost")
1185
1186
  .Caption("Ghost in the Shell").Right()
1186
- .MaximizeButton().Show(0))
1187
+ .MaximizeButton().Show(1))
1187
1188
 
1188
1189
  self._mgr.AddPane(self.watcher,
1189
1190
  aui.AuiPaneInfo().Name("watcher")
@@ -1205,6 +1206,8 @@ class ShellFrame(MiniFrame):
1205
1206
 
1206
1207
  self.Bind(wx.EVT_FIND, self.OnFindNext)
1207
1208
  self.Bind(wx.EVT_FIND_NEXT, self.OnFindNext)
1209
+ self.Bind(wx.EVT_FIND_REPLACE, self.OnFindNext)
1210
+ self.Bind(wx.EVT_FIND_REPLACE_ALL, self.OnFindNext)
1208
1211
  self.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
1209
1212
 
1210
1213
  self.indicator = Indicator(self.statusbar, value=1)
@@ -1236,39 +1239,39 @@ class ShellFrame(MiniFrame):
1236
1239
  self.indicator.Value = 7
1237
1240
  evt.Skip()
1238
1241
 
1239
- self.handler.update({ # DNA<ShellFrame>
1242
+ self.handler.update({ # DNA<ShellFrame>
1240
1243
  None : {
1241
- 'debug_begin' : [ None, self.on_debug_begin ],
1242
- 'debug_next' : [ None, self.on_debug_next ],
1243
- 'debug_end' : [ None, self.on_debug_end ],
1244
- 'trace_begin' : [ None, self.on_trace_begin ],
1245
- 'trace_hook' : [ None, self.on_trace_hook ],
1246
- 'trace_end' : [ None, self.on_trace_end ],
1247
- 'monitor_begin' : [ None, self.on_monitor_begin ],
1248
- 'monitor_end' : [ None, self.on_monitor_end ],
1249
- 'buffer_new' : [ None, ],
1250
- 'shell_new' : [ None, ],
1251
- 'add_log' : [ None, self.add_log ],
1252
- 'add_help' : [ None, self.add_help ],
1253
- 'title_window' : [ None, self.on_title_window ],
1254
- 'buffer_caption_updated' : [ None, self.on_buffer_caption ], # => self.OnActivate
1244
+ 'debug_begin' : [None, self.on_debug_begin],
1245
+ 'debug_next' : [None, self.on_debug_next],
1246
+ 'debug_end' : [None, self.on_debug_end],
1247
+ 'trace_begin' : [None, self.on_trace_begin],
1248
+ 'trace_hook' : [None, self.on_trace_hook],
1249
+ 'trace_end' : [None, self.on_trace_end],
1250
+ 'monitor_begin' : [None, self.on_monitor_begin],
1251
+ 'monitor_end' : [None, self.on_monitor_end],
1252
+ 'shell_new' : [None, ],
1253
+ 'book_new' : [None, ],
1254
+ 'add_log' : [None, self.add_log],
1255
+ 'add_help' : [None, self.add_help],
1256
+ 'title_window' : [None, self.on_title_window],
1255
1257
  },
1256
1258
  0 : {
1257
1259
  '* pressed' : (0, fork_debugger),
1258
1260
  '* released' : (0, fork_debugger),
1259
1261
  'C-g pressed' : (0, self.Quit, fork_debugger),
1260
1262
  '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')),
1263
+ 'C-f pressed' : (0, self.on_search_dialog),
1264
+ 'C-S-f pressed' : (0, self.on_replace_dialog),
1265
+ 'f3 pressed' : (0, self.repeat_forward_search),
1266
+ 'S-f3 pressed' : (0, self.repeat_backward_search),
1267
+ 'f11 pressed' : (0, _F(self.toggle_window, win=self.ghost, alias='toggle_ghost')),
1268
+ 'S-f11 pressed' : (0, _F(self.toggle_window, win=self.watcher, alias='toggle_watcher')),
1266
1269
  'f12 pressed' : (0, _F(self.Close, alias="close")),
1267
1270
  '*f[0-9]* pressed' : (0, ),
1268
1271
  },
1269
1272
  })
1270
1273
 
1271
- ## Session files
1274
+ ## Session files.
1272
1275
  self.SESSION_FILE = get_rootpath(".debrc")
1273
1276
  self.SCRATCH_FILE = get_rootpath("scratch.py")
1274
1277
  self.LOGGING_FILE = get_rootpath("deb-logging.log")
@@ -1277,9 +1280,7 @@ class ShellFrame(MiniFrame):
1277
1280
  self.load_session(session or self.SESSION_FILE)
1278
1281
  else:
1279
1282
  self.SESSION_FILE = None
1280
-
1281
- self.postInit()
1282
-
1283
+
1283
1284
  def load_session(self, filename):
1284
1285
  """Load session from file.
1285
1286
  Buffers, pointers, and the entire layout are loaded.
@@ -1292,10 +1293,10 @@ class ShellFrame(MiniFrame):
1292
1293
  except Exception:
1293
1294
  pass
1294
1295
 
1295
- _fload(self.Scratch, self.SCRATCH_FILE) # restore scratch
1296
+ _fload(self.Scratch, self.SCRATCH_FILE) # restore scratch
1296
1297
 
1297
1298
  ## Re-open the *log* file.
1298
- self.add_log("#! Opened: <{}>\r\n".format(datetime.datetime.now()))
1299
+ self.add_log("#! Opened: <{}>\r\n".format(datetime.now()))
1299
1300
 
1300
1301
  session = os.path.abspath(filename)
1301
1302
  try:
@@ -1303,12 +1304,14 @@ class ShellFrame(MiniFrame):
1303
1304
  exec(i.read())
1304
1305
  except FileNotFoundError:
1305
1306
  pass
1307
+ except Exception as e:
1308
+ print("- Failed to load session;", e)
1306
1309
  self.SESSION_FILE = session
1307
1310
 
1308
1311
  ## Reposition the window if it is not on the desktop.
1309
1312
  if wx.Display.GetFromWindow(self) == -1:
1310
1313
  self.Position = (0, 0)
1311
-
1314
+
1312
1315
  def save_session(self):
1313
1316
  """Save session to file.
1314
1317
  Buffers, pointers, and the entire layout are saved.
@@ -1321,8 +1324,8 @@ class ShellFrame(MiniFrame):
1321
1324
  except Exception:
1322
1325
  pass
1323
1326
 
1324
- _fsave(self.Scratch, self.SCRATCH_FILE) # save scratch
1325
- _fsave(self.Log, self.LOGGING_FILE) # save log
1327
+ _fsave(self.Scratch, self.SCRATCH_FILE) # save scratch
1328
+ _fsave(self.Log, self.LOGGING_FILE) # save log
1326
1329
 
1327
1330
  if not self.SESSION_FILE:
1328
1331
  return
@@ -1332,8 +1335,10 @@ class ShellFrame(MiniFrame):
1332
1335
  o.write("self.SetSize({})\n".format(self.Size))
1333
1336
  o.write("self.SetPosition({})\n".format(self.Position))
1334
1337
 
1335
- for book in self.all_editors:
1336
- for buf in book.all_buffers:
1338
+ for book in self.get_all_editors():
1339
+ if book.Name not in ("Scratch", "Log", "Help"): # Save default editors only.
1340
+ continue
1341
+ for buf in book.get_all_buffers():
1337
1342
  if buf.mtdelta is not None:
1338
1343
  o.write("self.{}.load_file({!r}, {})\n"
1339
1344
  .format(book.Name, buf.filename, buf.markline+1))
@@ -1345,35 +1350,7 @@ class ShellFrame(MiniFrame):
1345
1350
  "self._mgr.LoadPerspective({!r})".format(self._mgr.SavePerspective()),
1346
1351
  "self._mgr.Update()\n",
1347
1352
  )))
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
-
1353
+
1377
1354
  def Init(self):
1378
1355
  """Initialize self-specific builtins.
1379
1356
  Note:
@@ -1382,7 +1359,7 @@ class ShellFrame(MiniFrame):
1382
1359
  try:
1383
1360
  builtins.dive
1384
1361
  except AttributeError:
1385
- ## Add useful built-in functions and methods
1362
+ ## Add useful built-in functions and methods.
1386
1363
  builtins.apropos = apropos
1387
1364
  builtins.typename = typename
1388
1365
  builtins.reload = reload
@@ -1401,10 +1378,10 @@ class ShellFrame(MiniFrame):
1401
1378
  builtins.profile = self.profile
1402
1379
  builtins.highlight = self.highlight
1403
1380
  builtins.filling = filling
1404
-
1381
+
1405
1382
  def Destroy(self):
1406
1383
  try:
1407
- ## Remove built-in self methods
1384
+ ## Remove built-in self methods.
1408
1385
  del builtins.info
1409
1386
  del builtins.help
1410
1387
  del builtins.load
@@ -1416,16 +1393,21 @@ class ShellFrame(MiniFrame):
1416
1393
  del builtins.highlight
1417
1394
  except AttributeError:
1418
1395
  pass
1419
- try:
1420
- self.timer.Stop()
1421
- self.save_session()
1422
- finally:
1423
- self._mgr.UnInit()
1424
- return MiniFrame.Destroy(self)
1425
-
1396
+
1397
+ self.timer.Stop()
1398
+ self.save_session()
1399
+ self._mgr.UnInit()
1400
+ return MiniFrame.Destroy(self)
1401
+
1402
+ def Close(self):
1403
+ if self.__standalone:
1404
+ MiniFrame.Close(self)
1405
+ else:
1406
+ self.Show(not self.Shown)
1407
+
1426
1408
  def OnClose(self, evt):
1427
1409
  if self.debugger.busy:
1428
- if wx.MessageBox( # Confirm debugger close.
1410
+ if wx.MessageBox( # Confirm closing the debugger.
1429
1411
  "The debugger is running.\n\n"
1430
1412
  "Enter [q]uit to exit before closing.\n"
1431
1413
  "Continue closing?",
@@ -1433,20 +1415,20 @@ class ShellFrame(MiniFrame):
1433
1415
  self.message("The close has been canceled.")
1434
1416
  evt.Veto()
1435
1417
  return
1436
- #? RuntimeError('wrapped C/C++ object ... has been deleted')
1418
+ ## RuntimeError('wrapped C/C++ object ... has been deleted')
1437
1419
  self.Quit()
1438
1420
 
1439
1421
  if self.debugger.tracing:
1440
1422
  wx.MessageBox("The debugger ends tracing.\n\n"
1441
1423
  "The trace pointer will be cleared.")
1442
- self.debugger.unwatch() # cf. [pointer_unset] stop_trace
1424
+ self.debugger.unwatch() # cf. [pointer_unset] stop_trace
1443
1425
 
1444
- for book in self.all_editors:
1445
- for buf in book.all_buffers:
1426
+ for book in self.get_all_editors():
1427
+ for buf in book.get_all_buffers():
1446
1428
  if buf.need_buffer_save:
1447
1429
  self.popup_window(book)
1448
1430
  buf.SetFocus()
1449
- if wx.MessageBox( # Confirm close.
1431
+ if wx.MessageBox( # Confirm closing the buffer.
1450
1432
  "You are closing unsaved content.\n\n"
1451
1433
  "Changes to the content will be discarded.\n"
1452
1434
  "Continue closing?",
@@ -1455,58 +1437,60 @@ class ShellFrame(MiniFrame):
1455
1437
  self.message("The close has been canceled.")
1456
1438
  evt.Veto()
1457
1439
  return
1458
- break # Don't ask any more.
1440
+ break # Don't ask any more.
1459
1441
  if self.__standalone:
1460
- evt.Skip() # Close the window
1442
+ evt.Skip() # Close the window
1461
1443
  else:
1462
- self.Show(0) # Don't destroy the window
1463
-
1444
+ self.Show(0) # Don't destroy the window
1445
+
1464
1446
  def OnActivate(self, evt):
1465
1447
  if not evt.Active:
1466
1448
  ## Reset autoload when active focus going outside.
1467
1449
  self.__autoload = True
1468
- elif evt.GetActivationReason() == evt.Reason_Mouse\
1469
- and self.__autoload:
1450
+ ## elif evt.GetActivationReason() == evt.Reason_Mouse and self.__autoload:
1451
+ elif self.__autoload:
1470
1452
  ## Check all buffers that need to be loaded.
1471
- for book in self.all_editors:
1472
- for buf in book.all_buffers:
1453
+ verbose = 1
1454
+ for book in self.get_all_editors():
1455
+ for buf in book.get_all_buffers():
1473
1456
  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)
1457
+ buf.update_caption()
1458
+ if verbose:
1459
+ with wx.MessageDialog(self, # Confirm load.
1460
+ "The file has been modified externally.\n\n"
1461
+ "The contents of the buffer will be overwritten.\n"
1462
+ "Continue loading {}/{}?".format(book.Name, buf.name),
1463
+ "Load {!r}".format(buf.name),
1464
+ style=wx.YES_NO|wx.CANCEL|wx.HELP|wx.ICON_INFORMATION) as dlg:
1465
+ dlg.SetHelpLabel("Yes to All")
1466
+ ret = dlg.ShowModal()
1467
+ if ret == wx.ID_NO:
1468
+ continue
1469
+ if ret == wx.ID_CANCEL:
1470
+ break # all
1471
+ if ret == wx.ID_HELP: # ID_YESTOALL
1472
+ verbose = 0
1473
+ book.load_file(buf.filename, buf.markline+1)
1474
+ self.__autoload = False
1483
1475
  ## Reinitialize self-specific builtins if other instances are destroyed.
1484
1476
  if evt.Active:
1485
1477
  self.Init()
1486
1478
  evt.Skip()
1487
-
1479
+
1488
1480
  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()
1481
+ for pane in self._mgr.GetAllPanes():
1482
+ ## When the window is hidden, disable docking and keep child panes floating.
1483
+ pane.Dockable(evt.IsShown() or pane.IsDocked())
1498
1484
  evt.Skip()
1499
-
1485
+
1500
1486
  def OnGhostShow(self, evt):
1501
1487
  if evt.IsShown():
1502
1488
  self.inspector.watch()
1503
- self.monitor.watch()
1504
1489
  else:
1505
1490
  self.inspector.unwatch()
1506
- self.monitor.unwatch()
1507
1491
  evt.Skip()
1508
-
1509
- def OnConsolePageChanged(self, evt): #<wx._aui.AuiNotebookEvent>
1492
+
1493
+ def OnConsolePageChanged(self, evt): # <wx._aui.AuiNotebookEvent>
1510
1494
  nb = evt.EventObject
1511
1495
  win = nb.CurrentPage
1512
1496
  if win is self.rootshell:
@@ -1515,24 +1499,12 @@ class ShellFrame(MiniFrame):
1515
1499
  nb.WindowStyle |= aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
1516
1500
  nb.TabCtrlHeight = 0 if nb.PageCount == 1 else -1
1517
1501
  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>
1502
+
1503
+ def OnConsolePageClose(self, evt): # <wx._aui.AuiNotebookEvent>
1532
1504
  nb = evt.EventObject
1533
- win = nb.all_pages[evt.Selection]
1505
+ win = list(nb.get_pages())[evt.Selection]
1534
1506
  if win is self.rootshell:
1535
- ## self.message("Don't close the root shell.")
1507
+ # self.message("Don't close the root shell.")
1536
1508
  nb.WindowStyle &= ~aui.AUI_NB_CLOSE_ON_ACTIVE_TAB
1537
1509
  evt.Veto()
1538
1510
  elif self.debugger.busy and win is self.debugger.interactive_shell:
@@ -1541,39 +1513,37 @@ class ShellFrame(MiniFrame):
1541
1513
  evt.Veto()
1542
1514
  else:
1543
1515
  evt.Skip()
1544
-
1516
+
1545
1517
  def About(self, evt=None):
1546
1518
  self.add_help(
1547
1519
  '\n\n'.join((
1548
- "#<module 'mwx' from {!r}>".format(__file__),
1549
- "Author: {!r}".format(__author__),
1550
- "Version: {!s}".format(__version__),
1520
+ f"# <module 'mwx' from {__file__!r}>",
1521
+ f"Author: {__author__!r}",
1522
+ f"Version: {__version__!s}",
1551
1523
  self.__class__.__doc__,
1552
1524
  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),
1525
+ ## <--- Thanks to <wx.py.shell> --->
1526
+ f"# {wx.py!r}",
1527
+ f"Author: {wx.py.version.__author__!r}",
1528
+ f"Version: {wx.py.version.VERSION!s}",
1558
1529
  wx.py.shell.Shell.__doc__,
1559
1530
  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",
1531
+ ## <--- Thanks are also due to <wx> --->
1532
+ # f"#{wx!r}",
1533
+ # f"To show the credit, press [C-M-Mbutton].", # cf. wx.InfoMessageBox(None)
1564
1534
  ))
1565
1535
  )
1566
-
1536
+
1567
1537
  def toggle_window(self, win):
1568
1538
  pane = self._mgr.GetPane(win)
1569
1539
  if pane.IsDocked():
1570
1540
  if not self.console.IsShown():
1571
- self._mgr.RestoreMaximizedPane() # いったん表示切替
1572
- self._mgr.Update() # 更新後に best_size 取得
1541
+ self._mgr.RestoreMaximizedPane() # いったん表示切替
1542
+ self._mgr.Update() # 更新後に best_size 取得
1573
1543
  if pane.IsShown():
1574
1544
  pane.best_size = win.Size
1575
1545
  self.popup_window(win, not pane.IsShown())
1576
-
1546
+
1577
1547
  @save_focus_excursion()
1578
1548
  def popup_window(self, win, show=True):
1579
1549
  """Show the notebook page and keep the focus."""
@@ -1581,47 +1551,48 @@ class ShellFrame(MiniFrame):
1581
1551
  nb = pane.window
1582
1552
  if nb is win:
1583
1553
  break
1584
- j = nb.GetPageIndex(win) # find and select page
1554
+ j = nb.GetPageIndex(win) # find and select page
1585
1555
  if j != -1:
1586
1556
  if j != nb.Selection:
1587
- nb.Selection = j # the focus moves
1557
+ nb.Selection = j # the focus moves
1588
1558
  break
1589
1559
  else:
1590
- return # no such pane.window
1560
+ return # no such pane.window
1591
1561
 
1592
1562
  ## Modify the floating position of the pane when displayed.
1593
1563
  ## Note: This is a known bug in wxWidgets 3.17 -- 3.20,
1594
- ## and will be fixed in wxPython 4.2.1.
1564
+ ## and will be fixed in wx ver 4.2.1.
1595
1565
  if wx.Display.GetFromWindow(pane.window) == -1:
1596
1566
  pane.floating_pos = wx.GetMousePosition()
1597
1567
 
1598
1568
  nb.Show(show)
1599
1569
  pane.Show(show)
1600
1570
  self._mgr.Update()
1601
-
1571
+
1602
1572
  ## --------------------------------
1603
- ## Actions for handler
1573
+ ## Actions for handler.
1604
1574
  ## --------------------------------
1605
-
1575
+
1606
1576
  def Quit(self, evt=None):
1607
1577
  """Stop debugger and monitor."""
1578
+ self.monitor.unwatch()
1608
1579
  self.debugger.unwatch()
1609
- self.debugger.send_input('\n') # terminates the reader of threading pdb
1610
- shell = self.debugger.interactive_shell # reset interp locals
1580
+ self.debugger.send_input('\n') # terminates the reader of threading pdb
1581
+ shell = self.debugger.interactive_shell # reset interp locals
1611
1582
  del shell.locals
1612
1583
  del shell.globals
1613
1584
  self.indicator.Value = 1
1614
1585
  self.message("Quit")
1615
-
1586
+
1616
1587
  @save_focus_excursion()
1617
1588
  def load(self, filename, lineno=0, show=True):
1618
1589
  """Load file @where the object is defined.
1619
1590
 
1620
1591
  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.
1592
+ filename: target filename:str or object.
1593
+ It also supports <'filename:lineno'> format.
1594
+ lineno: Set mark to lineno on load.
1595
+ show: Show the page.
1625
1596
  """
1626
1597
  if not isinstance(filename, str):
1627
1598
  filename = where(filename)
@@ -1632,18 +1603,18 @@ class ShellFrame(MiniFrame):
1632
1603
  if m:
1633
1604
  filename, ln = m.groups()
1634
1605
  lineno = int(ln)
1635
- editor = self.find_editor(filename) or self.Log
1606
+ editor = next(self.get_all_editors(filename), self.Log)
1636
1607
  ret = editor.load_file(filename, lineno)
1637
- if ret:
1608
+ if ret and show:
1638
1609
  self.popup_window(editor, show)
1639
1610
  return ret
1640
-
1611
+
1641
1612
  def info(self, obj):
1642
1613
  self.rootshell.info(obj)
1643
-
1614
+
1644
1615
  def help(self, obj):
1645
1616
  self.rootshell.help(obj)
1646
-
1617
+
1647
1618
  def watch(self, obj):
1648
1619
  if isinstance(obj, wx.Object):
1649
1620
  self.monitor.watch(obj)
@@ -1652,10 +1623,12 @@ class ShellFrame(MiniFrame):
1652
1623
  self.linfo.watch(obj.__dict__)
1653
1624
  self.ginfo.watch(None)
1654
1625
  self.popup_window(self.linfo)
1655
-
1626
+ else:
1627
+ raise TypeError("primitive objects cannot be set as watch targets")
1628
+
1656
1629
  def highlight(self, obj, *args, **kwargs):
1657
1630
  self.inspector.highlight(obj, *args, **kwargs)
1658
-
1631
+
1659
1632
  def timeit(self, obj, *args, **kwargs):
1660
1633
  """Measure the duration cpu time (per one execution)."""
1661
1634
  from timeit import timeit
@@ -1673,8 +1646,8 @@ class ShellFrame(MiniFrame):
1673
1646
  except Exception as e:
1674
1647
  print(e)
1675
1648
  else:
1676
- print("- obj is neither a string nor callable")
1677
-
1649
+ print("- obj must be either a string or a callable.")
1650
+
1678
1651
  def profile(self, obj, *args, **kwargs):
1679
1652
  """Profile a single function call."""
1680
1653
  from profile import Profile
@@ -1694,9 +1667,9 @@ class ShellFrame(MiniFrame):
1694
1667
  except TypeError as e:
1695
1668
  print(e)
1696
1669
  else:
1697
- print("- obj must be callable or be a string, bytes or code object")
1698
-
1699
- ## Note: history に余計な文字列が入らないようにする
1670
+ print("- obj must be callable, or a string, bytes, or code object.")
1671
+
1672
+ ## Note: history に余計な文字列が入らないようにする.
1700
1673
  @postcall
1701
1674
  def debug(self, obj, *args, **kwargs):
1702
1675
  shell = self.debugger.interactive_shell
@@ -1723,13 +1696,13 @@ class ShellFrame(MiniFrame):
1723
1696
  style=wx.ICON_ERROR)
1724
1697
  finally:
1725
1698
  self.debugger.interactive_shell = shell
1726
-
1699
+
1727
1700
  def on_debug_begin(self, frame):
1728
1701
  """Called before set_trace."""
1729
1702
  if not self:
1730
1703
  return
1731
1704
  shell = self.debugger.interactive_shell
1732
- shell.write("#<-- Enter [n]ext to continue.\n", -1)
1705
+ shell.write("# <-- Enter [n]ext to continue.\n", -1)
1733
1706
  shell.prompt()
1734
1707
  shell.SetFocus()
1735
1708
  self.Show()
@@ -1739,7 +1712,7 @@ class ShellFrame(MiniFrame):
1739
1712
  self.indicator.Value = 2
1740
1713
  if wx.IsBusy():
1741
1714
  wx.EndBusyCursor()
1742
-
1715
+
1743
1716
  def on_debug_next(self, frame):
1744
1717
  """Called from cmdloop."""
1745
1718
  if not self:
@@ -1760,13 +1733,13 @@ class ShellFrame(MiniFrame):
1760
1733
  command = re.sub(r"^(.*)", r" \1", command, flags=re.M)
1761
1734
  self.add_log(command)
1762
1735
  self.message("Debugger is busy now (Press [C-g] to quit).")
1763
-
1736
+
1764
1737
  def on_debug_end(self, frame):
1765
1738
  """Called after set_quit."""
1766
1739
  if not self:
1767
1740
  return
1768
1741
  shell = self.debugger.interactive_shell
1769
- shell.write("#--> Debugger closed successfully.\n", -1)
1742
+ shell.write("# --> Debugger closed successfully.\n", -1)
1770
1743
  shell.prompt()
1771
1744
  self.add_log("--> End of debugger\r\n")
1772
1745
  self.linfo.unwatch()
@@ -1777,7 +1750,7 @@ class ShellFrame(MiniFrame):
1777
1750
  self.indicator.Value = 1
1778
1751
  if wx.IsBusy():
1779
1752
  wx.EndBusyCursor()
1780
-
1753
+
1781
1754
  def set_hookable(self, editor, traceable=True):
1782
1755
  """Bind pointer to set/unset trace."""
1783
1756
  if traceable:
@@ -1786,100 +1759,103 @@ class ShellFrame(MiniFrame):
1786
1759
  else:
1787
1760
  editor.handler.unbind('pointer_set')
1788
1761
  editor.handler.unbind('pointer_unset')
1789
-
1762
+
1790
1763
  def start_trace(self, line, editor):
1791
1764
  if not self.debugger.busy:
1792
1765
  self.debugger.unwatch()
1793
1766
  self.debugger.editor = editor
1794
1767
  self.debugger.watch((editor.buffer.filename, line+1))
1795
- self.debugger.send_input('') # clear input
1796
-
1768
+ self.debugger.send_input('') # clear input
1769
+
1797
1770
  def stop_trace(self, line, editor):
1798
1771
  if self.debugger.busy:
1799
1772
  return
1800
1773
  if self.debugger.tracing:
1801
1774
  self.debugger.editor = None
1802
1775
  self.debugger.unwatch()
1803
-
1776
+
1804
1777
  def on_trace_begin(self, frame):
1805
1778
  """Called when set-trace."""
1806
1779
  self.message("Debugger has started tracing {!r}.".format(frame))
1807
1780
  self.indicator.Value = 3
1808
-
1781
+
1809
1782
  def on_trace_hook(self, frame):
1810
1783
  """Called when a breakpoint is reached."""
1811
1784
  self.message("Debugger hooked {!r}.".format(frame))
1812
-
1785
+
1813
1786
  def on_trace_end(self, frame):
1814
1787
  """Called when unset-trace."""
1815
1788
  self.message("Debugger has stopped tracing {!r}.".format(frame))
1816
1789
  self.indicator.Value = 1
1817
-
1790
+
1818
1791
  def on_monitor_begin(self, widget):
1819
1792
  """Called when monitor watch."""
1820
1793
  self.inspector.set_colour(widget, 'blue')
1821
1794
  self.message("Started monitoring {!r}.".format(widget))
1822
-
1795
+
1823
1796
  def on_monitor_end(self, widget):
1824
1797
  """Called when monitor unwatch."""
1825
1798
  self.inspector.set_colour(widget, 'black')
1826
1799
  self.message("Stopped monitoring {!r}.".format(widget))
1827
-
1800
+
1828
1801
  def on_title_window(self, obj):
1829
1802
  """Set title to the frame."""
1830
1803
  title = obj if isinstance(obj, str) else repr(obj)
1831
1804
  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
-
1805
+
1840
1806
  def add_log(self, text, noerr=None):
1841
1807
  """Add text to the logging buffer.
1842
- If noerr <bool> is specified, add a line-marker.
1808
+ If noerr:bool is specified, add a line-marker.
1843
1809
  """
1844
1810
  buf = self.Log.default_buffer or self.Log.new_buffer()
1845
1811
  with buf.off_readonly():
1846
- buf.goto_char(buf.TextLength) # line to set an arrow marker
1812
+ buf.goto_char(buf.TextLength) # line to set an arrow marker
1847
1813
  buf.write(text)
1848
1814
  if noerr is not None:
1849
1815
  ## Set a marker on the current line.
1850
- buf.add_marker(buf.cline, 1 if noerr else 2) # 1:white 2:red-arrow
1816
+ buf.add_marker(buf.cline, 1 if noerr else 2) # 1:white 2:red-arrow
1851
1817
  return
1852
1818
 
1853
1819
  ## 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."""
1820
+ # with open(self.LOGGING_FILE, 'a', encoding='utf-8', newline='') as o:
1821
+ # o.write(text)
1822
+
1823
+ def add_help(self, text, title=None):
1824
+ """Add text to the help buffer.
1825
+ If title:str is specified, create a new buffer with that title.
1826
+ """
1859
1827
  buf = self.Help.default_buffer or self.Help.new_buffer()
1828
+ if title is not None:
1829
+ self.Help.find_file(f"*{title}*")
1830
+ buf = self.Help.buffer
1860
1831
  with buf.off_readonly():
1861
- buf.SetText(text)
1832
+ buf.Text = text
1833
+ buf.EmptyUndoBuffer()
1834
+ buf.SetSavePoint()
1862
1835
  ## Overwrite text and popup the window.
1863
1836
  self.popup_window(self.Help)
1864
1837
  self.Help.swap_page(buf)
1865
-
1838
+
1866
1839
  def clone_shell(self, target):
1867
1840
  if not hasattr(target, '__dict__'):
1868
1841
  raise TypeError("primitive objects cannot be targeted")
1869
1842
 
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))
1843
+ try:
1844
+ shell = next(self.get_all_shells(target))
1845
+ except StopIteration:
1846
+ shell = self.rootshell.__class__(self, target, name="clone",
1847
+ style=wx.CLIP_CHILDREN|wx.BORDER_NONE)
1848
+ self.console.AddPage(shell, typename(shell.target))
1849
+ self.handler('shell_new', shell)
1875
1850
  self.popup_window(shell)
1851
+ self.Show()
1876
1852
  shell.SetFocus()
1877
1853
  return shell
1878
-
1854
+
1879
1855
  def delete_shell(self, shell):
1880
1856
  """Close the current shell."""
1881
1857
  if shell is self.rootshell:
1882
- ## self.message("- Don't close the root shell.")
1858
+ # self.message("- Don't close the root shell.")
1883
1859
  return
1884
1860
  if self.debugger.busy and shell is self.debugger.interactive_shell:
1885
1861
  wx.MessageBox("The debugger is running.\n\n"
@@ -1887,63 +1863,86 @@ class ShellFrame(MiniFrame):
1887
1863
  return
1888
1864
  j = self.console.GetPageIndex(shell)
1889
1865
  if j != -1:
1890
- self.console.DeletePage(j) # Destroy the window
1891
-
1866
+ self.console.DeletePage(j) # Destroy the window
1867
+
1892
1868
  ## --------------------------------
1893
- ## Attributes for notebook pages
1869
+ ## Attributes for notebook pages.
1894
1870
  ## --------------------------------
1895
-
1871
+
1896
1872
  def get_all_pages(self, type=None):
1897
1873
  """Yields all pages of the specified type in the notebooks."""
1898
1874
  yield from self.console.get_pages(type)
1899
1875
  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
-
1876
+
1877
+ def get_all_shells(self, target=None):
1878
+ """Yields all shells with specified target.
1879
+
1880
+ If the shell is found, it switches to the corresponding page.
1881
+ If `target` is not provided, it yields all shells in the notebooks.
1882
+ """
1883
+ if target is None:
1884
+ yield from self.console.get_pages(type(self.rootshell))
1885
+ else:
1886
+ for shell in self.console.get_pages(type(self.rootshell)):
1887
+ if shell.target is target:
1888
+ self.console.swap_page(shell)
1889
+ yield shell
1890
+
1891
+ def get_all_editors(self, fn=None):
1892
+ """Yields all editors with specified fn:filename or code.
1893
+
1894
+ If the editor is found, it switches to the corresponding page.
1895
+ If `fn` is not provided, it yields all editors in the notebooks.
1896
+ """
1897
+ if fn is None:
1898
+ yield from self.ghost.get_pages(type(self.Log))
1899
+ else:
1900
+ for book in self.ghost.get_pages(type(self.Log)):
1901
+ buf = book.find_buffer(fn)
1902
+ if buf:
1903
+ book.swap_page(buf)
1904
+ yield book
1905
+
1906
1906
  @property
1907
1907
  def current_shell(self):
1908
1908
  """Currently selected shell or rootshell."""
1909
1909
  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
-
1910
+
1916
1911
  @property
1917
1912
  def current_editor(self):
1918
1913
  """Currently selected editor or scratch."""
1919
1914
  editor = self.ghost.CurrentPage
1920
1915
  if isinstance(editor, type(self.Log)):
1921
1916
  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.
1917
+ return next((book for book in self.get_all_editors() if book.IsShown()), self.Scratch)
1918
+
1919
+ def create_editor(self, bookname):
1920
+ """Create a new editor (internal use only)..
1921
+ If such an editor already exists, no new editor is created.
1927
1922
  """
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
-
1923
+ try:
1924
+ return next(book for book in self.get_all_editors() if book.Name == bookname)
1925
+ except StopIteration:
1926
+ with wx.FrozenWindow(self.ghost):
1927
+ editor = self.Log.__class__(self, bookname)
1928
+ self.ghost.AddPage(editor, bookname)
1929
+ self.ghost.move_tab(editor, 0)
1930
+ self.handler('book_new', editor)
1931
+
1932
+ def _attach():
1933
+ editor.handler.append(self.Bookshelf.context)
1934
+ self.Bookshelf.build_tree(clear=0)
1935
+ wx.CallAfter(_attach)
1936
+ return editor
1937
+
1939
1938
  ## --------------------------------
1940
- ## Find text dialog
1939
+ ## Find / Replace text dialog.
1941
1940
  ## --------------------------------
1942
1941
  ## *** The following code is a modification of <wx.py.frame.Frame> ***
1943
-
1942
+
1944
1943
  __find_target = None
1945
-
1946
- def OnFindText(self, evt):
1944
+
1945
+ def on_search_dialog(self, evt, flags=0):
1947
1946
  if self.findDlg is not None:
1948
1947
  self.findDlg.SetFocus()
1949
1948
  return
@@ -1952,32 +1951,54 @@ class ShellFrame(MiniFrame):
1952
1951
  if not isinstance(wnd, stc.StyledTextCtrl):
1953
1952
  return
1954
1953
  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)
1954
+ topic = wnd.topic_at_caret
1955
+ if topic:
1956
+ self.findData.FindString = topic
1957
+ self.findData.Flags |= wx.FR_DOWN
1958
+ self.findDlg = wx.FindReplaceDialog(wnd, self.findData, "Find", flags)
1958
1959
  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):
1960
+
1961
+ def on_replace_dialog(self, evt):
1962
+ self.on_search_dialog(evt, flags=wx.FR_REPLACEDIALOG)
1963
+
1964
+ def repeat_forward_search(self, evt):
1965
+ self.OnFindNext(evt, backward=False)
1966
+
1967
+ def repeat_backward_search(self, evt):
1978
1968
  self.OnFindNext(evt, backward=True)
1979
-
1980
- def OnFindClose(self, evt): #<wx._core.FindDialogEvent>
1969
+
1970
+ def OnFindNext(self, evt, backward=None): # <wx._core.FindDialogEvent>
1971
+ if not self.findData.FindString:
1972
+ self.message("No last search.")
1973
+ return
1974
+ if isinstance(evt, wx.FindDialogEvent):
1975
+ wnd = self.findDlg.Parent
1976
+ else:
1977
+ wnd = evt.EventObject
1978
+ if not isinstance(wnd, stc.StyledTextCtrl):
1979
+ wnd = self.__find_target
1980
+ if backward:
1981
+ self.findData.Flags &= ~wx.FR_DOWN
1982
+ else:
1983
+ self.findData.Flags |= wx.FR_DOWN
1984
+
1985
+ if evt.EventType == wx.EVT_FIND_REPLACE_ALL.typeId: # replace-all
1986
+ n = wnd.DoReplaceAll(self.findData)
1987
+ self.message(f"Replaced {n} strings.")
1988
+ if self.findDlg:
1989
+ self.OnFindClose(None)
1990
+ return
1991
+ elif evt.EventType == wx.EVT_FIND_REPLACE.typeId: # replace
1992
+ loc = wnd.DoReplaceNext(self.findData)
1993
+ else:
1994
+ loc = wnd.DoFindNext(self.findData)
1995
+ if self.findDlg:
1996
+ if not (self.findDlg.WindowStyle & wx.FR_REPLACEDIALOG): # for search-dialog
1997
+ self.OnFindClose(None)
1998
+ if loc < 0:
1999
+ self.message("Unable to find the search text.")
2000
+
2001
+ def OnFindClose(self, evt): # <wx._core.FindDialogEvent>
1981
2002
  self.findDlg.Destroy()
1982
2003
  self.findDlg = None
1983
2004
 
@@ -1989,7 +2010,7 @@ def filling(obj=None, **kwargs):
1989
2010
  rootLabel=typename(obj),
1990
2011
  pos=wx.GetMousePosition(),
1991
2012
  **kwargs)
1992
- frame.filling.text.WrapMode = 0 # no wrap
1993
- frame.filling.text.Zoom = -1 # zoom level of size of fonts
2013
+ frame.filling.text.WrapMode = 0 # no wrap
2014
+ frame.filling.text.Zoom = -1 # zoom level of size of fonts
1994
2015
  frame.Show()
1995
2016
  return frame