# -*- coding: ISO-8859-1 -*-
#
# generated by wxGlade 0.9.3 on Thu Jun 27 21:45:40 2019
#

import sys
import threading
import traceback

import wx.ribbon as RB

from About import About
from Adjustments import Adjustments
from Alignment import Alignment
from BufferView import BufferView
from CameraInteface import CameraInterface
from Controller import Controller
from DeviceManager import DeviceManager
from ImageProperty import ImageProperty
from JobInfo import JobInfo
from JobSpooler import JobSpooler
from Kernel import *
from Keymap import Keymap
from LaserOperation import *
from LaserRender import *
from Navigation import Navigation
from Notes import Notes
from OperationPreprocessor import OperationPreprocessor
from OperationProperty import OperationProperty
from PathProperty import PathProperty
from Preferences import Preferences
from RasterWizard import RasterWizard
from RotarySettings import RotarySettings
from Settings import Settings
from Terminal import Terminal
from TextProperty import TextProperty
from UsbConnect import UsbConnect
from Widget import Scene, GridWidget, GuideWidget, ReticleWidget, ElementsWidget, SelectionWidget, \
    LaserPathWidget, RectSelectWidget
from icons import *
from svgelements import *

"""
Laser software for the Stock-LIHUIYU laserboard.

MeerK40t (pronounced MeerKat) is a built-from-the-ground-up MIT licensed 
open-source laser cutting software. See https://github.com/meerk40t/meerk40t
for full details.

wxMeerK40t is the primary gui addon for MeerK40t. It requires wxPython for the interface.
The Transformations work in Windows/OSX/Linux for wxPython 4.0+ (and likely before)

"""

MILS_IN_MM = 39.3701
MEERK40T_ISSUES = "https://github.com/meerk40t/meerk40t/issues"
MEERK40T_HELP = "https://github.com/meerk40t/meerk40t/wiki"
MEERK40T_WEBSITE = "https://github.com/meerk40t/meerk40t"


ID_MAIN_TOOLBAR = wx.NewId()
ID_ADD_FILE = wx.NewId()
ID_OPEN = wx.NewId()

ID_SAVE = wx.NewId()
ID_NAV = wx.NewId()
ID_USB = wx.NewId()
ID_CONTROLLER = wx.NewId()
ID_PREFERENCES = wx.NewId()
ID_DEVICES = wx.NewId()
ID_CAMERA = wx.NewId()
ID_JOB = wx.NewId()
ID_PAUSE = wx.NewId()

ID_SPOOLER = wx.NewId()
ID_KEYMAP = wx.NewId()
ID_NOTES = wx.NewId()
ID_OPERATIONS = wx.NewId()
ID_TERMINAL = wx.NewId()
ID_ROTARY = wx.NewId()
ID_RASTER = wx.NewId()

ID_HOMEPAGE = wx.NewId()

ID_CUT_CONFIGURATION = wx.NewId()
ID_SELECT = wx.NewId()

ID_MENU_IMPORT = wx.NewId()
ID_MENU_RECENT = wx.NewId()
ID_MENU_ZOOM_OUT = wx.NewId()
ID_MENU_ZOOM_IN = wx.NewId()
ID_MENU_ZOOM_SIZE = wx.NewId()

# 1 fill, 2 grids, 4 guides, 8 laserpath, 16 writer_position, 32 selection
ID_MENU_HIDE_FILLS = wx.NewId()
ID_MENU_HIDE_GUIDES = wx.NewId()
ID_MENU_HIDE_GRID = wx.NewId()
ID_MENU_HIDE_BACKGROUND = wx.NewId()
ID_MENU_HIDE_STROKES = wx.NewId()
ID_MENU_HIDE_ICONS = wx.NewId()
ID_MENU_HIDE_TREE = wx.NewId()
ID_MENU_HIDE_LASERPATH = wx.NewId()
ID_MENU_HIDE_RETICLE = wx.NewId()
ID_MENU_HIDE_SELECTION = wx.NewId()
ID_MENU_SCREEN_REFRESH = wx.NewId()
ID_MENU_SCREEN_ANIMATE = wx.NewId()
ID_MENU_SCREEN_INVERT = wx.NewId()
ID_MENU_SCREEN_FLIPXY = wx.NewId()
ID_MENU_PREVENT_CACHING = wx.NewId()
ID_MENU_HIDE_IMAGE = wx.NewId()
ID_MENU_HIDE_PATH = wx.NewId()
ID_MENU_HIDE_TEXT = wx.NewId()

ID_MENU_FILE0 = wx.NewId()
ID_MENU_FILE1 = wx.NewId()
ID_MENU_FILE2 = wx.NewId()
ID_MENU_FILE3 = wx.NewId()
ID_MENU_FILE4 = wx.NewId()
ID_MENU_FILE5 = wx.NewId()
ID_MENU_FILE6 = wx.NewId()
ID_MENU_FILE7 = wx.NewId()
ID_MENU_FILE8 = wx.NewId()
ID_MENU_FILE9 = wx.NewId()
ID_MENU_FILE_CLEAR = wx.NewId()

ID_MENU_ALIGNMENT = wx.NewId()
ID_MENU_KEYMAP = wx.NewId()
ID_MENU_DEVICE_MANAGER = wx.NewId()
ID_MENU_SETTINGS = wx.NewId()
ID_MENU_ROTARY = wx.NewId()
ID_MENU_NAVIGATION = wx.NewId()
ID_MENU_NOTES = wx.NewId()
ID_MENU_OPERATIONS = wx.NewId()
ID_MENU_CONTROLLER = wx.NewId()
ID_MENU_CAMERA = wx.NewId()
ID_MENU_TERMINAL = wx.NewId()
ID_MENU_USB = wx.NewId()
ID_MENU_SPOOLER = wx.NewId()
ID_MENU_JOB = wx.NewId()
ID_MENU_TREE = wx.NewId()

_ = wx.GetTranslation
supported_languages = (('en', u'English', wx.LANGUAGE_ENGLISH),
                       ('it', u'italiano', wx.LANGUAGE_ITALIAN),
                       ('fr', u'franšais', wx.LANGUAGE_FRENCH),
                       ('de', u'Deutsch', wx.LANGUAGE_GERMAN),
                       ('es', u'espa˝ol', wx.LANGUAGE_SPANISH),
                       ('zh', u'Chinese', wx.LANGUAGE_CHINESE))


