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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1532 statements  

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 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']) 

50 

51def body_cmd(name): 

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

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

54 

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.""" 

66 

67 def __init__(self, c, parentFrame): 

68 pass 

69 

70 def clear(self): 

71 pass 

72 

73 def disable(self, background=None): 

74 pass 

75 

76 def enable(self, background="white"): 

77 pass 

78 

79 def get(self): 

80 return '' 

81 

82 def isEnabled(self): 

83 return False 

84 

85 def put(self, s, bg=None, fg=None): 

86 pass 

87 

88 def setFocus(self): 

89 pass 

90 

91 def update(self): 

92 pass 

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

94class TreeAPI: 

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

96 

97 def __init__(self, frame): 

98 pass 

99 # Must be defined in subclasses. 

100 

101 def drawIcon(self, p): 

102 pass 

103 

104 def editLabel(self, v, selectAll=False, selection=None): 

105 pass 

106 

107 def edit_widget(self, p): 

108 return None 

109 

110 def redraw(self, p=None): 

111 pass 

112 redraw_now = redraw 

113 

114 def scrollTo(self, p): 

115 pass 

116 # May be defined in subclasses. 

117 

118 def initAfterLoad(self): 

119 pass 

120 

121 def onHeadChanged(self, p, undoType='Typing', s=None, e=None): 

122 pass 

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

124 

125 def redraw_after_contract(self, p): 

126 pass 

127 

128 def redraw_after_expand(self, p): 

129 pass 

130 

131 def redraw_after_head_changed(self): 

132 pass 

133 

134 def redraw_after_icons_changed(self): 

135 pass 

136 

137 def redraw_after_select(self, p=None): 

138 pass 

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

140 # def OnIconDoubleClick (self,p): 

141 

142 def OnIconCtrlClick(self, p): 

143 pass 

144 

145 def endEditLabel(self): 

146 pass 

147 

148 def getEditTextDict(self, v): 

149 return None 

150 

151 def injectCallbacks(self): 

152 pass 

153 

154 def onHeadlineKey(self, event): 

155 pass 

156 

157 def select(self, p): 

158 pass 

159 

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.""" 

165 

166 def __init__(self, c): 

167 pass 

168 

169 def appendText(self, s): 

170 pass 

171 

172 def clipboard_append(self, s): 

173 pass 

174 

175 def clipboard_clear(self): 

176 pass 

177 

178 def delete(self, i, j=None): 

179 pass 

180 

181 def deleteTextSelection(self): 

182 pass 

183 

184 def disable(self): 

185 pass 

186 

187 def enable(self, enabled=True): 

188 pass 

189 

190 def flashCharacter(self, i, bg='white', fg='red', flashes=3, delay=75): 

191 pass 

192 

193 def get(self, i, j): 

194 return '' 

195 

196 def getAllText(self): 

197 return '' 

198 

199 def getInsertPoint(self): 

200 return 0 

201 

202 def getSelectedText(self): 

203 return '' 

204 

205 def getSelectionRange(self): 

206 return (0, 0) 

207 

208 def getXScrollPosition(self): 

209 return 0 

210 

211 def getYScrollPosition(self): 

212 return 0 

213 

214 def hasSelection(self): 

215 return False 

216 

217 def insert(self, i, s): 

218 pass 

219 

220 def see(self, i): 

221 pass 

222 

223 def seeInsertPoint(self): 

224 pass 

225 

226 def selectAllText(self, insert=None): 

227 pass 

228 

229 def setAllText(self, s): 

230 pass 

231 

232 def setFocus(self): 

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

234 

235 def setInsertPoint(self, pos, s=None): 

236 pass 

237 

238 def setSelectionRange(self, i, j, insert=None): 

239 pass 

240 

241 def setXScrollPosition(self, i): 

242 pass 

243 

244 def setYScrollPosition(self, i): 

245 pass 

246 

247 def tag_configure(self, colorName, **keys): 

248 pass 

249 

250 def toPythonIndex(self, index): 

251 return 0 

