Coverage for C:\Repos\leo-editor\leo\core\leoGui.py: 70%

391 statements  

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

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20031218072017.3719: * @file leoGui.py 

4#@@first 

5""" 

6A module containing the base gui-related classes. 

7 

8These classes hide the details of which gui is actually being used. 

9Leo's core calls this class to allocate all gui objects. 

10 

11Plugins may define their own gui classes by setting g.app.gui. 

12""" 

13#@+<< imports leoGui.py >> 

14#@+node:ekr.20220414080546.1: ** << imports leoGui.py >> 

15from typing import Any, Callable, Dict, List, Optional, Tuple, Union 

16from typing import TYPE_CHECKING 

17from leo.core import leoGlobals as g 

18from leo.core import leoFrame 

19if TYPE_CHECKING: 

20 from leo.core.leoCommands import Commands as Cmdr 

21 from leo.core.leoNodes import Position as Pos 

22 # from leo.core.leoQt import QtWidgets 

23 

24else: 

25 Cmdr = Pos = Any 

26 # for NullGui and StringTextWrapper. 

27Event = Any 

28Widget = Any 

29Wrapper = Any 

30#@-<< imports leoGui.py >> 

31#@+others 

32#@+node:ekr.20031218072017.3720: ** class LeoGui 

33class LeoGui: 

34 """The base class of all gui classes. 

35 

36 Subclasses are expected to override all do-nothing methods of this class. 

37 """ 

38 #@+others 

39 #@+node:ekr.20031218072017.3722: *3* LeoGui.__init__ 

40 def __init__(self, guiName: str) -> None: 

41 """Ctor for the LeoGui class.""" 

42 self.active = False # Used only by qt_gui. 

43 self.consoleOnly = True # True if g.es goes to console. 

44 self.globalFindTabManager: Any = None 

45 self.globalFindTab: Widget = None 

46 self.idleTimeClass = None 

47 self.isNullGui = False 

48 self.lastFrame: Wrapper = None 

49 self.leoIcon = None 

50 self.mGuiName = guiName 

51 self.mainLoop = None 

52 self.plainTextWidget: Widget = None # For SpellTabHandler class only. 

53 self.root: Pos = None 

54 self.script: Optional[str] = None 

55 self.splashScreen: Widget = None 

56 self.utils = None 

57 # To keep pylint happy. 

58 self.ScriptingControllerClass = NullScriptingControllerClass 

59 # 

60 # Define special keys that may be overridden is subclasses. 

61 self.ignoreChars: List[str] = [] # Keys that should always be ignored. 

62 self.FKeys: List[str] = [] # The representation of F-keys. 

63 self.specialChars: List[str] = [] # A list of characters/keys to be handle specially. 

64 #@+node:ekr.20061109212618.1: *3* LeoGui: Must be defined only in base class 

65 #@+node:ekr.20110605121601.18847: *4* LeoGui.create_key_event (LeoGui) 

66 def create_key_event( 

67 self, 

68 c: Cmdr, 

69 binding: str=None, 

70 char: str=None, 

71 event: Event=None, 

72 w: Wrapper=None, 

73 x: int=None, 

74 x_root: int=None, 

75 y: int=None, 

76 y_root: int=None, 

77 ) -> Event: 

78 # Do not call strokeFromSetting here! 

79 # For example, this would wrongly convert Ctrl-C to Ctrl-c, 

80 # in effect, converting a user binding from Ctrl-Shift-C to Ctrl-C. 

81 return LeoKeyEvent(c, char, event, binding, w, x, y, x_root, y_root) 

82 #@+node:ekr.20031218072017.3740: *4* LeoGui.guiName 

83 def guiName(self) -> str: 

84 try: 

85 return self.mGuiName 

86 except Exception: 

87 return "invalid gui name" 

88 #@+node:ekr.20031218072017.2231: *4* LeoGui.setScript 

89 def setScript(self, script: str=None, scriptFileName: str=None) -> None: 

90 self.script = script 

91 self.scriptFileName = scriptFileName 

92 #@+node:ekr.20110605121601.18845: *4* LeoGui.event_generate (LeoGui) 

93 def event_generate(self, c: Cmdr, char: str, shortcut: str, w: Wrapper) -> None: 

94 event = self.create_key_event(c, binding=shortcut, char=char, w=w) 

95 c.k.masterKeyHandler(event) 

96 c.outerUpdate() 

