فهرست منبع

Centralized object creation. Cleaner notebook handling. Centralized form generation. Some threading improvement. Comments.

Juan Pablo Caram 12 سال پیش
والد
کامیت
c3260802df
2فایلهای تغییر یافته به همراه344 افزوده شده و 301 حذف شده
  1. 294 170
      cirkuix.py
  2. 50 131
      cirkuix.ui

+ 294 - 170
cirkuix.py

@@ -1,8 +1,7 @@
 
 import threading
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GLib
+from gi.repository import Gtk, Gdk, GLib, GObject
+
 
 from matplotlib.figure import Figure
 from numpy import arange, sin, pi
@@ -14,6 +13,12 @@ from camlib import *
 
 
 class CirkuixObj:
+    """
+    Base type of objects handled in Cirkuix. These become interactive
+    in the GUI, can be plotted, and their options can be modified
+    by the user in their respective forms.
+    """
+
     form_getters = {}
 
     form_setters = {}
@@ -55,8 +60,42 @@ class CirkuixObj:
         for name in self.form_getters:
             self.options[name] = self.form_getters[name]()
 
+    def build_ui(self, kind):
+        """
+        Sets up the UI/form for this object.
+        @param kind: Kind of object, i.e. 'gerber'
+        @type kind: str
+        @return: None
+        """
+
+        # Where to the UI for this object
+        box_selected = self.app.builder.get_object("box_selected")
+
+        # Remove anything else in the box
+        box_children = box_selected.get_children()
+        for child in box_children:
+            box_selected.remove(child)
+
+        osw = self.app.builder.get_object("offscrwindow_" + kind)  # offscreenwindow
+        sw = self.app.builder.get_object("sw_" + kind)  # scrollwindows
+        osw.remove(sw)  # TODO: Is this needed ?
+        vp = self.app.builder.get_object("vp_" + kind)  # Viewport
+        vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
+
+        # Put in the UI
+        box_selected.pack_start(sw, True, True, 0)
+
+        entry_name = self.app.builder.get_object("entry_" + kind + "name")
+        entry_name.set_text(self.name)
+        entry_name.connect("activate", self.app.on_activate_name)
+        self.to_form()
+        sw.show()
+
 
 class CirkuixGerber(CirkuixObj, Gerber):
+    """
+    Represents Gerber code.
+    """
 
     def __init__(self, name):
         Gerber.__init__(self)
@@ -75,20 +114,10 @@ class CirkuixGerber(CirkuixObj, Gerber):
         }
 
     def build_ui(self):
-        print "cirkuixgerber.build_ui()"
-        osw = self.app.builder.get_object("offscrwindow_gerber")
-        #box1 = self.app.builder.get_object("box_gerber")
-        #osw.remove(box1)
-        sw = self.app.builder.get_object("sw_gerber")
-        osw.remove(sw)
-        vp = self.app.builder.get_object("vp_gerber")
-        vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
-        self.app.notebook.append_page(sw, Gtk.Label("Selection"))
-        entry_name = self.app.builder.get_object("entry_gerbername")
-        entry_name.set_text(self.name)
-        entry_name.connect("activate", self.app.on_activate_name)
-        self.to_form()
-        sw.show()
+        """
+        @return: None
+        """
+        CirkuixObj.build_ui(self, "gerber")
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -118,6 +147,9 @@ class CirkuixGerber(CirkuixObj, Gerber):
 
 
 class CirkuixExcellon(CirkuixObj, Excellon):
+    """
+    Represents Excellon code.
+    """
 
     def __init__(self, name):
         Excellon.__init__(self)
@@ -130,20 +162,7 @@ class CirkuixExcellon(CirkuixObj, Excellon):
         }
 
     def build_ui(self):
-        print "build_excellon_ui()"
-        osw = self.app.builder.get_object("offscrwindow_excellon")
-        #box1 = self.app.builder.get_object("box_excellon")
-        #osw.remove(box1)
-        sw = self.app.builder.get_object("sw_excellon")
-        osw.remove(sw)
-        vp = self.app.builder.get_object("vp_excellon")
-        vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
-        self.app.notebook.append_page(sw, Gtk.Label("Selection"))
-        entry_name = self.app.builder.get_object("entry_excellonname")
-        entry_name.set_text(self.name)
-        entry_name.connect("activate", self.app.on_activate_name)
-        self.to_form()
-        sw.show()
+        CirkuixObj.build_ui(self, "excellon")
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -159,6 +178,9 @@ class CirkuixExcellon(CirkuixObj, Excellon):
 
 
 class CirkuixCNCjob(CirkuixObj, CNCjob):
