示例#1
0
    def _launch_menu(self, coords):
        """
        Callback function in response to the pressing of the Return key

        Launches a context menu based on the location of the mouse
        :param coords:
        :return:
        """
        # Configure the "static" menu entries -- they can't be static without seriously destroying readability
        # due to the Python version that is being used -.- so now it has to be not optimal until I find a better
        # solution
        p_menu = Menu(self._canvas)
        item = self._get_current_item((self._cache["x"], self._cache["y"]))
        updated_coords = self._canvas_to_screen((self._cache["x"], self._cache["y"]))
        if item is None:
            # No node is currently selected, create the general menu
            p_menu.add_command(label="Place Room", command=lambda: self.create_new_node((self._cache["x"], self._cache["y"])))
            p_menu.add_command(label="Delete All", command=lambda: self.delete_all())
            p_menu.tk_popup(updated_coords[0], updated_coords[1])
            return

        if self._is_node(item):
            # Create the node specific menu
            p_menu.add_command(label="Place Object", command=lambda: self._mark_object((self._cache["x"], self._cache["y"])))
            p_menu.add_command(label="Edit Room", command=lambda: self._selection_operation((self._cache["x"], self._cache["y"])))
            p_menu.add_command(label="Delete Room", command=lambda: self.delete_node(self._get_current_item((self._cache["x"], self._cache["y"]))))
            p_menu.add_command(label="Mark as start", command=lambda: self._mark_start_node(self._get_current_item((self._cache["x"], self._cache["y"]))))

            if self._is_object(item):
                # Launch the node menu as well as an an added option for selecting stuff to edit an object
                p_menu.add_command(label="Edit Object", command=lambda: self._edit_object(coords))
                p_menu.add_command(label="Delete Object", command=lambda: self._delete_object(self._get_current_item((self._cache["x"], self._cache["y"]))))
                p_menu.delete(0)
            p_menu.tk_popup(updated_coords[0], updated_coords[1])
            return

        if self._is_edge(item):
            p_menu.add_command(label="Edit Corridor", command=lambda: self._selection_operation((self._cache["x"], self._cache["y"])))
            p_menu.add_command(label="Delete Corridor", command=lambda: self.delete_edge(self._get_current_item((self._cache["x"], self._cache["y"]))))
            p_menu.tk_popup(updated_coords[0], updated_coords[1])
            return

        self._clear_cache(coords)
示例#2
0
class ShiftReduceApp(object):
    """
    A graphical tool for exploring the shift-reduce parser.  The tool
    displays the parser's stack and the remaining text, and allows the
    user to control the parser's operation.  In particular, the user
    can shift tokens onto the stack, and can perform reductions on the
    top elements of the stack.  A "step" button simply steps through
    the parsing process, performing the operations that
    ``nltk.parse.ShiftReduceParser`` would use.
    """
    def __init__(self, grammar, sent, trace=0):
        self._sent = sent
        self._parser = SteppingShiftReduceParser(grammar, trace)

        # Set up the main window.
        self._top = Tk()
        self._top.title('Shift Reduce Parser Application')

        # Animations.  animating_lock is a lock to prevent the demo
        # from performing new operations while it's animating.
        self._animating_lock = 0
        self._animate = IntVar(self._top)
        self._animate.set(10)  # = medium

        # The user can hide the grammar.
        self._show_grammar = IntVar(self._top)
        self._show_grammar.set(1)

        # Initialize fonts.
        self._init_fonts(self._top)

        # Set up key bindings.
        self._init_bindings()

        # Create the basic frames.
        self._init_menubar(self._top)
        self._init_buttons(self._top)
        self._init_feedback(self._top)
        self._init_grammar(self._top)
        self._init_canvas(self._top)

        # A popup menu for reducing.
        self._reduce_menu = Menu(self._canvas, tearoff=0)

        # Reset the demo, and set the feedback frame to empty.
        self.reset()
        self._lastoper1['text'] = ''

    #########################################
    ##  Initialization Helpers
    #########################################

    def _init_fonts(self, root):
        # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
        self._sysfont = tkFont.Font(font=Button()["font"])
        root.option_add("*Font", self._sysfont)

        # TWhat's our font size (default=same as sysfont)
        self._size = IntVar(root)
        self._size.set(self._sysfont.cget('size'))

        self._boldfont = tkFont.Font(family='helvetica',
                                     weight='bold',
                                     size=self._size.get())
        self._font = tkFont.Font(family='helvetica', size=self._size.get())

    def _init_grammar(self, parent):
        # Grammar view.
        self._prodframe = listframe = Frame(parent)
        self._prodframe.pack(fill='both', side='left', padx=2)
        self._prodlist_label = Label(self._prodframe,
                                     font=self._boldfont,
                                     text='Available Reductions')
        self._prodlist_label.pack()
        self._prodlist = Listbox(self._prodframe,
                                 selectmode='single',
                                 relief='groove',
                                 background='white',
                                 foreground='#909090',
                                 font=self._font,
                                 selectforeground='#004040',
                                 selectbackground='#c0f0c0')

        self._prodlist.pack(side='right', fill='both', expand=1)

        self._productions = list(self._parser.grammar().productions())
        for production in self._productions:
            self._prodlist.insert('end', (' %s' % production))
        self._prodlist.config(height=min(len(self._productions), 25))

        # Add a scrollbar if there are more than 25 productions.
        if 1:  #len(self._productions) > 25:
            listscroll = Scrollbar(self._prodframe, orient='vertical')
            self._prodlist.config(yscrollcommand=listscroll.set)
            listscroll.config(command=self._prodlist.yview)
            listscroll.pack(side='left', fill='y')

        # If they select a production, apply it.
        self._prodlist.bind('<<ListboxSelect>>', self._prodlist_select)

        # When they hover over a production, highlight it.
        self._hover = -1
        self._prodlist.bind('<Motion>', self._highlight_hover)
        self._prodlist.bind('<Leave>', self._clear_hover)

    def _init_bindings(self):
        # Quit
        self._top.bind('<Control-q>', self.destroy)
        self._top.bind('<Control-x>', self.destroy)
        self._top.bind('<Alt-q>', self.destroy)
        self._top.bind('<Alt-x>', self.destroy)

        # Ops (step, shift, reduce, undo)
        self._top.bind('<space>', self.step)
        self._top.bind('<s>', self.shift)
        self._top.bind('<Alt-s>', self.shift)
        self._top.bind('<Control-s>', self.shift)
        self._top.bind('<r>', self.reduce)
        self._top.bind('<Alt-r>', self.reduce)
        self._top.bind('<Control-r>', self.reduce)
        self._top.bind('<Delete>', self.reset)
        self._top.bind('<u>', self.undo)
        self._top.bind('<Alt-u>', self.undo)
        self._top.bind('<Control-u>', self.undo)
        self._top.bind('<Control-z>', self.undo)
        self._top.bind('<BackSpace>', self.undo)

        # Misc
        self._top.bind('<Control-p>', self.postscript)
        self._top.bind('<Control-h>', self.help)
        self._top.bind('<F1>', self.help)
        self._top.bind('<Control-g>', self.edit_grammar)
        self._top.bind('<Control-t>', self.edit_sentence)

        # Animation speed control
        self._top.bind('-', lambda e, a=self._animate: a.set(20))
        self._top.bind('=', lambda e, a=self._animate: a.set(10))
        self._top.bind('+', lambda e, a=self._animate: a.set(4))

    def _init_buttons(self, parent):
        # Set up the frames.
        self._buttonframe = buttonframe = Frame(parent)
        buttonframe.pack(fill='none', side='bottom')
        Button(
            buttonframe,
            text='Step',
            background='#90c0d0',
            foreground='black',
            command=self.step,
        ).pack(side='left')
        Button(buttonframe,
               text='Shift',
               underline=0,
               background='#90f090',
               foreground='black',
               command=self.shift).pack(side='left')
        Button(buttonframe,
               text='Reduce',
               underline=0,
               background='#90f090',
               foreground='black',
               command=self.reduce).pack(side='left')
        Button(buttonframe,
               text='Undo',
               underline=0,
               background='#f0a0a0',
               foreground='black',
               command=self.undo).pack(side='left')

    def _init_menubar(self, parent):
        menubar = Menu(parent)

        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_command(label='Reset Parser',
                             underline=0,
                             command=self.reset,
                             accelerator='Del')
        filemenu.add_command(label='Print to Postscript',
                             underline=0,
                             command=self.postscript,
                             accelerator='Ctrl-p')
        filemenu.add_command(label='Exit',
                             underline=1,
                             command=self.destroy,
                             accelerator='Ctrl-x')
        menubar.add_cascade(label='File', underline=0, menu=filemenu)

        editmenu = Menu(menubar, tearoff=0)
        editmenu.add_command(label='Edit Grammar',
                             underline=5,
                             command=self.edit_grammar,
                             accelerator='Ctrl-g')
        editmenu.add_command(label='Edit Text',
                             underline=5,
                             command=self.edit_sentence,
                             accelerator='Ctrl-t')
        menubar.add_cascade(label='Edit', underline=0, menu=editmenu)

        rulemenu = Menu(menubar, tearoff=0)
        rulemenu.add_command(label='Step',
                             underline=1,
                             command=self.step,
                             accelerator='Space')
        rulemenu.add_separator()
        rulemenu.add_command(label='Shift',
                             underline=0,
                             command=self.shift,
                             accelerator='Ctrl-s')
        rulemenu.add_command(label='Reduce',
                             underline=0,
                             command=self.reduce,
                             accelerator='Ctrl-r')
        rulemenu.add_separator()
        rulemenu.add_command(label='Undo',
                             underline=0,
                             command=self.undo,
                             accelerator='Ctrl-u')
        menubar.add_cascade(label='Apply', underline=0, menu=rulemenu)

        viewmenu = Menu(menubar, tearoff=0)
        viewmenu.add_checkbutton(label="Show Grammar",
                                 underline=0,
                                 variable=self._show_grammar,
                                 command=self._toggle_grammar)
        viewmenu.add_separator()
        viewmenu.add_radiobutton(label='Tiny',
                                 variable=self._size,
                                 underline=0,
                                 value=10,
                                 command=self.resize)
        viewmenu.add_radiobutton(label='Small',
                                 variable=self._size,
                                 underline=0,
                                 value=12,
                                 command=self.resize)
        viewmenu.add_radiobutton(label='Medium',
                                 variable=self._size,
                                 underline=0,
                                 value=14,
                                 command=self.resize)
        viewmenu.add_radiobutton(label='Large',
                                 variable=self._size,
                                 underline=0,
                                 value=18,
                                 command=self.resize)
        viewmenu.add_radiobutton(label='Huge',
                                 variable=self._size,
                                 underline=0,
                                 value=24,
                                 command=self.resize)
        menubar.add_cascade(label='View', underline=0, menu=viewmenu)

        animatemenu = Menu(menubar, tearoff=0)
        animatemenu.add_radiobutton(label="No Animation",
                                    underline=0,
                                    variable=self._animate,
                                    value=0)
        animatemenu.add_radiobutton(label="Slow Animation",
                                    underline=0,
                                    variable=self._animate,
                                    value=20,
                                    accelerator='-')
        animatemenu.add_radiobutton(label="Normal Animation",
                                    underline=0,
                                    variable=self._animate,
                                    value=10,
                                    accelerator='=')
        animatemenu.add_radiobutton(label="Fast Animation",
                                    underline=0,
                                    variable=self._animate,
                                    value=4,
                                    accelerator='+')
        menubar.add_cascade(label="Animate", underline=1, menu=animatemenu)

        helpmenu = Menu(menubar, tearoff=0)
        helpmenu.add_command(label='About', underline=0, command=self.about)
        helpmenu.add_command(label='Instructions',
                             underline=0,
                             command=self.help,
                             accelerator='F1')
        menubar.add_cascade(label='Help', underline=0, menu=helpmenu)

        parent.config(menu=menubar)

    def _init_feedback(self, parent):
        self._feedbackframe = feedbackframe = Frame(parent)
        feedbackframe.pack(fill='x', side='bottom', padx=3, pady=3)
        self._lastoper_label = Label(feedbackframe,
                                     text='Last Operation:',
                                     font=self._font)
        self._lastoper_label.pack(side='left')
        lastoperframe = Frame(feedbackframe, relief='sunken', border=1)
        lastoperframe.pack(fill='x', side='right', expand=1, padx=5)
        self._lastoper1 = Label(lastoperframe,
                                foreground='#007070',
                                background='#f0f0f0',
                                font=self._font)
        self._lastoper2 = Label(lastoperframe,
                                anchor='w',
                                width=30,
                                foreground='#004040',
                                background='#f0f0f0',
                                font=self._font)
        self._lastoper1.pack(side='left')
        self._lastoper2.pack(side='left', fill='x', expand=1)

    def _init_canvas(self, parent):
        self._cframe = CanvasFrame(parent,
                                   background='white',
                                   width=525,
                                   closeenough=10,
                                   border=2,
                                   relief='sunken')
        self._cframe.pack(expand=1, fill='both', side='top', pady=2)
        canvas = self._canvas = self._cframe.canvas()

        self._stackwidgets = []
        self._rtextwidgets = []
        self._titlebar = canvas.create_rectangle(0,
                                                 0,
                                                 0,
                                                 0,
                                                 fill='#c0f0f0',
                                                 outline='black')
        self._exprline = canvas.create_line(0, 0, 0, 0, dash='.')
        self._stacktop = canvas.create_line(0, 0, 0, 0, fill='#408080')
        size = self._size.get() + 4
        self._stacklabel = TextWidget(canvas,
                                      'Stack',
                                      color='#004040',
                                      font=self._boldfont)
        self._rtextlabel = TextWidget(canvas,
                                      'Remaining Text',
                                      color='#004040',
                                      font=self._boldfont)
        self._cframe.add_widget(self._stacklabel)
        self._cframe.add_widget(self._rtextlabel)

    #########################################
    ##  Main draw procedure
    #########################################

    def _redraw(self):
        scrollregion = self._canvas['scrollregion'].split()
        (cx1, cy1, cx2, cy2) = [int(c) for c in scrollregion]

        # Delete the old stack & rtext widgets.
        for stackwidget in self._stackwidgets:
            self._cframe.destroy_widget(stackwidget)
        self._stackwidgets = []
        for rtextwidget in self._rtextwidgets:
            self._cframe.destroy_widget(rtextwidget)
        self._rtextwidgets = []

        # Position the titlebar & exprline
        (x1, y1, x2, y2) = self._stacklabel.bbox()
        y = y2 - y1 + 10
        self._canvas.coords(self._titlebar, -5000, 0, 5000, y - 4)
        self._canvas.coords(self._exprline, 0, y * 2 - 10, 5000, y * 2 - 10)

        # Position the titlebar labels..
        (x1, y1, x2, y2) = self._stacklabel.bbox()
        self._stacklabel.move(5 - x1, 3 - y1)
        (x1, y1, x2, y2) = self._rtextlabel.bbox()
        self._rtextlabel.move(cx2 - x2 - 5, 3 - y1)

        # Draw the stack.
        stackx = 5
        for tok in self._parser.stack():
            if isinstance(tok, Tree):
                attribs = {
                    'tree_color': '#4080a0',
                    'tree_width': 2,
                    'node_font': self._boldfont,
                    'node_color': '#006060',
                    'leaf_color': '#006060',
                    'leaf_font': self._font
                }
                widget = tree_to_treesegment(self._canvas, tok, **attribs)
                widget.node()['color'] = '#000000'
            else:
                widget = TextWidget(self._canvas,
                                    tok,
                                    color='#000000',
                                    font=self._font)
            widget.bind_click(self._popup_reduce)
            self._stackwidgets.append(widget)
            self._cframe.add_widget(widget, stackx, y)
            stackx = widget.bbox()[2] + 10

        # Draw the remaining text.
        rtextwidth = 0
        for tok in self._parser.remaining_text():
            widget = TextWidget(self._canvas,
                                tok,
                                color='#000000',
                                font=self._font)
            self._rtextwidgets.append(widget)
            self._cframe.add_widget(widget, rtextwidth, y)
            rtextwidth = widget.bbox()[2] + 4

        # Allow enough room to shift the next token (for animations)
        if len(self._rtextwidgets) > 0:
            stackx += self._rtextwidgets[0].width()

        # Move the remaining text to the correct location (keep it
        # right-justified, when possible); and move the remaining text
        # label, if necessary.
        stackx = max(stackx, self._stacklabel.width() + 25)
        rlabelwidth = self._rtextlabel.width() + 10
        if stackx >= cx2 - max(rtextwidth, rlabelwidth):
            cx2 = stackx + max(rtextwidth, rlabelwidth)
        for rtextwidget in self._rtextwidgets:
            rtextwidget.move(4 + cx2 - rtextwidth, 0)
        self._rtextlabel.move(cx2 - self._rtextlabel.bbox()[2] - 5, 0)

        midx = (stackx + cx2 - max(rtextwidth, rlabelwidth)) / 2
        self._canvas.coords(self._stacktop, midx, 0, midx, 5000)
        (x1, y1, x2, y2) = self._stacklabel.bbox()

        # Set up binding to allow them to shift a token by dragging it.
        if len(self._rtextwidgets) > 0:

            def drag_shift(widget, midx=midx, self=self):
                if widget.bbox()[0] < midx: self.shift()
                else: self._redraw()

            self._rtextwidgets[0].bind_drag(drag_shift)
            self._rtextwidgets[0].bind_click(self.shift)

        # Draw the stack top.
        self._highlight_productions()

    def _draw_stack_top(self, widget):
        # hack..
        midx = widget.bbox()[2] + 50
        self._canvas.coords(self._stacktop, midx, 0, midx, 5000)

    def _highlight_productions(self):
        # Highlight the productions that can be reduced.
        self._prodlist.selection_clear(0, 'end')
        for prod in self._parser.reducible_productions():
            index = self._productions.index(prod)
            self._prodlist.selection_set(index)

    #########################################
    ##  Button Callbacks
    #########################################

    def destroy(self, *e):
        if self._top is None: return
        self._top.destroy()
        self._top = None

    def reset(self, *e):
        self._parser.initialize(self._sent)
        self._lastoper1['text'] = 'Reset App'
        self._lastoper2['text'] = ''
        self._redraw()

    def step(self, *e):
        if self.reduce(): return 1
        elif self.shift(): return 1
        else:
            if len(self._parser.parses()) > 0:
                self._lastoper1['text'] = 'Finished:'
                self._lastoper2['text'] = 'Success'
            else:
                self._lastoper1['text'] = 'Finished:'
                self._lastoper2['text'] = 'Failure'

    def shift(self, *e):
        if self._animating_lock: return
        if self._parser.shift():
            tok = self._parser.stack()[-1]
            self._lastoper1['text'] = 'Shift:'
            self._lastoper2['text'] = '%r' % tok
            if self._animate.get():
                self._animate_shift()
            else:
                self._redraw()
            return 1
        return 0

    def reduce(self, *e):
        if self._animating_lock: return
        production = self._parser.reduce()
        if production:
            self._lastoper1['text'] = 'Reduce:'
            self._lastoper2['text'] = '%s' % production
            if self._animate.get():
                self._animate_reduce()
            else:
                self._redraw()
        return production

    def undo(self, *e):
        if self._animating_lock: return
        if self._parser.undo():
            self._redraw()

    def postscript(self, *e):
        self._cframe.print_to_file()

    def mainloop(self, *args, **kwargs):
        """
        Enter the Tkinter mainloop.  This function must be called if
        this demo is created from a non-interactive program (e.g.
        from a secript); otherwise, the demo will close as soon as
        the script completes.
        """
        if in_idle(): return
        self._top.mainloop(*args, **kwargs)

    #########################################
    ##  Menubar callbacks
    #########################################

    def resize(self, size=None):
        if size is not None: self._size.set(size)
        size = self._size.get()
        self._font.configure(size=-(abs(size)))
        self._boldfont.configure(size=-(abs(size)))
        self._sysfont.configure(size=-(abs(size)))

        #self._stacklabel['font'] = ('helvetica', -size-4, 'bold')
        #self._rtextlabel['font'] = ('helvetica', -size-4, 'bold')
        #self._lastoper_label['font'] = ('helvetica', -size)
        #self._lastoper1['font'] = ('helvetica', -size)
        #self._lastoper2['font'] = ('helvetica', -size)
        #self._prodlist['font'] = ('helvetica', -size)
        #self._prodlist_label['font'] = ('helvetica', -size-2, 'bold')
        self._redraw()

    def help(self, *e):
        # The default font's not very legible; try using 'fixed' instead.
        try:
            ShowText(self._top,
                     'Help: Shift-Reduce Parser Application', (__doc__
                                                               or '').strip(),
                     width=75,
                     font='fixed')
        except:
            ShowText(self._top,
                     'Help: Shift-Reduce Parser Application', (__doc__
                                                               or '').strip(),
                     width=75)

    def about(self, *e):
        ABOUT = ("NLTK Shift-Reduce Parser Application\n" +
                 "Written by Edward Loper")
        TITLE = 'About: Shift-Reduce Parser Application'
        try:
            from tkMessageBox import Message
            Message(message=ABOUT, title=TITLE).show()
        except:
            ShowText(self._top, TITLE, ABOUT)

    def edit_grammar(self, *e):
        CFGEditor(self._top, self._parser.grammar(), self.set_grammar)

    def set_grammar(self, grammar):
        self._parser.set_grammar(grammar)
        self._productions = list(grammar.productions())
        self._prodlist.delete(0, 'end')
        for production in self._productions:
            self._prodlist.insert('end', (' %s' % production))

    def edit_sentence(self, *e):
        sentence = string.join(self._sent)
        title = 'Edit Text'
        instr = 'Enter a new sentence to parse.'
        EntryDialog(self._top, sentence, instr, self.set_sentence, title)

    def set_sentence(self, sent):
        self._sent = sent.split()  #[XX] use tagged?
        self.reset()

    #########################################
    ##  Reduce Production Selection
    #########################################

    def _toggle_grammar(self, *e):
        if self._show_grammar.get():
            self._prodframe.pack(fill='both',
                                 side='left',
                                 padx=2,
                                 after=self._feedbackframe)
            self._lastoper1['text'] = 'Show Grammar'
        else:
            self._prodframe.pack_forget()
            self._lastoper1['text'] = 'Hide Grammar'
        self._lastoper2['text'] = ''

    def _prodlist_select(self, event):
        selection = self._prodlist.curselection()
        if len(selection) != 1: return
        index = int(selection[0])
        production = self._parser.reduce(self._productions[index])
        if production:
            self._lastoper1['text'] = 'Reduce:'
            self._lastoper2['text'] = '%s' % production
            if self._animate.get():
                self._animate_reduce()
            else:
                self._redraw()
        else:
            # Reset the production selections.
            self._prodlist.selection_clear(0, 'end')
            for prod in self._parser.reducible_productions():
                index = self._productions.index(prod)
                self._prodlist.selection_set(index)

    def _popup_reduce(self, widget):
        # Remove old commands.
        productions = self._parser.reducible_productions()
        if len(productions) == 0: return

        self._reduce_menu.delete(0, 'end')
        for production in productions:
            self._reduce_menu.add_command(label=str(production),
                                          command=self.reduce)
        self._reduce_menu.post(self._canvas.winfo_pointerx(),
                               self._canvas.winfo_pointery())

    #########################################
    ##  Animations
    #########################################

    def _animate_shift(self):
        # What widget are we shifting?
        widget = self._rtextwidgets[0]

        # Where are we shifting from & to?
        right = widget.bbox()[0]
        if len(self._stackwidgets) == 0: left = 5
        else: left = self._stackwidgets[-1].bbox()[2] + 10

        # Start animating.
        dt = self._animate.get()
        dx = (left - right) * 1.0 / dt
        self._animate_shift_frame(dt, widget, dx)

    def _animate_shift_frame(self, frame, widget, dx):
        if frame > 0:
            self._animating_lock = 1
            widget.move(dx, 0)
            self._top.after(10, self._animate_shift_frame, frame - 1, widget,
                            dx)
        else:
            # but: stacktop??

            # Shift the widget to the stack.
            del self._rtextwidgets[0]
            self._stackwidgets.append(widget)
            self._animating_lock = 0

            # Display the available productions.
            self._draw_stack_top(widget)
            self._highlight_productions()

    def _animate_reduce(self):
        # What widgets are we shifting?
        numwidgets = len(self._parser.stack()[-1])  # number of children
        widgets = self._stackwidgets[-numwidgets:]

        # How far are we moving?
        if isinstance(widgets[0], TreeSegmentWidget):
            ydist = 15 + widgets[0].node().height()
        else:
            ydist = 15 + widgets[0].height()

        # Start animating.
        dt = self._animate.get()
        dy = ydist * 2.0 / dt
        self._animate_reduce_frame(dt / 2, widgets, dy)

    def _animate_reduce_frame(self, frame, widgets, dy):
        if frame > 0:
            self._animating_lock = 1
            for widget in widgets:
                widget.move(0, dy)
            self._top.after(10, self._animate_reduce_frame, frame - 1, widgets,
                            dy)
        else:
            del self._stackwidgets[-len(widgets):]
            for widget in widgets:
                self._cframe.remove_widget(widget)
            tok = self._parser.stack()[-1]
            if not isinstance(tok, Tree): raise ValueError()
            label = TextWidget(self._canvas,
                               str(tok.node),
                               color='#006060',
                               font=self._boldfont)
            widget = TreeSegmentWidget(self._canvas, label, widgets, width=2)
            (x1, y1, x2, y2) = self._stacklabel.bbox()
            y = y2 - y1 + 10
            if not self._stackwidgets: x = 5
            else: x = self._stackwidgets[-1].bbox()[2] + 10
            self._cframe.add_widget(widget, x, y)
            self._stackwidgets.append(widget)

            # Display the available productions.
            self._draw_stack_top(widget)
            self._highlight_productions()

            #             # Delete the old widgets..
            #             del self._stackwidgets[-len(widgets):]
            #             for widget in widgets:
            #                 self._cframe.destroy_widget(widget)
            #
            #             # Make a new one.
            #             tok = self._parser.stack()[-1]
            #             if isinstance(tok, Tree):
            #                 attribs = {'tree_color': '#4080a0', 'tree_width': 2,
            #                            'node_font': bold, 'node_color': '#006060',
            #                            'leaf_color': '#006060', 'leaf_font':self._font}
            #                 widget = tree_to_treesegment(self._canvas, tok.type(),
            #                                              **attribs)
            #                 widget.node()['color'] = '#000000'
            #             else:
            #                 widget = TextWidget(self._canvas, tok.type(),
            #                                     color='#000000', font=self._font)
            #             widget.bind_click(self._popup_reduce)
            #             (x1, y1, x2, y2) = self._stacklabel.bbox()
            #             y = y2-y1+10
            #             if not self._stackwidgets: x = 5
            #             else: x = self._stackwidgets[-1].bbox()[2] + 10
            #             self._cframe.add_widget(widget, x, y)
            #             self._stackwidgets.append(widget)

            #self._redraw()
            self._animating_lock = 0

    #########################################
    ##  Hovering.
    #########################################

    def _highlight_hover(self, event):
        # What production are we hovering over?
        index = self._prodlist.nearest(event.y)
        if self._hover == index: return

        # Clear any previous hover highlighting.
        self._clear_hover()

        # If the production corresponds to an available reduction,
        # highlight the stack.
        selection = [int(s) for s in self._prodlist.curselection()]
        if index in selection:
            rhslen = len(self._productions[index].rhs())
            for stackwidget in self._stackwidgets[-rhslen:]:
                if isinstance(stackwidget, TreeSegmentWidget):
                    stackwidget.node()['color'] = '#00a000'
                else:
                    stackwidget['color'] = '#00a000'

        # Remember what production we're hovering over.
        self._hover = index

    def _clear_hover(self, *event):
        # Clear any previous hover highlighting.
        if self._hover == -1: return
        self._hover = -1
        for stackwidget in self._stackwidgets:
            if isinstance(stackwidget, TreeSegmentWidget):
                stackwidget.node()['color'] = 'black'
            else:
                stackwidget['color'] = 'black'
