Просмотр исходного кода

jpcgt/flatcam/Beta слито с Beta

Camellan 6 лет назад
Родитель
Сommit
71d93d73bb
54 измененных файлов с 9636 добавлено и 6940 удалено
  1. 512 149
      FlatCAMApp.py
  2. 93 45
      FlatCAMObj.py
  3. 4 2
      FlatCAMTranslation.py
  4. 2 2
      ObjectCollection.py
  5. 81 1
      README.md
  6. 10 2
      camlib.py
  7. 4 0
      descartes/__init__.py
  8. 66 0
      descartes/patch.py
  9. 111 63
      flatcamEditors/FlatCAMExcEditor.py
  10. 79 40
      flatcamEditors/FlatCAMGeoEditor.py
  11. 137 83
      flatcamEditors/FlatCAMGrbEditor.py
  12. 156 4526
      flatcamGUI/FlatCAMGUI.py
  13. 2 2
      flatcamGUI/PlotCanvas.py
  14. 1008 0
      flatcamGUI/PlotCanvasLegacy.py
  15. 4724 0
      flatcamGUI/PreferencesUI.py
  16. 102 36
      flatcamTools/ToolCutOut.py
  17. 110 47
      flatcamTools/ToolMeasurement.py
  18. 61 23
      flatcamTools/ToolMove.py
  19. 174 153
      flatcamTools/ToolNonCopperClear.py
  20. 217 165
      flatcamTools/ToolPaint.py
  21. 4 4
      flatcamTools/ToolPanelize.py
  22. 2 2
      flatcamTools/ToolSub.py
  23. BIN
      locale/de/LC_MESSAGES/strings.mo
  24. 265 225
      locale/de/LC_MESSAGES/strings.po
  25. BIN
      locale/en/LC_MESSAGES/strings.mo
  26. 259 225
      locale/en/LC_MESSAGES/strings.po
  27. BIN
      locale/es/LC_MESSAGES/strings.mo
  28. 259 225
      locale/es/LC_MESSAGES/strings.po
  29. BIN
      locale/pt_BR/LC_MESSAGES/strings.mo
  30. 239 231
      locale/pt_BR/LC_MESSAGES/strings.po
  31. BIN
      locale/ro/LC_MESSAGES/strings.mo
  32. 259 225
      locale/ro/LC_MESSAGES/strings.po
  33. BIN
      locale/ru/LC_MESSAGES/strings.mo
  34. 238 230
      locale/ru/LC_MESSAGES/strings.po
  35. 232 224
      locale_template/strings.pot
  36. 7 1
      requirements.txt
  37. 3 0
      setup_ubuntu.sh
  38. BIN
      share/active.gif
  39. BIN
      share/active_2.gif
  40. BIN
      share/active_2_static.png
  41. BIN
      share/active_3.gif
  42. BIN
      share/active_3_static.png
  43. BIN
      share/active_4.gif
  44. BIN
      share/active_4_static.png
  45. BIN
      share/active_static.png
  46. BIN
      share/activity2.gif
  47. BIN
      share/flatcam_icon32_green.png
  48. BIN
      share/script14.png
  49. BIN
      share/splash.png
  50. 2 2
      tclCommands/TclCommandPlotAll.py
  51. 8 7
      tclCommands/TclCommandPlotObjects.py
  52. 47 0
      tclCommands/TclCommandQuit.py
  53. 1 0
      tclCommands/__init__.py
  54. 158 0
      tests/titlebar_custom.py

Разница между файлами не показана из-за своего большого размера
+ 512 - 149
FlatCAMApp.py


+ 93 - 45
FlatCAMObj.py

@@ -12,6 +12,7 @@ from datetime import datetime
 
 from flatcamGUI.ObjectUI import *
 from FlatCAMCommon import LoudDict
+from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
 from camlib import *
 import itertools
 
@@ -74,10 +75,17 @@ class FlatCAMObj(QtCore.QObject):
         # store here the default data for Geometry Data
         self.default_data = {}
 
+        # 2D mode
+        # Axes must exist and be attached to canvas.
+        self.axes = None
+
         self.kind = None  # Override with proper name
 
         # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene)
-        self.shapes = self.app.plotcanvas.new_shape_group()
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_group()
+        else:
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app)
 
         # self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)
         self.mark_shapes = {}
@@ -391,11 +399,12 @@ class FlatCAMObj(QtCore.QObject):
         def worker_task(app_obj):
             self.shapes.visible = value
 
-            # Not all object types has annotations
-            try:
-                self.annotation.visible = value
-            except Exception as e:
-                pass
+            if self.app.is_legacy is False:
+                # Not all object types has annotations
+                try:
+                    self.annotation.visible = value
+                except Exception as e:
+                    pass
 
         if threaded is False:
             worker_task(app_obj=self.app)
@@ -627,8 +636,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.ui.create_buffer_button.hide()
 
         # add the shapes storage for marking apertures
-        for ap_code in self.apertures:
-            self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=2)
+        if self.app.is_legacy is False:
+            for ap_code in self.apertures:
+                self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=2)
+        else:
+            for ap_code in self.apertures:
+                self.mark_shapes[ap_code] = ShapeCollectionLegacy(obj=self, app=self.app,
+                                                                  name=self.options['name'] + str(ap_code))
 
         # set initial state of the aperture table and associated widgets
         self.on_aperture_table_visibility_change()
@@ -1342,10 +1356,26 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         except TypeError:
             geometry = [geometry]
 
-        def random_color():
-            color = np.random.rand(4)
-            color[3] = 1
-            return color
+        if self.app.is_legacy is False:
+            def random_color():
+                color = np.random.rand(4)
+                color[3] = 1
+                return color
+        else:
+            def random_color():
+                while True:
+                    color = np.random.rand(4)
+                    color[3] = 1
+
+                    new_color = '#'
+                    for idx in range(len(color)):
+                        new_color += '%x' % int(color[idx] * 255)
+                    # do it until a valid color is generated
+                    # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha
+                    # for a total of 9 chars
+                    if len(new_color) == 9:
+                        break
+                return new_color
 
         try:
             if self.options["solid"]:
@@ -1380,11 +1410,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             self.shapes.redraw()
         except (ObjectDeleted, AttributeError):
             self.shapes.clear(update=True)
+        except Exception as e:
+            log.debug("FlatCAMGerber.plot() --> %s" % str(e))
 
     # experimental plot() when the solid_geometry is stored in the self.apertures
-    def plot_aperture(self, **kwargs):
+    def plot_aperture(self, run_thread=True, **kwargs):
         """
 
+        :param run_thread: if True run the aperture plot as a thread in a worker
         :param kwargs: color and face_color
         :return:
         """
@@ -1414,31 +1447,34 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         else:
             visibility = kwargs['visible']
 
-        with self.app.proc_container.new(_("Plotting Apertures")) as proc:
+        with self.app.proc_container.new(_("Plotting Apertures")):
             self.app.progress.emit(30)
 
             def job_thread(app_obj):
-                self.app.progress.emit(30)
                 try:
                     if aperture_to_plot_mark in self.apertures:
                         for elem in self.apertures[aperture_to_plot_mark]['geometry']:
                             if 'solid' in elem:
-                                geo = elem['solid']
-                                if type(geo) == Polygon or type(geo) == LineString:
-                                    self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
-                                                        face_color=color, visible=visibility)
-                                else:
-                                    for el in geo:
-                                        self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
+                                    geo = elem['solid']
+                                    if type(geo) == Polygon or type(geo) == LineString:
+                                        self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color,
                                                             face_color=color, visible=visibility)
+                                    else:
+                                        for el in geo:
+                                            self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color,
+                                                                face_color=color, visible=visibility)
 
                     self.mark_shapes[aperture_to_plot_mark].redraw()
-                    self.app.progress.emit(100)
 
                 except (ObjectDeleted, AttributeError):
                     self.clear_plot_apertures()
+                except Exception as e:
+                    print(str(e))
 
-            self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
+            if run_thread:
+                self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
+            else:
+                job_thread(self)
 
     def clear_plot_apertures(self, aperture='all'):
         """
@@ -1482,8 +1518,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         if self.ui.apertures_table.cellWidget(cw_row, 5).isChecked():
             self.marked_rows.append(True)
             # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
-            self.plot_aperture(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
-            self.mark_shapes[aperture].redraw()
+            self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'FF',
+                               marked_aperture=aperture, visible=True, run_thread=True)
+            # self.mark_shapes[aperture].redraw()
         else:
             self.marked_rows.append(False)
             self.clear_plot_apertures(aperture=aperture)
@@ -1519,7 +1556,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         if mark_all:
             for aperture in self.apertures:
                 # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True)
-                self.plot_aperture(color=self.app.defaults['global_sel_draw_color'],
+                self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'FF',
                                    marked_aperture=aperture, visible=True)
             # HACK: enable/disable the grid for a better look
             self.app.ui.grid_snap_btn.trigger()
@@ -5349,7 +5386,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         return factor
 
-    def plot_element(self, element, color='red', visible=None):
+    def plot_element(self, element, color='#FF0000FF', visible=None):
 
         visible = visible if visible else self.options['plot']
 
@@ -5358,8 +5395,10 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 self.plot_element(sub_el)
 
         except TypeError:  # Element is not iterable...
+            # if self.app.is_legacy is False:
             self.add_shape(shape=element, color=color, visible=visible, layer=0)
 
+
     def plot(self, visible=None, kind=None):
         """
         Plot the object.
@@ -5389,7 +5428,9 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     self.plot_element(self.solid_geometry, visible=visible)
 
             # self.plot_element(self.solid_geometry, visible=self.options['plot'])
+
             self.shapes.redraw()
+
         except (ObjectDeleted, AttributeError):
             self.shapes.clear(update=True)
 
@@ -5551,9 +5592,10 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         # from predecessors.
         self.ser_attrs += ['options', 'kind', 'cnc_tools', 'multitool']
 
-        self.text_col = self.app.plotcanvas.new_text_collection()
-        self.text_col.enabled = True
-        self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
+        if self.app.is_legacy is False:
+            self.text_col = self.app.plotcanvas.new_text_collection()
+            self.text_col.enabled = True
+            self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col)
 
     def build_ui(self):
         self.ui_disconnect()
@@ -5728,8 +5770,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             pass
         self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change)
 
-        # set if to display text annotations
-        self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
+        if self.app.is_legacy is False:
+            # set if to display text annotations
+            self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"])
 
         # Show/Hide Advanced Options
         if self.app.defaults["global_app_level"] == 'b':
@@ -6178,11 +6221,12 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
         visible = visible if visible else self.options['plot']
 
-        if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
-            self.text_col.enabled = True
-        else:
-            self.text_col.enabled = False
-        self.annotation.redraw()
+        if self.app.is_legacy is False:
+            if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
+                self.text_col.enabled = True
+            else:
+                self.text_col.enabled = False
+            self.annotation.redraw()
 
         try:
             if self.multitool is False:  # single tool usage
@@ -6201,16 +6245,20 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             self.shapes.redraw()
         except (ObjectDeleted, AttributeError):
             self.shapes.clear(update=True)
-            self.annotation.clear(update=True)
+            if self.app.is_legacy is False:
+                self.annotation.clear(update=True)
 
     def on_annotation_change(self):
-        if self.ui.annotation_cb.get_value():
-            self.text_col.enabled = True
+        if self.app.is_legacy is False:
+            if self.ui.annotation_cb.get_value():
+                self.text_col.enabled = True
+            else:
+                self.text_col.enabled = False
+            # kind = self.ui.cncplot_method_combo.get_value()
+            # self.plot(kind=kind)
+            self.annotation.redraw()
         else:
-            self.text_col.enabled = False
-        # kind = self.ui.cncplot_method_combo.get_value()
-        # self.plot(kind=kind)
-        self.annotation.redraw()
+            self.inform.emit(_("Not available with the current Graphic Engine Legacy(2D)."))
 
     def convert_units(self, units):
         log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()")

+ 4 - 2
FlatCAMTranslation.py

@@ -16,6 +16,7 @@ from PyQt5.QtCore import QSettings
 from flatcamGUI.GUIElements import log
 import gettext
 
+
 # import builtins
 #
 # if '_' not in builtins.__dict__:
@@ -154,12 +155,13 @@ def apply_language(domain, lang=None):
         return name
 
 
-def restart_program(app):
+def restart_program(app, ask=None):
     """Restarts the current program.
     Note: this function does not return. Any cleanup action (like
     saving data) must be done before calling this function.
     """
-    if app.should_we_save and app.collection.get_list():
+
+    if app.should_we_save and app.collection.get_list() or ask is True:
         msgbox = QtWidgets.QMessageBox()
         msgbox.setText(_("There are files/objects modified in FlatCAM. "
                          "\n"

+ 2 - 2
ObjectCollection.py

@@ -588,8 +588,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         self.app.all_objects_list = self.get_list()
 
         self.endRemoveRows()
-
-        self.app.plotcanvas.redraw()
+        if self.app.is_legacy is False:
+            self.app.plotcanvas.redraw()
 
         if select_project:
             # always go to the Project Tab after object deletion as it may be done with a shortcut key

+ 81 - 1
README.md

@@ -1,4 +1,4 @@
-latCAM: 2D Computer-Aided PCB Manufacturing
+FlatCAM: 2D Computer-Aided PCB Manufacturing
 =================================================
 
 (c) 2014-2019 Juan Pablo Caram
@@ -9,6 +9,86 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+22.09.2019
+
+- fixed zoom directions legacy graphic engine (previous commit)
+- fixed display of MultiGeo geometries in legacy graphic engine
+- fixed Paint tool to work in legacy graphic engine
+- fixed CutOut Tool to work in legacy graphic engine
+- fixed display of distance labels and code optimizations in ToolPaint and NCC Tool
+- adjusted axis at startup for legacy graphic engine plotcanvas
+- when the graphic engine is changed in Edit -> Preferences -> General -> App Preferences, the application will restart
+- made hover shapes work in legacy graphic engine
+- fixed bug in display of the apertures marked in the Aperture table found in the Gerber Selected tab and through this made it to also work with the legacy graphic engine
+- fixed annotation in Mark Area Tool in Gerber Editor to work in legacy graphic engine
+- fixed the MultiColor plot option Gerber selected tab to work in legacy graphic engine
+- documented some methods in the ShapeCollectionLegacy class
+- updated the files: setup_ubuntu.sh and requirements.txt
+- some strings changed to be easier for translation
+- updated the .POT file and the translation files
+- updated and corrected the Romanian and Spanish translations
+- updated the .PO files for the rest of the translations, they need to be filled in.
+
+21.09.2019
+
+- fixed Measuring Tool in legacy graphic engine
+- fixed Gerber plotting in legacy graphic engine
+- fixed Geometry plotting in legacy graphic engine
+- fixed CNCJob and Excellon plotting in legacy graphic engine
+- in legacy graphic engine fixed the travel vs cut lines in CNCJob objects
+- final fix for key shortcuts with modifier in legacy graphic engine
+- refactored some of the code in the legacy graphic engine
+- fixed drawing of selection box when dragging mouse on screen and the selection shape drawing on the selected objects
+- fixed the moving drawing shape in Tool Move in legacy graphic engine
+- fixed moving geometry in Tool Measurement in legacy graphic engine
+- fixed Geometry Editor to work in legacy graphic engine
+- fixed Excellon Editor to work in legacy graphic engine
+- fixed Gerber Editor to work in legacy graphic engine
+- fixed NCC tool to work in legacy graphic engine
+
+20.09.2019
+
+- final fix for the --shellvar having spaces within the assigned value; now they are retained
+- legacy graphic engine - made the mouse events work (click, release, doubleclick, dragging)
+- legacy graphic engine - made the key events work (simple or with modifiers)
+- legacy graphic engine - made the mouse cursor work (enabled/disabled, position report); snapping is not moving the cursor yet
+- made the mouse cursor snap to the grid when grid snapping is active
+- changed the axis color to the one used in the OpenGL graphic engine
+- work on ShapeCollectionLegacy
+- fixed mouse cursor to work for all objects
+- fixed event signals to work in both graphic engines: 2D and 3D
+
+19.09.2019
+
+- made sure that if FlatCAM is registered with a file extension that it does not recognize it will exit
+- added some fixes in the the file extension detection
+- added some status messages for the Tcl script related methods
+- made sure that optionally, when a script is run then it is also loaded into the code editor
+- added control over the display of Sys Tray Icon in Edit -> Preferences -> General -> GUI Settings -> Sys Tray Icon checkbox
+- updated some of the default values to more reasonable ones
+- FlatCAM can be run in HEADLESS mode now. This mode can be selected by using the --headless=1 command line argument or by changing the line headless=False to True in config/configuration.txt file. In this mod the Sys Tray Icon menu will hold only the Run Scrip menu entry and Exit entry.
+- added a new TclCommand named quit_flatcam which will ... quit FlatCAM from Tcl Shell or from a script
+- fixed the command line argument --shellvar to work when there are spaces in the argument value
+- fixed bug in Gerber editor that did not allow to display all shapes after it encountered one shape without 'solid' geometry
+- fixed bug in Gerber Editor -> selection area handler where if some of the selected shapes did not had the 'solid' geometry will silently abort selection of further shapes
+- added new control in Edit -> Preferences -> General -> Gui Preferences -> Activity Icon. Will select a GIF from a selection, the one used to show that FlatCAM is working.
+- changed the script icon to a smaller one in the sys tray menu
+- fixed bug with losing the visibility of toolbars if at first startup the user tries to change something in the Preferences before doing a first save of Preferences
+- changed a bit the splash PNG file
+- moved all the GUI Preferences classes into it's own file flatcamGUI.PreferencesUI.py
+- changed the default method for Paint Tool to 'all'
+
+18.09.2019
+
+- added more functionality to the Extension registration with FLatCAM and added to the GUI in Edit -> Preferences -> Utilities
+- fixed the parsing of the Manufacturing files when double clicking them and they are registered with FlatCAM
+- fixed showing the GUI when some settings (maximized_GUI) are missing from QSettings
+- added sys tray menu
+- added possibility to edit the custom keywords used by the autocompleter (in Tcl Shell and in the Code Editor). It is done in the Edit -> Preferences -> Utilities
+- added a new setting in Edit -> Preferences -> General -> GUI Settings -> Textbox Font which control the font on the Textbox GUI elements
+- fixed issue with the sys tray icon not hiding after application close
+- added option to run a script from the context menu of the sys tray icon. Changed the color of the sys tray icon to a green one so it will be visible on light and dark themes
+
 17.09.2019
 
 - added more programmers that contributed to FlatCAM over the years, in the "About FlatCAM" -> Programmers window

+ 10 - 2
camlib.py

@@ -36,6 +36,10 @@ from shapely.wkt import dumps as sdumps
 from shapely.geometry.base import BaseGeometry
 from shapely.geometry import shape
 
+# needed for legacy mode
+# Used for solid polygons in Matplotlib
+from descartes.patch import PolygonPatch
+
 import collections
 from collections import Iterable
 
@@ -117,7 +121,11 @@ class Geometry(object):
         self.old_disp_number = 0
         self.el_count = 0
 
-        self.temp_shapes = self.app.plotcanvas.new_shape_group()
+        if self.app.is_legacy is False:
+            self.temp_shapes = self.app.plotcanvas.new_shape_group()
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.temp_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='camlib.geometry')
 
         # if geo_steps_per_circle is None:
         #     geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"])
@@ -3430,7 +3438,7 @@ class Gerber (Geometry):
                 return 'fail'
 
             log.warning("Joining %d polygons." % len(poly_buffer))
-            self.app.inform.emit('%s %d %s.' % (_("Gerber processing. Joining"), len(poly_buffer), _("polygons")))
+            self.app.inform.emit('%s: %d.' % (_("Gerber processing. Joining polygons"), len(poly_buffer)))
 
             if self.use_buffer_for_union:
                 log.debug("Union by buffer...")

+ 4 - 0
descartes/__init__.py

@@ -0,0 +1,4 @@
+"""Turn geometric objects into matplotlib patches"""
+
+from descartes.patch import PolygonPatch
+

+ 66 - 0
descartes/patch.py

@@ -0,0 +1,66 @@
+"""Paths and patches"""
+
+from matplotlib.patches import PathPatch
+from matplotlib.path import Path
+from numpy import asarray, concatenate, ones
+
+
+class Polygon(object):
+    # Adapt Shapely or GeoJSON/geo_interface polygons to a common interface
+    def __init__(self, context):
+        if hasattr(context, 'interiors'):
+            self.context = context
+        else:
+            self.context = getattr(context, '__geo_interface__', context)
+    @property
+    def geom_type(self):
+        return (getattr(self.context, 'geom_type', None)
+                or self.context['type'])
+    @property
+    def exterior(self):
+        return (getattr(self.context, 'exterior', None) 
+                or self.context['coordinates'][0])
+    @property
+    def interiors(self):
+        value = getattr(self.context, 'interiors', None)
+        if value is None:
+            value = self.context['coordinates'][1:]
+        return value
+
+
+def PolygonPath(polygon):
+    """Constructs a compound matplotlib path from a Shapely or GeoJSON-like
+    geometric object"""
+    this = Polygon(polygon)
+    assert this.geom_type == 'Polygon'
+    def coding(ob):
+        # The codes will be all "LINETO" commands, except for "MOVETO"s at the
+        # beginning of each subpath
+        n = len(getattr(ob, 'coords', None) or ob)
+        vals = ones(n, dtype=Path.code_type) * Path.LINETO
+        vals[0] = Path.MOVETO
+        return vals
+    vertices = concatenate(
+                    [asarray(this.exterior)] 
+                    + [asarray(r) for r in this.interiors])
+    codes = concatenate(
+                [coding(this.exterior)] 
+                + [coding(r) for r in this.interiors])
+    return Path(vertices, codes)
+
+
+def PolygonPatch(polygon, **kwargs):
+    """Constructs a matplotlib patch from a geometric object
+    
+    The `polygon` may be a Shapely or GeoJSON-like object with or without holes.
+    The `kwargs` are those supported by the matplotlib.patches.Polygon class
+    constructor. Returns an instance of matplotlib.patches.PathPatch.
+
+    Example (using Shapely Point and a matplotlib axes):
+
+      >>> b = Point(0, 0).buffer(1.0)
+      >>> patch = PolygonPatch(b, fc='blue', ec='blue', alpha=0.5)
+      >>> axis.add_patch(patch)
+
+    """
+    return PathPatch(PolygonPath(polygon), **kwargs)

+ 111 - 63
flatcamEditors/FlatCAMExcEditor.py

@@ -2012,8 +2012,14 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.exc_obj = None
 
         # VisPy Visuals
-        self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
-        self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
+            self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_exc_editor')
+            self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_exc_editor')
+
         self.app.pool_recreated.connect(self.pool_recreated)
 
         # Remove from scene
@@ -2082,6 +2088,11 @@ class FlatCAMExcEditor(QtCore.QObject):
         def entry2option(option, entry):
             self.options[option] = float(entry.text())
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
         log.debug("Initialization of the FlatCAM Excellon Editor is finished ...")
@@ -2445,8 +2456,10 @@ class FlatCAMExcEditor(QtCore.QObject):
                 row_to_be_selected = int(key) - 1
                 self.last_tool_selected = int(key)
                 break
-
-        self.tools_table_exc.selectRow(row_to_be_selected)
+        try:
+            self.tools_table_exc.selectRow(row_to_be_selected)
+        except TypeError as e:
+            log.debug("FlatCAMExcEditor.on_tool_add() --> %s" % str(e))
 
     def on_tool_delete(self, dia=None):
         self.is_modified = True
@@ -2791,16 +2804,23 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_connect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_connect('mouse_release', self.on_exc_click_release)
+        self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_exc_click_release)
 
         # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
         # but those from FlatCAMGeoEditor
-        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        if self.app.is_legacy is False:
+            self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        else:
+            self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mdc)
+
         self.app.collection.view.clicked.disconnect()
 
         self.app.ui.popmenu_copy.triggered.disconnect()
@@ -2819,15 +2839,22 @@ class FlatCAMExcEditor(QtCore.QObject):
         # we restore the key and mouse control to FlatCAMApp method
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
+        self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+        self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
+        self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                              self.app.on_mouse_click_release_over_plot)
+        self.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click',
+                                                               self.app.on_double_click_over_plot)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
-        self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_disconnect('mouse_release', self.on_exc_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
+            self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_exc_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mp)
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         try:
             self.app.ui.popmenu_copy.triggered.disconnect(self.exc_copy_drills)
@@ -2903,6 +2930,11 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.select_tool("drill_select")
 
+        # reset the tool table
+        self.tools_table_exc.clear()
+        self.tools_table_exc.setHorizontalHeaderLabels(['#', _('Diameter'), 'D', 'S'])
+        self.last_tool_selected = None
+
         self.set_ui()
 
         # now that we hava data, create the GUI interface and add it to the Tool Tab
@@ -3053,6 +3085,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         # element[1] of the tuple is a list of coordinates (a tuple themselves)
         ordered_edited_points = sorted(zip(edited_points.keys(), edited_points.values()))
 
+
         current_tool = 0
         for tool_dia in ordered_edited_points:
             current_tool += 1
@@ -3121,26 +3154,13 @@ class FlatCAMExcEditor(QtCore.QObject):
                     self.edited_obj_name += "_1"
             else:
                 self.edited_obj_name += "_edit"
-
-        self.app.worker_task.emit({'fcn': self.new_edited_excellon,
-                                   'params': [self.edited_obj_name]})
-
         self.new_tool_offset = self.exc_obj.tool_offset
 
-        # reset the tool table
-        self.tools_table_exc.clear()
-        self.tools_table_exc.setHorizontalHeaderLabels(['#', _('Diameter'), 'D', 'S'])
-        self.last_tool_selected = None
-
-        # delete the edited Excellon object which will be replaced by a new one having the edited content of the first
-        # self.app.collection.set_active(self.exc_obj.options['name'])
-        # self.app.collection.delete_active()
-
-        # restore GUI to the Selected TAB
-        # Remove anything else in the GUI
-        self.app.ui.tool_scroll_area.takeWidget()
-        # Switch notebook to Selected page
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+        self.app.worker_task.emit({'fcn': self.new_edited_excellon,
+                                   'params': [self.edited_obj_name,
+                                              self.new_drills,
+                                              self.new_slots,
+                                              self.new_tools]})
 
     def update_options(self, obj):
         try:
@@ -3157,7 +3177,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             obj.options = {}
             return True
 
-    def new_edited_excellon(self, outname):
+    def new_edited_excellon(self, outname, n_drills, n_slots, n_tools):
         """
         Creates a new Excellon object for the edited Excellon. Thread-safe.
 
@@ -3170,12 +3190,17 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.app.log.debug("Update the Excellon object with edited content. Source is %s" %
                            self.exc_obj.options['name'])
 
+        new_drills = n_drills
+        new_slots = n_slots
+        new_tools = n_tools
+
         # How the object should be initialized
         def obj_init(excellon_obj, app_obj):
+
             # self.progress.emit(20)
-            excellon_obj.drills = self.new_drills
-            excellon_obj.tools = self.new_tools
-            excellon_obj.slots = self.new_slots
+            excellon_obj.drills = deepcopy(new_drills)
+            excellon_obj.tools = deepcopy(new_tools)
+            excellon_obj.slots = deepcopy(new_slots)
             excellon_obj.tool_offset = self.new_tool_offset
             excellon_obj.options['name'] = outname
 
@@ -3192,15 +3217,17 @@ class FlatCAMExcEditor(QtCore.QObject):
                 app_obj.inform.emit(msg)
                 raise
                 # raise
-            excellon_obj.source_file = self.app.export_excellon(obj_name=outname, filename=None,
-                                                                local_use=excellon_obj, use_thread=False)
 
         with self.app.proc_container.new(_("Creating Excellon.")):
 
             try:
-                self.app.new_object("excellon", outname, obj_init)
+                edited_obj = self.app.new_object("excellon", outname, obj_init)
+                edited_obj.source_file = self.app.export_excellon(obj_name=edited_obj.options['name'],
+                                                                  local_use=edited_obj,
+                                                                  filename=None,
+                                                                  use_thread=False)
             except Exception as e:
-                log.error("Error on object creation: %s" % str(e))
+                log.error("Error on Edited object creation: %s" % str(e))
                 self.app.progress.emit(100)
                 return
 
@@ -3282,25 +3309,28 @@ class FlatCAMExcEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy
         :return: None
         """
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
 
-        self.pos = self.canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             self.pos  = self.app.geo_editor.snap(self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
-                                         size=20)
         else:
             self.pos = (self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = False
 
-        if event.button is 1:
+        if event.button == 1:
             self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
             # Selection with left mouse button
-            if self.active_tool is not None and event.button is 1:
+            if self.active_tool is not None and event.button == 1:
                 # Dispatch event to active_tool
                 # msg = self.active_tool.click(self.app.geo_editor.snap(event.xdata, event.ydata))
                 self.active_tool.click(self.app.geo_editor.snap(self.pos[0], self.pos[1]))
@@ -3318,6 +3348,7 @@ class FlatCAMExcEditor(QtCore.QObject):
                         modifier_to_use = Qt.ControlModifier
                     else:
                         modifier_to_use = Qt.ShiftModifier
+
                     # if modifier key is pressed then we add to the selected list the current shape but if it's already
                     # in the selected list, we removed it. Therefore first click selects, second deselects.
                     if key_modifier == modifier_to_use:
@@ -3422,7 +3453,17 @@ class FlatCAMExcEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
-        pos_canvas = self.canvas.translate_coords(event.pos)
+
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        pos_canvas = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -3432,7 +3473,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         # if the released mouse button was RMB then test if it was a panning motion or not, if not it was a context
         # canvas menu
         try:
-            if event.button == 2:  # right click
+            if event.button == right_button:  # right click
                 if self.app.ui.popMenu.mouse_is_panning is False:
                     try:
                         QtGui.QGuiApplication.restoreOverrideCursor()
@@ -3580,7 +3621,16 @@ class FlatCAMExcEditor(QtCore.QObject):
         :return: None
         """
 
-        pos = self.canvas.translate_coords(event.pos)
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        pos = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
         self.x = event.xdata
@@ -3589,7 +3639,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.app.ui.popMenu.mouse_is_panning = False
 
         # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-        if event.button == 2 and event.is_dragging == 1:
+        if event.button == right_button and event_is_dragging == 1:
             self.app.ui.popMenu.mouse_is_panning = True
             return
 
@@ -3605,11 +3655,9 @@ class FlatCAMExcEditor(QtCore.QObject):
         # ## Snap coordinates
         if self.app.grid_status() == True:
             x, y = self.app.geo_editor.snap(x, y)
-            self.app.app_cursor.enabled = True
+
             # Update cursor
             self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
-        else:
-            self.app.app_cursor.enabled = False
 
         self.snap_x = x
         self.snap_y = y
@@ -3636,7 +3684,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             self.draw_utility_geometry(geo=geo)
 
         # ## Selection area on canvas section # ##
-        if event.is_dragging == 1 and event.button == 1:
+        if event_is_dragging == 1 and event.button == 1:
             # I make an exception for FCDrillAdd and FCDrillArray because clicking and dragging while making regions
             # can create strange issues. Also for FCSlot and FCSlotArray
             if isinstance(self.active_tool, FCDrillAdd) or isinstance(self.active_tool, FCDrillArray) or \
@@ -3718,10 +3766,10 @@ class FlatCAMExcEditor(QtCore.QObject):
                     continue
 
                 if shape_plus in self.selected:
-                    self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_sel_draw_color'],
+                    self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_sel_draw_color'] + 'FF',
                                     linewidth=2)
                     continue
-                self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_draw_color'])
+                self.plot_shape(geometry=shape_plus.geo, color=self.app.defaults['global_draw_color'] + 'FF')
 
         # for shape in self.storage.get_objects():
         #     if shape.geo is None:  # TODO: This shouldn't have happened
@@ -3739,7 +3787,7 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.shapes.redraw()
 
-    def plot_shape(self, geometry=None, color='black', linewidth=1):
+    def plot_shape(self, geometry=None, color='0x000000FF', linewidth=1):
         """
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.

+ 79 - 40
flatcamEditors/FlatCAMGeoEditor.py

@@ -3025,8 +3025,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # VisPy visuals
         self.fcgeometry = None
-        self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
-        self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        if self.app.is_legacy is False:
+            self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
+            self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_geo_editor')
+            self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_geo_editor')
+
         self.app.pool_recreated.connect(self.pool_recreated)
 
         # Remove from scene
@@ -3163,6 +3169,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         self.transform_complete.connect(self.on_transform_complete)
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # store the status of the editor so the Delete at object level will not work until the edit is finished
         self.editor_active = False
         log.debug("Initialization of the FlatCAM Geometry Editor is finished ...")
@@ -3271,7 +3282,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # Disable visuals
         self.shapes.enabled = False
         self.tool_shape.enabled = False
-        self.app.app_cursor.enabled = False
 
         self.app.ui.geo_editor_menu.setDisabled(True)
         self.app.ui.geo_editor_menu.menuAction().setVisible(False)
@@ -3309,16 +3319,23 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_connect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_connect('mouse_release', self.on_geo_click_release)
+        self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_geo_click_release)
+
+        if self.app.is_legacy is False:
+            # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
+            # but those from FlatCAMGeoEditor
+            self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        else:
 
-        # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
-        # but those from FlatCAMGeoEditor
-        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mdc)
 
         # self.app.collection.view.clicked.disconnect()
         self.app.ui.popmenu_copy.triggered.disconnect()
@@ -3354,15 +3371,22 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # we restore the key and mouse control to FlatCAMApp method
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
+        self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+        self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
+        self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                              self.app.on_mouse_click_release_over_plot)
+        self.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click',
+                                                               self.app.on_double_click_over_plot)
         # self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
-        self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_disconnect('mouse_release', self.on_geo_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
+            self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_geo_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mp)
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         try:
             self.app.ui.popmenu_copy.triggered.disconnect(lambda: self.select_tool('copy'))
@@ -3625,18 +3649,17 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :param event: Event object dispatched by Matplotlib
         :return: None
         """
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
 
-        self.pos = self.canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
-                                         size=20)
         else:
             self.pos = (self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = False
 
         if event.button == 1:
             self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
@@ -3649,8 +3672,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
                     self.app.defaults["global_point_clipboard_format"] % (self.pos[0], self.pos[1]))
                 return
 
+
             # Selection with left mouse button
-            if self.active_tool is not None and event.button is 1:
+            if self.active_tool is not None and event.button == 1:
 
                 # Dispatch event to active_tool
                 self.active_tool.click(self.snap(self.pos[0], self.pos[1]))
@@ -3678,7 +3702,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
-        pos = self.canvas.translate_coords(event.pos)
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        pos = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
         self.x = event.xdata
@@ -3687,8 +3720,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.popMenu.mouse_is_panning = False
 
         # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-        if event.button == 2:
-            if event.is_dragging:
+        if event.button == right_button:
+            if event_is_dragging:
                 self.app.ui.popMenu.mouse_is_panning = True
                 # return
             else:
@@ -3706,11 +3739,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # ### Snap coordinates ###
         if self.app.grid_status() == True:
             x, y = self.snap(x, y)
-            self.app.app_cursor.enabled = True
+
             # Update cursor
             self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
-        else:
-            self.app.app_cursor.enabled = False
 
         self.snap_x = x
         self.snap_y = y
@@ -3728,7 +3759,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                            "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
 
-        if event.button == 1 and event.is_dragging and isinstance(self.active_tool, FCEraser):
+        if event.button == 1 and event_is_dragging and isinstance(self.active_tool, FCEraser):
             pass
         else:
             # ### Utility geometry (animated) ###
@@ -3740,7 +3771,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
         # ### Selection area on canvas section ###
         dx = pos[0] - self.pos[0]
-        if event.is_dragging and event.button == 1:
+        if event_is_dragging and event.button == 1:
             self.app.delete_selection_shape()
             if dx < 0:
                 self.app.draw_moving_selection_shape((self.pos[0], self.pos[1]), (x, y),
@@ -3754,7 +3785,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.app.selection_type = None
 
     def on_geo_click_release(self, event):
-        pos_canvas = self.canvas.translate_coords(event.pos)
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        pos_canvas = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             pos = self.snap(pos_canvas[0], pos_canvas[1])
@@ -3776,7 +3816,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
                     self.active_tool.click_release((self.pos[0], self.pos[1]))
                     # self.app.inform.emit(msg)
                     self.replot()
-            elif event.button == 2:  # right click
+            elif event.button == right_button:  # right click
                 if self.app.ui.popMenu.mouse_is_panning == False:
                     if self.in_action is False:
                         try:
@@ -3943,7 +3983,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         # return [shape for shape in self.shape_buffer if shape["selected"]]
         return self.selected
 
-    def plot_shape(self, geometry=None, color='black', linewidth=1):
+    def plot_shape(self, geometry=None, color='#000000FF', linewidth=1):
         """
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
@@ -3961,7 +4001,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
         try:
             for geo in geometry:
                 plot_elements += self.plot_shape(geometry=geo, color=color, linewidth=linewidth)
-
         # Non-iterable
         except TypeError:
 
@@ -3999,10 +4038,10 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 continue
 
             if shape in self.selected:
-                self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'], linewidth=2)
+                self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_sel_draw_color'] + 'FF', linewidth=2)
                 continue
 
-            self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'])
+            self.plot_shape(geometry=shape.geo, color=self.app.defaults['global_draw_color'] + "FF")
 
         for shape in self.utility:
             self.plot_shape(geometry=shape.geo, linewidth=1)

+ 137 - 83
flatcamEditors/FlatCAMGrbEditor.py

@@ -2607,8 +2607,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.ma_lower_threshold_entry = FCEntry()
         self.ma_lower_threshold_entry.setValidator(QtGui.QDoubleValidator(0.0000, 9999.9999, 4))
 
-        ma_form_layout.addRow(self.ma_upper_threshold_lbl, self.ma_upper_threshold_entry)
         ma_form_layout.addRow(self.ma_lower_threshold_lbl, self.ma_lower_threshold_entry)
+        ma_form_layout.addRow(self.ma_upper_threshold_lbl, self.ma_upper_threshold_entry)
 
         # Buttons
         hlay_ma = QtWidgets.QHBoxLayout()
@@ -2821,12 +2821,27 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.gerber_obj_options = dict()
 
         # VisPy Visuals
-        self.shapes = self.canvas.new_shape_collection(layers=1)
-        self.tool_shape = self.canvas.new_shape_collection(layers=1)
-        self.ma_annotation = self.canvas.new_text_group()
+        if self.app.is_legacy is False:
+            self.shapes = self.canvas.new_shape_collection(layers=1)
+            self.tool_shape = self.canvas.new_shape_collection(layers=1)
+            self.ma_annotation = self.canvas.new_text_group()
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_grb_editor')
+            self.tool_shape = ShapeCollectionLegacy(obj=self, app=self.app, name='tool_shapes_grb_editor')
+            self.ma_annotation = ShapeCollectionLegacy(
+                obj=self,
+                app=self.app,
+                name='ma_anno_grb_editor',
+                annotation_job=True)
 
         self.app.pool_recreated.connect(self.pool_recreated)
 
+        # Event signals disconnect id holders
+        self.mp = None
+        self.mm = None
+        self.mr = None
+
         # Remove from scene
         self.shapes.enabled = False
         self.tool_shape.enabled = False
@@ -2972,8 +2987,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.buffer_distance_entry.set_value(self.app.defaults["gerber_editor_buff_f"])
         self.scale_factor_entry.set_value(self.app.defaults["gerber_editor_scale_f"])
-        self.ma_upper_threshold_entry.set_value(self.app.defaults["gerber_editor_ma_low"])
-        self.ma_lower_threshold_entry.set_value(self.app.defaults["gerber_editor_ma_high"])
+        self.ma_upper_threshold_entry.set_value(self.app.defaults["gerber_editor_ma_high"])
+        self.ma_lower_threshold_entry.set_value(self.app.defaults["gerber_editor_ma_low"])
 
         self.apsize_entry.set_value(self.app.defaults["gerber_editor_newsize"])
         self.aptype_cb.set_value(self.app.defaults["gerber_editor_newtype"])
@@ -3511,14 +3526,21 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_connect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_connect('mouse_release', self.on_grb_click_release)
-
-        self.canvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.canvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.canvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.canvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        self.mp = self.canvas.graph_event_connect('mouse_press', self.on_canvas_click)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_canvas_move)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_grb_click_release)
+
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.canvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+            self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.canvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
+        else:
+            self.canvas.graph_event_disconnect(self.app.mp)
+            self.canvas.graph_event_disconnect(self.app.mm)
+            self.canvas.graph_event_disconnect(self.app.mr)
+            self.canvas.graph_event_disconnect(self.app.mdc)
+
         self.app.collection.view.clicked.disconnect()
 
         self.app.ui.popmenu_copy.triggered.disconnect()
@@ -3550,15 +3572,20 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # we restore the key and mouse control to FlatCAMApp method
         # first connect to new, then disconnect the old handlers
         # don't ask why but if there is nothing connected I've seen issues
-        self.canvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.canvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.canvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
+        self.app.mp = self.canvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+        self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
+        self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+        self.app.mdc = self.canvas.graph_event_connect('mouse_double_click', self.app.on_double_click_over_plot)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
-        self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
-        self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
-        self.canvas.vis_disconnect('mouse_release', self.on_grb_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_press', self.on_canvas_click)
+            self.canvas.graph_event_disconnect('mouse_move', self.on_canvas_move)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_grb_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mp)
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         try:
             self.app.ui.popmenu_copy.triggered.disconnect(self.on_copy_button)
@@ -3654,6 +3681,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.deactivate_grb_editor()
         self.activate_grb_editor()
 
+        # reset the tool table
+        self.apertures_table.clear()
+
+        self.apertures_table.setHorizontalHeaderLabels(['#', _('Code'), _('Type'), _('Size'), _('Dim')])
+        self.last_aperture_selected = None
+
         # create a reference to the source object
         self.gerber_obj = orig_grb_obj
         self.gerber_obj_options = orig_grb_obj.options
@@ -3702,6 +3735,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # list of clear geos that are to be applied to the entire file
         global_clear_geo = []
 
+        # create one big geometry made out of all 'negative' (clear) polygons
         for apid in self.gerber_obj.apertures:
             # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo
             if 'geometry' in self.gerber_obj.apertures[apid]:
@@ -3710,8 +3744,19 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         global_clear_geo.append(elem['clear'])
         log.warning("Found %d clear polygons." % len(global_clear_geo))
 
+        global_clear_geo = MultiPolygon(global_clear_geo)
+        if isinstance(global_clear_geo, Polygon):
+            global_clear_geo = list(global_clear_geo)
+
+        # for debugging
+        # for geo in global_clear_geo:
+        #     self.shapes.add(shape=geo, color='black', face_color='#000000'+'AF', layer=0, tolerance=self.tolerance)
+        # self.shapes.redraw()
+
+        # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of clear geometry
+        # that fits inside the solid. otherwise we may loose the solid
         for apid in self.gerber_obj.apertures:
-            temp_elem = []
+            temp_solid_geometry= []
             if 'geometry' in self.gerber_obj.apertures[apid]:
                 # for elem in self.gerber_obj.apertures[apid]['geometry']:
                 #     if 'solid' in elem:
@@ -3742,9 +3787,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 #             temp_elem.append(deepcopy(new_elem))
                 for elem in self.gerber_obj.apertures[apid]['geometry']:
                     new_elem = dict()
-
                     if 'solid' in elem:
                         solid_geo = elem['solid']
+
                         for clear_geo in global_clear_geo:
                             # Make sure that the clear_geo is within the solid_geo otherwise we loose
                             # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to
@@ -3757,15 +3802,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         new_elem['clear'] = elem['clear']
                     if 'follow' in elem:
                         new_elem['follow'] = elem['follow']
-                    temp_elem.append(deepcopy(new_elem))
+                    temp_solid_geometry.append(deepcopy(new_elem))
 
-            self.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_elem)
+                self.gerber_obj.apertures[apid]['geometry'] = deepcopy(temp_solid_geometry)
         log.warning("Polygon difference done for %d apertures." % len(self.gerber_obj.apertures))
 
         # and then add it to the storage elements (each storage elements is a member of a list
         def job_thread(aperture_id):
-            with self.app.proc_container.new('%s: %s %s...' %
-                                             (_("Adding aperture"),  str(aperture_id), _("geo"))):
+            with self.app.proc_container.new('%s: %s ...' %
+                                             (_("Adding geometry for aperture"),  str(aperture_id))):
                 storage_elem = []
                 self.storage_dict[aperture_id] = {}
 
@@ -3781,6 +3826,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                             self.storage_dict[aperture_id][k] = self.gerber_obj.apertures[aperture_id][k]
                     except Exception as e:
                         log.debug("FlatCAMGrbEditor.edit_fcgerber().job_thread() --> %s" % str(e))
+
                 # Check promises and clear if exists
                 while True:
                     try:
@@ -3789,6 +3835,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     except ValueError:
                         break
 
+        # we create a job work each aperture, job that work in a threaded way to store the geometry in local storage
+        # as DrawToolShapes
         for ap_id in self.gerber_obj.apertures:
             self.grb_plot_promises.append(ap_id)
             self.app.worker_task.emit({'fcn': job_thread, 'params': [ap_id]})
@@ -3831,19 +3879,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             new_grb_name = self.edited_obj_name + "_edit"
 
         self.app.worker_task.emit({'fcn': self.new_edited_gerber,
-                                   'params': [new_grb_name]})
-
-        # reset the tool table
-        self.apertures_table.clear()
-
-        self.apertures_table.setHorizontalHeaderLabels(['#', _('Code'), _('Type'), _('Size'), _('Dim')])
-        self.last_aperture_selected = None
-
-        # restore GUI to the Selected TAB
-        # Remove anything else in the GUI
-        self.app.ui.selected_scroll_area.takeWidget()
-        # Switch notebook to Selected page
-        self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+                                   'params': [new_grb_name, self.storage_dict]})
 
     @staticmethod
     def update_options(obj):
@@ -3861,12 +3897,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
             obj.options = dict()
             return True
 
-    def new_edited_gerber(self, outname):
+    def new_edited_gerber(self, outname, aperture_storage):
         """
         Creates a new Gerber object for the edited Gerber. Thread-safe.
 
         :param outname: Name of the resulting object. None causes the name to be that of the file.
         :type outname: str
+        :param aperture_storage: a dictionary that holds all the objects geometry
         :return: None
         """
 
@@ -3874,13 +3911,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
                            self.gerber_obj.options['name'].upper())
 
         out_name = outname
+        storage_dict = aperture_storage
 
         local_storage_dict = dict()
-        for aperture in self.storage_dict:
-            if 'geometry' in self.storage_dict[aperture]:
+        for aperture in storage_dict:
+            if 'geometry' in storage_dict[aperture]:
                 # add aperture only if it has geometry
-                if len(self.storage_dict[aperture]['geometry']) > 0:
-                    local_storage_dict[aperture] = deepcopy(self.storage_dict[aperture])
+                if len(storage_dict[aperture]['geometry']) > 0:
+                    local_storage_dict[aperture] = deepcopy(storage_dict[aperture])
 
         # How the object should be initialized
         def obj_init(grb_obj, app_obj):
@@ -3969,7 +4007,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             try:
                 self.app.new_object("gerber", outname, obj_init)
             except Exception as e:
-                log.error("Error on object creation: %s" % str(e))
+                log.error("Error on Edited object creation: %s" % str(e))
                 self.app.progress.emit(100)
                 return
 
@@ -4116,20 +4154,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy
         :return: None
         """
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
 
-        self.pos = self.canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event_pos)
 
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = True
-            # Update cursor
-            self.app.app_cursor.set_data(np.asarray([(self.pos[0], self.pos[1])]), symbol='++', edge_color='black',
-                                         size=20)
         else:
             self.pos = (self.pos[0], self.pos[1])
-            self.app.app_cursor.enabled = False
 
-        if event.button is 1:
+        if event.button == 1:
             self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
                                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
@@ -4180,8 +4221,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
     def on_grb_click_release(self, event):
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
 
-        pos_canvas = self.canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event_pos)
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
@@ -4190,7 +4239,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # if the released mouse button was RMB then test if it was a panning motion or not, if not it was a context
         # canvas menu
         try:
-            if event.button == 2:  # right click
+            if event.button == right_button:  # right click
                 if self.app.ui.popMenu.mouse_is_panning is False:
                     if self.in_action is False:
                         try:
@@ -4271,8 +4320,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         self.app.delete_selection_shape()
         for storage in self.storage_dict:
-            try:
-                for obj in self.storage_dict[storage]['geometry']:
+            for obj in self.storage_dict[storage]['geometry']:
+                if 'solid' in obj.geo:
                     geometric_data = obj.geo['solid']
                     if (sel_type is True and poly_selection.contains(geometric_data)) or \
                             (sel_type is False and poly_selection.intersects(geometric_data)):
@@ -4286,8 +4335,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         else:
                             self.selected.append(obj)
                             sel_aperture.add(storage)
-            except KeyError:
-                pass
+
         try:
             self.apertures_table.cellPressed.disconnect()
         except Exception as e:
@@ -4314,8 +4362,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
 
-        pos_canvas = self.canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos_canvas[0], pos_canvas[1]
 
         self.x = event.xdata
@@ -4324,7 +4380,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         self.app.ui.popMenu.mouse_is_panning = False
 
         # if the RMB is clicked and mouse is moving over plot then 'panning_action' is True
-        if event.button == 2 and event.is_dragging == 1:
+        if event.button == right_button and event_is_dragging == 1:
             self.app.ui.popMenu.mouse_is_panning = True
             return
 
@@ -4340,11 +4396,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
         # # ## Snap coordinates
         if self.app.grid_status() == True:
             x, y = self.app.geo_editor.snap(x, y)
-            self.app.app_cursor.enabled = True
+
             # Update cursor
             self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
-        else:
-            self.app.app_cursor.enabled = False
 
         self.snap_x = x
         self.snap_y = y
@@ -4371,7 +4425,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             self.draw_utility_geometry(geo=geo)
 
         # # ## Selection area on canvas section # ##
-        if event.is_dragging == 1 and event.button == 1:
+        if event_is_dragging == 1 and event.button == 1:
             # I make an exception for FCRegion and FCTrack because clicking and dragging while making regions can
             # create strange issues like missing a point in a track/region
             if isinstance(self.active_tool, FCRegion) or isinstance(self.active_tool, FCTrack):
@@ -4420,34 +4474,32 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :rtype: None
         """
         with self.app.proc_container.new("Plotting"):
-
             self.shapes.clear(update=True)
 
             for storage in self.storage_dict:
-                try:
-                    for elem in self.storage_dict[storage]['geometry']:
+                for elem in self.storage_dict[storage]['geometry']:
+                    if 'solid' in elem.geo:
                         geometric_data = elem.geo['solid']
                         if geometric_data is None:
                             continue
 
                         if elem in self.selected:
                             self.plot_shape(geometry=geometric_data,
-                                            color=self.app.defaults['global_sel_draw_color'],
+                                            color=self.app.defaults['global_sel_draw_color'] + 'FF',
                                             linewidth=2)
-                            continue
-                        self.plot_shape(geometry=geometric_data,
-                                        color=self.app.defaults['global_draw_color'])
-                except KeyError:
-                    pass
+                        else:
+                            self.plot_shape(geometry=geometric_data,
+                                            color=self.app.defaults['global_draw_color'] + 'FF')
 
-            for elem in self.utility:
-                geometric_data = elem.geo['solid']
-                self.plot_shape(geometry=geometric_data, linewidth=1)
-                continue
+            if self.utility:
+                for elem in self.utility:
+                    geometric_data = elem.geo['solid']
+                    self.plot_shape(geometry=geometric_data, linewidth=1)
+                    continue
 
             self.shapes.redraw()
 
-    def plot_shape(self, geometry=None, color='black', linewidth=1):
+    def plot_shape(self, geometry=None, color='#000000FF', linewidth=1):
         """
         Plots a geometric object or list of objects without rendering. Plotted objects
         are returned as a list. This allows for efficient/animated rendering.
@@ -4463,10 +4515,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         try:
             self.shapes.add(shape=geometry.geo, color=color, face_color=color, layer=0, tolerance=self.tolerance)
-        except AttributeError:
+        except AttributeError as e:
             if type(geometry) == Point:
                 return
-            self.shapes.add(shape=geometry, color=color, face_color=color+'AF', layer=0, tolerance=self.tolerance)
+            if len(color) == 9:
+                color = color[:7] + 'AF'
+            self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance)
 
     def start_delayed_plot(self, check_period):
         """
@@ -4785,7 +4839,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                         except Exception as e:
                             lower_threshold_val = 0.0
 
-                        if area < float(upper_threshold_val) and area > float(lower_threshold_val):
+                        if float(upper_threshold_val) > area > float(lower_threshold_val):
                             current_pos = geo_el['solid'].exterior.coords[-1]
                             text_elem = '%.4f' % area
                             text.append(text_elem)
@@ -4794,7 +4848,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         if text:
             self.ma_annotation.set(text=text, pos=position, visible=True,
                                    font_size=self.app.defaults["cncjob_annotation_fontsize"],
-                                   color=self.app.defaults["global_sel_draw_color"])
+                                   color='#000000FF')
             self.app.inform.emit('[success] %s' %
                                  _("Polygon areas marked."))
         else:

+ 156 - 4526
flatcamGUI/FlatCAMGUI.py

@@ -11,13 +11,8 @@
 # Date: 3/10/2019                                          #
 # ##########################################################
 
-from PyQt5.QtCore import QSettings
-from flatcamGUI.GUIElements import *
-import platform
-import webbrowser
-import sys
-
-from flatcamEditors.FlatCAMGeoEditor import FCShapeTool
+from flatcamGUI.PreferencesUI import *
+from matplotlib.backend_bases import KeyEvent as mpl_key_event
 
 import gettext
 import FlatCAMTranslation as fcTranslate
@@ -958,7 +953,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         self.fa_tab = QtWidgets.QWidget()
         self.fa_tab.setObjectName("fa_tab")
-        self.pref_tab_area.addTab(self.fa_tab, _("FILE ASSOCIATIONS"))
+        self.pref_tab_area.addTab(self.fa_tab, _("UTILITIES"))
         self.fa_tab_lay = QtWidgets.QVBoxLayout()
         self.fa_tab_lay.setContentsMargins(2, 2, 2, 2)
         self.fa_tab.setLayout(self.fa_tab_lay)
@@ -1318,24 +1313,28 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     </tr>
                 </tbody>
             </table>
-            ''' % (
-            _("SHOW SHORTCUT LIST"), _("Switch to Project Tab"), _("Switch to Selected Tab"), _("Switch to Tool Tab"),
-            _("New Gerber"), _("Edit Object (if selected)"), _("Grid On/Off"), _("Jump to Coordinates"),
-            _("New Excellon"), _("Move Obj"), _("New Geometry"), _("Set Origin"), _("Change Units"),
-            _("Open Properties Tool"), _("Rotate by 90 degree CW"), _("Shell Toggle"),
-            _("Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)"), _("Zoom Fit"),
-            _("Flip on X_axis"), _("Flip on Y_axis"), _("Zoom Out"), _("Zoom In"), _("Select All"), _("Copy Obj"),
-            _("Open Excellon File"), _("Open Gerber File"), _("New Project"), _("Measurement Tool"), _("Open Project"),
-            _("Save Project As"), _("Toggle Plot Area"), _("Copy Obj_Name"), _("Toggle Code Editor"),
-            _("Toggle the axis"), _("Open Preferences Window"), _("Rotate by 90 degree CCW"), _("Run a Script"),
-            _("Toggle the workspace"), _("Skew on X axis"), _("Skew on Y axis"), _("Calculators Tool"),
-            _("2-Sided PCB Tool"), _("Solder Paste Dispensing Tool"), _("Film PCB Tool"), _("Non-Copper Clearing Tool"),
-            _("Paint Area Tool"), _("PDF Import Tool"), _("Transformations Tool"), _("View File Source"),
-            _("Cutout PCB Tool"), _("Enable all Plots"), _("Disable all Plots"), _("Disable Non-selected Plots"),
-            _("Toggle Full Screen"), _("Abort current task (gracefully)"), _("Open Online Manual"),
-            _("Open Online Tutorials"), _("Refresh Plots"), _("Delete Object"), _("Alternate: Delete Tool"),
-            _("(left to Key_1)Toogle Notebook Area (Left Side)"), _("En(Dis)able Obj Plot"), _("Deselects all objects")
-        )
+            ''' %
+            (
+                _("SHOW SHORTCUT LIST"), _("Switch to Project Tab"), _("Switch to Selected Tab"),
+                _("Switch to Tool Tab"),
+                _("New Gerber"), _("Edit Object (if selected)"), _("Grid On/Off"), _("Jump to Coordinates"),
+                _("New Excellon"), _("Move Obj"), _("New Geometry"), _("Set Origin"), _("Change Units"),
+                _("Open Properties Tool"), _("Rotate by 90 degree CW"), _("Shell Toggle"),
+                _("Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)"), _("Zoom Fit"),
+                _("Flip on X_axis"), _("Flip on Y_axis"), _("Zoom Out"), _("Zoom In"), _("Select All"), _("Copy Obj"),
+                _("Open Excellon File"), _("Open Gerber File"), _("New Project"), _("Measurement Tool"),
+                _("Open Project"), _("Save Project As"), _("Toggle Plot Area"), _("Copy Obj_Name"),
+                _("Toggle Code Editor"), _("Toggle the axis"), _("Open Preferences Window"),
+                _("Rotate by 90 degree CCW"), _("Run a Script"), _("Toggle the workspace"), _("Skew on X axis"),
+                _("Skew on Y axis"), _("Calculators Tool"), _("2-Sided PCB Tool"), _("Solder Paste Dispensing Tool"),
+                _("Film PCB Tool"), _("Non-Copper Clearing Tool"),
+                _("Paint Area Tool"), _("PDF Import Tool"), _("Transformations Tool"), _("View File Source"),
+                _("Cutout PCB Tool"), _("Enable all Plots"), _("Disable all Plots"), _("Disable Non-selected Plots"),
+                _("Toggle Full Screen"), _("Abort current task (gracefully)"), _("Open Online Manual"),
+                _("Open Online Tutorials"), _("Refresh Plots"), _("Delete Object"), _("Alternate: Delete Tool"),
+                _("(left to Key_1)Toogle Notebook Area (Left Side)"), _("En(Dis)able Obj Plot"),
+                _("Deselects all objects")
+            )
         )
 
         self.sh_app = QtWidgets.QTextEdit()
@@ -1916,9 +1915,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.progress_bar.setMaximum(100)
         # infobar.addWidget(self.progress_bar)
 
-        self.activity_view = FlatCAMActivityView()
-        self.infobar.addWidget(self.activity_view)
-
         # ###########################################################################
         # ####### Set the APP ICON and the WINDOW TITLE and GEOMETRY ################
         # ###########################################################################
@@ -1972,7 +1968,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.geometry_defaults_form = GeometryPreferencesUI()
         self.cncjob_defaults_form = CNCJobPreferencesUI()
         self.tools_defaults_form = ToolsPreferencesUI()
-        self.fa_defaults_form = FAPreferencesUI()
+        self.util_defaults_form = UtilPreferencesUI()
 
         self.general_options_form = GeneralPreferencesUI()
         self.gerber_options_form = GerberPreferencesUI()
@@ -1980,7 +1976,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         self.geometry_options_form = GeometryPreferencesUI()
         self.cncjob_options_form = CNCJobPreferencesUI()
         self.tools_options_form = ToolsPreferencesUI()
-        self.fa_options_form = FAPreferencesUI()
+        self.util_options_form = UtilPreferencesUI()
 
         QtWidgets.qApp.installEventFilter(self)
 
@@ -1995,11 +1991,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         if settings.contains("layout"):
             layout = settings.value('layout', type=str)
             if layout == 'standard':
-                self.exc_edit_toolbar.setVisible(False)
+                # self.exc_edit_toolbar.setVisible(False)
                 self.exc_edit_toolbar.setDisabled(True)
-                self.geo_edit_toolbar.setVisible(False)
+                # self.geo_edit_toolbar.setVisible(False)
                 self.geo_edit_toolbar.setDisabled(True)
-                self.grb_edit_toolbar.setVisible(False)
+                # self.grb_edit_toolbar.setVisible(False)
                 self.grb_edit_toolbar.setDisabled(True)
 
                 self.corner_snap_btn.setVisible(False)
@@ -2015,17 +2011,17 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 self.corner_snap_btn.setDisabled(True)
             log.debug("FlatCAMGUI.__init__() --> UI layout restored from QSettings.")
         else:
-            self.exc_edit_toolbar.setVisible(False)
+            # self.exc_edit_toolbar.setVisible(False)
             self.exc_edit_toolbar.setDisabled(True)
-            self.geo_edit_toolbar.setVisible(False)
+            # self.geo_edit_toolbar.setVisible(False)
             self.geo_edit_toolbar.setDisabled(True)
-            self.grb_edit_toolbar.setVisible(False)
+            # self.grb_edit_toolbar.setVisible(False)
             self.grb_edit_toolbar.setDisabled(True)
 
             self.corner_snap_btn.setVisible(False)
             self.snap_magnet.setVisible(False)
-            settings.setValue('layout', "standard")
 
+            settings.setValue('layout', "standard")
             # This will write the setting to the platform specific storage.
             del settings
             log.debug("FlatCAMGUI.__init__() --> UI layout restored from defaults. QSettings set to 'standard'")
@@ -2276,6 +2272,24 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         # events from the GUI are of type QKeyEvent
         elif type(event) == QtGui.QKeyEvent:
             key = event.key()
+        elif isinstance(event, mpl_key_event):  # MatPlotLib key events are trickier to interpret than the rest
+            key = event.key
+            key = QtGui.QKeySequence(key)
+
+            # check for modifiers
+            key_string = key.toString().lower()
+            if '+' in key_string:
+                mod, __, key_text = key_string.rpartition('+')
+                if mod.lower() == 'ctrl':
+                    modifiers = QtCore.Qt.ControlModifier
+                elif mod.lower() == 'alt':
+                    modifiers = QtCore.Qt.AltModifier
+                elif mod.lower() == 'shift':
+                    modifiers = QtCore.Qt.ShiftModifier
+                else:
+                    modifiers = QtCore.Qt.NoModifier
+                key = QtGui.QKeySequence(key_text)
+
         # events from Vispy are of type KeyEvent
         else:
             key = event.key
@@ -2499,7 +2513,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
                     # try to disconnect the slot from Set Origin
                     try:
-                        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_set_zero_click)
+                        self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_set_zero_click)
                     except TypeError:
                         pass
                     self.app.inform.emit("")
@@ -3442,4532 +3456,148 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
         event.ignore()
 
 
-class GeneralPreferencesUI(QtWidgets.QWidget):
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.general_app_group = GeneralAppPrefGroupUI()
-        self.general_app_group.setMinimumWidth(290)
-
-        self.general_gui_group = GeneralGUIPrefGroupUI()
-        self.general_gui_group.setMinimumWidth(250)
-
-        self.general_gui_set_group = GeneralGUISetGroupUI()
-        self.general_gui_set_group.setMinimumWidth(250)
-
-        self.layout.addWidget(self.general_app_group)
-        self.layout.addWidget(self.general_gui_group)
-        self.layout.addWidget(self.general_gui_set_group)
-
-        self.layout.addStretch()
-
-
-class GerberPreferencesUI(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.gerber_gen_group = GerberGenPrefGroupUI()
-        self.gerber_gen_group.setMinimumWidth(250)
-        self.gerber_opt_group = GerberOptPrefGroupUI()
-        self.gerber_opt_group.setMinimumWidth(250)
-        self.gerber_exp_group = GerberExpPrefGroupUI()
-        self.gerber_exp_group.setMinimumWidth(230)
-        self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI()
-        self.gerber_adv_opt_group.setMinimumWidth(200)
-        self.gerber_editor_group = GerberEditorPrefGroupUI()
-        self.gerber_editor_group.setMinimumWidth(200)
-
-        self.vlay = QtWidgets.QVBoxLayout()
-        self.vlay.addWidget(self.gerber_opt_group)
-        self.vlay.addWidget(self.gerber_exp_group)
-
-        self.layout.addWidget(self.gerber_gen_group)
-        self.layout.addLayout(self.vlay)
-        self.layout.addWidget(self.gerber_adv_opt_group)
-        self.layout.addWidget(self.gerber_editor_group)
-
-        self.layout.addStretch()
-
-
-class ExcellonPreferencesUI(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.excellon_gen_group = ExcellonGenPrefGroupUI()
-        self.excellon_gen_group.setMinimumWidth(220)
-        self.excellon_opt_group = ExcellonOptPrefGroupUI()
-        self.excellon_opt_group.setMinimumWidth(290)
-        self.excellon_exp_group = ExcellonExpPrefGroupUI()
-        self.excellon_exp_group.setMinimumWidth(250)
-        self.excellon_adv_opt_group = ExcellonAdvOptPrefGroupUI()
-        self.excellon_adv_opt_group.setMinimumWidth(250)
-        self.excellon_editor_group = ExcellonEditorPrefGroupUI()
-        self.excellon_editor_group.setMinimumWidth(260)
-
-        self.vlay = QtWidgets.QVBoxLayout()
-        self.vlay.addWidget(self.excellon_opt_group)
-        self.vlay.addWidget(self.excellon_exp_group)
-
-        self.layout.addWidget(self.excellon_gen_group)
-        self.layout.addLayout(self.vlay)
-        self.layout.addWidget(self.excellon_adv_opt_group)
-        self.layout.addWidget(self.excellon_editor_group)
-
-        self.layout.addStretch()
-
-
-class GeometryPreferencesUI(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.geometry_gen_group = GeometryGenPrefGroupUI()
-        self.geometry_gen_group.setMinimumWidth(220)
-        self.geometry_opt_group = GeometryOptPrefGroupUI()
-        self.geometry_opt_group.setMinimumWidth(300)
-        self.geometry_adv_opt_group = GeometryAdvOptPrefGroupUI()
-        self.geometry_adv_opt_group.setMinimumWidth(270)
-        self.geometry_editor_group = GeometryEditorPrefGroupUI()
-        self.geometry_editor_group.setMinimumWidth(250)
-
-        self.layout.addWidget(self.geometry_gen_group)
-        self.layout.addWidget(self.geometry_opt_group)
-        self.layout.addWidget(self.geometry_adv_opt_group)
-        self.layout.addWidget(self.geometry_editor_group)
-
-        self.layout.addStretch()
-
-
-class ToolsPreferencesUI(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.tools_ncc_group = ToolsNCCPrefGroupUI()
-        self.tools_ncc_group.setMinimumWidth(220)
-        self.tools_paint_group = ToolsPaintPrefGroupUI()
-        self.tools_paint_group.setMinimumWidth(220)
-
-        self.tools_cutout_group = ToolsCutoutPrefGroupUI()
-        self.tools_cutout_group.setMinimumWidth(220)
-
-        self.tools_2sided_group = Tools2sidedPrefGroupUI()
-        self.tools_2sided_group.setMinimumWidth(220)
-
-        self.tools_film_group = ToolsFilmPrefGroupUI()
-        self.tools_film_group.setMinimumWidth(220)
-
-        self.tools_panelize_group = ToolsPanelizePrefGroupUI()
-        self.tools_panelize_group.setMinimumWidth(220)
-
-        self.tools_calculators_group = ToolsCalculatorsPrefGroupUI()
-        self.tools_calculators_group.setMinimumWidth(220)
-
-        self.tools_transform_group = ToolsTransformPrefGroupUI()
-        self.tools_transform_group.setMinimumWidth(200)
-
-        self.tools_solderpaste_group = ToolsSolderpastePrefGroupUI()
-        self.tools_solderpaste_group.setMinimumWidth(200)
-
-        self.tools_sub_group = ToolsSubPrefGroupUI()
-        self.tools_sub_group.setMinimumWidth(200)
-
-        self.vlay = QtWidgets.QVBoxLayout()
-        self.vlay.addWidget(self.tools_ncc_group)
-        self.vlay.addWidget(self.tools_paint_group)
-
-        self.vlay1 = QtWidgets.QVBoxLayout()
-        self.vlay1.addWidget(self.tools_cutout_group)
-        self.vlay1.addWidget(self.tools_transform_group)
-        self.vlay1.addWidget(self.tools_2sided_group)
-
-        self.vlay2 = QtWidgets.QVBoxLayout()
-        self.vlay2.addWidget(self.tools_panelize_group)
-        self.vlay2.addWidget(self.tools_calculators_group)
-
-        self.vlay3 = QtWidgets.QVBoxLayout()
-        self.vlay3.addWidget(self.tools_solderpaste_group)
-        self.vlay3.addWidget(self.tools_sub_group)
-        self.vlay3.addWidget(self.tools_film_group)
-
-        self.layout.addLayout(self.vlay)
-        self.layout.addLayout(self.vlay1)
-        self.layout.addLayout(self.vlay2)
-        self.layout.addLayout(self.vlay3)
-
-        self.layout.addStretch()
-
-
-class CNCJobPreferencesUI(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.cncjob_gen_group = CNCJobGenPrefGroupUI()
-        self.cncjob_gen_group.setMinimumWidth(320)
-        self.cncjob_opt_group = CNCJobOptPrefGroupUI()
-        self.cncjob_opt_group.setMinimumWidth(260)
-        self.cncjob_adv_opt_group = CNCJobAdvOptPrefGroupUI()
-        self.cncjob_adv_opt_group.setMinimumWidth(260)
-
-        self.layout.addWidget(self.cncjob_gen_group)
-        self.layout.addWidget(self.cncjob_opt_group)
-        self.layout.addWidget(self.cncjob_adv_opt_group)
-
-        self.layout.addStretch()
-
-
-class FAPreferencesUI(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        QtWidgets.QWidget.__init__(self, parent=parent)
-        self.layout = QtWidgets.QHBoxLayout()
-        self.setLayout(self.layout)
-
-        self.fa_excellon_group = FAExcPrefGroupUI()
-        self.fa_excellon_group.setMinimumWidth(260)
-        self.fa_gcode_group = FAGcoPrefGroupUI()
-        self.fa_gcode_group.setMinimumWidth(260)
-        self.fa_gerber_group = FAGrbPrefGroupUI()
-        self.fa_gerber_group.setMinimumWidth(260)
-
-        self.layout.addWidget(self.fa_excellon_group)
-        self.layout.addWidget(self.fa_gcode_group)
-        self.layout.addWidget(self.fa_gerber_group)
+class FlatCAMActivityView(QtWidgets.QWidget):
 
-        self.layout.addStretch()
+    def __init__(self, movie="share/active.gif", icon='share/active_static.png', parent=None):
+        super().__init__(parent=parent)
 
+        self.setMinimumWidth(200)
+        self.movie_path = movie
+        self.icon_path = icon
 
-class OptionsGroupUI(QtWidgets.QGroupBox):
-    def __init__(self, title, parent=None):
-        # QtGui.QGroupBox.__init__(self, title, parent=parent)
-        super(OptionsGroupUI, self).__init__()
-        self.setStyleSheet("""
-        QGroupBox
-        {
-            font-size: 16px;
-            font-weight: bold;
-        }
-        """)
+        self.icon = QtWidgets.QLabel(self)
+        self.icon.setGeometry(0, 0, 16, 12)
+        self.movie = QtGui.QMovie(self.movie_path)
 
-        self.layout = QtWidgets.QVBoxLayout()
-        self.setLayout(self.layout)
+        self.icon.setMovie(self.movie)
+        # self.movie.start()
 
+        layout = QtWidgets.QHBoxLayout()
+        layout.setContentsMargins(5, 0, 5, 0)
+        layout.setAlignment(QtCore.Qt.AlignLeft)
+        self.setLayout(layout)
 
-class GeneralGUIPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        super(GeneralGUIPrefGroupUI, self).__init__(self)
+        layout.addWidget(self.icon)
+        self.text = QtWidgets.QLabel(self)
+        self.text.setText(_("Idle."))
+        self.icon.setPixmap(QtGui.QPixmap(self.icon_path))
 
-        self.setTitle(str(_("GUI Preferences")))
+        layout.addWidget(self.text)
 
-        # Create a form layout for the Application general settings
-        self.form_box = QtWidgets.QFormLayout()
+    def set_idle(self):
+        self.movie.stop()
+        self.text.setText(_("Idle."))
 
-        # Grid X Entry
-        self.gridx_label = QtWidgets.QLabel('%s:' % _('Grid X value'))
-        self.gridx_label.setToolTip(
-           _("This is the Grid snap value on X axis.")
-        )
-        self.gridx_entry = FCEntry3()
+    def set_busy(self, msg, no_movie=None):
+        if no_movie is not True:
+            self.icon.setMovie(self.movie)
+            self.movie.start()
+        self.text.setText(msg)
 
-        # Grid Y Entry
-        self.gridy_label = QtWidgets.QLabel('%s:' % _('Grid Y value'))
-        self.gridy_label.setToolTip(
-            _("This is the Grid snap value on Y axis.")
-        )
-        self.gridy_entry = FCEntry3()
 
-        # Snap Max Entry
-        self.snap_max_label = QtWidgets.QLabel('%s:' % _('Snap Max'))
-        self.snap_max_label.setToolTip(_("Max. magnet distance"))
-        self.snap_max_dist_entry = FCEntry()
+class FlatCAMInfoBar(QtWidgets.QWidget):
 
-        # Workspace
-        self.workspace_lbl = QtWidgets.QLabel('%s:' % _('Workspace'))
-        self.workspace_lbl.setToolTip(
-           _("Draw a delimiting rectangle on canvas.\n"
-             "The purpose is to illustrate the limits for our work.")
-        )
-        self.workspace_type_lbl = QtWidgets.QLabel('%s:' % _('Wk. format'))
-        self.workspace_type_lbl.setToolTip(
-           _("Select the type of rectangle to be used on canvas,\n"
-             "as valid workspace.")
-        )
-        self.workspace_cb = FCCheckBox()
-        self.wk_cb = FCComboBox()
-        self.wk_cb.addItem('A4P')
-        self.wk_cb.addItem('A4L')
-        self.wk_cb.addItem('A3P')
-        self.wk_cb.addItem('A3L')
-
-        self.wks = OptionalInputSection(self.workspace_cb, [self.workspace_type_lbl, self.wk_cb])
-
-        # Plot Fill Color
-        self.pf_color_label = QtWidgets.QLabel('%s:' % _('Plot Fill'))
-        self.pf_color_label.setToolTip(
-           _("Set the fill color for plotted objects.\n"
-             "First 6 digits are the color and the last 2\n"
-             "digits are for alpha (transparency) level.")
-        )
-        self.pf_color_entry = FCEntry()
-        self.pf_color_button = QtWidgets.QPushButton()
-        self.pf_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_1 = QtWidgets.QHBoxLayout()
-        self.form_box_child_1.addWidget(self.pf_color_entry)
-        self.form_box_child_1.addWidget(self.pf_color_button)
-        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Plot Fill Transparency Level
-        self.pf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha Level'))
-        self.pf_alpha_label.setToolTip(
-           _("Set the fill transparency for plotted objects.")
-        )
-        self.pf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.pf_color_alpha_slider.setMinimum(0)
-        self.pf_color_alpha_slider.setMaximum(255)
-        self.pf_color_alpha_slider.setSingleStep(1)
-
-        self.pf_color_alpha_spinner = FCSpinner()
-        self.pf_color_alpha_spinner.setMinimumWidth(70)
-        self.pf_color_alpha_spinner.setMinimum(0)
-        self.pf_color_alpha_spinner.setMaximum(255)
-
-        self.form_box_child_2 = QtWidgets.QHBoxLayout()
-        self.form_box_child_2.addWidget(self.pf_color_alpha_slider)
-        self.form_box_child_2.addWidget(self.pf_color_alpha_spinner)
-
-        # Plot Line Color
-        self.pl_color_label = QtWidgets.QLabel('%s:' % _('Plot Line'))
-        self.pl_color_label.setToolTip(
-           _("Set the line color for plotted objects.")
-        )
-        self.pl_color_entry = FCEntry()
-        self.pl_color_button = QtWidgets.QPushButton()
-        self.pl_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_3 = QtWidgets.QHBoxLayout()
-        self.form_box_child_3.addWidget(self.pl_color_entry)
-        self.form_box_child_3.addWidget(self.pl_color_button)
-        self.form_box_child_3.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Plot Selection (left - right) Fill Color
-        self.sf_color_label = QtWidgets.QLabel('%s:' % _('Sel. Fill'))
-        self.sf_color_label.setToolTip(
-            _("Set the fill color for the selection box\n"
-              "in case that the selection is done from left to right.\n"
-              "First 6 digits are the color and the last 2\n"
-              "digits are for alpha (transparency) level.")
-        )
-        self.sf_color_entry = FCEntry()
-        self.sf_color_button = QtWidgets.QPushButton()
-        self.sf_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_4 = QtWidgets.QHBoxLayout()
-        self.form_box_child_4.addWidget(self.sf_color_entry)
-        self.form_box_child_4.addWidget(self.sf_color_button)
-        self.form_box_child_4.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Plot Selection (left - right) Fill Transparency Level
-        self.sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha Level'))
-        self.sf_alpha_label.setToolTip(
-            _("Set the fill transparency for the 'left to right' selection box.")
-        )
-        self.sf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.sf_color_alpha_slider.setMinimum(0)
-        self.sf_color_alpha_slider.setMaximum(255)
-        self.sf_color_alpha_slider.setSingleStep(1)
-
-        self.sf_color_alpha_spinner = FCSpinner()
-        self.sf_color_alpha_spinner.setMinimumWidth(70)
-        self.sf_color_alpha_spinner.setMinimum(0)
-        self.sf_color_alpha_spinner.setMaximum(255)
-
-        self.form_box_child_5 = QtWidgets.QHBoxLayout()
-        self.form_box_child_5.addWidget(self.sf_color_alpha_slider)
-        self.form_box_child_5.addWidget(self.sf_color_alpha_spinner)
-
-        # Plot Selection (left - right) Line Color
-        self.sl_color_label = QtWidgets.QLabel('%s:' % _('Sel. Line'))
-        self.sl_color_label.setToolTip(
-            _("Set the line color for the 'left to right' selection box.")
-        )
-        self.sl_color_entry = FCEntry()
-        self.sl_color_button = QtWidgets.QPushButton()
-        self.sl_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_6 = QtWidgets.QHBoxLayout()
-        self.form_box_child_6.addWidget(self.sl_color_entry)
-        self.form_box_child_6.addWidget(self.sl_color_button)
-        self.form_box_child_6.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Plot Selection (right - left) Fill Color
-        self.alt_sf_color_label = QtWidgets.QLabel('%s:' % _('Sel2. Fill'))
-        self.alt_sf_color_label.setToolTip(
-            _("Set the fill color for the selection box\n"
-              "in case that the selection is done from right to left.\n"
-              "First 6 digits are the color and the last 2\n"
-              "digits are for alpha (transparency) level.")
-        )
-        self.alt_sf_color_entry = FCEntry()
-        self.alt_sf_color_button = QtWidgets.QPushButton()
-        self.alt_sf_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_7 = QtWidgets.QHBoxLayout()
-        self.form_box_child_7.addWidget(self.alt_sf_color_entry)
-        self.form_box_child_7.addWidget(self.alt_sf_color_button)
-        self.form_box_child_7.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Plot Selection (right - left) Fill Transparency Level
-        self.alt_sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha Level'))
-        self.alt_sf_alpha_label.setToolTip(
-            _("Set the fill transparency for selection 'right to left' box.")
-        )
-        self.alt_sf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
-        self.alt_sf_color_alpha_slider.setMinimum(0)
-        self.alt_sf_color_alpha_slider.setMaximum(255)
-        self.alt_sf_color_alpha_slider.setSingleStep(1)
-
-        self.alt_sf_color_alpha_spinner = FCSpinner()
-        self.alt_sf_color_alpha_spinner.setMinimumWidth(70)
-        self.alt_sf_color_alpha_spinner.setMinimum(0)
-        self.alt_sf_color_alpha_spinner.setMaximum(255)
-
-        self.form_box_child_8 = QtWidgets.QHBoxLayout()
-        self.form_box_child_8.addWidget(self.alt_sf_color_alpha_slider)
-        self.form_box_child_8.addWidget(self.alt_sf_color_alpha_spinner)
-
-        # Plot Selection (right - left) Line Color
-        self.alt_sl_color_label = QtWidgets.QLabel('%s:' % _('Sel2. Line'))
-        self.alt_sl_color_label.setToolTip(
-            _("Set the line color for the 'right to left' selection box.")
-        )
-        self.alt_sl_color_entry = FCEntry()
-        self.alt_sl_color_button = QtWidgets.QPushButton()
-        self.alt_sl_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_9 = QtWidgets.QHBoxLayout()
-        self.form_box_child_9.addWidget(self.alt_sl_color_entry)
-        self.form_box_child_9.addWidget(self.alt_sl_color_button)
-        self.form_box_child_9.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Editor Draw Color
-        self.draw_color_label = QtWidgets.QLabel('%s:' % _('Editor Draw'))
-        self.alt_sf_color_label.setToolTip(
-            _("Set the color for the shape.")
-        )
-        self.draw_color_entry = FCEntry()
-        self.draw_color_button = QtWidgets.QPushButton()
-        self.draw_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_10 = QtWidgets.QHBoxLayout()
-        self.form_box_child_10.addWidget(self.draw_color_entry)
-        self.form_box_child_10.addWidget(self.draw_color_button)
-        self.form_box_child_10.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Editor Draw Selection Color
-        self.sel_draw_color_label = QtWidgets.QLabel('%s:' % _('Editor Draw Sel.'))
-        self.sel_draw_color_label.setToolTip(
-            _("Set the color of the shape when selected.")
-        )
-        self.sel_draw_color_entry = FCEntry()
-        self.sel_draw_color_button = QtWidgets.QPushButton()
-        self.sel_draw_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_11 = QtWidgets.QHBoxLayout()
-        self.form_box_child_11.addWidget(self.sel_draw_color_entry)
-        self.form_box_child_11.addWidget(self.sel_draw_color_button)
-        self.form_box_child_11.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Project Tab items color
-        self.proj_color_label = QtWidgets.QLabel('%s:' % _('Project Items'))
-        self.proj_color_label.setToolTip(
-            _("Set the color of the items in Project Tab Tree.")
-        )
-        self.proj_color_entry = FCEntry()
-        self.proj_color_button = QtWidgets.QPushButton()
-        self.proj_color_button.setFixedSize(15, 15)
-
-        self.form_box_child_12 = QtWidgets.QHBoxLayout()
-        self.form_box_child_12.addWidget(self.proj_color_entry)
-        self.form_box_child_12.addWidget(self.proj_color_button)
-        self.form_box_child_12.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        self.proj_color_dis_label = QtWidgets.QLabel('%s:' % _('Proj. Dis. Items'))
-        self.proj_color_dis_label.setToolTip(
-            _("Set the color of the items in Project Tab Tree,\n"
-              "for the case when the items are disabled.")
-        )
-        self.proj_color_dis_entry = FCEntry()
-        self.proj_color_dis_button = QtWidgets.QPushButton()
-        self.proj_color_dis_button.setFixedSize(15, 15)
-
-        self.form_box_child_13 = QtWidgets.QHBoxLayout()
-        self.form_box_child_13.addWidget(self.proj_color_dis_entry)
-        self.form_box_child_13.addWidget(self.proj_color_dis_button)
-        self.form_box_child_13.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        # Just to add empty rows
-        self.spacelabel = QtWidgets.QLabel('')
-
-        # Add (label - input field) pair to the QFormLayout
-        self.form_box.addRow(self.spacelabel, self.spacelabel)
-
-        self.form_box.addRow(self.gridx_label, self.gridx_entry)
-        self.form_box.addRow(self.gridy_label, self.gridy_entry)
-        self.form_box.addRow(self.snap_max_label, self.snap_max_dist_entry)
-
-        self.form_box.addRow(self.workspace_lbl, self.workspace_cb)
-        self.form_box.addRow(self.workspace_type_lbl, self.wk_cb)
-        self.form_box.addRow(self.spacelabel, self.spacelabel)
-        self.form_box.addRow(self.pf_color_label, self.form_box_child_1)
-        self.form_box.addRow(self.pf_alpha_label, self.form_box_child_2)
-        self.form_box.addRow(self.pl_color_label, self.form_box_child_3)
-        self.form_box.addRow(self.sf_color_label, self.form_box_child_4)
-        self.form_box.addRow(self.sf_alpha_label, self.form_box_child_5)
-        self.form_box.addRow(self.sl_color_label, self.form_box_child_6)
-        self.form_box.addRow(self.alt_sf_color_label, self.form_box_child_7)
-        self.form_box.addRow(self.alt_sf_alpha_label, self.form_box_child_8)
-        self.form_box.addRow(self.alt_sl_color_label, self.form_box_child_9)
-        self.form_box.addRow(self.draw_color_label, self.form_box_child_10)
-        self.form_box.addRow(self.sel_draw_color_label, self.form_box_child_11)
-        self.form_box.addRow(QtWidgets.QLabel(""))
-        self.form_box.addRow(self.proj_color_label, self.form_box_child_12)
-        self.form_box.addRow(self.proj_color_dis_label, self.form_box_child_13)
-
-        self.form_box.addRow(self.spacelabel, self.spacelabel)
-
-        # Add the QFormLayout that holds the Application general defaults
-        # to the main layout of this TAB
-        self.layout.addLayout(self.form_box)
-
-
-class GeneralGUISetGroupUI(OptionsGroupUI):
     def __init__(self, parent=None):
-        super(GeneralGUISetGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("GUI Settings")))
-
-        # Create a form layout for the Application general settings
-        self.form_box = QtWidgets.QFormLayout()
-
-        # Layout selection
-        self.layout_label = QtWidgets.QLabel('%s:' % _('Layout'))
-        self.layout_label.setToolTip(
-            _("Select an layout for FlatCAM.\n"
-              "It is applied immediately.")
-        )
-        self.layout_combo = FCComboBox()
-        # don't translate the QCombo items as they are used in QSettings and identified by name
-        self.layout_combo.addItem("standard")
-        self.layout_combo.addItem("compact")
-
-        # Set the current index for layout_combo
-        settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("layout"):
-            layout = settings.value('layout', type=str)
-            idx = self.layout_combo.findText(layout.capitalize())
-            self.layout_combo.setCurrentIndex(idx)
-
-        # Style selection
-        self.style_label = QtWidgets.QLabel('%s:' % _('Style'))
-        self.style_label.setToolTip(
-            _("Select an style for FlatCAM.\n"
-              "It will be applied at the next app start.")
-        )
-        self.style_combo = FCComboBox()
-        self.style_combo.addItems(QtWidgets.QStyleFactory.keys())
-        # find current style
-        index = self.style_combo.findText(QtWidgets.qApp.style().objectName(), QtCore.Qt.MatchFixedString)
-        self.style_combo.setCurrentIndex(index)
-        self.style_combo.activated[str].connect(self.handle_style)
-
-        # Enable High DPI Support
-        self.hdpi_label = QtWidgets.QLabel('%s:' % _('HDPI Support'))
-        self.hdpi_label.setToolTip(
-            _("Enable High DPI support for FlatCAM.\n"
-              "It will be applied at the next app start.")
-        )
-        self.hdpi_cb = FCCheckBox()
+        super(FlatCAMInfoBar, self).__init__(parent=parent)
 
-        settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("hdpi"):
-            self.hdpi_cb.set_value(settings.value('hdpi', type=int))
-        else:
-            self.hdpi_cb.set_value(False)
-        self.hdpi_cb.stateChanged.connect(self.handle_hdpi)
-
-        # Clear Settings
-        self.clear_label = QtWidgets.QLabel('%s:' % _('Clear GUI Settings'))
-        self.clear_label.setToolTip(
-            _("Clear the GUI settings for FlatCAM,\n"
-              "such as: layout, gui state, style, hdpi support etc.")
-        )
-        self.clear_btn = FCButton(_("Clear"))
-        self.clear_btn.clicked.connect(self.handle_clear)
-
-        # Enable Hover box
-        self.hover_label = QtWidgets.QLabel('%s:' % _('Hover Shape'))
-        self.hover_label.setToolTip(
-            _("Enable display of a hover shape for FlatCAM objects.\n"
-              "It is displayed whenever the mouse cursor is hovering\n"
-              "over any kind of not-selected object.")
-        )
-        self.hover_cb = FCCheckBox()
-
-        # Enable Selection box
-        self.selection_label = QtWidgets.QLabel('%s:' % _('Sel. Shape'))
-        self.selection_label.setToolTip(
-            _("Enable the display of a selection shape for FlatCAM objects.\n"
-              "It is displayed whenever the mouse selects an object\n"
-              "either by clicking or dragging mouse from left to right or\n"
-              "right to left.")
-        )
-        self.selection_cb = FCCheckBox()
-
-        # Notebook Font Size
-        self.notebook_font_size_label = QtWidgets.QLabel('%s:' % _('NB Font Size'))
-        self.notebook_font_size_label.setToolTip(
-            _("This sets the font size for the elements found in the Notebook.\n"
-              "The notebook is the collapsible area in the left side of the GUI,\n"
-              "and include the Project, Selected and Tool tabs.")
-        )
+        self.icon = QtWidgets.QLabel(self)
+        self.icon.setGeometry(0, 0, 12, 12)
+        self.pmap = QtGui.QPixmap('share/graylight12.png')
+        self.icon.setPixmap(self.pmap)
 
-        self.notebook_font_size_spinner = FCSpinner()
-        self.notebook_font_size_spinner.setRange(8, 40)
-        self.notebook_font_size_spinner.setWrapping(True)
+        layout = QtWidgets.QHBoxLayout()
+        layout.setContentsMargins(5, 0, 5, 0)
+        self.setLayout(layout)
 
-        settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("notebook_font_size"):
-            self.notebook_font_size_spinner.set_value(settings.value('notebook_font_size', type=int))
-        else:
-            self.notebook_font_size_spinner.set_value(12)
+        layout.addWidget(self.icon)
 
-        # Axis Font Size
-        self.axis_font_size_label = QtWidgets.QLabel('%s:' % _('Axis Font Size'))
-        self.axis_font_size_label.setToolTip(
-            _("This sets the font size for canvas axis.")
-        )
+        self.text = QtWidgets.QLabel(self)
+        self.text.setText(_("Application started ..."))
+        self.text.setToolTip(_("Hello!"))
 
-        self.axis_font_size_spinner = FCSpinner()
-        self.axis_font_size_spinner.setRange(8, 40)
-        self.axis_font_size_spinner.setWrapping(True)
+        layout.addWidget(self.text)
 
-        settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("axis_font_size"):
-            self.axis_font_size_spinner.set_value(settings.value('axis_font_size', type=int))
-        else:
-            self.axis_font_size_spinner.set_value(8)
+        layout.addStretch()
 
-        # Just to add empty rows
-        self.spacelabel = QtWidgets.QLabel('')
+    def set_text_(self, text, color=None):
+        self.text.setText(text)
+        self.text.setToolTip(text)
+        if color:
+            self.text.setStyleSheet('color: %s' % str(color))
 
-        # Splash Screen
-        self.splash_label = QtWidgets.QLabel('%s:' % _('Splash Screen'))
-        self.splash_label.setToolTip(
-            _("Enable display of the splash screen at application startup.")
-        )
-        self.splash_cb = FCCheckBox()
-        settings = QSettings("Open Source", "FlatCAM")
-        if settings.value("splash_screen"):
-            self.splash_cb.set_value(True)
+    def set_status(self, text, level="info"):
+        level = str(level)
+        self.pmap.fill()
+        if level == "ERROR" or level == "ERROR_NOTCL":
+            self.pmap = QtGui.QPixmap('share/redlight12.png')
+        elif level == "success" or level == "SUCCESS":
+            self.pmap = QtGui.QPixmap('share/greenlight12.png')
+        elif level == "WARNING" or level == "WARNING_NOTCL":
+            self.pmap = QtGui.QPixmap('share/yellowlight12.png')
+        elif level == "selected" or level == "SELECTED":
+            self.pmap = QtGui.QPixmap('share/bluelight12.png')
         else:
-            self.splash_cb.set_value(False)
-
-        # Shell StartUp CB
-        self.shell_startup_label = QtWidgets.QLabel('%s:' % _('Shell at StartUp'))
-        self.shell_startup_label.setToolTip(
-            _("Check this box if you want the shell to\n"
-              "start automatically at startup.")
-        )
-        self.shell_startup_cb = FCCheckBox(label='')
-        self.shell_startup_cb.setToolTip(
-            _("Check this box if you want the shell to\n"
-              "start automatically at startup.")
-        )
-
-        # Project at StartUp CB
-        self.project_startup_label = QtWidgets.QLabel('%s:' % _('Project at StartUp'))
-        self.project_startup_label.setToolTip(
-            _("Check this box if you want the project/selected/tool tab area to\n"
-              "to be shown automatically at startup.")
-        )
-        self.project_startup_cb = FCCheckBox(label='')
-        self.project_startup_cb.setToolTip(
-            _("Check this box if you want the project/selected/tool tab area to\n"
-              "to be shown automatically at startup.")
-        )
-
-        # Project autohide CB
-        self.project_autohide_label = QtWidgets.QLabel('%s:' % _('Project AutoHide'))
-        self.project_autohide_label.setToolTip(
-            _("Check this box if you want the project/selected/tool tab area to\n"
-              "hide automatically when there are no objects loaded and\n"
-              "to show whenever a new object is created.")
-        )
-        self.project_autohide_cb = FCCheckBox(label='')
-        self.project_autohide_cb.setToolTip(
-            _("Check this box if you want the project/selected/tool tab area to\n"
-              "hide automatically when there are no objects loaded and\n"
-              "to show whenever a new object is created.")
-        )
-
-        # Enable/Disable ToolTips globally
-        self.toggle_tooltips_label = QtWidgets.QLabel('<b>%s:</b>' % _('Enable ToolTips'))
-        self.toggle_tooltips_label.setToolTip(
-            _("Check this box if you want to have toolTips displayed\n"
-              "when hovering with mouse over items throughout the App.")
-        )
-        self.toggle_tooltips_cb = FCCheckBox(label='')
-        self.toggle_tooltips_cb.setToolTip(
-            _("Check this box if you want to have toolTips displayed\n"
-              "when hovering with mouse over items throughout the App.")
-        )
-
-        # Add (label - input field) pair to the QFormLayout
-        self.form_box.addRow(self.spacelabel, self.spacelabel)
-
-        self.form_box.addRow(self.layout_label, self.layout_combo)
-        self.form_box.addRow(self.style_label, self.style_combo)
-        self.form_box.addRow(self.hdpi_label, self.hdpi_cb)
-        self.form_box.addRow(self.clear_label, self.clear_btn)
-        self.form_box.addRow(self.hover_label, self.hover_cb)
-        self.form_box.addRow(self.selection_label, self.selection_cb)
-        self.form_box.addRow(QtWidgets.QLabel(''))
-        self.form_box.addRow(self.notebook_font_size_label, self.notebook_font_size_spinner)
-        self.form_box.addRow(self.axis_font_size_label, self.axis_font_size_spinner)
-        self.form_box.addRow(QtWidgets.QLabel(''))
-        self.form_box.addRow(self.splash_label, self.splash_cb)
-        self.form_box.addRow(self.shell_startup_label, self.shell_startup_cb)
-        self.form_box.addRow(self.project_startup_label, self.project_startup_cb)
-        self.form_box.addRow(self.project_autohide_label, self.project_autohide_cb)
-        self.form_box.addRow(QtWidgets.QLabel(''))
-        self.form_box.addRow(self.toggle_tooltips_label, self.toggle_tooltips_cb)
-
-        # Add the QFormLayout that holds the Application general defaults
-        # to the main layout of this TAB
-        self.layout.addLayout(self.form_box)
-
-        # Delete confirmation
-        self.delete_conf_cb = FCCheckBox(_('Delete object confirmation'))
-        self.delete_conf_cb.setToolTip(
-            _("When checked the application will ask for user confirmation\n"
-              "whenever the Delete object(s) event is triggered, either by\n"
-              "menu shortcut or key shortcut.")
-        )
-        self.layout.addWidget(self.delete_conf_cb)
-
-        self.layout.addStretch()
-
-    def handle_style(self, style):
-        # set current style
-        settings = QSettings("Open Source", "FlatCAM")
-        settings.setValue('style', style)
-
-        # This will write the setting to the platform specific storage.
-        del settings
-
-    def handle_hdpi(self, state):
-        # set current HDPI
-        settings = QSettings("Open Source", "FlatCAM")
-        settings.setValue('hdpi', state)
-
-        # This will write the setting to the platform specific storage.
-        del settings
-
-    def handle_clear(self):
-        msgbox = QtWidgets.QMessageBox()
-        msgbox.setText(_("Are you sure you want to delete the GUI Settings? "
-                         "\n")
-                       )
-        msgbox.setWindowTitle(_("Clear GUI Settings"))
-        msgbox.setWindowIcon(QtGui.QIcon('share/trash32.png'))
-        bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
-        bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
-
-        msgbox.setDefaultButton(bt_no)
-        msgbox.exec_()
-        response = msgbox.clickedButton()
-
-        if response == bt_yes:
-            settings = QSettings("Open Source", "FlatCAM")
-            for key in settings.allKeys():
-                settings.remove(key)
-            # This will write the setting to the platform specific storage.
-            del settings
-
-
-class GeneralAppPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        super(GeneralAppPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("App Preferences")))
-
-        # Create a form layout for the Application general settings
-        self.form_box = QtWidgets.QFormLayout()
-
-        # Units for FlatCAM
-        self.unitslabel = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Units'))
-        self.unitslabel.setToolTip(_("The default value for FlatCAM units.\n"
-                                     "Whatever is selected here is set every time\n"
-                                     "FLatCAM is started."))
-        self.units_radio = RadioSet([{'label': _('IN'), 'value': 'IN'},
-                                     {'label': _('MM'), 'value': 'MM'}])
-
-        # Application Level for FlatCAM
-        self.app_level_label = QtWidgets.QLabel('<b>%s:</b>' % _('APP. LEVEL'))
-        self.app_level_label.setToolTip(_("Choose the default level of usage for FlatCAM.\n"
-                                          "BASIC level -> reduced functionality, best for beginner's.\n"
-                                          "ADVANCED level -> full functionality.\n\n"
-                                          "The choice here will influence the parameters in\n"
-                                          "the Selected Tab for all kinds of FlatCAM objects."))
-        self.app_level_radio = RadioSet([{'label': _('Basic'), 'value': 'b'},
-                                         {'label': _('Advanced'), 'value': 'a'}])
-
-        # Application Level for FlatCAM
-        self.portability_label = QtWidgets.QLabel('%s:' % _('Portable app'))
-        self.portability_label.setToolTip(_("Choose if the application should run as portable.\n\n"
-                                            "If Checked the application will run portable,\n"
-                                            "which means that the preferences files will be saved\n"
-                                            "in the application folder, in the lib\config subfolder."))
-        self.portability_cb = FCCheckBox()
-
-        # Languages for FlatCAM
-        self.languagelabel = QtWidgets.QLabel('<b>%s:</b>' % _('Languages'))
-        self.languagelabel.setToolTip(_("Set the language used throughout FlatCAM."))
-        self.language_cb = FCComboBox()
-        self.languagespace = QtWidgets.QLabel('')
-        self.language_apply_btn = FCButton(_("Apply Language"))
-        self.language_apply_btn.setToolTip(_("Set the language used throughout FlatCAM.\n"
-                                             "The app will restart after click."
-                                             "Windows: When FlatCAM is installed in Program Files\n"
-                                             "directory, it is possible that the app will not\n"
-                                             "restart after the button is clicked due of Windows\n"
-                                             "security features. In this case the language will be\n"
-                                             "applied at the next app start."))
-
-        # Version Check CB
-        self.version_check_label = QtWidgets.QLabel('%s:' % _('Version Check'))
-        self.version_check_label.setToolTip(
-            _("Check this box if you want to check\n"
-              "for a new version automatically at startup.")
-        )
-        self.version_check_cb = FCCheckBox(label='')
-        self.version_check_cb.setToolTip(
-            _("Check this box if you want to check\n"
-              "for a new version automatically at startup.")
-        )
-
-        # Send Stats CB
-        self.send_stats_label = QtWidgets.QLabel('%s:' % _('Send Stats'))
-        self.send_stats_label.setToolTip(
-            _("Check this box if you agree to send anonymous\n"
-              "stats automatically at startup, to help improve FlatCAM.")
-        )
-        self.send_stats_cb = FCCheckBox(label='')
-        self.send_stats_cb.setToolTip(
-            _("Check this box if you agree to send anonymous\n"
-              "stats automatically at startup, to help improve FlatCAM.")
-        )
-
-        self.ois_version_check = OptionalInputSection(self.version_check_cb, [self.send_stats_cb])
-
-        # Select mouse pan button
-        self.panbuttonlabel = QtWidgets.QLabel('<b>%s:</b>' % _('Pan Button'))
-        self.panbuttonlabel.setToolTip(_("Select the mouse button to use for panning:\n"
-                                         "- MMB --> Middle Mouse Button\n"
-                                         "- RMB --> Right Mouse Button"))
-        self.pan_button_radio = RadioSet([{'label': _('MMB'), 'value': '3'},
-                                          {'label': _('RMB'), 'value': '2'}])
-
-        # Multiple Selection Modifier Key
-        self.mselectlabel = QtWidgets.QLabel('<b>%s:</b>' % _('Multiple Sel'))
-        self.mselectlabel.setToolTip(_("Select the key used for multiple selection."))
-        self.mselect_radio = RadioSet([{'label': _('CTRL'), 'value': 'Control'},
-                                       {'label': _('SHIFT'), 'value': 'Shift'}])
-
-       # Worker Numbers
-        self.worker_number_label = QtWidgets.QLabel('%s:' % _('Workers number'))
-        self.worker_number_label.setToolTip(
-            _("The number of Qthreads made available to the App.\n"
-              "A bigger number may finish the jobs more quickly but\n"
-              "depending on your computer speed, may make the App\n"
-              "unresponsive. Can have a value between 2 and 16.\n"
-              "Default value is 2.\n"
-              "After change, it will be applied at next App start.")
-        )
-        self.worker_number_sb = FCSpinner()
-        self.worker_number_sb.setToolTip(
-            _("The number of Qthreads made available to the App.\n"
-              "A bigger number may finish the jobs more quickly but\n"
-              "depending on your computer speed, may make the App\n"
-              "unresponsive. Can have a value between 2 and 16.\n"
-              "Default value is 2.\n"
-              "After change, it will be applied at next App start.")
-        )
-        self.worker_number_sb.set_range(2, 16)
-
-        # Geometric tolerance
-        tol_label = QtWidgets.QLabel('%s:' % _("Geo Tolerance"))
-        tol_label.setToolTip(_(
-            "This value can counter the effect of the Circle Steps\n"
-            "parameter. Default value is 0.01.\n"
-            "A lower value will increase the detail both in image\n"
-            "and in Gcode for the circles, with a higher cost in\n"
-            "performance. Higher value will provide more\n"
-            "performance at the expense of level of detail."
-        ))
-        self.tol_entry = FCEntry()
-        self.tol_entry.setToolTip(_(
-            "This value can counter the effect of the Circle Steps\n"
-            "parameter. Default value is 0.01.\n"
-            "A lower value will increase the detail both in image\n"
-            "and in Gcode for the circles, with a higher cost in\n"
-            "performance. Higher value will provide more\n"
-            "performance at the expense of level of detail."
-        ))
-        # Just to add empty rows
-        self.spacelabel = QtWidgets.QLabel('')
-
-        # Add (label - input field) pair to the QFormLayout
-        self.form_box.addRow(self.unitslabel, self.units_radio)
-        self.form_box.addRow(self.app_level_label, self.app_level_radio)
-        self.form_box.addRow(self.portability_label, self.portability_cb)
-        self.form_box.addRow(QtWidgets.QLabel(''))
-
-        self.form_box.addRow(self.languagelabel, self.language_cb)
-        self.form_box.addRow(self.languagespace, self.language_apply_btn)
-
-        self.form_box.addRow(self.spacelabel, self.spacelabel)
-        self.form_box.addRow(self.version_check_label, self.version_check_cb)
-        self.form_box.addRow(self.send_stats_label, self.send_stats_cb)
-
-        self.form_box.addRow(self.panbuttonlabel, self.pan_button_radio)
-        self.form_box.addRow(self.mselectlabel, self.mselect_radio)
-        self.form_box.addRow(self.worker_number_label, self.worker_number_sb)
-        self.form_box.addRow(tol_label, self.tol_entry)
-
-        self.form_box.addRow(self.spacelabel, self.spacelabel)
-
-        # Add the QFormLayout that holds the Application general defaults
-        # to the main layout of this TAB
-        self.layout.addLayout(self.form_box)
-
-        # Open behavior
-        self.open_style_cb = FCCheckBox('%s' % _('"Open" behavior'))
-        self.open_style_cb.setToolTip(
-            _("When checked the path for the last saved file is used when saving files,\n"
-              "and the path for the last opened file is used when opening files.\n\n"
-              "When unchecked the path for opening files is the one used last: either the\n"
-              "path for saving files or the path for opening files.")
-        )
-        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        self.layout.addWidget(self.open_style_cb)
-
-        # Save compressed project CB
-        self.save_type_cb = FCCheckBox(_('Save Compressed Project'))
-        self.save_type_cb.setToolTip(
-            _("Whether to save a compressed or uncompressed project.\n"
-              "When checked it will save a compressed FlatCAM project.")
-        )
-        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        self.layout.addWidget(self.save_type_cb)
-
-        hlay1 = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay1)
-
-        # Project LZMA Comppression Level
-        self.compress_combo = FCComboBox()
-        self.compress_label = QtWidgets.QLabel('%s:' % _('Compression Level'))
-        self.compress_label.setToolTip(
-            _("The level of compression used when saving\n"
-              "a FlatCAM project. Higher value means better compression\n"
-              "but require more RAM usage and more processing time.")
-        )
-        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
-        self.compress_combo.addItems([str(i) for i in range(10)])
-
-        hlay1.addWidget(self.compress_label)
-        hlay1.addWidget(self.compress_combo)
-
-        self.proj_ois = OptionalInputSection(self.save_type_cb, [self.compress_label, self.compress_combo], True)
-
-        self.form_box_2 = QtWidgets.QFormLayout()
-        self.layout.addLayout(self.form_box_2)
-
-        self.layout.addStretch()
-
-        if sys.platform != 'win32':
-            self.portability_label.hide()
-            self.portability_cb.hide()
-
-
-class GerberGenPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gerber General Preferences", parent=parent)
-        super(GerberGenPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Gerber General")))
-
-        # ## Plot options
-        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.layout.addWidget(self.plot_options_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Solid CB
-        self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
-        self.solid_cb.setToolTip(
-            _("Solid color polygons.")
-        )
-        grid0.addWidget(self.solid_cb, 0, 0)
-
-        # Multicolored CB
-        self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
-        self.multicolored_cb.setToolTip(
-            _("Draw polygons in different colors.")
-        )
-        grid0.addWidget(self.multicolored_cb, 0, 1)
-
-        # Plot CB
-        self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
-        self.plot_options_label.setToolTip(
-            _("Plot (show) this object.")
-        )
-        grid0.addWidget(self.plot_cb, 0, 2)
-
-        # Number of circle steps for circular aperture linear approximation
-        self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
-        self.circle_steps_label.setToolTip(
-            _("The number of circle steps for Gerber \n"
-              "circular aperture linear approximation.")
-        )
-        self.circle_steps_entry = IntEntry()
-        grid0.addWidget(self.circle_steps_label, 1, 0)
-        grid0.addWidget(self.circle_steps_entry, 1, 1)
-
-        self.layout.addStretch()
-
-
-class GerberOptPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gerber Options Preferences", parent=parent)
-        super(GerberOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Gerber Options")))
-
-        # ## Isolation Routing
-        self.isolation_routing_label = QtWidgets.QLabel("<b>%s:</b>" % _("Isolation Routing"))
-        self.isolation_routing_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut outside polygons.")
-        )
-        self.layout.addWidget(self.isolation_routing_label)
-
-        # Cutting Tool Diameter
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        tdlabel.setToolTip(
-            _("Diameter of the cutting tool.")
-        )
-        grid0.addWidget(tdlabel, 0, 0)
-        self.iso_tool_dia_entry = LengthEntry()
-        grid0.addWidget(self.iso_tool_dia_entry, 0, 1)
-
-        # Nr of passes
-        passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
-        passlabel.setToolTip(
-            _("Width of the isolation gap in\n"
-              "number (integer) of tool widths.")
-        )
-        grid0.addWidget(passlabel, 1, 0)
-        self.iso_width_entry = FCSpinner()
-        self.iso_width_entry.setRange(1, 999)
-        grid0.addWidget(self.iso_width_entry, 1, 1)
-
-        # Pass overlap
-        overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
-        overlabel.setToolTip(
-            _("How much (fraction) of the tool width to overlap each tool pass.\n"
-              "Example:\n"
-              "A value here of 0.25 means an overlap of 25%% from the tool diameter found above.")
-        )
-        grid0.addWidget(overlabel, 2, 0)
-        self.iso_overlap_entry = FCDoubleSpinner()
-        self.iso_overlap_entry.set_precision(3)
-        self.iso_overlap_entry.setWrapping(True)
-        self.iso_overlap_entry.setRange(0.000, 0.999)
-        self.iso_overlap_entry.setSingleStep(0.1)
-        grid0.addWidget(self.iso_overlap_entry, 2, 1)
-
-        # Milling Type
-        milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
-        milling_type_label.setToolTip(
-            _("Milling type:\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-        grid0.addWidget(milling_type_label, 3, 0)
-        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conv.'), 'value': 'cv'}])
-        grid0.addWidget(self.milling_type_radio, 3, 1)
-
-        # Combine passes
-        self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
-        self.combine_passes_cb.setToolTip(
-            _("Combine all passes into one object")
-        )
-        grid0.addWidget(self.combine_passes_cb, 4, 0, 1, 2)
-
-        # ## Clear non-copper regions
-        self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
-        self.clearcopper_label.setToolTip(
-            _("Create polygons covering the\n"
-              "areas without copper on the PCB.\n"
-              "Equivalent to the inverse of this\n"
-              "object. Can be used to remove all\n"
-              "copper from a specified region.")
-        )
-        self.layout.addWidget(self.clearcopper_label)
-
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-
-        # Margin
-        bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
-        bmlabel.setToolTip(
-            _("Specify the edge of the PCB\n"
-              "by drawing a box around all\n"
-              "objects with this minimum\n"
-              "distance.")
-        )
-        grid1.addWidget(bmlabel, 0, 0)
-        self.noncopper_margin_entry = LengthEntry()
-        grid1.addWidget(self.noncopper_margin_entry, 0, 1)
-
-        # Rounded corners
-        self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
-        self.noncopper_rounded_cb.setToolTip(
-            _("Resulting geometry will have rounded corners.")
-        )
-        grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
-
-        # ## Bounding box
-        self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
-        self.layout.addWidget(self.boundingbox_label)
-
-        grid2 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid2)
-
-        bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
-        bbmargin.setToolTip(
-            _("Distance of the edges of the box\n"
-              "to the nearest polygon.")
-        )
-        grid2.addWidget(bbmargin, 0, 0)
-        self.bbmargin_entry = LengthEntry()
-        grid2.addWidget(self.bbmargin_entry, 0, 1)
-
-        self.bbrounded_cb = FCCheckBox(label='%s' % _("Rounded Geo"))
-        self.bbrounded_cb.setToolTip(
-            _("If the bounding box is \n"
-              "to have rounded corners\n"
-              "their radius is equal to\n"
-              "the margin.")
-        )
-        grid2.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
-        self.layout.addStretch()
+            self.pmap = QtGui.QPixmap('share/graylight12.png')
 
+        self.set_text_(text)
+        self.icon.setPixmap(self.pmap)
 
-class GerberAdvOptPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
-        super(GerberAdvOptPrefGroupUI, self).__init__(self)
 
-        self.setTitle(str(_("Gerber Adv. Options")))
+class FlatCAMSystemTray(QtWidgets.QSystemTrayIcon):
 
-        # ## Advanced Gerber Parameters
-        self.adv_param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
-        self.adv_param_label.setToolTip(
-            _("A list of Gerber advanced parameters.\n"
-              "Those parameters are available only for\n"
-              "Advanced App. Level.")
-        )
-        self.layout.addWidget(self.adv_param_label)
+    def __init__(self, app, icon, headless=None, parent=None):
+        # QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
+        super().__init__(icon, parent=parent)
+        self.app = app
 
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
+        menu = QtWidgets.QMenu(parent)
 
-        # Follow Attribute
-        self.follow_cb = FCCheckBox(label=_('"Follow"'))
-        self.follow_cb.setToolTip(
-            _("Generate a 'Follow' geometry.\n"
-              "This means that it will cut through\n"
-              "the middle of the trace.")
+        menu_runscript = QtWidgets.QAction(QtGui.QIcon('share/script14.png'), '%s' % _('Run Script ...'), self)
+        menu_runscript.setToolTip(
+            _("Will run the opened Tcl Script thus\n"
+              "enabling the automation of certain\n"
+              "functions of FlatCAM.")
         )
-        grid0.addWidget(self.follow_cb, 0, 0, 1, 2)
+        menu.addAction(menu_runscript)
 
-        # Aperture Table Visibility CB
-        self.aperture_table_visibility_cb = FCCheckBox(label=_('Table Show/Hide'))
-        self.aperture_table_visibility_cb.setToolTip(
-            _("Toggle the display of the Gerber Apertures Table.\n"
-              "Also, on hide, it will delete all mark shapes\n"
-              "that are drawn on canvas.")
+        menu.addSeparator()
 
-        )
-        grid0.addWidget(self.aperture_table_visibility_cb, 1, 0, 1, 2)
-
-        # Buffering Type
-        buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
-        buffering_label.setToolTip(
-            _("Buffering type:\n"
-              "- None --> best performance, fast file loading but no so good display\n"
-              "- Full --> slow file loading but good visuals. This is the default.\n"
-              "<<WARNING>>: Don't change this unless you know what you are doing !!!")
-        )
-        self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'},
-                                          {'label': _('Full'), 'value': 'full'}])
-        grid0.addWidget(buffering_label, 2, 0)
-        grid0.addWidget(self.buffering_radio, 2, 1)
-
-        # Simplification
-        self.simplify_cb = FCCheckBox(label=_('Simplify'))
-        self.simplify_cb.setToolTip(_("When checked all the Gerber polygons will be\n"
-                                      "loaded with simplification having a set tolerance."))
-        grid0.addWidget(self.simplify_cb, 3, 0, 1, 2)
-
-        # Simplification tolerance
-        self.simplification_tol_label = QtWidgets.QLabel(_('Tolerance'))
-        self.simplification_tol_label.setToolTip(_("Tolerance for poligon simplification."))
-
-        self.simplification_tol_spinner = FCDoubleSpinner()
-        self.simplification_tol_spinner.set_precision(5)
-        self.simplification_tol_spinner.setWrapping(True)
-        self.simplification_tol_spinner.setRange(0.00000, 0.01000)
-        self.simplification_tol_spinner.setSingleStep(0.0001)
-
-        grid0.addWidget(self.simplification_tol_label, 4, 0)
-        grid0.addWidget(self.simplification_tol_spinner, 4, 1)
-        self.ois_simplif = OptionalInputSection(self.simplify_cb,
-                                                [self.simplification_tol_label, self.simplification_tol_spinner],
-                                                logic=True)
-
-        # Scale Aperture Factor
-        # self.scale_aperture_label = QtWidgets.QLabel(_('Ap. Scale Factor:'))
-        # self.scale_aperture_label.setToolTip(
-        #     _("Change the size of the selected apertures.\n"
-        #     "Factor by which to multiply\n"
-        #     "geometric features of this object.")
-        # )
-        # grid0.addWidget(self.scale_aperture_label, 2, 0)
-        #
-        # self.scale_aperture_entry = FloatEntry2()
-        # grid0.addWidget(self.scale_aperture_entry, 2, 1)
-
-        # Buffer Aperture Factor
-        # self.buffer_aperture_label = QtWidgets.QLabel(_('Ap. Buffer Factor:'))
-        # self.buffer_aperture_label.setToolTip(
-        #     _("Change the size of the selected apertures.\n"
-        #     "Factor by which to expand/shrink\n"
-        #     "geometric features of this object.")
-        # )
-        # grid0.addWidget(self.buffer_aperture_label, 3, 0)
-        #
-        # self.buffer_aperture_entry = FloatEntry2()
-        # grid0.addWidget(self.buffer_aperture_entry, 3, 1)
-
-        self.layout.addStretch()
-
-
-class GerberExpPrefGroupUI(OptionsGroupUI):
+        if headless is None:
+            self.menu_open = menu.addMenu(QtGui.QIcon('share/folder32_bis.png'), _('Open'))
 
-    def __init__(self, parent=None):
-        super(GerberExpPrefGroupUI, self).__init__(self)
+            # Open Project ...
+            menu_openproject = QtWidgets.QAction(QtGui.QIcon('share/folder16.png'), _('Open Project ...'), self)
+            self.menu_open.addAction(menu_openproject)
+            self.menu_open.addSeparator()
 
-        self.setTitle(str(_("Gerber Export")))
+            # Open Gerber ...
+            menu_opengerber = QtWidgets.QAction(QtGui.QIcon('share/flatcam_icon24.png'),
+                                                _('Open &Gerber ...\tCTRL+G'), self)
+            self.menu_open.addAction(menu_opengerber)
 
-        # Plot options
-        self.export_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export Options"))
-        self.export_options_label.setToolTip(
-            _("The parameters set here are used in the file exported\n"
-              "when using the File -> Export -> Export Gerber menu entry.")
-        )
-        self.layout.addWidget(self.export_options_label)
+            # Open Excellon ...
+            menu_openexcellon = QtWidgets.QAction(QtGui.QIcon('share/open_excellon32.png'),
+                                                  _('Open &Excellon ...\tCTRL+E'), self)
+            self.menu_open.addAction(menu_openexcellon)
 
-        form = QtWidgets.QFormLayout()
-        self.layout.addLayout(form)
+            # Open G-Code ...
+            menu_opengcode = QtWidgets.QAction(QtGui.QIcon('share/code.png'), _('Open G-&Code ...'), self)
+            self.menu_open.addAction(menu_opengcode)
 
-        # Gerber Units
-        self.gerber_units_label = QtWidgets.QLabel('<b>%s:</b>' % _('Units'))
-        self.gerber_units_label.setToolTip(
-            _("The units used in the Gerber file.")
-        )
+            self.menu_open.addSeparator()
 
-        self.gerber_units_radio = RadioSet([{'label': _('INCH'), 'value': 'IN'},
-                                            {'label': _('MM'), 'value': 'MM'}])
-        self.gerber_units_radio.setToolTip(
-            _("The units used in the Gerber file.")
-        )
+            menu_openproject.triggered.connect(self.app.on_file_openproject)
+            menu_opengerber.triggered.connect(self.app.on_fileopengerber)
+            menu_openexcellon.triggered.connect(self.app.on_fileopenexcellon)
+            menu_opengcode.triggered.connect(self.app.on_fileopengcode)
 
-        form.addRow(self.gerber_units_label, self.gerber_units_radio)
+        exitAction = menu.addAction(_("Exit"))
+        exitAction.setIcon(QtGui.QIcon('share/power16.png'))
+        self.setContextMenu(menu)
 
-        # Gerber format
-        self.digits_label = QtWidgets.QLabel("<b>%s:</b>" % _("Int/Decimals"))
-        self.digits_label.setToolTip(
-            _("The number of digits in the whole part of the number\n"
-              "and in the fractional part of the number.")
-        )
+        menu_runscript.triggered.connect(lambda: self.app.on_filerunscript(
+            silent=True if self.app.cmd_line_headless == 1 else False))
 
-        hlay1 = QtWidgets.QHBoxLayout()
+        exitAction.triggered.connect(self.app.final_save)
 
-        self.format_whole_entry = IntEntry()
-        self.format_whole_entry.setMaxLength(1)
-        self.format_whole_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.format_whole_entry.setMinimumWidth(30)
-        self.format_whole_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the whole part of Gerber coordinates.")
-        )
-        hlay1.addWidget(self.format_whole_entry, QtCore.Qt.AlignLeft)
-
-        gerber_separator_label = QtWidgets.QLabel(':')
-        gerber_separator_label.setFixedWidth(5)
-        hlay1.addWidget(gerber_separator_label, QtCore.Qt.AlignLeft)
-
-        self.format_dec_entry = IntEntry()
-        self.format_dec_entry.setMaxLength(1)
-        self.format_dec_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.format_dec_entry.setMinimumWidth(30)
-        self.format_dec_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the decimal part of Gerber coordinates.")
-        )
-        hlay1.addWidget(self.format_dec_entry, QtCore.Qt.AlignLeft)
-        hlay1.addStretch()
-
-        form.addRow(self.digits_label, hlay1)
-
-        # Gerber Zeros
-        self.zeros_label = QtWidgets.QLabel('<b>%s:</b>' % _('Zeros'))
-        self.zeros_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.zeros_label.setToolTip(
-            _("This sets the type of Gerber zeros.\n"
-              "If LZ then Leading Zeros are removed and\n"
-              "Trailing Zeros are kept.\n"
-              "If TZ is checked then Trailing Zeros are removed\n"
-              "and Leading Zeros are kept.")
-        )
-
-        self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'L'},
-                                     {'label': _('TZ'), 'value': 'T'}])
-        self.zeros_radio.setToolTip(
-            _("This sets the type of Gerber zeros.\n"
-              "If LZ then Leading Zeros are removed and\n"
-              "Trailing Zeros are kept.\n"
-              "If TZ is checked then Trailing Zeros are removed\n"
-              "and Leading Zeros are kept.")
-        )
-
-        form.addRow(self.zeros_label, self.zeros_radio)
-
-        self.layout.addStretch()
-
-
-class GerberEditorPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
-        super(GerberEditorPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Gerber Editor")))
-
-        # Advanced Gerber Parameters
-        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.param_label.setToolTip(
-            _("A list of Gerber Editor parameters.")
-        )
-        self.layout.addWidget(self.param_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Selection Limit
-        self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
-        self.sel_limit_label.setToolTip(
-            _("Set the number of selected Gerber geometry\n"
-              "items above which the utility geometry\n"
-              "becomes just a selection rectangle.\n"
-              "Increases the performance when moving a\n"
-              "large number of geometric elements.")
-        )
-        self.sel_limit_entry = IntEntry()
-
-        grid0.addWidget(self.sel_limit_label, 0, 0)
-        grid0.addWidget(self.sel_limit_entry, 0, 1)
-
-        # New aperture code
-        self.addcode_entry_lbl = QtWidgets.QLabel('%s:' % _('New Aperture code'))
-        self.addcode_entry_lbl.setToolTip(
-            _("Code for the new aperture")
-        )
-
-        self.addcode_entry = FCEntry()
-        self.addcode_entry.setValidator(QtGui.QIntValidator(0, 99))
-
-        grid0.addWidget(self.addcode_entry_lbl, 1, 0)
-        grid0.addWidget(self.addcode_entry, 1, 1)
-
-        # New aperture size
-        self.addsize_entry_lbl = QtWidgets.QLabel('%s:' % _('New Aperture size'))
-        self.addsize_entry_lbl.setToolTip(
-            _("Size for the new aperture")
-        )
-
-        self.addsize_entry = FCEntry()
-        self.addsize_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
-
-        grid0.addWidget(self.addsize_entry_lbl, 2, 0)
-        grid0.addWidget(self.addsize_entry, 2, 1)
-
-        # New aperture type
-        self.addtype_combo_lbl = QtWidgets.QLabel('%s:' % _('New Aperture type'))
-        self.addtype_combo_lbl.setToolTip(
-            _("Type for the new aperture.\n"
-              "Can be 'C', 'R' or 'O'.")
-        )
-
-        self.addtype_combo = FCComboBox()
-        self.addtype_combo.addItems(['C', 'R', 'O'])
-
-        grid0.addWidget(self.addtype_combo_lbl, 3, 0)
-        grid0.addWidget(self.addtype_combo, 3, 1)
-
-        # Number of pads in a pad array
-        self.grb_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of pads'))
-        self.grb_array_size_label.setToolTip(
-            _("Specify how many pads to be in the array.")
-        )
-
-        self.grb_array_size_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_array_size_label, 4, 0)
-        grid0.addWidget(self.grb_array_size_entry, 4, 1)
-
-        self.adddim_label = QtWidgets.QLabel('%s:' % _('Aperture Dimensions'))
-        self.adddim_label.setToolTip(
-            _("Diameters of the cutting tools, separated by ','")
-        )
-        grid0.addWidget(self.adddim_label, 5, 0)
-        self.adddim_entry = FCEntry()
-        grid0.addWidget(self.adddim_entry, 5, 1)
-
-        self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))
-        grid0.addWidget(self.grb_array_linear_label, 6, 0, 1, 2)
-
-        # Linear Pad Array direction
-        self.grb_axis_label = QtWidgets.QLabel('%s:' %  _('Linear Dir.'))
-        self.grb_axis_label.setToolTip(
-            _("Direction on which the linear array is oriented:\n"
-              "- 'X' - horizontal axis \n"
-              "- 'Y' - vertical axis or \n"
-              "- 'Angle' - a custom angle for the array inclination")
-        )
-
-        self.grb_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
-                                        {'label': _('Y'), 'value': 'Y'},
-                                        {'label': _('Angle'), 'value': 'A'}])
-
-        grid0.addWidget(self.grb_axis_label, 7, 0)
-        grid0.addWidget(self.grb_axis_radio, 7, 1)
-
-        # Linear Pad Array pitch distance
-        self.grb_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
-        self.grb_pitch_label.setToolTip(
-            _("Pitch = Distance between elements of the array.")
-        )
-        # self.drill_pitch_label.setMinimumWidth(100)
-        self.grb_pitch_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_pitch_label, 8, 0)
-        grid0.addWidget(self.grb_pitch_entry, 8, 1)
-
-        # Linear Pad Array custom angle
-        self.grb_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
-        self.grb_angle_label.setToolTip(
-            _("Angle at which each element in circular array is placed.")
-        )
-        self.grb_angle_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_angle_label, 9, 0)
-        grid0.addWidget(self.grb_angle_entry, 9, 1)
-
-        self.grb_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Circular Pad Array'))
-        grid0.addWidget(self.grb_array_circ_label, 10, 0, 1, 2)
-
-        # Circular Pad Array direction
-        self.grb_circular_direction_label = QtWidgets.QLabel('%s:' % _('Circular Dir.'))
-        self.grb_circular_direction_label.setToolTip(
-            _("Direction for circular array.\n"
-              "Can be CW = clockwise or CCW = counter clockwise.")
-        )
-
-        self.grb_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
-                                                {'label': _('CCW'), 'value': 'CCW'}])
-
-        grid0.addWidget(self.grb_circular_direction_label, 11, 0)
-        grid0.addWidget(self.grb_circular_dir_radio, 11, 1)
-
-        # Circular Pad Array Angle
-        self.grb_circular_angle_label = QtWidgets.QLabel('%s:' % _('Circ. Angle'))
-        self.grb_circular_angle_label.setToolTip(
-            _("Angle at which each element in circular array is placed.")
-        )
-        self.grb_circular_angle_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_circular_angle_label, 12, 0)
-        grid0.addWidget(self.grb_circular_angle_entry, 12, 1)
-
-        self.grb_array_tools_b_label = QtWidgets.QLabel('<b>%s:</b>' % _('Buffer Tool'))
-        grid0.addWidget(self.grb_array_tools_b_label, 13, 0, 1, 2)
-
-        # Buffer Distance
-        self.grb_buff_label = QtWidgets.QLabel('%s:' % _('Buffer distance'))
-        self.grb_buff_label.setToolTip(
-            _("Distance at which to buffer the Gerber element.")
-        )
-        self.grb_buff_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_buff_label, 14, 0)
-        grid0.addWidget(self.grb_buff_entry, 14, 1)
-
-        self.grb_array_tools_s_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scale Tool'))
-        grid0.addWidget(self.grb_array_tools_s_label, 15, 0, 1, 2)
-
-        # Scale Factor
-        self.grb_scale_label = QtWidgets.QLabel('%s:' % _('Scale factor'))
-        self.grb_scale_label.setToolTip(
-            _("Factor to scale the Gerber element.")
-        )
-        self.grb_scale_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_scale_label, 16, 0)
-        grid0.addWidget(self.grb_scale_entry, 16, 1)
-
-        self.grb_array_tools_ma_label = QtWidgets.QLabel('<b>%s:</b>' % _('Mark Area Tool'))
-        grid0.addWidget(self.grb_array_tools_ma_label, 17, 0, 1, 2)
-
-        # Mark area Tool low threshold
-        self.grb_ma_low_label = QtWidgets.QLabel('%s:' % _('Threshold low'))
-        self.grb_ma_low_label.setToolTip(
-            _("Threshold value under which the apertures are not marked.")
-        )
-        self.grb_ma_low_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_ma_low_label, 18, 0)
-        grid0.addWidget(self.grb_ma_low_entry, 18, 1)
-
-        # Mark area Tool high threshold
-        self.grb_ma_high_label = QtWidgets.QLabel('%s:' % _('Threshold low'))
-        self.grb_ma_high_label.setToolTip(
-            _("Threshold value over which the apertures are not marked.")
-        )
-        self.grb_ma_high_entry = LengthEntry()
-
-        grid0.addWidget(self.grb_ma_high_label, 19, 0)
-        grid0.addWidget(self.grb_ma_high_entry, 19, 1)
-
-        self.layout.addStretch()
-
-
-class ExcellonGenPrefGroupUI(OptionsGroupUI):
-
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Excellon Options", parent=parent)
-        super(ExcellonGenPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Excellon General")))
-
-        # Plot options
-        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.layout.addWidget(self.plot_options_label)
-
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-
-        self.plot_cb = FCCheckBox(label=_('Plot'))
-        self.plot_cb.setToolTip(
-            "Plot (show) this object."
-        )
-        grid1.addWidget(self.plot_cb, 0, 0)
-
-        self.solid_cb = FCCheckBox(label=_('Solid'))
-        self.solid_cb.setToolTip(
-            "Plot as solid circles."
-        )
-        grid1.addWidget(self.solid_cb, 0, 1)
-
-        # Excellon format
-        self.excellon_format_label = QtWidgets.QLabel("<b>%s:</b>" % _("Excellon Format"))
-        self.excellon_format_label.setToolTip(
-            _("The NC drill files, usually named Excellon files\n"
-              "are files that can be found in different formats.\n"
-              "Here we set the format used when the provided\n"
-              "coordinates are not using period.\n"
-              "\n"
-              "Possible presets:\n"
-              "\n"
-              "PROTEUS 3:3 MM LZ\n"
-              "DipTrace 5:2 MM TZ\n"
-              "DipTrace 4:3 MM LZ\n"
-              "\n"
-              "EAGLE 3:3 MM TZ\n"
-              "EAGLE 4:3 MM TZ\n"
-              "EAGLE 2:5 INCH TZ\n"
-              "EAGLE 3:5 INCH TZ\n"
-              "\n"
-              "ALTIUM 2:4 INCH LZ\n"
-              "Sprint Layout 2:4 INCH LZ"
-              "\n"
-              "KiCAD 3:5 INCH TZ")
-        )
-        self.layout.addWidget(self.excellon_format_label)
-
-        hlay1 = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay1)
-        self.excellon_format_in_label = QtWidgets.QLabel('%s:' % _("INCH"))
-        self.excellon_format_in_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.excellon_format_in_label.setToolTip(
-            _("Default values for INCH are 2:4"))
-        hlay1.addWidget(self.excellon_format_in_label, QtCore.Qt.AlignLeft)
-
-        self.excellon_format_upper_in_entry = IntEntry()
-        self.excellon_format_upper_in_entry.setMaxLength(1)
-        self.excellon_format_upper_in_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.excellon_format_upper_in_entry.setMinimumWidth(30)
-        self.excellon_format_upper_in_entry.setToolTip(
-           _("This numbers signify the number of digits in\n"
-             "the whole part of Excellon coordinates.")
-        )
-        hlay1.addWidget(self.excellon_format_upper_in_entry, QtCore.Qt.AlignLeft)
-
-        excellon_separator_in_label = QtWidgets.QLabel(':')
-        excellon_separator_in_label.setFixedWidth(5)
-        hlay1.addWidget(excellon_separator_in_label, QtCore.Qt.AlignLeft)
-
-        self.excellon_format_lower_in_entry = IntEntry()
-        self.excellon_format_lower_in_entry.setMaxLength(1)
-        self.excellon_format_lower_in_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.excellon_format_lower_in_entry.setMinimumWidth(30)
-        self.excellon_format_lower_in_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the decimal part of Excellon coordinates.")
-        )
-        hlay1.addWidget(self.excellon_format_lower_in_entry, QtCore.Qt.AlignLeft)
-        hlay1.addStretch()
-
-        hlay2 = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay2)
-        self.excellon_format_mm_label = QtWidgets.QLabel('%s:' % _("METRIC"))
-        self.excellon_format_mm_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.excellon_format_mm_label.setToolTip(
-            _("Default values for METRIC are 3:3"))
-        hlay2.addWidget(self.excellon_format_mm_label, QtCore.Qt.AlignLeft)
-
-        self.excellon_format_upper_mm_entry = IntEntry()
-        self.excellon_format_upper_mm_entry.setMaxLength(1)
-        self.excellon_format_upper_mm_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.excellon_format_upper_mm_entry.setMinimumWidth(30)
-        self.excellon_format_upper_mm_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the whole part of Excellon coordinates.")
-        )
-        hlay2.addWidget(self.excellon_format_upper_mm_entry, QtCore.Qt.AlignLeft)
-
-        excellon_separator_mm_label = QtWidgets.QLabel(':')
-        excellon_separator_mm_label.setFixedWidth(5)
-        hlay2.addWidget(excellon_separator_mm_label, QtCore.Qt.AlignLeft)
-
-        self.excellon_format_lower_mm_entry = IntEntry()
-        self.excellon_format_lower_mm_entry.setMaxLength(1)
-        self.excellon_format_lower_mm_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.excellon_format_lower_mm_entry.setMinimumWidth(30)
-        self.excellon_format_lower_mm_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the decimal part of Excellon coordinates.")
-        )
-        hlay2.addWidget(self.excellon_format_lower_mm_entry, QtCore.Qt.AlignLeft)
-        hlay2.addStretch()
-
-        grid2 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid2)
-
-        self.excellon_zeros_label = QtWidgets.QLabel('%s:' % _('Default <b>Zeros</b>'))
-        self.excellon_zeros_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.excellon_zeros_label.setToolTip(
-            _("This sets the type of Excellon zeros.\n"
-              "If LZ then Leading Zeros are kept and\n"
-              "Trailing Zeros are removed.\n"
-              "If TZ is checked then Trailing Zeros are kept\n"
-              "and Leading Zeros are removed.")
-        )
-        grid2.addWidget(self.excellon_zeros_label, 0, 0)
-
-        self.excellon_zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'L'},
-                                              {'label': _('TZ'), 'value': 'T'}])
-        self.excellon_zeros_radio.setToolTip(
-            _("This sets the default type of Excellon zeros.\n"
-              "If it is not detected in the parsed file the value here\n"
-              "will be used."
-              "If LZ then Leading Zeros are kept and\n"
-              "Trailing Zeros are removed.\n"
-              "If TZ is checked then Trailing Zeros are kept\n"
-              "and Leading Zeros are removed.")
-        )
-        grid2.addWidget(self.excellon_zeros_radio, 0, 1)
-
-        self.excellon_units_label = QtWidgets.QLabel('%s:' % _('Default <b>Units</b>'))
-        self.excellon_units_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.excellon_units_label.setToolTip(
-            _("This sets the default units of Excellon files.\n"
-              "If it is not detected in the parsed file the value here\n"
-              "will be used."
-              "Some Excellon files don't have an header\n"
-              "therefore this parameter will be used.")
-        )
-        grid2.addWidget(self.excellon_units_label, 1, 0)
-
-        self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
-                                              {'label': _('MM'), 'value': 'METRIC'}])
-        self.excellon_units_radio.setToolTip(
-            _("This sets the units of Excellon files.\n"
-              "Some Excellon files don't have an header\n"
-              "therefore this parameter will be used.")
-        )
-        grid2.addWidget(self.excellon_units_radio, 1, 1)
-
-        self.update_excellon_cb = FCCheckBox(label=_('Update Export settings'))
-        self.update_excellon_cb.setToolTip(
-            "If checked, the Excellon Export settings will be updated with the ones above."
-        )
-        grid2.addWidget(self.update_excellon_cb, 2, 0, 1, 2)
-
-        grid2.addWidget(QtWidgets.QLabel(""), 3, 0)
-
-        self.excellon_general_label = QtWidgets.QLabel("<b>%s:</b>" % _("Excellon Optimization"))
-        grid2.addWidget(self.excellon_general_label, 4, 0, 1, 2)
-
-        self.excellon_optimization_label = QtWidgets.QLabel(_('Algorithm:'))
-        self.excellon_optimization_label.setToolTip(
-            _("This sets the optimization type for the Excellon drill path.\n"
-              "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
-              "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
-              "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
-              "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
-              "drill path optimization.\n"
-              "\n"
-              "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
-              "Travelling Salesman algorithm for path optimization.")
-        )
-        grid2.addWidget(self.excellon_optimization_label, 5, 0)
-
-        self.excellon_optimization_radio = RadioSet([{'label': _('MetaHeuristic'), 'value': 'M'},
-                                                     {'label': _('Basic'), 'value': 'B'},
-                                                     {'label': _('TSA'), 'value': 'T'}],
-                                                    orientation='vertical', stretch=False)
-        self.excellon_optimization_radio.setToolTip(
-            _("This sets the optimization type for the Excellon drill path.\n"
-              "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
-              "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
-              "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
-              "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
-              "drill path optimization.\n"
-              "\n"
-              "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
-              "Travelling Salesman algorithm for path optimization.")
-        )
-        grid2.addWidget(self.excellon_optimization_radio, 5, 1)
-
-        self.optimization_time_label = QtWidgets.QLabel('%s:' % _('Optimization Time'))
-        self.optimization_time_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.optimization_time_label.setToolTip(
-            _("When OR-Tools Metaheuristic (MH) is enabled there is a\n"
-              "maximum threshold for how much time is spent doing the\n"
-              "path optimization. This max duration is set here.\n"
-              "In seconds.")
-
-        )
-        grid2.addWidget(self.optimization_time_label, 6, 0)
-
-        self.optimization_time_entry = IntEntry()
-        self.optimization_time_entry.setValidator(QtGui.QIntValidator(0, 999))
-        grid2.addWidget(self.optimization_time_entry, 6, 1)
-
-        current_platform = platform.architecture()[0]
-        if current_platform == '64bit':
-            self.excellon_optimization_label.setDisabled(False)
-            self.excellon_optimization_radio.setDisabled(False)
-            self.optimization_time_label.setDisabled(False)
-            self.optimization_time_entry.setDisabled(False)
-            self.excellon_optimization_radio.activated_custom.connect(self.optimization_selection)
-
-        else:
-            self.excellon_optimization_label.setDisabled(True)
-            self.excellon_optimization_radio.setDisabled(True)
-            self.optimization_time_label.setDisabled(True)
-            self.optimization_time_entry.setDisabled(True)
-
-        self.layout.addStretch()
-
-    def optimization_selection(self):
-        if self.excellon_optimization_radio.get_value() == 'M':
-            self.optimization_time_label.setDisabled(False)
-            self.optimization_time_entry.setDisabled(False)
-        else:
-            self.optimization_time_label.setDisabled(True)
-            self.optimization_time_entry.setDisabled(True)
-
-
-class ExcellonOptPrefGroupUI(OptionsGroupUI):
-
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Excellon Options", parent=parent)
-        super(ExcellonOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Excellon Options")))
-
-        # ## Create CNC Job
-        self.cncjob_label = QtWidgets.QLabel('<b>%s</b>' % _('Create CNC Job'))
-        self.cncjob_label.setToolTip(
-            _("Parameters used to create a CNC Job object\n"
-              "for this drill object.")
-        )
-        self.layout.addWidget(self.cncjob_label)
-
-        grid2 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid2)
-
-        # Cut Z
-        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        cutzlabel.setToolTip(
-            _("Drill depth (negative)\n"
-              "below the copper surface.")
-        )
-        grid2.addWidget(cutzlabel, 0, 0)
-        self.cutz_entry = LengthEntry()
-        grid2.addWidget(self.cutz_entry, 0, 1)
-
-        # Travel Z
-        travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
-        travelzlabel.setToolTip(
-            _("Tool height when travelling\n"
-              "across the XY plane.")
-        )
-        grid2.addWidget(travelzlabel, 1, 0)
-        self.travelz_entry = LengthEntry()
-        grid2.addWidget(self.travelz_entry, 1, 1)
-
-        # Tool change:
-        toolchlabel = QtWidgets.QLabel('%s:' % _("Tool change"))
-        toolchlabel.setToolTip(
-            _("Include tool-change sequence\n"
-              "in G-Code (Pause for tool change).")
-        )
-        self.toolchange_cb = FCCheckBox()
-        grid2.addWidget(toolchlabel, 2, 0)
-        grid2.addWidget(self.toolchange_cb, 2, 1)
-
-        toolchangezlabel = QtWidgets.QLabel('%s:' % _('Toolchange Z'))
-        toolchangezlabel.setToolTip(
-            _("Z-axis position (height) for\n"
-              "tool change.")
-        )
-        grid2.addWidget(toolchangezlabel, 3, 0)
-        self.toolchangez_entry = LengthEntry()
-        grid2.addWidget(self.toolchangez_entry, 3, 1)
-
-        # End Move Z
-        endzlabel = QtWidgets.QLabel('%s:' % _('End move Z'))
-        endzlabel.setToolTip(
-            _("Height of the tool after\n"
-              "the last move at the end of the job.")
-        )
-        grid2.addWidget(endzlabel, 4, 0)
-        self.eendz_entry = LengthEntry()
-        grid2.addWidget(self.eendz_entry, 4, 1)
-
-        # Feedrate Z
-        frlabel = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
-        frlabel.setToolTip(
-            _("Tool speed while drilling\n"
-              "(in units per minute).\n"
-              "So called 'Plunge' feedrate.\n"
-              "This is for linear move G01.")
-        )
-        grid2.addWidget(frlabel, 5, 0)
-        self.feedrate_entry = LengthEntry()
-        grid2.addWidget(self.feedrate_entry, 5, 1)
-
-        # Spindle speed
-        spdlabel = QtWidgets.QLabel('%s:' % _('Spindle Speed'))
-        spdlabel.setToolTip(
-            _("Speed of the spindle\n"
-              "in RPM (optional)")
-        )
-        grid2.addWidget(spdlabel, 6, 0)
-        self.spindlespeed_entry = IntEntry(allow_empty=True)
-        grid2.addWidget(self.spindlespeed_entry, 6, 1)
-
-        # Dwell
-        dwelllabel = QtWidgets.QLabel('%s:' % _('Dwell'))
-        dwelllabel.setToolTip(
-            _("Pause to allow the spindle to reach its\n"
-              "speed before cutting.")
-        )
-        dwelltime = QtWidgets.QLabel('%s:' % _('Duration'))
-        dwelltime.setToolTip(
-            _("Number of time units for spindle to dwell.")
-        )
-        self.dwell_cb = FCCheckBox()
-        self.dwelltime_entry = FCEntry()
-
-        grid2.addWidget(dwelllabel, 7, 0)
-        grid2.addWidget(self.dwell_cb, 7, 1)
-        grid2.addWidget(dwelltime, 8, 0)
-        grid2.addWidget(self.dwelltime_entry, 8, 1)
-
-        self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
-
-        # postprocessor selection
-        pp_excellon_label = QtWidgets.QLabel('%s:' % _("Postprocessor"))
-        pp_excellon_label.setToolTip(
-            _("The postprocessor JSON file that dictates\n"
-              "Gcode output.")
-        )
-        grid2.addWidget(pp_excellon_label, 9, 0)
-        self.pp_excellon_name_cb = FCComboBox()
-        self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
-        grid2.addWidget(self.pp_excellon_name_cb, 9, 1)
-
-        # ### Choose what to use for Gcode creation: Drills, Slots or Both
-        excellon_gcode_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Gcode'))
-        excellon_gcode_type_label.setToolTip(
-            _("Choose what to use for GCode generation:\n"
-              "'Drills', 'Slots' or 'Both'.\n"
-              "When choosing 'Slots' or 'Both', slots will be\n"
-              "converted to drills.")
-        )
-        self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
-                                                   {'label': 'Slots', 'value': 'slots'},
-                                                   {'label': 'Both', 'value': 'both'}])
-        grid2.addWidget(excellon_gcode_type_label, 10, 0)
-        grid2.addWidget(self.excellon_gcode_type_radio, 10, 1)
-
-        # until I decide to implement this feature those remain disabled
-        excellon_gcode_type_label.hide()
-        self.excellon_gcode_type_radio.setVisible(False)
-
-        # ### Milling Holes ## ##
-        self.mill_hole_label = QtWidgets.QLabel('<b>%s</b>' % _('Mill Holes'))
-        self.mill_hole_label.setToolTip(
-            _("Create Geometry for milling holes.")
-        )
-        grid2.addWidget(self.mill_hole_label, 11, 0, 1, 2)
-
-        tdlabel = QtWidgets.QLabel('%s:' % _('Drill Tool dia'))
-        tdlabel.setToolTip(
-            _("Diameter of the cutting tool.")
-        )
-        grid2.addWidget(tdlabel, 12, 0)
-        self.tooldia_entry = LengthEntry()
-        grid2.addWidget(self.tooldia_entry, 12, 1)
-        stdlabel = QtWidgets.QLabel('%s:' % _('Slot Tool dia'))
-        stdlabel.setToolTip(
-            _("Diameter of the cutting tool\n"
-              "when milling slots.")
-        )
-        grid2.addWidget(stdlabel, 13, 0)
-        self.slot_tooldia_entry = LengthEntry()
-        grid2.addWidget(self.slot_tooldia_entry, 13, 1)
-
-        grid4 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid4)
-
-        # Adding the Excellon Format Defaults Button
-        self.excellon_defaults_button = QtWidgets.QPushButton()
-        self.excellon_defaults_button.setText(str(_("Defaults")))
-        self.excellon_defaults_button.setMinimumWidth(80)
-        grid4.addWidget(self.excellon_defaults_button, 0, 0, QtCore.Qt.AlignRight)
-
-        self.layout.addStretch()
-
-
-class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
-
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Excellon Advanced Options", parent=parent)
-        super(ExcellonAdvOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Excellon Adv. Options")))
-
-        # #######################
-        # ## ADVANCED OPTIONS ###
-        # #######################
-
-        self.exc_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
-        self.exc_label.setToolTip(
-            _("A list of Excellon advanced parameters.\n"
-              "Those parameters are available only for\n"
-              "Advanced App. Level.")
-        )
-        self.layout.addWidget(self.exc_label)
-
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-
-        offsetlabel = QtWidgets.QLabel('%s:' % _('Offset Z'))
-        offsetlabel.setToolTip(
-            _("Some drill bits (the larger ones) need to drill deeper\n"
-              "to create the desired exit hole diameter due of the tip shape.\n"
-              "The value here can compensate the Cut Z parameter."))
-        grid1.addWidget(offsetlabel, 0, 0)
-        self.offset_entry = LengthEntry()
-        grid1.addWidget(self.offset_entry, 0, 1)
-
-        toolchange_xy_label = QtWidgets.QLabel('%s:' % _('Toolchange X,Y'))
-        toolchange_xy_label.setToolTip(
-            _("Toolchange X,Y position.")
-        )
-        grid1.addWidget(toolchange_xy_label, 1, 0)
-        self.toolchangexy_entry = FCEntry()
-        grid1.addWidget(self.toolchangexy_entry, 1, 1)
-
-        startzlabel = QtWidgets.QLabel('%s:' % _('Start move Z'))
-        startzlabel.setToolTip(
-            _("Height of the tool just after start.\n"
-              "Delete the value if you don't need this feature.")
-        )
-        grid1.addWidget(startzlabel, 2, 0)
-        self.estartz_entry = FloatEntry()
-        grid1.addWidget(self.estartz_entry, 2, 1)
-
-        # Feedrate Rapids
-        fr_rapid_label = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
-        fr_rapid_label.setToolTip(
-            _("Tool speed while drilling\n"
-              "(in units per minute).\n"
-              "This is for the rapid move G00.\n"
-              "It is useful only for Marlin,\n"
-              "ignore for any other cases.")
-        )
-        grid1.addWidget(fr_rapid_label, 3, 0)
-        self.feedrate_rapid_entry = LengthEntry()
-        grid1.addWidget(self.feedrate_rapid_entry, 3, 1)
-
-        # Probe depth
-        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
-        self.pdepth_label.setToolTip(
-            _("The maximum depth that the probe is allowed\n"
-              "to probe. Negative value, in current units.")
-        )
-        grid1.addWidget(self.pdepth_label, 4, 0)
-        self.pdepth_entry = FCEntry()
-        grid1.addWidget(self.pdepth_entry, 4, 1)
-
-        # Probe feedrate
-        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
-        self.feedrate_probe_label.setToolTip(
-           _("The feedrate used while the probe is probing.")
-        )
-        grid1.addWidget(self.feedrate_probe_label, 5, 0)
-        self.feedrate_probe_entry = FCEntry()
-        grid1.addWidget(self.feedrate_probe_entry, 5, 1)
-
-        # Spindle direction
-        spindle_dir_label = QtWidgets.QLabel('%s:' % _('Spindle dir.'))
-        spindle_dir_label.setToolTip(
-            _("This sets the direction that the spindle is rotating.\n"
-              "It can be either:\n"
-              "- CW = clockwise or\n"
-              "- CCW = counter clockwise")
-        )
-
-        self.spindledir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
-                                          {'label': _('CCW'), 'value': 'CCW'}])
-        grid1.addWidget(spindle_dir_label, 6, 0)
-        grid1.addWidget(self.spindledir_radio, 6, 1)
-
-        fplungelabel = QtWidgets.QLabel('%s:' % _('Fast Plunge'))
-        fplungelabel.setToolTip(
-            _("By checking this, the vertical move from\n"
-              "Z_Toolchange to Z_move is done with G0,\n"
-              "meaning the fastest speed available.\n"
-              "WARNING: the move is done at Toolchange X,Y coords.")
-        )
-        self.fplunge_cb = FCCheckBox()
-        grid1.addWidget(fplungelabel, 7, 0)
-        grid1.addWidget(self.fplunge_cb, 7, 1)
-
-        fretractlabel = QtWidgets.QLabel('%s:' % _('Fast Retract'))
-        fretractlabel.setToolTip(
-            _("Exit hole strategy.\n"
-              " - When uncheked, while exiting the drilled hole the drill bit\n"
-              "will travel slow, with set feedrate (G1), up to zero depth and then\n"
-              "travel as fast as possible (G0) to the Z Move (travel height).\n"
-              " - When checked the travel from Z cut (cut depth) to Z_move\n"
-              "(travel height) is done as fast as possible (G0) in one move.")
-        )
-        self.fretract_cb = FCCheckBox()
-        grid1.addWidget(fretractlabel, 8, 0)
-        grid1.addWidget(self.fretract_cb, 8, 1)
-
-        self.layout.addStretch()
-
-
-class ExcellonExpPrefGroupUI(OptionsGroupUI):
-
-    def __init__(self, parent=None):
-        super(ExcellonExpPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Excellon Export")))
-
-        # Plot options
-        self.export_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export Options"))
-        self.export_options_label.setToolTip(
-            _("The parameters set here are used in the file exported\n"
-              "when using the File -> Export -> Export Excellon menu entry.")
-        )
-        self.layout.addWidget(self.export_options_label)
-
-        form = QtWidgets.QFormLayout()
-        self.layout.addLayout(form)
-
-        # Excellon Units
-        self.excellon_units_label = QtWidgets.QLabel('<b>%s:</b>' % _('Units'))
-        self.excellon_units_label.setToolTip(
-            _("The units used in the Excellon file.")
-        )
-
-        self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
-                                              {'label': _('MM'), 'value': 'METRIC'}])
-        self.excellon_units_radio.setToolTip(
-            _("The units used in the Excellon file.")
-        )
-
-        form.addRow(self.excellon_units_label, self.excellon_units_radio)
-
-        # Excellon non-decimal format
-        self.digits_label = QtWidgets.QLabel("<b>%s:</b>" % _("Int/Decimals"))
-        self.digits_label.setToolTip(
-            _("The NC drill files, usually named Excellon files\n"
-              "are files that can be found in different formats.\n"
-              "Here we set the format used when the provided\n"
-              "coordinates are not using period.")
-        )
-
-        hlay1 = QtWidgets.QHBoxLayout()
-
-        self.format_whole_entry = IntEntry()
-        self.format_whole_entry.setMaxLength(1)
-        self.format_whole_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.format_whole_entry.setMinimumWidth(30)
-        self.format_whole_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the whole part of Excellon coordinates.")
-        )
-        hlay1.addWidget(self.format_whole_entry, QtCore.Qt.AlignLeft)
-
-        excellon_separator_label = QtWidgets.QLabel(':')
-        excellon_separator_label.setFixedWidth(5)
-        hlay1.addWidget(excellon_separator_label, QtCore.Qt.AlignLeft)
-
-        self.format_dec_entry = IntEntry()
-        self.format_dec_entry.setMaxLength(1)
-        self.format_dec_entry.setAlignment(QtCore.Qt.AlignRight)
-        self.format_dec_entry.setMinimumWidth(30)
-        self.format_dec_entry.setToolTip(
-            _("This numbers signify the number of digits in\n"
-              "the decimal part of Excellon coordinates.")
-        )
-        hlay1.addWidget(self.format_dec_entry, QtCore.Qt.AlignLeft)
-        hlay1.addStretch()
-
-        form.addRow(self.digits_label, hlay1)
-
-        # Select the Excellon Format
-        self.format_label = QtWidgets.QLabel("<b>%s:</b>" % _("Format"))
-        self.format_label.setToolTip(
-            _("Select the kind of coordinates format used.\n"
-              "Coordinates can be saved with decimal point or without.\n"
-              "When there is no decimal point, it is required to specify\n"
-              "the number of digits for integer part and the number of decimals.\n"
-              "Also it will have to be specified if LZ = leading zeros are kept\n"
-              "or TZ = trailing zeros are kept.")
-        )
-        self.format_radio = RadioSet([{'label': _('Decimal'), 'value': 'dec'},
-                                      {'label': _('No-Decimal'), 'value': 'ndec'}])
-        self.format_radio.setToolTip(
-            _("Select the kind of coordinates format used.\n"
-              "Coordinates can be saved with decimal point or without.\n"
-              "When there is no decimal point, it is required to specify\n"
-              "the number of digits for integer part and the number of decimals.\n"
-              "Also it will have to be specified if LZ = leading zeros are kept\n"
-              "or TZ = trailing zeros are kept.")
-        )
-
-        form.addRow(self.format_label, self.format_radio)
-
-        # Excellon Zeros
-        self.zeros_label = QtWidgets.QLabel('<b>%s:</b>' % _('Zeros'))
-        self.zeros_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.zeros_label.setToolTip(
-            _("This sets the type of Excellon zeros.\n"
-              "If LZ then Leading Zeros are kept and\n"
-              "Trailing Zeros are removed.\n"
-              "If TZ is checked then Trailing Zeros are kept\n"
-              "and Leading Zeros are removed.")
-        )
-
-        self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
-                                     {'label': _('TZ'), 'value': 'TZ'}])
-        self.zeros_radio.setToolTip(
-            _("This sets the default type of Excellon zeros.\n"
-              "If LZ then Leading Zeros are kept and\n"
-              "Trailing Zeros are removed.\n"
-              "If TZ is checked then Trailing Zeros are kept\n"
-              "and Leading Zeros are removed.")
-        )
-
-        form.addRow(self.zeros_label, self.zeros_radio)
-
-        # Slot type
-        self.slot_type_label = QtWidgets.QLabel('<b>%s:</b>' % _('Slot type'))
-        self.slot_type_label.setAlignment(QtCore.Qt.AlignLeft)
-        self.slot_type_label.setToolTip(
-            _("This sets how the slots will be exported.\n"
-              "If ROUTED then the slots will be routed\n"
-              "using M15/M16 commands.\n"
-              "If DRILLED(G85) the slots will be exported\n"
-              "using the Drilled slot command (G85).")
-        )
-
-        self.slot_type_radio = RadioSet([{'label': _('Routed'), 'value': 'routing'},
-                                         {'label': _('Drilled(G85)'), 'value': 'drilling'}])
-        self.slot_type_radio.setToolTip(
-            _("This sets how the slots will be exported.\n"
-              "If ROUTED then the slots will be routed\n"
-              "using M15/M16 commands.\n"
-              "If DRILLED(G85) the slots will be exported\n"
-              "using the Drilled slot command (G85).")
-        )
-
-        form.addRow(self.slot_type_label, self.slot_type_radio)
-
-        self.layout.addStretch()
-        self.format_radio.activated_custom.connect(self.optimization_selection)
-
-    def optimization_selection(self):
-        if self.format_radio.get_value() == 'dec':
-            self.zeros_label.setDisabled(True)
-            self.zeros_radio.setDisabled(True)
-        else:
-            self.zeros_label.setDisabled(False)
-            self.zeros_radio.setDisabled(False)
-
-
-class ExcellonEditorPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        super(ExcellonEditorPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Excellon Editor")))
-
-        # Excellon Editor Parameters
-        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.param_label.setToolTip(
-            _("A list of Excellon Editor parameters.")
-        )
-        self.layout.addWidget(self.param_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Selection Limit
-        self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
-        self.sel_limit_label.setToolTip(
-            _("Set the number of selected Excellon geometry\n"
-              "items above which the utility geometry\n"
-              "becomes just a selection rectangle.\n"
-              "Increases the performance when moving a\n"
-              "large number of geometric elements.")
-        )
-        self.sel_limit_entry = IntEntry()
-
-        grid0.addWidget(self.sel_limit_label, 0, 0)
-        grid0.addWidget(self.sel_limit_entry, 0, 1)
-
-        # New tool diameter
-        self.addtool_entry_lbl = QtWidgets.QLabel('%s:' % _('New Tool Dia'))
-        self.addtool_entry_lbl.setToolTip(
-            _("Diameter for the new tool")
-        )
-
-        self.addtool_entry = FCEntry()
-        self.addtool_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
-
-        grid0.addWidget(self.addtool_entry_lbl, 1, 0)
-        grid0.addWidget(self.addtool_entry, 1, 1)
-
-        # Number of drill holes in a drill array
-        self.drill_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of drills'))
-        self.drill_array_size_label.setToolTip(
-            _("Specify how many drills to be in the array.")
-        )
-        # self.drill_array_size_label.setMinimumWidth(100)
-
-        self.drill_array_size_entry = LengthEntry()
-
-        grid0.addWidget(self.drill_array_size_label, 2, 0)
-        grid0.addWidget(self.drill_array_size_entry, 2, 1)
-
-        self.drill_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Drill Array'))
-        grid0.addWidget(self.drill_array_linear_label, 3, 0, 1, 2)
-
-        # Linear Drill Array direction
-        self.drill_axis_label = QtWidgets.QLabel('%s:' % _('Linear Dir.'))
-        self.drill_axis_label.setToolTip(
-            _("Direction on which the linear array is oriented:\n"
-              "- 'X' - horizontal axis \n"
-              "- 'Y' - vertical axis or \n"
-              "- 'Angle' - a custom angle for the array inclination")
-        )
-        # self.drill_axis_label.setMinimumWidth(100)
-        self.drill_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
-                                          {'label': _('Y'), 'value': 'Y'},
-                                          {'label': _('Angle'), 'value': 'A'}])
-
-        grid0.addWidget(self.drill_axis_label, 4, 0)
-        grid0.addWidget(self.drill_axis_radio, 4, 1)
-
-        # Linear Drill Array pitch distance
-        self.drill_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
-        self.drill_pitch_label.setToolTip(
-            _("Pitch = Distance between elements of the array.")
-        )
-        # self.drill_pitch_label.setMinimumWidth(100)
-        self.drill_pitch_entry = LengthEntry()
-
-        grid0.addWidget(self.drill_pitch_label, 5, 0)
-        grid0.addWidget(self.drill_pitch_entry, 5, 1)
-
-        # Linear Drill Array custom angle
-        self.drill_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
-        self.drill_angle_label.setToolTip(
-            _("Angle at which each element in circular array is placed.")
-        )
-        self.drill_angle_entry = LengthEntry()
-
-        grid0.addWidget(self.drill_angle_label, 6, 0)
-        grid0.addWidget(self.drill_angle_entry, 6, 1)
-
-        self.drill_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Circular Drill Array'))
-        grid0.addWidget(self.drill_array_circ_label, 7, 0, 1, 2)
-
-        # Circular Drill Array direction
-        self.drill_circular_direction_label = QtWidgets.QLabel('%s:' % _('Circular Dir.'))
-        self.drill_circular_direction_label.setToolTip(
-            _("Direction for circular array.\n"
-              "Can be CW = clockwise or CCW = counter clockwise.")
-        )
-
-        self.drill_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
-                                                  {'label': _('CCW'), 'value': 'CCW'}])
-
-        grid0.addWidget(self.drill_circular_direction_label, 8, 0)
-        grid0.addWidget(self.drill_circular_dir_radio, 8, 1)
-
-        # Circular Drill Array Angle
-        self.drill_circular_angle_label = QtWidgets.QLabel('%s:' % _('Circ. Angle'))
-        self.drill_circular_angle_label.setToolTip(
-            _("Angle at which each element in circular array is placed.")
-        )
-        self.drill_circular_angle_entry = LengthEntry()
-
-        grid0.addWidget(self.drill_circular_angle_label, 9, 0)
-        grid0.addWidget(self.drill_circular_angle_entry, 9, 1)
-
-        # ##### SLOTS #####
-        # #################
-        self.drill_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Slots'))
-        grid0.addWidget(self.drill_array_circ_label, 10, 0, 1, 2)
-
-        # Slot length
-        self.slot_length_label = QtWidgets.QLabel('%s:' % _('Length'))
-        self.slot_length_label.setToolTip(
-            _("Length = The length of the slot.")
-        )
-        self.slot_length_label.setMinimumWidth(100)
-
-        self.slot_length_entry = LengthEntry()
-        grid0.addWidget(self.slot_length_label, 11, 0)
-        grid0.addWidget(self.slot_length_entry, 11, 1)
-
-        # Slot direction
-        self.slot_axis_label = QtWidgets.QLabel('%s:' % _('Direction'))
-        self.slot_axis_label.setToolTip(
-            _("Direction on which the slot is oriented:\n"
-              "- 'X' - horizontal axis \n"
-              "- 'Y' - vertical axis or \n"
-              "- 'Angle' - a custom angle for the slot inclination")
-        )
-        self.slot_axis_label.setMinimumWidth(100)
-
-        self.slot_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
-                                         {'label': _('Y'), 'value': 'Y'},
-                                         {'label': _('Angle'), 'value': 'A'}])
-        grid0.addWidget(self.slot_axis_label, 12, 0)
-        grid0.addWidget(self.slot_axis_radio, 12, 1)
-
-        # Slot custom angle
-        self.slot_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
-        self.slot_angle_label.setToolTip(
-            _("Angle at which the slot is placed.\n"
-              "The precision is of max 2 decimals.\n"
-              "Min value is: -359.99 degrees.\n"
-              "Max value is:  360.00 degrees.")
-        )
-        self.slot_angle_label.setMinimumWidth(100)
-
-        self.slot_angle_spinner = FCDoubleSpinner()
-        self.slot_angle_spinner.set_precision(2)
-        self.slot_angle_spinner.setWrapping(True)
-        self.slot_angle_spinner.setRange(-359.99, 360.00)
-        self.slot_angle_spinner.setSingleStep(1.0)
-        grid0.addWidget(self.slot_angle_label, 13, 0)
-        grid0.addWidget(self.slot_angle_spinner, 13, 1)
-
-        # #### SLOTS ARRAY #######
-        # ########################
-
-        self.slot_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Slot Array'))
-        grid0.addWidget(self.slot_array_linear_label, 14, 0, 1, 2)
-
-        # Number of slot holes in a drill array
-        self.slot_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of slots'))
-        self.drill_array_size_label.setToolTip(
-            _("Specify how many slots to be in the array.")
-        )
-        # self.slot_array_size_label.setMinimumWidth(100)
-
-        self.slot_array_size_entry = LengthEntry()
-
-        grid0.addWidget(self.slot_array_size_label, 15, 0)
-        grid0.addWidget(self.slot_array_size_entry, 15, 1)
-
-        # Linear Slot Array direction
-        self.slot_array_axis_label = QtWidgets.QLabel('%s:' % _('Linear Dir.'))
-        self.slot_array_axis_label.setToolTip(
-            _("Direction on which the linear array is oriented:\n"
-              "- 'X' - horizontal axis \n"
-              "- 'Y' - vertical axis or \n"
-              "- 'Angle' - a custom angle for the array inclination")
-        )
-        # self.slot_axis_label.setMinimumWidth(100)
-        self.slot_array_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
-                                               {'label': _('Y'), 'value': 'Y'},
-                                               {'label': _('Angle'), 'value': 'A'}])
-
-        grid0.addWidget(self.slot_array_axis_label, 16, 0)
-        grid0.addWidget(self.slot_array_axis_radio, 16, 1)
-
-        # Linear Slot Array pitch distance
-        self.slot_array_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
-        self.slot_array_pitch_label.setToolTip(
-            _("Pitch = Distance between elements of the array.")
-        )
-        # self.drill_pitch_label.setMinimumWidth(100)
-        self.slot_array_pitch_entry = LengthEntry()
-
-        grid0.addWidget(self.slot_array_pitch_label, 17, 0)
-        grid0.addWidget(self.slot_array_pitch_entry, 17, 1)
-
-        # Linear Slot Array custom angle
-        self.slot_array_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
-        self.slot_array_angle_label.setToolTip(
-            _("Angle at which each element in circular array is placed.")
-        )
-        self.slot_array_angle_entry = LengthEntry()
-
-        grid0.addWidget(self.slot_array_angle_label, 18, 0)
-        grid0.addWidget(self.slot_array_angle_entry, 18, 1)
-
-        self.slot_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Circular Slot Array'))
-        grid0.addWidget(self.slot_array_circ_label, 19, 0, 1, 2)
-
-        # Circular Slot Array direction
-        self.slot_array_circular_direction_label = QtWidgets.QLabel('%s:' % _('Circular Dir.'))
-        self.slot_array_circular_direction_label.setToolTip(
-            _("Direction for circular array.\n"
-              "Can be CW = clockwise or CCW = counter clockwise.")
-        )
-
-        self.slot_array_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
-                                                       {'label': _('CCW'), 'value': 'CCW'}])
-
-        grid0.addWidget(self.slot_array_circular_direction_label, 20, 0)
-        grid0.addWidget(self.slot_array_circular_dir_radio, 20, 1)
-
-        # Circular Slot Array Angle
-        self.slot_array_circular_angle_label = QtWidgets.QLabel('%s:' % _('Circ. Angle'))
-        self.slot_array_circular_angle_label.setToolTip(
-            _("Angle at which each element in circular array is placed.")
-        )
-        self.slot_array_circular_angle_entry = LengthEntry()
-
-        grid0.addWidget(self.slot_array_circular_angle_label, 21, 0)
-        grid0.addWidget(self.slot_array_circular_angle_entry, 21, 1)
-
-        self.layout.addStretch()
-
-
-class GeometryGenPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Geometry General Preferences", parent=parent)
-        super(GeometryGenPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Geometry General")))
-
-        # ## Plot options
-        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.layout.addWidget(self.plot_options_label)
-
-        # Plot CB
-        self.plot_cb = FCCheckBox(label=_('Plot'))
-        self.plot_cb.setToolTip(
-            _("Plot (show) this object.")
-        )
-        self.layout.addWidget(self.plot_cb)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Number of circle steps for circular aperture linear approximation
-        self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
-        self.circle_steps_label.setToolTip(
-            _("The number of circle steps for <b>Geometry</b> \n"
-              "circle and arc shapes linear approximation.")
-        )
-        grid0.addWidget(self.circle_steps_label, 1, 0)
-        self.circle_steps_entry = IntEntry()
-        grid0.addWidget(self.circle_steps_entry, 1, 1)
-
-        # Tools
-        self.tools_label = QtWidgets.QLabel("<b>%s:</b>" % _("Tools"))
-        grid0.addWidget(self.tools_label, 2, 0, 1, 2)
-
-        # Tooldia
-        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        tdlabel.setToolTip(
-            _("Diameters of the cutting tools, separated by ','")
-        )
-        grid0.addWidget(tdlabel, 3, 0)
-        self.cnctooldia_entry = FCEntry()
-        grid0.addWidget(self.cnctooldia_entry, 3, 1)
-
-        self.layout.addStretch()
-
-
-class GeometryOptPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Geometry Options Preferences", parent=parent)
-        super(GeometryOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Geometry Options")))
-
-        # ------------------------------
-        # ## Create CNC Job
-        # ------------------------------
-        self.cncjob_label = QtWidgets.QLabel('<b>%s:</b>' % _('Create CNC Job'))
-        self.cncjob_label.setToolTip(
-            _("Create a CNC Job object\n"
-              "tracing the contours of this\n"
-              "Geometry object.")
-        )
-        self.layout.addWidget(self.cncjob_label)
-
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-
-        # Cut Z
-        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        cutzlabel.setToolTip(
-            _("Cutting depth (negative)\n"
-              "below the copper surface.")
-        )
-        grid1.addWidget(cutzlabel, 0, 0)
-        self.cutz_entry = LengthEntry()
-        grid1.addWidget(self.cutz_entry, 0, 1)
-
-        # Multidepth CheckBox
-        self.multidepth_cb = FCCheckBox(label=_('Multi-Depth'))
-        self.multidepth_cb.setToolTip(
-            _(
-                "Use multiple passes to limit\n"
-                "the cut depth in each pass. Will\n"
-                "cut multiple times until Cut Z is\n"
-                "reached."
-            )
-        )
-        grid1.addWidget(self.multidepth_cb, 1, 0)
-
-        # Depth/pass
-        dplabel = QtWidgets.QLabel('%s:' % _('Depth/Pass'))
-        dplabel.setToolTip(
-            _("The depth to cut on each pass,\n"
-              "when multidepth is enabled.\n"
-              "It has positive value although\n"
-              "it is a fraction from the depth\n"
-              "which has negative value.")
-        )
-
-        grid1.addWidget(dplabel, 2, 0)
-        self.depthperpass_entry = LengthEntry()
-        grid1.addWidget(self.depthperpass_entry, 2, 1)
-
-        self.ois_multidepth = OptionalInputSection(self.multidepth_cb, [self.depthperpass_entry])
-
-        # Travel Z
-        travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
-        travelzlabel.setToolTip(
-            _("Height of the tool when\n"
-              "moving without cutting.")
-        )
-        grid1.addWidget(travelzlabel, 3, 0)
-        self.travelz_entry = LengthEntry()
-        grid1.addWidget(self.travelz_entry, 3, 1)
-
-        # Tool change:
-        toolchlabel = QtWidgets.QLabel('%s:' % _("Tool change"))
-        toolchlabel.setToolTip(
-            _(
-                "Include tool-change sequence\n"
-                "in the Machine Code (Pause for tool change)."
-            )
-        )
-        self.toolchange_cb = FCCheckBox()
-        grid1.addWidget(toolchlabel, 4, 0)
-        grid1.addWidget(self.toolchange_cb, 4, 1)
-
-        # Toolchange Z
-        toolchangezlabel = QtWidgets.QLabel('%s:' % _('Toolchange Z'))
-        toolchangezlabel.setToolTip(
-            _(
-                "Z-axis position (height) for\n"
-                "tool change."
-            )
-        )
-        grid1.addWidget(toolchangezlabel, 5, 0)
-        self.toolchangez_entry = LengthEntry()
-        grid1.addWidget(self.toolchangez_entry, 5, 1)
-
-        # End move Z
-        endzlabel = QtWidgets.QLabel('%s:' % _('End move Z'))
-        endzlabel.setToolTip(
-            _("Height of the tool after\n"
-              "the last move at the end of the job.")
-        )
-        grid1.addWidget(endzlabel, 6, 0)
-        self.gendz_entry = LengthEntry()
-        grid1.addWidget(self.gendz_entry, 6, 1)
-
-        # Feedrate X-Y
-        frlabel = QtWidgets.QLabel('%s:' % _('Feed Rate X-Y'))
-        frlabel.setToolTip(
-            _("Cutting speed in the XY\n"
-              "plane in units per minute")
-        )
-        grid1.addWidget(frlabel, 7, 0)
-        self.cncfeedrate_entry = LengthEntry()
-        grid1.addWidget(self.cncfeedrate_entry, 7, 1)
-
-        # Feedrate Z (Plunge)
-        frz_label = QtWidgets.QLabel('%s:' % _('Feed Rate Z'))
-        frz_label.setToolTip(
-            _("Cutting speed in the XY\n"
-              "plane in units per minute.\n"
-              "It is called also Plunge.")
-        )
-        grid1.addWidget(frz_label, 8, 0)
-        self.cncplunge_entry = LengthEntry()
-        grid1.addWidget(self.cncplunge_entry, 8, 1)
-
-        # Spindle Speed
-        spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
-        spdlabel.setToolTip(
-            _(
-                "Speed of the spindle in RPM (optional).\n"
-                "If LASER postprocessor is used,\n"
-                "this value is the power of laser."
-            )
-        )
-        grid1.addWidget(spdlabel, 9, 0)
-        self.cncspindlespeed_entry = IntEntry(allow_empty=True)
-        grid1.addWidget(self.cncspindlespeed_entry, 9, 1)
-
-        # Dwell
-        self.dwell_cb = FCCheckBox(label='%s:' % _('Dwell'))
-        self.dwell_cb.setToolTip(
-            _("Pause to allow the spindle to reach its\n"
-              "speed before cutting.")
-        )
-        dwelltime = QtWidgets.QLabel('%s:' % _('Duration'))
-        dwelltime.setToolTip(
-            _("Number of time units for spindle to dwell.")
-        )
-        self.dwelltime_entry = FCEntry()
-        grid1.addWidget(self.dwell_cb, 10, 0)
-        grid1.addWidget(dwelltime, 11, 0)
-        grid1.addWidget(self.dwelltime_entry, 11, 1)
-
-        self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
-
-        # postprocessor selection
-        pp_label = QtWidgets.QLabel('%s:' % _("Postprocessor"))
-        pp_label.setToolTip(
-            _("The Postprocessor file that dictates\n"
-              "the Machine Code (like GCode, RML, HPGL) output.")
-        )
-        grid1.addWidget(pp_label, 12, 0)
-        self.pp_geometry_name_cb = FCComboBox()
-        self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
-        grid1.addWidget(self.pp_geometry_name_cb, 12, 1)
-
-        self.layout.addStretch()
-
-
-class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Geometry Advanced Options Preferences", parent=parent)
-        super(GeometryAdvOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Geometry Adv. Options")))
-
-        # ------------------------------
-        # ## Advanced Options
-        # ------------------------------
-        self.geo_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
-        self.geo_label.setToolTip(
-            _("A list of Geometry advanced parameters.\n"
-              "Those parameters are available only for\n"
-              "Advanced App. Level.")
-        )
-        self.layout.addWidget(self.geo_label)
-
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-
-        # Toolchange X,Y
-        toolchange_xy_label = QtWidgets.QLabel('%s:' %  _('Toolchange X-Y'))
-        toolchange_xy_label.setToolTip(
-            _("Toolchange X,Y position.")
-        )
-        grid1.addWidget(toolchange_xy_label, 1, 0)
-        self.toolchangexy_entry = FCEntry()
-        grid1.addWidget(self.toolchangexy_entry, 1, 1)
-
-        # Start move Z
-        startzlabel = QtWidgets.QLabel('%s:' % _('Start move Z'))
-        startzlabel.setToolTip(
-            _("Height of the tool just after starting the work.\n"
-              "Delete the value if you don't need this feature.")
-        )
-        grid1.addWidget(startzlabel, 2, 0)
-        self.gstartz_entry = FloatEntry()
-        grid1.addWidget(self.gstartz_entry, 2, 1)
-
-        # Feedrate rapids
-        fr_rapid_label = QtWidgets.QLabel('%s:' % _('Feed Rate Rapids'))
-        fr_rapid_label.setToolTip(
-            _("Cutting speed in the XY plane\n"
-              "(in units per minute).\n"
-              "This is for the rapid move G00.\n"
-              "It is useful only for Marlin,\n"
-              "ignore for any other cases.")
-        )
-        grid1.addWidget(fr_rapid_label, 4, 0)
-        self.cncfeedrate_rapid_entry = LengthEntry()
-        grid1.addWidget(self.cncfeedrate_rapid_entry, 4, 1)
-
-        # End move extra cut
-        self.extracut_cb = FCCheckBox(label='%s' % _('Re-cut 1st pt.'))
-        self.extracut_cb.setToolTip(
-            _("In order to remove possible\n"
-              "copper leftovers where first cut\n"
-              "meet with last cut, we generate an\n"
-              "extended cut over the first cut section.")
-        )
-        grid1.addWidget(self.extracut_cb, 5, 0)
-
-        # Probe depth
-        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
-        self.pdepth_label.setToolTip(
-            _("The maximum depth that the probe is allowed\n"
-              "to probe. Negative value, in current units.")
-        )
-        grid1.addWidget(self.pdepth_label, 6, 0)
-        self.pdepth_entry = FCEntry()
-        grid1.addWidget(self.pdepth_entry, 6, 1)
-
-        # Probe feedrate
-        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
-        self.feedrate_probe_label.setToolTip(
-            _("The feedrate used while the probe is probing.")
-        )
-        grid1.addWidget(self.feedrate_probe_label, 7, 0)
-        self.feedrate_probe_entry = FCEntry()
-        grid1.addWidget(self.feedrate_probe_entry, 7, 1)
-
-        # Spindle direction
-        spindle_dir_label = QtWidgets.QLabel('%s:' % _('Spindle dir.'))
-        spindle_dir_label.setToolTip(
-            _("This sets the direction that the spindle is rotating.\n"
-              "It can be either:\n"
-              "- CW = clockwise or\n"
-              "- CCW = counter clockwise")
-        )
-
-        self.spindledir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
-                                          {'label': _('CCW'), 'value': 'CCW'}])
-        grid1.addWidget(spindle_dir_label, 8, 0)
-        grid1.addWidget(self.spindledir_radio, 8, 1)
-
-        # Fast Move from Z Toolchange
-        fplungelabel = QtWidgets.QLabel('%s:' % _('Fast Plunge'))
-        fplungelabel.setToolTip(
-            _("By checking this, the vertical move from\n"
-              "Z_Toolchange to Z_move is done with G0,\n"
-              "meaning the fastest speed available.\n"
-              "WARNING: the move is done at Toolchange X,Y coords.")
-        )
-        self.fplunge_cb = FCCheckBox()
-        grid1.addWidget(fplungelabel, 9, 0)
-        grid1.addWidget(self.fplunge_cb, 9, 1)
-
-        # Size of trace segment on X axis
-        segx_label = QtWidgets.QLabel('%s:' % _("Seg. X size"))
-        segx_label.setToolTip(
-            _("The size of the trace segment on the X axis.\n"
-              "Useful for auto-leveling.\n"
-              "A value of 0 means no segmentation on the X axis.")
-        )
-        grid1.addWidget(segx_label, 10, 0)
-        self.segx_entry = FCEntry()
-        grid1.addWidget(self.segx_entry, 10, 1)
-
-        # Size of trace segment on Y axis
-        segy_label = QtWidgets.QLabel('%s:' % _("Seg. Y size"))
-        segy_label.setToolTip(
-            _("The size of the trace segment on the Y axis.\n"
-              "Useful for auto-leveling.\n"
-              "A value of 0 means no segmentation on the Y axis.")
-        )
-        grid1.addWidget(segy_label, 11, 0)
-        self.segy_entry = FCEntry()
-        grid1.addWidget(self.segy_entry, 11, 1)
-
-        self.layout.addStretch()
-
-
-class GeometryEditorPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
-        super(GeometryEditorPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Geometry Editor")))
-
-        # Advanced Geometry Parameters
-        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.param_label.setToolTip(
-            _("A list of Geometry Editor parameters.")
-        )
-        self.layout.addWidget(self.param_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Selection Limit
-        self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
-        self.sel_limit_label.setToolTip(
-            _("Set the number of selected geometry\n"
-              "items above which the utility geometry\n"
-              "becomes just a selection rectangle.\n"
-              "Increases the performance when moving a\n"
-              "large number of geometric elements.")
-        )
-        self.sel_limit_entry = IntEntry()
-
-        grid0.addWidget(self.sel_limit_label, 0, 0)
-        grid0.addWidget(self.sel_limit_entry, 0, 1)
-
-        self.layout.addStretch()
-
-
-class CNCJobGenPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "CNC Job General Preferences", parent=None)
-        super(CNCJobGenPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("CNC Job General")))
-
-        # ## Plot options
-        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
-        self.layout.addWidget(self.plot_options_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-        # grid0.setColumnStretch(1, 1)
-        # grid0.setColumnStretch(2, 1)
-
-        # Plot CB
-        # self.plot_cb = QtWidgets.QCheckBox('Plot')
-        self.plot_cb = FCCheckBox(_('Plot Object'))
-        self.plot_cb.setToolTip(_("Plot (show) this object."))
-        grid0.addWidget(self.plot_cb, 0, 0)
-
-        # Plot Kind
-        self.cncplot_method_label = QtWidgets.QLabel('%s:' % _("Plot kind"))
-        self.cncplot_method_label.setToolTip(
-            _("This selects the kind of geometries on the canvas to plot.\n"
-              "Those can be either of type 'Travel' which means the moves\n"
-              "above the work piece or it can be of type 'Cut',\n"
-              "which means the moves that cut into the material.")
-        )
-
-        self.cncplot_method_radio = RadioSet([
-            {"label": _("All"), "value": "all"},
-            {"label": _("Travel"), "value": "travel"},
-            {"label": _("Cut"), "value": "cut"}
-        ], stretch=False)
-
-        grid0.addWidget(self.cncplot_method_label, 1, 0)
-        grid0.addWidget(self.cncplot_method_radio, 1, 1)
-        grid0.addWidget(QtWidgets.QLabel(''), 1, 2)
-
-        # Display Annotation
-        self.annotation_label = QtWidgets.QLabel('%s:' % _("Display Annotation"))
-        self.annotation_label.setToolTip(
-            _("This selects if to display text annotation on the plot.\n"
-              "When checked it will display numbers in order for each end\n"
-              "of a travel line."
-              )
-        )
-        self.annotation_cb = FCCheckBox()
-
-        grid0.addWidget(self.annotation_label, 2, 0)
-        grid0.addWidget(self.annotation_cb, 2, 1)
-        grid0.addWidget(QtWidgets.QLabel(''), 2, 2)
-
-        # ###################################################################
-        # Number of circle steps for circular aperture linear approximation #
-        # ###################################################################
-        self.steps_per_circle_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
-        self.steps_per_circle_label.setToolTip(
-            _("The number of circle steps for <b>GCode</b> \n"
-              "circle and arc shapes linear approximation.")
-        )
-        grid0.addWidget(self.steps_per_circle_label, 3, 0)
-        self.steps_per_circle_entry = IntEntry()
-        grid0.addWidget(self.steps_per_circle_entry, 3, 1)
-
-        # Tool dia for plot
-        tdlabel = QtWidgets.QLabel('%s:' % _('Travel dia'))
-        tdlabel.setToolTip(
-            _("The width of the travel lines to be\n"
-              "rendered in the plot.")
-        )
-        self.tooldia_entry = LengthEntry()
-        grid0.addWidget(tdlabel, 4, 0)
-        grid0.addWidget(self.tooldia_entry, 4, 1)
-
-        # add a space
-        grid0.addWidget(QtWidgets.QLabel(''), 5, 0)
-
-        # Number of decimals to use in GCODE coordinates
-        cdeclabel = QtWidgets.QLabel('%s:' % _('Coordinates decimals'))
-        cdeclabel.setToolTip(
-            _("The number of decimals to be used for \n"
-              "the X, Y, Z coordinates in CNC code (GCODE, etc.)")
-        )
-        self.coords_dec_entry = IntEntry()
-        grid0.addWidget(cdeclabel, 6, 0)
-        grid0.addWidget(self.coords_dec_entry, 6, 1)
-
-        # Number of decimals to use in GCODE feedrate
-        frdeclabel = QtWidgets.QLabel('%s:' % _('Feedrate decimals'))
-        frdeclabel.setToolTip(
-            _("The number of decimals to be used for \n"
-              "the Feedrate parameter in CNC code (GCODE, etc.)")
-        )
-        self.fr_dec_entry = IntEntry()
-        grid0.addWidget(frdeclabel, 7, 0)
-        grid0.addWidget(self.fr_dec_entry, 7, 1)
-
-        # The type of coordinates used in the Gcode: Absolute or Incremental
-        coords_type_label = QtWidgets.QLabel('%s:' % _('Coordinates type'))
-        coords_type_label.setToolTip(
-            _("The type of coordinates to be used in Gcode.\n"
-              "Can be:\n"
-              "- Absolute G90 -> the reference is the origin x=0, y=0\n"
-              "- Incremental G91 -> the reference is the previous position")
-        )
-        self.coords_type_radio = RadioSet([
-            {"label": _("Absolute G90"), "value": "G90"},
-            {"label": _("Incremental G91"), "value": "G91"}
-        ], orientation='vertical', stretch=False)
-        grid0.addWidget(coords_type_label, 8, 0)
-        grid0.addWidget(self.coords_type_radio, 8, 1)
-
-        # hidden for the time being, until implemented
-        coords_type_label.hide()
-        self.coords_type_radio.hide()
-
-        self.layout.addStretch()
-
-
-class CNCJobOptPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "CNC Job Options Preferences", parent=None)
-        super(CNCJobOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("CNC Job Options")))
-
-        # ## Export G-Code
-        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export G-Code"))
-        self.export_gcode_label.setToolTip(
-            _("Export and save G-Code to\n"
-              "make this object to a file.")
-        )
-        self.layout.addWidget(self.export_gcode_label)
-
-        # Prepend to G-Code
-        prependlabel = QtWidgets.QLabel('%s:' % _('Prepend to G-Code'))
-        prependlabel.setToolTip(
-            _("Type here any G-Code commands you would\n"
-              "like to add at the beginning of the G-Code file.")
-        )
-        self.layout.addWidget(prependlabel)
-
-        self.prepend_text = FCTextArea()
-        self.layout.addWidget(self.prepend_text)
-
-        # Append text to G-Code
-        appendlabel = QtWidgets.QLabel('%s:' % _('Append to G-Code'))
-        appendlabel.setToolTip(
-            _("Type here any G-Code commands you would\n"
-              "like to append to the generated file.\n"
-              "I.e.: M2 (End of program)")
-        )
-        self.layout.addWidget(appendlabel)
-
-        self.append_text = FCTextArea()
-        self.layout.addWidget(self.append_text)
-
-        self.layout.addStretch()
-
-
-class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "CNC Job Advanced Options Preferences", parent=None)
-        super(CNCJobAdvOptPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("CNC Job Adv. Options")))
-
-        # ## Export G-Code
-        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export CNC Code"))
-        self.export_gcode_label.setToolTip(
-            _("Export and save G-Code to\n"
-              "make this object to a file.")
-        )
-        self.layout.addWidget(self.export_gcode_label)
-
-        # Prepend to G-Code
-        toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code'))
-        toolchangelabel.setToolTip(
-            _(
-                "Type here any G-Code commands you would\n"
-                "like to be executed when Toolchange event is encountered.\n"
-                "This will constitute a Custom Toolchange GCode,\n"
-                "or a Toolchange Macro.\n"
-                "The FlatCAM variables are surrounded by '%' symbol.\n\n"
-                "WARNING: it can be used only with a postprocessor file\n"
-                "that has 'toolchange_custom' in it's name and this is built\n"
-                "having as template the 'Toolchange Custom' posprocessor file."
-            )
-        )
-        self.layout.addWidget(toolchangelabel)
-
-        self.toolchange_text = FCTextArea()
-        self.layout.addWidget(self.toolchange_text)
-
-        hlay = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay)
-
-        # Toolchange Replacement GCode
-        self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro'))
-        self.toolchange_cb.setToolTip(
-            _("Check this box if you want to use\n"
-              "a Custom Toolchange GCode (macro).")
-        )
-        hlay.addWidget(self.toolchange_cb)
-        hlay.addStretch()
-
-        hlay1 = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay1)
-
-        # Variable list
-        self.tc_variable_combo = FCComboBox()
-        self.tc_variable_combo.setToolTip(
-            _("A list of the FlatCAM variables that can be used\n"
-              "in the Toolchange event.\n"
-              "They have to be surrounded by the '%' symbol")
-        )
-        hlay1.addWidget(self.tc_variable_combo)
-
-        # Populate the Combo Box
-        variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
-                     'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime']
-        self.tc_variable_combo.addItems(variables)
-        self.tc_variable_combo.setItemData(0, _("FlatCAM CNC parameters"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(1, _("tool = tool number"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(2, _("tooldia = tool diameter"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(3, _("t_drills = for Excellon, total number of drills"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(4, _("x_toolchange = X coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(5, _("y_toolchange = Y coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(6, _("z_toolchange = Z coord for Toolchange"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(7, _("z_cut = Z depth for the cut"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(8, _("z_move = Z height for travel"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(9, _("z_depthpercut = the step value for multidepth cut"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(10, _("spindlesspeed = the value for the spindle speed"), Qt.ToolTipRole)
-        self.tc_variable_combo.setItemData(11,
-                                           _("dwelltime = time to dwell to allow the spindle to reach it's set RPM"),
-                                           Qt.ToolTipRole)
-
-        hlay1.addStretch()
-
-        # Insert Variable into the Toolchange G-Code Text Box
-        # self.tc_insert_buton = FCButton("Insert")
-        # self.tc_insert_buton.setToolTip(
-        #     "Insert the variable in the GCode Box\n"
-        #     "surrounded by the '%' symbol."
-        # )
-        # hlay1.addWidget(self.tc_insert_buton)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        grid0.addWidget(QtWidgets.QLabel(''), 1, 0, 1, 2)
-
-        # Annotation Font Size
-        self.annotation_fontsize_label = QtWidgets.QLabel('%s:' % _("Annotation Size"))
-        self.annotation_fontsize_label.setToolTip(
-            _("The font size of the annotation text. In pixels.")
-        )
-        grid0.addWidget(self.annotation_fontsize_label, 2, 0)
-        self.annotation_fontsize_sp = FCSpinner()
-        grid0.addWidget(self.annotation_fontsize_sp, 2, 1)
-        grid0.addWidget(QtWidgets.QLabel(''), 2, 2)
-
-        # Annotation Font Color
-        self.annotation_color_label = QtWidgets.QLabel('%s:' % _('Annotation Color'))
-        self.annotation_color_label.setToolTip(
-            _("Set the font color for the annotation texts.")
-        )
-        self.annotation_fontcolor_entry = FCEntry()
-        self.annotation_fontcolor_button = QtWidgets.QPushButton()
-        self.annotation_fontcolor_button.setFixedSize(15, 15)
-
-        self.form_box_child = QtWidgets.QHBoxLayout()
-        self.form_box_child.setContentsMargins(0, 0, 0, 0)
-        self.form_box_child.addWidget(self.annotation_fontcolor_entry)
-        self.form_box_child.addWidget(self.annotation_fontcolor_button, alignment=Qt.AlignRight)
-        self.form_box_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        color_widget = QtWidgets.QWidget()
-        color_widget.setLayout(self.form_box_child)
-        grid0.addWidget(self.annotation_color_label, 3, 0)
-        grid0.addWidget(color_widget, 3, 1)
-        grid0.addWidget(QtWidgets.QLabel(''), 3, 2)
-
-        self.layout.addStretch()
-
-
-class ToolsNCCPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "NCC Tool Options", parent=parent)
-        super(ToolsNCCPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("NCC Tool Options")))
-
-        # ## Clear non-copper regions
-        self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.clearcopper_label.setToolTip(
-            _("Create a Geometry object with\n"
-              "toolpaths to cut all non-copper regions.")
-        )
-        self.layout.addWidget(self.clearcopper_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        ncctdlabel = QtWidgets.QLabel('%s:' % _('Tools dia'))
-        ncctdlabel.setToolTip(
-            _("Diameters of the cutting tools, separated by ','")
-        )
-        grid0.addWidget(ncctdlabel, 0, 0)
-        self.ncc_tool_dia_entry = FCEntry()
-        grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
-
-        # Tool Type Radio Button
-        self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
-        self.tool_type_label.setToolTip(
-            _("Default tool type:\n"
-              "- 'V-shape'\n"
-              "- Circular")
-        )
-
-        self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'},
-                                         {'label': _('Circular'), 'value': 'C1'}])
-        self.tool_type_radio.setToolTip(
-            _("Default tool type:\n"
-              "- 'V-shape'\n"
-              "- Circular")
-        )
-
-        grid0.addWidget(self.tool_type_label, 1, 0)
-        grid0.addWidget(self.tool_type_radio, 1, 1)
-
-        # Tip Dia
-        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
-        self.tipdialabel.setToolTip(
-            _("The tip diameter for V-Shape Tool"))
-        self.tipdia_entry = LengthEntry()
-
-        grid0.addWidget(self.tipdialabel, 2, 0)
-        grid0.addWidget(self.tipdia_entry, 2, 1)
-
-        # Tip Angle
-        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
-        self.tipanglelabel.setToolTip(
-            _("The tip angle for V-Shape Tool.\n"
-              "In degree."))
-        self.tipangle_entry = LengthEntry()
-
-        grid0.addWidget(self.tipanglelabel, 3, 0)
-        grid0.addWidget(self.tipangle_entry, 3, 1)
-
-        # Milling Type Radio Button
-        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
-        self.milling_type_label.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-
-        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
-                                            {'label': _('Conv.'), 'value': 'cv'}])
-        self.milling_type_radio.setToolTip(
-            _("Milling type when the selected tool is of type: 'iso_op':\n"
-              "- climb / best for precision milling and to reduce tool usage\n"
-              "- conventional / useful when there is no backlash compensation")
-        )
-
-        grid0.addWidget(self.milling_type_label, 4, 0)
-        grid0.addWidget(self.milling_type_radio, 4, 1)
-
-        # Tool order Radio Button
-        self.ncc_order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
-        self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
-                                          "'No' --> means that the used order is the one in the tool table\n"
-                                          "'Forward' --> means that the tools will be ordered from small to big\n"
-                                          "'Reverse' --> menas that the tools will ordered from big to small\n\n"
-                                          "WARNING: using rest machining will automatically set the order\n"
-                                          "in reverse and disable this control."))
-
-        self.ncc_order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
-                                         {'label': _('Forward'), 'value': 'fwd'},
-                                         {'label': _('Reverse'), 'value': 'rev'}])
-        self.ncc_order_radio.setToolTip(_("This set the way that the tools in the tools table are used.\n"
-                                          "'No' --> means that the used order is the one in the tool table\n"
-                                          "'Forward' --> means that the tools will be ordered from small to big\n"
-                                          "'Reverse' --> menas that the tools will ordered from big to small\n\n"
-                                          "WARNING: using rest machining will automatically set the order\n"
-                                          "in reverse and disable this control."))
-        grid0.addWidget(self.ncc_order_label, 5, 0)
-        grid0.addWidget(self.ncc_order_radio, 5, 1)
-
-        # Cut Z entry
-        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
-        cutzlabel.setToolTip(
-           _("Depth of cut into material. Negative value.\n"
-             "In FlatCAM units.")
-        )
-        self.cutz_entry = FloatEntry()
-        self.cutz_entry.setToolTip(
-           _("Depth of cut into material. Negative value.\n"
-             "In FlatCAM units.")
-        )
-
-        grid0.addWidget(cutzlabel, 6, 0)
-        grid0.addWidget(self.cutz_entry, 6, 1)
-
-        # Overlap Entry
-        nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
-        nccoverlabel.setToolTip(
-           _("How much (fraction) of the tool width to overlap each tool pass.\n"
-             "Example:\n"
-             "A value here of 0.25 means 25%% from the tool diameter found above.\n\n"
-             "Adjust the value starting with lower values\n"
-             "and increasing it if areas that should be cleared are still \n"
-             "not cleared.\n"
-             "Lower values = faster processing, faster execution on PCB.\n"
-             "Higher values = slow processing and slow execution on CNC\n"
-             "due of too many paths.")
-        )
-        self.ncc_overlap_entry = FCDoubleSpinner()
-        self.ncc_overlap_entry.set_precision(3)
-        self.ncc_overlap_entry.setWrapping(True)
-        self.ncc_overlap_entry.setRange(0.000, 0.999)
-        self.ncc_overlap_entry.setSingleStep(0.1)
-        grid0.addWidget(nccoverlabel, 7, 0)
-        grid0.addWidget(self.ncc_overlap_entry, 7, 1)
-
-        # Margin entry
-        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        nccmarginlabel.setToolTip(
-            _("Bounding box margin.")
-        )
-        grid0.addWidget(nccmarginlabel, 8, 0)
-        self.ncc_margin_entry = FloatEntry()
-        grid0.addWidget(self.ncc_margin_entry, 8, 1)
-
-        # Method
-        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
-        methodlabel.setToolTip(
-            _("Algorithm for non-copper clearing:<BR>"
-              "<B>Standard</B>: Fixed step inwards.<BR>"
-              "<B>Seed-based</B>: Outwards from seed.<BR>"
-              "<B>Line-based</B>: Parallel lines.")
-        )
-        grid0.addWidget(methodlabel, 9, 0)
-        self.ncc_method_radio = RadioSet([
-            {"label": _("Standard"), "value": "standard"},
-            {"label": _("Seed-based"), "value": "seed"},
-            {"label": _("Straight lines"), "value": "lines"}
-        ], orientation='vertical', stretch=False)
-        grid0.addWidget(self.ncc_method_radio, 9, 1)
-
-        # Connect lines
-        pathconnectlabel = QtWidgets.QLabel('%s:' % _("Connect"))
-        pathconnectlabel.setToolTip(
-            _("Draw lines between resulting\n"
-              "segments to minimize tool lifts.")
-        )
-        grid0.addWidget(pathconnectlabel, 10, 0)
-        self.ncc_connect_cb = FCCheckBox()
-        grid0.addWidget(self.ncc_connect_cb, 10, 1)
-
-        # Contour Checkbox
-        contourlabel = QtWidgets.QLabel('%s:' % _("Contour"))
-        contourlabel.setToolTip(
-           _("Cut around the perimeter of the polygon\n"
-             "to trim rough edges.")
-        )
-        grid0.addWidget(contourlabel, 11, 0)
-        self.ncc_contour_cb = FCCheckBox()
-        grid0.addWidget(self.ncc_contour_cb, 11, 1)
-
-        # Rest machining CheckBox
-        restlabel = QtWidgets.QLabel('%s:' % _("Rest M."))
-        restlabel.setToolTip(
-            _("If checked, use 'rest machining'.\n"
-              "Basically it will clear copper outside PCB features,\n"
-              "using the biggest tool and continue with the next tools,\n"
-              "from bigger to smaller, to clear areas of copper that\n"
-              "could not be cleared by previous tool, until there is\n"
-              "no more copper to clear or there are no more tools.\n"
-              "If not checked, use the standard algorithm.")
-        )
-        grid0.addWidget(restlabel, 12, 0)
-        self.ncc_rest_cb = FCCheckBox()
-        grid0.addWidget(self.ncc_rest_cb, 12, 1)
-
-        # ## NCC Offset choice
-        self.ncc_offset_choice_label = QtWidgets.QLabel('%s:' % _("Offset"))
-        self.ncc_offset_choice_label.setToolTip(
-            _("If used, it will add an offset to the copper features.\n"
-              "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
-        )
-        grid0.addWidget(self.ncc_offset_choice_label, 13, 0)
-        self.ncc_choice_offset_cb = FCCheckBox()
-        grid0.addWidget(self.ncc_choice_offset_cb, 13, 1)
-
-        # ## NCC Offset value
-        self.ncc_offset_label = QtWidgets.QLabel('%s:' % _("Offset value"))
-        self.ncc_offset_label.setToolTip(
-            _("If used, it will add an offset to the copper features.\n"
-              "The copper clearing will finish to a distance\n"
-              "from the copper features.\n"
-              "The value can be between 0 and 10 FlatCAM units.")
-        )
-        grid0.addWidget(self.ncc_offset_label, 14, 0)
-        self.ncc_offset_spinner = FCDoubleSpinner()
-        self.ncc_offset_spinner.set_range(0.00, 10.00)
-        self.ncc_offset_spinner.set_precision(4)
-        self.ncc_offset_spinner.setWrapping(True)
-        self.ncc_offset_spinner.setSingleStep(0.1)
-
-        grid0.addWidget(self.ncc_offset_spinner, 14, 1)
-
-        # ## Reference
-        self.reference_radio = RadioSet([{'label': _('Itself'), 'value': 'itself'},
-                                         {"label": _("Area"), "value": "area"},
-                                         {'label': _('Ref'), 'value': 'box'}])
-        reference_label = QtWidgets.QLabel('%s:' % _("Reference"))
-        reference_label.setToolTip(
-            _("- 'Itself' -  the non copper clearing extent\n"
-              "is based on the object that is copper cleared.\n "
-              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
-              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
-              "- 'Reference Object' -  will do non copper clearing within the area\n"
-              "specified by another object.")
-        )
-        grid0.addWidget(reference_label, 15, 0)
-        grid0.addWidget(self.reference_radio, 15, 1)
-
-        # ## Plotting type
-        self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
-                                            {"label": _("Progressive"), "value": "progressive"}])
-        plotting_label = QtWidgets.QLabel('%s:' % _("NCC Plotting"))
-        plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the NCC job\n"
-              "- 'Progressive' - after each shape is generated it will be plotted.")
-        )
-        grid0.addWidget(plotting_label, 16, 0)
-        grid0.addWidget(self.ncc_plotting_radio, 16, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsCutoutPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Cutout Tool Options", parent=parent)
-        super(ToolsCutoutPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Cutout Tool Options")))
-
-        # ## Board cuttout
-        self.board_cutout_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.board_cutout_label.setToolTip(
-            _("Create toolpaths to cut around\n"
-              "the PCB and separate it from\n"
-              "the original board.")
-        )
-        self.layout.addWidget(self.board_cutout_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        tdclabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        tdclabel.setToolTip(
-            _("Diameter of the tool used to cutout\n"
-              "the PCB shape out of the surrounding material.")
-        )
-        grid0.addWidget(tdclabel, 0, 0)
-        self.cutout_tooldia_entry = LengthEntry()
-        grid0.addWidget(self.cutout_tooldia_entry, 0, 1)
-
-        # Object kind
-        kindlabel = QtWidgets.QLabel('%s:' % _('Obj kind'))
-        kindlabel.setToolTip(
-            _("Choice of what kind the object we want to cutout is.<BR>"
-              "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
-              "- <B>Panel</B>: a panel PCB Gerber object, which is made\n"
-              "out of many individual PCB outlines.")
-        )
-        grid0.addWidget(kindlabel, 1, 0)
-        self.obj_kind_combo = RadioSet([
-            {"label": _("Single"), "value": "single"},
-            {"label": _("Panel"), "value": "panel"},
-        ])
-        grid0.addWidget(self.obj_kind_combo, 1, 1)
-
-        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        marginlabel.setToolTip(
-            _("Margin over bounds. A positive value here\n"
-              "will make the cutout of the PCB further from\n"
-              "the actual PCB border")
-        )
-        grid0.addWidget(marginlabel, 2, 0)
-        self.cutout_margin_entry = LengthEntry()
-        grid0.addWidget(self.cutout_margin_entry, 2, 1)
-
-        gaplabel = QtWidgets.QLabel('%s:' % _('Gap size'))
-        gaplabel.setToolTip(
-            _("The size of the bridge gaps in the cutout\n"
-              "used to keep the board connected to\n"
-              "the surrounding material (the one \n"
-              "from which the PCB is cutout).")
-        )
-        grid0.addWidget(gaplabel, 3, 0)
-        self.cutout_gap_entry = LengthEntry()
-        grid0.addWidget(self.cutout_gap_entry, 3, 1)
-
-        gaps_label = QtWidgets.QLabel('%s:' % _('Gaps'))
-        gaps_label.setToolTip(
-            _("Number of gaps used for the cutout.\n"
-              "There can be maximum 8 bridges/gaps.\n"
-              "The choices are:\n"
-              "- None  - no gaps\n"
-              "- lr    - left + right\n"
-              "- tb    - top + bottom\n"
-              "- 4     - left + right +top + bottom\n"
-              "- 2lr   - 2*left + 2*right\n"
-              "- 2tb  - 2*top + 2*bottom\n"
-              "- 8     - 2*left + 2*right +2*top + 2*bottom")
-        )
-        grid0.addWidget(gaps_label, 4, 0)
-        self.gaps_combo = FCComboBox()
-        grid0.addWidget(self.gaps_combo, 4, 1)
-
-        gaps_items = ['None', 'LR', 'TB', '4', '2LR', '2TB', '8']
-        for it in gaps_items:
-            self.gaps_combo.addItem(it)
-            self.gaps_combo.setStyleSheet('background-color: rgb(255,255,255)')
-
-        # Surrounding convex box shape
-        self.convex_box = FCCheckBox()
-        self.convex_box_label = QtWidgets.QLabel('%s:' % _("Convex Sh."))
-        self.convex_box_label.setToolTip(
-            _("Create a convex shape surrounding the entire PCB.\n"
-              "Used only if the source object type is Gerber.")
-        )
-        grid0.addWidget(self.convex_box_label, 5, 0)
-        grid0.addWidget(self.convex_box, 5, 1)
-
-        self.layout.addStretch()
-
-
-class Tools2sidedPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "2sided Tool Options", parent=parent)
-        super(Tools2sidedPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("2Sided Tool Options")))
-
-        # ## Board cuttout
-        self.dblsided_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.dblsided_label.setToolTip(
-            _("A tool to help in creating a double sided\n"
-              "PCB using alignment holes.")
-        )
-        self.layout.addWidget(self.dblsided_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # ## Drill diameter for alignment holes
-        self.drill_dia_entry = LengthEntry()
-        self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
-        self.dd_label.setToolTip(
-            _("Diameter of the drill for the "
-              "alignment holes.")
-        )
-        grid0.addWidget(self.dd_label, 0, 0)
-        grid0.addWidget(self.drill_dia_entry, 0, 1)
-
-        # ## Axis
-        self.mirror_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
-                                           {'label': 'Y', 'value': 'Y'}])
-        self.mirax_label = QtWidgets.QLabel(_("Mirror Axis:"))
-        self.mirax_label.setToolTip(
-            _("Mirror vertically (X) or horizontally (Y).")
-        )
-        # grid_lay.addRow("Mirror Axis:", self.mirror_axis)
-        self.empty_lb1 = QtWidgets.QLabel("")
-        grid0.addWidget(self.empty_lb1, 1, 0)
-        grid0.addWidget(self.mirax_label, 2, 0)
-        grid0.addWidget(self.mirror_axis_radio, 2, 1)
-
-        # ## Axis Location
-        self.axis_location_radio = RadioSet([{'label': _('Point'), 'value': 'point'},
-                                             {'label': _('Box'), 'value': 'box'}])
-        self.axloc_label = QtWidgets.QLabel('%s:' % _("Axis Ref"))
-        self.axloc_label.setToolTip(
-            _("The axis should pass through a <b>point</b> or cut\n "
-              "a specified <b>box</b> (in a FlatCAM object) through \n"
-              "the center.")
-        )
-        # grid_lay.addRow("Axis Location:", self.axis_location)
-        grid0.addWidget(self.axloc_label, 3, 0)
-        grid0.addWidget(self.axis_location_radio, 3, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsPaintPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Paint Area Tool Options", parent=parent)
-        super(ToolsPaintPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Paint Tool Options")))
-
-        # ------------------------------
-        # ## Paint area
-        # ------------------------------
-        self.paint_label = QtWidgets.QLabel(_('<b>Parameters:</b>'))
-        self.paint_label.setToolTip(
-            _("Creates tool paths to cover the\n"
-              "whole area of a polygon (remove\n"
-              "all copper). You will be asked\n"
-              "to click on the desired polygon.")
-        )
-        self.layout.addWidget(self.paint_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Tool dia
-        ptdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
-        ptdlabel.setToolTip(
-            _("Diameter of the tool to\n"
-              "be used in the operation.")
-        )
-        grid0.addWidget(ptdlabel, 0, 0)
-
-        self.painttooldia_entry = LengthEntry()
-        grid0.addWidget(self.painttooldia_entry, 0, 1)
-
-        self.paint_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
-        self.paint_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
-                                            "'No' --> means that the used order is the one in the tool table\n"
-                                            "'Forward' --> means that the tools will be ordered from small to big\n"
-                                            "'Reverse' --> menas that the tools will ordered from big to small\n\n"
-                                            "WARNING: using rest machining will automatically set the order\n"
-                                            "in reverse and disable this control."))
-
-        self.paint_order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
-                                           {'label': _('Forward'), 'value': 'fwd'},
-                                           {'label': _('Reverse'), 'value': 'rev'}])
-        self.paint_order_radio.setToolTip(_("This set the way that the tools in the tools table are used.\n"
-                                            "'No' --> means that the used order is the one in the tool table\n"
-                                            "'Forward' --> means that the tools will be ordered from small to big\n"
-                                            "'Reverse' --> menas that the tools will ordered from big to small\n\n"
-                                            "WARNING: using rest machining will automatically set the order\n"
-                                            "in reverse and disable this control."))
-        grid0.addWidget(self.paint_order_label, 1, 0)
-        grid0.addWidget(self.paint_order_radio, 1, 1)
-
-        # Overlap
-        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
-        ovlabel.setToolTip(
-            _("How much (fraction) of the tool width to overlap each tool pass.\n"
-              "Example:\n"
-              "A value here of 0.25 means 25%% from the tool diameter found above.\n\n"
-              "Adjust the value starting with lower values\n"
-              "and increasing it if areas that should be painted are still \n"
-              "not painted.\n"
-              "Lower values = faster processing, faster execution on PCB.\n"
-              "Higher values = slow processing and slow execution on CNC\n"
-              "due of too many paths.")
-        )
-        self.paintoverlap_entry = FCDoubleSpinner()
-        self.paintoverlap_entry.set_precision(3)
-        self.paintoverlap_entry.setWrapping(True)
-        self.paintoverlap_entry.setRange(0.000, 0.999)
-        self.paintoverlap_entry.setSingleStep(0.1)
-        grid0.addWidget(ovlabel, 2, 0)
-        grid0.addWidget(self.paintoverlap_entry, 2, 1)
-
-        # Margin
-        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
-        marginlabel.setToolTip(
-            _("Distance by which to avoid\n"
-              "the edges of the polygon to\n"
-              "be painted.")
-        )
-        grid0.addWidget(marginlabel, 3, 0)
-        self.paintmargin_entry = LengthEntry()
-        grid0.addWidget(self.paintmargin_entry, 3, 1)
-
-        # Method
-        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
-        methodlabel.setToolTip(
-            _("Algorithm for non-copper clearing:<BR>"
-              "<B>Standard</B>: Fixed step inwards.<BR>"
-              "<B>Seed-based</B>: Outwards from seed.<BR>"
-              "<B>Line-based</B>: Parallel lines.")
-        )
-        grid0.addWidget(methodlabel, 4, 0)
-        self.paintmethod_combo = RadioSet([
-            {"label": _("Standard"), "value": "standard"},
-            {"label": _("Seed-based"), "value": "seed"},
-            {"label": _("Straight lines"), "value": "lines"}
-        ], orientation='vertical', stretch=False)
-        grid0.addWidget(self.paintmethod_combo, 4, 1)
-
-        # Connect lines
-        pathconnectlabel = QtWidgets.QLabel('%s:' % _("Connect"))
-        pathconnectlabel.setToolTip(
-            _("Draw lines between resulting\n"
-              "segments to minimize tool lifts.")
-        )
-        grid0.addWidget(pathconnectlabel, 5, 0)
-        self.pathconnect_cb = FCCheckBox()
-        grid0.addWidget(self.pathconnect_cb, 5, 1)
-
-        # Paint contour
-        contourlabel = QtWidgets.QLabel('%s:' % _("Contour"))
-        contourlabel.setToolTip(
-            _("Cut around the perimeter of the polygon\n"
-              "to trim rough edges.")
-        )
-        grid0.addWidget(contourlabel, 6, 0)
-        self.contour_cb = FCCheckBox()
-        grid0.addWidget(self.contour_cb, 6, 1)
-
-        # Polygon selection
-        selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
-        selectlabel.setToolTip(
-            _("How to select Polygons to be painted.\n\n"
-              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
-              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
-              "- 'All Polygons' - the Paint will start after click.\n"
-              "- 'Reference Object' -  will do non copper clearing within the area\n"
-              "specified by another object.")
-        )
-        self.selectmethod_combo = RadioSet([
-            {"label": _("Single"), "value": "single"},
-            {"label": _("Area"), "value": "area"},
-            {"label": _("All"), "value": "all"},
-            {"label": _("Ref."), "value": "ref"}
-        ])
-        grid0.addWidget(selectlabel, 7, 0)
-        grid0.addWidget(self.selectmethod_combo, 7, 1)
-
-        # ## Plotting type
-        self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
-                                              {"label": _("Progressive"), "value": "progressive"}])
-        plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
-        plotting_label.setToolTip(
-            _("- 'Normal' -  normal plotting, done at the end of the Paint job\n"
-              "- 'Progressive' - after each shape is generated it will be plotted.")
-        )
-        grid0.addWidget(plotting_label, 8, 0)
-        grid0.addWidget(self.paint_plotting_radio, 8, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsFilmPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Cutout Tool Options", parent=parent)
-        super(ToolsFilmPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Film Tool Options")))
-
-        # ## Board cuttout
-        self.film_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.film_label.setToolTip(
-            _("Create a PCB film from a Gerber or Geometry\n"
-              "FlatCAM object.\n"
-              "The file is saved in SVG format.")
-        )
-        self.layout.addWidget(self.film_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        self.film_type_radio = RadioSet([{'label': 'Pos', 'value': 'pos'},
-                                         {'label': 'Neg', 'value': 'neg'}])
-        ftypelbl = QtWidgets.QLabel('%s:' % _('Film Type'))
-        ftypelbl.setToolTip(
-            _("Generate a Positive black film or a Negative film.\n"
-              "Positive means that it will print the features\n"
-              "with black on a white canvas.\n"
-              "Negative means that it will print the features\n"
-              "with white on a black canvas.\n"
-              "The Film format is SVG.")
-        )
-        grid0.addWidget(ftypelbl, 0, 0)
-        grid0.addWidget(self.film_type_radio, 0, 1)
-
-        # Film Color
-        self.film_color_label = QtWidgets.QLabel('%s:' % _('Film Color'))
-        self.film_color_label.setToolTip(
-            _("Set the film color when positive film is selected.")
-        )
-        self.film_color_entry = FCEntry()
-        self.film_color_button = QtWidgets.QPushButton()
-        self.film_color_button.setFixedSize(15, 15)
-
-        self.form_box_child = QtWidgets.QHBoxLayout()
-        self.form_box_child.setContentsMargins(0, 0, 0, 0)
-        self.form_box_child.addWidget(self.film_color_entry)
-        self.form_box_child.addWidget(self.film_color_button, alignment=Qt.AlignRight)
-        self.form_box_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-        film_color_widget = QtWidgets.QWidget()
-        film_color_widget.setLayout(self.form_box_child)
-        grid0.addWidget(self.film_color_label, 1, 0)
-        grid0.addWidget(film_color_widget, 1, 1)
-
-        self.film_boundary_entry = FCEntry()
-        self.film_boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
-        self.film_boundary_label.setToolTip(
-            _("Specify a border around the object.\n"
-              "Only for negative film.\n"
-              "It helps if we use as a Box Object the same \n"
-              "object as in Film Object. It will create a thick\n"
-              "black bar around the actual print allowing for a\n"
-              "better delimitation of the outline features which are of\n"
-              "white color like the rest and which may confound with the\n"
-              "surroundings if not for this border.")
-        )
-        grid0.addWidget(self.film_boundary_label, 2, 0)
-        grid0.addWidget(self.film_boundary_entry, 2, 1)
-
-        self.film_scale_entry = FCEntry()
-        self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
-        self.film_scale_label.setToolTip(
-            _("Scale the line stroke thickness of each feature in the SVG file.\n"
-              "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
-              "therefore the fine features may be more affected by this parameter.")
-        )
-        grid0.addWidget(self.film_scale_label, 3, 0)
-        grid0.addWidget(self.film_scale_entry, 3, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsPanelizePrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Cutout Tool Options", parent=parent)
-        super(ToolsPanelizePrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Panelize Tool Options")))
-
-        # ## Board cuttout
-        self.panelize_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.panelize_label.setToolTip(
-            _("Create an object that contains an array of (x, y) elements,\n"
-              "each element is a copy of the source object spaced\n"
-              "at a X distance, Y distance of each other.")
-        )
-        self.layout.addWidget(self.panelize_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # ## Spacing Columns
-        self.pspacing_columns = FCEntry()
-        self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
-        self.spacing_columns_label.setToolTip(
-            _("Spacing between columns of the desired panel.\n"
-              "In current units.")
-        )
-        grid0.addWidget(self.spacing_columns_label, 0, 0)
-        grid0.addWidget(self.pspacing_columns, 0, 1)
-
-        # ## Spacing Rows
-        self.pspacing_rows = FCEntry()
-        self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
-        self.spacing_rows_label.setToolTip(
-            _("Spacing between rows of the desired panel.\n"
-              "In current units.")
-        )
-        grid0.addWidget(self.spacing_rows_label, 1, 0)
-        grid0.addWidget(self.pspacing_rows, 1, 1)
-
-        # ## Columns
-        self.pcolumns = FCEntry()
-        self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
-        self.columns_label.setToolTip(
-            _("Number of columns of the desired panel")
-        )
-        grid0.addWidget(self.columns_label, 2, 0)
-        grid0.addWidget(self.pcolumns, 2, 1)
-
-        # ## Rows
-        self.prows = FCEntry()
-        self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
-        self.rows_label.setToolTip(
-            _("Number of rows of the desired panel")
-        )
-        grid0.addWidget(self.rows_label, 3, 0)
-        grid0.addWidget(self.prows, 3, 1)
-
-        # ## Type of resulting Panel object
-        self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
-                                          {'label': _('Geo'), 'value': 'geometry'}])
-        self.panel_type_label = QtWidgets.QLabel('%s:' % _("Panel Type"))
-        self.panel_type_label.setToolTip(
-           _("Choose the type of object for the panel object:\n"
-             "- Gerber\n"
-             "- Geometry")
-        )
-
-        grid0.addWidget(self.panel_type_label, 4, 0)
-        grid0.addWidget(self.panel_type_radio, 4, 1)
-
-        # ## Constrains
-        self.pconstrain_cb = FCCheckBox('%s:' % _("Constrain within"))
-        self.pconstrain_cb.setToolTip(
-            _("Area define by DX and DY within to constrain the panel.\n"
-              "DX and DY values are in current units.\n"
-              "Regardless of how many columns and rows are desired,\n"
-              "the final panel will have as many columns and rows as\n"
-              "they fit completely within selected area.")
-        )
-        grid0.addWidget(self.pconstrain_cb, 5, 0)
-
-        self.px_width_entry = FCEntry()
-        self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
-        self.x_width_lbl.setToolTip(
-            _("The width (DX) within which the panel must fit.\n"
-              "In current units.")
-        )
-        grid0.addWidget(self.x_width_lbl, 6, 0)
-        grid0.addWidget(self.px_width_entry, 6, 1)
-
-        self.py_height_entry = FCEntry()
-        self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
-        self.y_height_lbl.setToolTip(
-            _("The height (DY)within which the panel must fit.\n"
-              "In current units.")
-        )
-        grid0.addWidget(self.y_height_lbl, 7, 0)
-        grid0.addWidget(self.py_height_entry, 7, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent)
-        super(ToolsCalculatorsPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Calculators Tool Options")))
-
-        # ## V-shape Calculator Tool
-        self.vshape_tool_label = QtWidgets.QLabel("<b>%s:</b>" % _("V-Shape Tool Calculator"))
-        self.vshape_tool_label.setToolTip(
-            _("Calculate the tool diameter for a given V-shape tool,\n"
-              "having the tip diameter, tip angle and\n"
-              "depth-of-cut as parameters.")
-        )
-        self.layout.addWidget(self.vshape_tool_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # ## Tip Diameter
-        self.tip_dia_entry = FCEntry()
-        self.tip_dia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
-        self.tip_dia_label.setToolTip(
-            _("This is the tool tip diameter.\n"
-              "It is specified by manufacturer.")
-        )
-        grid0.addWidget(self.tip_dia_label, 0, 0)
-        grid0.addWidget(self.tip_dia_entry, 0, 1)
-
-        # ## Tip angle
-        self.tip_angle_entry = FCEntry()
-        self.tip_angle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
-        self.tip_angle_label.setToolTip(
-            _("This is the angle on the tip of the tool.\n"
-              "It is specified by manufacturer.")
-        )
-        grid0.addWidget(self.tip_angle_label, 1, 0)
-        grid0.addWidget(self.tip_angle_entry, 1, 1)
-
-        # ## Depth-of-cut Cut Z
-        self.cut_z_entry = FCEntry()
-        self.cut_z_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
-        self.cut_z_label.setToolTip(
-            _("This is depth to cut into material.\n"
-              "In the CNCJob object it is the CutZ parameter.")
-        )
-        grid0.addWidget(self.cut_z_label, 2, 0)
-        grid0.addWidget(self.cut_z_entry, 2, 1)
-
-        # ## Electroplating Calculator Tool
-        self.plate_title_label = QtWidgets.QLabel("<b>%s:</b>" % _("ElectroPlating Calculator"))
-        self.plate_title_label.setToolTip(
-            _("This calculator is useful for those who plate the via/pad/drill holes,\n"
-              "using a method like grahite ink or calcium hypophosphite ink or palladium chloride.")
-        )
-        self.layout.addWidget(self.plate_title_label)
-
-        grid1 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid1)
-
-        # ## PCB Length
-        self.pcblength_entry = FCEntry()
-        self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
-
-        self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
-        grid1.addWidget(self.pcblengthlabel, 0, 0)
-        grid1.addWidget(self.pcblength_entry, 0, 1)
-
-        # ## PCB Width
-        self.pcbwidth_entry = FCEntry()
-        self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
-
-        self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
-        grid1.addWidget(self.pcbwidthlabel, 1, 0)
-        grid1.addWidget(self.pcbwidth_entry, 1, 1)
-
-        # ## Current Density
-        self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
-        self.cdensity_entry = FCEntry()
-
-        self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
-                                         "In Amps per Square Feet ASF."))
-        grid1.addWidget(self.cdensity_label, 2, 0)
-        grid1.addWidget(self.cdensity_entry, 2, 1)
-
-        # ## PCB Copper Growth
-        self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
-        self.growth_entry = FCEntry()
-
-        self.growth_label.setToolTip(_("How thick the copper growth is intended to be.\n"
-                                       "In microns."))
-        grid1.addWidget(self.growth_label, 3, 0)
-        grid1.addWidget(self.growth_entry, 3, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsTransformPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-
-        super(ToolsTransformPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Transform Tool Options")))
-
-        # ## Transformations
-        self.transform_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.transform_label.setToolTip(
-            _("Various transformations that can be applied\n"
-              "on a FlatCAM object.")
-        )
-        self.layout.addWidget(self.transform_label)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # ## Rotate Angle
-        self.rotate_entry = FCEntry()
-        self.rotate_label = QtWidgets.QLabel('%s:' % _("Rotate Angle"))
-        self.rotate_label.setToolTip(
-            _("Angle for Rotation action, in degrees.\n"
-              "Float number between -360 and 359.\n"
-              "Positive numbers for CW motion.\n"
-              "Negative numbers for CCW motion.")
-        )
-        grid0.addWidget(self.rotate_label, 0, 0)
-        grid0.addWidget(self.rotate_entry, 0, 1)
-
-        # ## Skew/Shear Angle on X axis
-        self.skewx_entry = FCEntry()
-        self.skewx_label = QtWidgets.QLabel('%s:' % _("Skew_X angle"))
-        self.skewx_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
-              "Float number between -360 and 359.")
-        )
-        grid0.addWidget(self.skewx_label, 1, 0)
-        grid0.addWidget(self.skewx_entry, 1, 1)
-
-        # ## Skew/Shear Angle on Y axis
-        self.skewy_entry = FCEntry()
-        self.skewy_label = QtWidgets.QLabel('%s:' % _("Skew_Y angle"))
-        self.skewy_label.setToolTip(
-            _("Angle for Skew action, in degrees.\n"
-              "Float number between -360 and 359.")
-        )
-        grid0.addWidget(self.skewy_label, 2, 0)
-        grid0.addWidget(self.skewy_entry, 2, 1)
-
-        # ## Scale factor on X axis
-        self.scalex_entry = FCEntry()
-        self.scalex_label = QtWidgets.QLabel('%s:' % _("Scale_X factor"))
-        self.scalex_label.setToolTip(
-            _("Factor for scaling on X axis.")
-        )
-        grid0.addWidget(self.scalex_label, 3, 0)
-        grid0.addWidget(self.scalex_entry, 3, 1)
-
-        # ## Scale factor on X axis
-        self.scaley_entry = FCEntry()
-        self.scaley_label = QtWidgets.QLabel('%s:' % _("Scale_Y factor"))
-        self.scaley_label.setToolTip(
-            _("Factor for scaling on Y axis.")
-        )
-        grid0.addWidget(self.scaley_label, 4, 0)
-        grid0.addWidget(self.scaley_entry, 4, 1)
-
-        # ## Link Scale factors
-        self.link_cb = FCCheckBox(_("Link"))
-        self.link_cb.setToolTip(
-            _("Scale the selected object(s)\n"
-              "using the Scale_X factor for both axis.")
-        )
-        grid0.addWidget(self.link_cb, 5, 0)
-
-        # ## Scale Reference
-        self.reference_cb = FCCheckBox('%s' % _("Scale Reference"))
-        self.reference_cb.setToolTip(
-            _("Scale the selected object(s)\n"
-              "using the origin reference when checked,\n"
-              "and the center of the biggest bounding box\n"
-              "of the selected objects when unchecked.")
-        )
-        grid0.addWidget(self.reference_cb, 5, 1)
-
-        # ## Offset distance on X axis
-        self.offx_entry = FCEntry()
-        self.offx_label = QtWidgets.QLabel('%s:' % _("Offset_X val"))
-        self.offx_label.setToolTip(
-           _("Distance to offset on X axis. In current units.")
-        )
-        grid0.addWidget(self.offx_label, 6, 0)
-        grid0.addWidget(self.offx_entry, 6, 1)
-
-        # ## Offset distance on Y axis
-        self.offy_entry = FCEntry()
-        self.offy_label = QtWidgets.QLabel('%s:' % _("Offset_Y val"))
-        self.offy_label.setToolTip(
-            _("Distance to offset on Y axis. In current units.")
-        )
-        grid0.addWidget(self.offy_label, 7, 0)
-        grid0.addWidget(self.offy_entry, 7, 1)
-
-        # ## Mirror (Flip) Reference Point
-        self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
-        self.mirror_reference_cb.setToolTip(
-            _("Flip the selected object(s)\n"
-              "around the point in Point Entry Field.\n"
-              "\n"
-              "The point coordinates can be captured by\n"
-              "left click on canvas together with pressing\n"
-              "SHIFT key. \n"
-              "Then click Add button to insert coordinates.\n"
-              "Or enter the coords in format (x, y) in the\n"
-              "Point Entry field and click Flip on X(Y)"))
-        grid0.addWidget(self.mirror_reference_cb, 8, 1)
-
-        self.flip_ref_label = QtWidgets.QLabel('%s:' % _(" Mirror Ref. Point"))
-        self.flip_ref_label.setToolTip(
-            _("Coordinates in format (x, y) used as reference for mirroring.\n"
-              "The 'x' in (x, y) will be used when using Flip on X and\n"
-              "the 'y' in (x, y) will be used when using Flip on Y and")
-        )
-        self.flip_ref_entry = EvalEntry2("(0, 0)")
-
-        grid0.addWidget(self.flip_ref_label, 9, 0)
-        grid0.addWidget(self.flip_ref_entry, 9, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-
-        super(ToolsSolderpastePrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("SolderPaste Tool Options")))
-
-        # ## Solder Paste Dispensing
-        self.solderpastelabel = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.solderpastelabel.setToolTip(
-            _("A tool to create GCode for dispensing\n"
-              "solder paste onto a PCB.")
-        )
-        self.layout.addWidget(self.solderpastelabel)
-
-        grid0 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0)
-
-        # Nozzle Tool Diameters
-        nozzletdlabel = QtWidgets.QLabel('%s:' % _('Tools dia'))
-        nozzletdlabel.setToolTip(
-            _("Diameters of nozzle tools, separated by ','")
-        )
-        self.nozzle_tool_dia_entry = FCEntry()
-        grid0.addWidget(nozzletdlabel, 0, 0)
-        grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
-
-        # New Nozzle Tool Dia
-        self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('New Nozzle Dia'))
-        self.addtool_entry_lbl.setToolTip(
-            _("Diameter for the new Nozzle tool to add in the Tool Table")
-        )
-        self.addtool_entry = FCEntry()
-        grid0.addWidget(self.addtool_entry_lbl, 1, 0)
-        grid0.addWidget(self.addtool_entry, 1, 1)
-
-        # Z dispense start
-        self.z_start_entry = FCEntry()
-        self.z_start_label = QtWidgets.QLabel('%s:' % _("Z Dispense Start"))
-        self.z_start_label.setToolTip(
-            _("The height (Z) when solder paste dispensing starts.")
-        )
-        grid0.addWidget(self.z_start_label, 2, 0)
-        grid0.addWidget(self.z_start_entry, 2, 1)
-
-        # Z dispense
-        self.z_dispense_entry = FCEntry()
-        self.z_dispense_label = QtWidgets.QLabel('%s:' % _("Z Dispense"))
-        self.z_dispense_label.setToolTip(
-            _("The height (Z) when doing solder paste dispensing.")
-        )
-        grid0.addWidget(self.z_dispense_label, 3, 0)
-        grid0.addWidget(self.z_dispense_entry, 3, 1)
-
-        # Z dispense stop
-        self.z_stop_entry = FCEntry()
-        self.z_stop_label = QtWidgets.QLabel('%s:' % _("Z Dispense Stop"))
-        self.z_stop_label.setToolTip(
-            _("The height (Z) when solder paste dispensing stops.")
-        )
-        grid0.addWidget(self.z_stop_label, 4, 0)
-        grid0.addWidget(self.z_stop_entry, 4, 1)
-
-        # Z travel
-        self.z_travel_entry = FCEntry()
-        self.z_travel_label = QtWidgets.QLabel('%s:' % _("Z Travel"))
-        self.z_travel_label.setToolTip(
-            _("The height (Z) for travel between pads\n"
-              "(without dispensing solder paste).")
-        )
-        grid0.addWidget(self.z_travel_label, 5, 0)
-        grid0.addWidget(self.z_travel_entry, 5, 1)
-
-        # Z toolchange location
-        self.z_toolchange_entry = FCEntry()
-        self.z_toolchange_label = QtWidgets.QLabel('%s:' % _("Z Toolchange"))
-        self.z_toolchange_label.setToolTip(
-            _("The height (Z) for tool (nozzle) change.")
-        )
-        grid0.addWidget(self.z_toolchange_label, 6, 0)
-        grid0.addWidget(self.z_toolchange_entry, 6, 1)
-
-        # X,Y Toolchange location
-        self.xy_toolchange_entry = FCEntry()
-        self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
-        self.xy_toolchange_label.setToolTip(
-            _("The X,Y location for tool (nozzle) change.\n"
-              "The format is (x, y) where x and y are real numbers.")
-        )
-        grid0.addWidget(self.xy_toolchange_label, 7, 0)
-        grid0.addWidget(self.xy_toolchange_entry, 7, 1)
-
-        # Feedrate X-Y
-        self.frxy_entry = FCEntry()
-        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
-        self.frxy_label.setToolTip(
-            _("Feedrate (speed) while moving on the X-Y plane.")
-        )
-        grid0.addWidget(self.frxy_label, 8, 0)
-        grid0.addWidget(self.frxy_entry, 8, 1)
-
-        # Feedrate Z
-        self.frz_entry = FCEntry()
-        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
-        self.frz_label.setToolTip(
-            _("Feedrate (speed) while moving vertically\n"
-              "(on Z plane).")
-        )
-        grid0.addWidget(self.frz_label, 9, 0)
-        grid0.addWidget(self.frz_entry, 9, 1)
-
-        # Feedrate Z Dispense
-        self.frz_dispense_entry = FCEntry()
-        self.frz_dispense_label = QtWidgets.QLabel('%s:' % _("Feedrate Z Dispense"))
-        self.frz_dispense_label.setToolTip(
-            _("Feedrate (speed) while moving up vertically\n"
-              "to Dispense position (on Z plane).")
-        )
-        grid0.addWidget(self.frz_dispense_label, 10, 0)
-        grid0.addWidget(self.frz_dispense_entry, 10, 1)
-
-        # Spindle Speed Forward
-        self.speedfwd_entry = FCEntry()
-        self.speedfwd_label = QtWidgets.QLabel('%s:' % _("Spindle Speed FWD"))
-        self.speedfwd_label.setToolTip(
-            _("The dispenser speed while pushing solder paste\n"
-              "through the dispenser nozzle.")
-        )
-        grid0.addWidget(self.speedfwd_label, 11, 0)
-        grid0.addWidget(self.speedfwd_entry, 11, 1)
-
-        # Dwell Forward
-        self.dwellfwd_entry = FCEntry()
-        self.dwellfwd_label = QtWidgets.QLabel('%s:' % _("Dwell FWD"))
-        self.dwellfwd_label.setToolTip(
-            _("Pause after solder dispensing.")
-        )
-        grid0.addWidget(self.dwellfwd_label, 12, 0)
-        grid0.addWidget(self.dwellfwd_entry, 12, 1)
-
-        # Spindle Speed Reverse
-        self.speedrev_entry = FCEntry()
-        self.speedrev_label = QtWidgets.QLabel('%s:' % _("Spindle Speed REV"))
-        self.speedrev_label.setToolTip(
-            _("The dispenser speed while retracting solder paste\n"
-              "through the dispenser nozzle.")
-        )
-        grid0.addWidget(self.speedrev_label, 13, 0)
-        grid0.addWidget(self.speedrev_entry, 13, 1)
-
-        # Dwell Reverse
-        self.dwellrev_entry = FCEntry()
-        self.dwellrev_label = QtWidgets.QLabel('%s:' % _("Dwell REV"))
-        self.dwellrev_label.setToolTip(
-            _("Pause after solder paste dispenser retracted,\n"
-              "to allow pressure equilibrium.")
-        )
-        grid0.addWidget(self.dwellrev_label, 14, 0)
-        grid0.addWidget(self.dwellrev_entry, 14, 1)
-
-        # Postprocessors
-        pp_label = QtWidgets.QLabel('%s:' % _('PostProcessor'))
-        pp_label.setToolTip(
-            _("Files that control the GCode generation.")
-        )
-
-        self.pp_combo = FCComboBox()
-        grid0.addWidget(pp_label, 15, 0)
-        grid0.addWidget(self.pp_combo, 15, 1)
-
-        self.layout.addStretch()
-
-
-class ToolsSubPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-
-        super(ToolsSubPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Substractor Tool Options")))
-
-        # ## Solder Paste Dispensing
-        self.sublabel = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
-        self.sublabel.setToolTip(
-            _("A tool to substract one Gerber or Geometry object\n"
-              "from another of the same type.")
-        )
-        self.layout.addWidget(self.sublabel)
-
-        self.close_paths_cb = FCCheckBox(_("Close paths"))
-        self.close_paths_cb.setToolTip(_("Checking this will close the paths cut by the Geometry substractor object."))
-        self.layout.addWidget(self.close_paths_cb)
-
-        self.layout.addStretch()
-
-
-class FAExcPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)
-        super(FAExcPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Excellon File associations")))
-
-        # ## Export G-Code
-        self.exc_list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Extensions list"))
-        self.exc_list_label.setToolTip(
-            _("List of file extensions to be\n"
-              "associated with FlatCAM.")
-        )
-        self.layout.addWidget(self.exc_list_label)
-
-        self.exc_list_text = FCTextArea()
-        # self.exc_list_text.sizeHint(custom_sizehint=150)
-        font = QtGui.QFont()
-        font.setPointSize(12)
-        self.exc_list_text.setFont(font)
-
-        self.layout.addWidget(self.exc_list_text)
-
-        self.exc_list_btn = FCButton(_("Apply"))
-        self.exc_list_btn.setToolTip(_("Apply the file associations between\n"
-                                       "FlatCAM and the files with above extensions.\n"
-                                       "They will be active after next logon.\n"
-                                       "This work only in Windows."))
-        self.layout.addWidget(self.exc_list_btn)
-
-        # self.layout.addStretch()
-
-
-class FAGcoPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gcode File associations Preferences", parent=None)
-        super(FAGcoPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("GCode File associations")))
-
-        # ## Export G-Code
-        self.gco_list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Extensions list"))
-        self.gco_list_label.setToolTip(
-            _("List of file extensions to be\n"
-              "associated with FlatCAM.")
-        )
-        self.layout.addWidget(self.gco_list_label)
-
-        self.gco_list_text = FCTextArea()
-        # self.gco_list_text.sizeHint(custom_sizehint=150)
-        font = QtGui.QFont()
-        font.setPointSize(12)
-        self.gco_list_text.setFont(font)
-
-        self.layout.addWidget(self.gco_list_text)
-
-        self.gco_list_btn = FCButton(_("Apply"))
-        self.gco_list_btn.setToolTip(_("Apply the file associations between\n"
-                                       "FlatCAM and the files with above extensions.\n"
-                                       "They will be active after next logon.\n"
-                                       "This work only in Windows."))
-        self.layout.addWidget(self.gco_list_btn)
-
-        # self.layout.addStretch()
-
-
-class FAGrbPrefGroupUI(OptionsGroupUI):
-    def __init__(self, parent=None):
-        # OptionsGroupUI.__init__(self, "Gerber File associations Preferences", parent=None)
-        super(FAGrbPrefGroupUI, self).__init__(self)
-
-        self.setTitle(str(_("Gerber File associations")))
-
-        # ## Export G-Code
-        self.grb_list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Extensions list"))
-        self.grb_list_label.setToolTip(
-            _("List of file extensions to be\n"
-              "associated with FlatCAM.")
-        )
-        self.layout.addWidget(self.grb_list_label)
-
-        self.grb_list_text = FCTextArea()
-        # self.grb_list_text.sizeHint(custom_sizehint=150)
-        self.layout.addWidget(self.grb_list_text)
-        font = QtGui.QFont()
-        font.setPointSize(12)
-        self.grb_list_text.setFont(font)
-
-        self.grb_list_btn = FCButton(_("Apply"))
-        self.grb_list_btn.setToolTip(_("Apply the file associations between\n"
-                                       "FlatCAM and the files with above extensions.\n"
-                                       "They will be active after next logon.\n"
-                                       "This work only in Windows."))
-
-        self.layout.addWidget(self.grb_list_btn)
-
-        # self.layout.addStretch()
-
-
-class FlatCAMActivityView(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        super().__init__(parent=parent)
-
-        self.setMinimumWidth(200)
-
-        self.icon = QtWidgets.QLabel(self)
-        self.icon.setGeometry(0, 0, 16, 12)
-        self.movie = QtGui.QMovie("share/active.gif")
-        self.icon.setMovie(self.movie)
-        # self.movie.start()
-
-        layout = QtWidgets.QHBoxLayout()
-        layout.setContentsMargins(5, 0, 5, 0)
-        layout.setAlignment(QtCore.Qt.AlignLeft)
-        self.setLayout(layout)
-
-        layout.addWidget(self.icon)
-        self.text = QtWidgets.QLabel(self)
-        self.text.setText(_("Idle."))
-
-        layout.addWidget(self.text)
-
-    def set_idle(self):
-        self.movie.stop()
-        self.text.setText(_("Idle."))
-
-    def set_busy(self, msg, no_movie=None):
-        if no_movie is not True:
-            self.movie.start()
-        self.text.setText(msg)
-
-
-class FlatCAMInfoBar(QtWidgets.QWidget):
-
-    def __init__(self, parent=None):
-        super(FlatCAMInfoBar, self).__init__(parent=parent)
-
-        self.icon = QtWidgets.QLabel(self)
-        self.icon.setGeometry(0, 0, 12, 12)
-        self.pmap = QtGui.QPixmap('share/graylight12.png')
-        self.icon.setPixmap(self.pmap)
-
-        layout = QtWidgets.QHBoxLayout()
-        layout.setContentsMargins(5, 0, 5, 0)
-        self.setLayout(layout)
-
-        layout.addWidget(self.icon)
-
-        self.text = QtWidgets.QLabel(self)
-        self.text.setText(_("Application started ..."))
-        self.text.setToolTip(_("Hello!"))
-
-        layout.addWidget(self.text)
-
-        layout.addStretch()
-
-    def set_text_(self, text, color=None):
-        self.text.setText(text)
-        self.text.setToolTip(text)
-        if color:
-            self.text.setStyleSheet('color: %s' % str(color))
-
-    def set_status(self, text, level="info"):
-        level = str(level)
-        self.pmap.fill()
-        if level == "ERROR" or level == "ERROR_NOTCL":
-            self.pmap = QtGui.QPixmap('share/redlight12.png')
-        elif level == "success" or level == "SUCCESS":
-            self.pmap = QtGui.QPixmap('share/greenlight12.png')
-        elif level == "WARNING" or level == "WARNING_NOTCL":
-            self.pmap = QtGui.QPixmap('share/yellowlight12.png')
-        elif level == "selected" or level == "SELECTED":
-            self.pmap = QtGui.QPixmap('share/bluelight12.png')
-        else:
-            self.pmap = QtGui.QPixmap('share/graylight12.png')
-
-        self.set_text_(text)
-        self.icon.setPixmap(self.pmap)
 # end of file

+ 2 - 2
flatcamGUI/PlotCanvas.py

@@ -152,10 +152,10 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
         except Exception as e:
             pass
 
-    def vis_connect(self, event_name, callback):
+    def graph_event_connect(self, event_name, callback):
         return getattr(self.events, event_name).connect(callback)
 
-    def vis_disconnect(self, event_name, callback=None):
+    def graph_event_disconnect(self, event_name, callback=None):
         if callback is None:
             getattr(self.events, event_name).disconnect()
         else:

+ 1008 - 0
flatcamGUI/PlotCanvasLegacy.py

@@ -0,0 +1,1008 @@
+############################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# http://caram.cl/software/flatcam                         #
+# Author: Juan Pablo Caram (c)                             #
+# Date: 2/5/2014                                           #
+# MIT Licence                                              #
+# Modified by Marius Stanciu 09/21/2019                    #
+############################################################
+
+from PyQt5 import QtGui, QtCore, QtWidgets
+
+# Prevent conflict with Qt5 and above.
+from matplotlib import use as mpl_use
+
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.backends.backend_agg import FigureCanvasAgg
+from matplotlib.widgets import Cursor
+
+# needed for legacy mode
+# Used for solid polygons in Matplotlib
+from descartes.patch import PolygonPatch
+
+from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString
+
+import FlatCAMApp
+from copy import deepcopy
+import logging
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+mpl_use("Qt5Agg")
+log = logging.getLogger('base')
+
+
+class CanvasCache(QtCore.QObject):
+    """
+
+    Case story #1:
+
+    1) No objects in the project.
+    2) Object is created (new_object() emits object_created(obj)).
+       on_object_created() adds (i) object to collection and emits
+       (ii) new_object_available() then calls (iii) object.plot()
+    3) object.plot() creates axes if necessary on
+       app.collection.figure. Then plots on it.
+    4) Plots on a cache-size canvas (in background).
+    5) Plot completes. Bitmap is generated.
+    6) Visible canvas is painted.
+
+    """
+
+    # Signals:
+    # A bitmap is ready to be displayed.
+    new_screen = QtCore.pyqtSignal()
+
+    def __init__(self, plotcanvas, app, dpi=50):
+
+        super(CanvasCache, self).__init__()
+
+        self.app = app
+
+        self.plotcanvas = plotcanvas
+        self.dpi = dpi
+
+        self.figure = Figure(dpi=dpi)
+
+        self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0)
+        self.axes.set_frame_on(False)
+        self.axes.set_xticks([])
+        self.axes.set_yticks([])
+
+        self.canvas = FigureCanvasAgg(self.figure)
+
+        self.cache = None
+
+    def run(self):
+
+        log.debug("CanvasCache Thread Started!")
+        self.plotcanvas.update_screen_request.connect(self.on_update_req)
+
+    def on_update_req(self, extents):
+        """
+        Event handler for an updated display request.
+
+        :param extents: [xmin, xmax, ymin, ymax, zoom(optional)]
+        """
+
+        # log.debug("Canvas update requested: %s" % str(extents))
+
+        # Note: This information below might be out of date. Establish
+        # a protocol regarding when to change the canvas in the main
+        # thread and when to check these values here in the background,
+        # or pass this data in the signal (safer).
+        # log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize()))
+        # log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density()))
+
+        # Move the requested screen portion to the main thread
+        # and inform about the update:
+
+        self.new_screen.emit()
+
+        # Continue to update the cache.
+
+    # def on_new_object_available(self):
+    #
+    #     log.debug("A new object is available. Should plot it!")
+
+
+class PlotCanvasLegacy(QtCore.QObject):
+    """
+    Class handling the plotting area in the application.
+    """
+
+    # Signals:
+    # Request for new bitmap to display. The parameter
+    # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
+    update_screen_request = QtCore.pyqtSignal(list)
+    double_click = QtCore.pyqtSignal(object)
+
+    def __init__(self, container, app):
+        """
+        The constructor configures the Matplotlib figure that
+        will contain all plots, creates the base axes and connects
+        events to the plotting area.
+
+        :param container: The parent container in which to draw plots.
+        :rtype: PlotCanvas
+        """
+
+        super(PlotCanvasLegacy, self).__init__()
+
+        self.app = app
+
+        # Options
+        self.x_margin = 15  # pixels
+        self.y_margin = 25  # Pixels
+
+        # Parent container
+        self.container = container
+
+        # Plots go onto a single matplotlib.figure
+        self.figure = Figure(dpi=50)  # TODO: dpi needed?
+        self.figure.patch.set_visible(False)
+
+        # These axes show the ticks and grid. No plotting done here.
+        # New axes must have a label, otherwise mpl returns an existing one.
+        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+        self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
+        self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
+
+        # The canvas is the top level container (FigureCanvasQTAgg)
+        self.canvas = FigureCanvas(self.figure)
+        self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
+        self.canvas.setFocus()
+        self.native = self.canvas
+
+        self.adjust_axes(-10, -10, 100, 100)
+        # self.canvas.set_can_focus(True)  # For key press
+
+        # Attach to parent
+        # self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
+        self.container.addWidget(self.canvas)  # Qt
+
+        # Copy a bitmap of the canvas for quick animation.
+        # Update every time the canvas is re-drawn.
+        self.background = self.canvas.copy_from_bbox(self.axes.bbox)
+
+        # ## Bitmap Cache
+        self.cache = CanvasCache(self, self.app)
+        self.cache_thread = QtCore.QThread()
+        self.cache.moveToThread(self.cache_thread)
+        # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
+        self.cache_thread.started.connect(self.cache.run)
+
+        self.cache_thread.start()
+        self.cache.new_screen.connect(self.on_new_screen)
+
+        # Events
+        self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press)
+        self.mr = self.graph_event_connect('button_release_event', self.on_mouse_release)
+        self.mm = self.graph_event_connect('motion_notify_event', self.on_mouse_move)
+        # self.canvas.connect('configure-event', self.auto_adjust_axes)
+        self.aaa = self.graph_event_connect('resize_event', self.auto_adjust_axes)
+        # self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
+        # self.canvas.connect("scroll-event", self.on_scroll)
+        self.osc = self.graph_event_connect('scroll_event', self.on_scroll)
+        # self.graph_event_connect('key_press_event', self.on_key_down)
+        # self.graph_event_connect('key_release_event', self.on_key_up)
+        self.odr = self.graph_event_connect('draw_event', self.on_draw)
+
+        self.mouse = [0, 0]
+        self.key = None
+
+        self.pan_axes = []
+        self.panning = False
+
+        # signal is the mouse is dragging
+        self.is_dragging = False
+
+        # signal if there is a doubleclick
+        self.is_dblclk = False
+
+    def graph_event_connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the Matplotlib interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: func
+        :return: Connection id
+        :rtype: int
+        """
+        if event_name == 'mouse_move':
+            event_name = 'motion_notify_event'
+        if event_name == 'mouse_press':
+            event_name = 'button_press_event'
+        if event_name == 'mouse_release':
+            event_name = 'button_release_event'
+        if event_name == 'mouse_double_click':
+            return self.double_click.connect(callback)
+
+        if event_name == 'key_press':
+            event_name = 'key_press_event'
+
+        return self.canvas.mpl_connect(event_name, callback)
+
+    def graph_event_disconnect(self, cid):
+        """
+        Disconnect callback with the give id.
+        :param cid: Callback id.
+        :return: None
+        """
+
+        # self.double_click.disconnect(cid)
+
+        self.canvas.mpl_disconnect(cid)
+
+    def on_new_screen(self):
+        pass
+        # log.debug("Cache updated the screen!")
+
+    def new_cursor(self, axes=None):
+        # if axes is None:
+        #     c = MplCursor(axes=self.axes, color='black', linewidth=1)
+        # else:
+        #     c = MplCursor(axes=axes, color='black', linewidth=1)
+
+        c = FakeCursor()
+
+        return c
+
+    def on_key_down(self, event):
+        """
+
+        :param event:
+        :return:
+        """
+        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
+        self.key = event.key
+
+    def on_key_up(self, event):
+        """
+
+        :param event:
+        :return:
+        """
+        self.key = None
+
+    def connect(self, event_name, callback):
+        """
+        Attach an event handler to the canvas through the native Qt interface.
+
+        :param event_name: Name of the event
+        :type event_name: str
+        :param callback: Function to call
+        :type callback: function
+        :return: Nothing
+        """
+        self.canvas.connect(event_name, callback)
+
+    def clear(self):
+        """
+        Clears axes and figure.
+
+        :return: None
+        """
+
+        # Clear
+        self.axes.cla()
+        try:
+            self.figure.clf()
+        except KeyError:
+            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
+
+        # Re-build
+        self.figure.add_axes(self.axes)
+        self.axes.set_aspect(1)
+        self.axes.grid(True)
+
+        # Re-draw
+        self.canvas.draw_idle()
+
+    def adjust_axes(self, xmin, ymin, xmax, ymax):
+        """
+        Adjusts all axes while maintaining the use of the whole canvas
+        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
+        request that will be modified to fit these restrictions.
+
+        :param xmin: Requested minimum value for the X axis.
+        :type xmin: float
+        :param ymin: Requested minimum value for the Y axis.
+        :type ymin: float
+        :param xmax: Requested maximum value for the X axis.
+        :type xmax: float
+        :param ymax: Requested maximum value for the Y axis.
+        :type ymax: float
+        :return: None
+        """
+
+        # FlatCAMApp.App.log.debug("PC.adjust_axes()")
+
+        width = xmax - xmin
+        height = ymax - ymin
+        try:
+            r = width / height
+        except ZeroDivisionError:
+            FlatCAMApp.App.log.error("Height is %f" % height)
+            return
+        canvas_w, canvas_h = self.canvas.get_width_height()
+        canvas_r = float(canvas_w) / canvas_h
+        x_ratio = float(self.x_margin) / canvas_w
+        y_ratio = float(self.y_margin) / canvas_h
+
+        if r > canvas_r:
+            ycenter = (ymin + ymax) / 2.0
+            newheight = height * r / canvas_r
+            ymin = ycenter - newheight / 2.0
+            ymax = ycenter + newheight / 2.0
+        else:
+            xcenter = (xmax + xmin) / 2.0
+            newwidth = width * canvas_r / r
+            xmin = xcenter - newwidth / 2.0
+            xmax = xcenter + newwidth / 2.0
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            if ax._label != 'base':
+                ax.set_frame_on(False)  # No frame
+                ax.set_xticks([])  # No tick
+                ax.set_yticks([])  # No ticks
+                ax.patch.set_visible(False)  # No background
+                ax.set_aspect(1)
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
+
+        # Sync re-draw to proper paint on form resize
+        self.canvas.draw()
+
+        # #### Temporary place-holder for cached update #####
+        self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+    def auto_adjust_axes(self, *args):
+        """
+        Calls ``adjust_axes()`` using the extents of the base axes.
+
+        :rtype : None
+        :return: None
+        """
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        self.adjust_axes(xmin, ymin, xmax, ymax)
+
+    def fit_view(self):
+        self.auto_adjust_axes()
+
+    def zoom(self, factor, center=None):
+        """
+        Zooms the plot by factor around a given
+        center point. Takes care of re-drawing.
+
+        :param factor: Number by which to scale the plot.
+        :type factor: float
+        :param center: Coordinates [x, y] of the point around which to scale the plot.
+        :type center: list
+        :return: None
+        """
+
+        factor = 1 / factor
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        if center is None or center == [None, None]:
+            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
+
+        # For keeping the point at the pointer location
+        relx = (xmax - center[0]) / width
+        rely = (ymax - center[1]) / height
+
+        new_width = width / factor
+        new_height = height / factor
+
+        xmin = center[0] - new_width * (1 - relx)
+        xmax = center[0] + new_width * relx
+        ymin = center[1] - new_height * (1 - rely)
+        ymax = center[1] + new_height * rely
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin, xmax))
+            ax.set_ylim((ymin, ymax))
+
+        # Async re-draw
+        self.canvas.draw_idle()
+
+        # #### Temporary place-holder for cached update #####
+        self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+    def pan(self, x, y):
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        # Adjust axes
+        for ax in self.figure.get_axes():
+            ax.set_xlim((xmin + x * width, xmax + x * width))
+            ax.set_ylim((ymin + y * height, ymax + y * height))
+
+        # Re-draw
+        self.canvas.draw_idle()
+
+        # #### Temporary place-holder for cached update #####
+        self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+    def new_axes(self, name):
+        """
+        Creates and returns an Axes object attached to this object's Figure.
+
+        :param name: Unique label for the axes.
+        :return: Axes attached to the figure.
+        :rtype: Axes
+        """
+
+        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
+
+    def on_scroll(self, event):
+        """
+        Scroll event handler.
+
+        :param event: Event object containing the event information.
+        :return: None
+        """
+
+        # So it can receive key presses
+        # self.canvas.grab_focus()
+        self.canvas.setFocus()
+
+        # Event info
+        # z, direction = event.get_scroll_direction()
+
+        if self.key is None:
+
+            if event.button == 'up':
+                self.zoom(1 / 1.5, self.mouse)
+            else:
+                self.zoom(1.5, self.mouse)
+            return
+
+        if self.key == 'shift':
+
+            if event.button == 'up':
+                self.pan(0.3, 0)
+            else:
+                self.pan(-0.3, 0)
+            return
+
+        if self.key == 'control':
+
+            if event.button == 'up':
+                self.pan(0, 0.3)
+            else:
+                self.pan(0, -0.3)
+            return
+
+    def on_mouse_press(self, event):
+
+        self.is_dragging = True
+
+        # Check for middle mouse button press
+        if self.app.defaults["global_pan_button"] == '2':
+            pan_button = 3  # right button for Matplotlib
+        else:
+            pan_button = 2  # middle button for Matplotlib
+
+        if event.button == pan_button:
+            # Prepare axes for pan (using 'matplotlib' pan function)
+            self.pan_axes = []
+            for a in self.figure.get_axes():
+                if (event.x is not None and event.y is not None and a.in_axes(event) and
+                        a.get_navigate() and a.can_pan()):
+                    a.start_pan(event.x, event.y, 1)
+                    self.pan_axes.append(a)
+
+            # Set pan view flag
+            if len(self.pan_axes) > 0:
+                self.panning = True
+
+        if event.dblclick:
+            self.double_click.emit(event)
+
+    def on_mouse_release(self, event):
+
+        self.is_dragging = False
+
+        # Check for middle mouse button release to complete pan procedure
+        # Check for middle mouse button press
+        if self.app.defaults["global_pan_button"] == '2':
+            pan_button = 3  # right button for Matplotlib
+        else:
+            pan_button = 2  # middle button for Matplotlib
+
+        if event.button == pan_button:
+            for a in self.pan_axes:
+                a.end_pan()
+
+            # Clear pan flag
+            self.panning = False
+
+    def on_mouse_move(self, event):
+        """
+        Mouse movement event hadler. Stores the coordinates. Updates view on pan.
+
+        :param event: Contains information about the event.
+        :return: None
+        """
+
+        try:
+            x = float(event.xdata)
+            y = float(event.ydata)
+        except TypeError:
+            return
+
+        self.mouse = [event.xdata, event.ydata]
+
+        self.canvas.restore_region(self.background)
+
+        # Update pan view on mouse move
+        if self.panning is True:
+            # x_pan, y_pan = self.app.geo_editor.snap(event.xdata, event.ydata)
+            # self.app.app_cursor.set_data(event, (x_pan, y_pan))
+            for a in self.pan_axes:
+                a.drag_pan(1, event.key, event.x, event.y)
+
+            # Async re-draw (redraws only on thread idle state, uses timer on backend)
+            self.canvas.draw_idle()
+
+            # #### Temporary place-holder for cached update #####
+            self.update_screen_request.emit([0, 0, 0, 0, 0])
+
+        x, y = self.app.geo_editor.snap(x, y)
+        if self.app.app_cursor.enabled is True:
+            # Pointer (snapped)
+            elements = self.axes.plot(x, y, 'k+', ms=40, mew=2, animated=True)
+            for el in elements:
+                self.axes.draw_artist(el)
+
+        self.canvas.blit(self.axes.bbox)
+
+    def translate_coords(self, position):
+        """
+        This does not do much. It's just for code compatibility
+
+        :param position: Mouse event position
+        :return: Tuple with mouse position
+        """
+        return (position[0], position[1])
+
+    def on_draw(self, renderer):
+
+        # Store background on canvas redraw
+        self.background = self.canvas.copy_from_bbox(self.axes.bbox)
+
+    def get_axes_pixelsize(self):
+        """
+        Axes size in pixels.
+
+        :return: Pixel width and height
+        :rtype: tuple
+        """
+        bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
+        width, height = bbox.width, bbox.height
+        width *= self.figure.dpi
+        height *= self.figure.dpi
+        return width, height
+
+    def get_density(self):
+        """
+        Returns unit length per pixel on horizontal
+        and vertical axes.
+
+        :return: X and Y density
+        :rtype: tuple
+        """
+        xpx, ypx = self.get_axes_pixelsize()
+
+        xmin, xmax = self.axes.get_xlim()
+        ymin, ymax = self.axes.get_ylim()
+        width = xmax - xmin
+        height = ymax - ymin
+
+        return width / xpx, height / ypx
+
+
+class FakeCursor:
+    """
+    This is a fake cursor to ensure compatibility with the OpenGL engine (VisPy).
+    This way I don't have to chane (disable) things related to the cursor all over when
+    using the low performance Matplotlib 2D graphic engine.
+    """
+    def __init__(self):
+        self._enabled = True
+
+    @property
+    def enabled(self):
+        return True if self._enabled else False
+
+    @enabled.setter
+    def enabled(self, value):
+        self._enabled = value
+
+    def set_data(self, pos, **kwargs):
+        """Internal event handler to draw the cursor when the mouse moves."""
+        pass
+
+
+class MplCursor(Cursor):
+    """
+    Unfortunately this gets attached to the current axes and if a new axes is added
+    it will not be showed until that axes is deleted.
+    Not the kind of behavior needed here so I don't use it anymore.
+    """
+    def __init__(self, axes, color='red', linewidth=1):
+
+        super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth)
+        self._enabled = True
+
+        self.axes = axes
+        self.color = color
+        self.linewidth = linewidth
+
+        self.x = None
+        self.y = None
+
+    @property
+    def enabled(self):
+        return True if self._enabled else False
+
+    @enabled.setter
+    def enabled(self, value):
+        self._enabled = value
+        self.visible = self._enabled
+        self.canvas.draw()
+
+    def onmove(self, event):
+        pass
+
+    def set_data(self, event, pos):
+        """Internal event handler to draw the cursor when the mouse moves."""
+        self.x = pos[0]
+        self.y = pos[1]
+
+        if self.ignore(event):
+            return
+        if not self.canvas.widgetlock.available(self):
+            return
+        if event.inaxes != self.ax:
+            self.linev.set_visible(False)
+            self.lineh.set_visible(False)
+
+            if self.needclear:
+                self.canvas.draw()
+                self.needclear = False
+            return
+        self.needclear = True
+        if not self.visible:
+            return
+        self.linev.set_xdata((self.x, self.x))
+
+        self.lineh.set_ydata((self.y, self.y))
+        self.linev.set_visible(self.visible and self.vertOn)
+        self.lineh.set_visible(self.visible and self.horizOn)
+
+        self._update()
+
+
+class ShapeCollectionLegacy:
+    """
+    This will create the axes for each collection of shapes and will also
+    hold the collection of shapes into a dict self._shapes.
+    This handles the shapes redraw on canvas.
+    """
+    def __init__(self, obj, app, name=None, annotation_job=None):
+        """
+
+        :param obj: this is the object to which the shapes collection is attached and for
+        which it will have to draw shapes
+        :param app: this is the FLatCAM.App usually, needed because we have to access attributes there
+        :param name: this is the name given to the Matplotlib axes; it needs to be unique due of Matplotlib requurements
+        :param annotation_job: make this True if the job needed is just for annotation
+        """
+        self.obj = obj
+        self.app = app
+        self.annotation_job = annotation_job
+
+        self._shapes = dict()
+        self.shape_dict = dict()
+        self.shape_id = 0
+
+        self._color = None
+        self._face_color = None
+        self._visible = True
+        self._update = False
+        self._alpha = None
+        self._tool_tolerance = None
+        self._tooldia = None
+
+        self._obj = None
+        self._gcode_parsed = None
+
+        if name is None:
+            axes_name = self.obj.options['name']
+        else:
+            axes_name = name
+
+        # Axes must exist and be attached to canvas.
+        if axes_name not in self.app.plotcanvas.figure.axes:
+            self.axes = self.app.plotcanvas.new_axes(axes_name)
+
+    def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
+            update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None):
+        """
+        This function will add shapes to the shape collection
+
+        :param shape: the Shapely shape to be added to the shape collection
+        :param color: edge color of the shape, hex value
+        :param face_color: the body color of the shape, hex value
+        :param alpha: level of transparency of the shape [0.0 ... 1.0]; Float
+        :param visible: if True will allow the shapes to be added
+        :param update: not used; just for compatibility with VIsPy canvas
+        :param layer: just for compatibility with VIsPy canvas
+        :param tolerance: just for compatibility with VIsPy canvas
+        :param obj: not used
+        :param gcode_parsed: not used; just for compatibility with VIsPy canvas
+        :param tool_tolerance: just for compatibility with VIsPy canvas
+        :param tooldia:
+        :return:
+        """
+        self._color = color[:-2] if color is not None else None
+        self._face_color = face_color[:-2] if face_color is not None else None
+        self._alpha = int(face_color[-2:], 16) / 255 if face_color is not None else 0.75
+
+        if alpha is not None:
+            self._alpha = alpha
+
+        self._visible = visible
+        self._update = update
+
+        # CNCJob object related arguments
+        self._obj = obj
+        self._gcode_parsed = gcode_parsed
+        self._tool_tolerance = tool_tolerance
+        self._tooldia = tooldia
+
+        # if self._update:
+        #     self.clear()
+
+        try:
+            for sh in shape:
+                self.shape_id += 1
+                self.shape_dict.update({
+                    'color': self._color,
+                    'face_color': self._face_color,
+                    'alpha': self._alpha,
+                    'shape': sh
+                })
+
+                self._shapes.update({
+                    self.shape_id: deepcopy(self.shape_dict)
+                })
+        except TypeError:
+            self.shape_id += 1
+            self.shape_dict.update({
+                'color': self._color,
+                'face_color': self._face_color,
+                'alpha': self._alpha,
+                'shape': shape
+            })
+
+            self._shapes.update({
+                self.shape_id: deepcopy(self.shape_dict)
+            })
+
+        return self.shape_id
+
+    def clear(self, update=None):
+        """
+        Clear the canvas of the shapes.
+
+        :param update:
+        :return: None
+        """
+        self._shapes.clear()
+        self.shape_id = 0
+
+        self.axes.cla()
+        self.app.plotcanvas.auto_adjust_axes()
+
+        if update is True:
+            self.redraw()
+
+    def redraw(self):
+        """
+        This draw the shapes in the shapes collection, on canvas
+
+        :return: None
+        """
+        path_num = 0
+        local_shapes = deepcopy(self._shapes)
+
+        try:
+            obj_type = self.obj.kind
+        except AttributeError:
+            obj_type = 'utility'
+
+        if self._visible:
+            for element in local_shapes:
+                if obj_type == 'excellon':
+                    # Plot excellon (All polygons?)
+                    if self.obj.options["solid"] and isinstance(local_shapes[element]['shape'], Polygon):
+                        patch = PolygonPatch(local_shapes[element]['shape'],
+                                             facecolor="#C40000",
+                                             edgecolor="#750000",
+                                             alpha=local_shapes[element]['alpha'],
+                                             zorder=3)
+                        self.axes.add_patch(patch)
+                    else:
+                        x, y = local_shapes[element]['shape'].exterior.coords.xy
+                        self.axes.plot(x, y, 'r-')
+                        for ints in local_shapes[element]['shape'].interiors:
+                            x, y = ints.coords.xy
+                            self.axes.plot(x, y, 'o-')
+                elif obj_type == 'geometry':
+                    if type(local_shapes[element]['shape']) == Polygon:
+                        x, y = local_shapes[element]['shape'].exterior.coords.xy
+                        self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                        for ints in local_shapes[element]['shape'].interiors:
+                            x, y = ints.coords.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                    elif type(local_shapes[element]['shape']) == LineString or \
+                            type(local_shapes[element]['shape']) == LinearRing:
+
+                        x, y = local_shapes[element]['shape'].coords.xy
+                        self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+
+                elif obj_type == 'gerber':
+                    if self.obj.options["multicolored"]:
+                        linespec = '-'
+                    else:
+                        linespec = 'k-'
+
+                    if self.obj.options["solid"]:
+                        try:
+                            patch = PolygonPatch(local_shapes[element]['shape'],
+                                                 facecolor=local_shapes[element]['face_color'],
+                                                 edgecolor=local_shapes[element]['color'],
+                                                 alpha=local_shapes[element]['alpha'],
+                                                 zorder=2)
+                            self.axes.add_patch(patch)
+                        except AssertionError:
+                            FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
+                            FlatCAMApp.App.log.warning(str(element))
+                    else:
+                        x, y = local_shapes[element]['shape'].exterior.xy
+                        self.axes.plot(x, y, linespec)
+                        for ints in local_shapes[element]['shape'].interiors:
+                            x, y = ints.coords.xy
+                            self.axes.plot(x, y, linespec)
+                elif obj_type == 'cncjob':
+
+                    if local_shapes[element]['face_color'] is None:
+                        linespec = '--'
+                        linecolor = local_shapes[element]['color']
+                        # if geo['kind'][0] == 'C':
+                        #     linespec = 'k-'
+                        x, y = local_shapes[element]['shape'].coords.xy
+                        self.axes.plot(x, y, linespec, color=linecolor)
+                    else:
+                        path_num += 1
+                        if isinstance(local_shapes[element]['shape'], Polygon):
+                            self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].exterior.coords[0],
+                                               xycoords='data', fontsize=20)
+                        else:
+                            self.axes.annotate(str(path_num), xy=local_shapes[element]['shape'].coords[0],
+                                               xycoords='data', fontsize=20)
+
+                        patch = PolygonPatch(local_shapes[element]['shape'],
+                                             facecolor=local_shapes[element]['face_color'],
+                                             edgecolor=local_shapes[element]['color'],
+                                             alpha=local_shapes[element]['alpha'], zorder=2)
+                        self.axes.add_patch(patch)
+                elif obj_type == 'utility':
+                    # not a FlatCAM object, must be utility
+                    if local_shapes[element]['face_color']:
+                        try:
+                            patch = PolygonPatch(local_shapes[element]['shape'],
+                                                 facecolor=local_shapes[element]['face_color'],
+                                                 edgecolor=local_shapes[element]['color'],
+                                                 alpha=local_shapes[element]['alpha'],
+                                                 zorder=2)
+                            self.axes.add_patch(patch)
+                        except Exception as e:
+                            log.debug("ShapeCollectionLegacy.redraw() --> %s" % str(e))
+                    else:
+                        if isinstance(local_shapes[element]['shape'], Polygon):
+                            x, y = local_shapes[element]['shape'].exterior.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                            for ints in local_shapes[element]['shape'].interiors:
+                                x, y = ints.coords.xy
+                                self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+                        else:
+                            x, y = local_shapes[element]['shape'].coords.xy
+                            self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
+
+        self.app.plotcanvas.auto_adjust_axes()
+
+    def set(self, text, pos, visible=True, font_size=16, color=None):
+        """
+        This will set annotations on the canvas.
+
+        :param text: a list of text elements to be used as annotations
+        :param pos: a list of positions for showing the text elements above
+        :param visible: if True will display annotations, if False will clear them on canvas
+        :param font_size: the font size or the annotations
+        :param color: color of the annotations
+        :return: None
+        """
+        if color is None:
+            color = "#000000FF"
+
+        if visible is not True:
+            self.clear()
+            return
+
+        if len(text) != len(pos):
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not annotate due of a difference between the number "
+                                                        "of text elements and the number of text positions."))
+            return
+
+        for idx in range(len(text)):
+            try:
+                self.axes.annotate(text[idx], xy=pos[idx], xycoords='data', fontsize=font_size, color=color)
+            except Exception as e:
+                log.debug("ShapeCollectionLegacy.set() --> %s" % str(e))
+
+        self.app.plotcanvas.auto_adjust_axes()
+
+    @property
+    def visible(self):
+        return self._visible
+
+    @visible.setter
+    def visible(self, value):
+        if value is False:
+            self.axes.cla()
+            self.app.plotcanvas.auto_adjust_axes()
+        else:
+            if self._visible is False:
+                self.redraw()
+        self._visible = value
+
+    @property
+    def enabled(self):
+        return self._visible
+
+    @enabled.setter
+    def enabled(self, value):
+        if value is False:
+            self.axes.cla()
+            self.app.plotcanvas.auto_adjust_axes()
+        else:
+            if self._visible is False:
+                self.redraw()
+        self._visible = value

+ 4724 - 0
flatcamGUI/PreferencesUI.py

@@ -0,0 +1,4724 @@
+from PyQt5.QtCore import QSettings
+from flatcamGUI.GUIElements import *
+import platform
+import webbrowser
+import sys
+
+from flatcamEditors.FlatCAMGeoEditor import FCShapeTool
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class OptionsGroupUI(QtWidgets.QGroupBox):
+    def __init__(self, title, parent=None):
+        # QtGui.QGroupBox.__init__(self, title, parent=parent)
+        super(OptionsGroupUI, self).__init__()
+        self.setStyleSheet("""
+        QGroupBox
+        {
+            font-size: 16px;
+            font-weight: bold;
+        }
+        """)
+
+        self.layout = QtWidgets.QVBoxLayout()
+        self.setLayout(self.layout)
+
+
+class GeneralPreferencesUI(QtWidgets.QWidget):
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.general_app_group = GeneralAppPrefGroupUI()
+        self.general_app_group.setMinimumWidth(290)
+
+        self.general_gui_group = GeneralGUIPrefGroupUI()
+        self.general_gui_group.setMinimumWidth(250)
+
+        self.general_gui_set_group = GeneralGUISetGroupUI()
+        self.general_gui_set_group.setMinimumWidth(250)
+
+        self.layout.addWidget(self.general_app_group)
+        self.layout.addWidget(self.general_gui_group)
+        self.layout.addWidget(self.general_gui_set_group)
+
+        self.layout.addStretch()
+
+
+class GerberPreferencesUI(QtWidgets.QWidget):
+
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.gerber_gen_group = GerberGenPrefGroupUI()
+        self.gerber_gen_group.setMinimumWidth(250)
+        self.gerber_opt_group = GerberOptPrefGroupUI()
+        self.gerber_opt_group.setMinimumWidth(250)
+        self.gerber_exp_group = GerberExpPrefGroupUI()
+        self.gerber_exp_group.setMinimumWidth(230)
+        self.gerber_adv_opt_group = GerberAdvOptPrefGroupUI()
+        self.gerber_adv_opt_group.setMinimumWidth(200)
+        self.gerber_editor_group = GerberEditorPrefGroupUI()
+        self.gerber_editor_group.setMinimumWidth(200)
+
+        self.vlay = QtWidgets.QVBoxLayout()
+        self.vlay.addWidget(self.gerber_opt_group)
+        self.vlay.addWidget(self.gerber_exp_group)
+
+        self.layout.addWidget(self.gerber_gen_group)
+        self.layout.addLayout(self.vlay)
+        self.layout.addWidget(self.gerber_adv_opt_group)
+        self.layout.addWidget(self.gerber_editor_group)
+
+        self.layout.addStretch()
+
+
+class ExcellonPreferencesUI(QtWidgets.QWidget):
+
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.excellon_gen_group = ExcellonGenPrefGroupUI()
+        self.excellon_gen_group.setMinimumWidth(220)
+        self.excellon_opt_group = ExcellonOptPrefGroupUI()
+        self.excellon_opt_group.setMinimumWidth(290)
+        self.excellon_exp_group = ExcellonExpPrefGroupUI()
+        self.excellon_exp_group.setMinimumWidth(250)
+        self.excellon_adv_opt_group = ExcellonAdvOptPrefGroupUI()
+        self.excellon_adv_opt_group.setMinimumWidth(250)
+        self.excellon_editor_group = ExcellonEditorPrefGroupUI()
+        self.excellon_editor_group.setMinimumWidth(260)
+
+        self.vlay = QtWidgets.QVBoxLayout()
+        self.vlay.addWidget(self.excellon_opt_group)
+        self.vlay.addWidget(self.excellon_exp_group)
+
+        self.layout.addWidget(self.excellon_gen_group)
+        self.layout.addLayout(self.vlay)
+        self.layout.addWidget(self.excellon_adv_opt_group)
+        self.layout.addWidget(self.excellon_editor_group)
+
+        self.layout.addStretch()
+
+
+class GeometryPreferencesUI(QtWidgets.QWidget):
+
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.geometry_gen_group = GeometryGenPrefGroupUI()
+        self.geometry_gen_group.setMinimumWidth(220)
+        self.geometry_opt_group = GeometryOptPrefGroupUI()
+        self.geometry_opt_group.setMinimumWidth(300)
+        self.geometry_adv_opt_group = GeometryAdvOptPrefGroupUI()
+        self.geometry_adv_opt_group.setMinimumWidth(270)
+        self.geometry_editor_group = GeometryEditorPrefGroupUI()
+        self.geometry_editor_group.setMinimumWidth(250)
+
+        self.layout.addWidget(self.geometry_gen_group)
+        self.layout.addWidget(self.geometry_opt_group)
+        self.layout.addWidget(self.geometry_adv_opt_group)
+        self.layout.addWidget(self.geometry_editor_group)
+
+        self.layout.addStretch()
+
+
+class ToolsPreferencesUI(QtWidgets.QWidget):
+
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.tools_ncc_group = ToolsNCCPrefGroupUI()
+        self.tools_ncc_group.setMinimumWidth(220)
+        self.tools_paint_group = ToolsPaintPrefGroupUI()
+        self.tools_paint_group.setMinimumWidth(220)
+
+        self.tools_cutout_group = ToolsCutoutPrefGroupUI()
+        self.tools_cutout_group.setMinimumWidth(220)
+
+        self.tools_2sided_group = Tools2sidedPrefGroupUI()
+        self.tools_2sided_group.setMinimumWidth(220)
+
+        self.tools_film_group = ToolsFilmPrefGroupUI()
+        self.tools_film_group.setMinimumWidth(220)
+
+        self.tools_panelize_group = ToolsPanelizePrefGroupUI()
+        self.tools_panelize_group.setMinimumWidth(220)
+
+        self.tools_calculators_group = ToolsCalculatorsPrefGroupUI()
+        self.tools_calculators_group.setMinimumWidth(220)
+
+        self.tools_transform_group = ToolsTransformPrefGroupUI()
+        self.tools_transform_group.setMinimumWidth(200)
+
+        self.tools_solderpaste_group = ToolsSolderpastePrefGroupUI()
+        self.tools_solderpaste_group.setMinimumWidth(200)
+
+        self.tools_sub_group = ToolsSubPrefGroupUI()
+        self.tools_sub_group.setMinimumWidth(200)
+
+        self.vlay = QtWidgets.QVBoxLayout()
+        self.vlay.addWidget(self.tools_ncc_group)
+        self.vlay.addWidget(self.tools_paint_group)
+
+        self.vlay1 = QtWidgets.QVBoxLayout()
+        self.vlay1.addWidget(self.tools_cutout_group)
+        self.vlay1.addWidget(self.tools_transform_group)
+        self.vlay1.addWidget(self.tools_2sided_group)
+
+        self.vlay2 = QtWidgets.QVBoxLayout()
+        self.vlay2.addWidget(self.tools_panelize_group)
+        self.vlay2.addWidget(self.tools_calculators_group)
+
+        self.vlay3 = QtWidgets.QVBoxLayout()
+        self.vlay3.addWidget(self.tools_solderpaste_group)
+        self.vlay3.addWidget(self.tools_sub_group)
+        self.vlay3.addWidget(self.tools_film_group)
+
+        self.layout.addLayout(self.vlay)
+        self.layout.addLayout(self.vlay1)
+        self.layout.addLayout(self.vlay2)
+        self.layout.addLayout(self.vlay3)
+
+        self.layout.addStretch()
+
+
+class CNCJobPreferencesUI(QtWidgets.QWidget):
+
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.cncjob_gen_group = CNCJobGenPrefGroupUI()
+        self.cncjob_gen_group.setMinimumWidth(320)
+        self.cncjob_opt_group = CNCJobOptPrefGroupUI()
+        self.cncjob_opt_group.setMinimumWidth(260)
+        self.cncjob_adv_opt_group = CNCJobAdvOptPrefGroupUI()
+        self.cncjob_adv_opt_group.setMinimumWidth(260)
+
+        self.layout.addWidget(self.cncjob_gen_group)
+        self.layout.addWidget(self.cncjob_opt_group)
+        self.layout.addWidget(self.cncjob_adv_opt_group)
+
+        self.layout.addStretch()
+
+
+class UtilPreferencesUI(QtWidgets.QWidget):
+
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent=parent)
+        self.layout = QtWidgets.QHBoxLayout()
+        self.setLayout(self.layout)
+
+        self.vlay = QtWidgets.QVBoxLayout()
+        self.fa_excellon_group = FAExcPrefGroupUI()
+        self.fa_excellon_group.setMinimumWidth(260)
+
+        self.fa_gcode_group = FAGcoPrefGroupUI()
+        self.fa_gcode_group.setMinimumWidth(260)
+
+        self.vlay.addWidget(self.fa_excellon_group)
+        self.vlay.addWidget(self.fa_gcode_group)
+
+        self.fa_gerber_group = FAGrbPrefGroupUI()
+        self.fa_gerber_group.setMinimumWidth(260)
+
+        self.kw_group = AutoCompletePrefGroupUI()
+        self.kw_group.setMinimumWidth(260)
+
+        self.layout.addLayout(self.vlay)
+        self.layout.addWidget(self.fa_gerber_group)
+        self.layout.addWidget(self.kw_group)
+
+        self.layout.addStretch()
+
+
+class GeneralGUIPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        super(GeneralGUIPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("GUI Preferences")))
+
+        # Create a form layout for the Application general settings
+        self.form_box = QtWidgets.QFormLayout()
+
+        # Grid X Entry
+        self.gridx_label = QtWidgets.QLabel('%s:' % _('Grid X value'))
+        self.gridx_label.setToolTip(
+           _("This is the Grid snap value on X axis.")
+        )
+        self.gridx_entry = FCEntry3()
+
+        # Grid Y Entry
+        self.gridy_label = QtWidgets.QLabel('%s:' % _('Grid Y value'))
+        self.gridy_label.setToolTip(
+            _("This is the Grid snap value on Y axis.")
+        )
+        self.gridy_entry = FCEntry3()
+
+        # Snap Max Entry
+        self.snap_max_label = QtWidgets.QLabel('%s:' % _('Snap Max'))
+        self.snap_max_label.setToolTip(_("Max. magnet distance"))
+        self.snap_max_dist_entry = FCEntry()
+
+        # Workspace
+        self.workspace_lbl = QtWidgets.QLabel('%s:' % _('Workspace'))
+        self.workspace_lbl.setToolTip(
+           _("Draw a delimiting rectangle on canvas.\n"
+             "The purpose is to illustrate the limits for our work.")
+        )
+        self.workspace_type_lbl = QtWidgets.QLabel('%s:' % _('Wk. format'))
+        self.workspace_type_lbl.setToolTip(
+           _("Select the type of rectangle to be used on canvas,\n"
+             "as valid workspace.")
+        )
+        self.workspace_cb = FCCheckBox()
+        self.wk_cb = FCComboBox()
+        self.wk_cb.addItem('A4P')
+        self.wk_cb.addItem('A4L')
+        self.wk_cb.addItem('A3P')
+        self.wk_cb.addItem('A3L')
+
+        self.wks = OptionalInputSection(self.workspace_cb, [self.workspace_type_lbl, self.wk_cb])
+
+        # Plot Fill Color
+        self.pf_color_label = QtWidgets.QLabel('%s:' % _('Plot Fill'))
+        self.pf_color_label.setToolTip(
+           _("Set the fill color for plotted objects.\n"
+             "First 6 digits are the color and the last 2\n"
+             "digits are for alpha (transparency) level.")
+        )
+        self.pf_color_entry = FCEntry()
+        self.pf_color_button = QtWidgets.QPushButton()
+        self.pf_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_1 = QtWidgets.QHBoxLayout()
+        self.form_box_child_1.addWidget(self.pf_color_entry)
+        self.form_box_child_1.addWidget(self.pf_color_button)
+        self.form_box_child_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Plot Fill Transparency Level
+        self.pf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha Level'))
+        self.pf_alpha_label.setToolTip(
+           _("Set the fill transparency for plotted objects.")
+        )
+        self.pf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
+        self.pf_color_alpha_slider.setMinimum(0)
+        self.pf_color_alpha_slider.setMaximum(255)
+        self.pf_color_alpha_slider.setSingleStep(1)
+
+        self.pf_color_alpha_spinner = FCSpinner()
+        self.pf_color_alpha_spinner.setMinimumWidth(70)
+        self.pf_color_alpha_spinner.setMinimum(0)
+        self.pf_color_alpha_spinner.setMaximum(255)
+
+        self.form_box_child_2 = QtWidgets.QHBoxLayout()
+        self.form_box_child_2.addWidget(self.pf_color_alpha_slider)
+        self.form_box_child_2.addWidget(self.pf_color_alpha_spinner)
+
+        # Plot Line Color
+        self.pl_color_label = QtWidgets.QLabel('%s:' % _('Plot Line'))
+        self.pl_color_label.setToolTip(
+           _("Set the line color for plotted objects.")
+        )
+        self.pl_color_entry = FCEntry()
+        self.pl_color_button = QtWidgets.QPushButton()
+        self.pl_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_3 = QtWidgets.QHBoxLayout()
+        self.form_box_child_3.addWidget(self.pl_color_entry)
+        self.form_box_child_3.addWidget(self.pl_color_button)
+        self.form_box_child_3.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Plot Selection (left - right) Fill Color
+        self.sf_color_label = QtWidgets.QLabel('%s:' % _('Sel. Fill'))
+        self.sf_color_label.setToolTip(
+            _("Set the fill color for the selection box\n"
+              "in case that the selection is done from left to right.\n"
+              "First 6 digits are the color and the last 2\n"
+              "digits are for alpha (transparency) level.")
+        )
+        self.sf_color_entry = FCEntry()
+        self.sf_color_button = QtWidgets.QPushButton()
+        self.sf_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_4 = QtWidgets.QHBoxLayout()
+        self.form_box_child_4.addWidget(self.sf_color_entry)
+        self.form_box_child_4.addWidget(self.sf_color_button)
+        self.form_box_child_4.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Plot Selection (left - right) Fill Transparency Level
+        self.sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha Level'))
+        self.sf_alpha_label.setToolTip(
+            _("Set the fill transparency for the 'left to right' selection box.")
+        )
+        self.sf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
+        self.sf_color_alpha_slider.setMinimum(0)
+        self.sf_color_alpha_slider.setMaximum(255)
+        self.sf_color_alpha_slider.setSingleStep(1)
+
+        self.sf_color_alpha_spinner = FCSpinner()
+        self.sf_color_alpha_spinner.setMinimumWidth(70)
+        self.sf_color_alpha_spinner.setMinimum(0)
+        self.sf_color_alpha_spinner.setMaximum(255)
+
+        self.form_box_child_5 = QtWidgets.QHBoxLayout()
+        self.form_box_child_5.addWidget(self.sf_color_alpha_slider)
+        self.form_box_child_5.addWidget(self.sf_color_alpha_spinner)
+
+        # Plot Selection (left - right) Line Color
+        self.sl_color_label = QtWidgets.QLabel('%s:' % _('Sel. Line'))
+        self.sl_color_label.setToolTip(
+            _("Set the line color for the 'left to right' selection box.")
+        )
+        self.sl_color_entry = FCEntry()
+        self.sl_color_button = QtWidgets.QPushButton()
+        self.sl_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_6 = QtWidgets.QHBoxLayout()
+        self.form_box_child_6.addWidget(self.sl_color_entry)
+        self.form_box_child_6.addWidget(self.sl_color_button)
+        self.form_box_child_6.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Plot Selection (right - left) Fill Color
+        self.alt_sf_color_label = QtWidgets.QLabel('%s:' % _('Sel2. Fill'))
+        self.alt_sf_color_label.setToolTip(
+            _("Set the fill color for the selection box\n"
+              "in case that the selection is done from right to left.\n"
+              "First 6 digits are the color and the last 2\n"
+              "digits are for alpha (transparency) level.")
+        )
+        self.alt_sf_color_entry = FCEntry()
+        self.alt_sf_color_button = QtWidgets.QPushButton()
+        self.alt_sf_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_7 = QtWidgets.QHBoxLayout()
+        self.form_box_child_7.addWidget(self.alt_sf_color_entry)
+        self.form_box_child_7.addWidget(self.alt_sf_color_button)
+        self.form_box_child_7.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Plot Selection (right - left) Fill Transparency Level
+        self.alt_sf_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha Level'))
+        self.alt_sf_alpha_label.setToolTip(
+            _("Set the fill transparency for selection 'right to left' box.")
+        )
+        self.alt_sf_color_alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
+        self.alt_sf_color_alpha_slider.setMinimum(0)
+        self.alt_sf_color_alpha_slider.setMaximum(255)
+        self.alt_sf_color_alpha_slider.setSingleStep(1)
+
+        self.alt_sf_color_alpha_spinner = FCSpinner()
+        self.alt_sf_color_alpha_spinner.setMinimumWidth(70)
+        self.alt_sf_color_alpha_spinner.setMinimum(0)
+        self.alt_sf_color_alpha_spinner.setMaximum(255)
+
+        self.form_box_child_8 = QtWidgets.QHBoxLayout()
+        self.form_box_child_8.addWidget(self.alt_sf_color_alpha_slider)
+        self.form_box_child_8.addWidget(self.alt_sf_color_alpha_spinner)
+
+        # Plot Selection (right - left) Line Color
+        self.alt_sl_color_label = QtWidgets.QLabel('%s:' % _('Sel2. Line'))
+        self.alt_sl_color_label.setToolTip(
+            _("Set the line color for the 'right to left' selection box.")
+        )
+        self.alt_sl_color_entry = FCEntry()
+        self.alt_sl_color_button = QtWidgets.QPushButton()
+        self.alt_sl_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_9 = QtWidgets.QHBoxLayout()
+        self.form_box_child_9.addWidget(self.alt_sl_color_entry)
+        self.form_box_child_9.addWidget(self.alt_sl_color_button)
+        self.form_box_child_9.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Editor Draw Color
+        self.draw_color_label = QtWidgets.QLabel('%s:' % _('Editor Draw'))
+        self.alt_sf_color_label.setToolTip(
+            _("Set the color for the shape.")
+        )
+        self.draw_color_entry = FCEntry()
+        self.draw_color_button = QtWidgets.QPushButton()
+        self.draw_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_10 = QtWidgets.QHBoxLayout()
+        self.form_box_child_10.addWidget(self.draw_color_entry)
+        self.form_box_child_10.addWidget(self.draw_color_button)
+        self.form_box_child_10.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Editor Draw Selection Color
+        self.sel_draw_color_label = QtWidgets.QLabel('%s:' % _('Editor Draw Sel.'))
+        self.sel_draw_color_label.setToolTip(
+            _("Set the color of the shape when selected.")
+        )
+        self.sel_draw_color_entry = FCEntry()
+        self.sel_draw_color_button = QtWidgets.QPushButton()
+        self.sel_draw_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_11 = QtWidgets.QHBoxLayout()
+        self.form_box_child_11.addWidget(self.sel_draw_color_entry)
+        self.form_box_child_11.addWidget(self.sel_draw_color_button)
+        self.form_box_child_11.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Project Tab items color
+        self.proj_color_label = QtWidgets.QLabel('%s:' % _('Project Items'))
+        self.proj_color_label.setToolTip(
+            _("Set the color of the items in Project Tab Tree.")
+        )
+        self.proj_color_entry = FCEntry()
+        self.proj_color_button = QtWidgets.QPushButton()
+        self.proj_color_button.setFixedSize(15, 15)
+
+        self.form_box_child_12 = QtWidgets.QHBoxLayout()
+        self.form_box_child_12.addWidget(self.proj_color_entry)
+        self.form_box_child_12.addWidget(self.proj_color_button)
+        self.form_box_child_12.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        self.proj_color_dis_label = QtWidgets.QLabel('%s:' % _('Proj. Dis. Items'))
+        self.proj_color_dis_label.setToolTip(
+            _("Set the color of the items in Project Tab Tree,\n"
+              "for the case when the items are disabled.")
+        )
+        self.proj_color_dis_entry = FCEntry()
+        self.proj_color_dis_button = QtWidgets.QPushButton()
+        self.proj_color_dis_button.setFixedSize(15, 15)
+
+        self.form_box_child_13 = QtWidgets.QHBoxLayout()
+        self.form_box_child_13.addWidget(self.proj_color_dis_entry)
+        self.form_box_child_13.addWidget(self.proj_color_dis_button)
+        self.form_box_child_13.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        # Activity monitor icon
+        self.activity_label = QtWidgets.QLabel('%s:' % _("Activity Icon"))
+        self.activity_label.setToolTip(
+            _("Select the GIF that show activity when FlatCAM is active.")
+        )
+        self.activity_combo = FCComboBox()
+        self.activity_combo.addItems(['Ball black', 'Ball green', 'Arrow green', 'Eclipse green'])
+
+        # Just to add empty rows
+        self.spacelabel = QtWidgets.QLabel('')
+
+        # Add (label - input field) pair to the QFormLayout
+        self.form_box.addRow(self.spacelabel, self.spacelabel)
+
+        self.form_box.addRow(self.gridx_label, self.gridx_entry)
+        self.form_box.addRow(self.gridy_label, self.gridy_entry)
+        self.form_box.addRow(self.snap_max_label, self.snap_max_dist_entry)
+
+        self.form_box.addRow(self.workspace_lbl, self.workspace_cb)
+        self.form_box.addRow(self.workspace_type_lbl, self.wk_cb)
+        self.form_box.addRow(self.spacelabel, self.spacelabel)
+        self.form_box.addRow(self.pf_color_label, self.form_box_child_1)
+        self.form_box.addRow(self.pf_alpha_label, self.form_box_child_2)
+        self.form_box.addRow(self.pl_color_label, self.form_box_child_3)
+        self.form_box.addRow(self.sf_color_label, self.form_box_child_4)
+        self.form_box.addRow(self.sf_alpha_label, self.form_box_child_5)
+        self.form_box.addRow(self.sl_color_label, self.form_box_child_6)
+        self.form_box.addRow(self.alt_sf_color_label, self.form_box_child_7)
+        self.form_box.addRow(self.alt_sf_alpha_label, self.form_box_child_8)
+        self.form_box.addRow(self.alt_sl_color_label, self.form_box_child_9)
+        self.form_box.addRow(self.draw_color_label, self.form_box_child_10)
+        self.form_box.addRow(self.sel_draw_color_label, self.form_box_child_11)
+        self.form_box.addRow(QtWidgets.QLabel(""))
+        self.form_box.addRow(self.proj_color_label, self.form_box_child_12)
+        self.form_box.addRow(self.proj_color_dis_label, self.form_box_child_13)
+
+        self.form_box.addRow(self.activity_label, self.activity_combo)
+
+        self.form_box.addRow(self.spacelabel, self.spacelabel)
+
+        # Add the QFormLayout that holds the Application general defaults
+        # to the main layout of this TAB
+        self.layout.addLayout(self.form_box)
+
+
+class GeneralGUISetGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        super(GeneralGUISetGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("GUI Settings")))
+
+        # Create a form layout for the Application general settings
+        self.form_box = QtWidgets.QFormLayout()
+
+        # Layout selection
+        self.layout_label = QtWidgets.QLabel('%s:' % _('Layout'))
+        self.layout_label.setToolTip(
+            _("Select an layout for FlatCAM.\n"
+              "It is applied immediately.")
+        )
+        self.layout_combo = FCComboBox()
+        # don't translate the QCombo items as they are used in QSettings and identified by name
+        self.layout_combo.addItem("standard")
+        self.layout_combo.addItem("compact")
+
+        # Set the current index for layout_combo
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("layout"):
+            layout = settings.value('layout', type=str)
+            idx = self.layout_combo.findText(layout.capitalize())
+            self.layout_combo.setCurrentIndex(idx)
+
+        # Style selection
+        self.style_label = QtWidgets.QLabel('%s:' % _('Style'))
+        self.style_label.setToolTip(
+            _("Select an style for FlatCAM.\n"
+              "It will be applied at the next app start.")
+        )
+        self.style_combo = FCComboBox()
+        self.style_combo.addItems(QtWidgets.QStyleFactory.keys())
+        # find current style
+        index = self.style_combo.findText(QtWidgets.qApp.style().objectName(), QtCore.Qt.MatchFixedString)
+        self.style_combo.setCurrentIndex(index)
+        self.style_combo.activated[str].connect(self.handle_style)
+
+        # Enable High DPI Support
+        self.hdpi_label = QtWidgets.QLabel('%s:' % _('HDPI Support'))
+        self.hdpi_label.setToolTip(
+            _("Enable High DPI support for FlatCAM.\n"
+              "It will be applied at the next app start.")
+        )
+        self.hdpi_cb = FCCheckBox()
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("hdpi"):
+            self.hdpi_cb.set_value(settings.value('hdpi', type=int))
+        else:
+            self.hdpi_cb.set_value(False)
+        self.hdpi_cb.stateChanged.connect(self.handle_hdpi)
+
+        # Clear Settings
+        self.clear_label = QtWidgets.QLabel('%s:' % _('Clear GUI Settings'))
+        self.clear_label.setToolTip(
+            _("Clear the GUI settings for FlatCAM,\n"
+              "such as: layout, gui state, style, hdpi support etc.")
+        )
+        self.clear_btn = FCButton(_("Clear"))
+        self.clear_btn.clicked.connect(self.handle_clear)
+
+        # Enable Hover box
+        self.hover_label = QtWidgets.QLabel('%s:' % _('Hover Shape'))
+        self.hover_label.setToolTip(
+            _("Enable display of a hover shape for FlatCAM objects.\n"
+              "It is displayed whenever the mouse cursor is hovering\n"
+              "over any kind of not-selected object.")
+        )
+        self.hover_cb = FCCheckBox()
+
+        # Enable Selection box
+        self.selection_label = QtWidgets.QLabel('%s:' % _('Sel. Shape'))
+        self.selection_label.setToolTip(
+            _("Enable the display of a selection shape for FlatCAM objects.\n"
+              "It is displayed whenever the mouse selects an object\n"
+              "either by clicking or dragging mouse from left to right or\n"
+              "right to left.")
+        )
+        self.selection_cb = FCCheckBox()
+
+        # Notebook Font Size
+        self.notebook_font_size_label = QtWidgets.QLabel('%s:' % _('NB Font Size'))
+        self.notebook_font_size_label.setToolTip(
+            _("This sets the font size for the elements found in the Notebook.\n"
+              "The notebook is the collapsible area in the left side of the GUI,\n"
+              "and include the Project, Selected and Tool tabs.")
+        )
+
+        self.notebook_font_size_spinner = FCSpinner()
+        self.notebook_font_size_spinner.setRange(8, 40)
+        self.notebook_font_size_spinner.setWrapping(True)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("notebook_font_size"):
+            self.notebook_font_size_spinner.set_value(settings.value('notebook_font_size', type=int))
+        else:
+            self.notebook_font_size_spinner.set_value(12)
+
+        # Axis Font Size
+        self.axis_font_size_label = QtWidgets.QLabel('%s:' % _('Axis Font Size'))
+        self.axis_font_size_label.setToolTip(
+            _("This sets the font size for canvas axis.")
+        )
+
+        self.axis_font_size_spinner = FCSpinner()
+        self.axis_font_size_spinner.setRange(8, 40)
+        self.axis_font_size_spinner.setWrapping(True)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("axis_font_size"):
+            self.axis_font_size_spinner.set_value(settings.value('axis_font_size', type=int))
+        else:
+            self.axis_font_size_spinner.set_value(8)
+
+        # TextBox Font Size
+        self.textbox_font_size_label = QtWidgets.QLabel('%s:' % _('Textbox Font Size'))
+        self.textbox_font_size_label.setToolTip(
+            _("This sets the font size for the Textbox GUI\n"
+              "elements that are used in FlatCAM.")
+        )
+
+        self.textbox_font_size_spinner = FCSpinner()
+        self.textbox_font_size_spinner.setRange(8, 40)
+        self.textbox_font_size_spinner.setWrapping(True)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            self.textbox_font_size_spinner.set_value(settings.value('textbox_font_size', type=int))
+        else:
+            self.textbox_font_size_spinner.set_value(10)
+
+        # Just to add empty rows
+        self.spacelabel = QtWidgets.QLabel('')
+
+        # Splash Screen
+        self.splash_label = QtWidgets.QLabel('%s:' % _('Splash Screen'))
+        self.splash_label.setToolTip(
+            _("Enable display of the splash screen at application startup.")
+        )
+        self.splash_cb = FCCheckBox()
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.value("splash_screen"):
+            self.splash_cb.set_value(True)
+        else:
+            self.splash_cb.set_value(False)
+
+        # Sys Tray Icon
+        self.systray_label = QtWidgets.QLabel('%s:' % _('Sys Tray Icon'))
+        self.systray_label.setToolTip(
+            _("Enable display of FlatCAM icon in Sys Tray.")
+        )
+        self.systray_cb = FCCheckBox()
+
+        # Shell StartUp CB
+        self.shell_startup_label = QtWidgets.QLabel('%s:' % _('Shell at StartUp'))
+        self.shell_startup_label.setToolTip(
+            _("Check this box if you want the shell to\n"
+              "start automatically at startup.")
+        )
+        self.shell_startup_cb = FCCheckBox(label='')
+        self.shell_startup_cb.setToolTip(
+            _("Check this box if you want the shell to\n"
+              "start automatically at startup.")
+        )
+
+        # Project at StartUp CB
+        self.project_startup_label = QtWidgets.QLabel('%s:' % _('Project at StartUp'))
+        self.project_startup_label.setToolTip(
+            _("Check this box if you want the project/selected/tool tab area to\n"
+              "to be shown automatically at startup.")
+        )
+        self.project_startup_cb = FCCheckBox(label='')
+        self.project_startup_cb.setToolTip(
+            _("Check this box if you want the project/selected/tool tab area to\n"
+              "to be shown automatically at startup.")
+        )
+
+        # Project autohide CB
+        self.project_autohide_label = QtWidgets.QLabel('%s:' % _('Project AutoHide'))
+        self.project_autohide_label.setToolTip(
+            _("Check this box if you want the project/selected/tool tab area to\n"
+              "hide automatically when there are no objects loaded and\n"
+              "to show whenever a new object is created.")
+        )
+        self.project_autohide_cb = FCCheckBox(label='')
+        self.project_autohide_cb.setToolTip(
+            _("Check this box if you want the project/selected/tool tab area to\n"
+              "hide automatically when there are no objects loaded and\n"
+              "to show whenever a new object is created.")
+        )
+
+        # Enable/Disable ToolTips globally
+        self.toggle_tooltips_label = QtWidgets.QLabel('<b>%s:</b>' % _('Enable ToolTips'))
+        self.toggle_tooltips_label.setToolTip(
+            _("Check this box if you want to have toolTips displayed\n"
+              "when hovering with mouse over items throughout the App.")
+        )
+        self.toggle_tooltips_cb = FCCheckBox(label='')
+        self.toggle_tooltips_cb.setToolTip(
+            _("Check this box if you want to have toolTips displayed\n"
+              "when hovering with mouse over items throughout the App.")
+        )
+
+        # Add (label - input field) pair to the QFormLayout
+        self.form_box.addRow(self.spacelabel, self.spacelabel)
+
+        self.form_box.addRow(self.layout_label, self.layout_combo)
+        self.form_box.addRow(self.style_label, self.style_combo)
+        self.form_box.addRow(self.hdpi_label, self.hdpi_cb)
+        self.form_box.addRow(self.clear_label, self.clear_btn)
+        self.form_box.addRow(self.hover_label, self.hover_cb)
+        self.form_box.addRow(self.selection_label, self.selection_cb)
+        self.form_box.addRow(QtWidgets.QLabel(''))
+        self.form_box.addRow(self.notebook_font_size_label, self.notebook_font_size_spinner)
+        self.form_box.addRow(self.axis_font_size_label, self.axis_font_size_spinner)
+        self.form_box.addRow(self.textbox_font_size_label, self.textbox_font_size_spinner)
+        self.form_box.addRow(QtWidgets.QLabel(''))
+        self.form_box.addRow(self.splash_label, self.splash_cb)
+        self.form_box.addRow(self.systray_label, self.systray_cb)
+        self.form_box.addRow(self.shell_startup_label, self.shell_startup_cb)
+        self.form_box.addRow(self.project_startup_label, self.project_startup_cb)
+        self.form_box.addRow(self.project_autohide_label, self.project_autohide_cb)
+        self.form_box.addRow(QtWidgets.QLabel(''))
+        self.form_box.addRow(self.toggle_tooltips_label, self.toggle_tooltips_cb)
+
+        # Add the QFormLayout that holds the Application general defaults
+        # to the main layout of this TAB
+        self.layout.addLayout(self.form_box)
+
+        # Delete confirmation
+        self.delete_conf_cb = FCCheckBox(_('Delete object confirmation'))
+        self.delete_conf_cb.setToolTip(
+            _("When checked the application will ask for user confirmation\n"
+              "whenever the Delete object(s) event is triggered, either by\n"
+              "menu shortcut or key shortcut.")
+        )
+        self.layout.addWidget(self.delete_conf_cb)
+
+        self.layout.addStretch()
+
+    def handle_style(self, style):
+        # set current style
+        settings = QSettings("Open Source", "FlatCAM")
+        settings.setValue('style', style)
+
+        # This will write the setting to the platform specific storage.
+        del settings
+
+    def handle_hdpi(self, state):
+        # set current HDPI
+        settings = QSettings("Open Source", "FlatCAM")
+        settings.setValue('hdpi', state)
+
+        # This will write the setting to the platform specific storage.
+        del settings
+
+    def handle_clear(self):
+        msgbox = QtWidgets.QMessageBox()
+        msgbox.setText(_("Are you sure you want to delete the GUI Settings? "
+                         "\n")
+                       )
+        msgbox.setWindowTitle(_("Clear GUI Settings"))
+        msgbox.setWindowIcon(QtGui.QIcon('share/trash32.png'))
+        bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole)
+        bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole)
+
+        msgbox.setDefaultButton(bt_no)
+        msgbox.exec_()
+        response = msgbox.clickedButton()
+
+        if response == bt_yes:
+            settings = QSettings("Open Source", "FlatCAM")
+            for key in settings.allKeys():
+                settings.remove(key)
+            # This will write the setting to the platform specific storage.
+            del settings
+
+
+class GeneralAppPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        super(GeneralAppPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("App Preferences")))
+
+        # Create a form layout for the Application general settings
+        self.form_box = QtWidgets.QFormLayout()
+
+        # Units for FlatCAM
+        self.unitslabel = QtWidgets.QLabel('<span style="color:red;"><b>%s:</b></span>' % _('Units'))
+        self.unitslabel.setToolTip(_("The default value for FlatCAM units.\n"
+                                     "Whatever is selected here is set every time\n"
+                                     "FLatCAM is started."))
+        self.units_radio = RadioSet([{'label': _('IN'), 'value': 'IN'},
+                                     {'label': _('MM'), 'value': 'MM'}])
+
+        # Graphic Engine for FlatCAM
+        self.ge_label = QtWidgets.QLabel('<b>%s:</b>' % _('Graphic Engine'))
+        self.ge_label.setToolTip(_("Choose what graphic engine to use in FlatCAM.\n"
+                                   "Legacy(2D) -> reduced functionality, slow performance but enhanced compatibility.\n"
+                                   "OpenGL(3D) -> full functionality, high performance\n"
+                                   "Some graphic cards are too old and do not work in OpenGL(3D) mode, like:\n"
+                                   "Intel HD3000 or older. In this case the plot area will be black therefore\n"
+                                   "use the Legacy(2D) mode."))
+        self.ge_radio = RadioSet([{'label': _('Legacy(2D)'), 'value': '2D'},
+                                  {'label': _('OpenGL(3D)'), 'value': '3D'}])
+
+        # Application Level for FlatCAM
+        self.app_level_label = QtWidgets.QLabel('<b>%s:</b>' % _('APP. LEVEL'))
+        self.app_level_label.setToolTip(_("Choose the default level of usage for FlatCAM.\n"
+                                          "BASIC level -> reduced functionality, best for beginner's.\n"
+                                          "ADVANCED level -> full functionality.\n\n"
+                                          "The choice here will influence the parameters in\n"
+                                          "the Selected Tab for all kinds of FlatCAM objects."))
+        self.app_level_radio = RadioSet([{'label': _('Basic'), 'value': 'b'},
+                                         {'label': _('Advanced'), 'value': 'a'}])
+
+        # Application Level for FlatCAM
+        self.portability_label = QtWidgets.QLabel('%s:' % _('Portable app'))
+        self.portability_label.setToolTip(_("Choose if the application should run as portable.\n\n"
+                                            "If Checked the application will run portable,\n"
+                                            "which means that the preferences files will be saved\n"
+                                            "in the application folder, in the lib\\config subfolder."))
+        self.portability_cb = FCCheckBox()
+
+        # Languages for FlatCAM
+        self.languagelabel = QtWidgets.QLabel('<b>%s:</b>' % _('Languages'))
+        self.languagelabel.setToolTip(_("Set the language used throughout FlatCAM."))
+        self.language_cb = FCComboBox()
+        self.languagespace = QtWidgets.QLabel('')
+        self.language_apply_btn = FCButton(_("Apply Language"))
+        self.language_apply_btn.setToolTip(_("Set the language used throughout FlatCAM.\n"
+                                             "The app will restart after click."
+                                             "Windows: When FlatCAM is installed in Program Files\n"
+                                             "directory, it is possible that the app will not\n"
+                                             "restart after the button is clicked due of Windows\n"
+                                             "security features. In this case the language will be\n"
+                                             "applied at the next app start."))
+
+        # Version Check CB
+        self.version_check_label = QtWidgets.QLabel('%s:' % _('Version Check'))
+        self.version_check_label.setToolTip(
+            _("Check this box if you want to check\n"
+              "for a new version automatically at startup.")
+        )
+        self.version_check_cb = FCCheckBox(label='')
+        self.version_check_cb.setToolTip(
+            _("Check this box if you want to check\n"
+              "for a new version automatically at startup.")
+        )
+
+        # Send Stats CB
+        self.send_stats_label = QtWidgets.QLabel('%s:' % _('Send Stats'))
+        self.send_stats_label.setToolTip(
+            _("Check this box if you agree to send anonymous\n"
+              "stats automatically at startup, to help improve FlatCAM.")
+        )
+        self.send_stats_cb = FCCheckBox(label='')
+        self.send_stats_cb.setToolTip(
+            _("Check this box if you agree to send anonymous\n"
+              "stats automatically at startup, to help improve FlatCAM.")
+        )
+
+        self.ois_version_check = OptionalInputSection(self.version_check_cb, [self.send_stats_cb])
+
+        # Select mouse pan button
+        self.panbuttonlabel = QtWidgets.QLabel('<b>%s:</b>' % _('Pan Button'))
+        self.panbuttonlabel.setToolTip(_("Select the mouse button to use for panning:\n"
+                                         "- MMB --> Middle Mouse Button\n"
+                                         "- RMB --> Right Mouse Button"))
+        self.pan_button_radio = RadioSet([{'label': _('MMB'), 'value': '3'},
+                                          {'label': _('RMB'), 'value': '2'}])
+
+        # Multiple Selection Modifier Key
+        self.mselectlabel = QtWidgets.QLabel('<b>%s:</b>' % _('Multiple Sel'))
+        self.mselectlabel.setToolTip(_("Select the key used for multiple selection."))
+        self.mselect_radio = RadioSet([{'label': _('CTRL'), 'value': 'Control'},
+                                       {'label': _('SHIFT'), 'value': 'Shift'}])
+
+        # Worker Numbers
+        self.worker_number_label = QtWidgets.QLabel('%s:' % _('Workers number'))
+        self.worker_number_label.setToolTip(
+            _("The number of Qthreads made available to the App.\n"
+              "A bigger number may finish the jobs more quickly but\n"
+              "depending on your computer speed, may make the App\n"
+              "unresponsive. Can have a value between 2 and 16.\n"
+              "Default value is 2.\n"
+              "After change, it will be applied at next App start.")
+        )
+        self.worker_number_sb = FCSpinner()
+        self.worker_number_sb.setToolTip(
+            _("The number of Qthreads made available to the App.\n"
+              "A bigger number may finish the jobs more quickly but\n"
+              "depending on your computer speed, may make the App\n"
+              "unresponsive. Can have a value between 2 and 16.\n"
+              "Default value is 2.\n"
+              "After change, it will be applied at next App start.")
+        )
+        self.worker_number_sb.set_range(2, 16)
+
+        # Geometric tolerance
+        tol_label = QtWidgets.QLabel('%s:' % _("Geo Tolerance"))
+        tol_label.setToolTip(_(
+            "This value can counter the effect of the Circle Steps\n"
+            "parameter. Default value is 0.01.\n"
+            "A lower value will increase the detail both in image\n"
+            "and in Gcode for the circles, with a higher cost in\n"
+            "performance. Higher value will provide more\n"
+            "performance at the expense of level of detail."
+        ))
+        self.tol_entry = FCEntry()
+        self.tol_entry.setToolTip(_(
+            "This value can counter the effect of the Circle Steps\n"
+            "parameter. Default value is 0.01.\n"
+            "A lower value will increase the detail both in image\n"
+            "and in Gcode for the circles, with a higher cost in\n"
+            "performance. Higher value will provide more\n"
+            "performance at the expense of level of detail."
+        ))
+        # Just to add empty rows
+        self.spacelabel = QtWidgets.QLabel('')
+
+        # Add (label - input field) pair to the QFormLayout
+        self.form_box.addRow(self.unitslabel, self.units_radio)
+        self.form_box.addRow(self.ge_label, self.ge_radio)
+        self.form_box.addRow(QtWidgets.QLabel(''))
+        self.form_box.addRow(self.app_level_label, self.app_level_radio)
+        self.form_box.addRow(self.portability_label, self.portability_cb)
+        self.form_box.addRow(QtWidgets.QLabel(''))
+
+        self.form_box.addRow(self.languagelabel, self.language_cb)
+        self.form_box.addRow(self.languagespace, self.language_apply_btn)
+
+        self.form_box.addRow(self.spacelabel, self.spacelabel)
+        self.form_box.addRow(self.version_check_label, self.version_check_cb)
+        self.form_box.addRow(self.send_stats_label, self.send_stats_cb)
+
+        self.form_box.addRow(self.panbuttonlabel, self.pan_button_radio)
+        self.form_box.addRow(self.mselectlabel, self.mselect_radio)
+        self.form_box.addRow(self.worker_number_label, self.worker_number_sb)
+        self.form_box.addRow(tol_label, self.tol_entry)
+
+        self.form_box.addRow(self.spacelabel, self.spacelabel)
+
+        # Add the QFormLayout that holds the Application general defaults
+        # to the main layout of this TAB
+        self.layout.addLayout(self.form_box)
+
+        # Open behavior
+        self.open_style_cb = FCCheckBox('%s' % _('"Open" behavior'))
+        self.open_style_cb.setToolTip(
+            _("When checked the path for the last saved file is used when saving files,\n"
+              "and the path for the last opened file is used when opening files.\n\n"
+              "When unchecked the path for opening files is the one used last: either the\n"
+              "path for saving files or the path for opening files.")
+        )
+        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        self.layout.addWidget(self.open_style_cb)
+
+        # Save compressed project CB
+        self.save_type_cb = FCCheckBox(_('Save Compressed Project'))
+        self.save_type_cb.setToolTip(
+            _("Whether to save a compressed or uncompressed project.\n"
+              "When checked it will save a compressed FlatCAM project.")
+        )
+        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        self.layout.addWidget(self.save_type_cb)
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+
+        # Project LZMA Comppression Level
+        self.compress_combo = FCComboBox()
+        self.compress_label = QtWidgets.QLabel('%s:' % _('Compression Level'))
+        self.compress_label.setToolTip(
+            _("The level of compression used when saving\n"
+              "a FlatCAM project. Higher value means better compression\n"
+              "but require more RAM usage and more processing time.")
+        )
+        # self.advanced_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
+        self.compress_combo.addItems([str(i) for i in range(10)])
+
+        hlay1.addWidget(self.compress_label)
+        hlay1.addWidget(self.compress_combo)
+
+        self.proj_ois = OptionalInputSection(self.save_type_cb, [self.compress_label, self.compress_combo], True)
+
+        self.form_box_2 = QtWidgets.QFormLayout()
+        self.layout.addLayout(self.form_box_2)
+
+        self.layout.addStretch()
+
+        if sys.platform != 'win32':
+            self.portability_label.hide()
+            self.portability_cb.hide()
+
+
+class GerberGenPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber General Preferences", parent=parent)
+        super(GerberGenPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber General")))
+
+        # ## Plot options
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
+        self.layout.addWidget(self.plot_options_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Solid CB
+        self.solid_cb = FCCheckBox(label='%s' % _('Solid'))
+        self.solid_cb.setToolTip(
+            _("Solid color polygons.")
+        )
+        grid0.addWidget(self.solid_cb, 0, 0)
+
+        # Multicolored CB
+        self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
+        self.multicolored_cb.setToolTip(
+            _("Draw polygons in different colors.")
+        )
+        grid0.addWidget(self.multicolored_cb, 0, 1)
+
+        # Plot CB
+        self.plot_cb = FCCheckBox(label='%s' % _('Plot'))
+        self.plot_options_label.setToolTip(
+            _("Plot (show) this object.")
+        )
+        grid0.addWidget(self.plot_cb, 0, 2)
+
+        # Number of circle steps for circular aperture linear approximation
+        self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
+        self.circle_steps_label.setToolTip(
+            _("The number of circle steps for Gerber \n"
+              "circular aperture linear approximation.")
+        )
+        self.circle_steps_entry = IntEntry()
+        grid0.addWidget(self.circle_steps_label, 1, 0)
+        grid0.addWidget(self.circle_steps_entry, 1, 1)
+
+        self.layout.addStretch()
+
+
+class GerberOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Options Preferences", parent=parent)
+        super(GerberOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber Options")))
+
+        # ## Isolation Routing
+        self.isolation_routing_label = QtWidgets.QLabel("<b>%s:</b>" % _("Isolation Routing"))
+        self.isolation_routing_label.setToolTip(
+            _("Create a Geometry object with\n"
+              "toolpaths to cut outside polygons.")
+        )
+        self.layout.addWidget(self.isolation_routing_label)
+
+        # Cutting Tool Diameter
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
+        tdlabel.setToolTip(
+            _("Diameter of the cutting tool.")
+        )
+        grid0.addWidget(tdlabel, 0, 0)
+        self.iso_tool_dia_entry = LengthEntry()
+        grid0.addWidget(self.iso_tool_dia_entry, 0, 1)
+
+        # Nr of passes
+        passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
+        passlabel.setToolTip(
+            _("Width of the isolation gap in\n"
+              "number (integer) of tool widths.")
+        )
+        grid0.addWidget(passlabel, 1, 0)
+        self.iso_width_entry = FCSpinner()
+        self.iso_width_entry.setRange(1, 999)
+        grid0.addWidget(self.iso_width_entry, 1, 1)
+
+        # Pass overlap
+        overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
+        overlabel.setToolTip(
+            _("How much (fraction) of the tool width to overlap each tool pass.\n"
+              "Example:\n"
+              "A value here of 0.25 means an overlap of 25%% from the tool diameter found above.")
+        )
+        grid0.addWidget(overlabel, 2, 0)
+        self.iso_overlap_entry = FCDoubleSpinner()
+        self.iso_overlap_entry.set_precision(3)
+        self.iso_overlap_entry.setWrapping(True)
+        self.iso_overlap_entry.setRange(0.000, 0.999)
+        self.iso_overlap_entry.setSingleStep(0.1)
+        grid0.addWidget(self.iso_overlap_entry, 2, 1)
+
+        # Milling Type
+        milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
+        milling_type_label.setToolTip(
+            _("Milling type:\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+        grid0.addWidget(milling_type_label, 3, 0)
+        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
+                                            {'label': _('Conv.'), 'value': 'cv'}])
+        grid0.addWidget(self.milling_type_radio, 3, 1)
+
+        # Combine passes
+        self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
+        self.combine_passes_cb.setToolTip(
+            _("Combine all passes into one object")
+        )
+        grid0.addWidget(self.combine_passes_cb, 4, 0, 1, 2)
+
+        # ## Clear non-copper regions
+        self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
+        self.clearcopper_label.setToolTip(
+            _("Create polygons covering the\n"
+              "areas without copper on the PCB.\n"
+              "Equivalent to the inverse of this\n"
+              "object. Can be used to remove all\n"
+              "copper from a specified region.")
+        )
+        self.layout.addWidget(self.clearcopper_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        # Margin
+        bmlabel = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
+        bmlabel.setToolTip(
+            _("Specify the edge of the PCB\n"
+              "by drawing a box around all\n"
+              "objects with this minimum\n"
+              "distance.")
+        )
+        grid1.addWidget(bmlabel, 0, 0)
+        self.noncopper_margin_entry = LengthEntry()
+        grid1.addWidget(self.noncopper_margin_entry, 0, 1)
+
+        # Rounded corners
+        self.noncopper_rounded_cb = FCCheckBox(label=_("Rounded Geo"))
+        self.noncopper_rounded_cb.setToolTip(
+            _("Resulting geometry will have rounded corners.")
+        )
+        grid1.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
+
+        # ## Bounding box
+        self.boundingbox_label = QtWidgets.QLabel('<b>%s:</b>' % _('Bounding Box'))
+        self.layout.addWidget(self.boundingbox_label)
+
+        grid2 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid2)
+
+        bbmargin = QtWidgets.QLabel('%s:' % _('Boundary Margin'))
+        bbmargin.setToolTip(
+            _("Distance of the edges of the box\n"
+              "to the nearest polygon.")
+        )
+        grid2.addWidget(bbmargin, 0, 0)
+        self.bbmargin_entry = LengthEntry()
+        grid2.addWidget(self.bbmargin_entry, 0, 1)
+
+        self.bbrounded_cb = FCCheckBox(label='%s' % _("Rounded Geo"))
+        self.bbrounded_cb.setToolTip(
+            _("If the bounding box is \n"
+              "to have rounded corners\n"
+              "their radius is equal to\n"
+              "the margin.")
+        )
+        grid2.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
+        self.layout.addStretch()
+
+
+class GerberAdvOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
+        super(GerberAdvOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber Adv. Options")))
+
+        # ## Advanced Gerber Parameters
+        self.adv_param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
+        self.adv_param_label.setToolTip(
+            _("A list of Gerber advanced parameters.\n"
+              "Those parameters are available only for\n"
+              "Advanced App. Level.")
+        )
+        self.layout.addWidget(self.adv_param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Follow Attribute
+        self.follow_cb = FCCheckBox(label=_('"Follow"'))
+        self.follow_cb.setToolTip(
+            _("Generate a 'Follow' geometry.\n"
+              "This means that it will cut through\n"
+              "the middle of the trace.")
+        )
+        grid0.addWidget(self.follow_cb, 0, 0, 1, 2)
+
+        # Aperture Table Visibility CB
+        self.aperture_table_visibility_cb = FCCheckBox(label=_('Table Show/Hide'))
+        self.aperture_table_visibility_cb.setToolTip(
+            _("Toggle the display of the Gerber Apertures Table.\n"
+              "Also, on hide, it will delete all mark shapes\n"
+              "that are drawn on canvas.")
+
+        )
+        grid0.addWidget(self.aperture_table_visibility_cb, 1, 0, 1, 2)
+
+        # Buffering Type
+        buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
+        buffering_label.setToolTip(
+            _("Buffering type:\n"
+              "- None --> best performance, fast file loading but no so good display\n"
+              "- Full --> slow file loading but good visuals. This is the default.\n"
+              "<<WARNING>>: Don't change this unless you know what you are doing !!!")
+        )
+        self.buffering_radio = RadioSet([{'label': _('None'), 'value': 'no'},
+                                         {'label': _('Full'), 'value': 'full'}])
+        grid0.addWidget(buffering_label, 2, 0)
+        grid0.addWidget(self.buffering_radio, 2, 1)
+
+        # Simplification
+        self.simplify_cb = FCCheckBox(label=_('Simplify'))
+        self.simplify_cb.setToolTip(_("When checked all the Gerber polygons will be\n"
+                                      "loaded with simplification having a set tolerance."))
+        grid0.addWidget(self.simplify_cb, 3, 0, 1, 2)
+
+        # Simplification tolerance
+        self.simplification_tol_label = QtWidgets.QLabel(_('Tolerance'))
+        self.simplification_tol_label.setToolTip(_("Tolerance for poligon simplification."))
+
+        self.simplification_tol_spinner = FCDoubleSpinner()
+        self.simplification_tol_spinner.set_precision(5)
+        self.simplification_tol_spinner.setWrapping(True)
+        self.simplification_tol_spinner.setRange(0.00000, 0.01000)
+        self.simplification_tol_spinner.setSingleStep(0.0001)
+
+        grid0.addWidget(self.simplification_tol_label, 4, 0)
+        grid0.addWidget(self.simplification_tol_spinner, 4, 1)
+        self.ois_simplif = OptionalInputSection(self.simplify_cb,
+                                                [self.simplification_tol_label, self.simplification_tol_spinner],
+                                                logic=True)
+
+        # Scale Aperture Factor
+        # self.scale_aperture_label = QtWidgets.QLabel(_('Ap. Scale Factor:'))
+        # self.scale_aperture_label.setToolTip(
+        #     _("Change the size of the selected apertures.\n"
+        #     "Factor by which to multiply\n"
+        #     "geometric features of this object.")
+        # )
+        # grid0.addWidget(self.scale_aperture_label, 2, 0)
+        #
+        # self.scale_aperture_entry = FloatEntry2()
+        # grid0.addWidget(self.scale_aperture_entry, 2, 1)
+
+        # Buffer Aperture Factor
+        # self.buffer_aperture_label = QtWidgets.QLabel(_('Ap. Buffer Factor:'))
+        # self.buffer_aperture_label.setToolTip(
+        #     _("Change the size of the selected apertures.\n"
+        #     "Factor by which to expand/shrink\n"
+        #     "geometric features of this object.")
+        # )
+        # grid0.addWidget(self.buffer_aperture_label, 3, 0)
+        #
+        # self.buffer_aperture_entry = FloatEntry2()
+        # grid0.addWidget(self.buffer_aperture_entry, 3, 1)
+
+        self.layout.addStretch()
+
+
+class GerberExpPrefGroupUI(OptionsGroupUI):
+
+    def __init__(self, parent=None):
+        super(GerberExpPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber Export")))
+
+        # Plot options
+        self.export_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export Options"))
+        self.export_options_label.setToolTip(
+            _("The parameters set here are used in the file exported\n"
+              "when using the File -> Export -> Export Gerber menu entry.")
+        )
+        self.layout.addWidget(self.export_options_label)
+
+        form = QtWidgets.QFormLayout()
+        self.layout.addLayout(form)
+
+        # Gerber Units
+        self.gerber_units_label = QtWidgets.QLabel('<b>%s:</b>' % _('Units'))
+        self.gerber_units_label.setToolTip(
+            _("The units used in the Gerber file.")
+        )
+
+        self.gerber_units_radio = RadioSet([{'label': _('INCH'), 'value': 'IN'},
+                                            {'label': _('MM'), 'value': 'MM'}])
+        self.gerber_units_radio.setToolTip(
+            _("The units used in the Gerber file.")
+        )
+
+        form.addRow(self.gerber_units_label, self.gerber_units_radio)
+
+        # Gerber format
+        self.digits_label = QtWidgets.QLabel("<b>%s:</b>" % _("Int/Decimals"))
+        self.digits_label.setToolTip(
+            _("The number of digits in the whole part of the number\n"
+              "and in the fractional part of the number.")
+        )
+
+        hlay1 = QtWidgets.QHBoxLayout()
+
+        self.format_whole_entry = IntEntry()
+        self.format_whole_entry.setMaxLength(1)
+        self.format_whole_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.format_whole_entry.setMinimumWidth(30)
+        self.format_whole_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the whole part of Gerber coordinates.")
+        )
+        hlay1.addWidget(self.format_whole_entry, QtCore.Qt.AlignLeft)
+
+        gerber_separator_label = QtWidgets.QLabel(':')
+        gerber_separator_label.setFixedWidth(5)
+        hlay1.addWidget(gerber_separator_label, QtCore.Qt.AlignLeft)
+
+        self.format_dec_entry = IntEntry()
+        self.format_dec_entry.setMaxLength(1)
+        self.format_dec_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.format_dec_entry.setMinimumWidth(30)
+        self.format_dec_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the decimal part of Gerber coordinates.")
+        )
+        hlay1.addWidget(self.format_dec_entry, QtCore.Qt.AlignLeft)
+        hlay1.addStretch()
+
+        form.addRow(self.digits_label, hlay1)
+
+        # Gerber Zeros
+        self.zeros_label = QtWidgets.QLabel('<b>%s:</b>' % _('Zeros'))
+        self.zeros_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.zeros_label.setToolTip(
+            _("This sets the type of Gerber zeros.\n"
+              "If LZ then Leading Zeros are removed and\n"
+              "Trailing Zeros are kept.\n"
+              "If TZ is checked then Trailing Zeros are removed\n"
+              "and Leading Zeros are kept.")
+        )
+
+        self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'L'},
+                                     {'label': _('TZ'), 'value': 'T'}])
+        self.zeros_radio.setToolTip(
+            _("This sets the type of Gerber zeros.\n"
+              "If LZ then Leading Zeros are removed and\n"
+              "Trailing Zeros are kept.\n"
+              "If TZ is checked then Trailing Zeros are removed\n"
+              "and Leading Zeros are kept.")
+        )
+
+        form.addRow(self.zeros_label, self.zeros_radio)
+
+        self.layout.addStretch()
+
+
+class GerberEditorPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
+        super(GerberEditorPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber Editor")))
+
+        # Advanced Gerber Parameters
+        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.param_label.setToolTip(
+            _("A list of Gerber Editor parameters.")
+        )
+        self.layout.addWidget(self.param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Selection Limit
+        self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
+        self.sel_limit_label.setToolTip(
+            _("Set the number of selected Gerber geometry\n"
+              "items above which the utility geometry\n"
+              "becomes just a selection rectangle.\n"
+              "Increases the performance when moving a\n"
+              "large number of geometric elements.")
+        )
+        self.sel_limit_entry = IntEntry()
+
+        grid0.addWidget(self.sel_limit_label, 0, 0)
+        grid0.addWidget(self.sel_limit_entry, 0, 1)
+
+        # New aperture code
+        self.addcode_entry_lbl = QtWidgets.QLabel('%s:' % _('New Aperture code'))
+        self.addcode_entry_lbl.setToolTip(
+            _("Code for the new aperture")
+        )
+
+        self.addcode_entry = FCEntry()
+        self.addcode_entry.setValidator(QtGui.QIntValidator(0, 99))
+
+        grid0.addWidget(self.addcode_entry_lbl, 1, 0)
+        grid0.addWidget(self.addcode_entry, 1, 1)
+
+        # New aperture size
+        self.addsize_entry_lbl = QtWidgets.QLabel('%s:' % _('New Aperture size'))
+        self.addsize_entry_lbl.setToolTip(
+            _("Size for the new aperture")
+        )
+
+        self.addsize_entry = FCEntry()
+        self.addsize_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
+
+        grid0.addWidget(self.addsize_entry_lbl, 2, 0)
+        grid0.addWidget(self.addsize_entry, 2, 1)
+
+        # New aperture type
+        self.addtype_combo_lbl = QtWidgets.QLabel('%s:' % _('New Aperture type'))
+        self.addtype_combo_lbl.setToolTip(
+            _("Type for the new aperture.\n"
+              "Can be 'C', 'R' or 'O'.")
+        )
+
+        self.addtype_combo = FCComboBox()
+        self.addtype_combo.addItems(['C', 'R', 'O'])
+
+        grid0.addWidget(self.addtype_combo_lbl, 3, 0)
+        grid0.addWidget(self.addtype_combo, 3, 1)
+
+        # Number of pads in a pad array
+        self.grb_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of pads'))
+        self.grb_array_size_label.setToolTip(
+            _("Specify how many pads to be in the array.")
+        )
+
+        self.grb_array_size_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_array_size_label, 4, 0)
+        grid0.addWidget(self.grb_array_size_entry, 4, 1)
+
+        self.adddim_label = QtWidgets.QLabel('%s:' % _('Aperture Dimensions'))
+        self.adddim_label.setToolTip(
+            _("Diameters of the cutting tools, separated by ','")
+        )
+        grid0.addWidget(self.adddim_label, 5, 0)
+        self.adddim_entry = FCEntry()
+        grid0.addWidget(self.adddim_entry, 5, 1)
+
+        self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))
+        grid0.addWidget(self.grb_array_linear_label, 6, 0, 1, 2)
+
+        # Linear Pad Array direction
+        self.grb_axis_label = QtWidgets.QLabel('%s:' % _('Linear Dir.'))
+        self.grb_axis_label.setToolTip(
+            _("Direction on which the linear array is oriented:\n"
+              "- 'X' - horizontal axis \n"
+              "- 'Y' - vertical axis or \n"
+              "- 'Angle' - a custom angle for the array inclination")
+        )
+
+        self.grb_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
+                                        {'label': _('Y'), 'value': 'Y'},
+                                        {'label': _('Angle'), 'value': 'A'}])
+
+        grid0.addWidget(self.grb_axis_label, 7, 0)
+        grid0.addWidget(self.grb_axis_radio, 7, 1)
+
+        # Linear Pad Array pitch distance
+        self.grb_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
+        self.grb_pitch_label.setToolTip(
+            _("Pitch = Distance between elements of the array.")
+        )
+        # self.drill_pitch_label.setMinimumWidth(100)
+        self.grb_pitch_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_pitch_label, 8, 0)
+        grid0.addWidget(self.grb_pitch_entry, 8, 1)
+
+        # Linear Pad Array custom angle
+        self.grb_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
+        self.grb_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.grb_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_angle_label, 9, 0)
+        grid0.addWidget(self.grb_angle_entry, 9, 1)
+
+        self.grb_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Circular Pad Array'))
+        grid0.addWidget(self.grb_array_circ_label, 10, 0, 1, 2)
+
+        # Circular Pad Array direction
+        self.grb_circular_direction_label = QtWidgets.QLabel('%s:' % _('Circular Dir.'))
+        self.grb_circular_direction_label.setToolTip(
+            _("Direction for circular array.\n"
+              "Can be CW = clockwise or CCW = counter clockwise.")
+        )
+
+        self.grb_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
+                                                {'label': _('CCW'), 'value': 'CCW'}])
+
+        grid0.addWidget(self.grb_circular_direction_label, 11, 0)
+        grid0.addWidget(self.grb_circular_dir_radio, 11, 1)
+
+        # Circular Pad Array Angle
+        self.grb_circular_angle_label = QtWidgets.QLabel('%s:' % _('Circ. Angle'))
+        self.grb_circular_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.grb_circular_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_circular_angle_label, 12, 0)
+        grid0.addWidget(self.grb_circular_angle_entry, 12, 1)
+
+        self.grb_array_tools_b_label = QtWidgets.QLabel('<b>%s:</b>' % _('Buffer Tool'))
+        grid0.addWidget(self.grb_array_tools_b_label, 13, 0, 1, 2)
+
+        # Buffer Distance
+        self.grb_buff_label = QtWidgets.QLabel('%s:' % _('Buffer distance'))
+        self.grb_buff_label.setToolTip(
+            _("Distance at which to buffer the Gerber element.")
+        )
+        self.grb_buff_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_buff_label, 14, 0)
+        grid0.addWidget(self.grb_buff_entry, 14, 1)
+
+        self.grb_array_tools_s_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scale Tool'))
+        grid0.addWidget(self.grb_array_tools_s_label, 15, 0, 1, 2)
+
+        # Scale Factor
+        self.grb_scale_label = QtWidgets.QLabel('%s:' % _('Scale factor'))
+        self.grb_scale_label.setToolTip(
+            _("Factor to scale the Gerber element.")
+        )
+        self.grb_scale_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_scale_label, 16, 0)
+        grid0.addWidget(self.grb_scale_entry, 16, 1)
+
+        self.grb_array_tools_ma_label = QtWidgets.QLabel('<b>%s:</b>' % _('Mark Area Tool'))
+        grid0.addWidget(self.grb_array_tools_ma_label, 17, 0, 1, 2)
+
+        # Mark area Tool low threshold
+        self.grb_ma_low_label = QtWidgets.QLabel('%s:' % _('Threshold low'))
+        self.grb_ma_low_label.setToolTip(
+            _("Threshold value under which the apertures are not marked.")
+        )
+        self.grb_ma_low_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_ma_low_label, 18, 0)
+        grid0.addWidget(self.grb_ma_low_entry, 18, 1)
+
+        # Mark area Tool high threshold
+        self.grb_ma_high_label = QtWidgets.QLabel('%s:' % _('Threshold low'))
+        self.grb_ma_high_label.setToolTip(
+            _("Threshold value over which the apertures are not marked.")
+        )
+        self.grb_ma_high_entry = LengthEntry()
+
+        grid0.addWidget(self.grb_ma_high_label, 19, 0)
+        grid0.addWidget(self.grb_ma_high_entry, 19, 1)
+
+        self.layout.addStretch()
+
+
+class ExcellonGenPrefGroupUI(OptionsGroupUI):
+
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Excellon Options", parent=parent)
+        super(ExcellonGenPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Excellon General")))
+
+        # Plot options
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
+        self.layout.addWidget(self.plot_options_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        self.plot_cb = FCCheckBox(label=_('Plot'))
+        self.plot_cb.setToolTip(
+            "Plot (show) this object."
+        )
+        grid1.addWidget(self.plot_cb, 0, 0)
+
+        self.solid_cb = FCCheckBox(label=_('Solid'))
+        self.solid_cb.setToolTip(
+            "Plot as solid circles."
+        )
+        grid1.addWidget(self.solid_cb, 0, 1)
+
+        # Excellon format
+        self.excellon_format_label = QtWidgets.QLabel("<b>%s:</b>" % _("Excellon Format"))
+        self.excellon_format_label.setToolTip(
+            _("The NC drill files, usually named Excellon files\n"
+              "are files that can be found in different formats.\n"
+              "Here we set the format used when the provided\n"
+              "coordinates are not using period.\n"
+              "\n"
+              "Possible presets:\n"
+              "\n"
+              "PROTEUS 3:3 MM LZ\n"
+              "DipTrace 5:2 MM TZ\n"
+              "DipTrace 4:3 MM LZ\n"
+              "\n"
+              "EAGLE 3:3 MM TZ\n"
+              "EAGLE 4:3 MM TZ\n"
+              "EAGLE 2:5 INCH TZ\n"
+              "EAGLE 3:5 INCH TZ\n"
+              "\n"
+              "ALTIUM 2:4 INCH LZ\n"
+              "Sprint Layout 2:4 INCH LZ"
+              "\n"
+              "KiCAD 3:5 INCH TZ")
+        )
+        self.layout.addWidget(self.excellon_format_label)
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+        self.excellon_format_in_label = QtWidgets.QLabel('%s:' % _("INCH"))
+        self.excellon_format_in_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.excellon_format_in_label.setToolTip(
+            _("Default values for INCH are 2:4"))
+        hlay1.addWidget(self.excellon_format_in_label, QtCore.Qt.AlignLeft)
+
+        self.excellon_format_upper_in_entry = IntEntry()
+        self.excellon_format_upper_in_entry.setMaxLength(1)
+        self.excellon_format_upper_in_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.excellon_format_upper_in_entry.setMinimumWidth(30)
+        self.excellon_format_upper_in_entry.setToolTip(
+           _("This numbers signify the number of digits in\n"
+             "the whole part of Excellon coordinates.")
+        )
+        hlay1.addWidget(self.excellon_format_upper_in_entry, QtCore.Qt.AlignLeft)
+
+        excellon_separator_in_label = QtWidgets.QLabel(':')
+        excellon_separator_in_label.setFixedWidth(5)
+        hlay1.addWidget(excellon_separator_in_label, QtCore.Qt.AlignLeft)
+
+        self.excellon_format_lower_in_entry = IntEntry()
+        self.excellon_format_lower_in_entry.setMaxLength(1)
+        self.excellon_format_lower_in_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.excellon_format_lower_in_entry.setMinimumWidth(30)
+        self.excellon_format_lower_in_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the decimal part of Excellon coordinates.")
+        )
+        hlay1.addWidget(self.excellon_format_lower_in_entry, QtCore.Qt.AlignLeft)
+        hlay1.addStretch()
+
+        hlay2 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay2)
+        self.excellon_format_mm_label = QtWidgets.QLabel('%s:' % _("METRIC"))
+        self.excellon_format_mm_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.excellon_format_mm_label.setToolTip(
+            _("Default values for METRIC are 3:3"))
+        hlay2.addWidget(self.excellon_format_mm_label, QtCore.Qt.AlignLeft)
+
+        self.excellon_format_upper_mm_entry = IntEntry()
+        self.excellon_format_upper_mm_entry.setMaxLength(1)
+        self.excellon_format_upper_mm_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.excellon_format_upper_mm_entry.setMinimumWidth(30)
+        self.excellon_format_upper_mm_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the whole part of Excellon coordinates.")
+        )
+        hlay2.addWidget(self.excellon_format_upper_mm_entry, QtCore.Qt.AlignLeft)
+
+        excellon_separator_mm_label = QtWidgets.QLabel(':')
+        excellon_separator_mm_label.setFixedWidth(5)
+        hlay2.addWidget(excellon_separator_mm_label, QtCore.Qt.AlignLeft)
+
+        self.excellon_format_lower_mm_entry = IntEntry()
+        self.excellon_format_lower_mm_entry.setMaxLength(1)
+        self.excellon_format_lower_mm_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.excellon_format_lower_mm_entry.setMinimumWidth(30)
+        self.excellon_format_lower_mm_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the decimal part of Excellon coordinates.")
+        )
+        hlay2.addWidget(self.excellon_format_lower_mm_entry, QtCore.Qt.AlignLeft)
+        hlay2.addStretch()
+
+        grid2 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid2)
+
+        self.excellon_zeros_label = QtWidgets.QLabel('%s:' % _('Default <b>Zeros</b>'))
+        self.excellon_zeros_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.excellon_zeros_label.setToolTip(
+            _("This sets the type of Excellon zeros.\n"
+              "If LZ then Leading Zeros are kept and\n"
+              "Trailing Zeros are removed.\n"
+              "If TZ is checked then Trailing Zeros are kept\n"
+              "and Leading Zeros are removed.")
+        )
+        grid2.addWidget(self.excellon_zeros_label, 0, 0)
+
+        self.excellon_zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'L'},
+                                              {'label': _('TZ'), 'value': 'T'}])
+        self.excellon_zeros_radio.setToolTip(
+            _("This sets the default type of Excellon zeros.\n"
+              "If it is not detected in the parsed file the value here\n"
+              "will be used."
+              "If LZ then Leading Zeros are kept and\n"
+              "Trailing Zeros are removed.\n"
+              "If TZ is checked then Trailing Zeros are kept\n"
+              "and Leading Zeros are removed.")
+        )
+        grid2.addWidget(self.excellon_zeros_radio, 0, 1)
+
+        self.excellon_units_label = QtWidgets.QLabel('%s:' % _('Default <b>Units</b>'))
+        self.excellon_units_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.excellon_units_label.setToolTip(
+            _("This sets the default units of Excellon files.\n"
+              "If it is not detected in the parsed file the value here\n"
+              "will be used."
+              "Some Excellon files don't have an header\n"
+              "therefore this parameter will be used.")
+        )
+        grid2.addWidget(self.excellon_units_label, 1, 0)
+
+        self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
+                                              {'label': _('MM'), 'value': 'METRIC'}])
+        self.excellon_units_radio.setToolTip(
+            _("This sets the units of Excellon files.\n"
+              "Some Excellon files don't have an header\n"
+              "therefore this parameter will be used.")
+        )
+        grid2.addWidget(self.excellon_units_radio, 1, 1)
+
+        self.update_excellon_cb = FCCheckBox(label=_('Update Export settings'))
+        self.update_excellon_cb.setToolTip(
+            "If checked, the Excellon Export settings will be updated with the ones above."
+        )
+        grid2.addWidget(self.update_excellon_cb, 2, 0, 1, 2)
+
+        grid2.addWidget(QtWidgets.QLabel(""), 3, 0)
+
+        self.excellon_general_label = QtWidgets.QLabel("<b>%s:</b>" % _("Excellon Optimization"))
+        grid2.addWidget(self.excellon_general_label, 4, 0, 1, 2)
+
+        self.excellon_optimization_label = QtWidgets.QLabel(_('Algorithm:'))
+        self.excellon_optimization_label.setToolTip(
+            _("This sets the optimization type for the Excellon drill path.\n"
+              "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
+              "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
+              "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
+              "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
+              "drill path optimization.\n"
+              "\n"
+              "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
+              "Travelling Salesman algorithm for path optimization.")
+        )
+        grid2.addWidget(self.excellon_optimization_label, 5, 0)
+
+        self.excellon_optimization_radio = RadioSet([{'label': _('MetaHeuristic'), 'value': 'M'},
+                                                     {'label': _('Basic'), 'value': 'B'},
+                                                     {'label': _('TSA'), 'value': 'T'}],
+                                                    orientation='vertical', stretch=False)
+        self.excellon_optimization_radio.setToolTip(
+            _("This sets the optimization type for the Excellon drill path.\n"
+              "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
+              "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
+              "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
+              "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
+              "drill path optimization.\n"
+              "\n"
+              "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
+              "Travelling Salesman algorithm for path optimization.")
+        )
+        grid2.addWidget(self.excellon_optimization_radio, 5, 1)
+
+        self.optimization_time_label = QtWidgets.QLabel('%s:' % _('Optimization Time'))
+        self.optimization_time_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.optimization_time_label.setToolTip(
+            _("When OR-Tools Metaheuristic (MH) is enabled there is a\n"
+              "maximum threshold for how much time is spent doing the\n"
+              "path optimization. This max duration is set here.\n"
+              "In seconds.")
+
+        )
+        grid2.addWidget(self.optimization_time_label, 6, 0)
+
+        self.optimization_time_entry = IntEntry()
+        self.optimization_time_entry.setValidator(QtGui.QIntValidator(0, 999))
+        grid2.addWidget(self.optimization_time_entry, 6, 1)
+
+        current_platform = platform.architecture()[0]
+        if current_platform == '64bit':
+            self.excellon_optimization_label.setDisabled(False)
+            self.excellon_optimization_radio.setDisabled(False)
+            self.optimization_time_label.setDisabled(False)
+            self.optimization_time_entry.setDisabled(False)
+            self.excellon_optimization_radio.activated_custom.connect(self.optimization_selection)
+
+        else:
+            self.excellon_optimization_label.setDisabled(True)
+            self.excellon_optimization_radio.setDisabled(True)
+            self.optimization_time_label.setDisabled(True)
+            self.optimization_time_entry.setDisabled(True)
+
+        self.layout.addStretch()
+
+    def optimization_selection(self):
+        if self.excellon_optimization_radio.get_value() == 'M':
+            self.optimization_time_label.setDisabled(False)
+            self.optimization_time_entry.setDisabled(False)
+        else:
+            self.optimization_time_label.setDisabled(True)
+            self.optimization_time_entry.setDisabled(True)
+
+
+class ExcellonOptPrefGroupUI(OptionsGroupUI):
+
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Excellon Options", parent=parent)
+        super(ExcellonOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Excellon Options")))
+
+        # ## Create CNC Job
+        self.cncjob_label = QtWidgets.QLabel('<b>%s</b>' % _('Create CNC Job'))
+        self.cncjob_label.setToolTip(
+            _("Parameters used to create a CNC Job object\n"
+              "for this drill object.")
+        )
+        self.layout.addWidget(self.cncjob_label)
+
+        grid2 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid2)
+
+        # Cut Z
+        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
+        cutzlabel.setToolTip(
+            _("Drill depth (negative)\n"
+              "below the copper surface.")
+        )
+        grid2.addWidget(cutzlabel, 0, 0)
+        self.cutz_entry = LengthEntry()
+        grid2.addWidget(self.cutz_entry, 0, 1)
+
+        # Travel Z
+        travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
+        travelzlabel.setToolTip(
+            _("Tool height when travelling\n"
+              "across the XY plane.")
+        )
+        grid2.addWidget(travelzlabel, 1, 0)
+        self.travelz_entry = LengthEntry()
+        grid2.addWidget(self.travelz_entry, 1, 1)
+
+        # Tool change:
+        toolchlabel = QtWidgets.QLabel('%s:' % _("Tool change"))
+        toolchlabel.setToolTip(
+            _("Include tool-change sequence\n"
+              "in G-Code (Pause for tool change).")
+        )
+        self.toolchange_cb = FCCheckBox()
+        grid2.addWidget(toolchlabel, 2, 0)
+        grid2.addWidget(self.toolchange_cb, 2, 1)
+
+        toolchangezlabel = QtWidgets.QLabel('%s:' % _('Toolchange Z'))
+        toolchangezlabel.setToolTip(
+            _("Z-axis position (height) for\n"
+              "tool change.")
+        )
+        grid2.addWidget(toolchangezlabel, 3, 0)
+        self.toolchangez_entry = LengthEntry()
+        grid2.addWidget(self.toolchangez_entry, 3, 1)
+
+        # End Move Z
+        endzlabel = QtWidgets.QLabel('%s:' % _('End move Z'))
+        endzlabel.setToolTip(
+            _("Height of the tool after\n"
+              "the last move at the end of the job.")
+        )
+        grid2.addWidget(endzlabel, 4, 0)
+        self.eendz_entry = LengthEntry()
+        grid2.addWidget(self.eendz_entry, 4, 1)
+
+        # Feedrate Z
+        frlabel = QtWidgets.QLabel('%s:' % _('Feedrate Z'))
+        frlabel.setToolTip(
+            _("Tool speed while drilling\n"
+              "(in units per minute).\n"
+              "So called 'Plunge' feedrate.\n"
+              "This is for linear move G01.")
+        )
+        grid2.addWidget(frlabel, 5, 0)
+        self.feedrate_entry = LengthEntry()
+        grid2.addWidget(self.feedrate_entry, 5, 1)
+
+        # Spindle speed
+        spdlabel = QtWidgets.QLabel('%s:' % _('Spindle Speed'))
+        spdlabel.setToolTip(
+            _("Speed of the spindle\n"
+              "in RPM (optional)")
+        )
+        grid2.addWidget(spdlabel, 6, 0)
+        self.spindlespeed_entry = IntEntry(allow_empty=True)
+        grid2.addWidget(self.spindlespeed_entry, 6, 1)
+
+        # Dwell
+        dwelllabel = QtWidgets.QLabel('%s:' % _('Dwell'))
+        dwelllabel.setToolTip(
+            _("Pause to allow the spindle to reach its\n"
+              "speed before cutting.")
+        )
+        dwelltime = QtWidgets.QLabel('%s:' % _('Duration'))
+        dwelltime.setToolTip(
+            _("Number of time units for spindle to dwell.")
+        )
+        self.dwell_cb = FCCheckBox()
+        self.dwelltime_entry = FCEntry()
+
+        grid2.addWidget(dwelllabel, 7, 0)
+        grid2.addWidget(self.dwell_cb, 7, 1)
+        grid2.addWidget(dwelltime, 8, 0)
+        grid2.addWidget(self.dwelltime_entry, 8, 1)
+
+        self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
+
+        # postprocessor selection
+        pp_excellon_label = QtWidgets.QLabel('%s:' % _("Postprocessor"))
+        pp_excellon_label.setToolTip(
+            _("The postprocessor JSON file that dictates\n"
+              "Gcode output.")
+        )
+        grid2.addWidget(pp_excellon_label, 9, 0)
+        self.pp_excellon_name_cb = FCComboBox()
+        self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
+        grid2.addWidget(self.pp_excellon_name_cb, 9, 1)
+
+        # ### Choose what to use for Gcode creation: Drills, Slots or Both
+        excellon_gcode_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Gcode'))
+        excellon_gcode_type_label.setToolTip(
+            _("Choose what to use for GCode generation:\n"
+              "'Drills', 'Slots' or 'Both'.\n"
+              "When choosing 'Slots' or 'Both', slots will be\n"
+              "converted to drills.")
+        )
+        self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
+                                                   {'label': 'Slots', 'value': 'slots'},
+                                                   {'label': 'Both', 'value': 'both'}])
+        grid2.addWidget(excellon_gcode_type_label, 10, 0)
+        grid2.addWidget(self.excellon_gcode_type_radio, 10, 1)
+
+        # until I decide to implement this feature those remain disabled
+        excellon_gcode_type_label.hide()
+        self.excellon_gcode_type_radio.setVisible(False)
+
+        # ### Milling Holes ## ##
+        self.mill_hole_label = QtWidgets.QLabel('<b>%s</b>' % _('Mill Holes'))
+        self.mill_hole_label.setToolTip(
+            _("Create Geometry for milling holes.")
+        )
+        grid2.addWidget(self.mill_hole_label, 11, 0, 1, 2)
+
+        tdlabel = QtWidgets.QLabel('%s:' % _('Drill Tool dia'))
+        tdlabel.setToolTip(
+            _("Diameter of the cutting tool.")
+        )
+        grid2.addWidget(tdlabel, 12, 0)
+        self.tooldia_entry = LengthEntry()
+        grid2.addWidget(self.tooldia_entry, 12, 1)
+        stdlabel = QtWidgets.QLabel('%s:' % _('Slot Tool dia'))
+        stdlabel.setToolTip(
+            _("Diameter of the cutting tool\n"
+              "when milling slots.")
+        )
+        grid2.addWidget(stdlabel, 13, 0)
+        self.slot_tooldia_entry = LengthEntry()
+        grid2.addWidget(self.slot_tooldia_entry, 13, 1)
+
+        grid4 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid4)
+
+        # Adding the Excellon Format Defaults Button
+        self.excellon_defaults_button = QtWidgets.QPushButton()
+        self.excellon_defaults_button.setText(str(_("Defaults")))
+        self.excellon_defaults_button.setMinimumWidth(80)
+        grid4.addWidget(self.excellon_defaults_button, 0, 0, QtCore.Qt.AlignRight)
+
+        self.layout.addStretch()
+
+
+class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
+
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Excellon Advanced Options", parent=parent)
+        super(ExcellonAdvOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Excellon Adv. Options")))
+
+        # #######################
+        # ## ADVANCED OPTIONS ###
+        # #######################
+
+        self.exc_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
+        self.exc_label.setToolTip(
+            _("A list of Excellon advanced parameters.\n"
+              "Those parameters are available only for\n"
+              "Advanced App. Level.")
+        )
+        self.layout.addWidget(self.exc_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        offsetlabel = QtWidgets.QLabel('%s:' % _('Offset Z'))
+        offsetlabel.setToolTip(
+            _("Some drill bits (the larger ones) need to drill deeper\n"
+              "to create the desired exit hole diameter due of the tip shape.\n"
+              "The value here can compensate the Cut Z parameter."))
+        grid1.addWidget(offsetlabel, 0, 0)
+        self.offset_entry = LengthEntry()
+        grid1.addWidget(self.offset_entry, 0, 1)
+
+        toolchange_xy_label = QtWidgets.QLabel('%s:' % _('Toolchange X,Y'))
+        toolchange_xy_label.setToolTip(
+            _("Toolchange X,Y position.")
+        )
+        grid1.addWidget(toolchange_xy_label, 1, 0)
+        self.toolchangexy_entry = FCEntry()
+        grid1.addWidget(self.toolchangexy_entry, 1, 1)
+
+        startzlabel = QtWidgets.QLabel('%s:' % _('Start move Z'))
+        startzlabel.setToolTip(
+            _("Height of the tool just after start.\n"
+              "Delete the value if you don't need this feature.")
+        )
+        grid1.addWidget(startzlabel, 2, 0)
+        self.estartz_entry = FloatEntry()
+        grid1.addWidget(self.estartz_entry, 2, 1)
+
+        # Feedrate Rapids
+        fr_rapid_label = QtWidgets.QLabel('%s:' % _('Feedrate Rapids'))
+        fr_rapid_label.setToolTip(
+            _("Tool speed while drilling\n"
+              "(in units per minute).\n"
+              "This is for the rapid move G00.\n"
+              "It is useful only for Marlin,\n"
+              "ignore for any other cases.")
+        )
+        grid1.addWidget(fr_rapid_label, 3, 0)
+        self.feedrate_rapid_entry = LengthEntry()
+        grid1.addWidget(self.feedrate_rapid_entry, 3, 1)
+
+        # Probe depth
+        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
+        self.pdepth_label.setToolTip(
+            _("The maximum depth that the probe is allowed\n"
+              "to probe. Negative value, in current units.")
+        )
+        grid1.addWidget(self.pdepth_label, 4, 0)
+        self.pdepth_entry = FCEntry()
+        grid1.addWidget(self.pdepth_entry, 4, 1)
+
+        # Probe feedrate
+        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
+        self.feedrate_probe_label.setToolTip(
+           _("The feedrate used while the probe is probing.")
+        )
+        grid1.addWidget(self.feedrate_probe_label, 5, 0)
+        self.feedrate_probe_entry = FCEntry()
+        grid1.addWidget(self.feedrate_probe_entry, 5, 1)
+
+        # Spindle direction
+        spindle_dir_label = QtWidgets.QLabel('%s:' % _('Spindle dir.'))
+        spindle_dir_label.setToolTip(
+            _("This sets the direction that the spindle is rotating.\n"
+              "It can be either:\n"
+              "- CW = clockwise or\n"
+              "- CCW = counter clockwise")
+        )
+
+        self.spindledir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
+                                          {'label': _('CCW'), 'value': 'CCW'}])
+        grid1.addWidget(spindle_dir_label, 6, 0)
+        grid1.addWidget(self.spindledir_radio, 6, 1)
+
+        fplungelabel = QtWidgets.QLabel('%s:' % _('Fast Plunge'))
+        fplungelabel.setToolTip(
+            _("By checking this, the vertical move from\n"
+              "Z_Toolchange to Z_move is done with G0,\n"
+              "meaning the fastest speed available.\n"
+              "WARNING: the move is done at Toolchange X,Y coords.")
+        )
+        self.fplunge_cb = FCCheckBox()
+        grid1.addWidget(fplungelabel, 7, 0)
+        grid1.addWidget(self.fplunge_cb, 7, 1)
+
+        fretractlabel = QtWidgets.QLabel('%s:' % _('Fast Retract'))
+        fretractlabel.setToolTip(
+            _("Exit hole strategy.\n"
+              " - When uncheked, while exiting the drilled hole the drill bit\n"
+              "will travel slow, with set feedrate (G1), up to zero depth and then\n"
+              "travel as fast as possible (G0) to the Z Move (travel height).\n"
+              " - When checked the travel from Z cut (cut depth) to Z_move\n"
+              "(travel height) is done as fast as possible (G0) in one move.")
+        )
+        self.fretract_cb = FCCheckBox()
+        grid1.addWidget(fretractlabel, 8, 0)
+        grid1.addWidget(self.fretract_cb, 8, 1)
+
+        self.layout.addStretch()
+
+
+class ExcellonExpPrefGroupUI(OptionsGroupUI):
+
+    def __init__(self, parent=None):
+        super(ExcellonExpPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Excellon Export")))
+
+        # Plot options
+        self.export_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export Options"))
+        self.export_options_label.setToolTip(
+            _("The parameters set here are used in the file exported\n"
+              "when using the File -> Export -> Export Excellon menu entry.")
+        )
+        self.layout.addWidget(self.export_options_label)
+
+        form = QtWidgets.QFormLayout()
+        self.layout.addLayout(form)
+
+        # Excellon Units
+        self.excellon_units_label = QtWidgets.QLabel('<b>%s:</b>' % _('Units'))
+        self.excellon_units_label.setToolTip(
+            _("The units used in the Excellon file.")
+        )
+
+        self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
+                                              {'label': _('MM'), 'value': 'METRIC'}])
+        self.excellon_units_radio.setToolTip(
+            _("The units used in the Excellon file.")
+        )
+
+        form.addRow(self.excellon_units_label, self.excellon_units_radio)
+
+        # Excellon non-decimal format
+        self.digits_label = QtWidgets.QLabel("<b>%s:</b>" % _("Int/Decimals"))
+        self.digits_label.setToolTip(
+            _("The NC drill files, usually named Excellon files\n"
+              "are files that can be found in different formats.\n"
+              "Here we set the format used when the provided\n"
+              "coordinates are not using period.")
+        )
+
+        hlay1 = QtWidgets.QHBoxLayout()
+
+        self.format_whole_entry = IntEntry()
+        self.format_whole_entry.setMaxLength(1)
+        self.format_whole_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.format_whole_entry.setMinimumWidth(30)
+        self.format_whole_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the whole part of Excellon coordinates.")
+        )
+        hlay1.addWidget(self.format_whole_entry, QtCore.Qt.AlignLeft)
+
+        excellon_separator_label = QtWidgets.QLabel(':')
+        excellon_separator_label.setFixedWidth(5)
+        hlay1.addWidget(excellon_separator_label, QtCore.Qt.AlignLeft)
+
+        self.format_dec_entry = IntEntry()
+        self.format_dec_entry.setMaxLength(1)
+        self.format_dec_entry.setAlignment(QtCore.Qt.AlignRight)
+        self.format_dec_entry.setMinimumWidth(30)
+        self.format_dec_entry.setToolTip(
+            _("This numbers signify the number of digits in\n"
+              "the decimal part of Excellon coordinates.")
+        )
+        hlay1.addWidget(self.format_dec_entry, QtCore.Qt.AlignLeft)
+        hlay1.addStretch()
+
+        form.addRow(self.digits_label, hlay1)
+
+        # Select the Excellon Format
+        self.format_label = QtWidgets.QLabel("<b>%s:</b>" % _("Format"))
+        self.format_label.setToolTip(
+            _("Select the kind of coordinates format used.\n"
+              "Coordinates can be saved with decimal point or without.\n"
+              "When there is no decimal point, it is required to specify\n"
+              "the number of digits for integer part and the number of decimals.\n"
+              "Also it will have to be specified if LZ = leading zeros are kept\n"
+              "or TZ = trailing zeros are kept.")
+        )
+        self.format_radio = RadioSet([{'label': _('Decimal'), 'value': 'dec'},
+                                      {'label': _('No-Decimal'), 'value': 'ndec'}])
+        self.format_radio.setToolTip(
+            _("Select the kind of coordinates format used.\n"
+              "Coordinates can be saved with decimal point or without.\n"
+              "When there is no decimal point, it is required to specify\n"
+              "the number of digits for integer part and the number of decimals.\n"
+              "Also it will have to be specified if LZ = leading zeros are kept\n"
+              "or TZ = trailing zeros are kept.")
+        )
+
+        form.addRow(self.format_label, self.format_radio)
+
+        # Excellon Zeros
+        self.zeros_label = QtWidgets.QLabel('<b>%s:</b>' % _('Zeros'))
+        self.zeros_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.zeros_label.setToolTip(
+            _("This sets the type of Excellon zeros.\n"
+              "If LZ then Leading Zeros are kept and\n"
+              "Trailing Zeros are removed.\n"
+              "If TZ is checked then Trailing Zeros are kept\n"
+              "and Leading Zeros are removed.")
+        )
+
+        self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
+                                     {'label': _('TZ'), 'value': 'TZ'}])
+        self.zeros_radio.setToolTip(
+            _("This sets the default type of Excellon zeros.\n"
+              "If LZ then Leading Zeros are kept and\n"
+              "Trailing Zeros are removed.\n"
+              "If TZ is checked then Trailing Zeros are kept\n"
+              "and Leading Zeros are removed.")
+        )
+
+        form.addRow(self.zeros_label, self.zeros_radio)
+
+        # Slot type
+        self.slot_type_label = QtWidgets.QLabel('<b>%s:</b>' % _('Slot type'))
+        self.slot_type_label.setAlignment(QtCore.Qt.AlignLeft)
+        self.slot_type_label.setToolTip(
+            _("This sets how the slots will be exported.\n"
+              "If ROUTED then the slots will be routed\n"
+              "using M15/M16 commands.\n"
+              "If DRILLED(G85) the slots will be exported\n"
+              "using the Drilled slot command (G85).")
+        )
+
+        self.slot_type_radio = RadioSet([{'label': _('Routed'), 'value': 'routing'},
+                                         {'label': _('Drilled(G85)'), 'value': 'drilling'}])
+        self.slot_type_radio.setToolTip(
+            _("This sets how the slots will be exported.\n"
+              "If ROUTED then the slots will be routed\n"
+              "using M15/M16 commands.\n"
+              "If DRILLED(G85) the slots will be exported\n"
+              "using the Drilled slot command (G85).")
+        )
+
+        form.addRow(self.slot_type_label, self.slot_type_radio)
+
+        self.layout.addStretch()
+        self.format_radio.activated_custom.connect(self.optimization_selection)
+
+    def optimization_selection(self):
+        if self.format_radio.get_value() == 'dec':
+            self.zeros_label.setDisabled(True)
+            self.zeros_radio.setDisabled(True)
+        else:
+            self.zeros_label.setDisabled(False)
+            self.zeros_radio.setDisabled(False)
+
+
+class ExcellonEditorPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        super(ExcellonEditorPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Excellon Editor")))
+
+        # Excellon Editor Parameters
+        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.param_label.setToolTip(
+            _("A list of Excellon Editor parameters.")
+        )
+        self.layout.addWidget(self.param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Selection Limit
+        self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
+        self.sel_limit_label.setToolTip(
+            _("Set the number of selected Excellon geometry\n"
+              "items above which the utility geometry\n"
+              "becomes just a selection rectangle.\n"
+              "Increases the performance when moving a\n"
+              "large number of geometric elements.")
+        )
+        self.sel_limit_entry = IntEntry()
+
+        grid0.addWidget(self.sel_limit_label, 0, 0)
+        grid0.addWidget(self.sel_limit_entry, 0, 1)
+
+        # New tool diameter
+        self.addtool_entry_lbl = QtWidgets.QLabel('%s:' % _('New Tool Dia'))
+        self.addtool_entry_lbl.setToolTip(
+            _("Diameter for the new tool")
+        )
+
+        self.addtool_entry = FCEntry()
+        self.addtool_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
+
+        grid0.addWidget(self.addtool_entry_lbl, 1, 0)
+        grid0.addWidget(self.addtool_entry, 1, 1)
+
+        # Number of drill holes in a drill array
+        self.drill_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of drills'))
+        self.drill_array_size_label.setToolTip(
+            _("Specify how many drills to be in the array.")
+        )
+        # self.drill_array_size_label.setMinimumWidth(100)
+
+        self.drill_array_size_entry = LengthEntry()
+
+        grid0.addWidget(self.drill_array_size_label, 2, 0)
+        grid0.addWidget(self.drill_array_size_entry, 2, 1)
+
+        self.drill_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Drill Array'))
+        grid0.addWidget(self.drill_array_linear_label, 3, 0, 1, 2)
+
+        # Linear Drill Array direction
+        self.drill_axis_label = QtWidgets.QLabel('%s:' % _('Linear Dir.'))
+        self.drill_axis_label.setToolTip(
+            _("Direction on which the linear array is oriented:\n"
+              "- 'X' - horizontal axis \n"
+              "- 'Y' - vertical axis or \n"
+              "- 'Angle' - a custom angle for the array inclination")
+        )
+        # self.drill_axis_label.setMinimumWidth(100)
+        self.drill_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
+                                          {'label': _('Y'), 'value': 'Y'},
+                                          {'label': _('Angle'), 'value': 'A'}])
+
+        grid0.addWidget(self.drill_axis_label, 4, 0)
+        grid0.addWidget(self.drill_axis_radio, 4, 1)
+
+        # Linear Drill Array pitch distance
+        self.drill_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
+        self.drill_pitch_label.setToolTip(
+            _("Pitch = Distance between elements of the array.")
+        )
+        # self.drill_pitch_label.setMinimumWidth(100)
+        self.drill_pitch_entry = LengthEntry()
+
+        grid0.addWidget(self.drill_pitch_label, 5, 0)
+        grid0.addWidget(self.drill_pitch_entry, 5, 1)
+
+        # Linear Drill Array custom angle
+        self.drill_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
+        self.drill_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.drill_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.drill_angle_label, 6, 0)
+        grid0.addWidget(self.drill_angle_entry, 6, 1)
+
+        self.drill_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Circular Drill Array'))
+        grid0.addWidget(self.drill_array_circ_label, 7, 0, 1, 2)
+
+        # Circular Drill Array direction
+        self.drill_circular_direction_label = QtWidgets.QLabel('%s:' % _('Circular Dir.'))
+        self.drill_circular_direction_label.setToolTip(
+            _("Direction for circular array.\n"
+              "Can be CW = clockwise or CCW = counter clockwise.")
+        )
+
+        self.drill_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
+                                                  {'label': _('CCW'), 'value': 'CCW'}])
+
+        grid0.addWidget(self.drill_circular_direction_label, 8, 0)
+        grid0.addWidget(self.drill_circular_dir_radio, 8, 1)
+
+        # Circular Drill Array Angle
+        self.drill_circular_angle_label = QtWidgets.QLabel('%s:' % _('Circ. Angle'))
+        self.drill_circular_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.drill_circular_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.drill_circular_angle_label, 9, 0)
+        grid0.addWidget(self.drill_circular_angle_entry, 9, 1)
+
+        # ##### SLOTS #####
+        # #################
+        self.drill_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Slots'))
+        grid0.addWidget(self.drill_array_circ_label, 10, 0, 1, 2)
+
+        # Slot length
+        self.slot_length_label = QtWidgets.QLabel('%s:' % _('Length'))
+        self.slot_length_label.setToolTip(
+            _("Length = The length of the slot.")
+        )
+        self.slot_length_label.setMinimumWidth(100)
+
+        self.slot_length_entry = LengthEntry()
+        grid0.addWidget(self.slot_length_label, 11, 0)
+        grid0.addWidget(self.slot_length_entry, 11, 1)
+
+        # Slot direction
+        self.slot_axis_label = QtWidgets.QLabel('%s:' % _('Direction'))
+        self.slot_axis_label.setToolTip(
+            _("Direction on which the slot is oriented:\n"
+              "- 'X' - horizontal axis \n"
+              "- 'Y' - vertical axis or \n"
+              "- 'Angle' - a custom angle for the slot inclination")
+        )
+        self.slot_axis_label.setMinimumWidth(100)
+
+        self.slot_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
+                                         {'label': _('Y'), 'value': 'Y'},
+                                         {'label': _('Angle'), 'value': 'A'}])
+        grid0.addWidget(self.slot_axis_label, 12, 0)
+        grid0.addWidget(self.slot_axis_radio, 12, 1)
+
+        # Slot custom angle
+        self.slot_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
+        self.slot_angle_label.setToolTip(
+            _("Angle at which the slot is placed.\n"
+              "The precision is of max 2 decimals.\n"
+              "Min value is: -359.99 degrees.\n"
+              "Max value is:  360.00 degrees.")
+        )
+        self.slot_angle_label.setMinimumWidth(100)
+
+        self.slot_angle_spinner = FCDoubleSpinner()
+        self.slot_angle_spinner.set_precision(2)
+        self.slot_angle_spinner.setWrapping(True)
+        self.slot_angle_spinner.setRange(-359.99, 360.00)
+        self.slot_angle_spinner.setSingleStep(1.0)
+        grid0.addWidget(self.slot_angle_label, 13, 0)
+        grid0.addWidget(self.slot_angle_spinner, 13, 1)
+
+        # #### SLOTS ARRAY #######
+        # ########################
+
+        self.slot_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Slot Array'))
+        grid0.addWidget(self.slot_array_linear_label, 14, 0, 1, 2)
+
+        # Number of slot holes in a drill array
+        self.slot_array_size_label = QtWidgets.QLabel('%s:' % _('Nr of slots'))
+        self.drill_array_size_label.setToolTip(
+            _("Specify how many slots to be in the array.")
+        )
+        # self.slot_array_size_label.setMinimumWidth(100)
+
+        self.slot_array_size_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_size_label, 15, 0)
+        grid0.addWidget(self.slot_array_size_entry, 15, 1)
+
+        # Linear Slot Array direction
+        self.slot_array_axis_label = QtWidgets.QLabel('%s:' % _('Linear Dir.'))
+        self.slot_array_axis_label.setToolTip(
+            _("Direction on which the linear array is oriented:\n"
+              "- 'X' - horizontal axis \n"
+              "- 'Y' - vertical axis or \n"
+              "- 'Angle' - a custom angle for the array inclination")
+        )
+        # self.slot_axis_label.setMinimumWidth(100)
+        self.slot_array_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'},
+                                               {'label': _('Y'), 'value': 'Y'},
+                                               {'label': _('Angle'), 'value': 'A'}])
+
+        grid0.addWidget(self.slot_array_axis_label, 16, 0)
+        grid0.addWidget(self.slot_array_axis_radio, 16, 1)
+
+        # Linear Slot Array pitch distance
+        self.slot_array_pitch_label = QtWidgets.QLabel('%s:' % _('Pitch'))
+        self.slot_array_pitch_label.setToolTip(
+            _("Pitch = Distance between elements of the array.")
+        )
+        # self.drill_pitch_label.setMinimumWidth(100)
+        self.slot_array_pitch_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_pitch_label, 17, 0)
+        grid0.addWidget(self.slot_array_pitch_entry, 17, 1)
+
+        # Linear Slot Array custom angle
+        self.slot_array_angle_label = QtWidgets.QLabel('%s:' % _('Angle'))
+        self.slot_array_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.slot_array_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_angle_label, 18, 0)
+        grid0.addWidget(self.slot_array_angle_entry, 18, 1)
+
+        self.slot_array_circ_label = QtWidgets.QLabel('<b>%s:</b>' % _('Circular Slot Array'))
+        grid0.addWidget(self.slot_array_circ_label, 19, 0, 1, 2)
+
+        # Circular Slot Array direction
+        self.slot_array_circular_direction_label = QtWidgets.QLabel('%s:' % _('Circular Dir.'))
+        self.slot_array_circular_direction_label.setToolTip(
+            _("Direction for circular array.\n"
+              "Can be CW = clockwise or CCW = counter clockwise.")
+        )
+
+        self.slot_array_circular_dir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
+                                                       {'label': _('CCW'), 'value': 'CCW'}])
+
+        grid0.addWidget(self.slot_array_circular_direction_label, 20, 0)
+        grid0.addWidget(self.slot_array_circular_dir_radio, 20, 1)
+
+        # Circular Slot Array Angle
+        self.slot_array_circular_angle_label = QtWidgets.QLabel('%s:' % _('Circ. Angle'))
+        self.slot_array_circular_angle_label.setToolTip(
+            _("Angle at which each element in circular array is placed.")
+        )
+        self.slot_array_circular_angle_entry = LengthEntry()
+
+        grid0.addWidget(self.slot_array_circular_angle_label, 21, 0)
+        grid0.addWidget(self.slot_array_circular_angle_entry, 21, 1)
+
+        self.layout.addStretch()
+
+
+class GeometryGenPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Geometry General Preferences", parent=parent)
+        super(GeometryGenPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Geometry General")))
+
+        # ## Plot options
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
+        self.layout.addWidget(self.plot_options_label)
+
+        # Plot CB
+        self.plot_cb = FCCheckBox(label=_('Plot'))
+        self.plot_cb.setToolTip(
+            _("Plot (show) this object.")
+        )
+        self.layout.addWidget(self.plot_cb)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Number of circle steps for circular aperture linear approximation
+        self.circle_steps_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
+        self.circle_steps_label.setToolTip(
+            _("The number of circle steps for <b>Geometry</b> \n"
+              "circle and arc shapes linear approximation.")
+        )
+        grid0.addWidget(self.circle_steps_label, 1, 0)
+        self.circle_steps_entry = IntEntry()
+        grid0.addWidget(self.circle_steps_entry, 1, 1)
+
+        # Tools
+        self.tools_label = QtWidgets.QLabel("<b>%s:</b>" % _("Tools"))
+        grid0.addWidget(self.tools_label, 2, 0, 1, 2)
+
+        # Tooldia
+        tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
+        tdlabel.setToolTip(
+            _("Diameters of the cutting tools, separated by ','")
+        )
+        grid0.addWidget(tdlabel, 3, 0)
+        self.cnctooldia_entry = FCEntry()
+        grid0.addWidget(self.cnctooldia_entry, 3, 1)
+
+        self.layout.addStretch()
+
+
+class GeometryOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Geometry Options Preferences", parent=parent)
+        super(GeometryOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Geometry Options")))
+
+        # ------------------------------
+        # ## Create CNC Job
+        # ------------------------------
+        self.cncjob_label = QtWidgets.QLabel('<b>%s:</b>' % _('Create CNC Job'))
+        self.cncjob_label.setToolTip(
+            _("Create a CNC Job object\n"
+              "tracing the contours of this\n"
+              "Geometry object.")
+        )
+        self.layout.addWidget(self.cncjob_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        # Cut Z
+        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
+        cutzlabel.setToolTip(
+            _("Cutting depth (negative)\n"
+              "below the copper surface.")
+        )
+        grid1.addWidget(cutzlabel, 0, 0)
+        self.cutz_entry = LengthEntry()
+        grid1.addWidget(self.cutz_entry, 0, 1)
+
+        # Multidepth CheckBox
+        self.multidepth_cb = FCCheckBox(label=_('Multi-Depth'))
+        self.multidepth_cb.setToolTip(
+            _(
+                "Use multiple passes to limit\n"
+                "the cut depth in each pass. Will\n"
+                "cut multiple times until Cut Z is\n"
+                "reached."
+            )
+        )
+        grid1.addWidget(self.multidepth_cb, 1, 0)
+
+        # Depth/pass
+        dplabel = QtWidgets.QLabel('%s:' % _('Depth/Pass'))
+        dplabel.setToolTip(
+            _("The depth to cut on each pass,\n"
+              "when multidepth is enabled.\n"
+              "It has positive value although\n"
+              "it is a fraction from the depth\n"
+              "which has negative value.")
+        )
+
+        grid1.addWidget(dplabel, 2, 0)
+        self.depthperpass_entry = LengthEntry()
+        grid1.addWidget(self.depthperpass_entry, 2, 1)
+
+        self.ois_multidepth = OptionalInputSection(self.multidepth_cb, [self.depthperpass_entry])
+
+        # Travel Z
+        travelzlabel = QtWidgets.QLabel('%s:' % _('Travel Z'))
+        travelzlabel.setToolTip(
+            _("Height of the tool when\n"
+              "moving without cutting.")
+        )
+        grid1.addWidget(travelzlabel, 3, 0)
+        self.travelz_entry = LengthEntry()
+        grid1.addWidget(self.travelz_entry, 3, 1)
+
+        # Tool change:
+        toolchlabel = QtWidgets.QLabel('%s:' % _("Tool change"))
+        toolchlabel.setToolTip(
+            _(
+                "Include tool-change sequence\n"
+                "in the Machine Code (Pause for tool change)."
+            )
+        )
+        self.toolchange_cb = FCCheckBox()
+        grid1.addWidget(toolchlabel, 4, 0)
+        grid1.addWidget(self.toolchange_cb, 4, 1)
+
+        # Toolchange Z
+        toolchangezlabel = QtWidgets.QLabel('%s:' % _('Toolchange Z'))
+        toolchangezlabel.setToolTip(
+            _(
+                "Z-axis position (height) for\n"
+                "tool change."
+            )
+        )
+        grid1.addWidget(toolchangezlabel, 5, 0)
+        self.toolchangez_entry = LengthEntry()
+        grid1.addWidget(self.toolchangez_entry, 5, 1)
+
+        # End move Z
+        endzlabel = QtWidgets.QLabel('%s:' % _('End move Z'))
+        endzlabel.setToolTip(
+            _("Height of the tool after\n"
+              "the last move at the end of the job.")
+        )
+        grid1.addWidget(endzlabel, 6, 0)
+        self.gendz_entry = LengthEntry()
+        grid1.addWidget(self.gendz_entry, 6, 1)
+
+        # Feedrate X-Y
+        frlabel = QtWidgets.QLabel('%s:' % _('Feed Rate X-Y'))
+        frlabel.setToolTip(
+            _("Cutting speed in the XY\n"
+              "plane in units per minute")
+        )
+        grid1.addWidget(frlabel, 7, 0)
+        self.cncfeedrate_entry = LengthEntry()
+        grid1.addWidget(self.cncfeedrate_entry, 7, 1)
+
+        # Feedrate Z (Plunge)
+        frz_label = QtWidgets.QLabel('%s:' % _('Feed Rate Z'))
+        frz_label.setToolTip(
+            _("Cutting speed in the XY\n"
+              "plane in units per minute.\n"
+              "It is called also Plunge.")
+        )
+        grid1.addWidget(frz_label, 8, 0)
+        self.cncplunge_entry = LengthEntry()
+        grid1.addWidget(self.cncplunge_entry, 8, 1)
+
+        # Spindle Speed
+        spdlabel = QtWidgets.QLabel('%s:' % _('Spindle speed'))
+        spdlabel.setToolTip(
+            _(
+                "Speed of the spindle in RPM (optional).\n"
+                "If LASER postprocessor is used,\n"
+                "this value is the power of laser."
+            )
+        )
+        grid1.addWidget(spdlabel, 9, 0)
+        self.cncspindlespeed_entry = IntEntry(allow_empty=True)
+        grid1.addWidget(self.cncspindlespeed_entry, 9, 1)
+
+        # Dwell
+        self.dwell_cb = FCCheckBox(label='%s:' % _('Dwell'))
+        self.dwell_cb.setToolTip(
+            _("Pause to allow the spindle to reach its\n"
+              "speed before cutting.")
+        )
+        dwelltime = QtWidgets.QLabel('%s:' % _('Duration'))
+        dwelltime.setToolTip(
+            _("Number of time units for spindle to dwell.")
+        )
+        self.dwelltime_entry = FCEntry()
+        grid1.addWidget(self.dwell_cb, 10, 0)
+        grid1.addWidget(dwelltime, 11, 0)
+        grid1.addWidget(self.dwelltime_entry, 11, 1)
+
+        self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
+
+        # postprocessor selection
+        pp_label = QtWidgets.QLabel('%s:' % _("Postprocessor"))
+        pp_label.setToolTip(
+            _("The Postprocessor file that dictates\n"
+              "the Machine Code (like GCode, RML, HPGL) output.")
+        )
+        grid1.addWidget(pp_label, 12, 0)
+        self.pp_geometry_name_cb = FCComboBox()
+        self.pp_geometry_name_cb.setFocusPolicy(Qt.StrongFocus)
+        grid1.addWidget(self.pp_geometry_name_cb, 12, 1)
+
+        self.layout.addStretch()
+
+
+class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Geometry Advanced Options Preferences", parent=parent)
+        super(GeometryAdvOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Geometry Adv. Options")))
+
+        # ------------------------------
+        # ## Advanced Options
+        # ------------------------------
+        self.geo_label = QtWidgets.QLabel('<b>%s:</b>' % _('Advanced Options'))
+        self.geo_label.setToolTip(
+            _("A list of Geometry advanced parameters.\n"
+              "Those parameters are available only for\n"
+              "Advanced App. Level.")
+        )
+        self.layout.addWidget(self.geo_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        # Toolchange X,Y
+        toolchange_xy_label = QtWidgets.QLabel('%s:' % _('Toolchange X-Y'))
+        toolchange_xy_label.setToolTip(
+            _("Toolchange X,Y position.")
+        )
+        grid1.addWidget(toolchange_xy_label, 1, 0)
+        self.toolchangexy_entry = FCEntry()
+        grid1.addWidget(self.toolchangexy_entry, 1, 1)
+
+        # Start move Z
+        startzlabel = QtWidgets.QLabel('%s:' % _('Start move Z'))
+        startzlabel.setToolTip(
+            _("Height of the tool just after starting the work.\n"
+              "Delete the value if you don't need this feature.")
+        )
+        grid1.addWidget(startzlabel, 2, 0)
+        self.gstartz_entry = FloatEntry()
+        grid1.addWidget(self.gstartz_entry, 2, 1)
+
+        # Feedrate rapids
+        fr_rapid_label = QtWidgets.QLabel('%s:' % _('Feed Rate Rapids'))
+        fr_rapid_label.setToolTip(
+            _("Cutting speed in the XY plane\n"
+              "(in units per minute).\n"
+              "This is for the rapid move G00.\n"
+              "It is useful only for Marlin,\n"
+              "ignore for any other cases.")
+        )
+        grid1.addWidget(fr_rapid_label, 4, 0)
+        self.cncfeedrate_rapid_entry = LengthEntry()
+        grid1.addWidget(self.cncfeedrate_rapid_entry, 4, 1)
+
+        # End move extra cut
+        self.extracut_cb = FCCheckBox(label='%s' % _('Re-cut 1st pt.'))
+        self.extracut_cb.setToolTip(
+            _("In order to remove possible\n"
+              "copper leftovers where first cut\n"
+              "meet with last cut, we generate an\n"
+              "extended cut over the first cut section.")
+        )
+        grid1.addWidget(self.extracut_cb, 5, 0)
+
+        # Probe depth
+        self.pdepth_label = QtWidgets.QLabel('%s:' % _("Probe Z depth"))
+        self.pdepth_label.setToolTip(
+            _("The maximum depth that the probe is allowed\n"
+              "to probe. Negative value, in current units.")
+        )
+        grid1.addWidget(self.pdepth_label, 6, 0)
+        self.pdepth_entry = FCEntry()
+        grid1.addWidget(self.pdepth_entry, 6, 1)
+
+        # Probe feedrate
+        self.feedrate_probe_label = QtWidgets.QLabel('%s:' % _("Feedrate Probe"))
+        self.feedrate_probe_label.setToolTip(
+            _("The feedrate used while the probe is probing.")
+        )
+        grid1.addWidget(self.feedrate_probe_label, 7, 0)
+        self.feedrate_probe_entry = FCEntry()
+        grid1.addWidget(self.feedrate_probe_entry, 7, 1)
+
+        # Spindle direction
+        spindle_dir_label = QtWidgets.QLabel('%s:' % _('Spindle dir.'))
+        spindle_dir_label.setToolTip(
+            _("This sets the direction that the spindle is rotating.\n"
+              "It can be either:\n"
+              "- CW = clockwise or\n"
+              "- CCW = counter clockwise")
+        )
+
+        self.spindledir_radio = RadioSet([{'label': _('CW'), 'value': 'CW'},
+                                          {'label': _('CCW'), 'value': 'CCW'}])
+        grid1.addWidget(spindle_dir_label, 8, 0)
+        grid1.addWidget(self.spindledir_radio, 8, 1)
+
+        # Fast Move from Z Toolchange
+        fplungelabel = QtWidgets.QLabel('%s:' % _('Fast Plunge'))
+        fplungelabel.setToolTip(
+            _("By checking this, the vertical move from\n"
+              "Z_Toolchange to Z_move is done with G0,\n"
+              "meaning the fastest speed available.\n"
+              "WARNING: the move is done at Toolchange X,Y coords.")
+        )
+        self.fplunge_cb = FCCheckBox()
+        grid1.addWidget(fplungelabel, 9, 0)
+        grid1.addWidget(self.fplunge_cb, 9, 1)
+
+        # Size of trace segment on X axis
+        segx_label = QtWidgets.QLabel('%s:' % _("Seg. X size"))
+        segx_label.setToolTip(
+            _("The size of the trace segment on the X axis.\n"
+              "Useful for auto-leveling.\n"
+              "A value of 0 means no segmentation on the X axis.")
+        )
+        grid1.addWidget(segx_label, 10, 0)
+        self.segx_entry = FCEntry()
+        grid1.addWidget(self.segx_entry, 10, 1)
+
+        # Size of trace segment on Y axis
+        segy_label = QtWidgets.QLabel('%s:' % _("Seg. Y size"))
+        segy_label.setToolTip(
+            _("The size of the trace segment on the Y axis.\n"
+              "Useful for auto-leveling.\n"
+              "A value of 0 means no segmentation on the Y axis.")
+        )
+        grid1.addWidget(segy_label, 11, 0)
+        self.segy_entry = FCEntry()
+        grid1.addWidget(self.segy_entry, 11, 1)
+
+        self.layout.addStretch()
+
+
+class GeometryEditorPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber Adv. Options Preferences", parent=parent)
+        super(GeometryEditorPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Geometry Editor")))
+
+        # Advanced Geometry Parameters
+        self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.param_label.setToolTip(
+            _("A list of Geometry Editor parameters.")
+        )
+        self.layout.addWidget(self.param_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Selection Limit
+        self.sel_limit_label = QtWidgets.QLabel('%s:' % _("Selection limit"))
+        self.sel_limit_label.setToolTip(
+            _("Set the number of selected geometry\n"
+              "items above which the utility geometry\n"
+              "becomes just a selection rectangle.\n"
+              "Increases the performance when moving a\n"
+              "large number of geometric elements.")
+        )
+        self.sel_limit_entry = IntEntry()
+
+        grid0.addWidget(self.sel_limit_label, 0, 0)
+        grid0.addWidget(self.sel_limit_entry, 0, 1)
+
+        self.layout.addStretch()
+
+
+class CNCJobGenPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "CNC Job General Preferences", parent=None)
+        super(CNCJobGenPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("CNC Job General")))
+
+        # ## Plot options
+        self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
+        self.layout.addWidget(self.plot_options_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+        # grid0.setColumnStretch(1, 1)
+        # grid0.setColumnStretch(2, 1)
+
+        # Plot CB
+        # self.plot_cb = QtWidgets.QCheckBox('Plot')
+        self.plot_cb = FCCheckBox(_('Plot Object'))
+        self.plot_cb.setToolTip(_("Plot (show) this object."))
+        grid0.addWidget(self.plot_cb, 0, 0)
+
+        # Plot Kind
+        self.cncplot_method_label = QtWidgets.QLabel('%s:' % _("Plot kind"))
+        self.cncplot_method_label.setToolTip(
+            _("This selects the kind of geometries on the canvas to plot.\n"
+              "Those can be either of type 'Travel' which means the moves\n"
+              "above the work piece or it can be of type 'Cut',\n"
+              "which means the moves that cut into the material.")
+        )
+
+        self.cncplot_method_radio = RadioSet([
+            {"label": _("All"), "value": "all"},
+            {"label": _("Travel"), "value": "travel"},
+            {"label": _("Cut"), "value": "cut"}
+        ], stretch=False)
+
+        grid0.addWidget(self.cncplot_method_label, 1, 0)
+        grid0.addWidget(self.cncplot_method_radio, 1, 1)
+        grid0.addWidget(QtWidgets.QLabel(''), 1, 2)
+
+        # Display Annotation
+        self.annotation_label = QtWidgets.QLabel('%s:' % _("Display Annotation"))
+        self.annotation_label.setToolTip(
+            _("This selects if to display text annotation on the plot.\n"
+              "When checked it will display numbers in order for each end\n"
+              "of a travel line."
+              )
+        )
+        self.annotation_cb = FCCheckBox()
+
+        grid0.addWidget(self.annotation_label, 2, 0)
+        grid0.addWidget(self.annotation_cb, 2, 1)
+        grid0.addWidget(QtWidgets.QLabel(''), 2, 2)
+
+        # ###################################################################
+        # Number of circle steps for circular aperture linear approximation #
+        # ###################################################################
+        self.steps_per_circle_label = QtWidgets.QLabel('%s:' % _("Circle Steps"))
+        self.steps_per_circle_label.setToolTip(
+            _("The number of circle steps for <b>GCode</b> \n"
+              "circle and arc shapes linear approximation.")
+        )
+        grid0.addWidget(self.steps_per_circle_label, 3, 0)
+        self.steps_per_circle_entry = IntEntry()
+        grid0.addWidget(self.steps_per_circle_entry, 3, 1)
+
+        # Tool dia for plot
+        tdlabel = QtWidgets.QLabel('%s:' % _('Travel dia'))
+        tdlabel.setToolTip(
+            _("The width of the travel lines to be\n"
+              "rendered in the plot.")
+        )
+        self.tooldia_entry = LengthEntry()
+        grid0.addWidget(tdlabel, 4, 0)
+        grid0.addWidget(self.tooldia_entry, 4, 1)
+
+        # add a space
+        grid0.addWidget(QtWidgets.QLabel(''), 5, 0)
+
+        # Number of decimals to use in GCODE coordinates
+        cdeclabel = QtWidgets.QLabel('%s:' % _('Coordinates decimals'))
+        cdeclabel.setToolTip(
+            _("The number of decimals to be used for \n"
+              "the X, Y, Z coordinates in CNC code (GCODE, etc.)")
+        )
+        self.coords_dec_entry = IntEntry()
+        grid0.addWidget(cdeclabel, 6, 0)
+        grid0.addWidget(self.coords_dec_entry, 6, 1)
+
+        # Number of decimals to use in GCODE feedrate
+        frdeclabel = QtWidgets.QLabel('%s:' % _('Feedrate decimals'))
+        frdeclabel.setToolTip(
+            _("The number of decimals to be used for \n"
+              "the Feedrate parameter in CNC code (GCODE, etc.)")
+        )
+        self.fr_dec_entry = IntEntry()
+        grid0.addWidget(frdeclabel, 7, 0)
+        grid0.addWidget(self.fr_dec_entry, 7, 1)
+
+        # The type of coordinates used in the Gcode: Absolute or Incremental
+        coords_type_label = QtWidgets.QLabel('%s:' % _('Coordinates type'))
+        coords_type_label.setToolTip(
+            _("The type of coordinates to be used in Gcode.\n"
+              "Can be:\n"
+              "- Absolute G90 -> the reference is the origin x=0, y=0\n"
+              "- Incremental G91 -> the reference is the previous position")
+        )
+        self.coords_type_radio = RadioSet([
+            {"label": _("Absolute G90"), "value": "G90"},
+            {"label": _("Incremental G91"), "value": "G91"}
+        ], orientation='vertical', stretch=False)
+        grid0.addWidget(coords_type_label, 8, 0)
+        grid0.addWidget(self.coords_type_radio, 8, 1)
+
+        # hidden for the time being, until implemented
+        coords_type_label.hide()
+        self.coords_type_radio.hide()
+
+        self.layout.addStretch()
+
+
+class CNCJobOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "CNC Job Options Preferences", parent=None)
+        super(CNCJobOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("CNC Job Options")))
+
+        # ## Export G-Code
+        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export G-Code"))
+        self.export_gcode_label.setToolTip(
+            _("Export and save G-Code to\n"
+              "make this object to a file.")
+        )
+        self.layout.addWidget(self.export_gcode_label)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            tb_fsize = settings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+
+        # Prepend to G-Code
+        prependlabel = QtWidgets.QLabel('%s:' % _('Prepend to G-Code'))
+        prependlabel.setToolTip(
+            _("Type here any G-Code commands you would\n"
+              "like to add at the beginning of the G-Code file.")
+        )
+        self.layout.addWidget(prependlabel)
+
+        self.prepend_text = FCTextArea()
+        self.layout.addWidget(self.prepend_text)
+        self.prepend_text.setFont(font)
+
+        # Append text to G-Code
+        appendlabel = QtWidgets.QLabel('%s:' % _('Append to G-Code'))
+        appendlabel.setToolTip(
+            _("Type here any G-Code commands you would\n"
+              "like to append to the generated file.\n"
+              "I.e.: M2 (End of program)")
+        )
+        self.layout.addWidget(appendlabel)
+
+        self.append_text = FCTextArea()
+        self.layout.addWidget(self.append_text)
+        self.append_text.setFont(font)
+
+        self.layout.addStretch()
+
+
+class CNCJobAdvOptPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "CNC Job Advanced Options Preferences", parent=None)
+        super(CNCJobAdvOptPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("CNC Job Adv. Options")))
+
+        # ## Export G-Code
+        self.export_gcode_label = QtWidgets.QLabel("<b>%s:</b>" % _("Export CNC Code"))
+        self.export_gcode_label.setToolTip(
+            _("Export and save G-Code to\n"
+              "make this object to a file.")
+        )
+        self.layout.addWidget(self.export_gcode_label)
+
+        # Prepend to G-Code
+        toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code'))
+        toolchangelabel.setToolTip(
+            _(
+                "Type here any G-Code commands you would\n"
+                "like to be executed when Toolchange event is encountered.\n"
+                "This will constitute a Custom Toolchange GCode,\n"
+                "or a Toolchange Macro.\n"
+                "The FlatCAM variables are surrounded by '%' symbol.\n\n"
+                "WARNING: it can be used only with a postprocessor file\n"
+                "that has 'toolchange_custom' in it's name and this is built\n"
+                "having as template the 'Toolchange Custom' posprocessor file."
+            )
+        )
+        self.layout.addWidget(toolchangelabel)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            tb_fsize = settings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+
+        self.toolchange_text = FCTextArea()
+        self.layout.addWidget(self.toolchange_text)
+        self.toolchange_text.setFont(font)
+
+        hlay = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay)
+
+        # Toolchange Replacement GCode
+        self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro'))
+        self.toolchange_cb.setToolTip(
+            _("Check this box if you want to use\n"
+              "a Custom Toolchange GCode (macro).")
+        )
+        hlay.addWidget(self.toolchange_cb)
+        hlay.addStretch()
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+
+        # Variable list
+        self.tc_variable_combo = FCComboBox()
+        self.tc_variable_combo.setToolTip(
+            _("A list of the FlatCAM variables that can be used\n"
+              "in the Toolchange event.\n"
+              "They have to be surrounded by the '%' symbol")
+        )
+        hlay1.addWidget(self.tc_variable_combo)
+
+        # Populate the Combo Box
+        variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange',
+                     'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime']
+        self.tc_variable_combo.addItems(variables)
+        self.tc_variable_combo.setItemData(0, _("FlatCAM CNC parameters"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(1, _("tool = tool number"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(2, _("tooldia = tool diameter"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(3, _("t_drills = for Excellon, total number of drills"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(4, _("x_toolchange = X coord for Toolchange"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(5, _("y_toolchange = Y coord for Toolchange"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(6, _("z_toolchange = Z coord for Toolchange"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(7, _("z_cut = Z depth for the cut"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(8, _("z_move = Z height for travel"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(9, _("z_depthpercut = the step value for multidepth cut"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(10, _("spindlesspeed = the value for the spindle speed"), Qt.ToolTipRole)
+        self.tc_variable_combo.setItemData(11,
+                                           _("dwelltime = time to dwell to allow the spindle to reach it's set RPM"),
+                                           Qt.ToolTipRole)
+
+        hlay1.addStretch()
+
+        # Insert Variable into the Toolchange G-Code Text Box
+        # self.tc_insert_buton = FCButton("Insert")
+        # self.tc_insert_buton.setToolTip(
+        #     "Insert the variable in the GCode Box\n"
+        #     "surrounded by the '%' symbol."
+        # )
+        # hlay1.addWidget(self.tc_insert_buton)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 1, 0, 1, 2)
+
+        # Annotation Font Size
+        self.annotation_fontsize_label = QtWidgets.QLabel('%s:' % _("Annotation Size"))
+        self.annotation_fontsize_label.setToolTip(
+            _("The font size of the annotation text. In pixels.")
+        )
+        grid0.addWidget(self.annotation_fontsize_label, 2, 0)
+        self.annotation_fontsize_sp = FCSpinner()
+        grid0.addWidget(self.annotation_fontsize_sp, 2, 1)
+        grid0.addWidget(QtWidgets.QLabel(''), 2, 2)
+
+        # Annotation Font Color
+        self.annotation_color_label = QtWidgets.QLabel('%s:' % _('Annotation Color'))
+        self.annotation_color_label.setToolTip(
+            _("Set the font color for the annotation texts.")
+        )
+        self.annotation_fontcolor_entry = FCEntry()
+        self.annotation_fontcolor_button = QtWidgets.QPushButton()
+        self.annotation_fontcolor_button.setFixedSize(15, 15)
+
+        self.form_box_child = QtWidgets.QHBoxLayout()
+        self.form_box_child.setContentsMargins(0, 0, 0, 0)
+        self.form_box_child.addWidget(self.annotation_fontcolor_entry)
+        self.form_box_child.addWidget(self.annotation_fontcolor_button, alignment=Qt.AlignRight)
+        self.form_box_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        color_widget = QtWidgets.QWidget()
+        color_widget.setLayout(self.form_box_child)
+        grid0.addWidget(self.annotation_color_label, 3, 0)
+        grid0.addWidget(color_widget, 3, 1)
+        grid0.addWidget(QtWidgets.QLabel(''), 3, 2)
+
+        self.layout.addStretch()
+
+
+class ToolsNCCPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "NCC Tool Options", parent=parent)
+        super(ToolsNCCPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("NCC Tool Options")))
+
+        # ## Clear non-copper regions
+        self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.clearcopper_label.setToolTip(
+            _("Create a Geometry object with\n"
+              "toolpaths to cut all non-copper regions.")
+        )
+        self.layout.addWidget(self.clearcopper_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        ncctdlabel = QtWidgets.QLabel('%s:' % _('Tools dia'))
+        ncctdlabel.setToolTip(
+            _("Diameters of the cutting tools, separated by ','")
+        )
+        grid0.addWidget(ncctdlabel, 0, 0)
+        self.ncc_tool_dia_entry = FCEntry()
+        grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
+
+        # Tool Type Radio Button
+        self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
+        self.tool_type_label.setToolTip(
+            _("Default tool type:\n"
+              "- 'V-shape'\n"
+              "- Circular")
+        )
+
+        self.tool_type_radio = RadioSet([{'label': _('V-shape'), 'value': 'V'},
+                                         {'label': _('Circular'), 'value': 'C1'}])
+        self.tool_type_radio.setToolTip(
+            _("Default tool type:\n"
+              "- 'V-shape'\n"
+              "- Circular")
+        )
+
+        grid0.addWidget(self.tool_type_label, 1, 0)
+        grid0.addWidget(self.tool_type_radio, 1, 1)
+
+        # Tip Dia
+        self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
+        self.tipdialabel.setToolTip(
+            _("The tip diameter for V-Shape Tool"))
+        self.tipdia_entry = LengthEntry()
+
+        grid0.addWidget(self.tipdialabel, 2, 0)
+        grid0.addWidget(self.tipdia_entry, 2, 1)
+
+        # Tip Angle
+        self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
+        self.tipanglelabel.setToolTip(
+            _("The tip angle for V-Shape Tool.\n"
+              "In degree."))
+        self.tipangle_entry = LengthEntry()
+
+        grid0.addWidget(self.tipanglelabel, 3, 0)
+        grid0.addWidget(self.tipangle_entry, 3, 1)
+
+        # Milling Type Radio Button
+        self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
+        self.milling_type_label.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
+                                            {'label': _('Conv.'), 'value': 'cv'}])
+        self.milling_type_radio.setToolTip(
+            _("Milling type when the selected tool is of type: 'iso_op':\n"
+              "- climb / best for precision milling and to reduce tool usage\n"
+              "- conventional / useful when there is no backlash compensation")
+        )
+
+        grid0.addWidget(self.milling_type_label, 4, 0)
+        grid0.addWidget(self.milling_type_radio, 4, 1)
+
+        # Tool order Radio Button
+        self.ncc_order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
+        self.ncc_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                          "'No' --> means that the used order is the one in the tool table\n"
+                                          "'Forward' --> means that the tools will be ordered from small to big\n"
+                                          "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                          "WARNING: using rest machining will automatically set the order\n"
+                                          "in reverse and disable this control."))
+
+        self.ncc_order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
+                                         {'label': _('Forward'), 'value': 'fwd'},
+                                         {'label': _('Reverse'), 'value': 'rev'}])
+        self.ncc_order_radio.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                          "'No' --> means that the used order is the one in the tool table\n"
+                                          "'Forward' --> means that the tools will be ordered from small to big\n"
+                                          "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                          "WARNING: using rest machining will automatically set the order\n"
+                                          "in reverse and disable this control."))
+        grid0.addWidget(self.ncc_order_label, 5, 0)
+        grid0.addWidget(self.ncc_order_radio, 5, 1)
+
+        # Cut Z entry
+        cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
+        cutzlabel.setToolTip(
+           _("Depth of cut into material. Negative value.\n"
+             "In FlatCAM units.")
+        )
+        self.cutz_entry = FloatEntry()
+        self.cutz_entry.setToolTip(
+           _("Depth of cut into material. Negative value.\n"
+             "In FlatCAM units.")
+        )
+
+        grid0.addWidget(cutzlabel, 6, 0)
+        grid0.addWidget(self.cutz_entry, 6, 1)
+
+        # Overlap Entry
+        nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
+        nccoverlabel.setToolTip(
+           _("How much (fraction) of the tool width to overlap each tool pass.\n"
+             "Example:\n"
+             "A value here of 0.25 means 25%% from the tool diameter found above.\n\n"
+             "Adjust the value starting with lower values\n"
+             "and increasing it if areas that should be cleared are still \n"
+             "not cleared.\n"
+             "Lower values = faster processing, faster execution on PCB.\n"
+             "Higher values = slow processing and slow execution on CNC\n"
+             "due of too many paths.")
+        )
+        self.ncc_overlap_entry = FCDoubleSpinner()
+        self.ncc_overlap_entry.set_precision(3)
+        self.ncc_overlap_entry.setWrapping(True)
+        self.ncc_overlap_entry.setRange(0.000, 0.999)
+        self.ncc_overlap_entry.setSingleStep(0.1)
+        grid0.addWidget(nccoverlabel, 7, 0)
+        grid0.addWidget(self.ncc_overlap_entry, 7, 1)
+
+        # Margin entry
+        nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        nccmarginlabel.setToolTip(
+            _("Bounding box margin.")
+        )
+        grid0.addWidget(nccmarginlabel, 8, 0)
+        self.ncc_margin_entry = FloatEntry()
+        grid0.addWidget(self.ncc_margin_entry, 8, 1)
+
+        # Method
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
+        methodlabel.setToolTip(
+            _("Algorithm for non-copper clearing:<BR>"
+              "<B>Standard</B>: Fixed step inwards.<BR>"
+              "<B>Seed-based</B>: Outwards from seed.<BR>"
+              "<B>Line-based</B>: Parallel lines.")
+        )
+        grid0.addWidget(methodlabel, 9, 0)
+        self.ncc_method_radio = RadioSet([
+            {"label": _("Standard"), "value": "standard"},
+            {"label": _("Seed-based"), "value": "seed"},
+            {"label": _("Straight lines"), "value": "lines"}
+        ], orientation='vertical', stretch=False)
+        grid0.addWidget(self.ncc_method_radio, 9, 1)
+
+        # Connect lines
+        pathconnectlabel = QtWidgets.QLabel('%s:' % _("Connect"))
+        pathconnectlabel.setToolTip(
+            _("Draw lines between resulting\n"
+              "segments to minimize tool lifts.")
+        )
+        grid0.addWidget(pathconnectlabel, 10, 0)
+        self.ncc_connect_cb = FCCheckBox()
+        grid0.addWidget(self.ncc_connect_cb, 10, 1)
+
+        # Contour Checkbox
+        contourlabel = QtWidgets.QLabel('%s:' % _("Contour"))
+        contourlabel.setToolTip(
+           _("Cut around the perimeter of the polygon\n"
+             "to trim rough edges.")
+        )
+        grid0.addWidget(contourlabel, 11, 0)
+        self.ncc_contour_cb = FCCheckBox()
+        grid0.addWidget(self.ncc_contour_cb, 11, 1)
+
+        # Rest machining CheckBox
+        restlabel = QtWidgets.QLabel('%s:' % _("Rest M."))
+        restlabel.setToolTip(
+            _("If checked, use 'rest machining'.\n"
+              "Basically it will clear copper outside PCB features,\n"
+              "using the biggest tool and continue with the next tools,\n"
+              "from bigger to smaller, to clear areas of copper that\n"
+              "could not be cleared by previous tool, until there is\n"
+              "no more copper to clear or there are no more tools.\n"
+              "If not checked, use the standard algorithm.")
+        )
+        grid0.addWidget(restlabel, 12, 0)
+        self.ncc_rest_cb = FCCheckBox()
+        grid0.addWidget(self.ncc_rest_cb, 12, 1)
+
+        # ## NCC Offset choice
+        self.ncc_offset_choice_label = QtWidgets.QLabel('%s:' % _("Offset"))
+        self.ncc_offset_choice_label.setToolTip(
+            _("If used, it will add an offset to the copper features.\n"
+              "The copper clearing will finish to a distance\n"
+              "from the copper features.\n"
+              "The value can be between 0 and 10 FlatCAM units.")
+        )
+        grid0.addWidget(self.ncc_offset_choice_label, 13, 0)
+        self.ncc_choice_offset_cb = FCCheckBox()
+        grid0.addWidget(self.ncc_choice_offset_cb, 13, 1)
+
+        # ## NCC Offset value
+        self.ncc_offset_label = QtWidgets.QLabel('%s:' % _("Offset value"))
+        self.ncc_offset_label.setToolTip(
+            _("If used, it will add an offset to the copper features.\n"
+              "The copper clearing will finish to a distance\n"
+              "from the copper features.\n"
+              "The value can be between 0 and 10 FlatCAM units.")
+        )
+        grid0.addWidget(self.ncc_offset_label, 14, 0)
+        self.ncc_offset_spinner = FCDoubleSpinner()
+        self.ncc_offset_spinner.set_range(0.00, 10.00)
+        self.ncc_offset_spinner.set_precision(4)
+        self.ncc_offset_spinner.setWrapping(True)
+        self.ncc_offset_spinner.setSingleStep(0.1)
+
+        grid0.addWidget(self.ncc_offset_spinner, 14, 1)
+
+        # ## Reference
+        self.reference_radio = RadioSet([{'label': _('Itself'), 'value': 'itself'},
+                                         {"label": _("Area"), "value": "area"},
+                                         {'label': _('Ref'), 'value': 'box'}])
+        reference_label = QtWidgets.QLabel('%s:' % _("Reference"))
+        reference_label.setToolTip(
+            _("- 'Itself' -  the non copper clearing extent\n"
+              "is based on the object that is copper cleared.\n "
+              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
+              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
+              "- 'Reference Object' -  will do non copper clearing within the area\n"
+              "specified by another object.")
+        )
+        grid0.addWidget(reference_label, 15, 0)
+        grid0.addWidget(self.reference_radio, 15, 1)
+
+        # ## Plotting type
+        self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
+                                            {"label": _("Progressive"), "value": "progressive"}])
+        plotting_label = QtWidgets.QLabel('%s:' % _("NCC Plotting"))
+        plotting_label.setToolTip(
+            _("- 'Normal' -  normal plotting, done at the end of the NCC job\n"
+              "- 'Progressive' - after each shape is generated it will be plotted.")
+        )
+        grid0.addWidget(plotting_label, 16, 0)
+        grid0.addWidget(self.ncc_plotting_radio, 16, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsCutoutPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Cutout Tool Options", parent=parent)
+        super(ToolsCutoutPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Cutout Tool Options")))
+
+        # ## Board cuttout
+        self.board_cutout_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.board_cutout_label.setToolTip(
+            _("Create toolpaths to cut around\n"
+              "the PCB and separate it from\n"
+              "the original board.")
+        )
+        self.layout.addWidget(self.board_cutout_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        tdclabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
+        tdclabel.setToolTip(
+            _("Diameter of the tool used to cutout\n"
+              "the PCB shape out of the surrounding material.")
+        )
+        grid0.addWidget(tdclabel, 0, 0)
+        self.cutout_tooldia_entry = LengthEntry()
+        grid0.addWidget(self.cutout_tooldia_entry, 0, 1)
+
+        # Object kind
+        kindlabel = QtWidgets.QLabel('%s:' % _('Obj kind'))
+        kindlabel.setToolTip(
+            _("Choice of what kind the object we want to cutout is.<BR>"
+              "- <B>Single</B>: contain a single PCB Gerber outline object.<BR>"
+              "- <B>Panel</B>: a panel PCB Gerber object, which is made\n"
+              "out of many individual PCB outlines.")
+        )
+        grid0.addWidget(kindlabel, 1, 0)
+        self.obj_kind_combo = RadioSet([
+            {"label": _("Single"), "value": "single"},
+            {"label": _("Panel"), "value": "panel"},
+        ])
+        grid0.addWidget(self.obj_kind_combo, 1, 1)
+
+        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        marginlabel.setToolTip(
+            _("Margin over bounds. A positive value here\n"
+              "will make the cutout of the PCB further from\n"
+              "the actual PCB border")
+        )
+        grid0.addWidget(marginlabel, 2, 0)
+        self.cutout_margin_entry = LengthEntry()
+        grid0.addWidget(self.cutout_margin_entry, 2, 1)
+
+        gaplabel = QtWidgets.QLabel('%s:' % _('Gap size'))
+        gaplabel.setToolTip(
+            _("The size of the bridge gaps in the cutout\n"
+              "used to keep the board connected to\n"
+              "the surrounding material (the one \n"
+              "from which the PCB is cutout).")
+        )
+        grid0.addWidget(gaplabel, 3, 0)
+        self.cutout_gap_entry = LengthEntry()
+        grid0.addWidget(self.cutout_gap_entry, 3, 1)
+
+        gaps_label = QtWidgets.QLabel('%s:' % _('Gaps'))
+        gaps_label.setToolTip(
+            _("Number of gaps used for the cutout.\n"
+              "There can be maximum 8 bridges/gaps.\n"
+              "The choices are:\n"
+              "- None  - no gaps\n"
+              "- lr    - left + right\n"
+              "- tb    - top + bottom\n"
+              "- 4     - left + right +top + bottom\n"
+              "- 2lr   - 2*left + 2*right\n"
+              "- 2tb  - 2*top + 2*bottom\n"
+              "- 8     - 2*left + 2*right +2*top + 2*bottom")
+        )
+        grid0.addWidget(gaps_label, 4, 0)
+        self.gaps_combo = FCComboBox()
+        grid0.addWidget(self.gaps_combo, 4, 1)
+
+        gaps_items = ['None', 'LR', 'TB', '4', '2LR', '2TB', '8']
+        for it in gaps_items:
+            self.gaps_combo.addItem(it)
+            self.gaps_combo.setStyleSheet('background-color: rgb(255,255,255)')
+
+        # Surrounding convex box shape
+        self.convex_box = FCCheckBox()
+        self.convex_box_label = QtWidgets.QLabel('%s:' % _("Convex Sh."))
+        self.convex_box_label.setToolTip(
+            _("Create a convex shape surrounding the entire PCB.\n"
+              "Used only if the source object type is Gerber.")
+        )
+        grid0.addWidget(self.convex_box_label, 5, 0)
+        grid0.addWidget(self.convex_box, 5, 1)
+
+        self.layout.addStretch()
+
+
+class Tools2sidedPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "2sided Tool Options", parent=parent)
+        super(Tools2sidedPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("2Sided Tool Options")))
+
+        # ## Board cuttout
+        self.dblsided_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.dblsided_label.setToolTip(
+            _("A tool to help in creating a double sided\n"
+              "PCB using alignment holes.")
+        )
+        self.layout.addWidget(self.dblsided_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # ## Drill diameter for alignment holes
+        self.drill_dia_entry = LengthEntry()
+        self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
+        self.dd_label.setToolTip(
+            _("Diameter of the drill for the "
+              "alignment holes.")
+        )
+        grid0.addWidget(self.dd_label, 0, 0)
+        grid0.addWidget(self.drill_dia_entry, 0, 1)
+
+        # ## Axis
+        self.mirror_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
+                                           {'label': 'Y', 'value': 'Y'}])
+        self.mirax_label = QtWidgets.QLabel(_("Mirror Axis:"))
+        self.mirax_label.setToolTip(
+            _("Mirror vertically (X) or horizontally (Y).")
+        )
+        # grid_lay.addRow("Mirror Axis:", self.mirror_axis)
+        self.empty_lb1 = QtWidgets.QLabel("")
+        grid0.addWidget(self.empty_lb1, 1, 0)
+        grid0.addWidget(self.mirax_label, 2, 0)
+        grid0.addWidget(self.mirror_axis_radio, 2, 1)
+
+        # ## Axis Location
+        self.axis_location_radio = RadioSet([{'label': _('Point'), 'value': 'point'},
+                                             {'label': _('Box'), 'value': 'box'}])
+        self.axloc_label = QtWidgets.QLabel('%s:' % _("Axis Ref"))
+        self.axloc_label.setToolTip(
+            _("The axis should pass through a <b>point</b> or cut\n "
+              "a specified <b>box</b> (in a FlatCAM object) through \n"
+              "the center.")
+        )
+        # grid_lay.addRow("Axis Location:", self.axis_location)
+        grid0.addWidget(self.axloc_label, 3, 0)
+        grid0.addWidget(self.axis_location_radio, 3, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsPaintPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Paint Area Tool Options", parent=parent)
+        super(ToolsPaintPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Paint Tool Options")))
+
+        # ------------------------------
+        # ## Paint area
+        # ------------------------------
+        self.paint_label = QtWidgets.QLabel(_('<b>Parameters:</b>'))
+        self.paint_label.setToolTip(
+            _("Creates tool paths to cover the\n"
+              "whole area of a polygon (remove\n"
+              "all copper). You will be asked\n"
+              "to click on the desired polygon.")
+        )
+        self.layout.addWidget(self.paint_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Tool dia
+        ptdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
+        ptdlabel.setToolTip(
+            _("Diameter of the tool to\n"
+              "be used in the operation.")
+        )
+        grid0.addWidget(ptdlabel, 0, 0)
+
+        self.painttooldia_entry = LengthEntry()
+        grid0.addWidget(self.painttooldia_entry, 0, 1)
+
+        self.paint_order_label = QtWidgets.QLabel('<b>%s:</b>' % _('Tool order'))
+        self.paint_order_label.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                            "'No' --> means that the used order is the one in the tool table\n"
+                                            "'Forward' --> means that the tools will be ordered from small to big\n"
+                                            "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                            "WARNING: using rest machining will automatically set the order\n"
+                                            "in reverse and disable this control."))
+
+        self.paint_order_radio = RadioSet([{'label': _('No'), 'value': 'no'},
+                                           {'label': _('Forward'), 'value': 'fwd'},
+                                           {'label': _('Reverse'), 'value': 'rev'}])
+        self.paint_order_radio.setToolTip(_("This set the way that the tools in the tools table are used.\n"
+                                            "'No' --> means that the used order is the one in the tool table\n"
+                                            "'Forward' --> means that the tools will be ordered from small to big\n"
+                                            "'Reverse' --> menas that the tools will ordered from big to small\n\n"
+                                            "WARNING: using rest machining will automatically set the order\n"
+                                            "in reverse and disable this control."))
+        grid0.addWidget(self.paint_order_label, 1, 0)
+        grid0.addWidget(self.paint_order_radio, 1, 1)
+
+        # Overlap
+        ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
+        ovlabel.setToolTip(
+            _("How much (fraction) of the tool width to overlap each tool pass.\n"
+              "Example:\n"
+              "A value here of 0.25 means 25%% from the tool diameter found above.\n\n"
+              "Adjust the value starting with lower values\n"
+              "and increasing it if areas that should be painted are still \n"
+              "not painted.\n"
+              "Lower values = faster processing, faster execution on PCB.\n"
+              "Higher values = slow processing and slow execution on CNC\n"
+              "due of too many paths.")
+        )
+        self.paintoverlap_entry = FCDoubleSpinner()
+        self.paintoverlap_entry.set_precision(3)
+        self.paintoverlap_entry.setWrapping(True)
+        self.paintoverlap_entry.setRange(0.000, 0.999)
+        self.paintoverlap_entry.setSingleStep(0.1)
+        grid0.addWidget(ovlabel, 2, 0)
+        grid0.addWidget(self.paintoverlap_entry, 2, 1)
+
+        # Margin
+        marginlabel = QtWidgets.QLabel('%s:' % _('Margin'))
+        marginlabel.setToolTip(
+            _("Distance by which to avoid\n"
+              "the edges of the polygon to\n"
+              "be painted.")
+        )
+        grid0.addWidget(marginlabel, 3, 0)
+        self.paintmargin_entry = LengthEntry()
+        grid0.addWidget(self.paintmargin_entry, 3, 1)
+
+        # Method
+        methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
+        methodlabel.setToolTip(
+            _("Algorithm for non-copper clearing:<BR>"
+              "<B>Standard</B>: Fixed step inwards.<BR>"
+              "<B>Seed-based</B>: Outwards from seed.<BR>"
+              "<B>Line-based</B>: Parallel lines.")
+        )
+        grid0.addWidget(methodlabel, 4, 0)
+        self.paintmethod_combo = RadioSet([
+            {"label": _("Standard"), "value": "standard"},
+            {"label": _("Seed-based"), "value": "seed"},
+            {"label": _("Straight lines"), "value": "lines"}
+        ], orientation='vertical', stretch=False)
+        grid0.addWidget(self.paintmethod_combo, 4, 1)
+
+        # Connect lines
+        pathconnectlabel = QtWidgets.QLabel('%s:' % _("Connect"))
+        pathconnectlabel.setToolTip(
+            _("Draw lines between resulting\n"
+              "segments to minimize tool lifts.")
+        )
+        grid0.addWidget(pathconnectlabel, 5, 0)
+        self.pathconnect_cb = FCCheckBox()
+        grid0.addWidget(self.pathconnect_cb, 5, 1)
+
+        # Paint contour
+        contourlabel = QtWidgets.QLabel('%s:' % _("Contour"))
+        contourlabel.setToolTip(
+            _("Cut around the perimeter of the polygon\n"
+              "to trim rough edges.")
+        )
+        grid0.addWidget(contourlabel, 6, 0)
+        self.contour_cb = FCCheckBox()
+        grid0.addWidget(self.contour_cb, 6, 1)
+
+        # Polygon selection
+        selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
+        selectlabel.setToolTip(
+            _("How to select Polygons to be painted.\n\n"
+              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
+              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
+              "- 'All Polygons' - the Paint will start after click.\n"
+              "- 'Reference Object' -  will do non copper clearing within the area\n"
+              "specified by another object.")
+        )
+        self.selectmethod_combo = RadioSet([
+            {"label": _("Single"), "value": "single"},
+            {"label": _("Area"), "value": "area"},
+            {"label": _("All"), "value": "all"},
+            {"label": _("Ref."), "value": "ref"}
+        ])
+        grid0.addWidget(selectlabel, 7, 0)
+        grid0.addWidget(self.selectmethod_combo, 7, 1)
+
+        # ## Plotting type
+        self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
+                                              {"label": _("Progressive"), "value": "progressive"}])
+        plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
+        plotting_label.setToolTip(
+            _("- 'Normal' -  normal plotting, done at the end of the Paint job\n"
+              "- 'Progressive' - after each shape is generated it will be plotted.")
+        )
+        grid0.addWidget(plotting_label, 8, 0)
+        grid0.addWidget(self.paint_plotting_radio, 8, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsFilmPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Cutout Tool Options", parent=parent)
+        super(ToolsFilmPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Film Tool Options")))
+
+        # ## Board cuttout
+        self.film_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.film_label.setToolTip(
+            _("Create a PCB film from a Gerber or Geometry\n"
+              "FlatCAM object.\n"
+              "The file is saved in SVG format.")
+        )
+        self.layout.addWidget(self.film_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        self.film_type_radio = RadioSet([{'label': 'Pos', 'value': 'pos'},
+                                         {'label': 'Neg', 'value': 'neg'}])
+        ftypelbl = QtWidgets.QLabel('%s:' % _('Film Type'))
+        ftypelbl.setToolTip(
+            _("Generate a Positive black film or a Negative film.\n"
+              "Positive means that it will print the features\n"
+              "with black on a white canvas.\n"
+              "Negative means that it will print the features\n"
+              "with white on a black canvas.\n"
+              "The Film format is SVG.")
+        )
+        grid0.addWidget(ftypelbl, 0, 0)
+        grid0.addWidget(self.film_type_radio, 0, 1)
+
+        # Film Color
+        self.film_color_label = QtWidgets.QLabel('%s:' % _('Film Color'))
+        self.film_color_label.setToolTip(
+            _("Set the film color when positive film is selected.")
+        )
+        self.film_color_entry = FCEntry()
+        self.film_color_button = QtWidgets.QPushButton()
+        self.film_color_button.setFixedSize(15, 15)
+
+        self.form_box_child = QtWidgets.QHBoxLayout()
+        self.form_box_child.setContentsMargins(0, 0, 0, 0)
+        self.form_box_child.addWidget(self.film_color_entry)
+        self.form_box_child.addWidget(self.film_color_button, alignment=Qt.AlignRight)
+        self.form_box_child.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+        film_color_widget = QtWidgets.QWidget()
+        film_color_widget.setLayout(self.form_box_child)
+        grid0.addWidget(self.film_color_label, 1, 0)
+        grid0.addWidget(film_color_widget, 1, 1)
+
+        self.film_boundary_entry = FCEntry()
+        self.film_boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
+        self.film_boundary_label.setToolTip(
+            _("Specify a border around the object.\n"
+              "Only for negative film.\n"
+              "It helps if we use as a Box Object the same \n"
+              "object as in Film Object. It will create a thick\n"
+              "black bar around the actual print allowing for a\n"
+              "better delimitation of the outline features which are of\n"
+              "white color like the rest and which may confound with the\n"
+              "surroundings if not for this border.")
+        )
+        grid0.addWidget(self.film_boundary_label, 2, 0)
+        grid0.addWidget(self.film_boundary_entry, 2, 1)
+
+        self.film_scale_entry = FCEntry()
+        self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
+        self.film_scale_label.setToolTip(
+            _("Scale the line stroke thickness of each feature in the SVG file.\n"
+              "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
+              "therefore the fine features may be more affected by this parameter.")
+        )
+        grid0.addWidget(self.film_scale_label, 3, 0)
+        grid0.addWidget(self.film_scale_entry, 3, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsPanelizePrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Cutout Tool Options", parent=parent)
+        super(ToolsPanelizePrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Panelize Tool Options")))
+
+        # ## Board cuttout
+        self.panelize_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.panelize_label.setToolTip(
+            _("Create an object that contains an array of (x, y) elements,\n"
+              "each element is a copy of the source object spaced\n"
+              "at a X distance, Y distance of each other.")
+        )
+        self.layout.addWidget(self.panelize_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # ## Spacing Columns
+        self.pspacing_columns = FCEntry()
+        self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
+        self.spacing_columns_label.setToolTip(
+            _("Spacing between columns of the desired panel.\n"
+              "In current units.")
+        )
+        grid0.addWidget(self.spacing_columns_label, 0, 0)
+        grid0.addWidget(self.pspacing_columns, 0, 1)
+
+        # ## Spacing Rows
+        self.pspacing_rows = FCEntry()
+        self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
+        self.spacing_rows_label.setToolTip(
+            _("Spacing between rows of the desired panel.\n"
+              "In current units.")
+        )
+        grid0.addWidget(self.spacing_rows_label, 1, 0)
+        grid0.addWidget(self.pspacing_rows, 1, 1)
+
+        # ## Columns
+        self.pcolumns = FCEntry()
+        self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
+        self.columns_label.setToolTip(
+            _("Number of columns of the desired panel")
+        )
+        grid0.addWidget(self.columns_label, 2, 0)
+        grid0.addWidget(self.pcolumns, 2, 1)
+
+        # ## Rows
+        self.prows = FCEntry()
+        self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
+        self.rows_label.setToolTip(
+            _("Number of rows of the desired panel")
+        )
+        grid0.addWidget(self.rows_label, 3, 0)
+        grid0.addWidget(self.prows, 3, 1)
+
+        # ## Type of resulting Panel object
+        self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
+                                          {'label': _('Geo'), 'value': 'geometry'}])
+        self.panel_type_label = QtWidgets.QLabel('%s:' % _("Panel Type"))
+        self.panel_type_label.setToolTip(
+           _("Choose the type of object for the panel object:\n"
+             "- Gerber\n"
+             "- Geometry")
+        )
+
+        grid0.addWidget(self.panel_type_label, 4, 0)
+        grid0.addWidget(self.panel_type_radio, 4, 1)
+
+        # ## Constrains
+        self.pconstrain_cb = FCCheckBox('%s:' % _("Constrain within"))
+        self.pconstrain_cb.setToolTip(
+            _("Area define by DX and DY within to constrain the panel.\n"
+              "DX and DY values are in current units.\n"
+              "Regardless of how many columns and rows are desired,\n"
+              "the final panel will have as many columns and rows as\n"
+              "they fit completely within selected area.")
+        )
+        grid0.addWidget(self.pconstrain_cb, 5, 0)
+
+        self.px_width_entry = FCEntry()
+        self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
+        self.x_width_lbl.setToolTip(
+            _("The width (DX) within which the panel must fit.\n"
+              "In current units.")
+        )
+        grid0.addWidget(self.x_width_lbl, 6, 0)
+        grid0.addWidget(self.px_width_entry, 6, 1)
+
+        self.py_height_entry = FCEntry()
+        self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
+        self.y_height_lbl.setToolTip(
+            _("The height (DY)within which the panel must fit.\n"
+              "In current units.")
+        )
+        grid0.addWidget(self.y_height_lbl, 7, 0)
+        grid0.addWidget(self.py_height_entry, 7, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsCalculatorsPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent)
+        super(ToolsCalculatorsPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Calculators Tool Options")))
+
+        # ## V-shape Calculator Tool
+        self.vshape_tool_label = QtWidgets.QLabel("<b>%s:</b>" % _("V-Shape Tool Calculator"))
+        self.vshape_tool_label.setToolTip(
+            _("Calculate the tool diameter for a given V-shape tool,\n"
+              "having the tip diameter, tip angle and\n"
+              "depth-of-cut as parameters.")
+        )
+        self.layout.addWidget(self.vshape_tool_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # ## Tip Diameter
+        self.tip_dia_entry = FCEntry()
+        self.tip_dia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
+        self.tip_dia_label.setToolTip(
+            _("This is the tool tip diameter.\n"
+              "It is specified by manufacturer.")
+        )
+        grid0.addWidget(self.tip_dia_label, 0, 0)
+        grid0.addWidget(self.tip_dia_entry, 0, 1)
+
+        # ## Tip angle
+        self.tip_angle_entry = FCEntry()
+        self.tip_angle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
+        self.tip_angle_label.setToolTip(
+            _("This is the angle on the tip of the tool.\n"
+              "It is specified by manufacturer.")
+        )
+        grid0.addWidget(self.tip_angle_label, 1, 0)
+        grid0.addWidget(self.tip_angle_entry, 1, 1)
+
+        # ## Depth-of-cut Cut Z
+        self.cut_z_entry = FCEntry()
+        self.cut_z_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
+        self.cut_z_label.setToolTip(
+            _("This is depth to cut into material.\n"
+              "In the CNCJob object it is the CutZ parameter.")
+        )
+        grid0.addWidget(self.cut_z_label, 2, 0)
+        grid0.addWidget(self.cut_z_entry, 2, 1)
+
+        # ## Electroplating Calculator Tool
+        self.plate_title_label = QtWidgets.QLabel("<b>%s:</b>" % _("ElectroPlating Calculator"))
+        self.plate_title_label.setToolTip(
+            _("This calculator is useful for those who plate the via/pad/drill holes,\n"
+              "using a method like grahite ink or calcium hypophosphite ink or palladium chloride.")
+        )
+        self.layout.addWidget(self.plate_title_label)
+
+        grid1 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid1)
+
+        # ## PCB Length
+        self.pcblength_entry = FCEntry()
+        self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
+
+        self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
+        grid1.addWidget(self.pcblengthlabel, 0, 0)
+        grid1.addWidget(self.pcblength_entry, 0, 1)
+
+        # ## PCB Width
+        self.pcbwidth_entry = FCEntry()
+        self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
+
+        self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
+        grid1.addWidget(self.pcbwidthlabel, 1, 0)
+        grid1.addWidget(self.pcbwidth_entry, 1, 1)
+
+        # ## Current Density
+        self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
+        self.cdensity_entry = FCEntry()
+
+        self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
+                                         "In Amps per Square Feet ASF."))
+        grid1.addWidget(self.cdensity_label, 2, 0)
+        grid1.addWidget(self.cdensity_entry, 2, 1)
+
+        # ## PCB Copper Growth
+        self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
+        self.growth_entry = FCEntry()
+
+        self.growth_label.setToolTip(_("How thick the copper growth is intended to be.\n"
+                                       "In microns."))
+        grid1.addWidget(self.growth_label, 3, 0)
+        grid1.addWidget(self.growth_entry, 3, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsTransformPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+
+        super(ToolsTransformPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Transform Tool Options")))
+
+        # ## Transformations
+        self.transform_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.transform_label.setToolTip(
+            _("Various transformations that can be applied\n"
+              "on a FlatCAM object.")
+        )
+        self.layout.addWidget(self.transform_label)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # ## Rotate Angle
+        self.rotate_entry = FCEntry()
+        self.rotate_label = QtWidgets.QLabel('%s:' % _("Rotate Angle"))
+        self.rotate_label.setToolTip(
+            _("Angle for Rotation action, in degrees.\n"
+              "Float number between -360 and 359.\n"
+              "Positive numbers for CW motion.\n"
+              "Negative numbers for CCW motion.")
+        )
+        grid0.addWidget(self.rotate_label, 0, 0)
+        grid0.addWidget(self.rotate_entry, 0, 1)
+
+        # ## Skew/Shear Angle on X axis
+        self.skewx_entry = FCEntry()
+        self.skewx_label = QtWidgets.QLabel('%s:' % _("Skew_X angle"))
+        self.skewx_label.setToolTip(
+            _("Angle for Skew action, in degrees.\n"
+              "Float number between -360 and 359.")
+        )
+        grid0.addWidget(self.skewx_label, 1, 0)
+        grid0.addWidget(self.skewx_entry, 1, 1)
+
+        # ## Skew/Shear Angle on Y axis
+        self.skewy_entry = FCEntry()
+        self.skewy_label = QtWidgets.QLabel('%s:' % _("Skew_Y angle"))
+        self.skewy_label.setToolTip(
+            _("Angle for Skew action, in degrees.\n"
+              "Float number between -360 and 359.")
+        )
+        grid0.addWidget(self.skewy_label, 2, 0)
+        grid0.addWidget(self.skewy_entry, 2, 1)
+
+        # ## Scale factor on X axis
+        self.scalex_entry = FCEntry()
+        self.scalex_label = QtWidgets.QLabel('%s:' % _("Scale_X factor"))
+        self.scalex_label.setToolTip(
+            _("Factor for scaling on X axis.")
+        )
+        grid0.addWidget(self.scalex_label, 3, 0)
+        grid0.addWidget(self.scalex_entry, 3, 1)
+
+        # ## Scale factor on X axis
+        self.scaley_entry = FCEntry()
+        self.scaley_label = QtWidgets.QLabel('%s:' % _("Scale_Y factor"))
+        self.scaley_label.setToolTip(
+            _("Factor for scaling on Y axis.")
+        )
+        grid0.addWidget(self.scaley_label, 4, 0)
+        grid0.addWidget(self.scaley_entry, 4, 1)
+
+        # ## Link Scale factors
+        self.link_cb = FCCheckBox(_("Link"))
+        self.link_cb.setToolTip(
+            _("Scale the selected object(s)\n"
+              "using the Scale_X factor for both axis.")
+        )
+        grid0.addWidget(self.link_cb, 5, 0)
+
+        # ## Scale Reference
+        self.reference_cb = FCCheckBox('%s' % _("Scale Reference"))
+        self.reference_cb.setToolTip(
+            _("Scale the selected object(s)\n"
+              "using the origin reference when checked,\n"
+              "and the center of the biggest bounding box\n"
+              "of the selected objects when unchecked.")
+        )
+        grid0.addWidget(self.reference_cb, 5, 1)
+
+        # ## Offset distance on X axis
+        self.offx_entry = FCEntry()
+        self.offx_label = QtWidgets.QLabel('%s:' % _("Offset_X val"))
+        self.offx_label.setToolTip(
+           _("Distance to offset on X axis. In current units.")
+        )
+        grid0.addWidget(self.offx_label, 6, 0)
+        grid0.addWidget(self.offx_entry, 6, 1)
+
+        # ## Offset distance on Y axis
+        self.offy_entry = FCEntry()
+        self.offy_label = QtWidgets.QLabel('%s:' % _("Offset_Y val"))
+        self.offy_label.setToolTip(
+            _("Distance to offset on Y axis. In current units.")
+        )
+        grid0.addWidget(self.offy_label, 7, 0)
+        grid0.addWidget(self.offy_entry, 7, 1)
+
+        # ## Mirror (Flip) Reference Point
+        self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
+        self.mirror_reference_cb.setToolTip(
+            _("Flip the selected object(s)\n"
+              "around the point in Point Entry Field.\n"
+              "\n"
+              "The point coordinates can be captured by\n"
+              "left click on canvas together with pressing\n"
+              "SHIFT key. \n"
+              "Then click Add button to insert coordinates.\n"
+              "Or enter the coords in format (x, y) in the\n"
+              "Point Entry field and click Flip on X(Y)"))
+        grid0.addWidget(self.mirror_reference_cb, 8, 1)
+
+        self.flip_ref_label = QtWidgets.QLabel('%s:' % _(" Mirror Ref. Point"))
+        self.flip_ref_label.setToolTip(
+            _("Coordinates in format (x, y) used as reference for mirroring.\n"
+              "The 'x' in (x, y) will be used when using Flip on X and\n"
+              "the 'y' in (x, y) will be used when using Flip on Y and")
+        )
+        self.flip_ref_entry = EvalEntry2("(0, 0)")
+
+        grid0.addWidget(self.flip_ref_label, 9, 0)
+        grid0.addWidget(self.flip_ref_entry, 9, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+
+        super(ToolsSolderpastePrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("SolderPaste Tool Options")))
+
+        # ## Solder Paste Dispensing
+        self.solderpastelabel = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.solderpastelabel.setToolTip(
+            _("A tool to create GCode for dispensing\n"
+              "solder paste onto a PCB.")
+        )
+        self.layout.addWidget(self.solderpastelabel)
+
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+
+        # Nozzle Tool Diameters
+        nozzletdlabel = QtWidgets.QLabel('%s:' % _('Tools dia'))
+        nozzletdlabel.setToolTip(
+            _("Diameters of nozzle tools, separated by ','")
+        )
+        self.nozzle_tool_dia_entry = FCEntry()
+        grid0.addWidget(nozzletdlabel, 0, 0)
+        grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
+
+        # New Nozzle Tool Dia
+        self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('New Nozzle Dia'))
+        self.addtool_entry_lbl.setToolTip(
+            _("Diameter for the new Nozzle tool to add in the Tool Table")
+        )
+        self.addtool_entry = FCEntry()
+        grid0.addWidget(self.addtool_entry_lbl, 1, 0)
+        grid0.addWidget(self.addtool_entry, 1, 1)
+
+        # Z dispense start
+        self.z_start_entry = FCEntry()
+        self.z_start_label = QtWidgets.QLabel('%s:' % _("Z Dispense Start"))
+        self.z_start_label.setToolTip(
+            _("The height (Z) when solder paste dispensing starts.")
+        )
+        grid0.addWidget(self.z_start_label, 2, 0)
+        grid0.addWidget(self.z_start_entry, 2, 1)
+
+        # Z dispense
+        self.z_dispense_entry = FCEntry()
+        self.z_dispense_label = QtWidgets.QLabel('%s:' % _("Z Dispense"))
+        self.z_dispense_label.setToolTip(
+            _("The height (Z) when doing solder paste dispensing.")
+        )
+        grid0.addWidget(self.z_dispense_label, 3, 0)
+        grid0.addWidget(self.z_dispense_entry, 3, 1)
+
+        # Z dispense stop
+        self.z_stop_entry = FCEntry()
+        self.z_stop_label = QtWidgets.QLabel('%s:' % _("Z Dispense Stop"))
+        self.z_stop_label.setToolTip(
+            _("The height (Z) when solder paste dispensing stops.")
+        )
+        grid0.addWidget(self.z_stop_label, 4, 0)
+        grid0.addWidget(self.z_stop_entry, 4, 1)
+
+        # Z travel
+        self.z_travel_entry = FCEntry()
+        self.z_travel_label = QtWidgets.QLabel('%s:' % _("Z Travel"))
+        self.z_travel_label.setToolTip(
+            _("The height (Z) for travel between pads\n"
+              "(without dispensing solder paste).")
+        )
+        grid0.addWidget(self.z_travel_label, 5, 0)
+        grid0.addWidget(self.z_travel_entry, 5, 1)
+
+        # Z toolchange location
+        self.z_toolchange_entry = FCEntry()
+        self.z_toolchange_label = QtWidgets.QLabel('%s:' % _("Z Toolchange"))
+        self.z_toolchange_label.setToolTip(
+            _("The height (Z) for tool (nozzle) change.")
+        )
+        grid0.addWidget(self.z_toolchange_label, 6, 0)
+        grid0.addWidget(self.z_toolchange_entry, 6, 1)
+
+        # X,Y Toolchange location
+        self.xy_toolchange_entry = FCEntry()
+        self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
+        self.xy_toolchange_label.setToolTip(
+            _("The X,Y location for tool (nozzle) change.\n"
+              "The format is (x, y) where x and y are real numbers.")
+        )
+        grid0.addWidget(self.xy_toolchange_label, 7, 0)
+        grid0.addWidget(self.xy_toolchange_entry, 7, 1)
+
+        # Feedrate X-Y
+        self.frxy_entry = FCEntry()
+        self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y"))
+        self.frxy_label.setToolTip(
+            _("Feedrate (speed) while moving on the X-Y plane.")
+        )
+        grid0.addWidget(self.frxy_label, 8, 0)
+        grid0.addWidget(self.frxy_entry, 8, 1)
+
+        # Feedrate Z
+        self.frz_entry = FCEntry()
+        self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z"))
+        self.frz_label.setToolTip(
+            _("Feedrate (speed) while moving vertically\n"
+              "(on Z plane).")
+        )
+        grid0.addWidget(self.frz_label, 9, 0)
+        grid0.addWidget(self.frz_entry, 9, 1)
+
+        # Feedrate Z Dispense
+        self.frz_dispense_entry = FCEntry()
+        self.frz_dispense_label = QtWidgets.QLabel('%s:' % _("Feedrate Z Dispense"))
+        self.frz_dispense_label.setToolTip(
+            _("Feedrate (speed) while moving up vertically\n"
+              "to Dispense position (on Z plane).")
+        )
+        grid0.addWidget(self.frz_dispense_label, 10, 0)
+        grid0.addWidget(self.frz_dispense_entry, 10, 1)
+
+        # Spindle Speed Forward
+        self.speedfwd_entry = FCEntry()
+        self.speedfwd_label = QtWidgets.QLabel('%s:' % _("Spindle Speed FWD"))
+        self.speedfwd_label.setToolTip(
+            _("The dispenser speed while pushing solder paste\n"
+              "through the dispenser nozzle.")
+        )
+        grid0.addWidget(self.speedfwd_label, 11, 0)
+        grid0.addWidget(self.speedfwd_entry, 11, 1)
+
+        # Dwell Forward
+        self.dwellfwd_entry = FCEntry()
+        self.dwellfwd_label = QtWidgets.QLabel('%s:' % _("Dwell FWD"))
+        self.dwellfwd_label.setToolTip(
+            _("Pause after solder dispensing.")
+        )
+        grid0.addWidget(self.dwellfwd_label, 12, 0)
+        grid0.addWidget(self.dwellfwd_entry, 12, 1)
+
+        # Spindle Speed Reverse
+        self.speedrev_entry = FCEntry()
+        self.speedrev_label = QtWidgets.QLabel('%s:' % _("Spindle Speed REV"))
+        self.speedrev_label.setToolTip(
+            _("The dispenser speed while retracting solder paste\n"
+              "through the dispenser nozzle.")
+        )
+        grid0.addWidget(self.speedrev_label, 13, 0)
+        grid0.addWidget(self.speedrev_entry, 13, 1)
+
+        # Dwell Reverse
+        self.dwellrev_entry = FCEntry()
+        self.dwellrev_label = QtWidgets.QLabel('%s:' % _("Dwell REV"))
+        self.dwellrev_label.setToolTip(
+            _("Pause after solder paste dispenser retracted,\n"
+              "to allow pressure equilibrium.")
+        )
+        grid0.addWidget(self.dwellrev_label, 14, 0)
+        grid0.addWidget(self.dwellrev_entry, 14, 1)
+
+        # Postprocessors
+        pp_label = QtWidgets.QLabel('%s:' % _('PostProcessor'))
+        pp_label.setToolTip(
+            _("Files that control the GCode generation.")
+        )
+
+        self.pp_combo = FCComboBox()
+        grid0.addWidget(pp_label, 15, 0)
+        grid0.addWidget(self.pp_combo, 15, 1)
+
+        self.layout.addStretch()
+
+
+class ToolsSubPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+
+        super(ToolsSubPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Substractor Tool Options")))
+
+        # ## Solder Paste Dispensing
+        self.sublabel = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
+        self.sublabel.setToolTip(
+            _("A tool to substract one Gerber or Geometry object\n"
+              "from another of the same type.")
+        )
+        self.layout.addWidget(self.sublabel)
+
+        self.close_paths_cb = FCCheckBox(_("Close paths"))
+        self.close_paths_cb.setToolTip(_("Checking this will close the paths cut by the Geometry substractor object."))
+        self.layout.addWidget(self.close_paths_cb)
+
+        self.layout.addStretch()
+
+
+class FAExcPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)
+        super().__init__(self)
+
+        self.setTitle(str(_("Excellon File associations")))
+
+        self.layout.setContentsMargins(2, 2, 2, 2)
+
+        self.vertical_lay = QtWidgets.QVBoxLayout()
+        scroll_widget = QtWidgets.QWidget()
+
+        scroll = VerticalScrollArea()
+        scroll.setWidget(scroll_widget)
+        scroll.setWidgetResizable(True)
+        scroll.setFrameShape(QtWidgets.QFrame.NoFrame)
+
+        self.restore_btn = FCButton(_("Restore"))
+        self.restore_btn.setToolTip(_("Restore the extension list to the default state."))
+        self.del_all_btn = FCButton(_("Delete All"))
+        self.del_all_btn.setToolTip(_("Delete all extensions from the list."))
+
+        hlay0 = QtWidgets.QHBoxLayout()
+        hlay0.addWidget(self.restore_btn)
+        hlay0.addWidget(self.del_all_btn)
+        self.vertical_lay.addLayout(hlay0)
+
+        # # ## Excellon associations
+        list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Extensions list"))
+        list_label.setToolTip(
+            _("List of file extensions to be\n"
+              "associated with FlatCAM.")
+        )
+        self.vertical_lay.addWidget(list_label)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            tb_fsize = settings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+
+        self.exc_list_text = FCTextArea()
+        self.exc_list_text.setReadOnly(True)
+        # self.exc_list_text.sizeHint(custom_sizehint=150)
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+        self.exc_list_text.setFont(font)
+
+        self.vertical_lay.addWidget(self.exc_list_text)
+
+        self.ext_label = QtWidgets.QLabel('%s:' % _("Extension"))
+        self.ext_label.setToolTip(_("A file extension to be added or deleted to the list."))
+        self.ext_entry = FCEntry()
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.vertical_lay.addLayout(hlay1)
+        hlay1.addWidget(self.ext_label)
+        hlay1.addWidget(self.ext_entry)
+
+        self.add_btn = FCButton(_("Add Extension"))
+        self.add_btn.setToolTip(_("Add a file extension to the list"))
+        self.del_btn = FCButton(_("Delete Extension"))
+        self.del_btn.setToolTip(_("Delete a file extension from the list"))
+
+        hlay2 = QtWidgets.QHBoxLayout()
+        self.vertical_lay.addLayout(hlay2)
+        hlay2.addWidget(self.add_btn)
+        hlay2.addWidget(self.del_btn)
+
+        self.exc_list_btn = FCButton(_("Apply Association"))
+        self.exc_list_btn.setToolTip(_("Apply the file associations between\n"
+                                       "FlatCAM and the files with above extensions.\n"
+                                       "They will be active after next logon.\n"
+                                       "This work only in Windows."))
+        self.vertical_lay.addWidget(self.exc_list_btn)
+
+        scroll_widget.setLayout(self.vertical_lay)
+        self.layout.addWidget(scroll)
+
+        # self.vertical_lay.addStretch()
+
+
+class FAGcoPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gcode File associations Preferences", parent=None)
+        super(FAGcoPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("GCode File associations")))
+
+        self.restore_btn = FCButton(_("Restore"))
+        self.restore_btn.setToolTip(_("Restore the extension list to the default state."))
+        self.del_all_btn = FCButton(_("Delete All"))
+        self.del_all_btn.setToolTip(_("Delete all extensions from the list."))
+
+        hlay0 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay0)
+        hlay0.addWidget(self.restore_btn)
+        hlay0.addWidget(self.del_all_btn)
+
+        # ## G-Code associations
+        self.gco_list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Extensions list"))
+        self.gco_list_label.setToolTip(
+            _("List of file extensions to be\n"
+              "associated with FlatCAM.")
+        )
+        self.layout.addWidget(self.gco_list_label)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            tb_fsize = settings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+
+        self.gco_list_text = FCTextArea()
+        self.gco_list_text.setReadOnly(True)
+        # self.gco_list_text.sizeHint(custom_sizehint=150)
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+        self.gco_list_text.setFont(font)
+
+        self.layout.addWidget(self.gco_list_text)
+
+        self.ext_label = QtWidgets.QLabel('%s:' % _("Extension"))
+        self.ext_label.setToolTip(_("A file extension to be added or deleted to the list."))
+        self.ext_entry = FCEntry()
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+        hlay1.addWidget(self.ext_label)
+        hlay1.addWidget(self.ext_entry)
+
+        self.add_btn = FCButton(_("Add Extension"))
+        self.add_btn.setToolTip(_("Add a file extension to the list"))
+        self.del_btn = FCButton(_("Delete Extension"))
+        self.del_btn.setToolTip(_("Delete a file extension from the list"))
+
+        hlay2 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay2)
+        hlay2.addWidget(self.add_btn)
+        hlay2.addWidget(self.del_btn)
+
+        self.gco_list_btn = FCButton(_("Apply Association"))
+        self.gco_list_btn.setToolTip(_("Apply the file associations between\n"
+                                       "FlatCAM and the files with above extensions.\n"
+                                       "They will be active after next logon.\n"
+                                       "This work only in Windows."))
+        self.layout.addWidget(self.gco_list_btn)
+
+        # self.layout.addStretch()
+
+
+class FAGrbPrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber File associations Preferences", parent=None)
+        super(FAGrbPrefGroupUI, self).__init__(self)
+
+        self.setTitle(str(_("Gerber File associations")))
+
+        self.restore_btn = FCButton(_("Restore"))
+        self.restore_btn.setToolTip(_("Restore the extension list to the default state."))
+        self.del_all_btn = FCButton(_("Delete All"))
+        self.del_all_btn.setToolTip(_("Delete all extensions from the list."))
+
+        hlay0 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay0)
+        hlay0.addWidget(self.restore_btn)
+        hlay0.addWidget(self.del_all_btn)
+
+        # ## Gerber associations
+        self.grb_list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Extensions list"))
+        self.grb_list_label.setToolTip(
+            _("List of file extensions to be\n"
+              "associated with FlatCAM.")
+        )
+        self.layout.addWidget(self.grb_list_label)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            tb_fsize = settings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+
+        self.grb_list_text = FCTextArea()
+        self.grb_list_text.setReadOnly(True)
+        # self.grb_list_text.sizeHint(custom_sizehint=150)
+        self.layout.addWidget(self.grb_list_text)
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+        self.grb_list_text.setFont(font)
+
+        self.ext_label = QtWidgets.QLabel('%s:' % _("Extension"))
+        self.ext_label.setToolTip(_("A file extension to be added or deleted to the list."))
+        self.ext_entry = FCEntry()
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+        hlay1.addWidget(self.ext_label)
+        hlay1.addWidget(self.ext_entry)
+
+        self.add_btn = FCButton(_("Add Extension"))
+        self.add_btn.setToolTip(_("Add a file extension to the list"))
+        self.del_btn = FCButton(_("Delete Extension"))
+        self.del_btn.setToolTip(_("Delete a file extension from the list"))
+
+        hlay2 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay2)
+        hlay2.addWidget(self.add_btn)
+        hlay2.addWidget(self.del_btn)
+
+        self.grb_list_btn = FCButton(_("Apply Association"))
+        self.grb_list_btn.setToolTip(_("Apply the file associations between\n"
+                                       "FlatCAM and the files with above extensions.\n"
+                                       "They will be active after next logon.\n"
+                                       "This work only in Windows."))
+
+        self.layout.addWidget(self.grb_list_btn)
+
+        # self.layout.addStretch()
+
+
+class AutoCompletePrefGroupUI(OptionsGroupUI):
+    def __init__(self, parent=None):
+        # OptionsGroupUI.__init__(self, "Gerber File associations Preferences", parent=None)
+        super().__init__(self, parent=parent)
+
+        self.setTitle(str(_("Autocompleter Keywords")))
+
+        self.restore_btn = FCButton(_("Restore"))
+        self.restore_btn.setToolTip(_("Restore the autocompleter keywords list to the default state."))
+        self.del_all_btn = FCButton(_("Delete All"))
+        self.del_all_btn.setToolTip(_("Delete all autocompleter keywords from the list."))
+
+        hlay0 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay0)
+        hlay0.addWidget(self.restore_btn)
+        hlay0.addWidget(self.del_all_btn)
+
+        # ## Gerber associations
+        self.grb_list_label = QtWidgets.QLabel("<b>%s:</b>" % _("Keywords list"))
+        self.grb_list_label.setToolTip(
+            _("List of keywords used by\n"
+              "the autocompleter in FlatCAM.\n"
+              "The autocompleter is installed\n"
+              "in the Code Editor and for the Tcl Shell.")
+        )
+        self.layout.addWidget(self.grb_list_label)
+
+        settings = QSettings("Open Source", "FlatCAM")
+        if settings.contains("textbox_font_size"):
+            tb_fsize = settings.value('textbox_font_size', type=int)
+        else:
+            tb_fsize = 10
+
+        self.kw_list_text = FCTextArea()
+        self.kw_list_text.setReadOnly(True)
+        # self.grb_list_text.sizeHint(custom_sizehint=150)
+        self.layout.addWidget(self.kw_list_text)
+        font = QtGui.QFont()
+        font.setPointSize(tb_fsize)
+        self.kw_list_text.setFont(font)
+
+        self.kw_label = QtWidgets.QLabel('%s:' % _("Extension"))
+        self.kw_label.setToolTip(_("A keyword to be added or deleted to the list."))
+        self.kw_entry = FCEntry()
+
+        hlay1 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay1)
+        hlay1.addWidget(self.kw_label)
+        hlay1.addWidget(self.kw_entry)
+
+        self.add_btn = FCButton(_("Add keyword"))
+        self.add_btn.setToolTip(_("Add a keyword to the list"))
+        self.del_btn = FCButton(_("Delete keyword"))
+        self.del_btn.setToolTip(_("Delete a keyword from the list"))
+
+        hlay2 = QtWidgets.QHBoxLayout()
+        self.layout.addLayout(hlay2)
+        hlay2.addWidget(self.add_btn)
+        hlay2.addWidget(self.del_btn)
+
+        # self.layout.addStretch()

+ 102 - 36
flatcamTools/ToolCutOut.py

@@ -298,6 +298,11 @@ class CutOut(FlatCAMTool):
         # if mouse is dragging set the object True
         self.mouse_is_dragging = False
 
+        # event handlers references
+        self.kp = None
+        self.mm = None
+        self.mr = None
+
         # hold the mouse position here
         self.x_pos = None
         self.y_pos = None
@@ -780,13 +785,21 @@ class CutOut(FlatCAMTool):
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve Geometry object"), name))
             return "Could not retrieve object: %s" % name
 
-        self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
-        self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-        self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-        self.app.plotcanvas.vis_connect('key_press', self.on_key_press)
-        self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move)
-        self.app.plotcanvas.vis_connect('mouse_release', self.on_mouse_click_release)
+        if self.app.is_legacy is False:
+            self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent)
+            self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+        else:
+            self.app.plotcanvas.graph_event_disconnect(self.app.kp)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+            self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+
+        self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
+        self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+        self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
+
 
     def on_manual_cutout(self, click_pos):
         name = self.man_object_combo.currentText()
@@ -923,33 +936,46 @@ class CutOut(FlatCAMTool):
     # To be called after clicking on the plot.
     def on_mouse_click_release(self, event):
 
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+        event_pos = (x, y)
+
         # do paint single only for left mouse clicks
         if event.button == 1:
             self.app.inform.emit(_("Making manual bridge gap..."))
-            pos = self.app.plotcanvas.translate_coords(event.pos)
-            self.on_manual_cutout(click_pos=pos)
 
-            # self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            # self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            # self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
-            # self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+            pos = self.app.plotcanvas.translate_coords(event_pos)
 
-            # self.app.geo_editor.tool_shape.clear(update=True)
-            # self.app.geo_editor.tool_shape.enabled = False
-            # self.gapFinished.emit()
+            self.on_manual_cutout(click_pos=pos)
 
         # if RMB then we exit
-        elif event.button == 2 and self.mouse_is_dragging is False:
-            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+        elif event.button == right_button and self.mouse_is_dragging is False:
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.kp)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
 
             # Remove any previous utility shape
             self.app.geo_editor.tool_shape.clear(update=True)
@@ -959,10 +985,26 @@ class CutOut(FlatCAMTool):
 
         self.app.on_mouse_move_over_plot(event=event)
 
-        pos = self.canvas.translate_coords(event.pos)
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+        event_pos = (x, y)
+
+        pos = self.canvas.translate_coords(event_pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
-        if event.is_dragging is True:
+        if event_is_dragging is True:
             self.mouse_is_dragging = True
         else:
             self.mouse_is_dragging = False
@@ -1058,19 +1100,43 @@ class CutOut(FlatCAMTool):
         # events from the GUI are of type QKeyEvent
         elif type(event) == QtGui.QKeyEvent:
             key = event.key()
+        elif isinstance(event, mpl_key_event):  # MatPlotLib key events are trickier to interpret than the rest
+            key = event.key
+            key = QtGui.QKeySequence(key)
+
+            # check for modifiers
+            key_string = key.toString().lower()
+            if '+' in key_string:
+                mod, __, key_text = key_string.rpartition('+')
+                if mod.lower() == 'ctrl':
+                    modifiers = QtCore.Qt.ControlModifier
+                elif mod.lower() == 'alt':
+                    modifiers = QtCore.Qt.AltModifier
+                elif mod.lower() == 'shift':
+                    modifiers = QtCore.Qt.ShiftModifier
+                else:
+                    modifiers = QtCore.Qt.NoModifier
+                key = QtGui.QKeySequence(key_text)
         # events from Vispy are of type KeyEvent
         else:
             key = event.key
 
         # Escape = Deselect All
         if key == QtCore.Qt.Key_Escape or key == 'Escape':
-            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.kp)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+
+            self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
 
             # Remove any previous utility shape
             self.app.geo_editor.tool_shape.clear(update=True)

+ 110 - 47
flatcamTools/ToolMeasurement.py

@@ -45,10 +45,10 @@ class Measurement(FlatCAMTool):
         self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
         self.units_value.setDisabled(True)
 
-        self.start_label = QtWidgets.QLabel("<b>%s</b> %s:" % (_('Start'), _('Coords')))
+        self.start_label = QtWidgets.QLabel("%s:" % _('Start Coords'))
         self.start_label.setToolTip(_("This is measuring Start point coordinates."))
 
-        self.stop_label = QtWidgets.QLabel("<b>%s</b> %s:" % (_('Stop'), _('Coords')))
+        self.stop_label = QtWidgets.QLabel("%s:" % _('Stop Coords'))
         self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
 
         self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
@@ -113,7 +113,11 @@ class Measurement(FlatCAMTool):
         self.original_call_source = 'app'
 
         # VisPy visuals
-        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        if self.app.is_legacy is False:
+            self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='measurement')
 
         self.measure_btn.clicked.connect(self.activate_measure_tool)
 
@@ -178,26 +182,49 @@ class Measurement(FlatCAMTool):
 
         # we can connect the app mouse events to the measurement tool
         # NEVER DISCONNECT THOSE before connecting some other handlers; it breaks something in VisPy
-        self.canvas.vis_connect('mouse_move', self.on_mouse_move_meas)
-        self.canvas.vis_connect('mouse_release', self.on_mouse_click_release)
+        self.mm = self.canvas.graph_event_connect('mouse_move', self.on_mouse_move_meas)
+        self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
 
         # we disconnect the mouse/key handlers from wherever the measurement tool was called
         if self.app.call_source == 'app':
-            self.canvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.canvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.canvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.canvas.graph_event_disconnect(self.app.mm)
+                self.canvas.graph_event_disconnect(self.app.mp)
+                self.canvas.graph_event_disconnect(self.app.mr)
+
         elif self.app.call_source == 'geo_editor':
-            self.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
-            self.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
-            self.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_geo_click_release)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.geo_editor.on_geo_click_release)
+            else:
+                self.canvas.graph_event_disconnect(self.app.geo_editor.mm)
+                self.canvas.graph_event_disconnect(self.app.geo_editor.mp)
+                self.canvas.graph_event_disconnect(self.app.geo_editor.mr)
+
         elif self.app.call_source == 'exc_editor':
-            self.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
-            self.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
-            self.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_exc_click_release)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.exc_editor.on_exc_click_release)
+            else:
+                self.canvas.graph_event_disconnect(self.app.exc_editor.mm)
+                self.canvas.graph_event_disconnect(self.app.exc_editor.mp)
+                self.canvas.graph_event_disconnect(self.app.exc_editor.mr)
+
         elif self.app.call_source == 'grb_editor':
-            self.canvas.vis_disconnect('mouse_move', self.app.grb_editor.on_canvas_move)
-            self.canvas.vis_disconnect('mouse_press', self.app.grb_editor.on_canvas_click)
-            self.canvas.vis_disconnect('mouse_release', self.app.grb_editor.on_grb_click_release)
+            if self.app.is_legacy is False:
+                self.canvas.graph_event_disconnect('mouse_move', self.app.grb_editor.on_canvas_move)
+                self.canvas.graph_event_disconnect('mouse_press', self.app.grb_editor.on_canvas_click)
+                self.canvas.graph_event_disconnect('mouse_release', self.app.grb_editor.on_grb_click_release)
+            else:
+                self.canvas.graph_event_disconnect(self.app.grb_editor.mm)
+                self.canvas.graph_event_disconnect(self.app.grb_editor.mp)
+                self.canvas.graph_event_disconnect(self.app.grb_editor.mr)
 
         self.app.call_source = 'measurement'
 
@@ -210,25 +237,35 @@ class Measurement(FlatCAMTool):
 
         self.app.call_source = copy(self.original_call_source)
         if self.original_call_source == 'app':
-            self.canvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.canvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.canvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
+            self.app.mp = self.canvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+
         elif self.original_call_source == 'geo_editor':
-            self.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
-            self.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
-            self.canvas.vis_connect('mouse_release', self.app.geo_editor.on_geo_click_release)
+            self.app.geo_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.geo_editor.on_canvas_move)
+            self.app.geo_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.geo_editor.on_canvas_click)
+            self.app.geo_editor.mr = self.canvas.graph_event_connect('mouse_release',
+                                                                     self.app.geo_editor.on_geo_click_release)
+
         elif self.original_call_source == 'exc_editor':
-            self.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
-            self.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
-            self.canvas.vis_connect('mouse_release', self.app.exc_editor.on_exc_click_release)
+            self.app.exc_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.exc_editor.on_canvas_move)
+            self.app.exc_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.exc_editor.on_canvas_click)
+            self.app.exc_editor.mr = self.canvas.graph_event_connect('mouse_release',
+                                                                     self.app.exc_editor.on_exc_click_release)
+
         elif self.original_call_source == 'grb_editor':
-            self.canvas.vis_connect('mouse_move', self.app.grb_editor.on_canvas_move)
-            self.canvas.vis_connect('mouse_press', self.app.grb_editor.on_canvas_click)
-            self.canvas.vis_connect('mouse_release', self.app.grb_editor.on_grb_click_release)
+            self.app.grb_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.grb_editor.on_canvas_move)
+            self.app.grb_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.grb_editor.on_canvas_click)
+            self.app.grb_editor.mr = self.canvas.graph_event_connect('mouse_release',
+                                                                     self.app.grb_editor.on_grb_click_release)
 
         # disconnect the mouse/key events from functions of measurement tool
-        self.canvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
-        self.canvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
+        if self.app.is_legacy is False:
+            self.canvas.graph_event_disconnect('mouse_move', self.on_mouse_move_meas)
+            self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
+        else:
+            self.canvas.graph_event_disconnect(self.mm)
+            self.canvas.graph_event_disconnect(self.mr)
 
         # self.app.ui.notebook.setTabText(2, _("Tools"))
         # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
@@ -247,7 +284,13 @@ class Measurement(FlatCAMTool):
         log.debug("Measuring Tool --> mouse click release")
 
         if event.button == 1:
-            pos_canvas = self.canvas.translate_coords(event.pos)
+            if self.app.is_legacy is False:
+                event_pos = event.pos
+            else:
+                event_pos = (event.xdata, event.ydata)
+
+            pos_canvas = self.canvas.translate_coords(event_pos)
+
             # if GRID is active we need to get the snapped positions
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -267,8 +310,7 @@ class Measurement(FlatCAMTool):
             if len(self.points) == 1:
                 self.start_entry.set_value("(%.4f, %.4f)" % pos)
                 self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
-
-            if len(self.points) == 2:
+            elif len(self.points) == 2:
                 dx = self.points[1][0] - self.points[0][0]
                 dy = self.points[1][1] - self.points[0][1]
                 d = sqrt(dx ** 2 + dy ** 2)
@@ -280,47 +322,68 @@ class Measurement(FlatCAMTool):
                 self.distance_x_entry.set_value('%.4f' % abs(dx))
                 self.distance_y_entry.set_value('%.4f' % abs(dy))
                 self.total_distance_entry.set_value('%.4f' % abs(d))
-                self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                       "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (pos[0], pos[1]))
+                self.app.ui.rel_position_label.setText("<b>Dx</b>: {0:.4f}&nbsp;&nbsp;  <b>Dy</b>: "
+                                                       "{0:.4f}&nbsp;&nbsp;&nbsp;&nbsp;".format(pos[0], pos[1]))
                 self.deactivate_measure_tool()
 
     def on_mouse_move_meas(self, event):
         try:  # May fail in case mouse not within axes
-            pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+            if self.app.is_legacy is False:
+                event_pos = event.pos
+            else:
+                event_pos = (event.xdata, event.ydata)
+
+            try:
+                x = float(event_pos[0])
+                y = float(event_pos[1])
+            except TypeError:
+                return
+
+            pos_canvas = self.app.plotcanvas.translate_coords((x, y))
+
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
-                self.app.app_cursor.enabled = True
+
                 # Update cursor
                 self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
                                              symbol='++', edge_color='black', size=20)
             else:
                 pos = (pos_canvas[0], pos_canvas[1])
-                self.app.app_cursor.enabled = False
+
+            self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {0:.4f}&nbsp;&nbsp;   "
+                                               "<b>Y</b>: {0:.4f}".format(pos[0], pos[1]))
 
             if self.rel_point1 is not None:
-                dx = pos[0] - self.rel_point1[0]
-                dy = pos[1] - self.rel_point1[1]
+                dx = pos[0] - float(self.rel_point1[0])
+                dy = pos[1] - float(self.rel_point1[1])
             else:
                 dx = pos[0]
                 dy = pos[1]
 
-            self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
-                                               "<b>Y</b>: %.4f" % (pos[0], pos[1]))
-            self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
-                                                   "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+            self.app.ui.rel_position_label.setText("<b>Dx</b>: {0:.4f}&nbsp;&nbsp;  <b>Dy</b>: "
+                                                   "{0:.4f}&nbsp;&nbsp;&nbsp;&nbsp;".format(dx, dy))
+
             # update utility geometry
+
             if len(self.points) == 1:
                 self.utility_geometry(pos=pos)
         except Exception as e:
+            log.debug("Measurement.on_mouse_move_meas() --> %s" % str(e))
             self.app.ui.position_label.setText("")
             self.app.ui.rel_position_label.setText("")
 
     def utility_geometry(self, pos):
         # first delete old shape
         self.delete_shape()
+
         # second draw the new shape of the utility geometry
-        self.meas_line = LineString([pos, self.points[0]])
-        self.sel_shapes.add(self.meas_line, color='black', update=True, layer=0, tolerance=None)
+        meas_line = LineString([pos, self.points[0]])
+
+        color = '#00000000'
+        self.sel_shapes.add(meas_line, color=color, update=True, layer=0, tolerance=None)
+
+        if self.app.is_legacy is True:
+            self.sel_shapes.redraw()
 
     def delete_shape(self):
         self.sel_shapes.clear()

+ 61 - 23
flatcamTools/ToolMove.py

@@ -44,7 +44,11 @@ class ToolMove(FlatCAMTool):
         self.old_coords = []
 
         # VisPy visuals
-        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        if self.app.is_legacy is False:
+            self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
+        else:
+            from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
+            self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="move")
 
         self.replot_signal[list].connect(self.replot)
 
@@ -62,10 +66,16 @@ class ToolMove(FlatCAMTool):
         if self.isVisible():
             self.setVisible(False)
 
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click)
-            self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_move)
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.on_left_click)
+                self.app.plotcanvas.graph_event_disconnect('key_release', self.on_key_press)
+                self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.kr)
+                self.app.kr = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent)
 
             self.clicked_move = 0
 
@@ -95,9 +105,14 @@ class ToolMove(FlatCAMTool):
         # this is necessary because right mouse click and middle mouse click
         # are used for panning on the canvas
 
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
+
         if event.button == 1:
             if self.clicked_move == 0:
-                pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+                pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
 
                 # if GRID is active we need to get the snapped positions
                 if self.app.grid_status() == True:
@@ -114,7 +129,7 @@ class ToolMove(FlatCAMTool):
 
             if self.clicked_move == 1:
                 try:
-                    pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+                    pos_canvas = self.app.plotcanvas.translate_coords(event_pos)
 
                     # delete the selection bounding box
                     self.delete_shape()
@@ -174,7 +189,8 @@ class ToolMove(FlatCAMTool):
                     self.toggle()
                     return
 
-                except TypeError:
+                except TypeError as e:
+                    log.debug("ToolMove.on_left_click() --> %s" % str(e))
                     self.app.inform.emit('[ERROR_NOTCL] %s' %
                                          _('ToolMove.on_left_click() --> Error when mouse left click.'))
                     return
@@ -191,7 +207,19 @@ class ToolMove(FlatCAMTool):
         self.app.worker_task.emit({'fcn': worker_task, 'params': []})
 
     def on_move(self, event):
-        pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
+
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+        else:
+            event_pos = (event.xdata, event.ydata)
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+
+        pos_canvas = self.app.plotcanvas.translate_coords((x, y))
 
         # if GRID is active we need to get the snapped positions
         if self.app.grid_status() == True:
@@ -228,9 +256,9 @@ class ToolMove(FlatCAMTool):
             self.toggle()
         else:
             # if we have an object selected then we can safely activate the mouse events
-            self.app.plotcanvas.vis_connect('mouse_move', self.on_move)
-            self.app.plotcanvas.vis_connect('mouse_press', self.on_left_click)
-            self.app.plotcanvas.vis_connect('key_release', self.on_key_press)
+            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_move)
+            self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.on_left_click)
+            self.kr = self.app.plotcanvas.graph_event_connect('key_release', self.on_key_press)
             # first get a bounding box to fit all
             for obj in obj_list:
                 xmin, ymin, xmax, ymax = obj.bounds()
@@ -249,8 +277,12 @@ class ToolMove(FlatCAMTool):
             p2 = (xmaximal, yminimal)
             p3 = (xmaximal, ymaximal)
             p4 = (xminimal, ymaximal)
+
             self.old_coords = [p1, p2, p3, p4]
-            self.draw_shape(self.old_coords)
+            self.draw_shape(Polygon(self.old_coords))
+
+            if self.app.is_legacy is True:
+                self.sel_shapes.redraw()
 
     def update_sel_bbox(self, pos):
         self.delete_shape()
@@ -259,24 +291,30 @@ class ToolMove(FlatCAMTool):
         pt2 = (self.old_coords[1][0] + pos[0], self.old_coords[1][1] + pos[1])
         pt3 = (self.old_coords[2][0] + pos[0], self.old_coords[2][1] + pos[1])
         pt4 = (self.old_coords[3][0] + pos[0], self.old_coords[3][1] + pos[1])
+        self.draw_shape(Polygon([pt1, pt2, pt3, pt4]))
 
-        self.draw_shape([pt1, pt2, pt3, pt4])
+        if self.app.is_legacy is True:
+            self.sel_shapes.redraw()
 
     def delete_shape(self):
         self.sel_shapes.clear()
         self.sel_shapes.redraw()
 
-    def draw_shape(self, coords):
-        self.sel_rect = Polygon(coords)
+    def draw_shape(self, shape):
+
         if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
-            self.sel_rect = self.sel_rect.buffer(-0.1)
-            self.sel_rect = self.sel_rect.buffer(0.2)
+            proc_shape = shape.buffer(-0.1)
+            proc_shape = proc_shape.buffer(0.2)
         else:
-            self.sel_rect = self.sel_rect.buffer(-0.00393)
-            self.sel_rect = self.sel_rect.buffer(0.00787)
+            proc_shape = shape.buffer(-0.00393)
+            proc_shape = proc_shape.buffer(0.00787)
+
+        # face = Color('blue')
+        # face.alpha = 0.2
+
+        face = '#0000FFAF' + str(hex(int(0.2 * 255)))[2:]
+        outline = '#0000FFAF'
 
-        blue_t = Color('blue')
-        blue_t.alpha = 0.2
-        self.sel_shapes.add(self.sel_rect, color='blue', face_color=blue_t, update=True, layer=0, tolerance=None)
+        self.sel_shapes.add(proc_shape, color=outline, face_color=face, update=True, layer=0, tolerance=None)
 
 # end of file

+ 174 - 153
flatcamTools/ToolNonCopperClear.py

@@ -470,10 +470,21 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.bound_obj_name = ""
         self.bound_obj = None
 
+        self.ncc_dia_list = []
+        self.iso_dia_list = []
+        self.has_offset = None
+        self.o_name = None
+        self.overlap = None
+        self.connect = None
+        self.contour = None
+        self.rest = None
+
         self.first_click = False
         self.cursor_pos = None
         self.mouse_is_dragging = False
 
+        self.mm = None
+        self.mr = None
         # store here solid_geometry when there are tool with isolation job
         self.solid_geometry = []
 
@@ -1057,27 +1068,27 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.app.report_usage(_("on_paint_button_click"))
 
         try:
-            overlap = float(self.ncc_overlap_entry.get_value())
+            self.overlap = float(self.ncc_overlap_entry.get_value())
         except ValueError:
             # try to convert comma to decimal point. if it's still not working error message and return
             try:
-                overlap = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
+                self.overlap = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
             except ValueError:
                 self.app.inform.emit('[ERROR_NOTCL]  %s' % _("Wrong value format entered, "
                                                              "use a number."))
                 return
 
-        if overlap >= 1 or overlap < 0:
+        if self.overlap >= 1 or self.overlap < 0:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("Overlap value must be between "
                                                         "0 (inclusive) and 1 (exclusive), "))
             return
 
-        connect = self.ncc_connect_cb.get_value()
-        contour = self.ncc_contour_cb.get_value()
+        self.connect = self.ncc_connect_cb.get_value()
+        self.contour = self.ncc_contour_cb.get_value()
 
-        has_offset = self.ncc_choice_offset_cb.isChecked()
+        self.has_offset = self.ncc_choice_offset_cb.isChecked()
 
-        rest = self.ncc_rest_cb.get_value()
+        self.rest = self.ncc_rest_cb.get_value()
 
         self.obj_name = self.object_combo.currentText()
         # Get source object.
@@ -1092,34 +1103,34 @@ class NonCopperClear(FlatCAMTool, Gerber):
             return
 
         # use the selected tools in the tool table; get diameters for non-copper clear
-        iso_dia_list = list()
+        self.iso_dia_list = list()
         # use the selected tools in the tool table; get diameters for non-copper clear
-        ncc_dia_list = list()
+        self.ncc_dia_list = list()
         if self.tools_table.selectedItems():
             for x in self.tools_table.selectedItems():
                 try:
-                    tooldia = float(self.tools_table.item(x.row(), 1).text())
+                    self.tooldia = float(self.tools_table.item(x.row(), 1).text())
                 except ValueError:
                     # try to convert comma to decimal point. if it's still not working error message and return
                     try:
-                        tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
+                        self.tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
                     except ValueError:
                         self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong Tool Dia value format entered, "
                                                                     "use a number."))
                         continue
 
                 if self.tools_table.cellWidget(x.row(), 4).currentText() == 'iso_op':
-                    iso_dia_list.append(tooldia)
+                    self.iso_dia_list.append(self.tooldia)
                 else:
-                    ncc_dia_list.append(tooldia)
+                    self.ncc_dia_list.append(self.tooldia)
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' % _("No selected tools in Tool Table."))
             return
 
-        o_name = '%s_ncc' % self.obj_name
+        self.o_name = '%s_ncc' % self.obj_name
 
-        select_method = self.reference_radio.get_value()
-        if select_method == 'itself':
+        self.select_method = self.reference_radio.get_value()
+        if self.select_method == 'itself':
             self.bound_obj_name = self.object_combo.currentText()
             # Get source object.
             try:
@@ -1129,138 +1140,29 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 return "Could not retrieve object: %s" % self.obj_name
 
             self.clear_copper(ncc_obj=self.ncc_obj,
-                              ncctooldia=ncc_dia_list,
-                              isotooldia=iso_dia_list,
-                              has_offset=has_offset,
-                              outname=o_name,
-                              overlap=overlap,
-                              connect=connect,
-                              contour=contour,
-                              rest=rest)
-        elif select_method == 'area':
+                              ncctooldia=self.ncc_dia_list,
+                              isotooldia=self.iso_dia_list,
+                              has_offset=self.has_offset,
+                              outname=self.o_name,
+                              overlap=self.overlap,
+                              connect=self.connect,
+                              contour=self.contour,
+                              rest=self.rest)
+        elif self.select_method == 'area':
             self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
 
-            # use the first tool in the tool table; get the diameter
-            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
-
-            # To be called after clicking on the plot.
-            def on_mouse_release(event):
-                # do clear area only for left mouse clicks
-                if event.button == 1:
-                    if self.first_click is False:
-                        self.first_click = True
-                        self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the paint area."))
-
-                        self.cursor_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
-                    else:
-                        self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
-                        self.app.delete_selection_shape()
-
-                        curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-
-                        x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
-                        x1, y1 = curr_pos[0], curr_pos[1]
-                        pt1 = (x0, y0)
-                        pt2 = (x1, y0)
-                        pt3 = (x1, y1)
-                        pt4 = (x0, y1)
-                        self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
-                        self.first_click = False
-                        return
-
-                        # modifiers = QtWidgets.QApplication.keyboardModifiers()
-                        #
-                        # if modifiers == QtCore.Qt.ShiftModifier:
-                        #     mod_key = 'Shift'
-                        # elif modifiers == QtCore.Qt.ControlModifier:
-                        #     mod_key = 'Control'
-                        # else:
-                        #     mod_key = None
-                        #
-                        # if mod_key == self.app.defaults["global_mselect_key"]:
-                        #     self.first_click = False
-                        #     return
-                        #
-                        # self.sel_rect = cascaded_union(self.sel_rect)
-                        # self.clear_copper(ncc_obj=self.ncc_obj,
-                        #                   sel_obj=self.bound_obj,
-                        #                   ncctooldia=ncc_dia_list,
-                        #                   isotooldia=iso_dia_list,
-                        #                   has_offset=has_offset,
-                        #                   outname=o_name,
-                        #                   overlap=overlap,
-                        #                   connect=connect,
-                        #                   contour=contour,
-                        #                   rest=rest)
-                        #
-                        # self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                        # self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-                        #
-                        # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-                elif event.button == 2 and self.mouse_is_dragging == False:
-                    self.first_click = False
-
-                    self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                    self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-
-                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-                    if len(self.sel_rect) == 0:
-                        return
-
-                    self.sel_rect = cascaded_union(self.sel_rect)
-                    self.clear_copper(ncc_obj=self.ncc_obj,
-                                      sel_obj=self.bound_obj,
-                                      ncctooldia=ncc_dia_list,
-                                      isotooldia=iso_dia_list,
-                                      has_offset=has_offset,
-                                      outname=o_name,
-                                      overlap=overlap,
-                                      connect=connect,
-                                      contour=contour,
-                                      rest=rest)
-
-            # called on mouse move
-            def on_mouse_move(event):
-                curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                self.app.app_cursor.enabled = False
-
-                # detect mouse dragging motion
-                if event.is_dragging is True:
-                    self.mouse_is_dragging = True
-                else:
-                    self.mouse_is_dragging = False
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
 
-                # update the cursor position
-                if self.app.grid_status() == True:
-                    self.app.app_cursor.enabled = True
-                    # Update cursor
-                    curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-                    self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
-                                                 symbol='++', edge_color='black', size=20)
-
-                # draw the utility geometry
-                if self.first_click:
-                    self.app.delete_selection_shape()
-                    self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
-                                                         coords=(curr_pos[0], curr_pos[1]),
-                                                         face_alpha=0.0)
-
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-            self.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
-            self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
-        elif select_method == 'box':
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
+            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+        elif self.select_method == 'box':
             self.bound_obj_name = self.box_combo.currentText()
             # Get source object.
             try:
@@ -1271,14 +1173,133 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
             self.clear_copper(ncc_obj=self.ncc_obj,
                               sel_obj=self.bound_obj,
-                              ncctooldia=ncc_dia_list,
-                              isotooldia=iso_dia_list,
-                              has_offset=has_offset,
-                              outname=o_name,
-                              overlap=overlap,
-                              connect=connect,
-                              contour=contour,
-                              rest=rest)
+                              ncctooldia=self.ncc_dia_list,
+                              isotooldia=self.iso_dia_list,
+                              has_offset=self.has_offset,
+                              outname=self.o_name,
+                              overlap=self.overlap,
+                              connect=self.connect,
+                              contour=self.contour,
+                              rest=self.rest)
+
+    # To be called after clicking on the plot.
+    def on_mouse_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        event_pos = self.app.plotcanvas.translate_coords(event_pos)
+
+        # do clear area only for left mouse clicks
+        if event.button == 1:
+            if self.first_click is False:
+                self.first_click = True
+                self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the paint area."))
+
+                self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() == True:
+                    self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+            else:
+                self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
+                self.app.delete_selection_shape()
+
+                if self.app.grid_status() == True:
+                    curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
+                else:
+                    curr_pos = (event_pos[0], event_pos[1])
+
+                x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                x1, y1 = curr_pos[0], curr_pos[1]
+                pt1 = (x0, y0)
+                pt2 = (x1, y0)
+                pt3 = (x1, y1)
+                pt4 = (x0, y1)
+
+                self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
+                self.first_click = False
+                return
+
+        elif event.button == right_button and self.mouse_is_dragging == False:
+            self.first_click = False
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                  self.app.on_mouse_click_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+                                                                  self.app.on_mouse_move_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+
+            if len(self.sel_rect) == 0:
+                return
+
+            self.sel_rect = cascaded_union(self.sel_rect)
+
+            self.clear_copper(ncc_obj=self.ncc_obj,
+                              sel_obj=self.bound_obj,
+                              ncctooldia=self.ncc_dia_list,
+                              isotooldia=self.iso_dia_list,
+                              has_offset=self.has_offset,
+                              outname=self.o_name,
+                              overlap=self.overlap,
+                              connect=self.connect,
+                              contour=self.contour,
+                              rest=self.rest)
+
+    # called on mouse move
+    def on_mouse_move(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+
+        # detect mouse dragging motion
+        if event_is_dragging is True:
+            self.mouse_is_dragging = True
+        else:
+            self.mouse_is_dragging = False
+
+        # update the cursor position
+        if self.app.grid_status() == True:
+            # Update cursor
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+            self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                         symbol='++', edge_color='black', size=20)
+
+        # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
+        if self.cursor_pos is None:
+            self.cursor_pos = (0, 0)
+
+        dx = curr_pos[0] - float(self.cursor_pos[0])
+        dy = curr_pos[1] - float(self.cursor_pos[1])
+        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+
+        # draw the utility geometry
+        if self.first_click:
+            self.app.delete_selection_shape()
+            self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+                                                 coords=(curr_pos[0], curr_pos[1]))
 
     def clear_copper(self, ncc_obj,
                      sel_obj=None,

+ 217 - 165
flatcamTools/ToolPaint.py

@@ -357,6 +357,13 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.bound_obj_name = ""
         self.bound_obj = None
 
+        self.tooldia_list = []
+        self.sel_rect = None
+        self.o_name = None
+        self.overlap = None
+        self.connect = None
+        self.contour = None
+
         self.units = ''
         self.paint_tools = {}
         self.tooluid = 0
@@ -364,6 +371,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.cursor_pos = None
         self.mouse_is_dragging = False
 
+        self.mm = None
+        self.mp = None
+
         self.sel_rect = []
 
         # store here the default data for Geometry Data
@@ -916,17 +926,17 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.app.inform.emit(_("Paint Tool. Reading parameters."))
 
         try:
-            overlap = float(self.paintoverlap_entry.get_value())
+            self.overlap = float(self.paintoverlap_entry.get_value())
         except ValueError:
             # try to convert comma to decimal point. if it's still not working error message and return
             try:
-                overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
+                self.overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
             except ValueError:
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("Wrong value format entered, use a number."))
                 return
 
-        if overlap >= 1 or overlap < 0:
+        if self.overlap >= 1 or self.overlap < 0:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("Overlap value must be between 0 (inclusive) and 1 (exclusive)"))
             return
@@ -934,9 +944,9 @@ class ToolPaint(FlatCAMTool, Gerber):
         self.app.inform.emit('[WARNING_NOTCL] %s' %
                              _("Click inside the desired polygon."))
 
-        connect = self.pathconnect_cb.get_value()
-        contour = self.paintcontour_cb.get_value()
-        select_method = self.selectmethod_combo.get_value()
+        self.connect = self.pathconnect_cb.get_value()
+        self.contour = self.paintcontour_cb.get_value()
+        self.select_method = self.selectmethod_combo.get_value()
 
         self.obj_name = self.obj_combo.currentText()
 
@@ -966,34 +976,34 @@ class ToolPaint(FlatCAMTool, Gerber):
         o_name = '%s_multitool_paint' % self.obj_name
 
         # use the selected tools in the tool table; get diameters
-        tooldia_list = list()
+        self.tooldia_list = list()
         if self.tools_table.selectedItems():
             for x in self.tools_table.selectedItems():
                 try:
-                    tooldia = float(self.tools_table.item(x.row(), 1).text())
+                    self.tooldia = float(self.tools_table.item(x.row(), 1).text())
                 except ValueError:
                     # try to convert comma to decimal point. if it's still not working error message and return
                     try:
-                        tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
+                        self.tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
                     except ValueError:
                         self.app.inform.emit('[ERROR_NOTCL] %s' %
                                              _("Wrong value format entered, use a number."))
                         continue
-                tooldia_list.append(tooldia)
+                self.tooldia_list.append(self.tooldia)
         else:
             self.app.inform.emit('[ERROR_NOTCL] %s' %
                                  _("No selected tools in Tool Table."))
             return
 
-        if select_method == "all":
+        if self.select_method == "all":
             self.paint_poly_all(self.paint_obj,
-                                tooldia=tooldia_list,
-                                outname=o_name,
-                                overlap=overlap,
-                                connect=connect,
-                                contour=contour)
+                                tooldia=self.tooldia_list,
+                                outname=self.o_name,
+                                overlap=self.overlap,
+                                connect=self.connect,
+                                contour=self.contour)
 
-        elif select_method == "single":
+        elif self.select_method == "single":
             self.app.inform.emit('[WARNING_NOTCL] %s' %
                                  _("Click inside the desired polygon."))
 
@@ -1005,7 +1015,10 @@ class ToolPaint(FlatCAMTool, Gerber):
                 # do paint single only for left mouse clicks
                 if event.button == 1:
                     self.app.inform.emit(_("Painting polygon..."))
-                    self.app.plotcanvas.vis_disconnect('mouse_press', doit)
+                    if self.app.is_legacy:
+                        self.app.plotcanvas.graph_event_disconnect('mouse_press', doit)
+                    else:
+                        self.app.plotcanvas.graph_event_disconnect(self.mp)
 
                     pos = self.app.plotcanvas.translate_coords(event.pos)
                     if self.app.grid_status() == True:
@@ -1013,137 +1026,40 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                     self.paint_poly(self.paint_obj,
                                     inside_pt=[pos[0], pos[1]],
-                                    tooldia=tooldia_list,
-                                    overlap=overlap,
-                                    connect=connect,
-                                    contour=contour)
-                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_press', doit)
-
-        elif select_method == "area":
+                                    tooldia=self.tooldia_list,
+                                    overlap=self.overlap,
+                                    connect=self.connect,
+                                    contour=self.contour)
+                    self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                          self.app.on_mouse_click_over_plot)
+                    self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                          self.app.on_mouse_click_release_over_plot)
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+            self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', doit)
+
+        elif self.select_method == "area":
             self.app.inform.emit('[WARNING_NOTCL] %s' %
                                  _("Click the start point of the paint area."))
 
-            # use the first tool in the tool table; get the diameter
-            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
-
-            # To be called after clicking on the plot.
-            def on_mouse_release(event):
-                # do paint single only for left mouse clicks
-                if event.button == 1:
-                    if not self.first_click:
-                        self.first_click = True
-                        self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                             _("Click the end point of the paint area."))
-
-                        self.cursor_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
-                    else:
-                        self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
-                        self.app.delete_selection_shape()
-
-                        curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                        if self.app.grid_status() == True:
-                            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-
-                        x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
-                        x1, y1 = curr_pos[0], curr_pos[1]
-                        pt1 = (x0, y0)
-                        pt2 = (x1, y0)
-                        pt3 = (x1, y1)
-                        pt4 = (x0, y1)
-                        self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
-                        self.first_click = False
-                        return
-                        # modifiers = QtWidgets.QApplication.keyboardModifiers()
-                        #
-                        # if modifiers == QtCore.Qt.ShiftModifier:
-                        #     mod_key = 'Shift'
-                        # elif modifiers == QtCore.Qt.ControlModifier:
-                        #     mod_key = 'Control'
-                        # else:
-                        #     mod_key = None
-                        #
-                        # if mod_key == self.app.defaults["global_mselect_key"]:
-                        #     self.first_click = False
-                        #     return
-                        #
-                        # self.sel_rect = cascaded_union(self.sel_rect)
-                        # self.paint_poly_area(obj=self.paint_obj,
-                        #                      tooldia=tooldia_list,
-                        #                      sel_obj= self.sel_rect,
-                        #                      outname=o_name,
-                        #                      overlap=overlap,
-                        #                      connect=connect,
-                        #                      contour=contour)
-                        #
-                        # self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                        # self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-                        #
-                        # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                        # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-                elif event.button == 2 and self.mouse_is_dragging is False:
-                    self.first_click = False
-
-                    self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
-                    self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
-
-                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-                    if len(self.sel_rect) == 0:
-                        return
-
-                    self.sel_rect = cascaded_union(self.sel_rect)
-                    self.paint_poly_area(obj=self.paint_obj,
-                                         tooldia=tooldia_list,
-                                         sel_obj=self.sel_rect,
-                                         outname=o_name,
-                                         overlap=overlap,
-                                         connect=connect,
-                                         contour=contour)
-
-            # called on mouse move
-            def on_mouse_move(event):
-                curr_pos = self.app.plotcanvas.translate_coords(event.pos)
-                self.app.app_cursor.enabled = False
-
-                # detect mouse dragging motion
-                if event.is_dragging is True:
-                    self.mouse_is_dragging = True
-                else:
-                    self.mouse_is_dragging = False
-
-                # update the cursor position
-                if self.app.grid_status() == True:
-                    self.app.app_cursor.enabled = True
-                    # Update cursor
-                    curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
-                    self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
-                                                 symbol='++', edge_color='black', size=20)
-
-                # draw the utility geometry
-                if self.first_click:
-                    self.app.delete_selection_shape()
-                    self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
-                                                         coords=(curr_pos[0], curr_pos[1]),
-                                                         face_alpha=0.0)
-
-            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+                self.app.plotcanvas.graph_event_disconnect(self.app.mr)
 
-            self.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
-            self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
+            self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
+            self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
 
-        elif select_method == 'ref':
+        elif self.select_method == 'ref':
             self.bound_obj_name = self.box_combo.currentText()
             # Get source object.
             try:
@@ -1156,11 +1072,137 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             self.paint_poly_ref(obj=self.paint_obj,
                                 sel_obj=self.bound_obj,
-                                tooldia=tooldia_list,
-                                overlap=overlap,
-                                outname=o_name,
-                                connect=connect,
-                                contour=contour)
+                                tooldia=self.tooldia_list,
+                                overlap=self.overlap,
+                                outname=self.o_name,
+                                connect=self.connect,
+                                contour=self.contour)
+
+    # To be called after clicking on the plot.
+    def on_mouse_release(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+
+        event_pos = (x, y)
+
+        # do paint single only for left mouse clicks
+        if event.button == 1:
+            if not self.first_click:
+                self.first_click = True
+                self.app.inform.emit('[WARNING_NOTCL] %s' %
+                                     _("Click the end point of the paint area."))
+
+                self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() == True:
+                    self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
+            else:
+                self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
+                self.app.delete_selection_shape()
+
+                curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+                if self.app.grid_status() == True:
+                    curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+                x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+                x1, y1 = curr_pos[0], curr_pos[1]
+                pt1 = (x0, y0)
+                pt2 = (x1, y0)
+                pt3 = (x1, y1)
+                pt4 = (x0, y1)
+                self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
+                self.first_click = False
+                return
+
+        elif event.button == right_button and self.mouse_is_dragging is False:
+            self.first_click = False
+
+            if self.app.is_legacy is False:
+                self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+                self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+            else:
+                self.app.plotcanvas.graph_event_disconnect(self.mr)
+                self.app.plotcanvas.graph_event_disconnect(self.mm)
+
+            self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+                                                                  self.app.on_mouse_click_over_plot)
+            self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+                                                                  self.app.on_mouse_move_over_plot)
+            self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+                                                                  self.app.on_mouse_click_release_over_plot)
+
+            if len(self.sel_rect) == 0:
+                return
+
+            self.sel_rect = cascaded_union(self.sel_rect)
+            self.paint_poly_area(obj=self.paint_obj,
+                                 tooldia=self.tooldia_list,
+                                 sel_obj=self.sel_rect,
+                                 outname=self.o_name,
+                                 overlap=self.overlap,
+                                 connect=self.connect,
+                                 contour=self.contour)
+
+    # called on mouse move
+    def on_mouse_move(self, event):
+        if self.app.is_legacy is False:
+            event_pos = event.pos
+            event_is_dragging = event.is_dragging
+            right_button = 2
+        else:
+            event_pos = (event.xdata, event.ydata)
+            event_is_dragging = self.app.plotcanvas.is_dragging
+            right_button = 3
+
+        try:
+            x = float(event_pos[0])
+            y = float(event_pos[1])
+        except TypeError:
+            return
+
+        curr_pos = self.app.plotcanvas.translate_coords((x, y))
+
+        # detect mouse dragging motion
+        if event_is_dragging == 1:
+            self.mouse_is_dragging = True
+        else:
+            self.mouse_is_dragging = False
+
+        # update the cursor position
+        if self.app.grid_status() == True:
+            # Update cursor
+            curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+            self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
+                                         symbol='++', edge_color='black', size=20)
+
+        # update the positions on status bar
+        self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp;   "
+                                           "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
+        if self.cursor_pos is None:
+            self.cursor_pos = (0, 0)
+
+        dx = curr_pos[0] - float(self.cursor_pos[0])
+        dy = curr_pos[1] - float(self.cursor_pos[1])
+        self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp;  <b>Dy</b>: "
+                                               "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
+
+        # draw the utility geometry
+        if self.first_click:
+            self.app.delete_selection_shape()
+            self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+                                                 coords=(curr_pos[0], curr_pos[1]))
 
     def paint_poly(self, obj,
                    inside_pt=None,
@@ -1199,13 +1241,15 @@ class ToolPaint(FlatCAMTool, Gerber):
         # poly = find_polygon(self.solid_geometry, inside_pt)
         if isinstance(obj, FlatCAMGerber):
             if self.app.defaults["gerber_buffering"] == 'no':
-                self.app.inform.emit('%s %s' %
-                                     (_("Paint Tool. Normal painting polygon task started."),
+                self.app.inform.emit('%s %s %s' %
+                                     (_("Paint Tool."), _("Normal painting polygon task started."),
                                       _("Buffering geometry...")))
             else:
-                self.app.inform.emit(_("Paint Tool. Normal painting polygon task started."))
+                self.app.inform.emit('%s %s' %
+                                     (_("Paint Tool."), _("Normal painting polygon task started.")))
         else:
-            self.app.inform.emit(_("Paint Tool. Normal painting polygon task started."))
+            self.app.inform.emit('%s %s' %
+                                 (_("Paint Tool."), _("Normal painting polygon task started.")))
 
         if isinstance(obj, FlatCAMGerber):
             if self.app.defaults["tools_paint_plotting"] == 'progressive':
@@ -1243,7 +1287,8 @@ class ToolPaint(FlatCAMTool, Gerber):
             return
 
         proc = self.app.proc_container.new(_("Painting polygon..."))
-        self.app.inform.emit('%s: %s' % (_("Paint Tool. Painting polygon at location"), str(inside_pt)))
+        self.app.inform.emit('%s %s: %s' %
+                             (_("Paint Tool."), _("Painting polygon at location"), str(inside_pt)))
 
         name = outname if outname is not None else self.obj_name + "_paint"
 
@@ -1769,13 +1814,15 @@ class ToolPaint(FlatCAMTool, Gerber):
             log.debug("Paint Tool. Rest machining painting all task started.")
             if isinstance(obj, FlatCAMGerber):
                 if app_obj.defaults["gerber_buffering"] == 'no':
-                    app_obj.inform.emit('%s %s' %
-                                        (_("Paint Tool. Rest machining painting all task started."),
+                    app_obj.inform.emit('%s %s %s' %
+                                        (_("Paint Tool."), _("Rest machining painting all task started."),
                                          _("Buffering geometry...")))
                 else:
-                    app_obj.inform.emit(_("Paint Tool. Rest machining painting all task started."))
+                    app_obj.inform.emit('%s %s' %
+                                        (_("Paint Tool."), _("Rest machining painting all task started.")))
             else:
-                app_obj.inform.emit(_("Paint Tool. Rest machining painting all task started."))
+                app_obj.inform.emit('%s %s' %
+                                    (_("Paint Tool."), _("Rest machining painting all task started.")))
 
             tool_dia = None
             sorted_tools.sort(reverse=True)
@@ -2056,13 +2103,16 @@ class ToolPaint(FlatCAMTool, Gerber):
             log.debug("Paint Tool. Normal painting area task started.")
             if isinstance(obj, FlatCAMGerber):
                 if app_obj.defaults["gerber_buffering"] == 'no':
-                    app_obj.inform.emit('%s %s' %
-                                        (_("Paint Tool. Normal painting area task started."),
+                    app_obj.inform.emit('%s %s %s' %
+                                        (_("Paint Tool."),
+                                         _("Normal painting area task started."),
                                          _("Buffering geometry...")))
                 else:
-                    app_obj.inform.emit(_("Paint Tool. Normal painting area task started."))
+                    app_obj.inform.emit('%s %s' %
+                                        (_("Paint Tool."), _("Normal painting area task started.")))
             else:
-                app_obj.inform.emit(_("Paint Tool. Normal painting area task started."))
+                app_obj.inform.emit('%s %s' %
+                                    (_("Paint Tool."), _("Normal painting area task started.")))
 
             tool_dia = None
             if order == 'fwd':
@@ -2235,13 +2285,15 @@ class ToolPaint(FlatCAMTool, Gerber):
             log.debug("Paint Tool. Rest machining painting area task started.")
             if isinstance(obj, FlatCAMGerber):
                 if app_obj.defaults["gerber_buffering"] == 'no':
-                    app_obj.inform.emit('%s %s' %
-                                        (_("Paint Tool. Rest machining painting area task started."),
+                    app_obj.inform.emit('%s %s %s' %
+                                        (_("Paint Tool."),
+                                         _("Rest machining painting area task started."),
                                          _("Buffering geometry...")))
                 else:
                     app_obj.inform.emit(_("Paint Tool. Rest machining painting area task started."))
             else:
-                app_obj.inform.emit(_("Paint Tool. Rest machining painting area task started."))
+                app_obj.inform.emit('%s %s' %
+                                    (_("Paint Tool."), _("Rest machining painting area task started.")))
 
             tool_dia = None
             sorted_tools.sort(reverse=True)

+ 4 - 4
flatcamTools/ToolPanelize.py

@@ -772,8 +772,8 @@ class Panelize(FlatCAMTool):
                         currenty += lenghty
 
                     if panel_type == 'gerber':
-                        self.app.inform.emit('%s %s' %
-                                             (_("Generating panel ..."), _("Adding the Gerber code.")))
+                        self.app.inform.emit('%s' %
+                                             _("Generating panel ... Adding the Gerber code."))
                         obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
                                                                      local_use=obj_fin, use_thread=False)
 
@@ -784,8 +784,8 @@ class Panelize(FlatCAMTool):
                     # app_obj.log.debug("Finished creating a cascaded union for the panel.")
                     self.app.proc_container.update_view_text('')
 
-                self.app.inform.emit('%s %s: %d' %
-                                     (_("Generating panel ..."), _("Spawning copies"), (int(rows * columns))))
+                self.app.inform.emit('%s: %d' %
+                                     (_("Generating panel... Spawning copies"), (int(rows * columns))))
                 if isinstance(panel_obj, FlatCAMExcellon):
                     self.app.progress.emit(50)
                     self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)

+ 2 - 2
flatcamTools/ToolSub.py

@@ -311,7 +311,7 @@ class ToolSub(FlatCAMTool):
 
         log.debug("Working on promise: %s" % str(apid))
 
-        with self.app.proc_container.new('%s %s %s...' % (_("Parsing aperture", str(apid), _("geometry")))):
+        with self.app.proc_container.new('%s: %s...' % (_("Parsing geometry for aperture", str(apid)))):
             for geo_el in geo:
                 new_el = dict()
 
@@ -520,7 +520,7 @@ class ToolSub(FlatCAMTool):
         if tool == "single":
             text = _("Parsing solid_geometry ...")
         else:
-            text = '%s %s %s...' % (_("Parsing tool"), str(tool), _("geometry"))
+            text = '%s: %s...' % (_("Parsing solid_geometry for tool"), str(tool))
 
         with self.app.proc_container.new(text):
             # resulting paths are closed resulting into Polygons

BIN
locale/de/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 265 - 225
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 259 - 225
locale/en/LC_MESSAGES/strings.po


BIN
locale/es/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 259 - 225
locale/es/LC_MESSAGES/strings.po


BIN
locale/pt_BR/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 239 - 231
locale/pt_BR/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 259 - 225
locale/ro/LC_MESSAGES/strings.po


BIN
locale/ru/LC_MESSAGES/strings.mo


Разница между файлами не показана из-за своего большого размера
+ 238 - 230
locale/ru/LC_MESSAGES/strings.po


Разница между файлами не показана из-за своего большого размера
+ 232 - 224
locale_template/strings.pot


+ 7 - 1
requirements.txt

@@ -1,7 +1,13 @@
 # This file contains python only requirements to be installed with pip
 # Python pacakges that cannot be installed with pip (e.g. PyQt5, GDAL) are not included.
 # Usage: pip install -r requirements.txt
-numpy>=1.8
+numpy>=1.11
+matplotlib>=3.1
+cycler>=0.10
+python-dateutil>=2.1
+kiwisolver>=1.0.1
+six
+setuptools
 dill
 rtree
 pyopengl

+ 3 - 0
setup_ubuntu.sh

@@ -14,6 +14,9 @@ apt-get install python3-tk
 apt-get install libspatialindex-dev
 apt-get install python3-gdal
 apt-get install python3-lxml
+pip3 install --upgrade cycler
+pip3 install --upgrade python-dateutil
+pip3 install --upgrade kiwisolver
 pip3 install --upgrade dill
 pip3 install --upgrade Shapely
 pip3 install --upgrade vispy

BIN
share/active.gif


BIN
share/active_2.gif


BIN
share/active_2_static.png


BIN
share/active_3.gif


BIN
share/active_3_static.png


BIN
share/active_4.gif


BIN
share/active_4_static.png


BIN
share/active_static.png


BIN
share/activity2.gif


BIN
share/flatcam_icon32_green.png


BIN
share/script14.png


BIN
share/splash.png


+ 2 - 2
tclCommands/TclCommandPlotAll.py

@@ -42,5 +42,5 @@ class TclCommandPlotAll(TclCommand):
         :param unnamed_args:
         :return:
         """
-
-        self.app.plot_all()
+        if self.app.cmd_line_headless != 1:
+            self.app.plot_all()

+ 8 - 7
tclCommands/TclCommandPlotObjects.py

@@ -42,10 +42,11 @@ class TclCommandPlotObjects(TclCommand):
         :param unnamed_args:
         :return:
         """
-        names = [x.strip() for x in args['names'].split(",")]
-        objs = []
-        for name in names:
-            objs.append(self.app.collection.get_by_name(name))
-
-        for obj in objs:
-            obj.plot()
+        if self.app.cmd_line_headless != 1:
+            names = [x.strip() for x in args['names'].split(",")]
+            objs = []
+            for name in names:
+                objs.append(self.app.collection.get_by_name(name))
+
+            for obj in objs:
+                obj.plot()

+ 47 - 0
tclCommands/TclCommandQuit.py

@@ -0,0 +1,47 @@
+from ObjectCollection import *
+from tclCommands.TclCommand import TclCommand
+
+
+class TclCommandQuit(TclCommand):
+    """
+    Tcl shell command to quit FlatCAM from Tcl shell.
+
+    example:
+
+    """
+
+    # List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['quit_flatcam']
+
+    # Dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+
+    ])
+
+    # Dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict([
+
+    ])
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = []
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Tcl shell command to quit FlatCAM from Tcl shell.",
+        'args': collections.OrderedDict([
+
+        ]),
+        'examples': ['quit_flatcam']
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+
+        :param args:
+        :param unnamed_args:
+        :return:
+        """
+
+        self.app.quit_application()
+

+ 1 - 0
tclCommands/__init__.py

@@ -48,6 +48,7 @@ import tclCommands.TclCommandPaint
 import tclCommands.TclCommandPanelize
 import tclCommands.TclCommandPlotAll
 import tclCommands.TclCommandPlotObjects
+import tclCommands.TclCommandQuit
 import tclCommands.TclCommandSaveProject
 import tclCommands.TclCommandSaveSys
 import tclCommands.TclCommandScale

+ 158 - 0
tests/titlebar_custom.py

@@ -0,0 +1,158 @@
+#########################################################
+## customize Title bar
+## dotpy.ir
+## iraj.jelo@gmail.com
+#########################################################
+import sys
+from PyQt5 import QtWidgets, QtGui
+from PyQt5 import QtCore
+from PyQt5.QtCore import Qt
+
+
+class TitleBar(QtWidgets.QDialog):
+    def __init__(self, parent=None):
+        QtWidgets.QWidget.__init__(self, parent)
+        self.setWindowFlags(Qt.FramelessWindowHint)
+        css = """
+        QWidget{
+            Background: #0000FF;
+            color:white;
+            font:12px bold;
+            font-weight:bold;
+            border-radius: 1px;
+            height: 11px;
+        }
+        QDialog{
+            Background-image:url('img/titlebar bg.png');
+            font-size:12px;
+            color: black;
+
+        }
+        QToolButton{
+            Background:#AA00AA;
+            font-size:11px;
+        }
+        QToolButton:hover{
+            Background: #FF00FF;
+            font-size:11px;
+        }
+        """
+        self.setAutoFillBackground(True)
+        self.setBackgroundRole(QtGui.QPalette.Highlight)
+        self.setStyleSheet(css)
+        self.minimize=QtWidgets.QToolButton(self)
+        self.minimize.setIcon(QtGui.QIcon('img/min.png'))
+        self.maximize=QtWidgets.QToolButton(self)
+        self.maximize.setIcon(QtGui.QIcon('img/max.png'))
+        close=QtWidgets.QToolButton(self)
+        close.setIcon(QtGui.QIcon('img/close.png'))
+        self.minimize.setMinimumHeight(10)
+        close.setMinimumHeight(10)
+        self.maximize.setMinimumHeight(10)
+        label=QtWidgets.QLabel(self)
+        label.setText("Window Title")
+        self.setWindowTitle("Window Title")
+        hbox=QtWidgets.QHBoxLayout(self)
+        hbox.addWidget(label)
+        hbox.addWidget(self.minimize)
+        hbox.addWidget(self.maximize)
+        hbox.addWidget(close)
+        hbox.insertStretch(1,500)
+        hbox.setSpacing(0)
+        self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Fixed)
+        self.maxNormal=False
+        close.clicked.connect(self.close)
+        self.minimize.clicked.connect(self.showSmall)
+        self.maximize.clicked.connect(self.showMaxRestore)
+
+    def showSmall(self):
+        box.showMinimized()
+
+    def showMaxRestore(self):
+        if(self.maxNormal):
+            box.showNormal()
+            self.maxNormal= False
+            self.maximize.setIcon(QtGui.QIcon('img/max.png'))
+        else:
+            box.showMaximized()
+            self.maxNormal = True
+            self.maximize.setIcon(QtGui.QIcon('img/max2.png'))
+
+    def close(self):
+        box.close()
+
+    def mousePressEvent(self,event):
+        if event.button() == Qt.LeftButton:
+            box.moving = True
+            box.offset = event.pos()
+            if event.type() == QtCore.QEvent.MouseButtonDblClick:
+                self.showMaxRestore()
+
+    def mouseMoveEvent(self,event):
+        if box.isMaximized():
+            self.showMaxRestore()
+            box.move(event.globalPos() - box.offset)
+        else:
+            if box.moving:
+                box.move(event.globalPos()-box.offset)
+
+
+class Frame(QtWidgets.QFrame):
+    def __init__(self, parent=None):
+        QtWidgets.QFrame.__init__(self, parent)
+        self.m_mouse_down= False
+        self.setFrameShape(QtWidgets.QFrame.StyledPanel)
+        css = """
+        QFrame{
+            Background:  #FFFFF0;
+            color:white;
+            font:13px ;
+            font-weight:bold;
+            }
+        """
+        self.setStyleSheet(css)
+        self.setWindowFlags(Qt.FramelessWindowHint)
+        self.setMouseTracking(True)
+        self.m_titleBar= TitleBar(self)
+        self.m_content= QtWidgets.QWidget(self)
+        vbox=QtWidgets.QVBoxLayout(self)
+        vbox.addWidget(self.m_titleBar)
+        vbox.setContentsMargins(0, 0, 0, 0)
+        vbox.setSpacing(0)
+        layout=QtWidgets.QVBoxLayout()
+        layout.addWidget(self.m_content)
+        layout.setContentsMargins(5, 5, 5, 5)
+        layout.setSpacing(0)
+        vbox.addLayout(layout)
+        # Allows you to access the content area of the frame
+        # where widgets and layouts can be added
+
+    def contentWidget(self):
+        return self.m_content
+
+    def titleBar(self):
+        return self.m_titleBar
+
+    def mousePressEvent(self,event):
+        self.m_old_pos = event.pos()
+        self.m_mouse_down = event.button()== Qt.LeftButton
+
+    def mouseMoveEvent(self,event):
+        x=event.x()
+        y=event.y()
+
+    def mouseReleaseEvent(self,event):
+        m_mouse_down=False
+
+if __name__ == '__main__':
+    app = QtWidgets.QApplication(sys.argv)
+    box = Frame()
+    box.move(60,60)
+    l=QtWidgets.QVBoxLayout(box.contentWidget())
+    l.setContentsMargins(0, 0, 0, 0)
+    edit=QtWidgets.QLabel("""I would've did anything for you to show you how much I adored you
+But it's over now, it's too late to save our loveJust promise me you'll think of me
+Every time you look up in the sky and see a star 'cuz I'm  your star.""")
+    l.addWidget(edit)
+    box.show()
+    app.exec_()

Некоторые файлы не были показаны из-за большого количества измененных файлов