Source

lyne / lyne / gui / treewidget.py

Full commit
import time
import os

import Tkinter
import ttk

from lyne import LYNE_BASE_PATH

from lyne.lib.tree import Tree
from lyne.lib.node import ConstantNode, CountingNode, OperatorNode
from lyne.lib.stoppablethread import StoppableThread

from lyne.gui.headerbarwidget import HeaderbarWidget


(IN, OUT) = range(0, 2)


class AbstractNodeWidgetFactory(Tkinter.Frame):
    def __init__(self, canvas):
        Tkinter.Frame.__init__(self, canvas, borderwidth=4, 
                               relief=Tkinter.RAISED)
        self._lines_in = []
        self._lines_out = []
        self._create_widgets()

        Tkinter.Widget.bind(self, '<Enter>', self._highlight)
        Tkinter.Widget.bind(self, '<Leave>', self._unhighlight)

        self.configure({'cursor': 'crosshair'})

    def _highlight(self, event):
        self.config(background='#FFFFFF')

    def _unhighlight(self, event):
        self.config(background='#d9d9d9')

    def _create_widgets(self):
        self._headerbar = HeaderbarWidget(self)
        self._headerbar.pack(fill=Tkinter.X)

        Tkinter.Widget.bind(self._headerbar.handle, '<Button-1>',
                            self._set_initial_xy)
        Tkinter.Widget.bind(self._headerbar.handle, '<B1-Motion>',
                            self._handle_item_drag)
        Tkinter.Widget.bind(self._headerbar.quit_button, '<Button-1>',
                            self.delete)

        Tkinter.Widget.bind(self, '<Button-1>', self._notify_canvas)

    def delete(self, event=None):
        # delete this widget from the canvas
        self.master.delete(self._w_id)

        # delete the reference to this item 
        # from the app's widget reference dict
        del self.dgw._widgets[self._w_id]

        # delete attached lines / references to widgets
        for widget in self._lines_in:
            widget._lines_out.remove(self)
            self.dgw._lines[(widget, self)].delete()
            del self.dgw._lines[(widget, self)]
        for widget in self._lines_out:
            widget._lines_in.remove(self)
            self.dgw._lines[(self, widget)].delete()
            del self.dgw._lines[(self, widget)]

        # if this item was a counting widget, then
        # tell that the app
        if isinstance(self._tree.node, CountingNode):
            self.dgw.master._countingnode = None

        # finally, update the underlying data model
        self._tree.delete()

    def _notify_canvas(self, event):
        self.dgw.handle_item_plug_click(self._w_id)


    def _set_initial_xy(self, event):
        self.initial_x = event.x
        self.initial_y = event.y

    def _handle_item_drag(self, event):
        self.dgw.move_item(
            self._w_id, 
            event.x-self.initial_x, 
            event.y-self.initial_y
        )
        # do not save the event's x and y coords in our last.x and y coords
        # because  when we move the item on the canvas we also move the
        # coordinate system.
        # x and y coordinates are relative to the handle
        # and not to the canvas!

    def set_window_id(self, w_id):
        self._w_id = w_id

    def attach_parentwidget(self, widget):
        self._lines_in.append(widget)

    def attach_subwidget(self, widget):
        self._lines_out.append(widget)

    def has_inputs(self):
        ''' Override this method if necessary.
        '''
        return False

    def has_output(self):
        ''' Override this method if necessary.
        '''
        return False


class TreeItemWidget(AbstractNodeWidgetFactory):
    def __init__(self, canvas):
        AbstractNodeWidgetFactory.__init__(self, canvas)

        self._tree = None # a reference to the underlying data model item
        self._widget = None

        self._animation_thread = None

        self._icon_ref = Tkinter.PhotoImage(
            file=os.path.join(LYNE_BASE_PATH,
            'data', 'images_3rd_party', 'sound.gif'))

        self._bottom_frame = Tkinter.Frame(self)
        self._bottom_frame.pack(fill=Tkinter.BOTH)

        self._sound_button = Tkinter.Button(
            self._bottom_frame, image=self._icon_ref, width=16, height=16,
            cursor='arrow')
        self._sound_button.pack(side=Tkinter.RIGHT)

        Tkinter.Widget.bind(self._sound_button, '<Button-1>',
                            self._toggle_noise)
   
    def _toggle_noise(self, event):
        self.dgw.master.toggle_noise(self)

    def set_active(self):
        self.config(background='#FF0000')
        
    def set_inactive(self):
        self.config(background='#D9D9D9')

    def start_animation(self):
        if self._animation_thread == None or not self._animation_thread.isAlive():
            self._animation_thread = StoppableThread(
                target=self._animate)
            self._animation_thread.start()

    def stop_animation(self):
        if self._animation_thread:
            self._animation_thread.stop()

    def _animate(self):
        self.config(background='#00FF00')
        time.sleep(0.25)
        self.config(background='#FF0000')
        time.sleep(0.75)


class TreeItemConstantWidget(TreeItemWidget):
    def __init__(self, canvas, tree=None):
        TreeItemWidget.__init__(self, canvas)

        self._tree = Tree(ConstantNode()) if not tree else tree

        self._widget = Tkinter.Entry(
            self._bottom_frame, 
            width=5, 
            validate="key",
            validatecommand=(self.register(self._text_entered), '%W', '%P')
        )
        self._widget.pack(side=Tkinter.RIGHT, fill=Tkinter.X)
        self._widget.insert(0, str(self._tree.node._value))

    def _text_entered(self, widget, value):
        try:
            value_int = int(value if len(value)>0 else 0)
        except ValueError:
            return False
        self._tree.node.value = value_int
        return True

    def has_output(self):
        return True


class TreeItemCounterWidget(TreeItemWidget):
    def __init__(self, canvas, tree=None):
        TreeItemWidget.__init__(self, canvas)

        self._tree = Tree(CountingNode()) if not tree else tree

        self._widget = Tkinter.Label(
            self._bottom_frame, text='Counter', background='white', width=5,
            cursor='arrow')
        self._widget.pack(fill=Tkinter.X)

    def has_output(self):
        return True


class TreeItemOperatorWidget(TreeItemWidget):
    def __init__(self, canvas, tree=None):
        TreeItemWidget.__init__(self, canvas)

        self._tree = Tree(OperatorNode()) if not tree else tree

        try:
            idx = OperatorNode.allowed_operators.index(
                self._tree.node._operator)
        except ValueError, msg:
            raise ValueError, 'Invalid operator found: %r\n[%s]' % (
                self._tree.node._operator, msg)

        self._widget = ttk.Combobox(
            self._bottom_frame, values=OperatorNode.allowed_operators,
            state='readonly', width=4, cursor='arrow')
        self._widget.pack(fill=Tkinter.X)
        self._widget.current(idx)

        Tkinter.Widget.bind(self._widget, '<<ComboboxSelected>>',
                            self._combobox_selected)

    def _combobox_selected(self, event):
        assert isinstance(event.widget, ttk.Combobox), 'Expected widget ' \
            'of type Combobox, not %r' % (event.widget,)
        current_selection = event.widget.current()
        new_operator = OperatorNode.allowed_operators[current_selection]
        self._tree.node.operator = new_operator

    def has_inputs(self):
        return True

    def has_output(self):
        return True