Пример #1
0
    def __init__(self, app):

        wx.Frame.__init__(self, parent=None)

        # Build menus and bind callback
        self.build_menus(app)

        # Bind idle callback
        self.Bind(wx.EVT_IDLE, app.idle)

        # The main sizer for the application
        sizer = wx.BoxSizer(wx.VERTICAL)
        version = '%s %s' % (koko.NAME, koko.VERSION)
        sizer.Add(wx.StaticText(self, label=version),
                  flag=wx.ALIGN_RIGHT | wx.ALL,
                  border=5)

        # Horizontal sizer that contains script, output, and canvases
        core = wx.BoxSizer(wx.HORIZONTAL)

        koko.IMPORT = ImportPanel(app, self)

        editor_panel = wx.Panel(self)
        editor_sizer = wx.BoxSizer(wx.VERTICAL)

        # Vertical sizer that contains the editor and the output panel
        koko.EDITOR = Editor(editor_panel, style=wx.NO_BORDER, size=(300, 400))
        koko.EDITOR.load_template()
        koko.EDITOR.bind_callbacks(app)

        editor_sizer.Add(koko.EDITOR, proportion=2, flag=wx.EXPAND)
        self.show_editor = lambda b: editor_sizer.ShowItems(b)

        self._output = Editor(editor_panel,
                              margins=False,
                              style=wx.NO_BORDER,
                              size=(300, 100))
        self._output.SetWrapStartIndent(4)
        self._output.SetReadOnly(True)
        self._output.SetCaretLineVisible(False)
        self._output.SetWrapMode(wx.stc.STC_WRAP_WORD)
        editor_sizer.Add(self._output,
                         proportion=1,
                         border=10,
                         flag=wx.EXPAND | wx.TOP)
        editor_panel.SetSizerAndFit(editor_sizer)

        self.show_editor = lambda b: editor_panel.Show(b)

        # Vertical / Horizontal sizer that contains the two canvases
        canvas_sizer = wx.BoxSizer(wx.VERTICAL)
        self.set_canvas_orientation = lambda o: canvas_sizer.SetOrientation(o)

        koko.CANVAS = Canvas(self, app, size=(300, 300))
        canvas_sizer.Add(koko.CANVAS, proportion=1, flag=wx.EXPAND)
        koko.GLCANVAS = GLCanvas(self, size=(300, 300))
        canvas_sizer.Add(koko.GLCANVAS, proportion=1, flag=wx.EXPAND)
        koko.GLCANVAS.Hide()

        core.Add(koko.IMPORT, flag=wx.EXPAND | wx.RIGHT, border=20)
        koko.IMPORT.Hide()
        core.Add(editor_panel,
                 proportion=4,
                 flag=wx.EXPAND | wx.RIGHT,
                 border=10)
        core.Add(canvas_sizer,
                 proportion=6,
                 flag=wx.EXPAND | wx.RIGHT,
                 border=10)
        koko.FAB = FabWorkflowPanel(self)
        core.Add(koko.FAB, proportion=3, flag=wx.EXPAND | wx.RIGHT, border=10)
        koko.FAB.Hide()

        sizer.Add(core, proportion=1, flag=wx.EXPAND)

        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._hint = wx.lib.stattext.GenStaticText(self)
        bottom_sizer.Add(self._hint, proportion=1)

        self._status = wx.lib.stattext.GenStaticText(self,
                                                     style=wx.ALIGN_RIGHT
                                                     | wx.ST_NO_AUTORESIZE)
        bottom_sizer.Add(self._status, proportion=1)

        sizer.Add(bottom_sizer, flag=wx.EXPAND | wx.ALL, border=10)

        self.SetSizerAndFit(sizer)
        APP_THEME.apply(self)

        self._status.SetForegroundColour(wx.Colour(100, 100, 100))

        # By default, hide the output panel
        self._output.Hide()
        self.Layout()
        """
        # Settings for screen recording
        self.SetClientSize((1280, 720))
        self.SetPosition((0,wx.DisplaySize()[1] - self.GetSize()[1]))
        """

        self.Maximize()