97 #@+node:ekr.20061109212618: *3* LeoGu: Must be defined in subclasses 

98 #@+node:ekr.20031218072017.3725: *4* LeoGui.destroySelf 

99 def destroySelf(self) -> None: 

100 self.oops() 

101 #@+node:ekr.20031218072017.3730: *4* LeoGui.dialogs 

102 def runAboutLeoDialog(self, c: Cmdr, version: str, theCopyright: str, url: str, email: str) -> Any: 

103 """Create and run Leo's About Leo dialog.""" 

104 self.oops() 

105 

106 def runAskLeoIDDialog(self) -> Any: 

107 """Create and run a dialog to get g.app.LeoID.""" 

108 self.oops() 

109 

110 def runAskOkDialog(self, c: Cmdr, title: str, message: str=None, text: str="Ok") -> Any: 

111 """Create and run an askOK dialog .""" 

112 self.oops() 

113 

114 def runAskOkCancelNumberDialog(self, 

115 c: Cmdr, 

116 title: str, 

117 message: str, 

118 cancelButtonText: str=None, 

119 okButtonText: str=None, 

120 ) -> Any: 

121 """Create and run askOkCancelNumber dialog .""" 

122 self.oops() 

123 

124 def runAskOkCancelStringDialog( 

125 self, 

126 c: Cmdr, 

127 title: str, 

128 message: str, 

129 cancelButtonText: str=None, 

130 okButtonText: str=None, 

131 default: str="", 

132 wide: bool=False, 

133 ) -> Any: 

134 """Create and run askOkCancelString dialog .""" 

135 self.oops() 

136 

137 def runAskYesNoDialog(self, 

138 c: Cmdr, 

139 title: str, 

140 message: str=None, 

141 yes_all: bool=False, 

142 no_all: bool=False, 

143 ) -> Any: 

144 """Create and run an askYesNo dialog.""" 

145 self.oops() 

146 

147 def runAskYesNoCancelDialog( 

148 self, 

149 c: Cmdr, 

150 title: str, 

151 message: str=None, 

152 yesMessage: str="Yes", 

153 noMessage: str="No", 

154 yesToAllMessage: str=None, 

155 defaultButton: str="Yes", 

156 cancelMessage: str=None, 

157 ) -> Any: 

158 """Create and run an askYesNoCancel dialog .""" 

159 self.oops() 

160 

161 def runPropertiesDialog(self, 

162 title: str='Properties', 

163 data: str=None, 

164 callback: Callable=None, 

165 buttons: str=None, 

166 ) -> Any: 

167 """Dispay a modal TkPropertiesDialog""" 

168 self.oops() 

169 #@+node:ekr.20031218072017.3731: *4* LeoGui.file dialogs 

170 def runOpenFileDialog(self, 

171 c: Cmdr, 

172 title: str, 

173 filetypes: List[str], 

174 defaultextension: str, 

175 multiple: bool=False, 

176 startpath: str=None, 

177 ) -> Union[List[str], str]: # Return type depends on the evil multiple keyword. 

178 """Create and run an open file dialog .""" 

179 self.oops() 

180 return 'no' 

181 

182 def runSaveFileDialog(self, c: Cmdr, title: str, filetypes: List[str], defaultextension: str) -> str: 

183 """Create and run a save file dialog .""" 

184 self.oops() 

185 return 'no' 

186 #@+node:ekr.20031218072017.3732: *4* LeoGui.panels 

187 def createColorPanel(self, c: Cmdr) -> None: 

188 """Create a color panel""" 

189 self.oops() 

190 

191 def createComparePanel(self, c: Cmdr) -> None: 

192 """Create Compare panel.""" 

193 self.oops() 

194 

195 def createFindTab(self, c: Cmdr, parentFrame: Widget) -> None: 

196 """Create a find tab in the indicated frame.""" 

197 self.oops() 

198 

199 def createFontPanel(self, c: Cmdr) -> None: 

200 """Create a hidden Font panel.""" 

201 self.oops() 

202 

203 def createLeoFrame(self, c: Cmdr, title: str) -> None: 

204 """Create a new Leo frame.""" 

205 self.oops() 

206 #@+node:ekr.20031218072017.3729: *4* LeoGui.runMainLoop 

207 def runMainLoop(self) -> None: 

208 """Run the gui's main loop.""" 

209 self.oops() 

210 #@+node:ekr.20031218072017.3733: *4* LeoGui.utils 

