Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#@+leo-ver=5-thin 

2#@+node:ekr.20031218072017.3655: * @file leoFrame.py 

3""" 

4The base classes for all Leo Windows, their body, log and tree panes, key bindings and menus. 

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 

311 # May be overridden in subclasses. 

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

313 def forceFullRecolor(self): 

314 pass 

315 

316 def getColorizer(self): 

317 return self.colorizer 

318 

319 def updateSyntaxColorer(self, p): 

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

321 

322 def recolor(self, p): 

323 self.c.recolor() 

324 

325 recolor_now = recolor 

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

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

328 # Fail loudly if these methods are not defined. 

329 

330 def oops(self): 

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

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

333 

334 def createEditorFrame(self, w): 

335 self.oops() 

336 

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

338 self.oops() 

339 

340 def packEditorLabelWidget(self, w): 

341 self.oops() 

342 

343 def onFocusOut(self, obj): 

344 pass 

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

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

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

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

349 def addEditor(self, event=None): 

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

351 c, p = self.c, self.c.p 

352 self.totalNumberOfEditors += 1 

353 self.numberOfEditors += 1 

354 if self.numberOfEditors == 2: 

355 # Inject the ivars into the first editor. 

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

357 d = self.editorWrappers 

358 keys = list(d.keys()) 

359 if len(keys) == 1: 

360 # Immediately create the label in the old editor. 

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

362 self.updateInjectedIvars(w_old, p) 

363 self.selectLabel(w_old) 

364 else: 

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

366 name = f"{self.totalNumberOfEditors}" 

367 pane = self.pb.add(name) 

368 panes = self.pb.panes() 

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

370 # Create the text wrapper. 

371 f = self.createEditorFrame(pane) 

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

373 wrapper.delete(0, 'end') 

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

375 wrapper.see(0) 

376 c.k.completeAllBindingsForWidget(wrapper) 

377 self.recolorWidget(p, wrapper) 

378 self.editorWrappers[name] = wrapper 

379 for pane in panes: 

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

381 self.pb.updatelayout() 

382 c.frame.body.wrapper = wrapper 

383 # Finish... 

384 self.updateInjectedIvars(wrapper, p) 

385 self.selectLabel(wrapper) 

386 self.selectEditor(wrapper) 

387 self.updateEditors() 

388 c.bodyWantsFocus() 

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

390 def assignPositionToEditor(self, p): 

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

392 c = self.c 

393 w = c.frame.body.widget 

394 self.updateInjectedIvars(w, p) 

395 self.selectLabel(w) 

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

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

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

399 def cycleEditorFocus(self, event=None): 

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

401 c = self.c 

402 d = self.editorWrappers 

403 w = c.frame.body.wrapper 

404 values = list(d.values()) 

405 if len(values) > 1: 

406 i = values.index(w) + 1 

407 if i == len(values): 

408 i = 0 

409 w2 = values[i] 

410 assert w != w2 

411 self.selectEditor(w2) 

412 c.frame.body.wrapper = w2 

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

414 def deleteEditor(self, event=None): 

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

416 c = self.c 

417 w = c.frame.body.wapper 

418 d = self.editorWrappers 

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

420 return 

421 name = w.leo_name 

422 del d[name] 

423 self.pb.delete(name) 

424 panes = self.pb.panes() 

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

426 for pane in panes: 

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

428 # Select another editor. 

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

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

431 self.numberOfEditors -= 1 

432 self.selectEditor(w) 

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

434 def findEditorForChapter(self, chapter, p): 

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

436 c = self.c 

437 d = self.editorWrappers 

438 values = list(d.values()) 

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

440 if p: 

441 for w in values: 

442 if ( 

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

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

445 ): 

446 return w 

447 # Next, try to match just the chapter. 

448 for w in values: 

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

450 return w 

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

452 return c.frame.body.wrapper 

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

454 def unselectLabel(self, w): 

455 self.createChapterIvar(w) 

456 self.packEditorLabelWidget(w) 

457 s = self.computeLabel(w) 

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

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

460 

461 def selectLabel(self, w): 

462 if self.numberOfEditors > 1: 

463 self.createChapterIvar(w) 

464 self.packEditorLabelWidget(w) 

465 s = self.computeLabel(w) 

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

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

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

469 w.leo_label.pack_forget() 

470 w.leo_label = None 

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

472 selectEditorLockout = False 

473 

474 def selectEditor(self, w): 

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

476 # Called whenever wrapper must be selected. 

477 c = self.c 

478 if self.selectEditorLockout: 

479 return None 

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

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

482 c.selectPosition(w.leo_p) 

483 c.bodyWantsFocus() 

484 return None 

485 try: 

486 val = None 

487 self.selectEditorLockout = True 

488 val = self.selectEditorHelper(w) 

489 finally: 

490 self.selectEditorLockout = False 

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

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

493 def selectEditorHelper(self, wrapper): 

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

495 c = self.c 

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

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

498 return 

499 self.deactivateActiveEditor(wrapper) 

500 # The actual switch. 

501 c.frame.body.wrapper = wrapper 

502 wrapper.leo_active = True 

503 self.switchToChapter(wrapper) 

504 self.selectLabel(wrapper) 

505 if not self.ensurePositionExists(wrapper): 

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

507 return 

508 p = wrapper.leo_p 

509 c.redraw(p) 

510 c.recolor() 

511 c.bodyWantsFocus() 

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

513 # Called from addEditor and assignPositionToEditor 

514 

515 def updateEditors(self): 

516 c, p = self.c, self.c.p 

517 d = self.editorWrappers 

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

519 return # There is only the main widget. 

520 for key in d: 

521 wrapper = d.get(key) 

522 v = wrapper.leo_v 

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

524 wrapper.delete(0, 'end') 

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

526 self.recolorWidget(p, wrapper) 

527 c.bodyWantsFocus() 

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

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

530 def computeLabel(self, w): 

531 s = w.leo_label_s 

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

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

534 return s 

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

536 def createChapterIvar(self, w): 

537 c = self.c 

538 cc = c.chapterController 

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

540 if cc and self.use_chapters: 

541 w.leo_chapter = cc.getSelectedChapter() 

542 else: 

543 w.leo_chapter = None 

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

545 def ensurePositionExists(self, w): 

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

547 c = self.c 

548 if c.positionExists(w.leo_p): 

549 return True 

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

551 for p2 in c.all_unique_positions(): 

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

553 w.leo_p = p2.copy() 

554 return True 

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

556 w.leo_p = c.p 

557 return False 

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

559 # Not used in Qt. 

560 

561 def deactivateActiveEditor(self, w): 

562 """Inactivate the previously active editor.""" 

563 d = self.editorWrappers 

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

565 for key in d: 

566 w2 = d.get(key) 

567 if w2 != w and w2.leo_active: 

568 w2.leo_active = False 

569 self.unselectLabel(w2) 

570 return 

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

572 def recolorWidget(self, p, w): 

573 # Support QScintillaColorizer.colorize. 

574 c = self.c 

575 colorizer = c.frame.body.colorizer 

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

577 old_wrapper = c.frame.body.wrapper 

578 c.frame.body.wrapper = w 

579 try: 

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

581 finally: 

582 c.frame.body.wrapper = old_wrapper 

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

584 def switchToChapter(self, w): 

585 """select w.leo_chapter.""" 

586 c = self.c 

587 cc = c.chapterController 

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

589 chapter = w.leo_chapter 

590 name = chapter and chapter.name 

591 oldChapter = cc.getSelectedChapter() 

592 if chapter != oldChapter: 

593 cc.selectChapterByName(name) 

594 c.bodyWantsFocus() 

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

596 # Called from addEditor and assignPositionToEditor. 

597 

598 def updateInjectedIvars(self, w, p): 

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

600 if not w: 

601 return 

602 c = self.c 

603 cc = c.chapterController 

604 # Was in ctor. 

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

606 if cc and use_chapters: 

607 w.leo_chapter = cc.getSelectedChapter() 

608 else: 

609 w.leo_chapter = None 

610 w.leo_p = p.copy() 

611 w.leo_v = w.leo_p.v 

612 w.leo_label_s = p.h 

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

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

615 def getInsertLines(self): 

616 """ 