Пример #2
0
    def __init__(self, callbacks):
        wx.Frame.__init__(self, parent=None)
        
        # Bind menu callbacks        
        self.Bind(wx.EVT_MENU_HIGHLIGHT, self.OnMenuHighlight)
        self.Bind(wx.EVT_MENU_CLOSE, self.OnMenuClose)
        
        # Bind menu callbacks
        self.build_menus(callbacks)
        
        # Bind idle callback
        self.Bind(wx.EVT_IDLE, callbacks['idle'])
        
        #######################################################################

        
        #######################################################################
        # Create a canvas with a border and status text below
        canvasPanel = wx.Panel(self)
        canvasSizer = wx.BoxSizer(wx.VERTICAL)

        version = wx.StaticText(canvasPanel, label='%s %s ' % 
                                (koko.about.NAME, koko.about.VERSION))
        canvasSizer.Add(version, flag=wx.ALIGN_RIGHT)
        
        self.canvas = Canvas(canvasPanel, callbacks, size=(300, 300))
        koko.globals.CANVAS = weakref.proxy(self.canvas)
        
        canvasSizer.Add(self.canvas, proportion=2,
                        flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND,
                        border=20)
        
        # Add status text
        canvasSizer.Add((0,0), border=5, flag=wx.BOTTOM)
        self._status = wx.StaticText(
            canvasPanel, style=wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE)
        canvasSizer.Add(self._status, border=20, flag=wx.EXPAND | wx.RIGHT)
        canvasSizer.Add((0,0), border=15, flag=wx.BOTTOM)
        
        # Add output panel for error messages, etc.
        self._output = Editor(canvasPanel, margins=False, style=wx.NO_BORDER, size=(300, 100))
        self._output.SetReadOnly(True)
        self._output.SetCaretLineVisible(False)
        self._output.SetWrapMode(wx.stc.STC_WRAP_WORD)
        
        self.hide_output = lambda: (self._output.Hide(), canvasPanel.Layout())
        self.show_output = lambda: (self._output.Show(), canvasPanel.Layout())
        
        canvasSizer.Add(self._output, border=20, proportion=1,
                        flag=wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT)
        canvasPanel.SetSizerAndFit(canvasSizer)
        #######################################################################
        
        
        #######################################################################
        # Pack everything into the window        

        editorPanel = wx.Panel(self)
        editorSizer = wx.BoxSizer(wx.VERTICAL)
        
        editorSizer.Add((0,0), border=15, flag=wx.TOP)
        self.editor = Editor(editorPanel, style=wx.NO_BORDER, size=(300, 400))
        koko.globals.EDITOR = weakref.proxy(self.editor)
        
        self.editor.load_template()
        self.editor.bind_callbacks(callbacks)
        
        editorSizer.Add(self.editor, proportion=1, flag=wx.EXPAND)

        self._hint = wx.StaticText(editorPanel)
        editorSizer.Add(self._hint, border=5, flag=wx.ALL)

        editorPanel.SetSizerAndFit(editorSizer)
        
        #######################################################################
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(editorPanel, proportion=2, flag=wx.EXPAND)
        sizer.Add(canvasPanel, proportion=3, flag=wx.EXPAND)
        
        self.hide_script = lambda: (editorPanel.Hide(), self.Layout())
        self.show_script = lambda: (editorPanel.Show(), self.Layout())
        
        self.SetSizerAndFit(sizer)
        #######################################################################
        
        
        DARK_THEME.apply(self)
        DARK_THEME.apply(canvasPanel)
        DARK_THEME.apply(editorPanel)
        DARK_THEME.apply(version)
        DARK_THEME.apply(self._hint)
        DARK_THEME.apply(self._status)
        DARK_THEME.apply(self._output)
        DARK_THEME.apply(self.editor)
        
        self._status.SetForegroundColour(wx.Colour(100, 100, 100))

        self.hide_output()
        self.Maximize()
