Coverage for C:\Repos\leo-editor\leo\core\leoFrame.py: 57%

1613 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

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. 

5 

6These classes should be overridden to create frames for a particular gui. 

7""" 

8#@+<< imports >> 

9#@+node:ekr.20120219194520.10464: ** << imports >> (leoFrame) 

10import os 

11import re 

12import string 

13from typing import Any, Callable, Dict, List, Tuple 

14from typing import TYPE_CHECKING 

15from leo.core import leoGlobals as g 

16from leo.core import leoColorizer # NullColorizer is a subclass of ColorizerMixin 

17from leo.core import leoMenu 

18from leo.core import leoNodes 

19 

20#@-<< imports >> 

21#@+<< type aliases leoFrame >> 

22#@+node:ekr.20220415013957.1: ** << type aliases leoFrame >> 

23if TYPE_CHECKING: # Always False at runtime. 

24 from leo.core.leoCommands import Commands as Cmdr 

25 from leo.core.leoNodes import Position as Pos 

26 from leo.core.leoNodes import VNode 

27else: 

28 Cmdr = Pos = VNode = Any 

29Event = Any 

30Index = Any # For now, really Union[int, str], but that creates type-checking problems. 

31Widget = Any 

32Wrapper = Any 

33#@-<< type aliases leoFrame >> 

34#@+<< About handling events >> 

35#@+node:ekr.20031218072017.2410: ** << About handling events >> 

36#@+at Leo must handle events or commands that change the text in the outline 

37# or body panes. We must ensure that headline and body text corresponds 

38# to the VNode corresponding to presently selected outline, and vice 

39# versa. For example, when the user selects a new headline in the 

40# outline pane, we must ensure that: 

41# 

42# 1) All vnodes have up-to-date information and 

43# 

44# 2) the body pane is loaded with the correct data. 

45# 

46# Early versions of Leo attempted to satisfy these conditions when the user 

47# switched outline nodes. Such attempts never worked well; there were too many 

48# special cases. Later versions of Leo use a much more direct approach: every 

49# keystroke in the body pane updates the presently selected VNode immediately. 

50# 

51# The LeoTree class contains all the event handlers for the tree pane, and the 

52# LeoBody class contains the event handlers for the body pane. The following 

53# convenience methods exists: 

54# 

55# - body.updateBody & tree.updateBody: 

56# These are suprising complex. 

57# 

58# - body.bodyChanged & tree.headChanged: 

59# Called by commands throughout Leo's core that change the body or headline. 

60# These are thin wrappers for updateBody and updateTree. 

61#@-<< About handling events >> 

62#@+<< command decorators >> 

63#@+node:ekr.20150509054428.1: ** << command decorators >> (leoFrame.py) 

64def log_cmd(name: str) -> Callable: # Not used. 

65 """Command decorator for the LeoLog class.""" 

66 return g.new_cmd_decorator(name, ['c', 'frame', 'log']) 

67 

68def body_cmd(name: str) -> Callable: 

69 """Command decorator for the c.frame.body class.""" 

70 return g.new_cmd_decorator(name, ['c', 'frame', 'body']) 

71 

72def frame_cmd(name: str) -> Callable: 

73 """Command decorator for the LeoFrame class.""" 

74 return g.new_cmd_decorator(name, ['c', 'frame',]) 

75#@-<< command decorators >> 

76#@+others 

77#@+node:ekr.20140907201613.18660: ** API classes 

78# These classes are for documentation and unit testing. 

79# They are the base class for no class. 

80#@+node:ekr.20140904043623.18576: *3* class StatusLineAPI 

81class StatusLineAPI: 

82 """The required API for c.frame.statusLine.""" 

83 

84 def __init__(self, c: Cmdr, parentFrame: Widget) -> None: 

85 pass 

86 

87 def clear(self) -> None: 

88 pass 

89 

90 def disable(self, background: str=None) -> None: 

91 pass 

92 

93 def enable(self, background: str="white") -> None: 

94 pass 

95 

96 def get(self) -> str: 

97 return '' 

98 

99 def isEnabled(self) -> bool: 

100 return False 

101 

102 def put(self, s: str, bg: str=None, fg: str=None) -> None: 

103 pass 

104 

105 def setFocus(self) -> None: 

106 pass 

107 

108 def update(self) -> None: 

109 pass 

110#@+node:ekr.20140907201613.18663: *3* class TreeAPI 

111class TreeAPI: 

112 """The required API for c.frame.tree.""" 

113 

114 def __init__(self, frame: Widget) -> None: 

115 pass 

116 # Must be defined in subclasses. 

117 

118 def drawIcon(self, p: Pos) -> None: 

119 pass 

120 

121 def editLabel(self, v: VNode, selectAll: bool=False, selection: Any=None) -> None: 

122 pass 

123 

124 def edit_widget(self, p: Pos) -> None: 

125 return None 

126 

127 def redraw(self, p: Pos=None) -> None: 

128 pass 

129 redraw_now = redraw 

130 

131 def scrollTo(self, p: Pos) -> None: 

132 pass 

133 # May be defined in subclasses. 

134 

135 def initAfterLoad(self) -> None: 

136 pass 

137 

138 def onHeadChanged(self, p: Pos, undoType: str='Typing', s: str=None, e: str=None) -> None: 

139 pass 

140 # Hints for optimization. The proper default is c.redraw() 

141 

142 def redraw_after_contract(self, p: Pos) -> None: 

143 pass 

144 

145 def redraw_after_expand(self, p: Pos) -> None: 

146 pass 

147 

148 def redraw_after_head_changed(self) -> None: 

149 pass 

150 

151 def redraw_after_icons_changed(self) -> None: 

152 pass 

153 

154 def redraw_after_select(self, p: Pos=None) -> None: 

155 pass 

156 # Must be defined in the LeoTree class... 

157 # def OnIconDoubleClick (self,p): 

158 

159 def OnIconCtrlClick(self, p: Pos) -> None: 

160 pass 

161 

162 def endEditLabel(self) -> None: 

163 pass 

164 

165 def getEditTextDict(self, v: VNode) -> None: 

166 return None 

167 

168 def injectCallbacks(self) -> None: 

169 pass 

170 

171 def onHeadlineKey(self, event: Event) -> None: 

172 pass 

173 

174 def select(self, p: Pos) -> None: 

175 pass 

176 

177 def updateHead(self, event: Event, w: Wrapper) -> None: 

178 pass 

179#@+node:ekr.20140903025053.18631: *3* class WrapperAPI 

180class WrapperAPI: 

181 """A class specifying the wrapper api used throughout Leo's core.""" 

182 

183 def __init__(self, c: Cmdr) -> None: 

184 pass 

185 

186 def appendText(self, s: str) -> None: 

187 pass 

188 

189 def clipboard_append(self, s: str) -> None: 

190 pass 

191 

192 def clipboard_clear(self) -> None: 

193 pass 

194 

195 def delete(self, i: Index, j: Index=None) -> None: 

196 pass 

197 

198 def deleteTextSelection(self) -> None: 

199 pass 

200 

201 def disable(self) -> None: 

202 pass 

203 

204 def enable(self, enabled: bool=True) -> None: 

205 pass 

206 

207 def flashCharacter(self, i: int, bg: str='white', fg: str='red', flashes: int=3, delay: int=75) -> None: 

208 pass 

209 

210 def get(self, i: int, j: int) -> str: 

211 return '' 

212 

213 def getAllText(self) -> str: 

214 return '' 

215 

216 def getInsertPoint(self) -> int: 

217 return 0 

218 

219 def getSelectedText(self) -> str: 

220 return '' 

221 

222 def getSelectionRange(self) -> Tuple[int, int]: 

223 return (0, 0) 

224 

225 def getXScrollPosition(self) -> int: 

226 return 0 

227 

228 def getYScrollPosition(self) -> int: 

229 return 0 

230 

231 def hasSelection(self) -> bool: 

232 return False 

233 

234 def insert(self, i: Index, s: str) -> None: 

235 pass 

236 

237 def see(self, i: int) -> None: 

238 pass 

239 

240 def seeInsertPoint(self) -> None: 

241 pass 

242 

243 def selectAllText(self, insert: str=None) -> None: 

244 pass 

245 

246 def setAllText(self, s: str) -> None: 

247 pass 

248 

249 def setFocus(self) -> None: 

250 pass # Required: sets the focus to wrapper.widget. 

251 

252 def setInsertPoint(self, pos: str, s: str=None) -> None: 

253 pass 

254 

255 def setSelectionRange(self, i: Index, j: Index, insert: Index=None) -> None: 

256 pass 

257 

258 def setXScrollPosition(self, i: int) -> None: 

259 pass 

260 

261 def setYScrollPosition(self, i: int) -> None: 

262 pass 

263 

264 def toPythonIndex(self, index: Index) -> int: 

265 return 0 

266 

267 def toPythonIndexRowCol(self, index: str) -> Tuple[int, int, int]: 

268 return (0, 0, 0) 

269#@+node:ekr.20140904043623.18552: ** class IconBarAPI 

270class IconBarAPI: 

271 """The required API for c.frame.iconBar.""" 

272 

273 def __init__(self, c: Cmdr, parentFrame: Widget) -> None: 

274 pass 

275 

276 def add(self, *args: str, **keys: str) -> None: 

277 pass 

278 

279 def addRow(self, height: str=None) -> None: 

280 pass 

281 

282 def addRowIfNeeded(self) -> None: 

283 pass 

284 

285 def addWidget(self, w: Wrapper) -> None: 

286 pass 

287 

288 def clear(self) -> None: 

289 pass 

290 

291 def createChaptersIcon(self) -> None: 

292 pass 

293 

294 def deleteButton(self, w: Wrapper) -> None: 

295 pass 

296 

297 def getNewFrame(self) -> None: 

298 pass 

299 

300 def setCommandForButton(self, 

301 button: Any, command: str, command_p: Pos, controller: Cmdr, gnx: str, script: str, 

302 ) -> None: 

303 pass 

304#@+node:ekr.20031218072017.3656: ** class LeoBody 

305class LeoBody: 

306 """The base class for the body pane in Leo windows.""" 

307 #@+others 

308 #@+node:ekr.20031218072017.3657: *3* LeoBody.__init__ 

309 def __init__(self, frame: Widget, parentFrame: Widget) -> None: 

310 """Ctor for LeoBody class.""" 

311 c = frame.c 

312 frame.body = self 

313 self.c = c 

314 self.editorWrappers: Dict[str, Widget] = {} # keys are pane names, values are text widgets 

315 self.frame = frame 

316 self.parentFrame: Widget = parentFrame # New in Leo 4.6. 

317 self.totalNumberOfEditors = 0 

318 # May be overridden in subclasses... 

319 self.widget: Widget = None # set in LeoQtBody.set_widget. 

320 self.wrapper: Wrapper = None # set in LeoQtBody.set_widget. 

321 self.numberOfEditors = 1 

322 self.pb = None # paned body widget. 

323 # Must be overridden in subclasses... 

324 self.colorizer = None 

325 # Init user settings. 

326 self.use_chapters = False # May be overridden in subclasses. 

327 #@+node:ekr.20031218072017.3677: *3* LeoBody.Coloring 

328 def forceFullRecolor(self) -> None: 

329 pass 

330 

331 def getColorizer(self) -> None: 

332 return self.colorizer 

333 

334 def updateSyntaxColorer(self, p: Pos) -> None: 

335 return self.colorizer.updateSyntaxColorer(p.copy()) 

336 

337 def recolor(self, p: Pos) -> None: 

338 self.c.recolor() 

339 

340 recolor_now = recolor 

341 #@+node:ekr.20140903103455.18574: *3* LeoBody.Defined in subclasses 

342 # Methods of this class call the following methods of subclasses (LeoQtBody) 

343 # Fail loudly if these methods are not defined. 

344 

345 def oops(self) -> None: 

346 """Say that a required method in a subclass is missing.""" 

347 g.trace("(LeoBody) %s should be overridden in a subclass", g.callers()) 

348 

349 def createEditorFrame(self, w: Wrapper) -> Wrapper: 

350 self.oops() 

351 

352 def createTextWidget(self, parentFrame: Widget, p: Pos, name: str) -> Wrapper: 

353 self.oops() 

354 

355 def packEditorLabelWidget(self, w: Wrapper) -> None: 

356 self.oops() 

357 

358 def onFocusOut(self, obj: Any) -> None: 

359 pass 

360 #@+node:ekr.20060528100747: *3* LeoBody.Editors 

361 # This code uses self.pb, a paned body widget, created by tkBody.finishCreate. 

362 #@+node:ekr.20070424053629: *4* LeoBody.entries 

363 #@+node:ekr.20060528100747.1: *5* LeoBody.addEditor (overridden) 

364 def addEditor(self, event: Event=None) -> None: 

365 """Add another editor to the body pane.""" 

366 c, p = self.c, self.c.p 

367 self.totalNumberOfEditors += 1 

368 self.numberOfEditors += 1 

369 if self.numberOfEditors == 2: 

370 # Inject the ivars into the first editor. 

371 # Bug fix: Leo 4.4.8 rc1: The name of the last editor need not be '1' 