示例#3
0
class ShiftReduceApp(object):
    """
    A graphical tool for exploring the shift-reduce parser.  The tool
    displays the parser's stack and the remaining text, and allows the
    user to control the parser's operation.  In particular, the user
    can shift tokens onto the stack, and can perform reductions on the
    top elements of the stack.  A "step" button simply steps through
    the parsing process, performing the operations that
    ``nltk.parse.ShiftReduceParser`` would use.
    """

    def __init__(self, grammar, sent, trace=0):
        self._sent = sent
        self._parser = SteppingShiftReduceParser(grammar, trace)

        # Set up the main window.
        self._top = Tk()
        self._top.title("Shift Reduce Parser Application")

        # Animations.  animating_lock is a lock to prevent the demo
        # from performing new operations while it's animating.
        self._animating_lock = 0
        self._animate = IntVar(self._top)
        self._animate.set(10)  # = medium

        # The user can hide the grammar.
        self._show_grammar = IntVar(self._top)
        self._show_grammar.set(1)

        # Initialize fonts.
        self._init_fonts(self._top)

        # Set up key bindings.
        self._init_bindings()

        # Create the basic frames.
        self._init_menubar(self._top)
        self._init_buttons(self._top)
        self._init_feedback(self._top)
        self._init_grammar(self._top)
        self._init_canvas(self._top)

        # A popup menu for reducing.
        self._reduce_menu = Menu(self._canvas, tearoff=0)

        # Reset the demo, and set the feedback frame to empty.
        self.reset()
        self._lastoper1["text"] = ""

    #########################################
    ##  Initialization Helpers
    #########################################

    def _init_fonts(self, root):
        # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
        self._sysfont = tkFont.Font(font=Button()["font"])
        root.option_add("*Font", self._sysfont)

        # TWhat's our font size (default=same as sysfont)
        self._size = IntVar(root)
        self._size.set(self._sysfont.cget("size"))

        self._boldfont = tkFont.Font(family="helvetica", weight="bold", size=self._size.get())
        self._font = tkFont.Font(family="helvetica", size=self._size.get())

    def _init_grammar(self, parent):
        # Grammar view.
        self._prodframe = listframe = Frame(parent)
        self._prodframe.pack(fill="both", side="left", padx=2)
        self._prodlist_label = Label(self._prodframe, font=self._boldfont, text="Available Reductions")
        self._prodlist_label.pack()
        self._prodlist = Listbox(
            self._prodframe,
            selectmode="single",
            relief="groove",
            background="white",
            foreground="#909090",
            font=self._font,
            selectforeground="#004040",
            selectbackground="#c0f0c0",
        )

        self._prodlist.pack(side="right", fill="both", expand=1)

        self._productions = list(self._parser.grammar().productions())
        for production in self._productions:
            self._prodlist.insert("end", (" %s" % production))
        self._prodlist.config(height=min(len(self._productions), 25))

        # Add a scrollbar if there are more than 25 productions.
        if 1:  # len(self._productions) > 25:
            listscroll = Scrollbar(self._prodframe, orient="vertical")
            self._prodlist.config(yscrollcommand=listscroll.set)
            listscroll.config(command=self._prodlist.yview)
            listscroll.pack(side="left", fill="y")

        # If they select a production, apply it.
        self._prodlist.bind("<<ListboxSelect>>", self._prodlist_select)

        # When they hover over a production, highlight it.
        self._hover = -1
        self._prodlist.bind("<Motion>", self._highlight_hover)
        self._prodlist.bind("<Leave>", self._clear_hover)

    def _init_bindings(self):
        # Quit
        self._top.bind("<Control-q>", self.destroy)
        self._top.bind("<Control-x>", self.destroy)
        self._top.bind("<Alt-q>", self.destroy)
        self._top.bind("<Alt-x>", self.destroy)

        # Ops (step, shift, reduce, undo)
        self._top.bind("<space>", self.step)
        self._top.bind("<s>", self.shift)
        self._top.bind("<Alt-s>", self.shift)
        self._top.bind("<Control-s>", self.shift)
        self._top.bind("<r>", self.reduce)
        self._top.bind("<Alt-r>", self.reduce)
        self._top.bind("<Control-r>", self.reduce)
        self._top.bind("<Delete>", self.reset)
        self._top.bind("<u>", self.undo)
        self._top.bind("<Alt-u>", self.undo)
        self._top.bind("<Control-u>", self.undo)
        self._top.bind("<Control-z>", self.undo)
        self._top.bind("<BackSpace>", self.undo)

        # Misc
        self._top.bind("<Control-p>", self.postscript)
        self._top.bind("<Control-h>", self.help)
        self._top.bind("<F1>", self.help)
        self._top.bind("<Control-g>", self.edit_grammar)
        self._top.bind("<Control-t>", self.edit_sentence)

        # Animation speed control
        self._top.bind("-", lambda e, a=self._animate: a.set(20))
        self._top.bind("=", lambda e, a=self._animate: a.set(10))
        self._top.bind("+", lambda e, a=self._animate: a.set(4))

    def _init_buttons(self, parent):
        # Set up the frames.
        self._buttonframe = buttonframe = Frame(parent)
        buttonframe.pack(fill="none", side="bottom")
        Button(buttonframe, text="Step", background="#90c0d0", foreground="black", command=self.step).pack(side="left")
        Button(
            buttonframe, text="Shift", underline=0, background="#90f090", foreground="black", command=self.shift
        ).pack(side="left")
        Button(
            buttonframe, text="Reduce", underline=0, background="#90f090", foreground="black", command=self.reduce
        ).pack(side="left")
        Button(buttonframe, text="Undo", underline=0, background="#f0a0a0", foreground="black", command=self.undo).pack(
            side="left"
        )

    def _init_menubar(self, parent):
        menubar = Menu(parent)

        filemenu = Menu(menubar, tearoff=0)
        filemenu.add_command(label="Reset Parser", underline=0, command=self.reset, accelerator="Del")
        filemenu.add_command(label="Print to Postscript", underline=0, command=self.postscript, accelerator="Ctrl-p")
        filemenu.add_command(label="Exit", underline=1, command=self.destroy, accelerator="Ctrl-x")
        menubar.add_cascade(label="File", underline=0, menu=filemenu)

        editmenu = Menu(menubar, tearoff=0)
        editmenu.add_command(label="Edit Grammar", underline=5, command=self.edit_grammar, accelerator="Ctrl-g")
        editmenu.add_command(label="Edit Text", underline=5, command=self.edit_sentence, accelerator="Ctrl-t")
        menubar.add_cascade(label="Edit", underline=0, menu=editmenu)

        rulemenu = Menu(menubar, tearoff=0)
        rulemenu.add_command(label="Step", underline=1, command=self.step, accelerator="Space")
        rulemenu.add_separator()
        rulemenu.add_command(label="Shift", underline=0, command=self.shift, accelerator="Ctrl-s")
        rulemenu.add_command(label="Reduce", underline=0, command=self.reduce, accelerator="Ctrl-r")
        rulemenu.add_separator()
        rulemenu.add_command(label="Undo", underline=0, command=self.undo, accelerator="Ctrl-u")
        menubar.add_cascade(label="Apply", underline=0, menu=rulemenu)

        viewmenu = Menu(menubar, tearoff=0)
        viewmenu.add_checkbutton(
            label="Show Grammar", underline=0, variable=self._show_grammar, command=self._toggle_grammar
        )
        viewmenu.add_separator()
        viewmenu.add_radiobutton(label="Tiny", variable=self._size, underline=0, value=10, command=self.resize)
        viewmenu.add_radiobutton(label="Small", variable=self._size, underline=0, value=12, command=self.resize)
        viewmenu.add_radiobutton(label="Medium", variable=self._size, underline=0, value=14, command=self.resize)
        viewmenu.add_radiobutton(label="Large", variable=self._size, underline=0, value=18, command=self.resize)
        viewmenu.add_radiobutton(label="Huge", variable=self._size, underline=0, value=24, command=self.resize)
        menubar.add_cascade(label="View", underline=0, menu=viewmenu)

        animatemenu = Menu(menubar, tearoff=0)
        animatemenu.add_radiobutton(label="No Animation", underline=0, variable=self._animate, value=0)
        animatemenu.add_radiobutton(
            label="Slow Animation", underline=0, variable=self._animate, value=20, accelerator="-"
        )
        animatemenu.add_radiobutton(
            label="Normal Animation", underline=0, variable=self._animate, value=10, accelerator="="
        )
        animatemenu.add_radiobutton(
            label="Fast Animation", underline=0, variable=self._animate, value=4, accelerator="+"
        )
        menubar.add_cascade(label="Animate", underline=1, menu=animatemenu)

        helpmenu = Menu(menubar, tearoff=0)
        helpmenu.add_command(label="About", underline=0, command=self.about)
        helpmenu.add_command(label="Instructions", underline=0, command=self.help, accelerator="F1")
        menubar.add_cascade(label="Help", underline=0, menu=helpmenu)

        parent.config(menu=menubar)

    def _init_feedback(self, parent):
        self._feedbackframe = feedbackframe = Frame(parent)
        feedbackframe.pack(fill="x", side="bottom", padx=3, pady=3)
        self._lastoper_label = Label(feedbackframe, text="Last Operation:", font=self._font)
        self._lastoper_label.pack(side="left")
        lastoperframe = Frame(feedbackframe, relief="sunken", border=1)
        lastoperframe.pack(fill="x", side="right", expand=1, padx=5)
        self._lastoper1 = Label(lastoperframe, foreground="#007070", background="#f0f0f0", font=self._font)
        self._lastoper2 = Label(
            lastoperframe, anchor="w", width=30, foreground="#004040", background="#f0f0f0", font=self._font
        )
        self._lastoper1.pack(side="left")
        self._lastoper2.pack(side="left", fill="x", expand=1)

    def _init_canvas(self, parent):
        self._cframe = CanvasFrame(parent, background="white", width=525, closeenough=10, border=2, relief="sunken")
        self._cframe.pack(expand=1, fill="both", side="top", pady=2)
        canvas = self._canvas = self._cframe.canvas()

        self._stackwidgets = []
        self._rtextwidgets = []
        self._titlebar = canvas.create_rectangle(0, 0, 0, 0, fill="#c0f0f0", outline="black")
        self._exprline = canvas.create_line(0, 0, 0, 0, dash=".")
        self._stacktop = canvas.create_line(0, 0, 0, 0, fill="#408080")
        size = self._size.get() + 4
        self._stacklabel = TextWidget(canvas, "Stack", color="#004040", font=self._boldfont)
        self._rtextlabel = TextWidget(canvas, "Remaining Text", color="#004040", font=self._boldfont)
        self._cframe.add_widget(self._stacklabel)
        self._cframe.add_widget(self._rtextlabel)

    #########################################
    ##  Main draw procedure
    #########################################

    def _redraw(self):
        scrollregion = self._canvas["scrollregion"].split()
        (cx1, cy1, cx2, cy2) = [int(c) for c in scrollregion]

        # Delete the old stack & rtext widgets.
        for stackwidget in self._stackwidgets:
            self._cframe.destroy_widget(stackwidget)
        self._stackwidgets = []
        for rtextwidget in self._rtextwidgets:
            self._cframe.destroy_widget(rtextwidget)
        self._rtextwidgets = []

        # Position the titlebar & exprline
        (x1, y1, x2, y2) = self._stacklabel.bbox()
        y = y2 - y1 + 10
        self._canvas.coords(self._titlebar, -5000, 0, 5000, y - 4)
        self._canvas.coords(self._exprline, 0, y * 2 - 10, 5000, y * 2 - 10)

        # Position the titlebar labels..
        (x1, y1, x2, y2) = self._stacklabel.bbox()
        self._stacklabel.move(5 - x1, 3 - y1)
        (x1, y1, x2, y2) = self._rtextlabel.bbox()
        self._rtextlabel.move(cx2 - x2 - 5, 3 - y1)

        # Draw the stack.
        stackx = 5
        for tok in self._parser.stack():
            if isinstance(tok, Tree):
                attribs = {
                    "tree_color": "#4080a0",
                    "tree_width": 2,
                    "node_font": self._boldfont,
                    "node_color": "#006060",
                    "leaf_color": "#006060",
                    "leaf_font": self._font,
                }
                widget = tree_to_treesegment(self._canvas, tok, **attribs)
                widget.node()["color"] = "#000000"
            else:
                widget = TextWidget(self._canvas, tok, color="#000000", font=self._font)
            widget.bind_click(self._popup_reduce)
            self._stackwidgets.append(widget)
            self._cframe.add_widget(widget, stackx, y)
            stackx = widget.bbox()[2] + 10

        # Draw the remaining text.
        rtextwidth = 0
        for tok in self._parser.remaining_text():
            widget = TextWidget(self._canvas, tok, color="#000000", font=self._font)
            self._rtextwidgets.append(widget)
            self._cframe.add_widget(widget, rtextwidth, y)
            rtextwidth = widget.bbox()[2] + 4

        # Allow enough room to shift the next token (for animations)
        if len(self._rtextwidgets) > 0:
            stackx += self._rtextwidgets[0].width()

        # Move the remaining text to the correct location (keep it
        # right-justified, when possible); and move the remaining text
        # label, if necessary.
        stackx = max(stackx, self._stacklabel.width() + 25)
        rlabelwidth = self._rtextlabel.width() + 10
        if stackx >= cx2 - max(rtextwidth, rlabelwidth):
            cx2 = stackx + max(rtextwidth, rlabelwidth)
        for rtextwidget in self._rtextwidgets:
            rtextwidget.move(4 + cx2 - rtextwidth, 0)
        self._rtextlabel.move(cx2 - self._rtextlabel.bbox()[2] - 5, 0)

        midx = (stackx + cx2 - max(rtextwidth, rlabelwidth)) / 2
        self._canvas.coords(self._stacktop, midx, 0, midx, 5000)
        (x1, y1, x2, y2) = self._stacklabel.bbox()

        # Set up binding to allow them to shift a token by dragging it.
        if len(self._rtextwidgets) > 0:

            def drag_shift(widget, midx=midx, self=self):
                if widget.bbox()[0] < midx:
                    self.shift()
                else:
                    self._redraw()

            self._rtextwidgets[0].bind_drag(drag_shift)
            self._rtextwidgets[0].bind_click(self.shift)

        # Draw the stack top.
        self._highlight_productions()

    def _draw_stack_top(self, widget):
        # hack..
        midx = widget.bbox()[2] + 50
        self._canvas.coords(self._stacktop, midx, 0, midx, 5000)

    def _highlight_productions(self):
        # Highlight the productions that can be reduced.
        self._prodlist.selection_clear(0, "end")
        for prod in self._parser.reducible_productions():
            index = self._productions.index(prod)
            self._prodlist.selection_set(index)

    #########################################
    ##  Button Callbacks
    #########################################

    def destroy(self, *e):
        if self._top is None:
            return
        self._top.destroy()
        self._top = None

    def reset(self, *e):
        self._parser.initialize(self._sent)
        self._lastoper1["text"] = "Reset App"
        self._lastoper2["text"] = ""
        self._redraw()

    def step(self, *e):
        if self.reduce():
            return 1
        elif self.shift():
            return 1
        else:
            if len(self._parser.parses()) > 0:
                self._lastoper1["text"] = "Finished:"
                self._lastoper2["text"] = "Success"
            else:
                self._lastoper1["text"] = "Finished:"
                self._lastoper2["text"] = "Failure"

    def shift(self, *e):
        if self._animating_lock:
            return
        if self._parser.shift():
            tok = self._parser.stack()[-1]
            self._lastoper1["text"] = "Shift:"
            self._lastoper2["text"] = "%r" % tok
            if self._animate.get():
                self._animate_shift()
            else:
                self._redraw()
            return 1
        return 0

    def reduce(self, *e):
        if self._animating_lock:
            return
        production = self._parser.reduce()
        if production:
            self._lastoper1["text"] = "Reduce:"
            self._lastoper2["text"] = "%s" % production
            if self._animate.get():
                self._animate_reduce()
            else:
                self._redraw()
        return production

    def undo(self, *e):
        if self._animating_lock:
            return
        if self._parser.undo():
            self._redraw()

    def postscript(self, *e):
        self._cframe.print_to_file()

    def mainloop(self, *args, **kwargs):
        """
        Enter the Tkinter mainloop.  This function must be called if
        this demo is created from a non-interactive program (e.g.
        from a secript); otherwise, the demo will close as soon as
        the script completes.
        """
        if in_idle():
            return
        self._top.mainloop(*args, **kwargs)

    #########################################
    ##  Menubar callbacks
    #########################################

    def resize(self, size=None):
        if size is not None:
            self._size.set(size)
        size = self._size.get()
        self._font.configure(size=-(abs(size)))
        self._boldfont.configure(size=-(abs(size)))
        self._sysfont.configure(size=-(abs(size)))

        # self._stacklabel['font'] = ('helvetica', -size-4, 'bold')
        # self._rtextlabel['font'] = ('helvetica', -size-4, 'bold')
        # self._lastoper_label['font'] = ('helvetica', -size)
        # self._lastoper1['font'] = ('helvetica', -size)
        # self._lastoper2['font'] = ('helvetica', -size)
        # self._prodlist['font'] = ('helvetica', -size)
        # self._prodlist_label['font'] = ('helvetica', -size-2, 'bold')
        self._redraw()

    def help(self, *e):
        # The default font's not very legible; try using 'fixed' instead.
        try:
            ShowText(self._top, "Help: Shift-Reduce Parser Application", (__doc__).strip(), width=75, font="fixed")
        except:
            ShowText(self._top, "Help: Shift-Reduce Parser Application", (__doc__).strip(), width=75)

    def about(self, *e):
        ABOUT = "NLTK Shift-Reduce Parser Application\n" + "Written by Edward Loper"
        TITLE = "About: Shift-Reduce Parser Application"
        try:
            from tkMessageBox import Message

            Message(message=ABOUT, title=TITLE).show()
        except:
            ShowText(self._top, TITLE, ABOUT)

    def edit_grammar(self, *e):
        CFGEditor(self._top, self._parser.grammar(), self.set_grammar)

    def set_grammar(self, grammar):
        self._parser.set_grammar(grammar)
        self._productions = list(grammar.productions())
        self._prodlist.delete(0, "end")
        for production in self._productions:
            self._prodlist.insert("end", (" %s" % production))

    def edit_sentence(self, *e):
        sentence = string.join(self._sent)
        title = "Edit Text"
        instr = "Enter a new sentence to parse."
        EntryDialog(self._top, sentence, instr, self.set_sentence, title)

    def set_sentence(self, sent):
        self._sent = sent.split()  # [XX] use tagged?
        self.reset()

    #########################################
    ##  Reduce Production Selection
    #########################################

    def _toggle_grammar(self, *e):
        if self._show_grammar.get():
            self._prodframe.pack(fill="both", side="left", padx=2, after=self._feedbackframe)
            self._lastoper1["text"] = "Show Grammar"
        else:
            self._prodframe.pack_forget()
            self._lastoper1["text"] = "Hide Grammar"
        self._lastoper2["text"] = ""

    def _prodlist_select(self, event):
        selection = self._prodlist.curselection()
        if len(selection) != 1:
            return
        index = int(selection[0])
        production = self._parser.reduce(self._productions[index])
        if production:
            self._lastoper1["text"] = "Reduce:"
            self._lastoper2["text"] = "%s" % production
            if self._animate.get():
                self._animate_reduce()
            else:
                self._redraw()
        else:
            # Reset the production selections.
            self._prodlist.selection_clear(0, "end")
            for prod in self._parser.reducible_productions():
                index = self._productions.index(prod)
                self._prodlist.selection_set(index)

    def _popup_reduce(self, widget):
        # Remove old commands.
        productions = self._parser.reducible_productions()
        if len(productions) == 0:
            return

        self._reduce_menu.delete(0, "end")
        for production in productions:
            self._reduce_menu.add_command(label=str(production), command=self.reduce)
        self._reduce_menu.post(self._canvas.winfo_pointerx(), self._canvas.winfo_pointery())

    #########################################
    ##  Animations
    #########################################

    def _animate_shift(self):
        # What widget are we shifting?
        widget = self._rtextwidgets[0]

        # Where are we shifting from & to?
        right = widget.bbox()[0]
        if len(self._stackwidgets) == 0:
            left = 5
        else:
            left = self._stackwidgets[-1].bbox()[2] + 10

        # Start animating.
        dt = self._animate.get()
        dx = (left - right) * 1.0 / dt
        self._animate_shift_frame(dt, widget, dx)

    def _animate_shift_frame(self, frame, widget, dx):
        if frame > 0:
            self._animating_lock = 1
            widget.move(dx, 0)
            self._top.after(10, self._animate_shift_frame, frame - 1, widget, dx)
        else:
            # but: stacktop??

            # Shift the widget to the stack.
            del self._rtextwidgets[0]
            self._stackwidgets.append(widget)
            self._animating_lock = 0

            # Display the available productions.
            self._draw_stack_top(widget)
            self._highlight_productions()

    def _animate_reduce(self):
        # What widgets are we shifting?
        numwidgets = len(self._parser.stack()[-1])  # number of children
        widgets = self._stackwidgets[-numwidgets:]

        # How far are we moving?
        if isinstance(widgets[0], TreeSegmentWidget):
            ydist = 15 + widgets[0].node().height()
        else:
            ydist = 15 + widgets[0].height()

        # Start animating.
        dt = self._animate.get()
        dy = ydist * 2.0 / dt
        self._animate_reduce_frame(dt / 2, widgets, dy)

    def _animate_reduce_frame(self, frame, widgets, dy):
        if frame > 0:
            self._animating_lock = 1
            for widget in widgets:
                widget.move(0, dy)
            self._top.after(10, self._animate_reduce_frame, frame - 1, widgets, dy)
        else:
            del self._stackwidgets[-len(widgets) :]
            for widget in widgets:
                self._cframe.remove_widget(widget)
            tok = self._parser.stack()[-1]
            if not isinstance(tok, Tree):
                raise ValueError()
            label = TextWidget(self._canvas, str(tok.node), color="#006060", font=self._boldfont)
            widget = TreeSegmentWidget(self._canvas, label, widgets, width=2)
            (x1, y1, x2, y2) = self._stacklabel.bbox()
            y = y2 - y1 + 10
            if not self._stackwidgets:
                x = 5
            else:
                x = self._stackwidgets[-1].bbox()[2] + 10
            self._cframe.add_widget(widget, x, y)
            self._stackwidgets.append(widget)

            # Display the available productions.
            self._draw_stack_top(widget)
            self._highlight_productions()

            #             # Delete the old widgets..
            #             del self._stackwidgets[-len(widgets):]
            #             for widget in widgets:
            #                 self._cframe.destroy_widget(widget)
            #
            #             # Make a new one.
            #             tok = self._parser.stack()[-1]
            #             if isinstance(tok, Tree):
            #                 attribs = {'tree_color': '#4080a0', 'tree_width': 2,
            #                            'node_font': bold, 'node_color': '#006060',
            #                            'leaf_color': '#006060', 'leaf_font':self._font}
            #                 widget = tree_to_treesegment(self._canvas, tok.type(),
            #                                              **attribs)
            #                 widget.node()['color'] = '#000000'
            #             else:
            #                 widget = TextWidget(self._canvas, tok.type(),
            #                                     color='#000000', font=self._font)
            #             widget.bind_click(self._popup_reduce)
            #             (x1, y1, x2, y2) = self._stacklabel.bbox()
            #             y = y2-y1+10
            #             if not self._stackwidgets: x = 5
            #             else: x = self._stackwidgets[-1].bbox()[2] + 10
            #             self._cframe.add_widget(widget, x, y)
            #             self._stackwidgets.append(widget)

            # self._redraw()
            self._animating_lock = 0

    #########################################
    ##  Hovering.
    #########################################

    def _highlight_hover(self, event):
        # What production are we hovering over?
        index = self._prodlist.nearest(event.y)
        if self._hover == index:
            return

        # Clear any previous hover highlighting.
        self._clear_hover()

        # If the production corresponds to an available reduction,
        # highlight the stack.
        selection = [int(s) for s in self._prodlist.curselection()]
        if index in selection:
            rhslen = len(self._productions[index].rhs())
            for stackwidget in self._stackwidgets[-rhslen:]:
                if isinstance(stackwidget, TreeSegmentWidget):
                    stackwidget.node()["color"] = "#00a000"
                else:
                    stackwidget["color"] = "#00a000"

        # Remember what production we're hovering over.
        self._hover = index

    def _clear_hover(self, *event):
        # Clear any previous hover highlighting.
        if self._hover == -1:
            return
        self._hover = -1
        for stackwidget in self._stackwidgets:
            if isinstance(stackwidget, TreeSegmentWidget):
                stackwidget.node()["color"] = "black"
            else:
                stackwidget["color"] = "black"