617 Return before,after where: 

618 

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

620 sel is the line containing the insert point. 

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

622 

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

624 """ 

625 body = self 

626 w = body.wrapper 

627 s = w.getAllText() 

628 insert = w.getInsertPoint() 

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

630 before = s[0:i] 

631 ins = s[i:j] 

632 after = s[j:] 

633 before = g.checkUnicode(before) 

634 ins = g.checkUnicode(ins) 

635 after = g.checkUnicode(after) 

636 return before, ins, after 

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

638 def getSelectionAreas(self): 

639 """ 

640 Return before,sel,after where: 

641 

642 before is the text before the selected text 

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

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

645 after is the text after the selected text 

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

647 """ 

648 body = self 

649 w = body.wrapper 

650 s = w.getAllText() 

651 i, j = w.getSelectionRange() 

652 if i == j: 

653 j = i + 1 

654 before = s[0:i] 

655 sel = s[i:j] 

656 after = s[j:] 

657 before = g.checkUnicode(before) 

658 sel = g.checkUnicode(sel) 

659 after = g.checkUnicode(after) 

660 return before, sel, after 

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

662 def getSelectionLines(self): 

663 """ 

664 Return before,sel,after where: 

665 

666 before is the all lines before the selected text 

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

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

669 after is all lines after the selected text 

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

671 """ 

672 if g.app.batchMode: 

673 return '', '', '' 

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

675 body = self 

676 w = body.wrapper 

677 s = w.getAllText() 

678 i, j = w.getSelectionRange() 

679 if i == j: 

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

681 else: 

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

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

684 j -= 1 

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

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

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

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

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

690 return before, sel, after # 3 strings. 

691 #@-others 

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

693class LeoFrame: 

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

695 instances = 0 

696 #@+others 

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

698 def __init__(self, c, gui): 

699 self.c = c 

700 self.gui = gui 

701 self.iconBarClass = NullIconBarClass 

702 self.statusLineClass = NullStatusLineClass 

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

704 # Objects attached to this frame. 

705 self.body = None 

706 self.colorPanel = None 

707 self.comparePanel = None 

708 self.findPanel = None 

709 self.fontPanel = None 

710 self.iconBar = None 

711 self.isNullFrame = False 

712 self.keys = None 

713 self.log = None 

714 self.menu = None 

715 self.miniBufferWidget = None 

716 self.outerFrame = None 

717 self.prefsPanel = None 

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

719 self.tree = None 

720 self.useMiniBufferWidget = False 

721 # Gui-independent data 

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

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

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

725 self.openDirectory = "" 

726 self.saved = False # True if ever saved 

727 self.splitVerticalFlag = True 

728 # Set by initialRatios later. 

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

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

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

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

733 def createFirstTreeNode(self): 

734 c = self.c 

735 # 

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

737 c.hiddenRootNode.children = [] 

738 # 

739 # #1817: Clear the gnxDict. 

740 c.fileCommands.gnxDict = {} 

741 # 

742 # Create the first node. 

743 v = leoNodes.VNode(context=c) 

744 p = leoNodes.Position(v) 

745 v.initHeadString("NewHeadline") 

746 # 

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

748 # the node hasn't been linked yet. 

749 p._linkAsRoot() 

750 return v 

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

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

753 def OnBodyClick(self, event=None): 

754 pass 

755 

756 def OnBodyRClick(self, event=None): 

757 pass 

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

759 def getTitle(self): 

760 return self.title 

761 

762 def setTitle(self, title): 

763 self.title = title 

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

765 def initAfterLoad(self): 

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

767 frame = self 

768 frame.body.initAfterLoad() 

769 frame.log.initAfterLoad() 

770 frame.menu.initAfterLoad() 

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

772 frame.tree.initAfterLoad() 

773 

774 def initCompleteHint(self): 

775 pass 

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

777 def setTabWidth(self, w): 

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

779 # Subclasses may override this to affect drawing. 

780 self.tab_width = w 

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

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

783 def initialRatios(self): 

784 c = self.c 

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

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

787 if verticalFlag: 

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

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

790 r = 0.5 

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

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

793 r2 = 0.8 

794 else: 

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

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

797 r = 0.3 

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

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

800 r2 = 0.8 

801 return verticalFlag, r, r2 

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

803 def longFileName(self): 

804 return self.c.mFileName 

805 

806 def shortFileName(self): 

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

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

809 def oops(self): 

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

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

812 def promptForSave(self): 

813 """ 

814 Prompt the user to save changes. 

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