372 d = self.editorWrappers 

373 keys = list(d.keys()) 

374 if len(keys) == 1: 

375 # Immediately create the label in the old editor. 

376 w_old = d.get(keys[0]) 

377 self.updateInjectedIvars(w_old, p) 

378 self.selectLabel(w_old) 

379 else: 

380 g.trace('can not happen: unexpected editorWrappers', d) 

381 name = f"{self.totalNumberOfEditors}" 

382 pane = self.pb.add(name) 

383 panes = self.pb.panes() 

384 minSize = float(1.0 / float(len(panes))) 

385 # Create the text wrapper. 

386 f = self.createEditorFrame(pane) 

387 wrapper = self.createTextWidget(f, name=name, p=p) 

388 wrapper.delete(0, 'end') 

389 wrapper.insert('end', p.b) 

390 wrapper.see(0) 

391 c.k.completeAllBindingsForWidget(wrapper) 

392 self.recolorWidget(p, wrapper) 

393 self.editorWrappers[name] = wrapper 

394 for pane in panes: 

395 self.pb.configurepane(pane, size=minSize) 

396 self.pb.updatelayout() 

397 c.frame.body.wrapper = wrapper 

398 # Finish... 

399 self.updateInjectedIvars(wrapper, p) 

400 self.selectLabel(wrapper) 

401 self.selectEditor(wrapper) 

402 self.updateEditors() 

403 c.bodyWantsFocus() 

404 #@+node:ekr.20060528132829: *5* LeoBody.assignPositionToEditor 

405 def assignPositionToEditor(self, p: Pos) -> None: 

406 """Called *only* from tree.select to select the present body editor.""" 

407 c = self.c 

408 w = c.frame.body.widget 

409 self.updateInjectedIvars(w, p) 

410 self.selectLabel(w) 

411 #@+node:ekr.20200415041750.1: *5* LeoBody.cycleEditorFocus (restored) 

412 @body_cmd('editor-cycle-focus') 

413 @body_cmd('cycle-editor-focus') # There is no LeoQtBody method 

414 def cycleEditorFocus(self, event: Event=None) -> None: 

415 """Cycle keyboard focus between the body text editors.""" 

416 c = self.c 

417 d = self.editorWrappers 

418 w = c.frame.body.wrapper 

419 values = list(d.values()) 

420 if len(values) > 1: 

421 i = values.index(w) + 1 

422 if i == len(values): 

423 i = 0 

424 w2 = values[i] 

425 assert w != w2 

426 self.selectEditor(w2) 

427 c.frame.body.wrapper = w2 

428 #@+node:ekr.20060528113806: *5* LeoBody.deleteEditor (overridden) 

429 def deleteEditor(self, event: Event=None) -> None: 

430 """Delete the presently selected body text editor.""" 

431 c = self.c 

432 w = c.frame.body.wapper 

433 d = self.editorWrappers 

434 if len(list(d.keys())) == 1: 

435 return 

436 name = w.leo_name 

437 del d[name] 

438 self.pb.delete(name) 

439 panes = self.pb.panes() 

440 minSize = float(1.0 / float(len(panes))) 

441 for pane in panes: 

442 self.pb.configurepane(pane, size=minSize) 

443 # Select another editor. 

444 w = list(d.values())[0] 

445 # c.frame.body.wrapper = w # Don't do this now? 

446 self.numberOfEditors -= 1 

447 self.selectEditor(w) 

448 #@+node:ekr.20070425180705: *5* LeoBody.findEditorForChapter 

449 def findEditorForChapter(self, chapter: str, p: Pos) -> None: 

450 """Return an editor to be assigned to chapter.""" 

451 c = self.c 

452 d = self.editorWrappers 

453 values = list(d.values()) 

454 # First, try to match both the chapter and position. 

455 if p: 

456 for w in values: 

457 if ( 

458 hasattr(w, 'leo_chapter') and w.leo_chapter == chapter and 

459 hasattr(w, 'leo_p') and w.leo_p and w.leo_p == p 

460 ): 

461 return w 

462 # Next, try to match just the chapter. 

463 for w in values: 

464 if hasattr(w, 'leo_chapter') and w.leo_chapter == chapter: 

465 return w 

466 # As a last resort, return the present editor widget. 

467 return c.frame.body.wrapper 

468 #@+node:ekr.20060530210057: *5* LeoBody.select/unselectLabel 

469 def unselectLabel(self, w: Wrapper) -> None: 

470 self.createChapterIvar(w) 

471 self.packEditorLabelWidget(w) 

472 s = self.computeLabel(w) 

473 if hasattr(w, 'leo_label') and w.leo_label: 

474 w.leo_label.configure(text=s, bg='LightSteelBlue1') 

475 

476 def selectLabel(self, w: Wrapper) -> None: 

477 if self.numberOfEditors > 1: 

478 self.createChapterIvar(w) 

479 self.packEditorLabelWidget(w) 

480 s = self.computeLabel(w) 

481 if hasattr(w, 'leo_label') and w.leo_label: 

482 w.leo_label.configure(text=s, bg='white') 

483 elif hasattr(w, 'leo_label') and w.leo_label: 

484 w.leo_label.pack_forget() 

485 w.leo_label = None 

486 #@+node:ekr.20061017083312: *5* LeoBody.selectEditor & helpers 

487 selectEditorLockout = False 

488 

489 def selectEditor(self, w: Wrapper) -> None: 

490 """Select the editor given by w and node w.leo_p.""" 

491 # Called whenever wrapper must be selected. 

492 c = self.c 

493 if self.selectEditorLockout: 

494 return 

495 if w and w == self.c.frame.body.widget: 

496 if w.leo_p and w.leo_p != c.p: 

497 c.selectPosition(w.leo_p) 

498 c.bodyWantsFocus() 

499 return 

500 try: 

501 self.selectEditorLockout = True 

502 self.selectEditorHelper(w) 

503 finally: 

504 self.selectEditorLockout = False 

505 #@+node:ekr.20070423102603: *6* LeoBody.selectEditorHelper 

506 def selectEditorHelper(self, wrapper: str) -> None: 

507 """Select the editor whose widget is given.""" 

508 c = self.c 

509 if not (hasattr(wrapper, 'leo_p') and wrapper.leo_p): 

510 g.trace('no wrapper.leo_p') 

511 return 

512 self.deactivateActiveEditor(wrapper) 

513 # The actual switch. 

514 c.frame.body.wrapper = wrapper 

515 wrapper.leo_active = True 

516 self.switchToChapter(wrapper) 

517 self.selectLabel(wrapper) 

518 if not self.ensurePositionExists(wrapper): 

519 g.trace('***** no position editor!') 

520 return 

521 p = wrapper.leo_p 

522 c.redraw(p) 

523 c.recolor() 

524 c.bodyWantsFocus() 

525 #@+node:ekr.20060528131618: *5* LeoBody.updateEditors 

526 # Called from addEditor and assignPositionToEditor 

527 

528 def updateEditors(self) -> None: 

529 c, p = self.c, self.c.p 

530 d = self.editorWrappers 

531 if len(list(d.keys())) < 2: 

532 return # There is only the main widget. 

533 for key in d: 

534 wrapper = d.get(key) 

535 v = wrapper.leo_v 

536 if v and v == p.v and wrapper != c.frame.body.wrapper: 

537 wrapper.delete(0, 'end') 

538 wrapper.insert('end', p.b) 

539 self.recolorWidget(p, wrapper) 

540 c.bodyWantsFocus() 

541 #@+node:ekr.20070424053629.1: *4* LeoBody.utils 

542 #@+node:ekr.20070422093128: *5* LeoBody.computeLabel 

543 def computeLabel(self, w: Wrapper) -> str: 

544 s = w.leo_label_s 

545 if hasattr(w, 'leo_chapter') and w.leo_chapter: 

546 s = f"{w.leo_chapter.name}: {s}" 

547 return s 

548 #@+node:ekr.20070422094710: *5* LeoBody.createChapterIvar 

549 def createChapterIvar(self, w: Wrapper) -> None: 

550 c = self.c 

551 cc = c.chapterController 

552 if not hasattr(w, 'leo_chapter') or not w.leo_chapter: 

553 if cc and self.use_chapters: 

554 w.leo_chapter = cc.getSelectedChapter() 

555 else: 

556 w.leo_chapter = None 

557 #@+node:ekr.20070424084651: *5* LeoBody.ensurePositionExists 

558 def ensurePositionExists(self, w: Wrapper) -> bool: 

559 """Return True if w.leo_p exists or can be reconstituted.""" 

560 c = self.c 

561 if c.positionExists(w.leo_p): 

562 return True 

563 g.trace('***** does not exist', w.leo_name) 

564 for p2 in c.all_unique_positions(): 

565 if p2.v and p2.v == w.leo_v: 

566 w.leo_p = p2.copy() 

567 return True 

568 # This *can* happen when selecting a deleted node. 

569 w.leo_p = c.p 

570 return False 

571 #@+node:ekr.20070424080640: *5* LeoBody.deactivateActiveEditor 

572 # Not used in Qt. 

573 

574 def deactivateActiveEditor(self, w: Wrapper) -> None: 

575 """Inactivate the previously active editor.""" 

576 d = self.editorWrappers 

577 # Don't capture ivars here! assignPositionToEditor keeps them up-to-date. (??) 

578 for key in d: 

579 w2 = d.get(key) 

580 if w2 != w and w2.leo_active: 

581 w2.leo_active = False 

582 self.unselectLabel(w2) 

583 return 

584 #@+node:ekr.20060530204135: *5* LeoBody.recolorWidget (QScintilla only) 

585 def recolorWidget(self, p: Pos, w: Wrapper) -> None: 

586 # Support QScintillaColorizer.colorize. 

587 c = self.c 

588 colorizer = c.frame.body.colorizer 

589 if p and colorizer and hasattr(colorizer, 'colorize'): 

590 old_wrapper = c.frame.body.wrapper 

591 c.frame.body.wrapper = w 

592 try: 

593 c.frame.body.colorizer.colorize(p) 

594 finally: 

595 c.frame.body.wrapper = old_wrapper 

596 #@+node:ekr.20070424084012: *5* LeoBody.switchToChapter 

597 def switchToChapter(self, w: Wrapper) -> None: 

598 """select w.leo_chapter.""" 

599 c = self.c 

600 cc = c.chapterController 

601 if hasattr(w, 'leo_chapter') and w.leo_chapter: 

602 chapter = w.leo_chapter 

603 name = chapter and chapter.name 

604 oldChapter = cc.getSelectedChapter() 

605 if chapter != oldChapter: 

606 cc.selectChapterByName(name) 

607 c.bodyWantsFocus() 

608 #@+node:ekr.20070424092855: *5* LeoBody.updateInjectedIvars 

609 # Called from addEditor and assignPositionToEditor. 

610 

611 def updateInjectedIvars(self, w: Wrapper, p: Pos) -> None: 

612 """Inject updated ivars in w, a gui widget.""" 

613 if not w: 

614 return 

615 c = self.c 

616 cc = c.chapterController 

617 # Was in ctor. 

618 use_chapters = c.config.getBool('use-chapters') 

619 if cc and use_chapters: 

620 w.leo_chapter = cc.getSelectedChapter() 

621 else: 

622 w.leo_chapter = None 

623 w.leo_p = p.copy() 

624 w.leo_v = w.leo_p.v 

625 w.leo_label_s = p.h 

626 #@+node:ekr.20031218072017.4018: *3* LeoBody.Text 

627 #@+node:ekr.20031218072017.4030: *4* LeoBody.getInsertLines 

628 def getInsertLines(self) -> Tuple[str, str, str]: 

629 """ 

630 Return before,after where: 

631 

632 before is all the lines before the line containing the insert point. 

633 sel is the line containing the insert point. 

634 after is all the lines after the line containing the insert point. 

635 

636 All lines end in a newline, except possibly the last line. 

637 """ 

638 body = self 

639 w = body.wrapper 

640 s = w.getAllText() 

641 insert = w.getInsertPoint() 

642 i, j = g.getLine(s, insert) 

643 before = s[0:i] 

644 ins = s[i:j] 

645 after = s[j:] 

646 before = g.checkUnicode(before) 

647 ins = g.checkUnicode(ins) 

648 after = g.checkUnicode(after) 

649 return before, ins, after 

650 #@+node:ekr.20031218072017.4031: *4* LeoBody.getSelectionAreas 

651 def getSelectionAreas(self) -> Tuple[str, str, str]: 

652 """ 

653 Return before,sel,after where: 

654 

655 before is the text before the selected text 

656 (or the text before the insert point if no selection) 

657 sel is the selected text (or "" if no selection) 

658 after is the text after the selected text 

659 (or the text after the insert point if no selection) 

660 """ 

661 body = self 

662 w = body.wrapper 

663 s = w.getAllText() 

664 i, j = w.getSelectionRange() 

665 if i == j: 

666 j = i + 1 

667 before = s[0:i] 

668 sel = s[i:j] 

669 after = s[j:] 

670 before = g.checkUnicode(before) 

671 sel = g.checkUnicode(sel) 

672 after = g.checkUnicode(after) 

