فهرست منبع

CirkuixObj and derivatives modified and enhanced to handle form interaction better. Some threading changes.

Juan Pablo Caram 12 سال پیش
والد
کامیت
4ede050ba6
2فایلهای تغییر یافته به همراه213 افزوده شده و 236 حذف شده
  1. 201 224
      cirkuix.py
  2. 12 12
      cirkuix.ui

+ 201 - 224
cirkuix.py

@@ -1,4 +1,3 @@
-
 import threading
 from gi.repository import Gtk, Gdk, GLib, GObject
 
@@ -12,6 +11,9 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan
 from camlib import *
 
 
+########################################
+##            CirkuixObj              ##
+########################################
 class CirkuixObj:
     """
     Base type of objects handled in Cirkuix. These become interactive
@@ -31,13 +33,17 @@ class CirkuixObj:
     app = None
 
     def __init__(self, name):
-        self.name = name
+        self.options = {"name": name}
+        self.form_kinds = {"name": "entry_text"}  # Kind of form element for each option
+        self.radios = {}  # Name value pairs for radio sets
+        self.radios_inv = {}  # Inverse of self.radios
         self.axes = None  # Matplotlib axes
-        self.options = {}
+        self.kind = None  # Override with proper name
 
     def setup_axes(self, figure):
         if self.axes is None:
-            self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9], label=self.name)
+            self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9],
+                                        label=self.options["name"])
         elif self.axes not in figure.axes:
             figure.add_axes(self.axes)
 
@@ -52,23 +58,21 @@ class CirkuixObj:
         return
 
     def to_form(self):
-        for name in self.options:
-            if name in self.form_setters:
-                self.form_setters[name](self.options[name])
+        for option in self.options:
+            self.set_form_item(option)
 
     def read_form(self):
-        for name in self.form_getters:
-            self.options[name] = self.form_getters[name]()
+        for option in self.form_getters:
+            self.read_form_item(option)
 
-    def build_ui(self, kind):
+    def build_ui(self):
         """
         Sets up the UI/form for this object.
-        @param kind: Kind of object, i.e. 'gerber'
-        @type kind: str
         @return: None
+        @rtype : None
         """
 
-        # Where to the UI for this object
+        # Where the UI for this object is drawn
         box_selected = self.app.builder.get_object("box_selected")
 
         # Remove anything else in the box
@@ -76,21 +80,53 @@ class CirkuixObj:
         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 = self.app.builder.get_object("offscrwindow_" + self.kind)  # offscreenwindow
+        sw = self.app.builder.get_object("sw_" + self.kind)  # scrollwindows
         osw.remove(sw)  # TODO: Is this needed ?
-        vp = self.app.builder.get_object("vp_" + kind)  # Viewport
+        vp = self.app.builder.get_object("vp_" + self.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 = self.app.builder.get_object("entry_text_" + self.kind + "_name")
         entry_name.connect("activate", self.app.on_activate_name)
         self.to_form()
         sw.show()
 
+    def set_form_item(self, option):
+        fkind = self.form_kinds[option]
+        fname = fkind + "_" + self.kind + "_" + option
+
+        if fkind == 'entry_eval' or fkind == 'entry_text':
+            self.app.builder.get_object(fname).set_text(str(self.options[option]))
+            return
+        if fkind == 'cb':
+            self.app.builder.get_object(fname).set_active(self.options[option])
+            return
+        if fkind == 'radio':
+            self.app.builder.get_object(self.radios_inv[option][self.options[option]]).set_active(True)
+            return
+        print "Unknown kind of form item:", fkind
+
+    def read_form_item(self, option):
+        fkind = self.form_kinds[option]
+        fname = fkind + "_" + self.kind + "_" + option
+
+        if fkind == 'entry_text':
+            self.options[option] = self.app.builder(fname).get_text()
+            return
+        if fkind == 'entry_eval':
+            self.options[option] = self.app.get_eval(fname)
+            return
+        if fkind == 'cb':
+            self.options[option] = self.app.builder.get_object(fname).get_active()
+            return
+        if fkind == 'radio':
+            self.options[option] = self.app.get_radio_value(self.radios[option])
+            return
+        print "Unknown kind of form item:", fkind
+
 
 class CirkuixGerber(CirkuixObj, Gerber):
     """