Пример #3
0
class MainFrame(wx.Frame):
    def __init__(self, app):

        wx.Frame.__init__(self, parent=None)

        # Build menus and bind callback
        self.build_menus(app)

        # Bind idle callback
        self.Bind(wx.EVT_IDLE, app.idle)

        # The main sizer for the application
        sizer = wx.BoxSizer(wx.VERTICAL)
        version = '%s %s' % (koko.NAME, koko.VERSION)
        sizer.Add(wx.StaticText(self, label=version),
                  flag=wx.ALIGN_RIGHT | wx.ALL,
                  border=5)

        # Horizontal sizer that contains script, output, and canvases
        core = wx.BoxSizer(wx.HORIZONTAL)

        koko.IMPORT = ImportPanel(app, self)

        editor_panel = wx.Panel(self)
        editor_sizer = wx.BoxSizer(wx.VERTICAL)

        # Vertical sizer that contains the editor and the output panel
        koko.EDITOR = Editor(editor_panel, style=wx.NO_BORDER, size=(300, 400))
        koko.EDITOR.load_template()
        koko.EDITOR.bind_callbacks(app)

        editor_sizer.Add(koko.EDITOR, proportion=2, flag=wx.EXPAND)
        self.show_editor = lambda b: editor_sizer.ShowItems(b)

        self._output = Editor(editor_panel,
                              margins=False,
                              style=wx.NO_BORDER,
                              size=(300, 100))
        self._output.SetWrapStartIndent(4)
        self._output.SetReadOnly(True)
        self._output.SetCaretLineVisible(False)
        self._output.SetWrapMode(wx.stc.STC_WRAP_WORD)
        editor_sizer.Add(self._output,
                         proportion=1,
                         border=10,
                         flag=wx.EXPAND | wx.TOP)
        editor_panel.SetSizerAndFit(editor_sizer)

        self.show_editor = lambda b: editor_panel.Show(b)

        # Vertical / Horizontal sizer that contains the two canvases
        canvas_sizer = wx.BoxSizer(wx.VERTICAL)
        self.set_canvas_orientation = lambda o: canvas_sizer.SetOrientation(o)

        koko.CANVAS = Canvas(self, app, size=(300, 300))
        canvas_sizer.Add(koko.CANVAS, proportion=1, flag=wx.EXPAND)
        koko.GLCANVAS = GLCanvas(self, size=(300, 300))
        canvas_sizer.Add(koko.GLCANVAS, proportion=1, flag=wx.EXPAND)
        koko.GLCANVAS.Hide()

        core.Add(koko.IMPORT, flag=wx.EXPAND | wx.RIGHT, border=20)
        koko.IMPORT.Hide()
        core.Add(editor_panel,
                 proportion=4,
                 flag=wx.EXPAND | wx.RIGHT,
                 border=10)
        core.Add(canvas_sizer,
                 proportion=6,
                 flag=wx.EXPAND | wx.RIGHT,
                 border=10)
        koko.FAB = FabWorkflowPanel(self)
        core.Add(koko.FAB, proportion=3, flag=wx.EXPAND | wx.RIGHT, border=10)
        koko.FAB.Hide()

        sizer.Add(core, proportion=1, flag=wx.EXPAND)

        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self._hint = wx.lib.stattext.GenStaticText(self)
        bottom_sizer.Add(self._hint, proportion=1)

        self._status = wx.lib.stattext.GenStaticText(self,
                                                     style=wx.ALIGN_RIGHT
                                                     | wx.ST_NO_AUTORESIZE)
        bottom_sizer.Add(self._status, proportion=1)

        sizer.Add(bottom_sizer, flag=wx.EXPAND | wx.ALL, border=10)

        self.SetSizerAndFit(sizer)
        APP_THEME.apply(self)

        self._status.SetForegroundColour(wx.Colour(100, 100, 100))

        # By default, hide the output panel
        self._output.Hide()
        self.Layout()
        """
        # Settings for screen recording
        self.SetClientSize((1280, 720))
        self.SetPosition((0,wx.DisplaySize()[1] - self.GetSize()[1]))
        """

        self.Maximize()