252 

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.""" 

258 

259 def __init__(self, c, parentFrame): 

260 pass 

261 

262 def add(self, *args, **keys): 

263 pass 

264 

265 def addRow(self, height=None): 

266 pass 

267 

268 def addRowIfNeeded(self): 

269 pass 

270 

271 def addWidget(self, w): 

272 pass 

273 

274 def clear(self): 

275 pass 

276 

277 def createChaptersIcon(self): 

278 pass 

279 

280 def deleteButton(self, w): 

281 pass 

282 

283 def getNewFrame(self): 

284 pass 

285 

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 # May be overridden in subclasses. 

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

312 def forceFullRecolor(self): 

313 pass 

314 

315 def getColorizer(self): 

316 return self.colorizer 

317 

318 def updateSyntaxColorer(self, p): 

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

320 

321 def recolor(self, p): 

322 self.c.recolor() 

323 

324 recolor_now = recolor 

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

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

327 # Fail loudly if these methods are not defined. 

328 

329 def oops(self): 

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

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

332 

333 def createEditorFrame(self, w): 

334 self.oops() 

335 

336 def createTextWidget(self, parentFrame, p, name): 

337 self.oops() 

338 

339 def packEditorLabelWidget(self, w): 

340 self.oops() 

341 

342 def onFocusOut(self, obj): 

343 pass 

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

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

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

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

348 def addEditor(self, event=None): 

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

350 c, p = self.c, self.c.p 

351 self.totalNumberOfEditors += 1 

352 self.numberOfEditors += 1 

353 if self.numberOfEditors == 2: 

354 # Inject the ivars into the first editor. 

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

356 d = self.editorWrappers 

357 keys = list(d.keys()) 

358 if len(keys) == 1: 

359 # Immediately create the label in the old editor. 

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

361 self.updateInjectedIvars(w_old, p) 

362 self.selectLabel(w_old) 

363 else: 

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

365 name = f"{self.totalNumberOfEditors}" 

366 pane = self.pb.add(name) 

367 panes = self.pb.panes() 

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

369 # Create the text wrapper. 

370 f = self.createEditorFrame(pane) 

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

372 wrapper.delete(0, 'end') 

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

374 wrapper.see(0) 

375 c.k.completeAllBindingsForWidget(wrapper) 

376 self.recolorWidget(p, wrapper) 

377 self.editorWrappers[name] = wrapper 

378 for pane in panes: 

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

380 self.pb.updatelayout() 

381 c.frame.body.wrapper = wrapper 

382 # Finish... 

383 self.updateInjectedIvars(wrapper, p) 

384 self.selectLabel(wrapper) 

385 self.selectEditor(wrapper) 

386 self.updateEditors() 

387 c.bodyWantsFocus() 

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

389 def assignPositionToEditor(self, p): 

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

391 c = self.c 

392 w = c.frame.body.widget 

393 self.updateInjectedIvars(w, p) 

394 self.selectLabel(w) 

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

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

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

398 def cycleEditorFocus(self, event=None): 

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

400 c = self.c 

401 d = self.editorWrappers 

402 w = c.frame.body.wrapper 

403 values = list(d.values()) 

404 if len(values) > 1: 

405 i = values.index(w) + 1 

406 if i == len(values): 

407 i = 0 

408 w2 = values[i] 

409 assert w != w2 

410 self.selectEditor(w2) 

411 c.frame.body.wrapper = w2 

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

413 def deleteEditor(self, event=None): 

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

415 c = self.c 

416 w = c.frame.body.wapper 

417 d = self.editorWrappers 

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

419 return 

420 name = w.leo_name 

421 del d[name] 

422 self.pb.delete(name) 

423 panes = self.pb.panes() 

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

425 for pane in panes: 

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

427 # Select another editor. 

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

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

430 self.numberOfEditors -= 1 

431 self.selectEditor(w) 

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

433 def findEditorForChapter(self, chapter, p): 

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

435 c = self.c 

436 d = self.editorWrappers 

437 values = list(d.values()) 

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

439 if p: 

440 for w in values: 

441 if ( 

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

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

444 ): 

445 return w 

446 # Next, try to match just the chapter. 

447 for w in values: 

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

449 return w 

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

451 return c.frame.body.wrapper 

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

453 def unselectLabel(self, w): 

454 self.createChapterIvar(w) 

455 self.packEditorLabelWidget(w) 

456 s = self.computeLabel(w) 

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

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

459 

460 def selectLabel(self, w): 

461 if self.numberOfEditors > 1: 

462 self.createChapterIvar(w) 

463 self.packEditorLabelWidget(w) 

464 s = self.computeLabel(w) 

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

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

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

468 w.leo_label.pack_forget() 

469 w.leo_label = None 

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

471 selectEditorLockout = False 

472 

473 def selectEditor(self, w): 

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

475 # Called whenever wrapper must be selected. 

476 c = self.c 

477 if self.selectEditorLockout: 

478 return None 

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

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

481 c.selectPosition(w.leo_p) 

482 c.bodyWantsFocus() 

483 return None 

484 try: 

485 val = None 

486 self.selectEditorLockout = True 

487 val = self.selectEditorHelper(w) 

488 finally: 

489 self.selectEditorLockout = False 

490 return val # Don't put a return in a finally clause. 

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

492 def selectEditorHelper(self, wrapper): 

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

494 c = self.c 

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

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

497 return 

498 self.deactivateActiveEditor(wrapper) 

499 # The actual switch. 

500 c.frame.body.wrapper = wrapper 

501 wrapper.leo_active = True 

502 self.switchToChapter(wrapper) 

503 self.selectLabel(wrapper) 

504 if not self.ensurePositionExists(wrapper): 

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

506 return 

507 p = wrapper.leo_p 

508 c.redraw(p) 

509 c.recolor() 

510 c.bodyWantsFocus() 

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

512 # Called from addEditor and assignPositionToEditor 

513 

514 def updateEditors(self): 

515 c, p = self.c, self.c.p 

516 d = self.editorWrappers 

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

518 return # There is only the main widget. 

519 for key in d: 

520 wrapper = d.get(key) 

521 v = wrapper.leo_v 

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

523 wrapper.delete(0, 'end') 

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

525 self.recolorWidget(p, wrapper) 

526 c.bodyWantsFocus() 

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

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

529 def computeLabel(self, w): 

530 s = w.leo_label_s 

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

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

533 return s 

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

535 def createChapterIvar(self, w): 

536 c = self.c 

537 cc = c.chapterController 

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

539 if cc and self.use_chapters: 

540 w.leo_chapter = cc.getSelectedChapter() 

541 else: 

542 w.leo_chapter = None 

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

544 def ensurePositionExists(self, w): 

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

546 c = self.c 

547 if c.positionExists(w.leo_p): 

548 return True 

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

550 for p2 in c.all_unique_positions(): 

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

552 w.leo_p = p2.copy() 

553 return True 

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

555 w.leo_p = c.p 

556 return False 

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

558 # Not used in Qt. 

559 

560 def deactivateActiveEditor(self, w): 

561 """Inactivate the previously active editor.""" 

562 d = self.editorWrappers 

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

564 for key in d: 

565 w2 = d.get(key) 

566 if w2 != w and w2.leo_active: 

567 w2.leo_active = False 

568 self.unselectLabel(w2) 

569 return 

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

571 def recolorWidget(self, p, w): 

572 # Support QScintillaColorizer.colorize. 

573 c = self.c 

574 colorizer = c.frame.body.colorizer 

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

576 old_wrapper = c.frame.body.wrapper 

577 c.frame.body.wrapper = w 

578 try: 

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

580 finally: 

581 c.frame.body.wrapper = old_wrapper 

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

583 def switchToChapter(self, w): 

584 """select w.leo_chapter.""" 

585 c = self.c 

586 cc = c.chapterController 

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

588 chapter = w.leo_chapter 

589 name = chapter and chapter.name 

590 oldChapter = cc.getSelectedChapter() 

591 if chapter != oldChapter: 

592 cc.selectChapterByName(name) 

593 c.bodyWantsFocus() 

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

595 # Called from addEditor and assignPositionToEditor. 

596 

597 def updateInjectedIvars(self, w, p): 

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

599 if not w: 

600 return 

601 c = self.c 

602 cc = c.chapterController 

603 # Was in ctor. 

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

605 if cc and use_chapters: 

606 w.leo_chapter = cc.getSelectedChapter() 

607 else: 

608 w.leo_chapter = None 

609 w.leo_p = p.copy() 

610 w.leo_v = w.leo_p.v 

611 w.leo_label_s = p.h 

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

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

614 def getInsertLines(self): 

615 """ 

616 Return before,after where: 

617 

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

619 sel is the line containing the insert point. 

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

621 

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

623 """ 

624 body = self 

625 w = body.wrapper 

626 s = w.getAllText() 

627 insert = w.getInsertPoint() 

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

629 before = s[0:i] 

630 ins = s[i:j] 

631 after = s[j:] 

632 before = g.checkUnicode(before) 

633 ins = g.checkUnicode(ins) 

634 after = g.checkUnicode(after) 

635 return before, ins, after 

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

637 def getSelectionAreas(self): 

638 """ 

639 Return before,sel,after where: 

640 

641 before is the text before the selected text 

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

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

644 after is the text after the selected text 

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

646 """ 

647 body = self 

648 w = body.wrapper 

649 s = w.getAllText() 

650 i, j = w.getSelectionRange() 

651 if i == j: 

652 j = i + 1 

653 before = s[0:i] 

654 sel = s[i:j] 

655 after = s[j:] 

656 before = g.checkUnicode(before) 

657 sel = g.checkUnicode(sel) 

658 after = g.checkUnicode(after) 

659 return before, sel, after 

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

661 def getSelectionLines(self): 

662 """ 

663 Return before,sel,after where: 

664 

665 before is the all lines before the selected text 

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

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

668 after is all lines after the selected text 

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