673 return before, sel, after 

674 #@+node:ekr.20031218072017.2377: *4* LeoBody.getSelectionLines 

675 def getSelectionLines(self) -> Tuple[str, str, str]: 

676 """ 

677 Return before,sel,after where: 

678 

679 before is the all lines before the selected text 

680 (or the text before the insert point if no selection) 

681 sel is the selected text (or "" if no selection) 

682 after is all lines after the selected text 

683 (or the text after the insert point if no selection) 

684 """ 

685 if g.app.batchMode: 

686 return '', '', '' 

687 # At present, called only by c.getBodyLines. 

688 body = self 

689 w = body.wrapper 

690 s = w.getAllText() 

691 i, j = w.getSelectionRange() 

692 if i == j: 

693 i, j = g.getLine(s, i) 

694 else: 

695 # #1742: Move j back if it is at the start of a line. 

696 if j > i and j > 0 and s[j - 1] == '\n': 

697 j -= 1 

698 i, junk = g.getLine(s, i) 

699 junk, j = g.getLine(s, j) 

700 before = g.checkUnicode(s[0:i]) 

701 sel = g.checkUnicode(s[i:j]) 

702 after = g.checkUnicode(s[j : len(s)]) 

703 return before, sel, after # 3 strings. 

704 #@-others 

705#@+node:ekr.20031218072017.3678: ** class LeoFrame 

706class LeoFrame: 

707 """The base class for all Leo windows.""" 

708 instances = 0 

709 #@+others 

710 #@+node:ekr.20031218072017.3679: *3* LeoFrame.__init__ & reloadSettings 

711 def __init__(self, c: Cmdr, gui: Any) -> None: 

712 self.c = c 

713 self.gui = gui 

714 self.iconBarClass = NullIconBarClass 

715 self.statusLineClass = NullStatusLineClass 

716 self.title: str = None # Must be created by subclasses. 

717 # Objects attached to this frame. 

718 self.body = None 

719 self.colorPanel = None 

720 self.comparePanel = None 

721 self.findPanel: Widget = None 

722 self.fontPanel: Widget = None 

723 self.iconBar: Widget = None 

724 self.isNullFrame = False 

725 self.keys = None 

726 self.log: Wrapper = None 

727 self.menu: Wrapper = None 

728 self.miniBufferWidget: Widget = None 

729 self.outerFrame: Widget = None 

730 self.prefsPanel: Widget = None 

731 self.statusLine: Widget = g.NullObject() # For unit tests. 

732 self.tree: Wrapper = None 

733 self.useMiniBufferWidget = False 

734 # Gui-independent data 

735 self.cursorStay = True # May be overridden in subclass.reloadSettings. 

736 self.componentsDict: Dict[str, Any] = {} # Keys are names, values are componentClass instances. 

737 self.es_newlines = 0 # newline count for this log stream 

738 self.openDirectory = "" 

739 self.saved = False # True if ever saved 

740 self.splitVerticalFlag = True # Set by initialRatios later. 

741 self.startupWindow = False # True if initially opened window 

742 self.stylesheet = None # The contents of <?xml-stylesheet...?> line. 

743 self.tab_width = 0 # The tab width in effect in this pane. 

744 #@+node:ekr.20051009045404: *4* frame.createFirstTreeNode 

745 def createFirstTreeNode(self) -> VNode: 

746 c = self.c 

747 # 

748 # #1631: Initialize here, not in p._linkAsRoot. 

749 c.hiddenRootNode.children = [] 

750 # 

751 # #1817: Clear the gnxDict. 

752 c.fileCommands.gnxDict = {} 

753 # 

754 # Create the first node. 

755 v = leoNodes.VNode(context=c) 

756 p = leoNodes.Position(v) 

757 v.initHeadString("NewHeadline") 

758 # 

759 # New in Leo 4.5: p.moveToRoot would be wrong: 

760 # the node hasn't been linked yet. 

761 p._linkAsRoot() 

762 return v 

763 #@+node:ekr.20061109125528: *3* LeoFrame.May be defined in subclasses 

764 #@+node:ekr.20031218072017.3688: *4* LeoFrame.getTitle & setTitle 

765 def getTitle(self) -> str: 

766 return self.title 

767 

768 def setTitle(self, title: str) -> None: 

769 self.title = title 

770 #@+node:ekr.20081005065934.3: *4* LeoFrame.initAfterLoad & initCompleteHint 

771 def initAfterLoad(self) -> None: 

772 """Provide offical hooks for late inits of components of Leo frames.""" 

773 frame = self 

774 frame.body.initAfterLoad() 

775 frame.log.initAfterLoad() 

776 frame.menu.initAfterLoad() 

777 # if frame.miniBufferWidget: frame.miniBufferWidget.initAfterLoad() 

778 frame.tree.initAfterLoad() 

779 

780 def initCompleteHint(self) -> None: 

781 pass 

782 #@+node:ekr.20031218072017.3687: *4* LeoFrame.setTabWidth 

783 def setTabWidth(self, w: Wrapper) -> None: 

784 """Set the tab width in effect for this frame.""" 

785 # Subclasses may override this to affect drawing. 

786 self.tab_width = w 

787 #@+node:ekr.20061109125528.1: *3* LeoFrame.Must be defined in base class 

788 #@+node:ekr.20031218072017.3689: *4* LeoFrame.initialRatios 

789 def initialRatios(self) -> Tuple[bool, float, float]: 

790 c = self.c 

791 s = c.config.get("initial_split_orientation", "string") 

792 verticalFlag = s is None or (s != "h" and s != "horizontal") 

793 if verticalFlag: 

794 r = c.config.getRatio("initial-vertical-ratio") 

795 if r is None or r < 0.0 or r > 1.0: 

796 r = 0.5 

797 r2 = c.config.getRatio("initial-vertical-secondary-ratio") 

798 if r2 is None or r2 < 0.0 or r2 > 1.0: 

799 r2 = 0.8 

800 else: 

801 r = c.config.getRatio("initial-horizontal-ratio") 

802 if r is None or r < 0.0 or r > 1.0: 

803 r = 0.3 

804 r2 = c.config.getRatio("initial-horizontal-secondary-ratio") 

805 if r2 is None or r2 < 0.0 or r2 > 1.0: 

806 r2 = 0.8 

807 return verticalFlag, r, r2 

808 #@+node:ekr.20031218072017.3690: *4* LeoFrame.longFileName & shortFileName 

809 def longFileName(self) -> str: 

810 return self.c.mFileName 

811 

812 def shortFileName(self) -> str: 

813 return g.shortFileName(self.c.mFileName) 

814 #@+node:ekr.20031218072017.3691: *4* LeoFrame.oops 

815 def oops(self) -> None: 

816 g.pr("LeoFrame oops:", g.callers(4), "should be overridden in subclass") 

817 #@+node:ekr.20031218072017.3692: *4* LeoFrame.promptForSave 

818 def promptForSave(self) -> bool: 

819 """ 

820 Prompt the user to save changes. 

821 Return True if the user vetos the quit or save operation. 

822 """ 

823 c = self.c 

824 theType = "quitting?" if g.app.quitting else "closing?" 

825 # See if we are in quick edit/save mode. 

826 root = c.rootPosition() 

827 quick_save = not c.mFileName and not root.next() and root.isAtEditNode() 

828 if quick_save: 

829 name = g.shortFileName(root.atEditNodeName()) 

830 else: 

831 name = c.mFileName if c.mFileName else self.title 

832 answer = g.app.gui.runAskYesNoCancelDialog( 

833 c, 

834 title='Confirm', 

835 message=f"Save changes to {g.splitLongFileName(name)} before {theType}", 

836 ) 

837 if answer == "cancel": 

838 return True # Veto. 

839 if answer == "no": 

840 return False # Don't save and don't veto. 

841 if not c.mFileName: 

842 root = c.rootPosition() 

843 if not root.next() and root.isAtEditNode(): 

844 # There is only a single @edit node in the outline. 

845 # A hack to allow "quick edit" of non-Leo files. 

846 # See https://bugs.launchpad.net/leo-editor/+bug/381527 

847 # Write the @edit node if needed. 

848 if root.isDirty(): 

849 c.atFileCommands.writeOneAtEditNode(root) 

850 return False # Don't save and don't veto. 

851 c.mFileName = g.app.gui.runSaveFileDialog(c, 

852 title="Save", 

853 filetypes=[("Leo files", "*.leo")], 

854 defaultextension=".leo") 

855 c.bringToFront() 

856 if c.mFileName: 

857 if g.app.gui.guiName() == 'curses': 

858 g.pr(f"Saving: {c.mFileName}") 

859 ok = c.fileCommands.save(c.mFileName) 

860 return not ok # Veto if the save did not succeed. 

861 return True # Veto. 

862 #@+node:ekr.20031218072017.1375: *4* LeoFrame.frame.scanForTabWidth 

863 def scanForTabWidth(self, p: Pos) -> None: 

864 """Return the tab width in effect at p.""" 

865 c = self.c 

866 tab_width = c.getTabWidth(p) 

867 c.frame.setTabWidth(tab_width) 

868 #@+node:ekr.20061119120006: *4* LeoFrame.Icon area convenience methods 

869 def addIconButton(self, *args: str, **keys: str) -> None: 

870 if self.iconBar: 

871 return self.iconBar.add(*args, **keys) 

872 return None 

873 

874 def addIconRow(self) -> None: 

875 if self.iconBar: 

876 return self.iconBar.addRow() 

877 return None 

878 

879 def addIconWidget(self, w: Wrapper) -> None: 

880 if self.iconBar: 

881 return self.iconBar.addWidget(w) 

882 return None 

883 

884 def clearIconBar(self) -> None: 

885 if self.iconBar: 

886 return self.iconBar.clear() 

887 return None 

888 

889 def createIconBar(self) -> None: 

890 c = self.c 

891 if not self.iconBar: 

892 self.iconBar = self.iconBarClass(c, self.outerFrame) 

893 return self.iconBar 

894 

895 def getIconBar(self) -> None: 

896 if not self.iconBar: 

897 self.iconBar = self.iconBarClass(self.c, self.outerFrame) 

898 return self.iconBar 

899 

900 getIconBarObject = getIconBar 

901 

902 def getNewIconFrame(self) -> None: 

903 if not self.iconBar: 

904 self.iconBar = self.iconBarClass(self.c, self.outerFrame) 

905 return self.iconBar.getNewFrame() 

906 

907 def hideIconBar(self) -> None: 

908 if self.iconBar: 

909 self.iconBar.hide() 

910 

911 def showIconBar(self) -> None: 

912 if self.iconBar: 

913 self.iconBar.show() 

914 #@+node:ekr.20041223105114.1: *4* LeoFrame.Status line convenience methods 

915 def createStatusLine(self) -> str: 

916 if not self.statusLine: 

917 self.statusLine = self.statusLineClass(self.c, self.outerFrame) # type:ignore 

918 return self.statusLine 

919 

920 def clearStatusLine(self) -> None: 

921 if self.statusLine: 

922 self.statusLine.clear() 

923 

924 def disableStatusLine(self, background: str=None) -> None: 

925 if self.statusLine: 

926 self.statusLine.disable(background) 

927 

928 def enableStatusLine(self, background: str="white") -> None: 

929 if self.statusLine: 

930 self.statusLine.enable(background) 

931 

932 def getStatusLine(self) -> str: 

933 return self.statusLine 

934 

935 getStatusObject = getStatusLine 

936 

937 def putStatusLine(self, s: str, bg: str=None, fg: str=None) -> None: 

938 if self.statusLine: 

939 self.statusLine.put(s, bg, fg) 

940 

941 def setFocusStatusLine(self) -> None: 

942 if self.statusLine: 

943 self.statusLine.setFocus() 

944 

945 def statusLineIsEnabled(self) -> bool: 

946 if self.statusLine: 

947 return self.statusLine.isEnabled() 

948 return False 

949 

950 def updateStatusLine(self) -> None: 

951 if self.statusLine: 

952 self.statusLine.update() 

953 #@+node:ekr.20070130115927.4: *4* LeoFrame.Cut/Copy/Paste 

954 #@+node:ekr.20070130115927.5: *5* LeoFrame.copyText 

955 @frame_cmd('copy-text') 

956 def copyText(self, event: Event=None) -> None: 

957 """Copy the selected text from the widget to the clipboard.""" 

958 # f = self 

959 w = event and event.widget 

960 # wname = c.widget_name(w) 

961 if not w or not g.isTextWrapper(w): 

962 return 

963 # Set the clipboard text. 

964 i, j = w.getSelectionRange() 

965 if i == j: 

966 ins = w.getInsertPoint() 

967 i, j = g.getLine(w.getAllText(), ins) 

968 # 2016/03/27: Fix a recent buglet. 

969 # Don't clear the clipboard if we hit ctrl-c by mistake. 

970 s = w.get(i, j) 

971 if s: 

972 g.app.gui.replaceClipboardWith(s) 

973 

974 OnCopyFromMenu = copyText 

975 #@+node:ekr.20070130115927.6: *5* LeoFrame.cutText 

976 @frame_cmd('cut-text') 