示例#4
0
class MainApp(object):
    """
    The MainApp based on TKinter

    Attributes
    ----------

    root             : Tk
                      The Tk object
    dbfdata          : dictionary
                      The current dbf data
    menubar          : Menu
                      The menu bar
    attibmenu        : Menu
                      The attribute menu
    """
    def __init__(self):
        self.root = Tk()
        self.root.geometry("300x250")
        self.root.title("Shape file Reader")
        self.createMenu()
        self.root.mainloop()

    def createMenu(self):       
        """
        Creates GUI components and register events
        """
        self.menubar = Menu(self.root)
        self.dbfdata = None
        
        filemenu = Menu(self.menubar, tearoff=0)
        filemenu.add_command(label="Open", command=self.__openShpfile)
        filemenu.add_separator()
        filemenu.add_command(label="Exit", command=self.root.quit)
        self.menubar.add_cascade(label="File", menu=filemenu)
        
        self.attibmenu = Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="Attibutes", menu=self.attibmenu,state='disabled')

        #Sagar Jha
        self.menubar.add_cascade(label="Simulator",command=self.__simulate) 
        #Added Simulator 
        
        self.root.config(menu=self.menubar)

    def __simulate(self):
        """
        This function will create object of SimulatorExperimence 
        """
        self.Sim = SimulatorExperimence()
        ## Test to check object type       
        ## print(type(self.Sim))
        self.Sim.doTest()
        
        
    def __openShpfile(self):
        """
        Open a shapefile and read in the contents, pop up the attribute menu 
        with the attributes of the shapefile
        """   
        print "open shape file!"
        directory="C:/Users/sjha1/Documents/GitHub/Simulator_GUI/Python-Sample/data/states/48States.shp"
        #tkFileDialog.askopenfilename(filetypes=[("SHAPE_FILE","*.shp")])
        print directory
        
        if directory == "":
            return
        
        self.shapes, self.shp_type, self.bbox = shp_reader.read_shp(directory)
        #read corresponding dbf data
        dbfFile = dbf.DbfLoader(directory[:-3] + "dbf")
        
        t = dbfFile.table2list()
        varNames = dbfFile.get_field_names()
        variables = {}
        for variable in varNames:
            variables[variable] = [record[varNames.index(variable)] for record in t]
            
        if self.dbfdata!=None:
            self.attibmenu.delete(0, len(self.dbfdata)-1)
            
        #add attributes into menu
        for key in variables.keys():
            self.__addAttribute(key)
        
        self.dbfdata = variables
        self.menubar.entryconfig(2, state=Tkconstants.NORMAL)
        self.__updateCanvas("STATE_NAME")
    
    def __addAttribute(self,attributeName):
        """
        Add an attribute to the menu
        """
        self.attibmenu.add_command(label=attributeName, command=lambda i=attributeName:self.__updateCanvas(i))
        
    def __updateCanvas(self, attributeName):
        """
        Updates the canvas and showing statistical information
        """
        print "update Canvas "+attributeName
        data_list=self.dbfdata[attributeName]
        
        print "attribute values: ", data_list
        try:
            n, min_max, mean, var, skew, kurt = stats.describe(data_list)
            print "============================"
            print "attribute statistics\n"
            print("Number of units: {0:d}".format(n))
            print("Minimum: {0:8.6f} Maximum: {1:8.6f}".format(min_max[0], min_max[1]))
            print("Mean: {0:8.6f}".format(mean))
            print("Standard deviation: {0:8.6f}".format(math.sqrt(var)))
            print("Skew : {0:8.6f}".format(skew))
            print("Kurtosis: {0:8.6f}".format(kurt))
            print "\n============================"
            
            high=max(data_list)
            low=min(data_list)
            dif=float(high-low)
            
            for i in range(len(data_list)):
                #map colors to 0-200, 0-200, 0-200 (avoid pure white for display purpose)
                index=float(data_list[i]-low)/dif*200
                index=str(hex(200-int(index)).split('x')[1])
    
                color="#"+index+index+index    
                self.shapes[i].color=color
        except:
            print "non-numeric attribute"
            
        self.canvas=MainCanvas(self.shapes,self.bbox,self.shp_type,self.root,attributeName,data_list)