+    """
+    Represents G-Code.
+    """
     def __init__(self, name, units="in", kind="generic", z_move=0.1,
                  feedrate=3.0, z_cut=-0.002, tooldia=0.0):
         CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
@@ -172,20 +194,7 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
         }
 
     def build_ui(self):
-        print "build_cncjob_ui()"
-        osw = self.app.builder.get_object("offscrwindow_cncjob")
-        #box1 = self.app.builder.get_object("box_cncjob")
-        #osw.remove(box1)
-        sw = self.app.builder.get_object("sw_cncjob")
-        osw.remove(sw)
-        vp = self.app.builder.get_object("vp_cncjob")
-        vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
-        self.app.notebook.append_page(sw, Gtk.Label("Selection"))
-        entry_name = self.app.builder.get_object("entry_cncjobname")
-        entry_name.set_text(self.name)
-        entry_name.connect("activate", self.app.on_activate_name)
-        self.to_form()
-        sw.show()
+        CirkuixObj.build_ui(self, "cncjob")
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -193,6 +202,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
 
 
 class CirkuixGeometry(CirkuixObj, Geometry):
+    """
+    Geometric object not associated with a specific
+    format.
+    """
+
     def __init__(self, name):
         CirkuixObj.__init__(self, name)
         self.options = {"plot": True,
@@ -209,20 +223,7 @@ class CirkuixGeometry(CirkuixObj, Geometry):
         }
 
     def build_ui(self):
-        print "build_geometry_ui()"
-        osw = self.app.builder.get_object("offscrwindow_geometry")
-        #box1 = self.app.builder.get_object("box_geometry")
-        #osw.remove(box1)
-        sw = self.app.builder.get_object("sw_geometry")
-        osw.remove(sw)
-        vp = self.app.builder.get_object("vp_geometry")
-        vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
-        self.app.notebook.append_page(sw, Gtk.Label("Selection"))
-        entry_name = self.app.builder.get_object("entry_geometryname")
-        entry_name.set_text(self.name)
-        entry_name.connect("activate", self.app.on_activate_name)
-        self.to_form()
-        sw.show()
+        CirkuixObj.build_ui(self, "geometry")
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -244,9 +245,20 @@ class CirkuixGeometry(CirkuixObj, Geometry):
 
 
 class App:
+    """
+    The main application class. The constructor starts the GUI.
+    """
+
     def __init__(self):
+        """
+        Starts the application and the Gtk.main().
+        @return: app
+        """
+
         # Needed to interact with the GUI from other threads.
         GLib.threads_init()
+        GObject.threads_init()
+        #Gdk.threads_init()
 
         ########################################
         ##                GUI                 ##
@@ -296,9 +308,17 @@ class App:
         ##              START                 ##
         ########################################
         self.window.show_all()
-        Gtk.main()
+        #Gtk.main()
         
     def setup_plot(self):
+        """
+        Sets up the main plotting area by creating a matplotlib
+        figure in self.canvas, adding axes and configuring them.
+        These axes should not be ploted on and are just there to
+        display the axes ticks and grid.
+        @return: None
+        """
+
         self.figure = Figure(dpi=50)
         self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
         self.axes.set_aspect(1)
@@ -398,7 +418,9 @@ class App:
         """
         List or Tree where whatever has been loaded or created is
         displayed.
+        @return: None
         """
+
         self.store = Gtk.ListStore(str)
         self.tree = Gtk.TreeView(self.store)
         #self.list = Gtk.ListBox()
@@ -407,24 +429,42 @@ class App:
         renderer = Gtk.CellRendererText()
         column = Gtk.TreeViewColumn("Title", renderer, text=0)
         self.tree.append_column(column)
-        self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
+        #self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
+        self.builder.get_object("box_project").pack_start(self.tree, False, False, 1)
         
     def setup_component_editor(self):
+        """
+        Initial configuration of the component editor. Creates
+        a page titled "Selection" on the notebook on the left
+        side of the main window.
+        @return: None
+        """
+
+        box_selected = self.builder.get_object("box_selected")
+
+        # Remove anything else in the box
+        box_children = box_selected.get_children()
+        for child in box_children:
+            box_selected.remove(child)
+
         box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
         label1 = Gtk.Label("Choose an item from Project")
         box1.pack_start(label1, False, False, 1)
