瀏覽代碼

Add initial code to start up connection to Modelverse

Yentl Van Tendeloo 8 年之前
當前提交
e7981802e3
共有 8 個文件被更改,包括 2658 次插入0 次删除
  1. 204 0
      classes/main_app.xml
  2. 177 0
      classes/modelverse/http_client.xml
  3. 1854 0
      classes/modelverse/modelverse.xml
  4. 31 0
      classes/window/splash_window.xml
  5. 2 0
      classes/window/window.xml
  6. 22 0
      frontend.xml
  7. 341 0
      mvk_widget.py
  8. 27 0
      runner.py

+ 204 - 0
classes/main_app.xml

@@ -0,0 +1,204 @@
+<class name="MainApp" default="true">
+    <relationships>
+        <association name="modelverse" class="Modelverse" min="1" max="1"/>
+        <association name="windows" class="Window"/>
+    </relationships>
+    <constructor>
+        <parameter name="root"/>
+        <body>
+            self.nr_of_windows = 0
+            self.root = root
+
+            self.splash_window = None
+
+            self.address = "127.0.0.1:8001"
+            self.timeout = 20.0
+            self.username = "admin"
+            self.password = "admin"
+        </body>
+    </constructor>
+    <scxml initial="parallel">
+        <parallel id="parallel">
+            <state id="behaviour" initial="init_modelverse">
+                <state id="init_modelverse" initial="splash">
+                    <state id="splash">
+                        <onentry>
+                            <raise event="create_instance" scope="cd">
+                                <parameter expr="'windows'"/>
+                                <parameter expr="'SplashWindow'"/>
+                            </raise>
+                        </onentry>
+
+                        <transition event="instance_created" target="../instantiate">
+                            <parameter name="association_name"/>
+                            <raise event="start_instance" scope="cd">
+                                <parameter expr="association_name"/>
+                            </raise>
+
+                            <script>
+                                self.splash_window = association_name
+                            </script>
+                        </transition>
+                    </state>
+
+                    <state id="instantiate">
+                        <onentry>
+                            <raise event="create_instance" scope="cd">
+                                <parameter expr="'modelverse'"/>
+                                <parameter expr="'Modelverse'"/>
+                            </raise>
+
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="0"/>
+                                <parameter expr="'Waiting for Modelverse Instantiation...'"/>
+                            </raise>
+                        </onentry>
+
+                        <transition event="instance_created" target="../initializing_network">
+                            <parameter name="association_name"/>
+
+                            <raise event="start_instance" scope="cd">
+                                <parameter expr="association_name"/>
+                            </raise>
+
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="15"/>
+                                <parameter expr="'Waiting for Modelverse Instantiation... OK'"/>
+                            </raise>
+                        </transition>
+                    </state>
+
+                    <state id="initializing_network">
+                        <onentry>
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="30"/>
+                                <parameter expr="'Waiting for HTTP connection to Modelverse...'"/>
+                            </raise>
+                        </onentry>
+
+                        <transition event="mv_ready" target="../initializing_modelverse">
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="40"/>
+                                <parameter expr="'Waiting for HTTP connection to Modelverse... OK'"/>
+                            </raise>
+                        </transition>
+                    </state>
+
+                    <state id="initializing_modelverse">
+                        <onentry>
+                            <raise event="mv_request">
+                                <parameter expr="'init'"/>
+                                <parameter expr="[self.address, self.timeout]"/>
+                            </raise>
+
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="50"/>
+                                <parameter expr="'Waiting for Modelverse initialization...'"/>
+                            </raise>
+                        </onentry>
+
+                        <transition event="mv_response" target="../logging_in_modelverse">
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="70"/>
+                                <parameter expr="'Waiting for Modelverse initialization... OK'"/>
+                            </raise>
+                        </transition>
+                    </state>
+
+                    <state id="logging_in_modelverse">
+                        <onentry>
+                            <raise event="mv_request">
+                                <parameter expr="'login'"/>
+                                <parameter expr="[self.username, self.password]"/>
+                            </raise>
+
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="90"/>
+                                <parameter expr="'Logging in...'"/>
+                            </raise>
+                        </onentry>
+
+                        <transition event="mv_response" target="../../init_main_window">
+                            <raise event="update_status" scope="narrow" target="self.splash_window">
+                                <parameter expr="100"/>
+                                <parameter expr="'Logging in... OK'"/>
+                            </raise>
+                        </transition>
+                    </state>
+                </state>
+
+                <state id="init_main_window">
+                    <onentry>
+                        <raise event="create_instance" scope="cd">
+                            <parameter expr="'windows'"/>
+                            <parameter expr="'MainWindow'"/>
+                        </raise>
+
+                        <raise event="delete_instance" scope="cd">
+                            <parameter expr="self.splash_window"/>
+                        </raise>
+                    </onentry>
+
+                    <transition event="instance_created" target="../main_behaviour"/>
+                </state>
+
+                <state id="main_behaviour">
+                    <transition event="window_created" target=".">
+                        <script>
+                            self.nr_of_windows += 1
+                        </script>
+                    </transition>
+                    <transition event="window_deleted" target="." cond="self.nr_of_windows > 1">
+                        <script>
+                            self.nr_of_windows -= 1
+                        </script>
+                    </transition>
+                    <transition cond="self.nr_of_windows == 0" target="../stopped">
+                        <raise event="stop" scope="local" />
+                    </transition>
+                </state>
+
+                <state id="stopped">
+                    <onentry>
+                        <script>
+                            self.root.quit()
+                        </script>
+                    </onentry>
+                </state>
+
+                <transition event="mv_exception" target="stopped">
+                    <parameter name="ID"/>
+                    <parameter name="exception_name"/>
+                    <parameter name="exception"/>
+                    <script>
+                        print("Got uncaught exception from Modelverse: " + str(exception_name))
+                        print("Parameters: " + str(exception))
+                    </script>
+                </transition>
+            </state>
+
+            <state id="forward_requests">
+                <state id="forward">
+                    <transition event="mv_request" target=".">
+                        <parameter name="name"/>
+                        <parameter name="parameters"/>
+                        <raise event="action" scope="narrow" target="'modelverse'">
+                            <parameter expr="name"/>
+                            <parameter expr="None"/>
+                            <parameter expr="None"/>
+                            <parameter expr="parameters"/>
+                        </raise>
+                    </transition>
+
+                    <transition event="mv_result" target=".">
+                        <parameter name="ID"/>
+                        <parameter name="result"/>
+                        <raise event="mv_response">
+                            <parameter expr="result"/>
+                        </raise>
+                    </transition>
+                </state>
+            </state>
+        </parallel>
+    </scxml>
+</class>