816 """ 

817 c = self.c 

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

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

820 root = c.rootPosition() 

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

822 if quick_save: 

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

824 else: 

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

826 answer = g.app.gui.runAskYesNoCancelDialog( 

827 c, 

828 title='Confirm', 

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

830 ) 

831 if answer == "cancel": 

832 return True # Veto. 

833 if answer == "no": 

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

835 if not c.mFileName: 

836 root = c.rootPosition() 

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

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

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

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

841 # Write the @edit node if needed. 

842 if root.isDirty(): 

843 c.atFileCommands.writeOneAtEditNode(root) 

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

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

846 title="Save", 

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

848 defaultextension=".leo") 

849 c.bringToFront() 

850 if c.mFileName: 

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

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

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

854 return not ok 

855 # Veto if the save did not succeed. 

856 return True # Veto. 

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

858 def scanForTabWidth(self, p): 

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

860 c = self.c 

861 tab_width = c.getTabWidth(p) 

862 c.frame.setTabWidth(tab_width) 

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

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

865 if self.iconBar: 

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

867 return None 

868 

869 def addIconRow(self): 

870 if self.iconBar: 

871 return self.iconBar.addRow() 

872 return None 

873 

874 def addIconWidget(self, w): 

875 if self.iconBar: 

876 return self.iconBar.addWidget(w) 

877 return None 

878 

879 def clearIconBar(self): 

880 if self.iconBar: 

881 return self.iconBar.clear() 

882 return None 

883 

884 def createIconBar(self): 

885 c = self.c 

886 if not self.iconBar: 

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

888 return self.iconBar 

889 

890 def getIconBar(self): 

891 if not self.iconBar: 

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

893 return self.iconBar 

894 

895 getIconBarObject = getIconBar 

896 

897 def getNewIconFrame(self): 

898 if not self.iconBar: 

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

900 return self.iconBar.getNewFrame() 

901 

902 def hideIconBar(self): 

903 if self.iconBar: 

904 self.iconBar.hide() 

905 

906 def showIconBar(self): 

907 if self.iconBar: 

908 self.iconBar.show() 

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

910 def createStatusLine(self): 

911 if not self.statusLine: 

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

913 return self.statusLine 

914 

915 def clearStatusLine(self): 

916 if self.statusLine: 

917 self.statusLine.clear() 

918 

919 def disableStatusLine(self, background=None): 

920 if self.statusLine: 

921 self.statusLine.disable(background) 

922 

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

924 if self.statusLine: 

925 self.statusLine.enable(background) 

926 

927 def getStatusLine(self): 

928 return self.statusLine 

929 

930 getStatusObject = getStatusLine 

931 

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

933 if self.statusLine: 

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

935 

936 def setFocusStatusLine(self): 

937 if self.statusLine: 

938 self.statusLine.setFocus() 

939 

940 def statusLineIsEnabled(self): 

941 if self.statusLine: 

942 return self.statusLine.isEnabled() 

943 return False 

944 

945 def updateStatusLine(self): 

946 if self.statusLine: 

947 self.statusLine.update() 

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

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

950 @frame_cmd('copy-text') 

951 def copyText(self, event=None): 

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

953 # f = self 

954 w = event and event.widget 

955 # wname = c.widget_name(w) 

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

957 return 

958 # Set the clipboard text. 

959 i, j = w.getSelectionRange() 

960 if i == j: 

961 ins = w.getInsertPoint() 

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

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

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

965 s = w.get(i, j) 

966 if s: 

967 g.app.gui.replaceClipboardWith(s) 

968 

969 OnCopyFromMenu = copyText 

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

971 @frame_cmd('cut-text') 

972 def cutText(self, event=None): 

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

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

975 w = event and event.widget 

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

977 return 

978 bunch = u.beforeChangeBody(p) 

979 name = c.widget_name(w) 

980 oldText = w.getAllText() 

981 i, j = w.getSelectionRange() 

982 # Update the widget and set the clipboard text. 

983 s = w.get(i, j) 

984 if i != j: 

985 w.delete(i, j) 

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

987 g.app.gui.replaceClipboardWith(s) 

988 else: 

989 ins = w.getInsertPoint() 

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

991 s = w.get(i, j) 

992 w.delete(i, j) 

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

994 g.app.gui.replaceClipboardWith(s) 

995 if name.startswith('body'): 

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

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

998 elif name.startswith('head'): 

999 # The headline is not officially changed yet. 

1000 s = w.getAllText() 

1001 else: 

1002 pass 

1003 

1004 OnCutFromMenu = cutText 

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

1006 @frame_cmd('paste-text') 

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

1008 """ 

1009 Paste the clipboard into a widget. 

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