-        self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
+        box_selected.pack_start(box1, True, True, 0)
 
     def info(self, text):
         """
         Show text on the status bar.
+        @return: None
         """
+
         self.info_label.set_text(text)
 
     def zoom(self, factor, center=None):
         """
         Zooms the plot by factor around a given
         center point. Takes care of re-drawing.
+        @return: None
         """
         xmin, xmax = self.axes.get_xlim()
         ymin, ymax = self.axes.get_ylim()
@@ -455,6 +495,11 @@ class App:
         self.canvas.queue_draw()
 
     def build_list(self):
+        """
+        Clears and re-populates the list of objects in tcurrently
+        in the project.
+        @return: None
+        """
         self.store.clear()
         for key in self.stuff:
             self.store.append([key])
@@ -463,12 +508,19 @@ class App:
         """
         Returns the radio_set[key] if the radiobutton
         whose name is key is active.
+        @return: radio_set[key]
         """
+
         for name in radio_set:
             if self.builder.get_object(name).get_active():
                 return radio_set[name]
 
     def plot_all(self):
+        """
+        Re-generates all plots from all objects.
+        @return: None
+        """
+
         self.clear_plots()
         
         for i in self.stuff:
@@ -479,88 +531,148 @@ class App:
         self.canvas.queue_draw()
         
     def clear_plots(self):
+        """
+        Clears self.axes and self.figure.
+        @return: None
+        """
+
         self.axes.cla()
         self.figure.clf()
         self.figure.add_axes(self.axes)
         self.canvas.queue_draw()
 
     def get_eval(self, widget_name):
+        """
+        Runs eval() on the on the text entry of name 'widget_name'
+        and returns the results.
+        @param widget_name: Name of Gtk.Entry
+        @return: Depends on contents of the entry text.
+        """
+
         value = self.builder.get_object(widget_name).get_text()
         return eval(value)
 
     def set_list_selection(self, name):
+        """
+        Marks a given object as selected in the list ob objects
+        in the GUI. This selection will in turn trigger
+        self.on_tree_selection_changed().
+        @param name: Name of the object.
+        @return: None
+        """
+
         iter = self.store.get_iter_first()
         while iter is not None and self.store[iter][0] != name:
             iter = self.store.iter_next(iter)
         self.tree_select.unselect_all()
         self.tree_select.select_iter(iter)
 
+    def new_object(self, kind, name, initialize):
+        """
+        Creates a new specalized CirkuixObj and attaches it to the application,
+        this is, updates the GUI accordingly, any other records and plots it.
+        @param kind: Knd of object to create.
+        @param name: Name for the object.
+        @param initilize: Function to run after the
+            object has been created but before attacing it
+            to the application. Takes the new object and the
+            app as parameters.
+        @return: The object requested
+        @rtype : CirkuixObj extended
+        """
+
+        # Check for existing name
+        if name in self.stuff:
+            return None
+
+        # Create object
+        classdict = {
+            "gerber": CirkuixGerber,
+            "excellon": CirkuixExcellon,
+            "cncjob": CirkuixCNCjob,
+            "geometry": CirkuixGeometry
+        }
+        obj = classdict[kind](name)
+
+        # Initialize as per user request
+        initialize(obj, self)
+
+        # Add to our records
+        self.stuff[name] = obj
+
+        # Update GUI list and select it
+        self.store.append([name])
+        #self.build_list()
+        self.set_list_selection(name)
+        GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
+
+        # Plot
+        obj.plot(self.figure)
+        obj.axes.set_alpha(0.0)
+        self.on_zoom_fit(None)
+
+        return obj
+
+
     ########################################
     ##         EVENT HANDLERS             ##
     ########################################
     def on_gerber_generate_noncopper(self, widget):
-        gerber = self.stuff[self.selected_item_name]
-        gerber.read_form()
-
         name = self.selected_item_name + "_noncopper"
 
-        bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
-
-        non_copper = bounding_box.difference(gerber.solid_geometry)
-
-        geometry = CirkuixGeometry(name)
-        geometry.solid_geometry = non_copper
-
-        self.stuff[name] = geometry
-        self.build_list()
+        def geo_init(geo_obj, app_obj):
+            assert isinstance(geo_obj, CirkuixGeometry)
+            gerber = app_obj.stuff[self.selected_item_name]
+            assert isinstance(gerber, CirkuixGerber)
+            gerber.read_form()
+            bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
+            non_copper = bounding_box.difference(gerber.solid_geometry)
+            geo_obj.solid_geometry = non_copper
 