@@ -101,7 +137,10 @@ class CirkuixGerber(CirkuixObj, Gerber):
         Gerber.__init__(self)
         CirkuixObj.__init__(self, name)
 
-        self.options = {
+        self.kind = "gerber"
+
+        # The 'name' is already in self.options
+        self.options.update({
             "plot": True,
             "mergepolys": True,
             "multicolored": False,
@@ -111,20 +150,29 @@ class CirkuixGerber(CirkuixObj, Gerber):
             "cutoutgapsize": 0.15,
             "gaps": "tb",
             "noncoppermargin": 0.0
-        }
-
-    def build_ui(self):
-        """
-        @return: None
-        """
-        CirkuixObj.build_ui(self, "gerber")
+        })
+
+        self.form_kinds.update({
+            "plot": "cb",
+            "mergepolys": "cb",
+            "multicolored": "cb",
+            "solid": "cb",
+            "isotooldia": "entry_eval",
+            "cutoutmargin": "entry_eval",
+            "cutoutgapsize": "entry_eval",
+            "gaps": "radio",
+            "noncoppermargin": "entry_eval"
+        })
+
+        self.radios = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
+        self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}}
 
     def plot(self, figure):
         self.setup_axes(figure)
 
         self.create_geometry()
 
-        geometry = None
+        geometry = None  # TODO: Test if needed
         if self.options["mergepolys"]:
             geometry = self.solid_geometry
         else:
@@ -132,7 +180,7 @@ class CirkuixGerber(CirkuixObj, Gerber):
                         [poly['polygon'] for poly in self.regions] + \
                         self.flash_geometry
 
-        linespec = None
+        linespec = None  # TODO: Test if needed
         if self.options["multicolored"]:
             linespec = '-'
         else:
@@ -155,14 +203,19 @@ class CirkuixExcellon(CirkuixObj, Excellon):
         Excellon.__init__(self)
         CirkuixObj.__init__(self, name)
 
-        self.options = {
+        self.kind = "excellon"
+
+        self.options.update({
             "plot": True,
             "solid": False,
             "multicolored": False
-        }
+        })
 
-    def build_ui(self):
-        CirkuixObj.build_ui(self, "excellon")
+        self.form_kinds.update({
+            "plot": "cb",
+            "solid": "cb",
+            "multicolored": "cb"
+        })
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -187,14 +240,21 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
                         feedrate=feedrate, z_cut=z_cut, tooldia=tooldia)
         CirkuixObj.__init__(self, name)
 
-        self.options = {
+        self.kind = "cncjob"
+
+        self.options.update({
             "plot": True,
             "solid": False,
+            "multicolored": "cb",
             "tooldia": 0.4/25.4  # 0.4mm in inches
-        }
+        })
 
-    def build_ui(self):
-        CirkuixObj.build_ui(self, "cncjob")
+        self.form_kinds.update({
+            "plot": "cb",
+            "solid": "cb",
+            "multicolored": "cb",
+            "tooldia": "entry_eval"
+        })
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -209,21 +269,26 @@ class CirkuixGeometry(CirkuixObj, Geometry):
 
     def __init__(self, name):
         CirkuixObj.__init__(self, name)
-        self.options = {"plot": True,
-                        "solid": False,
-                        "multicolored": False}
 
-        self.options = {
+        self.kind = "geometry"
+
+        self.options.update({
             "plot": True,
             "solid": False,
             "multicolored": False,
             "cutz": -0.002,
             "travelz": 0.1,
             "feedrate": 5.0
-        }
+        })
 
-    def build_ui(self):
-        CirkuixObj.build_ui(self, "geometry")
+        self.form_kinds.update({
+            "plot": "cb",
+            "solid": "cb",
+            "multicolored": "cb",
+            "cutz": "entry_eval",
+            "travelz": "entry_eval",
+            "feedrate": "entry_eval"
+        })
 
     def plot(self, figure):
         self.setup_axes(figure)
@@ -244,6 +309,9 @@ class CirkuixGeometry(CirkuixObj, Geometry):
                 continue
 
 