977 def cutText(self, event: Event=None) -> None: 

978 """Invoked from the mini-buffer and from shortcuts.""" 

979 c, p, u = self.c, self.c.p, self.c.undoer 

980 w = event and event.widget 

981 if not w or not g.isTextWrapper(w): 

982 return 

983 bunch = u.beforeChangeBody(p) 

984 name = c.widget_name(w) 

985 oldText = w.getAllText() 

986 i, j = w.getSelectionRange() 

987 # Update the widget and set the clipboard text. 

988 s = w.get(i, j) 

989 if i != j: 

990 w.delete(i, j) 

991 w.see(i) # 2016/01/19: important 

992 g.app.gui.replaceClipboardWith(s) 

993 else: 

994 ins = w.getInsertPoint() 

995 i, j = g.getLine(oldText, ins) 

996 s = w.get(i, j) 

997 w.delete(i, j) 

998 w.see(i) # 2016/01/19: important 

999 g.app.gui.replaceClipboardWith(s) 

1000 if name.startswith('body'): 

1001 p.v.b = w.getAllText() 

1002 u.afterChangeBody(p, 'Cut', bunch) 

1003 elif name.startswith('head'): 

1004 # The headline is not officially changed yet. 

1005 s = w.getAllText() 

1006 else: 

1007 pass 

1008 

1009 OnCutFromMenu = cutText 

1010 #@+node:ekr.20070130115927.7: *5* LeoFrame.pasteText 

1011 @frame_cmd('paste-text') 

1012 def pasteText(self, event: Event=None, middleButton: bool=False) -> None: 

1013 """ 

1014 Paste the clipboard into a widget. 

1015 If middleButton is True, support x-windows middle-mouse-button easter-egg. 

1016 """ 

1017 trace = False and not g.unitTesting 

1018 c, p, u = self.c, self.c.p, self.c.undoer 

1019 w = event and event.widget 

1020 wname = c.widget_name(w) 

1021 if not w or not g.isTextWrapper(w): 

1022 if trace: 

1023 g.trace('===== BAD W', repr(w)) 

1024 return 

1025 if trace: 

1026 g.trace('===== Entry') 

1027 bunch = u.beforeChangeBody(p) 

1028 if self.cursorStay and wname.startswith('body'): 

1029 tCurPosition = w.getInsertPoint() 

1030 i, j = w.getSelectionRange() # Returns insert point if no selection. 

1031 if middleButton and c.k.previousSelection is not None: 

1032 start, end = c.k.previousSelection 

1033 s = w.getAllText() 

1034 s = s[start:end] 

1035 c.k.previousSelection = None 

1036 else: 

1037 s = g.app.gui.getTextFromClipboard() 

1038 s = g.checkUnicode(s) 

1039 singleLine = wname.startswith('head') or wname.startswith('minibuffer') 

1040 if singleLine: 

1041 # Strip trailing newlines so the truncation doesn't cause confusion. 

1042 while s and s[-1] in ('\n', '\r'): 

1043 s = s[:-1] 

1044 # Save the horizontal scroll position. 

1045 if hasattr(w, 'getXScrollPosition'): 

1046 x_pos = w.getXScrollPosition() 

1047 # Update the widget. 

1048 if i != j: 

1049 w.delete(i, j) 

1050 # #2593: Replace link patterns with html links. 

1051 if wname.startswith('log'): 

1052 if c.frame.log.put_html_links(s): 

1053 return # create_html_links has done all the work. 

1054 w.insert(i, s) 

1055 w.see(i + len(s) + 2) 

1056 if wname.startswith('body'): 

1057 if self.cursorStay: 

1058 if tCurPosition == j: 

1059 offset = len(s) - (j - i) 

1060 else: 

1061 offset = 0 

1062 newCurPosition = tCurPosition + offset 

1063 w.setSelectionRange(i=newCurPosition, j=newCurPosition) 

1064 p.v.b = w.getAllText() 

1065 u.afterChangeBody(p, 'Paste', bunch) 

1066 elif singleLine: 

1067 s = w.getAllText() 

1068 while s and s[-1] in ('\n', '\r'): 

1069 s = s[:-1] 

1070 else: 

1071 pass 

1072 # Never scroll horizontally. 

1073 if hasattr(w, 'getXScrollPosition'): 

1074 w.setXScrollPosition(x_pos) 

1075 

1076 OnPasteFromMenu = pasteText 

1077 #@+node:ekr.20061016071937: *5* LeoFrame.OnPaste (support middle-button paste) 

1078 def OnPaste(self, event: Event=None) -> None: 

1079 return self.pasteText(event=event, middleButton=True) 

1080 #@+node:ekr.20031218072017.3980: *4* LeoFrame.Edit Menu 

1081 #@+node:ekr.20031218072017.3982: *5* LeoFrame.endEditLabelCommand 

1082 @frame_cmd('end-edit-headline') 

1083 def endEditLabelCommand(self, event: Event=None, p: Pos=None) -> None: 

1084 """End editing of a headline and move focus to the body pane.""" 

1085 frame = self 

1086 c = frame.c 

1087 k = c.k 

1088 if g.app.batchMode: 

1089 c.notValidInBatchMode("End Edit Headline") 

1090 return 

1091 w = event and event.w or c.get_focus() # #1413. 

1092 w_name = g.app.gui.widget_name(w) 

1093 if w_name.startswith('head'): 

1094 c.endEditing() 

1095 c.treeWantsFocus() 

1096 else: 

1097 c.bodyWantsFocus() 

1098 k.setDefaultInputState() 

1099 # Recolor the *body* text, **not** the headline. 

1100 k.showStateAndMode(w=c.frame.body.wrapper) 

1101 #@+node:ekr.20031218072017.3680: *3* LeoFrame.Must be defined in subclasses 

1102 def bringToFront(self) -> None: 

1103 self.oops() 

1104 

1105 def cascade(self, event: Event=None) -> None: 

1106 self.oops() 

1107 

1108 def contractBodyPane(self, event: Event=None) -> None: 

1109 self.oops() 

1110 

1111 def contractLogPane(self, event: Event=None) -> None: 

1112 self.oops() 

1113 

1114 def contractOutlinePane(self, event: Event=None) -> None: 

1115 self.oops() 

1116 

1117 def contractPane(self, event: Event=None) -> None: 

1118 self.oops() 

1119 

1120 def deiconify(self) -> None: 

1121 self.oops() 

1122 

1123 def equalSizedPanes(self, event: Event=None) -> None: 

1124 self.oops() 

1125 

1126 def expandBodyPane(self, event: Event=None) -> None: 

1127 self.oops() 

1128 

1129 def expandLogPane(self, event: Event=None) -> None: 

1130 self.oops() 

1131 

1132 def expandOutlinePane(self, event: Event=None) -> None: 

1133 self.oops() 

1134 

1135 def expandPane(self, event: Event=None) -> None: 

1136 self.oops() 

1137 

1138 def fullyExpandBodyPane(self, event: Event=None) -> None: 

1139 self.oops() 

1140 

1141 def fullyExpandLogPane(self, event: Event=None) -> None: 

1142 self.oops() 

1143 

1144 def fullyExpandOutlinePane(self, event: Event=None) -> None: 

1145 self.oops() 

1146 

1147 def fullyExpandPane(self, event: Event=None) -> None: 

1148 self.oops() 

1149 

1150 def get_window_info(self) -> Tuple[int, int, int, int]: 

1151 self.oops() 

1152 return 0, 0, 0, 0 

1153 

1154 def hideBodyPane(self, event: Event=None) -> None: 

1155 self.oops() 

1156 

1157 def hideLogPane(self, event: Event=None) -> None: 

1158 self.oops() 

1159 

1160 def hideLogWindow(self, event: Event=None) -> None: 

1161 self.oops() 

1162 

1163 def hideOutlinePane(self, event: Event=None) -> None: 

1164 self.oops() 

1165 

1166 def hidePane(self, event: Event=None) -> None: 

1167 self.oops() 

1168 

1169 def leoHelp(self, event: Event=None) -> None: 

1170 self.oops() 

1171 

1172 def lift(self) -> None: 

1173 self.oops() 

1174 

1175 def minimizeAll(self, event: Event=None) -> None: 

1176 self.oops() 

1177 

1178 def resizePanesToRatio(self, ratio: float, secondary_ratio: float) -> None: 

1179 self.oops() 

1180 

1181 def resizeToScreen(self, event: Event=None) -> None: 

1182 self.oops() 

1183 

1184 def setInitialWindowGeometry(self) -> None: 

1185 self.oops() 

1186 

1187 def setTopGeometry(self, w: int, h: int, x: int, y: int) -> None: 

1188 self.oops() 

1189 

1190 def toggleActivePane(self, event: Event=None) -> None: 

1191 self.oops() 

1192 

1193 def toggleSplitDirection(self, event: Event=None) -> None: 

1194 self.oops() 

1195 #@-others 

1196#@+node:ekr.20031218072017.3694: ** class LeoLog 

1197class LeoLog: 

1198 """The base class for the log pane in Leo windows.""" 

1199 #@+others 

1200 #@+node:ekr.20150509054436.1: *3* LeoLog.Birth 

1201 #@+node:ekr.20031218072017.3695: *4* LeoLog.ctor 

1202 def __init__(self, frame: Widget, parentFrame: Widget) -> None: 

1203 """Ctor for LeoLog class.""" 

1204 self.frame = frame 

1205 self.c = frame.c if frame else None 

1206 self.enabled = True 

1207 self.newlines = 0 

1208 self.isNull = False 

1209 # Official ivars... 

1210 self.canvasCtrl: Widget = None # Set below. Same as self.canvasDict.get(self.tabName) 

1211 # Important: depending on the log *tab*, logCtrl may be either a wrapper or a widget. 

1212 self.logCtrl: Widget = None # Set below. Same as self.textDict.get(self.tabName) 

1213 self.tabName: str = None # The name of the active tab. 

1214 self.tabFrame: Widget = None # Same as self.frameDict.get(self.tabName) 

1215 self.canvasDict: Dict[str, Widget] = {} # Keys are page names. Values are Widgets. 

1216 self.frameDict: Dict[str, Widget] = {} # Keys are page names. Values are Frames 

1217 self.logNumber = 0 # To create unique name fields for text widgets. 

1218 self.newTabCount = 0 # Number of new tabs created. 

1219 self.textDict: Dict[str, Widget] = {} # Keys are page names. Values are logCtrl's (text widgets). 

1220 #@+node:ekr.20070302094848.1: *3* LeoLog.clearTab 

1221 def clearTab(self, tabName: str, wrap: str='none') -> None: 

1222 self.selectTab(tabName, wrap=wrap) 

1223 w = self.logCtrl 

1224 if w: 

1225 w.delete(0, 'end') 

1226 #@+node:ekr.20070302094848.2: *3* LeoLog.createTab 

1227 def createTab(self, tabName: str, createText=True, widget: Widget=None, wrap: str='none') -> Widget: 

1228 # Important: widget *is* used in subclasses. Do not change the signature above. 

1229 if createText: 

1230 w = self.createTextWidget(self.tabFrame) 

1231 self.canvasDict[tabName] = None 

1232 self.textDict[tabName] = w 

1233 else: 

1234 self.canvasDict[tabName] = None 

1235 self.textDict[tabName] = None 

1236 self.frameDict[tabName] = tabName # tabFrame 

1237 #@+node:ekr.20140903143741.18550: *3* LeoLog.LeoLog.createTextWidget 

1238 def createTextWidget(self, parentFrame: Widget) -> Widget: 

1239 return None 

1240 #@+node:ekr.20070302094848.5: *3* LeoLog.deleteTab 

1241 def deleteTab(self, tabName: str) -> None: 

1242 c = self.c 

1243 if tabName == 'Log': 

1244 pass 

1245 elif tabName in ('Find', 'Spell'): 

1246 self.selectTab('Log') 

1247 else: 

1248 for d in (self.canvasDict, self.textDict, self.frameDict): 

1249 if tabName in d: 

1250 del d[tabName] 

1251 self.tabName = None 

1252 self.selectTab('Log') 

1253 c.invalidateFocus() 

1254 c.bodyWantsFocus() 

1255 #@+node:ekr.20140903143741.18549: *3* LeoLog.enable/disable 

1256 def disable(self) -> None: 

1257 self.enabled = False 

1258 

1259 def enable(self, enabled: bool=True) -> None: 

1260 self.enabled = enabled 

1261 #@+node:ekr.20070302094848.7: *3* LeoLog.getSelectedTab 

1262 def getSelectedTab(self) -> str: 

1263 return self.tabName 

1264 #@+node:ekr.20070302094848.6: *3* LeoLog.hideTab 

1265 def hideTab(self, tabName: str) -> None: 

1266 self.selectTab('Log') 

1267 #@+node:ekr.20070302094848.8: *3* LeoLog.lower/raiseTab 

1268 def lowerTab(self, tabName: str) -> None: 

1269 self.c.invalidateFocus() 

1270 self.c.bodyWantsFocus() 

1271 

1272 def raiseTab(self, tabName: str) -> None: 

1273 self.c.invalidateFocus() 