+        # TODO: Check for None
+        self.new_object("geometry", name, geo_init)
 
     def on_gerber_generate_cutout(self, widget):
-        margin = self.get_eval("entry_gerber_cutout_margin")
-        gap_size = self.get_eval("entry_gerber_cutout_gapsize")
-        gerber = self.stuff[self.selected_item_name]
-        minx, miny, maxx, maxy = gerber.bounds()
-        minx -= margin
-        maxx += margin
-        miny -= margin
-        maxy += margin
-        midx = 0.5 * (minx + maxx)
-        midy = 0.5 * (miny + maxy)
-        hgap = 0.5 * gap_size
-        pts = [[midx-hgap, maxy],
-               [minx, maxy],
-               [minx, midy+hgap],
-               [minx, midy-hgap],
-               [minx, miny],
-               [midx-hgap, miny],
-               [midx+hgap, miny],
-               [maxx, miny],
-               [maxx, midy-hgap],
-               [maxx, midy+hgap],
-               [maxx, maxy],
-               [midx+hgap, maxy]]
-        cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
-                        [pts[6], pts[7], pts[10], pts[11]]],
-                 "lr": [[pts[9], pts[10], pts[1], pts[2]],
-                        [pts[3], pts[4], pts[7], pts[8]]],
-                 "4": [[pts[0], pts[1], pts[2]],
-                       [pts[3], pts[4], pts[5]],
-                       [pts[6], pts[7], pts[8]],
-                       [pts[9], pts[10], pts[11]]]}
         name = self.selected_item_name + "_cutout"
-        geometry = CirkuixGeometry(name)
-        cuts = None
-        if self.builder.get_object("rb_2tb").get_active():
-            cuts = cases["tb"]
-        elif self.builder.get_object("rb_2lr").get_active():
-            cuts = cases["lr"]
-        else:
-            cuts = cases["4"]
-        geometry.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
 
