From 5e3ef77c36fc1381e800aa669e741195832bdeb0 Mon Sep 17 00:00:00 2001 From: Nathan Richard Date: Sat, 29 Dec 2018 17:21:57 +0100 Subject: [PATCH] Handling of style-oriented ascii espace sequences. Using the RichTextCtrl widget we are able to use advanced formatting in the console pannel. The formatting information is extracted from the text stream by searching for terminal escape sequences. The sequence values are taken from the colored package, which becomes a lazy dependency (imported only when the feature is activated). The previous implementation (using the TextCtrl widget) has been extracted into a separate class. The client code chooses between the basic or the rich class based on the richtext_controls configuration parameter. The default is to use the previous implementation based on TextCtrl. --- gooey/gui/application.py | 1 + gooey/gui/components/console.py | 9 +- .../components/widgets/basictextconsole.py | 5 ++ .../gui/components/widgets/richtextconsole.py | 85 +++++++++++++++++++ gooey/python_bindings/config_generator.py | 1 + requirements.txt | 1 + setup.py | 3 +- 7 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 gooey/gui/components/widgets/basictextconsole.py create mode 100644 gooey/gui/components/widgets/richtextconsole.py diff --git a/gooey/gui/application.py b/gooey/gui/application.py index 35d4b41..b486720 100644 --- a/gooey/gui/application.py +++ b/gooey/gui/application.py @@ -3,6 +3,7 @@ Main runner entry point for Gooey. ''' import wx +import wx.richtext # Need to be imported before the wx.App object is created. import wx.lib.inspection from gooey.gui.lang import i18n diff --git a/gooey/gui/components/console.py b/gooey/gui/components/console.py index d0a3d31..fdf4655 100644 --- a/gooey/gui/components/console.py +++ b/gooey/gui/components/console.py @@ -1,6 +1,7 @@ import wx from gooey.gui.lang import i18n +from .widgets.basictextconsole import BasicTextConsole class Console(wx.Panel): @@ -13,9 +14,11 @@ class Console(wx.Panel): 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 - ) + if buildSpec["richtext_controls"]: + from .widgets.richtextconsole import RichTextConsole + self.textbox = RichTextConsole(self) + else: + self.textbox = BasicTextConsole(self) self.defaultFont = self.textbox.GetFont() diff --git a/gooey/gui/components/widgets/basictextconsole.py b/gooey/gui/components/widgets/basictextconsole.py new file mode 100644 index 0000000..d2841f5 --- /dev/null +++ b/gooey/gui/components/widgets/basictextconsole.py @@ -0,0 +1,5 @@ +import wx + +class BasicTextConsole(wx.TextCtrl): + def __init__(self, parent): + super().__init__(parent, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH) diff --git a/gooey/gui/components/widgets/richtextconsole.py b/gooey/gui/components/widgets/richtextconsole.py new file mode 100644 index 0000000..52ae92e --- /dev/null +++ b/gooey/gui/components/widgets/richtextconsole.py @@ -0,0 +1,85 @@ +import wx +import wx.richtext +import colored + +kColorList = ["#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0", + "#808080", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff", "#000000", + "#00005f", "#000087", "#0000af", "#0000d7", "#0000ff", "#005f00", "#005f5f", "#005f87", "#005faf", + "#005fd7", "#005fff", "#008700", "#00875f", "#008787", "#0087af", "#0087d7", "#0087ff", "#00af00", + "#00af5f", "#00af87", "#00afaf", "#00afd7", "#00afff", "#00d700", "#00d75f", "#00d787", "#00d7af", + "#00d7d7", "#00d7ff", "#00ff00", "#00ff5f", "#00ff87", "#00ffaf", "#00ffd7", "#00ffff", "#5f0000", + "#5f005f", "#5f0087", "#5f00af", "#5f00d7", "#5f00ff", "#5f5f00", "#5f5f5f", "#5f5f87", "#5f5faf", + "#5f5fd7", "#5f5fff", "#5f8700", "#5f875f", "#5f8787", "#5f87af", "#5f87d7", "#5f87ff", "#5faf00", + "#5faf5f", "#5faf87", "#5fafaf", "#5fafd7", "#5fafff", "#5fd700", "#5fd75f", "#5fd787", "#5fd7af", + "#5fd7d7", "#5fd7ff", "#5fff00", "#5fff5f", "#5fff87", "#5fffaf", "#5fffd7", "#5fffff", "#870000", + "#87005f", "#870087", "#8700af", "#8700d7", "#8700ff", "#875f00", "#875f5f", "#875f87", "#875faf", + "#875fd7", "#875fff", "#878700", "#87875f", "#878787", "#8787af", "#8787d7", "#8787ff", "#87af00", + "#87af5f", "#87af87", "#87afaf", "#87afd7", "#87afff", "#87d700", "#87d75f", "#87d787", "#87d7af", + "#87d7d7", "#87d7ff", "#87ff00", "#87ff5f", "#87ff87", "#87ffaf", "#87ffd7", "#87ffff", "#af0000", + "#af005f", "#af0087", "#af00af", "#af00d7", "#af00ff", "#af5f00", "#af5f5f", "#af5f87", "#af5faf", + "#af5fd7", "#af5fff", "#af8700", "#af875f", "#af8787", "#af87af", "#af87d7", "#af87ff", "#afaf00", + "#afaf5f", "#afaf87", "#afafaf", "#afafd7", "#afafff", "#afd700", "#afd75f", "#afd787", "#afd7af", + "#afd7d7", "#afd7ff", "#afff00", "#afff5f", "#afff87", "#afffaf", "#afffd7", "#afffff", "#d70000", + "#d7005f", "#d70087", "#d700af", "#d700d7", "#d700ff", "#d75f00", "#d75f5f", "#d75f87", "#d75faf", + "#d75fd7", "#d75fff", "#d78700", "#d7875f", "#d78787", "#d787af", "#d787d7", "#d787ff", "#d7af00", + "#d7af5f", "#d7af87", "#d7afaf", "#d7afd7", "#d7afff", "#d7d700", "#d7d75f", "#d7d787", "#d7d7af", + "#d7d7d7", "#d7d7ff", "#d7ff00", "#d7ff5f", "#d7ff87", "#d7ffaf", "#d7ffd7", "#d7ffff", "#ff0000", + "#ff005f", "#ff0087", "#ff00af", "#ff00d7", "#ff00ff", "#ff5f00", "#ff5f5f", "#ff5f87", "#ff5faf", + "#ff5fd7", "#ff5fff", "#ff8700", "#ff875f", "#ff8787", "#ff87af", "#ff87d7", "#ff87ff", "#ffaf00", + "#ffaf5f", "#ffaf87", "#ffafaf", "#ffafd7", "#ffafff", "#ffd700", "#ffd75f", "#ffd787", "#ffd7af", + "#ffd7d7", "#ffd7ff", "#ffff00", "#ffff5f", "#ffff87", "#ffffaf", "#ffffd7", "#ffffff", "#080808", + "#121212", "#1c1c1c", "#262626", "#303030", "#3a3a3a", "#444444", "#4e4e4e", "#585858", "#626262", + "#6c6c6c", "#767676", "#808080", "#8a8a8a", "#949494", "#9e9e9e", "#a8a8a8", "#b2b2b2", "#bcbcbc", + "#c6c6c6", "#d0d0d0", "#dadada", "#e4e4e4", "#eeeeee"] + +class RichTextConsole(wx.richtext.RichTextCtrl): + """ + An advanced rich test console pannel supporting some Xterm control codes. + """ + + def __init__(self, parent): + super().__init__(parent, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY) + self.esc = colored.style.ESC + self.end = colored.style.END + self.noop = lambda *args, **kwargs: None + + self.actionsMap = dict() + + # Supported font altering actions + self.actionsMap[colored.style.BOLD] = self.BeginBold + self.actionsMap[colored.style.RES_BOLD] = self.EndBold + self.actionsMap[colored.style.UNDERLINED] = self.BeginUnderline + self.actionsMap[colored.style.RES_UNDERLINED] = self.EndUnderline + self.actionsMap[colored.style.RESET] = self.EndAllStyles + + # Actions for coloring text + for index, hex in enumerate(kColorList): + escSeq = '{}{}{}'.format(colored.fore.ESC, index, colored.fore.END) + wxcolor = wx.Colour(int(hex[1:3],16), int(hex[3:5],16), int(hex[5:],16), alpha=wx.ALPHA_OPAQUE) + # NB : we use a default parameter to force the evaluation of the binding + self.actionsMap[escSeq] = lambda bindedColor=wxcolor: self.BeginTextColour(bindedColor) + + def AppendText(self, content): + """ + wx method overriden to capture the terminal control character and translate them into wx styles. + Complexity : o(len(content)) + """ + unprocIndex = 0 + while True: + # Invariant : unprocIndex is the starting index of the unprocessed part of the buffer + escPos = content.find(self.esc, unprocIndex) + if escPos == -1: + break + # Invariant : found an escape sequence starting at escPos + # NB : we flush all the characters before the escape sequence, if any + if content[unprocIndex:escPos]: + self.WriteText(content[unprocIndex:escPos]) + endEsc = content.find(self.end, escPos) + if endEsc == -1: + unprocIndex = escPos + len(self.esc) + continue + # Invariant : end of sequence has been found + self.actionsMap.get(content[escPos:endEsc+1], self.noop)() + unprocIndex = endEsc + 1 + # Invariant : unprocessed end of buffer is escape-free, ready to be printed + self.WriteText(content[unprocIndex:]) diff --git a/gooey/python_bindings/config_generator.py b/gooey/python_bindings/config_generator.py index 28e3b42..bdecfa5 100644 --- a/gooey/python_bindings/config_generator.py +++ b/gooey/python_bindings/config_generator.py @@ -89,6 +89,7 @@ def create_from_parser(parser, source_path, **kwargs): 'terminal_font_family': kwargs.get('terminal_font_family', None), 'terminal_font_weight': kwargs.get('terminal_font_weight', None), 'terminal_font_size': kwargs.get('terminal_font_size', None), + 'richtext_controls': kwargs.get('richtext_controls', False), 'error_color': kwargs.get('error_color', '#ea7878') } diff --git a/requirements.txt b/requirements.txt index 62674fb..f50e308 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ RXPY>=0.1.0 wxpython>=4.0.0b1 Pillow>=4.3.0 psutil>=5.4.2 +colored>=1.3.93 diff --git a/setup.py b/setup.py index 54f2bd6..0ce8cd2 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ version = '1.0.2' deps = [ 'Pillow>=4.3.0', - 'psutil>=5.4.2' + 'psutil>=5.4.2', + 'colored>=1.3.93' ] if sys.version[0] == '3':