1274 self.c.bodyWantsFocus() 

1275 #@+node:ekr.20111122080923.10184: *3* LeoLog.orderedTabNames 

1276 def orderedTabNames(self, LeoLog: str=None) -> List: 

1277 return list(self.frameDict.values()) 

1278 #@+node:ekr.20070302094848.9: *3* LeoLog.numberOfVisibleTabs 

1279 def numberOfVisibleTabs(self) -> int: 

1280 return len([val for val in list(self.frameDict.values()) if val is not None]) 

1281 #@+node:ekr.20070302101304: *3* LeoLog.put, putnl & helper 

1282 # All output to the log stream eventually comes here. 

1283 

1284 def put(self, s: str, color: str=None, tabName: str='Log', from_redirect: bool=False, nodeLink: str=None) -> None: 

1285 print(s) 

1286 

1287 def putnl(self, tabName: str='Log') -> None: 

1288 pass 

1289 #@+node:ekr.20220410180439.1: *4* LeoLog.put_html_links & helpers 

1290 error_patterns = (g.mypy_pat, g.pyflakes_pat, g.pylint_pat, g.python_pat) 

1291 

1292 # This table encodes which groups extract the filename and line_number from global regex patterns. 

1293 # This is the *only* method that should need to know this information! 

1294 

1295 link_table: List[Tuple[int, int, re.Pattern]] = [ 

1296 # (filename_i, line_number_i, pattern) 

1297 (1, 2, g.mypy_pat), 

1298 (1, 2, g.pyflakes_pat), 

1299 (1, 2, g.pylint_pat), 

1300 (1, 2, g.python_pat), 

1301 ] 

1302 

1303 def put_html_links(self, s: str) -> bool: 

1304 """ 

1305 If *any* line is s contains a matches against known error patterns, 

1306 then output *all* lines in s to the log, and return True. 

1307 Otherwise, return False 

1308 """ 

1309 c = self.c 

1310 trace = False and not g.unitTesting 

1311 

1312 #@+others # Define helpers 

1313 #@+node:ekr.20220420100806.1: *5* function: find_match 

1314 def find_match(line: str) -> Tuple[re.Match, int, int]: 

1315 """Search line for any pattern in link_table.""" 

1316 if not line.strip(): 

1317 return None, None, None 

1318 for filename_i, line_number_i, pattern in self.link_table: 

1319 m = pattern.match(line) 

1320 if m and trace: 

1321 g.trace(f"Match! {i:2} {m.group(filename_i)}:{m.group(line_number_i)}") 

1322 print(' ', repr(line)) 

1323 if m: 

1324 return m, filename_i, line_number_i 

1325 return None, None, None 

1326 #@+node:ekr.20220412084258.1: *5* function: find_at_file_node 

1327 def find_at_file_node(filename: str) -> Pos: 

1328 """Find a position corresponding to filename s""" 

1329 target = os.path.normpath(filename) 

1330 parts = target.split(os.sep) 

1331 while parts: 

1332 target = os.sep.join(parts) 

1333 parts.pop(0) 

1334 # Search twice, prefering exact matches. 

1335 for p in at_file_nodes: 

1336 if target == os.path.normpath(p.anyAtFileNodeName()): 

1337 return p 

1338 for p in at_file_nodes: 

1339 if os.path.normpath(p.anyAtFileNodeName()).endswith(target): 

1340 return p 

1341 return None 

1342 #@-others 

1343 

1344 # Report any bad chars. 

1345 printables = string.ascii_letters + string.digits + string.punctuation + ' ' + '\n' 

1346 bad = list(set(ch for ch in s if ch not in printables)) 

1347 # Strip bad chars. 

1348 if bad: 

1349 g.trace('Strip unprintables', repr(bad), 'in', repr(s)) 

1350 # Strip unprintable chars. 

1351 s = ''.join(ch for ch in s if ch in printables) 

1352 lines = s.split('\n') 

1353 # Trace lines. 

1354 if trace: 

1355 g.trace(c.shortFileName()) 

1356 for i, line in enumerate(lines): 

1357 print(f"{i:2} {line!r}") 

1358 # Return False if no lines match initially. This is an efficiency measure. 

1359 for line in lines: 

1360 m, junk, junk = find_match(line) 

1361 if m: 

1362 break 

1363 else: 

1364 if trace: 

1365 print('No matches found!') 

1366 return False # The caller must handle s. 

1367 # Find all @<file> nodes. 

1368 at_file_nodes = [p for p in c.all_positions() if p.isAnyAtFileNode()] 

1369 if not at_file_nodes: 

1370 if trace: 

1371 print('No @<file> nodes') 

1372 return False 

1373 # Output each line using log.put, with or without a nodeLink. 

1374 found_matches = 0 

1375 for i, line in enumerate(lines): 

1376 m, filename_i, line_number_i = find_match(line) 

1377 if m: 

1378 filename = m.group(filename_i) 

1379 line_number = m.group(line_number_i) 

1380 p = find_at_file_node(filename) # Find a corresponding @<file> node. 

1381 if p: 

1382 unl = p.get_UNL() 

1383 found_matches += 1 

1384 self.put(line, nodeLink=f"{unl}::-{line_number}") # Use global line. 

1385 else: # An unusual case. 

1386 if not g.unitTesting: 

1387 print(f"{i:2} p not found! {filename!r}") 

1388 self.put(line) 

1389 else: # None of the patterns match. 

1390 if trace: 

1391 print(f"{i:2} No match!") 

1392 self.put(line) 

1393 if trace: 

1394 g.trace('Found', found_matches, 'matches') 

1395 return bool(found_matches) 

1396 #@+node:ekr.20070302094848.10: *3* LeoLog.renameTab 

1397 def renameTab(self, oldName: str, newName: str) -> None: 

1398 pass 

1399 #@+node:ekr.20070302094848.11: *3* LeoLog.selectTab 

1400 def selectTab(self, tabName: str, wrap: str='none') -> None: 

1401 """Create the tab if necessary and make it active.""" 

1402 c = self.c 

1403 tabFrame = self.frameDict.get(tabName) 

1404 if not tabFrame: 

1405 self.createTab(tabName, createText=True) 

1406 # Update the status vars. 

1407 self.tabName = tabName 

1408 self.canvasCtrl = self.canvasDict.get(tabName) 

1409 self.logCtrl = self.textDict.get(tabName) 

1410 self.tabFrame = self.frameDict.get(tabName) 

1411 if 0: 

1412 # Absolutely do not do this here! 

1413 # It is a cause of the 'sticky focus' problem. 

1414 c.widgetWantsFocusNow(self.logCtrl) 

1415 return tabFrame 

1416 #@-others 

1417#@+node:ekr.20031218072017.3704: ** class LeoTree 

1418class LeoTree: 

1419 """The base class for the outline pane in Leo windows.""" 

1420 #@+others 

1421 #@+node:ekr.20081005065934.8: *3* LeoTree.May be defined in subclasses 

1422 # These are new in Leo 4.6. 

1423 

1424 def initAfterLoad(self) -> None: 

1425 """Do late initialization. Called in g.openWithFileName after a successful load.""" 

1426 

1427 # Hints for optimization. The proper default is c.redraw() 

1428 

1429 def redraw_after_contract(self, p: Pos) -> None: 

1430 self.c.redraw() 

1431 

1432 def redraw_after_expand(self, p: Pos) -> None: 

1433 self.c.redraw() 

1434 

1435 def redraw_after_head_changed(self) -> None: 

1436 self.c.redraw() 

1437 

1438 def redraw_after_icons_changed(self) -> None: 

1439 self.c.redraw() 

1440 

1441 def redraw_after_select(self, p: Pos=None) -> None: 

1442 self.c.redraw() 

1443 #@+node:ekr.20040803072955.91: *4* LeoTree.onHeadChanged 

1444 # Tricky code: do not change without careful thought and testing. 

1445 # Important: This code *is* used by the leoBridge module. 

1446 def onHeadChanged(self, p: Pos, undoType: str='Typing') -> None: 

1447 """ 

1448 Officially change a headline. 

1449 Set the old undo text to the previous revert point. 

1450 """ 

1451 c, u, w = self.c, self.c.undoer, self.edit_widget(p) 

1452 if not w: 

1453 g.trace('no w') 

1454 return 

1455 ch = '\n' # We only report the final keystroke. 

1456 s = w.getAllText() 

1457 #@+<< truncate s if it has multiple lines >> 

1458 #@+node:ekr.20040803072955.94: *5* << truncate s if it has multiple lines >> 

1459 # Remove trailing newlines before warning of truncation. 

1460 while s and s[-1] == '\n': 

1461 s = s[:-1] 

1462 # Warn if there are multiple lines. 

1463 i = s.find('\n') 

1464 if i > -1: 

1465 g.warning("truncating headline to one line") 

1466 s = s[:i] 

1467 limit = 1000 

1468 if len(s) > limit: 

1469 g.warning("truncating headline to", limit, "characters") 

1470 s = s[:limit] 

1471 s = g.checkUnicode(s or '') 

1472 #@-<< truncate s if it has multiple lines >> 

1473 # Make the change official, but undo to the *old* revert point. 

1474 changed = s != p.h 

1475 if not changed: 

1476 return # Leo 6.4: only call the hooks if the headline has actually changed. 

1477 if g.doHook("headkey1", c=c, p=p, ch=ch, changed=changed): 

1478 return # The hook claims to have handled the event. 

1479 # Handle undo. 

1480 undoData = u.beforeChangeHeadline(p) 

1481 p.initHeadString(s) # change p.h *after* calling undoer's before method. 

1482 if not c.changed: 

1483 c.setChanged() 

1484 # New in Leo 4.4.5: we must recolor the body because 

1485 # the headline may contain directives. 

1486 c.frame.scanForTabWidth(p) 

1487 c.frame.body.recolor(p) 

1488 p.setDirty() 

1489 u.afterChangeHeadline(p, undoType, undoData) 

1490 # Fix bug 1280689: don't call the non-existent c.treeEditFocusHelper 

1491 c.redraw_after_head_changed() 

1492 g.doHook("headkey2", c=c, p=p, ch=ch, changed=changed) 

1493 #@+node:ekr.20031218072017.3705: *3* LeoTree.__init__ 

1494 def __init__(self, frame: Widget) -> None: 

1495 """Ctor for the LeoTree class.""" 

1496 self.frame = frame 

1497 self.c = frame.c 

1498 # New in 3.12: keys vnodes, values are edit_widgets. 

1499 # New in 4.2: keys are vnodes, values are pairs (p,edit widgets). 

1500 self.edit_text_dict: Dict[VNode, Tuple[Pos, Any]] = {} 

1501 # "public" ivars: correspond to setters & getters. 

1502 self.drag_p = None 

1503 self.generation = 0 # low-level vnode methods increment this count. 

1504 self.redrawCount = 0 # For traces 

1505 self.use_chapters = False # May be overridden in subclasses. 

1506 # Define these here to keep pylint happy. 

1507 self.canvas = None 

1508 #@+node:ekr.20061109165848: *3* LeoTree.Must be defined in base class 

1509 #@+node:ekr.20040803072955.126: *4* LeoTree.endEditLabel 

1510 def endEditLabel(self) -> None: 

1511 """End editing of a headline and update p.h.""" 

1512 # Important: this will redraw if necessary. 

1513 self.onHeadChanged(self.c.p) 

1514 # Do *not* call setDefaultUnboundKeyAction here: it might put us in ignore mode! 

1515 # k.setDefaultInputState() 

1516 # k.showStateAndMode() 

1517 # This interferes with the find command and interferes with focus generally! 

1518 # c.bodyWantsFocus() 

1519 #@+node:ekr.20031218072017.3716: *4* LeoTree.getEditTextDict 

1520 def getEditTextDict(self, v: VNode) -> Any: 

1521 # New in 4.2: the default is an empty list. 

1522 return self.edit_text_dict.get(v, []) 

1523 #@+node:ekr.20040803072955.88: *4* LeoTree.onHeadlineKey 

1524 def onHeadlineKey(self, event: Event) -> None: 

1525 """Handle a key event in a headline.""" 

1526 w = event.widget if event else None 

1527 ch = event.char if event else '' 

1528 # This test prevents flashing in the headline when the control key is held down. 

1529 if ch: 

1530 self.updateHead(event, w) 

1531 #@+node:ekr.20120314064059.9739: *4* LeoTree.OnIconCtrlClick (@url) 

1532 def OnIconCtrlClick(self, p: Pos) -> None: 

1533 g.openUrl(p) 

1534 #@+node:ekr.20031218072017.2312: *4* LeoTree.OnIconDoubleClick (do nothing) 

1535 def OnIconDoubleClick(self, p: Pos) -> None: 

1536 pass 

1537 #@+node:ekr.20051026083544.2: *4* LeoTree.updateHead 

1538 def updateHead(self, event: Event, w: Wrapper) -> None: 

1539 """ 

1540 Update a headline from an event. 

1541 

1542 The headline officially changes only when editing ends. 

1543 """ 

1544 k = self.c.k 

1545 ch = event.char if event else '' 

1546 i, j = w.getSelectionRange() 

1547 ins = w.getInsertPoint() 