示例#5
0
class SubMenu():
    """
    Defines a generic Sub menu class that may be used as part of a larger menu
    """
    def __init__(self, parent_menu, label):
        """
        Construct the SubMenu instance
        :param parent_menu:             The menu in which this submenu will be accessed from
        :param label:                   The label to give the submenu, this is what the user will see
        :return:                        An instance of SubMenu
        """
        self._parent_menu = parent_menu
        self._options = {}
        self._label = label
        self._menu = Menu(parent_menu)
        self._key_underline = 0

    def add_option(self, label, action, type_func, shortcut):
        """
        Adds a menu item to the submenu

        Adds a menu item to the tk representation of the menu, as well as the underlying
        dictionary that defines the menu. This method also allows the addition of a keyboard
        shortcut to be defined for a particular menu option
        :param label:
        :param action:
        :param type_func:
        :param shortcut:
        :return:
        """
        pass

    def add_option(self, label, action, type_func):
        """
        Adds a menu item to the submenu

        Adds a menu item to the dictionary of menu items that make up this
        submenu. Also adds the command to actual Tk menu structure,
        Function will fail if the menu item already exists
        :param label(string):           The label of the option to add
        :param action(function):        The action callback to be executed
        :param type_func(string):       The menu entry function for the type of item to add to the menu
        """
        _type = \
            {          "command"        : self._menu.add_command,
                       "checkbutton"    : self._menu.add_checkbutton,
                       "radiobutton"    : self._menu.add_radiobutton
            }

        if label in self._options:
            raise DuplicateCommandException(label)

        self._options[label] = action
        _type[type_func](label=label, command=action)

    def remove_option(self, label):
        """
        Remove a menu option from this submenu

        Removes a menu option from this submenu
        Will fail silently if the menu item does not exist

        :param label:       The label of the menu item that needs to be removed
        """
        del self._options[label]
        index = self._menu.index(label)
        self._menu.delete(index, index)
示例#6
0
class SurfaceManipulator(Frame):
    r"""
    A translation surface editor in tk.
    """

    # STATIC METHODS AND OBJECTS

    current = None
    # boolean variable to remember if the hook was run!
    _clear_hook_was_run = 0

    @staticmethod
    def launch(geometry="800x700+10+10"):
        r"""Prefered way to gain access to a SurfaceManipulator window."""
        if SurfaceManipulator.current is None:
            SurfaceManipulator._clear_hook()
            root = Tk()
            root.geometry(geometry)
            SurfaceManipulator.current = SurfaceManipulator(root)
        return SurfaceManipulator.current

    @staticmethod
    def _window_destroyed(surface_manipulator):
        if SurfaceManipulator.current is surface_manipulator:
            SurfaceManipulator.current = None

    @staticmethod
    def _clear_hook():
        if not SurfaceManipulator._clear_hook_was_run:
            # Hack due to Nathan Dunfield (http://trac.sagemath.org/ticket/15152)
            import IPython.lib.inputhook as ih
            ih.clear_inputhook()
            SurfaceManipulator._clear_hook_was_run = 1

    # NORMAL METHODS

    def __init__(self, parent, surface=None, surfaces=[]):
        r"""
        INPUT:
        - ``surfaces`` -- a list of surfaces that the editor may modify
        - ``surface`` -- surface selected by default
        - ``parent`` -- parent Tk window
        """
        Frame.__init__(self, parent)
        self._parent = parent
        self.pack(fill="both", expand=1)

        # Run something when closing
        self._parent.wm_protocol("WM_DELETE_WINDOW", self.exit)

        # Surface currently being manipulated
        self._surface = None
        # List of surfaces in editor
        self._surfaces = []
        # More variables to initialize
        self._currentActor = None
        # Initialization of GUI
        self._init_menu()
        self._init_gui()
        # Setup surface list
        for s in surfaces:
            self.add_surface(s)
        # Setup initial surface
        if surface is not None:
            self.add_surface(surface)
        self.set_surface(surface)

    def __repr__(self):
        return "Surface manipulator"

    def add_mega_wollmilchsau(self):
        from geometry.mega_wollmilchsau import MegaWollmilchsau
        s = MegaWollmilchsau()
        sm, sb = s.get_bundle()
        self.set_surface(sb)

    def add_octagon(self):
        from geometry.similarity_surface_generators import TranslationSurfaceGenerators
        ss = TranslationSurfaceGenerators.regular_octagon()
        ss.edit()

    def _init_menu(self):

        self._menubar = Menu(self._parent)
        menubar = self._menubar
        self._parent.config(menu=menubar)

        #new_menu = Menu(menubar, tearoff=0)
        #new_menu.add_command(label="Billiard Table", command=self.on_new_similarity_surface)

        file_menu = Menu(menubar, tearoff=0)
        #file_menu.add_cascade(label="New", menu=new_menu)
        file_menu.add_command(label="Octagon", command=self.add_octagon)
        file_menu.add_command(label="MegaWollmilchsau",
                              command=self.add_mega_wollmilchsau)
        file_menu.add_separator()
        file_menu.add_command(label="About", command=self.on_about)
        file_menu.add_command(label="Export PostScript",
                              command=self.on_export)
        file_menu.add_command(label="Exit",
                              command=self.exit,
                              accelerator="Alt+F4")
        menubar.add_cascade(label="File", underline=0, menu=file_menu)

        self._surface_menu = Menu(menubar, tearoff=0)
        self._selected_surface = IntVar()
        self._selected_surface.set(-1)
        menubar.add_cascade(label="Surface",
                            underline=0,
                            menu=self._surface_menu)
        self._surface_menu.add_radiobutton(label="None",
                                           command=self.menu_select_surface,
                                           variable=self._selected_surface,
                                           value=-1)

    def _init_gui(self):
        self._parent.title("FlatSurf Editor")

        self._canvas = Canvas(self, bg="#444", width=300, height=200)
        self._canvas.pack(fill="both", expand=1)

        self.bottom_text = Label(self, text="Welcome to FlatSurf.", anchor="w")
        self.bottom_text.pack(fill="x", expand=0)

        self.set_actor(None)

    def add_surface(self, newsurface):
        r"""
        Add a surface to the display list for the window.
        Returns the index of the new surface in the surface list.
        """
        if (newsurface == None):
            return -1
        i = 0
        for s in self._surfaces:
            if (s == newsurface):
                return i
            i = i + 1
        self._surfaces.append(newsurface)
        newsurface.zoom_fit_nice_boundary()
        self._reset_surface_menu()
        return len(self._surfaces) - 1

    def exit(self):
        SurfaceManipulator._window_destroyed(self)
        self._parent.destroy()

    def find_bundle(self, surface):
        for surface_bundle in self._surfaces:
            if surface is surface_bundle.get_surface():
                return surface_bundle
        return None

    def get_bundle(self):
        return self._surface

    def get_bundles(self):
        return self._surfaces

    def get_canvas(self):
        return self._canvas

    def get_center(self):
        r"""
        Return the center of the canvas as a pair of integers.
        """
        return (self.get_width() / 2, self.get_height() / 2)

    def get_height(self):
        r"""
        Return the height of the canvas (an integer).
        """
        return self.get_canvas().winfo_height()

    def get_parent(self):
        return self._parent

    def get_surface_bundle(self):
        r"""
        Get the current surface bundle, or None if there is none.
        """
        return self._surface

    def get_width(self):
        r"""
        Return the width of the canvas (an integer).
        """
        return self.get_canvas().winfo_width()

    def menu_select_surface(self):
        r"""
        Called when a surface is selected from a menu.
        """
        i = self._selected_surface.get()
        if i == -1:
            self.set_surface(None)
        else:
            self.set_surface(self._surfaces[i])

    def on_about(self):
        self.set_text("Written by Vincent Delecroix and Pat Hooper.")

    def on_delete_junk(self):
        self._canvas.delete("junk")

    def on_export(self):
        r"""
        Export image as postscript file.
        """
        myFormats = [('PostScript', '*.ps')]
        fileName = tkFileDialog.asksaveasfilename(parent=self,
                                                  filetypes=myFormats,
                                                  title="Save image as...")
        if len(fileName) > 0:
            self._canvas.update()
            self._canvas.postscript(file=fileName)
            self.set_text("Wrote image to " + fileName)

#    def on_new_similarity_surface(self):
#        s = CreateSimilaritySurfaceBundle(len(self._surfaces),self)
#        if s is not None:
#            i = self.set_surface(s)
#            self.set_text("Created new surface `"+self._surfaces[i].get_name()+"'.")

    def _on_no_surface(self):
        self._canvas.delete("all")

    def _on_zoom(self):
        self.set_actor(ZoomActor(self))

    def _on_zoom_box(self):
        self.set_actor(ZoomBoxActor(self))

    def _on_redraw_all(self):
        if self._surface is not None:
            self._surface.redraw_all()

    def _on_recenter(self):
        self.set_actor(RecenterActor(self))

    def _reset_menus(self):
        r"""
        Reset all menus except the file and surface menu
        """
        # The following loop removes all but the first two menus (File and Surface).
        num = len(self._menubar.children)
        for i in range(num, 2, -1):
            self._menubar.delete(i)
        if self._surface != None:
            self._surface.make_menus(self._menubar)

    def _reset_surface_menu(self):
        r"""
        Reset the surface menu.
        """
        ### This is a hack to get the number of items in the menu:
        num = self._surface_menu.index(100) + 1
        # First we remove everything but the first entry ("None")
        for i in range(num - 1, 0, -1):
            #print("removing a child2: "+str(i)+" of "+str(num))
            self._surface_menu.delete(i)
        # Add an entry for every surface in the list.
        for i in range(len(self._surfaces)):
            surface = self._surfaces[i]
            self._surface_menu.add_radiobutton(
                label=surface.get_name(),
                command=self.menu_select_surface,
                variable=self._selected_surface,
                value=i)

    def set_text(self, text):
        self.bottom_text["text"] = text

    def set_actor(self, actor):
        r"""
        Set the current mode of user interaction.
        """
        if (actor != self._currentActor):
            if self._currentActor != None:
                self._currentActor.on_deactivate()
            if (actor == None):
                self.set_text("Nothing going on.")
                # Event bindings
                self._canvas.unbind('<Button-1>')
                self._canvas.unbind('<Button-2>')
                self._canvas.unbind('<Button-3>')
                self._canvas.unbind('<Double-Button-1>')
                self._canvas.unbind('<Shift-Button-1>')
                self._canvas.unbind('<Motion>')
                self.unbind('<FocusIn>')
                self.unbind('<FocusOut>')
                #self._canvas.unbind('<ButtonPress-1>')
                self._canvas.unbind('<ButtonRelease-1>')
                self._canvas.unbind('<B1-Motion>')
                self._parent.unbind('<Key>')
                self._parent.unbind('<KeyRelease>')
            else:
                # Event bindings
                self._canvas.bind('<Button-1>', actor.single_left_click)
                self._canvas.bind('<Double-Button-1>', actor.double_left_click)
                self._canvas.bind('<Triple-Button-1>', actor.double_left_click)
                self._canvas.bind('<Button-2>', actor.single_middle_click)
                self._canvas.bind('<Double-Button-2>',
                                  actor.double_middle_click)
                self._canvas.bind('<Triple-Button-2>',
                                  actor.double_middle_click)
                self._canvas.bind('<Button-3>', actor.single_right_click)
                self._canvas.bind('<Double-Button-3>',
                                  actor.double_right_click)
                self._canvas.bind('<Triple-Button-3>',
                                  actor.double_right_click)
                self._canvas.bind('<Shift-Button-1>', actor.shift_click)
                self._canvas.bind('<Motion>', actor.mouse_moved)
                #self._canvas.bind('<ButtonPress-1>', actor.left_mouse_pressed)
                self._canvas.bind('<ButtonRelease-1>',
                                  actor.left_mouse_released)
                self._canvas.bind('<B1-Motion>', actor.left_dragged)
                self.bind('<FocusIn>', actor.focus_in)
                self.bind('<FocusOut>', actor.focus_out)
                self._parent.bind('<Key>', actor.key_press)
                self._parent.bind('<KeyRelease>', actor.key_release)
                self._currentActor = actor
                self._currentActor.on_activate()

    def set_surface(self, surface_bundle):
        r"""
        Set the current surface to the one given by surface_bundle
        """
        i = self.add_surface(surface_bundle)
        if surface_bundle != self._surface:
            self._canvas.delete("all")
            self._surface = surface_bundle
            self._surface_menu.invoke(i + 1)
            if i >= 0:
                self.set_text("Switched to `" + self._surface.get_name() +
                              "'.")
                self._parent.title(self._surface.get_name())
                self._reset_menus()
                # stop the actor (was a bug).
                self.set_actor(None)
                if isinstance(self._surface, EditorRenderer):
                    self._surface.initial_render()
            else:
                self.set_text("No surface selected.")
                self._parent.title("FlatSurf Editor")
                self._reset_menus()
                self.set_actor(None)
        return i

    def surface_renamed(self):
        if self._surface is not None:
            self._parent.title(self._surface.get_name())
            self._reset_surface_menu()