211 #@+at Subclasses are expected to subclass all of the following methods. 

212 # These are all do-nothing methods: callers are expected to check for 

213 # None returns. 

214 # The type of commander passed to methods depends on the type of frame 

215 # or dialog being created. The commander may be a Commands instance or 

216 # one of its subcommanders. 

217 #@+node:ekr.20031218072017.3734: *5* LeoGui.Clipboard 

218 def replaceClipboardWith(self, s: str) -> None: 

219 self.oops() 

220 

221 def getTextFromClipboard(self) -> str: 

222 self.oops() 

223 return '' 

224 #@+node:ekr.20031218072017.3735: *5* LeoGui.Dialog utils 

225 def attachLeoIcon(self, window: Any) -> None: 

226 """Attach the Leo icon to a window.""" 

227 self.oops() 

228 

229 def center_dialog(self, dialog: str) -> None: 

230 """Center a dialog.""" 

231 self.oops() 

232 

233 def create_labeled_frame(self, 

234 parent: str, 

235 caption: str=None, 

236 relief: str="groove", 

237 bd: int=2, 

238 padx: int=0, 

239 pady: int=0, 

240 ) -> None: 

241 """Create a labeled frame.""" 

242 self.oops() 

243 

244 def get_window_info(self, window: str) -> Tuple[int, int, int, int]: 

245 """Return the window information.""" 

246 self.oops() 

247 return 0, 0, 0, 0 

248 #@+node:ekr.20031218072017.3736: *5* LeoGui.Font 

249 def getFontFromParams(self, family: str, size: str, slant: str, weight: str, defaultSize: int=12) -> Any: 

250 

251 self.oops() 

252 #@+node:ekr.20070212145124: *5* LeoGui.getFullVersion 

253 def getFullVersion(self, c: Cmdr=None) -> str: 

254 return 'LeoGui: dummy version' 

255 #@+node:ekr.20070212070820: *5* LeoGui.makeScriptButton 

256 def makeScriptButton( 

257 self, 

258 c: Cmdr, 

259 args: str=None, 

260 p: Pos=None, 

261 script: str=None, 

262 buttonText: str=None, 

263 balloonText: str='Script Button', 

264 shortcut: str=None, 

265 bg: str='LightSteelBlue1', 

266 define_g: bool=True, 

267 define_name: str='__main__', 

268 silent: bool=False, 

269 ) -> None: 

270 self.oops() 

271 #@+node:ekr.20070228154059: *3* LeoGui: May be defined in subclasses 

272 #@+node:ekr.20110613103140.16423: *4* LeoGui.dismiss_spash_screen 

273 def dismiss_splash_screen(self) -> None: 

274 pass # May be overridden in subclasses. 

275 #@+node:tbrown.20110618095626.22068: *4* LeoGui.ensure_commander_visible 

276 def ensure_commander_visible(self, c: Cmdr) -> None: 

277 """E.g. if commanders are in tabs, make sure c's tab is visible""" 

278 pass 

279 #@+node:ekr.20070219084912: *4* LeoGui.finishCreate 

280 def finishCreate(self) -> None: 

281 # This may be overridden in subclasses. 

282 pass 

283 #@+node:ekr.20101028131948.5861: *4* LeoGui.killPopupMenu & postPopupMenu 

284 # These definitions keep pylint happy. 

285 

286 def postPopupMenu(self, *args: str, **keys: str) -> None: 

287 pass 

288 #@+node:ekr.20031218072017.3741: *4* LeoGui.oops 

289 def oops(self) -> Any: 

290 # It is not usually an error to call methods of this class. 

291 # However, this message is useful when writing gui plugins. 

292 if 1: 

293 g.pr("LeoGui oops", g.callers(4), "should be overridden in subclass") 

294 #@+node:ekr.20170612065049.1: *4* LeoGui.put_help 

295 def put_help(self, c: Cmdr, s: str, short_title: str) -> None: 

296 pass 

297 #@+node:ekr.20051206103652: *4* LeoGui.widget_name (LeoGui) 

298 def widget_name(self, w: Wrapper) -> str: 

299 # First try the widget's getName method. 

300 if not 'w': 

301 return '<no widget>' 

302 if hasattr(w, 'getName'): 

303 return w.getName() 

304 if hasattr(w, '_name'): 

305 return w._name 

306 return repr(w) 

307 #@-others 

308#@+node:ekr.20070228160107: ** class LeoKeyEvent 