1548 if i != j: 

1549 ins = i 

1550 if ch in ('\b', 'BackSpace'): 

1551 if i != j: 

1552 w.delete(i, j) 

1553 # Bug fix: 2018/04/19. 

1554 w.setSelectionRange(i, i, insert=i) 

1555 elif i > 0: 

1556 i -= 1 

1557 w.delete(i) 

1558 w.setSelectionRange(i, i, insert=i) 

1559 else: 

1560 w.setSelectionRange(0, 0, insert=0) 

1561 elif ch and ch not in ('\n', '\r'): 

1562 if i != j: 

1563 w.delete(i, j) 

1564 elif k.unboundKeyAction == 'overwrite': 

1565 w.delete(i, i + 1) 

1566 w.insert(ins, ch) 

1567 w.setSelectionRange(ins + 1, ins + 1, insert=ins + 1) 

1568 s = w.getAllText() 

1569 if s.endswith('\n'): 

1570 s = s[:-1] 

1571 # 2011/11/14: Not used at present. 

1572 # w.setWidth(self.headWidth(s=s)) 

1573 if ch in ('\n', '\r'): 

1574 self.endEditLabel() 

1575 #@+node:ekr.20031218072017.3706: *3* LeoTree.Must be defined in subclasses 

1576 # Drawing & scrolling. 

1577 

1578 def drawIcon(self, p: Pos) -> None: 

1579 self.oops() 

1580 

1581 def redraw(self, p: Pos=None) -> None: 

1582 self.oops() 

1583 redraw_now = redraw 

1584 

1585 def scrollTo(self, p: Pos) -> None: 

1586 self.oops() 

1587 

1588 # Headlines. 

1589 

1590 def editLabel(self, p: Pos, selectAll: bool=False, selection: Any=None) -> Wrapper: 

1591 self.oops() 

1592 

1593 def edit_widget(self, p: Pos) -> Wrapper: 

1594 self.oops() 

1595 #@+node:ekr.20040803072955.128: *3* LeoTree.select & helpers 

1596 tree_select_lockout = False 

1597 

1598 def select(self, p: Pos) -> None: 

1599 """ 

1600 Select a node. 

1601 Never redraws outline, but may change coloring of individual headlines. 

1602 The scroll argument is used by the gui to suppress scrolling while dragging. 

1603 """ 

1604 trace = 'select' in g.app.debug and not g.unitTesting 

1605 tag = 'LeoTree.select' 

1606 c = self.c 

1607 if g.app.killed or self.tree_select_lockout: # Essential. 

1608 return 

1609 if trace: 

1610 print(f"----- {tag}: {p.h}") 

1611 # print(f"{tag:>30}: {c.frame.body.wrapper} {p.h}") 

1612 # Format matches traces in leoflexx.py 

1613 # print(f"{tag:30}: {len(p.b):4} {p.gnx} {p.h}") 

1614 try: 

1615 self.tree_select_lockout = True 

1616 self.prev_v = c.p.v 

1617 self.selectHelper(p) 

1618 finally: 

1619 self.tree_select_lockout = False 

1620 if c.enableRedrawFlag: 

1621 p = c.p 

1622 # Don't redraw during unit testing: an important speedup. 

1623 if c.expandAllAncestors(p) and not g.unitTesting: 

1624 # This can happen when doing goto-next-clone. 

1625 c.redraw_later() # This *does* happen sometimes. 

1626 else: 

1627 c.outerUpdate() # Bring the tree up to date. 

1628 if hasattr(self, 'setItemForCurrentPosition'): 

1629 # pylint: disable=no-member 

1630 self.setItemForCurrentPosition() # type:ignore 

1631 else: 

1632 c.requestLaterRedraw = True 

1633 #@+node:ekr.20070423101911: *4* LeoTree.selectHelper & helpers 

1634 def selectHelper(self, p: Pos) -> None: 

1635 """ 

1636 A helper function for leoTree.select. 

1637 Do **not** "optimize" this by returning if p==c.p! 

1638 """ 

1639 if not p: 

1640 # This is not an error! We may be changing roots. 

1641 # Do *not* test c.positionExists(p) here! 

1642 return 

1643 c = self.c 

1644 if not c.frame.body.wrapper: 

1645 return # Defensive. 

1646 if p.v.context != c: 

1647 # Selecting a foreign position will not be pretty. 

1648 g.trace(f"Wrong context: {p.v.context!r} != {c!r}") 

1649 return 

1650 old_p = c.p 

1651 call_event_handlers = p != old_p 

1652 # Order is important... 

1653 # 1. Call c.endEditLabel. 

1654 self.unselect_helper(old_p, p) 

1655 # 2. Call set_body_text_after_select. 

1656 self.select_new_node(old_p, p) 

1657 # 3. Call c.undoer.onSelect. 

1658 self.change_current_position(old_p, p) 

1659 # 4. Set cursor in body. 

1660 self.scroll_cursor(p) 

1661 # 5. Last tweaks. 

1662 self.set_status_line(p) 

1663 if call_event_handlers: 

1664 g.doHook("select2", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p) 

1665 g.doHook("select3", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p) 

1666 #@+node:ekr.20140829053801.18453: *5* 1. LeoTree.unselect_helper 

1667 def unselect_helper(self, old_p: str, p: Pos) -> None: 

1668 """Unselect the old node, calling the unselect hooks.""" 

1669 c = self.c 

1670 call_event_handlers = p != old_p 

1671 if call_event_handlers: 

1672 unselect = not g.doHook( 

1673 "unselect1", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p) 

1674 else: 

1675 unselect = True 

1676 

1677 # Actually unselect the old node. 

1678 if unselect and old_p and old_p != p: 

1679 self.endEditLabel() 

1680 # #1168: Ctrl-minus selects multiple nodes. 

1681 if hasattr(self, 'unselectItem'): 

1682 # pylint: disable=no-member 

1683 self.unselectItem(old_p) # type:ignore 

1684 if call_event_handlers: 

1685 g.doHook("unselect2", c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p) 

1686 #@+node:ekr.20140829053801.18455: *5* 2. LeoTree.select_new_node & helper 

1687 def select_new_node(self, old_p: str, p: Pos) -> None: 

1688 """Select the new node, part 1.""" 

1689 c = self.c 

1690 call_event_handlers = p != old_p 

1691 if ( 

1692 call_event_handlers and g.doHook("select1", 

1693 c=c, new_p=p, old_p=old_p, new_v=p, old_v=old_p) 

1694 ): 

1695 if 'select' in g.app.debug: 

1696 g.trace('select1 override') 

1697 return 

1698 c.frame.setWrap(p) # Not that expensive 

1699 self.set_body_text_after_select(p, old_p) 

1700 c.nodeHistory.update(p) 

1701 #@+node:ekr.20090608081524.6109: *6* LeoTree.set_body_text_after_select 

1702 def set_body_text_after_select(self, p: Pos, old_p: str) -> None: 

1703 """Set the text after selecting a node.""" 

1704 c = self.c 

1705 w = c.frame.body.wrapper 

1706 s = p.v.b # Guaranteed to be unicode. 

1707 # Part 1: get the old text. 

1708 old_s = w.getAllText() 

1709 if p and p == old_p and s == old_s: 

1710 return 

1711 # Part 2: set the new text. This forces a recolor. 

1712 # Important: do this *before* setting text, 

1713 # so that the colorizer will have the proper c.p. 

1714 c.setCurrentPosition(p) 

1715 w.setAllText(s) 

1716 # This is now done after c.p has been changed. 

1717 # p.restoreCursorAndScroll() 

1718 #@+node:ekr.20140829053801.18458: *5* 3. LeoTree.change_current_position 

1719 def change_current_position(self, old_p: str, p: Pos) -> None: 

1720 """Select the new node, part 2.""" 

1721 c = self.c 

1722 # c.setCurrentPosition(p) 

1723 # This is now done in set_body_text_after_select. 

1724 #GS I believe this should also get into the select1 hook 

1725 c.frame.scanForTabWidth(p) 

1726 use_chapters = c.config.getBool('use-chapters') 

1727 if use_chapters: 

1728 cc = c.chapterController 

1729 theChapter = cc and cc.getSelectedChapter() 

1730 if theChapter: 

1731 theChapter.p = p.copy() 

1732 # Do not call treeFocusHelper here! 

1733 # c.treeFocusHelper() 

1734 c.undoer.onSelect(old_p, p) 

1735 #@+node:ekr.20140829053801.18459: *5* 4. LeoTree.scroll_cursor 

1736 def scroll_cursor(self, p: Pos) -> None: 

1737 """Scroll the cursor.""" 

1738 p.restoreCursorAndScroll() # Was in setBodyTextAfterSelect 

1739 #@+node:ekr.20140829053801.18460: *5* 5. LeoTree.set_status_line 

1740 def set_status_line(self, p: Pos) -> None: 

1741 """Update the status line.""" 

1742 c = self.c 

1743 c.frame.body.assignPositionToEditor(p) # New in Leo 4.4.1. 

1744 c.frame.updateStatusLine() # New in Leo 4.4.1. 

1745 c.frame.clearStatusLine() 

1746 if p and p.v: 

1747 c.frame.putStatusLine(p.get_UNL()) 

1748 #@+node:ekr.20031218072017.3718: *3* LeoTree.oops 

1749 def oops(self) -> None: 

1750 g.pr("LeoTree oops:", g.callers(4), "should be overridden in subclass") 

1751 #@-others 

1752#@+node:ekr.20070317073627: ** class LeoTreeTab 

1753class LeoTreeTab: 

1754 """A class representing a tabbed outline pane.""" 

1755 #@+others 

1756 #@+node:ekr.20070317073627.1: *3* ctor (LeoTreeTab) 

1757 def __init__(self, c: Cmdr, chapterController: Any, parentFrame: Widget) -> None: 

1758 self.c = c 

1759 self.cc: Any = chapterController 

1760 self.nb: Any = None # Created in createControl. 

1761 self.parentFrame: Widget = parentFrame 

1762 #@+node:ekr.20070317073755: *3* Must be defined in subclasses 

1763 def createControl(self) -> Wrapper: 

1764 self.oops() 

1765 

1766 def createTab(self, tabName: str, createText: bool=True, widget: Widget=None, select: bool=True) -> None: 

1767 self.oops() 

1768 

1769 def destroyTab(self, tabName: str) -> None: 

1770 self.oops() 

1771 

1772 def selectTab(self, tabName: str, wrap: str='none') -> None: 

1773 self.oops() 

1774 

1775 def setTabLabel(self, tabName: str) -> None: 

1776 self.oops() 

1777 #@+node:ekr.20070317083104: *3* oops 

1778 def oops(self) -> None: 

1779 g.pr("LeoTreeTree oops:", g.callers(4), "should be overridden in subclass") 

1780 #@-others 

1781#@+node:ekr.20031218072017.2191: ** class NullBody (LeoBody) 

1782class NullBody(LeoBody): 

1783 """A do-nothing body class.""" 

1784 #@+others 

1785 #@+node:ekr.20031218072017.2192: *3* NullBody.__init__ 

1786 def __init__(self, frame: Widget=None, parentFrame: Widget=None) -> None: 

1787 """Ctor for NullBody class.""" 

1788 super().__init__(frame, parentFrame) 

1789 self.insertPoint = 0 

1790 self.selection = 0, 0 

1791 self.s = "" # The body text 

1792 self.widget: Widget = None 

1793 self.wrapper: Wrapper = StringTextWrapper(c=self.c, name='body') 

1794 self.editorWrappers['1'] = self.wrapper 

1795 self.colorizer: Any = NullColorizer(self.c) 

1796 #@+node:ekr.20031218072017.2197: *3* NullBody: LeoBody interface 

1797 # Birth, death... 

1798 

1799 def createControl(self, parentFrame: Widget, p: Pos) -> Wrapper: 

1800 pass 

1801 # Editors... 

1802 

1803 def addEditor(self, event: Event=None) -> None: 

1804 pass 

1805 

1806 def assignPositionToEditor(self, p: Pos) -> None: 

1807 pass 

1808 

1809 def createEditorFrame(self, w: Wrapper) -> Wrapper: 

1810 return None 

1811 

1812 def cycleEditorFocus(self, event: Event=None) -> None: 

1813 pass 

1814 

1815 def deleteEditor(self, event: Event=None) -> None: 

1816 pass 

1817 

1818 def selectEditor(self, w: Wrapper) -> None: 

1819 pass 

1820 

1821 def selectLabel(self, w: Wrapper) -> None: 

1822 pass 

1823 

1824 def setEditorColors(self, bg: str, fg: str) -> None: 

1825 pass 

1826 

1827 def unselectLabel(self, w: Wrapper) -> None: 

1828 pass 

1829 

1830 def updateEditors(self) -> None: 

1831 pass 

1832 # Events... 

1833 

1834 def forceFullRecolor(self) -> None: 

1835 pass 

1836 

1837 def scheduleIdleTimeRoutine(self, function: str, *args: str, **keys: str) -> None: 

1838 pass 

1839 # Low-level gui... 

1840 

1841 def setFocus(self) -> None: 

1842 pass 