1011 """ 

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

1013 w = event and event.widget 

1014 wname = c.widget_name(w) 

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

1016 return 

1017 bunch = u.beforeChangeBody(p) 

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

1019 tCurPosition = w.getInsertPoint() 

1020 i, j = w.getSelectionRange() 

1021 # Returns insert point if no selection. 

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

1023 start, end = c.k.previousSelection 

1024 s = w.getAllText() 

1025 s = s[start:end] 

1026 c.k.previousSelection = None 

1027 else: 

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

1029 s = g.checkUnicode(s) 

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

1031 if singleLine: 

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

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

1034 s = s[:-1] 

1035 # Save the horizontal scroll position. 

1036 if hasattr(w, 'getXScrollPosition'): 

1037 x_pos = w.getXScrollPosition() 

1038 # Update the widget. 

1039 if i != j: 

1040 w.delete(i, j) 

1041 w.insert(i, s) 

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

1043 if wname.startswith('body'): 

1044 if self.cursorStay: 

1045 if tCurPosition == j: 

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

1047 else: 

1048 offset = 0 

1049 newCurPosition = tCurPosition + offset 

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

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

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

1053 elif singleLine: 

1054 s = w.getAllText() 

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

1056 s = s[:-1] 

1057 else: 

1058 pass 

1059 # Never scroll horizontally. 

1060 if hasattr(w, 'getXScrollPosition'): 

1061 w.setXScrollPosition(x_pos) 

1062 

1063 OnPasteFromMenu = pasteText 

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

1065 def OnPaste(self, event=None): 

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

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

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

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

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

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

1072 frame = self 

1073 c = frame.c 

1074 k = c.k 

1075 if g.app.batchMode: 

1076 c.notValidInBatchMode("End Edit Headline") 

1077 return 

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

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

1080 if w_name.startswith('head'): 

1081 c.endEditing() 

1082 c.treeWantsFocus() 

1083 else: 

1084 c.bodyWantsFocus() 

1085 k.setDefaultInputState() 

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

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

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

1089 def bringToFront(self): 

1090 self.oops() 

1091 

1092 def cascade(self, event=None): 

1093 self.oops() 

1094 

1095 def contractBodyPane(self, event=None): 

1096 self.oops() 

1097 

1098 def contractLogPane(self, event=None): 

1099 self.oops() 

1100 

1101 def contractOutlinePane(self, event=None): 

1102 self.oops() 

1103 

1104 def contractPane(self, event=None): 

1105 self.oops() 

1106 

1107 def deiconify(self): 

1108 self.oops() 

1109 

1110 def equalSizedPanes(self, event=None): 

1111 self.oops() 

1112 

1113 def expandBodyPane(self, event=None): 

1114 self.oops() 

1115 

1116 def expandLogPane(self, event=None): 

1117 self.oops() 

1118 

1119 def expandOutlinePane(self, event=None): 

1120 self.oops() 

1121 

1122 def expandPane(self, event=None): 

1123 self.oops() 

1124 

1125 def fullyExpandBodyPane(self, event=None): 

1126 self.oops() 

1127 

1128 def fullyExpandLogPane(self, event=None): 

1129 self.oops() 

1130 

1131 def fullyExpandOutlinePane(self, event=None): 

1132 self.oops() 

1133 

1134 def fullyExpandPane(self, event=None): 

1135 self.oops() 

1136 

1137 def get_window_info(self): 

1138 self.oops() 

1139 

1140 def hideBodyPane(self, event=None): 

1141 self.oops() 

1142 

1143 def hideLogPane(self, event=None): 

1144 self.oops() 

1145 

1146 def hideLogWindow(self, event=None): 

1147 self.oops() 

1148 

1149 def hideOutlinePane(self, event=None): 

1150 self.oops() 

1151 

1152 def hidePane(self, event=None): 

1153 self.oops() 

1154 

1155 def leoHelp(self, event=None): 

1156 self.oops() 

1157 

1158 def lift(self): 

1159 self.oops() 

1160 

1161 def minimizeAll(self, event=None): 

1162 self.oops() 

1163 

1164 def resizePanesToRatio(self, ratio, secondary_ratio): 

1165 self.oops() 

1166 

1167 def resizeToScreen(self, event=None): 

1168 self.oops() 

1169 

1170 def setInitialWindowGeometry(self): 

1171 self.oops() 

1172 

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

1174 self.oops() 

1175 

1176 def toggleActivePane(self, event=None): 

1177 self.oops() 

1178 

1179 def toggleSplitDirection(self, event=None): 

1180 self.oops() 

1181 #@-others 

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

1183class LeoLog: 

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

1185 #@+others 

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

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

1188 def __init__(self, frame, parentFrame): 

1189 """Ctor for LeoLog class.""" 

1190 self.frame = frame 

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

1192 self.enabled = True 

1193 self.newlines = 0 

1194 self.isNull = False 

1195 # Official ivars... 

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

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

1198 # Important: depeding on the log *tab*, 

1199 # logCtrl may be either a wrapper or a widget. 

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

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

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

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

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

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

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

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

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

1209 self.selectTab(tabName, wrap=wrap) 

1210 w = self.logCtrl 

1211 if w: 

1212 w.delete(0, 'end') 

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

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

1215 if createText: 

1216 w = self.createTextWidget(self.tabFrame) 

1217 self.canvasDict[tabName] = None 

1218 self.textDict[tabName] = w 

1219 else: 

1220 self.canvasDict[tabName] = None 

1221 self.textDict[tabName] = None 

1222 self.frameDict[tabName] = tabName # tabFrame 

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

1224 def createTextWidget(self, parentFrame): 

1225 return None 

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

1227 def deleteTab(self, tabName): 

1228 c = self.c 

1229 if tabName == 'Log': 

1230 pass 

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

1232 self.selectTab('Log') 

1233 else: 

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

1235 if tabName in d: 

1236 del d[tabName] 

1237 self.tabName = None 

1238 self.selectTab('Log') 

1239 c.invalidateFocus() 

1240 c.bodyWantsFocus() 

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

1242 def disable(self): 

1243 self.enabled = False 

1244 

1245 def enable(self, enabled=True): 

1246 self.enabled = enabled 

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

1248 def getSelectedTab(self): 

1249 return self.tabName 

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

1251 def hideTab(self, tabName): 

1252 self.selectTab('Log') 

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

1254 def lowerTab(self, tabName): 

1255 self.c.invalidateFocus() 

1256 self.c.bodyWantsFocus() 

1257 

1258 def raiseTab(self, tabName): 

1259 self.c.invalidateFocus() 

1260 self.c.bodyWantsFocus() 

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

1262 def orderedTabNames(self, LeoLog=None): 

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

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

1265 def numberOfVisibleTabs(self): 

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

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

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

1269 

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

1271 print(s) 

1272 

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

1274 pass # print ('') 

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

1276 def renameTab(self, oldName, newName): 

1277 pass 

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

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

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

1281 c = self.c 

1282 tabFrame = self.frameDict.get(tabName) 

1283 if not tabFrame: 

1284 self.createTab(tabName, createText=createText) 

1285 # Update the status vars. 

1286 self.tabName = tabName 

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

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

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

1290 if 0: 

1291 # Absolutely do not do this here! 

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

1293 c.widgetWantsFocusNow(self.logCtrl) 

1294 return tabFrame 

1295 #@-others 

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

1297class LeoTree: 

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

1299 #@+others 

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

1301 def __init__(self, frame): 

1302 """Ctor for the LeoTree class.""" 

1303 self.frame = frame 

1304 self.c = frame.c 

1305 self.edit_text_dict = {} 

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

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

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

1309 self.drag_p = None 

1310 self.generation = 0 

1311 # Leo 5.6: low-level vnode methods increment 

1312 # this count whenever the tree changes. 

1313 self.redrawCount = 0 # For traces 

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

1315 # Define these here to keep pylint happy. 

1316 self.canvas = None 

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

1318 # These are new in Leo 4.6. 

1319 

1320 def initAfterLoad(self): 

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

1322 

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

1324 

1325 def redraw_after_contract(self, p): 

1326 self.c.redraw() 

1327 

1328 def redraw_after_expand(self, p): 

1329 self.c.redraw() 

1330 

1331 def redraw_after_head_changed(self): 

1332 self.c.redraw() 

1333 

1334 def redraw_after_icons_changed(self): 

1335 self.c.redraw() 

1336 

1337 def redraw_after_select(self, p=None): 

1338 self.c.redraw() 

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

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

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

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

1343 """ 