670 """ 

671 if g.app.batchMode: 

672 return '', '', '' 

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

674 body = self 

675 w = body.wrapper 

676 s = w.getAllText() 

677 i, j = w.getSelectionRange() 

678 if i == j: 

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

680 else: 

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

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

683 j -= 1 

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

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

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

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

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

689 return before, sel, after # 3 strings. 

690 #@-others 

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

692class LeoFrame: 

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

694 instances = 0 

695 #@+others 

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

697 def __init__(self, c, gui): 

698 self.c = c 

699 self.gui = gui 

700 self.iconBarClass = NullIconBarClass 

701 self.statusLineClass = NullStatusLineClass 

702 self.title = None # Must be created by subclasses. 

703 # Objects attached to this frame. 

704 self.body = None 

705 self.colorPanel = None 

706 self.comparePanel = None 

707 self.findPanel = None 

708 self.fontPanel = None 

709 self.iconBar = None 

710 self.isNullFrame = False 

711 self.keys = None 

712 self.log = None 

713 self.menu = None 

714 self.miniBufferWidget = None 

715 self.outerFrame = None 

716 self.prefsPanel = None 

717 self.statusLine = g.NullObject() # For unit tests. 

718 self.tree = None 

719 self.useMiniBufferWidget = False 

720 # Gui-independent data 

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

722 self.componentsDict = {} # Keys are names, values are componentClass instances. 

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

724 self.openDirectory = "" 

725 self.saved = False # True if ever saved 

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

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

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

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

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

731 def createFirstTreeNode(self): 

732 c = self.c 

733 # 

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

735 c.hiddenRootNode.children = [] 

736 # 

737 # #1817: Clear the gnxDict. 

738 c.fileCommands.gnxDict = {} 

739 # 

740 # Create the first node. 

741 v = leoNodes.VNode(context=c) 

742 p = leoNodes.Position(v) 

743 v.initHeadString("NewHeadline") 

744 # 

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

746 # the node hasn't been linked yet. 

747 p._linkAsRoot() 

748 return v 

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

750 #@+node:ekr.20071027150501: *4* LeoFrame.event handlers 

751 def OnBodyClick(self, event=None): 

752 pass 

753 

754 def OnBodyRClick(self, event=None): 

755 pass 

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

757 def getTitle(self): 

758 return self.title 

759 

760 def setTitle(self, title): 

761 self.title = title 

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

763 def initAfterLoad(self): 

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

765 frame = self 

766 frame.body.initAfterLoad() 

767 frame.log.initAfterLoad() 

768 frame.menu.initAfterLoad() 

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

770 frame.tree.initAfterLoad() 

771 

772 def initCompleteHint(self): 

773 pass 

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

775 def setTabWidth(self, w): 

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

777 # Subclasses may override this to affect drawing. 

778 self.tab_width = w 

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

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

781 def initialRatios(self): 

782 c = self.c 

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

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

785 if verticalFlag: 

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

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

788 r = 0.5 

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

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

791 r2 = 0.8 

792 else: 

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

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

795 r = 0.3 

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

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

798 r2 = 0.8 

799 return verticalFlag, r, r2 

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

801 def longFileName(self): 

802 return self.c.mFileName 

803 

804 def shortFileName(self): 

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

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

807 def oops(self): 

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

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

810 def promptForSave(self): 

811 """ 

812 Prompt the user to save changes. 

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

814 """ 

815 c = self.c 

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

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

818 root = c.rootPosition() 

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

820 if quick_save: 

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

822 else: 

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

824 answer = g.app.gui.runAskYesNoCancelDialog( 

825 c, 

826 title='Confirm', 

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

828 ) 

829 if answer == "cancel": 

830 return True # Veto. 

831 if answer == "no": 

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

833 if not c.mFileName: 

834 root = c.rootPosition() 

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

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

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

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

839 # Write the @edit node if needed. 

840 if root.isDirty(): 

841 c.atFileCommands.writeOneAtEditNode(root) 

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

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

844 title="Save", 

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

846 defaultextension=".leo") 

847 c.bringToFront() 

848 if c.mFileName: 

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

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

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

852 return not ok 

853 # Veto if the save did not succeed. 

854 return True # Veto. 

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

856 def scanForTabWidth(self, p): 

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

858 c = self.c 

859 tab_width = c.getTabWidth(p) 

860 c.frame.setTabWidth(tab_width) 

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

862 def addIconButton(self, *args, **keys): 

863 if self.iconBar: 

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

865 return None 

866 

867 def addIconRow(self): 

868 if self.iconBar: 

869 return self.iconBar.addRow() 

870 return None 

871 

872 def addIconWidget(self, w): 

873 if self.iconBar: 

874 return self.iconBar.addWidget(w) 

875 return None 

876 

877 def clearIconBar(self): 

878 if self.iconBar: 

879 return self.iconBar.clear() 

880 return None 

881 

882 def createIconBar(self): 

883 c = self.c 

884 if not self.iconBar: 

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

886 return self.iconBar 

887 

888 def getIconBar(self): 

889 if not self.iconBar: 

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

891 return self.iconBar 

892 

893 getIconBarObject = getIconBar 

894 

895 def getNewIconFrame(self): 

896 if not self.iconBar: 

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

898 return self.iconBar.getNewFrame() 

899 

900 def hideIconBar(self): 

901 if self.iconBar: 

902 self.iconBar.hide() 

903 

904 def showIconBar(self): 

905 if self.iconBar: 

906 self.iconBar.show() 

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

908 def createStatusLine(self): 

909 if not self.statusLine: 

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

911 return self.statusLine 

912 

913 def clearStatusLine(self): 

914 if self.statusLine: 

915 self.statusLine.clear() 

916 

917 def disableStatusLine(self, background=None): 

918 if self.statusLine: 

919 self.statusLine.disable(background) 

920 

921 def enableStatusLine(self, background="white"): 

922 if self.statusLine: 

923 self.statusLine.enable(background) 

924 

925 def getStatusLine(self): 

926 return self.statusLine 

927 

928 getStatusObject = getStatusLine 

929 

930 def putStatusLine(self, s, bg=None, fg=None): 

931 if self.statusLine: 

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

933 

934 def setFocusStatusLine(self): 

935 if self.statusLine: 

936 self.statusLine.setFocus() 

937 

938 def statusLineIsEnabled(self): 

939 if self.statusLine: 

940 return self.statusLine.isEnabled() 

941 return False 

942 

943 def updateStatusLine(self): 

944 if self.statusLine: 

945 self.statusLine.update() 

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

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

948 @frame_cmd('copy-text') 

949 def copyText(self, event=None): 

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

951 # f = self 

952 w = event and event.widget 

953 # wname = c.widget_name(w) 

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

955 return 

956 # Set the clipboard text. 

957 i, j = w.getSelectionRange() 

958 if i == j: 

959 ins = w.getInsertPoint() 

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

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

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

963 s = w.get(i, j) 

964 if s: 

965 g.app.gui.replaceClipboardWith(s) 

966 

967 OnCopyFromMenu = copyText 

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

969 @frame_cmd('cut-text') 

970 def cutText(self, event=None): 

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

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

973 w = event and event.widget 

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

975 return 

976 bunch = u.beforeChangeBody(p) 

977 name = c.widget_name(w) 

978 oldText = w.getAllText() 

979 i, j = w.getSelectionRange() 

980 # Update the widget and set the clipboard text. 

981 s = w.get(i, j) 

982 if i != j: 

983 w.delete(i, j) 

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

985 g.app.gui.replaceClipboardWith(s) 

986 else: 

987 ins = w.getInsertPoint() 

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

989 s = w.get(i, j) 

990 w.delete(i, j) 

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

992 g.app.gui.replaceClipboardWith(s) 

993 if name.startswith('body'): 

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

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

996 elif name.startswith('head'): 

997 # The headline is not officially changed yet. 

998 s = w.getAllText() 

999 else: 

1000 pass 

1001 

1002 OnCutFromMenu = cutText 

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

1004 @frame_cmd('paste-text') 

1005 def pasteText(self, event=None, middleButton=False): 

1006 """ 

1007 Paste the clipboard into a widget. 

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