309class LeoKeyEvent: 

310 """A gui-independent wrapper for gui events.""" 

311 #@+others 

312 #@+node:ekr.20110605121601.18846: *3* LeoKeyEvent.__init__ 

313 def __init__( 

314 self, 

315 c: Cmdr, 

316 char: str, 

317 event: Event, 

318 binding: Any, 

319 w: Wrapper, 

320 x: int=None, 

321 y: int=None, 

322 x_root: int=None, 

323 y_root: int=None, 

324 ) -> None: 

325 """Ctor for LeoKeyEvent class.""" 

326 stroke: Any 

327 if g.isStroke(binding): 

328 g.trace('***** (LeoKeyEvent) oops: already a stroke', binding, g.callers()) 

329 stroke = binding 

330 else: 

331 stroke = g.KeyStroke(binding) if binding else None 

332 assert g.isStrokeOrNone(stroke), f"(LeoKeyEvent) {stroke!r} {g.callers()}" 

333 if 0: # Doesn't add much. 

334 if 'keys' in g.app.debug: 

335 print(f"LeoKeyEvent: binding: {binding}, stroke: {stroke}, char: {char!r}") 

336 self.c = c 

337 self.char = char or '' 

338 self.event = event # New in Leo 4.11. 

339 self.stroke = stroke 

340 self.w = self.widget = w 

341 # Optional ivars 

342 self.x = x 

343 self.y = y 

344 # Support for fastGotoNode plugin 

345 self.x_root = x_root 

346 self.y_root = y_root 

347 #@+node:ekr.20140907103315.18774: *3* LeoKeyEvent.__repr__ 

348 def __repr__(self) -> str: 

349 

350 d = {'c': self.c.shortFileName()} 

351 for ivar in ('char', 'event', 'stroke', 'w'): 

352 d[ivar] = getattr(self, ivar) 

353 return f"LeoKeyEvent:\n{g.objToString(d)}" 

354 #@+node:ekr.20150511181702.1: *3* LeoKeyEvent.get & __getitem__ 

355 def get(self, attr: str) -> None: 

356 """Compatibility with g.bunch: return an attr.""" 

357 return getattr(self, attr, None) 

358 

359 def __getitem__(self, attr: str) -> None: 

360 """Compatibility with g.bunch: return an attr.""" 

361 return getattr(self, attr, None) 

362 #@+node:ekr.20140907103315.18775: *3* LeoKeyEvent.type 

363 def type(self) -> str: 

364 return 'LeoKeyEvent' 

365 #@-others 

366#@+node:ekr.20031218072017.2223: ** class NullGui (LeoGui) 

367class NullGui(LeoGui): 

368 """Null gui class.""" 

369 #@+others 

370 #@+node:ekr.20031218072017.2225: *3* NullGui.__init__ 

371 def __init__(self, guiName: str='nullGui') -> None: 

372 """ctor for the NullGui class.""" 

373 super().__init__(guiName) 

374 self.clipboardContents = '' 

375 self.focusWidget: Widget = None 

376 self.script = None 

377 self.lastFrame: Wrapper = None # The outer frame, to set g.app.log in runMainLoop. 

378 self.isNullGui = True 

379 self.idleTimeClass: Any = g.NullObject 

380 #@+node:ekr.20031218072017.3744: *3* NullGui.dialogs 

381 def runAboutLeoDialog(self, c: Cmdr, version: str, theCopyright: str, url: str, email: str) -> str: 

382 return self.simulateDialog("aboutLeoDialog", None) 

383 

384 def runAskLeoIDDialog(self) -> str: 

385 return self.simulateDialog("leoIDDialog", None) 

386 

387 def runAskOkDialog(self, c: Cmdr, title: str, message: str=None, text: str="Ok") -> str: 

388 return self.simulateDialog("okDialog", "Ok") 

389 

390 def runAskOkCancelNumberDialog( 

391 self, 

392 c: Cmdr, 

393 title: str, 

394 message: str, 

395 cancelButtonText: str=None, 

396 okButtonText: str=None, 

397 ) -> str: 

398 return self.simulateDialog("numberDialog", 'no') 

399 

400 def runAskOkCancelStringDialog( 

401 self, 

402 c: Cmdr, 

403 title: str, 

404 message: str, 

405 cancelButtonText: str=None, 

406 okButtonText: str=None, 

407 default: str="", 

408 wide: bool=False, 

409 ) -> str: 