1843 #@-others 

1844#@+node:ekr.20031218072017.2218: ** class NullColorizer (BaseColorizer) 

1845class NullColorizer(leoColorizer.BaseColorizer): 

1846 """A colorizer class that doesn't color.""" 

1847 

1848 recolorCount = 0 

1849 

1850 def colorize(self, p: Pos) -> None: 

1851 self.recolorCount += 1 # For #503: Use string/null gui for unit tests 

1852#@+node:ekr.20031218072017.2222: ** class NullFrame (LeoFrame) 

1853class NullFrame(LeoFrame): 

1854 """A null frame class for tests and batch execution.""" 

1855 #@+others 

1856 #@+node:ekr.20040327105706: *3* NullFrame.ctor 

1857 def __init__(self, c: Cmdr, title: str, gui: Any) -> None: 

1858 """Ctor for the NullFrame class.""" 

1859 super().__init__(c, gui) 

1860 assert self.c 

1861 self.wrapper: Wrapper = None 

1862 self.iconBar: Wrapper = NullIconBarClass(self.c, self) 

1863 self.initComplete = True 

1864 self.isNullFrame = True 

1865 self.outerFrame: Wrapper = None 

1866 self.ratio = self.secondary_ratio = 0.5 

1867 self.statusLineClass: Any = NullStatusLineClass 

1868 self.title = title 

1869 self.top = None # Always None. 

1870 # Create the component objects. 

1871 self.body: Wrapper = NullBody(frame=self, parentFrame=None) 

1872 self.log: Wrapper = NullLog(frame=self, parentFrame=None) 

1873 self.menu: Wrapper = leoMenu.NullMenu(frame=self) 

1874 self.tree: Wrapper = NullTree(frame=self) 

1875 # Default window position. 

1876 self.w = 600 

1877 self.h = 500 

1878 self.x = 40 

1879 self.y = 40 

1880 #@+node:ekr.20061109124552: *3* NullFrame.do nothings 

1881 def bringToFront(self) -> None: 

1882 pass 

1883 

1884 def cascade(self, event: Event=None) -> None: 

1885 pass 

1886 

1887 def contractBodyPane(self, event: Event=None) -> None: 

1888 pass 

1889 

1890 def contractLogPane(self, event: Event=None) -> None: 

1891 pass 

1892 

1893 def contractOutlinePane(self, event: Event=None) -> None: 

1894 pass 

1895 

1896 def contractPane(self, event: Event=None) -> None: 

1897 pass 

1898 

1899 def deiconify(self) -> None: 

1900 pass 

1901 

1902 def destroySelf(self) -> None: 

1903 pass 

1904 

1905 def equalSizedPanes(self, event: Event=None) -> None: 

1906 pass 

1907 

1908 def expandBodyPane(self, event: Event=None) -> None: 

1909 pass 

1910 

1911 def expandLogPane(self, event: Event=None) -> None: 

1912 pass 

1913 

1914 def expandOutlinePane(self, event: Event=None) -> None: 

1915 pass 

1916 

1917 def expandPane(self, event: Event=None) -> None: 

1918 pass 

1919 

1920 def forceWrap(self, p: Pos) -> None: 

1921 pass 

1922 

1923 def fullyExpandBodyPane(self, event: Event=None) -> None: 

1924 pass 

1925 

1926 def fullyExpandLogPane(self, event: Event=None) -> None: 

1927 pass 

1928 

1929 def fullyExpandOutlinePane(self, event: Event=None) -> None: 

1930 pass 

1931 

1932 def fullyExpandPane(self, event: Event=None) -> None: 

1933 pass 

1934 

1935 def get_window_info(self) -> Tuple[int, int, int, int]: 

1936 return 600, 500, 20, 20 

1937 

1938 def hideBodyPane(self, event: Event=None) -> None: 

1939 pass 

1940 

1941 def hideLogPane(self, event: Event=None) -> None: 

1942 pass 

1943 

1944 def hideLogWindow(self, event: Event=None) -> None: 

1945 pass 

1946 

1947 def hideOutlinePane(self, event: Event=None) -> None: 

1948 pass 

1949 

1950 def hidePane(self, event: Event=None) -> None: 

1951 pass 

1952 

1953 def leoHelp(self, event: Event=None) -> None: 

1954 pass 

1955 

1956 def lift(self) -> None: 

1957 pass 

1958 

1959 def minimizeAll(self, event: Event=None) -> None: 

1960 pass 

1961 

1962 def oops(self) -> None: 

1963 g.trace("NullFrame", g.callers(4)) 

1964 

1965 def resizePanesToRatio(self, ratio: float, secondary_ratio: float) -> None: 

1966 pass 

1967 

1968 def resizeToScreen(self, event: Event=None) -> None: 

1969 pass 

1970 

1971 def setInitialWindowGeometry(self) -> None: 

1972 pass 

1973 

1974 def setTopGeometry(self, w: int, h: int, x: int, y: int) -> None: 

1975 pass 

1976 

1977 def setWrap(self, flag: str, force: bool=False) -> None: 

1978 pass 

1979 

1980 def toggleActivePane(self, event: Event=None) -> None: 

1981 pass 

1982 

1983 def toggleSplitDirection(self, event: Event=None) -> None: 

1984 pass 

1985 

1986 def update(self) -> None: 

1987 pass 

1988 #@+node:ekr.20171112115045.1: *3* NullFrame.finishCreate 

1989 def finishCreate(self) -> None: 

1990 

1991 # 2017/11/12: For #503: Use string/null gui for unit tests. 

1992 self.createFirstTreeNode() # Call the base LeoFrame method. 

1993 #@-others 

1994#@+node:ekr.20070301164543: ** class NullIconBarClass 

1995class NullIconBarClass: 

1996 """A class representing the singleton Icon bar""" 

1997 #@+others 

1998 #@+node:ekr.20070301164543.1: *3* NullIconBarClass.ctor 

1999 def __init__(self, c: Cmdr, parentFrame: Widget) -> None: 

2000 """Ctor for NullIconBarClass.""" 

2001 self.c = c 

2002 self.iconFrame = None 

2003 self.parentFrame: Widget = parentFrame 

2004 self.w: Widget = g.NullObject() 

2005 #@+node:ekr.20070301165343: *3* NullIconBarClass.Do nothing 

2006 def addRow(self, height: str=None) -> None: 

2007 pass 

2008 

2009 def addRowIfNeeded(self) -> None: 

2010 pass 

2011 

2012 def addWidget(self, w: Wrapper) -> None: 

2013 pass 

2014 

2015 def createChaptersIcon(self) -> None: 

2016 pass 

2017 

2018 def deleteButton(self, w: Wrapper) -> None: 

2019 pass 

2020 

2021 def getNewFrame(self) -> None: 

2022 return None 

2023 

2024 def hide(self) -> None: 

2025 pass 

2026 

2027 def show(self) -> None: 

2028 pass 

2029 #@+node:ekr.20070301164543.2: *3* NullIconBarClass.add 

2030 def add(self, *args: str, **keys: str) -> Widget: 

2031 """Add a (virtual) button to the (virtual) icon bar.""" 

2032 command: Any = keys.get('command') 

2033 text = keys.get('text') 

2034 try: 

2035 g.app.iconWidgetCount += 1 

2036 except Exception: 

2037 g.app.iconWidgetCount = 1 

2038 n = g.app.iconWidgetCount 

2039 name = f"nullButtonWidget {n}" 

2040 if not command: 

2041 

2042 def commandCallback(name: str=name) -> None: 

2043 g.pr(f"command for {name}") 

2044 

2045 command = commandCallback 

2046 

2047 

2048 class nullButtonWidget: 

2049 

2050 def __init__(self, c: Cmdr, command: Any, name: str, text: str) -> None: 

2051 self.c = c 

2052 self.command = command 

2053 self.name = name 

2054 self.text = text 

2055 

2056 def __repr__(self) -> str: 

2057 return self.name 

2058 

2059 b = nullButtonWidget(self.c, command, name, text) 

2060 return b 

2061 #@+node:ekr.20140904043623.18574: *3* NullIconBarClass.clear 

2062 def clear(self) -> None: 

2063 g.app.iconWidgetCount = 0 

2064 g.app.iconImageRefs = [] 

2065 #@+node:ekr.20140904043623.18575: *3* NullIconBarClass.setCommandForButton 

2066 def setCommandForButton(self, 

2067 button: Any, 

2068 command: str, 

2069 command_p: Pos, 

2070 controller: Cmdr,\ 

2071 gnx: str, 

2072 script: str, 

2073 ) -> None: 

2074 button.command = command 

2075 try: 

2076 # See PR #2441: Add rclick support. 

2077 from leo.plugins.mod_scripting import build_rclick_tree 

2078 rclicks = build_rclick_tree(command_p, top_level=True) 

2079 button.rclicks = rclicks 

2080 except Exception: 

2081 pass 

2082 #@-others 

2083#@+node:ekr.20031218072017.2232: ** class NullLog (LeoLog) 

2084class NullLog(LeoLog): 

2085 """A do-nothing log class.""" 

2086 #@+others 

2087 #@+node:ekr.20070302095500: *3* NullLog.Birth 

2088 #@+node:ekr.20041012083237: *4* NullLog.__init__ 

2089 def __init__(self, frame: Widget=None, parentFrame: Widget=None) -> None: 

2090 

2091 super().__init__(frame, parentFrame) 

2092 self.isNull = True 

2093 # self.logCtrl is now a property of the base LeoLog class. 

2094 self.logNumber = 0 

2095 self.widget: Widget = self.createControl(parentFrame) 

2096 #@+node:ekr.20120216123546.10951: *4* NullLog.finishCreate 

2097 def finishCreate(self) -> None: 

2098 pass 

2099 #@+node:ekr.20041012083237.1: *4* NullLog.createControl 

2100 def createControl(self, parentFrame: Widget) -> Wrapper: 

2101 return self.createTextWidget(parentFrame) 

2102 #@+node:ekr.20070302095121: *4* NullLog.createTextWidge 

2103 def createTextWidget(self, parentFrame: Widget) -> Wrapper: 

2104 self.logNumber += 1 

2105 c = self.c 

2106 log = StringTextWrapper(c=c, name=f"log-{self.logNumber}") 

2107 return log 

2108 #@+node:ekr.20181119135041.1: *3* NullLog.hasSelection 

2109 def hasSelection(self) -> None: 

2110 return self.widget.hasSelection() 

2111 #@+node:ekr.20111119145033.10186: *3* NullLog.isLogWidget 

2112 def isLogWidget(self, w: Wrapper) -> bool: 

2113 return False 

2114 #@+node:ekr.20041012083237.2: *3* NullLog.oops 

2115 def oops(self) -> None: 

2116 g.trace("NullLog:", g.callers(4)) 

2117 #@+node:ekr.20041012083237.3: *3* NullLog.put and putnl 

2118 def put(self, 

2119 s: str, color: str=None, tabName: str='Log', from_redirect: bool=False, nodeLink: str=None, 

2120 ) -> None: 

2121 if self.enabled and not g.unitTesting: 

2122 try: 

2123 g.pr(s, newline=False) 

2124 except UnicodeError: 

2125 s = s.encode('ascii', 'replace') # type:ignore 

2126 g.pr(s, newline=False) 

2127 

2128 def putnl(self, tabName: str='Log') -> None: 

2129 if self.enabled and not g.unitTesting: 

2130 g.pr('') 

2131 #@+node:ekr.20060124085830: *3* NullLog.tabs 

2132 def clearTab(self, tabName: str, wrap: str='none') -> None: 

2133 pass 

2134 

2135 def createCanvas(self, tabName: str) -> None: 

2136 pass 

2137 

2138 def createTab(self, tabName: str, createText: bool=True, widget: Widget=None, wrap: str='none') -> None: 

2139 pass 

2140 

2141 def deleteTab(self, tabName: str) -> None: 

2142 pass 

2143 

2144 def getSelectedTab(self) -> None: 

2145 return None 

2146 

2147 def lowerTab(self, tabName: str) -> None: 

2148 pass 

2149 

2150 def raiseTab(self, tabName: str) -> None: 

2151 pass 

2152 

2153 def renameTab(self, oldName: str, newName: str) -> None: 

2154 pass 

2155 

2156 def selectTab(self, tabName: str, wrap: str='none') -> None: 

2157 pass 

2158 #@-others 

2159#@+node:ekr.20070302171509: ** class NullStatusLineClass 

2160class NullStatusLineClass: 

2161 """A do-nothing status line.""" 

2162 

2163 def __init__(self, c: Cmdr, parentFrame: Widget) -> None: 

2164 """Ctor for NullStatusLine class.""" 

2165 self.c = c 

2166 self.enabled = False 

2167 self.parentFrame = parentFrame 

2168 self.textWidget: Wrapper = StringTextWrapper(c, name='status-line') 

2169 # Set the official ivars. 

2170 c.frame.statusFrame = None 

2171 c.frame.statusLabel = None 

2172 c.frame.statusText = self.textWidget 

2173 #@+others 

2174 #@+node:ekr.20070302171917: *3* NullStatusLineClass.methods 