################################################################################

    def build_menus(self, app):
        '''Build a set of menus and attach associated callbacks.'''
        def attach(menu,
                   command,
                   callback,
                   shortcut='',
                   help='',
                   wxID=wx.ID_ANY,
                   attach_function=None):
            '''Helper function to add an item to a menu and bind the
               associated callback.'''

            if shortcut: menu_text = '%s\t%s' % (command, shortcut)
            else: menu_text = command

            if attach_function is None:
                attach_function = menu.Append

            item = attach_function(wxID, menu_text, help)
            self.Bind(wx.EVT_MENU, callback, item)

            return item

        menu_bar = wx.MenuBar()

        file = wx.Menu()
        attach(file, 'New', app.new, 'Ctrl+N', 'Start a new design', wx.ID_NEW)
        file.AppendSeparator()

        attach(file, 'Open', app.open, 'Ctrl+O', 'Open a design file',
               wx.ID_OPEN)
        attach(file, 'Reload', app.reload, 'Ctrl+R', 'Reload the current file')

        file.AppendSeparator()

        attach(file, 'Save', app.save, 'Ctrl+S', 'Save the current file',
               wx.ID_SAVE)
        attach(file, 'Save As', app.save_as, 'Ctrl+Shift+S',
               'Save the current file', wx.ID_SAVEAS)

        if not 'Darwin' in os.uname():
            file.AppendSeparator()

        attach(file, 'About', show_about_box, '', 'Display an About box',
               wx.ID_ABOUT)
        attach(file, 'Exit', app.exit, 'Ctrl+Q', 'Terminate the program',
               wx.ID_EXIT)

        menu_bar.Append(file, 'File')

        view = wx.Menu()
        output = attach(view,
                        'Show output',
                        self.show_output,
                        'Ctrl+D',
                        'Display errors in a separate pane',
                        attach_function=view.AppendCheckItem)
        script = attach(view,
                        'Show script',
                        self.show_script,
                        'Ctrl+T',
                        'Display Python script',
                        attach_function=view.AppendCheckItem)
        script.Toggle()

        view.AppendSeparator()
        attach(view,
               '2D',
               app.render_mode,
               attach_function=view.AppendRadioItem)
        attach(view,
               '3D',
               app.render_mode,
               attach_function=view.AppendRadioItem)
        attach(view,
               'Both',
               app.render_mode,
               attach_function=view.AppendRadioItem)

        view.AppendSeparator()

        shaders = wx.Menu()
        for s in ['Shaded', 'Wireframe', 'Normals', 'Subdivision']:
            m = shaders.AppendRadioItem(wx.ID_ANY, s)
            m.Enable(False)
            if s == 'Show shaded': m.Check(True)
            self.Bind(wx.EVT_MENU, lambda e: self.Refresh(), m)

        view.AppendSubMenu(shaders, 'Shading mode')

        view.AppendSeparator()

        attach(view,
               'Show axes',
               lambda e: self.Refresh(),
               'Display X, Y, and Z axes on frame',
               attach_function=view.AppendCheckItem)
        attach(view,
               'Show bounds',
               lambda e: self.Refresh(),
               'Display object bounds',
               attach_function=view.AppendCheckItem)
        attach(view,
               'Show traverses',
               lambda e: self.Refresh(),
               'Display toolpath traverses',
               attach_function=view.AppendCheckItem)

        view.AppendSeparator()

        attach(view, 'Re-render', app.mark_changed_design, 'Ctrl+Enter',
               'Re-render the output image')

        menu_bar.Append(view, 'View')

        export = wx.Menu()
        attach(export, '.png', app.export, help='Export to image file')
        attach(export, '.svg', app.export, help='Export to svg file')
        attach(export, '.stl', app.export, help='Export to stl file')
        attach(export,
               '.dot',
               app.export,
               help='Export to dot / Graphviz file')
        export.AppendSeparator()
        attach(export, '.asdf', app.export, help='Export to .asdf file')
        export.AppendSeparator()
        attach(export,
               'Show CAM panel',
               self.show_cam,
               'Ctrl+M',
               '',
               attach_function=export.AppendCheckItem)

        menu_bar.Append(export, 'Export')

        libraries = wx.Menu()

        attach(libraries,
               'koko.lib.shapes2d',
               app.show_library,
               help='2D Shapes library')
        attach(libraries,
               'koko.lib.shapes3d',
               app.show_library,
               help='3D Shapes library')
        attach(libraries,
               'koko.lib.text',
               app.show_library,
               help='Text library')

        menu_bar.Append(libraries, 'Libraries')

        self.SetMenuBar(menu_bar)

        self.Bind(wx.EVT_MENU_HIGHLIGHT, self.OnMenuHighlight)
        self.Bind(wx.EVT_MENU_CLOSE, self.OnMenuClose)

