Пример #1
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 = tkinter.font.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 = tkinter.font.Font(family='helvetica', weight='bold',
                                    size=self._size.get())
        self._font = tkinter.font.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.label()['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 True
        elif self.shift(): return True
        else:
            if list(self._parser.parses()):
                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 True
        return False

    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 tkinter.messagebox 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 = " ".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].label().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.label()), 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.label()['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.label()['color'] = 'black'
            else:
                stackwidget['color'] = 'black'
Пример #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 = tkinter.font.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 = tkinter.font.Font(family='helvetica',
                                           weight='bold',
                                           size=self._size.get())
        self._font = tkinter.font.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.label()['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 True
        elif self.shift(): return True
        else:
            if list(self._parser.parses()):
                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 True
        return False

    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 tkinter.messagebox 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 = " ".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].label().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.label()),
                               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.label()['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.label()['color'] = 'black'
            else:
                stackwidget['color'] = 'black'
Пример #3
0
class Window2(Frame):
    def __init__(self, parent, context):
        super(Window2, self).__init__(parent.Frame)
        context.add_observer(self)
        self.parent = parent
        self.context = context
        self.controller = parent.controller
        self._initialize()

        self.build()

    def _initialize(self):
        self.id = 'task'
        #画面遷移の順番を示す指示物
        self.unit_name_list = []

    def update(self, msg):
        self._gid = msg['gid']
        self.unit_name_list = _global_group[msg['gid'] - 1]

        self._update_unit_name_list()
        self.task_lbx.delete(0, END)

    def convert(self):

        units = self._task_box.get(0, END)
        if not units:
            self.context.notify('root', 'info', "처리할 작업이 없습니다.")
            return

        working_dir = self.context.get('directory')
        if not working_dir:
            self.context.directory()
            working_dir = self.context.get('directory')
            if not working_dir:
                messagebox.showwarning("경고", '작업디렉토리가 설정되어 있지 않습니다.')
                return

        # html변환여부 체크옵션
        _html = self.context.get('html')
        _gid = self._gid

        try:
            self.controller.convert(working_dir, _html, units, _gid)

            self.context.notify('root', 'info', "작업완료")
            messagebox.showinfo("알림", message="작업이 완료되었습니다.")

        except Exception as e:
            print(e)
            messagebox.showerror('포맷오류', '잘못된 포맷을 적용하였습니다.')
            self.context.notify('root', 'info', "작업오류")

    def build(self):
        componets = self.component_list()
        tasks = self.task_list()

        componets.pack(side="left", fill='y')
        componets.config(bg='steel blue3')
        tasks.pack(side='right', fill='both', expand=True)
        tasks.config(bg='light grey', padx=3)

    def _update_unit_name_list(self):
        self._list.delete(0, END)

        for i, name in enumerate(self.unit_name_list):
            self._list.insert(i, name)

    def component_list(self):
        #sandy brown
        fr1 = Frame(self)
        scrollbar = Scrollbar(fr1)
        scrollbar.pack(side="right", fill="y")

        self._list = Listbox(fr1,
                             bg="dim grey",
                             fg="white",
                             width=20,
                             yscrollcommand=scrollbar.set)

        mb1 = Menubutton(fr1, text='선택메뉴', relief='flat', bg='steel blue3')
        mb1.menu = Menu(mb1, tearoff=0)
        mb1['menu'] = mb1.menu
        mb1.menu.add_command(label='등록', command=self.choose_all)
        mb1.pack(side='bottom')
        mb1.menu.add_command(label='선택',
                             command=lambda: self._list.select_set(0, END))
        mb1.menu.add_command(
            label='해제', command=lambda: self._list.selection_clear(0, 'end'))

        self._list.pack(anchor="center",
                        fill="both",
                        expand=True,
                        padx=3,
                        pady=3)

        scrollbar.config(command=self._list.yview)
        self._list.config(highlightcolor='green',
                          font=('나눔고딕', 10),
                          activestyle='none',
                          selectmode='extended')

        self._list.bind('<<ListboxSelect>>', self.select_item)
        self._list.bind('<Button-3>', self.sub_menu1)
        self._list.exportselection = 0

        return fr1

    def select_item(self, event):
        self.clear_submenu()

        widget = event.widget
        #print("select item",widget.curselection())
        # if isinstance(widget,Listbox):
        v = widget.curselection()
        t = [widget.get(i) for i in v]

        self.context.notify('root', 'info', t)

    def clear_submenu(self):
        if hasattr(self, 'sub_fr'):
            self.sub_fr.destroy()

    def _setActivate(self, obj, index):
        obj.selection_set(index)
        # obj.see(index)
        # obj.activate(index)
        # obj.selection_anchor(index)

    def sub_menu1(self, event):
        self.clear_submenu()
        x, y = event.x, event.y

        self._setActivate(self._list, self._list.nearest(y))

        self.sub_fr = Frame(self, height=10)
        b1 = Button(self.sub_fr,
                    text='reg',
                    command=self.choose_task,
                    relief='flat')
        b1.pack(side='top', fill='both')
        self.sub_fr.place(x=x + 15, y=y)

    def choose_all(self):
        self.clear_submenu()
        for el in self._list.get(0, END):
            if not self._isduplicate(el):
                self._task_box.insert(END, el)

    def choose_task(self):
        self.clear_submenu()
        ixs = self._list.curselection()
        for ix in ixs:
            el = self._list.get(ix)
            if not self._isduplicate(el):
                self._task_box.insert(END, el)

    def _isduplicate(self, txt):
        for v in list(self._task_box.get(0, 'end')):
            if v == txt: return True
        return False

    def task_list(self):
        fr = Frame(self)
        fr_top = Frame(fr)
        task_lbx = Listbox(fr_top)
        task_lbx.pack(anchor="center",
                      fill="both",
                      expand=True,
                      padx=3,
                      pady=3)
        self._task_box = task_lbx

        fr_bot = Frame(fr, height='2')
        b1 = Menubutton(fr_bot, text='실행메뉴')
        b1.menu = Menu(b1, tearoff=0)
        b1['menu'] = b1.menu
        b1.menu.add_command(label='실행', command=self.convert)
        b1.menu.add_command(label='비우기', command=self.delete_task_item)
        b1.pack()

        fr_top.pack(side='top', fill='both', expand=True)
        fr_bot.pack(side='bottom', fill='x')
        task_lbx.config(highlightcolor='green',
                        font=('굴림체', 10),
                        activestyle='none',
                        selectmode='extended')

        self.task_lbx = task_lbx

        return fr

    def delete_task_item(self):
        v = self.task_lbx.curselection()
        #print(v)
        if not v:
            self.task_lbx.delete(0, END)
        elif len(v) == 1:
            self.task_lbx.delete(v)
        else:
            self.task_lbx.delete(v[0], v[-1])
Пример #4
0
class ListboxVidget(Vidget, Eventor):
    """
    ListboxVidget contains a Listbox widget. It adds the following abilities:
    - Store items of any type, unlike Listbox widget that only stores texts.
    - Remember selected item even if the listbox widget lost focus.
    - Notify pre-change and post-change events.
    """

    # Error raised when trying to change the listbox while a change is going on
    class CircularCallError(ValueError):
        pass

    # Error raised when trying to change the listbox while it is disabled
    class DisabledError(ValueError):
        pass

    # Event notified when the listbox's items are to be changed
    ITEMS_CHANGE_SOON = 'ITEMS_CHANGE_SOON'

    # Event notified when the listbox's items are changed
    ITEMS_CHANGE_DONE = 'ITEMS_CHANGE_DONE'

    # Event notified when the listbox's active item is to be changed
    ITEMCUR_CHANGE_SOON = 'ITEMCUR_CHANGE_SOON'

    # Event notified when the listbox's active item is changed
    ITEMCUR_CHANGE_DONE = 'ITEMCUR_CHANGE_DONE'

    # Events list
    EVENTS = (
        ITEMS_CHANGE_SOON,
        ITEMS_CHANGE_DONE,
        ITEMCUR_CHANGE_SOON,
        ITEMCUR_CHANGE_DONE,
    )

    def __init__(
        self,
        items=None,
        item_to_text=None,
        normal_bg='',
        normal_fg='',
        active_bg='sky blue',
        active_fg='white',
        selected_bg='steel blue',
        selected_fg='white',
        master=None,
    ):
        """
        Initialize object.

        @param items: Items list.

        @param item_to_text: Item-to-text function. Default is `str`.

        @param normal_bg: Unselected item background color.

        @param normal_fg: Unselected item foreground color.

        @param active_bg: Active item background color. `Active` means the item
        is selected (in general meaning) but the listbox has no focus.

        @param active_fg: Active item foreground color. `Active` means the item
        is selected (in general meaning) but the listbox has no focus.

        @param selected_bg: Selected item background color. `Selected` means
        the item is selected (in general meaning) and the listbox has focus.

        @param selected_fg: Selected item foreground color. `Selected` means
        the item is selected (in general meaning) and the listbox has focus.

        @param master: Master widget.

        @return: None.
        """
        # Initialize Vidget.
        # Create main frame widget.
        Vidget.__init__(
            self,
            master=master,
        )

        # Initialize Eventor
        Eventor.__init__(self)

        # If items list is given
        if items is not None:
            # If items list is not list
            if not isinstance(items, list):
                # Raise error
                raise TypeError(items)

            # If items list is list.

        # If items list is not given, or items list is given and is list

        # Items list
        self._items = items if items is not None else []

        # Item-to-text function. Default is `str`.
        self._item_to_text = item_to_text if item_to_text is not None else str

        # Unselected item background color
        self._normal_fg = normal_fg

        # Unselected item foreground color
        self._normal_bg = normal_bg

        # Active item background color
        self._active_fg = active_fg

        # Active item foreground color
        self._active_bg = active_bg

        # Selected item background color
        self._selected_fg = selected_fg

        # Selected item foreground color
        self._selected_bg = selected_bg

        # Whether the listbox is changing
        self._is_changing = False

        # Active index. `-1` means void, i.e. no item is active.
        self._indexcur = -1

        # Whether active index is being reset to same value
        self._is_resetting = False

        # Create listbox widget
        self._listbox = Listbox(
            master=self.widget(),
            relief='groove',
            activestyle='none',
            highlightthickness=0,
            # Active index cache only supports single-selection mode for now.
            # See 2N6OR.
            selectmode='single',
        )

        # Set the listbox widget as config target
        self.config_target_set(self._listbox)

        # Create x-axis scrollbar
        self._scrollbar_xview = _HiddenScrollbar(
            self.widget(),
            orient=HORIZONTAL,
        )

        # Create y-axis scrollbar
        self._scrollbar_yview = _HiddenScrollbar(
            self.widget(),
            orient=VERTICAL,
        )

        # Mount scrollbars
        self._listbox.config(xscrollcommand=self._scrollbar_xview.set)

        self._listbox.config(yscrollcommand=self._scrollbar_yview.set)

        self._scrollbar_xview.config(command=self._listbox.xview)

        self._scrollbar_yview.config(command=self._listbox.yview)

        # Bind single-click event handler
        self._listbox.bind('<Button-1>', self._on_single_click)

        # Bind double-click event handler
        self._listbox.bind('<Double-Button-1>', self._on_double_click)

        # Update listbox widget
        self._listbox_widget_update(keep_active=False)

        # Update widget
        self._widget_update()

    def _widget_update(self):
        """
        Update widget.

        @return: None.
        """
        # Row 0 for listbox and y-axis scrollbar
        self.widget().rowconfigure(0, weight=1)

        # Row 1 for x-axis scrollbar
        self.widget().rowconfigure(1, weight=0)

        # Column 0 for listbox and x-axis scrollbar
        self.widget().columnconfigure(0, weight=1)

        # Column 1 for y-axis scrollbar
        self.widget().columnconfigure(1, weight=0)

        # Lay out listbox
        self._listbox.grid(row=0, column=0, sticky='NSEW')

        # Lay out x-axis scrollbar
        self._scrollbar_xview.grid(row=1, column=0, sticky='EW')

        # Lay out y-axis scrollbar
        self._scrollbar_yview.grid(row=0, column=1, sticky='NS')

    def is_enabled(self):
        """
        Test whether the listbox is enabled.

        @return: Boolean.
        """
        # Return whether the listbox is enabled
        return self._listbox.config('state')[4] != DISABLED

    def is_changing(self):
        """
        Test whether the listbox is changing.

        @return: Boolean.
        """
        # Return whether the listbox is changing
        return self._is_changing

    def is_resetting(self):
        """
        Test whether the listbox is setting active index to the same value.

        @return: Boolean.
        """
        # Return whether the listbox is setting active index to the same value
        return self._is_resetting

    def size(self):
        """
        Get number of items.

        @return: Number of items.
        """
        # Return number of items
        return len(self._items)

    def items(self):
        """
        Get items list.
        Notice do not change the list outside.

        @return: Items list.
        """
        # Return items list
        return self._items

    def items_set(
        self,
        items,
        notify=True,
        keep_active=False,
    ):
        """
        Set items list.

        Notice do not change the list outside.

        @param items: Items list.

        @param notify: Whether notify pre-change and post-change events.

        @param keep_active: Whether keep or clear active index.

        @return: None.
        """
        # If the items is not list
        if not isinstance(items, list):
            # Raise error
            raise TypeError(items)

        # If the items is list.

        # If the listbox is disabled
        if not self.is_enabled():
            # Raise error
            raise ListboxVidget.DisabledError()

        # If the listbox is not disabled.

        # If the listbox is changing
        if self._is_changing:
            # Raise error
            raise ListboxVidget.CircularCallError()

        # If the listbox is not changing.

        # Set changing flag on
        self._is_changing = True

        # If notify events
        if notify:
            # Notify pre-change event
            self.handler_notify(self.ITEMS_CHANGE_SOON)

        # Store the new items
        self._items = items

        # Update listbox widget
        self._listbox_widget_update(
            keep_active=keep_active
        )

        # If notify events
        if notify:
            # Notify post-change event
            self.handler_notify(self.ITEMS_CHANGE_DONE)

        # Set changing flag off
        self._is_changing = False

    def index_is_valid(self, index):
        """
        Test whether given index is valid. Notice -1 is not valid.

        @param index: Index to test.

        @return: Boolean.
        """
        # Test whether given index is valid
        return 0 <= index and index < self.size()

    def index_is_valid_or_void(self, index):
        """
        Test whether given index is valid or is -1.

        @param index: Index to test.

        @return: Boolean.
        """
        # Test whether given index is valid or is -1
        return index == -1 or self.index_is_valid(index)

    def index_first(self):
        """
        Get the first item's index.

        @return: First item's index, or -1 if the listbox is empty.
        """
        # Return the first item's index
        return 0 if self.size() > 0 else -1

    def index_last(self):
        """
        Get the last item's index.

        @return: Last item's index, or -1 if the listbox is empty.
        """
        # Return the last item's index
        return self.size() - 1

    def indexcur(self, internal=False, raise_error=False):
        """
        Get the active index.

        @param internal: See 2N6OR.

        @return: The active index. If no active active, either return -1, or
        raise IndexError if `raise_error` is True.
        """
        # Get active indexes
        indexcurs = self._indexcurs(internal=internal)

        # If have active indexes
        if indexcurs:
            # Return the first active index
            return indexcurs[0]

        # If no active indexes
        else:
            # If raise error
            if raise_error:
                # Raise error
                raise IndexError(-1)

            # If not raise error
            else:
                # Return -1
                return -1

    def _indexcurs(self, internal=False):
        """
        Get active indexes list.

        2N6OR
        @param internal: Whether use listbox widget's selected indexes, instead
        of cached active index.
        Notice listbox widget has no selected indexes if it has no focus.
        Notice using cached active index only supports single-selection mode,
        which means the result list has at most one index.

        @return: Active indexes list.
        """
        # If use listbox widget's selected indexes
        if internal:
            # Return listbox widget's selected indexes list
            return [int(x) for x in self._listbox.curselection()]

        # If not use listbox widget's selected indexes
        else:
            # If cached active index is valid
            if self.index_is_valid(self._indexcur):
                # Return a list with the cached active index
                return [self._indexcur]

            # If cached active index is not valid
            else:
                # Return empty list
                return []

    def indexcur_set(
        self,
        index,
        focus=False,
        notify=True,
        notify_arg=None,
    ):
        """
        Set active index.

        @param index: The index to set.

        @param focus: Whether set focus on the listbox widget.

        @param notify: Whether notify pre-change and post-change events.

        @param notify_arg: Event argument.

        @return: None.
        """
        # If the index is not valid or -1
        if not self.index_is_valid_or_void(index):
            # Raise error
            raise IndexError(index)

        # If the index is valid or is -1.

        # If the listbox is not enabled
        if not self.is_enabled():
            # Raise error
            raise ListboxVidget.DisabledError()

        # If the listbox is enabled.

        # If the listbox is changing
        if self._is_changing:
            # Raise error
            raise ListboxVidget.CircularCallError()

        # If the listbox is not changing.

        # Set changing flag on
        self._is_changing = True

        # Get old active index
        old_indexcur = self._indexcur

        # Set resetting flag on if new and old indexes are equal
        self._is_resetting = (index == old_indexcur)

        # If notify events
        if notify:
            # Notify pre-change event
            self.handler_notify(self.ITEMCUR_CHANGE_SOON, notify_arg)

        # If old active index is valid
        if self.index_is_valid(old_indexcur):
            # Set old active item's background color to normal color
            self._listbox.itemconfig(old_indexcur, background=self._normal_bg)

            # Set old active item's foreground color to normal color
            self._listbox.itemconfig(old_indexcur, foreground=self._normal_fg)

        # Cache new active index
        self._indexcur = index

        # Clear listbox widget's selection
        self._listbox.selection_clear(0, END)

        # Set listbox widget's selection
        self._listbox.selection_set(index)

        # Set listbox widget's activated index
        self._listbox.activate(index)

        # If new active index is valid
        if index != -1:
            # Set new active item's background color to active color
            self._listbox.itemconfig(index, background=self._active_bg)

            # Set new active item's foreground color to active color
            self._listbox.itemconfig(index, foreground=self._active_fg)

        # If set focus
        if focus:
            # Set focus on the listbox widget
            self._listbox.focus_set()

        # If new active index is valid
        if index != -1:
            # Make the active item visible
            self._listbox.see(index)

        # If notify events
        if notify:
            # Notify post-change event
            self.handler_notify(self.ITEMCUR_CHANGE_DONE, notify_arg)

        # Set resetting flag off
        self._is_resetting = False

        # Set changing flag off
        self._is_changing = False

    def indexcur_set_by_event(
        self,
        event,
        focus=False,
        notify=True,
        notify_arg=None,
    ):
        """
        Set active index using a Tkinter event object that contains coordinates
        of the active item.

        @param event: Tkinter event object.

        @param focus: Whether set focus on the listbox widget.

        @param notify: Whether notify pre-change and post-change events.

        @param notify_arg: Event argument.

        @return: None.
        """
        # Get the event's y co-ordinate's nearest listbox item index
        index = self._listbox.nearest(event.y)

        # If the index is not valid
        if not self.index_is_valid_or_void(index):
            # Ignore the event
            return

        # If the index is valid
        else:
            # Set the index as active index
            self.indexcur_set(
                index=index,
                focus=focus,
                notify=notify,
                notify_arg=notify_arg,
            )

    def item(self, index):
        """
        Get item at given index.

        @return: Item at given index, or IndexError if the index is not valid.
        """
        return self.items()[index]

    def itemcur(self, internal=False, raise_error=False):
        """
        Get the active item.

        @param internal: See 2N6OR.

        @param raise_error: Whether raise error if no active item.

        @return: The active item. If no active item, if `raise_error` is
        True, raise IndexError, otherwise return None.
        """
        # Get active index.
        # May raise IndexError if `raise_error` is True.
        indexcur = self.indexcur(
            internal=internal,
            raise_error=raise_error,
        )

        # If no active index
        if indexcur == -1:
            # Return None
            return None

        # If have active index
        else:
            # Return the active item
            return self.items()[indexcur]

    def item_insert(
        self,
        item,
        index=None,
        notify=True,
        keep_active=True,
    ):
        """
        Insert item at given index.

        @param item: Item to insert.

        @param index: Index to insert. `None` means active index, and if no
        active index, insert at the end.

        @param notify: Whether notify pre-change and post-change events.

        @param keep_active: Whether keep or clear active index.

        @return: None.
        """
        # If notify events
        if notify:
            # Notify pre-change events
            self.handler_notify(self.ITEMCUR_CHANGE_SOON)

            self.handler_notify(self.ITEMS_CHANGE_SOON)

        # Get old active index
        active_index = self.indexcur()

        # If the index is None,
        # it means use active index.
        if index is None:
            # Use active index.
            # `-1` works and means appending.
            index = active_index

        # Insert the item to the items list
        self._items.insert(index, item)

        # If old active index is valid
        if active_index != -1:
            # If old active index is GE the inserted index
            if active_index >= index:
                # Shift active index by one
                active_index += 1

            # If old active index is not GE the inserted index, use it as-is.

        # Set new active index
        self.indexcur_set(index=active_index, notify=False)

        # Update listbox widget
        self._listbox_widget_update(
            keep_active=keep_active
        )

        # If notify events
        if notify:
            # Notify post-change events
            self.handler_notify(self.ITEMS_CHANGE_DONE)

            self.handler_notify(self.ITEMCUR_CHANGE_DONE)

    def item_remove(
        self,
        index,
        notify=True,
        keep_active=True,
    ):
        """
        Remove item at given index.

        @param index: Index to remove.

        @param notify: Whether notify pre-change and post-change events.

        @param keep_active: Whether keep or clear active index.

        @return: None.
        """
        # If the index is not valid
        if not self.index_is_valid(index):
            # Raise error
            raise ValueError(index)

        # If the index is valid.

        # If notify events
        if notify:
            # Notify pre-change events
            self.handler_notify(self.ITEMCUR_CHANGE_SOON)

            self.handler_notify(self.ITEMS_CHANGE_SOON)

        # Get old active index
        active_index = self.indexcur()

        # Remove item at the index
        del self._items[index]

        # If old active index is valid
        if active_index != -1:
            # Get the last index
            index_last = self.index_last()

            # If old active index is GT the last index
            if active_index > index_last:
                # Use the last index as new active index
                active_index = index_last

            # If old active index is not GT the last index, use it as-is.

        # Set new active index
        self.indexcur_set(index=active_index, notify=False)

        # Update listbox widget
        self._listbox_widget_update(
            keep_active=keep_active
        )

        # If notify events
        if notify:
            # Notify post-change events
            self.handler_notify(self.ITEMS_CHANGE_DONE)

            self.handler_notify(self.ITEMCUR_CHANGE_DONE)

    def handler_add(
        self,
        event,
        handler,
        need_arg=False,
    ):
        """
        Add event handler for an event.
        If the event is ListboxVidget event, add the event handler to Eventor.
        If the event is not ListboxVidget event, add the event handler to
        listbox widget.

        Notice this method overrides `Eventor.handler_add` in order to add
        non-ListboxVidget event handler to listbox widget.

        @param event: Event name.

        @param handler: Event handler.

        @param need_arg: Whether the event handler needs event argument.

        @return: None.
        """
        # If the event is ListboxVidget event
        if event in self.EVENTS:
            # Add the event handler to Eventor
            return Eventor.handler_add(
                self,
                event=event,
                handler=handler,
                need_arg=need_arg,
            )

        # If the event is not ListboxVidget event,
        # it is assumed to be Tkinter widget event.
        else:
            # Add the event handler to listbox widget
            return self.bind(
                event=event,
                handler=handler,
            )

    def bind(
        self,
        event,
        handler,
    ):
        """
        Add event handler to listbox widget.

        ListboxVidget internally uses `<Button-1>` and `<Double-Button-1>` to
        capture active index changes. So if the given event is `<Button-1>` or
        `<Double-Button-1>`, the given handler will be wrapped.

        @param event: Event name.

        @param handler: Event handler.

        @return: None.
        """
        # If the event is not `<Button-1>` or `<Double-Button-1>`
        if event not in ['<Button-1>', '<Double-Button-1>']:
            # Add the event handler to listbox widget
            self._listbox.bind(event, handler)

        # If the event is `<Button-1>` or `<Double-Button-1>`
        else:
            # Create event handler wrapper
            def handler_wrapper(e):
                """
                Event handler wrapper that sets new active index and then calls
                the wrapped event handler.

                Setting new active index is needed because when this handler is
                called by Tkinter, the active index of the listbox is still
                old.

                @param e: Tkinter event object.

                @return: None.
                """
                # Set new active index
                self.indexcur_set_by_event(e, notify=True)

                # Call the wrapped event handler
                handler(e)

            # Add the event handler wrapper to the listbox widget
            self._listbox.bind(event, handler_wrapper)

    def _on_single_click(self, event):
        """
        `<Button-1>` event handler that updates active index.

        @param event: Tkinter event object.

        @return: None.
        """
        # Updates active index
        self.indexcur_set_by_event(event, notify=True)

    def _on_double_click(self, event):
        """
        `<Double-Button-1>` event handler that updates active index.

        @param event: Tkinter event object.

        @return: None.
        """
        # Updates active index
        self.indexcur_set_by_event(event, notify=True)

    def _listbox_widget_update(
        self,
        keep_active,
    ):
        """
        Update listbox widget's items and selection.

        @param keep_active: Whether keep or clear active index.

        @return: None.
        """
        # Remove old items from listbox widget
        self._listbox.delete(0, END)

        # Insert new items into listbox widget.
        # For each ListboxVidget items.
        for index, item in enumerate(self.items()):
            # Get item text
            item_text = self._item_to_text(item)

            # Insert the item text into listbox widget
            self._listbox.insert(index, item_text)

            # Set the item's normal background color
            self._listbox.itemconfig(index, background=self._normal_bg)

            # Set the item's normal foreground color
            self._listbox.itemconfig(index, foreground=self._normal_fg)

            # Set the item's selected background color
            self._listbox.itemconfig(index, selectbackground=self._selected_bg)

            # Set the item's selected foreground color
            self._listbox.itemconfig(index, selectforeground=self._selected_fg)

        # If keep active index
        if keep_active:
            # Use old active index
            indexcur = self._indexcur

        # If not keep active index
        else:
            # Set active index to -1
            indexcur = self._indexcur = -1

        # Clear old selection
        self._listbox.selection_clear(0, END)

        # Set new selection.
        # `-1` works.
        self._listbox.selection_set(indexcur)

        # Set new active index.
        # `-1` works.
        self._listbox.activate(indexcur)

        # If new active index is valid
        if indexcur != -1:
            # Set active background color
            self._listbox.itemconfig(indexcur, background=self._active_bg)

            # Set active foreground color
            self._listbox.itemconfig(indexcur, foreground=self._active_fg)

            # Make the active item visible
            self._listbox.see(indexcur)
Пример #5
0
class Application(Frame):
    """Container class, encapsulates app"""

    # this class inherits from Tkinter.parent
    def __init__(self, path, master=None):
        """Constructor"""
        # call parent class constructor
        Frame.__init__(self, master)

        self.path = path
        self.image_filenames = find_image_files(path)

        # necessary to make the application actually appear on the screen
        self.grid(sticky=N + S + E + W)

        # PIL image, for loading from file and for resizing
        self.image_pil = None

        # Tk photoimage, for display
        self.image_tk = None

        # list of coords (2-element lists) of current selection's pts
        self.points_orig = []

        # points_canvas is 'points_orig', in canvas coordinates
        self.points_canvas = []

        # x- and y-coords of displayed image in canvas coordinate frame
        self.x_offset = -1
        self.y_offset = -1

        # font in listbox text
        self.font = Font(family='Helvetica', size=10, weight='normal')

        # crosshair line size , as fraction of
        # min(displayed-imagewidth, displayed-image-height)
        self.crosshair_fraction = 0.05

        # color for drawing first crosshair - the origin
        self.crosshair1_color = 'red'

        # color for drawing second crosshair - (together with the first
        # crosshair, these two points define a coordinate frame) -
        self.crosshair2_color = 'green'

        # color for drawing third and on crosshairs - all points other
        # than the first and second, have this color
        self.crosshair3_color = 'cyan'

        # the width, in pixels, of crosshairs
        self.crosshair_thickness = 2

        # length of crosshair (updated upon display)
        self.crosshair_radius = -1

        # the scale of currently displayed image (updated upon display)
        self.image_scaling = 1.0

        # create all widges and set their initial conditions
        self.create_widgets()

    def create_widgets(self):
        """Set up all application graphics"""
        # get the top level winddow
        top = self.winfo_toplevel()

        # set the title of the top level window
        top.title('Image Point Tagging Tool')

        # make row 0 of the top level window's grid stretchable
        top.rowconfigure(0, weight=1)

        # make column 0 of the top level window's grid stretchable
        top.columnconfigure(0, weight=1)

        # bind keys for entire app
        top.bind_all('<Up>', self.select_prev)
        top.bind_all('<Down>', self.select_next)

        # make row 0 of Application's widget's grid stretchable
        self.rowconfigure(0, weight=1)

        # make column 0 of Application's widget's grid stretchable
        self.columnconfigure(0, weight=1)

        self.canvas = Canvas(self, bg='gray')
        self.canvas.grid(row=0, column=0, rowspan=2, sticky=N + S + E + W)
        self.canvas.rowconfigure(0, weight=1)
        self.canvas.columnconfigure(0, weight=1)

        # bind resize events (need -4 here bec. event gives 4+(real_size))
        self.canvas.bind(
            '<Configure>',
            lambda e, s=self: s.on_resize_canvas(e.width - 2, e.height - 2))

        # bind canvas mouse clicks
        self.canvas.bind('<Button-1>', self.on_click_button1)
        self.canvas.bind('<Button-3>', self.on_click_button3)

        # create scrollbars
        self.scrollbar_x = Scrollbar(self, orient=HORIZONTAL, width=10)
        self.scrollbar_y = Scrollbar(self, orient=VERTICAL, width=10)

        self.scrollbar_x.grid(row=1, column=1, columnspan=2, sticky=E + W)
        self.scrollbar_y.grid(row=0, column=3, sticky=N + S)

        # create lb for showing labeled/not-labeled images
        self.listbox_marks = Listbox(self,
                                     width=1,
                                     takefocus=0,
                                     exportselection=0,
                                     font=self.font)

        self.listbox_marks.grid(row=0, column=1, sticky=N + S + E + W)

        # create lb for showing image filenames
        self.lisbox_filenames = Listbox(self,
                                        width=30,
                                        selectmode=SINGLE,
                                        xscrollcommand=self.scrollbar_x.set,
                                        yscrollcommand=self.scrollbar_y.set,
                                        exportselection=0,
                                        font=self.font)
        self.lisbox_filenames.grid(row=0, column=2, sticky=N + S + E + W)

        # bind scrollbar movement
        self.scrollbar_x['command'] = self.lisbox_filenames.xview
        self.scrollbar_y['command'] = self.on_scrollbar_y

        # bind left mouse click selection
        self.lisbox_filenames.bind(
            '<Button-1>',
            lambda e, s=self: s.select(self.lisbox_filenames.nearest(e.y)))
        self.listbox_marks.bind(
            '<Button-1>',
            lambda e, s=self: s.select(self.lisbox_filenames.nearest(e.y)))

        # bind wheel scroll
        self.lisbox_filenames.bind(
            '<Button-4>',
            lambda e, s=self: on_mousewheel(self.listbox_marks, 4))
        self.lisbox_filenames.bind(
            '<Button-5>',
            lambda e, s=self: on_mousewheel(self.listbox_marks, 5))
        self.listbox_marks.bind(
            '<Button-4>',
            lambda e, s=self: on_mousewheel(self.lisbox_filenames, 4))
        self.listbox_marks.bind(
            '<Button-5>',
            lambda e, s=self: on_mousewheel(self.lisbox_filenames, 5))

        # skip is # of chars to skip in path string so that only the
        # part of the path that was not supplied is displayed
        skip = len(self.path)
        if self.path[skip - 1] != '/':
            skip += 1

        # insert image filenames plus marks into lists and
        # select first image that does not have pts file
        i = 0
        index_of_image_with_no_pts_file = -1
        for image_filename in self.image_filenames:
            self.lisbox_filenames.insert(END, image_filename[skip:])
            if self.has_pts_file(i):
                self.listbox_marks.insert(END, '+')
            else:
                self.listbox_marks.insert(END, '')
                if index_of_image_with_no_pts_file < 0:
                    index_of_image_with_no_pts_file = i
            i += 1

        if index_of_image_with_no_pts_file < 0:
            self.select(0)
        else:
            self.select(index_of_image_with_no_pts_file)

    def on_scrollbar_y(self, *args):
        """Vertical scrollbar motion callback"""
        apply(self.lisbox_filenames.yview, args)
        apply(self.listbox_marks.yview, args)

    def on_click_button1(self, event):
        """Button 1 click callback: adds a crosshair at click location"""
        if self.coord_in_img(event.x, event.y):
            point = [(event.x - self.x_offset) / self.image_scaling,
                     (event.y - self.y_offset) / self.image_scaling]
            point_scaled = [float(event.x), float(event.y)]
            self.points_orig.append(point)
            self.points_canvas.append(point_scaled)
            if len(self.points_orig) == 1:
                self.mark_labeled()
            self.on_resize_canvas(int(self.canvas['width']),
                                  int(self.canvas['height']))
            self.save_points()

    def on_click_button3(self, event):
        """Button 3 click callback: deletes landmark near click location"""
        if not self.coord_in_img(event.x, event.y):
            return
        i = self.find_point_near_crosshair(event.x, event.y)
        if i >= 0:
            del self.points_orig[i]
            del self.points_canvas[i]
            if len(self.points_orig) == 0:
                self.mark_unlabeled()
            self.on_resize_canvas(int(self.canvas['width']),
                                  int(self.canvas['height']))
            self.save_points()

    def select(self, i):
        """Select the i'th image to work with - make current selection = i"""
        # uncomment the following line if you are only dealing with
        # faces that have three points labeled on them and you want to
        # automatically reorder a previously tagged database so that
        # the person's right eye is the first point, left eye is
        # second point and mouth is third point
        self.sort_points()
        self.lisbox_filenames.selection_clear(0, END)
        self.listbox_marks.selection_clear(0, END)
        self.lisbox_filenames.selection_set(i)
        self.listbox_marks.selection_set(i)
        self.lisbox_filenames.see(i)
        self.listbox_marks.see(i)
        self.image_pil = PIL.Image.open(self.get_image_filename())
        self.points_orig = self.read_pts_file()
        self.on_resize_canvas(int(self.canvas['width']),
                              int(self.canvas['height']))

    def select_prev(self, *args):
        #pylint: disable=unused-argument
        """Select entry that comes before current selection"""
        i = self.get_selected_index()
        if i > 0:
            self.select(i - 1)

    def select_next(self, *args):
        #pylint: disable=unused-argument
        """Select entry that comes after current selection"""
        i = self.get_selected_index()
        if i < len(self.image_filenames) - 1:
            self.select(i + 1)

    def on_resize_canvas(self, width, height):
        """Called when canvas is resized"""
        if width <= 0 or height <= 0:
            return
        # maximize image width or height depending on aspect ratios
        image_width = self.image_pil.size[0]
        image_height = self.image_pil.size[1]
        image_aspect_ratio = float(image_width) / float(image_height)

        self.canvas['width'] = width
        self.canvas['height'] = height
        canvas_width = int(self.canvas['width'])
        canvas_height = int(self.canvas['height'])
        canvas_aspect_ratio = float(canvas_width) / float(canvas_height)

        if image_aspect_ratio < canvas_aspect_ratio:
            new_image_width = int(image_aspect_ratio * float(canvas_height))
            new_image_height = canvas_height
        else:
            new_image_width = canvas_width
            new_image_height = int(float(canvas_width) / image_aspect_ratio)

        self.image_tk = PhotoImage(
            self.image_pil.resize((new_image_width, new_image_height),
                                  PIL.Image.BILINEAR))

        self.x_offset = 0.5 * (float(canvas_width) - float(new_image_width))
        self.y_offset = 0.5 * (float(canvas_height) - float(new_image_height))

        self.crosshair_radius = 0.5 * self.crosshair_fraction * float(
            min(new_image_width, new_image_height))

        self.canvas.delete('image')
        self.canvas.create_image(self.x_offset,
                                 self.y_offset,
                                 anchor=NW,
                                 image=self.image_tk,
                                 tags='image')

        width_scale = float(new_image_width) / float(image_width)
        height_scale = float(new_image_height) / float(image_height)
        self.image_scaling = 0.5 * (width_scale + height_scale)
        self.points_canvas = [[
            x[0] * self.image_scaling + self.x_offset,
            x[1] * self.image_scaling + self.y_offset
        ] for x in self.points_orig]
        self.redraw_points()

    def redraw_points(self):
        """redraw points in current entry's .pts file"""
        self.canvas.delete('line')

        # draw first crosshair in color1
        if len(self.points_canvas) > 0:
            point1 = self.points_canvas[0]
            self.draw_crosshair(point1[0], point1[1], self.crosshair1_color)

        # draw second crosshair in color2
        if len(self.points_canvas) > 1:
            point2 = self.points_canvas[1]
            self.draw_crosshair(point2[0], point2[1], self.crosshair2_color)

        # draw third or higher crosshair in color3
        if len(self.points_canvas) > 2:
            for point in self.points_canvas[2:]:
                self.draw_crosshair(point[0], point[1], self.crosshair3_color)

    def draw_crosshair(self, x_coord, y_coord, fill_color):
        """Draw a cross at (x_coord, y_coord) in the currently selected image"""
        start_x = x_coord - self.crosshair_radius
        start_y = y_coord - self.crosshair_radius

        end_x = x_coord + self.crosshair_radius
        end_y = y_coord + self.crosshair_radius

        min_x = self.x_offset
        min_y = self.y_offset

        max_x = self.x_offset + self.image_tk.width() - 1
        max_y = self.y_offset + self.image_tk.height() - 1

        if start_x < min_x:
            start_x = min_x
        if start_y < min_y:
            start_y = min_y

        if end_x > max_x:
            end_x = max_x
        if end_y > max_y:
            end_y = max_y

        self.canvas.create_line(x_coord,
                                start_y,
                                x_coord,
                                end_y,
                                width=self.crosshair_thickness,
                                tags='line',
                                fill=fill_color)
        self.canvas.create_line(start_x,
                                y_coord,
                                end_x,
                                y_coord,
                                width=self.crosshair_thickness,
                                tags='line',
                                fill=fill_color)

    def get_selected_index(self):
        """Returns index of current selection"""
        return int(self.lisbox_filenames.curselection()[0])

    def coord_in_img(self, x_coord, y_coord):
        """Returns whether (x_coord, y_coord) is inside the shown image"""
        return (x_coord >= self.x_offset and y_coord >= self.y_offset
                and x_coord < self.x_offset + self.image_tk.width()
                and y_coord < self.y_offset + self.image_tk.height())

    def find_point_near_crosshair(self, x_coord, y_coord):
        """Returns index of landmark point near (x_coord, y_coord), or -1"""
        i = 0
        i_min = -1
        min_dist = self.image_tk.width() + self.image_tk.height()
        for pair in self.points_canvas:
            x_dist = x_coord - pair[0]
            y_dist = y_coord - pair[1]
            dist = sqrt(x_dist * x_dist + y_dist * y_dist)
            if dist <= self.crosshair_radius and dist < min_dist:
                i_min = i
            i += 1
        return i_min

    def save_points(self):
        """Save current points to pts file"""
        # remove whatever was there before
        if self.has_pts_file():
            os.remove(self.get_pts_filename())
        # save current result
        if len(self.points_orig) > 0:
            filehandle = open(self.get_pts_filename(), 'w')
            for pair in self.points_orig:
                message = str(pair[0]) + ', ' + str(pair[1]) + '\n'
                filehandle.write(message)
            filehandle.close()

    def sort_points(self):
        """
        Reorder points, assuming face labeling, so that the first point
        is always the person's right eye, the second point is the person's
        left eye and the third point is the mouth.  NB: this function only
        (destructively) works on self.points_orig
        """
        if len(self.points_orig) != 3:
            return
        # step 1 sort the points according to y-value
        self.points_orig.sort(key=lambda pt: pt[1])
        # step 2: from the top-most two points, call the leftmost one
        # the person's right eye and call the other the person's left eye
        if self.points_orig[0][0] > self.points_orig[1][0]:
            # swap first and second points' x-coordinate
            tmp = self.points_orig[0][0]
            self.points_orig[0][0] = self.points_orig[1][0]
            self.points_orig[1][0] = tmp
            # swap first and second points' y-coordinate
            tmp = self.points_orig[0][1]
            self.points_orig[0][1] = self.points_orig[1][1]
            self.points_orig[1][1] = tmp
            # order changed, so re-save
            self.save_points()

    def has_pts_file(self, i=None):
        """Returns whether (i'th) selection has a pts file with landmarks"""
        if i is None:
            i = self.get_selected_index()
        return os.path.exists(self.get_pts_filename(i))

    def get_pts_filename(self, i=None):
        """Returns filename of selected (or i'th) .pts file"""
        if i is None:
            i = self.get_selected_index()
        image_filename = self.image_filenames[i]
        return os.path.splitext(image_filename)[0] + '.pts'

    def get_image_filename(self, i=None):
        """Returns filename of (i'th) selection's image"""
        if i is None:
            i = self.get_selected_index()
        return self.image_filenames[i]

    def read_pts_file(self, i=None):
        """Returns list of points (lists) in (i'th) selection's .pts file"""
        if i is None:
            i = self.get_selected_index()
        if self.has_pts_file(i):
            filehandle = open(self.get_pts_filename(i), 'r')
            lines = filehandle.readlines()
            filehandle.close()
            return [[float(pair[0]), float(pair[1])]
                    for pair in [line.split(',') for line in lines]]
        else:
            return []

    def mark_labeled(self, i=None):
        """Mark (i'th) selection as having a .pts file"""
        if i is None:
            i = self.get_selected_index()
        self.listbox_marks.insert(i, '+')
        self.listbox_marks.delete(i + 1)
        self.listbox_marks.selection_set(i)

    def mark_unlabeled(self, i=None):
        """Unmark (i'th) selection as having a .pts file"""
        if i is None:
            i = self.get_selected_index()
        self.listbox_marks.insert(i, '')
        self.listbox_marks.delete(i + 1)
        self.listbox_marks.selection_set(i)
Пример #6
0
class FontChooser(Toplevel):
    """Font chooser dialog."""

    def __init__(self, master, font_dict={}, text="Abcd", title="Font Chooser",
                 **kwargs):
        """
        Create a new FontChooser instance.
        Arguments:
            master : Tk or Toplevel instance
                master window
            font_dict : dict
                dictionnary, like the one returned by the ``actual`` method of a ``Font`` object:
                ::
                    {'family': str,
                     'size': int,
                     'weight': 'bold'/'normal',
                     'slant': 'italic'/'roman',
                     'underline': bool,
                     'overstrike': bool}
            text : str
                text to be displayed in the preview label
            title : str
                window title
            kwargs : dict
                additional keyword arguments to be passed to ``Toplevel.__init__``
        """
        Toplevel.__init__(self, master, **kwargs)
        self.title(title)
        self.resizable(False, False)
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self._validate_family = self.register(self.validate_font_family)
        self._validate_size = self.register(self.validate_font_size)

        # --- variable storing the chosen font
        self.res = ""

        style = Style(self)
        style.configure("prev.TLabel", background="white")
        bg = style.lookup("TLabel", "background")
        self.configure(bg=bg)

        # --- family list
        self.fonts = list(set(families()))
        self.fonts.append("TkDefaultFont")
        self.fonts.sort()
        for i in range(len(self.fonts)):
            self.fonts[i] = self.fonts[i].replace(" ", "\ ")
        max_length = int(2.5 * max([len(font) for font in self.fonts])) // 3
        self.sizes = ["%i" % i for i in (list(range(6, 17)) + list(range(18, 32, 2)))]
        # --- font default
        font_dict["weight"] = font_dict.get("weight", "normal")
        font_dict["slant"] = font_dict.get("slant", "roman")
        font_dict["underline"] = font_dict.get("underline", False)
        font_dict["overstrike"] = font_dict.get("overstrike", False)
        font_dict["family"] = font_dict.get("family",
                                            self.fonts[0].replace('\ ', ' '))
        font_dict["size"] = font_dict.get("size", 10)

        # --- creation of the widgets
        # ------ style parameters (bold, italic ...)
        options_frame = Frame(self, relief='groove', borderwidth=2)
        self.font_family = StringVar(self, " ".join(self.fonts))
        self.font_size = StringVar(self, " ".join(self.sizes))
        self.var_bold = BooleanVar(self, font_dict["weight"] == "bold")
        b_bold = Checkbutton(options_frame, text=TR["Bold"],
                             command=self.toggle_bold,
                             variable=self.var_bold)
        b_bold.grid(row=0, sticky="w", padx=4, pady=(4, 2))
        self.var_italic = BooleanVar(self, font_dict["slant"] == "italic")
        b_italic = Checkbutton(options_frame, text=TR["Italic"],
                               command=self.toggle_italic,
                               variable=self.var_italic)
        b_italic.grid(row=1, sticky="w", padx=4, pady=2)
        self.var_underline = BooleanVar(self, font_dict["underline"])
        b_underline = Checkbutton(options_frame, text=TR["Underline"],
                                  command=self.toggle_underline,
                                  variable=self.var_underline)
        b_underline.grid(row=2, sticky="w", padx=4, pady=2)
        self.var_overstrike = BooleanVar(self, font_dict["overstrike"])
        b_overstrike = Checkbutton(options_frame, text=TR["Overstrike"],
                                   variable=self.var_overstrike,
                                   command=self.toggle_overstrike)
        b_overstrike.grid(row=3, sticky="w", padx=4, pady=(2, 4))
        # ------ Size and family
        self.var_size = StringVar(self)
        self.entry_family = Entry(self, width=max_length, validate="key",
                                  validatecommand=(self._validate_family, "%d", "%S",
                                                   "%i", "%s", "%V"))
        self.entry_size = Entry(self, width=4, validate="key",
                                textvariable=self.var_size,
                                validatecommand=(self._validate_size, "%d", "%P", "%V"))
        self.list_family = Listbox(self, selectmode="browse",
                                   listvariable=self.font_family,
                                   highlightthickness=0,
                                   exportselection=False,
                                   width=max_length)
        self.list_size = Listbox(self, selectmode="browse",
                                 listvariable=self.font_size,
                                 highlightthickness=0,
                                 exportselection=False,
                                 width=4)
        scroll_family = Scrollbar(self, orient='vertical',
                                  command=self.list_family.yview)
        scroll_size = Scrollbar(self, orient='vertical',
                                command=self.list_size.yview)
        self.preview_font = Font(self, **font_dict)
        if len(text) > 30:
            text = text[:30]
        self.preview = Label(self, relief="groove", style="prev.TLabel",
                             text=text, font=self.preview_font,
                             anchor="center")

        # --- widget configuration
        self.list_family.configure(yscrollcommand=scroll_family.set)
        self.list_size.configure(yscrollcommand=scroll_size.set)

        self.entry_family.insert(0, font_dict["family"])
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        self.entry_size.insert(0, font_dict["size"])

        try:
            i = self.fonts.index(self.entry_family.get().replace(" ", "\ "))
        except ValueError:
            # unknown font
            i = 0
        self.list_family.selection_clear(0, "end")
        self.list_family.selection_set(i)
        self.list_family.see(i)
        try:
            i = self.sizes.index(self.entry_size.get())
            self.list_size.selection_clear(0, "end")
            self.list_size.selection_set(i)
            self.list_size.see(i)
        except ValueError:
            # size not in list
            pass

        self.entry_family.grid(row=0, column=0, sticky="ew",
                               pady=(10, 1), padx=(10, 0))
        self.entry_size.grid(row=0, column=2, sticky="ew",
                             pady=(10, 1), padx=(10, 0))
        self.list_family.grid(row=1, column=0, sticky="nsew",
                              pady=(1, 10), padx=(10, 0))
        self.list_size.grid(row=1, column=2, sticky="nsew",
                            pady=(1, 10), padx=(10, 0))
        scroll_family.grid(row=1, column=1, sticky='ns', pady=(1, 10))
        scroll_size.grid(row=1, column=3, sticky='ns', pady=(1, 10))
        options_frame.grid(row=0, column=4, rowspan=2,
                           padx=10, pady=10, ipadx=10)

        self.preview.grid(row=2, column=0, columnspan=5, sticky="eswn",
                          padx=10, pady=(0, 10), ipadx=4, ipady=4)

        button_frame = Frame(self)
        button_frame.grid(row=3, column=0, columnspan=5, pady=(0, 10), padx=10)

        Button(button_frame, text="Ok",
               command=self.ok).grid(row=0, column=0, padx=4, sticky='ew')
        Button(button_frame, text=TR["Cancel"],
               command=self.quit).grid(row=0, column=1, padx=4, sticky='ew')
        self.list_family.bind('<<ListboxSelect>>', self.update_entry_family)
        self.list_size.bind('<<ListboxSelect>>', self.update_entry_size,
                            add=True)
        self.list_family.bind("<KeyPress>", self.keypress)
        self.entry_family.bind("<Return>", self.change_font_family)
        self.entry_family.bind("<Tab>", self.tab)
        self.entry_size.bind("<Return>", self.change_font_size)

        self.entry_family.bind("<Down>", self.down_family)
        self.entry_size.bind("<Down>", self.down_size)

        self.entry_family.bind("<Up>", self.up_family)
        self.entry_size.bind("<Up>", self.up_size)

        # bind Ctrl+A to select all instead of go to beginning
        self.bind_class("TEntry", "<Control-a>", self.select_all)

        self.wait_visibility(self)
        self.grab_set()
        self.entry_family.focus_set()
        self.lift()

    def select_all(self, event):
        """Select all entry content."""
        event.widget.selection_range(0, "end")

    def keypress(self, event):
        """Select the first font whose name begin by the key pressed."""
        key = event.char.lower()
        l = [i for i in self.fonts if i[0].lower() == key]
        if l:
            i = self.fonts.index(l[0])
            self.list_family.selection_clear(0, "end")
            self.list_family.selection_set(i)
            self.list_family.see(i)
            self.update_entry_family()

    def up_family(self, event):
        """Navigate in the family listbox with up key."""
        try:
            i = self.list_family.curselection()[0]
            self.list_family.selection_clear(0, "end")
            if i <= 0:
                i = len(self.fonts)
            self.list_family.see(i - 1)
            self.list_family.select_set(i - 1)
        except TclError:
            self.list_family.selection_clear(0, "end")
            i = len(self.fonts)
            self.list_family.see(i - 1)
            self.list_family.select_set(i - 1)
        self.list_family.event_generate('<<ListboxSelect>>')

    def up_size(self, event):
        """Navigate in the size listbox with up key."""
        try:
            s = self.var_size.get()
            if s in self.sizes:
                i = self.sizes.index(s)
            elif s:
                sizes = list(self.sizes)
                sizes.append(s)
                sizes.sort(key=lambda x: int(x))
                i = sizes.index(s)
            else:
                i = 0
            self.list_size.selection_clear(0, "end")
            if i <= 0:
                i = len(self.sizes)
            self.list_size.see(i - 1)
            self.list_size.select_set(i - 1)
        except TclError:
            i = len(self.sizes)
            self.list_size.see(i - 1)
            self.list_size.select_set(i - 1)
        self.list_size.event_generate('<<ListboxSelect>>')

    def down_family(self, event):
        """Navigate in the family listbox with down key."""
        try:
            i = self.list_family.curselection()[0]
            self.list_family.selection_clear(0, "end")
            if i >= len(self.fonts):
                i = -1
            self.list_family.see(i + 1)
            self.list_family.select_set(i + 1)
        except TclError:
            self.list_family.selection_clear(0, "end")
            self.list_family.see(0)
            self.list_family.select_set(0)
        self.list_family.event_generate('<<ListboxSelect>>')

    def down_size(self, event):
        """Navigate in the size listbox with down key."""
        try:
            s = self.var_size.get()
            if s in self.sizes:
                i = self.sizes.index(s)
            elif s:
                sizes = list(self.sizes)
                sizes.append(s)
                sizes.sort(key=lambda x: int(x))
                i = sizes.index(s) - 1
            else:
                s = len(self.sizes) - 1
            self.list_size.selection_clear(0, "end")
            if i < len(self.sizes) - 1:
                self.list_size.selection_set(i + 1)
                self.list_size.see(i + 1)
            else:
                self.list_size.see(0)
                self.list_size.select_set(0)
        except TclError:
            self.list_size.selection_set(0)
        self.list_size.event_generate('<<ListboxSelect>>')

    def toggle_bold(self):
        """Update font preview weight."""
        b = self.var_bold.get()
        self.preview_font.configure(weight=["normal", "bold"][b])

    def toggle_italic(self):
        """Update font preview slant."""
        b = self.var_italic.get()
        self.preview_font.configure(slant=["roman", "italic"][b])

    def toggle_underline(self):
        """Update font preview underline."""
        b = self.var_underline.get()
        self.preview_font.configure(underline=b)

    def toggle_overstrike(self):
        """Update font preview overstrike."""
        b = self.var_overstrike.get()
        self.preview_font.configure(overstrike=b)

    def change_font_family(self, event=None):
        """Update font preview family."""
        family = self.entry_family.get()
        if family.replace(" ", "\ ") in self.fonts:
            self.preview_font.configure(family=family)

    def change_font_size(self, event=None):
        """Update font preview size."""
        size = int(self.var_size.get())
        self.preview_font.configure(size=size)

    def validate_font_size(self, d, ch, V):
        """Validation of the size entry content."""
        l = [i for i in self.sizes if i[:len(ch)] == ch]
        i = None
        if l:
            i = self.sizes.index(l[0])
        elif ch.isdigit():
            sizes = list(self.sizes)
            sizes.append(ch)
            sizes.sort(key=lambda x: int(x))
            i = min(sizes.index(ch), len(self.sizes))
        if i is not None:
            self.list_size.selection_clear(0, "end")
            self.list_size.selection_set(i)
            deb = self.list_size.nearest(0)
            fin = self.list_size.nearest(self.list_size.winfo_height())
            if V != "forced":
                if i < deb or i > fin:
                    self.list_size.see(i)
                return True
        if d == '1':
            return ch.isdigit()
        else:
            return True

    def tab(self, event):
        """Move at the end of selected text on tab press."""
        self.entry_family = event.widget
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        return "break"

    def validate_font_family(self, action, modif, pos, prev_txt, V):
        """Completion of the text in the entry with existing font names."""
        if self.entry_family.selection_present():
            sel = self.entry_family.selection_get()
            txt = prev_txt.replace(sel, '')
        else:
            txt = prev_txt
        if action == "0":
            txt = txt[:int(pos)] + txt[int(pos) + 1:]
            return True
        else:
            txt = txt[:int(pos)] + modif + txt[int(pos):]
            ch = txt.replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(ch)] == ch]
            if l:
                i = self.fonts.index(l[0])
                self.list_family.selection_clear(0, "end")
                self.list_family.selection_set(i)
                deb = self.list_family.nearest(0)
                fin = self.list_family.nearest(self.list_family.winfo_height())
                index = self.entry_family.index("insert")
                self.entry_family.delete(0, "end")
                self.entry_family.insert(0, l[0].replace("\ ", " "))
                self.entry_family.selection_range(index + 1, "end")
                self.entry_family.icursor(index + 1)
                if V != "forced":
                    if i < deb or i > fin:
                        self.list_family.see(i)
                return True
            else:
                return False

    def update_entry_family(self, event=None):
        """Update family entry when an item is selected in the family listbox."""
        #  family = self.list_family.get("@%i,%i" % (event.x , event.y))
        family = self.list_family.get(self.list_family.curselection()[0])
        self.entry_family.delete(0, "end")
        self.entry_family.insert(0, family)
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        self.change_font_family()

    def update_entry_size(self, event):
        """Update size entry when an item is selected in the size listbox."""
        #  size = self.list_size.get("@%i,%i" % (event.x , event.y))
        size = self.list_size.get(self.list_size.curselection()[0])
        self.var_size.set(size)
        self.change_font_size()

    def ok(self):
        """Validate choice."""
        self.res = self.preview_font.actual()
        self.quit()

    def get_res(self):
        """Return chosen font."""
        return self.res

    def quit(self):
        self.destroy()
