Coverage for C:\leo.repo\leo-editor\leo\core\leoFrame.py : 56%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#@+leo-ver=5-thin
2#@+node:ekr.20031218072017.3655: * @file leoFrame.py
3"""
4The base classes for all Leo Windows, their body, log and tree panes, key bindings and menus.
6These classes should be overridden to create frames for a particular gui.
7"""
8#@+<< imports >>
9#@+node:ekr.20120219194520.10464: ** << imports >> (leoFrame)
10import time
11from leo.core import leoGlobals as g
12from leo.core import leoColorizer # NullColorizer is a subclass of ColorizerMixin
13from leo.core import leoMenu
14from leo.core import leoNodes
15assert time
16#@-<< imports >>
17#@+<< About handling events >>
18#@+node:ekr.20031218072017.2410: ** << About handling events >>
19#@+at Leo must handle events or commands that change the text in the outline
20# or body panes. We must ensure that headline and body text corresponds
21# to the VNode corresponding to presently selected outline, and vice
22# versa. For example, when the user selects a new headline in the
23# outline pane, we must ensure that:
24#
25# 1) All vnodes have up-to-date information and
26#
27# 2) the body pane is loaded with the correct data.
28#
29# Early versions of Leo attempted to satisfy these conditions when the user
30# switched outline nodes. Such attempts never worked well; there were too many
31# special cases. Later versions of Leo use a much more direct approach: every
32# keystroke in the body pane updates the presently selected VNode immediately.
33#
34# The LeoTree class contains all the event handlers for the tree pane, and the
35# LeoBody class contains the event handlers for the body pane. The following
36# convenience methods exists:
37#
38# - body.updateBody & tree.updateBody:
39# These are suprising complex.
40#
41# - body.bodyChanged & tree.headChanged:
42# Called by commands throughout Leo's core that change the body or headline.
43# These are thin wrappers for updateBody and updateTree.
44#@-<< About handling events >>
45#@+<< command decorators >>
46#@+node:ekr.20150509054428.1: ** << command decorators >> (leoFrame.py)
47def log_cmd(name): # Not used.
48 """Command decorator for the LeoLog class."""
49 return g.new_cmd_decorator(name, ['c', 'frame', 'log'])
51def body_cmd(name):
52 """Command decorator for the c.frame.body class."""
53 return g.new_cmd_decorator(name, ['c', 'frame', 'body'])
55def frame_cmd(name):
56 """Command decorator for the LeoFrame class."""
57 return g.new_cmd_decorator(name, ['c', 'frame',])
58#@-<< command decorators >>
59#@+others
60#@+node:ekr.20140907201613.18660: ** API classes
61# These classes are for documentation and unit testing.
62# They are the base class for no class.
63#@+node:ekr.20140904043623.18576: *3* class StatusLineAPI
64class StatusLineAPI:
65 """The required API for c.frame.statusLine."""
67 def __init__(self, c, parentFrame):
68 pass
70 def clear(self):
71 pass
73 def disable(self, background=None):
74 pass
76 def enable(self, background="white"):
77 pass
79 def get(self):
80 return ''
82 def isEnabled(self):
83 return False
85 def put(self, s, bg=None, fg=None):
86 pass
88 def setFocus(self):
89 pass
91 def update(self):
92 pass
93#@+node:ekr.20140907201613.18663: *3* class TreeAPI
94class TreeAPI:
95 """The required API for c.frame.tree."""
97 def __init__(self, frame):
98 pass
99 # Must be defined in subclasses.
101 def drawIcon(self, p):
102 pass
104 def editLabel(self, v, selectAll=False, selection=None):
105 pass
107 def edit_widget(self, p):
108 return None
110 def redraw(self, p=None):
111 pass
112 redraw_now = redraw
114 def scrollTo(self, p):
115 pass
116 # May be defined in subclasses.
118 def initAfterLoad(self):
119 pass
121 def onHeadChanged(self, p, undoType='Typing', s=None, e=None):
122 pass
123 # Hints for optimization. The proper default is c.redraw()
125 def redraw_after_contract(self, p):
126 pass
128 def redraw_after_expand(self, p):
129 pass
131 def redraw_after_head_changed(self):
132 pass
134 def redraw_after_icons_changed(self):
135 pass
137 def redraw_after_select(self, p=None):
138 pass
139 # Must be defined in the LeoTree class...
140 # def OnIconDoubleClick (self,p):
142 def OnIconCtrlClick(self, p):
143 pass
145 def endEditLabel(self):
146 pass
148 def getEditTextDict(self, v):
149 return None
151 def injectCallbacks(self):
152 pass
154 def onHeadlineKey(self, event):
155 pass
157 def select(self, p):
158 pass
160 def updateHead(self, event, w):
161 pass
162#@+node:ekr.20140903025053.18631: *3* class WrapperAPI
163class WrapperAPI:
164 """A class specifying the wrapper api used throughout Leo's core."""
166 def __init__(self, c):
167 pass
169 def appendText(self, s):
170 pass
172 def clipboard_append(self, s):
173 pass
175 def clipboard_clear(self):
176 pass
178 def delete(self, i, j=None):
179 pass
181 def deleteTextSelection(self):
182 pass
184 def disable(self):
185 pass
187 def enable(self, enabled=True):
188 pass
190 def flashCharacter(self, i, bg='white', fg='red', flashes=3, delay=75):
191 pass
193 def get(self, i, j):
194 return ''
196 def getAllText(self):
197 return ''
199 def getInsertPoint(self):
200 return 0
202 def getSelectedText(self):
203 return ''
205 def getSelectionRange(self):
206 return (0, 0)
208 def getXScrollPosition(self):
209 return 0
211 def getYScrollPosition(self):
212 return 0
214 def hasSelection(self):
215 return False
217 def insert(self, i, s):
218 pass
220 def see(self, i):
221 pass
223 def seeInsertPoint(self):
224 pass
226 def selectAllText(self, insert=None):
227 pass
229 def setAllText(self, s):
230 pass
232 def setFocus(self):
233 pass # Required: sets the focus to wrapper.widget.
235 def setInsertPoint(self, pos, s=None):
236 pass
238 def setSelectionRange(self, i, j, insert=None):
239 pass
241 def setXScrollPosition(self, i):
242 pass
244 def setYScrollPosition(self, i):
245 pass
247 def tag_configure(self, colorName, **keys):
248 pass
250 def toPythonIndex(self, index):
251 return 0
253 def toPythonIndexRowCol(self, index):
254 return (0, 0, 0)
255#@+node:ekr.20140904043623.18552: ** class IconBarAPI
256class IconBarAPI:
257 """The required API for c.frame.iconBar."""
259 def __init__(self, c, parentFrame):
260 pass
262 def add(self, *args, **keys):
263 pass
265 def addRow(self, height=None):
266 pass
268 def addRowIfNeeded(self):
269 pass
271 def addWidget(self, w):
272 pass
274 def clear(self):
275 pass
277 def createChaptersIcon(self):
278 pass
280 def deleteButton(self, w):
281 pass
283 def getNewFrame(self):
284 pass
286 def setCommandForButton(self, button, command, command_p, controller, gnx, script):
287 pass
288#@+node:ekr.20031218072017.3656: ** class LeoBody
289class LeoBody:
290 """The base class for the body pane in Leo windows."""
291 #@+others
292 #@+node:ekr.20031218072017.3657: *3* LeoBody.__init__
293 def __init__(self, frame, parentFrame):
294 """Ctor for LeoBody class."""
295 c = frame.c
296 frame.body = self
297 self.c = c
298 self.editorWrappers = {} # keys are pane names, values are text widgets
299 self.frame = frame
300 self.parentFrame = parentFrame # New in Leo 4.6.
301 self.totalNumberOfEditors = 0
302 # May be overridden in subclasses...
303 self.widget = None # set in LeoQtBody.set_widget.
304 self.wrapper = None # set in LeoQtBody.set_widget.
305 self.numberOfEditors = 1
306 self.pb = None # paned body widget.
307 # Must be overridden in subclasses...
308 self.colorizer = None
309 # Init user settings.
310 self.use_chapters = False
311 # May be overridden in subclasses.
312 #@+node:ekr.20031218072017.3677: *3* LeoBody.Coloring
313 def forceFullRecolor(self):
314 pass
316 def getColorizer(self):
317 return self.colorizer
319 def updateSyntaxColorer(self, p):
320 return self.colorizer.updateSyntaxColorer(p.copy())
322 def recolor(self, p):
323 self.c.recolor()
325 recolor_now = recolor
326 #@+node:ekr.20140903103455.18574: *3* LeoBody.Defined in subclasses
327 # Methods of this class call the following methods of subclasses (LeoQtBody)
328 # Fail loudly if these methods are not defined.
330 def oops(self):
331 """Say that a required method in a subclass is missing."""
332 g.trace("(LeoBody) %s should be overridden in a subclass", g.callers())
334 def createEditorFrame(self, w):
335 self.oops()
337 def createTextWidget(self, parentFrame, p, name):
338 self.oops()
340 def packEditorLabelWidget(self, w):
341 self.oops()
343 def onFocusOut(self, obj):
344 pass
345 #@+node:ekr.20060528100747: *3* LeoBody.Editors
346 # This code uses self.pb, a paned body widget, created by tkBody.finishCreate.
347 #@+node:ekr.20070424053629: *4* LeoBody.entries
348 #@+node:ekr.20060528100747.1: *5* LeoBody.addEditor (overridden)
349 def addEditor(self, event=None):
350 """Add another editor to the body pane."""
351 c, p = self.c, self.c.p
352 self.totalNumberOfEditors += 1
353 self.numberOfEditors += 1
354 if self.numberOfEditors == 2:
355 # Inject the ivars into the first editor.
356 # Bug fix: Leo 4.4.8 rc1: The name of the last editor need not be '1'
357 d = self.editorWrappers
358 keys = list(d.keys())
359 if len(keys) == 1:
360 # Immediately create the label in the old editor.
361 w_old = d.get(keys[0])
362 self.updateInjectedIvars(w_old, p)
363 self.selectLabel(w_old)
364 else:
365 g.trace('can not happen: unexpected editorWrappers', d)
366 name = f"{self.totalNumberOfEditors}"
367 pane = self.pb.add(name)
368 panes = self.pb.panes()
369 minSize = float(1.0 / float(len(panes)))
370 # Create the text wrapper.
371 f = self.createEditorFrame(pane)
372 wrapper = self.createTextWidget(f, name=name, p=p)
373 wrapper.delete(0, 'end')
374 wrapper.insert('end', p.b)
375 wrapper.see(0)
376 c.k.completeAllBindingsForWidget(wrapper)
377 self.recolorWidget(p, wrapper)
378 self.editorWrappers[name] = wrapper
379 for pane in panes:
380 self.pb.configurepane(pane, size=minSize)
381 self.pb.updatelayout()
382 c.frame.body.wrapper = wrapper
383 # Finish...
384 self.updateInjectedIvars(wrapper, p)
385 self.selectLabel(wrapper)
386 self.selectEditor(wrapper)
387 self.updateEditors()
388 c.bodyWantsFocus()
389 #@+node:ekr.20060528132829: *5* LeoBody.assignPositionToEditor
390 def assignPositionToEditor(self, p):
391 """Called *only* from tree.select to select the present body editor."""
392 c = self.c
393 w = c.frame.body.widget
394 self.updateInjectedIvars(w, p)
395 self.selectLabel(w)
396 #@+node:ekr.20200415041750.1: *5* LeoBody.cycleEditorFocus (restored)
397 @body_cmd('editor-cycle-focus')
398 @body_cmd('cycle-editor-focus') # There is no LeoQtBody method
399 def cycleEditorFocus(self, event=None):
400 """Cycle keyboard focus between the body text editors."""
401 c = self.c
402 d = self.editorWrappers
403 w = c.frame.body.wrapper
404 values = list(d.values())
405 if len(values) > 1:
406 i = values.index(w) + 1
407 if i == len(values):
408 i = 0
409 w2 = values[i]
410 assert w != w2
411 self.selectEditor(w2)
412 c.frame.body.wrapper = w2
413 #@+node:ekr.20060528113806: *5* LeoBody.deleteEditor (overridden)
414 def deleteEditor(self, event=None):
415 """Delete the presently selected body text editor."""
416 c = self.c
417 w = c.frame.body.wapper
418 d = self.editorWrappers
419 if len(list(d.keys())) == 1:
420 return
421 name = w.leo_name
422 del d[name]
423 self.pb.delete(name)
424 panes = self.pb.panes()
425 minSize = float(1.0 / float(len(panes)))
426 for pane in panes:
427 self.pb.configurepane(pane, size=minSize)
428 # Select another editor.
429 w = list(d.values())[0]
430 # c.frame.body.wrapper = w # Don't do this now?
431 self.numberOfEditors -= 1
432 self.selectEditor(w)
433 #@+node:ekr.20070425180705: *5* LeoBody.findEditorForChapter
434 def findEditorForChapter(self, chapter, p):
435 """Return an editor to be assigned to chapter."""
436 c = self.c
437 d = self.editorWrappers
438 values = list(d.values())
439 # First, try to match both the chapter and position.
440 if p:
441 for w in values:
442 if (
443 hasattr(w, 'leo_chapter') and w.leo_chapter == chapter and
444 hasattr(w, 'leo_p') and w.leo_p and w.leo_p == p
445 ):
446 return w
447 # Next, try to match just the chapter.
448 for w in values:
449 if hasattr(w, 'leo_chapter') and w.leo_chapter == chapter:
450 return w
451 # As a last resort, return the present editor widget.
452 return c.frame.body.wrapper
453 #@+node:ekr.20060530210057: *5* LeoBody.select/unselectLabel
454 def unselectLabel(self, w):
455 self.createChapterIvar(w)
456 self.packEditorLabelWidget(w)
457 s = self.computeLabel(w)
458 if hasattr(w, 'leo_label') and w.leo_label:
459 w.leo_label.configure(text=s, bg='LightSteelBlue1')
461 def selectLabel(self, w):
462 if self.numberOfEditors > 1:
463 self.createChapterIvar(w)
464 self.packEditorLabelWidget(w)
465 s = self.computeLabel(w)
466 if hasattr(w, 'leo_label') and w.leo_label:
467 w.leo_label.configure(text=s, bg='white')
468 elif hasattr(w, 'leo_label') and w.leo_label:
469 w.leo_label.pack_forget()
470 w.leo_label = None
471 #@+node:ekr.20061017083312: *5* LeoBody.selectEditor & helpers
472 selectEditorLockout = False
474 def selectEditor(self, w):
475 """Select the editor given by w and node w.leo_p."""
476 # Called whenever wrapper must be selected.
477 c = self.c
478 if self.selectEditorLockout:
479 return None
480 if w and w == self.c.frame.body.widget:
481 if w.leo_p and w.leo_p != c.p:
482 c.selectPosition(w.leo_p)
483 c.bodyWantsFocus()
484 return None
485 try:
486 val = None
487 self.selectEditorLockout = True
488 val = self.selectEditorHelper(w)
489 finally:
490 self.selectEditorLockout = False
491 return val # Don't put a return in a finally clause.
492 #@+node:ekr.20070423102603: *6* LeoBody.selectEditorHelper
493 def selectEditorHelper(self, wrapper):
494 """Select the editor whose widget is given."""
495 c = self.c
496 if not (hasattr(wrapper, 'leo_p') and wrapper.leo_p):
497 g.trace('no wrapper.leo_p')
498 return
499 self.deactivateActiveEditor(wrapper)
500 # The actual switch.
501 c.frame.body.wrapper = wrapper
502 wrapper.leo_active = True
503 self.switchToChapter(wrapper)
504 self.selectLabel(wrapper)
505 if not self.ensurePositionExists(wrapper):
506 g.trace('***** no position editor!')
507 return
508 p = wrapper.leo_p
509 c.redraw(p)
510 c.recolor()
511 c.bodyWantsFocus()
512 #@+node:ekr.20060528131618: *5* LeoBody.updateEditors
513 # Called from addEditor and assignPositionToEditor
515 def updateEditors(self):
516 c, p = self.c, self.c.p
517 d = self.editorWrappers
518 if len(list(d.keys())) < 2:
519 return # There is only the main widget.
520 for key in d:
521 wrapper = d.get(key)
522 v = wrapper.leo_v
523 if v and v == p.v and wrapper != c.frame.body.wrapper:
524 wrapper.delete(0, 'end')
525 wrapper.insert('end', p.b)
526 self.recolorWidget(p, wrapper)
527 c.bodyWantsFocus()
528 #@+node:ekr.20070424053629.1: *4* LeoBody.utils
529 #@+node:ekr.20070422093128: *5* LeoBody.computeLabel
530 def computeLabel(self, w):
531 s = w.leo_label_s
532 if hasattr(w, 'leo_chapter') and w.leo_chapter:
533 s = f"{w.leo_chapter.name}: {s}"
534 return s
535 #@+node:ekr.20070422094710: *5* LeoBody.createChapterIvar
536 def createChapterIvar(self, w):
537 c = self.c
538 cc = c.chapterController
539 if not hasattr(w, 'leo_chapter') or not w.leo_chapter:
540 if cc and self.use_chapters:
541 w.leo_chapter = cc.getSelectedChapter()
542 else:
543 w.leo_chapter = None
544 #@+node:ekr.20070424084651: *5* LeoBody.ensurePositionExists
545 def ensurePositionExists(self, w):
546 """Return True if w.leo_p exists or can be reconstituted."""
547 c = self.c
548 if c.positionExists(w.leo_p):
549 return True
550 g.trace('***** does not exist', w.leo_name)
551 for p2 in c.all_unique_positions():
552 if p2.v and p2.v == w.leo_v:
553 w.leo_p = p2.copy()
554 return True
555 # This *can* happen when selecting a deleted node.
556 w.leo_p = c.p
557 return False
558 #@+node:ekr.20070424080640: *5* LeoBody.deactivateActiveEditor
559 # Not used in Qt.
561 def deactivateActiveEditor(self, w):
562 """Inactivate the previously active editor."""
563 d = self.editorWrappers
564 # Don't capture ivars here! assignPositionToEditor keeps them up-to-date. (??)
565 for key in d:
566 w2 = d.get(key)
567 if w2 != w and w2.leo_active:
568 w2.leo_active = False
569 self.unselectLabel(w2)
570 return
571 #@+node:ekr.20060530204135: *5* LeoBody.recolorWidget (QScintilla only)
572 def recolorWidget(self, p, w):
573 # Support QScintillaColorizer.colorize.
574 c = self.c
575 colorizer = c.frame.body.colorizer
576 if p and colorizer and hasattr(colorizer, 'colorize'):
577 old_wrapper = c.frame.body.wrapper
578 c.frame.body.wrapper = w
579 try:
580 c.frame.body.colorizer.colorize(p)
581 finally:
582 c.frame.body.wrapper = old_wrapper
583 #@+node:ekr.20070424084012: *5* LeoBody.switchToChapter
584 def switchToChapter(self, w):
585 """select w.leo_chapter."""
586 c = self.c
587 cc = c.chapterController
588 if hasattr(w, 'leo_chapter') and w.leo_chapter:
589 chapter = w.leo_chapter
590 name = chapter and chapter.name
591 oldChapter = cc.getSelectedChapter()
592 if chapter != oldChapter:
593 cc.selectChapterByName(name)
594 c.bodyWantsFocus()
595 #@+node:ekr.20070424092855: *5* LeoBody.updateInjectedIvars
596 # Called from addEditor and assignPositionToEditor.
598 def updateInjectedIvars(self, w, p):
599 """Inject updated ivars in w, a gui widget."""
600 if not w:
601 return
602 c = self.c
603 cc = c.chapterController
604 # Was in ctor.
605 use_chapters = c.config.getBool('use-chapters')
606 if cc and use_chapters:
607 w.leo_chapter = cc.getSelectedChapter()
608 else:
609 w.leo_chapter = None
610 w.leo_p = p.copy()
611 w.leo_v = w.leo_p.v
612 w.leo_label_s = p.h
613 #@+node:ekr.20031218072017.4018: *3* LeoBody.Text
614 #@+node:ekr.20031218072017.4030: *4* LeoBody.getInsertLines
615 def getInsertLines(self):
616 """
617 Return before,after where:
619 before is all the lines before the line containing the insert point.
620 sel is the line containing the insert point.
621 after is all the lines after the line containing the insert point.
623 All lines end in a newline, except possibly the last line.
624 """
625 body = self
626 w = body.wrapper
627 s = w.getAllText()
628 insert = w.getInsertPoint()
629 i, j = g.getLine(s, insert)
630 before = s[0:i]
631 ins = s[i:j]
632 after = s[j:]
633 before = g.checkUnicode(before)
634 ins = g.checkUnicode(ins)
635 after = g.checkUnicode(after)
636 return before, ins, after
637 #@+node:ekr.20031218072017.4031: *4* LeoBody.getSelectionAreas
638 def getSelectionAreas(self):
639 """
640 Return before,sel,after where:
642 before is the text before the selected text
643 (or the text before the insert point if no selection)
644 sel is the selected text (or "" if no selection)
645 after is the text after the selected text
646 (or the text after the insert point if no selection)
647 """
648 body = self
649 w = body.wrapper
650 s = w.getAllText()
651 i, j = w.getSelectionRange()
652 if i == j:
653 j = i + 1
654 before = s[0:i]
655 sel = s[i:j]
656 after = s[j:]
657 before = g.checkUnicode(before)
658 sel = g.checkUnicode(sel)
659 after = g.checkUnicode(after)
660 return before, sel, after
661 #@+node:ekr.20031218072017.2377: *4* LeoBody.getSelectionLines
662 def getSelectionLines(self):
663 """
664 Return before,sel,after where:
666 before is the all lines before the selected text
667 (or the text before the insert point if no selection)
668 sel is the selected text (or "" if no selection)
669 after is all lines after the selected text
670 (or the text after the insert point if no selection)
671 """
672 if g.app.batchMode:
673 return '', '', ''
674 # At present, called only by c.getBodyLines.
675 body = self
676 w = body.wrapper
677 s = w.getAllText()
678 i, j = w.getSelectionRange()
679 if i == j:
680 i, j = g.getLine(s, i)
681 else:
682 # #1742: Move j back if it is at the start of a line.
683 if j > i and j > 0 and s[j - 1] == '\n':
684 j -= 1
685 i, junk = g.getLine(s, i)
686 junk, j = g.getLine(s, j)
687 before = g.checkUnicode(s[0:i])
688 sel = g.checkUnicode(s[i:j])
689 after = g.checkUnicode(s[j : len(s)])
690 return before, sel, after # 3 strings.
691 #@-others
692#@+node:ekr.20031218072017.3678: ** class LeoFrame
693class LeoFrame:
694 """The base class for all Leo windows."""
695 instances = 0
696 #@+others
697 #@+node:ekr.20031218072017.3679: *3* LeoFrame.__init__ & reloadSettings
698 def __init__(self, c, gui):
699 self.c = c
700 self.gui = gui
701 self.iconBarClass = NullIconBarClass
702 self.statusLineClass = NullStatusLineClass
703 self.title = None # Must be created by subclasses.
704 # Objects attached to this frame.
705 self.body = None
706 self.colorPanel = None
707 self.comparePanel = None
708 self.findPanel = None
709 self.fontPanel = None
710 self.iconBar = None
711 self.isNullFrame = False
712 self.keys = None
713 self.log = None
714 self.menu = None
715 self.miniBufferWidget = None
716 self.outerFrame = None
717 self.prefsPanel = None
718 self.statusLine = g.NullObject() # For unit tests.
719 self.tree = None
720 self.useMiniBufferWidget = False
721 # Gui-independent data
722 self.cursorStay = True # May be overridden in subclass.reloadSettings.
723 self.componentsDict = {} # Keys are names, values are componentClass instances.
724 self.es_newlines = 0 # newline count for this log stream
725 self.openDirectory = ""
726 self.saved = False # True if ever saved
727 self.splitVerticalFlag = True
728 # Set by initialRatios later.
729 self.startupWindow = False # True if initially opened window
730 self.stylesheet = None # The contents of <?xml-stylesheet...?> line.
731 self.tab_width = 0 # The tab width in effect in this pane.
732 #@+node:ekr.20051009045404: *4* frame.createFirstTreeNode
733 def createFirstTreeNode(self):
734 c = self.c
735 #
736 # #1631: Initialize here, not in p._linkAsRoot.
737 c.hiddenRootNode.children = []
738 #
739 # #1817: Clear the gnxDict.
740 c.fileCommands.gnxDict = {}
741 #
742 # Create the first node.
743 v = leoNodes.VNode(context=c)
744 p = leoNodes.Position(v)
745 v.initHeadString("NewHeadline")
746 #
747 # New in Leo 4.5: p.moveToRoot would be wrong:
748 # the node hasn't been linked yet.
749 p._linkAsRoot()
750 return v
751 #@+node:ekr.20061109125528: *3* LeoFrame.May be defined in subclasses
752 #@+node:ekr.20071027150501: *4* LeoFrame.event handlers
753 def OnBodyClick(self, event=None):
754 pass
756 def OnBodyRClick(self, event=None):
757 pass
758 #@+node:ekr.20031218072017.3688: *4* LeoFrame.getTitle & setTitle
759 def getTitle(self):
760 return self.title
762 def setTitle(self, title):
763 self.title = title
764 #@+node:ekr.20081005065934.3: *4* LeoFrame.initAfterLoad & initCompleteHint
765 def initAfterLoad(self):
766 """Provide offical hooks for late inits of components of Leo frames."""
767 frame = self
768 frame.body.initAfterLoad()
769 frame.log.initAfterLoad()
770 frame.menu.initAfterLoad()
771 # if frame.miniBufferWidget: frame.miniBufferWidget.initAfterLoad()
772 frame.tree.initAfterLoad()
774 def initCompleteHint(self):
775 pass
776 #@+node:ekr.20031218072017.3687: *4* LeoFrame.setTabWidth
777 def setTabWidth(self, w):
778 """Set the tab width in effect for this frame."""
779 # Subclasses may override this to affect drawing.
780 self.tab_width = w
781 #@+node:ekr.20061109125528.1: *3* LeoFrame.Must be defined in base class
782 #@+node:ekr.20031218072017.3689: *4* LeoFrame.initialRatios
783 def initialRatios(self):
784 c = self.c
785 s = c.config.get("initial_split_orientation", "string")
786 verticalFlag = s is None or (s != "h" and s != "horizontal")
787 if verticalFlag:
788 r = c.config.getRatio("initial-vertical-ratio")
789 if r is None or r < 0.0 or r > 1.0:
790 r = 0.5
791 r2 = c.config.getRatio("initial-vertical-secondary-ratio")
792 if r2 is None or r2 < 0.0 or r2 > 1.0:
793 r2 = 0.8
794 else:
795 r = c.config.getRatio("initial-horizontal-ratio")
796 if r is None or r < 0.0 or r > 1.0:
797 r = 0.3
798 r2 = c.config.getRatio("initial-horizontal-secondary-ratio")
799 if r2 is None or r2 < 0.0 or r2 > 1.0:
800 r2 = 0.8
801 return verticalFlag, r, r2
802 #@+node:ekr.20031218072017.3690: *4* LeoFrame.longFileName & shortFileName
803 def longFileName(self):
804 return self.c.mFileName
806 def shortFileName(self):
807 return g.shortFileName(self.c.mFileName)
808 #@+node:ekr.20031218072017.3691: *4* LeoFrame.oops
809 def oops(self):
810 g.pr("LeoFrame oops:", g.callers(4), "should be overridden in subclass")
811 #@+node:ekr.20031218072017.3692: *4* LeoFrame.promptForSave
812 def promptForSave(self):
813 """
814 Prompt the user to save changes.
815 Return True if the user vetos the quit or save operation.
816 """
817 c = self.c
818 theType = "quitting?" if g.app.quitting else "closing?"
819 # See if we are in quick edit/save mode.
820 root = c.rootPosition()
821 quick_save = not c.mFileName and not root.next() and root.isAtEditNode()
822 if quick_save:
823 name = g.shortFileName(root.atEditNodeName())
824 else:
825 name = c.mFileName if c.mFileName else self.title
826 answer = g.app.gui.runAskYesNoCancelDialog(
827 c,
828 title='Confirm',
829 message=f"Save changes to {g.splitLongFileName(name)} before {theType}",
830 )
831 if answer == "cancel":
832 return True # Veto.
833 if answer == "no":
834 return False # Don't save and don't veto.
835 if not c.mFileName:
836 root = c.rootPosition()
837 if not root.next() and root.isAtEditNode():
838 # There is only a single @edit node in the outline.
839 # A hack to allow "quick edit" of non-Leo files.
840 # See https://bugs.launchpad.net/leo-editor/+bug/381527
841 # Write the @edit node if needed.
842 if root.isDirty():
843 c.atFileCommands.writeOneAtEditNode(root)
844 return False # Don't save and don't veto.
845 c.mFileName = g.app.gui.runSaveFileDialog(c,
846 title="Save",
847 filetypes=[("Leo files", "*.leo")],
848 defaultextension=".leo")
849 c.bringToFront()
850 if c.mFileName:
851 if g.app.gui.guiName() == 'curses':
852 g.pr(f"Saving: {c.mFileName}")
853 ok = c.fileCommands.save(c.mFileName)
854 return not ok
855 # Veto if the save did not succeed.
856 return True # Veto.
857 #@+node:ekr.20031218072017.1375: *4* LeoFrame.frame.scanForTabWidth
858 def scanForTabWidth(self, p):
859 """Return the tab width in effect at p."""
860 c = self.c
861 tab_width = c.getTabWidth(p)
862 c.frame.setTabWidth(tab_width)
863 #@+node:ekr.20061119120006: *4* LeoFrame.Icon area convenience methods
864 def addIconButton(self, *args, **keys):
865 if self.iconBar:
866 return self.iconBar.add(*args, **keys)
867 return None
869 def addIconRow(self):
870 if self.iconBar:
871 return self.iconBar.addRow()
872 return None
874 def addIconWidget(self, w):
875 if self.iconBar:
876 return self.iconBar.addWidget(w)
877 return None
879 def clearIconBar(self):
880 if self.iconBar:
881 return self.iconBar.clear()
882 return None
884 def createIconBar(self):
885 c = self.c
886 if not self.iconBar:
887 self.iconBar = self.iconBarClass(c, self.outerFrame)
888 return self.iconBar
890 def getIconBar(self):
891 if not self.iconBar:
892 self.iconBar = self.iconBarClass(self.c, self.outerFrame)
893 return self.iconBar
895 getIconBarObject = getIconBar
897 def getNewIconFrame(self):
898 if not self.iconBar:
899 self.iconBar = self.iconBarClass(self.c, self.outerFrame)
900 return self.iconBar.getNewFrame()
902 def hideIconBar(self):
903 if self.iconBar:
904 self.iconBar.hide()
906 def showIconBar(self):
907 if self.iconBar:
908 self.iconBar.show()
909 #@+node:ekr.20041223105114.1: *4* LeoFrame.Status line convenience methods
910 def createStatusLine(self):
911 if not self.statusLine:
912 self.statusLine = self.statusLineClass(self.c, self.outerFrame) # type:ignore
913 return self.statusLine
915 def clearStatusLine(self):
916 if self.statusLine:
917 self.statusLine.clear()
919 def disableStatusLine(self, background=None):
920 if self.statusLine:
921 self.statusLine.disable(background)
923 def enableStatusLine(self, background="white"):
924 if self.statusLine:
925 self.statusLine.enable(background)
927 def getStatusLine(self):
928 return self.statusLine
930 getStatusObject = getStatusLine
932 def putStatusLine(self, s, bg=None, fg=None):
933 if self.statusLine:
934 self.statusLine.put(s, bg, fg)
936 def setFocusStatusLine(self):
937 if self.statusLine:
938 self.statusLine.setFocus()
940 def statusLineIsEnabled(self):
941 if self.statusLine:
942 return self.statusLine.isEnabled()
943 return False
945 def updateStatusLine(self):
946 if self.statusLine:
947 self.statusLine.update()
948 #@+node:ekr.20070130115927.4: *4* LeoFrame.Cut/Copy/Paste
949 #@+node:ekr.20070130115927.5: *5* LeoFrame.copyText
950 @frame_cmd('copy-text')
951 def copyText(self, event=None):
952 """Copy the selected text from the widget to the clipboard."""
953 # f = self
954 w = event and event.widget
955 # wname = c.widget_name(w)
956 if not w or not g.isTextWrapper(w):
957 return
958 # Set the clipboard text.
959 i, j = w.getSelectionRange()
960 if i == j:
961 ins = w.getInsertPoint()
962 i, j = g.getLine(w.getAllText(), ins)
963 # 2016/03/27: Fix a recent buglet.
964 # Don't clear the clipboard if we hit ctrl-c by mistake.
965 s = w.get(i, j)
966 if s:
967 g.app.gui.replaceClipboardWith(s)
969 OnCopyFromMenu = copyText
970 #@+node:ekr.20070130115927.6: *5* LeoFrame.cutText
971 @frame_cmd('cut-text')
972 def cutText(self, event=None):
973 """Invoked from the mini-buffer and from shortcuts."""
974 c, p, u = self.c, self.c.p, self.c.undoer
975 w = event and event.widget
976 if not w or not g.isTextWrapper(w):
977 return
978 bunch = u.beforeChangeBody(p)
979 name = c.widget_name(w)
980 oldText = w.getAllText()
981 i, j = w.getSelectionRange()
982 # Update the widget and set the clipboard text.
983 s = w.get(i, j)
984 if i != j:
985 w.delete(i, j)
986 w.see(i) # 2016/01/19: important
987 g.app.gui.replaceClipboardWith(s)
988 else:
989 ins = w.getInsertPoint()
990 i, j = g.getLine(oldText, ins)
991 s = w.get(i, j)
992 w.delete(i, j)
993 w.see(i) # 2016/01/19: important
994 g.app.gui.replaceClipboardWith(s)
995 if name.startswith('body'):
996 p.v.b = w.getAllText()
997 u.afterChangeBody(p, 'Cut', bunch)
998 elif name.startswith('head'):
999 # The headline is not officially changed yet.
1000 s = w.getAllText()
1001 else:
1002 pass
1004 OnCutFromMenu = cutText
1005 #@+node:ekr.20070130115927.7: *5* LeoFrame.pasteText
1006 @frame_cmd('paste-text')
1007 def pasteText(self, event=None, middleButton=False):
1008 """
1009 Paste the clipboard into a widget.
1010 If middleButton is True, support x-windows middle-mouse-button easter-egg.
1011 """
1012 c, p, u = self.c, self.c.p, self.c.undoer
1013 w = event and event.widget
1014 wname = c.widget_name(w)
1015 if not w or not g.isTextWrapper(w):
1016 return
1017 bunch = u.beforeChangeBody(p)
1018 if self.cursorStay and wname.startswith('body'):
1019 tCurPosition = w.getInsertPoint()
1020 i, j = w.getSelectionRange()
1021 # Returns insert point if no selection.
1022 if middleButton and c.k.previousSelection is not None:
1023 start, end = c.k.previousSelection
1024 s = w.getAllText()
1025 s = s[start:end]
1026 c.k.previousSelection = None
1027 else:
1028 s = g.app.gui.getTextFromClipboard()
1029 s = g.checkUnicode(s)
1030 singleLine = wname.startswith('head') or wname.startswith('minibuffer')
1031 if singleLine:
1032 # Strip trailing newlines so the truncation doesn't cause confusion.
1033 while s and s[-1] in ('\n', '\r'):
1034 s = s[:-1]
1035 # Save the horizontal scroll position.
1036 if hasattr(w, 'getXScrollPosition'):
1037 x_pos = w.getXScrollPosition()
1038 # Update the widget.
1039 if i != j:
1040 w.delete(i, j)
1041 w.insert(i, s)
1042 w.see(i + len(s) + 2)
1043 if wname.startswith('body'):
1044 if self.cursorStay:
1045 if tCurPosition == j:
1046 offset = len(s) - (j - i)
1047 else:
1048 offset = 0
1049 newCurPosition = tCurPosition + offset
1050 w.setSelectionRange(i=newCurPosition, j=newCurPosition)
1051 p.v.b = w.getAllText()
1052 u.afterChangeBody(p, 'Paste', bunch)
1053 elif singleLine:
1054 s = w.getAllText()
1055 while s and s[-1] in ('\n', '\r'):
1056 s = s[:-1]
1057 else:
1058 pass
1059 # Never scroll horizontally.
1060 if hasattr(w, 'getXScrollPosition'):
1061 w.setXScrollPosition(x_pos)
1063 OnPasteFromMenu = pasteText
1064 #@+node:ekr.20061016071937: *5* LeoFrame.OnPaste (support middle-button paste)
1065 def OnPaste(self, event=None):
1066 return self.pasteText(event=event, middleButton=True)
1067 #@+node:ekr.20031218072017.3980: *4* LeoFrame.Edit Menu
1068 #@+node:ekr.20031218072017.3982: *5* LeoFrame.endEditLabelCommand
1069 @frame_cmd('end-edit-headline')
1070 def endEditLabelCommand(self, event=None, p=None):
1071 """End editing of a headline and move focus to the body pane."""
1072 frame = self
1073 c = frame.c
1074 k = c.k
1075 if g.app.batchMode:
1076 c.notValidInBatchMode("End Edit Headline")
1077 return
1078 w = event and event.w or c.get_focus() # #1413.
1079 w_name = g.app.gui.widget_name(w)
1080 if w_name.startswith('head'):
1081 c.endEditing()
1082 c.treeWantsFocus()
1083 else:
1084 c.bodyWantsFocus()
1085 k.setDefaultInputState()
1086 k.showStateAndMode(w=c.frame.body.wrapper)
1087 # Recolor the *body* text, **not** the headline.
1088 #@+node:ekr.20031218072017.3680: *3* LeoFrame.Must be defined in subclasses
1089 def bringToFront(self):
1090 self.oops()
1092 def cascade(self, event=None):
1093 self.oops()
1095 def contractBodyPane(self, event=None):
1096 self.oops()
1098 def contractLogPane(self, event=None):
1099 self.oops()
1101 def contractOutlinePane(self, event=None):
1102 self.oops()
1104 def contractPane(self, event=None):
1105 self.oops()
1107 def deiconify(self):
1108 self.oops()
1110 def equalSizedPanes(self, event=None):
1111 self.oops()
1113 def expandBodyPane(self, event=None):
1114 self.oops()
1116 def expandLogPane(self, event=None):
1117 self.oops()
1119 def expandOutlinePane(self, event=None):
1120 self.oops()
1122 def expandPane(self, event=None):
1123 self.oops()
1125 def fullyExpandBodyPane(self, event=None):
1126 self.oops()
1128 def fullyExpandLogPane(self, event=None):
1129 self.oops()
1131 def fullyExpandOutlinePane(self, event=None):
1132 self.oops()
1134 def fullyExpandPane(self, event=None):
1135 self.oops()
1137 def get_window_info(self):
1138 self.oops()
1140 def hideBodyPane(self, event=None):
1141 self.oops()
1143 def hideLogPane(self, event=None):
1144 self.oops()
1146 def hideLogWindow(self, event=None):
1147 self.oops()
1149 def hideOutlinePane(self, event=None):
1150 self.oops()
1152 def hidePane(self, event=None):
1153 self.oops()
1155 def leoHelp(self, event=None):
1156 self.oops()
1158 def lift(self):
1159 self.oops()
1161 def minimizeAll(self, event=None):
1162 self.oops()
1164 def resizePanesToRatio(self, ratio, secondary_ratio):
1165 self.oops()
1167 def resizeToScreen(self, event=None):
1168 self.oops()
1170 def setInitialWindowGeometry(self):
1171 self.oops()
1173 def setTopGeometry(self, w, h, x, y):
1174 self.oops()
1176 def toggleActivePane(self, event=None):
1177 self.oops()
1179 def toggleSplitDirection(self, event=None):
1180 self.oops()
1181 #@-others
1182#@+node:ekr.20031218072017.3694: ** class LeoLog
1183class LeoLog:
1184 """The base class for the log pane in Leo windows."""
1185 #@+others
1186 #@+node:ekr.20150509054436.1: *3* LeoLog.Birth
1187 #@+node:ekr.20031218072017.3695: *4* LeoLog.ctor
1188 def __init__(self, frame, parentFrame):
1189 """Ctor for LeoLog class."""
1190 self.frame = frame
1191 self.c = frame.c if frame else None
1192 self.enabled = True
1193 self.newlines = 0
1194 self.isNull = False
1195 # Official ivars...
1196 self.canvasCtrl = None # Set below. Same as self.canvasDict.get(self.tabName)
1197 self.logCtrl = None # Set below. Same as self.textDict.get(self.tabName)
1198 # Important: depeding on the log *tab*,
1199 # logCtrl may be either a wrapper or a widget.
1200 self.tabName = None # The name of the active tab.
1201 self.tabFrame = None # Same as self.frameDict.get(self.tabName)
1202 self.canvasDict = {} # Keys are page names. Values are Tk.Canvas's.
1203 self.frameDict = {} # Keys are page names. Values are Tk.Frames.
1204 self.logNumber = 0 # To create unique name fields for text widgets.
1205 self.newTabCount = 0 # Number of new tabs created.
1206 self.textDict = {} # Keys are page names. Values are logCtrl's (text widgets).
1207 #@+node:ekr.20070302094848.1: *3* LeoLog.clearTab
1208 def clearTab(self, tabName, wrap='none'):
1209 self.selectTab(tabName, wrap=wrap)
1210 w = self.logCtrl
1211 if w:
1212 w.delete(0, 'end')
1213 #@+node:ekr.20070302094848.2: *3* LeoLog.createTab
1214 def createTab(self, tabName, createText=True, widget=None, wrap='none'):
1215 if createText:
1216 w = self.createTextWidget(self.tabFrame)
1217 self.canvasDict[tabName] = None
1218 self.textDict[tabName] = w
1219 else:
1220 self.canvasDict[tabName] = None
1221 self.textDict[tabName] = None
1222 self.frameDict[tabName] = tabName # tabFrame
1223 #@+node:ekr.20140903143741.18550: *3* LeoLog.LeoLog.createTextWidget
1224 def createTextWidget(self, parentFrame):
1225 return None
1226 #@+node:ekr.20070302094848.5: *3* LeoLog.deleteTab
1227 def deleteTab(self, tabName):
1228 c = self.c
1229 if tabName == 'Log':
1230 pass
1231 elif tabName in ('Find', 'Spell'):
1232 self.selectTab('Log')
1233 else:
1234 for d in (self.canvasDict, self.textDict, self.frameDict):
1235 if tabName in d:
1236 del d[tabName]
1237 self.tabName = None
1238 self.selectTab('Log')
1239 c.invalidateFocus()
1240 c.bodyWantsFocus()
1241 #@+node:ekr.20140903143741.18549: *3* LeoLog.enable/disable
1242 def disable(self):
1243 self.enabled = False
1245 def enable(self, enabled=True):
1246 self.enabled = enabled
1247 #@+node:ekr.20070302094848.7: *3* LeoLog.getSelectedTab
1248 def getSelectedTab(self):
1249 return self.tabName
1250 #@+node:ekr.20070302094848.6: *3* LeoLog.hideTab
1251 def hideTab(self, tabName):
1252 self.selectTab('Log')
1253 #@+node:ekr.20070302094848.8: *3* LeoLog.lower/raiseTab
1254 def lowerTab(self, tabName):
1255 self.c.invalidateFocus()
1256 self.c.bodyWantsFocus()
1258 def raiseTab(self, tabName):
1259 self.c.invalidateFocus()
1260 self.c.bodyWantsFocus()
1261 #@+node:ekr.20111122080923.10184: *3* LeoLog.orderedTabNames
1262 def orderedTabNames(self, LeoLog=None):
1263 return list(self.frameDict.values())
1264 #@+node:ekr.20070302094848.9: *3* LeoLog.numberOfVisibleTabs
1265 def numberOfVisibleTabs(self):
1266 return len([val for val in list(self.frameDict.values()) if val is not None])
1267 #@+node:ekr.20070302101304: *3* LeoLog.put & putnl
1268 # All output to the log stream eventually comes here.
1270 def put(self, s, color=None, tabName='Log', from_redirect=False, nodeLink=None):
1271 print(s)
1273 def putnl(self, tabName='Log'):
1274 pass # print ('')
1275 #@+node:ekr.20070302094848.10: *3* LeoLog.renameTab
1276 def renameTab(self, oldName, newName):
1277 pass
1278 #@+node:ekr.20070302094848.11: *3* LeoLog.selectTab
1279 def selectTab(self, tabName, createText=True, widget=None, wrap='none'): # widget unused.
1280 """Create the tab if necessary and make it active."""
1281 c = self.c
1282 tabFrame = self.frameDict.get(tabName)
1283 if not tabFrame:
1284 self.createTab(tabName, createText=createText)
1285 # Update the status vars.
1286 self.tabName = tabName
1287 self.canvasCtrl = self.canvasDict.get(tabName)
1288 self.logCtrl = self.textDict.get(tabName)
1289 self.tabFrame = self.frameDict.get(tabName)
1290 if 0:
1291 # Absolutely do not do this here!
1292 # It is a cause of the 'sticky focus' problem.
1293 c.widgetWantsFocusNow(self.logCtrl)
1294 return tabFrame
1295 #@-others
1296#@+node:ekr.20031218072017.3704: ** class LeoTree
1297class LeoTree:
1298 """The base class for the outline pane in Leo windows."""
1299 #@+others
1300 #@+node:ekr.20031218072017.3705: *3* LeoTree.__init__
1301 def __init__(self, frame):
1302 """Ctor for the LeoTree class."""
1303 self.frame = frame
1304 self.c = frame.c
1305 self.edit_text_dict = {}
1306 # New in 3.12: keys vnodes, values are edit_widgets.
1307 # New in 4.2: keys are vnodes, values are pairs (p,edit widgets).
1308 # "public" ivars: correspond to setters & getters.
1309 self.drag_p = None
1310 self.generation = 0
1311 # Leo 5.6: low-level vnode methods increment
1312 # this count whenever the tree changes.
1313 self.redrawCount = 0 # For traces
1314 self.use_chapters = False # May be overridden in subclasses.
1315 # Define these here to keep pylint happy.
1316 self.canvas = None
1317 #@+node:ekr.20081005065934.8: *3* LeoTree.May be defined in subclasses
1318 # These are new in Leo 4.6.
1320 def initAfterLoad(self):
1321 """Do late initialization. Called in g.openWithFileName after a successful load."""
1323 # Hints for optimization. The proper default is c.redraw()
1325 def redraw_after_contract(self, p):
1326 self.c.redraw()
1328 def redraw_after_expand(self, p):
1329 self.c.redraw()
1331 def redraw_after_head_changed(self):
1332 self.c.redraw()
1334 def redraw_after_icons_changed(self):
1335 self.c.redraw()
1337 def redraw_after_select(self, p=None):
1338 self.c.redraw()
1339 #@+node:ekr.20040803072955.91: *4* LeoTree.onHeadChanged
1340 # Tricky code: do not change without careful thought and testing.
1341 # Important: This code *is* used by the leoBridge module.
1342 def onHeadChanged(self, p, undoType='Typing'):
1343 """
1344 Officially change a headline.
1345 Set the old undo text to the previous revert point.
1346 """
1347 c, u, w = self.c, self.c.undoer, self.edit_widget(p)
1348 if not w:
1349 g.trace('no w')
1350 return
1351 ch = '\n' # We only report the final keystroke.
1352 s = w.getAllText()
1353 #@+<< truncate s if it has multiple lines >>
1354 #@+node:ekr.20040803072955.94: *5* << truncate s if it has multiple lines >>
1355 # Remove trailing newlines before warning of truncation.
1356 while s and s[-1] == '\n':
1357 s = s[:-1]
1358 # Warn if there are multiple lines.
1359 i = s.find('\n')
1360 if i > -1:
1361 g.warning("truncating headline to one line")
1362 s = s[:i]
1363 limit = 1000
1364 if len(s) > limit:
1365 g.warning("truncating headline to", limit, "characters")
1366 s = s[:limit]
1367 s = g.checkUnicode(s or '')
1368 #@-<< truncate s if it has multiple lines >>
1369 # Make the change official, but undo to the *old* revert point.
1370 changed = s != p.h
1371 if not changed:
1372 return # Leo 6.4: only call the hooks if the headline has actually changed.
1373 if g.doHook("headkey1", c=c, p=p, ch=ch, changed=changed):
1374 return # The hook claims to have handled the event.
1375 # Handle undo.
1376 undoData = u.beforeChangeHeadline(p)
1377 p.initHeadString(s) # change p.h *after* calling undoer's before method.
1378 if not c.changed:
1379 c.setChanged()
1380 # New in Leo 4.4.5: we must recolor the body because
1381 # the headline may contain directives.
1382 c.frame.scanForTabWidth(p)
1383 c.frame.body.recolor(p)
1384 p.setDirty()
1385 u.afterChangeHeadline(p, undoType, undoData)
1386 c.redraw_after_head_changed()
1387 # Fix bug 1280689: don't call the non-existent c.treeEditFocusHelper
1388 g.doHook("headkey2", c=c, p=p, ch=ch, changed=changed)
1389 #@+node:ekr.20061109165848: *3* LeoTree.Must be defined in base class
1390 #@+node:ekr.20040803072955.126: *4* LeoTree.endEditLabel
1391 def endEditLabel(self):
1392 """End editing of a headline and update p.h."""
1393 # Important: this will redraw if necessary.
1394 self.onHeadChanged(self.c.p)
1395 # Do *not* call setDefaultUnboundKeyAction here: it might put us in ignore mode!
1396 # k.setDefaultInputState()
1397 # k.showStateAndMode()
1398 # This interferes with the find command and interferes with focus generally!
1399 # c.bodyWantsFocus()
1400 #@+node:ekr.20031218072017.3716: *4* LeoTree.getEditTextDict
1401 def getEditTextDict(self, v):
1402 # New in 4.2: the default is an empty list.
1403 return self.edit_text_dict.get(v, [])
1404 #@+node:ekr.20040803072955.88: *4* LeoTree.onHeadlineKey
1405 def onHeadlineKey(self, event):
1406 """Handle a key event in a headline."""
1407 w = event.widget if event else None
1408 ch = event.char if event else ''
1409 # This test prevents flashing in the headline when the control key is held down.
1410 if ch:
1411 self.updateHead(event, w)
1412 #@+node:ekr.20120314064059.9739: *4* LeoTree.OnIconCtrlClick (@url)
1413 def OnIconCtrlClick(self, p):
1414 g.openUrl(p)
1415 #@+node:ekr.20031218072017.2312: *4* LeoTree.OnIconDoubleClick (do nothing)
1416 def OnIconDoubleClick(self, p):
1417 pass
1418 #@+node:ekr.20051026083544.2: *4* LeoTree.updateHead
1419 def updateHead(self, event, w):
1420 """
1421 Update a headline from an event.
1423 The headline officially changes only when editing ends.
1424 """
1425 k = self.c.k
1426 ch = event.char if event else ''
1427 i, j = w.getSelectionRange()
1428 ins = w.getInsertPoint()
1429 if i != j:
1430 ins = i
1431 if ch in ('\b', 'BackSpace'):
1432 if i != j:
1433 w.delete(i, j)
1434 # Bug fix: 2018/04/19.
1435 w.setSelectionRange(i, i, insert=i)
1436 elif i > 0:
1437 i -= 1
1438 w.delete(i)
1439 w.setSelectionRange(i, i, insert=i)
1440 else:
1441 w.setSelectionRange(0, 0, insert=0)
1442 elif ch and ch not in ('\n', '\r'):
1443 if i != j:
1444 w.delete(i, j)
1445 elif k.unboundKeyAction == 'overwrite':
1446 w.delete(i, i + 1)
1447 w.insert(ins, ch)
1448 w.setSelectionRange(ins + 1, ins + 1, insert=ins + 1)
1449 s = w.getAllText()
1450 if s.endswith('\n'):
1451 s = s[:-1]
1452 # 2011/11/14: Not used at present.
1453 # w.setWidth(self.headWidth(s=s))
1454 if ch in ('\n', '\r'):
1455 self.endEditLabel()
1456 #@+node:ekr.20031218072017.3706: *3* LeoTree.Must be defined in subclasses
1457 # Drawing & scrolling.
1459 def drawIcon(self, p):
1460 self.oops()
1462 def redraw(self, p=None):
1463 self.oops()
1464 redraw_now = redraw
1466 def scrollTo(self, p):
1467 self.oops()
1469 # Headlines.
1471 def editLabel(self, p, selectAll=False, selection=None):
1472 self.oops()
1474 def edit_widget(self, p):
1475 self.oops()
1476 #@+node:ekr.20040803072955.128: *3* LeoTree.select & helpers
1477 tree_select_lockout = False
1479 def select(self, p):
1480 """
1481 Select a node.
1482 Never redraws outline, but may change coloring of individual headlines.
1483 The scroll argument is used by the gui to suppress scrolling while dragging.
1484 """
1485 trace = 'select' in g.app.debug and not g.unitTesting
1486 tag = 'LeoTree.select'
1487 c = self.c
1488 if g.app.killed or self.tree_select_lockout: # Essential.
1489 return
1490 if trace:
1491 print(f"----- {tag}: {p.h}")
1492 # print(f"{tag:>30}: {c.frame.body.wrapper} {p.h}")
1493 # Format matches traces in leoflexx.py
1494 # print(f"{tag:30}: {len(p.b):4} {p.gnx} {p.h}")
1495 try:
1496 self.tree_select_lockout = True
1497 self.prev_v = c.p.v
1498 self.selectHelper(p)
1499 finally:
1500 self.tree_select_lockout = False
1501 if c.enableRedrawFlag:
1502 p = c.p
1503 # Don't redraw during unit testing: an important speedup.
1504 if c.expandAllAncestors(p) and not g.unitTesting:
1505 # This can happen when doing goto-next-clone.
1506 c.redraw_later()
1507 # This *does* happen sometimes.
1508 else:
1509 c.outerUpdate() # Bring the tree up to date.
1510 if hasattr(self, 'setItemForCurrentPosition'):
1511 # pylint: disable=no-member
1512 self.setItemForCurrentPosition() # type:ignore
1513 else:
1514 c.requestLaterRedraw = True
1515 #@+node:ekr.20070423101911: *4* LeoTree.selectHelper & helpers
1516 def selectHelper(self, p):
1517 """
1518 A helper function for leoTree.select.
1519 Do **not** "optimize" this by returning if p==c.p!
1520 """
1521 if not p:
1522 # This is not an error! We may be changing roots.
1523 # Do *not* test c.positionExists(p) here!
1524 return
1525 c = self.c
1526 if not c.frame.body.wrapper:
1527 return # Defensive.
1528 if p.v.context != c:
1529 # Selecting a foreign position will not be pretty.
1530 g.trace(f"Wrong context: {p.v.context!r} != {c!r}")
1531 return
1532 old_p = c.p
1533 call_event_handlers = p != old_p
1534 # Order is important...
1535 self.unselect_helper(old_p, p)
1536 # 1. Call c.endEditLabel.
1537 self.select_new_node(old_p, p)
1538 # 2. Call set_body_text_after_select.
1539 self.change_current_position(old_p, p)
1540 # 3. Call c.undoer.onSelect.
1541 self.scroll_cursor(p)
1542 # 4. Set cursor in body.
1543 self.set_status_line(p)
1544 # 5. Last tweaks.
1545 if call_event_handlers:
1546 g.doHook("select2", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p)
1547 g.doHook("select3", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p)
1548 #@+node:ekr.20140829053801.18453: *5* 1. LeoTree.unselect_helper
1549 def unselect_helper(self, old_p, p):
1550 """Unselect the old node, calling the unselect hooks."""
1551 c = self.c
1552 call_event_handlers = p != old_p
1553 if call_event_handlers:
1554 unselect = not g.doHook(
1555 "unselect1", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p)
1556 else:
1557 unselect = True
1559 # Actually unselect the old node.
1560 if unselect and old_p and old_p != p:
1561 self.endEditLabel()
1562 # #1168: Ctrl-minus selects multiple nodes.
1563 if hasattr(self, 'unselectItem'):
1564 # pylint: disable=no-member
1565 self.unselectItem(old_p) # type:ignore
1566 if call_event_handlers:
1567 g.doHook("unselect2", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p)
1568 #@+node:ekr.20140829053801.18455: *5* 2. LeoTree.select_new_node & helper
1569 def select_new_node(self, old_p, p):
1570 """Select the new node, part 1."""
1571 c = self.c
1572 call_event_handlers = p != old_p
1573 if (
1574 call_event_handlers and g.doHook("select1",
1575 c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p)
1576 ):
1577 if 'select' in g.app.debug:
1578 g.trace('select1 override')
1579 return
1580 c.frame.setWrap(p)
1581 # Not that expensive
1582 self.set_body_text_after_select(p, old_p)
1583 c.nodeHistory.update(p)
1584 #@+node:ekr.20090608081524.6109: *6* LeoTree.set_body_text_after_select
1585 def set_body_text_after_select(self, p, old_p):
1586 """Set the text after selecting a node."""
1587 c = self.c
1588 w = c.frame.body.wrapper
1589 s = p.v.b # Guaranteed to be unicode.
1590 # Part 1: get the old text.
1591 old_s = w.getAllText()
1592 if p and p == old_p and s == old_s:
1593 return
1594 # Part 2: set the new text. This forces a recolor.
1595 c.setCurrentPosition(p)
1596 # Important: do this *before* setting text,
1597 # so that the colorizer will have the proper c.p.
1598 w.setAllText(s)
1599 # This is now done after c.p has been changed.
1600 # p.restoreCursorAndScroll()
1601 #@+node:ekr.20140829053801.18458: *5* 3. LeoTree.change_current_position
1602 def change_current_position(self, old_p, p):
1603 """Select the new node, part 2."""
1604 c = self.c
1605 # c.setCurrentPosition(p)
1606 # This is now done in set_body_text_after_select.
1607 c.frame.scanForTabWidth(p)
1608 #GS I believe this should also get into the select1 hook
1609 use_chapters = c.config.getBool('use-chapters')
1610 if use_chapters:
1611 cc = c.chapterController
1612 theChapter = cc and cc.getSelectedChapter()
1613 if theChapter:
1614 theChapter.p = p.copy()
1615 # Do not call treeFocusHelper here!
1616 # c.treeFocusHelper()
1617 c.undoer.onSelect(old_p, p)
1618 #@+node:ekr.20140829053801.18459: *5* 4. LeoTree.scroll_cursor
1619 def scroll_cursor(self, p):
1620 """Scroll the cursor."""
1621 p.restoreCursorAndScroll()
1622 # Was in setBodyTextAfterSelect
1623 #@+node:ekr.20140829053801.18460: *5* 5. LeoTree.set_status_line
1624 def set_status_line(self, p):
1625 """Update the status line."""
1626 c = self.c
1627 c.frame.body.assignPositionToEditor(p)
1628 # New in Leo 4.4.1.
1629 c.frame.updateStatusLine()
1630 # New in Leo 4.4.1.
1631 c.frame.clearStatusLine()
1632 if p and p.v:
1633 c.frame.putStatusLine(p.get_UNL())
1634 #@+node:ekr.20031218072017.3718: *3* LeoTree.oops
1635 def oops(self):
1636 g.pr("LeoTree oops:", g.callers(4), "should be overridden in subclass")
1637 #@-others
1638#@+node:ekr.20070317073627: ** class LeoTreeTab
1639class LeoTreeTab:
1640 """A class representing a tabbed outline pane."""
1641 #@+others
1642 #@+node:ekr.20070317073627.1: *3* ctor (LeoTreeTab)
1643 def __init__(self, c, chapterController, parentFrame):
1644 self.c = c
1645 self.cc = chapterController
1646 self.nb = None # Created in createControl.
1647 self.parentFrame = parentFrame
1648 #@+node:ekr.20070317073755: *3* Must be defined in subclasses
1649 def createControl(self):
1650 self.oops()
1652 def createTab(self, tabName, select=True):
1653 self.oops()
1655 def destroyTab(self, tabName):
1656 self.oops()
1658 def selectTab(self, tabName):
1659 self.oops()
1661 def setTabLabel(self, tabName):
1662 self.oops()
1663 #@+node:ekr.20070317083104: *3* oops
1664 def oops(self):
1665 g.pr("LeoTreeTree oops:", g.callers(4), "should be overridden in subclass")
1666 #@-others
1667#@+node:ekr.20031218072017.2191: ** class NullBody (LeoBody)
1668class NullBody(LeoBody):
1669 """A do-nothing body class."""
1670 #@+others
1671 #@+node:ekr.20031218072017.2192: *3* NullBody.__init__
1672 def __init__(self, frame=None, parentFrame=None):
1673 """Ctor for NullBody class."""
1674 super().__init__(frame, parentFrame)
1675 self.insertPoint = 0
1676 self.selection = 0, 0
1677 self.s = "" # The body text
1678 self.widget = None
1679 self.wrapper = wrapper = StringTextWrapper(c=self.c, name='body')
1680 self.editorWrappers['1'] = wrapper
1681 self.colorizer = NullColorizer(self.c)
1682 #@+node:ekr.20031218072017.2197: *3* NullBody: LeoBody interface
1683 # Birth, death...
1685 def createControl(self, parentFrame, p):
1686 pass
1687 # Editors...
1689 def addEditor(self, event=None):
1690 pass
1692 def assignPositionToEditor(self, p):
1693 pass
1695 def createEditorFrame(self, w):
1696 return None
1698 def cycleEditorFocus(self, event=None):
1699 pass
1701 def deleteEditor(self, event=None):
1702 pass
1704 def selectEditor(self, w):
1705 pass
1707 def selectLabel(self, w):
1708 pass
1710 def setEditorColors(self, bg, fg):
1711 pass
1713 def unselectLabel(self, w):
1714 pass
1716 def updateEditors(self):
1717 pass
1718 # Events...
1720 def forceFullRecolor(self):
1721 pass
1723 def scheduleIdleTimeRoutine(self, function, *args, **keys):
1724 pass
1725 # Low-level gui...
1727 def setFocus(self):
1728 pass
1729 #@-others
1730#@+node:ekr.20031218072017.2218: ** class NullColorizer (BaseColorizer)
1731class NullColorizer(leoColorizer.BaseColorizer):
1732 """A colorizer class that doesn't color."""
1734 recolorCount = 0
1736 def colorize(self, p):
1737 self.recolorCount += 1
1738 # For #503: Use string/null gui for unit tests
1739#@+node:ekr.20031218072017.2222: ** class NullFrame (LeoFrame)
1740class NullFrame(LeoFrame):
1741 """A null frame class for tests and batch execution."""
1742 #@+others
1743 #@+node:ekr.20040327105706: *3* NullFrame.ctor
1744 def __init__(self, c, title, gui):
1745 """Ctor for the NullFrame class."""
1746 super().__init__(c, gui)
1747 assert self.c
1748 self.wrapper = None
1749 self.iconBar = NullIconBarClass(self.c, self)
1750 self.initComplete = True
1751 self.isNullFrame = True
1752 self.outerFrame = None
1753 self.ratio = self.secondary_ratio = 0.5
1754 self.statusLineClass = NullStatusLineClass
1755 self.title = title
1756 self.top = None # Always None.
1757 # Create the component objects.
1758 self.body = NullBody(frame=self, parentFrame=None)
1759 self.log = NullLog(frame=self, parentFrame=None)
1760 self.menu = leoMenu.NullMenu(frame=self)
1761 self.tree = NullTree(frame=self)
1762 # Default window position.
1763 self.w = 600
1764 self.h = 500
1765 self.x = 40
1766 self.y = 40
1767 #@+node:ekr.20061109124552: *3* NullFrame.do nothings
1768 def bringToFront(self):
1769 pass
1771 def cascade(self, event=None):
1772 pass
1774 def contractBodyPane(self, event=None):
1775 pass
1777 def contractLogPane(self, event=None):
1778 pass
1780 def contractOutlinePane(self, event=None):
1781 pass
1783 def contractPane(self, event=None):
1784 pass
1786 def deiconify(self):
1787 pass
1789 def destroySelf(self):
1790 pass
1792 def equalSizedPanes(self, event=None):
1793 pass
1795 def expandBodyPane(self, event=None):
1796 pass
1798 def expandLogPane(self, event=None):
1799 pass
1801 def expandOutlinePane(self, event=None):
1802 pass
1804 def expandPane(self, event=None):
1805 pass
1807 def forceWrap(self, p):
1808 pass
1810 def fullyExpandBodyPane(self, event=None):
1811 pass
1813 def fullyExpandLogPane(self, event=None):
1814 pass
1816 def fullyExpandOutlinePane(self, event=None):
1817 pass
1819 def fullyExpandPane(self, event=None):
1820 pass
1822 def get_window_info(self):
1823 return 600, 500, 20, 20
1825 def hideBodyPane(self, event=None):
1826 pass
1828 def hideLogPane(self, event=None):
1829 pass
1831 def hideLogWindow(self, event=None):
1832 pass
1834 def hideOutlinePane(self, event=None):
1835 pass
1837 def hidePane(self, event=None):
1838 pass
1840 def leoHelp(self, event=None):
1841 pass
1843 def lift(self):
1844 pass
1846 def minimizeAll(self, event=None):
1847 pass
1849 def oops(self):
1850 g.trace("NullFrame", g.callers(4))
1852 def resizePanesToRatio(self, ratio, secondary_ratio):
1853 pass
1855 def resizeToScreen(self, event=None):
1856 pass
1858 def setInitialWindowGeometry(self):
1859 pass
1861 def setTopGeometry(self, w, h, x, y):
1862 return 0, 0, 0, 0
1864 def setWrap(self, flag, force=False):
1865 pass
1867 def toggleActivePane(self, event=None):
1868 pass
1870 def toggleSplitDirection(self, event=None):
1871 pass
1873 def update(self):
1874 pass
1875 #@+node:ekr.20171112115045.1: *3* NullFrame.finishCreate
1876 def finishCreate(self):
1878 # 2017/11/12: For #503: Use string/null gui for unit tests.
1879 self.createFirstTreeNode()
1880 # Call the base LeoFrame method.
1881 #@-others
1882#@+node:ekr.20070301164543: ** class NullIconBarClass
1883class NullIconBarClass:
1884 """A class representing the singleton Icon bar"""
1885 #@+others
1886 #@+node:ekr.20070301164543.1: *3* NullIconBarClass.ctor
1887 def __init__(self, c, parentFrame):
1888 """Ctor for NullIconBarClass."""
1889 self.c = c
1890 self.iconFrame = None
1891 self.parentFrame = parentFrame
1892 self.w = g.NullObject()
1893 #@+node:ekr.20070301165343: *3* NullIconBarClass.Do nothing
1894 def addRow(self, height=None):
1895 pass
1897 def addRowIfNeeded(self):
1898 pass
1900 def addWidget(self, w):
1901 pass
1903 def createChaptersIcon(self):
1904 pass
1906 def deleteButton(self, w):
1907 pass
1909 def getNewFrame(self):
1910 return None
1912 def hide(self):
1913 pass
1915 def show(self):
1916 pass
1917 #@+node:ekr.20070301164543.2: *3* NullIconBarClass.add
1918 def add(self, *args, **keys):
1919 """Add a (virtual) button to the (virtual) icon bar."""
1920 command = keys.get('command')
1921 text = keys.get('text')
1922 try:
1923 g.app.iconWidgetCount += 1
1924 except Exception:
1925 g.app.iconWidgetCount = 1
1926 n = g.app.iconWidgetCount
1927 name = f"nullButtonWidget {n}"
1928 if not command:
1930 def commandCallback(name=name):
1931 g.pr(f"command for {name}")
1933 command = commandCallback
1936 class nullButtonWidget:
1938 def __init__(self, c, command, name, text):
1939 self.c = c
1940 self.command = command
1941 self.name = name
1942 self.text = text
1944 def __repr__(self):
1945 return self.name
1947 b = nullButtonWidget(self.c, command, name, text)
1948 return b
1949 #@+node:ekr.20140904043623.18574: *3* NullIconBarClass.clear
1950 def clear(self):
1951 g.app.iconWidgetCount = 0
1952 g.app.iconImageRefs = []
1953 #@+node:ekr.20140904043623.18575: *3* NullIconBarClass.setCommandForButton
1954 def setCommandForButton(self, button, command, command_p, controller, gnx, script):
1955 button.command = command
1956 try:
1957 # See PR #2441: Add rclick support.
1958 from leo.plugins.mod_scripting import build_rclick_tree
1959 rclicks = build_rclick_tree(command_p, top_level=True)
1960 button.rclicks = rclicks
1961 except Exception:
1962 pass
1963 #@-others
1964#@+node:ekr.20031218072017.2232: ** class NullLog (LeoLog)
1965class NullLog(LeoLog):
1966 """A do-nothing log class."""
1967 #@+others
1968 #@+node:ekr.20070302095500: *3* NullLog.Birth
1969 #@+node:ekr.20041012083237: *4* NullLog.__init__
1970 def __init__(self, frame=None, parentFrame=None):
1972 super().__init__(frame, parentFrame)
1973 self.isNull = True
1974 self.logNumber = 0
1975 self.widget = self.createControl(parentFrame)
1976 # self.logCtrl is now a property of the base LeoLog class.
1977 #@+node:ekr.20120216123546.10951: *4* NullLog.finishCreate
1978 def finishCreate(self):
1979 pass
1980 #@+node:ekr.20041012083237.1: *4* NullLog.createControl
1981 def createControl(self, parentFrame):
1982 return self.createTextWidget(parentFrame)
1983 #@+node:ekr.20070302095121: *4* NullLog.createTextWidge
1984 def createTextWidget(self, parentFrame):
1985 self.logNumber += 1
1986 c = self.c
1987 log = StringTextWrapper(c=c, name=f"log-{self.logNumber}")
1988 return log
1989 #@+node:ekr.20181119135041.1: *3* NullLog.hasSelection
1990 def hasSelection(self):
1991 return self.widget.hasSelection()
1992 #@+node:ekr.20111119145033.10186: *3* NullLog.isLogWidget
1993 def isLogWidget(self, w):
1994 return False
1995 #@+node:ekr.20041012083237.2: *3* NullLog.oops
1996 def oops(self):
1997 g.trace("NullLog:", g.callers(4))
1998 #@+node:ekr.20041012083237.3: *3* NullLog.put and putnl
1999 def put(self, s, color=None, tabName='Log', from_redirect=False, nodeLink=None):
2000 # print('(nullGui) print',repr(s))
2001 if self.enabled:
2002 try:
2003 g.pr(s, newline=False)
2004 except UnicodeError:
2005 s = s.encode('ascii', 'replace')
2006 g.pr(s, newline=False)
2008 def putnl(self, tabName='Log'):
2009 if self.enabled:
2010 g.pr('')
2011 #@+node:ekr.20060124085830: *3* NullLog.tabs
2012 def clearTab(self, tabName, wrap='none'):
2013 pass
2015 def createCanvas(self, tabName):
2016 pass
2018 def createTab(self, tabName, createText=True, widget=None, wrap='none'):
2019 pass
2021 def deleteTab(self, tabName):
2022 pass
2024 def getSelectedTab(self):
2025 return None
2027 def lowerTab(self, tabName):
2028 pass
2030 def raiseTab(self, tabName):
2031 pass
2033 def renameTab(self, oldName, newName):
2034 pass
2036 def selectTab(self, tabName, createText=True, widget=None, wrap='none'):
2037 pass
2038 #@-others
2039#@+node:ekr.20070302171509: ** class NullStatusLineClass
2040class NullStatusLineClass:
2041 """A do-nothing status line."""
2043 def __init__(self, c, parentFrame):
2044 """Ctor for NullStatusLine class."""
2045 self.c = c
2046 self.enabled = False
2047 self.parentFrame = parentFrame
2048 self.textWidget = StringTextWrapper(c, name='status-line')
2049 # Set the official ivars.
2050 c.frame.statusFrame = None
2051 c.frame.statusLabel = None
2052 c.frame.statusText = self.textWidget
2053 #@+others
2054 #@+node:ekr.20070302171917: *3* NullStatusLineClass.methods
2055 def disable(self, background=None):
2056 self.enabled = False
2057 # self.c.bodyWantsFocus()
2059 def enable(self, background="white"):
2060 self.c.widgetWantsFocus(self.textWidget)
2061 self.enabled = True
2063 def clear(self):
2064 self.textWidget.delete(0, 'end')
2066 def get(self):
2067 return self.textWidget.getAllText()
2069 def isEnabled(self):
2070 return self.enabled
2072 def put(self, s, bg=None, fg=None):
2073 self.textWidget.insert('end', s)
2075 def setFocus(self):
2076 pass
2078 def update(self):
2079 pass
2080 #@-others
2081#@+node:ekr.20031218072017.2233: ** class NullTree (LeoTree)
2082class NullTree(LeoTree):
2083 """A do-almost-nothing tree class."""
2084 #@+others
2085 #@+node:ekr.20031218072017.2234: *3* NullTree.__init__
2086 def __init__(self, frame):
2087 """Ctor for NullTree class."""
2088 super().__init__(frame)
2089 assert self.frame
2090 self.c = frame.c
2091 self.editWidgetsDict = {} # Keys are vnodes, values are StringTextWidgets.
2092 self.font = None
2093 self.fontName = None
2094 self.canvas = None
2095 self.treeWidget = g.NullObject()
2096 self.redrawCount = 0
2097 self.updateCount = 0
2098 #@+node:ekr.20070228163350.2: *3* NullTree.edit_widget
2099 def edit_widget(self, p):
2100 d = self.editWidgetsDict
2101 if not p or not p.v:
2102 return None
2103 w = d.get(p.v)
2104 if not w:
2105 d[p.v] = w = StringTextWrapper(
2106 c=self.c,
2107 name=f"head-{1 + len(list(d.keys())):d}")
2108 w.setAllText(p.h)
2109 return w
2110 #@+node:ekr.20070228164730: *3* NullTree.editLabel
2111 def editLabel(self, p, selectAll=False, selection=None):
2112 """Start editing p's headline."""
2113 self.endEditLabel()
2114 if p:
2115 wrapper = StringTextWrapper(c=self.c, name='head-wrapper')
2116 e = None
2117 return e, wrapper
2118 return None, None
2119 #@+node:ekr.20070228173611: *3* NullTree.printWidgets
2120 def printWidgets(self):
2121 d = self.editWidgetsDict
2122 for key in d:
2123 # keys are vnodes, values are StringTextWidgets.
2124 w = d.get(key)
2125 g.pr('w', w, 'v.h:', key.headString, 's:', repr(w.s))
2126 #@+node:ekr.20070228163350.1: *3* NullTree.Drawing & scrolling
2127 def drawIcon(self, p):
2128 pass
2130 def redraw(self, p=None):
2131 self.redrawCount += 1
2132 return p
2133 # Support for #503: Use string/null gui for unit tests
2135 redraw_now = redraw
2137 def redraw_after_contract(self, p):
2138 self.redraw()
2140 def redraw_after_expand(self, p):
2141 self.redraw()
2143 def redraw_after_head_changed(self):
2144 self.redraw()
2146 def redraw_after_icons_changed(self):
2147 self.redraw()
2149 def redraw_after_select(self, p=None):
2150 self.redraw()
2152 def scrollTo(self, p):
2153 pass
2155 def updateAllIcons(self, p):
2156 pass
2158 def updateIcon(self, p):
2159 pass
2160 #@+node:ekr.20070228160345: *3* NullTree.setHeadline
2161 def setHeadline(self, p, s):
2162 """Set the actual text of the headline widget.
2164 This is called from the undo/redo logic to change the text before redrawing."""
2165 w = self.edit_widget(p)
2166 if w:
2167 w.delete(0, 'end')
2168 if s.endswith('\n') or s.endswith('\r'):
2169 s = s[:-1]
2170 w.insert(0, s)
2171 else:
2172 g.trace('-' * 20, 'oops')
2173 #@-others
2174#@+node:ekr.20070228074228.1: ** class StringTextWrapper
2175class StringTextWrapper:
2176 """A class that represents text as a Python string."""
2177 #@+others
2178 #@+node:ekr.20070228074228.2: *3* stw.ctor
2179 def __init__(self, c, name):
2180 """Ctor for the StringTextWrapper class."""
2181 self.c = c
2182 self.name = name
2183 self.ins = 0
2184 self.sel = 0, 0
2185 self.s = ''
2186 self.supportsHighLevelInterface = True
2187 self.virtualInsertPoint = 0
2188 self.widget = None # This ivar must exist, and be None.
2190 def __repr__(self):
2191 return f"<StringTextWrapper: {id(self)} {self.name}>"
2193 def getName(self):
2194 """StringTextWrapper."""
2195 return self.name # Essential.
2196 #@+node:ekr.20140903172510.18578: *3* stw.Clipboard
2197 def clipboard_clear(self):
2198 g.app.gui.replaceClipboardWith('')
2200 def clipboard_append(self, s):
2201 s1 = g.app.gui.getTextFromClipboard()
2202 g.app.gui.replaceClipboardWith(s1 + s)
2203 #@+node:ekr.20140903172510.18579: *3* stw.Do-nothings
2204 # For StringTextWrapper.
2206 def flashCharacter(self, i, bg='white', fg='red', flashes=3, delay=75):
2207 pass
2209 def getXScrollPosition(self):
2210 return 0
2212 def getYScrollPosition(self):
2213 return 0
2215 def see(self, i):
2216 pass
2218 def seeInsertPoint(self):
2219 pass
2221 def setFocus(self):
2222 pass
2224 def setStyleClass(self, name):
2225 pass
2227 def setXScrollPosition(self, i):
2228 pass
2230 def setYScrollPosition(self, i):
2231 pass
2233 def tag_configure(self, colorName, **keys):
2234 pass
2235 #@+node:ekr.20140903172510.18591: *3* stw.Text
2236 #@+node:ekr.20140903172510.18592: *4* stw.appendText
2237 def appendText(self, s):
2238 """StringTextWrapper."""
2239 self.s = self.s + g.toUnicode(s)
2240 # defensive
2241 self.ins = len(self.s)
2242 self.sel = self.ins, self.ins
2243 #@+node:ekr.20140903172510.18593: *4* stw.delete
2244 def delete(self, i, j=None):
2245 """StringTextWrapper."""
2246 i = self.toPythonIndex(i)
2247 if j is None:
2248 j = i + 1
2249 j = self.toPythonIndex(j)
2250 # This allows subclasses to use this base class method.
2251 if i > j:
2252 i, j = j, i
2253 s = self.getAllText()
2254 self.setAllText(s[:i] + s[j:])
2255 # Bug fix: 2011/11/13: Significant in external tests.
2256 self.setSelectionRange(i, i, insert=i)
2257 #@+node:ekr.20140903172510.18594: *4* stw.deleteTextSelection
2258 def deleteTextSelection(self):
2259 """StringTextWrapper."""
2260 i, j = self.getSelectionRange()
2261 self.delete(i, j)
2262 #@+node:ekr.20140903172510.18595: *4* stw.get
2263 def get(self, i, j=None):
2264 """StringTextWrapper."""
2265 i = self.toPythonIndex(i)
2266 if j is None:
2267 j = i + 1
2268 j = self.toPythonIndex(j)
2269 s = self.s[i:j]
2270 return g.toUnicode(s)
2271 #@+node:ekr.20140903172510.18596: *4* stw.getAllText
2272 def getAllText(self):
2273 """StringTextWrapper."""
2274 s = self.s
2275 return g.checkUnicode(s)
2276 #@+node:ekr.20140903172510.18584: *4* stw.getInsertPoint
2277 def getInsertPoint(self):
2278 """StringTextWrapper."""
2279 i = self.ins
2280 if i is None:
2281 if self.virtualInsertPoint is None:
2282 i = 0
2283 else:
2284 i = self.virtualInsertPoint
2285 self.virtualInsertPoint = i
2286 return i
2287 #@+node:ekr.20140903172510.18597: *4* stw.getSelectedText
2288 def getSelectedText(self):
2289 """StringTextWrapper."""
2290 i, j = self.sel
2291 s = self.s[i:j]
2292 return g.checkUnicode(s)
2293 #@+node:ekr.20140903172510.18585: *4* stw.getSelectionRange
2294 def getSelectionRange(self, sort=True):
2295 """Return the selected range of the widget."""
2296 sel = self.sel
2297 if len(sel) == 2 and sel[0] >= 0 and sel[1] >= 0:
2298 i, j = sel
2299 if sort and i > j:
2300 sel = j, i # Bug fix: 10/5/07
2301 return sel
2302 i = self.ins
2303 return i, i
2304 #@+node:ekr.20140903172510.18586: *4* stw.hasSelection
2305 def hasSelection(self):
2306 """StringTextWrapper."""
2307 i, j = self.getSelectionRange()
2308 return i != j
2309 #@+node:ekr.20140903172510.18598: *4* stw.insert
2310 def insert(self, i, s):
2311 """StringTextWrapper."""
2312 i = self.toPythonIndex(i)
2313 s1 = s
2314 self.s = self.s[:i] + s1 + self.s[i:]
2315 i += len(s1)
2316 self.ins = i
2317 self.sel = i, i
2318 #@+node:ekr.20140903172510.18589: *4* stw.selectAllText
2319 def selectAllText(self, insert=None):
2320 """StringTextWrapper."""
2321 self.setSelectionRange(0, 'end', insert=insert)
2322 #@+node:ekr.20140903172510.18600: *4* stw.setAllText
2323 def setAllText(self, s):
2324 """StringTextWrapper."""
2325 self.s = s
2326 i = len(self.s)
2327 self.ins = i
2328 self.sel = i, i
2329 #@+node:ekr.20140903172510.18587: *4* stw.setInsertPoint
2330 def setInsertPoint(self, pos, s=None):
2331 """StringTextWrapper."""
2332 i = self.toPythonIndex(pos)
2333 self.virtualInsertPoint = i
2334 self.ins = i
2335 self.sel = i, i
2336 #@+node:ekr.20070228111853: *4* stw.setSelectionRange
2337 def setSelectionRange(self, i, j, insert=None):
2338 """StringTextWrapper."""
2339 i, j = self.toPythonIndex(i), self.toPythonIndex(j)
2340 self.sel = i, j
2341 self.ins = j if insert is None else self.toPythonIndex(insert)
2342 #@+node:ekr.20140903172510.18581: *4* stw.toPythonIndex
2343 def toPythonIndex(self, index):
2344 """
2345 StringTextWrapper.toPythonIndex.
2347 Convert indices of the form 'end' or 'n1.n2' to integer indices into self.s.
2349 Unit tests *do* use non-integer indices, so removing this method would be tricky.
2350 """
2351 return g.toPythonIndex(self.s, index)
2352 #@+node:ekr.20140903172510.18582: *4* stw.toPythonIndexRowCol
2353 def toPythonIndexRowCol(self, index):
2354 """StringTextWrapper."""
2355 s = self.getAllText()
2356 i = self.toPythonIndex(index)
2357 row, col = g.convertPythonIndexToRowCol(s, i)
2358 return i, row, col
2359 #@-others
2360#@-others
2361#@@language python
2362#@@tabwidth -4
2363#@@pagewidth 70
2364#@-leo