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
# 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 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