1009 """ 

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

1011 w = event and event.widget 

1012 wname = c.widget_name(w) 

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

1014 return 

1015 bunch = u.beforeChangeBody(p) 

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

1017 tCurPosition = w.getInsertPoint() 

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

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

1020 start, end = c.k.previousSelection 

1021 s = w.getAllText() 

1022 s = s[start:end] 

1023 c.k.previousSelection = None 

1024 else: 

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

1026 s = g.checkUnicode(s) 

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

1028 if singleLine: 

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

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

1031 s = s[:-1] 

1032 # Save the horizontal scroll position. 

1033 if hasattr(w, 'getXScrollPosition'): 

1034 x_pos = w.getXScrollPosition() 

1035 # Update the widget. 

1036 if i != j: 

1037 w.delete(i, j) 

1038 w.insert(i, s) 

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

1040 if wname.startswith('body'): 

1041 if self.cursorStay: 

1042 if tCurPosition == j: 

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

1044 else: 

1045 offset = 0 

1046 newCurPosition = tCurPosition + offset 

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

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

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

1050 elif singleLine: 

1051 s = w.getAllText() 

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

1053 s = s[:-1] 

1054 else: 

1055 pass 

1056 # Never scroll horizontally. 

1057 if hasattr(w, 'getXScrollPosition'): 

1058 w.setXScrollPosition(x_pos) 

1059 

1060 OnPasteFromMenu = pasteText 

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

1062 def OnPaste(self, event=None): 

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

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

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

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

1067 def endEditLabelCommand(self, event=None, p=None): 

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

1069 frame = self 

1070 c = frame.c 

1071 k = c.k 

1072 if g.app.batchMode: 

1073 c.notValidInBatchMode("End Edit Headline") 

1074 return 

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

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

1077 if w_name.startswith('head'): 

1078 c.endEditing() 

1079 c.treeWantsFocus() 

1080 else: 

1081 c.bodyWantsFocus() 

1082 k.setDefaultInputState() 

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

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

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

1086 def bringToFront(self): 

1087 self.oops() 

1088 

1089 def cascade(self, event=None): 

1090 self.oops() 

1091 

1092 def contractBodyPane(self, event=None): 

1093 self.oops() 

1094 

1095 def contractLogPane(self, event=None): 

1096 self.oops() 

1097 

1098 def contractOutlinePane(self, event=None): 

1099 self.oops() 

1100 

1101 def contractPane(self, event=None): 

1102 self.oops() 

1103 

1104 def deiconify(self): 

1105 self.oops() 

1106 

1107 def equalSizedPanes(self, event=None): 

1108 self.oops() 

1109 

1110 def expandBodyPane(self, event=None): 

1111 self.oops() 

1112 

1113 def expandLogPane(self, event=None): 

1114 self.oops() 

1115 

1116 def expandOutlinePane(self, event=None): 

1117 self.oops() 

1118 

1119 def expandPane(self, event=None): 

1120 self.oops() 

1121 

1122 def fullyExpandBodyPane(self, event=None): 

1123 self.oops() 

1124 

1125 def fullyExpandLogPane(self, event=None): 

1126 self.oops() 

1127 

1128 def fullyExpandOutlinePane(self, event=None): 

1129 self.oops() 

1130 

1131 def fullyExpandPane(self, event=None): 

1132 self.oops() 

1133 

1134 def get_window_info(self): 

1135 self.oops() 

1136 

1137 def hideBodyPane(self, event=None): 

1138 self.oops() 

1139 

1140 def hideLogPane(self, event=None): 

1141 self.oops() 

1142 

1143 def hideLogWindow(self, event=None): 

1144 self.oops() 

1145 

1146 def hideOutlinePane(self, event=None): 

1147 self.oops() 

1148 

1149 def hidePane(self, event=None): 

1150 self.oops() 

1151 

1152 def leoHelp(self, event=None): 

1153 self.oops() 

1154 

1155 def lift(self): 

1156 self.oops() 

1157 

1158 def minimizeAll(self, event=None): 

1159 self.oops() 

1160 

1161 def resizePanesToRatio(self, ratio, secondary_ratio): 

1162 self.oops() 

1163 

1164 def resizeToScreen(self, event=None): 

1165 self.oops() 

1166 

1167 def setInitialWindowGeometry(self): 

1168 self.oops() 

1169 

1170 def setTopGeometry(self, w, h, x, y): 

1171 self.oops() 

1172 

1173 def toggleActivePane(self, event=None): 

1174 self.oops() 

1175 

1176 def toggleSplitDirection(self, event=None): 

1177 self.oops() 

1178 #@-others 

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

1180class LeoLog: 

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

1182 #@+others 

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

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

1185 def __init__(self, frame, parentFrame): 

1186 """Ctor for LeoLog class.""" 

1187 self.frame = frame 

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

1189 self.enabled = True 

1190 self.newlines = 0 

1191 self.isNull = False 

1192 # Official ivars... 

1193 self.canvasCtrl = None # Set below. Same as self.canvasDict.get(self.tabName) 

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

1195 self.logCtrl = None # Set below. Same as self.textDict.get(self.tabName) 

1196 self.tabName = None # The name of the active tab. 

1197 self.tabFrame = None # Same as self.frameDict.get(self.tabName) 

1198 self.canvasDict = {} # Keys are page names. Values are Tk.Canvas's. 

1199 self.frameDict = {} # Keys are page names. Values are Tk.Frames. 

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

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

1202 self.textDict = {} # Keys are page names. Values are logCtrl's (text widgets). 

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

1204 def clearTab(self, tabName, wrap='none'): 

1205 self.selectTab(tabName, wrap=wrap) 

1206 w = self.logCtrl 

1207 if w: 

1208 w.delete(0, 'end') 

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

1210 def createTab(self, tabName, createText=True, widget=None, wrap='none'): 

1211 if createText: 

1212 w = self.createTextWidget(self.tabFrame) 

1213 self.canvasDict[tabName] = None 

1214 self.textDict[tabName] = w 

1215 else: 

1216 self.canvasDict[tabName] = None 

1217 self.textDict[tabName] = None 

1218 self.frameDict[tabName] = tabName # tabFrame 

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

1220 def createTextWidget(self, parentFrame): 

1221 return None 

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

1223 def deleteTab(self, tabName): 

1224 c = self.c 

1225 if tabName == 'Log': 

1226 pass 

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

1228 self.selectTab('Log') 

1229 else: 

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

1231 if tabName in d: 

1232 del d[tabName] 

1233 self.tabName = None 

1234 self.selectTab('Log') 

1235 c.invalidateFocus() 

1236 c.bodyWantsFocus() 

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

1238 def disable(self): 

1239 self.enabled = False 

1240 

1241 def enable(self, enabled=True): 

1242 self.enabled = enabled 

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

1244 def getSelectedTab(self): 

1245 return self.tabName 

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

1247 def hideTab(self, tabName): 

1248 self.selectTab('Log') 

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

1250 def lowerTab(self, tabName): 

1251 self.c.invalidateFocus() 

1252 self.c.bodyWantsFocus() 

1253 

1254 def raiseTab(self, tabName): 

1255 self.c.invalidateFocus() 

1256 self.c.bodyWantsFocus() 

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

1258 def orderedTabNames(self, LeoLog=None): 

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

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

1261 def numberOfVisibleTabs(self): 

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

1263 #@+node:ekr.20070302101304: *3* LeoLog.put & putnl 

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

1265 

1266 def put(self, s, color=None, tabName='Log', from_redirect=False, nodeLink=None): 

1267 print(s) 

1268 

1269 def putnl(self, tabName='Log'): 

1270 pass # print ('') 

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

1272 def renameTab(self, oldName, newName): 

1273 pass 

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

1275 def selectTab(self, tabName, createText=True, widget=None, wrap='none'): # widget unused. 

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

1277 c = self.c 

1278 tabFrame = self.frameDict.get(tabName) 

1279 if not tabFrame: 

1280 self.createTab(tabName, createText=createText) 

1281 # Update the status vars. 

1282 self.tabName = tabName 

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

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

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

1286 if 0: 

1287 # Absolutely do not do this here! 

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

1289 c.widgetWantsFocusNow(self.logCtrl) 

1290 return tabFrame 

1291 #@-others 

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

1293class LeoTree: 

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

1295 #@+others 

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

1297 # These are new in Leo 4.6. 

1298 

1299 def initAfterLoad(self): 

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

1301 

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

1303 

1304 def redraw_after_contract(self, p): 

1305 self.c.redraw() 

1306 

1307 def redraw_after_expand(self, p): 

1308 self.c.redraw() 

1309 

1310 def redraw_after_head_changed(self): 

1311 self.c.redraw() 

1312 

1313 def redraw_after_icons_changed(self): 

1314 self.c.redraw() 

1315 

1316 def redraw_after_select(self, p=None): 

1317 self.c.redraw() 

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

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

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

1321 def onHeadChanged(self, p, undoType='Typing'): 

1322 """ 