410 return self.simulateDialog("stringDialog", '') 

411 

412 def runCompareDialog(self, c: Cmdr) -> str: 

413 return self.simulateDialog("compareDialog", '') 

414 

415 def runOpenFileDialog( 

416 self, 

417 c: Cmdr, 

418 title: str, 

419 filetypes: List[str], 

420 defaultextension: str, 

421 multiple: bool=False, 

422 startpath: str=None, 

423 ) -> Union[List[str], str]: # Return type depends on the evil multiple keyword. 

424 return self.simulateDialog("openFileDialog", None) 

425 

426 def runSaveFileDialog(self, c: Cmdr, title: str, filetypes: List[str], defaultextension: str) -> str: 

427 return self.simulateDialog("saveFileDialog", None) 

428 

429 def runAskYesNoDialog( 

430 self, 

431 c: Cmdr, 

432 title: str, 

433 message: str=None, 

434 yes_all: bool=False, 

435 no_all: bool=False, 

436 ) -> str: 

437 return self.simulateDialog("yesNoDialog", "no") 

438 

439 def runAskYesNoCancelDialog( 

440 self, 

441 c: Cmdr, 

442 title: str, 

443 message: str=None, 

444 yesMessage: str="Yes", 

445 noMessage: str="No", 

446 yesToAllMessage: str=None, 

447 defaultButton: str="Yes", 

448 cancelMessage: str=None, 

449 ) -> str: 

450 return self.simulateDialog("yesNoCancelDialog", "cancel") 

451 

452 def simulateDialog(self, key: str, defaultVal: str) -> str: 

453 return defaultVal 

454 #@+node:ekr.20170613101737.1: *3* NullGui.clipboard & focus 

455 def get_focus(self, *args: str, **kwargs: str) -> Widget: 

456 return self.focusWidget 

457 

458 def getTextFromClipboard(self) -> str: 

459 return self.clipboardContents 

460 

461 def replaceClipboardWith(self, s: str) -> None: 

462 self.clipboardContents = s 

463 

464 def set_focus(self, commander: str, widget: str) -> None: 

465 self.focusWidget = widget 

466 #@+node:ekr.20070301171901: *3* NullGui.do nothings 

467 def alert(self, c: Cmdr, message: str) -> None: 

468 pass 

469 

470 def attachLeoIcon(self, window: Any) -> None: 

471 pass 

472 

473 def destroySelf(self) -> None: 

474 pass 

475 

476 def finishCreate(self) -> None: 

477 pass 

478 

479 def getFontFromParams(self, family: str, size: str, slant: str, weight: str, defaultSize: int=12) -> Any: 

480 return g.app.config.defaultFont 

481 

482 def getIconImage(self, name: str) -> None: 

483 return None 

484 

485 def getImageImage(self, name: str) -> None: 

486 return None 

487 

488 def getTreeImage(self, c: Cmdr, path: str) -> None: 

489 return None 

490 

491 def get_window_info(self, window: str) -> Tuple[int, int, int, int]: 

492 return 600, 500, 20, 20 

493 

494 def onActivateEvent(self, *args: str, **keys: str) -> None: 

495 pass 

496 

497 def onDeactivateEvent(self, *args: str, **keys: str) -> None: 

498 pass 

499 

500 def set_top_geometry(self, w: Wrapper, h: str, x: str, y: str) -> None: 

501 pass 

502 #@+node:ekr.20070228155807: *3* NullGui.isTextWidget & isTextWrapper 

503 def isTextWidget(self, w: Wrapper) -> bool: 

504 return True # Must be True for unit tests. 

505 

506 def isTextWrapper(self, w: Wrapper) -> bool: 

507 """Return True if w is a Text widget suitable for text-oriented commands.""" 

508 return w and getattr(w, 'supportsHighLevelInterface', None) 

509 #@+node:ekr.20031218072017.2230: *3* NullGui.oops 

510 def oops(self) -> None: 

511 g.trace("NullGui", g.callers(4)) 

512 #@+node:ekr.20070301172456: *3* NullGui.panels 

513 def createComparePanel(self, c: Cmdr) -> None: 

514 """Create Compare panel.""" 

515 self.oops() 

516 

517 def createFindTab(self, c: Cmdr, parentFrame: Widget) -> None: 

518 """Create a find tab in the indicated frame.""" 

519 pass # Now always done during startup. 

520 

521 def createLeoFrame(self, c: Cmdr, title: str) -> Widget: 