Пример #7
0
class _select_recent_file(_Dialog):
    def body(self, master):
        dialogframe = Frame(master, width=248, height=293)
        self.dialogframe = dialogframe
        dialogframe.pack()

        self.make_Label_1(
            self.dialogframe)  #       Label: Select Recent File : at Main(1,1)
        self.make_Label_2(self.dialogframe)  #       Label:  at Main(2,1)
        self.make_Listbox_1(self.dialogframe)  #     Listbox:  at Main(3,1)

        # make a Status Bar
        self.statusMessage = StringVar()
        self.statusMessage.set("")
        self.statusbar = Label(self.dialogframe,
                               textvariable=self.statusMessage,
                               bd=1,
                               relief=SUNKEN)
        self.statusbar.grid(row=99, column=0, columnspan=99, sticky='ew')

        # >>>>>>insert any user code below this comment for section "top_of_init"

        self.statusMessage.set("Select File from Recent List")

    # TkGridGUI generated code. DO NOT EDIT THE FOLLOWING. section "make_Label_1"
    def make_Label_1(self, frame):
        """       Label: Select Recent File : at Main(1,1)"""
        self.Label_1 = Label(frame, text="Select Recent File", width="30")
        self.Label_1.grid(row=1, column=1)

        # >>>>>>insert any user code below this comment for section "make_Label_1"

    # TkGridGUI generated code. DO NOT EDIT THE FOLLOWING. section "make_Label_2"
    def make_Label_2(self, frame):
        """       Label:  at Main(2,1)"""
        self.Label_2 = Label(frame, text="", width="30")
        self.Label_2.grid(row=2, column=1)

        # >>>>>>insert any user code below this comment for section "make_Label_2"

    # TkGridGUI generated code. DO NOT EDIT THE FOLLOWING. section "make_Listbox_1"
    def make_Listbox_1(self, frame):
        """     Listbox:  at Main(3,1)"""

        lbframe = Frame(frame)
        self.Listbox_1_frame = lbframe
        vbar = Scrollbar(lbframe, orient=VERTICAL)
        self.Listbox_1 = Listbox(lbframe,
                                 width="30",
                                 height="12",
                                 yscrollcommand=vbar.set)
        vbar.config(command=self.Listbox_1.yview)

        vbar.grid(row=0, column=1, sticky='ns')
        self.Listbox_1.grid(row=0, column=0)

        self.Listbox_1_frame.grid(row=3, column=1)

        # >>>>>>insert any user code below this comment for section "make_Listbox_1"

        self.input_fileL = []  # list of (head, tail)

        # Edit the Listbox Entries
        self.Listbox_1.configure(
            exportselection=False)  # stay highlighted after focus leaves
        for i, name in enumerate(self.dialogOptions['fileL']):
            head, tail = os.path.split(name)
            self.input_fileL.append((head, tail))
            self.Listbox_1.insert(END, tail)

        self.Listbox_1.bind("<ButtonRelease-1>", self.Listbox_1_Click)
        self.Listbox_1.bind('<Double-1>',
                            self.double_click)  # bind double left clicks

        self.Listbox_1.bind('<Enter>', self.snapHighlightToMouse)
        self.Listbox_1.bind('<Motion>', self.snapHighlightToMouse)
        self.Listbox_1.bind('<Leave>', self.unhighlight)

    def snapHighlightToMouse(self, event):
        #self.Listbox_1.selection_clear(0, END)
        #self.Listbox_1.selection_set( self.Listbox_1.nearest(event.y) )
        #print( self.Listbox_1.get( self.Listbox_1.nearest(event.y) ), self.Listbox_1.nearest(event.y) )

        self.statusMessage.set(self.input_fileL[self.Listbox_1.nearest(
            event.y)][0])

    def unhighlight(self, event=None):
        #self.Listbox_1.selection_clear(0, END)
        pass

    # TkGridGUI generated code. DO NOT EDIT THE FOLLOWING. section "Listbox_1_Click"
    def Listbox_1_Click(self, event):  #bind method for component ID=Listbox_1
        """     Listbox:  at Main(3,1)"""
        pass
        # >>>>>>insert any user code below this comment for section "Listbox_1_Click"
        # replace, delete, or comment-out the following
        #print( "executed method Listbox_1_Click" )
        #print(event)

        #print( "current selection(s) =",self.Listbox_1.curselection() )
        labelL = []
        isel = -1
        for i in self.Listbox_1.curselection():
            isel = i
            labelL.append(self.Listbox_1.get(i))
        #print( "current label(s) =",labelL )

        if isel >= 0:
            self.Label_1.config(text=self.input_fileL[isel][0])
            self.Label_2.config(text=self.input_fileL[isel][1])

    def double_click(self, event):
        self.Listbox_1_Click(event)
        self.ok()

    # TkGridGUI generated code. DO NOT EDIT THE FOLLOWING. section "dialog_validate"
    def validate(self):
        self.result = {}  # return a dictionary of results

        # >>>>>>insert any user code below this comment for section "dialog_validate"
        # set values in "self.result" dictionary for return
        # for example...
        # self.result["age"] = self.Entry_2_StringVar.get()
        self.result["file_name"] = ''
        for i in self.Listbox_1.curselection():
            self.result["file_name"] = self.dialogOptions['fileL'][i]
            break

        return 1

    """
    Use this for testing dialog
        fileL = ['c:/myhome/file 1', 'd:/really/deep/path/to/file/Really_Important_File','e:/yesterdays/news/YesterdayAtNoon']
        for i in range(30):
            fileL.append('f:/dummy%i/area%i/Dummy_File_%i'%(i,i,i) + 'x'*i)
        dialog = _select_recent_file(self.master, "Pick Recent File", dialogOptions={'fileL':fileL})
    
    """

    # TkGridGUI generated code. DO NOT EDIT THE FOLLOWING. section "end"

    def apply(self):
        pass