class SurfaceManipulator(Frame):
    r"""
    A translation surface editor in tk.
    """
  
    # STATIC METHODS AND OBJECTS

    current = None
    # boolean variable to remember if the hook was run!
    _clear_hook_was_run = 0

    @staticmethod
    def launch(geometry = "800x700+10+10"):
        r"""Prefered way to gain access to a SurfaceManipulator window."""
        if SurfaceManipulator.current is None:
            SurfaceManipulator._clear_hook()
            root = Tk()
            root.geometry(geometry)
            SurfaceManipulator.current=SurfaceManipulator(root)
        return SurfaceManipulator.current
    
    @staticmethod
    def _window_destroyed(surface_manipulator):
        if SurfaceManipulator.current is surface_manipulator:
            SurfaceManipulator.current = None

    @staticmethod
    def _clear_hook():
        if not SurfaceManipulator._clear_hook_was_run:
            # Hack due to Nathan Dunfield (http://trac.sagemath.org/ticket/15152)
            import IPython.lib.inputhook as ih
            ih.clear_inputhook()
            SurfaceManipulator._clear_hook_was_run = 1

    # NORMAL METHODS


    def __init__(self, parent, surface=None, surfaces=[]):
        r"""
        INPUT:
        - ``surfaces`` -- a list of surfaces that the editor may modify
        - ``surface`` -- surface selected by default
        - ``parent`` -- parent Tk window
        """
        Frame.__init__(self, parent)   
        self._parent = parent
        self.pack(fill="both", expand=1)

        # Run something when closing
        self._parent.wm_protocol ("WM_DELETE_WINDOW", self.exit)

        # Surface currently being manipulated
        self._surface=None
        # List of surfaces in editor
        self._surfaces=[]
        # More variables to initialize
        self._currentActor=None
        # Initialization of GUI
        self._init_menu()
        self._init_gui()
        # Setup surface list
        for s in surfaces:
            self.add_surface(s)
        # Setup initial surface
        if surface is not None:
            self.add_surface(surface)
        self.set_surface(surface)

    def __repr__(self):
        return "Surface manipulator"

    def add_mega_wollmilchsau(self):
        from geometry.mega_wollmilchsau import MegaWollmilchsau
        s = MegaWollmilchsau()
        sm,sb = s.get_bundle()
        self.set_surface(sb)

    def add_octagon(self):
        from geometry.similarity_surface_generators import TranslationSurfaceGenerators
        ss=TranslationSurfaceGenerators.regular_octagon()
        ss.edit()

    def _init_menu(self):
        
        self._menubar = Menu(self._parent)
        menubar=self._menubar
        self._parent.config(menu=menubar)
        
        #new_menu = Menu(menubar, tearoff=0)
        #new_menu.add_command(label="Billiard Table", command=self.on_new_similarity_surface)
        
        file_menu = Menu(menubar, tearoff=0)
        #file_menu.add_cascade(label="New", menu=new_menu)
        file_menu.add_command(label="Octagon", command=self.add_octagon)
        file_menu.add_command(label="MegaWollmilchsau", command=self.add_mega_wollmilchsau)
        file_menu.add_separator()
        file_menu.add_command(label="About", command=self.on_about)
        file_menu.add_command(label="Export PostScript", command=self.on_export)
        file_menu.add_command(label="Exit", command=self.exit, accelerator="Alt+F4")
        menubar.add_cascade(label="File", underline=0, menu=file_menu)

        self._surface_menu = Menu(menubar, tearoff=0)
        self._selected_surface = IntVar()
        self._selected_surface.set(-1)
        menubar.add_cascade(label="Surface", underline=0, menu=self._surface_menu)
        self._surface_menu.add_radiobutton(label="None", 
            command=self.menu_select_surface, variable=self._selected_surface, 
            value=-1)

    def _init_gui(self):
        self._parent.title("FlatSurf Editor")

        self._canvas = Canvas(self, bg="#444", width=300, height=200)
        self._canvas.pack(fill="both", expand=1)
        
        self.bottom_text = Label(self, text="Welcome to FlatSurf.",
            anchor="w")
        self.bottom_text.pack(fill="x", expand=0)

        self.set_actor(None)

    def add_surface(self,newsurface):
        r"""
        Add a surface to the display list for the window.
        Returns the index of the new surface in the surface list.
        """
        if (newsurface==None):
            return -1
        i=0
        for s in self._surfaces:
            if (s==newsurface):
                return i
            i=i+1
        self._surfaces.append(newsurface)
        newsurface.zoom_fit_nice_boundary()
        self._reset_surface_menu()
        return len(self._surfaces)-1

    def exit(self):
        SurfaceManipulator._window_destroyed(self)
        self._parent.destroy()

    def find_bundle(self, surface):
        for surface_bundle in self._surfaces:
            if surface is surface_bundle.get_surface():
                return surface_bundle
        return None

    def get_bundle(self):
        return self._surface

    def get_bundles(self):
        return self._surfaces

    def get_canvas(self):
        return self._canvas

    def get_center(self):
        r"""
        Return the center of the canvas as a pair of integers.
        """
        return ( self.get_width()/2, self.get_height()/2 )

    def get_height(self):
        r"""
        Return the height of the canvas (an integer).
        """
        return self.get_canvas().winfo_height()

    def get_parent(self):
        return self._parent

    def get_surface_bundle(self):
        r"""
        Get the current surface bundle, or None if there is none.
        """
        return self._surface

    def get_width(self):
        r"""
        Return the width of the canvas (an integer).
        """
        return self.get_canvas().winfo_width()

    def menu_select_surface(self):
        r"""
        Called when a surface is selected from a menu.
        """
        i = self._selected_surface.get()
        if i == -1:
            self.set_surface(None)
        else:
            self.set_surface(self._surfaces[i])

    def on_about(self):
        self.set_text("Written by Vincent Delecroix and Pat Hooper.")

    def on_delete_junk(self):
        self._canvas.delete("junk")

    def on_export(self):
        r"""
        Export image as postscript file.
        """
        myFormats = [('PostScript','*.ps')]
        fileName = tkFileDialog.asksaveasfilename(parent=self,
            filetypes=myFormats , title="Save image as...")
        if len(fileName ) > 0:
            self._canvas.update() 
            self._canvas.postscript(file = fileName) 
            self.set_text("Wrote image to "+fileName)

#    def on_new_similarity_surface(self):  
#        s = CreateSimilaritySurfaceBundle(len(self._surfaces),self)
#        if s is not None:
#            i = self.set_surface(s)
#            self.set_text("Created new surface `"+self._surfaces[i].get_name()+"'.")

    def _on_no_surface(self):
        self._canvas.delete("all")

    def _on_zoom(self):
        self.set_actor(ZoomActor(self))

    def _on_zoom_box(self):
        self.set_actor(ZoomBoxActor(self))

    def _on_redraw_all(self):
        if self._surface is not None:
            self._surface.redraw_all()

    def _on_recenter(self):
        self.set_actor(RecenterActor(self))

    def _reset_menus(self):
        r"""
        Reset all menus except the file and surface menu
        """
        # The following loop removes all but the first two menus (File and Surface).
        num = len(self._menubar.children)
        for i in range(num,2,-1):
            self._menubar.delete(i)
        if self._surface!= None:
            self._surface.make_menus(self._menubar)

    def _reset_surface_menu(self):
        r"""
        Reset the surface menu.
        """
        ### This is a hack to get the number of items in the menu: 
        num = self._surface_menu.index(100)+1
        # First we remove everything but the first entry ("None")
        for i in range(num-1,0,-1):
            #print("removing a child2: "+str(i)+" of "+str(num))
            self._surface_menu.delete(i)
        # Add an entry for every surface in the list.
        for i in range( len(self._surfaces) ):
            surface = self._surfaces[i]
            self._surface_menu.add_radiobutton(label=surface.get_name(),
                command=self.menu_select_surface, variable=self._selected_surface,
                value=i)

    def set_text(self, text):
        self.bottom_text["text"]=text

    def set_actor(self, actor):
        r"""
        Set the current mode of user interaction.
        """
        if (actor != self._currentActor):
            if self._currentActor != None:
                self._currentActor.on_deactivate()
            if (actor==None):
                self.set_text("Nothing going on.")
                # Event bindings
                self._canvas.unbind('<Button-1>')
                self._canvas.unbind('<Button-2>')
                self._canvas.unbind('<Button-3>')
                self._canvas.unbind('<Double-Button-1>')
                self._canvas.unbind('<Shift-Button-1>')
                self._canvas.unbind('<Motion>')
                self.unbind('<FocusIn>')
                self.unbind('<FocusOut>')
                #self._canvas.unbind('<ButtonPress-1>')
                self._canvas.unbind('<ButtonRelease-1>')
                self._canvas.unbind('<B1-Motion>')
                self._parent.unbind('<Key>')
                self._parent.unbind('<KeyRelease>')
            else:
                # Event bindings
                self._canvas.bind('<Button-1>', actor.single_left_click)
                self._canvas.bind('<Double-Button-1>', actor.double_left_click)
                self._canvas.bind('<Triple-Button-1>', actor.double_left_click)
                self._canvas.bind('<Button-2>', actor.single_middle_click)
                self._canvas.bind('<Double-Button-2>', actor.double_middle_click)
                self._canvas.bind('<Triple-Button-2>', actor.double_middle_click)
                self._canvas.bind('<Button-3>', actor.single_right_click)
                self._canvas.bind('<Double-Button-3>', actor.double_right_click)
                self._canvas.bind('<Triple-Button-3>', actor.double_right_click)
                self._canvas.bind('<Shift-Button-1>', actor.shift_click)
                self._canvas.bind('<Motion>', actor.mouse_moved)
                #self._canvas.bind('<ButtonPress-1>', actor.left_mouse_pressed)
                self._canvas.bind('<ButtonRelease-1>', actor.left_mouse_released)
                self._canvas.bind('<B1-Motion>',actor.left_dragged)
                self.bind('<FocusIn>', actor.focus_in)
                self.bind('<FocusOut>', actor.focus_out)
                self._parent.bind('<Key>', actor.key_press)
                self._parent.bind('<KeyRelease>', actor.key_release)
                self._currentActor=actor
                self._currentActor.on_activate()

    def set_surface(self,surface_bundle):
        r"""
        Set the current surface to the one given by surface_bundle
        """
        i = self.add_surface(surface_bundle)
        if surface_bundle != self._surface:
            self._canvas.delete("all")
            self._surface=surface_bundle
            self._surface_menu.invoke(i+1)
            if i >= 0:
                self.set_text("Switched to `"+self._surface.get_name()+"'.")
                self._parent.title(self._surface.get_name())
                self._reset_menus()
                # stop the actor (was a bug).
                self.set_actor(None)
                if isinstance(self._surface, EditorRenderer):
                    self._surface.initial_render()
            else:
                self.set_text("No surface selected.")
                self._parent.title("FlatSurf Editor")
                self._reset_menus()
                self.set_actor(None)
        return i

    def surface_renamed(self):
        if self._surface is not None:
            self._parent.title(self._surface.get_name())
            self._reset_surface_menu()