-        # Add to App and update.
-        self.stuff[name] = geometry
-        self.build_list()
+        def geo_init(geo_obj, app_obj):
+            # TODO: get from object
+            margin = app_obj.get_eval("entry_gerber_cutout_margin")
+            gap_size = app_obj.get_eval("entry_gerber_cutout_gapsize")
+            gerber = app_obj.stuff[app_obj.selected_item_name]
+            minx, miny, maxx, maxy = gerber.bounds()
+            minx -= margin
+            maxx += margin
+            miny -= margin
+            maxy += margin
+            midx = 0.5 * (minx + maxx)
+            midy = 0.5 * (miny + maxy)
+            hgap = 0.5 * gap_size
+            pts = [[midx-hgap, maxy],
+                   [minx, maxy],
+                   [minx, midy+hgap],
+                   [minx, midy-hgap],
+                   [minx, miny],
+                   [midx-hgap, miny],
+                   [midx+hgap, miny],
+                   [maxx, miny],
+                   [maxx, midy-hgap],
+                   [maxx, midy+hgap],
+                   [maxx, maxy],
+                   [midx+hgap, maxy]]
+            cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
+                            [pts[6], pts[7], pts[10], pts[11]]],
+                     "lr": [[pts[9], pts[10], pts[1], pts[2]],
+                            [pts[3], pts[4], pts[7], pts[8]]],
+                     "4": [[pts[0], pts[1], pts[2]],
+                           [pts[3], pts[4], pts[5]],
+                           [pts[6], pts[7], pts[8]],
+                           [pts[9], pts[10], pts[11]]]}
+            cuts = cases[app.get_radio_value({"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"})]
+            geo_obj.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
+
+        # TODO: Check for None
+        self.new_object("geometry", name, geo_init)
 
     def on_eval_update(self, widget):
         """
@@ -573,39 +685,40 @@ class App:
 
     def on_generate_isolation(self, widget):
         print "Generating Isolation Geometry:"
-        # Get required info
-        tooldia = self.builder.get_object("entry_gerberisotooldia").get_text()
-        tooldia = eval(tooldia)
-        print "tooldia:", tooldia
-        
-        # Generate
-        iso = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
-        # TODO: This will break if there is something with this name already        
         iso_name = self.selected_item_name + "_iso"
-        geo = CirkuixGeometry(iso_name)
-        geo.solid_geometry = iso
 
-        # Add to App and update.        
-        self.stuff[iso_name] = geo        
-        self.build_list()
+        def iso_init(geo_obj, app_obj):
+            # TODO: Object must be updated on form change and the options
+            # TODO: read from the object.
+            tooldia = app_obj.get_eval("entry_gerberisotooldia")
+            geo_obj.solid_geometry = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
+
+        # TODO: Do something if this is None. Offer changing name?
+        self.new_object("geometry", iso_name, iso_init)
+
         
     def on_generate_cncjob(self, widget):
         print "Generating CNC job"
-        # Get required info
-        cutz = self.get_eval("entry_geometry_cutz")
-        travelz = self.get_eval("entry_geometry_travelz")
-        feedrate = self.get_eval("entry_geometry_feedrate")
-        
-        geometry = self.stuff[self.selected_item_name]
+
         job_name = self.selected_item_name + "_cnc"
-        job = CirkuixCNCjob(job_name, z_move=travelz, z_cut=cutz, feedrate=feedrate)
-        job.generate_from_geometry(geometry.solid_geometry)
-        job.gcode_parse()
-        job.create_geometry()
-        
-        # Add to App and update.        
-        self.stuff[job_name] = job      
-        self.build_list()
+
+        def job_init(job_obj, app_obj):
+            # TODO: Object must be updated on form change and the options
+            # TODO: read from the object.
+            z_cut = app_obj.get_eval("entry_geometry_cutz")
+            z_move = app_obj.get_eval("entry_geometry_travelz")
+            feedrate = app_obj.get_eval("entry_geometry_feedrate")
+
+            geometry = app_obj.stuff[app_obj.selected_item_name]
+            assert isinstance(job_obj, CirkuixCNCjob)
+            job_obj.z_cut = z_cut
+            job_obj.z_move = z_move
+            job_obj.feedrate = feedrate
+            job_obj.generate_from_geometry(geometry.solid_geometry)
+            job_obj.gcode_parse()
+            job_obj.create_geometry()
+
+        self.new_object("cncjob", job_name, job_init)
 
     def on_cncjob_tooldia_activate(self, widget):
         job = self.stuff[self.selected_item_name]
@@ -626,7 +739,7 @@ class App:
         self.stuff.pop(self.selected_item_name)
         
         #self.tree.get_selection().disconnect(self.signal_id)
-        self.build_list() # Update the items list
+        self.build_list()  # Update the items list
         #self.signal_id = self.tree.get_selection().connect(
         #                     "changed", self.on_tree_selection_changed)
                              
@@ -658,27 +771,17 @@ class App:
 
         if treeiter is not None:
             print "You selected", model[treeiter][0]
+            self.selected_item_name = model[treeiter][0]
+            #self.stuff[self.selected_item_name].build_ui()
+            GLib.timeout_add(100, lambda: self.stuff[self.selected_item_name].build_ui())
         else:
-            return  # TODO: Clear "Selected" page
-        
-        self.selected_item_name = model[treeiter][0]
-        # Remove the current selection page
-        # from the notebook
-        # TODO: Assuming it was last page or #2. Find the right page
-        self.builder.get_object("notebook1").remove_page(2)
+            print "Nothing selected"
+            self.selected_item_name = None
+            self.setup_component_editor()
 
-        self.stuff[self.selected_item_name].build_ui()
+    def on_file_new(self, param):
+        print "File->New not implemented yet."
 
-        # Determine the kind of item selected
-        #kind = self.stuff[model[treeiter][0]].kind
-        
-        # Build the UI
-        # builder = {"gerber": self.build_gerber_ui,
-        #            "excellon": self.build_excellon_ui,
-        #            "cncjob": self.build_cncjob_ui,
-        #            "geometry": self.build_geometry_ui}
-        # builder[kind]()
-    
     def on_filequit(self, param):
         print "quit from menu"
         self.window.destroy()
@@ -755,14 +858,18 @@ class App:
             self.progress_bar.set_text("Done!")
             self.progress_bar.set_fraction(1.0)
 
-            self.notebook.set_current_page(1)
+            #self.notebook.set_current_page(0)
             self.set_list_selection(name)
+            #self.notebook.set_current_page(1)
+            GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
 
             def clear_bar(bar):
                 bar.set_text("")
                 bar.set_fraction(0.0)
+                return False
 
-            threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+            #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+            GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
         self.file_chooser_action(on_success)
     
     def on_fileopenexcellon(self, param):
@@ -790,10 +897,17 @@ class App:
             self.progress_bar.set_text("Done!")
             self.progress_bar.set_fraction(1.0)
 
+            #self.notebook.set_current_page(0)
+            self.set_list_selection(name)
+            #self.notebook.set_current_page(1)
+            GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
+
             def clear_bar(bar):
                 bar.set_text("")
                 bar.set_fraction(0.0)
-            threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+                return False
+            #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+            GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
 
         self.file_chooser_action(on_success)
     
@@ -828,10 +942,16 @@ class App:
             self.progress_bar.set_text("Done!")
             self.progress_bar.set_fraction(1.0)
 
+            #self.notebook.set_current_page(0)
+            self.set_list_selection(name)
+            #self.notebook.set_current_page(1)
+
             def clear_bar(bar):
                 bar.set_text("")
                 bar.set_fraction(0.0)
-            threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+                return False
+            #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
+            GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
         self.file_chooser_action(on_success)
         
     def on_mouse_move_over_plot(self, event):
@@ -846,9 +966,12 @@ class App:
     def on_click_over_plot(self, event):
         # For key presses
         self.canvas.grab_focus()
-        
-        print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
-        event.button, event.x, event.y, event.xdata, event.ydata)
+
+        try:
+            print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
+            event.button, event.x, event.y, event.xdata, event.ydata)
+        except:
+            print "Outside plot!"
         
     def on_zoom_in(self, event):
         self.zoom(1.5)
@@ -926,3 +1049,4 @@ class App:
             return
 
 app = App()
+Gtk.main()

+ 50 - 131
cirkuix.ui

@@ -1203,6 +1203,7 @@
                         <property name="can_focus">False</property>
                         <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_file_new" swapped="no"/>
                       </object>
                     </child>
                     <child>
@@ -1448,6 +1449,7 @@
             <property name="can_focus">True</property>
             <child>
               <object class="GtkNotebook" id="notebook1">
+                <property name="width_request">250</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="margin_left">3</property>
@@ -1456,135 +1458,12 @@
                 <property name="margin_bottom">3</property>
                 <property name="scrollable">True</property>
                 <child>
-                  <object class="GtkBox" id="box3">
+                  <object class="GtkBox" id="box_project">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="margin_left">3</property>
-                    <property name="margin_right">3</property>
-                    <property name="margin_top">3</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
                     <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="ypad">4</property>
-                        <property name="label" translatable="yes">GERBER</property>
-                        <property name="use_underline">True</property>
-                        <attributes>
-                          <attribute name="weight" value="semibold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="cb_mergepolys">
-                        <property name="label" translatable="yes">Merge Polygons</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="xalign">0</property>
-                        <property name="active">True</property>
-                        <property name="draw_indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="checkbutton1">
-                        <property name="label" translatable="yes">Solid</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="cb_multicolored">
-                        <property name="label" translatable="yes">Multi-colored</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="xalign">0</property>
-                        <property name="draw_indicator">True</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">3</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label5">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="ypad">4</property>
-                        <property name="label" translatable="yes">G-CODE</property>
-                        <attributes>
-                          <attribute name="weight" value="semibold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">4</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkBox" id="box4">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <child>
-                          <object class="GtkLabel" id="label6">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="label" translatable="yes">Tool dia: </property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">True</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkEntry" id="entry_tooldia">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="invisible_char">●</property>
-                            <property name="text" translatable="yes">0.0</property>
-                            <signal name="activate" handler="on_eval_update" swapped="no"/>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">True</property>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">5</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
                     <child>
                       <placeholder/>
                     </child>
@@ -1594,23 +1473,63 @@
                   <object class="GtkLabel" id="label1">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">Defaults</property>
+                    <property name="label" translatable="yes">Project</property>
                   </object>
                   <packing>
                     <property name="tab_fill">False</property>
                   </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkBox" id="box_selected">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
                 </child>
                 <child type="tab">
-                  <placeholder/>
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Selected</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                    <property name="tab_fill">False</property>
+                  </packing>
                 </child>
                 <child>
-                  <placeholder/>
+                  <object class="GtkBox" id="box_options">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
                 </child>
                 <child type="tab">
-                  <placeholder/>
+                  <object class="GtkLabel" id="label5">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Options</property>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                    <property name="tab_fill">False</property>
+                  </packing>
                 </child>
               </object>
               <packing>