1323 Officially change a headline. 

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

1325 """ 

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

1327 if not w: 

1328 g.trace('no w') 

1329 return 

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

1331 s = w.getAllText() 

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

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

1334 # Remove trailing newlines before warning of truncation. 

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

1336 s = s[:-1] 

1337 # Warn if there are multiple lines. 

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

1339 if i > -1: 

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

1341 s = s[:i] 

1342 limit = 1000 

1343 if len(s) > limit: 

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

1345 s = s[:limit] 

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

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

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

1349 changed = s != p.h 

1350 if not changed: 

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

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

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

1354 # Handle undo. 

1355 undoData = u.beforeChangeHeadline(p) 

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

1357 if not c.changed: 

1358 c.setChanged() 

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

1360 # the headline may contain directives. 

1361 c.frame.scanForTabWidth(p) 

1362 c.frame.body.recolor(p) 

1363 p.setDirty() 

1364 u.afterChangeHeadline(p, undoType, undoData) 

1365 c.redraw_after_head_changed() 

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

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

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

1369 def __init__(self, frame): 

1370 """Ctor for the LeoTree class.""" 

1371 self.frame = frame 

1372 self.c = frame.c 

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

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

1375 self.edit_text_dict = {} 

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

1377 self.drag_p = None 

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

1379 self.redrawCount = 0 # For traces 

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

1381 # Define these here to keep pylint happy. 

1382 self.canvas = None 

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

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

1385 def endEditLabel(self): 

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

1387 # Important: this will redraw if necessary. 

1388 self.onHeadChanged(self.c.p) 

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

1390 # k.setDefaultInputState() 

1391 # k.showStateAndMode() 

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

1393 # c.bodyWantsFocus() 

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

1395 def getEditTextDict(self, v): 

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

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

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

1399 def onHeadlineKey(self, event): 

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

1401 w = event.widget if event else None 

1402 ch = event.char if event else '' 

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

1404 if ch: 

1405 self.updateHead(event, w) 

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

1407 def OnIconCtrlClick(self, p): 

1408 g.openUrl(p) 

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

1410 def OnIconDoubleClick(self, p): 

1411 pass 

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

1413 def updateHead(self, event, w): 

1414 """ 

1415 Update a headline from an event. 

1416 

1417 The headline officially changes only when editing ends. 

1418 """ 

1419 k = self.c.k 

1420 ch = event.char if event else '' 

1421 i, j = w.getSelectionRange() 

1422 ins = w.getInsertPoint() 

1423 if i != j: 

1424 ins = i 

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

1426 if i != j: 

1427 w.delete(i, j) 

1428 # Bug fix: 2018/04/19. 

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

1430 elif i > 0: 

1431 i -= 1 

1432 w.delete(i) 

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

1434 else: 

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

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

1437 if i != j: 

1438 w.delete(i, j) 

1439 elif k.unboundKeyAction == 'overwrite': 

1440 w.delete(i, i + 1) 

1441 w.insert(ins, ch) 

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

1443 s = w.getAllText() 

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

1445 s = s[:-1] 

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

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

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

1449 self.endEditLabel() 

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

1451 # Drawing & scrolling. 

1452 

1453 def drawIcon(self, p): 

1454 self.oops() 

1455 

1456 def redraw(self, p=None): 

1457 self.oops() 

1458 redraw_now = redraw 

1459 

1460 def scrollTo(self, p): 

1461 self.oops() 

1462 

1463 # Headlines. 

1464 

1465 def editLabel(self, p, selectAll=False, selection=None): 

1466 self.oops() 

1467 

1468 def edit_widget(self, p): 

1469 self.oops() 

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

1471 tree_select_lockout = False 

1472 

1473 def select(self, p): 

1474 """ 

1475 Select a node. 

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

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

1478 """ 

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

1480 tag = 'LeoTree.select' 

1481 c = self.c 

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

1483 return 

1484 if trace: 

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

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

1487 # Format matches traces in leoflexx.py 

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

1489 try: 

1490 self.tree_select_lockout = True 

1491 self.prev_v = c.p.v 

1492 self.selectHelper(p) 

1493 finally: 

1494 self.tree_select_lockout = False 

1495 if c.enableRedrawFlag: 

1496 p = c.p 

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

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

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

1500 c.redraw_later() 

1501 # This *does* happen sometimes. 

1502 else: 

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

1504 if hasattr(self, 'setItemForCurrentPosition'): 

1505 # pylint: disable=no-member 

1506 self.setItemForCurrentPosition() # type:ignore 

1507 else: 

1508 c.requestLaterRedraw = True 

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

1510 def selectHelper(self, p): 

1511 """ 

1512 A helper function for leoTree.select. 

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