Пример #8
0
class FontChooser(Toplevel):
    """ Font chooser toplevel """
    def __init__(self,
                 master,
                 font_dict={},
                 text="Abcd",
                 title="Font Chooser",
                 **kwargs):
        """
            Create a new FontChooser instance.

            font: dictionnary, like the one returned by the .actual
                  method of a Font object

                    {'family': 'DejaVu Sans',
                     'overstrike':False,
                     'size': 12,
                     'slant': 'italic' or 'roman',
                     'underline': False,
                     'weight': 'bold' or 'normal'}

            text: text to be displayed in the preview label

            title: window title

            **kwargs: additional keyword arguments to be passed to
                      Toplevel.__init__
        """
        Toplevel.__init__(self, master, **kwargs)
        self.title(title)
        self.resizable(False, False)
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self._validate_family = self.register(self.validate_font_family)
        self._validate_size = self.register(self.validate_font_size)

        # variable storing the chosen font
        self.res = ""

        style = Style(self)
        style.configure("prev.TLabel", background="white")
        bg = style.lookup("TLabel", "background")
        self.configure(bg=bg)

        # family list
        self.fonts = list(set(families()))
        self.fonts.append("TkDefaultFont")
        self.fonts.sort()
        for i in range(len(self.fonts)):
            self.fonts[i] = self.fonts[i].replace(" ", "\ ")
        max_length = int(2.5 * max([len(font) for font in self.fonts])) // 3
        self.sizes = [
            "%i" % i for i in (list(range(6, 17)) + list(range(18, 32, 2)))
        ]
        # font default
        font_dict["weight"] = font_dict.get("weight", "normal")
        font_dict["slant"] = font_dict.get("slant", "roman")
        font_dict["family"] = font_dict.get("family",
                                            self.fonts[0].replace('\ ', ' '))
        font_dict["size"] = font_dict.get("size", 10)

        # Widgets creation
        options_frame = Frame(self, relief='groove', borderwidth=2)
        self.font_family = StringVar(self, " ".join(self.fonts))
        self.font_size = StringVar(self, " ".join(self.sizes))
        self.var_bold = BooleanVar(self, font_dict["weight"] == "bold")
        b_bold = Checkbutton(options_frame,
                             text=TR["Bold"],
                             command=self.toggle_bold,
                             variable=self.var_bold)
        b_bold.grid(row=0, sticky="w", padx=4, pady=(4, 2))
        self.var_italic = BooleanVar(self, font_dict["slant"] == "italic")
        b_italic = Checkbutton(options_frame,
                               text=TR["Italic"],
                               command=self.toggle_italic,
                               variable=self.var_italic)
        b_italic.grid(row=1, sticky="w", padx=4, pady=2)
        self.var_size = StringVar(self)
        self.entry_family = Entry(self,
                                  width=max_length,
                                  validate="key",
                                  validatecommand=(self._validate_family, "%d",
                                                   "%S", "%i", "%s", "%V"))
        entry_size = Entry(self,
                           width=4,
                           validate="key",
                           textvariable=self.var_size,
                           validatecommand=(self._validate_size, "%d", "%P",
                                            "%V"))
        self.list_family = Listbox(self,
                                   selectmode="browse",
                                   listvariable=self.font_family,
                                   highlightthickness=0,
                                   exportselection=False,
                                   width=max_length)
        self.list_size = Listbox(self,
                                 selectmode="browse",
                                 listvariable=self.font_size,
                                 highlightthickness=0,
                                 exportselection=False,
                                 width=4)
        scroll_family = Scrollbar(self,
                                  orient='vertical',
                                  command=self.list_family.yview)
        scroll_size = Scrollbar(self,
                                orient='vertical',
                                command=self.list_size.yview)
        self.preview_font = Font(self, **font_dict)
        if len(text) > 30:
            text = text[:30]
        self.preview = Label(self,
                             relief="groove",
                             style="prev.TLabel",
                             text=text,
                             font=self.preview_font,
                             anchor="center")

        # Widget configuration
        self.list_family.configure(yscrollcommand=scroll_family.set)
        self.list_size.configure(yscrollcommand=scroll_size.set)

        self.entry_family.insert(0, font_dict["family"])
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        entry_size.insert(0, font_dict["size"])

        i = self.fonts.index(self.entry_family.get().replace(" ", "\ "))
        self.list_family.selection_clear(0, "end")
        self.list_family.selection_set(i)
        self.list_family.see(i)
        i = self.sizes.index(entry_size.get())
        self.list_size.selection_clear(0, "end")
        self.list_size.selection_set(i)
        self.list_size.see(i)

        self.entry_family.grid(row=0,
                               column=0,
                               sticky="ew",
                               pady=(10, 1),
                               padx=(10, 0))
        entry_size.grid(row=0,
                        column=2,
                        sticky="ew",
                        pady=(10, 1),
                        padx=(10, 0))
        self.list_family.grid(row=1,
                              column=0,
                              sticky="nsew",
                              pady=(1, 10),
                              padx=(10, 0))
        self.list_size.grid(row=1,
                            column=2,
                            sticky="nsew",
                            pady=(1, 10),
                            padx=(10, 0))
        scroll_family.grid(row=1, column=1, sticky='ns', pady=(1, 10))
        scroll_size.grid(row=1, column=3, sticky='ns', pady=(1, 10))
        options_frame.grid(row=0,
                           column=4,
                           rowspan=2,
                           padx=10,
                           pady=10,
                           ipadx=10)

        self.preview.grid(row=2,
                          column=0,
                          columnspan=5,
                          sticky="eswn",
                          padx=10,
                          pady=(0, 10),
                          ipadx=4,
                          ipady=4)

        button_frame = Frame(self)
        button_frame.grid(row=3, column=0, columnspan=5, pady=(0, 10), padx=10)

        Button(button_frame, text="Ok", command=self.ok).grid(row=0,
                                                              column=0,
                                                              padx=4,
                                                              sticky='ew')
        Button(button_frame, text=TR["Cancel"],
               command=self.quit).grid(row=0, column=1, padx=4, sticky='ew')
        self.list_family.bind('<<ListboxSelect>>', self.update_entry_family)
        self.list_size.bind('<<ListboxSelect>>',
                            self.update_entry_size,
                            add=True)
        self.list_family.bind("<KeyPress>", self.keypress)
        self.entry_family.bind("<Return>", self.change_font_family)
        self.entry_family.bind("<Tab>", self.tab)
        entry_size.bind("<Return>", self.change_font_size)

        self.entry_family.bind("<Down>", self.down_family)
        entry_size.bind("<Down>", self.down_size)

        self.entry_family.bind("<Up>", self.up_family)
        entry_size.bind("<Up>", self.up_size)

        # bind Ctrl+A to select all instead of go to beginning
        self.bind_class("TEntry", "<Control-a>", self.select_all)

        self.update_idletasks()
        self.grab_set()
        self.entry_family.focus_set()
        self.lift()

    def select_all(self, event):
        event.widget.selection_range(0, "end")

    def keypress(self, event):
        key = event.char.lower()
        l = [i for i in self.fonts if i[0].lower() == key]
        if l:
            i = self.fonts.index(l[0])
            self.list_family.selection_clear(0, "end")
            self.list_family.selection_set(i)
            self.list_family.see(i)
            self.update_entry_family()

    def up_family(self, event):
        try:
            txt = self.entry_family.get().replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(txt)] == txt]
            if l:
                self.list_family.selection_clear(0, "end")
                i = self.fonts.index(l[0])
                if i > 0:
                    self.list_family.selection_set(i - 1)
                    self.list_family.see(i - 1)
                else:
                    i = len(self.fonts)
                    self.list_family.see(i - 1)
                    self.list_family.select_set(i - 1)

        except TclError:
            i = len(self.fonts)
            self.list_family.see(i - 1)
            self.list_family.select_set(i - 1)
        self.list_family.event_generate('<<ListboxSelect>>')

    def up_size(self, event):
        try:
            s = self.var_size.get()
            i = self.sizes.index(s)
            self.list_size.selection_clear(0, "end")
            if i > 0:
                self.list_size.selection_set(i - 1)
                self.list_size.see(i - 1)
            else:
                i = len(self.sizes)
                self.list_size.see(i - 1)
                self.list_size.select_set(i - 1)
        except TclError:
            i = len(self.sizes)
            self.list_size.see(i - 1)
            self.list_size.select_set(i - 1)
        self.list_size.event_generate('<<ListboxSelect>>')

    def down_family(self, event):
        try:
            txt = self.entry_family.get().replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(txt)] == txt]
            if l:
                self.list_family.selection_clear(0, "end")
                i = self.fonts.index(l[0])
                if i < len(self.fonts) - 1:
                    self.list_family.selection_set(i + 1)
                    self.list_family.see(i + 1)
                else:
                    self.list_family.see(0)
                    self.list_family.select_set(0)

        except TclError:
            self.list_family.selection_set(0)
        self.list_family.event_generate('<<ListboxSelect>>')

    def down_size(self, event):
        try:
            s = self.var_size.get()
            i = self.sizes.index(s)
            self.list_size.selection_clear(0, "end")
            if i < len(self.sizes) - 1:
                self.list_size.selection_set(i + 1)
                self.list_size.see(i + 1)
            else:
                self.list_size.see(0)
                self.list_size.select_set(0)
        except TclError:
            self.list_size.selection_set(0)
        self.list_size.event_generate('<<ListboxSelect>>')

    def toggle_bold(self):
        b = self.var_bold.get()
        self.preview_font.configure(weight=["normal", "bold"][b])

    def toggle_italic(self):
        b = self.var_italic.get()
        self.preview_font.configure(slant=["roman", "italic"][b])

    def toggle_underline(self):
        b = self.var_underline.get()
        self.preview_font.configure(underline=b)

    def toggle_overstrike(self):
        b = self.var_overstrike.get()
        self.preview_font.configure(overstrike=b)

    def change_font_family(self, event=None):
        family = self.entry_family.get()
        if family.replace(" ", "\ ") in self.fonts:
            self.preview_font.configure(family=family)

    def change_font_size(self, event=None):
        size = int(self.var_size.get())
        self.preview_font.configure(size=size)

    def validate_font_size(self, d, ch, V):
        ''' Validation of the size entry content '''
        l = [i for i in self.sizes if i[:len(ch)] == ch]
        if l:
            i = self.sizes.index(l[0])
            self.list_size.selection_clear(0, "end")
            self.list_size.selection_set(i)
            deb = self.list_size.nearest(0)
            fin = self.list_size.nearest(self.list_size.winfo_height())
            if V != "forced":
                if i < deb or i > fin:
                    self.list_size.see(i)
                return True
        if d == '1':
            return ch.isdigit()
        else:
            return True

    def tab(self, event):
        self.entry_family = event.widget
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        return "break"

    def validate_font_family(self, action, modif, pos, prev_txt, V):
        """ completion of the text in the path entry with existing
            folder/file names """
        if self.entry_family.selection_present():
            sel = self.entry_family.selection_get()
            txt = prev_txt.replace(sel, '')
        else:
            txt = prev_txt
        if action == "0":
            txt = txt[:int(pos)] + txt[int(pos) + 1:]
            return True
        else:
            txt = txt[:int(pos)] + modif + txt[int(pos):]
            ch = txt.replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(ch)] == ch]
            if l:
                i = self.fonts.index(l[0])
                self.list_family.selection_clear(0, "end")
                self.list_family.selection_set(i)
                deb = self.list_family.nearest(0)
                fin = self.list_family.nearest(self.list_family.winfo_height())
                index = self.entry_family.index("insert")
                self.entry_family.delete(0, "end")
                self.entry_family.insert(0, l[0].replace("\ ", " "))
                self.entry_family.selection_range(index + 1, "end")
                self.entry_family.icursor(index + 1)
                if V != "forced":
                    if i < deb or i > fin:
                        self.list_family.see(i)
                return True
            else:
                return False

    def update_entry_family(self, event=None):
        #  family = self.list_family.get("@%i,%i" % (event.x , event.y))
        family = self.list_family.get(self.list_family.curselection()[0])
        self.entry_family.delete(0, "end")
        self.entry_family.insert(0, family)
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        self.change_font_family()

    def update_entry_size(self, event):
        #  size = self.list_size.get("@%i,%i" % (event.x , event.y))
        size = self.list_size.get(self.list_size.curselection()[0])
        self.var_size.set(size)
        self.change_font_size()

    def ok(self):
        self.res = self.preview_font.actual()
        self.quit()

    def get_res(self):
        return self.res

    def quit(self):
        self.destroy()
