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
« 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.
8These classes hide the details of which gui is actually being used.
9Leo's core calls this class to allocate all gui objects.
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
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.
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()
106 def runAskLeoIDDialog(self) -> Any:
107 """Create and run a dialog to get g.app.LeoID."""
108 self.oops()
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()
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()
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()
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()
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()
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'
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()
191 def createComparePanel(self, c: Cmdr) -> None:
192 """Create Compare panel."""
193 self.oops()
195 def createFindTab(self, c: Cmdr, parentFrame: Widget) -> None:
196 """Create a find tab in the indicated frame."""
197 self.oops()
199 def createFontPanel(self, c: Cmdr) -> None:
200 """Create a hidden Font panel."""
201 self.oops()
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()
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()
229 def center_dialog(self, dialog: str) -> None:
230 """Center a dialog."""
231 self.oops()
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()
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:
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.
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:
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)
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)
384 def runAskLeoIDDialog(self) -> str:
385 return self.simulateDialog("leoIDDialog", None)
387 def runAskOkDialog(self, c: Cmdr, title: str, message: str=None, text: str="Ok") -> str:
388 return self.simulateDialog("okDialog", "Ok")
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')
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", '')
412 def runCompareDialog(self, c: Cmdr) -> str:
413 return self.simulateDialog("compareDialog", '')
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)
426 def runSaveFileDialog(self, c: Cmdr, title: str, filetypes: List[str], defaultextension: str) -> str:
427 return self.simulateDialog("saveFileDialog", None)
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")
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")
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
458 def getTextFromClipboard(self) -> str:
459 return self.clipboardContents
461 def replaceClipboardWith(self, s: str) -> None:
462 self.clipboardContents = s
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
470 def attachLeoIcon(self, window: Any) -> None:
471 pass
473 def destroySelf(self) -> None:
474 pass
476 def finishCreate(self) -> None:
477 pass
479 def getFontFromParams(self, family: str, size: str, slant: str, weight: str, defaultSize: int=12) -> Any:
480 return g.app.config.defaultFont
482 def getIconImage(self, name: str) -> None:
483 return None
485 def getImageImage(self, name: str) -> None:
486 return None
488 def getTreeImage(self, c: Cmdr, path: str) -> None:
489 return None
491 def get_window_info(self, window: str) -> Tuple[int, int, int, int]:
492 return 600, 500, 20, 20
494 def onActivateEvent(self, *args: str, **keys: str) -> None:
495 pass
497 def onDeactivateEvent(self, *args: str, **keys: str) -> None:
498 pass
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.
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()
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.
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.
541 This keeps pylint happy."""
543 def __init__(self, c: Cmdr, iconBar: Widget=None) -> None:
544 self.c = c
545 self.iconBar = iconBar
547 def createAllButtons(self) -> None:
548 pass
549#@+node:ekr.20171128093401.1: ** class StringCheckBox (leoGui.py)
550class StringCheckBox:
551 """Simulate a QCheckBox."""
553 def __init__(self, name: str, label: str=None) -> None:
554 self.label = label
555 self.name = name
556 self.value = True
558 def checkState(self) -> bool:
559 return self.value
561 isChecked = checkState
563 def objectName(self) -> str:
564 return self.name
566 def setCheckState(self, value: bool) -> None:
567 self.value = value
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
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
604 def init_focus(self) -> None:
605 pass
607 def set_entry_focus(self) -> None:
608 pass
609 #@+node:ekr.20210221130549.4: *3* sftm.get_settings
610 #@@nobeautify
612 def get_settings(self) -> Any:
613 """
614 Return a g.bunch representing all widget values.
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
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
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
726 def set_find_text(self, s: str) -> None:
727 w = self.find_findbox
728 w.clear()
729 w.insert(s)
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
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:
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."""
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 = ''
777 def clear(self) -> None:
778 self.pos = 0
779 self.s = ''
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)
787 def objectName(self) -> str:
788 return self.name
790 def text(self) -> str:
791 return self.s
792#@+node:ekr.20171128093602.1: ** class StringRadioButton (leoGui.py)
793class StringRadioButton:
794 """Simulate a QRadioButton."""
796 def __init__(self, name: str, label: str=None) -> None:
797 self.label = label
798 self.name = name
799 self.value = True
801 def isChecked(self) -> bool:
802 return self.value
804 def objectName(self) -> str:
805 return self.name
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
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?
830 def runAtIdle(self, aFunc: Callable) -> None:
831 """Run aFunc immediately for a unit test.
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