# **************************************************************************
# *
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
# * Jose Gutierrez (jose.gutierrez@cnb.csic.es)
# *
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307 USA
# *
# * All comments concerning this program package may be sent to the
# * e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
"""
Module to handling Dialogs
some code was taken from tkSimpleDialog
"""
import os.path
import tkinter as tk
import traceback
from tkcolorpicker import askcolor as _askColor
from pyworkflow import Config
from pyworkflow.exceptions import PyworkflowException
from pyworkflow.utils import Message, Icon, Color
from . import gui, Window, widgets, configureWeigths, LIST_TREEVIEW, defineStyle, ToolTip, getDefaultFont
from .tree import BoundTree, Tree
from .text import Text, TaggedText
# Possible result values for a Dialog
from .. import TK
RESULT_YES = 0
RESULT_NO = 1
RESULT_CANCEL = 2
RESULT_RUN_SINGLE = 3
RESULT_RUN_ALL = 4
RESULT_CLOSE = 5
[docs]class Dialog(tk.Toplevel):
_images = {} # Images cache
"""Implementation of our own dialog to display messages
It will have by default a three buttons: YES, NO and CANCEL
Subclasses can rename the labels of the buttons like: OK, CLOSE or others
The buttons(and theirs order) can be changed.
An image name can be passed to display left to the message.
"""
def __init__(self, parent, title, lockGui=True, **kwargs):
"""Initialize a dialog.
Arguments:
parent -- a parent window (the application window)
title -- the dialog title
**args accepts:
buttons -- list of buttons tuples containing which buttons to display
"""
if parent is None:
parent = tk.Tk()
parent.withdraw()
gui.setCommonFonts()
# invoke the button on the return key
parent.bind_class("Button", "<Key-Return>", lambda event: event.widget.invoke())
tk.Toplevel.__init__(self, parent)
defineStyle()
self.withdraw() # remain invisible for now
# If the master is not viewable, don't
# make the child transient, or else it
# would be opened withdrawn
if parent.winfo_viewable() and lockGui:
self.transient(parent)
if title:
self.title(title)
self.parent = parent
# Default to CANCEL so if window is "Closed" behaves the same.
self.result = RESULT_CANCEL
self.initial_focus = None
bodyFrame = tk.Frame(self)
# Call subclass method body to create that region
self.body(bodyFrame)
bodyFrame.grid(row=0, column=0, sticky='news',
padx=5, pady=5)
# Frame for the info/message label
infoFrame = tk.Frame(self)
infoFrame.grid(row=1, column=0, sticky='sew',
padx=5, pady=(0, 5))
self.floatingMessage = tk.Label(infoFrame, text="", fg=Config.SCIPION_MAIN_COLOR)
self.floatingMessage.grid(row=0, column=0, sticky='news')
# Create buttons
self.icons = kwargs.get('icons',
{RESULT_YES: Icon.BUTTON_SELECT,
RESULT_NO: Icon.BUTTON_CLOSE,
RESULT_CANCEL: Icon.BUTTON_CANCEL,
RESULT_CLOSE: Icon.BUTTON_CLOSE})
self.buttons = kwargs.get('buttons', [('OK', RESULT_YES),
('Cancel', RESULT_CANCEL)])
self.defaultButton = kwargs.get('default', 'OK')
# Frame for buttons
btnFrame = tk.Frame(self)
self.buttonbox(btnFrame)
btnFrame.grid(row=2, column=0, sticky='sew',
padx=5, pady=(0, 5))
gui.configureWeigths(self)
if self.initial_focus is None:
self.initial_focus = self
self.protocol("WM_DELETE_WINDOW", self.cancel)
if self.parent is not None:
position = kwargs.get('position', (parent.winfo_rootx() + 50,
parent.winfo_rooty() + 50))
self.geometry("+%d+%d" % position)
self.deiconify() # become visible now
self.initial_focus.focus_set()
# Pablo: I've commented this when migrating to python3 since I was getting and exception:
# window ".139897767953072.139897384058440" was deleted before its visibility changed
# wait for window to appear on screen before calling grab_set
self.wait_visibility()
if lockGui:
self.grab_set()
self.wait_window(self)
[docs] def getRoot(self):
return self
[docs] def destroy(self):
"""Destroy the window"""
self.initial_focus = None
tk.Toplevel.destroy(self)
#
# construction hooks
[docs] def body(self, master):
"""create dialog body.
return widget that should have initial focus.
This method should be overridden, and is called
by the __init__ method.
"""
pass
def _createButton(self, frame, text, result):
icon = None
if result in self.icons.keys():
icon = self.getImage(self.icons[result])
return tk.Button(frame, text=text, image=icon,
compound=tk.LEFT,
command=lambda: self._handleResult(result))
def _handleResult(self, resultValue):
"""This method will be called when any button is pressed.
It will set the resultValue associated with the button
and close the Dialog"""
self.result = resultValue
noCancel = self.result != RESULT_CANCEL and self.result != RESULT_CLOSE
callBack = self.validate if noCancel else self.validateClose
if not callBack():
self.initial_focus.focus_set() # put focus back
return
self.withdraw()
self.update_idletasks()
try:
if noCancel:
self.apply()
finally:
self.cancel()
def _handleReturn(self, e=None):
"""Handle press return key"""
# Check which of the buttons is the default
for button, result in self.buttons:
if self.defaultButton == button:
self._handleResult(result)
[docs] def cancel(self, event=None):
# put focus back to the parent window
if self.parent is not None:
self.parent.focus_set()
self.destroy()
#
# command hooks
[docs] def validate(self):
"""validate the data
This method is called automatically to validate the data before the
dialog is destroyed. By default, it always validates OK.
"""
return 1 # override
[docs] def validateClose(self):
return True
[docs] def apply(self):
"""process the data
This method is called automatically to process the data, *after*
the dialog is destroyed. By default, it does nothing.
"""
pass # override
[docs] def getImage(self, imgName):
"""A shortcut to get an image from its name"""
return gui.getImage(imgName)
[docs] def getResult(self):
return self.result
[docs] def resultYes(self):
return self.result == RESULT_YES
[docs] def resultNo(self):
return self.result == RESULT_NO
[docs] def resultCancel(self):
return self.result == RESULT_CANCEL
[docs] def info(self, message):
""" Shows a info message for long running processes to inform the user GUI is not frozen"""
self.floatingMessage.config(text=message)
### Basic GUI helper methods
def _addButton(self, frame, callback, text="", icon=None, row=0, col=0, tooltip=None, shortcut=""):
""" Adds a label button"""
btn = tk.Label(frame, text=text,
image=self.getImage(icon),
compound=tk.LEFT, cursor='hand2')
btn.grid(row=row, column=col, sticky='nw', padx=(5, 0), pady=(5, 0))
btn.bind('<Button-1>', callback)
if tooltip:
tooltip = tooltip + ' (%s)' % shortcut if shortcut else tooltip
ToolTip(btn, tooltip, delay=150)
if shortcut:
self.bind(shortcut, callback)
[docs]def fillMessageText(text, message):
# Insert lines of text
if isinstance(message, list):
lines = message
else:
lines = message.splitlines()
text.setReadOnly(False)
text.clear()
w = 0
for l in lines:
w = max(w, len(l))
text.addLine(l)
w = min(w + 5, 80)
h = min(len(lines) + 3, 30)
text.config(height=h, width=w)
text.addNewline()
text.setReadOnly(True)
[docs]def createMessageBody(bodyFrame, message, image,
frameBg=Config.SCIPION_BG_COLOR,
textBg=Config.SCIPION_BG_COLOR,
textPad=5):
""" Create a Text containing the message.
Params:
bodyFrame: tk.Frame to be filled.
msg: a str or list with the lines.
"""
bodyFrame.config(bg=frameBg, bd=0)
text = TaggedText(bodyFrame, bg=textBg, bd=0, highlightthickness=0)
# Insert image
if image:
label = tk.Label(bodyFrame, image=image, bg=textBg, bd=0)
label.grid(row=0, column=0, sticky='nw')
text.frame.grid(row=0, column=1, sticky='news',
padx=textPad, pady=textPad)
fillMessageText(text, message)
bodyFrame.rowconfigure(0, weight=1)
bodyFrame.columnconfigure(1, weight=1)
return text
[docs]class MessageDialog(Dialog):
"""Dialog subclasses to show message info, questions or errors.
It can display an icon with the message"""
def __init__(self, parent, title, msg, iconPath, **args):
self.msg = msg
self.iconPath = iconPath
if 'buttons' not in args:
args['buttons'] = [('OK', RESULT_YES)]
args['default'] = 'OK'
Dialog.__init__(self, parent, title, **args)
[docs] def body(self, bodyFrame):
self.image = gui.getImage(self.iconPath)
createMessageBody(bodyFrame, self.msg, self.image)
[docs]class ExceptionDialog(MessageDialog):
def __init__(self, *args, **kwargs):
self._exception = None if "exception" not in kwargs else kwargs['exception']
super().__init__(*args, **kwargs)
[docs] def body(self, bodyFrame):
super().body(bodyFrame)
def addTraceback(event):
detailsText = TaggedText(bodyFrame, bg=Config.SCIPION_BG_COLOR, bd=0, highlightthickness=0)
traceStr = traceback.format_exc()
fillMessageText(detailsText, traceStr)
detailsText.frame.grid(row=row + 1, column=0, columnspan=2, sticky='news', padx=5, pady=5)
event.widget.grid_forget()
row = 1
if self._exception:
if isinstance(self._exception, PyworkflowException):
helpUrl = self._exception.getUrl()
labelUrl = TaggedText(bodyFrame, bg=Config.SCIPION_BG_COLOR, bd=0, highlightthickness=0)
fillMessageText(labelUrl, "Please go here for more details: %s" % helpUrl)
labelUrl.grid(row=row, column=0, columnspan=2, sticky='news')
row += 1
label = tk.Label(bodyFrame, text="Show details...", bg=Config.SCIPION_BG_COLOR, bd=0)
label.grid(row=row, column=0, columnspan=2, sticky='news')
label.bind("<Button-1>", addTraceback)
[docs]class YesNoDialog(MessageDialog):
"""Ask a question with YES/NO answer"""
def __init__(self, master, title, msg, **kwargs):
buttonList = [('Yes', RESULT_YES), ('No', RESULT_NO)]
if kwargs.get('showCancel', False):
buttonList.append(('Cancel', RESULT_CANCEL))
MessageDialog.__init__(self, master, title, msg,
Icon.ALERT, default='No',
buttons=buttonList)
[docs]class GenericDialog(Dialog):
"""
Create a dialog with many buttons
Arguments:
parent -- a parent window (the application window)
title -- the dialog title
msg -- message to display into the dialog
iconPath -- path of the image to show into the dialog
**args accepts:
buttons -- list of buttons tuples containing which buttons to display and theirs values
icons -- list of icons for all buttons
default -- button default
Example:
buttons=[('Single', RESULT_RUN_SINGLE),
('All', RESULT_RUN_ALL),
('Cancel', RESULT_CANCEL)],
default='Cancel',
icons={RESULT_CANCEL: Icon.BUTTON_CANCEL,
RESULT_RUN_SINGLE: Icon.BUTTON_SELECT,
RESULT_RUN_ALL: Icon.ACTION_EXECUTE})
"""
def __init__(self, master, title, msg, iconPath, **kwargs):
self.msg = msg
self.iconPath = iconPath
Dialog.__init__(self, master, title, **kwargs)
[docs] def body(self, bodyFrame):
self.image = gui.getImage(self.iconPath)
createMessageBody(bodyFrame, self.msg, self.image)
[docs]class EntryDialog(Dialog):
"""Dialog to ask some entry"""
def __init__(self, parent, title, entryLabel, entryWidth=20,
defaultValue='', headerLabel=None):
self.entryLabel = entryLabel
self.entryWidth = entryWidth
self.headerLabel = headerLabel
self.tkvalue = tk.StringVar()
self.tkvalue.set(defaultValue)
self.value = None
Dialog.__init__(self, parent, title)
[docs] def body(self, bodyFrame):
bodyFrame.config(bg=Config.SCIPION_BG_COLOR)
frame = tk.Frame(bodyFrame, bg=Config.SCIPION_BG_COLOR)
frame.grid(row=0, column=0, padx=20, pady=20)
row = 0
if self.headerLabel:
label = tk.Label(bodyFrame, text=self.headerLabel, bg=Config.SCIPION_BG_COLOR, bd=0)
label.grid(row=row, column=0, columnspan=2, sticky='nw', padx=(15, 10), pady=15)
row += 1
label = tk.Label(bodyFrame, text=self.entryLabel, bg=Config.SCIPION_BG_COLOR, bd=0)
label.grid(row=row, column=0, sticky='nw', padx=(15, 10), pady=15)
self.entry = tk.Entry(bodyFrame, bg=gui.cfgEntryBgColor,
width=self.entryWidth, textvariable=self.tkvalue,
font=getDefaultFont())
self.entry.grid(row=row, column=1, sticky='new', padx=(0, 15), pady=15)
self.initial_focus = self.entry
[docs] def apply(self):
self.value = self.entry.get()
[docs] def validate(self):
if len(self.entry.get().strip()) == 0:
showError("Validation error", "Value is empty", self)
return False
return True
[docs]class EditObjectDialog(Dialog):
"""Dialog to edit some text"""
def __init__(self, parent, title, obj, mapper, **kwargs):
self.obj = obj
self.mapper = mapper
self.textWidth = 5
self.textHeight = 1
self.labelText = kwargs.get('labelText', Message.TITLE_LABEL)
self.valueText = self.obj.getObjLabel()
self.commentLabel = Message.TITLE_COMMENT
self.commentWidth = 50
self.commentHeight = 15
self.valueComment = self.obj.getObjComment()
Dialog.__init__(self, parent, title, **kwargs)
[docs] def body(self, bodyFrame):
bodyFrame.config(bg=Config.SCIPION_BG_COLOR)
frame = tk.Frame(bodyFrame, bg=Config.SCIPION_BG_COLOR)
frame.grid(row=0, column=0, padx=20, pady=20)
# Label
label_text = tk.Label(bodyFrame, text=self.labelText, bg=Config.SCIPION_BG_COLOR, bd=0)
label_text.grid(row=0, column=0, sticky='nw', padx=(15, 10), pady=15)
# Label box
var = tk.StringVar()
var.set(self.valueText)
self.textLabel = tk.Entry(bodyFrame, width=self.textWidth, textvariable=var, font=gui.getDefaultFont())
self.textLabel.grid(row=0, column=1, sticky='news', padx=5, pady=5)
# Comment
label_comment = tk.Label(bodyFrame, text=self.commentLabel, bg=Config.SCIPION_BG_COLOR, bd=0)
label_comment.grid(row=1, column=0, sticky='nw', padx=(15, 10), pady=15)
# Comment box
self.textComment = Text(bodyFrame, height=self.commentHeight,
width=self.commentWidth)
self.textComment.setReadOnly(False)
self.textComment.setText(self.valueComment)
self.textComment.grid(row=1, column=1, sticky='news', padx=5, pady=5)
self.initial_focus = self.textLabel
[docs] def getLabel(self):
return self.textLabel.get()
[docs] def apply(self):
self.obj.setObjLabel(self.getLabel())
self.obj.setObjComment(self.getComment())
if self.obj.hasObjId():
self.mapper.store(self.obj)
self.mapper.commit()
def _noReturn(self, e):
pass
""" Functions to display dialogs """
[docs]def askYesNo(title, msg, parent):
d = YesNoDialog(parent, title, msg)
return d.resultYes()
[docs]def askYesNoCancel(title, msg, parent):
d = YesNoDialog(parent, title, msg, showCancel=True)
return d.result
[docs]def askSingleAllCancel(title, msg, parent):
d = GenericDialog(parent, title, msg,
Icon.ALERT,
buttons=[('Single', RESULT_RUN_SINGLE),
('All', RESULT_RUN_ALL),
('Cancel', RESULT_CANCEL)],
default='Single',
icons={RESULT_CANCEL: Icon.BUTTON_CANCEL,
RESULT_RUN_SINGLE: Icon.BUTTON_SELECT,
RESULT_RUN_ALL: Icon.ACTION_EXECUTE})
return d.result
[docs]def showInfo(title, msg, parent):
MessageDialog(parent, title, msg, Icon.INFO)
[docs]def showWarning(title, msg, parent):
MessageDialog(parent, title, msg, Icon.ALERT)
[docs]def showError(title, msg, parent, exception=None):
ExceptionDialog(parent, title, msg, Icon.ERROR, exception=exception)
[docs]def askString(title, label, parent, entryWidth=20, defaultValue='', headerLabel=None):
d = EntryDialog(parent, title, label, entryWidth, defaultValue, headerLabel)
return d.value
[docs]def askColor(parent, defaultColor='black'):
(rgbcolor, hexcolor) = _askColor(defaultColor, parent=parent)
return hexcolor
[docs]def askPath(title=None, msg="Select a file of a folder", path=".", onlyFolders=False, master=None, returnBaseName=False):
from pyworkflow.gui.browser import FileBrowserWindow
if title is None:
title = "Select a folder" if onlyFolders else "Select a file"
browserW = FileBrowserWindow(title, master=master, path=path, onlyFolders=onlyFolders)
browserW.show(modal=True)
result = browserW.getLastSelection()
if returnBaseName:
result=os.path.basename(result)
return result
[docs]class ListDialog(Dialog):
"""
Dialog to select an element from a list.
It is implemented using the Tree widget.
"""
def __init__(self, parent, title, provider, message=None, **kwargs):
""" From kwargs:
:param message: message tooltip to show when browsing.
:param validateSelectionCallback: a callback function to validate selected items.
:param previewCallback: method to be called on item click to fill the callback frame.
:param selectmode: 'extended' by default. Selection mode of the tk.Tree
:param selectOnDoubleClick: (False). If True, double click will trigger "Select" button click
:param allowsEmptySelection: (False). Allows empty selection
:param allowSelect: if set to False, the 'Select' button will not be shown.
:param allowsEmptySelection: if set to True, it will not validate that at least one element was selected.
"""
self.values = []
self.provider = provider
self.message = message
self.validateSelectionCallback = kwargs.get('validateSelectionCallback', None)
self.previewCallBack = kwargs.get('previewCallback', None)
self._selectmode = kwargs.get('selectmode', 'extended')
self._selectOnDoubleClick = kwargs.get('selectOnDoubleClick', False)
self._allowsEmptySelection = kwargs.get('allowsEmptySelection', False)
if "buttons" not in kwargs:
buttons=[]
if kwargs.get('allowSelect', True):
buttons.append(('Select', RESULT_YES))
if kwargs.get('cancelButton', False):
buttons.append(('Close', RESULT_CLOSE))
else:
buttons.append(('Cancel', RESULT_CANCEL))
kwargs['buttons'] = buttons
Dialog.__init__(self, parent, title, **kwargs)
[docs] def body(self, bodyFrame):
bodyFrame.config()
gui.configureWeigths(bodyFrame)
dialogFrame = tk.Frame(bodyFrame)
dialogFrame.grid(row=0, column=0, sticky='news', padx=5, pady=5)
dialogFrame.config()
gui.configureWeigths(dialogFrame, row=1)
self._createFilterBox(dialogFrame)
self._createTree(dialogFrame)
if self.previewCallBack:
self._createPreviewPanel(dialogFrame)
if self.message:
label = tk.Label(bodyFrame, text=self.message, compound=tk.LEFT,
image=self.getImage(Icon.LIGHTBULB))
label.grid(row=2, column=0, sticky='nw', padx=5, pady=5)
self.initial_focus = self.tree
def _createTree(self, parent):
self.tree = BoundTree(parent, self.provider, selectmode=self._selectmode, style=LIST_TREEVIEW)
if self._selectOnDoubleClick:
self.tree.itemDoubleClick = lambda obj: self._handleResult(RESULT_YES)
if self.previewCallBack:
self.tree.itemClick = self._itemClick
self.tree.grid(row=1, column=0)
def _itemClick(self, obj):
self.previewCallBack(obj, self.previewFrame)
def _createPreviewPanel(self, parent):
self.previewFrame = tk.Frame(parent)
self.previewFrame.grid(row=1, column=1)
def _createFilterBox(self, content):
""" Create the Frame with Filter widgets """
self.searchBoxframe = tk.Frame(content)
label = tk.Label(self.searchBoxframe, text="Filter")
label.grid(row=0, column=0, sticky='nw')
self._searchVar = tk.StringVar(value='')
self.entry = tk.Entry(self.searchBoxframe, bg=Config.SCIPION_BG_COLOR,
textvariable=self._searchVar, width=40,
font=gui.getDefaultFont())
self.entry.bind('<KeyRelease>', self._onSearch)
self.entry.focus_set()
self.entry.grid(row=0, column=1, sticky='news')
self.searchBoxframe.grid(row=0, column=0, sticky='news', padx=5,
pady=(10, 5))
[docs] def refresh(self):
""" Refreshes the list taking into account the filter"""
self._onSearch()
def _onSearch(self, e=None):
def comparison():
pattern = self._searchVar.get().lower()
return [w[0] for w in self.lista.items()
if pattern in self.lista.get(w[0]).lower()]
self.tree.update()
self.lista = {}
for item in self.tree.get_children():
itemStr = self.tree.item(item)['text']
for value in self.tree.item(item)['values']:
if isinstance(value, int):
itemStr = itemStr + ' ' + str(value)
else:
itemStr = itemStr + ' ' + value
self.lista[item] = itemStr
if self._searchVar.get() != '':
matchs = comparison()
if matchs:
for item in self.tree.get_children():
if item not in matchs:
self.tree.delete(item)
else:
self.tree.delete(*self.tree.get_children())
[docs] def apply(self):
self.values = self.tree.getSelectedObjects()
[docs] def validate(self):
self.apply() # load self.values with selected items
err = ''
if self.values:
if self.validateSelectionCallback:
err = self.validateSelectionCallback(self.values)
else:
if not self._allowsEmptySelection:
err = "Please select an element"
if err:
showError("Validation error", err, self)
return False
return True
[docs]class FlashMessage:
def __init__(self, master, msg, delay=5, relief='solid', func=None):
self.root = tk.Toplevel(master=master)
# hides until know geometry
self.root.withdraw()
self.root.wm_overrideredirect(1)
tk.Label(self.root, text=" %s " % msg,
bd=1, bg='DodgerBlue4', fg='white').pack()
gui.centerWindows(self.root, refWindows=master)
self.root.deiconify()
self.root.grab_set()
self.msg = msg
if func:
self.root.update_idletasks()
self.root.after(10, self.process, func)
else:
self.root.after(int(delay * 1000), self.close)
self.root.wait_window(self.root)
[docs] def process(self, func):
func()
self.root.destroy()
[docs] def close(self):
self.root.destroy()
[docs]class FloatingMessage:
def __init__(self, master, msg, xPos=None, yPos=None, textWidth=280,
font='Helvetica', size=12, bd=1, bg=Config.SCIPION_MAIN_COLOR, fg='white'):
if xPos is None:
xPos = (master.winfo_width() - textWidth) / 2
yPos = master.winfo_height() / 2
self.floatingMessage = tk.Label(master, text=" %s " % msg,
bd=bd, bg=bg, fg=fg)
self.floatingMessage.place(x=xPos, y=yPos, width=textWidth)
self.floatingMessage.config(font=(font, size))
[docs] def setMessage(self, msg):
self.floatingMessage.config(text=msg)
[docs] def show(self):
self.floatingMessage.update_idletasks()
[docs] def close(self):
self.floatingMessage.destroy()
[docs]class SearchBaseWindow(Window):
""" Base window for searching in a list
You are going to implement several elements:
columnsConfig: a dictionary with elements with this structure:
<column-key>: (<title>,{kwargs for tree.column method}, weight, <casting_method>(optional, otherwise str))
Example:
columnConfig = {
'#0': ('Status', {'width': 50, 'minwidth': 50, 'stretch': tk.NO}, 3),
'protocol': ('Protocol', {'width': 300, 'stretch': tk.FALSE}), 5,
'streaming': ('Streamified', {'width': 100, 'stretch': tk.FALSE}, 3),
'installed': ('Installation', {'width': 110, 'stretch': tk.FALSE}, 3),
'help': ('Help', {'minwidth': 300, 'stretch': tk.YES}, 3),
'score': ('Score', {'width': 50, 'stretch': tk.FALSE}, 3, int),
}
_createResultsTree method
_onSearchClick method
See SearchProtocolWindow as an example
"""
COLUMN_TEXT_INDEX = 0
COLUMN_KWARGS_INDEX = 1
WEIGHT_INDEX = 2
CASTING_INDEX = 3
columnConfig = {} # Columns configuration
def __init__(self, parentWindow, title="Search element", onClick=None, onDoubleClick=None, **kwargs):
super().__init__(title=title,
masterWindow=parentWindow)
self.onClick = self._click if onClick is None else onClick
self.onDoubleClick = self._double_click if onDoubleClick is None else onDoubleClick
content = tk.Frame(self.root, bg=Config.SCIPION_BG_COLOR)
self._createContent(content)
content.grid(row=0, column=0, sticky='news')
content.columnconfigure(0, weight=1)
content.rowconfigure(1, weight=1)
[docs] def getColumnKeys(self):
return self.columnConfig.keys()
def _createContent(self, content):
self._createSearchBox(content)
self._createResultsBox(content)
def _createSearchBox(self, content):
""" Create the Frame with Search widgets """
frame = tk.Frame(content, bg=Config.SCIPION_BG_COLOR)
label = tk.Label(frame, text="Search", bg=Config.SCIPION_BG_COLOR)
label.grid(row=0, column=0, sticky='nw')
self._searchVar = tk.StringVar()
entry = tk.Entry(frame, bg='white', textvariable=self._searchVar, font=gui.getDefaultFont())
entry.bind(TK.RETURN, self._onSearchClick)
entry.bind(TK.ENTER, self._onSearchClick)
entry.focus_set()
entry.grid(row=0, column=1, sticky='nw')
btn = widgets.IconButton(frame, "Search",
imagePath=Icon.ACTION_SEARCH,
command=self._onSearchClick)
btn.grid(row=0, column=2, sticky='nw')
frame.grid(row=0, column=0, sticky='new', padx=5, pady=(10, 5))
def _createResultsBox(self, content):
frame = tk.Frame(content, bg=Color.ALT_COLOR, padx=5, pady=5)
configureWeigths(frame)
self._resultsTree = self._createResultsTree(frame,
show=None,
columns=list(self.getColumnKeys())[1:])
self._configureTreeColumns()
self._resultsTree.grid(row=0, column=0, sticky='news')
frame.grid(row=1, column=0, sticky='news', padx=5, pady=5)
def _createResultsTree(self, frame, show, columns):
t = Tree(frame, show=show, columns=columns, style=LIST_TREEVIEW)
t.column('#0', minwidth=100)
t.bind("<Button-1>", self.onClick)
t.bind("<Double-1>", self.onDoubleClick)
return t
def _click(self, event):
""" To be implemented, triggered on tree-view click """
pass
def _double_click(self, event):
""" To be implemented, triggered on tree-view double click """
pass
[docs] def addSearchWeight(self, line2Search, searchtext):
# Adds a weight value for the search
weight = 0
linelower = [str(v).lower() for v in line2Search]
for index, column in enumerate(self.columnConfig.values()):
if searchtext in linelower[index]:
# prioritize findings in label
weight += column[self.WEIGHT_INDEX] * 2
elif " " in searchtext:
for word in searchtext.split():
if word in linelower[index]:
weight += column[self.WEIGHT_INDEX]
return line2Search + (weight,)
def _configureTreeColumns(self):
for key, columnConf in self.columnConfig.items():
casting = str if len(columnConf) <= self.CASTING_INDEX else columnConf[self.CASTING_INDEX]
self._resultsTree.column(key, **columnConf[self.COLUMN_KWARGS_INDEX])
self._resultsTree.heading(key,
text=columnConf[self.COLUMN_TEXT_INDEX],
command=lambda bound_key=key, bound_casting=casting:
self._resultsTree.sortByColumn(bound_key, False, casting=bound_casting))
def _onSearchClick(self, e=None):
""" To be implemented, triggered on search button click"""
pass