示例#8
0
class App(Frame):
    def __init__(self, master=None):
        self.master = master
        Frame.__init__(self, master, relief=SUNKEN, bd=2)

        self.gcode = []
        self.slicing = False
        self.printing = False
        self.connected = False
        self.monitorTemp = False
        self.paused = False
        self.sdpresent = False
        self.sdlisting = False
        self.sdchecking = False
        self.sdprinting = False
        self.sdpaused = False
        self.sduploading = False
        self.sdbytes = 0
        self.sdmaxbytes = 0
        self.insidelisting = False
        self.readingFirmware = False
        self.sdfiles = []
        self.bedtemp = float(0)
        self.bedtarget = float(0)
        self.exttemp = float(0)
        self.exttarget = float(0)
        self.acceleration = 0
        self.m114count = 0
        self.speedcount = 0
        self.location = [0, 0, 0, 0]
        self.pausePoint = [0, 0, 0, 0]
        self.percent = 0.0
        self.ets = "??"
        self.gcodeInfo = None
        self.GCodeFile = None
        self.StlFile = None
        self.Profile = None
        self.printStartLine = 0
        self.startTime = 0
        self.endTime = 0
        self.elapsedTime = 0
        self.FanSpeed = 0
        self.FeedMultiply = 100
        self.ExtrudeMultiply = 100

        self.timingReport = None
        self.filamentReport = None
        self.measurementsReport = None

        self.macroButtons = None

        self.rpt1re = re.compile(" *T:([0-9\.]+) *E:[0-9\.]+ *B:([0-9\.]+)")
        self.rpt2re = re.compile(" *T:([0-9\.]+) *E:[0-9\.]+ *W:.*")
        self.locrptre = re.compile("^X:([0-9\.\-]+)Y:([0-9\.\-]+)Z:([0-9\.\-]+)E:([0-9\.\-]+) *Count")
        self.speedrptre = re.compile("Fan speed:([0-9]+) Feed Multiply:([0-9]+) Extrude Multiply:([0-9]+)")

        self.sdre = re.compile("SD printing byte *([0-9]+) *\/ *([0-9]+)")

        self.printer = printcore()
        self.settings = Settings()
        self.settings.cmdFolder = cmd_folder
        self.logger = Logger(self)

        if self.settings.speedcommand is not None:
            allow_while_printing.append(self.settings.speedcommand)

        self.acceleration = self.settings.acceleration

        self.dataLoggers = {}
        for d in DLLIST:
            self.dataLoggers[d] = DataLogger(self, d)

        self.skeinforge = Skeinforge(self.settings)
        self.slic3r = Slic3r(self.settings)

        self.httpServer = RepRapServer(self, self.printer, self.settings, self.logger, self.settings.port)

        self.menubar = Menu(self)
        self.filemenu = Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="File", menu=self.filemenu)
        self.filemenu.add_command(label="Slice", command=self.openSTLFile)
        self.filemenu.add_command(label="Load GCode", command=self.openGCodeFile)
        self.slicemenuindex = self.filemenu.index("Slice")
        self.loadgcodemenuindex = self.filemenu.index("Load GCode")

        self.filemenu.add_separator()
        self.filemenu.add_command(label="Exit", command=self.quitApp)

        self.editmenu = Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="Edit", menu=self.editmenu)

        self.editmenu.add_command(label="Settings", command=self.editSettings)
        self.editmenu.add_command(label="Firmware Settings", command=self.FirmwareSettings)
        self.editmenu.add_separator()
        self.editmenu.add_command(label=GCODE_MENU_TEXT, command=self.doGEdit, state=DISABLED)

        self.slicermenu = Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="Slicer", menu=self.slicermenu)
        self.rbSlicer = StringVar()

        self.slicermenu.add_radiobutton(
            label="Skeinforge", command=self.selSlicer, value=SKEINFORGE, variable=self.rbSlicer
        )
        self.slicermenu.add_command(label="Settings", command=self.skeinforgeSettings)
        self.slicermenu.add_command(label="Choose Profile", command=self.chooseSFProfile)
        self.SFprofileindex = self.slicermenu.index("Choose Profile")
        self.setSFProfileMenuText()
        self.slicermenu.add_command(label="Alterations", command=self.doEditAlterations)

        self.slicermenu.add_separator()
        self.slicermenu.add_radiobutton(label="Slic3r", command=self.selSlicer, value=SLIC3R, variable=self.rbSlicer)
        self.slicermenu.add_command(label="Settings", command=self.slic3rSettings)
        self.slicermenu.add_command(label="Choose Profile", command=self.chooseS3Profile)
        self.S3profileindex = self.slicermenu.index("Choose Profile")
        self.setS3ProfileMenuText()

        self.rbSlicer.set(self.settings.slicer)

        self.macromenu = Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="Macros", menu=self.macromenu)
        self.macromenu.add_command(label="New", command=self.doNewMacro)
        self.macromenu.add_command(label="Edit", command=self.doEditMacro)
        self.macromenu.add_command(label="Delete", command=self.doDelMacro)
        self.macromenu.add_separator()
        self.cbShowMacroButtons = BooleanVar()
        self.cbShowMacroButtons.set(self.settings.showmacrobuttons)
        self.macromenu.add_checkbutton(
            label="Show Macro Buttons",
            command=self.doShowButtons,
            onvalue=True,
            offvalue=False,
            variable=self.cbShowMacroButtons,
        )
        self.macromenu.add_separator()
        self.runmacromenu = Menu(self.macromenu, tearoff=0)
        self.loadMacros()
        self.macromenu.add_cascade(label="Run", menu=self.runmacromenu)

        self.reportmenu = Menu(self.menubar, tearoff=0)
        self.menubar.add_cascade(label="View", menu=self.reportmenu)
        self.cbShowPrevious = BooleanVar()
        self.cbShowPrevious.set(self.settings.showprevious)
        self.reportmenu.add_checkbutton(
            label="Show Previous Layer",
            command=self.toggleShowPrevious,
            onvalue=True,
            offvalue=False,
            variable=self.cbShowPrevious,
        )
        self.cbShowMoves = BooleanVar()
        self.cbShowMoves.set(self.settings.showmoves)
        self.reportmenu.add_checkbutton(
            label="Show Non-extrusion Moves",
            command=self.toggleShowMoves,
            onvalue=True,
            offvalue=False,
            variable=self.cbShowMoves,
        )
        self.reportmenu.add_separator()
        self.reportmenu.add_command(label="Show Hours of Usage", command=self.doDataLogReport)
        self.reportmenu.add_command(label="Reset Hours of Usage", command=self.doDataLogReset)
        self.reportmenu.add_separator()
        self.reportmenu.add_command(label="Layer by Layer Timing", command=self.doTimingReport)
        self.reportmenu.add_command(label="Layer by Layer Filament Usage", command=self.doFilamentReport)
        self.reportmenu.add_command(label="GCode Measurements", command=self.doMeasurementsReport)

        self.toolsmenu = Menu(self.menubar, tearoff=0)
        n = 0
        if self.settings.platercmd is not None:
            n += 1
            self.toolsmenu.add_command(label="Plater", command=self.doPlater)

        if self.settings.gcodeviewcmd is not None:
            n += 1
            self.toolsmenu.add_command(label="GCode Viewer", command=self.doGCodeView)

        if self.settings.stlviewcmd is not None:
            n += 1
            self.toolsmenu.add_command(label="STL Viewer", command=self.doSTLView)

        if self.settings.openscadcmd is not None:
            n += 1
            self.toolsmenu.add_command(label="OpenSCAD", command=self.doOpenSCAD)

        if n > 0:
            self.menubar.add_cascade(label="Tools", menu=self.toolsmenu)

        try:
            self.master.config(menu=self.menubar)
        except AttributeError:
            self.master.tk.call(master, "config", "-menu", self.menubar)

        self.toolbar = ToolBar(self, self.printer, self.settings, self.logger)
        self.toolbar.grid(row=1, column=1, columnspan=4, sticky=W)

        self.ctl = MoveControl(self, self.printer, self.settings, self.logger)
        self.ctl.grid(row=2, column=1, rowspan=3, sticky=N)

        self.extr = Extruder(self, self.printer, self.settings, self.logger)
        self.extr.grid(row=2, column=2, rowspan=1, sticky=N + E + W)

        self.temps = Temperatures(self, self.printer, self.settings, self.logger)
        self.temps.grid(row=3, column=2, rowspan=2, sticky=N + E + W)

        self.gc = GcFrame(self, None, [], self.settings, self.logger)
        self.gc.grid(row=2, column=3, rowspan=3, sticky=N)

        self.statline = Status(self, self.printer, self.settings, self.logger)
        self.statline.grid(row=5, column=1, columnspan=4, sticky=E + W)

        self.logger.grid(row=2, column=4, rowspan=2, sticky=N + E + W)

        self.sendgcode = SendGCode(self, self.printer, self.settings, self.logger)
        self.sendgcode.grid(row=4, column=4, sticky=N + E + W)

        self.printer.errorcb = self.errorcb
        self.printer.sendcb = self.sendcb
        self.printer.recvcb = self.recvcb

        self.sd = SDCard(self, self.printer, self.settings, self.logger)
        self.firmware = FirmwareParms(self, self.printer, self.settings, self.logger)
        self.bind(MWM_FIRMWARECOMPLETE, self.firmwareReportComplete)
        self.bind(MWM_SLICERCOMPLETE, self.sliceComplete)
        self.bind(MWM_GCODELOADCOMPLETE, self.loadgcodeFinished)
        self.bind(MWM_GEDITMEASURECOMPLETE, self.geditMeasureComplete)
        self.bind(MWM_REQUESTPOSITIONREPORT, self.requestPosition)

        self.doShowButtons()

    def doStopAll(self):
        self.toolbar.doPause()
        self.temps.doOffBed()
        self.temps.doOffExt()

    def requestPosition(self, *arg):
        self.m114count += 1
        self.printer.send_now("M400")  # finish all moves
        self.printer.send_now("M114")

    def doShowButtons(self):
        self.settings.showmacrobuttons = self.cbShowMacroButtons.get() == 1
        self.settings.setModified()
        if self.settings.showmacrobuttons:
            self.macroButtons = MacroButtons(self, self.printer, self.settings, self.logger)
        else:
            if self.macroButtons:
                self.macroButtons.close()
                self.macroButtons = None

    def toggleShowPrevious(self):
        self.settings.showprevious = self.cbShowPrevious.get() == 1
        self.settings.setModified()
        self.gc.drawCanvas()

    def toggleShowMoves(self):
        self.settings.showmoves = self.cbShowMoves.get() == 1
        self.settings.setModified()
        self.gc.drawCanvas()

    def selSlicer(self):
        self.settings.slicer = self.rbSlicer.get()
        self.settings.setModified()
        self.toolbar.setSliceText()

    def macroButtonClose(self):
        self.settings.showmacrobuttons = False
        self.settings.setModified()
        self.cbShowMacroButtons.set(False)

    def firmwareReportComplete(self, *arg):
        p = self.firmware.reportComplete()
        if p is not None:
            self.acceleration = p["m204_s"].getFlash()
            print "Retrieved acc value of ", self.acceleration

    def openSTLFile(self):
        if self.printing:
            self.logger.logMsg("Cannot open a new file while printing")
            return

        if self.StlFile is None:
            fn = askopenfilename(
                filetypes=[("STL files", "*.stl"), ("G Code files", "*.gcode")], initialdir=self.settings.lastdirectory
            )
        else:
            fn = askopenfilename(
                filetypes=[("STL files", "*.stl"), ("G Code files", "*.gcode")],
                initialdir=self.settings.lastdirectory,
                initialfile=os.path.basename(self.StlFile),
            )
        if fn:
            self.settings.lastdirectory = os.path.dirname(os.path.abspath(fn))
            self.settings.setModified()
            if fn.lower().endswith(".gcode"):
                self.StlFile = None
                self.loadgcode(fn)
            elif fn.lower().endswith(".stl"):
                self.StlFile = fn
                self.doSlice(fn)
            else:
                self.logger.logMsg("Invalid file type")

    def openGCodeFile(self):
        if self.printing:
            self.logger.logMsg("Cannot open a new file while printing")
            return

        fn = askopenfilename(filetypes=[("G Code files", "*.gcode")], initialdir=self.settings.lastdirectory)
        if fn:
            self.settings.lastdirectory = os.path.dirname(os.path.abspath(fn))
            self.settings.setModified()
            if fn.lower().endswith(".gcode"):
                self.StlFile = None
                self.loadgcode(fn)
            else:
                self.logger.logMsg("Invalid file type")
        else:
            self.toolbar.clearCancelMode()

    def loadgcode(self, fn):
        self.GCodeFile = fn
        self.gcodeloadSuccess = True
        self.toolbar.setLoading(True)
        self.loader = threading.Thread(target=self.loadgcodeThread)
        self.loader.daemon = True
        self.loader.start()

    def loadgcodeFinished(self, *arg):
        self.toolbar.setLoading(False)
        if self.gcodeloadSuccess:
            self.showMetrics()

    def loadgcodeThread(self):
        try:
            self.gcode = []
            l = list(open(self.GCodeFile))
            for s in l:
                self.gcode.append(s.rstrip())
            self.logger.logMsg("read %d lines from %s" % (len(self.gcode), os.path.basename(self.GCodeFile)))
        except:
            self.logger.logMsg("Problem reading gcode from %s" % self.GCodeFile)
            self.gcode = []
            self.GCodeFile = None

        if len(self.gcode) != 0:
            self.logger.logMsg("Processing...")
            self.gc.loadFile(self.GCodeFile, self.gcode)
            self.printStartLine = self.gc.getPrintStartLine()
            self.gcodeInfo = GCode(self.gcode)
            self.logger.logMsg("Measuring...")
            self.gcodeInfo.measure(self.acceleration)
            self.estEta = self.gcodeInfo.totalduration
            self.timeLayers = self.gcodeInfo.layerdurations
        else:
            self.gcodeloadSuccess = False

        self.event_generate(MWM_GCODELOADCOMPLETE)

    def replace(self, s, slicer):
        if slicer == SLIC3R:
            d = os.path.expandvars(os.path.expanduser(self.settings.s3profiledir))
            profile = os.path.join(d, self.slic3r.getProfile() + ".ini")
        else:
            profile = self.skeinforge.getProfile()

        d = {}

        d["%starttime%"] = time.strftime("%H:%M:%S", time.localtime(self.startTime))
        d["%endtime%"] = time.strftime("%H:%M:%S", time.localtime(self.endTime))
        d["%elapsed%"] = formatElapsed(self.elapsedTime)

        d["%profile%"] = profile
        d["%slicer%"] = self.settings.slicer

        if self.StlFile is not None:
            d["%stlbase%"] = os.path.basename(self.StlFile)
            d["%stl%"] = self.StlFile
        else:
            d["%stlbase%"] = ""
            d["%stl%"] = ""

        if self.GCodeFile is not None:
            d["%gcodebase%"] = os.path.basename(self.GCodeFile)
            d["%gcode%"] = self.GCodeFile
        else:
            d["%gcodebase%"] = ""
            d["%gcode%"] = ""

        for t in d.keys():
            if d[t] is not None:
                s = s.replace(t, d[t])

        s = s.replace('""', "")
        return s

    def showMetrics(self):
        if len(self.gcode) != 0:
            self.paused = False
            self.toolbar.initializeToolbar()
            self.toolbar.checkAllowPrint()
            self.allowGEdit()

            self.logger.logMsg(
                "Width: %f mm (%f -> %f)" % (self.gcodeInfo.width, self.gcodeInfo.xmin, self.gcodeInfo.xmax)
            )
            self.logger.logMsg(
                "Depth: %f mm (%f -> %f)" % (self.gcodeInfo.depth, self.gcodeInfo.ymin, self.gcodeInfo.ymax)
            )
            self.logger.logMsg(
                "Height: is %f mm (%f -> %f)" % (self.gcodeInfo.height, self.gcodeInfo.zmin, self.gcodeInfo.zmax)
            )
            self.logger.logMsg("Total extrusion length: %f mm" % self.gcodeInfo.filament_length())

            self.logger.logMsg("Estimated print time: %s" % formatElapsed(self.estEta))

    def calcEta(self, line, timeThusFar):
        foundLayer = False
        for i in range(len(self.timeLayers)):
            if self.timeLayers[i][0] > line:
                foundLayer = True
                break

        if not foundLayer:
            return 0

        currentLayer = i - 1
        if currentLayer < 0:
            return 0

        totalInLayer = self.timeLayers[i][0] - self.timeLayers[currentLayer][0]
        printedInLayer = line - self.timeLayers[currentLayer][0]
        pct = printedInLayer / float(totalInLayer)
        thisLayerTime = (self.timeLayers[currentLayer][1]) * pct
        ratio = (self.timeLayers[currentLayer][2] - self.timeLayers[currentLayer][1] + thisLayerTime) / float(
            timeThusFar
        )
        ne = self.estEta / float(ratio)
        return ne - timeThusFar

    def doTimingReport(self):
        if not self.printing:
            self.logger.logMsg("Only available while printing")
            return

        self.timingReport = TimingReport(self, self.printer, self.settings, self.logger)

    def closeTimingReport(self):
        if self.timingReport is not None:
            self.timingReport.cleanup()
            self.timingReport.destroy()

        self.timingReport = None

    def doMeasurementsReport(self):
        if not self.gcodeInfo:
            self.logger.logMsg("Only available when GCode loaded")
            return

        self.measurementsReport = MeasurementsReport(self, self.printer, self.settings, self.logger)

    def closeMeasurementsReport(self):
        if self.measurementsReport is not None:
            self.measurementsReport.destroy()

        self.measurementsReport = None

    def doFilamentReport(self):
        if not self.gcodeInfo:
            self.logger.logMsg("Only available when GCode loaded")
            return
        if len(self.gcodeInfo.filLayers) == 0:
            self.logger.logMsg("No filament usage in this gcode")
            return

        self.filamentReport = FilamentReport(self, self.printer, self.settings, self.logger)

    def closeFilamentReport(self):
        if self.filamentReport is not None:
            self.filamentReport.destroy()

        self.filamentReport = None

    def closeAllReports(self):
        self.closeTimingReport()
        self.closeFilamentReport()
        self.closeMeasurementsReport()

    def doPlater(self):
        s = self.replace(self.settings.platercmd, self.settings.slicer)
        args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
        subprocess.Popen(args, close_fds=True)

    def doGCodeView(self):
        s = self.replace(self.settings.gcodeviewcmd, self.settings.slicer)
        args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
        subprocess.Popen(args, close_fds=True)

    def doSTLView(self):
        s = self.replace(self.settings.stlviewcmd, self.settings.slicer)
        self.logger.logMsg(s)
        args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
        subprocess.Popen(args, close_fds=True)

    def doOpenSCAD(self):
        s = self.replace(self.settings.openscadcmd, self.settings.slicer)
        args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
        subprocess.Popen(args, close_fds=True)

    def doSlice(self, fn):
        self.paused = False
        self.toolbar.initializeToolbar()
        self.slicerfn = fn
        self.slicing = True
        self.slicerCancel = False
        self.toolbar.setCancelMode()
        if self.settings.slicer == SLIC3R:
            self.GCodeFile = fn.replace(".stl", ".gcode")
            cmd = self.replace(os.path.expandvars(os.path.expanduser(self.settings.s3cmd)), SLIC3R)
        else:
            self.GCodeFile = fn.replace(".stl", "_export.gcode")
            cmd = self.replace(os.path.expandvars(os.path.expanduser(self.settings.sfcmd)), SKEINFORGE)
        self.slicer = threading.Thread(target=self.slicerThread, args=(cmd,))
        self.slicer.daemon = True
        self.slicer.start()

    def slicerThread(self, cmd):
        args = shlex.split(cmd)
        p = subprocess.Popen(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
        obuf = ""
        while not self.slicerCancel:
            o = p.stdout.read(1)
            if o == "":
                break
            if o == "\r" or o == "\n":
                self.logger.logMsg(obuf)
                obuf = ""
            elif ord(o) < 32:
                pass
            else:
                obuf += o

        if self.slicerCancel:
            p.kill()

        p.wait()
        self.event_generate(MWM_SLICERCOMPLETE)

    def sliceComplete(self, *arg):
        self.slicing = False
        self.toolbar.clearCancelMode()
        if self.slicerCancel:
            self.slicerCancel = False
            self.logger.logMsg("Slicing was cancelled")
            return

        self.logger.logMsg("Slicing has completed successfully")
        if os.path.exists(self.GCodeFile):
            self.loadgcode(self.GCodeFile)
        else:
            self.logger.logMsg("Unable to find slicer output file: %s" % self.GCodeFile)

    def allowGEdit(self):
        self.editmenu.entryconfig(self.editmenu.index(GCODE_MENU_TEXT), state=NORMAL)

    def allowSliceMenu(self, flag=True):
        s = NORMAL
        if not flag:
            s = DISABLED
        self.filemenu.entryconfig(self.slicemenuindex, state=s)

    def allowLoadGCodeMenu(self, flag=True):
        s = NORMAL
        if not flag:
            s = DISABLED
        self.filemenu.entryconfig(self.loadgcodemenuindex, state=s)

    def doGEdit(self):
        GEditor(self, self.gcode, None)

    def geditMeasureComplete(self, *arg):
        self.showMetrics()

    def gEditSave(self, newgcode, fn, editwindow):
        if fn == None:
            self.gcode = newgcode
            self.meas = threading.Thread(target=self.geditMeasureThread)
            self.meas.daemon = True
            self.meas.start()
            return False
        else:
            try:
                f = open(fn, "w")
                for l in newgcode:
                    f.write(l + "\n")
                f.close()
                self.logger.logMsg("Alterations %s successfully saved" % fn)
                return True
            except:
                self.logger.logMsg("Unable to open %s for output" % fn)
                return False

    def geditMeasureThread(self):
        self.logger.logMsg("Processing...")
        self.gcodeInfo = GCode(self.gcode)
        self.logger.logMsg("Measuring...")
        self.gcodeInfo.measure(self.acceleration)
        self.estEta = self.gcodeInfo.totalduration
        self.timeLayers = self.gcodeInfo.layerdurations
        self.event_generate(MWM_GEDITMEASURECOMPLETE)

    def doSD(self):
        if not self.sd.isActive():
            self.sd.start()

    def setToolbarSDPrint(self):
        self.toolbar.setSDPrint()

    def startUpload(self):
        self.sduploading = True
        self.toolbar.doPrint()

    def printerAvailable(self, silent=False, cmd="xx"):
        if not self.connected:
            if not silent:
                self.logger.logMsg("Unable to comply - printer not on-line")
            return False
        if (self.printing or self.sdprinting) and cmd.upper() not in allow_while_printing:
            if not silent:
                self.logger.logMsg("Unable to comply - currently printing")
            return False

        return True

    def printerConnected(self, flag):
        self.sendgcode.activate(flag)
        self.connected = flag
        if flag:
            self.firmware.start(True)

    def updateScreen(self):
        if self.printing:
            self.gc.updatePrintProgress(self.printer.queueindex)

    def errorcb(self, s):
        self.logger.logMsg(s.rstrip())

    def sendcb(self, s):
        # self.logger.logMsg("Sent: %s" % s)
        pass

    def recvcb(self, s):
        if self.readingFirmware:
            if "M92" in s:
                X = self.parseG(s, "X")
                Y = self.parseG(s, "Y")
                Z = self.parseG(s, "Z")
                E = self.parseG(s, "E")
                self.firmware.m92(X, Y, Z, E)
                return
            elif "M201" in s:
                X = self.parseG(s, "X")
                Y = self.parseG(s, "Y")
                Z = self.parseG(s, "Z")
                E = self.parseG(s, "E")
                self.firmware.m201(X, Y, Z, E)
                return
            elif "M203" in s:
                X = self.parseG(s, "X")
                Y = self.parseG(s, "Y")
                Z = self.parseG(s, "Z")
                E = self.parseG(s, "E")
                self.firmware.m203(X, Y, Z, E)
                return
            elif "M204" in s:
                S = self.parseG(s, "S")
                T = self.parseG(s, "T")
                self.firmware.m204(S, T)
                return
            elif "M205" in s:
                S = self.parseG(s, "S")
                T = self.parseG(s, "T")
                B = self.parseG(s, "B")
                X = self.parseG(s, "X")
                Z = self.parseG(s, "Z")
                E = self.parseG(s, "E")
                self.firmware.m205(S, T, B, X, Z, E)
                return
            elif "M206" in s:
                X = self.parseG(s, "X")
                Y = self.parseG(s, "Y")
                Z = self.parseG(s, "Z")
                self.firmware.m206(X, Y, Z)
                return
            elif "M301" in s:
                P = self.parseG(s, "P")
                I = self.parseG(s, "I")
                D = self.parseG(s, "D")
                self.firmware.m301(P, I, D)
                return
            elif (
                ("Steps per unit" in s)
                or ("Acceleration:" in s)
                or ("Maximum Acceleration" in s)
                or ("Maximum feedrates" in s)
                or ("Advanced variables" in s)
                or ("Home offset" in s)
                or ("PID settings" in s)
                or ("Stored settings retreived" in s)
            ):
                return

        if self.sdchecking:
            if "SD card ok" in s:
                self.sdchecking = False
                self.sdpresent = True
                self.sd.sdCheckComplete(True)
                return
            elif "SD init fail" in s:
                self.sdchecking = False
                self.sdpresent = False
                self.sd.sdCheckComplete(False)
                return

        if self.sdlisting:
            if "Begin file list" in s:
                self.insidelisting = True
                self.sdfiles = []
                return
            elif "End file list" in s:
                self.sdlisting = False
                self.insidelisting = False
                self.sd.sdListComplete(self.sdfiles)
                return
            else:
                if self.insidelisting:
                    self.sdfiles.append(s.strip())
                    return

        if "SD printing byte" in s:
            m = self.sdre.search(s)
            t = m.groups()
            if len(t) != 2:
                return

            self.sdbytes = int(t[0])
            self.sdmaxbytes = int(t[1])
            return

        elif "Done printing file" in s:
            if self.sdprinting:
                self.sdprinting = False
                self.toolbar.clearSDPrint()

        m = self.speedrptre.search(s)
        if m:
            t = m.groups()
            if len(t) >= 3:
                if self.settings.forcefanspeed:
                    ns = int(t[0])
                    if ns != self.FanSpeed:
                        self.logger.logMsg("Asserting fan speed of %d" % self.FanSpeed)
                        self.toolbar.forceFanSpeed(self.FanSpeed)
                else:
                    self.FanSpeed = int(t[0])

                self.FeedMultiply = int(t[1])
                self.ExtrudeMultiply = int(t[2])
                self.toolbar.syncSpeeds()
                if self.speedcount > 0:
                    self.speedcount -= 1
                    return

        m = self.locrptre.search(s)
        if m:
            t = m.groups()
            if len(t) >= 4:
                self.location[XAxis] = float(t[0])
                self.location[YAxis] = float(t[1])
                self.location[ZAxis] = float(t[2])
                self.location[EAxis] = float(t[3])
                if self.m114count != 0:
                    self.m114count -= 1
                    if self.m114count < 0:
                        self.m114count = 0
                    return

        if s.startswith("ok"):
            return

        if s.startswith("echo:"):
            s = s[5:]

        m = self.rpt1re.search(s)
        if m:
            t = m.groups()
            if len(t) >= 1:
                self.exttemp = float(t[0])
            if len(t) >= 2:
                self.bedtemp = float(t[1])
            self.temps.updateTempDisplay(self.bedtemp, self.exttemp)

        m = self.rpt2re.search(s)
        if m:
            t = m.groups()
            if len(t) >= 1:
                self.exttemp = float(t[0])
            self.temps.updateTempDisplay(self.bedtemp, self.exttemp)

        self.logger.logMsg(s.rstrip())

    def parseG(self, s, v):
        l = s.split()
        for p in l:
            if p.startswith(v):
                try:
                    return float(p[1:])
                except:
                    return None
        return None

    def editSettings(self):
        # TO DO
        pass

    def slic3rSettings(self):
        s = self.replace(self.settings.s3config, SLIC3R)
        args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
        subprocess.Popen(args, close_fds=True)

    def skeinforgeSettings(self):
        s = self.replace(self.settings.sfconfig, SKEINFORGE)
        args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
        subprocess.Popen(args, close_fds=True)

    def chooseS3Profile(self):
        pl = self.slic3r.getProfileOptions()
        if len(pl) > 0:
            l = ListBoxChoice(
                clist=pl.keys(), master=self, title="Choose Slic3r Profile", message="Choose Slic3r profile"
            )
            pn = l.returnValue()
            if pn:
                self.slic3r.setProfile(pn)
                self.setS3ProfileMenuText()
                self.toolbar.setSliceText()
        else:
            self.logger.logMsg("Unable to retrieve available slic3r profiles")

    def chooseSFProfile(self):
        pl = self.skeinforge.getProfileOptions()
        if len(pl) > 0:
            l = ListBoxChoice(
                clist=pl.keys(), master=self, title="Choose Skeinforge Profile", message="Choose Skeinforge profile"
            )
            pn = l.returnValue()
            if pn:
                self.skeinforge.setProfile(pn)
                self.setSFProfileMenuText()
                self.toolbar.setSliceText()
        else:
            self.logger.logMsg("Unable to retrieve available skeinforge profiles")

    def setS3ProfileMenuText(self):
        self.slicermenu.entryconfig(self.S3profileindex, label="Choose Profile (%s)" % self.slic3r.getProfile())

    def setSFProfileMenuText(self):
        self.slicermenu.entryconfig(self.SFprofileindex, label="Choose Profile (%s)" % self.skeinforge.getProfile())

    def quitApp(self):
        self.cleanUp()
        self.master.quit()

    def cleanUp(self):
        if self.connected:
            self.printer.disconnect()
        if self.slicing:
            self.slicerCancel = True
        self.httpServer.close()
        self.statline.cleanUp()
        self.settings.cleanUp()

    def doEditMacro(self):
        idir = os.path.join(self.settings.cmdFolder, "macros")
        try:
            l = os.listdir(idir)
        except:
            self.logger.logMsg("Unable to get listing from macros directory: " + idir)
            return
        r = []
        for f in sorted(l):
            if f.endswith(".macro"):
                r.append(f)

        l = ListBoxChoice(master=self, title="Macro Files", message="Choose a macro to edit", clist=r)
        fn = l.returnValue()
        if fn:
            try:
                fn = os.path.join(idir, fn)
                with open(fn) as f:
                    text = f.read()
                self.macroEdit = MacroEdit(self, self.printer, self.settings, self.logger, fn, text)
            except:
                self.logger.logMsg("Unable to open %s for input" % fn)

    def doEditAlterations(self):
        idir = os.path.expandvars(os.path.expanduser(self.settings.sfalterationsdir))
        try:
            l = os.listdir(idir)
        except:
            self.logger.logMsg("Unable to get listing from alterations directory: " + idir)
            return
        r = []
        for f in sorted(l):
            if f.endswith(".gcode"):
                r.append(f)

        l = ListBoxChoice(master=self, title="Alteration Files", message="Choose an alteration file to edit", clist=r)
        fn = l.returnValue()
        if fn:
            try:
                fn = os.path.join(idir, fn)
                text = [line.strip() for line in open(fn)]
                GEditor(self, text, fn)
            except:
                self.logger.logMsg("Unable to open %s for input" % fn)

    def doNewMacro(self):
        self.macroEdit = MacroEdit(self, self.printer, self.settings, self.logger, None, "")

    def doDelMacro(self):
        idir = os.path.join(self.settings.cmdFolder, "macros")
        try:
            l = os.listdir(idir)
        except:
            self.logger.logMsg("Unable to get listing from macros directory: " + idir)
            return
        r = []
        for f in sorted(l):
            if f.endswith(".macro"):
                r.append(f)

        l = ListBoxChoice(master=self, title="Macro Files", message="Choose a macro to delete", clist=r)
        fn = l.returnValue()
        if fn:
            if askyesno("Delete?", "Are you sure you want to delete this macro?", parent=self):
                try:
                    os.unlink(os.path.join(idir, fn))
                    self.adjustMacroMenu(fn, True)
                    if self.settings.getMacroList().delMacroByName(fn):
                        self.settings.setModified()
                        if self.settings.showmacrobuttons:
                            self.macroButtons.close()
                            self.macroButtons = MacroButtons(self, self.printer, self.settings, self.logger)
                except:
                    self.logger.logMsg("Unable to delete %s" % fn)

    def macroEditSave(self, fn, text, btn, editwindow):
        if fn == None:
            idir = os.path.join(self.settings.cmdFolder, "macros")

            try:
                l = os.listdir(idir)
            except:
                self.logger.logMsg("Unable to get listing from macros directory: " + idir)
                return

            r = []
            for f in sorted(l):
                if f.endswith(".macro"):
                    r.append(f)

            l = ListBoxChoice(
                master=self,
                title="Macro Files",
                message="Choose a macro to overwrite",
                clist=r,
                newmessage="or enter new file name:",
            )
            fn = l.returnValue()
            if fn:
                fn = os.path.join(idir, fn)
                if not fn.endswith(".macro"):
                    fn += ".macro"

        if fn is None:
            return False

        try:
            f = open(fn, "w")
            f.write(text)
            f.close()
            self.settings.setModified()
            if btn != None:
                if not self.settings.getMacroList().addMacro(btn[0], btn[1], os.path.basename(fn), btn[2]):
                    self.settings.getMacroList().setMacroByName(btn[0], btn[1], os.path.basename(fn), btn[2])
                self.settings.setModified()

                if self.settings.showmacrobuttons:
                    self.macroButtons.close()
                    self.macroButtons = MacroButtons(self, self.printer, self.settings, self.logger)

            self.adjustMacroMenu(fn)
            self.logger.logMsg("Macro %s successfully saved" % fn)
            return True
        except:
            self.logger.logMsg("Unable to open %s for output" % fn)
            return False

    def macroEditExit(self, fn):
        pass

    def loadMacros(self):
        p = os.path.join(".", "macros")
        if not os.path.exists(p):
            os.makedirs(p)

        self.macroList = {}
        for filename in sorted(os.listdir(p)):
            if filename.endswith(".macro"):
                n = os.path.splitext(filename)[0]
                self.macroList[n] = 1
                self.runmacromenu.add_command(label=n, command=lambda m=n: self.doMacro(m))

    def adjustMacroMenu(self, fn, delete=False):
        mn = os.path.splitext(os.path.basename(fn))[0]

        if not delete:
            if mn in self.macroList:
                # nothing to be done here
                pass
            else:
                self.macroList[mn] = 1
                self.runmacromenu.add_command(label=mn, command=lambda m=mn: self.doMacro(name=mn))
        else:
            # delete the menu entry
            if mn in self.macroList:
                self.runmacromenu.delete(self.runmacromenu.index(mn))
                del self.macroList[mn]
            else:
                # huh??
                pass

    def doMacro(self, name=None, fname=None, silent=False):
        if name:
            if not silent:
                self.logger.logMsg("Invoking macro: %s" % name)
            n = os.path.join(self.settings.cmdFolder, "macros", name + ".macro")
        elif fname:
            if not silent:
                self.logger.logMsg("Invoking macro file: %s" % fname)
            n = os.path.join(self.settings.cmdFolder, "macros", fname)
        else:
            self.logger.logMsg("Error - must provide a macro name or filename")
            return

        try:
            l = list(open(n))
            for s in l:
                sl = s.lower()
                if sl.startswith("@log "):
                    self.logger.logMsg(self.replace(s[5:].strip(), self.settings.slicer))
                elif sl.startswith("@sh "):
                    s = self.replace(sl[4:], self.settings.slicer)
                    args = shlex.split(os.path.expandvars(os.path.expanduser(s)))
                    subprocess.Popen(args, close_fds=True)
                else:
                    verb = s.split()[0]
                    if self.printerAvailable(cmd=verb):
                        self.printer.send_now(s)
                    else:
                        self.logger.logMsg("Printer not available for %s command" % verb)

            if not silent:
                self.logger.logMsg("End of macro")
        except:
            self.logger.logMsg("Error attempting to invoke macro file %s" % n)

    def FirmwareSettings(self):
        if self.connected:
            if not self.firmware.isActive():
                self.firmware.start()
        else:
            self.logger.logMsg("Unable to comply - printer not on-line")

    def doDataLogReport(self):
        for d in DLLIST:
            self.logger.logMsg("%s Usage: %s" % (DLNAMES[d], self.dataLoggers[d].getToNowStr()))

    def doDataLogReset(self):
        DataLogReset(self)
class Facade(Frame):
    """This is a Frame that contains group info, group order, apex and prime
    graph.
    """
    def __init__(self, parent, group, show_graph=True, graph_class=None, **kw):
        """
        Parameters:
            show_graph: whether to create and show graph instantly or provide a button to do that lately
            graph_factory: callable accepting one argument - the Group instance and returning Graph instance. Note that
                __str__ method of the callable is used in the UI.
        """
        Frame.__init__(self, parent, **kw)
        self._group = group
        #        self._show_apex = True
        self._show_graph = show_graph
        self._graph_class = graph_class
        self._init_variables()
        self._init_menu()
        self._init_components()

    @property
    def group(self):
        return self._group

    @property
    def apex_list_container(self):
        return self._apex_container

    @property
    def graph_canvas(self):
        return self._graph_canvas

    def _show_graph_canvas(self):
        self._show_graph_button.forget()
        # TODO: add different layouts and other options
        graph_class = self._graph_class
        self.graph = graph_class(self._group)
        self._graph_canvas = GraphCanvas(self._right_pane,
                                         SpringLayout(self.graph),
                                         caption=str(graph_class))
        self._graph_canvas.pack(expand=True, fill='both')

        self._graph_canvas.vertex_label_mode = self.getvar(
            name=self.winfo_name() + ".vertexlabelposition")

        self._iterations_plugin = IterationsPlugin()
        self._iterations_plugin.apply(self._graph_canvas)

        self.update_layout()

    def _init_components(self):
        self._panes = PanedWindow(self,
                                  orient='horizontal',
                                  sashrelief='raised')
        self._panes.pack(expand=True, fill='both')

        self._left_pane = Frame(self._panes, padx=2, pady=2)
        self._right_pane = Frame(self._panes)
        self._panes.add(self._left_pane, width=250)
        self._panes.add(self._right_pane)

        # group name
        group_name_pane = LabelFrame(self._left_pane,
                                     text="Group",
                                     padx=10,
                                     pady=5)
        group_name_pane.pack(fill='x')

        self._group_name = GroupNameLabel(group_name_pane, self._group)
        self._group_name.pack(expand=True, fill='both')

        # group order
        group_order_pane = LabelFrame(self._left_pane,
                                      text="Order",
                                      padx=10,
                                      pady=5)
        group_order_pane.pack(fill='x')

        self._group_order = IntegerContainer(group_order_pane,
                                             integer=self._group.order())
        self._group_order.pack(expand=True, fill='both')

        # apex
        self._apex_pane = LabelFrame(self._left_pane,
                                     text="Apex",
                                     padx=10,
                                     pady=5)
        self._apex_pane.pack(expand=True, fill='both')

        self._apex_container = ApexListContainer(self._apex_pane,
                                                 apex=self._group.apex())
        self._apex_container.pack(expand=True, fill='both')

        # graph controls
        cocliques_frame = LabelFrame(self._left_pane,
                                     text="Cocliques",
                                     padx=10,
                                     pady=5)
        cocliques_frame.pack(fill='x')

        self._cocliques_button = Button(cocliques_frame,
                                        text="Calculate",
                                        command=self._show_cocliques)
        self._cocliques_button.pack(anchor='nw')

        self._cocliques_container = ListContainer(cocliques_frame)
        self._cocliques_list = Listbox(self._cocliques_container)
        self._cocliques_container.set_listbox(self._cocliques_list)

        # Button(graph_controls, text='Group equivalent vertices').pack(anchor='nw')

        # this is a button that show up instead of graph canvas if we uncheck 'Show graph' checkbox.
        self._show_graph_button = Button(self._right_pane,
                                         text='Show graph',
                                         command=self._show_graph_canvas)
        self._graph_canvas = None
        if self._show_graph:
            self._show_graph_canvas()
        else:
            self._show_graph_button.pack()

    def _init_variables(self):
        def set_default_var(self, name):
            """Sets widget-specific var with same value as root.
            """
            default_var = self.getvar(name)
            local_var_name = self.winfo_name() + "." + name
            self.setvar(local_var_name, default_var)
            return local_var_name

        local_name = set_default_var(self, "vertexlabelposition")
        tools.trace_variable(self, local_name, "w",
                             self._change_vertex_label_position)

    def _change_vertex_label_position(self, name, *arg):
        # override default value
        self.setvar("vertexlabelposition", self.getvar(name))
        if self._graph_canvas is not None:
            self._graph_canvas.vertex_label_mode = self.getvar(name)

    def _init_menu(self):
        """Init menu bar content.
        """
        toplevel = self.winfo_toplevel()
        if toplevel['menu']:
            self._menu = self.nametowidget(name=toplevel['menu'])
        else:
            self._menu = Menu(toplevel)
            toplevel['menu'] = self._menu

        graph_options = Menu(self._menu, tearoff=0)
        self._menu.add_cascade(label="Graph", menu=graph_options)
        self._menu_index = self._menu.index("end")

        vertex_label_position_menu = Menu(graph_options, tearoff=0)
        graph_options.add_cascade(label="Label position",
                                  menu=vertex_label_position_menu)

        menu_var = self.winfo_name() + ".vertexlabelposition"
        vertex_label_position_menu.add_radiobutton(variable=menu_var,
                                                   label="Auto",
                                                   value="auto")
        vertex_label_position_menu.add_radiobutton(variable=menu_var,
                                                   label="Center",
                                                   value="center")

        graph_options.add_command(label="Save graph...",
                                  command=self.call_graph_save_dialog)

        self.bind("<Destroy>", self.__destroy_menu)

    #noinspection PyUnusedLocal
    def __destroy_menu(self, event):
        try:
            self._menu.delete(self._menu_index)
        except TclError:
            pass

    def _show_cocliques(self):
        cocliques = self.graph.max_cocliques()

        def select_coclique(*_):
            index = next(iter(self._cocliques_list.curselection()), None)
            if index is not None:
                selected = cocliques[int(index)]
                pick_state = self._graph_canvas.picked_vertex_state
                pick_state.clear()
                for value in selected:
                    pick_state.pick(self._graph_canvas.get_vertex(value))

        self._cocliques_list.insert(
            0, *[', '.join(map(str, coclique)) for coclique in cocliques])
        self._cocliques_list.bind("<Double-Button-1>", select_coclique)

        self._cocliques_button.forget()
        self._cocliques_container.pack(expand=True, fill='both')

    def call_graph_save_dialog(self):
        file_name = tkFileDialog.asksaveasfilename(
            defaultextension='.ps',
            filetypes=[('PostScript', '.ps')],
            parent=self.winfo_toplevel(),
            title="Save graph as image")
        if file_name:
            with codecs.open(file_name, 'w', encoding='utf-8') as f:
                f.write(self._graph_canvas.postscript())

    def update_layout(self):
        try:
            self._iterations_plugin.iterate(50)
        except AttributeError:
            pass
示例#10
0
class Facade(Frame):
    """This is a Frame that contains group info, group order, apex and prime
    graph.
    """
    def __init__(self, parent, group, show_graph=True, graph_class=None, **kw):
        """
        Parameters:
            show_graph: whether to create and show graph instantly or provide a button to do that lately
            graph_factory: callable accepting one argument - the Group instance and returning Graph instance. Note that
                __str__ method of the callable is used in the UI.
        """
        Frame.__init__(self, parent, **kw)
        self._group = group
        #        self._show_apex = True
        self._show_graph = show_graph
        self._graph_class = graph_class
        self._init_variables()
        self._init_menu()
        self._init_components()

    @property
    def group(self):
        return self._group

    @property
    def apex_list_container(self):
        return self._apex_container

    @property
    def graph_canvas(self):
        return self._graph_canvas

    def _show_graph_canvas(self):
        self._show_graph_button.forget()
        # TODO: add different layouts and other options
        graph_class = self._graph_class
        self.graph = graph_class(self._group)
        self._graph_canvas = GraphCanvas(self._right_pane, SpringLayout(self.graph), caption=str(graph_class))
        self._graph_canvas.pack(expand=True, fill='both')

        self._graph_canvas.vertex_label_mode = self.getvar(
            name=self.winfo_name() + ".vertexlabelposition")

        self._iterations_plugin = IterationsPlugin()
        self._iterations_plugin.apply(self._graph_canvas)

        self.update_layout()

    def _init_components(self):
        self._panes = PanedWindow(self, orient='horizontal', sashrelief='raised')
        self._panes.pack(expand=True, fill='both')

        self._left_pane = Frame(self._panes, padx=2, pady=2)
        self._right_pane = Frame(self._panes)
        self._panes.add(self._left_pane, width=250)
        self._panes.add(self._right_pane)

        # group name
        group_name_pane = LabelFrame(self._left_pane, text="Group", padx=10, pady=5)
        group_name_pane.pack(fill='x')

        self._group_name = GroupNameLabel(group_name_pane, self._group)
        self._group_name.pack(expand=True, fill='both')

        # group order
        group_order_pane = LabelFrame(self._left_pane, text="Order", padx=10, pady=5)
        group_order_pane.pack(fill='x')

        self._group_order = IntegerContainer(group_order_pane, integer=self._group.order())
        self._group_order.pack(expand=True, fill='both')

        # apex
        self._apex_pane = LabelFrame(self._left_pane, text="Apex", padx=10, pady=5)
        self._apex_pane.pack(expand=True, fill='both')

        self._apex_container = ApexListContainer(self._apex_pane, apex=self._group.apex())
        self._apex_container.pack(expand=True, fill='both')

        # graph controls
        cocliques_frame = LabelFrame(self._left_pane, text="Cocliques", padx=10, pady=5)
        cocliques_frame.pack(fill='x')

        self._cocliques_button = Button(cocliques_frame, text="Calculate", command=self._show_cocliques)
        self._cocliques_button.pack(anchor='nw')

        self._cocliques_container = ListContainer(cocliques_frame)
        self._cocliques_list = Listbox(self._cocliques_container)
        self._cocliques_container.set_listbox(self._cocliques_list)

        # Button(graph_controls, text='Group equivalent vertices').pack(anchor='nw')

        # this is a button that show up instead of graph canvas if we uncheck 'Show graph' checkbox.
        self._show_graph_button = Button(self._right_pane, text='Show graph',
                                         command=self._show_graph_canvas)
        self._graph_canvas = None
        if self._show_graph:
            self._show_graph_canvas()
        else:
            self._show_graph_button.pack()

    def _init_variables(self):
        def set_default_var(self, name):
            """Sets widget-specific var with same value as root.
            """
            default_var = self.getvar(name)
            local_var_name = self.winfo_name() + "." + name
            self.setvar(local_var_name, default_var)
            return local_var_name

        local_name = set_default_var(self, "vertexlabelposition")
        tools.trace_variable(self, local_name, "w",
                             self._change_vertex_label_position)

    def _change_vertex_label_position(self, name, *arg):
        # override default value
        self.setvar("vertexlabelposition", self.getvar(name))
        if self._graph_canvas is not None:
            self._graph_canvas.vertex_label_mode = self.getvar(name)

    def _init_menu(self):
        """Init menu bar content.
        """
        toplevel = self.winfo_toplevel()
        if toplevel['menu']:
            self._menu = self.nametowidget(name=toplevel['menu'])
        else:
            self._menu = Menu(toplevel)
            toplevel['menu'] = self._menu

        graph_options = Menu(self._menu, tearoff=0)
        self._menu.add_cascade(label="Graph", menu=graph_options)
        self._menu_index = self._menu.index("end")

        vertex_label_position_menu = Menu(graph_options, tearoff=0)
        graph_options.add_cascade(label="Label position", menu=vertex_label_position_menu)

        menu_var = self.winfo_name() + ".vertexlabelposition"
        vertex_label_position_menu.add_radiobutton(variable=menu_var, label="Auto", value="auto")
        vertex_label_position_menu.add_radiobutton(variable=menu_var, label="Center", value="center")

        graph_options.add_command(label="Save graph...", command=self.call_graph_save_dialog)

        self.bind("<Destroy>", self.__destroy_menu)

    #noinspection PyUnusedLocal
    def __destroy_menu(self, event):
        try:
            self._menu.delete(self._menu_index)
        except TclError:
            pass

    def _show_cocliques(self):
        cocliques = self.graph.max_cocliques()

        def select_coclique(*_):
            index = next(iter(self._cocliques_list.curselection()), None)
            if index is not None:
                selected = cocliques[int(index)]
                pick_state = self._graph_canvas.picked_vertex_state
                pick_state.clear()
                for value in selected:
                    pick_state.pick(self._graph_canvas.get_vertex(value))

        self._cocliques_list.insert(0, *[', '.join(map(str, coclique)) for coclique in cocliques])
        self._cocliques_list.bind("<Double-Button-1>", select_coclique)

        self._cocliques_button.forget()
        self._cocliques_container.pack(expand=True, fill='both')

    def call_graph_save_dialog(self):
        file_name = tkFileDialog.asksaveasfilename(defaultextension='.ps',
                                                   filetypes=[('PostScript', '.ps')], parent=self.winfo_toplevel(),
                                                   title="Save graph as image")
        if file_name:
            with codecs.open(file_name, 'w', encoding='utf-8') as f:
                f.write(self._graph_canvas.postscript())

    def update_layout(self):
        try:
            self._iterations_plugin.iterate(50)
        except AttributeError:
            pass
示例#11
0
class Facade(Frame):
    """This is a Frame that contains group info, group order, apex and prime
    graph.
    """

    def __init__(self, parent, group, show_graph=True, **kw):
        Frame.__init__(self, parent, **kw)
        self._group = group
        #        self._show_apex = True
        self._show_graph = show_graph
        self._init_variables()
        self._init_menu()
        self._init_components()


    @property
    def group(self):
        return self._group

    @property
    def apex_list_container(self):
        return self._apex_container

    @property
    def graph_canvas(self):
        return self._graph_canvas

    def _show_graph_canvas(self):
        self._show_graph_button.forget()
        # TODO: add different layouts and other options
        graph = self._group.prime_graph()
        self._graph_canvas = GraphCanvas(self._right_pane, SpringLayout(graph))
        self._graph_canvas.pack(expand=True, fill='both')

        self._graph_canvas.vertex_label_mode = self.getvar(
            name=self.winfo_name() + ".vertexlabelposition")

        self._iterations_plugin = IterationsPlugin()
        self._iterations_plugin.apply(self._graph_canvas)

        self.update_layout()

    def _init_components(self):
        self._panes = PanedWindow(self, orient='horizontal',
            sashrelief='raised')
        self._panes.pack(expand=True, fill='both')

        self._left_pane = Frame(self._panes, padx=2, pady=2)
        self._right_pane = Frame(self._panes)
        self._panes.add(self._left_pane, width=500)
        self._panes.add(self._right_pane)

        # group name
        group_name_pane = LabelFrame(self._left_pane, text="Group")
        group_name_pane.pack(fill='x')

        self._group_name = GroupNameLabel(group_name_pane, self._group)
        self._group_name.pack(expand=True, fill='both')

        # group order
        group_order_pane = LabelFrame(self._left_pane, text="Order")
        group_order_pane.pack(fill='x')

        self._group_order = IntegerContainer(group_order_pane,
            integer=self._group.order())
        self._group_order.pack(expand=True, fill='both')

        # apex
        self._apex_pane = LabelFrame(self._left_pane, text="Apex")
        self._apex_pane.pack(expand=True, fill='both')

        self._apex_container = ApexListContainer(self._apex_pane,
            apex=self._group.apex())
        self._apex_container.pack(expand=True, fill='both')

        self._show_graph_button = Button(self._right_pane, text='Show graph',
            command=self._show_graph_canvas)
        self._graph_canvas = None
        if self._show_graph:
            self._show_graph_canvas()
        else:
            self._show_graph_button.pack()

    def _init_variables(self):
        def set_default_var(self, name):
            """Sets widget-specific var with same value as root.
            """
            default_var = self.getvar(name)
            local_var_name = self.winfo_name() + "." + name
            self.setvar(local_var_name, default_var)
            return local_var_name

        local_name = set_default_var(self, "vertexlabelposition")
        tools.trace_variable(self, local_name, "w",
            self._change_vertex_label_position)

    def _change_vertex_label_position(self, name, *arg):
        # override default value
        self.setvar("vertexlabelposition", self.getvar(name))
        if self._graph_canvas is not None:
            self._graph_canvas.vertex_label_mode = self.getvar(name)


    def _init_menu(self):
        toplevel = self.winfo_toplevel()
        if toplevel['menu']:
            self._menu = self.nametowidget(name=toplevel['menu'])
        else:
            self._menu = Menu(toplevel)
            toplevel['menu'] = self._menu

        graph_options = Menu(self._menu, tearoff=0)
        self._menu.add_cascade(label="Graph", menu=graph_options)
        self._menu_index = self._menu.index("end")

        vertex_label_position = Menu(graph_options, tearoff=0)
        graph_options.add_cascade(label="Label position",
            menu=vertex_label_position)

        vertexlabelposition_var = self.winfo_name() + ".vertexlabelposition"
        vertex_label_position.add_radiobutton(label="Auto", value="auto",
            variable=vertexlabelposition_var)
        vertex_label_position.add_radiobutton(label="Center", value="center",
            variable=vertexlabelposition_var)

        graph_options.add_command(label="Save graph...",
            command=self.call_graph_save_dialog)

        self.bind("<Destroy>", self.__destroy_menu)

    #noinspection PyUnusedLocal
    def __destroy_menu(self, event):
        try:
            self._menu.delete(self._menu_index)
        except TclError:
            pass

    def call_graph_save_dialog(self):
        file_name = tkFileDialog.asksaveasfilename(defaultextension='.ps',
            filetypes=[('PostScript', '.ps')], parent=self.winfo_toplevel(),
            title="Save graph as image")
        if file_name:
            with codecs.open(file_name, 'w', encoding='utf-8') as file:
                file.write(self._graph_canvas.postscript())


    def update_layout(self):
        try:
            self._iterations_plugin.iterate(50)
        except AttributeError:
            pass