2175 def disable(self, background: str=None) -> None: 

2176 self.enabled = False 

2177 # self.c.bodyWantsFocus() 

2178 

2179 def enable(self, background: str="white") -> None: 

2180 self.c.widgetWantsFocus(self.textWidget) 

2181 self.enabled = True 

2182 

2183 def clear(self) -> None: 

2184 self.textWidget.delete(0, 'end') 

2185 

2186 def get(self) -> str: 

2187 return self.textWidget.getAllText() 

2188 

2189 def isEnabled(self) -> bool: 

2190 return self.enabled 

2191 

2192 def put(self, s: str, bg: str=None, fg: str=None) -> None: 

2193 self.textWidget.insert('end', s) 

2194 

2195 def setFocus(self) -> None: 

2196 pass 

2197 

2198 def update(self) -> None: 

2199 pass 

2200 #@-others 

2201#@+node:ekr.20031218072017.2233: ** class NullTree (LeoTree) 

2202class NullTree(LeoTree): 

2203 """A do-almost-nothing tree class.""" 

2204 #@+others 

2205 #@+node:ekr.20031218072017.2234: *3* NullTree.__init__ 

2206 def __init__(self, frame: Widget) -> None: 

2207 """Ctor for NullTree class.""" 

2208 super().__init__(frame) 

2209 assert self.frame 

2210 self.c = frame.c 

2211 self.editWidgetsDict: Dict[VNode, Widget] = {} # Keys are vnodes, values are StringTextWidgets. 

2212 self.font = None 

2213 self.fontName = None 

2214 self.canvas = None 

2215 self.treeWidget = g.NullObject() 

2216 self.redrawCount = 0 

2217 self.updateCount = 0 

2218 #@+node:ekr.20070228163350.2: *3* NullTree.edit_widget 

2219 def edit_widget(self, p: Pos) -> Wrapper: 

2220 d = self.editWidgetsDict 

2221 if not p or not p.v: 

2222 return None 

2223 w = d.get(p.v) 

2224 if not w: 

2225 d[p.v] = w = StringTextWrapper( 

2226 c=self.c, 

2227 name=f"head-{1 + len(list(d.keys())):d}") 

2228 w.setAllText(p.h) 

2229 return w 

2230 #@+node:ekr.20070228164730: *3* NullTree.editLabel 

2231 def editLabel(self, p: Pos, selectAll: bool=False, selection: Any=None) -> Tuple[Any, Wrapper]: 

2232 """Start editing p's headline.""" 

2233 self.endEditLabel() 

2234 if p: 

2235 wrapper = StringTextWrapper(c=self.c, name='head-wrapper') 

2236 e = None 

2237 return e, wrapper 

2238 return None, None 

2239 #@+node:ekr.20070228173611: *3* NullTree.printWidgets 

2240 def printWidgets(self) -> None: 

2241 d = self.editWidgetsDict 

2242 for key in d: 

2243 # keys are vnodes, values are StringTextWidgets. 

2244 w = d.get(key) 

2245 g.pr('w', w, 'v.h:', key.headString, 's:', repr(w.s)) 

2246 #@+node:ekr.20070228163350.1: *3* NullTree.Drawing & scrolling 

2247 def drawIcon(self, p: Pos) -> None: 

2248 pass 

2249 

2250 def redraw(self, p: Pos=None) -> None: 

2251 self.redrawCount += 1 

2252 

2253 redraw_now = redraw 

2254 

2255 def redraw_after_contract(self, p: Pos) -> None: 

2256 self.redraw() 

2257 

2258 def redraw_after_expand(self, p: Pos) -> None: 

2259 self.redraw() 

2260 

2261 def redraw_after_head_changed(self) -> None: 

2262 self.redraw() 

2263 

2264 def redraw_after_icons_changed(self) -> None: 

2265 self.redraw() 

2266 

2267 def redraw_after_select(self, p: Pos=None) -> None: 

2268 self.redraw() 

2269 

2270 def scrollTo(self, p: Pos) -> None: 

2271 pass 

2272 

2273 def updateAllIcons(self, p: Pos) -> None: 

2274 pass 

2275 

2276 def updateIcon(self, p: Pos) -> None: 

2277 pass 

2278 #@+node:ekr.20070228160345: *3* NullTree.setHeadline 

2279 def setHeadline(self, p: Pos, s: str) -> None: 

2280 """Set the actual text of the headline widget. 

2281 

2282 This is called from the undo/redo logic to change the text before redrawing.""" 

2283 w = self.edit_widget(p) 

2284 if w: 

2285 w.delete(0, 'end') 

2286 if s.endswith('\n') or s.endswith('\r'): 

2287 s = s[:-1] 

2288 w.insert(0, s) 

2289 else: 

2290 g.trace('-' * 20, 'oops') 

2291 #@-others 

2292#@+node:ekr.20070228074228.1: ** class StringTextWrapper 

2293class StringTextWrapper: 

2294 """A class that represents text as a Python string.""" 

2295 #@+others 

2296 #@+node:ekr.20070228074228.2: *3* stw.ctor 

2297 def __init__(self, c: Cmdr, name: str) -> None: 

2298 """Ctor for the StringTextWrapper class.""" 

2299 self.c = c 

2300 self.name = name 

2301 self.ins = 0 

2302 self.sel = 0, 0 

2303 self.s = '' 

2304 self.supportsHighLevelInterface = True 

2305 self.virtualInsertPoint = 0 

2306 self.widget = None # This ivar must exist, and be None. 

2307 

2308 def __repr__(self) -> str: 

2309 return f"<StringTextWrapper: {id(self)} {self.name}>" 

2310 

2311 def getName(self) -> str: 

2312 """StringTextWrapper.""" 

2313 return self.name # Essential. 

2314 #@+node:ekr.20140903172510.18578: *3* stw.Clipboard 

2315 def clipboard_clear(self) -> None: 

2316 g.app.gui.replaceClipboardWith('') 

2317 

2318 def clipboard_append(self, s: str) -> None: 

2319 s1 = g.app.gui.getTextFromClipboard() 

2320 g.app.gui.replaceClipboardWith(s1 + s) 

2321 #@+node:ekr.20140903172510.18579: *3* stw.Do-nothings 

2322 # For StringTextWrapper. 

2323 

2324 def flashCharacter(self, i: int, bg: str='white', fg: str='red', flashes: int=3, delay: int=75) -> None: 

2325 pass 

2326 

2327 def getXScrollPosition(self) -> int: 

2328 return 0 

2329 

2330 def getYScrollPosition(self) -> int: 

2331 return 0 

2332 

2333 def see(self, i: int) -> None: 

2334 pass 

2335 

2336 def seeInsertPoint(self) -> None: 

2337 pass 

2338 

2339 def setFocus(self) -> None: 

2340 pass 

2341 

2342 def setStyleClass(self, name: str) -> None: 

2343 pass 

2344 

2345 def setXScrollPosition(self, i: int) -> None: 

2346 pass 

2347 

2348 def setYScrollPosition(self, i: int) -> None: 

2349 pass 

2350 #@+node:ekr.20140903172510.18591: *3* stw.Text 

2351 #@+node:ekr.20140903172510.18592: *4* stw.appendText 

2352 def appendText(self, s: str) -> None: 

2353 """StringTextWrapper.""" 

2354 self.s = self.s + g.toUnicode(s) # defensive 

2355 self.ins = len(self.s) 

2356 self.sel = self.ins, self.ins 

2357 #@+node:ekr.20140903172510.18593: *4* stw.delete 

2358 def delete(self, i: Index, j: Index=None) -> None: 

2359 """StringTextWrapper.""" 

2360 i = self.toPythonIndex(i) 

2361 if j is None: 

2362 j = i + 1 

2363 j = self.toPythonIndex(j) 

2364 # This allows subclasses to use this base class method. 

2365 if i > j: 

2366 i, j = j, i 

2367 s = self.getAllText() 

2368 self.setAllText(s[:i] + s[j:]) 

2369 # Bug fix: 2011/11/13: Significant in external tests. 

2370 self.setSelectionRange(i, i, insert=i) 

2371 #@+node:ekr.20140903172510.18594: *4* stw.deleteTextSelection 

2372 def deleteTextSelection(self) -> None: 

2373 """StringTextWrapper.""" 

2374 i, j = self.getSelectionRange() 

2375 self.delete(i, j) 

2376 #@+node:ekr.20140903172510.18595: *4* stw.get 

2377 def get(self, i: int, j: int=None) -> str: 

2378 """StringTextWrapper.""" 

2379 i = self.toPythonIndex(i) 

2380 if j is None: 

2381 j = i + 1 

2382 j = self.toPythonIndex(j) 

2383 s = self.s[i:j] 

2384 return g.toUnicode(s) 

2385 #@+node:ekr.20140903172510.18596: *4* stw.getAllText 

2386 def getAllText(self) -> str: 

2387 """StringTextWrapper.""" 

2388 s = self.s 

2389 return g.checkUnicode(s) 

2390 #@+node:ekr.20140903172510.18584: *4* stw.getInsertPoint 

2391 def getInsertPoint(self) -> int: 

2392 """StringTextWrapper.""" 

2393 i = self.ins 

2394 if i is None: 

2395 if self.virtualInsertPoint is None: 

2396 i = 0 

2397 else: 

2398 i = self.virtualInsertPoint 

2399 self.virtualInsertPoint = i 

2400 return i 

2401 #@+node:ekr.20140903172510.18597: *4* stw.getSelectedText 

2402 def getSelectedText(self) -> str: 

2403 """StringTextWrapper.""" 

2404 i, j = self.sel 

2405 s = self.s[i:j] 

2406 return g.checkUnicode(s) 

2407 #@+node:ekr.20140903172510.18585: *4* stw.getSelectionRange 

2408 def getSelectionRange(self, sort: bool=True) -> Tuple[int, int]: 

2409 """Return the selected range of the widget.""" 

2410 sel = self.sel 

2411 if len(sel) == 2 and sel[0] >= 0 and sel[1] >= 0: 

2412 i, j = sel 

2413 if sort and i > j: 

2414 sel = j, i # Bug fix: 10/5/07 

2415 return sel 

2416 i = self.ins 

2417 return i, i 

2418 #@+node:ekr.20140903172510.18586: *4* stw.hasSelection 

2419 def hasSelection(self) -> bool: 

2420 """StringTextWrapper.""" 

2421 i, j = self.getSelectionRange() 

2422 return i != j 

2423 #@+node:ekr.20140903172510.18598: *4* stw.insert 

2424 def insert(self, i: Index, s: str) -> None: 

2425 """StringTextWrapper.""" 

2426 i = self.toPythonIndex(i) 

2427 s1 = s 

2428 self.s = self.s[:i] + s1 + self.s[i:] 

2429 i += len(s1) 

2430 self.ins = i 

2431 self.sel = i, i 

2432 #@+node:ekr.20140903172510.18589: *4* stw.selectAllText 

2433 def selectAllText(self, insert: int=None) -> None: 

2434 """StringTextWrapper.""" 

2435 self.setSelectionRange(0, 'end', insert=insert) 

2436 #@+node:ekr.20140903172510.18600: *4* stw.setAllText 

2437 def setAllText(self, s: str) -> None: 

2438 """StringTextWrapper.""" 

2439 self.s = s 

2440 i = len(self.s) 

2441 self.ins = i 

2442 self.sel = i, i 

2443 #@+node:ekr.20140903172510.18587: *4* stw.setInsertPoint 

2444 def setInsertPoint(self, pos: str, s: str=None) -> None: 

2445 """StringTextWrapper.""" 

2446 i = self.toPythonIndex(pos) 

2447 self.virtualInsertPoint = i 

2448 self.ins = i 

2449 self.sel = i, i 

2450 #@+node:ekr.20070228111853: *4* stw.setSelectionRange 

2451 def setSelectionRange(self, i: Index, j: Index, insert: Index=None) -> None: 

2452 """StringTextWrapper.""" 

2453 i, j = self.toPythonIndex(i), self.toPythonIndex(j) 

2454 self.sel = i, j 

2455 self.ins = j if insert is None else self.toPythonIndex(insert) 

2456 #@+node:ekr.20140903172510.18581: *4* stw.toPythonIndex 

2457 def toPythonIndex(self, index: Index) -> int: 

2458 """ 

2459 StringTextWrapper.toPythonIndex. 

2460 

2461 Convert indices of the form 'end' or 'n1.n2' to integer indices into self.s. 

2462 

2463 Unit tests *do* use non-integer indices, so removing this method would be tricky. 

2464 """ 

2465 return g.toPythonIndex(self.s, index) 

2466 #@+node:ekr.20140903172510.18582: *4* stw.toPythonIndexRowCol 

2467 def toPythonIndexRowCol(self, index: str) -> Tuple[int, int, int]: 

2468 """StringTextWrapper.""" 

2469 s = self.getAllText() 

2470 i = self.toPythonIndex(index) 

2471 row, col = g.convertPythonIndexToRowCol(s, i) 

2472 return i, row, col 

2473 #@-others 

2474#@-others 

2475#@@language python 

2476#@@tabwidth -4 

2477#@@pagewidth 70 

2478#@-leo