+########################################
+##                App                 ##
+########################################
 class App:
     """
     The main application class. The constructor starts the GUI.
@@ -256,7 +324,7 @@ class App:
         """
 
         # Needed to interact with the GUI from other threads.
-        GLib.threads_init()
+        #GLib.threads_init()
         GObject.threads_init()
         #Gdk.threads_init()
 
@@ -331,10 +399,8 @@ class App:
         self.canvas = FigureCanvas(self.figure)  # a Gtk.DrawingArea
         self.canvas.set_hexpand(1)
         self.canvas.set_vexpand(1)
-        
-        ########################################
-        ##              EVENTS                ##
-        ########################################
+
+        # Events
         self.canvas.mpl_connect('button_press_event', self.on_click_over_plot)
         self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot)
         self.canvas.set_can_focus(True)  # For key press
@@ -346,74 +412,6 @@ class App:
     def setup_obj_classes(self):
         CirkuixObj.app = self
 
-        CirkuixGerber.form_getters = {
-            "plot": self.builder.get_object("cb_gerber_plot").get_active,
-            "mergepolys": self.builder.get_object("cb_gerber_mergepolys").get_active,
-            "solid": self.builder.get_object("cb_gerber_solid").get_active,
-            "multicolored": self.builder.get_object("cb_gerber_multicolored").get_active,
-            "isotooldia": lambda: self.get_eval("entry_gerberisotooldia"),
-            "cutoutmargin": lambda: self.get_eval("entry_gerber_cutout_margin"),
-            "cutoutgapsize": lambda: self.get_eval("entry_gerber_cutout_gapsize"),
-            "gaps": lambda: self.get_radio_value({"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}),
-            "noncoppermargin": lambda: self.get_eval("entry_gerber_noncopper_margin")
-        }
-
-        CirkuixGerber.form_setters = {
-            "plot": self.builder.get_object("cb_gerber_plot").set_active,
-            "mergepolys": self.builder.get_object("cb_gerber_mergepolys").set_active,
-            "solid": self.builder.get_object("cb_gerber_solid").set_active,
-            "multicolored": self.builder.get_object("cb_gerber_multicolored").set_active,
-            "isotooldia": lambda x: self.builder.get_object("entry_gerberisotooldia").set_text(str(x)),
-            "cutoutmargin": lambda x: self.builder.get_object("entry_gerber_cutout_margin").set_text(str(x)),
-            "cutoutgapsize": lambda x: self.builder.get_object("entry_gerber_cutout_gapsize").set_text(str(x)),
-            "gaps": lambda x: self.builder.get_object("cb_gerber_solid").set_active(
-                                    {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}[x]),
-            "noncoppermargin": lambda x: self.builder.get_object("entry_gerber_noncopper_margin").set_text(str(x))
-        }
-
-        CirkuixExcellon.form_getters = {
-            "plot": self.builder.get_object("cb_excellon_plot").get_active,
-            "solid": self.builder.get_object("cb_excellon_solid").get_active,
-            "multicolored": self.builder.get_object("cb_excellon_multicolored").get_active
-        }
-
-        CirkuixExcellon.form_setters = {
-            "plot": self.builder.get_object("cb_excellon_plot").set_active,
-            "solid": self.builder.get_object("cb_excellon_solid").set_active,
-            "multicolored": self.builder.get_object("cb_excellon_multicolored").set_active
-        }
-
-        CirkuixCNCjob.form_getters = {
-            "plot": self.builder.get_object("cb_cncjob_plot").get_active,
-            "solid": self.builder.get_object("cb_cncjob_solid").get_active,
-            "multicolored": self.builder.get_object("cb_cncjob_multicolored").get_active,
-            "tooldia": lambda: self.get_eval("entry_cncjob_tooldia")
-        }
-
-        CirkuixCNCjob.form_setters = {
-            "plot": self.builder.get_object("cb_cncjob_plot").set_active,
-            "solid": self.builder.get_object("cb_cncjob_solid").set_active,
-            "tooldia": lambda x: self.builder.get_object("entry_cncjob_tooldia").set_text(str(x))
-        }
-
-        CirkuixGeometry.form_getters = {
-            "plot": self.builder.get_object("cb_geometry_plot").get_active,
-            "solid": self.builder.get_object("cb_geometry_solid").get_active,
-            "multicolored": self.builder.get_object("cb_geometry_multicolored").get_active,
-            "cutz": lambda: self.get_eval("entry_geometry_cutz"),
-            "travelz": lambda: self.get_eval("entry_geometry_travelz"),
-            "feedrate": lambda: self.get_eval("entry_geometry_feedrate")
-        }
-
-        CirkuixGeometry.form_setters = {
-            "plot": self.builder.get_object("cb_geometry_plot").set_active,
-            "solid": self.builder.get_object("cb_geometry_solid").set_active,
-            "multicolored": self.builder.get_object("cb_geometry_multicolored").set_active,
-            "cutz": lambda x: self.builder.get_object("entry_geometry_cutz").set_text(str(x)),
-            "travelz": lambda x: self.builder.get_object("entry_geometry_travelz").set_text(str(x)),
-            "feedrate": lambda x: self.builder.get_object("entry_geometry_feedrate").set_text(str(x))
-        }
-
     def setup_component_viewer(self):
         """
         List or Tree where whatever has been loaded or created is
@@ -429,7 +427,6 @@ 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("box_project").pack_start(self.tree, False, False, 1)
         
     def setup_component_editor(self):
@@ -567,6 +564,10 @@ class App:
         self.tree_select.unselect_all()
         self.tree_select.select_iter(iter)
 
+        # Need to return False such that GLib.idle_add
+        # or .timeout_add do not repear.
+        return False
+
     def new_object(self, kind, name, initialize):
         """
         Creates a new specalized CirkuixObj and attaches it to the application,
@@ -595,24 +596,34 @@ class App:
         obj = classdict[kind](name)
 
         # Initialize as per user request
+        # User must take care to implement initialize
+        # in a thread-safe way as is is likely that we
+        # have been invoked in a separate thread.
         initialize(obj, self)
 
         # Add to our records
         self.stuff[name] = obj
 
-        # Update GUI list and select it
+        # Update GUI list and select it (Thread-safe?)
         self.store.append([name])
         #self.build_list()
-        self.set_list_selection(name)
+        GLib.idle_add(lambda: self.set_list_selection(name))
+        # TODO: Gtk.notebook.set_current_page is not known to
+        # TODO: return False. Fix this??
         GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
 
         # Plot
+        # TODO: (Thread-safe?)
         obj.plot(self.figure)
         obj.axes.set_alpha(0.0)
         self.on_zoom_fit(None)
 
         return obj
 
+    def set_progress_bar(self, percentage, text=""):
+        self.progress_bar.set_text(text)
+        self.progress_bar.set_fraction(percentage)
+        return False
 
     ########################################
     ##         EVENT HANDLERS             ##
@@ -637,8 +648,8 @@ class App:
 
         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")
+            margin = app_obj.get_eval("entry_eval_gerber_cutoutmargin")
+            gap_size = app_obj.get_eval("entry_eval_gerber_cutoutgapsize")
             gerber = app_obj.stuff[app_obj.selected_item_name]
             minx, miny, maxx, maxy = gerber.bounds()
             minx -= margin
@@ -690,7 +701,7 @@ class App:
         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")
+            tooldia = app_obj.get_eval("entry_eval_gerber_isotooldia")
             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?
@@ -705,9 +716,9 @@ class App:
         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")
+            z_cut = app_obj.get_eval("entry_eval_geometry_cutz")
+            z_move = app_obj.get_eval("entry_eval_geometry_travelz")
+            feedrate = app_obj.get_eval("entry_eval_geometry_feedrate")
 
             geometry = app_obj.stuff[app_obj.selected_item_name]
             assert isinstance(job_obj, CirkuixCNCjob)
@@ -722,7 +733,7 @@ class App:
 
     def on_cncjob_tooldia_activate(self, widget):
         job = self.stuff[self.selected_item_name]
-        tooldia = self.get_eval("entry_cncjob_tooldia")
+        tooldia = self.get_eval("entry_eval_cncjob_tooldia")
         job.tooldia = tooldia
         print "Changing tool diameter to:", tooldia
 
@@ -753,16 +764,23 @@ class App:
         self.clear_plots()
         
     def on_activate_name(self, entry):
-        '''
+        """
         Hitting 'Enter' after changing the name of an item
         updates the item dictionary and re-builds the item list.
-        '''
-        print "Changing name"
+        """
+
+        # Disconnect event listener
         self.tree.get_selection().disconnect(self.signal_id)
+
         new_name = entry.get_text()  # Get from form
         self.stuff[new_name] = self.stuff.pop(self.selected_item_name)  # Update dictionary
+        self.stuff[new_name].options["name"] = new_name  # update object
+        self.info('Name change: ' + self.selected_item_name + " to " + new_name)
         self.selected_item_name = new_name  # Update selection name
+
         self.build_list()  # Update the items list
+
+        # Reconnect event listener
         self.signal_id = self.tree.get_selection().connect(
                              "changed", self.on_tree_selection_changed)
                              
@@ -833,125 +851,84 @@ class App:
             dialog.destroy()
     
     def on_fileopengerber(self, param):
-        def on_success(self, filename):
-            self.progress_bar.set_text("Opening Gerber ...")
-            self.progress_bar.set_fraction(0.1)
-
-            name = filename.split('/')[-1].split('\\')[-1]
-            gerber = CirkuixGerber(name)
 
-            self.progress_bar.set_text("Parsing ...")
-            self.progress_bar.set_fraction(0.2)
+        # IMPORTANT: on_success will run on a separate thread. Use
+        # GLib.idle_add(function, **kwargs) to launch actions that will
+        # updata the GUI.
+        def on_success(app_obj, filename):
+            assert isinstance(app_obj, App)
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening Gerber ..."))
 
-            gerber.parse_file(filename)
-            self.store.append([name])
-            self.stuff[name] = gerber
+            def obj_init(gerber_obj, app_obj):
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
+                gerber_obj.parse_file(filename)
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
-            self.progress_bar.set_text("Plotting ...")
-            self.progress_bar.set_fraction(0.6)
-
-            #self.plot_gerber(gerber)
-            gerber.plot(self.figure)
-            gerber.axes.set_alpha(0.0)
-            self.on_zoom_fit(None)
-
-            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))
+            name = filename.split('/')[-1].split('\\')[-1]
+            app_obj.new_object("gerber", name, obj_init)
 
-            def clear_bar(bar):
-                bar.set_text("")
-                bar.set_fraction(0.0)
-                return False
+            GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
+            GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
 
-            #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
-            GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
+        # on_success gets run on a separate thread
         self.file_chooser_action(on_success)
     
     def on_fileopenexcellon(self, param):
-        def on_success(self, filename):
-            self.progress_bar.set_text("Opening Excellon ...")
-            self.progress_bar.set_fraction(0.1)
-
-            name = filename.split('/')[-1].split('\\')[-1]
-            excellon = CirkuixExcellon(name)
-
-            self.progress_bar.set_text("Parsing ...")
-            self.progress_bar.set_fraction(0.2)
-
-            excellon.parse_file(filename)
-            self.store.append([name])
-            self.stuff[name] = excellon
 
-            self.progress_bar.set_text("Plotting ...")
-            self.progress_bar.set_fraction(0.6)
+        # IMPORTANT: on_success will run on a separate thread. Use
+        # GLib.idle_add(function, **kwargs) to launch actions that will
+        # updata the GUI.
+        def on_success(app_obj, filename):
+            assert isinstance(app_obj, App)
+            GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening Excellon ..."))
 
-            #self.plot_excellon(excellon)
-            excellon.plot(self.figure)
-            self.on_zoom_fit(None)
-
-            self.progress_bar.set_text("Done!")
-            self.progress_bar.set_fraction(1.0)
+            def obj_init(excellon_obj, app_obj):
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
+                excellon_obj.parse_file(filename)
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
 
-            #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))
+            name = filename.split('/')[-1].split('\\')[-1]
+            app_obj.new_object("excellon", name, obj_init)
 
-            def clear_bar(bar):
-                bar.set_text("")
-                bar.set_fraction(0.0)
-                return False
-            #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
-            GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
+            GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
+            GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
 
+        # on_success gets run on a separate thread
         self.file_chooser_action(on_success)
     
     def on_fileopengcode(self, param):
-        def on_success(self, filename):
-            self.progress_bar.set_text("Opening G-Code ...")
-            self.progress_bar.set_fraction(0.1)
 
-            name = filename.split('/')[-1].split('\\')[-1]
-            f = open(filename)
-            gcode = f.read()
-            f.close()
-            tooldia = self.get_eval("entry_tooldia")
-            job = CirkuixCNCjob(name, tooldia=tooldia)
-            job.gcode = gcode
+        # IMPORTANT: on_success will run on a separate thread. Use
+        # GLib.idle_add(function, **kwargs) to launch actions that will
+        # updata the GUI.
+        def on_success(app_obj, filename):
+            assert isinstance(app_obj, App)
 
-            self.progress_bar.set_text("Parsing ...")
-            self.progress_bar.set_fraction(0.2)
+            def obj_init(job_obj, app_obj):
+                assert isinstance(app_obj, App)
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.1, "Opening G-Code ..."))
 
-            job.gcode_parse()
-            job.create_geometry()
-            self.store.append([name])
-            self.stuff[name] = job
+                f = open(filename)
+                gcode = f.read()
+                f.close()
 
-            self.progress_bar.set_text("Plotting ...")
-            self.progress_bar.set_fraction(0.6)
+                job_obj.gcode = gcode
 
-            #self.plot_cncjob(job)
-            job.plot(self.figure)
-            self.on_zoom_fit(None)
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
+                job_obj.gcode_parse()
+
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating geometry ..."))
+                job_obj.create_geometry()
 
-            self.progress_bar.set_text("Done!")
-            self.progress_bar.set_fraction(1.0)
+                GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
+
+            name = filename.split('/')[-1].split('\\')[-1]
+            app_obj.new_object("cncjob", name, obj_init)
 
-            #self.notebook.set_current_page(0)
-            self.set_list_selection(name)
-            #self.notebook.set_current_page(1)
+            GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
+            GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
 
-            def clear_bar(bar):
-                bar.set_text("")
-                bar.set_fraction(0.0)
-                return False
-            #threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
-            GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
+        # on_success gets run on a separate thread
         self.file_chooser_action(on_success)
         
     def on_mouse_move_over_plot(self, event):

+ 12 - 12
cirkuix.ui

@@ -71,7 +71,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_cncjobname">
+                      <object class="GtkEntry" id="entry_text_cncjob_name">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -174,7 +174,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_cncjob_tooldia">
+                      <object class="GtkEntry" id="entry_eval_cncjob_tooldia">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -292,7 +292,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_excellonname">
+                      <object class="GtkEntry" id="entry_text_excellon_name">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -448,7 +448,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_geometryname">
+                      <object class="GtkEntry" id="entry_text_geometry_name">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -556,7 +556,7 @@
                     <property name="row_spacing">2</property>
                     <property name="column_spacing">4</property>
                     <child>
-                      <object class="GtkEntry" id="entry_geometry_cutz">
+                      <object class="GtkEntry" id="entry_eval_geometry_cutz">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -570,7 +570,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_geometry_travelz">
+                      <object class="GtkEntry" id="entry_eval_geometry_travelz">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -584,7 +584,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_geometry_feedrate">
+                      <object class="GtkEntry" id="entry_eval_geometry_feedrate">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -726,7 +726,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_gerbername">
+                      <object class="GtkEntry" id="entry_text_gerber_name">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -864,7 +864,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_gerberisotooldia">
+                      <object class="GtkEntry" id="entry_eval_gerber_isotooldia">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="margin_top">2</property>
@@ -941,7 +941,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_gerber_cutout_margin">
+                      <object class="GtkEntry" id="entry_eval_gerber_cutoutmargin">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -957,7 +957,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_gerber_cutout_gapsize">
+                      <object class="GtkEntry" id="entry_eval_gerber_cutoutgapsize">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>
@@ -1123,7 +1123,7 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkEntry" id="entry_gerber_noncopper_margin">
+                      <object class="GtkEntry" id="entry_eval_gerber_noncoppermargin">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="invisible_char">●</property>