Source code for utils_tk

print('Loading tk functions...')

from omfit_classes.utils_base import *
from omfit_classes.utils_base import _streams

from utils_widgets import *
from utils_widgets import _defaultFont

import tkinter as tk
from tkinter import ttk
import tkinter.font as tkFont
import tkinter.filedialog as tkFileDialog

from idlelib.redirector import WidgetRedirector
from idlelib.percolator import Percolator
from idlelib.colorizer import ColorDelegator
from idlelib.search import SearchDialog


[docs]def get_entry_fieldbackground(): fbg = ttk.Style().lookup('TEntry', 'fieldbackground') if fbg == '': fbg = 'white' return fbg
# ---------------- # ttk GUI subclasses # --------------- _Combobox = ttk.Combobox
[docs]class Combobox(_Combobox): """ Monkey patch of ttk combobox to dynamically update its dropdown menu. The issue is ttk uses a tk Listbox that doesn't conform to the ttk theme for its dropdown (not cool ttk). This patch is modified from https://stackoverflow.com/questions/43086378/how-to-modify-ttk-combobox-fonts """ def __init__(self, *args, **kwargs): # initialisation of the combobox entry _Combobox.__init__(self, *args, **kwargs) # "initialisation" of the combobox popdown self._handle_popdown_font() def _handle_popdown_font(self): """Handle popdown font Note: https://github.com/nomad-software/tcltk/blob/master/dist/library/ttk/combobox.tcl#L270 """ try: # grab (create a new one or get existing) popdown popdown = self.tk.eval('ttk::combobox::PopdownWindow %s' % self) # configure popdown font self.tk.call('%s.f.l' % popdown, 'configure', '-font', self['font']) except tk.TclError as _err: # fails for old iris no_conda python, needed for old xarray unpickling pass
[docs] def configure(self, cnf=None, **kw): """Configure resources of a widget. Overridden! The values for resources are specified as keyword arguments. To get an overview about the allowed keyword arguments call the method keys. """ # default configure behavior result = self._configure('configure', cnf, kw) # if font was configured - configure font for popdown as well if 'font' in kw or (cnf is not None and 'font' in cnf): self._handle_popdown_font() return result
# keep overridden shortcut config = configure
# ttk.Combobox = Combobox # ---------------- # Tk GUI subclasses # Note: all these classes are added to the `tk` Python module # --------------- # Tk supports two kinds of selection: CLIPBOARD and PRIMARY. # Both selection buffers are capable of handling arbitrary data, but they default to simple ASCII text strings. # When making a selection, standard Tk widgets (such as Text and Entry) select PRIMARY and highlight the selection. # The widgets copy the selection to CLIPBOARD as well. This means that pasting text in Tk works in either of two ways: # # * Using the middle button, which copies the PRIMARY selection # # * Using the keyboard character `Control-v`, which copies the CLIPBOARD selection # # Tk widgets bind <<Copy>>, <<Cut>>, and <<Paste>> virtual events to class methods that manipulate the CLIPBOARD selection. # The MainWindow generates virtual <<Copy>>, <<Cut>>, and <<Paste>> events when it sees the characters Control-c, Control-x, and Control-v, respectively. # Venerable Unix applications tend to use PRIMARY, where you select text with mouse button 1 and paste with mouse button 3. def _clipboard_get(self, selection=None, type=None): """ :param selection: name of the selection (default 'PRIMARY', alternatively 'CLIPBOARD') :param type: type of the selection (default 'STRING', alternatively 'UTF8-SRTRING', 'FILE_NAME') """ if type is None: type = os.environ.get('OMFIT_CLIPBOARD_TYPE', 'STRING') if selection is None: selection = os.environ.get('OMFIT_CLIPBOARD_SELECTION', 'PRIMARY') try: return self.clipboard_get(selection=selection, type=type) except tk.TclError: try: return self.clipboard_get(type=type) except tk.TclError: return self.clipboard_get()
[docs]def omfit_sel_handle(offset, length, selection=None, type=None): """ This function must return the contents of the selection. The function will be called with the arguments OFFSET and LENGTH which allows the chunking of very long selections. The following keyword parameters can be provided: selection - name of the selection (default PRIMARY), type - type of the selection (e.g. STRING, FILE_NAME). :param offset: allows the chunking of very long selections :param length: allows the chunking of very long selections :param selection: name of the selection (default set by $OMFIT_CLIPBOARD_SELECTION) :param type: type of the selection (default set by $OMFIT_CLIPBOARD_TYPE) :return: clipboard selection """ return _clipboard_get(OMFITaux['rootGUI'], selection, type)[int(offset) : int(offset) + int(length)]
tk.omfit_sel_handle = omfit_sel_handle def _paste(self): # get the clipboard data, and replace all newlines with the literal string "\n"; also handle carriage returns. try: clipboard = _clipboard_get(self) except tk.TclError: return clipboard = clipboard.replace('\r\n', '\n') clipboard = clipboard.replace('\r', '\n') self.clipboard_clear() self.clipboard_append(clipboard) # delete the selected text, if any try: start = self.index("sel.first") end = self.index("sel.last") self.delete(start, end) except tk.TclError: # nothing was selected, so paste doesn't need # to delete anything pass _Toplevel = tk.Toplevel
[docs]class Toplevel(_Toplevel): """ Patch tk.Toplevel to get windows with ttk themed backgrounds. """ def __init__(self, *args, **kw): kw.setdefault('background', kw.pop('bg', ttk.Style().lookup('TFrame', 'background'))) kw.setdefault('highlightbackground', ttk.Style().lookup('TFrame', 'background')) _Toplevel.__init__(self, *args, **kw)
tk.Toplevel = Toplevel _Canvas = tk.Canvas
[docs]class Canvas(_Canvas): """ Patch tk.Canvas to get windows with ttk themed backgrounds. """ def __init__(self, *args, **kw): kw.setdefault('background', kw.pop('bg', ttk.Style().lookup('TFrame', 'background'))) kw.setdefault('highlightbackground', ttk.Style().lookup('TFrame', 'background')) _Canvas.__init__(self, *args, **kw)
tk.Canvas = Canvas _Menu = tk.Menu tk.Menu = Menu _Listbox = tk.Listbox
[docs]class Listbox(_Listbox): """ Patch tk.Listbox to get windows with ttk themed backgrounds. """ def __init__(self, *args, **kw): kw.setdefault('background', kw.pop('bg', get_entry_fieldbackground())) fg = kw.pop('fg', ttk.Style().lookup('TEntry', 'foreground')) if fg: # robust to no specific color kw.setdefault('foreground', fg) kw.setdefault('font', OMFITfont('normal', 0)) _Listbox.__init__(self, *args, **kw)
tk.Listbox = Listbox _Entry = tk.Entry
[docs]class Entry(_Entry): def __init__(self, *args, **kw): _Entry.__init__(self, *args, **kw) self.bind(f'<{ctrlCmd()}-v>', lambda *args, **kw: _paste(self)) self.bind(f'<{ctrlCmd()}-A>', self.select_all) self.changes = [""] self.steps = int() self.bind(f'<{ctrlCmd()}-z>', self.undo) self.bind(f'<{ctrlCmd()}-Z>', self.redo) self.bind('<Key>', self.add_changes)
[docs] def select_all(self, event): '''Set selection on the whole text''' self.selection_range(0, tk.END)
[docs] def undo(self, event=None): if self.steps != 0: self.steps -= 1 self.delete(0, tk.END) self.insert(tk.END, self.changes[self.steps])
[docs] def redo(self, event=None): if self.steps < len(self.changes): self.delete(0, tk.END) self.insert(tk.END, self.changes[self.steps]) self.steps += 1
[docs] def add_changes(self, event=None): if self.get() != self.changes[-1]: self.changes.append(self.get()) self.steps += 1
tk.Entry = Entry _Text = tk.Text
[docs]class Text(_Text): def __init__(self, *args, **kw): kw.setdefault('undo', tk.TRUE) kw.setdefault('maxundo', -1) kw.setdefault('background', kw.pop('bg', get_entry_fieldbackground())) fg = kw.pop('fg', ttk.Style().lookup('TEntry', 'foreground')) if fg: # robust to no specific color kw.setdefault('foreground', fg) kw.setdefault('highlightbackground', ttk.Style().lookup('TFrame', 'background')) kw.setdefault('font', OMFITfont('normal', 0)) _Text.__init__(self, *args, **kw) self.bind(f'<{ctrlCmd()}-v>', lambda *args, **kw: _paste(self)) self.bind(f'<{ctrlCmd()}-A>', self.select_all)
[docs] def select_all(self, event): self.tag_add(tk.SEL, "1.0", tk.END) # self.mark_set(tk.INSERT, "1.0") # self.see(tk.INSERT) return 'break'
tk.Text = Text class _OneLineText(tk.Text): def __init__(self, master, **kw): percolator = kw.pop('percolator', False) kw.setdefault('background', kw.pop('bg', get_entry_fieldbackground())) kw.setdefault('highlightbackground', ttk.Style().lookup('TFrame', 'background')) tk.Text.__init__(self, master, **kw) self.config(height=1, undo=tk.TRUE, maxundo=-1, wrap='none') self.config(font=OMFITfont()) tmp = [item for item in self.bindtags()] tmp.insert(2, 'post-class-bindings' + str(id(self))) tmp.insert(0, 'pre-class-bindings' + str(id(self))) self.bindtags(tuple(tmp)) self.denyEnter() # highlight self.prc = Percolator(self) # CONDA aqua variant of tk on mac has 100% cpu when using the ColorDelegator here # Use `conda install -c smithsp tk=8.6.10=h1de35cc_2` if you encounter this issue self.flt = ColorDelegator() if percolator: self.set_highlight(True) def set_highlight(self, set=True): try: if set: self.prc.insertfilter(self.flt) else: self.prc.removefilter(self.flt) except Exception: pass def allowEnter(self): self.unbind_class('pre-class-bindings' + str(id(self)), "<Tab>") self.unbind_class('pre-class-bindings' + str(id(self)), "<Shift-Tab>") self.unbind_class('pre-class-bindings' + str(id(self)), "<ISO_Left_Tab>") self.unbind_class('post-class-bindings' + str(id(self)), "<Key>") def denyEnter(self): self.bind_class('pre-class-bindings' + str(id(self)), '<Tab>', lambda event=None: self.__denyTab__(None, action='next')) self.bind_class('pre-class-bindings' + str(id(self)), '<Shift-Tab>', lambda event=None: self.__denyTab__(None, action='prev')) try: self.bind_class( 'pre-class-bindings' + str(id(self)), '<ISO_Left_Tab>', lambda event=None: self.__denyTab__(None, action='prev') ) except tk.TclError: pass self.bind_class('post-class-bindings' + str(id(self)), '<Key>', self.__denyEnter__) def __denyTab__(self, event=None, action='next'): try: if action == 'next': self.tk_focusNext().focus() else: self.tk_focusPrev().focus() return 'break' except Exception: pass def __denyEnter__(self, event=None): if self.get("%s-1c" % tk.INSERT, "%s" % tk.INSERT) == '\n': self.delete("%s-1c" % tk.INSERT, "%s" % tk.INSERT) def set(self, string): self.delete(1.0, tk.END) return super().insert(1.0, str(string)) def get(self, start=None, end=None): if start is None: start = 1.0 if end is None: end = 'end -1 chars' return str(super().get(start, end)) def icursor(self, index): self.see(index) tk.OneLineText = _OneLineText class _ReadOnlyEntry(tk.Entry): """ Adapted from http://stackoverflow.com/questions/3842155/is-there-a-way-to-make-the-tkinter-text-widget-read-only """ def __init__(self, *args, **kw): initial_text = kw.pop('initial_text', '') tk.Entry.__init__(self, *args, **kw) self.insert(0, initial_text) self.redirector = WidgetRedirector(self) self.insert = self.redirector.register("insert", lambda *args, **kw: "break") self.delete = self.redirector.register("delete", lambda *args, **kw: "break") tk.ReadOnlyEntry = _ReadOnlyEntry class _ReadOnlyText(tk.Text): """ From http://stackoverflow.com/questions/3842155/is-there-a-way-to-make-the-tkinter-text-widget-read-only (originally from http://tkinter.unpythonic.net/wiki/ReadOnlyText) """ def __init__(self, *args, **kw): initial_text = kw.pop('initial_text', '') tk.Text.__init__(self, *args, **kw) self.insert('1.0', initial_text) self.redirector = WidgetRedirector(self) self._ReadOnlyInsert = self.insert self._ReadOnlyDelete = self.delete self.insert = self.redirector.register("insert", lambda *args, **kw: "break") self.delete = self.redirector.register("delete", lambda *args, **kw: "break") def set(self, string): self.insert = self.redirector.register("insert", self._ReadOnlyInsert) self.delete = self.redirector.register("delete", self._ReadOnlyDelete) try: self.delete(1.0, tk.END) return self.insert(1.0, str(string)) finally: self.insert = self.redirector.register("insert", lambda *args, **kw: "break") self.delete = self.redirector.register("delete", lambda *args, **kw: "break") tk.ReadOnlyText = _ReadOnlyText class _ScrolledText(tk.Text): """ >> top=tk.Tk() >> tmp=_ScrolledText(top) >> tmp.pack() """ def __init__(self, master=None, wrapButton='char', **kw): percolator = kw.pop('percolator', False) self.frame = ttk.Frame(master) self.frame.grid_rowconfigure(0, weight=1) self.frame.grid_columnconfigure(0, weight=1) self.vbar = ttk.Scrollbar(self.frame) self.hbar = ttk.Scrollbar(self.frame, orient=tk.HORIZONTAL) kw.update({'yscrollcommand': self.vbar.set}) kw.update({'xscrollcommand': self.hbar.set}) kw.setdefault('background', kw.pop('bg', get_entry_fieldbackground())) kw.setdefault('highlightbackground', ttk.Style().lookup('TFrame', 'background')) tk.Text.__init__(self, self.frame, **kw) self.vbar['command'] = self.yview self.hbar['command'] = self.xview # wrapping if wrapButton: def toggleWrap(event=None): if self.configure('wrap')[4].lower() != 'none': self.configure(wrap='none') else: self.configure(wrap=wrapButton) self.wrap = ttk.Button(self.frame, text='w', command=toggleWrap, style='flat.TButton') self.wrap.grid(row=1, column=1, sticky='nsew', rowspan=2) self.vbar.grid(row=0, column=1, sticky='ns') self.hbar.grid(row=2, column=0, sticky='ew') self.grid(row=0, column=0, sticky='nswe', rowspan=2) self.configure(wrap=kw.get('wrap', 'none')) # Copy geometry methods of self.frame without overriding Text methods -- hack! text_meths = list(vars(tk.Text).keys()) methods = list(vars(tk.Pack).keys()) + list(vars(tk.Grid).keys()) + list(vars(tk.Place).keys()) methods = set(methods).difference(text_meths) for m in methods: if m[0] != '_' and m != 'config' and m != 'configure': setattr(self, m, getattr(self.frame, m)) # highlight self.prc = Percolator(self) # CONDA aqua variant of tk on mac has 100% cpu when using the ColorDelegator here # Use `conda install -c smithsp tk=8.6.10=h1de35cc_2` if you encounter this issue self.flt = ColorDelegator() if percolator: self.set_highlight(True) self.bind(f'<{ctrlCmd()}-c>', self._selown) self.bind(f'<{ctrlCmd()}-x>', self._selown) self.bind(f'<{ctrlCmd()}-f>', lambda event=None: self.search()) self.bind('<F3>', lambda event=None: self.search_again_forward()) self.bind('<Shift-F3>', lambda event=None: self.search_again_backward()) def __str__(self): return str(self.frame) def set_highlight(self, set=True): try: if set: self.prc.insertfilter(self.flt) else: self.prc.removefilter(self.flt) except Exception: pass def configure(self, *args, **kw): if 'wrap' in kw: if kw['wrap'].lower() != 'none': self.hbar.grid_remove() else: self.hbar.grid() return super().configure(*args, **kw) def clear(self, what=None): if what is not None: while len(self.tag_ranges(what)): self.delete(self.tag_ranges(what)[-2], self.tag_ranges(what)[-1]) else: self.delete('0.0', 'end') self.update_idletasks() def set(self, string): self.delete(1.0, tk.END) return super().insert(1.0, string) def get(self, start=None, end=None): if start is None: start = 1.0 if end is None: end = 'end -1 chars' return super().get(start, end) def icursor(self, index): self.see(index) def _selown(self, event=None): self.selection_own() def search(self): self.tag_add(tk.SEL, "1.0", tk.END) from idlelib import searchengine engine = searchengine.get(self._root()) if not hasattr(engine, "_searchdialog"): engine._searchdialog = SearchDialog(self._root(), engine) s = engine._searchdialog try: s.open(self) s.top.grab_release() except tk.TclError: pass s.top.wm_transient(self._root()) self.tag_remove(tk.SEL, "1.0", tk.END) return 'break' def search_again_forward(self): engine = SearchDialog.SearchEngine.get(self._root()) engine.backvar.set(False) SearchDialog.find_again(self) return 'break' def search_again_backward(self): engine = SearchDialog.SearchEngine.get(self._root()) engine.backvar.set(True) SearchDialog.find_again(self) return 'break' tk.ScrolledText = _ScrolledText class _ScrolledReadOnlyText(_ReadOnlyText): def __init__(self, master=None, initial_text='', **kw): self.frame = ttk.Frame(master) self.frame.grid_rowconfigure(0, weight=1) self.frame.grid_columnconfigure(0, weight=1) self.vbar = ttk.Scrollbar(self.frame) self.vbar.grid(row=0, column=1, sticky='ns') self.hbar = ttk.Scrollbar(self.frame, orient=tk.HORIZONTAL) self.hbar.grid(row=1, column=0, sticky='ew') kw.update({'yscrollcommand': self.vbar.set}) kw.update({'xscrollcommand': self.hbar.set}) _ReadOnlyText.__init__(self, self.frame, initial_text=initial_text, **kw) self.grid(row=0, column=0, sticky='nswe') self.vbar['command'] = self.yview self.hbar['command'] = self.xview # Copy geometry methods of self.frame without overriding Text # methods -- hack! text_meths = list(vars(_ReadOnlyText).keys()) methods = list(vars(tk.Pack).keys()) + list(vars(tk.Grid).keys()) + list(vars(tk.Place).keys()) methods = set(methods).difference(text_meths) for m in methods: if m[0] != '_' and m != 'config' and m != 'configure': setattr(self, m, getattr(self.frame, m)) self.configure(wrap=self.configure('wrap')[4]) tk.ScrolledReadOnlyText = _ScrolledReadOnlyText class _Treeview(ttk.Treeview): """ Subclass of ttk.Treeview which is used in OMFIT provides some keybindings and defines tags (for colors and actions) for different OMFITobjects """ def __init__(self, master, scrollup=None, **kw): self.frame = ttk.Frame(master) ttk.Treeview.__init__(self, self.frame, **kw) self.frame.bind_class("Treeview", "<space>", lambda event: None) self.frame.bind_class("Treeview", "<Return>", lambda event: None) self.frame.bind_class("Treeview", f'<{ctrlCmd()}-Left>', lambda event: None) self.frame.bind_class("Treeview", f'<{ctrlCmd()}-Right>', lambda event: None) self.frame.bind_class("Treeview", f'<{ctrlCmd()}-Up>', lambda event: None) self.frame.bind_class("Treeview", f'<{ctrlCmd()}-Down>', lambda event: None) self.frame.bind_class("Treeview", "<Prior>", lambda event: None) self.frame.bind_class("Treeview", "<Next>", lambda event: None) self.frame.bind_class("Treeview", "<Home>", lambda event: None) self.frame.bind_class("Treeview", "<End>", lambda event: None) self.frame.bind_class(f'<{ctrlCmd()}-Escape>', lambda event: None) self.frame.bind_class(f'<{ctrlCmd()}-Return>', lambda event: None) self.frame.bind_class('<Shift-Return>', lambda event: None) self.frame.bind_class("Treeview", "<Double-1>", lambda event: None) self.frame.bind_class("Treeview", "<Double-2>", lambda event: None) self.frame.bind_class("Treeview", "<Double-3>", lambda event: None) self.treeViewSelection = () self.bind("<<TreeviewSelect>>", self.viewSelectionChange) tmp = [item for item in self.bindtags()] tmp.insert(2, 'post-class-bindings') tmp.insert(0, 'pre-class-bindings') self.bindtags(tuple(tmp)) self.hScroll = ttk.Scrollbar(self.frame, orient=tk.HORIZONTAL) self.vScroll = ttk.Scrollbar(self.frame, orient=tk.VERTICAL) self.frame.grid_rowconfigure(0, weight=1) self.frame.grid_columnconfigure(1, weight=1) self.vScroll.grid(row=0, column=0, sticky='ns') self.grid(row=0, column=1, sticky='nswe') self.hScroll.grid(row=1, column=1, sticky='we') self.scrollup = scrollup self.configure(xscrollcommand=self.hScroll.set, yscrollcommand=self._scrollup) self.vScroll.config(command=self.yview) self.hScroll.config(command=self.xview) self.single_tags = [] self.tags() def viewSelectionChange(self, event=None): for node in self.treeViewSelection: if self.exists(node): priorTags = self.item(node)["tags"] if len(priorTags) and priorTags[-1] == 'selected': self.item(node, tags=tuple(priorTags[:-1])) self.treeViewSelection = self.selection() for node in self.treeViewSelection: priorTags = self.item(node)["tags"] if priorTags: self.item(node, tags=tuple(list(priorTags) + ['selected'])) def _scrollup(self, *args, **kw): if self.scrollup is not None: self.scrollup(self) self.vScroll.set(*args, **kw) def tags(self): # deduce if the theme is dark fg = ttk.Style().lookup('.', 'foreground') if not len(fg): fg = 'BLACK' dark_theme = False if fg.startswith('#'): if (eval('0x' + fg[1:3]) / 255.0 + eval('0x' + fg[3:5]) / 255.0 + eval('0x' + fg[5:7]) / 255.0) / 3.0 > 0.5: dark_theme = True elif fg.lower() in list(map(lambda x: str(x).lower(), ['white', 'systemModelessDialogActiveText'])): dark_theme = True printd('Tree detected ' + ['light', 'dark'][dark_theme] + ' theme (fg=%s)' % fg) self.single_tags[:] = [] # note to self: tags that appear first win over tags appearing later self.tag_configure('selected', background='#4B6886', foreground='white') self.tag_configure('other', background='PaleGreen2') self.tag_configure('OMFITmodule', background='gray25', foreground='white') self.tag_configure('MainSettings', background='gray40', foreground='white') self.tag_configure('OMFITtmp', background='gray40', foreground='white') self.tag_configure('OMFITnamespace', background='gray40', foreground='white') self.tag_configure('shotBookmarks', background='gray40', foreground='white') self.tag_configure('queryMatchFileContent', background='DarkOliveGreen3') self.tag_configure('queryMatchContent', background='DarkOliveGreen1') self.tag_configure('queryMatch', background='DarkOliveGreen2') self.tag_configure('modifyOriginal_readOnly', background='#ffb295', foreground='grey20') self.tag_configure('readOnly', background='#ffea87', foreground='grey20') self.tag_configure('modifyOriginal', background='#fcffb7', foreground='grey20') self.tag_configure('dynaLoad', font=OMFITfont(slant='italic')) self.tag_configure('MDSactive', foreground='indianRed1') if dark_theme: self.tag_configure('OMFITtree', foreground='dodgerblue') self.tag_configure('OMFITcollection', foreground='dodgerblue') else: self.tag_configure('OMFITtree', foreground='mediumblue') self.tag_configure('OMFITcollection', foreground='mediumblue', background='azure') self.tag_configure('OMFITgeqdsk', foreground='darkorange') self.tag_configure('OMFITaeqdsk', foreground='darkorange') self.tag_configure('OMFITmeqdsk', foreground='darkorange') self.tag_configure('OMFITseqdsk', foreground='darkorange') if dark_theme: self.tag_configure('OMFITsettings', foreground='maroon3') self.tag_configure('OMFITnamelist', foreground='maroon3') self.tag_configure('NamelistFile', foreground='maroon3') self.tag_configure('NamelistName', foreground='maroon1') self.tag_configure('SettingsName', foreground='maroon1') else: self.tag_configure('OMFITsettings', foreground='maroon4') self.tag_configure('OMFITnamelist', foreground='maroon4') self.tag_configure('NamelistFile', foreground='maroon4') self.tag_configure('NamelistName', foreground='maroon2') self.tag_configure('SettingsName', foreground='maroon2') self.tag_configure('OMFITnc', foreground='darkgreen') self.tag_configure('OMFITncDataset', foreground='darkgreen') self.tag_configure('OMFITGPECnc', foreground='chartreuse2') self.tag_configure('OMFITGPECbin', foreground='chartreuse3') self.tag_configure('OMFITGPECascii', foreground='chartreuse4') self.tag_configure('list', foreground='forestgreen') self.tag_configure('tuple', foreground='limegreen') self.tag_configure('ndarray', foreground='chartreuse4') self.tag_configure('DataArray', foreground='DarkOliveGreen4') self.tag_configure('Dataset', foreground='dark green') self.tag_configure('OMFITdataset', foreground='dark green') self.tag_configure('OMFITncDataset', foreground='dark green') self.tag_configure('OMFITprofiles', foreground='dark green') self.tag_configure('DataFrame', foreground='dark green') self.tag_configure('OMFITlazyLoad', foreground='gray30') self.tag_configure('Series', foreground='forestgreen') self.tag_configure('int', foreground='gold4') self.tag_configure('int32', foreground='gold4') self.tag_configure('bool', foreground='cyan4') self.tag_configure('float', foreground='red2') self.tag_configure('float32', foreground='red2') self.tag_configure('float64', foreground='red2') self.tag_configure('complex', foreground='SlateBlue4') self.tag_configure('instancemethod', foreground='sienna4') self.tag_configure('method', foreground='sienna4') self.tag_configure('function', foreground='sienna4') self.tag_configure('builtin_function_or_method', foreground='sienna4') self.tag_configure('method-wrapper', foreground='sienna4') if dark_theme: self.tag_configure('str', foreground='deep sky blue') else: self.tag_configure('str', foreground='dodgerblue') self.tag_configure('OMFITini', foreground='dark slate blue') self.tag_configure('OMFITerror', background='indianRed1') self.tag_configure('OMFITexpression', font=OMFITfont(weight='bold')) self.tag_configure('OMFITiterableExpression', font=OMFITfont(weight='bold')) from extras.graphics.colors import tk_colors for k in tk_colors: self.tag_configure('FG_' + re.sub(' ', '_', k), foreground=k) self.tag_configure('BG_' + re.sub(' ', '_', k), background=k) self.tag_configure('FG_', foreground='black') self.tag_configure('BG_', background='white') def force_selection(self, event=None): # focus and selection are two different things # focus is what makes things happen, whereas selection is the highligting # the purpose of force_selection is to make the two coincide if event is not None: if isinstance(event, str): focus = event else: focus = self.identify_row(event.y) else: focus = self.focus() # this logic is here because different versions of TK require different string escaping # and here we try to automatically detect which one is which try: self.selection_set(tkStringEncode(focus)) except tk.TclError as _excp: try: os.environ['OMFIT_ESCAPE_TK_SPACES'] = str(abs(int(os.environ.get('OMFIT_ESCAPE_TK_SPACES', '1')) - 1)) self.selection_set(tkStringEncode(focus)) except tk.TclError: os.environ['OMFIT_ESCAPE_TK_SPACES'] = str(abs(int(os.environ.get('OMFIT_ESCAPE_TK_SPACES', '1')) - 1)) raise _excp if not len(focus): self.selection_clear() self.focus(focus) self.see(focus) self.update_idletasks() return focus tk.Treeview = _Treeview _last_email_to = {} class _email_widget(tk.Toplevel): r""" A widget for sending an email. :param parent: parent tkInter widget :param fromm: email of the submitter :param to: email of the receiver (string of comma separated addresses) :param subject: subject of the email :param message: email message :param attachments: list of path to files :param prompt: label to be displayed :param lock_from: do not allow editing of from field :param lock_to: do not allow editing of to field :param lock_subject: do not allow editing of subject field :param title: title to display in the email window :param use_last_email_to: re-use the same email addresses as the last email that was sent :param quiet: print confirmation message on send :param \**kw: keywords passed to tkInter frame containing this widget Example usage from within OMFIT: eml = tk.email_widget(parent=OMFITaux['rootGUI']) eml.wait_window(eml) """ def __init__( self, parent, fromm='', to='', cc='', subject='', message='', attachments=[], prompt=None, lock_from=False, lock_to=False, lock_cc=False, lock_subject=False, title='Send email', use_last_email_to=False, quiet=False, **kw, ): tk.Toplevel.__init__(self, parent, **kw) self.withdraw() self.transient(parent) self.wm_title(title) self.use_last_email_to = use_last_email_to self.quiet = quiet self.sent = False # handle Nones if not isinstance(fromm, str): fromm = '' if isinstance(to, list): to = ','.join(map(str, to)) if not isinstance(to, str): to = '' if not isinstance(cc, str): cc = '' if not isinstance(subject, str): subject = '' if not isinstance(message, str): message = '' if not isinstance(attachments, list): attachments = list(map(str, list(attachments))) # re-use last to addresses if use_last_email_to and use_last_email_to in _last_email_to: to = _last_email_to[use_last_email_to] # from row = 0 ttk.Label(self, text='From:').grid(row=row, column=0, sticky=tk.W) if lock_from: _from = _ReadOnlyEntry(self, initial_text=fromm) else: _from = tk.Entry(self) _from.insert(0, fromm) _from.grid(row=row, column=1, sticky=tk.E + tk.W) self._from = _from # to row += 1 ttk.Label(self, text='To:').grid(row=row, sticky=tk.W) if isinstance(to, (list, tuple)): to = ','.join(to) if lock_to: _to = _ReadOnlyEntry(self, initial_text=to) else: _to = tk.Entry(self) _to.insert(0, to) _to.grid(row=row, column=1, sticky=tk.E + tk.W) self._to = _to # cc row += 1 ttk.Label(self, text='CC:').grid(row=row, sticky=tk.W) if isinstance(cc, (list, tuple)): cc = ','.join(cc) if lock_cc: _cc = _ReadOnlyEntry(self, initial_text=cc) else: _cc = tk.Entry(self) _cc.insert(0, cc) _cc.grid(row=row, column=1, sticky=tk.E + tk.W) self._cc = _cc # subject row += 1 ttk.Label(self, text='Subject:').grid(row=row, sticky=tk.W) if lock_subject: _subject = _ReadOnlyEntry(self, initial_text=subject) else: _subject = tk.Entry(self) _subject.insert(0, subject) _subject.grid(row=row, column=1, sticky=tk.E + tk.W) self._subject = _subject # attachments self._attachments = [] for k, attachment in enumerate(attachments): if not os.path.exists(attachment): printe(attachment + ' does not exist') continue row += 1 ttk.Label(self, text='Attachment [%d]:' % (k + 1)).grid(row=row, sticky=tk.W) _attachment = _ReadOnlyEntry(self, initial_text=os.path.split(attachment)[1]) _attachment.grid(row=row, column=1, sticky=tk.E + tk.W) self._attachments.append(attachment) # send button bt = ttk.Button(self, text='Send email', command=self.send_email) bt.grid(row=0, column=2, rowspan=row + 1, sticky=tk.E + tk.W + tk.N + tk.S) # message row += 2 if prompt: ttk.Label(self, text=prompt, justify=tk.LEFT).grid(row=row, columnspan=2, sticky=tk.W) st = _ScrolledText(self, wrap='word') st.insert(1.0, message) row += 1 st.grid(row=row, columnspan=3, sticky=tk.E + tk.W + tk.N + tk.S) self.st = st self.grid_columnconfigure(1, weight=1) tk_center(self, parent) self.deiconify() self.bind('<Escape>', lambda event=None: self.destroy()) def send_email(self): from utils import send_email if not self.validate_inputs(): return send_email( to=encode_ascii_ignore(self._to.get()), cc=encode_ascii_ignore(self._cc.get()), fromm=encode_ascii_ignore(self._from.get()), subject=encode_ascii_ignore(self._subject.get()), message=encode_ascii_ignore(self.st.get()), attachments=self._attachments, ) self.sent = True if not self.quiet: printi('Email sent to: ' + encode_ascii_ignore(self._to.get())) # store what emails were used if self.use_last_email_to: _last_email_to[self.use_last_email_to] = encode_ascii_ignore(self._to.get()) self.destroy() def validate_inputs(self): _from = self._from.get().strip() _to = self._to.get().strip() _cc = self._cc.get().strip() _subject = self._subject.get().strip() _message = self.st.get('1.0', tk.END).strip() valid = True for field in ['From', 'To', 'Subject', 'Message']: if not eval('_' + field.lower()): dialog( title='Invalid `' + field + '` Field', message='The `' + field + '` field cannot be blank', icon='error', answers=['Ok'] ) valid = False break for field in ['From', 'To', 'CC']: if eval('_' + field.lower()) and not is_email(eval('_' + field.lower())): dialog( title='Invalid `' + field + '` Field', message='The `' + field + '` field must be a valid email address', icon='error', answers=['Ok'], ) valid = False break return valid tk.email_widget = _email_widget class _ConsoleTextGUI(_ScrolledText): '''A Tkinter Text widget that provides a scrolling display of console stderr and stdout.''' class Redirector(object): '''A class for redirecting stdout and stderr to this Text widget''' def __init__(self, text_area, tag='STDOUT'): self.text_area = text_area self.tag = tag def write(self, string): self.text_area.write(string, self.tag) def __getattr__(self, attr): return getattr(self.text_area, attr) def __init__( self, parent=None, name='OMFIT command box', cnf={}, mirror=False, OMFITcwd=None, OMFITpythonTask=None, OMFITpythonGUI=None, OMFITx=None, **kw, ): '''See the __init__ for Tkinter.Text for most of this stuff.''' _ScrolledText.__init__(self, parent, wrapButton=False) self.config(undo=tk.TRUE, maxundo=-1) self.config(font=OMFITfont(_defaultFont.get('weight2', ''), _defaultFont.get('size2', 0), 'Courier')) self.config(*cnf, **kw) self.config(state=tk.NORMAL) self.mirror = mirror # store these infos, necessary for execution self.OMFITcwd = OMFITcwd self.OMFITpythonTask = OMFITpythonTask self.OMFITpythonGUI = OMFITpythonGUI self.OMFITx = OMFITx # execution namespace self.namespace = None self.name = name # behaviour self.started = False self.follow = True self.show = True self.clearTextOnExecution = False # buffered writing self.buffer = '' self.last_tag = None self.write_alarm = 0 self.lag = 10 # ms time self.block = 100 # ms time self.time_last_write = 0 # tags & streams for k in _streams.tags: self.tag_configure(k, foreground=_streams.tags[k][0]) # history self.history = [] self.histPtr = -1 # override default bindings for k in [f'<{ctrlCmd()}-Return>', '<Tab>', '<Shift-Tab>', '<ISO_Left_Tab>', f'<{ctrlCmd()}-f>']: try: self.bind_class('Text', k, 'break') except tk.TclError: pass self.q = streams_q # bindings self.bind('<<retrigger_tab_popup>>', self._process_tab) def isatty(self): return False def execute(self, event=None, f9f=False): if not f9f: tmp = self.get('1.0', 'end') else: try: tmp = self.selection_get() except Exception: tmp = self.get("insert linestart", "insert lineend") # find leftmost significant character exe = tmp.split('\n') for k, line in enumerate(exe): chtmp = len(re.sub(r'^(\s*).*', r'\1', line)) if k == 0: spaces = chtmp if len(line) - chtmp: spaces = min(spaces, chtmp) # remove spaces from all other lines exe = [line[spaces:] for line in exe] exe = '\n'.join(exe) if not f9f: if not len(self.history) or tmp != self.history[-1]: self.history.append(tmp) self.histPtr = len(self.history) if self.clearTextOnExecution: self.clear() def GlobLoc_tk(): filename = self.OMFITcwd + os.sep + self.name with open(filename, 'wb') as f: f.write(exe.encode('utf-8')) # Automatically determine if this is a GUI script # by checking if OMFITx. functions relevant to GUI # are used in it. tmp = '\n'.join([line.split('#')[0] for line in exe.split('\n')]) match_GUI = False for item in list(map(lambda x: x.__name__, OMFITaux['OMFITxGUI_functions'])): if 'OMFITx.' + item in tmp: match_GUI = True break if match_GUI: py = self.OMFITpythonGUI(filename, modifyOriginal=True) else: py = self.OMFITpythonTask(filename, modifyOriginal=True) if not f9f: # make a backup copy of the script py.modifyOriginal = False py._create_backup_copy() py.modifyOriginal = True return py.run(_relLoc=self.namespace, _OMFITscriptsDict=False, _OMFITconsoleDict=True, noGUI=None) def do_exec_delay(*args, **kwargs): try: self.OMFITx.manage_user_errors(GlobLoc_tk) finally: self.event_generate("<<update_treeGUI>>") if os.path.exists(self.OMFITcwd): os.chdir(self.OMFITcwd) self.after(1, do_exec_delay) return "break" def _histUP(self, event=None): self.delete('1.0', 'end') if self.tag_ranges('HIST'): self.delete(self.tag_ranges('HIST')[0], self.tag_ranges('HIST')[1]) if self.histPtr >= 0: self.histPtr -= 1 if self.histPtr >= 0 and self.histPtr <= (len(self.history) - 1): self.insert('insert', self.history[self.histPtr].strip('\n'), 'HIST') self.see('insert') def _histDOWN(self, event=None): self.delete('1.0', 'end') if self.tag_ranges('HIST'): self.delete(self.tag_ranges('HIST')[0], self.tag_ranges('HIST')[1]) if self.histPtr <= (len(self.history) - 1): self.histPtr += 1 if self.histPtr >= 0 and self.histPtr <= (len(self.history) - 1): self.insert('insert', self.history[self.histPtr].strip('\n'), 'HIST') self.see('insert') def start(self, use_queue=False): if self.started: return if use_queue: for k in _streams.tags: _streams[k] = qRedirector(k) sys.stdout = _streams['STDOUT'] sys.stderr = _streams['STDERR'] self._readq() else: for k in _streams.tags: _streams[k] = self.Redirector(self, k) sys.stdout = _streams['STDOUT'] sys.stderr = _streams['STDERR'] self.started = True def stop(self): if not self.started: return _streams.setDefaults() sys.stdout = _streams['STDOUT'] sys.stderr = _streams['STDERR'] if self.write_alarm: self.after_cancel(self.write_alarm) self.write_alarm = None self.started = False def interactive(self): self.set_highlight(True) global_event_bindings.add('COMMAND BOX: execute line or selection', self, '<F9>', lambda event=None: self.execute(None, f9f=True)) global_event_bindings.add('COMMAND BOX: execute', self, f'<{ctrlCmd()}-Return>', self.execute) global_event_bindings.add('COMMAND BOX: move history up', self, f'<{ctrlCmd()}-u>', self._histUP) global_event_bindings.add('COMMAND BOX: move history down', self, f'<{ctrlCmd()}-d>', self._histDOWN) global_event_bindings.add('COMMAND BOX: tab', self, '<Tab>', self._process_tab) global_event_bindings.add('COMMAND BOX: un-tab', self, '<Shift-Tab>', self._del_tab) global_event_bindings.add('COMMAND BOX: un-tab (alternative)', self, '<ISO_Left_Tab>', self._del_tab) global_event_bindings.add('COMMAND BOX: API reference', self, f'<{ctrlCmd()}-h>', lambda event=None: help()) def _readq(self, event=None): if self.write_alarm: self.after_cancel(self.write_alarm) self.write_alarm = None text = None k = 0 while not self.q.empty() or (time.time() - self.time_last_write) > (self.block / 1000.0): if not self.q.empty(): text, tag = self.q.get(block=False, timeout=0) if k == 0 or tag == self.last_tag: self.buffer += text.decode('utf-8', errors='ignore') self.last_tag = tag k += 1 else: # change of tag k = None break else: break if self.buffer: self._write(self.buffer, self.last_tag) self.buffer = '' self.time_last_write = time.time() if k is None: # change of tag self._write(text, tag) self.last_tag = tag self.write_alarm = self.after(0, self._readq) else: self.write_alarm = self.after(self.lag, self._readq) def write(self, val=None, tag='STDOUT'): # note: setting val=None forces a flush of the buffer if self.write_alarm: self.after_cancel(self.write_alarm) self.write_alarm = None if val is None or self.last_tag != tag or (time.time() - self.time_last_write) > (self.block / 1000.0): if len(self.buffer): self._write(self.buffer, self.last_tag) self.buffer = '' self.time_last_write = time.time() if val is not None: self.last_tag = tag try: self.buffer += val.decode('utf-8', errors='ignore') except Exception: self.buffer += str(val).encode('utf-8').decode('utf-8', errors='ignore') self.write_alarm = self.after(self.lag, self.write) def _write(self, val, tag='STDOUT'): if tag in ['PROGRAM_OUT', 'PROGRAM_ERR']: val = re.sub('\r\n', '\n', val) # handle return lines cr_sections = val.split('\r') if len(cr_sections) > 3: cr_sections = [cr_sections[0]] + [x for x in cr_sections[1:-1] if '\n' in x] + [cr_sections[-1]] for k, cr_section in enumerate(cr_sections): self.insert('end', cr_section, tag) if not self.follow: self.delete('1.0', 'end - 1000000 lines') else: self.delete('1.0', 'end - 100000 lines') self.see('end-1c linestart') if (k + 1) < len(cr_sections): self.delete("end-1c linestart", "end") self.insert('end', '\n', tag) if self.mirror and tag not in ['DEBUG']: sys.__stdout__.write(val) self.update_idletasks() def _make_popup_menu_jedi(self, event): def do_scriptpopup(event): # Determine the pixel location of the typing cursor x0 = self.winfo_rootx() dx = self.winfo_width() y0 = self.winfo_rooty() dy = self.winfo_height() where_am_i = self.index(tk.INSERT) found = False for r in range(0, dx, 5): for c in range(0, dy, 5): where = self.index(f'@{r},{c}') if str(where) == str(where_am_i): found = True break if found: break scriptpopup.post(x0 + r, y0 + c) scriptpopup.focus_set() return 'break' scriptpopup = tk.Menu(tearoff=0) names = [] names_params = [] completions = [] completions_params = [] for c in self.tab_comp: name = c.name if c.complete and c.complete[-1] == '(': name += '(' if '=' in name: names_params.append(name) completions_params.append(c.complete) else: names.append(name) completions.append(c.complete) for name, completion in list(zip(names_params + names, completions_params + completions))[:15]: def complete_i(i=completion): scriptpopup.unpost() self.insert("insert", i) self.focus() return 'break' scriptpopup.add_command(label=name, command=complete_i) def popup_process_event(event=None): if event.char == '??': return 'break' if event.keysym == 'BackSpace': scriptpopup.unpost() self.delete('insert-1c', 'insert') self.focus() self.after(1, lambda: self.event_generate("<<retrigger_tab_popup>>")) return 'break' if event.keysym == 'Escape': scriptpopup.unpost() self.focus() return 'break' if not event.char: return import string if event.char not in string.ascii_letters + string.digits + r'!@#$%^&*()-_=+[{]}\|;:\'",<.>/?`~\]': return self.insert("insert", event.char) scriptpopup.unpost() self.focus() if event.char: self.after(1, lambda: self.event_generate("<<retrigger_tab_popup>>")) return 'break' def focus_out(event): scriptpopup.unpost() self.focus() return 'break' scriptpopup.bind('<Key>', popup_process_event) scriptpopup.bind("<FocusOut>", focus_out) do_scriptpopup(event) return 'break' def _process_tab(self, event): prefix = self.get("insert linestart", "insert") # At beginning of line or some text selected if not prefix.strip() or self.tag_ranges(tk.SEL): return self._insert_tab(event) # try to import jedi try: import jedi if compare_version(jedi.__version__, '0.16.0') < 0: # warnings.warn('jedi package is old (<0.16.0)') # warnings.warn('Use `pip install jedi --upgrade` to rectify the situation') return 'break' except ImportError: # warnings.warn('jedi package not installed: No autocompletion available') # warnings.warn('Use `pip install jedi` to rectify the situation') return 'break' else: import jedi.settings jedi.settings.case_insensitive_completion = False jedi.settings.add_bracket_after_function = True # tab-complete based on the text in the command-box and the following namespaces import omfit_classes.omfit_python script = jedi.Interpreter( self.get(), [ self.namespace, # namespace of the command box (where root, rootName, and dependencies are defined...) omfit_classes.omfit_python.OMFITconsoleDict, # namespace with variables defined in the command box omfit_classes.omfit_python.__dict__, ], ) # namespace with all functions/classes available to the user # actual tab-complete at this line/char position line, char = map(int, self.index("insert").split('.')) self.tab_comp = script.complete(line, char) # no options --> do nothing if not len(self.tab_comp): pass # only one option --> complete elif len(self.tab_comp) == 1: self.insert("insert", self.tab_comp[0].complete) # multiple option --> show popup else: self._make_popup_menu_jedi(event) return 'break' def _insert_tab(self, event): # insert 4 spaces instead of a tab if self.tag_ranges(tk.SEL): first = 'sel.first linestart' last = 'sel.last lineend' ind1 = self.index(first).split('.')[0] ind2 = self.index(last).split('.')[0] for l in range(int(ind1), int(ind2) + 1): self.insert('%s.0' % l, ' ') self.tag_add('sel', '%s.0' % ind1, '%s lineend' % ind2) else: tmp = self.get("insert linestart", "insert lineend") self.insert("insert linestart", " " * (4 - int(np.mod(len(tmp) - len(tmp.lstrip(' ')), 4)))) return 'break' def _del_tab(self, event): # delete 4 spaces instead of a tab if self.tag_ranges(tk.SEL): first = 'sel.first linestart' last = 'sel.last lineend' ind1 = self.index(first).split('.')[0] ind2 = self.index(last).split('.')[0] tmp = self.get(first, last) self.replace(first, last, re.sub(r'^\s{1,4}', '', tmp, flags=re.MULTILINE)) self.tag_remove(tk.SEL, '1.0', tk.END) self.tag_add('sel', '%s.0 linestart' % ind1, '%s.0 lineend' % ind2) else: tmp = self.get("insert linestart", "insert lineend") for k in range(4 - int(np.mod(-len(tmp) + len(tmp.lstrip(' ')), 4))): if self.get("insert linestart", "insert linestart +1c") == ' ': self.delete("insert linestart", "insert linestart +1c") return 'break' def flush(self): pass def close(self): self.flush() tk.ConsoleTextGUI = _ConsoleTextGUI def __nonzero__(*args, **kw): return True tk.Misc.__nonzero__ = __nonzero__
[docs]class TtkScale(ttk.Frame): def __init__(self, master=None, **kwargs): ttk.Frame.__init__(self, master) self.columnconfigure(0, weight=1) self.tickinterval = kwargs.pop('tickinterval', 1) digits = kwargs.pop('digits', None) if np.any(map(is_float, [kwargs['to'], kwargs['from_'], self.tickinterval])): self.digits = 3 else: self.digits = 0 if digits is not None: self.digits = digits if 'command' in kwargs: # add self.display_value to the command fct = kwargs['command'] def cmd(value): value = self.display_value(value) fct(value) kwargs['command'] = cmd else: kwargs['command'] = self.display_value self.scale = ttk.Scale(self, **kwargs) # get slider length style = ttk.Style(self) style_name = kwargs.get('style', '%s.TScale' % (str(self.scale.cget('orient')).capitalize())) self.sliderlength = style.lookup(style_name, 'sliderlength', default=30) self.extent = float(kwargs['to'] - kwargs['from_']) self.start = kwargs['from_'] ttk.Label(self, text=' ').grid(row=0) self.label = ttk.Label(self, text='0') self.label.place(in_=self.scale, bordermode='outside', x=0, y=0, anchor='s') self.display_value(self.scale.get()) self.scale.grid(row=1, sticky='ew') # ticks if self.tickinterval: ttk.Label(self, text=' ').grid(row=2) self.ticks = [] self.ticklabels = [] nb_interv = int(round(self.extent / float(self.tickinterval))) formatter = '{:.' + str(self.digits) + 'f}' for i in range(max([nb_interv + 1, 2])): if i == 0: tick = kwargs['from_'] elif i >= nb_interv: tick = kwargs['to'] else: tick = kwargs['from_'] + i * self.tickinterval self.ticks.append(tick) self.ticklabels.append(ttk.Label(self, text=formatter.format(tick))) self.ticklabels[i].place(in_=self.scale, bordermode='outside', x=0, rely=1, anchor='n') self.place_ticks() self.scale.bind('<Configure>', self.on_configure)
[docs] def convert_to_pixels(self, value): try: return ((value - self.start) / self.extent) * (self.scale.winfo_width() - self.sliderlength) + self.sliderlength // 2 except Exception: return
[docs] def display_value(self, value): # position (in pixel) of the center of the slider x = self.convert_to_pixels(float(value)) # pay attention to the borders half_width = self.label.winfo_width() // 2 if x + half_width > self.scale.winfo_width(): x = self.scale.winfo_width() - half_width elif x - half_width < 0: x = half_width self.label.place_configure(x=x) formatter = '{:.' + str(self.digits) + 'f}' self.label.configure(text=formatter.format(float(value))) return formatter.format(float(value))
[docs] def place_ticks(self): # first tick tick = self.ticks[0] label = self.ticklabels[0] x = self.convert_to_pixels(tick) half_width = label.winfo_width() // 2 if x - half_width < 0: x = half_width label.place_configure(x=x) # ticks in the middle for tick, label in zip(self.ticks[1:-1], self.ticklabels[1:-1]): x = self.convert_to_pixels(tick) label.place_configure(x=x) # last tick tick = self.ticks[-1] label = self.ticklabels[-1] x = self.convert_to_pixels(tick) half_width = label.winfo_width() // 2 if x + half_width > self.scale.winfo_width(): x = self.scale.winfo_width() - half_width label.place_configure(x=x)
[docs] def on_configure(self, event): """Redisplay the ticks and the label so that they adapt to the new size of the scale.""" self.display_value(self.scale.get()) self.place_ticks()
[docs]def help(*args, **kw): # define help here to avoid GUI to lock pass
# ---------------- # screen geometry # --------------- def _winfo_screen(reset=False): """ This function detects and returns the screen width and height. This is necessary because XQuartz under OSX does not update the screen size when the monitors configuration changes. :param reset: forces the detection of the screen size on OSX (useful when the screen size changes for example because a external monitor is plugged to a laptop) :return: tuple with screen width and screen height """ if platform.system() == 'Darwin': if reset: _winfo_screen_cache[:] = [] if not len(_winfo_screen_cache): # find screensize using xrandr # (the problem is that XQuartz does not find the right screen size when # the screen configuration changes on the fly eg. adding/removing screen) s = subprocess.Popen( [distutils.spawn.find_executable('xrandr')], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) std_out, std_err = list(map(b2s, s.communicate())) for line in std_out.split('\n'): if '*' in line: geometry = re.sub(r'.* ([0-9]+\s*x\s*[0-9]+) .*', r'\1', line) _winfo_screen_cache[:] = list(map(int, geometry.split('x'))) break return _winfo_screen_cache[0], _winfo_screen_cache[1] else: return OMFITaux['rootGUI'].winfo_screenwidth(), OMFITaux['rootGUI'].winfo_screenheight() if platform.system() == 'Darwin': _winfo_screen_cache = [] def _winfo_screenwidth(*args, **kw): # returns the width of the screen return _winfo_screen()[0] def _winfo_screenheight(*args, **kw): # returns the height of the screen return _winfo_screen()[1] tk.Misc.winfo_screenwidth = _winfo_screenwidth tk.Misc.winfo_screenheight = _winfo_screenheight # --------------- # intercept tk variable generation and substitute with alternative # non-tk dependent class if OMFIT session without Tk is started # --------------- _globaTkVars = {} class _dummyTkVarClass(object): def __init__(self, default): self.value = default def set(self, value): self.value = value def get(self): return self.value def __del__(self): return def __getattr__(self, attr): raise Exception('this is not a full replacement for tk variables') _orig_tkBooleanVar = tk.BooleanVar def _tkBooleanVar(name=None, **kw): if OMFITaux['rootGUI'] is not None: return _orig_tkBooleanVar(name=name, **kw) elif name is None: return _dummyTkVarClass(False) else: return _globaTkVars.setdefault(name, _dummyTkVarClass(False)) _orig_tkStringVar = tk.StringVar def _tkStringVar(name=None, **kw): if OMFITaux['rootGUI'] is not None: return _orig_tkStringVar(name=name, **kw) elif name is None: return _dummyTkVarClass('') else: return _globaTkVars.setdefault(name, _dummyTkVarClass('')) tk.BooleanVar = _tkBooleanVar tk.StringVar = _tkStringVar