1344 Officially change a headline. 

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

1346 """ 

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

1348 if not w: 

1349 g.trace('no w') 

1350 return 

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

1352 s = w.getAllText() 

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

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

1355 # Remove trailing newlines before warning of truncation. 

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

1357 s = s[:-1] 

1358 # Warn if there are multiple lines. 

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

1360 if i > -1: 

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

1362 s = s[:i] 

1363 limit = 1000 

1364 if len(s) > limit: 

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

1366 s = s[:limit] 

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

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

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

1370 changed = s != p.h 

1371 if not changed: 

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

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

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

1375 # Handle undo. 

1376 undoData = u.beforeChangeHeadline(p) 

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

1378 if not c.changed: 

1379 c.setChanged() 

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

1381 # the headline may contain directives. 

1382 c.frame.scanForTabWidth(p) 

1383 c.frame.body.recolor(p) 

1384 p.setDirty() 

1385 u.afterChangeHeadline(p, undoType, undoData) 

1386 c.redraw_after_head_changed() 

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

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

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

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

1391 def endEditLabel(self): 

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

1393 # Important: this will redraw if necessary. 

1394 self.onHeadChanged(self.c.p) 

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

1396 # k.setDefaultInputState() 

1397 # k.showStateAndMode() 

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

1399 # c.bodyWantsFocus() 

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

1401 def getEditTextDict(self, v): 

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

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

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

1405 def onHeadlineKey(self, event): 

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

1407 w = event.widget if event else None 

1408 ch = event.char if event else '' 

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

1410 if ch: 

1411 self.updateHead(event, w) 

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

1413 def OnIconCtrlClick(self, p): 

1414 g.openUrl(p) 

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

1416 def OnIconDoubleClick(self, p): 

1417 pass 

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

1419 def updateHead(self, event, w): 

1420 """ 

1421 Update a headline from an event. 

1422 

1423 The headline officially changes only when editing ends. 

1424 """ 

1425 k = self.c.k 

1426 ch = event.char if event else '' 

1427 i, j = w.getSelectionRange() 

1428 ins = w.getInsertPoint() 

1429 if i != j: 

1430 ins = i 

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

1432 if i != j: 

1433 w.delete(i, j) 

1434 # Bug fix: 2018/04/19. 

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

1436 elif i > 0: 

1437 i -= 1 

1438 w.delete(i) 

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

1440 else: 

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

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

1443 if i != j: 

1444 w.delete(i, j) 

1445 elif k.unboundKeyAction == 'overwrite': 

1446 w.delete(i, i + 1) 

1447 w.insert(ins, ch) 

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

1449 s = w.getAllText() 

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

1451 s = s[:-1] 

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

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

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

1455 self.endEditLabel() 

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

1457 # Drawing & scrolling. 

1458 

1459 def drawIcon(self, p): 

1460 self.oops() 

1461 

1462 def redraw(self, p=None): 

1463 self.oops() 

1464 redraw_now = redraw 

1465 

1466 def scrollTo(self, p): 

1467 self.oops() 

1468 

1469 # Headlines. 

1470 

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

1472 self.oops() 

1473 

1474 def edit_widget(self, p): 

1475 self.oops() 

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

1477 tree_select_lockout = False 

1478 

1479 def select(self, p): 

1480 """ 

1481 Select a node. 

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

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

1484 """ 

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

1486 tag = 'LeoTree.select' 

1487 c = self.c 

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

1489 return 

1490 if trace: 

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

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

1493 # Format matches traces in leoflexx.py 

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

1495 try: 

1496 self.tree_select_lockout = True 

1497 self.prev_v = c.p.v 

1498 self.selectHelper(p) 

1499 finally: 

1500 self.tree_select_lockout = False 

1501 if c.enableRedrawFlag: 

1502 p = c.p 

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

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

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

1506 c.redraw_later() 

1507 # This *does* happen sometimes. 

1508 else: 

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

1510 if hasattr(self, 'setItemForCurrentPosition'): 

1511 # pylint: disable=no-member 

1512 self.setItemForCurrentPosition() # type:ignore 

1513 else: 

1514 c.requestLaterRedraw = True 

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

1516 def selectHelper(self, p): 

1517 """ 

1518 A helper function for leoTree.select. 

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