522 """Create a null Leo Frame.""" 

523 gui = self 

524 self.lastFrame = leoFrame.NullFrame(c, title, gui) 

525 return self.lastFrame 

526 #@+node:ekr.20031218072017.2229: *3* NullGui.runMainLoop 

527 def runMainLoop(self) -> None: 

528 """Run the null gui's main loop.""" 

529 if self.script: 

530 frame = self.lastFrame 

531 g.app.log = frame.log 

532 self.lastFrame.c.executeScript(script=self.script) 

533 else: 

534 print('**** NullGui.runMainLoop: terminating Leo.') 

535 # Getting here will terminate Leo. 

536 #@-others 

537#@+node:ekr.20080707150137.5: ** class NullScriptingControllerClass 

538class NullScriptingControllerClass: 

539 """A default, do-nothing class to be overridden by mod_scripting or other plugins. 

540 

541 This keeps pylint happy.""" 

542 

543 def __init__(self, c: Cmdr, iconBar: Widget=None) -> None: 

544 self.c = c 

545 self.iconBar = iconBar 

546 

547 def createAllButtons(self) -> None: 

548 pass 

549#@+node:ekr.20171128093401.1: ** class StringCheckBox (leoGui.py) 

550class StringCheckBox: 

551 """Simulate a QCheckBox.""" 

552 

553 def __init__(self, name: str, label: str=None) -> None: 

554 self.label = label 

555 self.name = name 

556 self.value = True 

557 

558 def checkState(self) -> bool: 

559 return self.value 

560 

561 isChecked = checkState 

562 

563 def objectName(self) -> str: 

564 return self.name 

565 

566 def setCheckState(self, value: bool) -> None: 

567 self.value = value 

568 

569 def toggle(self) -> None: 

570 self.value = not self.value 

571#@+node:ekr.20210221130549.1: ** class StringFindTabManager (leoGui.py) (new) 

572class StringFindTabManager: 

573 """A string-based FindTabManager class for unit tests.""" 

574 #@+others 

575 #@+node:ekr.20210221130549.2: *3* sftm.ctor 

576 #@@nobeautify 

577 

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

579 """Ctor for the FindTabManager class.""" 

580 self.c = c 

581 self.entry_focus = None # Accessed directly from code(!) 

582 # Find/change text boxes... 

583 self.find_findbox = StringLineEdit('find_text') 

584 self.find_replacebox = StringLineEdit('change_text') 

585 # Check boxes... 

586 self.check_box_ignore_case = StringCheckBox('ignore_case') 

587 self.check_box_mark_changes = StringCheckBox('mark_changes') 

588 self.check_box_mark_finds = StringCheckBox('mark_finds') 

589 self.check_box_regexp = StringCheckBox('pattern_match') 

590 self.check_box_search_body = StringCheckBox('search_body') 

591 self.check_box_search_headline = StringCheckBox('search_headline') 

592 self.check_box_whole_word = StringCheckBox('whole_word') 

593 # Radio buttons... 

594 self.radio_button_entire_outline = StringRadioButton('entire_outline') 

595 self.radio_button_node_only = StringRadioButton('node_only') 

596 self.radio_button_suboutline_only = StringRadioButton('suboutline_only') 

597 self.radio_button_file_only = StringRadioButton('file_only') 

598 # Init the default values. 

599 self.init_widgets() 

600 #@+node:ekr.20210221130549.5: *3* sftm.clear_focus & init_focus & set_entry_focus 

601 def clear_focus(self) -> None: 

602 pass 

603 

604 def init_focus(self) -> None: 

605 pass 

606 

607 def set_entry_focus(self) -> None: 

608 pass 

609 #@+node:ekr.20210221130549.4: *3* sftm.get_settings 

610 #@@nobeautify 

611 

612 def get_settings(self) -> Any: 

613 """ 

614 Return a g.bunch representing all widget values. 

615 

616 Similar to LeoFind.default_settings, but only for find-tab values. 

617 """ 

618 return g.Bunch( 

619 # Find/change strings... 

620 find_text = self.find_findbox.text(), 

621 change_text = self.find_replacebox.text(), 

622 # Find options... 

623 ignore_case = self.check_box_ignore_case.isChecked(), 

624 mark_changes = self.check_box_mark_changes.isChecked(), 

625 mark_finds = self.check_box_mark_finds.isChecked(), 

626 node_only = self.radio_button_node_only.isChecked(), 

627 pattern_match = self.check_box_regexp.isChecked(), 

628 search_body = self.check_box_search_body.isChecked(), 

629 search_headline = self.check_box_search_headline.isChecked(), 

630 suboutline_only = self.radio_button_suboutline_only.isChecked(), 

631 whole_word = self.check_box_whole_word.isChecked(), 

632 ) 