class MeerK40t(wx.Frame, Module):
    """
    MeerK40t main window
    """

    def __init__(self, parent, *args, **kwds):
        # begin wxGlade: MeerK40t.__init__
        wx.Frame.__init__(self, parent, -1, "", style=wx.DEFAULT_FRAME_STYLE)
        Module.__init__(self)
        self.DragAcceptFiles(True)

        self.tree = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_MULTIPLE | wx.TR_HIDE_ROOT | wx.TR_HAS_BUTTONS)
        self.scene = wx.Panel(self, style=wx.EXPAND | wx.WANTS_CHARS)
        self.scene.SetDoubleBuffered(True)

        self._ribbon = RB.RibbonBar(self, style=RB.RIBBON_BAR_DEFAULT_STYLE)

        home = RB.RibbonPage(self._ribbon, wx.ID_ANY, _("Home"), icons8_opened_folder_50.GetBitmap(), )

        toolbar_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Main"),
                                       style=wx.ribbon.RIBBON_PANEL_NO_AUTO_MINIMISE | RB.RIBBON_PANEL_FLEXIBLE)
        toolbar = RB.RibbonButtonBar(toolbar_panel)
        self.toolbar_button_bar = toolbar
        toolbar.AddButton(ID_OPEN, _("Open"), icons8_opened_folder_50.GetBitmap(), "")
        toolbar.AddButton(ID_SAVE, _("Save"), icons8_save_50.GetBitmap(), "")
        toolbar.AddButton(ID_JOB, _("Start Job"), icons8_laser_beam_52.GetBitmap(), "")
        toolbar.AddToggleButton(ID_PAUSE, _("Pause"), icons8_pause_50.GetBitmap(), "")

        windows_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Windows"), icons8_opened_folder_50.GetBitmap(),
                                       style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE)
        windows = RB.RibbonButtonBar(windows_panel)
        windows.AddButton(ID_NAV, _("Navigation"), icons8_move_50.GetBitmap(), "")
        windows.AddButton(ID_USB, _("Usb"), icons8_usb_connector_50.GetBitmap(), "")
        windows.AddButton(ID_SPOOLER, _("Spooler"), icons8_route_50.GetBitmap(), "")
        windows.AddButton(ID_CONTROLLER, _("Controller"), icons8_connected_50.GetBitmap(), "")
        windows.AddButton(ID_PREFERENCES, _("Preferences"), icons8_administrative_tools_50.GetBitmap(), "")
        windows.AddButton(ID_DEVICES, _("Devices"), icons8_manager_50.GetBitmap(), "")
        windows.AddButton(ID_CAMERA, _("Camera"), icons8_camera_50.GetBitmap(), "")
        windows.AddButton(ID_KEYMAP, _("Keymap"), icons8_keyboard_50.GetBitmap(), "")
        windows.AddButton(ID_NOTES, _("Notes"), icons8_comments_50.GetBitmap(), "")
        windows.AddButton(ID_TERMINAL, _("Terminal"), icons8_console_50.GetBitmap(), "")
        windows.AddButton(ID_ROTARY, _("Rotary"), icons8_roll_50.GetBitmap(), "")
        windows.AddButton(ID_RASTER, _("RasterWizard"), icons8_fantasy_50.GetBitmap(), "")
        # home = RB.RibbonPage(self._ribbon, wx.ID_ANY, _("Tools"), icons8_opened_folder_50.GetBitmap(), )
        #
        # align_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Align"), icons8_opened_folder_50.GetBitmap(),
        #                              style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE)
        # align = RB.RibbonButtonBar(align_panel)
        # align.AddButton(ID_DEVICES, _("Align Left"), icons8_align_left_50.GetBitmap(), "")
        # align.AddButton(ID_DEVICES, _("Align Right"), icons8_align_right_50.GetBitmap(), "")
        # align.AddButton(ID_DEVICES, _("Align Top"), icons8_align_top_50.GetBitmap(), "")
        # align.AddButton(ID_DEVICES, _("Align Bottom"), icons8_align_bottom_50.GetBitmap(), "")
        #
        # flip_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Flip"), icons8_opened_folder_50.GetBitmap(),
        #                             style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE)
        # flip = RB.RibbonButtonBar(flip_panel)
        # flip.AddButton(ID_DEVICES, _("Flip Horizontal"), icons8_flip_horizontal_50.GetBitmap(), "")
        # flip.AddButton(ID_DEVICES, _("Flip Vertical"), icons8_flip_vertical_50.GetBitmap(), "")
        #
        # group_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Group"), icons8_opened_folder_50.GetBitmap(),
        #                              style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE)
        # group = RB.RibbonButtonBar(group_panel)
        # group.AddButton(ID_DEVICES, _("Group"), icons8_group_objects_50.GetBitmap(), "")
        # group.AddButton(ID_DEVICES, _("Ungroup"), icons8_ungroup_objects_50.GetBitmap(), "")
        #
        # tool_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Tools"), icons8_opened_folder_50.GetBitmap(),
        #                             style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE)
        # tool = RB.RibbonButtonBar(tool_panel)
        # tool.AddButton(ID_DEVICES, _("Set Position"), icons8_place_marker_50.GetBitmap(), "")
        # tool.AddButton(ID_DEVICES, _("Oval"), icons8_oval_50.GetBitmap(), "")
        # tool.AddButton(ID_DEVICES, _("Circle"), icons8_circle_50.GetBitmap(), "")
        # tool.AddButton(ID_DEVICES, _("Polygon"), icons8_polygon_50.GetBitmap(), "")
        # tool.AddButton(ID_DEVICES, _("Polyline"), icons8_polyline_50.GetBitmap(), "")
        # tool.AddButton(ID_DEVICES, _("Rectangle"), icons8_rectangular_50.GetBitmap(), "")
        # tool.AddButton(ID_DEVICES, _("Text"), icons8_type_50.GetBitmap(), "")

        home = RB.RibbonPage(self._ribbon, wx.ID_ANY, _("Position"), icons8_opened_folder_50.GetBitmap(), )
        position_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Position"), icons8_opened_folder_50.GetBitmap(),
                                        style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE)

        self.text_x = wx.TextCtrl(position_panel, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER)
        self.text_y = wx.TextCtrl(position_panel, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER)
        self.text_w = wx.TextCtrl(position_panel, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER)
        self.button_aspect_ratio = wx.BitmapButton(position_panel, wx.ID_ANY, icons8_lock_50.GetBitmap())
        self.text_h = wx.TextCtrl(position_panel, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER)
        self.combo_box_units = wx.ComboBox(position_panel, wx.ID_ANY, choices=["mm", "cm", "inch", "mil", "%"],
                                           style=wx.CB_DROPDOWN | wx.CB_READONLY)

        self.button_aspect_ratio.SetSize(self.button_aspect_ratio.GetBestSize())
        self.combo_box_units.SetSelection(0)

        sizer_panel = wx.BoxSizer(wx.HORIZONTAL)
        sizer_units = wx.StaticBoxSizer(wx.StaticBox(position_panel, wx.ID_ANY, "Units:"),
                                        wx.HORIZONTAL)
        sizer_h = wx.StaticBoxSizer(wx.StaticBox(position_panel, wx.ID_ANY, "H:"), wx.HORIZONTAL)
        sizer_w = wx.StaticBoxSizer(wx.StaticBox(position_panel, wx.ID_ANY, "W:"), wx.HORIZONTAL)
        sizer_y = wx.StaticBoxSizer(wx.StaticBox(position_panel, wx.ID_ANY, "Y:"), wx.HORIZONTAL)
        sizer_x = wx.StaticBoxSizer(wx.StaticBox(position_panel, wx.ID_ANY, "X:"), wx.HORIZONTAL)
        sizer_x.Add(self.text_x, 1, 0, 0)
        sizer_panel.Add(sizer_x, 0, 0, 0)
        sizer_y.Add(self.text_y, 1, 0, 0)
        sizer_panel.Add(sizer_y, 0, 0, 0)
        sizer_w.Add(self.text_w, 1, 0, 0)
        sizer_panel.Add(sizer_w, 0, 0, 0)
        sizer_panel.Add(self.button_aspect_ratio, 0, 0, 0)
        sizer_h.Add(self.text_h, 1, 0, 0)
        sizer_panel.Add(sizer_h, 0, 0, 0)
        sizer_units.Add(self.combo_box_units, 0, 0, 0)
        sizer_panel.Add(sizer_units, 0, 0, 0)
        position_panel.SetSizer(sizer_panel)
        self._ribbon.Realize()

        self.ribbon_position_aspect_ratio = True
        self.ribbon_position_ignore_update = False
        self.ribbon_position_x = 0.0
        self.ribbon_position_y = 0.0
        self.ribbon_position_h = 0.0
        self.ribbon_position_w = 0.0
        self.ribbon_position_units = 0
        self.ribbon_position_name = None
        self.Bind(wx.EVT_TEXT, self.on_text_x, self.text_x)
        self.Bind(wx.EVT_TEXT_ENTER, self.on_text_pos_enter, self.text_x)
        self.Bind(wx.EVT_TEXT, self.on_text_y, self.text_y)
        self.Bind(wx.EVT_TEXT_ENTER, self.on_text_pos_enter, self.text_y)
        self.Bind(wx.EVT_TEXT, self.on_text_w, self.text_w)
        self.Bind(wx.EVT_TEXT_ENTER, self.on_text_dim_enter, self.text_w)
        self.Bind(wx.EVT_BUTTON, self.on_button_aspect_ratio, self.button_aspect_ratio)
        self.Bind(wx.EVT_TEXT, self.on_text_h, self.text_h)
        self.Bind(wx.EVT_TEXT_ENTER, self.on_text_dim_enter, self.text_h)
        self.Bind(wx.EVT_COMBOBOX, self.on_combo_box_units, self.combo_box_units)

        # Menu Bar
        self.main_menubar = wx.MenuBar()
        wxglade_tmp_menu = wx.Menu()
        wxglade_tmp_menu.Append(wx.ID_NEW, _("New\tCtrl-N", ""))
        wxglade_tmp_menu.Append(wx.ID_OPEN, _("Open Project\tCtrl-O", ""))
        self.recent_file_menu = wx.Menu()
        wxglade_tmp_menu.AppendSubMenu(self.recent_file_menu, _("Recent"))
        wxglade_tmp_menu.Append(ID_MENU_IMPORT, _("Import File"), "")
        wxglade_tmp_menu.AppendSeparator()
        wxglade_tmp_menu.Append(wx.ID_SAVE, _("Save\tCtrl-S", ""))
        wxglade_tmp_menu.Append(wx.ID_SAVEAS, _("Save As\tCtrl-Shift-S", ""))
        wxglade_tmp_menu.AppendSeparator()

        wxglade_tmp_menu.Append(wx.ID_EXIT, _("Exit"), "")
        self.main_menubar.Append(wxglade_tmp_menu, _("File"))
        wxglade_tmp_menu = wx.Menu()

        wxglade_tmp_menu.Append(ID_MENU_ZOOM_OUT, _("Zoom Out\tCtrl--"), "")
        wxglade_tmp_menu.Append(ID_MENU_ZOOM_IN, _("Zoom In\tCtrl-+"), "")
        wxglade_tmp_menu.Append(ID_MENU_ZOOM_SIZE, _("Zoom To Size"), "")
        wxglade_tmp_menu.AppendSeparator()
        wxglade_tmp_menu.Append(ID_MENU_HIDE_GRID, _("Hide Grid"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_BACKGROUND, _("Hide Background"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_GUIDES, _("Hide Guides"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_PATH, _("Hide Paths"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_IMAGE, _("Hide Images"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_TEXT, _("Hide Text"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_FILLS, _("Hide Fills"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_STROKES, _("Hide Strokes"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_LASERPATH, _("Hide Laserpath"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_RETICLE, _("Hide Reticle"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_SELECTION, _("Hide Selection"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_ICONS, _("Hide Icons"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_HIDE_TREE, _("Hide Tree"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_PREVENT_CACHING, _("Do Not Cache Image"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_SCREEN_REFRESH, _("Do Not Refresh"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_SCREEN_ANIMATE, _("Do Not Animate"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_SCREEN_INVERT, _("Invert"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_SCREEN_FLIPXY, _("Flip XY"), "", wx.ITEM_CHECK)

        self.main_menubar.Append(wxglade_tmp_menu, _("View"))
        wxglade_tmp_menu = wx.Menu()

        wxglade_tmp_menu.Append(wx.ID_PREFERENCES, _("Preferences"), "")
        wxglade_tmp_menu.Append(ID_MENU_SETTINGS, _("Settings"), "")
        wxglade_tmp_menu.Append(ID_MENU_ROTARY, _("Rotary Settings"), "")
        wxglade_tmp_menu.Append(ID_MENU_KEYMAP, _("Keymap Settings"), "")
        wxglade_tmp_menu.Append(ID_MENU_DEVICE_MANAGER, _("Device Manager"), "")
        wxglade_tmp_menu.Append(ID_MENU_ALIGNMENT, _("Alignment Ally"), "")
        wxglade_tmp_menu.Append(ID_MENU_CAMERA, _("Camera"), "")
        wxglade_tmp_menu.Append(ID_MENU_TERMINAL, _("Terminal"), "")
        wxglade_tmp_menu.Append(ID_MENU_NAVIGATION, _("Navigation"), "")
        wxglade_tmp_menu.Append(ID_MENU_CONTROLLER, _("Controller"), "")
        wxglade_tmp_menu.Append(ID_MENU_NOTES, _("Notes"), "")
        wxglade_tmp_menu.Append(ID_MENU_USB, _("USB"), "")
        wxglade_tmp_menu.Append(ID_MENU_SPOOLER, _("Job Spooler"), "")
        wxglade_tmp_menu.Append(ID_MENU_JOB, _("Execute Job"), "")

        self.main_menubar.Append(wxglade_tmp_menu, _("Tools"))

        from sys import platform
        if platform == "darwin":
            wxglade_tmp_menu = wx.Menu()
            self.main_menubar.Append(wxglade_tmp_menu, "Window")
        
        self.SetMenuBar(self.main_menubar)
        
        wxglade_tmp_menu = wx.Menu()
        wxglade_tmp_menu.Append(wx.ID_HELP, _("Help"), "")
        wxglade_tmp_menu.Append(ID_HOMEPAGE, _("Webpage"), "")
        wxglade_tmp_menu.Append(wx.ID_ABOUT, _("About"), "")
        self.main_menubar.Append(wxglade_tmp_menu, _("Help"))

        # Menu Bar end

        self.Bind(wx.EVT_MENU, self.on_click_new, id=wx.ID_NEW)
        self.Bind(wx.EVT_MENU, self.on_click_open, id=wx.ID_OPEN)
        self.Bind(wx.EVT_MENU, self.on_click_open, id=ID_MENU_IMPORT)
        self.Bind(wx.EVT_MENU, self.on_click_save, id=wx.ID_SAVE)
        self.Bind(wx.EVT_MENU, self.on_click_save_as, id=wx.ID_SAVEAS)

        self.Bind(wx.EVT_MENU, self.on_click_exit, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.on_click_zoom_out, id=ID_MENU_ZOOM_OUT)
        self.Bind(wx.EVT_MENU, self.on_click_zoom_in, id=ID_MENU_ZOOM_IN)
        self.Bind(wx.EVT_MENU, self.on_click_zoom_size, id=ID_MENU_ZOOM_SIZE)

        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_GRID), id=ID_MENU_HIDE_GRID)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_BACKGROUND), id=ID_MENU_HIDE_BACKGROUND)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_GUIDES), id=ID_MENU_HIDE_GUIDES)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_PATH), id=ID_MENU_HIDE_PATH)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_IMAGE), id=ID_MENU_HIDE_IMAGE)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_TEXT), id=ID_MENU_HIDE_TEXT)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_FILLS), id=ID_MENU_HIDE_FILLS)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_LASERPATH), id=ID_MENU_HIDE_LASERPATH)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_RETICLE), id=ID_MENU_HIDE_RETICLE)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_SELECTION), id=ID_MENU_HIDE_SELECTION)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_STROKES), id=ID_MENU_HIDE_STROKES)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_ICONS), id=ID_MENU_HIDE_ICONS)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_TREE), id=ID_MENU_HIDE_TREE)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_CACHE), id=ID_MENU_PREVENT_CACHING)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_REFRESH), id=ID_MENU_SCREEN_REFRESH)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_ANIMATE), id=ID_MENU_SCREEN_ANIMATE)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_INVERT), id=ID_MENU_SCREEN_INVERT)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(DRAW_MODE_FLIPXY), id=ID_MENU_SCREEN_FLIPXY)

        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "About", self), id=wx.ID_ABOUT)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Alignment", self), id=ID_MENU_ALIGNMENT)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "CameraInterface", self), id=ID_MENU_CAMERA)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Terminal", self), id=ID_MENU_TERMINAL)
        self.Bind(wx.EVT_MENU, lambda v: self.device.device_root.open('window', "DeviceManager", self),
                  id=ID_MENU_DEVICE_MANAGER)
        self.Bind(wx.EVT_MENU, lambda v: self.device.device_root.open('window', "Keymap", self),
                  id=ID_MENU_KEYMAP)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Preferences", self),
                  id=wx.ID_PREFERENCES)
        self.Bind(wx.EVT_MENU, lambda v: self.device.device_root.open('window', "Settings", self),
                  id=ID_MENU_SETTINGS)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Rotary", self), id=ID_MENU_ROTARY)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Navigation", self),
                  id=ID_MENU_NAVIGATION)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Notes", self),
                  id=ID_MENU_NOTES)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "Controller", self),
                  id=ID_MENU_CONTROLLER)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "UsbConnect", self), id=ID_MENU_USB)
        self.Bind(wx.EVT_MENU, lambda v: self.device.open('window', "JobSpooler", self), id=ID_MENU_SPOOLER)
        self.Bind(wx.EVT_MENU,
                  lambda v: self.device.open('window', "JobInfo", self, list(self.device.device_root.elements.ops())),
                  id=ID_MENU_JOB)
        self.Bind(wx.EVT_MENU, self.launch_help, id=wx.ID_HELP)
        self.Bind(wx.EVT_MENU, self.launch_website, id=ID_HOMEPAGE)
        self.Bind(RB.EVT_RIBBONBAR_HELP_CLICK, self.launch_help)

        toolbar.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.on_click_open, id=ID_OPEN)
        toolbar.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.on_click_save, id=ID_SAVE)
        toolbar.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "JobInfo", self,
                                                list(self.device.device_root.elements.ops())),
                     id=ID_JOB)
        toolbar.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.on_click_pause, id=ID_PAUSE)

        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "UsbConnect", self), id=ID_USB)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Navigation", self), id=ID_NAV)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Controller", self), id=ID_CONTROLLER)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Preferences", self), id=ID_PREFERENCES)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.device_root.open('window', "DeviceManager", self), id=ID_DEVICES)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "CameraInterface", self), id=ID_CAMERA)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "JobSpooler", self), id=ID_SPOOLER)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Keymap", self), id=ID_KEYMAP)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Notes", self), id=ID_NOTES)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Terminal", self), id=ID_TERMINAL)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Operations", self), id=ID_OPERATIONS)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "Rotary", self), id=ID_ROTARY)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED,
                     lambda v: self.device.open('window', "RasterWizard", self), id=ID_RASTER)
        self.main_statusbar = self.CreateStatusBar(3)

        # end wxGlade

        self.Bind(wx.EVT_DROP_FILES, self.on_drop_file)

        self.previous_position = None

        self.matrix = Matrix()
        self.previous_window_position = None
        self.previous_scene_position = None
        self.popup_window_position = None
        self.popup_scene_position = None
        self._Buffer = None
        self.screen_refresh_is_requested = False
        self.screen_refresh_is_running = False
        self.screen_refresh_lock = threading.Lock()
        self.background_brush = wx.Brush("Grey")
        self.renderer = None
        self.laserpath = [[0, 0] for i in range(1000)], [[0, 0] for i in range(1000)]
        self.laserpath_index = 0
        self.working_file = None

        self.__set_properties()
        self.__do_layout()

        self.selection_pen = wx.Pen()
        self.selection_pen.SetColour(wx.BLUE)
        self.selection_pen.SetWidth(25)
        self.selection_pen.SetStyle(wx.PENSTYLE_SHORT_DASH)

        self.scene.Bind(wx.EVT_PAINT, self.on_paint)
        self.scene.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase)

        self.scene.Bind(wx.EVT_MOTION, self.on_mouse_move)

        self.scene.Bind(wx.EVT_MOUSEWHEEL, self.on_mousewheel)

        self.scene.Bind(wx.EVT_MIDDLE_DOWN, self.on_mouse_middle_down)
        self.scene.Bind(wx.EVT_MIDDLE_UP, self.on_mouse_middle_up)

        self.scene.Bind(wx.EVT_LEFT_DCLICK, self.on_mouse_double_click)

        self.scene.Bind(wx.EVT_RIGHT_DOWN, self.on_right_mouse_down)
        self.scene.Bind(wx.EVT_RIGHT_UP, self.on_right_mouse_up)

        self.scene.Bind(wx.EVT_LEFT_DOWN, self.on_left_mouse_down)
        self.scene.Bind(wx.EVT_LEFT_UP, self.on_left_mouse_up)

        self.scene.Bind(wx.EVT_KEY_UP, self.on_key_up)
        self.scene.Bind(wx.EVT_KEY_DOWN, self.on_key_down)

        try:
            self.scene.Bind(wx.EVT_MAGNIFY, self.on_magnify_mouse)
            self.EnableTouchEvents(wx.TOUCH_ZOOM_GESTURE | wx.TOUCH_PAN_GESTURES)
            self.scene.Bind(wx.EVT_GESTURE_PAN, self.on_gesture)
            self.scene.Bind(wx.EVT_GESTURE_ZOOM, self.on_gesture)
            self.tree.Bind(wx.EVT_GESTURE_PAN, self.on_gesture)
            self.tree.Bind(wx.EVT_GESTURE_ZOOM, self.on_gesture)
        except AttributeError:
            # Not WX 4.1
            pass

        self.tree.Bind(wx.EVT_KEY_UP, self.on_key_up)
        self.tree.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
        self.Bind(wx.EVT_KEY_UP, self.on_key_up)
        self.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
        self.Bind(wx.EVT_CLOSE, self.on_close, self)
        self.scene.SetFocus()
        self.process = self.refresh_scene
        self.fps_job = None
        self.root = None
        self.widget_scene = None
        self.pipe_state = None
        self._rotary_view = False
        self._has_modifiers = False
        self._shift_down = False
        self.accelerator_table(self)

    def add_language_menu(self):
        tl = wx.FileTranslationsLoader()
        trans = tl.GetAvailableTranslations("meerk40t")

        def translate_list():
            tl = wx.FileTranslationsLoader()
            trans = tl.GetAvailableTranslations("meerk40t")
            for i in trans:
                yield i

        def translate_active(v):
            try:
                v = int(v)
            except ValueError:
                for i, lang in enumerate(supported_languages):
                    language_code, language_name, language_index = lang
                    if v == language_code:
                        v = i
                        break
            try:
                language_code, language_name, language_index = supported_languages[v]
                self.device.device_root.app.update_language(v)
                return language_name
            except (IndexError, ValueError):
                return "Failed."

        self.device.device_root.translate_list = translate_list
        self.device.device_root.translate_active = translate_active

        if trans:
            wxglade_tmp_menu = wx.Menu()
            i = 0
            for lang in supported_languages:
                language_code, language_name, language_index = lang
                m = wxglade_tmp_menu.Append(wx.ID_ANY, language_name, "", wx.ITEM_RADIO)
                if i == self.device.device_root.language:
                    m.Check(True)

                def language_update(q):
                    return lambda e: self.device.device_root.app.update_language(q)

                self.Bind(wx.EVT_MENU, language_update(i), id=m.GetId())
                if language_code not in trans and i != 0:
                    m.Enable(False)
                i += 1
            self.main_menubar.Append(wxglade_tmp_menu, _("Languages"))

    def on_close(self, event):
        if self.state == 5:
            event.Veto()
        else:
            self.state = 5
            self.device.close('window', self.name)
            event.Skip()  # Call destroy as regular.

    def accelerator_table(self, window):
        def close_window(e=None):
            try:
                window.Close(False)
            except RuntimeError:
                pass

        keyid = wx.NewId()
        accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('W'), keyid)])
        window.Bind(wx.EVT_MENU, close_window, id=keyid)
        window.SetAcceleratorTable(accel_tbl)

    def initialize(self, channel=None):
        self.device.close('window', self.name)
        self.Show()

        device = self.device
        kernel = self.device.device_root
        self.__set_titlebar()
        device.gui = self
        device.setting(int, "draw_mode", 0)
        device.setting(int, "window_width", 1200)
        device.setting(int, "window_height", 600)
        kernel.setting(float, "units_convert", MILS_IN_MM)
        kernel.setting(str, "units_name", 'mm')
        kernel.setting(int, "units_marks", 10)
        kernel.setting(int, "units_index", 0)
        kernel.setting(bool, "print_shutdown", False)
        device.setting(int, 'fps', 40)

        if device is not None:
            device.setting(int, "bed_width", 310)  # Default Value
            device.setting(int, "bed_height", 210)  # Default Value

        kernel.listen('element_added', self.on_rebuild_tree_request)
        kernel.listen('operation_added', self.on_rebuild_tree_request)
        kernel.listen('element_removed', self.on_rebuild_tree_request)
        kernel.listen('operation_removed', self.on_rebuild_tree_request)
        kernel.listen('units', self.space_changed)
        kernel.listen('emphasized', self.on_emphasized_elements_changed)
        kernel.listen('modified', self.on_element_modified)
        kernel.listen('altered', self.on_element_alteration)

        device.listen('background', self.on_background_signal)
        device.listen('rebuild_tree', self.on_rebuild_tree_signal)
        device.listen('refresh_scene', self.on_refresh_scene)
        device.listen('element_property_update', self.on_element_update)
        device.listen('pipe;error', self.on_usb_error)
        device.listen('pipe;usb_state_text', self.on_usb_state_text)
        device.listen('pipe;thread', self.on_pipe_state)
        device.listen('spooler;thread', self.on_spooler_state)
        device.listen('interpreter;position', self.update_position)
        device.listen('interpreter;mode', self.on_interpreter_mode)
        device.listen('bed_size', self.bed_changed)
        if device.fps <= 0:
            device.fps = 60
        self.renderer = LaserRender(device)
        self.root = RootNode(device, self, kernel.elements)
        device.root = self.root

        if device.window_width < 300:
            device.window_width = 300
        if device.window_height < 300:
            device.window_height = 300
        self.widget_scene = device.open('module', 'Scene')

        self.widget_scene.add_scenewidget(SelectionWidget(self.widget_scene, self.root))
        self.widget_scene.add_scenewidget(RectSelectWidget(self.widget_scene))
        self.widget_scene.add_scenewidget(LaserPathWidget(self.widget_scene))
        self.widget_scene.add_scenewidget(ElementsWidget(self.widget_scene, self.root, self.renderer))
        self.widget_scene.add_scenewidget(GridWidget(self.widget_scene))
        self.widget_scene.add_interfacewidget(GuideWidget(self.widget_scene))
        self.widget_scene.add_interfacewidget(ReticleWidget(self.widget_scene))

        device.control_instance_add("Transform", self.open_transform_dialog)
        device.control_instance_add("Flip", self.open_flip_dialog)
        device.control_instance_add("Path", self.open_path_dialog)
        device.control_instance_add("Fill", self.open_fill_dialog)
        device.control_instance_add("Stroke", self.open_stroke_dialog)
        device.control_instance_add("FPS", self.open_fps_dialog)
        device.control_instance_add("Speedcode-Gear-Force", self.open_speedcode_gear_dialog)
        device.control_instance_add("Jog Transition Test", self.run_jog_transition_test)
        device.control_instance_add("Jog Transition Switch Test", self.run_jog_transition_switch_test)
        device.control_instance_add("Jog Transition Finish Test", self.run_jog_transition_finish_test)
        device.control_instance_add("Home and Dot", self.run_home_and_dot_test)

        def test_crash_in_thread():
            def foo():
                a = 1 / 0

            device.threaded(foo)

        device.control_instance_add("Crash Thread", test_crash_in_thread)
        device.control_instance_add("Clear Laserpath", self.clear_laserpath)
        device.control_instance_add("RotaryView", self.toggle_rotary_view)
        device.control_instance_add("RotaryScale", self.apply_rotary_scale)
        device.control_instance_add("egv export", self.egv_export)
        device.control_instance_add("egv import", self.egv_import)
        self.SetSize((device.window_width, device.window_height))
        self.interval = 1.0 / float(device.fps)
        self.schedule()
        self.process()
        self.add_language_menu()

        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_FILLS)
        m.Check(self.device.draw_mode & DRAW_MODE_FILLS != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_GUIDES)
        m.Check(self.device.draw_mode & DRAW_MODE_GUIDES != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_BACKGROUND)
        m.Check(self.device.draw_mode & DRAW_MODE_BACKGROUND != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_GRID)
        m.Check(self.device.draw_mode & DRAW_MODE_GRID != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_LASERPATH)
        m.Check(self.device.draw_mode & DRAW_MODE_LASERPATH != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_RETICLE)
        m.Check(self.device.draw_mode & DRAW_MODE_RETICLE != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_SELECTION)
        m.Check(self.device.draw_mode & DRAW_MODE_SELECTION != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_STROKES)
        m.Check(self.device.draw_mode & DRAW_MODE_STROKES != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_ICONS)
        m.Check(self.device.draw_mode & DRAW_MODE_ICONS != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_TREE)
        m.Check(self.device.draw_mode & DRAW_MODE_TREE != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_PREVENT_CACHING)
        m.Check(self.device.draw_mode & DRAW_MODE_CACHE != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_SCREEN_REFRESH)
        m.Check(self.device.draw_mode & DRAW_MODE_REFRESH != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_SCREEN_ANIMATE)
        m.Check(self.device.draw_mode & DRAW_MODE_ANIMATE != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_PATH)
        m.Check(self.device.draw_mode & DRAW_MODE_PATH != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_IMAGE)
        m.Check(self.device.draw_mode & DRAW_MODE_IMAGE != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_TEXT)
        m.Check(self.device.draw_mode & DRAW_MODE_TEXT != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_SCREEN_FLIPXY)
        m.Check(self.device.draw_mode & DRAW_MODE_FLIPXY != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_SCREEN_INVERT)
        m.Check(self.device.draw_mode & DRAW_MODE_INVERT != 0)

        device.setting(str, 'file0', None)
        device.setting(str, 'file1', None)
        device.setting(str, 'file2', None)
        device.setting(str, 'file3', None)
        device.setting(str, 'file4', None)
        device.setting(str, 'file5', None)
        device.setting(str, 'file6', None)
        device.setting(str, 'file7', None)
        device.setting(str, 'file8', None)
        device.setting(str, 'file9', None)
        self.populate_recent_menu()

        self.Bind(wx.EVT_SIZE, self.on_size)

        self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.root.on_drag_begin_handler, self.tree)
        self.Bind(wx.EVT_TREE_END_DRAG, self.root.on_drag_end_handler, self.tree)
        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.root.on_item_activated, self.tree)
        self.Bind(wx.EVT_TREE_SEL_CHANGED, self.root.on_item_selection_changed, self.tree)
        self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.root.on_item_right_click, self.tree)

        if device is not None:
            bedwidth = device.bed_width
            bedheight = device.bed_height
            bbox = (0, 0, bedwidth * MILS_IN_MM, bedheight * MILS_IN_MM)
            self.widget_scene.widget_root.focus_viewport_scene(bbox, self.scene.ClientSize, 0.1)
            self.ribbon_position_units = kernel.units_index
            self.update_ribbon_position()
        self.CenterOnScreen()

    def finalize(self, channel=None):
        device = self.device
        kernel = device.device_root
        if kernel.print_shutdown:
            kernel.add_watcher('shutdown', print)
        device.stop()

        self.unschedule()
        self.screen_refresh_lock.acquire()

        device = self.device
        kernel = device.device_root

        kernel.unlisten('element_added', self.on_rebuild_tree_request)
        kernel.unlisten('operation_added', self.on_rebuild_tree_request)
        kernel.unlisten('element_removed', self.on_rebuild_tree_request)
        kernel.unlisten('operation_removed', self.on_rebuild_tree_request)
        kernel.unlisten('units', self.space_changed)
        kernel.unlisten('emphasized', self.on_emphasized_elements_changed)
        kernel.unlisten('modified', self.on_element_modified)
        kernel.unlisten('altered', self.on_element_alteration)

        device.unlisten('background', self.on_background_signal)
        device.unlisten('rebuild_tree', self.on_rebuild_tree_signal)
        device.unlisten('refresh_scene', self.on_refresh_scene)
        device.unlisten('element_property_update', self.on_element_update)
        device.unlisten('pipe;error', self.on_usb_error)
        device.unlisten('pipe;usb_state_text', self.on_usb_state_text)
        device.unlisten('pipe;thread', self.on_pipe_state)
        device.unlisten('spooler;thread', self.on_spooler_state)
        device.unlisten('interpreter;position', self.update_position)
        device.unlisten('interpreter;mode', self.on_interpreter_mode)
        device.unlisten('bed_size', self.bed_changed)
        try:
            self.Close()
        except RuntimeError:
            pass

    def shutdown(self, channel=None):
        try:
            self.Close()
        except RuntimeError:
            pass

    def set_fps(self, fps):
        if fps == 0:
            fps = 1
        self.device.fps = fps
        self.interval = 1.0 / float(self.device.fps)
        self.schedule()

    def on_element_update(self, *args):
        """
        Called by 'element_property_update' when the properties of an element are changed.

        :param args:
        :return:
        """
        if self.root is not None:
            self.root.on_element_update(*args)

    def on_rebuild_tree_request(self, *args):
        """
        Called by various functions, sends a rebuild_tree signal.
        This is to prevent multiple events from overtaxing the rebuild.

        :param args:
        :return:
        """
        self.device.signal('rebuild_tree')

    def on_rebuild_tree_signal(self, *args):
        """
        Called by 'rebuild_tree' signal. To refresh tree directly

        :param args:
        :return:
        """
        if self.root.device.draw_mode & DRAW_MODE_TREE != 0:
            self.root.gui.tree.Hide()
            return
        else:
            self.root.gui.tree.Show()
        self.root.rebuild_tree()
        self.request_refresh()

    def on_refresh_scene(self, *args):
        """
        Called by 'refresh_scene' change. To refresh tree.

        :param args:
        :return:
        """
        self.request_refresh()

    def on_usb_error(self, value):
        dlg = wx.MessageDialog(None, _("All attempts to connect to USB have failed."),
                               _("Usb Connection Problem."), wx.OK | wx.ICON_WARNING)
        dlg.ShowModal()
        dlg.Destroy()

    def on_usb_state_text(self, value):
        self.main_statusbar.SetStatusText(_("Usb: %s") % value, 0)

    def on_pipe_state(self, state):
        if state == self.pipe_state:
            return
        self.pipe_state = state

        self.main_statusbar.SetStatusText(_("Controller: %s") % self.device.get_text_thread_state(state), 1)
        self.toolbar_button_bar.ToggleButton(ID_PAUSE, state == STATE_BUSY)

    def on_spooler_state(self, value):
        self.main_statusbar.SetStatusText(_("Spooler: %s") % self.device.get_text_thread_state(value), 2)

    def on_interpreter_mode(self, state):
        if state == 0:
            self.background_brush = wx.Brush("Grey")
        else:
            self.background_brush = wx.Brush("Red")
        self.request_refresh_for_animation()

    def on_background_signal(self, background):
        self.widget_scene.signal('background', background)
        self.request_refresh()

    def __set_titlebar(self):
        device_text = ''
        device_name = ''
        device_version = ''
        if self.device is not None:
            device_text = '- %s:%s' % (self.device.device_name, self.device._uid)
            device_version = self.device.device_root.device_version
            device_name = self.device.device_root.device_name
        self.SetTitle(_("%s v%s %s") % (device_name, device_version, device_text))

    def __set_properties(self):
        # begin wxGlade: MeerK40t.__set_properties
        self.__set_titlebar()
        self.main_statusbar.SetStatusWidths([-1] * self.main_statusbar.GetFieldsCount())
        _icon = wx.NullIcon
        _icon.CopyFromBitmap(icon_meerk40t.GetBitmap())
        self.SetIcon(_icon)
        # statusbar fields
        main_statusbar_fields = ["Status"]
        for i in range(len(main_statusbar_fields)):
            self.main_statusbar.SetStatusText(main_statusbar_fields[i], i)
        self.tree.SetMaxSize((275, -1))

    def __do_layout(self):
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(self._ribbon, 0, wx.EXPAND, 0)
        widget_sizer = wx.BoxSizer(wx.HORIZONTAL)
        widget_sizer.Add(self.tree, 1, wx.EXPAND, 0)
        widget_sizer.Add(self.scene, 5, wx.EXPAND, 0)
        main_sizer.Add(widget_sizer, 8, wx.EXPAND, 0)
        self.SetSizer(main_sizer)
        self.Layout()

    def load_or_open(self, filename):
        """
        Loads recent file name given. If the filename cannot be opened attempts open dialog at last known location.
        """
        if os.path.exists(filename):
            try:
                self.load(filename)
            except PermissionError:
                self.tryopen(filename)
        else:
            self.tryopen(filename)

    def tryopen(self, filename):
        """
        Loads an open dialog at given filename to load data.
        """
        files = self.device.load_types()
        defaultFile = os.path.basename(filename)
        defaultDir = os.path.dirname(filename)

        with wx.FileDialog(
                self, _("Open"), defaultDir=defaultDir, defaultFile=defaultFile, wildcard=files, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
        ) as fileDialog:
            fileDialog.SetFilename(defaultFile)
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind
            pathname = fileDialog.GetPath()
            self.load(pathname)

    def populate_recent_menu(self):
        for i in range(self.recent_file_menu.MenuItemCount):
            self.recent_file_menu.Remove(self.recent_file_menu.FindItemByPosition(0))
        device = self.device
        if device.file0 is not None and len(device.file0):
            self.recent_file_menu.Append(ID_MENU_FILE0, device.file0, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file0), id=ID_MENU_FILE0)
        if device.file1 is not None and len(device.file1):
            self.recent_file_menu.Append(ID_MENU_FILE1, device.file1, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file1), id=ID_MENU_FILE1)
        if device.file2 is not None and len(device.file2):
            self.recent_file_menu.Append(ID_MENU_FILE2, device.file2, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file2), id=ID_MENU_FILE2)
        if device.file3 is not None and len(device.file3):
            self.recent_file_menu.Append(ID_MENU_FILE3, device.file3, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file3), id=ID_MENU_FILE3)
        if device.file4 is not None and len(device.file4):
            self.recent_file_menu.Append(ID_MENU_FILE4, device.file4, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file4), id=ID_MENU_FILE4)
        if device.file5 is not None and len(device.file5):
            self.recent_file_menu.Append(ID_MENU_FILE5, device.file5, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file5), id=ID_MENU_FILE5)
        if device.file6 is not None and len(device.file6):
            self.recent_file_menu.Append(ID_MENU_FILE6, device.file6, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file6), id=ID_MENU_FILE6)
        if device.file7 is not None and len(device.file7):
            self.recent_file_menu.Append(ID_MENU_FILE7, device.file7, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file7), id=ID_MENU_FILE7)
        if device.file8 is not None and len(device.file8):
            self.recent_file_menu.Append(ID_MENU_FILE8, device.file8, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file8), id=ID_MENU_FILE8)
        if device.file9 is not None and len(device.file9):
            self.recent_file_menu.Append(ID_MENU_FILE9, device.file9, "")
            self.Bind(wx.EVT_MENU, lambda e: self.load_or_open(device.file9), id=ID_MENU_FILE9)
        if self.recent_file_menu.MenuItemCount != 0:
            self.recent_file_menu.Append(ID_MENU_FILE_CLEAR, _("Clear Recent"), "")
            self.Bind(wx.EVT_MENU, lambda e: self.clear_recent(), id=ID_MENU_FILE_CLEAR)

    def clear_recent(self):
        for i in range(10):
            try:
                setattr(self.device, 'file' + str(i), '')
            except IndexError:
                break
        self.populate_recent_menu()

    def save_recent(self, pathname):
        recent = list()
        for i in range(10):
            recent.append(getattr(self.device, 'file' + str(i)))
        recent = [r for r in recent if r is not None and r != pathname and len(r) > 0]
        recent.insert(0, pathname)
        for i in range(10):
            try:
                setattr(self.device, 'file' + str(i), recent[i])
            except IndexError:
                break
        self.populate_recent_menu()

    def load(self, pathname):
        self.device.setting(bool, 'auto_note', True)
        self.device.device_root.setting(bool, 'uniform_svg', False)
        self.device.device_root.setting(float, 'svg_ppi', 96.0)
        self.device.device_root.setting(bool, 'default_operations', False)
        if self.device.device_root.default_operations:
            self.device.device_root.elements.load_default()
        with wx.BusyInfo(_("Loading File...")):
            n = self.device.device_root.elements.note
            is_svg = pathname.lower().endswith('svg')
            if is_svg:
                results = self.device.load(pathname,
                                           channel=self.device.channel_open('load'),
                                           svg_ppi=self.device.device_root.svg_ppi)
            else:
                results = self.device.load(pathname, channel=self.device.channel_open('load'))
            if results is not None:
                elements, pathname, basename = results
                self.save_recent(pathname)
                self.device.classify(elements)
                if n != self.device.device_root.elements.note and self.device.auto_note:
                    self.device.open('window', "Notes", self)
                if (self.device.device_root.uniform_svg and is_svg) or \
                        (len(elements) > 0 and 'meerK40t' in elements[0].values):
                    self.working_file = pathname
                return True
            return False

    def on_drop_file(self, event):
        """
        Drop file handler

        Accepts multiple files drops.
        """
        accepted = 0
        rejected = 0
        rejected_files = []
        for pathname in event.GetFiles():
            if self.load(pathname):
                accepted += 1
            else:
                rejected += 1
                rejected_files.append(pathname)
        if rejected != 0:
            reject = "\n".join(rejected_files)
            err_msg = _("Some files were unrecognized:\n%s") % reject
            dlg = wx.MessageDialog(None, err_msg, _('Error encountered'), wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()

    def on_paint(self, event):
        try:
            if self._Buffer is None:
                self.update_buffer_ui_thread()
            wx.BufferedPaintDC(self.scene, self._Buffer)
        except (RuntimeError, TypeError):
            # RuntimeError if the DC is deleted.
            # TypeError if the buffer didn't get added because it aborted early.
            pass

    def set_buffer(self):
        width, height = self.scene.ClientSize
        if width <= 0:
            width = 1
        if height <= 0:
            height = 1
        self._Buffer = wx.Bitmap(width, height)

    def on_size(self, event):
        if self.device is None:
            return
        self.Layout()
        self.device.window_width, self.device.window_height = self.Size
        self.widget_scene.signal('guide')
        self.request_refresh()

    def update_position(self, pos):
        self.laserpath[0][self.laserpath_index][0] = pos[0]
        self.laserpath[0][self.laserpath_index][1] = pos[1]
        self.laserpath[1][self.laserpath_index][0] = pos[2]
        self.laserpath[1][self.laserpath_index][1] = pos[3]
        self.laserpath_index += 1
        self.laserpath_index %= len(self.laserpath[0])
        self.request_refresh_for_animation()

    def space_changed(self, *args):
        self.ribbon_position_units = self.device.device_root.units_index
        self.update_ribbon_position()
        self.widget_scene.signal('grid')
        self.widget_scene.signal('guide')
        self.request_refresh()

    def bed_changed(self, *args):
        self.widget_scene.signal('grid')
        # self.widget_scene.signal('guide')
        self.request_refresh()

    def on_emphasized_elements_changed(self, *args):
        self.update_ribbon_position()
        self.clear_laserpath()
        self.request_refresh()

    def on_element_alteration(self, *args):
        self.device.signal('rebuild_tree')

    def on_element_modified(self, *args):
        self.update_ribbon_position()

    def clear_laserpath(self):
        self.laserpath = [[0, 0] for i in range(1000)], [[0, 0] for i in range(1000)]
        self.laserpath_index = 0

    def on_erase(self, event):
        pass

    def request_refresh_for_animation(self):
        """Called on the various signals trying to animate the screen."""
        if self.device.draw_mode & DRAW_MODE_ANIMATE == 0:
            self.request_refresh()

    def request_refresh(self):
        """Request an update to the scene."""
        if self.device.draw_mode & DRAW_MODE_REFRESH == 0:
            self.screen_refresh_is_requested = True

    def refresh_scene(self):
        """Called by the Scheduler at a given the specified framerate."""
        if self.screen_refresh_is_requested and not self.screen_refresh_is_running:
            self.screen_refresh_is_running = True
            if self.screen_refresh_lock.acquire(timeout=1):
                if not wx.IsMainThread():
                    wx.CallAfter(self._refresh_in_ui)
                else:
                    self._refresh_in_ui()
            else:
                self.screen_refresh_is_requested = False
                self.screen_refresh_is_running = False

    def _refresh_in_ui(self):
        """Called by refresh_scene() in the UI thread."""
        if self.device is None:
            return
        self.update_buffer_ui_thread()
        self.scene.Refresh()
        self.scene.Update()
        self.screen_refresh_is_requested = False
        self.screen_refresh_is_running = False
        self.screen_refresh_lock.release()

    def update_buffer_ui_thread(self):
        """Performs the redraw of the data in the UI thread."""
        try:
            dm = self.device.draw_mode
        except AttributeError:
            return  # Abort in a race condition. Do no draw the scene.
        if self._Buffer is None or self._Buffer.GetSize() != self.scene.ClientSize:
            self.set_buffer()
        dc = wx.MemoryDC()
        dc.SelectObject(self._Buffer)
        dc.SetBackground(self.background_brush)
        dc.Clear()
        w, h = dc.Size
        if dm & DRAW_MODE_FLIPXY != 0:
            dc.SetUserScale(-1, -1)
            dc.SetLogicalOrigin(w, h)
        gc = wx.GraphicsContext.Create(dc)
        gc.Size = dc.Size

        gc.laserpath = self.laserpath
        font = wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)
        gc.SetFont(font, wx.BLACK)
        if self.widget_scene is not None:
            self.widget_scene.draw(gc)
        if dm & DRAW_MODE_INVERT != 0:
            dc.Blit(0, 0, w, h, dc, 0, 0, wx.SRC_INVERT)
        gc.Destroy()
        del dc

    # Mouse Events.

    def on_mousewheel(self, event):
        if self.scene.HasCapture():
            return
        rotation = event.GetWheelRotation()
        if event.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL and not event.ShiftDown():

            if event.HasModifiers():
                if rotation > 1:
                    self.widget_scene.event(event.GetPosition(), 'wheelup_ctrl')
                elif rotation < -1:
                    self.widget_scene.event(event.GetPosition(), 'wheeldown_ctrl')
            else:
                if rotation > 1:
                    self.widget_scene.event(event.GetPosition(), 'wheelup')
                elif rotation < -1:
                    self.widget_scene.event(event.GetPosition(), 'wheeldown')
        else:
            if rotation > 1:
                self.widget_scene.event(event.GetPosition(), 'wheelleft')
            elif rotation < -1:
                self.widget_scene.event(event.GetPosition(), 'wheelright')

    def on_mousewheel_zoom(self, event):
        if self.scene.HasCapture():
            return
        rotation = event.GetWheelRotation()
        if self.device.device_root.mouse_zoom_invert:
            rotation = -rotation
        if rotation > 1:
            self.widget_scene.event(event.GetPosition(), 'wheelup')
        elif rotation < -1:
            self.widget_scene.event(event.GetPosition(), 'wheeldown')

    def on_mouse_middle_down(self, event):
        self.scene.SetFocus()
        if not self.scene.HasCapture():
            self.scene.CaptureMouse()
        self.widget_scene.event(event.GetPosition(), 'middledown')

    def on_mouse_middle_up(self, event):
        if self.scene.HasCapture():
            self.scene.ReleaseMouse()
        self.widget_scene.event(event.GetPosition(), 'middleup')

    def on_left_mouse_down(self, event):
        self.scene.SetFocus()
        if not self.scene.HasCapture():
            self.scene.CaptureMouse()
        self.widget_scene.event(event.GetPosition(), 'leftdown')

    def on_left_mouse_up(self, event):
        if self.scene.HasCapture():
            self.scene.ReleaseMouse()
        self.widget_scene.event(event.GetPosition(), 'leftup')

    def on_mouse_double_click(self, event):
        if self.scene.HasCapture():
            return
        self.widget_scene.event(event.GetPosition(), 'doubleclick')

    def on_mouse_move(self, event):
        if not event.Dragging():
            self.widget_scene.event(event.GetPosition(), 'hover')
            return
        self.widget_scene.event(event.GetPosition(), 'move')

    def on_right_mouse_down(self, event):
        self.scene.SetFocus()
        if event.AltDown():
            self.widget_scene.event(event.GetPosition(), 'rightdown+alt')
        elif event.ControlDown():
            self.widget_scene.event(event.GetPosition(), 'rightdown+control')
        else:
            self.widget_scene.event(event.GetPosition(), 'rightdown')

    def on_right_mouse_up(self, event):
        self.widget_scene.event(event.GetPosition(), 'rightup')

    def on_magnify_mouse(self, event):
        magnify = event.GetMagnification()
        if magnify > 0:
            self.widget_scene.event(event.GetPosition(), 'zoom-in')
        if magnify < 0:
            self.widget_scene.event(event.GetPosition(), 'zoom-out')

    def on_gesture(self, event):
        """
        This code requires WXPython 4.1 and the bind will fail otherwise.
        """
        if event.IsGestureStart():
            self.widget_scene.event(event.GetPosition(), 'gesture-start')
        elif event.IsGestureEnd():
            self.widget_scene.event(event.GetPosition(), 'gesture-end')
        else:
            try:
                zoom = event.GetZoomFactor()
            except AttributeError:
                zoom = 1.0
            self.widget_scene.event(event.GetPosition(), 'zoom %f' % zoom)

    def on_focus_lost(self, event):
        self.device.using('module', 'Console').write("-laser\nend\n")
        # event.Skip()

    def on_key_down(self, event):
        if event.GetKeyCode() == ord('W') and event.GetModifiers() == wx.MOD_CONTROL:
            self.Close(False)
            return
        keyvalue = get_key_name(event)
        keymap = self.device.device_root.keymap
        if keyvalue in keymap:
            action = keymap[keyvalue]
            self.device.using('module', 'Console').write(action + "\n")
        else:
            event.Skip()

    def on_key_up(self, event):
        keyvalue = get_key_name(event)
        keymap = self.device.device_root.keymap
        if keyvalue in keymap:
            action = keymap[keyvalue]
            if action.startswith('+'):
                # Keyup commands only trigger if the down command started with +
                action = '-' + action[1:]
                self.device.using('module', 'Console').write(action + "\n")
        else:
            event.Skip()

    def update_ribbon_position(self):
        p = self.device.device_root
        elements = p.elements
        bounds = elements.bounds()
        self.text_w.Enable(bounds is not None)
        self.text_h.Enable(bounds is not None)
        self.text_x.Enable(bounds is not None)
        self.text_y.Enable(bounds is not None)
        self.button_aspect_ratio.Enable(bounds is not None)
        if bounds is None:
            self.ribbon_position_ignore_update = True
            self.combo_box_units.SetSelection(self.ribbon_position_units)
            self.ribbon_position_ignore_update = False
            return
        x0, y0, x1, y1 = bounds
        conversion, name, index = (39.37, "mm", 0)
        if self.ribbon_position_units == 2:
            conversion, name, index = (1000.0, "in", 2)
        elif self.ribbon_position_units == 3:
            conversion, name, index = (1.0, "mil", 3)
        elif self.ribbon_position_units == 1:
            conversion, name, index = (393.7, "cm",  1)
        elif self.ribbon_position_units == 0:
            conversion, name, index = (39.37, "mm",  0)
        self.ribbon_position_name = name
        self.ribbon_position_x = (x0 / conversion)
        self.ribbon_position_y = (y0 / conversion)
        self.ribbon_position_w = ((x1 - x0) / conversion)
        self.ribbon_position_h = ((y1 - y0) / conversion)
        self.ribbon_position_ignore_update = True
        if self.ribbon_position_units != 4:
            self.text_x.SetValue("%.2f" % self.ribbon_position_x)
            self.text_y.SetValue("%.2f" % self.ribbon_position_y)
            self.text_w.SetValue("%.2f" % self.ribbon_position_w)
            self.text_h.SetValue("%.2f" % self.ribbon_position_h)
        else:
            self.text_x.SetValue("%.2f" % 100)
            self.text_y.SetValue("%.2f" % 100)
            self.text_w.SetValue("%.2f" % 100)
            self.text_h.SetValue("%.2f" % 100)
        self.combo_box_units.SetSelection(self.ribbon_position_units)
        self.ribbon_position_ignore_update = False

    def on_text_x(self, event):  # wxGlade: MyFrame.<event_handler>
        if self.ribbon_position_ignore_update:
            return
        try:
            if self.ribbon_position_units != 4:
                self.ribbon_position_x = float(self.text_x.GetValue())
        except ValueError:
            pass

    def on_text_y(self, event):  # wxGlade: MyFrame.<event_handler>
        if self.ribbon_position_ignore_update:
            return
        try:
            if self.ribbon_position_units != 4:
                self.ribbon_position_y = float(self.text_y.GetValue())
        except ValueError:
            pass

    def on_text_w(self, event):  # wxGlade: MyFrame.<event_handler>
        if self.ribbon_position_ignore_update:
            return
        try:
            new = float(self.text_w.GetValue())
            old = self.ribbon_position_w
            if self.ribbon_position_units == 4:
                ratio = new / 100.0
                if self.ribbon_position_aspect_ratio:
                    self.ribbon_position_ignore_update = True
                    self.text_h.SetValue("%.2f" % (ratio * 100))
                    self.ribbon_position_ignore_update = False
            else:
                ratio = new / old
                if self.ribbon_position_aspect_ratio:
                    self.ribbon_position_ignore_update = True
                    self.text_h.SetValue("%.2f" % (self.ribbon_position_h * ratio))
                    self.ribbon_position_ignore_update = False
        except (ValueError, ZeroDivisionError):
            pass

    def on_button_aspect_ratio(self, event):  # wxGlade: MyFrame.<event_handler>
        if self.ribbon_position_ignore_update:
            return
        if self.ribbon_position_aspect_ratio:
            self.button_aspect_ratio.SetBitmap(icons8_padlock_50.GetBitmap())
        else:
            self.button_aspect_ratio.SetBitmap(icons8_lock_50.GetBitmap())
        self.ribbon_position_aspect_ratio = not self.ribbon_position_aspect_ratio

    def on_text_h(self, event):  # wxGlade: MyFrame.<event_handler>
        if self.ribbon_position_ignore_update:
            return
        try:
            new = float(self.text_h.GetValue())
            old = self.ribbon_position_h
            if self.ribbon_position_units == 4:
                if self.ribbon_position_aspect_ratio:
                    self.ribbon_position_ignore_update = True
                    self.text_w.SetValue("%.2f" % (new))
                    self.ribbon_position_ignore_update = False
            else:
                if self.ribbon_position_aspect_ratio:
                    self.ribbon_position_ignore_update = True
                    self.text_w.SetValue("%.2f" % (self.ribbon_position_w * (new / old)))
                    self.ribbon_position_ignore_update = False
        except (ValueError, ZeroDivisionError):
            pass

    def on_text_dim_enter(self, event):
        if self.ribbon_position_units == 4:
            ratio_w = float(self.text_w.GetValue()) / 100.0
            ratio_h = float(self.text_h.GetValue()) / 100.0
            self.ribbon_position_w *= ratio_w
            self.ribbon_position_h *= ratio_h
        else:
            w = float(self.text_w.GetValue())
            h = float(self.text_h.GetValue())
            self.ribbon_position_w = w
            self.ribbon_position_h = h
        self.device.open('module', 'Console').write('resize %f%s %f%s %f%s %f%s\n' % (self.ribbon_position_x,
                                                                                      self.ribbon_position_name,
                                                                                      self.ribbon_position_y,
                                                                                      self.ribbon_position_name,
                                                                                      self.ribbon_position_w,
                                                                                      self.ribbon_position_name,
                                                                                      self.ribbon_position_h,
                                                                                      self.ribbon_position_name))
        self.update_ribbon_position()

    def on_text_pos_enter(self, event):
        if self.ribbon_position_units == 4:
            ratio_x = float(self.text_x.GetValue()) / 100.0
            ratio_y = float(self.text_y.GetValue()) / 100.0
            self.ribbon_position_x *= ratio_x
            self.ribbon_position_y *= ratio_y
        else:
            x = float(self.text_x.GetValue())
            y = float(self.text_y.GetValue())
            self.ribbon_position_x = x
            self.ribbon_position_y = y
        self.device.open('module', 'Console').write('resize %f%s %f%s %f%s %f%s\n' % (self.ribbon_position_x,
                                                                                      self.ribbon_position_name,
                                                                                      self.ribbon_position_y,
                                                                                      self.ribbon_position_name,
                                                                                      self.ribbon_position_w,
                                                                                      self.ribbon_position_name,
                                                                                      self.ribbon_position_h,
                                                                                      self.ribbon_position_name))
        self.update_ribbon_position()

    def on_combo_box_units(self, event):  # wxGlade: MyFrame.<event_handler>
        if self.ribbon_position_ignore_update:
            return
        self.ribbon_position_units = self.combo_box_units.GetSelection()
        self.update_ribbon_position()

    def on_click_new(self, event):  # wxGlade: MeerK40t.<event_handler>
        kernel = self.device.device_root
        self.working_file = None
        kernel.elements.clear_all()
        self.clear_laserpath()
        self.request_refresh()
        self.device.signal('rebuild_tree', 0)

    def on_click_open(self, event):  # wxGlade: MeerK40t.<event_handler>
        # This code should load just specific project files rather than all importable formats.
        files = self.device.load_types()
        with wx.FileDialog(self, _("Open"), wildcard=files,
                           style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind
            pathname = fileDialog.GetPath()
            self.load(pathname)

    def on_click_pause(self, event):
        self.device.using("module", "Console").write("control Realtime Pause_Resume\n")

    def on_click_save(self, event):
        if self.working_file is None:
            self.on_click_save_as(event)
        else:
            self.device.save(self.working_file)

    def on_click_save_as(self, event):
        files = self.device.save_types()
        with wx.FileDialog(self, "Save Project", wildcard=files,
                           style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind
            pathname = fileDialog.GetPath()
            if not pathname.lower().endswith('.svg'):
                pathname += '.svg'
            self.device.save(pathname)
            self.working_file = pathname

    def on_click_exit(self, event):  # wxGlade: MeerK40t.<event_handler>
        try:
            self.Close()
        except RuntimeError:
            pass

    def on_click_zoom_out(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Zoomout button press
        """
        m = self.scene.ClientSize / 2
        self.widget_scene.widget_root.scene_widget.scene_post_scale(1.0 / 1.5, 1.0 / 1.5, m[0], m[1])
        self.request_refresh()

    def on_click_zoom_in(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Zoomin button press
        """
        m = self.scene.ClientSize / 2
        self.widget_scene.widget_root.scene_widget.scene_post_scale(1.5, 1.5, m[0], m[1])
        self.request_refresh()

    def on_click_zoom_size(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Zoom size button press.
        """
        elements = self.device.device_root.elements
        bbox = elements.bounds()
        if bbox is None:
            bedwidth = self.device.bed_width
            bedheight = self.device.bed_height
            bbox = (0, 0, bedwidth * MILS_IN_MM, bedheight * MILS_IN_MM)
        self.widget_scene.widget_root.focus_viewport_scene(bbox, self.scene.ClientSize)

    def toggle_draw_mode(self, bits):
        """
        Toggle the draw mode.
        :param bits: Bit to toggle.
        :return: Toggle function.
        """

        def toggle(event):
            self.device.draw_mode ^= bits
            self.device.signal('draw_mode', self.device.draw_mode)
            self.request_refresh()

        return toggle

    def open_speedcode_gear_dialog(self):
        dlg = wx.TextEntryDialog(self, _("Enter Forced Gear"), _("Gear Entry"), '')
        dlg.SetValue('')

        if dlg.ShowModal() == wx.ID_OK:
            value = dlg.GetValue()
            if value in ('0', '1', '2', '3', '4'):
                self.device._stepping_force = int(value)
            else:
                self.device._stepping_force = None
        dlg.Destroy()

    def open_fps_dialog(self):
        dlg = wx.TextEntryDialog(self, _("Enter FPS Limit"), _("FPS Limit Entry"), '')
        dlg.SetValue('')

        if dlg.ShowModal() == wx.ID_OK:
            fps = dlg.GetValue()
            try:
                self.set_fps(int(fps))
            except ValueError:
                pass
        dlg.Destroy()

    def open_transform_dialog(self):
        dlg = wx.TextEntryDialog(self, _(
            "Enter SVG Transform Instruction e.g. 'scale(1.49, 1, $x, $y)', rotate, translate, etc..."),
                                 _("Transform Entry"), '')
        dlg.SetValue('')

        if dlg.ShowModal() == wx.ID_OK:
            p = self.device
            kernel = self.device.device_root
            m = str(dlg.GetValue())
            m = m.replace('$x', str(p.current_x))
            m = m.replace('$y', str(p.current_y))
            mx = Matrix(m)
            wmils = p.bed_width * 39.37
            hmils = p.bed_height * 39.37
            mx.render(ppi=1000, width=wmils, height=hmils)
            if mx.is_identity():
                dlg.Destroy()
                dlg = wx.MessageDialog(None, _("The entered command does nothing."),
                                       _("Non-Useful Matrix."), wx.OK | wx.ICON_WARNING)
                result = dlg.ShowModal()
                dlg.Destroy()
            else:
                for element in kernel.elements.elems():
                    try:
                        element *= mx
                        element.modified()
                    except AttributeError:
                        pass

    def open_fill_dialog(self):
        kernel = self.device.device_root
        elements = kernel.elements
        first_selected = elements.first_element(selected=True)
        if first_selected is None:
            return
        data = wx.ColourData()
        if first_selected.fill is not None and first_selected.fill != 'none':
            data.SetColour(wx.Colour(swizzlecolor(first_selected.fill)))
        dlg = wx.ColourDialog(self, data)
        if dlg.ShowModal() == wx.ID_OK:
            data = dlg.GetColourData()
            color = data.GetColour()
            rgb = color.GetRGB()
            color = swizzlecolor(rgb)
            color = Color(color, 1.0)
            for elem in elements.elems(emphasized=True):
                elem.fill = color
                elem.altered()

    def open_stroke_dialog(self):
        kernel = self.device.device_root
        elements = kernel.elements
        first_selected = elements.first_element(emphasized=True)
        if first_selected is None:
            return
        data = wx.ColourData()
        if first_selected.stroke is not None and first_selected.stroke != 'none':
            data.SetColour(wx.Colour(swizzlecolor(first_selected.stroke)))
        dlg = wx.ColourDialog(self, data)
        if dlg.ShowModal() == wx.ID_OK:
            data = dlg.GetColourData()
            color = data.GetColour()
            rgb = color.GetRGB()
            color = swizzlecolor(rgb)
            color = Color(color, 1.0)
            for elem in elements.elems(emphasized=True):
                elem.stroke = color
                elem.altered()

    def open_flip_dialog(self):
        dlg = wx.TextEntryDialog(self, _(
            "Material must be jigged at 0,0 either home or home offset.\nHow wide is your material (give units: in, mm, cm, px, etc)?"),
                                 _("Double Side Flip"), '')
        dlg.SetValue('')
        if dlg.ShowModal() == wx.ID_OK:
            p = self.device
            kernel = p.device_root
            wmils = p.bed_width * MILS_IN_MM
            hmils = p.bed_height * MILS_IN_MM
            length = Length(dlg.GetValue()).value(ppi=1000.0, relative_length=wmils)
            mx = Matrix()
            mx.post_scale(-1.0, 1, length / 2.0, 0)
            for element in kernel.elements.elems(emphasized=True):
                try:
                    element *= mx
                    element.modified()
                except AttributeError:
                    pass
        dlg.Destroy()

    def open_path_dialog(self):
        dlg = wx.TextEntryDialog(self, _("Enter SVG Path Data"), _("Path Entry"), '')
        dlg.SetValue('')

        if dlg.ShowModal() == wx.ID_OK:
            kernel = self.device.device_root
            path = Path(dlg.GetValue())
            path.stroke = 'blue'
            p = abs(path)
            kernel.elements.add_elem(p)
            self.device.classify(p)
        dlg.Destroy()

    def egv_import(self):
        pathname = None
        files = "*.egv"
        with wx.FileDialog(self, _("Import EGV"), wildcard=files,
                           style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind
            pathname = fileDialog.GetPath()
        if pathname is None:
            return
        with wx.BusyInfo(_("Loading File...")):
            console = self.device.using('module', 'Console')
            console.write("egv_import %s\n" % pathname)
            return

    def egv_export(self):
        pathname = None
        files = "*.egv"
        with wx.FileDialog(self, _("Export EGV"), wildcard=files, style=wx.FD_SAVE) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind
            pathname = fileDialog.GetPath()
        if pathname is None:
            return
        with wx.BusyInfo(_("Saving File...")):
            console = self.device.using('module', 'Console')
            console.write("egv_export %s\n" % pathname)
            return

    def apply_rotary_scale(self):
        kernel = self.device.device_root
        sx = self.device.scale_x
        sy = self.device.scale_y
        p = self.device

        mx = Matrix("scale(%f, %f, %f, %f)" % (sx, sy, p.current_x, p.current_y))
        for element in kernel.elements.elems():
            try:
                element *= mx
                element.modified()
            except AttributeError:
                pass

    def toggle_rotary_view(self):
        if self._rotary_view:
            self.widget_scene.rotary_stretch()
        else:
            self.widget_scene.rotary_unstretch()
        self._rotary_view = not self._rotary_view

    def run_jog_transition_finish_test(self):
        return self.run_jog_transition_test(COMMAND_JOG_FINISH)

    def run_jog_transition_switch_test(self):
        return self.run_jog_transition_test(COMMAND_JOG_SWITCH)

    def run_jog_transition_test(self, command=COMMAND_JOG):
        """"
        The Jog Transition Test is intended to test the jogging
        """

        def jog_transition_test():
            yield COMMAND_SET_ABSOLUTE
            yield COMMAND_MODE_RAPID
            yield COMMAND_HOME
            yield COMMAND_LASER_OFF
            yield COMMAND_WAIT_FINISH
            yield COMMAND_MOVE, 3000, 3000
            yield COMMAND_WAIT_FINISH
            yield COMMAND_LASER_ON
            yield COMMAND_WAIT, 0.05
            yield COMMAND_LASER_OFF
            yield COMMAND_WAIT_FINISH

            yield COMMAND_SET_SPEED, 10.0

            def pos(i):
                if i < 3:
                    x = 200
                elif i < 6:
                    x = -200
                else:
                    x = 0
                if i % 3 == 0:
                    y = 200
                elif i % 3 == 1:
                    y = -200
                else:
                    y = 0
                return x, y

            for q in range(8):
                top = q & 1
                left = q & 2
                x_val = q & 3
                yield COMMAND_SET_DIRECTION, top, left, x_val, not x_val
                yield COMMAND_MODE_PROGRAM
                for j in range(9):
                    jx, jy = pos(j)
                    for k in range(9):
                        kx, ky = pos(k)
                        yield COMMAND_MOVE, 3000, 3000
                        yield COMMAND_MOVE, 3000+jx, 3000+jy
                        yield command, 3000+jx+kx, 3000+jy+ky
                yield COMMAND_MOVE, 3000, 3000
                yield COMMAND_MODE_RAPID
                yield COMMAND_WAIT_FINISH
                yield COMMAND_LASER_ON
                yield COMMAND_WAIT, 0.05
                yield COMMAND_LASER_OFF
                yield COMMAND_WAIT_FINISH
        self.device.spooler.job(jog_transition_test)

    def run_home_and_dot_test(self):

        def home_dot_test():
            for i in range(25):
                yield COMMAND_SET_ABSOLUTE
                yield COMMAND_MODE_RAPID
                yield COMMAND_HOME
                yield COMMAND_LASER_OFF
                yield COMMAND_WAIT_FINISH
                yield COMMAND_MOVE, 3000, 3000
                yield COMMAND_WAIT_FINISH
                yield COMMAND_LASER_ON
                yield COMMAND_WAIT, 0.05
                yield COMMAND_LASER_OFF
                yield COMMAND_WAIT_FINISH
            yield COMMAND_HOME
            yield COMMAND_WAIT_FINISH

        self.device.spooler.job(home_dot_test)

    def launch_help(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Launch help wiki

        :param event:
        :return:
        """
        import webbrowser
        webbrowser.open(MEERK40T_HELP, new=0, autoraise=True)

    def launch_website(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Launch meerk40t mainpage

        :param event:
        :return:
        """
        import webbrowser
        webbrowser.open(MEERK40T_WEBSITE, new=0, autoraise=True)


NODE_ROOT = 0
NODE_OPERATION_BRANCH = 10
NODE_OPERATION = 11
NODE_OPERATION_ELEMENT = 12
NODE_ELEMENTS_BRANCH = 20
NODE_ELEMENT = 21
NODE_FILES_BRANCH = 30
NODE_FILE_FILE = 31
NODE_FILE_ELEMENT = 32


class Node(list):
    """
    Generic Node Type for use with RootNode
    Creating the object registers the position in the tree according to the parent and root.
    Deleting the object deregisters the node in the tree.
    """

    def __init__(self, node_type, data_object, parent, root, pos=None, name=None):
        list.__init__(self)
        self.parent = parent
        self.root = root
        self.object = data_object
        self.type = node_type
        self.name = name
        if self.name is None:
            try:
                self.name = self.object.id
                if self.name is None:
                    self.name = str(self.object)
            except AttributeError:
                self.name = str(self.object)
        parent.append(self)
        self.filepath = None
        try:
            self.bounds = data_object.bbox()
        except AttributeError:
            self.bounds = None
        parent_item = parent.item
        tree = root.tree
        if pos is None:
            item = tree.AppendItem(parent_item, self.name)
        else:
            item = tree.InsertItem(parent_item, pos, self.name)
        self.item = item
        if id(data_object) in self.root.tree_lookup:
            self.root.tree_lookup[id(data_object)].append(self)
        else:
            self.root.tree_lookup[id(data_object)] = [self]
        tree.SetItemData(self.item, self)
        try:
            stroke = data_object.values[SVG_ATTR_STROKE]
            color = wx.Colour(swizzlecolor(Color(stroke).value))
            tree.SetItemTextColour(item, color)
        except AttributeError:
            pass
        except KeyError:
            pass
        except TypeError:
            pass
        self.set_icon()
        root.notify_added(self)

    def __str__(self):
        return "Node(%s, %d)" % (str(self.item), self.type)

    def __repr__(self):
        return "Node(%d, %s, %s, %s)" % (self.type, str(self.object), str(self.parent), str(self.root))

    def update_name(self):
        self.name = None
        try:
            self.name = self.object.id
        except AttributeError:
            pass
        if self.name is None:
            self.name = str(self.object)
        self.root.tree.SetItemText(self.item, self.name)
        try:
            stroke = self.object.values[SVG_ATTR_STROKE]
            color = wx.Colour(swizzlecolor(Color(stroke).value))
            self.root.tree.SetItemTextColour(self.item, color)
        except AttributeError:
            pass
        try:
            color = self.object.color
            c = wx.Colour(swizzlecolor(Color(color)))
            self.root.tree.SetItemTextColour(self.item, c)
        except AttributeError:
            pass

    def remove_node(self):
        for q in self:
            q.remove_node()
        root = self.root
        links = root.tree_lookup[id(self.object)]
        links.remove(self)
        self.parent.remove(self)
        try:
            root.tree.Delete(self.item)
        except RuntimeError:
            return
        root.notify_removed(self)
        self.item = None
        self.parent = None
        self.root = None
        self.type = -1

    def move_node(self, new_parent, pos=None):
        tree = self.root.tree
        item = self.item
        image = tree.GetItemImage(item)
        data = tree.GetItemData(item)
        color = tree.GetItemTextColour(item)
        tree.Delete(item)
        if pos is None:
            self.item = tree.AppendItem(new_parent.item, self.name)
        else:
            self.item = tree.InsertItem(new_parent.item, pos, self.name)
        item = self.item
        tree.SetItemImage(item, image)
        tree.SetItemData(item, data)
        tree.SetItemTextColour(item, color)

    def __eq__(self, other):
        return other is self

    def set_color(self, color=None):
        root = self.root
        item = self.item
        tree = root.tree
        if color is None:
            tree.SetItemTextColour(item, None)
        else:
            tree.SetItemTextColour(item, wx.Colour(swizzlecolor(color)))

    def set_icon(self, icon=None):
        root = self.root
        drawmode = root.device.draw_mode
        if drawmode & DRAW_MODE_ICONS != 0:
            return
        item = self.item
        data_object = self.object
        tree = root.tree
        if icon is None:
            if isinstance(data_object, SVGImage):
                image = self.root.renderer.make_thumbnail(data_object.image, width=20, height=20)
                image_id = self.root.tree_images.Add(bitmap=image)
                tree.SetItemImage(item, image=image_id)
            if isinstance(data_object, (Path, SVGText)):
                image = self.root.renderer.make_raster(data_object, data_object.bbox(), width=20, height=20,
                                                       bitmap=True)
                if image is not None:
                    image_id = self.root.tree_images.Add(bitmap=image)
                    tree.SetItemImage(item, image=image_id)
                    tree.Update()
        else:
            image_id = self.root.tree_images.Add(bitmap=icon)
            tree.SetItemImage(item, image=image_id)

    def bbox(self):
        return OperationPreprocessor.bounding_box(self.object)

    def objects_of_children(self, types):
        if isinstance(self.object, types):
            yield self.object
        for q in self:
            for o in q.objects_of_children(types):
                yield o

    def contains_path(self):
        if isinstance(self.object, Path):
            return True
        for q in self:
            if q.contains_path():
                return True
        return False

    def contains_image(self):
        if isinstance(self.object, SVGImage):
            return True
        for q in self:
            if q.contains_image():
                return True
        return False

    def contains_text(self):
        if isinstance(self.object, SVGText):
            return True
        for q in self:
            if q.contains_text():
                return True
        return False


class RootNode(list):
    """"Nodes are the presentation layer used to wrap the LaserOperations and the SVGElement classes. Stored in the
    elements. This serves to help with menu creation, naming, drag and drop.

    The tree is structured with three main sub-elements of the RootNode, these are the Operations, the Elements, and
    the files. The primary distinction between the items within elements is that an item appears twice in the tree with
    different highlighting, selection, and interactions.

    The Operations each contain a list of elements they run in order and are stored within each operation.

    Elements store the scene's graphics elements. The Elements are stored in their order. Changes in this structure
    should reflect those changes back to structure of elements.

    Deleting an element from the tree should remove that element from any operation using it.

    Deleting an operation should make no change to the graphical elements structure.

    All the nodes store a reference to their given tree item. So that a determination can be made when those items have
    changed and provide piecemeal updates to the tree.
    """

    def __init__(self, device, gui, elements):
        list.__init__(self)
        self.root = self
        self.parent = self
        self.object = "Project"
        self.name = "Project"
        self.type = NODE_ROOT

        self.device = device
        self.gui = gui
        self.tree = gui.tree
        self.renderer = gui.renderer
        self.elements = elements

        self.item = None
        self.dragging_node = None
        self.dragging_parent = None
        self.tree_images = None
        self.tree_lookup = None
        self.node_elements = None
        self.node_operations = None
        self.node_files = None
        self.do_not_select = False
        self.device.signal('rebuild_tree')

    def refresh_tree(self, node=None):
        """Any tree elements currently displaying wrong data as per elements should be updated to display
        the proper values and contexts and icons. This will not happen for any elements currently within
        a closed branch."""
        if node is None:
            node = self.root.item
        if node is None:
            return
        tree = self.tree

        child, cookie = tree.GetFirstChild(node)
        while child.IsOk():
            child_node = self.tree.GetItemData(child)
            element = child_node.object
            tree.SetItemBackgroundColour(child, None)
            try:
                if element.highlighted:
                    tree.SetItemBackgroundColour(child, wx.YELLOW)
                elif element.emphasized:
                    tree.SetItemBackgroundColour(child, wx.CYAN)
                elif element.selected:
                    tree.SetItemBackgroundColour(child, wx.RED)
            except AttributeError:
                pass
            self.refresh_tree(child)
            child, cookie = tree.GetNextChild(node, cookie)

    def rebuild_tree(self):
        kernel = self.device.device_root
        elements = kernel.elements
        self.tree.DeleteAllItems()
        self.tree_images = wx.ImageList()
        self.tree_images.Create(width=20, height=20)
        self.tree_lookup = {}
        self.tree.SetImageList(self.tree_images)
        self.item = self.tree.AddRoot(self.name)
        self.node_operations = Node(NODE_OPERATION_BRANCH, elements._operations, self, self, name=_("Operations"))
        self.node_operations.set_icon(icons8_laser_beam_20.GetBitmap())
        self.build_tree(self.node_operations, list(elements.ops()))
        for n in self.node_operations:
            if n.object.operation in ("Raster", "Image"):
                n.set_icon(icons8_direction_20.GetBitmap())
            else:
                n.set_icon(icons8_laser_beam_20.GetBitmap())
            try:
                c = n.object.color
                n.set_color(c)
            except AttributeError:
                pass

        self.node_elements = Node(NODE_ELEMENTS_BRANCH, elements._elements, self, self, name=_("Elements"))
        self.node_elements.set_icon(icons8_vector_20.GetBitmap())
        self.build_tree(self.node_elements, list(elements.elems()))

        self.node_files = Node(NODE_FILES_BRANCH, elements.files(), self, self, name=_("Files"))
        self.node_files.set_icon(icons8_file_20.GetBitmap())
        self.build_tree(self.node_files, elements.files())

        for n in self.node_files:
            n.set_icon(icons8_file_20.GetBitmap())

        self.tree.ExpandAll()

    def build_tree(self, parent_node, objects):
        if isinstance(objects, list):
            for obj in objects:
                node = Node(parent_node.type + 1, obj, parent_node, self)
                self.build_tree(node, obj)
        elif isinstance(objects, dict):
            for obj_key, obj_value in objects.items():
                node = Node(parent_node.type + 1, obj_key, parent_node, self)
                node.filepath = obj_key
                if not isinstance(obj_value, (list, dict)):
                    obj_value = [obj_value]
                self.build_tree(node, obj_value)

    def notify_added(self, node):
        pass

    def notify_removed(self, node):
        pass

    def notify_tree_data_change(self):
        self.device.signal('rebuild_tree', 0)

    def notify_tree_data_cleared(self):
        self.device.signal('rebuild_tree', 0)

    def on_element_update(self, *args):
        element = args[0]
        try:
            nodes = self.tree_lookup[id(element)]
            for node in nodes:
                node.update_name()
        except KeyError:
            pass

    def on_drag_begin_handler(self, event):
        """
        Drag handler begin for the tree.

        :param event:
        :return:
        """
        self.dragging_node = None

        drag_item = event.GetItem()
        if drag_item is None:
            event.Skip()
            return
        if drag_item.ID is None:
            event.Skip()
            return
        if not drag_item.IsOk():
            event.Skip()
            return
        node_data = self.tree.GetItemData(drag_item)
        if node_data.type == NODE_ELEMENTS_BRANCH or node_data.type == NODE_OPERATION_BRANCH or \
                node_data.type == NODE_FILES_BRANCH or node_data.type == NODE_FILE_ELEMENT or node_data.type == NODE_FILE_FILE:
            event.Skip()
            return
        self.dragging_node = node_data
        event.Allow()

    def on_drag_end_handler(self, event):
        """
        Drag end handler for the tree

        :param event:
        :return:
        """
        if self.dragging_node is None:
            event.Skip()
            return
        drag_node = self.dragging_node
        self.dragging_node = None

        drop_item = event.GetItem()
        if drop_item is None:
            event.Skip()
            return
        if drop_item.ID is None:
            event.Skip()
            return

        drop_node = self.tree.GetItemData(drop_item)
        if drop_node is None or drop_node == drag_node:
            event.Skip()
            return
        if drag_node.type == NODE_ELEMENT:
            if drop_node.type == NODE_OPERATION:
                # Dragging element into operation adds that element to the op.
                drop_node.object.insert(0, drag_node.object)
                self.notify_tree_data_change()
                event.Allow()
                return
            elif drop_node.type == NODE_ELEMENT:
                # Dragging element into element.
                if drag_node.parent is drop_node.parent:
                    # Dragging and dropping within the same parent puts insert on other side.
                    drag_index = drag_node.parent.index(drag_node)
                    drag_node.parent.object[drag_index] = None
                    drop_index = drop_node.parent.index(drop_node)
                    if drag_index > drop_index:
                        drop_node.parent.object.insert(drop_index, drag_node.object)
                    else:
                        drop_node.parent.object.insert(drop_index + 1, drag_node.object)
                else:
                    drag_index = drag_node.parent.index(drag_node)
                    drag_node.parent.object[drag_index] = None

                    drop_index = drop_node.parent.index(drop_node)
                    drop_node.parent.object.insert(drop_index, drag_node.object)

                nodes = [n for n in drag_node.parent.object if n is not None]
                drag_node.parent.object.clear()
                drag_node.parent.object.extend(nodes)
                self.notify_tree_data_change()
                event.Allow()
                return
            elif drop_node.type == NODE_OPERATION_ELEMENT:
                drop_index = drop_node.parent.object.index(drop_node.object)
                drop_node.parent.object.insert(drop_index, drag_node.object)
                event.Allow()
                self.notify_tree_data_change()
                return
            elif drop_node.type == NODE_OPERATION_BRANCH:
                obj = drag_node.object
                self.device.classify([obj])
                event.Allow()
                self.notify_tree_data_change()
        elif drag_node.type == NODE_OPERATION_ELEMENT:
            if drop_node.type == NODE_OPERATION:
                # Dragging from op element to operation.
                drag_index = drag_node.parent.index(drag_node)
                drag_node.parent.object[drag_index] = None
                drop_node.object.append(drag_node.object)
                nodes = [op_elem for op_elem in drag_node.parent.object if op_elem is not None]
                drag_node.parent.object.clear()
                drag_node.parent.object.extend(nodes)
                event.Allow()
                self.notify_tree_data_change()
                return
            if drop_node.type == NODE_OPERATION_ELEMENT:
                if drag_node.parent is drop_node.parent:
                    # Dragging and dropping within the same parent puts insert on other side.
                    drag_index = drag_node.parent.index(drag_node)
                    drag_node.parent.object[drag_index] = None
                    drop_index = drop_node.parent.index(drop_node)
                    if drag_index > drop_index:
                        drop_node.parent.object.insert(drop_index, drag_node.object)
                    else:
                        drop_node.parent.object.insert(drop_index + 1, drag_node.object)
                else:
                    drag_index = drag_node.parent.index(drag_node)
                    drag_node.parent.object[drag_index] = None

                    drop_index = drop_node.parent.index(drop_node)
                    drop_node.parent.object.insert(drop_index, drag_node.object)

                nodes = [n for n in drag_node.parent.object if n is not None]
                drag_node.parent.object.clear()
                drag_node.parent.object.extend(nodes)

                event.Allow()
                self.notify_tree_data_change()
                return
        elif drag_node.type == NODE_OPERATION:
            if drop_node.type == NODE_OPERATION:
                # Dragging operation to different operation.
                ops = drop_node.parent
                drop_pos = ops.index(drop_node)
                drag_pos = ops.index(drag_node)
                ops.object[drag_pos] = None
                if drag_pos > drop_pos:
                    ops.object.insert(drop_pos, drag_node.object)
                else:
                    ops.object.insert(drop_pos + 1, drag_node.object)

                nodes = [n for n in ops.object if n is not None]
                ops.object.clear()
                ops.object.extend(nodes)
                event.Allow()
                self.notify_tree_data_change()
                return
            elif drop_node.type == NODE_OPERATION_BRANCH:
                # Dragging operation to op branch.
                pass

    def on_item_right_click(self, event):
        """
        Right click of element in tree.

        :param event:
        :return:
        """
        item = event.GetItem()
        if item is None:
            return
        node = self.tree.GetItemData(item)

        self.root.create_menu(self.gui, node)

    def on_item_activated(self, event):
        """
        Tree item is double-clicked. Launches PropertyWindow associated with that object.

        :param event:
        :return:
        """
        item = event.GetItem()
        node = self.tree.GetItemData(item)
        self.activated_node(node)

    def activated_node(self, node):
        if node is not None:
            self.activated_object(node.object)

    def activate_selected_node(self):
        self.activated_object(self.elements.first_element(selected=True))

    def activated_object(self, obj):
        if obj is None:
            return
        if isinstance(obj, LaserOperation):
            self.device.open('window', "OperationProperty", self.gui, obj)
        elif isinstance(obj, Path):
            self.device.open('window', "PathProperty", self.gui, obj)
        elif isinstance(obj, SVGText):
            self.device.open('window', "TextProperty", self.gui, obj)
        elif isinstance(obj, SVGImage):
            self.device.open('window', "ImageProperty", self.gui, obj)
        elif isinstance(obj, SVGElement):
            self.device.open('window', "PathProperty", self.gui, obj)

    def on_item_selection_changed(self, event):
        """
        Tree menu item is changed. Modify the selection.

        :param event:
        :return:
        """
        if self.do_not_select:
            return
        selected = [self.tree.GetItemData(item).object for item in self.tree.GetSelections()]
        self.elements.set_selected(selected)
        self.refresh_tree()
        self.gui.request_refresh()
        event.Allow()

    def select_in_tree_by_selected(self):
        """
        :return:
        """
        self.do_not_select = True
        for e in self.elements.elems():
            try:
                nodes = self.tree_lookup[id(e)]
                for n in nodes:
                    if n.type == NODE_ELEMENT:
                        self.tree.SelectItem(n.item, e.selected)
            except (KeyError, TypeError):
                self.device.signal('rebuild_tree', 0)
                break
        self.refresh_tree()
        self.gui.request_refresh()
        self.do_not_select = False

    def contains(self, box, x, y=None):
        if y is None:
            y = x[1]
            x = x[0]
        return box[0] <= x <= box[2] and box[1] <= y <= box[3]

    def create_menu(self, gui, node):
        """
        Create menu items. This is used for both the scene and the tree to create menu items.

        :param gui: Gui used to create menu items.
        :param node: The Node clicked on for the generated menu.
        :return:
        """
        if node is None:
            return
        if isinstance(node, SVGElement):
            # If this is called with an SVGElement rather than a Node. Convert them.
            match_object = node
            node = None
            for element in self.node_elements:
                if element.object is match_object:
                    node = element
                    break
        if node is None:
            return
        menu = wx.Menu()
        if isinstance(node, RootNode):
            return
        elements = self.elements

        t = node.type
        selections = [self.tree_lookup[id(e)] for e in elements.elems(emphasized=True)]
        locked = False
        try:
            if node.object.lock:
                locked = True
        except (AttributeError, ValueError):
            pass

        def combined(*args):
            for listv in args:
                for itemv in listv:
                    yield itemv

        selections = [s for s in combined(*selections) if s.type == t]
        if t == NODE_OPERATION:
            gui.Bind(wx.EVT_MENU, self.menu_execute(node),
                     menu.Append(wx.ID_ANY, _("Execute Job"), "", wx.ITEM_NORMAL))
        if t == NODE_OPERATION_BRANCH:
            gui.Bind(wx.EVT_MENU, self.menu_console("operation * delete"),
                     menu.Append(wx.ID_ANY, _("Clear All"), "", wx.ITEM_NORMAL))
        if t == NODE_FILES_BRANCH:
            gui.Bind(wx.EVT_MENU, self.menu_clear_all_files_branch(node),
                     menu.Append(wx.ID_ANY, _("Clear All"), "", wx.ITEM_NORMAL))
        if t == NODE_ELEMENTS_BRANCH:
            gui.Bind(wx.EVT_MENU, self.menu_console('element * delete'),
                     menu.Append(wx.ID_ANY, _("Clear All"), "", wx.ITEM_NORMAL))
        if t == NODE_OPERATION:
            gui.Bind(wx.EVT_MENU, self.menu_clear_all_operation(node),
                     menu.Append(wx.ID_ANY, _("Clear All"), "", wx.ITEM_NORMAL))
        if t in (NODE_OPERATION, NODE_ELEMENT, NODE_FILE_FILE, NODE_OPERATION_ELEMENT):
            gui.Bind(wx.EVT_MENU, self.menu_remove(node),
                     menu.Append(wx.ID_ANY, _("Remove: %s") % str(node.name)[:10], "", wx.ITEM_NORMAL))
        if t in (NODE_ELEMENT, NODE_OPERATION_ELEMENT) and len(selections) > 1:
            gui.Bind(wx.EVT_MENU, self.menu_console('element delete'),
                     menu.Append(wx.ID_ANY, _("Remove: %d objects") % len(selections), "", wx.ITEM_NORMAL))
        if t == NODE_OPERATION_ELEMENT:
            duplicate_menu_eop = wx.Menu()
            for i in range(1, 10):
                gui.Bind(wx.EVT_MENU, self.menu_duplicate_element_op(node, i),
                         duplicate_menu_eop.Append(wx.ID_ANY, _("Make %d copies.") % i, "", wx.ITEM_NORMAL))
            menu.AppendSubMenu(duplicate_menu_eop, _("Clone Reference"))
            duplicate_menu_eop = wx.Menu()
            for i in range(1, 10):
                gui.Bind(wx.EVT_MENU, self.menu_duplicate(node, i),
                         duplicate_menu_eop.Append(wx.ID_ANY, _("Make %d copies.") % i, "", wx.ITEM_NORMAL))
            menu.AppendSubMenu(duplicate_menu_eop, _("Duplicate"))
        if t in (NODE_OPERATION, NODE_ELEMENTS_BRANCH, NODE_OPERATION_BRANCH) and len(node) > 1:
            gui.Bind(wx.EVT_MENU, self.menu_reverse_order(node),
                     menu.Append(wx.ID_ANY, _("Reverse Layer Order"), "", wx.ITEM_NORMAL))
        if t == NODE_ROOT:
            pass
        elif t == NODE_OPERATION_BRANCH:
            gui.Bind(wx.EVT_MENU, self.menu_reclassify_operations(node),
                     menu.Append(wx.ID_ANY, _("Refresh Classification"), "", wx.ITEM_NORMAL))
            gui.Bind(wx.EVT_MENU, lambda e: self.device.device_root.elements.load_default(),
                     menu.Append(wx.ID_ANY, _("Set Other/Blue/Red Classify"), "", wx.ITEM_NORMAL))
            gui.Bind(wx.EVT_MENU, lambda e: self.device.device_root.elements.load_default2(),
                     menu.Append(wx.ID_ANY, _("Set Basic Classification"), "", wx.ITEM_NORMAL))
            gui.Bind(wx.EVT_MENU, lambda e: self.device.device_root.elements.add_op(LaserOperation()),
                     menu.Append(wx.ID_ANY, _("Add Operation"), "", wx.ITEM_NORMAL))

        elif t == NODE_ELEMENTS_BRANCH:
            gui.Bind(wx.EVT_MENU, self.menu_reclassify_operations(node),
                     menu.Append(wx.ID_ANY, _("Reclassify Operations"), "", wx.ITEM_NORMAL))
        elif t == NODE_FILES_BRANCH:
            pass
        elif t == NODE_OPERATION:
            operation_convert_submenu = wx.Menu()
            for name in ("Raster", "Engrave", "Cut", "Image"):
                menu_op = operation_convert_submenu.Append(wx.ID_ANY, _("Convert %s") % name, "", wx.ITEM_NORMAL)
                gui.Bind(wx.EVT_MENU, self.menu_convert_operation(node, name), menu_op)
            menu.AppendSubMenu(operation_convert_submenu, _("Convert Operation"))

            gui.Bind(wx.EVT_MENU, self.menu_duplicate_operation(node),
                     menu.Append(wx.ID_ANY, _("Duplicate Operation"), "", wx.ITEM_NORMAL))
            if node.object.operation in ("Image", "Engrave", "Cut"):
                duplicate_menu = wx.Menu()
                gui.Bind(wx.EVT_MENU, self.menu_passes(node, 1),
                         duplicate_menu.Append(wx.ID_ANY, _("Add 1 pass."), "", wx.ITEM_NORMAL))
                for i in range(2, 10):
                    gui.Bind(wx.EVT_MENU, self.menu_passes(node, i),
                             duplicate_menu.Append(wx.ID_ANY, _("Add %d passes.") % i, "", wx.ITEM_NORMAL))
                menu.AppendSubMenu(duplicate_menu, _("Passes"))

            if node.object.operation in ("Raster", "Image"):
                raster_step_menu = wx.Menu()
                for i in range(1, 10):
                    menu_item = raster_step_menu.Append(wx.ID_ANY, _("Step %d") % i, "", wx.ITEM_RADIO)
                    gui.Bind(wx.EVT_MENU, self.menu_raster_step_operation(node, i), menu_item)
                    step = float(node.object.raster_step)
                    if i == step:
                        menu_item.Check(True)
                menu.AppendSubMenu(raster_step_menu, _("Step"))
                gui.Bind(wx.EVT_MENU, self.menu_raster(node),
                         menu.Append(wx.ID_ANY, _("Make Raster Image"), "", wx.ITEM_NORMAL))
        elif t == NODE_FILE_FILE:
            if node.filepath is not None:
                name = os.path.basename(node.filepath)
                gui.Bind(wx.EVT_MENU, self.menu_reload(node),
                         menu.Append(wx.ID_ANY, _("Reload %s") % name, "", wx.ITEM_NORMAL))
        elif t == NODE_ELEMENT:
            duplicate_menu = wx.Menu()
            for i in range(1, 10):
                gui.Bind(wx.EVT_MENU, self.menu_duplicate(node, i),
                         duplicate_menu.Append(wx.ID_ANY, _("Make %d copies.") % i, "", wx.ITEM_NORMAL))
            menu.AppendSubMenu(duplicate_menu, _("Duplicate"))
        if t in (NODE_ELEMENTS_BRANCH, NODE_ELEMENT):
            if not locked:
                gui.Bind(wx.EVT_MENU, self.menu_console('reset'),
                         menu.Append(wx.ID_ANY, _("Reset User Changes"), "", wx.ITEM_NORMAL))
        if t == NODE_ELEMENT:
            path_scale_sub_menu = wx.Menu()
            for i in range(1, 25):
                gui.Bind(wx.EVT_MENU, self.menu_scale(node, 6.0 / float(i)),
                         path_scale_sub_menu.Append(wx.ID_ANY, _("Scale %.0f%%") % (600.0 / float(i)), "",
                                                    wx.ITEM_NORMAL))
            if not locked:
                menu.AppendSubMenu(path_scale_sub_menu, _("Scale"))

            path_rotate_sub_menu = wx.Menu()
            for i in range(2, 13):
                angle = Angle.turns(1.0 / float(i))
                gui.Bind(wx.EVT_MENU, self.menu_rotate(node, 1.0 / float(i)),
                         path_rotate_sub_menu.Append(wx.ID_ANY, _(u"Rotate turn/%d, %.0f░") % (i, angle.as_degrees),
                                                     "",
                                                     wx.ITEM_NORMAL))
            for i in range(2, 13):
                angle = Angle.turns(1.0 / float(i))
                gui.Bind(wx.EVT_MENU, self.menu_rotate(node, -1.0 / float(i)),
                         path_rotate_sub_menu.Append(wx.ID_ANY,
                                                     _(u"Rotate turn/%d, -%.0f░") % (i, angle.as_degrees), "",
                                                     wx.ITEM_NORMAL))
            if not locked:
                menu.AppendSubMenu(path_rotate_sub_menu, _("Rotate"))
            if not locked:
                gui.Bind(wx.EVT_MENU, self.menu_console('reify'),
                         menu.Append(wx.ID_ANY, _("Reify User Changes"), "", wx.ITEM_NORMAL))
            if isinstance(node.object, Path):
                gui.Bind(wx.EVT_MENU, self.menu_console('element subpath'),
                         menu.Append(wx.ID_ANY, _("Break Subpaths"), "", wx.ITEM_NORMAL))
            if isinstance(node.object, SVGImage):
                raster_step_menu = wx.Menu()
                for i in range(1, 10):
                    menu_item = raster_step_menu.Append(wx.ID_ANY, _("Step %d") % i, "", wx.ITEM_RADIO)
                    gui.Bind(wx.EVT_MENU, self.menu_raster_step_image(node, i), menu_item)
                    if 'raster_step' in node.object.values:
                        step = float(node.object.values['raster_step'])
                    else:
                        step = 1.0
                    if i == step:
                        m = node.object.transform
                        if m.a == step or m.b == 0.0 or m.c == 0.0 or m.d == step:
                            menu_item.Check(True)
                menu.AppendSubMenu(raster_step_menu, _("Step"))
                gui.Bind(wx.EVT_MENU, self.menu_console('image resample'),
                         menu.Append(wx.ID_ANY, _("Actualize Pixels"), "", wx.ITEM_NORMAL))
                raster_zdepth_menu = wx.Menu()

                for i in range(2, 10):
                    menu_item = raster_zdepth_menu.Append(wx.ID_ANY, _("Divide Into %d Images") % i, "", wx.ITEM_NORMAL)
                    gui.Bind(wx.EVT_MENU, self.menu_raster_zdepth(node, i), menu_item)
                if not locked:
                    menu.AppendSubMenu(raster_zdepth_menu, _("ZDepth Divide"))

                image_menu = wx.Menu()
                try:
                    if locked:
                        gui.Bind(wx.EVT_MENU, self.menu_console('image unlock'),
                                 image_menu.Append(wx.ID_ANY, _("Unlock Manipulations"), "", wx.ITEM_NORMAL))
                except (ValueError, AttributeError):
                    pass
                gui.Bind(wx.EVT_MENU, self.menu_console('image dither'),
                         image_menu.Append(wx.ID_ANY, _("Dither to 1 bit"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_console('image invert'),
                         image_menu.Append(wx.ID_ANY, _("Invert Image"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_console('image mirror'),
                         image_menu.Append(wx.ID_ANY, _("Mirror Horizontal"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_console('image flip'),
                         image_menu.Append(wx.ID_ANY, _("Flip Vertical"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_console('image cw'),
                         image_menu.Append(wx.ID_ANY, _("Rotate CW"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_console('image ccw'),
                         image_menu.Append(wx.ID_ANY, _("Rotate CCW"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_console('image save output.png'),
                         image_menu.Append(wx.ID_ANY, _("Save output.png"), "", wx.ITEM_NORMAL))
                if image_menu.MenuItemCount != 0:
                    menu.AppendSubMenu(image_menu, _("Image"))

                try:
                    raster_wizard_menu = wx.Menu()
                    for script in self.device.device_root.registered['raster_script']:
                        menu_item = raster_wizard_menu.Append(wx.ID_ANY, _("RasterWizard: %s") % script, "",
                                                              wx.ITEM_NORMAL)
                        gui.Bind(wx.EVT_MENU, self.menu_console('window open RasterWizard %s' % script), menu_item)
                    menu.AppendSubMenu(raster_wizard_menu, _("RasterWizard"))
                except KeyError:
                    pass
                try:
                    raster_wizard_apply_menu = wx.Menu()
                    for script in self.device.device_root.registered['raster_script']:
                        menu_item = raster_wizard_apply_menu.Append(wx.ID_ANY, _("Apply: %s") % script, "",
                                                                    wx.ITEM_NORMAL)
                        gui.Bind(wx.EVT_MENU, self.menu_console('image wizard %s\n' % script), menu_item)
                    menu.AppendSubMenu(raster_wizard_apply_menu, _("Apply Raster Script"))
                except KeyError:
                    pass
            # if isinstance(node.object, SVGText):
            #     gui.Bind(wx.EVT_MENU, self.menu_convert_text(node),
            #              menu.Append(wx.ID_ANY, _("Convert to Raster"), "", wx.ITEM_NORMAL))

        if menu.MenuItemCount != 0:
            gui.PopupMenu(menu)
            menu.Destroy()

    def menu_console(self, console_command):
        """
        Default menu item to send a console command.

        Automatically adds '\n'.

        :param console_command: command to send to console.
        :return: function that executes the provided command.
        """

        def specific(event):
            self.device.using('module', 'Console').write('%s\n' % console_command)

        return specific

    def menu_raster_step_operation(self, node, step_value):
        """
        Change raster step values of operation

        :param node:
        :param step_value:
        :return:
        """

        def specific(event):
            element = node.object
            element.raster_step = step_value
            self.device.signal('element_property_update', node.object)

        return specific

    def menu_raster_step_image(self, node, step_value):
        """
        Change raster step values of subelements.

        :param node:
        :param step_value:
        :return:
        """

        def specific(event):
            element = node.object
            element.values["raster_step"] = str(step_value)
            m = element.transform
            tx = m.e
            ty = m.f
            element.transform = Matrix.scale(float(step_value), float(step_value))
            element.transform.post_translate(tx, ty)
            element.modified()
            self.device.signal('element_property_update', node.object)
            self.root.gui.request_refresh()

        return specific

    def menu_raster_zdepth(self, node, divide=7):
        """
        Subdivides an image into a zdepth image set.

        :param node: SVGImage node.
        :return: zdepth function
        """

        def specific(event):
            kernel = self.device.device_root
            element = node.object
            if not isinstance(element, SVGImage):
                return
            if element.image.mode != 'RGBA':
                element.image = element.image.convert('RGBA')
            band = 255 / divide
            for i in range(0, divide):
                threshold_min = i * band
                threshold_max = threshold_min + band
                self.device.using('module', 'Console').write('image threshold %f %f\n' % (threshold_min, threshold_max))

        return specific

    def menu_raster(self, node):
        """
        Convert a vector element into a raster element.

        :param node:
        :return:
        """

        def specific(event):
            kernel = self.device.device_root
            elements = kernel.elements
            renderer = self.renderer
            child_objects = list(node.objects_of_children(SVGElement))
            bounds = OperationPreprocessor.bounding_box(child_objects)
            if bounds is None:
                return None
            step = float(node.object.raster_step)
            xmin, ymin, xmax, ymax = bounds

            image = renderer.make_raster(child_objects, bounds, width=(xmax - xmin), height=(ymax - ymin), step=step)
            image_element = SVGImage(image=image)
            image_element.transform.post_scale(step, step)
            image_element.transform.post_translate(xmin, ymin)
            image_element.values['raster_step'] = step

            elements.add_elem(image_element)
            node.object.clear()
            self.build_tree(self.node_elements, image_element)
            node.object.append(image_element)
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_rotate(self, node, value):
        """
        Menu to rotate an element.

        :param node:
        :param value:
        :return:
        """

        value *= tau

        def specific(event):
            bounds = OperationPreprocessor.bounding_box(node.parent)
            center_x = (bounds[2] + bounds[0]) / 2.0
            center_y = (bounds[3] + bounds[1]) / 2.0
            self.device.using('module', 'Console').write('rotate %frad %f %f\n' % (value, center_x, center_y))

        return specific

    def menu_scale(self, node, value):
        """
        Menu scale.

        :param node:
        :param value:
        :return:
        """

        def specific(event):
            center_x, center_y = self.elements.center()
            self.device.using('module', 'Console').write('scale %f %f %f %f\n' % (value, value, center_x, center_y))

        return specific

    def menu_reload(self, node):
        """
        Menu to reload the element from the file on disk.

        :param node:
        :return:
        """

        def specific(event):
            filepath = node.filepath
            self.elements.clear_elements_and_operations()
            self.gui.load(filepath)

        return specific

    def menu_remove(self, remove_node):
        """
        Menu to remove an element from the scene.

        :param remove_node:
        :return:
        """

        def specific(event):
            kernel = self.device.device_root
            elements = kernel.elements
            node = remove_node
            if node.type == NODE_ELEMENT:
                self.device.using('module', 'Console').write('element delete\n')
            elif node.type == NODE_OPERATION:
                self.device.using('module', 'Console').write('operation delete\n')
            elif node.type == NODE_FILE_FILE:
                # Removing file can only have 1 copy.
                elements.remove_files([node.filepath])
            elif node.type == NODE_OPERATION_ELEMENT:
                # Operation_element can occur many times in the same operation node.
                index = node.parent.index(node)
                op = node.parent.object
                if index == -1:
                    op.remove(node.object)
                else:
                    del op[index]
            self.elements.set_selected(None)
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_clear_all_operation(self, node):
        def specific(event):
            kernel = self.device.device_root
            elements = kernel.elements
            for op in elements.ops(emphasized=True):
                op.clear()
                self.device.signal('rebuild_tree', 0)

        return specific

    def menu_clear_all_files_branch(self, node):
        def specific(event):
            kernel = self.device.device_root
            elements = kernel.elements
            elements.clear_files()
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_duplicate_element_op(self, node, copies):
        def specific(event):
            node.parent.object.extend([node.object] * copies)
            node.parent.object.modified()
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_duplicate(self, node, copies):
        """
        Menu to duplicate elements.

        :param node:
        :return:
        """

        def specific(event):
            kernel = self.device.device_root
            elements = kernel.elements
            adding_elements = [copy(e) for e in list(self.elements.elems(emphasized=True)) * copies]
            elements.add_elems(adding_elements)
            elements.classify(adding_elements)
            elements.set_selected(None)

        return specific

    def menu_passes(self, node, copies):
        """
        Menu to duplicate operation element nodes

        :param node:
        :return:
        """

        def specific(event):
            op = node.object
            adding_elements = list(op) * copies
            op.extend(adding_elements)
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_execute(self, node):
        """
        Menu to launch Execute Job for the particular element.

        :param node:
        :return:
        """

        def open_jobinfo_window(event):
            self.device.open('window', "JobInfo", self.gui, list(self.elements.ops(selected=True)))

        return open_jobinfo_window

    def menu_reverse_order(self, node):
        """
        Menu to return and reverse order of the element to the scene.

        :param node:
        :return:
        """
        kernel = self.device.device_root
        elements = kernel.elements

        def specific(event):
            t = node.type
            if t == NODE_ELEMENTS_BRANCH:
                self.elements._elements.reverse()
            elif t == NODE_OPERATION_BRANCH:
                self.elements._operations.reverse()
            elif t == NODE_OPERATION:
                for op in elements.ops(emphasized=True):
                    op.reverse()
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_reclassify_operations(self, node):
        def specific(event):
            kernel = node.root.device.device_root
            elements = kernel.elements
            elements.remove_elements_from_operations(list(elements.elems()))
            elements.classify(list(elements.elems()))
            self.device.signal('rebuild_tree', 0)

        return specific

    def menu_convert_operation(self, node, name):
        def specific(event):
            node.object.operation = name
            self.device.signal('element_property_update', node.object)

        return specific

    def menu_duplicate_operation(self, node):
        def specific(event):
            op = LaserOperation(node.object)
            op.clear()
            op.extend(node.object)
            self.device.device_root.elements.add_op(op)

        return specific

    def menu_convert_text(self, node):
        def specific(event):
            raise NotImplementedError

        return specific


def get_key_name(event):
    keyvalue = ''
    if event.ControlDown():
        keyvalue += 'control+'
    if event.AltDown():
        keyvalue += 'alt+'
    if event.ShiftDown():
        keyvalue += 'shift+'
    if event.MetaDown():
        keyvalue += 'meta+'
    key = event.GetKeyCode()
    if key == wx.WXK_CONTROL:
        return
    if key == wx.WXK_ALT:
        return
    if key == wx.WXK_SHIFT:
        return
    if key == wx.WXK_F1:
        keyvalue += 'f1'
    elif key == wx.WXK_F2:
        keyvalue += 'f2'
    elif key == wx.WXK_F3:
        keyvalue += 'f3'
    elif key == wx.WXK_F4:
        keyvalue += 'f4'
    elif key == wx.WXK_F5:
        keyvalue += 'f5'
    elif key == wx.WXK_F6:
        keyvalue += 'f6'
    elif key == wx.WXK_F7:
        keyvalue += 'f7'
    elif key == wx.WXK_F8:
        keyvalue += 'f8'
    elif key == wx.WXK_F9:
        keyvalue += 'f9'
    elif key == wx.WXK_F10:
        keyvalue += 'f10'
    elif key == wx.WXK_F11:
        keyvalue += 'f11'
    elif key == wx.WXK_F12:
        keyvalue += 'f12'
    elif key == wx.WXK_F13:
        keyvalue += 'f13'
    elif key == wx.WXK_F14:
        keyvalue += 'f14'
    elif key == wx.WXK_F15:
        keyvalue += 'f15'
    elif key == wx.WXK_F16:
        keyvalue += 'f16'
    elif key == wx.WXK_ADD:
        keyvalue += '+'
    elif key == wx.WXK_END:
        keyvalue += 'end'
    elif key == wx.WXK_NUMPAD0:
        keyvalue += 'numpad0'
    elif key == wx.WXK_NUMPAD1:
        keyvalue += 'numpad1'
    elif key == wx.WXK_NUMPAD2:
        keyvalue += 'numpad2'
    elif key == wx.WXK_NUMPAD3:
        keyvalue += 'numpad3'
    elif key == wx.WXK_NUMPAD4:
        keyvalue += 'numpad4'
    elif key == wx.WXK_NUMPAD5:
        keyvalue += 'numpad5'
    elif key == wx.WXK_NUMPAD6:
        keyvalue += 'numpad6'
    elif key == wx.WXK_NUMPAD7:
        keyvalue += 'numpad7'
    elif key == wx.WXK_NUMPAD8:
        keyvalue += 'numpad8'
    elif key == wx.WXK_NUMPAD9:
        keyvalue += 'numpad9'
    elif key == wx.WXK_NUMPAD_ADD:
        keyvalue += 'numpad_add'
    elif key == wx.WXK_NUMPAD_SUBTRACT:
        keyvalue += 'numpad_subtract'
    elif key == wx.WXK_NUMPAD_MULTIPLY:
        keyvalue += 'numpad_multiply'
    elif key == wx.WXK_NUMPAD_DIVIDE:
        keyvalue += 'numpad_divide'
    elif key == wx.WXK_NUMPAD_DECIMAL:
        keyvalue += 'numpad.'
    elif key == wx.WXK_NUMPAD_ENTER:
        keyvalue += 'numpad_enter'
    elif key == wx.WXK_NUMPAD_RIGHT:
        keyvalue += 'numpad_right'
    elif key == wx.WXK_NUMPAD_LEFT:
        keyvalue += 'numpad_left'
    elif key == wx.WXK_NUMPAD_UP:
        keyvalue += 'numpad_up'
    elif key == wx.WXK_NUMPAD_DOWN:
        keyvalue += 'numpad_down'
    elif key == wx.WXK_NUMPAD_DELETE:
        keyvalue += 'numpad_delete'
    elif key == wx.WXK_NUMPAD_INSERT:
        keyvalue += 'numpad_insert'
    elif key == wx.WXK_NUMPAD_PAGEUP:
        keyvalue += 'numpad_pgup'
    elif key == wx.WXK_NUMPAD_PAGEDOWN:
        keyvalue += 'numpad_pgdn'
    elif key == wx.WXK_NUMLOCK:
        keyvalue += 'numlock'
    elif key == wx.WXK_SCROLL:
        keyvalue += 'scroll'
    elif key == wx.WXK_HOME:
        keyvalue += 'home'
    elif key == wx.WXK_DOWN:
        keyvalue += 'down'
    elif key == wx.WXK_UP:
        keyvalue += 'up'
    elif key == wx.WXK_RIGHT:
        keyvalue += 'right'
    elif key == wx.WXK_LEFT:
        keyvalue += 'left'
    elif key == wx.WXK_ESCAPE:
        keyvalue += 'escape'
    elif key == wx.WXK_BACK:
        keyvalue += 'back'
    elif key == wx.WXK_PAUSE:
        keyvalue += 'pause'
    elif key == wx.WXK_PAGEDOWN:
        keyvalue += 'pagedown'
    elif key == wx.WXK_PAGEUP:
        keyvalue += 'pageup'
    elif key == wx.WXK_PRINT:
        keyvalue += 'print'
    elif key == wx.WXK_RETURN:
        keyvalue += 'return'
    elif key == wx.WXK_SPACE:
        keyvalue += 'space'
    elif key == wx.WXK_TAB:
        keyvalue += 'tab'
    elif key == wx.WXK_DELETE:
        keyvalue += 'delete'
    elif key == wx.WXK_INSERT:
        keyvalue += 'insert'
    else:
        keyvalue += chr(key)
    return keyvalue.lower()


class wxMeerK40t(wx.App, Module):
    """
    wxMeerK40t is the wx.App main class and a qualified Module for the MeerK40t kernel.
    Running MeerK40t without the wxMeerK40t gui is both possible and reasonable. This should not change the way the
    underlying code runs. It should just be a series of frames held together with the kernel.
    """

    def __init__(self, device=None):
        self.init_device = device
        wx.App.__init__(self, 0)
        import icons
        icons.DARKMODE = wx.SystemSettings().GetColour(wx.SYS_COLOUR_WINDOW)[0] < 127
        Module.__init__(self)
        self.locale = None
        self.Bind(wx.EVT_CLOSE, self.on_app_close)
        self.Bind(wx.EVT_QUERY_END_SESSION, self.on_app_close)  # MAC DOCK QUIT.
        self.Bind(wx.EVT_END_SESSION, self.on_app_close)
        self.Bind(wx.EVT_END_PROCESS, self.on_app_close)
        # This catches events when the app is asked to activate by some other process
        self.Bind(wx.EVT_ACTIVATE_APP, self.OnActivate)
        try:
            self.InitLocale()
        except AttributeError:
            pass  # 4.1

    def on_app_close(self, event):
        try:
            if self.device is not None:
                self.device.stop()
        except AttributeError:
            pass

    def OnInit(self):
        return True

    def BringWindowToFront(self):
        try:  # it's possible for this event to come when the frame is closed
            self.GetTopWindow().Raise()
        except:
            pass

    def OnActivate(self, event):
        # if this is an activate event, rather than something else, like iconize.
        if event.GetActive():
            self.BringWindowToFront()
        event.Skip()

    def MacReopenApp(self):
        """Called when the doc icon is clicked, and ???"""
        self.BringWindowToFront()

    def MacNewFile(self):
        try:
            if self.device is not None:
                self.device.elements.clear_all()
        except AttributeError:
            pass

    def MacPrintFile(self, file_path):
        pass

    def MacOpenFile(self, filename):
        try:
            if self.init_device is not None:
                self.init_device.load(os.path.realpath(filename))
        except AttributeError:
            pass

    def MacOpenFiles(self, filenames):
        try:
            if self.init_device is not None:
                for filename in filenames:
                    self.init_device.load(os.path.realpath(filename))
        except AttributeError:
            pass

    @staticmethod
    def sub_register(device):
        device.register('window', "MeerK40t", MeerK40t)
        device.register('module', 'Scene', Scene)
        device.register('window', 'PathProperty', PathProperty)
        device.register('window', 'TextProperty', TextProperty)
        device.register('window', 'ImageProperty', ImageProperty)
        device.register('window', "OperationProperty", OperationProperty)
        device.register('window', 'Controller', Controller)
        device.register('window', "Preferences", Preferences)
        device.register('window', "CameraInterface", CameraInterface)
        device.register('window', "Terminal", Terminal)
        device.register('window', "Settings", Settings)
        device.register('window', "Rotary", RotarySettings)
        device.register('window', "Alignment", Alignment)
        device.register('window', "About", About)
        device.register('window', "DeviceManager", DeviceManager)
        device.register('window', "Keymap", Keymap)
        device.register('window', "UsbConnect", UsbConnect)
        device.register('window', "Navigation", Navigation)
        device.register('window', "Notes", Notes)
        device.register('window', "Controller", Controller)
        device.register('window', "JobSpooler", JobSpooler)
        device.register('window', "JobInfo", JobInfo)
        device.register('window', "BufferView", BufferView)
        device.register('window', "Adjustments", Adjustments)
        device.register('window', "RasterWizard", RasterWizard)

    def run_later(self, command, *args):
        if wx.IsMainThread():
            command(*args)
        else:
            wx.CallAfter(command, *args)

    def initialize(self, channel=None):
        device = self.device

        try:  # pyinstaller internal location
            _resource_path = os.path.join(sys._MEIPASS, 'locale')
            wx.Locale.AddCatalogLookupPathPrefix(_resource_path)
        except:
            pass

        try:  # Mac py2app resource
            _resource_path = os.path.join(os.environ['RESOURCEPATH'], 'locale')
            wx.Locale.AddCatalogLookupPathPrefix(_resource_path)
        except:
            pass

        wx.Locale.AddCatalogLookupPathPrefix('locale')  # Default Locale, prepended. Check this first.

        device.run_later = self.run_later
        device.translation = wx.GetTranslation

        device.set_config(wx.Config("MeerK40t"))
        device.app = self  # Registers self as kernel.app
        device.setting(int, 'language', None)
        device.add('control', "Delete Settings", self.clear_control)
        language = device.language
        if language is not None and language != 0:
            self.update_language(language)

    def clear_control(self):
        device = self.device.device_root
        if device.config is not None:
            device.config.DeleteAll()
            device.config = None
            device.stop()

    def update_language(self, lang):
        """
        Update language to the requested language.
        """
        device = self.device
        try:
            language_code, language_name, language_index = supported_languages[lang]
        except (IndexError, ValueError):
            return
        device.language = lang

        if self.locale:
            assert sys.getrefcount(self.locale) <= 2
            del self.locale
        self.locale = wx.Locale(language_index)
        if self.locale.IsOk():
            self.locale.AddCatalog('meerk40t')
        else:
            self.locale = None
        device.signal('language', (lang, language_code, language_name, language_index))


# end of class MeerK40tGui
def handleGUIException(exc_type, exc_value, exc_traceback):
    """
    Handler for errors. Save error to a file, and create dialog.

    :param exc_type:
    :param exc_value:
    :param exc_traceback:
    :return:
    """
    err_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
    print(err_msg)
    try:
        import datetime
        filename = "MeerK40t-{date:%Y-%m-%d_%H_%M_%S}.txt".format(date=datetime.datetime.now())
        print(_("Saving Log: %s") % filename)
        with open(filename, "w") as file:
            # Crash logs are not translated.
            file.write("MeerK40t crash log. Version: %s\n" % '0.6.22')
            file.write("Please report to: %s\n\n" % MEERK40T_ISSUES)
            file.write(err_msg)
            print(file)
        dlg = wx.MessageDialog(None, err_msg, _('Please Report to Development Team'), wx.OK | wx.ICON_ERROR)
        dlg.ShowModal()
        dlg.Destroy()
    except:  # I already crashed once, if there's another here just ignore it.
        pass


sys.excepthook = handleGUIException