################################################################################

    @property
    def status(self):
        return self._status.GetLabel()

    @status.setter
    def status(self, value):
        wx.CallAfter(self._status.SetLabel, value)

    def set_status(self, value):
        self.status = value

################################################################################

    @property
    def hint(self):
        return self._hint.GetLabel()

    @hint.setter
    def hint(self, value):
        wx.CallAfter(self._hint.SetLabel, value)

    def set_hint(self, value):
        self.hint = value

################################################################################

    @property
    def output(self):
        return self._output.text

    @output.setter
    def output(self, value):
        self._output.text = value

    def set_output(self, value):
        self.output = value

################################################################################

    def OnMenuHighlight(self, event):
        '''Sets an appropriate hint based on the highlighted menu item.'''
        id = event.GetMenuId()
        item = self.GetMenuBar().FindItemById(id)
        if not item or not item.GetHelp():
            self.hint = ''
        else:
            self.hint = item.GetHelp()

    def OnMenuClose(self, event):
        '''Clears the menu item hint.'''
        self.hint = ''

    def show_output(self, evt):
        ''' Shows or hides the output panel. '''
        if type(evt) is not bool: evt = evt.Checked()

        if evt:
            if koko.EDITOR.IsShown():
                self._output.Show()
        else:
            self._output.Hide()
        self.Layout()

    def show_script(self, evt):
        ''' Shows or hides the script panel. '''
        if type(evt) is not bool: evt = evt.Checked()

        if evt:
            self.show_editor(True)
            self.set_canvas_orientation(wx.VERTICAL)
        else:
            self.show_editor(False)
            self.set_canvas_orientation(wx.HORIZONTAL)
        self.Layout()

    def show_cam(self, evt):
        if type(evt) is not bool: evt = evt.Checked()
        koko.FAB.Show(evt)
        self.Layout()

    def show_import(self, evt):
        if type(evt) is not bool: evt = evt.Checked()
        koko.IMPORT.Show(evt)
        self.Layout()

    def get_menu(self, *args):
        m = [m[0] for m in self.GetMenuBar().Menus if m[1] == args[0]][0]

        m = [m for m in m.GetMenuItems() if m.GetLabel() == args[1]][0]

        sub = m.GetSubMenu()
        if sub is None: return m
        else: return sub.GetMenuItems()
