mirror of https://github.com/chriskiehl/Gooey.git
28 changed files with 1481 additions and 0 deletions
Unified View
Diff Options
-
0gooey/gui/components/__init__.py
-
191gooey/gui/components/config.py
-
82gooey/gui/components/console.py
-
134gooey/gui/components/footer.py
-
101gooey/gui/components/header.py
-
0gooey/gui/components/layouts/__init__.py
-
17gooey/gui/components/layouts/layouts.py
-
46gooey/gui/components/modals.py
-
86gooey/gui/components/sidebar.py
-
37gooey/gui/components/tabbar.py
-
12gooey/gui/components/widgets/__init__.py
-
151gooey/gui/components/widgets/bases.py
-
83gooey/gui/components/widgets/beep_boop.py
-
50gooey/gui/components/widgets/checkbox.py
-
35gooey/gui/components/widgets/choosers.py
-
8gooey/gui/components/widgets/command.py
-
2gooey/gui/components/widgets/core/__init__.py
-
102gooey/gui/components/widgets/core/chooser.py
-
43gooey/gui/components/widgets/core/text_input.py
-
16gooey/gui/components/widgets/counter.py
-
0gooey/gui/components/widgets/dialogs/__init__.py
-
50gooey/gui/components/widgets/dialogs/calender_dialog.py
-
35gooey/gui/components/widgets/dropdown.py
-
30gooey/gui/components/widgets/listbox.py
-
12gooey/gui/components/widgets/password.py
-
132gooey/gui/components/widgets/radio_group.py
-
6gooey/gui/components/widgets/textarea.py
-
20gooey/gui/components/widgets/textfield.py
@ -0,0 +1,191 @@ |
|||||
|
import wx |
||||
|
from wx.lib.scrolledpanel import ScrolledPanel |
||||
|
|
||||
|
from gooey.gui.util import wx_util |
||||
|
from gooey.util.functional import getin, flatmap, merge, compact, indexunique |
||||
|
from gooey.gui.components.widgets.radio_group import RadioGroup |
||||
|
|
||||
|
|
||||
|
class ConfigPage(ScrolledPanel): |
||||
|
def __init__(self, parent, rawWidgets, *args, **kwargs): |
||||
|
super(ConfigPage, self).__init__(parent, *args, **kwargs) |
||||
|
self.SetupScrolling(scroll_x=False, scrollToTop=False) |
||||
|
self.rawWidgets = rawWidgets |
||||
|
self.reifiedWidgets = [] |
||||
|
self.layoutComponent() |
||||
|
self.widgetsMap = indexunique(lambda x: x._id, self.reifiedWidgets) |
||||
|
## TODO: need to rethink what uniquely identifies an argument. |
||||
|
## Out-of-band IDs, while simple, make talking to the client program difficult |
||||
|
## unless they're agreed upon before hand. Commands, as used here, have the problem |
||||
|
## of (a) not being nearly granular enough (for instance, `-v` could represent totally different |
||||
|
## things given context/parser position), and (b) cannot identify positional args. |
||||
|
|
||||
|
|
||||
|
def firstCommandIfPresent(self, widget): |
||||
|
commands = widget._meta['commands'] |
||||
|
return commands[0] if commands else '' |
||||
|
|
||||
|
def getPositionalArgs(self): |
||||
|
return [widget.getValue()['cmd'] for widget in self.reifiedWidgets |
||||
|
if widget.info['cli_type'] == 'positional'] |
||||
|
|
||||
|
def getOptionalArgs(self): |
||||
|
return [widget.getValue()['cmd'] for widget in self.reifiedWidgets |
||||
|
if widget.info['cli_type'] != 'positional'] |
||||
|
|
||||
|
|
||||
|
def isValid(self): |
||||
|
states = [widget.getValue() for widget in self.reifiedWidgets] |
||||
|
return not any(compact([state['error'] for state in states])) |
||||
|
|
||||
|
|
||||
|
def seedUI(self, seeds): |
||||
|
radioWidgets = self.indexInternalRadioGroupWidgets() |
||||
|
for id, values in seeds.items(): |
||||
|
if id in self.widgetsMap: |
||||
|
self.widgetsMap[id].setOptions(values) |
||||
|
if id in radioWidgets: |
||||
|
radioWidgets[id].setOptions(values) |
||||
|
|
||||
|
def indexInternalRadioGroupWidgets(self): |
||||
|
groups = filter(lambda x: x.info['type'] == 'RadioGroup', self.reifiedWidgets) |
||||
|
widgets = flatmap(lambda group: group.widgets, groups) |
||||
|
return indexunique(lambda x: x._id, widgets) |
||||
|
|
||||
|
|
||||
|
def displayErrors(self): |
||||
|
states = [widget.getValue() for widget in self.reifiedWidgets] |
||||
|
errors = [state for state in states if state['error']] |
||||
|
for error in errors: |
||||
|
widget = self.widgetsMap[error['id']] |
||||
|
widget.setErrorString(error['error']) |
||||
|
widget.showErrorString(True) |
||||
|
while widget.GetParent(): |
||||
|
widget.Layout() |
||||
|
widget = widget.GetParent() |
||||
|
|
||||
|
def resetErrors(self): |
||||
|
for widget in self.reifiedWidgets: |
||||
|
widget.setErrorString('') |
||||
|
widget.showErrorString(False) |
||||
|
|
||||
|
def hideErrors(self): |
||||
|
for widget in self.reifiedWidgets: |
||||
|
widget.hideErrorString() |
||||
|
|
||||
|
|
||||
|
def layoutComponent(self): |
||||
|
sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
for item in self.rawWidgets['contents']: |
||||
|
self.makeGroup(self, sizer, item, 0, wx.EXPAND) |
||||
|
self.SetSizer(sizer) |
||||
|
|
||||
|
|
||||
|
def makeGroup(self, parent, thissizer, group, *args): |
||||
|
''' |
||||
|
Messily builds the (potentially) nested and grouped layout |
||||
|
|
||||
|
Note! Mutates `self.reifiedWidgets` in place with the widgets as they're |
||||
|
instantiated! I cannot figure out how to split out the creation of the |
||||
|
widgets from their styling without WxPython violently exploding |
||||
|
|
||||
|
TODO: sort out the WX quirks and clean this up. |
||||
|
''' |
||||
|
|
||||
|
# determine the type of border , if any, the main sizer will use |
||||
|
if getin(group, ['options', 'show_border'], False): |
||||
|
boxDetails = wx.StaticBox(parent, -1, group['name'] or '') |
||||
|
boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL) |
||||
|
else: |
||||
|
boxSizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
boxSizer.AddSpacer(10) |
||||
|
if group['name']: |
||||
|
boxSizer.Add(wx_util.h1(parent, group['name'] or ''), 0, wx.TOP | wx.BOTTOM | wx.LEFT, 8) |
||||
|
|
||||
|
group_description = getin(group, ['description']) |
||||
|
if group_description: |
||||
|
description = wx.StaticText(parent, label=group_description) |
||||
|
boxSizer.Add(description, 0, wx.EXPAND | wx.LEFT, 10) |
||||
|
|
||||
|
# apply an underline when a grouping border is not specified |
||||
|
if not getin(group, ['options', 'show_border'], False) and group['name']: |
||||
|
boxSizer.Add(wx_util.horizontal_rule(parent), 0, wx.EXPAND | wx.LEFT, 10) |
||||
|
|
||||
|
ui_groups = self.chunkWidgets(group) |
||||
|
|
||||
|
for uigroup in ui_groups: |
||||
|
sizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
for item in uigroup: |
||||
|
widget = self.reifyWidget(parent, item) |
||||
|
# !Mutate the reifiedWidgets instance variable in place |
||||
|
self.reifiedWidgets.append(widget) |
||||
|
sizer.Add(widget, 1, wx.ALL, 5) |
||||
|
boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5) |
||||
|
|
||||
|
# apply the same layout rules recursively for subgroups |
||||
|
hs = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
for e, subgroup in enumerate(group['groups']): |
||||
|
self.makeGroup(parent, hs, subgroup, 1, wx.ALL | wx.EXPAND, 5) |
||||
|
if e % getin(group, ['options', 'columns'], 2) \ |
||||
|
or e == len(group['groups']): |
||||
|
boxSizer.Add(hs, *args) |
||||
|
hs = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
|
||||
|
thissizer.Add(boxSizer, *args) |
||||
|
|
||||
|
|
||||
|
def chunkWidgets(self, group): |
||||
|
''' chunk the widgets up into groups based on their sizing hints ''' |
||||
|
ui_groups = [] |
||||
|
subgroup = [] |
||||
|
for index, item in enumerate(group['items']): |
||||
|
if getin(item, ['options', 'full_width'], False): |
||||
|
ui_groups.append(subgroup) |
||||
|
ui_groups.append([item]) |
||||
|
subgroup = [] |
||||
|
else: |
||||
|
subgroup.append(item) |
||||
|
if len(subgroup) == getin(group, ['options', 'columns'], 2) \ |
||||
|
or item == group['items'][-1]: |
||||
|
ui_groups.append(subgroup) |
||||
|
subgroup = [] |
||||
|
return ui_groups |
||||
|
|
||||
|
|
||||
|
def reifyWidget(self, parent, item): |
||||
|
''' Convert a JSON description of a widget into a WxObject ''' |
||||
|
from gooey.gui.components import widgets |
||||
|
widgetClass = getattr(widgets, item['type']) |
||||
|
return widgetClass(parent, item) |
||||
|
|
||||
|
|
||||
|
|
||||
|
class TabbedConfigPage(ConfigPage): |
||||
|
""" |
||||
|
Splits top-level groups across tabs |
||||
|
""" |
||||
|
|
||||
|
|
||||
|
def layoutComponent(self): |
||||
|
# self.rawWidgets['contents'] = self.rawWidgets['contents'][1:2] |
||||
|
self.notebook = wx.Notebook(self, style=wx.BK_DEFAULT) |
||||
|
|
||||
|
panels = [wx.Panel(self.notebook) for _ in self.rawWidgets['contents']] |
||||
|
sizers = [wx.BoxSizer(wx.VERTICAL) for _ in panels] |
||||
|
|
||||
|
for group, panel, sizer in zip(self.rawWidgets['contents'], panels, sizers): |
||||
|
self.makeGroup(panel, sizer, group, 0, wx.EXPAND) |
||||
|
panel.SetSizer(sizer) |
||||
|
panel.Layout() |
||||
|
self.notebook.AddPage(panel, group['name']) |
||||
|
self.notebook.Layout() |
||||
|
|
||||
|
|
||||
|
_sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
_sizer.Add(self.notebook, 1, wx.EXPAND) |
||||
|
self.SetSizer(_sizer) |
||||
|
self.Layout() |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,82 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui.lang import i18n |
||||
|
|
||||
|
|
||||
|
class Console(wx.Panel): |
||||
|
''' |
||||
|
Textbox console/terminal displayed during the client program's execution. |
||||
|
''' |
||||
|
|
||||
|
def __init__(self, parent, buildSpec, **kwargs): |
||||
|
wx.Panel.__init__(self, parent, **kwargs) |
||||
|
self.buildSpec = buildSpec |
||||
|
|
||||
|
self.text = wx.StaticText(self, label=i18n._("status")) |
||||
|
self.textbox = wx.TextCtrl( |
||||
|
self, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH |
||||
|
) |
||||
|
|
||||
|
self.defaultFont = self.textbox.GetFont() |
||||
|
|
||||
|
self.textbox.SetFont(wx.Font( |
||||
|
self.buildSpec['terminal_font_size'] or self.defaultFont.GetPointSize(), |
||||
|
self.getFontStyle(), |
||||
|
wx.NORMAL, |
||||
|
self.buildSpec['terminal_font_weight'] or wx.NORMAL, |
||||
|
False, |
||||
|
self.getFontFace(), |
||||
|
)) |
||||
|
self.textbox.SetForegroundColour(self.buildSpec['terminal_font_color']) |
||||
|
|
||||
|
self.layoutComponent() |
||||
|
self.Layout() |
||||
|
|
||||
|
|
||||
|
def getFontStyle(self): |
||||
|
""" |
||||
|
Force wx.Modern style to support legacy |
||||
|
monospace_display param when present |
||||
|
""" |
||||
|
return (wx.MODERN |
||||
|
if self.buildSpec['monospace_display'] |
||||
|
else wx.DEFAULT) |
||||
|
|
||||
|
|
||||
|
def getFontFace(self): |
||||
|
"""Choose the best font face available given the user options""" |
||||
|
userFace = self.buildSpec['terminal_font_family'] or self.defaultFont.GetFaceName() |
||||
|
return ('' |
||||
|
if self.buildSpec['monospace_display'] |
||||
|
else userFace) |
||||
|
|
||||
|
|
||||
|
def logOutput(self, *args, **kwargs): |
||||
|
"""Event Handler for console updates coming from the client's program""" |
||||
|
self.appendText(kwargs.get('msg')) |
||||
|
|
||||
|
|
||||
|
def appendText(self, txt): |
||||
|
""" |
||||
|
Append the text to the main TextCtrl. |
||||
|
|
||||
|
Note! Must be called from a Wx specific thread handler to avoid |
||||
|
multi-threaded explosions (e.g. wx.CallAfter) |
||||
|
""" |
||||
|
self.textbox.AppendText(txt) |
||||
|
|
||||
|
|
||||
|
def getText(self): |
||||
|
return self.textbox.GetValue() |
||||
|
|
||||
|
def layoutComponent(self): |
||||
|
self.SetBackgroundColour(self.buildSpec.get('terminal_bg_color', '#F0F0F0')) |
||||
|
sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
sizer.AddSpacer(10) |
||||
|
sizer.Add(self.text, 0, wx.LEFT, 20) |
||||
|
sizer.AddSpacer(10) |
||||
|
sizer.Add(self.textbox, 1, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 20) |
||||
|
sizer.AddSpacer(20) |
||||
|
self.SetSizer(sizer) |
||||
|
|
||||
|
|
@ -0,0 +1,134 @@ |
|||||
|
import sys |
||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import events |
||||
|
from gooey.gui.lang import i18n |
||||
|
from gooey.gui.pubsub import pub |
||||
|
|
||||
|
|
||||
|
class Footer(wx.Panel): |
||||
|
''' |
||||
|
Footer section used on the configuration |
||||
|
screen of the application |
||||
|
''' |
||||
|
|
||||
|
def __init__(self, parent, buildSpec, **kwargs): |
||||
|
wx.Panel.__init__(self, parent, **kwargs) |
||||
|
self.buildSpec = buildSpec |
||||
|
|
||||
|
self.SetMinSize((30, 53)) |
||||
|
# components |
||||
|
self.cancel_button = None |
||||
|
self.start_button = None |
||||
|
self.progress_bar = None |
||||
|
self.close_button = None |
||||
|
self.stop_button = None |
||||
|
self.restart_button = None |
||||
|
self.edit_button = None |
||||
|
self.buttons = [] |
||||
|
|
||||
|
self.layouts = {} |
||||
|
|
||||
|
self._init_components() |
||||
|
self._do_layout() |
||||
|
|
||||
|
for button in self.buttons: |
||||
|
self.Bind(wx.EVT_BUTTON, self.dispatch_click, button) |
||||
|
|
||||
|
def updateProgressBar(self, *args, **kwargs): |
||||
|
''' |
||||
|
value, disable_animation=False |
||||
|
:param args: |
||||
|
:param kwargs: |
||||
|
:return: |
||||
|
''' |
||||
|
value = kwargs.get('progress') |
||||
|
pb = self.progress_bar |
||||
|
if value is None: |
||||
|
return |
||||
|
if value < 0: |
||||
|
pb.Pulse() |
||||
|
else: |
||||
|
value = min(int(value), pb.GetRange()) |
||||
|
if pb.GetValue() != value: |
||||
|
# Windows 7 progress bar animation hack |
||||
|
# http://stackoverflow.com/questions/5332616/disabling-net-progressbar-animation-when-changing-value |
||||
|
if self.buildSpec['disable_progress_bar_animation'] \ |
||||
|
and sys.platform.startswith("win"): |
||||
|
if pb.GetRange() == value: |
||||
|
pb.SetValue(value) |
||||
|
pb.SetValue(value - 1) |
||||
|
else: |
||||
|
pb.SetValue(value + 1) |
||||
|
pb.SetValue(value) |
||||
|
|
||||
|
|
||||
|
def showButtons(self, *buttonsToShow): |
||||
|
for button in self.buttons: |
||||
|
button.Show(False) |
||||
|
for button in buttonsToShow: |
||||
|
getattr(self, button).Show(True) |
||||
|
self.Layout() |
||||
|
|
||||
|
|
||||
|
def _init_components(self): |
||||
|
self.cancel_button = self.button(i18n._('cancel'), wx.ID_CANCEL, event_id=events.WINDOW_CANCEL) |
||||
|
self.stop_button = self.button(i18n._('stop'), wx.ID_OK, event_id=events.WINDOW_STOP) |
||||
|
self.start_button = self.button(i18n._('start'), wx.ID_OK, event_id=int(events.WINDOW_START)) |
||||
|
self.close_button = self.button(i18n._("close"), wx.ID_OK, event_id=int(events.WINDOW_CLOSE)) |
||||
|
self.restart_button = self.button(i18n._('restart'), wx.ID_OK, event_id=int(events.WINDOW_RESTART)) |
||||
|
self.edit_button = self.button(i18n._('edit'), wx.ID_OK, event_id=int(events.WINDOW_EDIT)) |
||||
|
|
||||
|
self.progress_bar = wx.Gauge(self, range=100) |
||||
|
|
||||
|
self.buttons = [self.cancel_button, self.start_button, |
||||
|
self.stop_button, self.close_button, |
||||
|
self.restart_button, self.edit_button] |
||||
|
|
||||
|
if self.buildSpec['disable_stop_button']: |
||||
|
self.stop_button.Enable(False) |
||||
|
|
||||
|
|
||||
|
def _do_layout(self): |
||||
|
self.stop_button.Hide() |
||||
|
self.restart_button.Hide() |
||||
|
|
||||
|
v_sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
h_sizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
|
||||
|
h_sizer.Add(self.progress_bar, 1, |
||||
|
wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20) |
||||
|
self.progress_bar.Hide() |
||||
|
|
||||
|
h_sizer.AddStretchSpacer(1) |
||||
|
h_sizer.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) |
||||
|
h_sizer.Add(self.start_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) |
||||
|
h_sizer.Add(self.stop_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) |
||||
|
|
||||
|
v_sizer.AddStretchSpacer(1) |
||||
|
v_sizer.Add(h_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) |
||||
|
|
||||
|
h_sizer.Add(self.edit_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10) |
||||
|
h_sizer.Add(self.restart_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10) |
||||
|
h_sizer.Add(self.close_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) |
||||
|
self.edit_button.Hide() |
||||
|
self.restart_button.Hide() |
||||
|
self.close_button.Hide() |
||||
|
|
||||
|
v_sizer.AddStretchSpacer(1) |
||||
|
self.SetSizer(v_sizer) |
||||
|
|
||||
|
def button(self, label=None, style=None, event_id=-1): |
||||
|
return wx.Button( |
||||
|
parent=self, |
||||
|
id=event_id, |
||||
|
size=(90, 24), |
||||
|
label=i18n._(label), |
||||
|
style=style) |
||||
|
|
||||
|
def dispatch_click(self, event): |
||||
|
pub.send_message(event.GetId()) |
||||
|
|
||||
|
def hide_all_buttons(self): |
||||
|
for button in self.buttons: |
||||
|
button.Hide() |
@ -0,0 +1,101 @@ |
|||||
|
''' |
||||
|
Created on Dec 23, 2013 |
||||
|
|
||||
|
@author: Chris |
||||
|
''' |
||||
|
|
||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import imageutil, image_repository |
||||
|
from gooey.gui.util import wx_util |
||||
|
from gooey.gui.three_to_four import bitmapFromImage |
||||
|
from gooey.util.functional import getin |
||||
|
|
||||
|
PAD_SIZE = 10 |
||||
|
|
||||
|
|
||||
|
class FrameHeader(wx.Panel): |
||||
|
def __init__(self, parent, buildSpec, **kwargs): |
||||
|
wx.Panel.__init__(self, parent, **kwargs) |
||||
|
self.SetDoubleBuffered(True) |
||||
|
|
||||
|
self.buildSpec = buildSpec |
||||
|
|
||||
|
self._header = None |
||||
|
self._subheader = None |
||||
|
self.settings_img = None |
||||
|
self.running_img = None |
||||
|
self.check_mark = None |
||||
|
self.error_symbol = None |
||||
|
|
||||
|
self.images = [] |
||||
|
|
||||
|
self.layoutComponent() |
||||
|
|
||||
|
|
||||
|
def setTitle(self, title): |
||||
|
self._header.SetLabel(title) |
||||
|
|
||||
|
def setSubtitle(self, subtitle): |
||||
|
self._subheader.SetLabel(subtitle) |
||||
|
|
||||
|
def setImage(self, image): |
||||
|
for img in self.images: |
||||
|
img.Show(False) |
||||
|
getattr(self, image).Show(True) |
||||
|
self.Layout() |
||||
|
|
||||
|
|
||||
|
def layoutComponent(self): |
||||
|
|
||||
|
self.SetBackgroundColour(self.buildSpec['header_bg_color']) |
||||
|
self.SetSize((30, self.buildSpec['header_height'])) |
||||
|
self.SetMinSize((120, self.buildSpec['header_height'])) |
||||
|
|
||||
|
self._header = wx_util.h1(self, label=self.buildSpec['program_name']) |
||||
|
self._subheader = wx.StaticText(self, label=self.buildSpec['program_description']) |
||||
|
|
||||
|
images = self.buildSpec['images'] |
||||
|
targetHeight = self.buildSpec['header_height'] - 10 |
||||
|
self.settings_img = self._load_image(images['configIcon'], targetHeight) |
||||
|
self.running_img = self._load_image(images['runningIcon'], targetHeight) |
||||
|
self.check_mark = self._load_image(images['successIcon'], targetHeight) |
||||
|
self.error_symbol = self._load_image(images['errorIcon'], targetHeight) |
||||
|
|
||||
|
self.images = [ |
||||
|
self.settings_img, |
||||
|
self.running_img, |
||||
|
self.check_mark, |
||||
|
self.error_symbol |
||||
|
] |
||||
|
|
||||
|
vsizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
sizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
headings_sizer = self.build_heading_sizer() |
||||
|
sizer.Add(headings_sizer, 1, |
||||
|
wx.ALIGN_LEFT | wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND | wx.LEFT, |
||||
|
PAD_SIZE) |
||||
|
sizer.Add(self.settings_img, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) |
||||
|
sizer.Add(self.running_img, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) |
||||
|
sizer.Add(self.check_mark, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) |
||||
|
sizer.Add(self.error_symbol, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.RIGHT, PAD_SIZE) |
||||
|
self.running_img.Hide() |
||||
|
self.check_mark.Hide() |
||||
|
self.error_symbol.Hide() |
||||
|
vsizer.Add(sizer, 1, wx.EXPAND) |
||||
|
self.SetSizer(vsizer) |
||||
|
|
||||
|
|
||||
|
def _load_image(self, imgPath, targetHeight): |
||||
|
rawImage = imageutil.loadImage(imgPath) |
||||
|
sizedImage = imageutil.resizeImage(rawImage, targetHeight) |
||||
|
return imageutil.wrapBitmap(sizedImage, self) |
||||
|
|
||||
|
|
||||
|
def build_heading_sizer(self): |
||||
|
sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
sizer.AddStretchSpacer(1) |
||||
|
sizer.Add(self._header, 0) |
||||
|
sizer.Add(self._subheader, 0) |
||||
|
sizer.AddStretchSpacer(1) |
||||
|
return sizer |
@ -0,0 +1,17 @@ |
|||||
|
import wx |
||||
|
|
||||
|
|
||||
|
def standard_layout(title, subtitle, widget): |
||||
|
container = wx.BoxSizer(wx.VERTICAL) |
||||
|
|
||||
|
container.Add(title) |
||||
|
container.AddSpacer(2) |
||||
|
|
||||
|
if subtitle: |
||||
|
container.Add(subtitle, 1, wx.EXPAND) |
||||
|
container.AddSpacer(2) |
||||
|
else: |
||||
|
container.AddStretchSpacer(1) |
||||
|
|
||||
|
container.Add(widget, 0, wx.EXPAND) |
||||
|
return container |
@ -0,0 +1,46 @@ |
|||||
|
""" |
||||
|
All of the dialogs used throughout Gooey |
||||
|
""" |
||||
|
from collections import namedtuple |
||||
|
|
||||
|
import wx |
||||
|
|
||||
|
from gooey.gui.lang.i18n import _ |
||||
|
|
||||
|
|
||||
|
# These don't seem to be specified anywhere in WX for some reason |
||||
|
DialogConstants = namedtuple('DialogConstants', 'YES NO')(5103, 5104) |
||||
|
|
||||
|
|
||||
|
def showDialog(title, content, style): |
||||
|
dlg = wx.MessageDialog(None, content, title, style) |
||||
|
result = dlg.ShowModal() |
||||
|
dlg.Destroy() |
||||
|
return result |
||||
|
|
||||
|
|
||||
|
def missingArgsDialog(): |
||||
|
showDialog(_('error_title'), _('error_required_fields'), wx.ICON_ERROR) |
||||
|
|
||||
|
|
||||
|
def validationFailure(): |
||||
|
showDialog(_('error_title'), _('validation_failed'), wx.ICON_WARNING) |
||||
|
|
||||
|
|
||||
|
def showSuccess(): |
||||
|
showDialog(_('execution_finished'), _('success_message'), wx.ICON_INFORMATION) |
||||
|
|
||||
|
|
||||
|
def showFailure(): |
||||
|
showDialog(_('execution_finished'), _('uh_oh'), wx.ICON_ERROR) |
||||
|
|
||||
|
|
||||
|
def confirmExit(): |
||||
|
result = showDialog(_('sure_you_want_to_exit'), _('close_program'), wx.YES_NO | wx.ICON_INFORMATION) |
||||
|
return result == DialogConstants.YES |
||||
|
|
||||
|
|
||||
|
def confirmForceStop(): |
||||
|
result = showDialog(_('stop_task'), _('sure_you_want_to_stop'), wx.YES_NO | wx.ICON_WARNING) |
||||
|
return result == DialogConstants.YES |
||||
|
|
@ -0,0 +1,86 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui.util import wx_util |
||||
|
|
||||
|
|
||||
|
class Sidebar(wx.Panel): |
||||
|
""" |
||||
|
Sidebar handles the show/hide logic so that it mirrors the functionality |
||||
|
of the wx.Notebook class (which wants to control everything) |
||||
|
""" |
||||
|
def __init__(self, parent, buildSpec, configPanels, *args, **kwargs): |
||||
|
super(Sidebar, self).__init__(parent, *args, **kwargs) |
||||
|
self._parent = parent |
||||
|
self.buildSpec = buildSpec |
||||
|
self.configPanels = configPanels |
||||
|
self.activeSelection = 0 |
||||
|
self.options = list(self.buildSpec['widgets'].keys()) |
||||
|
self.leftPanel = wx.Panel(self) |
||||
|
self.label = wx_util.h1(self.leftPanel, self.buildSpec.get('sidebar_title')) |
||||
|
self.listbox = wx.ListBox(self.leftPanel, -1, choices=self.options) |
||||
|
self.Bind(wx.EVT_LISTBOX, self.swapConfigPanels, self.listbox) |
||||
|
self.layoutComponent() |
||||
|
self.listbox.SetSelection(0) |
||||
|
|
||||
|
|
||||
|
def getSelectedGroup(self): |
||||
|
"""Return the currently active 'group' i.e. the root SubParser """ |
||||
|
return self.options[self.activeSelection] |
||||
|
|
||||
|
|
||||
|
def getActiveConfig(self): |
||||
|
"""Return the currently visible config screen""" |
||||
|
return self.configPanels[self.activeSelection] |
||||
|
|
||||
|
|
||||
|
def swapConfigPanels(self, event): |
||||
|
"""Hide/show configuration panels based on the currently selected |
||||
|
option in the sidebar """ |
||||
|
for id, panel in enumerate(self.configPanels): |
||||
|
panel.Hide() |
||||
|
self.activeSelection = event.Selection |
||||
|
self.configPanels[event.Selection].Show() |
||||
|
self._parent.Layout() |
||||
|
|
||||
|
|
||||
|
def layoutComponent(self): |
||||
|
left = self.layoutLeftSide() |
||||
|
|
||||
|
hsizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
hsizer.Add(left, 0, wx.EXPAND) |
||||
|
|
||||
|
if not self.buildSpec['tabbed_groups']: |
||||
|
# only add it for non-tabbed layouts as it looks |
||||
|
# weird against the tabbed ones |
||||
|
hsizer.Add(wx_util.vertical_rule(self), 0, wx.EXPAND) |
||||
|
|
||||
|
for body in self.configPanels: |
||||
|
body.Reparent(self) |
||||
|
hsizer.Add(body, 1, wx.EXPAND) |
||||
|
body.Hide() |
||||
|
self.configPanels[0].Show() |
||||
|
self.SetSizer(hsizer) |
||||
|
|
||||
|
if not self.buildSpec['show_sidebar']: |
||||
|
left.Show(False) |
||||
|
|
||||
|
self.Layout() |
||||
|
|
||||
|
|
||||
|
def layoutLeftSide(self): |
||||
|
self.leftPanel.SetBackgroundColour(self.buildSpec['sidebar_bg_color']) |
||||
|
self.leftPanel.SetSize((180, 0)) |
||||
|
self.leftPanel.SetMinSize((180, 0)) |
||||
|
|
||||
|
container = wx.BoxSizer(wx.VERTICAL) |
||||
|
container.AddSpacer(15) |
||||
|
container.Add(self.label, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 10) |
||||
|
container.AddSpacer(5) |
||||
|
|
||||
|
container.Add(self.listbox, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10) |
||||
|
container.AddSpacer(20) |
||||
|
self.leftPanel.SetSizer(container) |
||||
|
return self.leftPanel |
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,37 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import events |
||||
|
from gooey.gui.pubsub import pub |
||||
|
from gooey.gui.util import wx_util |
||||
|
|
||||
|
|
||||
|
class Tabbar(wx.Panel): |
||||
|
def __init__(self, parent, buildSpec, configPanels, *args, **kwargs): |
||||
|
super(Tabbar, self).__init__(parent, *args, **kwargs) |
||||
|
self._parent = parent |
||||
|
self.notebook = wx.Notebook(self, style=wx.BK_DEFAULT) |
||||
|
self.buildSpec = buildSpec |
||||
|
self.configPanels = configPanels |
||||
|
self.options = list(self.buildSpec['widgets'].keys()) |
||||
|
self.layoutComponent() |
||||
|
|
||||
|
|
||||
|
def layoutComponent(self): |
||||
|
for group, panel in zip(self.options, self.configPanels): |
||||
|
panel.Reparent( self.notebook) |
||||
|
self.notebook.AddPage(panel, group) |
||||
|
self.notebook.Layout() |
||||
|
|
||||
|
sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
sizer.Add(self.notebook, 1, wx.EXPAND) |
||||
|
self.SetSizer(sizer) |
||||
|
self.Layout() |
||||
|
|
||||
|
def getSelectedGroup(self): |
||||
|
return self.options[self.notebook.Selection] |
||||
|
|
||||
|
def getActiveConfig(self): |
||||
|
return self.configPanels[self.notebook.Selection] |
||||
|
|
||||
|
def show(self, b): |
||||
|
self.Show(b) |
@ -0,0 +1,12 @@ |
|||||
|
from __future__ import absolute_import |
||||
|
|
||||
|
from .textfield import TextField |
||||
|
from .textarea import Textarea |
||||
|
from .password import PasswordField |
||||
|
from .command import CommandField |
||||
|
from .dropdown import Dropdown |
||||
|
from .listbox import Listbox |
||||
|
from .checkbox import CheckBox |
||||
|
from .counter import Counter |
||||
|
from .radio_group import RadioGroup |
||||
|
from .choosers import * |
@ -0,0 +1,151 @@ |
|||||
|
import wx |
||||
|
# from rx.subjects import Subject |
||||
|
|
||||
|
from gooey.gui import formatters, events |
||||
|
from gooey.gui.pubsub import pub |
||||
|
from gooey.gui.util import wx_util |
||||
|
from gooey.util.functional import getin, ifPresent |
||||
|
from gooey.gui.validators import runValidator |
||||
|
|
||||
|
|
||||
|
class BaseWidget(wx.Panel): |
||||
|
widget_class = None |
||||
|
|
||||
|
def arrange(self, label, text): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def getWidget(self, parent, **options): |
||||
|
return self.widget_class(parent, **options) |
||||
|
|
||||
|
def connectSignal(self): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def getSublayout(self, *args, **kwargs): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def receiveChange(self, *args, **kwargs): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def dispatchChange(self, value, **kwargs): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def formatOutput(self, metatdata, value): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
|
||||
|
class TextContainer(BaseWidget): |
||||
|
widget_class = None |
||||
|
|
||||
|
def __init__(self, parent, widgetInfo, *args, **kwargs): |
||||
|
super(TextContainer, self).__init__(parent, *args, **kwargs) |
||||
|
|
||||
|
self.info = widgetInfo |
||||
|
self._id = widgetInfo['id'] |
||||
|
self._meta = widgetInfo['data'] |
||||
|
self._options = widgetInfo['options'] |
||||
|
self.label = wx.StaticText(self, label=widgetInfo['data']['display_name']) |
||||
|
self.help_text = wx.StaticText(self, label=widgetInfo['data']['help'] or '') |
||||
|
self.error = wx.StaticText(self, label='') |
||||
|
self.error.Hide() |
||||
|
self.widget = self.getWidget(self) |
||||
|
self.layout = self.arrange(*args, **kwargs) |
||||
|
self.SetSizer(self.layout) |
||||
|
self.Bind(wx.EVT_SIZE, self.onSize) |
||||
|
if self._meta['default']: |
||||
|
self.setValue(self._meta['default']) |
||||
|
|
||||
|
|
||||
|
def arrange(self, *args, **kwargs): |
||||
|
wx_util.make_bold(self.label) |
||||
|
wx_util.dark_grey(self.help_text) |
||||
|
wx_util.withColor(self.error, self._options['error_color']) |
||||
|
|
||||
|
self.help_text.SetMinSize((0,-1)) |
||||
|
|
||||
|
layout = wx.BoxSizer(wx.VERTICAL) |
||||
|
layout.Add(self.label) |
||||
|
layout.AddSpacer(2) |
||||
|
if self.help_text: |
||||
|
layout.Add(self.help_text, 1, wx.EXPAND) |
||||
|
layout.AddSpacer(2) |
||||
|
else: |
||||
|
layout.AddStretchSpacer(1) |
||||
|
layout.Add(self.getSublayout(), 0, wx.EXPAND) |
||||
|
layout.Add(self.error) |
||||
|
self.error.Hide() |
||||
|
return layout |
||||
|
|
||||
|
def getWidget(self, *args, **options): |
||||
|
return self.widget_class(*args, **options) |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def getSublayout(self, *args, **kwargs): |
||||
|
layout = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
layout.Add(self.widget, 1, wx.EXPAND) |
||||
|
return layout |
||||
|
|
||||
|
def onSize(self, event): |
||||
|
self.error.Wrap(self.GetSize().width) |
||||
|
event.Skip() |
||||
|
|
||||
|
def getValue(self): |
||||
|
userValidator = getin(self._options, ['validator', 'test'], 'True') |
||||
|
message = getin(self._options, ['validator', 'message'], '') |
||||
|
testFunc = eval('lambda user_input: bool(%s)' % userValidator) |
||||
|
satisfies = testFunc if self._meta['required'] else ifPresent(testFunc) |
||||
|
value = self.getWidgetValue() |
||||
|
|
||||
|
return { |
||||
|
'id': self._id, |
||||
|
'cmd': self.formatOutput(self._meta, value), |
||||
|
'rawValue': value, |
||||
|
'test': runValidator(satisfies, value), |
||||
|
'error': None if runValidator(satisfies, value) else message, |
||||
|
'clitype': 'positional' |
||||
|
if self._meta['required'] and not self._meta['commands'] |
||||
|
else 'optional' |
||||
|
} |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.SetValue(value) |
||||
|
|
||||
|
def setErrorString(self, message): |
||||
|
self.error.SetLabel(message) |
||||
|
self.error.Wrap(self.Size.width) |
||||
|
self.Layout() |
||||
|
|
||||
|
def showErrorString(self, b): |
||||
|
self.error.Wrap(self.Size.width) |
||||
|
self.error.Show(b) |
||||
|
|
||||
|
def setOptions(self, values): |
||||
|
return None |
||||
|
|
||||
|
def receiveChange(self, metatdata, value): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def dispatchChange(self, value, **kwargs): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
def formatOutput(self, metadata, value): |
||||
|
raise NotImplementedError |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
class BaseChooser(TextContainer): |
||||
|
""" Base Class for the Chooser widget types """ |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.setValue(value) |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.getValue() |
||||
|
|
||||
|
def formatOutput(self, metatdata, value): |
||||
|
return formatters.general(metatdata, value) |
@ -0,0 +1,83 @@ |
|||||
|
import wx |
||||
|
|
||||
|
import wx.lib.inspection |
||||
|
from gooey.gui.components.widgets.textfield import TextField |
||||
|
from gooey.gui.components.widgets.textarea import Textarea |
||||
|
from gooey.gui.components.widgets.password import PasswordField |
||||
|
from gooey.gui.components.widgets.choosers import FileChooser, FileSaver, DirChooser, DateChooser |
||||
|
from gooey.gui.components.widgets.dropdown import Dropdown |
||||
|
from gooey.gui.components.widgets.listbox import Listbox |
||||
|
|
||||
|
|
||||
|
class CCC(wx.Frame): |
||||
|
def __init__(self, *args, **kwargs): |
||||
|
super(CCC, self).__init__(*args, **kwargs) |
||||
|
x = {'data':{'choices':['one', 'tw'], 'display_name': 'foo', 'help': 'bar', 'commands': ['-t']}, 'id': 1, 'options': {}} |
||||
|
|
||||
|
a = TextField(self, x) |
||||
|
c = Textarea(self, x) |
||||
|
b = PasswordField(self, x) |
||||
|
d = DirChooser(self, x) |
||||
|
e = FileChooser(self,x) |
||||
|
f = FileSaver(self, x) |
||||
|
g = DateChooser(self, x) |
||||
|
h = Dropdown(self, x) |
||||
|
i = Listbox(self, x) |
||||
|
|
||||
|
s = wx.BoxSizer(wx.VERTICAL) |
||||
|
s.Add(a, 0, wx.EXPAND) |
||||
|
s.Add(b, 0, wx.EXPAND) |
||||
|
s.Add(c, 0, wx.EXPAND) |
||||
|
s.Add(d, 0, wx.EXPAND) |
||||
|
s.Add(e, 0, wx.EXPAND) |
||||
|
s.Add(f, 0, wx.EXPAND) |
||||
|
s.Add(g, 0, wx.EXPAND) |
||||
|
s.Add(h, 0, wx.EXPAND) |
||||
|
s.Add(i, 0, wx.EXPAND) |
||||
|
|
||||
|
self.SetSizer(s) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
app = wx.App() |
||||
|
|
||||
|
frame = CCC(None, -1, 'simple.py') |
||||
|
frame.Show() |
||||
|
|
||||
|
app.MainLoop() |
||||
|
|
||||
|
|
||||
|
# import wx |
||||
|
# |
||||
|
# class MainWindow(wx.Frame): |
||||
|
# def __init__(self, *args, **kwargs): |
||||
|
# wx.Frame.__init__(self, *args, **kwargs) |
||||
|
# |
||||
|
# self.panel = wx.Panel(self) |
||||
|
# |
||||
|
# self.label = wx.StaticText(self.panel, label="Label") |
||||
|
# self.text = wx.TextCtrl(self.panel) |
||||
|
# self.button = wx.Button(self.panel, label="Test") |
||||
|
# |
||||
|
# self.button1 = wx.Button(self.panel, label="ABOVE") |
||||
|
# self.button2 = wx.Button(self.panel, label="BELLOW") |
||||
|
# |
||||
|
# self.horizontal = wx.BoxSizer() |
||||
|
# self.horizontal.Add(self.label, flag=wx.CENTER) |
||||
|
# self.horizontal.Add(self.text, proportion=1, flag=wx.CENTER) |
||||
|
# self.horizontal.Add(self.button, flag=wx.CENTER) |
||||
|
# |
||||
|
# self.vertical = wx.BoxSizer(wx.VERTICAL) |
||||
|
# self.vertical.Add(self.button1, flag=wx.EXPAND) |
||||
|
# self.vertical.Add(self.horizontal, proportion=1, flag=wx.EXPAND) |
||||
|
# self.vertical.Add(self.button2, flag=wx.EXPAND) |
||||
|
# |
||||
|
# self.panel.SetSizerAndFit(self.vertical) |
||||
|
# self.Show() |
||||
|
# |
||||
|
# |
||||
|
# app = wx.App(False) |
||||
|
# win = MainWindow(None) |
||||
|
# app.MainLoop() |
@ -0,0 +1,50 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import formatters, events |
||||
|
from gooey.gui.components.widgets.bases import TextContainer |
||||
|
from gooey.gui.pubsub import pub |
||||
|
from gooey.gui.util import wx_util |
||||
|
from gooey.util.functional import getin |
||||
|
|
||||
|
|
||||
|
class CheckBox(TextContainer): |
||||
|
|
||||
|
widget_class = wx.CheckBox |
||||
|
|
||||
|
def arrange(self, *args, **kwargs): |
||||
|
wx_util.make_bold(self.label) |
||||
|
wx_util.dark_grey(self.help_text) |
||||
|
wx_util.withColor(self.error, self._options['error_color']) |
||||
|
self.error.Hide() |
||||
|
|
||||
|
self.help_text.SetMinSize((0,-1)) |
||||
|
|
||||
|
layout = wx.BoxSizer(wx.VERTICAL) |
||||
|
layout.Add(self.label) |
||||
|
layout.AddSpacer(2) |
||||
|
layout.AddStretchSpacer(1) |
||||
|
if self.help_text: |
||||
|
hsizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
hsizer.Add(self.widget, 0) |
||||
|
hsizer.Add(self.help_text, 1) |
||||
|
layout.Add(hsizer, 1, wx.EXPAND) |
||||
|
layout.AddSpacer(2) |
||||
|
else: |
||||
|
layout.Add(self.widget, 0, wx.EXPAND) |
||||
|
layout.AddStretchSpacer(1) |
||||
|
return layout |
||||
|
|
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.SetValue(value) |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.GetValue() |
||||
|
|
||||
|
|
||||
|
def formatOutput(self, metatdata, value): |
||||
|
return formatters.checkbox(metatdata, value) |
||||
|
|
||||
|
|
||||
|
def hideInput(self): |
||||
|
self.widget.Hide() |
@ -0,0 +1,35 @@ |
|||||
|
from gooey.gui.components.widgets import core |
||||
|
from gooey.gui.components.widgets.bases import TextContainer, BaseChooser |
||||
|
|
||||
|
|
||||
|
__ALL__ = [ |
||||
|
'FileChooser', |
||||
|
'FileSaver', |
||||
|
'DirChooser', |
||||
|
'DateChooser' |
||||
|
] |
||||
|
|
||||
|
class FileChooser(BaseChooser): |
||||
|
# todo: allow wildcard from argparse |
||||
|
widget_class = core.FileChooser |
||||
|
|
||||
|
|
||||
|
class MultiFileChooser(BaseChooser): |
||||
|
# todo: allow wildcard from argparse |
||||
|
widget_class = core.MultiFileChooser |
||||
|
|
||||
|
|
||||
|
class FileSaver(BaseChooser): |
||||
|
# todo: allow wildcard |
||||
|
widget_class = core.FileSaver |
||||
|
|
||||
|
|
||||
|
class DirChooser(BaseChooser): |
||||
|
# todo: allow wildcard |
||||
|
widget_class = core.DirChooser |
||||
|
|
||||
|
|
||||
|
class DateChooser(BaseChooser): |
||||
|
# todo: allow wildcard |
||||
|
widget_class = core.DateChooser |
||||
|
|
@ -0,0 +1,8 @@ |
|||||
|
from gooey.gui.components.widgets.textfield import TextField |
||||
|
|
||||
|
|
||||
|
|
||||
|
__ALL__ = ('CommandField',) |
||||
|
|
||||
|
class CommandField(TextField): |
||||
|
pass |
@ -0,0 +1,2 @@ |
|||||
|
from . chooser import Chooser, FileChooser, FileSaver, DirChooser, DateChooser, MultiFileChooser |
||||
|
from . text_input import PasswordInput, MultilineTextInput, TextInput |
@ -0,0 +1,102 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui.components.widgets.core.text_input import TextInput |
||||
|
from gooey.gui.components.widgets.dialogs.calender_dialog import CalendarDlg |
||||
|
from gooey.util.functional import merge |
||||
|
|
||||
|
|
||||
|
class Chooser(wx.Panel): |
||||
|
""" |
||||
|
Base 'Chooser' type. |
||||
|
|
||||
|
Launches a Dialog box that allows the user to pick files, directories, |
||||
|
dates, etc.. and places the result into a TextInput in the UI |
||||
|
""" |
||||
|
|
||||
|
def __init__(self, parent, *args, **kwargs): |
||||
|
super(Chooser, self).__init__(parent) |
||||
|
buttonLabel = kwargs.pop('label', 'Browse') |
||||
|
self.widget = TextInput(self, *args, **kwargs) |
||||
|
self.button = wx.Button(self, label=buttonLabel) |
||||
|
self.button.Bind(wx.EVT_BUTTON, self.spawnDialog) |
||||
|
self.layout() |
||||
|
|
||||
|
|
||||
|
def layout(self): |
||||
|
layout = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
layout.Add(self.widget, 1, wx.EXPAND | wx.TOP, 2) |
||||
|
layout.Add(self.button, 0, wx.LEFT, 10) |
||||
|
|
||||
|
v = wx.BoxSizer(wx.VERTICAL) |
||||
|
v.Add(layout, 1, wx.EXPAND, wx.TOP, 1) |
||||
|
self.SetSizer(v) |
||||
|
|
||||
|
|
||||
|
def spawnDialog(self, event): |
||||
|
fd = self.getDialog() |
||||
|
if fd.ShowModal() == wx.ID_CANCEL: |
||||
|
return |
||||
|
self.processResult(self.getResult(fd)) |
||||
|
|
||||
|
|
||||
|
def getDialog(self): |
||||
|
return wx.FileDialog(self, 'Open File') |
||||
|
|
||||
|
def getResult(self, dialog): |
||||
|
return dialog.GetPath() |
||||
|
|
||||
|
|
||||
|
def processResult(self, result): |
||||
|
self.setValue(result) |
||||
|
|
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.setValue(value) |
||||
|
|
||||
|
def getValue(self): |
||||
|
return self.widget.getValue() |
||||
|
|
||||
|
|
||||
|
|
||||
|
class FileChooser(Chooser): |
||||
|
""" Retrieve an existing file from the system """ |
||||
|
def getDialog(self): |
||||
|
return wx.FileDialog(self, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) |
||||
|
|
||||
|
|
||||
|
class MultiFileChooser(Chooser): |
||||
|
""" Retrieve an multiple files from the system """ |
||||
|
def getDialog(self): |
||||
|
return wx.FileDialog(self, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) |
||||
|
|
||||
|
|
||||
|
class FileSaver(Chooser): |
||||
|
""" Specify the path to save a new file """ |
||||
|
def getDialog(self): |
||||
|
return wx.FileDialog( |
||||
|
self, |
||||
|
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, |
||||
|
defaultFile="Enter Filename" |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class DirChooser(Chooser): |
||||
|
""" Retrieve a path to the supplied directory """ |
||||
|
def getDialog(self): |
||||
|
return wx.DirDialog(self) |
||||
|
|
||||
|
|
||||
|
class DateChooser(Chooser): |
||||
|
""" Launches a date picker which returns and ISO Date """ |
||||
|
def __init__(self, *args, **kwargs): |
||||
|
defaults = {'label': 'Choose Date'} |
||||
|
super(DateChooser, self).__init__(*args, **merge(kwargs, defaults)) |
||||
|
|
||||
|
|
||||
|
def getDialog(self): |
||||
|
return CalendarDlg(self) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,43 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui.util.filedrop import FileDrop |
||||
|
from gooey.util.functional import merge |
||||
|
|
||||
|
|
||||
|
class TextInput(wx.Panel): |
||||
|
def __init__(self, parent, *args, **kwargs): |
||||
|
super(TextInput, self).__init__(parent) |
||||
|
self.widget = wx.TextCtrl(self, *args, **kwargs) |
||||
|
dt = FileDrop(self.widget) |
||||
|
self.widget.SetDropTarget(dt) |
||||
|
self.widget.SetMinSize((0, -1)) |
||||
|
self.widget.SetDoubleBuffered(True) |
||||
|
self.widget.AppendText('') |
||||
|
self.layout() |
||||
|
|
||||
|
|
||||
|
def layout(self): |
||||
|
sizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
sizer.Add(self.widget, 0, wx.EXPAND) |
||||
|
self.SetSizer(sizer) |
||||
|
|
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.Clear() |
||||
|
self.widget.AppendText(str(value)) |
||||
|
self.widget.SetInsertionPoint(0) |
||||
|
|
||||
|
|
||||
|
def getValue(self): |
||||
|
return self.widget.GetValue() |
||||
|
|
||||
|
|
||||
|
|
||||
|
def PasswordInput(_, parent, *args, **kwargs): |
||||
|
style = {'style': wx.TE_PASSWORD} |
||||
|
return TextInput(parent, *args, **merge(kwargs, style)) |
||||
|
|
||||
|
|
||||
|
def MultilineTextInput(_, parent, *args, **kwargs): |
||||
|
style = {'style': wx.TE_MULTILINE} |
||||
|
return TextInput(parent, *args, **merge(kwargs, style)) |
@ -0,0 +1,16 @@ |
|||||
|
from gooey.gui.components.widgets.dropdown import Dropdown |
||||
|
|
||||
|
from gooey.gui import formatters |
||||
|
|
||||
|
|
||||
|
class Counter(Dropdown): |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.SetSelection(value) |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.GetValue() |
||||
|
|
||||
|
|
||||
|
def formatOutput(self, metadata, value): |
||||
|
return formatters.counter(metadata, value) |
@ -0,0 +1,50 @@ |
|||||
|
__author__ = 'Chris' |
||||
|
|
||||
|
import wx |
||||
|
|
||||
|
from gooey.gui.util import wx_util |
||||
|
|
||||
|
from gooey.gui.three_to_four import Classes, Constants |
||||
|
|
||||
|
|
||||
|
class CalendarDlg(wx.Dialog): |
||||
|
def __init__(self, parent): |
||||
|
wx.Dialog.__init__(self, parent) |
||||
|
|
||||
|
self.SetBackgroundColour('#ffffff') |
||||
|
|
||||
|
self.ok_button = wx.Button(self, wx.ID_OK, label='Ok') |
||||
|
self.datepicker = Classes.DatePickerCtrl(self, style=Constants.WX_DP_DROPDOWN) |
||||
|
|
||||
|
vertical_container = wx.BoxSizer(wx.VERTICAL) |
||||
|
vertical_container.AddSpacer(10) |
||||
|
vertical_container.Add(wx_util.h1(self, label='Select a Date'), 0, wx.LEFT | wx.RIGHT, 15) |
||||
|
vertical_container.AddSpacer(10) |
||||
|
vertical_container.Add(self.datepicker, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 15) |
||||
|
|
||||
|
vertical_container.AddSpacer(10) |
||||
|
button_sizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
button_sizer.AddStretchSpacer(1) |
||||
|
button_sizer.Add(self.ok_button, 0) |
||||
|
|
||||
|
vertical_container.Add(button_sizer, 0, wx.LEFT | wx.RIGHT, 15) |
||||
|
vertical_container.AddSpacer(20) |
||||
|
self.SetSizerAndFit(vertical_container) |
||||
|
|
||||
|
self.Bind(wx.EVT_BUTTON, self.OnOkButton, self.ok_button) |
||||
|
|
||||
|
def OnOkButton(self, event): |
||||
|
self.EndModal(wx.ID_OK) |
||||
|
event.Skip() |
||||
|
|
||||
|
def OnCancellButton(self, event): |
||||
|
try: |
||||
|
return None |
||||
|
except: |
||||
|
self.Close() |
||||
|
|
||||
|
def GetPath(self): |
||||
|
return self.datepicker.GetValue().FormatISODate() |
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,35 @@ |
|||||
|
from gooey.gui.components.widgets.bases import TextContainer |
||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import formatters |
||||
|
|
||||
|
|
||||
|
class Dropdown(TextContainer): |
||||
|
|
||||
|
def getWidget(self, parent, *args, **options): |
||||
|
default = 'Select Option' |
||||
|
return wx.ComboBox( |
||||
|
parent=parent, |
||||
|
id=-1, |
||||
|
value=default, |
||||
|
choices=[default] + self._meta['choices'], |
||||
|
style=wx.CB_DROPDOWN) |
||||
|
|
||||
|
def setOptions(self, options): |
||||
|
prevSelection = self.widget.GetSelection() |
||||
|
self.widget.Clear() |
||||
|
for option in ['Select Option'] + options: |
||||
|
self.widget.Append(option) |
||||
|
self.widget.SetSelection(0) |
||||
|
|
||||
|
|
||||
|
def setValue(self, value): |
||||
|
## +1 to offset the default placeholder value |
||||
|
index = self._meta['choices'].index(value) + 1 |
||||
|
self.widget.SetSelection(index) |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.GetValue() |
||||
|
|
||||
|
def formatOutput(self, metadata, value): |
||||
|
return formatters.dropdown(metadata, value) |
@ -0,0 +1,30 @@ |
|||||
|
from gooey.gui.components.widgets.bases import TextContainer |
||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import formatters |
||||
|
|
||||
|
|
||||
|
class Listbox(TextContainer): |
||||
|
|
||||
|
def getWidget(self, parent, *args, **options): |
||||
|
default = 'Select Option' |
||||
|
return wx.ListBox( |
||||
|
parent=parent, |
||||
|
choices=self._meta['choices'], |
||||
|
size=(-1,60), |
||||
|
style=wx.LB_MULTIPLE |
||||
|
) |
||||
|
|
||||
|
def setOptions(self, options): |
||||
|
self.widget.SetChoices() |
||||
|
|
||||
|
def setValue(self, values): |
||||
|
for string in values: |
||||
|
self.widget.SetStringSelection(string) |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return [self.widget.GetString(index) |
||||
|
for index in self.widget.GetSelections()] |
||||
|
|
||||
|
def formatOutput(self, metadata, value): |
||||
|
return formatters.listbox(metadata, value) |
@ -0,0 +1,12 @@ |
|||||
|
from gooey.gui.components.widgets.core.text_input import PasswordInput |
||||
|
from gooey.gui.components.widgets.textfield import TextField |
||||
|
|
||||
|
|
||||
|
__ALL__ = ('PasswordField',) |
||||
|
|
||||
|
class PasswordField(TextField): |
||||
|
widget_class = PasswordInput |
||||
|
|
||||
|
def __init__(self, *args, **kwargs): |
||||
|
super(PasswordField, self).__init__(*args, **kwargs) |
||||
|
|
@ -0,0 +1,132 @@ |
|||||
|
import wx |
||||
|
from gooey.gui.components.widgets.bases import BaseWidget |
||||
|
from gooey.gui.util import wx_util |
||||
|
from gooey.gui.components.widgets import CheckBox |
||||
|
from gooey.util.functional import getin, findfirst, merge |
||||
|
|
||||
|
|
||||
|
class RadioGroup(BaseWidget): |
||||
|
|
||||
|
def __init__(self, parent, widgetInfo, *args, **kwargs): |
||||
|
super(RadioGroup, self).__init__(parent, *args, **kwargs) |
||||
|
self._parent = parent |
||||
|
self.info = widgetInfo |
||||
|
self._id = widgetInfo['id'] |
||||
|
self.widgetInfo = widgetInfo |
||||
|
self.error = wx.StaticText(self, label='') |
||||
|
self.radioButtons = self.createRadioButtons() |
||||
|
self.selected = None |
||||
|
self.widgets = self.createWidgets() |
||||
|
self.arrange() |
||||
|
self.applyStyleRules() |
||||
|
|
||||
|
for button in self.radioButtons: |
||||
|
button.Bind(wx.EVT_LEFT_DOWN, self.handleButtonClick) |
||||
|
|
||||
|
initialSelection = getin(self.info, ['options', 'initial_selection'], None) |
||||
|
if initialSelection is not None: |
||||
|
self.selected = self.radioButtons[initialSelection] |
||||
|
self.selected.SetValue(True) |
||||
|
self.handleImplicitCheck() |
||||
|
|
||||
|
|
||||
|
def getValue(self): |
||||
|
for button, widget in zip(self.radioButtons, self.widgets): |
||||
|
if button.GetValue(): # is Checked |
||||
|
return merge(widget.getValue(), {'id': self._id}) |
||||
|
else: |
||||
|
# just return the first widget's value even though it's |
||||
|
# not active so that the expected interface is satisfied |
||||
|
return self.widgets[0].getValue() |
||||
|
|
||||
|
def setErrorString(self, message): |
||||
|
for button, widget in zip(self.radioButtons, self.widgets): |
||||
|
if button.GetValue(): # is Checked |
||||
|
widget.setErrorString(message) |
||||
|
self.Layout() |
||||
|
|
||||
|
def showErrorString(self, b): |
||||
|
for button, widget in zip(self.radioButtons, self.widgets): |
||||
|
if button.GetValue(): # is Checked |
||||
|
widget.showErrorString(b) |
||||
|
|
||||
|
|
||||
|
def arrange(self, *args, **kwargs): |
||||
|
title = getin(self.widgetInfo, ['options', 'title'], 'Choose One') |
||||
|
if getin(self.widgetInfo, ['options', 'show_border'], False): |
||||
|
boxDetails = wx.StaticBox(self, -1, title) |
||||
|
boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL) |
||||
|
else: |
||||
|
boxSizer = wx.BoxSizer(wx.VERTICAL) |
||||
|
boxSizer.AddSpacer(10) |
||||
|
boxSizer.Add(wx_util.h1(self, title), 0) |
||||
|
|
||||
|
for btn, widget in zip(self.radioButtons, self.widgets): |
||||
|
sizer = wx.BoxSizer(wx.HORIZONTAL) |
||||
|
sizer.Add(btn,0, wx.RIGHT, 4) |
||||
|
sizer.Add(widget, 1, wx.EXPAND) |
||||
|
boxSizer.Add(sizer, 1, wx.ALL | wx.EXPAND, 5) |
||||
|
self.SetSizer(boxSizer) |
||||
|
|
||||
|
|
||||
|
def handleButtonClick(self, event): |
||||
|
if not self.widgetInfo['required']: |
||||
|
# if it's not a required group, allow deselection of the |
||||
|
# current option if the user clicks on a selected radio button |
||||
|
if event.EventObject.Id == getattr(self.selected, 'Id', None)\ |
||||
|
and event.EventObject.GetValue(): |
||||
|
event.EventObject.SetValue(False) |
||||
|
else: |
||||
|
self.selected = event.EventObject |
||||
|
self.selected.SetValue(True) |
||||
|
self.applyStyleRules() |
||||
|
self.handleImplicitCheck() |
||||
|
|
||||
|
def applyStyleRules(self): |
||||
|
""" |
||||
|
Conditionally disabled/enables form fields based on the current |
||||
|
section in the radio group |
||||
|
""" |
||||
|
for button, widget in zip(self.radioButtons, self.widgets): |
||||
|
if isinstance(widget, CheckBox): |
||||
|
widget.hideInput() |
||||
|
if not button.GetValue(): # not checked |
||||
|
widget.widget.Disable() |
||||
|
else: |
||||
|
widget.widget.Enable() |
||||
|
|
||||
|
def handleImplicitCheck(self): |
||||
|
""" |
||||
|
Checkboxes are hidden when inside of a RadioGroup as a selection of |
||||
|
the Radio button is an implicit selection of the Checkbox. As such, we have |
||||
|
to manually "check" any checkbox as needed. |
||||
|
""" |
||||
|
for button, widget in zip(self.radioButtons, self.widgets): |
||||
|
if isinstance(widget, CheckBox): |
||||
|
if button.GetValue(): # checked |
||||
|
widget.setValue(True) |
||||
|
else: |
||||
|
widget.setValue(False) |
||||
|
|
||||
|
|
||||
|
def createRadioButtons(self): |
||||
|
# button groups in wx are statefully determined via a style flag |
||||
|
# on the first button (what???). All button instances are part of the |
||||
|
# same group until a new button is created with the style flag RG_GROUP |
||||
|
# https://wxpython.org/Phoenix/docs/html/wx.RadioButton.html |
||||
|
# (What???) |
||||
|
firstButton = wx.RadioButton(self, style=wx.RB_GROUP) |
||||
|
firstButton.SetValue(False) |
||||
|
buttons = [firstButton] |
||||
|
|
||||
|
for _ in getin(self.widgetInfo, ['data','widgets'], [])[1:]: |
||||
|
buttons.append(wx.RadioButton(self)) |
||||
|
return buttons |
||||
|
|
||||
|
def createWidgets(self): |
||||
|
""" |
||||
|
Instantiate the Gooey Widgets that are used within the RadioGroup |
||||
|
""" |
||||
|
from gooey.gui.components import widgets |
||||
|
return [getattr(widgets, item['type'])(self, item) |
||||
|
for item in getin(self.widgetInfo, ['data', 'widgets'], [])] |
@ -0,0 +1,6 @@ |
|||||
|
from gooey.gui.components.widgets.core.text_input import MultilineTextInput |
||||
|
from gooey.gui.components.widgets.textfield import TextField |
||||
|
|
||||
|
|
||||
|
class Textarea(TextField): |
||||
|
widget_class = MultilineTextInput |
@ -0,0 +1,20 @@ |
|||||
|
import wx |
||||
|
from gooey.gui.components.widgets.bases import TextContainer |
||||
|
from gooey.gui import formatters, events |
||||
|
from gooey.gui.components.widgets.core.text_input import TextInput |
||||
|
from gooey.gui.pubsub import pub |
||||
|
from gooey.util.functional import getin |
||||
|
|
||||
|
|
||||
|
class TextField(TextContainer): |
||||
|
widget_class = TextInput |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.getValue() |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.setValue(str(value)) |
||||
|
|
||||
|
def formatOutput(self, metatdata, value): |
||||
|
return formatters.general(metatdata, value) |
||||
|
|
Write
Preview
Loading…
Cancel
Save