Пример #9
0
class MainWindow(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.title('Jeu de la Vie')
        # Index on the object selected in the listbox (-1 if no object is selected)
        self._objectSelected = -1
        self._fieldWidth = self.winfo_screenwidth() - 240
        self._fieldHeight = self.winfo_screenheight() - 90

        # Definition of widgets
        self._noteBook = ttk.Notebook(self)
        self._rightCommandsTab = Frame(self._noteBook)
        self._noteBook.add(self._rightCommandsTab, text="Commandes")
        self._objectsTab = Frame(self._noteBook)
        self._noteBook.add(self._objectsTab, text='Objets')

        self._objectsListbox = Listbox(self._objectsTab,
                                       selectmode=SINGLE,
                                       height=0,
                                       cursor='pencil')
        self._cancelObjectSelectionButton = Button(self._objectsTab,
                                                   text='Annuler',
                                                   command=self.unselectObject)

        self._field = Field(self,
                            width=self._fieldWidth,
                            height=self._fieldHeight,
                            bg='grey',
                            cursor='tcross')

        self._informationFrame = LabelFrame(self._rightCommandsTab,
                                            text="Infos",
                                            labelanchor='n')
        self._commandFrame = LabelFrame(self._rightCommandsTab,
                                        text="Commandes",
                                        labelanchor='n')
        self._nbCellsScaleFrame = LabelFrame(self._commandFrame,
                                             text="Taille des cellules")
        self._nbCellsScale = Scale(self._nbCellsScaleFrame,
                                   variable=self._field._cellSize,
                                   from_=cellSizeMin,
                                   to=cellSizeMax,
                                   command=self._field.updateField,
                                   orient='horizontal',
                                   length=100)
        self._speedScaleFrame = LabelFrame(self._commandFrame, text="Vitesse")
        self._speedScale = Scale(self._speedScaleFrame,
                                 variable=self._field._speed,
                                 from_=1,
                                 orient='horizontal',
                                 length=100)
        self._rectSelectButton = Button(self._commandFrame,
                                        text="Sélection\nRectangle",
                                        command=self.rectSelection,
                                        relief=RAISED)
        self._fieldShowCheckButton = Checkbutton(
            self._commandFrame,
            text="Grille",
            variable=self._field._isShowedField,
            command=self.showOrHideField)
        self._eraseButton = Button(self._commandFrame,
                                   text="Effacer",
                                   command=self.erase,
                                   relief=RAISED)
        self._generationCounterFrame = LabelFrame(self._informationFrame,
                                                  text="Génération")
        self._generationCounter = Label(self._generationCounterFrame,
                                        textvariable=self._field._generation)
        self._cellsCounterFrame = LabelFrame(self._informationFrame,
                                             text="Population")
        self._cellsCounter = Label(self._cellsCounterFrame,
                                   textvariable=self._field._nbCells)

        self._bottomCommands = Frame(self)
        self._start = Button(self._bottomCommands,
                             text='Démarrer',
                             command=self.start)
        self._stop = Button(self._bottomCommands,
                            text='Stop',
                            command=self._field.stop)
        self._quit = Button(self._bottomCommands,
                            text='Quitter',
                            command=self.quit)

        self._noteBook.select(self._rightCommandsTab)

        # Packing
        # self._objectsTab
        self._noteBook.pack(padx=10, side=RIGHT)
        self._objectsListbox.pack(side=TOP)
        self._cancelObjectSelectionButton.pack(pady=10, side=TOP)

        self._informationFrame.pack(side=TOP, pady=30)
        self._generationCounterFrame.pack(side=TOP, pady=10)
        self._generationCounter.pack()
        self._cellsCounterFrame.pack(side=TOP, pady=10)
        self._cellsCounter.pack()
        self._commandFrame.pack(side=TOP, pady=30)
        self._nbCellsScaleFrame.pack(side=TOP, pady=10)
        self._nbCellsScale.pack()
        self._speedScaleFrame.pack(side=TOP, pady=10)
        self._speedScale.pack()
        self._rectSelectButton.pack(side=TOP, pady=10)
        self._fieldShowCheckButton.pack(side=TOP, pady=10)
        self._eraseButton.pack(side=TOP, pady=10)

        self._bottomCommands.pack(padx=10, pady=10, side=BOTTOM)
        self._start.pack(side=LEFT, padx=10)
        self._stop.pack(side=LEFT, padx=10)
        self._quit.pack(side=LEFT, padx=10)

        self._field.pack(padx=10,
                         pady=10,
                         side=TOP,
                         anchor='center',
                         expand=False)

        # Binding events
        self._field.bind("<Button-1>", self.leftClickOnField)
        self._field.bind("<Button-3>", self.rightClickOnField)
        self._field.bind("<B1-Motion>", self.leftClickOnField)
        self._field.bind("<B3-Motion>", self.rightClickOnField)

        self._objectsListbox.bind("<Button-1>", self.leftClickOnMadeObject)
        self._noteBook.bind("<Button-1>", self.unselectObject)

        # Getting objects from the madeObjects.py file
        self.importObjects()

    def start(self):
        # Start button
        if self._field._stopped:
            self._field._stopped = False
            self._field.updateCellsState()

    def quit(self):
        # Quit button
        self.destroy()

    def convertCoordinates(self, event):
        # Converts the output of the mouse click event (x,y) into a cell row and column
        i = (event.y + self._field._yMin) // self._field._cellSize.get()
        j = (event.x + self._field._xMin) // self._field._cellSize.get()
        # print("event.x : "+str(event.x), "event.y : "+str(event.y))
        # print("(i,j) = " + str(i) + "," + str(j))
        return i, j

    def leftClickOnField(self, event):
        # event.x and event.y return coordinates with origin in the corner upper left ((x,y)!=(i,j))
        i, j = self.convertCoordinates(event)
        if self._field._rectSelectActivated.get():
            if not self._field._rectSelectedOneCell.get():
                # Just color with red the first cell
                self._field._rectSelectedOneCell.set(1)
                self._field.drawRedCell(i, j)
                self._field._redCell_i = i
                self._field._redCell_j = j
            else:
                # Awake each cell in the rectangle
                self._field._rectSelectedOneCell.set(0)
                self._field.deleteRedCell()
                i1, i2 = sorted((self._field._redCell_i, i))
                j1, j2 = sorted((self._field._redCell_j, j))
                for lig in range(i1, i2 + 1):
                    for col in range(j1, j2 + 1):
                        awaked = self._field._game_state.awake(lig, col)
                        if awaked:
                            self._field.drawAliveCell(lig, col)
        elif self._objectSelected >= 0:
            for (di, dj
                 ) in mo.objectsList[self._objectSelected].getAliveCellsList():
                awaked = self._field._game_state.awake(i + di, j + dj)
                if awaked:  # The cell has been awaked
                    self._field.drawAliveCell(i + di, j + dj)
        else:
            awaked = self._field._game_state.awake(i, j)
            if awaked:  # The cell has been awaked
                self._field.drawAliveCell(i, j)

    def rightClickOnField(self, event):
        i, j = self.convertCoordinates(event)
        killed = self._field._game_state.kill(i, j)
        if killed:
            self._field.eraseKilledCell(i, j)

    def rectSelection(self):
        if self._field._rectSelectActivated.get(
        ):  # Rectangle selection was activated
            self.cancelRectSelection()
        else:  # Rectangle selection wasn't activated
            self._field._rectSelectActivated.set(1)
            self._rectSelectButton.config(relief=SUNKEN)

    def cancelRectSelection(self):
        self._field._rectSelectActivated.set(0)
        self._rectSelectButton.config(relief=RAISED)
        if self._field._rectSelectedOneCell.get():
            self._field.deleteRedCell()
            self._field._rectSelectedOneCell.set(0)

    def showOrHideField(self):
        # Linked to the checkbox, hides of shows the field
        if self._field._isShowedField.get():
            self._field.showField()
        else:
            self._field.hideField()

    def erase(self):
        # Kills every cells
        cellsAlive = self._field._game_state._cellsAlive.copy()
        for i, j in cellsAlive:
            killed = self._field._game_state.kill(i, j)
            if killed:
                self._field.eraseKilledCell(i, j)
        self._field.stop()
        self._field._generation.set(0)

    def importObjects(self):
        for obj in mo.objectsList:
            self._objectsListbox.insert('end', obj.getName())

    def leftClickOnMadeObject(self, event):
        self._objectSelected = self._objectsListbox.nearest(event.y)

    def unselectObject(self, *event):
        # Delete th selected object if it exists
        self._objectsListbox.selection_clear(0, 'end')
        self._objectSelected = -1
        # If rectangle selection was activated
        if self._field._rectSelectActivated.get():
            self.cancelRectSelection()