+ 177 - 0
classes/modelverse/http_client.xml

@@ -0,0 +1,177 @@
+<class name="HTTPClient">
+    <constructor>
+        <body>
+            <![CDATA[
+            self.socket = None
+            self.received_data = ""
+            self.send_data = ""
+            self.queue = []
+            self.IDs = []
+            ]]>
+        </body>
+    </constructor>
+
+    <scxml initial="init">
+        <state id="init">
+            <onentry>
+                <script>
+                    self.ID = str(uuid.uuid4())
+                </script>
+                <raise scope="output" event="create_socket" port="socket_out">
+                    <parameter expr="self.ID"/>
+                </raise>
+            </onentry>
+
+            <transition port="socket_in" event="created_socket" cond="self.ID == ID" target="../waiting">
+                <parameter name="socket"/>
+                <parameter name="ID"/>
+                <script>
+                    self.socket = socket
+                </script>
+            </transition>
+        </state>
+
+        <state id="waiting">
+            <onentry>
+                <raise scope="broad" event="http_client_initialized"/>
+            </onentry>
+
+            <transition event="connect" target="../connecting">
+                <parameter name="address"/>
+                <parameter name="timeout"/>
+
+                <script>
+                    self.address = address
+                    self.timeout = timeout
+                </script>
+            </transition>
+        </state>
+
+        <state id="connecting" initial="connecting">
+            <state id="connecting">
+                <onentry>
+                    <raise scope="output" event="connect_socket" port="socket_out">
+                        <parameter expr="self.socket"/>
+                        <parameter expr="self.address"/>
+                    </raise>
+                </onentry>
+
+                <transition port="socket_in" event="error_socket" target="../cooldown"/>
+
+                <transition port="socket_in" event="connected_socket" cond="self.socket == socket" target="../../connected">
+                    <parameter name="socket"/>
+                    <raise scope="broad" event="http_client_ready"/>
+                </transition>
+            </state>
+
+            <state id="cooldown">
+                <transition after="0.1" target="../connecting"/>
+            </state>
+
+            <transition after="self.timeout" target="../waiting">
+                <raise scope="broad" event="http_client_timeout"/>
+            </transition>
+        </state>
+
+        <parallel id="connected">
+            <state id="listening" initial="listen">
+                <state id="listen">
+                    <onentry>
+                        <raise scope="output" port="socket_out" event="recv_socket">
+                            <parameter expr="self.socket"/>
+                        </raise>
+                    </onentry>
+                    <transition event="received_socket" port="socket_in" cond="(self.socket == socket) and (len(data) > 0)" target=".">
+                        <parameter name="socket"/>
+                        <parameter name="data"/>
+                        <script>
+                            self.received_data += data
+                        </script>
+                    </transition>
+                    <transition event="received_socket" port="socket_in" cond="(self.socket == socket) and (len(data) == 0)" target="../close">
+                        <parameter name="socket"/>
+                        <parameter name="data"/>
+                    </transition>
+                </state>
+                <state id="close">
+                </state>
+            </state>
+
+            <state id="sending" initial="waiting_for_data">
+                <state id="waiting_for_data">
+                    <transition cond="len(self.send_data) > 0" target="../transferring">
+                        <raise scope="output" port="socket_out" event="send_socket">
+                            <parameter expr="self.socket"/>
+                            <parameter expr="self.send_data"/>
+                        </raise>
+                    </transition>
+                </state>
+                <state id="transferring">
+                    <transition event="sent_socket" port="socket_in" cond="self.socket == socket" target="../waiting_for_data">
+                        <parameter name="socket"/>
+                        <parameter name="sent_bytes"/>
+                        <script>
+                            self.send_data = self.send_data[sent_bytes:]
+                        </script>
+                    </transition>
+                </state>
+            </state>
+
+            <state id="queueing">
+                <state id="queueing">
+                    <onentry>
+                    </onentry>
+                    <transition event="HTTP_input" target=".">
+                        <parameter name="data"/>
+                        <parameter name="ID"/>
+                        <script>
+                            self.send_data += "POST / HTTP/1.0\r\n"
+                            self.send_data += "Content-Length: %i\r\n" % len(str(data))
+                            self.send_data += "\r\n"
+                            self.send_data += data
+                            self.IDs.append(ID)
+                        </script>
+                    </transition>
+                </state>
+            </state>
+
+            <state id="parsing" initial="wait_for_header">
+                <state id="wait_for_header">
+                    <transition cond="'\r\n\r\n' in self.received_data" target="../wait_for_payload">
+                        <script>
+                            header, self.received_data = self.received_data.split("\r\n\r\n", 1)
+                            header = header.lower()
+                            if "content-length" in header:
+                                _, after = header.split("content-length:", 1)
+                                after, _ = after.split("\r\n", 1)
+                                after = after.strip()
+                                self.length = int(after)
+                            else:
+                                self.length = float('inf')
+                        </script>
+                    </transition>
+                </state>
+                <state id="wait_for_payload">
+                    <transition cond="len(self.received_data) >= self.length and self.IDs[0] is not None" target="../wait_for_header">
+                        <script>
+                            data = self.received_data[:self.length]
+                            self.received_data = self.received_data[self.length:]
+                        </script>
+                        <raise event="HTTP_output" scope="broad">
+                            <parameter expr="data"/>
+                            <parameter expr="self.IDs.pop(0)"/>
+                        </raise>
+                    </transition>
+
+                    <transition cond="len(self.received_data) >= self.length and self.IDs[0] is None" target="../wait_for_header">
+                        <script>
+                            # Drop data
+                            self.received_data = self.received_data[self.length:]
+                            self.IDs.pop(0)
+                        </script>
+                    </transition>
+                </state>
+            </state>
+        </parallel>
+    </scxml>
+</class>

