Browse Source

rewrite core components

pull/240/head
chriskiehl 7 years ago
parent
commit
8f313fc90b
28 changed files with 1481 additions and 0 deletions
  1. 0
      gooey/gui/components/__init__.py
  2. 191
      gooey/gui/components/config.py
  3. 82
      gooey/gui/components/console.py
  4. 134
      gooey/gui/components/footer.py
  5. 101
      gooey/gui/components/header.py
  6. 0
      gooey/gui/components/layouts/__init__.py
  7. 17
      gooey/gui/components/layouts/layouts.py
  8. 46
      gooey/gui/components/modals.py
  9. 86
      gooey/gui/components/sidebar.py
  10. 37
      gooey/gui/components/tabbar.py
  11. 12
      gooey/gui/components/widgets/__init__.py
  12. 151
      gooey/gui/components/widgets/bases.py
  13. 83
      gooey/gui/components/widgets/beep_boop.py
  14. 50
      gooey/gui/components/widgets/checkbox.py
  15. 35
      gooey/gui/components/widgets/choosers.py
  16. 8
      gooey/gui/components/widgets/command.py
  17. 2
      gooey/gui/components/widgets/core/__init__.py
  18. 102
      gooey/gui/components/widgets/core/chooser.py
  19. 43
      gooey/gui/components/widgets/core/text_input.py
  20. 16
      gooey/gui/components/widgets/counter.py
  21. 0
      gooey/gui/components/widgets/dialogs/__init__.py
  22. 50
      gooey/gui/components/widgets/dialogs/calender_dialog.py
  23. 35
      gooey/gui/components/widgets/dropdown.py
  24. 30
      gooey/gui/components/widgets/listbox.py
  25. 12
      gooey/gui/components/widgets/password.py
  26. 132
      gooey/gui/components/widgets/radio_group.py
  27. 6
      gooey/gui/components/widgets/textarea.py
  28. 20
      gooey/gui/components/widgets/textfield.py

0
gooey/gui/components/__init__.py

191
gooey/gui/components/config.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()

82
gooey/gui/components/console.py

@ -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)

134
gooey/gui/components/footer.py

@ -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()

101
gooey/gui/components/header.py

@ -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
gooey/gui/components/layouts/__init__.py

17
gooey/gui/components/layouts/layouts.py

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

46
gooey/gui/components/modals.py

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

86
gooey/gui/components/sidebar.py

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

37
gooey/gui/components/tabbar.py

@ -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)

12
gooey/gui/components/widgets/__init__.py

@ -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 *

151
gooey/gui/components/widgets/bases.py

@ -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)

83
gooey/gui/components/widgets/beep_boop.py

@ -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()

50
gooey/gui/components/widgets/checkbox.py

@ -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()

35
gooey/gui/components/widgets/choosers.py

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

8
gooey/gui/components/widgets/command.py

@ -0,0 +1,8 @@
from gooey.gui.components.widgets.textfield import TextField
__ALL__ = ('CommandField',)
class CommandField(TextField):
pass

2
gooey/gui/components/widgets/core/__init__.py

@ -0,0 +1,2 @@
from . chooser import Chooser, FileChooser, FileSaver, DirChooser, DateChooser, MultiFileChooser
from . text_input import PasswordInput, MultilineTextInput, TextInput

102
gooey/gui/components/widgets/core/chooser.py

@ -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)

43
gooey/gui/components/widgets/core/text_input.py

@ -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))

16
gooey/gui/components/widgets/counter.py

@ -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
gooey/gui/components/widgets/dialogs/__init__.py

50
gooey/gui/components/widgets/dialogs/calender_dialog.py

@ -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()

35
gooey/gui/components/widgets/dropdown.py

@ -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)

30
gooey/gui/components/widgets/listbox.py

@ -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)

12
gooey/gui/components/widgets/password.py

@ -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)

132
gooey/gui/components/widgets/radio_group.py

@ -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'], [])]

6
gooey/gui/components/widgets/textarea.py

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

20
gooey/gui/components/widgets/textfield.py

@ -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)
Loading…
Cancel
Save