Пример #4
0
class MainFrame(wx.Frame):
    def __init__(self, callbacks):
        wx.Frame.__init__(self, parent=None)
        
        # Bind menu callbacks        
        self.Bind(wx.EVT_MENU_HIGHLIGHT, self.OnMenuHighlight)
        self.Bind(wx.EVT_MENU_CLOSE, self.OnMenuClose)
        
        # Bind menu callbacks
        self.build_menus(callbacks)
        
        # Bind idle callback
        self.Bind(wx.EVT_IDLE, callbacks['idle'])
        
        #######################################################################

        
        #######################################################################
        # Create a canvas with a border and status text below
        canvasPanel = wx.Panel(self)
        canvasSizer = wx.BoxSizer(wx.VERTICAL)

        version = wx.StaticText(canvasPanel, label='%s %s ' % 
                                (koko.about.NAME, koko.about.VERSION))
        canvasSizer.Add(version, flag=wx.ALIGN_RIGHT)
        
        self.canvas = Canvas(canvasPanel, callbacks, size=(300, 300))
        koko.globals.CANVAS = weakref.proxy(self.canvas)
        
        canvasSizer.Add(self.canvas, proportion=2,
                        flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND,
                        border=20)
        
        # Add status text
        canvasSizer.Add((0,0), border=5, flag=wx.BOTTOM)
        self._status = wx.StaticText(
            canvasPanel, style=wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE)
        canvasSizer.Add(self._status, border=20, flag=wx.EXPAND | wx.RIGHT)
        canvasSizer.Add((0,0), border=15, flag=wx.BOTTOM)
        
        # Add output panel for error messages, etc.
        self._output = Editor(canvasPanel, margins=False, style=wx.NO_BORDER, size=(300, 100))
        self._output.SetReadOnly(True)
        self._output.SetCaretLineVisible(False)
        self._output.SetWrapMode(wx.stc.STC_WRAP_WORD)
        
        self.hide_output = lambda: (self._output.Hide(), canvasPanel.Layout())
        self.show_output = lambda: (self._output.Show(), canvasPanel.Layout())
        
        canvasSizer.Add(self._output, border=20, proportion=1,
                        flag=wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT)
        canvasPanel.SetSizerAndFit(canvasSizer)
        #######################################################################
        
        
        #######################################################################
        # Pack everything into the window        

        editorPanel = wx.Panel(self)
        editorSizer = wx.BoxSizer(wx.VERTICAL)
        
        editorSizer.Add((0,0), border=15, flag=wx.TOP)
        self.editor = Editor(editorPanel, style=wx.NO_BORDER, size=(300, 400))
        koko.globals.EDITOR = weakref.proxy(self.editor)
        
        self.editor.load_template()
        self.editor.bind_callbacks(callbacks)
        
        editorSizer.Add(self.editor, proportion=1, flag=wx.EXPAND)

        self._hint = wx.StaticText(editorPanel)
        editorSizer.Add(self._hint, border=5, flag=wx.ALL)

        editorPanel.SetSizerAndFit(editorSizer)
        
        #######################################################################
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(editorPanel, proportion=2, flag=wx.EXPAND)
        sizer.Add(canvasPanel, proportion=3, flag=wx.EXPAND)
        
        self.hide_script = lambda: (editorPanel.Hide(), self.Layout())
        self.show_script = lambda: (editorPanel.Show(), self.Layout())
        
        self.SetSizerAndFit(sizer)
        #######################################################################
        
        
        DARK_THEME.apply(self)
        DARK_THEME.apply(canvasPanel)
        DARK_THEME.apply(editorPanel)
        DARK_THEME.apply(version)
        DARK_THEME.apply(self._hint)
        DARK_THEME.apply(self._status)
        DARK_THEME.apply(self._output)
        DARK_THEME.apply(self.editor)
        
        self._status.SetForegroundColour(wx.Colour(100, 100, 100))

        self.hide_output()
        self.Maximize()