File diff suppressed because it is too large
+ 1854 - 0
classes/modelverse/modelverse.xml


+ 31 - 0
classes/window/splash_window.xml

@@ -0,0 +1,31 @@
+<class name="SplashWindow">
+    <relationships>
+        <inheritance class="tk.Toplevel" priority="1"/>
+    </relationships>
+
+    <constructor>
+        <super class="tk.TopLevel"/>
+        <body>
+            self.progressbar = ttk.Progressbar(self, orient=tk.HORIZONTAL, length=400)
+            self.progressbar.pack()
+            self.current = 0
+        </body>
+    </constructor>
+
+    <scxml initial="root">
+        <state id="root">
+            <transition event="update_status" target=".">
+                <parameter name="value"/>
+                <parameter name="text"/>
+                <script>
+                    delta = value - self.current
+                    self.progressbar.step(delta)
+                    self.current += delta
+
+                    #TODO update text as well
+                    print(text)
+                </script>
+            </transition>
+        </state>
+    </scxml>
+</class>

+ 2 - 0
classes/window/window.xml

@@ -0,0 +1,2 @@
+<class name="Window">
+</class>

+ 22 - 0
frontend.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" ?>
+<diagram author="Yentl Van Tendeloo and Addis Gebremichael" name="Modelverse Visual Editor - Tkinter Version ">
+    <description>
+        Modelverse Visual Editor
+    </description>
+    <top>
+        import Tkinter as tk
+        import ttk
+        from mvk_widget import MvKWidget
+        import uuid
+        import json
+        import urllib
+    </top>
+
+    <inport name="socket_in"/>
+    <outport name="socket_out"/>
+
+    <class src="classes/main_app.xml" default="true" />
+    <class src="classes/modelverse/modelverse.xml"/>
+    <class src="classes/modelverse/http_client.xml"/>
+    <class src="classes/window/splash_window.xml"/>
+</diagram>

