mirror of https://github.com/chriskiehl/Gooey.git
28 changed files with 1481 additions and 0 deletions
Split 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