1514 """ 

1515 if not p: 

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

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

1518 return 

1519 c = self.c 

1520 if not c.frame.body.wrapper: 

1521 return # Defensive. 

1522 if p.v.context != c: 

1523 # Selecting a foreign position will not be pretty. 

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

1525 return 

1526 old_p = c.p 

1527 call_event_handlers = p != old_p 

1528 # Order is important... 

1529 self.unselect_helper(old_p, p) 

1530 # 1. Call c.endEditLabel. 

1531 self.select_new_node(old_p, p) 

1532 # 2. Call set_body_text_after_select. 

1533 self.change_current_position(old_p, p) 

1534 # 3. Call c.undoer.onSelect. 

1535 self.scroll_cursor(p) 

1536 # 4. Set cursor in body. 

1537 self.set_status_line(p) 

1538 # 5. Last tweaks. 

1539 if call_event_handlers: 

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

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

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

1543 def unselect_helper(self, old_p, p): 

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

1545 c = self.c 

1546 call_event_handlers = p != old_p 

1547 if call_event_handlers: 

1548 unselect = not g.doHook( 

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

1550 else: 

1551 unselect = True 

1552 

1553 # Actually unselect the old node. 

1554 if unselect and old_p and old_p != p: 

1555 self.endEditLabel() 

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

1557 if hasattr(self, 'unselectItem'): 

1558 # pylint: disable=no-member 

1559 self.unselectItem(old_p) # type:ignore 

1560 if call_event_handlers: 

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

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

1563 def select_new_node(self, old_p, p): 

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

1565 c = self.c 

1566 call_event_handlers = p != old_p 

1567 if ( 

1568 call_event_handlers and g.doHook("select1", 

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

1570 ): 

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

1572 g.trace('select1 override') 

1573 return 

1574 c.frame.setWrap(p) 

1575 # Not that expensive 

1576 self.set_body_text_after_select(p, old_p) 

1577 c.nodeHistory.update(p) 

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

1579 def set_body_text_after_select(self, p, old_p): 

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

1581 c = self.c 

1582 w = c.frame.body.wrapper 

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

1584 # Part 1: get the old text. 

1585 old_s = w.getAllText() 

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

1587 return 

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

1589 c.setCurrentPosition(p) 

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

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

1592 w.setAllText(s) 

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

1594 # p.restoreCursorAndScroll() 

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

1596 def change_current_position(self, old_p, p): 

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

1598 c = self.c 

1599 # c.setCurrentPosition(p) 

1600 # This is now done in set_body_text_after_select. 

1601 c.frame.scanForTabWidth(p) 

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

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

1604 if use_chapters: 

1605 cc = c.chapterController 

1606 theChapter = cc and cc.getSelectedChapter() 

1607 if theChapter: 

1608 theChapter.p = p.copy() 

1609 # Do not call treeFocusHelper here! 

1610 # c.treeFocusHelper() 

1611 c.undoer.onSelect(old_p, p) 

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

1613 def scroll_cursor(self, p): 

1614 """Scroll the cursor.""" 

1615 p.restoreCursorAndScroll() 

1616 # Was in setBodyTextAfterSelect 

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

1618 def set_status_line(self, p): 

1619 """Update the status line.""" 

1620 c = self.c 

1621 c.frame.body.assignPositionToEditor(p) 

1622 # New in Leo 4.4.1. 

1623 c.frame.updateStatusLine() 

1624 # New in Leo 4.4.1. 

1625 c.frame.clearStatusLine() 

1626 if p and p.v: 

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

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

1629 def oops(self): 

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

1631 #@-others 

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

1633class LeoTreeTab: 

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

1635 #@+others 

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

1637 def __init__(self, c, chapterController, parentFrame): 

1638 self.c = c 

1639 self.cc = chapterController 

1640 self.nb = None # Created in createControl. 

1641 self.parentFrame = parentFrame 

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

1643 def createControl(self): 

1644 self.oops() 

1645 

1646 def createTab(self, tabName, select=True): 

1647 self.oops() 

1648 

1649 def destroyTab(self, tabName): 

1650 self.oops() 

1651 

1652 def selectTab(self, tabName): 

1653 self.oops() 

1654 

1655 def setTabLabel(self, tabName): 

1656 self.oops() 

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

1658 def oops(self): 

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

1660 #@-others 

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

1662class NullBody(LeoBody): 

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

1664 #@+others 

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

1666 def __init__(self, frame=None, parentFrame=None): 

1667 """Ctor for NullBody class.""" 

1668 super().__init__(frame, parentFrame) 

1669 self.insertPoint = 0 

1670 self.selection = 0, 0 

1671 self.s = "" # The body text 

1672 self.widget = None 

1673 self.wrapper = wrapper = StringTextWrapper(c=self.c, name='body') 

1674 self.editorWrappers['1'] = wrapper 

1675 self.colorizer = NullColorizer(self.c) 

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

1677 # Birth, death... 

1678 

1679 def createControl(self, parentFrame, p): 

1680 pass 

1681 # Editors... 

1682 

1683 def addEditor(self, event=None): 

1684 pass 

1685 

1686 def assignPositionToEditor(self, p): 

1687 pass 

1688 

1689 def createEditorFrame(self, w): 

1690 return None 

1691 

1692 def cycleEditorFocus(self, event=None): 

1693 pass 

1694 

1695 def deleteEditor(self, event=None): 

1696 pass 

1697 

1698 def selectEditor(self, w): 

1699 pass 

1700 

1701 def selectLabel(self, w): 

1702 pass 

1703 

1704 def setEditorColors(self, bg, fg): 

1705 pass 

1706 

1707 def unselectLabel(self, w): 

1708 pass 

1709 

1710 def updateEditors(self): 

1711 pass 

1712 # Events... 

1713 

1714 def forceFullRecolor(self): 

1715 pass 

1716 

1717 def scheduleIdleTimeRoutine(self, function, *args, **keys): 

1718 pass 

1719 # Low-level gui... 

1720 

1721 def setFocus(self): 

1722 pass 

1723 #@-others 

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

1725class NullColorizer(leoColorizer.BaseColorizer): 

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

1727 

1728 recolorCount = 0 

1729 

1730 def colorize(self, p): 

1731 self.recolorCount += 1 

1732 # For #503: Use string/null gui for unit tests 

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

1734class NullFrame(LeoFrame): 

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

1736 #@+others 

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

1738 def __init__(self, c, title, gui): 

1739 """Ctor for the NullFrame class.""" 

1740 super().__init__(c, gui) 

1741 assert self.c 

1742 self.wrapper = None 

1743 self.iconBar = NullIconBarClass(self.c, self) 

1744 self.initComplete = True 

1745 self.isNullFrame = True 

1746 self.outerFrame = None 

1747 self.ratio = self.secondary_ratio = 0.5 

1748 self.statusLineClass = NullStatusLineClass 

1749 self.title = title 

1750 self.top = None # Always None. 

1751 # Create the component objects. 

1752 self.body = NullBody(frame=self, parentFrame=None) 

1753 self.log = NullLog(frame=self, parentFrame=None) 

1754 self.menu = leoMenu.NullMenu(frame=self) 

1755 self.tree = NullTree(frame=self) 

1756 # Default window position. 

1757 self.w = 600 

1758 self.h = 500 

1759 self.x = 40 

1760 self.y = 40 

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

1762 def bringToFront(self): 

1763 pass 

1764 

1765 def cascade(self, event=None): 

1766 pass 

1767 

1768 def contractBodyPane(self, event=None): 

1769 pass 

1770 

1771 def contractLogPane(self, event=None): 

1772 pass 

1773 

1774 def contractOutlinePane(self, event=None): 

1775 pass 

1776 

1777 def contractPane(self, event=None): 

1778 pass 

1779 

1780 def deiconify(self): 

1781 pass 

1782 

1783 def destroySelf(self): 

1784 pass 

1785 

1786 def equalSizedPanes(self, event=None): 

1787 pass 

1788 

1789 def expandBodyPane(self, event=None): 

1790 pass 

1791 

1792 def expandLogPane(self, event=None): 

1793 pass 

1794 

1795 def expandOutlinePane(self, event=None): 

1796 pass 

1797 

1798 def expandPane(self, event=None): 

1799 pass 

1800 

1801 def forceWrap(self, p): 

1802 pass 

1803 

1804 def fullyExpandBodyPane(self, event=None): 

1805 pass 

1806 

1807 def fullyExpandLogPane(self, event=None): 

1808 pass 

1809 

1810 def fullyExpandOutlinePane(self, event=None): 

1811 pass 

1812 

1813 def fullyExpandPane(self, event=None): 

1814 pass 

1815 

1816 def get_window_info(self): 

1817 return 600, 500, 20, 20 

1818 

1819 def hideBodyPane(self, event=None): 

1820 pass 

1821 

1822 def hideLogPane(self, event=None): 

1823 pass 

1824 

1825 def hideLogWindow(self, event=None): 

1826 pass 

1827 

1828 def hideOutlinePane(self, event=None): 

1829 pass 

1830 

1831 def hidePane(self, event=None): 

1832 pass 

1833 

1834 def leoHelp(self, event=None): 

1835 pass 

1836 

1837 def lift(self): 

1838 pass 

1839 

1840 def minimizeAll(self, event=None): 

1841 pass 

1842 

1843 def oops(self): 

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

1845 

1846 def resizePanesToRatio(self, ratio, secondary_ratio): 

1847 pass 

1848 

1849 def resizeToScreen(self, event=None): 

1850 pass 

1851 

1852 def setInitialWindowGeometry(self): 

1853 pass 

1854 

1855 def setTopGeometry(self, w, h, x, y): 

1856 return 0, 0, 0, 0 

1857 

1858 def setWrap(self, flag, force=False): 

1859 pass 

1860 

1861 def toggleActivePane(self, event=None): 

1862 pass 

1863 

1864 def toggleSplitDirection(self, event=None): 

1865 pass 

1866 

1867 def update(self): 

1868 pass 

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

1870 def finishCreate(self): 

1871 

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

1873 self.createFirstTreeNode() 

1874 # Call the base LeoFrame method. 

1875 #@-others 

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

1877class NullIconBarClass: 

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

1879 #@+others 

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

1881 def __init__(self, c, parentFrame): 

1882 """Ctor for NullIconBarClass.""" 

1883 self.c = c 

1884 self.iconFrame = None 

1885 self.parentFrame = parentFrame 

1886 self.w = g.NullObject() 

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

1888 def addRow(self, height=None): 

1889 pass 

1890 

1891 def addRowIfNeeded(self): 

1892 pass 

1893 

1894 def addWidget(self, w): 

1895 pass 

1896 

1897 def createChaptersIcon(self): 

1898 pass 

1899 

1900 def deleteButton(self, w): 

1901 pass 

1902 

1903 def getNewFrame(self): 

1904 return None 

1905 

1906 def hide(self): 

1907 pass 

1908 

1909 def show(self): 

1910 pass 

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

1912 def add(self, *args, **keys): 

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

1914 command = keys.get('command') 

1915 text = keys.get('text') 

1916 try: 

1917 g.app.iconWidgetCount += 1 

1918 except Exception: 

1919 g.app.iconWidgetCount = 1 

1920 n = g.app.iconWidgetCount 

1921 name = f"nullButtonWidget {n}" 

1922 if not command: 

1923 

1924 def commandCallback(name=name): 

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

1926 

1927 command = commandCallback 

1928 

1929 

1930 class nullButtonWidget: 

1931 

1932 def __init__(self, c, command, name, text): 

1933 self.c = c 

1934 self.command = command 

1935 self.name = name 

1936 self.text = text 

1937 

1938 def __repr__(self): 

1939 return self.name 

1940 

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

1942 return b 

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

1944 def clear(self): 

1945 g.app.iconWidgetCount = 0 

1946 g.app.iconImageRefs = [] 

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

1948 def setCommandForButton(self, button, command, command_p, controller, gnx, script): 

1949 button.command = command 

1950 try: 

1951 # See PR #2441: Add rclick support. 

1952 from leo.plugins.mod_scripting import build_rclick_tree 

1953 rclicks = build_rclick_tree(command_p, top_level=True) 

1954 button.rclicks = rclicks 

1955 except Exception: 

1956 pass 

1957 #@-others 

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

1959class NullLog(LeoLog): 

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

1961 #@+others 

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

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

1964 def __init__(self, frame=None, parentFrame=None): 

1965 

1966 super().__init__(frame, parentFrame) 

1967 self.isNull = True 

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

1969 self.logNumber = 0 

1970 self.widget = self.createControl(parentFrame) 

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

1972 def finishCreate(self): 

1973 pass 

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

1975 def createControl(self, parentFrame): 

1976 return self.createTextWidget(parentFrame) 

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

1978 def createTextWidget(self, parentFrame): 

1979 self.logNumber += 1 

1980 c = self.c 

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

1982 return log 

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

1984 def hasSelection(self): 

1985 return self.widget.hasSelection() 

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

1987 def isLogWidget(self, w): 

1988 return False 

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

1990 def oops(self): 

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

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

1993 def put(self, s, color=None, tabName='Log', from_redirect=False, nodeLink=None): 

1994 # print('(nullGui) print',repr(s)) 

1995 if self.enabled: 

1996 try: 

1997 g.pr(s, newline=False) 

1998 except UnicodeError: 

1999 s = s.encode('ascii', 'replace') 

2000 g.pr(s, newline=False) 

2001 

2002 def putnl(self, tabName='Log'): 

2003 if self.enabled: 

2004 g.pr('') 

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

2006 def clearTab(self, tabName, wrap='none'): 

2007 pass 

2008 

2009 def createCanvas(self, tabName): 

2010 pass 

2011 

2012 def createTab(self, tabName, createText=True, widget=None, wrap='none'): 

2013 pass 

2014 

2015 def deleteTab(self, tabName): 

2016 pass 

2017 

2018 def getSelectedTab(self): 

2019 return None 

2020 

2021 def lowerTab(self, tabName): 

2022 pass 

2023 

2024 def raiseTab(self, tabName): 

2025 pass 

2026 

2027 def renameTab(self, oldName, newName): 

2028 pass 

2029 

2030 def selectTab(self, tabName, createText=True, widget=None, wrap='none'): 

2031 pass 

2032 #@-others 

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

2034class NullStatusLineClass: 

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

2036 

2037 def __init__(self, c, parentFrame): 

2038 """Ctor for NullStatusLine class.""" 

2039 self.c = c 

2040 self.enabled = False 

2041 self.parentFrame = parentFrame 

2042 self.textWidget = StringTextWrapper(c, name='status-line') 

2043 # Set the official ivars. 

2044 c.frame.statusFrame = None 

2045 c.frame.statusLabel = None 

2046 c.frame.statusText = self.textWidget 

2047 #@+others 

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

2049 def disable(self, background=None): 

2050 self.enabled = False 

2051 # self.c.bodyWantsFocus() 

2052 

2053 def enable(self, background="white"): 

2054 self.c.widgetWantsFocus(self.textWidget) 

2055 self.enabled = True 

2056 

2057 def clear(self): 

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

2059 

2060 def get(self): 

2061 return self.textWidget.getAllText() 

2062 

2063 def isEnabled(self): 

2064 return self.enabled 

2065 

2066 def put(self, s, bg=None, fg=None): 

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

2068 

2069 def setFocus(self): 

2070 pass 

2071 

2072 def update(self): 

2073 pass 

2074 #@-others 

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

2076class NullTree(LeoTree): 

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

2078 #@+others 

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

2080 def __init__(self, frame): 

2081 """Ctor for NullTree class.""" 

2082 super().__init__(frame) 

2083 assert self.frame 

2084 self.c = frame.c 

2085 self.editWidgetsDict = {} # Keys are vnodes, values are StringTextWidgets. 

2086 self.font = None 

2087 self.fontName = None 

2088 self.canvas = None 

2089 self.treeWidget = g.NullObject() 

2090 self.redrawCount = 0 

2091 self.updateCount = 0 

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

2093 def edit_widget(self, p): 

2094 d = self.editWidgetsDict 

2095 if not p or not p.v: 

2096 return None 

2097 w = d.get(p.v) 

2098 if not w: 

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

2100 c=self.c, 

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

2102 w.setAllText(p.h) 

2103 return w 

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

2105 def editLabel(self, p, selectAll=False, selection=None): 

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

2107 self.endEditLabel() 

2108 if p: 

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

2110 e = None 

2111 return e, wrapper 

2112 return None, None 

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

2114 def printWidgets(self): 

2115 d = self.editWidgetsDict 

2116 for key in d: 

2117 # keys are vnodes, values are StringTextWidgets. 

2118 w = d.get(key) 

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

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

2121 def drawIcon(self, p): 

2122 pass 

2123 

2124 def redraw(self, p=None): 

2125 self.redrawCount += 1 

2126 return p 

2127 # Support for #503: Use string/null gui for unit tests 

2128 

2129 redraw_now = redraw 

2130 

2131 def redraw_after_contract(self, p): 

2132 self.redraw() 

2133 

2134 def redraw_after_expand(self, p): 

2135 self.redraw() 

2136 

2137 def redraw_after_head_changed(self): 

2138 self.redraw() 

2139 

2140 def redraw_after_icons_changed(self): 

2141 self.redraw() 

2142 

2143 def redraw_after_select(self, p=None): 

2144 self.redraw() 

2145 

2146 def scrollTo(self, p): 

2147 pass 

2148 

2149 def updateAllIcons(self, p): 

2150 pass 

2151 

2152 def updateIcon(self, p): 

2153 pass 

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

2155 def setHeadline(self, p, s): 

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

2157 

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

2159 w = self.edit_widget(p) 

2160 if w: 

2161 w.delete(0, 'end') 

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

2163 s = s[:-1] 

2164 w.insert(0, s) 

2165 else: 

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

2167 #@-others 

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

2169class StringTextWrapper: 

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

2171 #@+others 

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

2173 def __init__(self, c, name): 

2174 """Ctor for the StringTextWrapper class.""" 

2175 self.c = c 

2176 self.name = name 

2177 self.ins = 0 

2178 self.sel = 0, 0 

2179 self.s = '' 

2180 self.supportsHighLevelInterface = True 

2181 self.virtualInsertPoint = 0 

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

2183 

2184 def __repr__(self): 

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

2186 

2187 def getName(self): 

2188 """StringTextWrapper.""" 

2189 return self.name # Essential. 

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

2191 def clipboard_clear(self): 

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

2193 

2194 def clipboard_append(self, s): 

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

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

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

2198 # For StringTextWrapper. 

2199 

2200 def flashCharacter(self, i, bg='white', fg='red', flashes=3, delay=75): 

2201 pass 

2202 

2203 def getXScrollPosition(self): 

2204 return 0 

2205 

2206 def getYScrollPosition(self): 

2207 return 0 

2208 

2209 def see(self, i): 

2210 pass 

2211 

2212 def seeInsertPoint(self): 

2213 pass 

2214 

2215 def setFocus(self): 

2216 pass 

2217 

2218 def setStyleClass(self, name): 

2219 pass 

2220 

2221 def setXScrollPosition(self, i): 

2222 pass 

2223 

2224 def setYScrollPosition(self, i): 

2225 pass 

2226 

2227 def tag_configure(self, colorName, **keys): 

2228 pass 

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

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

2231 def appendText(self, s): 

2232 """StringTextWrapper.""" 

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

2234 self.ins = len(self.s) 

2235 self.sel = self.ins, self.ins 

2236 #@+node:ekr.20140903172510.18593: *4* stw.delete 

2237 def delete(self, i, j=None): 

2238 """StringTextWrapper.""" 

2239 i = self.toPythonIndex(i) 

2240 if j is None: 

2241 j = i + 1 

2242 j = self.toPythonIndex(j) 

2243 # This allows subclasses to use this base class method. 

2244 if i > j: 

2245 i, j = j, i 

2246 s = self.getAllText() 

2247 self.setAllText(s[:i] + s[j:]) 

2248 # Bug fix: 2011/11/13: Significant in external tests. 

2249 self.setSelectionRange(i, i, insert=i) 

2250 #@+node:ekr.20140903172510.18594: *4* stw.deleteTextSelection 

2251 def deleteTextSelection(self): 

2252 """StringTextWrapper.""" 

2253 i, j = self.getSelectionRange() 

2254 self.delete(i, j) 

2255 #@+node:ekr.20140903172510.18595: *4* stw.get 

2256 def get(self, i, j=None): 

2257 """StringTextWrapper.""" 

2258 i = self.toPythonIndex(i) 

2259 if j is None: 

2260 j = i + 1 

2261 j = self.toPythonIndex(j) 

2262 s = self.s[i:j] 

2263 return g.toUnicode(s) 

2264 #@+node:ekr.20140903172510.18596: *4* stw.getAllText 

2265 def getAllText(self): 

2266 """StringTextWrapper.""" 

2267 s = self.s 

2268 return g.checkUnicode(s) 

2269 #@+node:ekr.20140903172510.18584: *4* stw.getInsertPoint 

2270 def getInsertPoint(self): 

2271 """StringTextWrapper.""" 

2272 i = self.ins 

2273 if i is None: 

2274 if self.virtualInsertPoint is None: 

2275 i = 0 

2276 else: 

2277 i = self.virtualInsertPoint 

2278 self.virtualInsertPoint = i 

2279 return i 

2280 #@+node:ekr.20140903172510.18597: *4* stw.getSelectedText 

2281 def getSelectedText(self): 

2282 """StringTextWrapper.""" 

2283 i, j = self.sel 

2284 s = self.s[i:j] 

2285 return g.checkUnicode(s) 

2286 #@+node:ekr.20140903172510.18585: *4* stw.getSelectionRange 

2287 def getSelectionRange(self, sort=True): 

2288 """Return the selected range of the widget.""" 

2289 sel = self.sel 

2290 if len(sel) == 2 and sel[0] >= 0 and sel[1] >= 0: 

2291 i, j = sel 

2292 if sort and i > j: 

2293 sel = j, i # Bug fix: 10/5/07 

2294 return sel 

2295 i = self.ins 

2296 return i, i 

2297 #@+node:ekr.20140903172510.18586: *4* stw.hasSelection 

2298 def hasSelection(self): 

2299 """StringTextWrapper.""" 

2300 i, j = self.getSelectionRange() 

2301 return i != j 

2302 #@+node:ekr.20140903172510.18598: *4* stw.insert 

2303 def insert(self, i, s): 

2304 """StringTextWrapper.""" 

2305 i = self.toPythonIndex(i) 

2306 s1 = s 

2307 self.s = self.s[:i] + s1 + self.s[i:] 

2308 i += len(s1) 

2309 self.ins = i 

2310 self.sel = i, i 

2311 #@+node:ekr.20140903172510.18589: *4* stw.selectAllText 

2312 def selectAllText(self, insert=None): 

2313 """StringTextWrapper.""" 

2314 self.setSelectionRange(0, 'end', insert=insert) 

2315 #@+node:ekr.20140903172510.18600: *4* stw.setAllText 

2316 def setAllText(self, s): 

2317 """StringTextWrapper.""" 

2318 self.s = s 

2319 i = len(self.s) 

2320 self.ins = i 

2321 self.sel = i, i 

2322 #@+node:ekr.20140903172510.18587: *4* stw.setInsertPoint 

2323 def setInsertPoint(self, pos, s=None): 

2324 """StringTextWrapper.""" 

2325 i = self.toPythonIndex(pos) 

2326 self.virtualInsertPoint = i 

2327 self.ins = i 

2328 self.sel = i, i 

2329 #@+node:ekr.20070228111853: *4* stw.setSelectionRange 

2330 def setSelectionRange(self, i, j, insert=None): 

2331 """StringTextWrapper.""" 

2332 i, j = self.toPythonIndex(i), self.toPythonIndex(j) 

2333 self.sel = i, j 

2334 self.ins = j if insert is None else self.toPythonIndex(insert) 

2335 #@+node:ekr.20140903172510.18581: *4* stw.toPythonIndex 

2336 def toPythonIndex(self, index): 

2337 """ 

2338 StringTextWrapper.toPythonIndex. 

2339 

2340 Convert indices of the form 'end' or 'n1.n2' to integer indices into self.s. 

2341 

2342 Unit tests *do* use non-integer indices, so removing this method would be tricky. 

2343 """ 

2344 return g.toPythonIndex(self.s, index) 

2345 #@+node:ekr.20140903172510.18582: *4* stw.toPythonIndexRowCol 

2346 def toPythonIndexRowCol(self, index): 

2347 """StringTextWrapper.""" 

2348 s = self.getAllText() 

2349 i = self.toPythonIndex(index) 

2350 row, col = g.convertPythonIndexToRowCol(s, i) 

2351 return i, row, col 

2352 #@-others 

2353#@-others 

2354#@@language python 

2355#@@tabwidth -4 

2356#@@pagewidth 70 

2357#@-leo