+ 341 - 0
mvk_widget.py

@@ -0,0 +1,341 @@
+'''
+Created on 31-jul.-2014
+
+@author: Simon
+'''
+
+from PIL import Image, ImageTk
+
+import Tkinter as tk
+import atexit
+from sccd.runtime.statecharts_core import Event
+
+class MvKWidget:
+    controller = None
+
+    def __init__(self, configure_later=False):
+        if not configure_later:
+            self.set_bindable_and_tagorid(None, None)
+
+    def set_bindable_and_tagorid(self, bindable=None, tagorid=None):
+        if bindable is None:
+            bindable = self
+        self.bindable = bindable
+        self.mytagorid = tagorid
+        if isinstance(self, tk.Toplevel):
+            self.protocol("WM_DELETE_WINDOW", self.window_close)
+        if tagorid is not None:
+            if not isinstance(tagorid, list):
+                tagorid = [tagorid]
+            for t in tagorid:
+                self.bindable.tag_bind(t, "<Button>", self.on_click)
+                self.bindable.tag_bind(t, "<Double-Button>", self.on_dbclick)
+                self.bindable.tag_bind(t, "<ButtonRelease>", self.on_release)
+                self.bindable.tag_bind(t, "<Motion>", self.on_motion)
+                self.bindable.tag_bind(t, "<Enter>", self.on_enter)
+                self.bindable.tag_bind(t, "<Leave>", self.on_leave)
+                self.bindable.tag_bind(t, "<Key>", self.on_key)
+                self.bindable.tag_bind(t, "<KeyRelease>", self.on_key_release)
+        else:
+            self.bindable.bind("<Button>", self.on_click)
+            self.bindable.bind("<ButtonRelease>", self.on_release)
+            self.bindable.bind("<Motion>", self.on_motion)
+            self.bindable.bind("<Enter>", self.on_enter)
+            self.bindable.bind("<Leave>", self.on_leave)
+            self.bindable.bind("<Key>", self.on_key)
+            self.bindable.bind("<KeyRelease>", self.on_key_release)
+            self.bindable.bind("<Shift-Up>",self.zoom_in)
+            self.bindable.bind("<Shift-Down>", self.zoom_out)
+            self.bindable.bind("<MouseWheel>", self.scroller)
+
+        self.last_x = 50
+        self.last_y = 50
+        self.event_delta = 0
+        self.zoomer_count = 0
+        self.selected_type = None
+
+    def scroller(self, event):
+        self.event_delta = event.delta
+        event_name= "scroll_mousewheel"
+        self.last_x = event.x
+        self.last_y = event.y
+        MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def zoom_in(self, event):
+        event_name = "zoomer_event"
+        self.last_x = event.x
+        self.last_y = event.y
+        if self.zoomer_count <=6:
+            self.event_delta = 120
+            self.zoomer_count +=1
+        MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def zoom_out(self, event):
+        event_name = "zoomer_event"
+        self.last_x = event.x
+        self.last_y = event.y
+        if self.zoomer_count >=-6:
+            self.event_delta = -120
+            self.zoomer_count -=1
+        MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def on_dbclick(self, event):
+        event_name = None
+
+        if event.num == 1:
+            event_name = "left-dbclick"
+        elif event.num == 2:
+            event_name = "middle-dbclick"
+        elif event.num == 3:
+            event_name = "right-dbclick"
+
+        if event_name:
+            self.last_x = event.x
+            self.last_y = event.y
+            MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def on_click(self, event):
+        event_name = None
+        if event.num == 1:
+            event_name = "left-click"
+        elif event.num == 2:
+            event_name = "middle-click"
+        elif event.num == 3:
+            event_name = "right-click"
+
+        if event_name:
+            self.last_x = event.x
+            self.last_y = event.y
+            MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def on_release(self, event):
+        event_name = None
+
+        if event.num == 1:
+            event_name = "left-release"
+        elif event.num == 2:
+            event_name = "middle-release"
+        elif event.num == 3:
+            event_name = "right-release"
+
+        if event_name:
+            self.last_x = event.x
+            self.last_y = event.y
+            MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def on_motion(self, event):
+        self.last_x = event.x
+        self.last_y = event.y
+        MvKWidget.controller.addInput(Event("motion", "input", [id(self)]))
+        return "break"
+
+    def on_enter(self, event):
+        MvKWidget.controller.addInput(Event("enter", "input", [id(self)]))
+        return "break"
+
+    def on_leave(self, event):
+        MvKWidget.controller.addInput(Event("leave", "input", [id(self)]))
+        return "break"
+
+    def on_key(self, event):
+        event_name = None
+
+        if event.keysym == 'Escape':
+            event_name = "escape"
+        elif event.keysym == 'Return':
+            event_name = "return"
+        elif event.keysym == 'Delete':
+            event_name = "delete"
+        elif event.keysym == 'Shift_L':
+            event_name = "shift"
+        elif event.keysym == 'Control_L':
+            event_name = "control"
+        elif event.keysym == 'Left':
+            event_name = "left_key"
+        elif event.keysym == 'Right':
+            event_name = "right_key"
+        elif event.keysym == 'Up':
+            event_name = "up_key"
+        elif event.keysym == 'Down':
+            event_name = "down_key"
+
+        if event_name:
+            MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def on_key_release(self, event):
+        event_name = None
+
+        if event.keysym == 'Escape':
+            event_name = "escape-release"
+        elif event.keysym == 'Return':
+            event_name = "return-release"
+        elif event.keysym == 'Delete':
+            event_name = "delete-release"
+        elif event.keysym == 'Shift_L':
+            event_name = "shift-release"
+        elif event.keysym == 'Control_L':
+            event_name = "control-release"
+
+        if event_name:
+            MvKWidget.controller.addInput(Event(event_name, "input", [id(self)]))
+        return "break"
+
+    def window_close(self):
+        MvKWidget.controller.addInput(Event("window-close", "input", [id(self)]))
+
+
+class HorizontalScrolledFrame(tk.Frame):
+    def __init__(self, parent, *args, **kw):
+        tk.Frame.__init__(self, parent, *args, **kw)
+
+        # create a canvas object and a horizontal scrollbar for scrolling it
+        hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
+        hscrollbar.pack(fill=tk.X, side=tk.BOTTOM, expand=tk.FALSE)
+        self.canvas = tk.Canvas(self, bd=0, highlightthickness=0,
+                                xscrollcommand=hscrollbar.set, height=78)
+        self.canvas.pack(side=tk.LEFT, fill=tk.X, expand=tk.TRUE)
+
+        hscrollbar.config(command=self.canvas.xview)
+
+        # reset the view
+        self.canvas.xview_moveto(0)
+        self.canvas.yview_moveto(0)
+
+        # create a frame inside the canvas which will be scrolled with it
+        self.interior = interior = tk.Frame(self.canvas)
+        self.canvas.create_window(0, 0, window=interior, anchor=tk.NW)
+
+        # track changes to the canvas and frame width and sync them,
+        # also updating the scrollbar
+        def _configure_interior(event):
+            # update the scrollbars to match the size of the inner frame
+            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
+            self.canvas.config(scrollregion="0 0 %s %s" % size)
+
+        interior.bind('<Configure>', _configure_interior)
+
+
+class VerticalScrolledFrame(tk.Frame):
+    def __init__(self, parent, *args, **kw):
+        tk.Frame.__init__(self, parent, *args, **kw)
+
+        vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
+        vscrollbar.pack(fill=tk.Y, side=tk.RIGHT, expand=tk.FALSE)
+        canvas = tk.Canvas(self, bd=0, highlightthickness=0,
+                           yscrollcommand=vscrollbar.set, width=100)
+        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.TRUE)
+        vscrollbar.config(command=canvas.yview)
+
+        canvas.xview_moveto(0)
+        canvas.yview_moveto(0)
+
+        self.interior = interior = tk.Frame(canvas)
+        interior_id = canvas.create_window(0, 0, window=interior,
+                                           anchor=tk.NW)
+
+        def _configure_interior(event):
+            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
+            canvas.config(scrollregion="0 0 %s %s" % size)
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                canvas.config(width=interior.winfo_reqwidth())
+
+        interior.bind('<Configure>', _configure_interior)
+
+        def _configure_canvas(event):
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
+
+        canvas.bind('<Configure>', _configure_canvas)
+
+        return
+
+class VerticallyScrolledWindow(tk.Frame):
+    def __init__(self, root):
+
+        tk.Frame.__init__(self, root)
+        defaultbg = root.cget('bg')
+        self.canvas = tk.Canvas(root, borderwidth=0, background=defaultbg)
+        self.frame = tk.Frame(self.canvas, background=defaultbg)
+        self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
+        self.canvas.configure(yscrollcommand=self.vsb.set)
+
+        self.vsb.pack(side="right", fill="y")
+        self.canvas.pack(side="left", fill="both", expand=True)
+        self.canvas.create_window((4,4), window=self.frame, anchor="nw",
+                                  tags="self.frame")
+
+        self.frame.bind("<Configure>", self.onFrameConfigure)
+        self.frame.bind_all("<MouseWheel>", self.scroll)
+
+    def scroll(self, event):
+        self.canvas.yview_scroll(-1 * (event.delta / 120), "units")
+
+    def onFrameConfigure(self, event):
+        '''Reset the scroll region to encompass the inner frame'''
+        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
+
+class Visual(object):
+    def get_params(self):
+        raise NotImplementedError()
+
+
+class TextVisual(Visual):
+    def __init__(self, text):
+        super(TextVisual, self).__init__()
+        self.text = text
+
+    def get_params(self):
+        return {'text': self.text}
+
+
+class ImageVisual(Visual):
+    def __init__(self, img_loc):
+        super(ImageVisual, self).__init__()
+        self.img = ImageTk.PhotoImage(Image.open(img_loc).resize((32, 32), Image.ANTIALIAS))
+
+    def get_params(self):
+        return {'image': self.img}
+
+
+class ToolTip:
+    def __init__(self, widget, text):
+        self.widget = widget
+        self.tipwindow = None
+        self.id = None
+        self.x = self.y = 0
+        self.text = text
+
+    def showtip(self):
+        "Display text in tooltip window"
+        if self.tipwindow or not self.text:
+            return
+        x, y, cx, cy = self.widget.bbox("insert")
+        x = x + cx + self.widget.winfo_rootx() + 27
+        y = y + cy + self.widget.winfo_rooty() - 20
+        self.tipwindow = tw = tk.Toplevel(self.widget)
+        tw.wm_overrideredirect(1)
+        tw.wm_geometry("+%d+%d" % (x, y))
+        try:
+            # For Mac OS
+            tw.tk.call("::tk::unsupported::MacWindowStyle",
+                       "style", tw._w,
+                       "help", "noActivates")
+        except tk.TclError:
+            pass
+        label = tk.Label(tw, text=self.text, justify=tk.LEFT,
+                         background="#ffffe0", relief=tk.SOLID, borderwidth=1,
+                         font=("tahoma", "8", "normal"))
+        label.pack(ipadx=1)
+
+    def hidetip(self):
+        tw = self.tipwindow
+        self.tipwindow = None
+        if tw:
+            tw.destroy()

+ 27 - 0
runner.py

@@ -0,0 +1,27 @@
+'''
+Created on 20-Jan.-2017
+
+@author: Addis G.
+'''
+
+import Tkinter as tk
+import frontend
+from sccd.runtime.tkinter_eventloop import *
+import sccd.runtime.socket2event as socket2event
+
+from mvk_widget import MvKWidget
+
+class Root(tk.Tk, MvKWidget):
+    def __init__(self):
+        tk.Tk.__init__(self)
+        MvKWidget.__init__(self)
+
+if __name__ == '__main__':
+    root = Root()
+    root.withdraw()
+    controller = frontend.Controller(root, TkEventLoop(root))
+    socket2event.boot_translation_service(controller)
+
+    MvKWidget.controller = controller
+    controller.start()
+    root.mainloop()