633 #@+node:ekr.20210221130549.7: *3* sftm.init_widgets 

634 def init_widgets(self) -> None: 

635 """ 

636 Init widgets and ivars from c.config settings. 

637 Create callbacks that always keep the LeoFind ivars up to date. 

638 """ 

639 c, find = self.c, self.c.findCommands 

640 # Find/change text boxes. 

641 table1 = ( 

642 ('find_findbox', 'find_text', '<find pattern here>'), 

643 ('find_replacebox', 'change_text', ''), 

644 ) 

645 for widget_ivar, setting_name, default in table1: 

646 w = getattr(self, widget_ivar) 

647 s = c.config.getString(setting_name) or default 

648 w.insert(s) 

649 # Check boxes. 

650 table2 = ( 

651 ('ignore_case', 'check_box_ignore_case'), 

652 ('mark_changes', 'check_box_mark_changes'), 

653 ('mark_finds', 'check_box_mark_finds'), 

654 ('pattern_match', 'check_box_regexp'), 

655 ('search_body', 'check_box_search_body'), 

656 ('search_headline', 'check_box_search_headline'), 

657 ('whole_word', 'check_box_whole_word'), 

658 ) 

659 for setting_name, widget_ivar in table2: 

660 w = getattr(self, widget_ivar) 

661 val = c.config.getBool(setting_name, default=False) 

662 setattr(find, setting_name, val) 

663 if val != w.isChecked(): # Support leoInteg. 

664 w.toggle() 

665 # Radio buttons 

666 table3 = ( 

667 ('node_only', 'node_only', 'radio_button_node_only'), 

668 ('entire_outline', None, 'radio_button_entire_outline'), 

669 ('suboutline_only', 'suboutline_only', 'radio_button_suboutline_only'), 

670 ) 

671 for setting_name, ivar, widget_ivar in table3: 

672 w = getattr(self, widget_ivar) 

673 val = c.config.getBool(setting_name, default=False) 

674 if ivar is not None: 

675 assert hasattr(find, setting_name), setting_name 

676 setattr(find, setting_name, val) 

677 if val != w.isChecked(): 

678 w.toggle() 

679 # Ensure one radio button is set. 

680 if not find.node_only and not find.suboutline_only: 

681 w = self.radio_button_entire_outline 

682 if val != w.isChecked(): 

683 w.toggle() 

684 #@+node:ekr.20210312122351.1: *3* sftm.set_body_and_headline_checkbox 

685 def set_body_and_headline_checkbox(self) -> None: 

686 """Return the search-body and search-headline checkboxes to their defaults.""" 

687 # #1840: headline-only one-shot 

688 c = self.c 

689 find = c.findCommands 

690 if not find: 

691 return 

692 table = ( 

693 ('search_body', self.check_box_search_body), 

694 ('search_headline', self.check_box_search_headline), 

695 ) 

696 for setting_name, w in table: 

697 val = c.config.getBool(setting_name, default=False) 

698 if val != w.isChecked(): 

699 w.toggle() 

700 #@+node:ekr.20210221130549.8: *3* sftm.set_radio_button 

701 #@@nobeautify 

702 

703 def set_radio_button(self, name: str) -> None: 

704 """Set the value of the radio buttons""" 

705 d = { 

706 'node-only': self.radio_button_node_only, 

707 'entire-outline': self.radio_button_entire_outline, 

708 'suboutline-only': self.radio_button_suboutline_only, 

709 } 

710 w = d.get(name) 

711 if not w.isChecked(): 

712 w.toggle() 

713 #@+node:ekr.20210221130549.3: *3* sftm.text getters/setters 

714 def get_find_text(self) -> str: 

715 s = self.find_findbox.text() 

716 if s and s[-1] in ('\r', '\n'): 

717 s = s[:-1] 

718 return s 

719 

720 def get_change_text(self) -> str: 

721 s = self.find_replacebox.text() 

722 if s and s[-1] in ('\r', '\n'): 

723 s = s[:-1] 

724 return s 

725 