################################################################################
        
    def build_menus(self, callbacks):
        '''Build a set of menus and attach associated callbacks.'''
    
        def attach(menu, command, shortcut='', help='', wxID=wx.ID_ANY,
                   attach_function = None):
            '''Helper function to add an item to a menu and bind the
               associated callback.'''
            if shortcut:
                menu_text = '%s\t%s' % (command, shortcut)
            else:
                menu_text = command

            if not command in callbacks.keys():
                raise KeyError('Could not find callback for menu item "%s"' %
                               command)
                               
            if attach_function is None:
                item = menu.Append(wxID, menu_text, help)
            else:
                item = attach_function(wxID, menu_text, help)
                
            self.Bind(wx.EVT_MENU, callbacks[command], item)
            
            return item
        
        menu_bar = wx.MenuBar()
        
        file = wx.Menu()
        attach(file, 'New', 'Ctrl+N', 'Start a new script design', wx.ID_NEW)
        attach(file, 'New PCB', '', 'Start a new pcb design')
        file.AppendSeparator()

        attach(file, 'Open', 'Ctrl+O', 'Open a design file', wx.ID_OPEN)
        attach(file, 'Reload', 'Ctrl+R', 'Reload the current file')
        
        file.AppendSeparator()
        
        attach(file, 'Save', 'Ctrl+S', 'Save the current file', wx.ID_SAVE)
        attach(file, 'Save As', 'Ctrl+Shift+S', 'Save the current file',
               wx.ID_SAVEAS)
        
        if not 'Darwin' in os.uname():
            file.AppendSeparator()
        
        attach(file, 'About', '', 'Display an About box', wx.ID_ABOUT)
        attach(file, 'Exit', 'Ctrl+Q', 'Terminate the program', wx.ID_EXIT)
        
        menu_bar.Append(file, 'File')
        
        view = wx.Menu()
        output = attach(view, 'Show output', '',
                        'Display errors in a separate pane',
                         attach_function=view.AppendCheckItem)
        script = attach(view, 'Show script', 'Ctrl+T',
                        'Display Python script',
                         attach_function=view.AppendCheckItem)
        script.Toggle()
        
        view.AppendSeparator()
        attach(view, 'Snap to bounds', '', 'Snap view to cad file bounds.')
        view.AppendSeparator()
        attach(view, 'Re-render', 'Ctrl+Enter', 'Re-render the output image')        
        
        menu_bar.Append(view, 'View')
        
        export = wx.Menu()
        attach(export, '.math', '', 'Export to .math file')
        attach(export, '.png', '', 'Export to image file')
        attach(export, '.svg', '', 'Export to svg file')
        attach(export, '.stl', '', 'Export to stl file')
        attach(export, '.dot', '', 'Export to dot / Graphviz file')
        export.AppendSeparator()
        self.start_fab = attach(export, 'Start fab modules', '',
                                'Load in fab modules')
        self.update_fab = attach(export, 'Update fab file', '',
                                 'Updates the file exported to the fab modules')
        self.update_fab.Enable(False)
        
        libraries = wx.Menu()
        attach(libraries, 'koko.lib.shapes', 'Standard shape library')
        attach(libraries, 'koko.lib.text', 'Standard text library')
        
    
        menu_bar.Append(export, 'Export')
        menu_bar.Append(libraries, 'Libraries')
        self.SetMenuBar(menu_bar)
        
################################################################################
    
    @property
    def status(self):
        return self._status.GetLabel()
    @status.setter
    def status(self, value):
        wx.CallAfter(self._status.SetLabel, value)
    
################################################################################
    
    @property
    def hint(self):
        return self._hint.GetLabel()
    @hint.setter
    def hint(self, value):
        wx.CallAfter(self._hint.SetLabel, value)
    
################################################################################
    
    @property
    def output(self):
        return self._output.text
    @output.setter
    def output(self, value):
        self._output.text = value
        
################################################################################

    def OnMenuHighlight(self, event):
        '''Sets an appropriate hint based on the highlighted menu item.'''
        id = event.GetMenuId()
        item = self.GetMenuBar().FindItemById(id)
        if not item or not item.GetHelp():
            self.hint = ''
        else:
            self.hint = item.GetHelp()
    
    def OnMenuClose(self, event):
        '''Clears the menu item hint.'''
        self.hint = ''