1520 """ 

1521 if not p: 

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

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

1524 return 

1525 c = self.c 

1526 if not c.frame.body.wrapper: 

1527 return # Defensive. 

1528 if p.v.context != c: 

1529 # Selecting a foreign position will not be pretty. 

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

1531 return 

1532 old_p = c.p 

1533 call_event_handlers = p != old_p 

1534 # Order is important... 

1535 self.unselect_helper(old_p, p) 

1536 # 1. Call c.endEditLabel. 

1537 self.select_new_node(old_p, p) 

1538 # 2. Call set_body_text_after_select. 

1539 self.change_current_position(old_p, p) 

1540 # 3. Call c.undoer.onSelect. 

1541 self.scroll_cursor(p) 

1542 # 4. Set cursor in body. 

1543 self.set_status_line(p) 

1544 # 5. Last tweaks. 

1545 if call_event_handlers: 

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

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

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

1549 def unselect_helper(self, old_p, p): 

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

1551 c = self.c 

1552 call_event_handlers = p != old_p 

1553 if call_event_handlers: 

1554 unselect = not g.doHook( 

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

1556 else: 

1557 unselect = True 

1558 

1559 # Actually unselect the old node. 

1560 if unselect and old_p and old_p != p: 

1561 self.endEditLabel() 

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

1563 if hasattr(self, 'unselectItem'): 

1564 # pylint: disable=no-member 

1565 self.unselectItem(old_p) # type:ignore 

1566 if call_event_handlers: 

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

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

1569 def select_new_node(self, old_p, p): 

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

1571 c = self.c 

1572 call_event_handlers = p != old_p 

1573 if ( 

1574 call_event_handlers and g.doHook("select1", 

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

1576 ): 

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

1578 g.trace('select1 override') 

1579 return 

1580 c.frame.setWrap(p) 

1581 # Not that expensive 

1582 self.set_body_text_after_select(p, old_p) 

1583 c.nodeHistory.update(p) 

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

1585 def set_body_text_after_select(self, p, old_p): 

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

1587 c = self.c 

1588 w = c.frame.body.wrapper 

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

1590 # Part 1: get the old text. 

1591 old_s = w.getAllText() 

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

1593 return 

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

1595 c.setCurrentPosition(p) 

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

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

1598 w.setAllText(s) 

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

1600 # p.restoreCursorAndScroll() 

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

1602 def change_current_position(self, old_p, p): 

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

1604 c = self.c 

1605 # c.setCurrentPosition(p) 

1606 # This is now done in set_body_text_after_select. 

1607 c.frame.scanForTabWidth(p) 

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

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

1610 if use_chapters: 

1611 cc = c.chapterController 

1612 theChapter = cc and cc.getSelectedChapter() 

1613 if theChapter: 

1614 theChapter.p = p.copy() 

1615 # Do not call treeFocusHelper here! 

1616 # c.treeFocusHelper() 

1617 c.undoer.onSelect(old_p, p) 

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

1619 def scroll_cursor(self, p): 

1620 """Scroll the cursor.""" 

1621 p.restoreCursorAndScroll() 

1622 # Was in setBodyTextAfterSelect 

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

1624 def set_status_line(self, p): 

1625 """Update the status line.""" 

1626 c = self.c 

1627 c.frame.body.assignPositionToEditor(p) 

1628 # New in Leo 4.4.1. 

1629 c.frame.updateStatusLine() 

1630 # New in Leo 4.4.1. 

1631 c.frame.clearStatusLine() 

1632 if p and p.v: 

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

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

1635 def oops(self): 

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

1637 #@-others 

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

1639class LeoTreeTab: 

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

1641 #@+others 

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

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

1644 self.c = c 

1645 self.cc = chapterController 

1646 self.nb = None # Created in createControl. 

1647 self.parentFrame = parentFrame 

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

1649 def createControl(self): 

1650 self.oops() 

1651 

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

1653 self.oops() 

1654 

1655 def destroyTab(self, tabName): 

1656 self.oops() 

1657 

1658 def selectTab(self, tabName): 

1659 self.oops() 

1660 

1661 def setTabLabel(self, tabName): 

1662 self.oops() 

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

1664 def oops(self): 

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

1666 #@-others 

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

1668class NullBody(LeoBody): 

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

1670 #@+others 

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

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

1673 """Ctor for NullBody class.""" 

1674 super().__init__(frame, parentFrame) 

1675 self.insertPoint = 0 

1676 self.selection = 0, 0 

1677 self.s = "" # The body text 

1678 self.widget = None 

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

1680 self.editorWrappers['1'] = wrapper 

1681 self.colorizer = NullColorizer(self.c) 

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

1683 # Birth, death... 

1684 

1685 def createControl(self, parentFrame, p): 

1686 pass 

1687 # Editors... 

1688 

1689 def addEditor(self, event=None): 

1690 pass 

1691 

1692 def assignPositionToEditor(self, p): 

1693 pass 

1694 

1695 def createEditorFrame(self, w): 

1696 return None 

1697 

1698 def cycleEditorFocus(self, event=None): 

1699 pass 

1700 

1701 def deleteEditor(self, event=None): 

1702 pass 

1703 

1704 def selectEditor(self, w): 

1705 pass 

1706 

1707 def selectLabel(self, w): 

1708 pass 

1709 

1710 def setEditorColors(self, bg, fg): 

1711 pass 

1712 

1713 def unselectLabel(self, w): 

1714 pass 

1715 

1716 def updateEditors(self): 

1717 pass 

1718 # Events... 

1719 

1720 def forceFullRecolor(self): 

1721 pass 

1722 

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

1724 pass 

1725 # Low-level gui... 

1726 

1727 def setFocus(self): 

1728 pass 

1729 #@-others 

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

1731class NullColorizer(leoColorizer.BaseColorizer): 

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

1733 

1734 recolorCount = 0 

1735 

1736 def colorize(self, p): 

1737 self.recolorCount += 1 

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

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

1740class NullFrame(LeoFrame): 

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

1742 #@+others 

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

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

1745 """Ctor for the NullFrame class.""" 

1746 super().__init__(c, gui) 

1747 assert self.c 

1748 self.wrapper = None 

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

1750 self.initComplete = True 

1751 self.isNullFrame = True 

1752 self.outerFrame = None 

1753 self.ratio = self.secondary_ratio = 0.5 

1754 self.statusLineClass = NullStatusLineClass 

1755 self.title = title 

1756 self.top = None # Always None. 

1757 # Create the component objects. 

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

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

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

1761 self.tree = NullTree(frame=self) 

1762 # Default window position. 

1763 self.w = 600 

1764 self.h = 500 

1765 self.x = 40 

1766 self.y = 40 

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

1768 def bringToFront(self): 

1769 pass 

1770 

1771 def cascade(self, event=None): 

1772 pass 

1773 

1774 def contractBodyPane(self, event=None): 

1775 pass 

1776 

1777 def contractLogPane(self, event=None): 

1778 pass 

1779 

1780 def contractOutlinePane(self, event=None): 

1781 pass 

1782 

1783 def contractPane(self, event=None): 

1784 pass 

1785 

1786 def deiconify(self): 

1787 pass 

1788 

1789 def destroySelf(self): 

1790 pass 

1791 

1792 def equalSizedPanes(self, event=None): 

1793 pass 

1794 

1795 def expandBodyPane(self, event=None): 

1796 pass 

1797 

1798 def expandLogPane(self, event=None): 

1799 pass 

1800 

1801 def expandOutlinePane(self, event=None): 

1802 pass 

1803 

1804 def expandPane(self, event=None): 

1805 pass 

1806 

1807 def forceWrap(self, p): 

1808 pass 

1809 

1810 def fullyExpandBodyPane(self, event=None): 

1811 pass 

1812 

1813 def fullyExpandLogPane(self, event=None): 

1814 pass 

1815 

1816 def fullyExpandOutlinePane(self, event=None): 

1817 pass 

1818 

1819 def fullyExpandPane(self, event=None): 

1820 pass 

1821 

1822 def get_window_info(self): 

1823 return 600, 500, 20, 20 

1824 

1825 def hideBodyPane(self, event=None): 

1826 pass 

1827 

1828 def hideLogPane(self, event=None): 

1829 pass 

1830 

1831 def hideLogWindow(self, event=None): 

1832 pass 

1833 

1834 def hideOutlinePane(self, event=None): 

1835 pass 

1836 

1837 def hidePane(self, event=None): 

1838 pass 

1839 

1840 def leoHelp(self, event=None): 

1841 pass 

1842 

1843 def lift(self): 

1844 pass 

1845 

1846 def minimizeAll(self, event=None): 

1847 pass 

1848 

1849 def oops(self): 

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

1851 

1852 def resizePanesToRatio(self, ratio, secondary_ratio): 

1853 pass 

1854 

1855 def resizeToScreen(self, event=None): 

1856 pass 

1857 

1858 def setInitialWindowGeometry(self): 

1859 pass 

1860 

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

1862 return 0, 0, 0, 0 

1863 

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

1865 pass 

1866 

1867 def toggleActivePane(self, event=None): 

1868 pass 

1869 

1870 def toggleSplitDirection(self, event=None): 

1871 pass 

1872 

1873 def update(self): 

1874 pass 

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

1876 def finishCreate(self): 

1877 

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

1879 self.createFirstTreeNode() 

1880 # Call the base LeoFrame method. 

1881 #@-others 

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

1883class NullIconBarClass: 

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

1885 #@+others 

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

1887 def __init__(self, c, parentFrame): 

1888 """Ctor for NullIconBarClass.""" 

1889 self.c = c 

1890 self.iconFrame = None 

1891 self.parentFrame = parentFrame 

1892 self.w = g.NullObject() 

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

1894 def addRow(self, height=None): 

1895 pass 

1896 

1897 def addRowIfNeeded(self): 

1898 pass 

1899 

1900 def addWidget(self, w): 

1901 pass 

1902 

1903 def createChaptersIcon(self): 

1904 pass 

1905 

1906 def deleteButton(self, w): 

1907 pass 

1908 

1909 def getNewFrame(self): 

1910 return None 

1911 

1912 def hide(self): 

1913 pass 

1914 

1915 def show(self): 

1916 pass 

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

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

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

1920 command = keys.get('command') 

1921 text = keys.get('text') 

1922 try: 

1923 g.app.iconWidgetCount += 1 

1924 except Exception: 

1925 g.app.iconWidgetCount = 1 

1926 n = g.app.iconWidgetCount 

1927 name = f"nullButtonWidget {n}" 

1928 if not command: 

1929 

1930 def commandCallback(name=name): 

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

1932 

1933 command = commandCallback 

1934 

1935 

1936 class nullButtonWidget: 

1937 

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

1939 self.c = c 

1940 self.command = command 

1941 self.name = name 

1942 self.text = text 

1943 

1944 def __repr__(self): 

1945 return self.name 

1946 

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

1948 return b 

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

1950 def clear(self): 

1951 g.app.iconWidgetCount = 0 

1952 g.app.iconImageRefs = [] 

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

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

1955 button.command = command 

1956 try: 

1957 # See PR #2441: Add rclick support. 

1958 from leo.plugins.mod_scripting import build_rclick_tree 

1959 rclicks = build_rclick_tree(command_p, top_level=True) 

1960 button.rclicks = rclicks 

1961 except Exception: 

1962 pass 

1963 #@-others 

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

1965class NullLog(LeoLog): 

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

1967 #@+others 

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

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

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

1971 

1972 super().__init__(frame, parentFrame) 

1973 self.isNull = True 

1974 self.logNumber = 0 

1975 self.widget = self.createControl(parentFrame) 

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

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

1978 def finishCreate(self): 

1979 pass 

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

1981 def createControl(self, parentFrame): 

1982 return self.createTextWidget(parentFrame) 

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

1984 def createTextWidget(self, parentFrame): 

1985 self.logNumber += 1 

1986 c = self.c 

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

1988 return log 

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

1990 def hasSelection(self): 

1991 return self.widget.hasSelection() 

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

1993 def isLogWidget(self, w): 

1994 return False 

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

1996 def oops(self): 

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

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

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

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

2001 if self.enabled: 

2002 try: 

2003 g.pr(s, newline=False) 

2004 except UnicodeError: 

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

2006 g.pr(s, newline=False) 

2007 

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

2009 if self.enabled: 

2010 g.pr('') 

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

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

2013 pass 

2014 

2015 def createCanvas(self, tabName): 

2016 pass 

2017 

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

2019 pass 

2020 

2021 def deleteTab(self, tabName): 

2022 pass 

2023 

2024 def getSelectedTab(self): 

2025 return None 

2026 

2027 def lowerTab(self, tabName): 

2028 pass 

2029 

2030 def raiseTab(self, tabName): 

2031 pass 

2032 

2033 def renameTab(self, oldName, newName): 

2034 pass 

2035 

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

2037 pass 

2038 #@-others 

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

2040class NullStatusLineClass: 

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

2042 

2043 def __init__(self, c, parentFrame): 

2044 """Ctor for NullStatusLine class.""" 

2045 self.c = c 

2046 self.enabled = False 

2047 self.parentFrame = parentFrame 

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

2049 # Set the official ivars. 

2050 c.frame.statusFrame = None 

2051 c.frame.statusLabel = None 

2052 c.frame.statusText = self.textWidget 

2053 #@+others 

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

2055 def disable(self, background=None): 

2056 self.enabled = False 

2057 # self.c.bodyWantsFocus() 

2058 

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

2060 self.c.widgetWantsFocus(self.textWidget) 

2061 self.enabled = True 

2062 

2063 def clear(self): 

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

2065 

2066 def get(self): 

2067 return self.textWidget.getAllText() 

2068 

2069 def isEnabled(self): 

2070 return self.enabled 

2071 

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

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

2074 

2075 def setFocus(self): 

2076 pass 

2077 

2078 def update(self): 

2079 pass 

2080 #@-others 

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

2082class NullTree(LeoTree): 

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

2084 #@+others 

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

2086 def __init__(self, frame): 

2087 """Ctor for NullTree class.""" 

2088 super().__init__(frame) 

2089 assert self.frame 

2090 self.c = frame.c 

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

2092 self.font = None 

2093 self.fontName = None 

2094 self.canvas = None 

2095 self.treeWidget = g.NullObject() 

2096 self.redrawCount = 0 

2097 self.updateCount = 0 

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

2099 def edit_widget(self, p): 

2100 d = self.editWidgetsDict 

2101 if not p or not p.v: 

2102 return None 

2103 w = d.get(p.v) 

2104 if not w: 

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

2106 c=self.c, 

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

2108 w.setAllText(p.h) 

2109 return w 

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

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

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

2113 self.endEditLabel() 

2114 if p: 

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

2116 e = None 

2117 return e, wrapper 

2118 return None, None 

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

2120 def printWidgets(self): 

2121 d = self.editWidgetsDict 

2122 for key in d: 

2123 # keys are vnodes, values are StringTextWidgets. 

2124 w = d.get(key) 

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

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

2127 def drawIcon(self, p): 

2128 pass 

2129 

2130 def redraw(self, p=None): 

2131 self.redrawCount += 1 

2132 return p 

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

2134 

2135 redraw_now = redraw 

2136 

2137 def redraw_after_contract(self, p): 

2138 self.redraw() 

2139 

2140 def redraw_after_expand(self, p): 

2141 self.redraw() 

2142 

2143 def redraw_after_head_changed(self): 

2144 self.redraw() 

2145 

2146 def redraw_after_icons_changed(self): 

2147 self.redraw() 

2148 

2149 def redraw_after_select(self, p=None): 

2150 self.redraw() 

2151 

2152 def scrollTo(self, p): 

2153 pass 

2154 

2155 def updateAllIcons(self, p): 

2156 pass 

2157 

2158 def updateIcon(self, p): 

2159 pass 

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

2161 def setHeadline(self, p, s): 

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

2163 

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

2165 w = self.edit_widget(p) 

2166 if w: 

2167 w.delete(0, 'end') 

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

2169 s = s[:-1] 

2170 w.insert(0, s) 

2171 else: 

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

2173 #@-others 

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

2175class StringTextWrapper: 

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

2177 #@+others 

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

2179 def __init__(self, c, name): 

2180 """Ctor for the StringTextWrapper class.""" 

2181 self.c = c 

2182 self.name = name 

2183 self.ins = 0 

2184 self.sel = 0, 0 

2185 self.s = '' 

2186 self.supportsHighLevelInterface = True 

2187 self.virtualInsertPoint = 0 

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

2189 

2190 def __repr__(self): 

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

2192 

2193 def getName(self): 

2194 """StringTextWrapper.""" 

2195 return self.name # Essential. 

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

2197 def clipboard_clear(self): 

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

2199 

2200 def clipboard_append(self, s): 

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

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

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

2204 # For StringTextWrapper. 

2205 

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

2207 pass 

2208 

2209 def getXScrollPosition(self): 

2210 return 0 

2211 

2212 def getYScrollPosition(self): 

2213 return 0 

2214 

2215 def see(self, i): 

2216 pass 

2217 

2218 def seeInsertPoint(self): 

2219 pass 

2220 

2221 def setFocus(self): 

2222 pass 

2223 

2224 def setStyleClass(self, name): 

2225 pass 

2226 

2227 def setXScrollPosition(self, i): 

2228 pass 

2229 

2230 def setYScrollPosition(self, i): 

2231 pass 

2232 

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

2234 pass 

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

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

2237 def appendText(self, s): 

2238 """StringTextWrapper.""" 

2239 self.s = self.s + g.toUnicode(s) 

2240 # defensive 

2241 self.ins = len(self.s) 

2242 self.sel = self.ins, self.ins 

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

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

2245 """StringTextWrapper.""" 

2246 i = self.toPythonIndex(i) 

2247 if j is None: 

2248 j = i + 1 

2249 j = self.toPythonIndex(j) 

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

2251 if i > j: 

2252 i, j = j, i 

2253 s = self.getAllText() 

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

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

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

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

2258 def deleteTextSelection(self): 

2259 """StringTextWrapper.""" 

2260 i, j = self.getSelectionRange() 

2261 self.delete(i, j) 

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

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

2264 """StringTextWrapper.""" 

2265 i = self.toPythonIndex(i) 

2266 if j is None: 

2267 j = i + 1 

2268 j = self.toPythonIndex(j) 

2269 s = self.s[i:j] 

2270 return g.toUnicode(s) 

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

2272 def getAllText(self): 

2273 """StringTextWrapper.""" 

2274 s = self.s 

2275 return g.checkUnicode(s) 

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

2277 def getInsertPoint(self): 

2278 """StringTextWrapper.""" 

2279 i = self.ins 

2280 if i is None: 

2281 if self.virtualInsertPoint is None: 

2282 i = 0 

2283 else: 

2284 i = self.virtualInsertPoint 

2285 self.virtualInsertPoint = i 

2286 return i 

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

2288 def getSelectedText(self): 

2289 """StringTextWrapper.""" 

2290 i, j = self.sel 

2291 s = self.s[i:j] 

2292 return g.checkUnicode(s) 

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

2294 def getSelectionRange(self, sort=True): 

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

2296 sel = self.sel 

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

2298 i, j = sel 

2299 if sort and i > j: 

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

2301 return sel 

2302 i = self.ins 

2303 return i, i 

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

2305 def hasSelection(self): 

2306 """StringTextWrapper.""" 

2307 i, j = self.getSelectionRange() 

2308 return i != j 

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

2310 def insert(self, i, s): 

2311 """StringTextWrapper.""" 

2312 i = self.toPythonIndex(i) 

2313 s1 = s 

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

2315 i += len(s1) 

2316 self.ins = i 

2317 self.sel = i, i 

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

2319 def selectAllText(self, insert=None): 

2320 """StringTextWrapper.""" 

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

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

2323 def setAllText(self, s): 

2324 """StringTextWrapper.""" 

2325 self.s = s 

2326 i = len(self.s) 

2327 self.ins = i 

2328 self.sel = i, i 

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

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

2331 """StringTextWrapper.""" 

2332 i = self.toPythonIndex(pos) 

2333 self.virtualInsertPoint = i 

2334 self.ins = i 

2335 self.sel = i, i 

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

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

2338 """StringTextWrapper.""" 

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

2340 self.sel = i, j 

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

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

2343 def toPythonIndex(self, index): 

2344 """ 

2345 StringTextWrapper.toPythonIndex. 

2346 

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

2348 

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

2350 """ 

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

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

2353 def toPythonIndexRowCol(self, index): 

2354 """StringTextWrapper.""" 

2355 s = self.getAllText() 

2356 i = self.toPythonIndex(index) 

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

2358 return i, row, col 

2359 #@-others 

2360#@-others 

2361#@@language python 

2362#@@tabwidth -4 

2363#@@pagewidth 70 

2364#@-leo