726 def set_find_text(self, s: str) -> None: 

727 w = self.find_findbox 

728 w.clear() 

729 w.insert(s) 

730 

731 def set_change_text(self, s: str) -> None: 

732 w = self.find_replacebox 

733 w.clear() 

734 w.insert(s) 

735 #@+node:ekr.20210221130549.9: *3* sftm.toggle_checkbox 

736 #@@nobeautify 

737 

738 def toggle_checkbox(self, checkbox_name: str) -> None: 

739 """Toggle the value of the checkbox whose name is given.""" 

740 d = { 

741 'ignore_case': self.check_box_ignore_case, 

742 'mark_changes': self.check_box_mark_changes, 

743 'mark_finds': self.check_box_mark_finds, 

744 'pattern_match': self.check_box_regexp, 

745 'search_body': self.check_box_search_body, 

746 'search_headline': self.check_box_search_headline, 

747 'whole_word': self.check_box_whole_word, 

748 } 

749 w = d.get(checkbox_name) 

750 w.toggle() 

751 #@-others 

752#@+node:ekr.20170613095422.1: ** class StringGui (LeoGui) 

753class StringGui(LeoGui): 

754 """ 

755 A class representing all on-screen objects using subclasses of the 

756 leoFrame.StringTextWrapper class. 

757 """ 

758 #@+others 

759 #@+node:ekr.20170613095422.7: *3* StringGui.oops 

760 def oops(self) -> None: 

761 

762 g.trace("StringGui", g.callers(4)) 

763 #@+node:ekr.20170613114120.1: *3* StringGui.runMainLoop 

764 def runMainLoop(self) -> None: 

765 self.oops() 

766 #@-others 

767#@+node:ekr.20171128093503.1: ** class StringLineEdit (leoGui) 

768class StringLineEdit: 

769 """Simulate a QLineEdit.""" 

770 

771 def __init__(self, name: str, disabled: bool=False) -> None: 

772 self.disabled = disabled 

773 self.name = name 

774 self.pos = 0 

775 self.s = '' 

776 

777 def clear(self) -> None: 

778 self.pos = 0 

779 self.s = '' 

780 

781 def insert(self, s: str) -> None: 

782 if s: 

783 i = self.pos 

784 self.s = self.s[:i] + s + self.s[i:] 

785 self.pos += len(s) 

786 

787 def objectName(self) -> str: 

788 return self.name 

789 

790 def text(self) -> str: 

791 return self.s 

792#@+node:ekr.20171128093602.1: ** class StringRadioButton (leoGui.py) 

793class StringRadioButton: 

794 """Simulate a QRadioButton.""" 

795 

796 def __init__(self, name: str, label: str=None) -> None: 

797 self.label = label 

798 self.name = name 

799 self.value = True 

800 

801 def isChecked(self) -> bool: 

802 return self.value 

803 

804 def objectName(self) -> str: 

805 return self.name 

806 

807 def toggle(self) -> None: 

808 self.value = not self.value 

809#@+node:ekr.20031218072017.3742: ** class UnitTestGui (NullGui) 

810class UnitTestGui(NullGui): 

811 """A gui class for use by unit tests.""" 

812 # Presently used only by the import/export unit tests. 

813 #@+others 

814 #@+node:ekr.20031218072017.3743: *3* UnitTestGui.__init__ 

815 def __init__(self, theDict: Dict=None) -> None: 

816 """ctor for the UnitTestGui class.""" 

817 self.oldGui = g.app.gui 

818 super().__init__("UnitTestGui") 

819 self.theDict = {} if theDict is None else theDict 

820 g.app.gui = self 

821 

822 def destroySelf(self) -> None: 

823 g.app.gui = self.oldGui 

824 #@+node:ekr.20071128094234.1: *3* UnitTestGui.createSpellTab 

825 def createSpellTab(self, c: Cmdr, spellHandler: str, tabName: str) -> None: 

826 pass # This method keeps pylint happy. 

827 #@+node:ekr.20111001155050.15484: *3* UnitTestGui.runAtIdle 

828 if 1: # Huh? 

829 

830 def runAtIdle(self, aFunc: Callable) -> None: 

831 """Run aFunc immediately for a unit test. 

832 

833 This is a kludge, but it is probably the best that can be done. 

834 """ 

835 aFunc() 

836 #@-others 

837#@-others 

838#@@language python 

839#@@tabwidth -4 

840#@@pagewidth 70 

841#@-leo