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

import sys
import traceback

import wx
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 DefaultModules import *
from DeviceManager import DeviceManager
from EngraveProperty import EngraveProperty
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 LaserRender, swizzlecolor
from Navigation import Navigation
from OperationPreprocessor import OperationPreprocessor
from PathProperty import PathProperty
from Preferences import Preferences
from RasterProperty import RasterProperty
from RotarySettings import RotarySettings
from Settings import Settings
from Shutdown import Shutdown
from TextProperty import TextProperty
from UsbConnect import UsbConnect
from ZMatrix import ZMatrix
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 for wxPython 4.0+ and OSX/Linux wxPython 4.1+.

"""

MILS_IN_MM = 39.3701
MEERK40T_VERSION = "0.5.2"
MEERK40T_ISSUES = "https://github.com/meerk40t/meerk40t/issues"
MEERK40T_WEBSITE = "https://github.com/meerk40t/meerk40t"


class IdInc:
    """
    Id Incrementor
    """

    def __init__(self):
        self.id_highest_value = wx.ID_HIGHEST

    def new(self):
        self.id_highest_value += 1
        return self.id_highest_value


idinc = IdInc()
ID_MAIN_TOOLBAR = idinc.new()
ID_ADD_FILE = idinc.new()
ID_OPEN = idinc.new()
ID_SAVE = idinc.new()
ID_NAV = idinc.new()
ID_USB = idinc.new()
ID_CONTROLLER = idinc.new()
ID_PREFERENCES = idinc.new()
ID_DEVICES = idinc.new()
ID_JOB = idinc.new()
ID_SPOOLER = idinc.new()

ID_CUT_CONFIGURATION = idinc.new()
ID_SELECT = idinc.new()

ID_MENU_NEW = idinc.new()
ID_MENU_OPEN_PROJECT = idinc.new()
ID_MENU_RECENT_PROJECT = idinc.new()

ID_MENU_IMPORT = idinc.new()
ID_MENU_SAVE = idinc.new()
ID_MENU_SAVE_AS = idinc.new()
ID_MENU_EXIT = idinc.new()
ID_MENU_ZOOM_OUT = idinc.new()
ID_MENU_ZOOM_IN = idinc.new()
ID_MENU_ZOOM_SIZE = idinc.new()

# 1 fill, 2 grids, 4 guides, 8 laserpath, 16 writer_position, 32 selection
ID_MENU_HIDE_FILLS = idinc.new()
ID_MENU_HIDE_GUIDES = idinc.new()
ID_MENU_HIDE_GRID = idinc.new()
ID_MENU_HIDE_STROKES = idinc.new()
ID_MENU_HIDE_LASERPATH = idinc.new()
ID_MENU_HIDE_RETICLE = idinc.new()
ID_MENU_HIDE_SELECTION = idinc.new()
ID_MENU_SCREEN_REFRESH = idinc.new()
ID_MENU_SCREEN_ANIMATE = idinc.new()
ID_MENU_HIDE_IMAGE = idinc.new()
ID_MENU_HIDE_PATH = idinc.new()
ID_MENU_HIDE_TEXT = idinc.new()

ID_MENU_ALIGNMENT = idinc.new()
ID_MENU_ABOUT = idinc.new()
ID_MENU_KEYMAP = idinc.new()
ID_MENU_DEVICE_MANAGER = idinc.new()
ID_MENU_PREFERENCES = idinc.new()
ID_MENU_SETTINGS = idinc.new()
ID_MENU_ROTARY = idinc.new()
ID_MENU_NAVIGATION = idinc.new()
ID_MENU_CONTROLLER = idinc.new()
ID_MENU_CAMERA = idinc.new()
ID_MENU_USB = idinc.new()
ID_MENU_SPOOLER = idinc.new()
ID_MENU_JOB = idinc.new()
ID_MENU_TREE = idinc.new()

ID_MENU_WEBPAGE = idinc.new()
ID_CUT_TREE = idinc.new()
ID_CUT_BURN_BUTTON = idinc.new()

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


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

    def __init__(self, *args, **kwds):
        # begin wxGlade: MeerK40t.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        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
                                                | RB.RIBBON_BAR_SHOW_PANEL_EXT_BUTTONS)

        home = RB.RibbonPage(self._ribbon, wx.ID_ANY, _("Examples"), icons8_opened_folder_50.GetBitmap())
        toolbar_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Toolbar"),
                                       style=RB.RIBBON_PANEL_NO_AUTO_MINIMISE | RB.RIBBON_PANEL_EXT_BUTTON)

        toolbar = RB.RibbonToolBar(toolbar_panel, ID_MAIN_TOOLBAR)
        self.toolbar = toolbar

        toolbar.AddTool(ID_OPEN, icons8_opened_folder_50.GetBitmap(), "")  # "Open",
        toolbar.AddTool(ID_SAVE, icons8_save_50.GetBitmap(), "")
        toolbar.AddTool(ID_JOB, icons8_laser_beam_52.GetBitmap(), "")

        windows_panel = RB.RibbonPanel(home, wx.ID_ANY, _("Windows"), icons8_opened_folder_50.GetBitmap())
        windows = RB.RibbonButtonBar(windows_panel)
        windows.AddButton(ID_NAV, _("Navigation"), icons8_move_32.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(), "")
        self._ribbon.Realize()

        self.CenterOnScreen()
        # Menu Bar
        self.main_menubar = wx.MenuBar()
        wxglade_tmp_menu = wx.Menu()
        wxglade_tmp_menu.Append(ID_MENU_NEW, _("New"), "")
        wxglade_tmp_menu.Append(ID_MENU_OPEN_PROJECT, _("Open Project"), "")
        wxglade_tmp_menu.Append(ID_MENU_IMPORT, _("Import File"), "")
        wxglade_tmp_menu.AppendSeparator()
        wxglade_tmp_menu.Append(ID_MENU_SAVE, _("Save"), "")
        wxglade_tmp_menu.Append(ID_MENU_SAVE_AS, _("Save As"), "")
        wxglade_tmp_menu.AppendSeparator()
        wxglade_tmp_menu.Append(ID_MENU_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"), "")
        wxglade_tmp_menu.Append(ID_MENU_ZOOM_IN, _("Zoom In"), "")
        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_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_SCREEN_REFRESH, _("Do Not Refresh"), "", wx.ITEM_CHECK)
        wxglade_tmp_menu.Append(ID_MENU_SCREEN_ANIMATE, _("Do Not Animate"), "", wx.ITEM_CHECK)
        self.main_menubar.Append(wxglade_tmp_menu, _("View"))
        wxglade_tmp_menu = wx.Menu()

        wxglade_tmp_menu.Append(ID_MENU_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_NAVIGATION, _("Navigation"), "")
        wxglade_tmp_menu.Append(ID_MENU_CONTROLLER, _("Controller"), "")
        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, _("Windows"))

        wxglade_tmp_menu = wx.Menu()
        wxglade_tmp_menu.Append(ID_MENU_WEBPAGE, _("Webpage"), "")
        wxglade_tmp_menu.Append(ID_MENU_ABOUT, _("About"), "")
        self.main_menubar.Append(wxglade_tmp_menu, _("Help"))

        self.SetMenuBar(self.main_menubar)
        # Menu Bar end

        self.Bind(wx.EVT_MENU, self.on_click_new, id=ID_MENU_NEW)
        self.Bind(wx.EVT_MENU, self.on_click_open, id=ID_MENU_OPEN_PROJECT)
        self.Bind(wx.EVT_MENU, self.on_click_open, id=ID_MENU_IMPORT)
        self.Bind(wx.EVT_MENU, self.on_click_save, id=ID_MENU_SAVE)
        self.Bind(wx.EVT_MENU, self.on_click_save_as, id=ID_MENU_SAVE_AS)

        self.Bind(wx.EVT_MENU, self.on_click_exit, id=ID_MENU_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(0x0004), id=ID_MENU_HIDE_GRID)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0002), id=ID_MENU_HIDE_GUIDES)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0400), id=ID_MENU_HIDE_PATH)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0800), id=ID_MENU_HIDE_IMAGE)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x1000), id=ID_MENU_HIDE_TEXT)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0001), id=ID_MENU_HIDE_FILLS)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0008), id=ID_MENU_HIDE_LASERPATH)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0010), id=ID_MENU_HIDE_RETICLE)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0020), id=ID_MENU_HIDE_SELECTION)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0040), id=ID_MENU_HIDE_STROKES)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0100), id=ID_MENU_SCREEN_REFRESH)
        self.Bind(wx.EVT_MENU, self.toggle_draw_mode(0x0200), id=ID_MENU_SCREEN_ANIMATE)

        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("About"), id=ID_MENU_ABOUT)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Alignment"), id=ID_MENU_ALIGNMENT)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("CameraInterface"), id=ID_MENU_CAMERA)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("DeviceManager"), id=ID_MENU_DEVICE_MANAGER)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Keymap"), id=ID_MENU_KEYMAP)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Preferences"), id=ID_MENU_PREFERENCES)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Settings"), id=ID_MENU_SETTINGS)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Rotary"), id=ID_MENU_ROTARY)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Navigation"), id=ID_MENU_NAVIGATION)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("Controller"), id=ID_MENU_CONTROLLER)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("UsbConnect"), id=ID_MENU_USB)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("JobSpooler"), id=ID_MENU_SPOOLER)
        self.Bind(wx.EVT_MENU, lambda v: self.kernel.open_window("JobInfo").set_operations(self.kernel.operations),
                  id=ID_MENU_JOB)

        self.Bind(wx.EVT_MENU, self.launch_webpage, id=ID_MENU_WEBPAGE)

        toolbar.Bind(RB.EVT_RIBBONTOOLBAR_CLICKED, self.on_click_open, id=ID_OPEN)
        toolbar.Bind(RB.EVT_RIBBONTOOLBAR_CLICKED, self.on_click_save, id=ID_SAVE)
        toolbar.Bind(RB.EVT_RIBBONTOOLBAR_CLICKED,
                     lambda v: self.kernel.open_window("JobInfo").set_operations(self.kernel.operations), id=ID_JOB)

        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, lambda v: self.kernel.open_window("UsbConnect"), id=ID_USB)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, lambda v: self.kernel.open_window("Navigation"), id=ID_NAV)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, lambda v: self.kernel.open_window("Controller"), id=ID_CONTROLLER)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, lambda v: self.kernel.open_window("Preferences"),
                     id=ID_PREFERENCES)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, lambda v: self.kernel.open_window("DeviceManager"), id=ID_DEVICES)
        windows.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, lambda v: self.kernel.open_window("JobSpooler"), id=ID_SPOOLER)

        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 = True
        self.screen_refresh_is_running = False
        self.background_brush = wx.Brush("Grey")
        self.renderer = None
        self.grid = None
        self.guide_lines = None
        self.laserpath = [[0, 0] for i in range(1000)], [[0, 0] for i in range(1000)]
        self.laserpath_index = 0
        self.mouse_move_function = self.move_pan
        self.working_file = None

        self.__set_properties()
        self.__do_layout()

        self.set_buffer()

        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_ENTER_WINDOW, lambda event: self.scene.SetFocus())  # Focus follows mouse.
        self.tree.Bind(wx.EVT_ENTER_WINDOW, lambda event: self.tree.SetFocus())  # Focus follows mouse.

        self.scene.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
        self.Bind(wx.EVT_KEY_DOWN, self.on_key_press)

        self.Bind(wx.EVT_CLOSE, self.on_close, self)

        self.fps_job = None
        self.kernel = None
        self.root = None  # RootNode value, must have kernel for init.
        self.device_listening = None
        self.background = None

    def notify_change(self):
        self.kernel.signal('rebuild_tree', 0)

    def add_language_menu(self):
        if os.path.exists('./locale'):
            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.kernel.language:
                    m.Check(True)
                self.Bind(wx.EVT_MENU, self.kernel.gui.language_to(i), id=m.GetId())
                if not os.path.exists('./locale/%s' % language_code) and i != 0:
                    m.Enable(False)
                i += 1
            self.main_menubar.Append(wxglade_tmp_menu, _("Languages"))

    def set_kernel(self, kernel):
        self.kernel = kernel
        kernel.setting(int, "draw_mode", 0)  # 1 fill, 2 grids, 4 guides, 8 laserpath, 16 writer_position, 32 selection
        kernel.setting(int, "window_width", 1200)
        kernel.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, "mouse_zoom_invert", False)
        kernel.setting(int, 'fps', 40)
        kernel.setting(int, "bed_width", 320)  # Default Value
        kernel.setting(int, "bed_height", 220)  # Default Value
        self.listen_scene()
        if kernel.fps <= 0:
            kernel.fps = 60
        self.renderer = LaserRender(kernel)
        self.root = RootNode(kernel, self)
        kernel.setting(wx.App, 'root', self.root)
        kernel.root = self.root

        if kernel.window_width < 300:
            kernel.window_width = 300
        if kernel.window_height < 300:
            kernel.window_height = 300

        kernel.add_control("Transform", self.open_transform_dialog)
        kernel.add_control("Path", self.open_path_dialog)
        kernel.add_control("FPS", self.open_fps_dialog)
        kernel.add_control("Speedcode-Gear-Force", self.open_speedcode_gear_dialog)
        kernel.add_control("Home and Dot", self.run_home_and_dot_test)

        self.SetSize((kernel.window_width, kernel.window_height))
        bedwidth = kernel.bed_width
        bedheight = kernel.bed_height

        self.kernel.boot()
        self.focus_viewport_scene((0, 0, bedwidth * MILS_IN_MM, bedheight * MILS_IN_MM), 0.1)
        self.fps_job = self.kernel.cron.add_job(self.refresh_scene, interval=1.0 / float(kernel.fps))
        self.add_language_menu()

        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_FILLS)
        m.Check(self.kernel.draw_mode & 0x0001 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_GUIDES)
        m.Check(self.kernel.draw_mode & 0x0002 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_GRID)
        m.Check(self.kernel.draw_mode & 0x0004 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_LASERPATH)
        m.Check(self.kernel.draw_mode & 0x0008 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_RETICLE)
        m.Check(self.kernel.draw_mode & 0x0010 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_SELECTION)
        m.Check(self.kernel.draw_mode & 0x0020 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_STROKES)
        m.Check(self.kernel.draw_mode & 0x0040 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_SCREEN_REFRESH)
        m.Check(self.kernel.draw_mode & 0x0100 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_SCREEN_ANIMATE)
        m.Check(self.kernel.draw_mode & 0x0200 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_PATH)
        m.Check(self.kernel.draw_mode & 0x0400 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_IMAGE)
        m.Check(self.kernel.draw_mode & 0x0800 != 0)
        m = self.GetMenuBar().FindItemById(ID_MENU_HIDE_TEXT)
        m.Check(self.kernel.draw_mode & 0x1000 != 0)
        self.on_size(None)
        self.Bind(wx.EVT_SIZE, self.on_size)
        self.space_changed(0)
        self.default_keymap()

        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_changed, self.tree)
        self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.root.on_item_right_click, self.tree)

    def set_fps(self, fps):
        if fps == 0:
            fps = 1
        self.fps_job.times = 0
        self.kernel.fps = fps
        self.fps_job = self.kernel.cron.add_job(self.refresh_scene, interval=1.0 / float(self.kernel.fps))

    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 'rebuild_tree' change. To refresh tree.

        :param args:
        :return:
        """
        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_status(self, value):
        if self.kernel is not None:
            self.main_statusbar.SetStatusText(_("Usb: %s") % value, 0)

    def on_pipe_state(self, value):
        if self.kernel is not None:
            self.main_statusbar.SetStatusText(_("Controller: %s") % self.kernel.get_text_thread_state(value), 1)

    def on_spooler_state(self, value):
        if self.kernel is not None:
            self.main_statusbar.SetStatusText(_("Spooler: %s") % self.kernel.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):
        if isinstance(background, str):
            return  # Assumed color.
        if isinstance(background, int):
            return  # Assumed color.
        self.background = background
        self.request_refresh()

    def on_device_switch(self, device):
        self.unlisten_device()
        self.listen_device(device)

    def listen_device(self, device):
        if self.device_listening is not None:
            self.unlisten_device()
        self.device_listening = device
        if device is not None:
            device.listen('pipe;error', self.on_usb_error)
            device.listen('pipe;usb_status', self.on_usb_status)
            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)

    def unlisten_device(self):
        if self.device_listening is None:
            return  # Can't unlisten to nothing, ---
        device = self.device_listening
        if device is not None:
            device.unlisten('pipe;error', self.on_usb_error)
            device.unlisten('pipe;usb_status', self.on_usb_status)
            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)
        self.device_listening = None

    def listen_scene(self):
        self.kernel.listen("background", self.on_background_signal)
        self.kernel.listen("device", self.on_device_switch)
        self.kernel.listen('rebuild_tree', self.on_rebuild_tree_request)
        self.kernel.listen('refresh_scene', self.on_refresh_scene)
        self.kernel.listen("element_property_update", self.on_element_update)
        self.kernel.listen("units", self.space_changed)
        self.kernel.listen("selected_elements", self.selection_changed)

    def unlisten_scene(self):
        self.kernel.unlisten("background", self.on_background_signal)
        self.kernel.unlisten("device", self.on_device_switch)
        self.kernel.unlisten('rebuild_tree', self.on_rebuild_tree_request)
        self.kernel.unlisten('refresh_scene', self.on_refresh_scene)
        self.kernel.unlisten("element_property_update", self.on_element_update)
        self.kernel.unlisten("units", self.space_changed)
        self.kernel.unlisten("selected_elements", self.selection_changed)

    def on_close(self, event):
        self.unlisten_device()
        self.unlisten_scene()
        self.kernel.open_window('Shutdown')
        self.kernel.mark_window_closed('MeerK40t')
        self.kernel.cron.stop()
        event.Skip()  # Call destroy as regular.

    def __set_properties(self):
        # begin wxGlade: MeerK40t.__set_properties
        self.SetTitle(_("MeerK40t v%s") % MEERK40T_VERSION)
        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)

    def __do_layout(self):
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        main_sizer.Add(self._ribbon, 1, 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.ALL | wx.EXPAND, 2)
        main_sizer.Add(widget_sizer, 5, wx.EXPAND, 0)
        self.SetSizer(main_sizer)
        # main_sizer.Fit(self)
        self.Layout()

    def load(self, pathname):
        results = self.kernel.load(pathname)
        if results is not None:
            elements, pathname, basename = results
            self.kernel.classify(elements)
            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()
        if accepted != 0:
            self.root.notify_tree_data_change()

    def on_paint(self, event):
        try:
            wx.BufferedPaintDC(self.scene, self._Buffer)
        except RuntimeError:
            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.kernel is None:
            return
        self.Layout()
        self.set_buffer()
        self.kernel.window_width, self.kernel.window_height = self.Size
        self.guide_lines = None
        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, units):
        self.grid = None
        self.on_size(None)

    def bed_changed(self, size):
        self.grid = None
        self.on_size(None)

    def selection_changed(self, selection):
        self.request_refresh()

    def on_erase(self, event):
        pass

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

    def request_refresh(self):
        """Request an update to the scene."""
        if self.kernel.draw_mode & 0x0100 == 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
            wx.CallAfter(self.refresh_in_ui)

    def refresh_in_ui(self):
        """Called by refresh_scene() in the UI thread."""
        if self.kernel is None:
            return
        self.update_buffer_ui_thread()
        self.scene.Refresh()
        self.screen_refresh_is_requested = False
        self.screen_refresh_is_running = False

    def update_buffer_ui_thread(self):
        """Performs the redraw of the data in the UI thread."""
        dc = wx.MemoryDC()
        dc.SelectObject(self._Buffer)
        dc.SetBackground(self.background_brush)
        dc.Clear()
        gc = wx.GraphicsContext.Create(dc)
        gc.SetTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix(self.matrix)))
        font = wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)
        gc.SetFont(font, wx.BLACK)

        self.on_draw_scene(gc)
        gc.SetTransform(wx.GraphicsContext.CreateMatrix(gc, ZMatrix()))
        self.on_draw_interface(gc)
        gc.Destroy()
        del dc

    def on_matrix_change(self):
        self.guide_lines = None

    def scene_matrix_reset(self):
        self.matrix.reset()
        self.on_matrix_change()

    def scene_post_scale(self, sx, sy=None, ax=0, ay=0):
        self.matrix.post_scale(sx, sy, ax, ay)
        self.on_matrix_change()

    def scene_post_pan(self, px, py):
        self.matrix.post_translate(px, py)
        self.on_matrix_change()

    def scene_post_rotate(self, angle, rx=0, ry=0):
        self.matrix.post_rotate(angle, rx, ry)
        self.on_matrix_change()

    def scene_pre_scale(self, sx, sy=None, ax=0, ay=0):
        self.matrix.pre_scale(sx, sy, ax, ay)
        self.on_matrix_change()

    def scene_pre_pan(self, px, py):
        self.matrix.pre_translate(px, py)
        self.on_matrix_change()

    def scene_pre_rotate(self, angle, rx=0, ry=0):
        self.matrix.pre_rotate(angle, rx, ry)
        self.on_matrix_change()

    def get_scale_x(self):
        return self.matrix.value_scale_x()

    def get_scale_y(self):
        return self.matrix.value_scale_y()

    def get_skew_x(self):
        return self.matrix.value_skew_x()

    def get_skew_y(self):
        return self.matrix.value_skew_y()

    def get_translate_x(self):
        return self.matrix.value_trans_x()

    def get_translate_y(self):
        return self.matrix.value_trans_y()

    def on_mousewheel(self, event):
        rotation = event.GetWheelRotation()
        mouse = event.GetPosition()
        if self.kernel.mouse_zoom_invert:
            rotation = -rotation
        if rotation > 1:
            self.scene_post_scale(1.1, 1.1, mouse[0], mouse[1])
        elif rotation < -1:
            self.scene_post_scale(0.9, 0.9, mouse[0], mouse[1])
        self.request_refresh()

    def on_mouse_middle_down(self, event):
        self.SetCursor(wx.Cursor(wx.CURSOR_HAND))
        self.scene.CaptureMouse()
        self.previous_window_position = event.GetPosition()
        self.previous_scene_position = self.convert_window_to_scene(self.previous_window_position)

    def on_mouse_middle_up(self, event):
        if self.scene.HasCapture():
            self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
            self.scene.ReleaseMouse()
        self.previous_window_position = None
        self.previous_scene_position = None

    def on_left_mouse_down(self, event):
        self.SetCursor(wx.Cursor(wx.CURSOR_HAND))
        self.scene.CaptureMouse()
        self.previous_window_position = event.GetPosition()
        self.previous_scene_position = self.convert_window_to_scene(self.previous_window_position)
        self.root.set_selected_by_position(self.previous_scene_position)
        self.mouse_move_function = self.move_selected
        self.request_refresh()

    def on_left_mouse_up(self, event):
        if self.scene.HasCapture():
            self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
            self.scene.ReleaseMouse()
        self.previous_window_position = None
        self.previous_scene_position = None
        self.mouse_move_function = self.move_pan

    def on_mouse_double_click(self, event):
        position = event.GetPosition()
        position = self.convert_window_to_scene(position)
        self.root.set_selected_by_position(position)
        self.root.activate_selected_node()

    def move_pan(self, wdx, wdy, sdx, sdy):
        self.scene_post_pan(wdx, wdy)
        self.request_refresh()

    def move_selected(self, wdx, wdy, sdx, sdy):
        self.root.move_selected(sdx, sdy)
        self.request_refresh()

    def on_mouse_move(self, event):
        if not event.Dragging():
            return
        else:
            self.SetCursor(wx.Cursor(wx.CURSOR_HAND))
        if self.previous_window_position is None:
            return

        pos = event.GetPosition()
        window_position = pos.x, pos.y
        scene_position = self.convert_window_to_scene([window_position[0], window_position[1]])
        sdx = (scene_position[0] - self.previous_scene_position[0])
        sdy = (scene_position[1] - self.previous_scene_position[1])
        wdx = (window_position[0] - self.previous_window_position[0])
        wdy = (window_position[1] - self.previous_window_position[1])
        self.mouse_move_function(wdx, wdy, sdx, sdy)
        self.previous_window_position = window_position
        self.previous_scene_position = scene_position

    def on_right_mouse_down(self, event):
        self.popup_window_position = event.GetPosition()
        self.popup_scene_position = self.convert_window_to_scene(self.popup_window_position)
        self.root.set_selected_by_position(self.popup_scene_position)
        if len(self.root.selected_elements) == 0:
            return
        self.root.create_menu(self, self.root.selected_elements[0])

    def on_right_mouse_up(self, event):
        self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

    def default_keymap(self):
        self.kernel.keymap[wx.WXK_ESCAPE] = MappedKey("escape", "window Adjustments")
        self.kernel.keymap[wx.WXK_RIGHT] = MappedKey("right", "move right 1mm")
        self.kernel.keymap[wx.WXK_LEFT] = MappedKey("left", "move left 1mm")
        self.kernel.keymap[wx.WXK_UP] = MappedKey("up", "move up 1mm")
        self.kernel.keymap[wx.WXK_DOWN] = MappedKey("down", "move down 1mm")
        self.kernel.keymap[ord('1')] = MappedKey('1', "set_position 1")
        self.kernel.keymap[ord('2')] = MappedKey('2', "set_position 2")
        self.kernel.keymap[ord('3')] = MappedKey('3', "set_position 3")
        self.kernel.keymap[ord('4')] = MappedKey('4', "set_position 4")
        self.kernel.keymap[ord('5')] = MappedKey('5', "set_position 5")
        self.kernel.keymap[wx.WXK_F4] = MappedKey('F4', "window CameraInterface")
        self.kernel.keymap[wx.WXK_F6] = MappedKey('F6', "window JobSpooler")
        self.kernel.keymap[wx.WXK_F7] = MappedKey('F7', "window Controller")
        self.kernel.keymap[wx.WXK_F8] = MappedKey('F8', "control Path")
        self.kernel.keymap[wx.WXK_F9] = MappedKey('F9', "control Transform")

    def execute_string_action(self, action, *args):
        device = self.kernel.device
        if device is None:
            return
        spooler = device.spooler
        if action == 'move':
            spooler.send_job(self.execute_move_action(*args))
        elif action == 'move_to':
            spooler.send_job(self.execute_move_to_action(*args))
        elif action == 'set_position':
            self.execute_set_position_action(*args)
        elif action == 'window':
            self.execute_open_window_action(*args)
        elif action == 'control':
            self.execute_execute_control(*args)

    def execute_execute_control(self, *args):
        self.kernel.execute(args[0])

    def execute_open_window_action(self, *args):
        window_name = args[0]
        if window_name in self.kernel.windows:
            self.kernel.open_window(window_name)

    def execute_set_position_action(self, index):
        x = self.kernel.device.current_x
        y = self.kernel.device.current_y
        self.kernel.keymap[ord(index)] = MappedKey(index, "move_to %d %d" % (x, y))

    def execute_move_action(self, direction, amount):
        min_dim = min(self.kernel.window_width, self.kernel.window_height)
        amount = Length(amount).value(ppi=1000.0, relative_length=min_dim)
        x = 0
        y = 0
        if direction == 'right':
            x = amount
        elif direction == 'left':
            x = -amount
        elif direction == 'up':
            y = -amount
        elif direction == 'down':
            y = amount

        def move():
            yield COMMAND_SET_INCREMENTAL
            yield COMMAND_RAPID_MOVE, (x, y)
            yield COMMAND_SET_ABSOLUTE

        return move

    def execute_move_to_action(self, position_x, position_y):
        def move():
            yield COMMAND_RAPID_MOVE, (int(position_x), int(position_y))

        return move

    def on_key_press(self, event):
        keycode = event.GetKeyCode()
        if event.ControlDown():
            pass
        if event.AltDown():
            pass
        if event.ShiftDown():
            pass
        if event.MetaDown():
            pass
        if keycode in self.kernel.keymap:
            action = self.kernel.keymap[keycode].command
            args = str(action).split(' ')
            self.execute_string_action(*args)

    def focus_on_elements(self):
        bbox = self.root.bounds
        if bbox is None:
            return
        self.focus_viewport_scene(bbox)
        self.request_refresh()

    def focus_position_scene(self, scene_point):
        window_width, window_height = self.scene.ClientSize
        scale_x = self.get_scale_x()
        scale_y = self.get_scale_y()
        self.scene_matrix_reset()
        self.scene_post_pan(-scene_point[0], -scene_point[1])
        self.scene_post_scale(scale_x, scale_y)
        self.scene_post_pan(window_width / 2.0, window_height / 2.0)

    def focus_viewport_scene(self, new_scene_viewport, buffer=0.0, lock=True):
        window_width, window_height = self.scene.ClientSize
        left = new_scene_viewport[0]
        top = new_scene_viewport[1]
        right = new_scene_viewport[2]
        bottom = new_scene_viewport[3]
        viewport_width = right - left
        viewport_height = bottom - top

        left -= viewport_width * buffer
        right += viewport_width * buffer
        top -= viewport_height * buffer
        bottom += viewport_height * buffer

        if right == left:
            scale_x = 100
        else:
            scale_x = window_width / float(right - left)
        if bottom == top:
            scale_y = 100
        else:
            scale_y = window_height / float(bottom - top)

        cx = ((right + left) / 2)
        cy = ((top + bottom) / 2)
        self.matrix.reset()
        self.matrix.post_translate(-cx, -cy)
        if lock:
            scale = min(scale_x, scale_y)
            if scale != 0:
                self.matrix.post_scale(scale)
        else:
            if scale_x != 0 and scale_y != 0:
                self.matrix.post_scale(scale_x, scale_y)
        self.matrix.post_translate(window_width / 2.0, window_height / 2.0)

    def convert_scene_to_window(self, position):
        point = self.matrix.point_in_matrix_space(position)
        return point[0], point[1]

    def convert_window_to_scene(self, position):
        point = self.matrix.point_in_inverse_space(position)
        return point[0], point[1]

    def calculate_grid(self):
        if self.kernel.device is not None:
            v = self.kernel.device
        else:
            v = self.kernel
        p = self.kernel
        wmils = v.bed_width * MILS_IN_MM
        hmils = v.bed_height * MILS_IN_MM
        convert = p.units_convert
        marks = p.units_marks
        step = convert * marks
        starts = []
        ends = []
        if step == 0:
            self.grid = None
            return starts, ends
        x = 0.0
        while x < wmils:
            starts.append((x, 0))
            ends.append((x, hmils))
            x += step
        y = 0.0
        while y < hmils:
            starts.append((0, y))
            ends.append((wmils, y))
            y += step
        self.grid = starts, ends

    def on_draw_grid(self, gc):
        # Convert to GC.
        if self.grid is None:
            self.calculate_grid()
        starts, ends = self.grid
        gc.StrokeLineSegments(starts, ends)

    def on_draw_guides(self, gc):
        w, h = self.Size
        p = self.kernel
        scaled_conversion = p.units_convert * self.matrix.value_scale_x()
        if scaled_conversion == 0:
            return

        wpoints = w / 15.0
        hpoints = h / 15.0
        points = min(wpoints, hpoints)
        # tweak the scaled points into being useful.
        # points = scaled_conversion * round(points / scaled_conversion * 10.0) / 10.0
        points = scaled_conversion * float('{:.1g}'.format(points / scaled_conversion))
        sx, sy = self.convert_scene_to_window([0, 0])
        if points == 0:
            return
        offset_x = sx % points
        offset_y = sy % points

        starts = []
        ends = []
        x = offset_x
        length = 50
        font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)
        gc.SetFont(font, wx.BLACK)
        while x < w:
            starts.append((x, 0))
            ends.append((x, length))

            starts.append((x, h))
            ends.append((x, h - length))

            mark_point = (x - sx) / scaled_conversion
            if round(mark_point * 1000) == 0:
                mark_point = 0.0  # prevents -0
            gc.DrawText("%g %s" % (mark_point, p.units_name), x, 0, -tau / 4)
            x += points

        y = offset_y
        while y < h:
            starts.append((0, y))
            ends.append((length, y))

            starts.append((w, y))
            ends.append((w - length, y))

            mark_point = (y - sy) / scaled_conversion
            if round(mark_point * 1000) == 0:
                mark_point = 0.0  # prevents -0
            gc.DrawText("%g %s" % (mark_point + 0, p.units_name), 0, y + 0)
            y += points
        gc.StrokeLineSegments(starts, ends)

    def on_draw_interface(self, gc):
        pen = wx.Pen(wx.BLACK)
        pen.SetWidth(1)
        pen.SetCap(wx.CAP_BUTT)
        gc.SetPen(pen)
        if self.kernel.draw_mode & 2 == 0:
            self.on_draw_guides(gc)
        if self.kernel.draw_mode & 16 == 0:
            # Draw Reticle
            gc.SetPen(wx.RED_PEN)
            gc.SetBrush(wx.TRANSPARENT_BRUSH)
            try:
                x = self.kernel.device.current_x
                y = self.kernel.device.current_y
                x, y = self.convert_scene_to_window([x, y])
                gc.DrawEllipse(x - 5, y - 5, 10, 10)
            except AttributeError:
                pass

    def on_draw_bed(self, gc):
        if self.kernel.device is not None:
            v = self.kernel.device
        else:
            v = self.kernel
        wmils = v.bed_width * MILS_IN_MM
        hmils = v.bed_height * MILS_IN_MM
        if self.background is None:
            gc.SetBrush(wx.WHITE_BRUSH)
            gc.DrawRectangle(0, 0, wmils, hmils)
        else:
            gc.DrawBitmap(self.background, 0, 0, wmils, hmils)

    def on_draw_selection(self, gc, draw_mode):
        """Draw Selection Box"""
        bounds = self.root.bounds
        if bounds is not None:
            linewidth = 3.0 / self.matrix.value_scale_x()
            self.selection_pen.SetWidth(linewidth)
            font = wx.Font(14.0 / self.matrix.value_scale_x(), wx.SWISS, wx.NORMAL, wx.BOLD)
            gc.SetFont(font, wx.BLACK)

            gc.SetPen(self.selection_pen)
            gc.SetBrush(wx.BLACK_BRUSH)
            x0, y0, x1, y1 = bounds
            center_x = (x0 + x1) / 2.0
            center_y = (y0 + y1) / 2.0
            gc.StrokeLine(center_x, 0, center_x, y0)
            gc.StrokeLine(0, center_y, x0, center_y)
            gc.StrokeLine(x0, y0, x1, y0)
            gc.StrokeLine(x1, y0, x1, y1)
            gc.StrokeLine(x1, y1, x0, y1)
            gc.StrokeLine(x0, y1, x0, y0)
            if draw_mode & 128 == 0:
                p = self.kernel
                conversion, name, marks, index = p.units_convert, p.units_name, p.units_marks, p.units_index
                gc.DrawText("%.1f%s" % (y0 / conversion, name), center_x, y0)
                gc.DrawText("%.1f%s" % (x0 / conversion, name), x0, center_y)
                gc.DrawText("%.1f%s" % ((y1 - y0) / conversion, name), x1, center_y)
                gc.DrawText("%.1f%s" % ((x1 - x0) / conversion, name), center_x, y1)

    def on_draw_laserpath(self, gc, draw_mode):
        gc.SetPen(wx.BLUE_PEN)
        starts, ends = self.laserpath
        gc.StrokeLineSegments(starts, ends)

    def on_draw_scene(self, gc):
        self.on_draw_bed(gc)
        gc.SetPen(wx.BLACK_PEN)
        if self.kernel.draw_mode & 4 == 0:
            self.on_draw_grid(gc)
        pen = wx.Pen(wx.BLACK)
        pen.SetWidth(1)
        pen.SetCap(wx.CAP_BUTT)
        gc.SetPen(pen)
        if self.kernel is None:
            return
        self.renderer.render(gc, self.kernel.draw_mode)
        if self.kernel.draw_mode & 32 == 0:
            self.on_draw_selection(gc, self.kernel.draw_mode)
        if self.kernel.draw_mode & 8 == 0:
            self.on_draw_laserpath(gc, self.kernel.draw_mode)

    def on_click_new(self, event):  # wxGlade: MeerK40t.<event_handler>
        self.working_file = None
        self.kernel.elements = []
        self.kernel.operations = []
        self.kernel.filenodes = {}
        self.request_refresh()
        self.kernel.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.kernel.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_save(self, event):
        if self.working_file is None:
            self.on_click_save_as(event)
        else:
            self.kernel.save(self.working_file)

    def on_click_save_as(self, event):
        files = self.kernel.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.kernel.save(pathname)
            self.working_file = pathname

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

    def on_click_zoom_out(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Zoomout button press
        """
        m = self.scene.ClientSize / 2
        self.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.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.
        """
        self.focus_on_elements()

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

        def toggle(event):
            self.kernel.draw_mode ^= bits
            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.kernel._stepping_force = int(value)
            else:
                self.kernel._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.kernel.device
            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 self.kernel.elements:
                    try:
                        element *= mx
                    except AttributeError:
                        pass
                self.kernel.signal('rebuild_tree', 0)

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

        if dlg.ShowModal() == wx.ID_OK:
            path = Path(dlg.GetValue())
            path.stroke = 'blue'
            p = abs(path)
            self.kernel.elements.append(p)
            self.kernel.classify(p)
            self.kernel.signal("rebuild_tree", 0)
        dlg.Destroy()

    def run_home_and_dot_test(self):
        self.kernel.signal("rebuild_tree", 0)

        def home_dot_test():
            for i in range(25):
                yield COMMAND_SET_ABSOLUTE
                yield COMMAND_MODE_DEFAULT
                yield COMMAND_HOME
                yield COMMAND_WAIT_BUFFER_EMPTY
                yield COMMAND_RAPID_MOVE, (3000, 3000)
                yield COMMAND_LOCK
                yield COMMAND_WAIT_BUFFER_EMPTY
                yield COMMAND_LASER_ON
                yield COMMAND_WAIT, 0.05
                yield COMMAND_LASER_OFF
                yield COMMAND_LOCK
                yield COMMAND_WAIT_BUFFER_EMPTY
            yield COMMAND_HOME
            yield COMMAND_WAIT_BUFFER_EMPTY

        self.kernel.device.spooler.send_job(home_dot_test)

    def launch_webpage(self, event):  # wxGlade: MeerK40t.<event_handler>
        """
        Launch webpage

        :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
        if name is None:
            self.name = str(data_object)
        else:
            self.name = name
        if len(self.name) >= 27:
            self.name = self.name[:28] + '...'
        self.type = node_type
        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 = str(self.object)
        if len(self.name) >= 27:
            self.name = self.name[:28] + '...'
        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

    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_icon(self, icon=None):
        root = self.root
        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, width=20, height=20)
                image_id = self.root.tree_images.Add(bitmap=image)
                tree.SetItemImage(item, image=image_id)
            if isinstance(data_object, Path):
                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 center(self):
        try:
            bounds = self.bounds
            return (bounds[2] + bounds[0]) / 2.0, (bounds[3] + bounds[1]) / 2.0
        except Exception:
            return None

    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
    kernel. This is to allow nested structures beyond the flat structure of the actual data. It serves to help with
    menu creation, name, drag and drop, bounding box cache, tree element updates.

    The tree is structured with three main sub-elements of the RootNode, these are the Operations, the Elements, and
    the files.

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

    Elements store the graphics elements stored within the scene. The Elements are a list of elements stored in their
    desired ordered. This structure should reflect those changes back to structure in the kernel.

    Deleting an element from the tree should remove that element from any operation using it.
    Deleting an operation should make no change to the 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 rather than recreating the entire thing.
    """

    def __init__(self, kernel, gui):
        list.__init__(self)
        self.root = self
        self.parent = self
        self.object = "Project"
        self.name = "Project"
        self.semi_selected = []
        self.highlighted = []
        self.type = NODE_ROOT

        self.kernel = kernel
        self.gui = gui
        self.tree = gui.tree
        self.renderer = gui.renderer

        self.bounds = None
        self.selected_elements = []
        self.selected_operations = []

        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.rebuild_tree()

    def highlight_select(self, item):
        if item not in self.highlighted:
            self.highlighted.append(item)
            self.tree.SetItemBackgroundColour(item, wx.YELLOW)

    def highlight_unselect(self):
        self.set_selected_elements(None)
        self.set_selected_operations(None)
        for item in self.highlighted:
            self.tree.SetItemBackgroundColour(item, wx.WHITE)
        self.highlighted.clear()

    def highlight_select_all(self, objects):
        for e in objects:
            self.highlight_select(e)

    def semi_select(self, item):
        if item not in self.semi_selected:
            self.semi_selected.append(item)
            self.tree.SetItemBackgroundColour(item, wx.CYAN)
            node = self.tree.GetItemData(item)
            if node.type == NODE_ELEMENT:
                self.selected_elements.append(node.object)
            elif node.type == NODE_OPERATION:
                self.selected_operations.append(node.object)

    def semi_unselect(self):
        self.set_selected_elements(None)
        self.set_selected_operations(None)
        for item in self.semi_selected:
            self.tree.SetItemBackgroundColour(item, wx.WHITE)
        self.semi_selected.clear()

    def semi_select_all(self, objects):
        for e in objects:
            self.semi_select(e)

    def rebuild_tree(self):
        self.semi_selected.clear()
        self.highlighted.clear()
        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, self.kernel.operations, self, self, name=_("Operations"))
        self.node_operations.set_icon(icons8_laser_beam_20.GetBitmap())
        self.build_tree(self.node_operations, self.kernel.operations)
        for n in self.node_operations:
            if isinstance(n.object, RasterOperation):
                n.set_icon(icons8_direction_20.GetBitmap())
            else:
                n.set_icon(icons8_laser_beam_20.GetBitmap())

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

        self.node_files = Node(NODE_FILES_BRANCH, self.kernel.filenodes, self, self, name=_("Files"))
        self.node_files.set_icon(icons8_file_20.GetBitmap())
        self.build_tree(self.node_files, self.kernel.filenodes)
        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.kernel.signal("rebuild_tree", 0)

    def notify_tree_data_cleared(self):
        self.kernel.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 set_selected_elements(self, selected):
        self.selected_operations.clear()
        self.selected_elements.clear()
        if selected is not None:
            if not isinstance(selected, list):
                self.selected_elements.append(selected)
            else:
                self.selected_elements.extend(selected)
        self.selection_updated()

    def set_selected_operations(self, selected):
        self.selected_operations.clear()
        self.selected_elements.clear()
        if selected is not None:
            if not isinstance(selected, list):
                self.selected_operations.append(selected)
            else:
                self.selected_operations.extend(selected)
        self.selection_updated()

    def selection_updated(self):
        self.kernel.signal("selected_ops", self.selected_operations)
        self.kernel.signal("selected_elements", self.selected_elements)
        self.selection_bounds_updated()

    def selection_bounds_updated(self):
        self.bounds = OperationPreprocessor.bounding_box(self.selected_elements)
        self.kernel.signal("selected_bounds", self.bounds)

    def activate_selected_node(self):
        if self.selected_elements is not None and len(self.selected_elements) != 0:
            self.activated_object(self.selected_elements[0])

    def move_selected(self, dx, dy):
        if self.selected_elements is None:
            return
        if len(self.selected_elements) == 0:
            return
        for obj in self.selected_elements:
            obj.transform.post_translate(dx, dy)
        b = self.bounds
        self.bounds = [b[0] + dx, b[1] + dy, b[2] + dx, b[3] + dy]
        self.kernel.signal("selected_bounds", self.bounds)

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

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

        drag_item = event.GetItem()
        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.kernel.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

        event.Skip()
        # Do not allow images added to engrave or cut operations
        # Group dragged into group, creates subgroup.
        # LaserOperation Elements dragged from one LaserOperation to another.

    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)
        event.Skip()

    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 activated_object(self, obj):
        if isinstance(obj, RasterOperation):
            self.kernel.open_window("RasterProperty").set_operation(obj)
        elif isinstance(obj, (CutOperation, EngraveOperation)):
            self.kernel.open_window("EngraveProperty").set_operation(obj)
        elif isinstance(obj, Path):
            self.kernel.open_window("PathProperty").set_element(obj)
        elif isinstance(obj, SVGText):
            self.kernel.open_window("TextProperty").set_element(obj)
        elif isinstance(obj, SVGImage):
            self.kernel.open_window("ImageProperty").set_element(obj)
        elif isinstance(obj, SVGElement):
            self.kernel.open_window("PathProperty").set_element(obj)
        elif isinstance(obj, LaserOperation):
            self.kernel.open_window("EngraveProperty").set_operation(obj)

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

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

        if node is None:
            return
        self.semi_unselect()
        self.highlight_unselect()
        self.semi_select_all(self.tree.GetSelections())
        if node.type == NODE_ELEMENTS_BRANCH:
            for n in self.node_elements:
                self.semi_select(n.item)
            self.gui.request_refresh()
            self.selection_updated()
            return
        elif node.type == NODE_OPERATION:
            for n in node:
                self.highlight_select(n.item)
            self.gui.request_refresh()
            self.selection_updated()
            return
        elif node.type == NODE_FILE_FILE:
            for n in node:
                obj = n.object
                links = self.tree_lookup[id(obj)]
                for link in links:
                    self.semi_select(link.item)
            self.gui.request_refresh()
            self.selection_updated()
            return
        elif node.type == NODE_OPERATION_ELEMENT:
            obj = node.object
            if len(self.semi_selected) != 1:
                return  # If this is a multi-selection event, do not select other nodeop_elements
            links = self.tree_lookup[id(obj)]
            for link in links:
                self.semi_select(link.item)
            self.selection_updated()
            return
        elif node.type == NODE_ELEMENT:
            for item in self.tree.GetSelections():
                node = self.tree.GetItemData(item)
                obj = node.object
                links = self.tree_lookup[id(obj)]
                for link in links:
                    self.semi_select(link.item)
            self.selection_updated()
            return
        self.gui.request_refresh()
        self.selection_updated()
        event.Allow()

    def set_selected_by_position(self, position):
        if self.selected_elements is not None:
            if self.bounds is not None and self.contains(self.bounds, position):
                return  # Select by position aborted since selection position within current select bounds.
        self.selected_elements.clear()
        for e in reversed(self.kernel.elements):
            bounds = e.bbox()
            if bounds is None:
                continue
            if self.contains(bounds, position):
                self.set_selected_elements(e)
                return
        self.selection_updated()

    def contains(self, box, x, y=None):
        if y is None:
            x, y = x
        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

        t = node.type
        selections = [self.tree.GetItemData(e) for e in self.semi_selected]
        selections = [s for s in 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 in (NODE_OPERATION_BRANCH, NODE_FILES_BRANCH, NODE_ELEMENTS_BRANCH, NODE_OPERATION):
            gui.Bind(wx.EVT_MENU, self.menu_clear_all(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_remove_multi(node),
                     menu.Append(wx.ID_ANY, _("Remove: %d objects") % len(selections), "", wx.ITEM_NORMAL))
        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:
            pass
        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"):
                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_op.Enable(False)
            for name in ("ZDepth_Raster", "Multishade_Raster", "Wait-Step_Raster"):
                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_op.Enable(False)
            menu.AppendSubMenu(operation_convert_submenu, _("Convert Operation"))
            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 isinstance(node.object, RasterOperation):
                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"))
            gui.Bind(wx.EVT_MENU, self.menu_reset(node),
                     menu.Append(wx.ID_ANY, _("Reset User Changes"), "", wx.ITEM_NORMAL))
            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))
            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))
            menu.AppendSubMenu(path_rotate_sub_menu, _("Rotate"))
            gui.Bind(wx.EVT_MENU, self.menu_reify(node),
                     menu.Append(wx.ID_ANY, _("Reify User Changes"), "", wx.ITEM_NORMAL))
            if isinstance(node.object, Path):
                gui.Bind(wx.EVT_MENU, self.menu_subpath(node),
                         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_raster_actualize(node),
                         menu.Append(wx.ID_ANY, _("Actualize Pixels"), "", wx.ITEM_NORMAL))
                gui.Bind(wx.EVT_MENU, self.menu_dither(node),
                         menu.Append(wx.ID_ANY, _("Dither to 1 bit"), "", 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)
                menu.AppendSubMenu(raster_zdepth_menu, _("ZDepth Divide"))

            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_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
            if isinstance(element, RasterOperation):
                element.raster_step = step_value
            self.kernel.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[VARIABLE_NAME_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)
            self.kernel.signal("element_property_update", node.object)
            self.root.gui.request_refresh()

        return specific

    def menu_raster_actualize(self, node):
        """
        Causes the raster image to be native at the current scale by rotating, scaling, skewing etc.

        :param node:
        :return:
        """

        def specific(event):
            element = node.object
            if isinstance(element, SVGImage):
                OperationPreprocessor.make_actual(element)
                node.bounds = None
                node.set_icon()
            self.selection_bounds_updated()
            self.kernel.signal('rebuild_tree', 0)

        return specific

    def menu_dither(self, node):
        """
        Change raster dither forcing raster elements to 1 bit.

        :param node:
        :return:
        """

        def specific(event):
            element = node.object
            if isinstance(element, SVGImage):
                img = element.image
                if img.mode == 'RGBA':
                    pixel_data = img.load()
                    width, height = img.size
                    for y in range(height):
                        for x in range(width):
                            if pixel_data[x, y][3] == 0:
                                pixel_data[x, y] = (255, 255, 255, 255)
                element.image = img.convert("1")
                element.cache = None
            self.kernel.signal('rebuild_tree', 0)

        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):
            element = node.object
            if not isinstance(element, SVGImage):
                return
            adding_elements = []
            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

                image_element = copy(element)
                image_element.image = image_element.image.copy()
                if OperationPreprocessor.needs_actualization(image_element):
                    OperationPreprocessor.make_actual(image_element)
                img = image_element.image
                new_data = img.load()
                width, height = img.size
                for y in range(height):
                    for x in range(width):
                        pixel = new_data[x, y]
                        if pixel[3] == 0:
                            new_data[x, y] = (255, 255, 255, 255)
                            continue
                        gray = (pixel[0] + pixel[1] + pixel[2]) / 3.0
                        if threshold_min >= gray:
                            new_data[x, y] = (0, 0, 0, 255)
                        elif threshold_max < gray:
                            new_data[x, y] = (255, 255, 255, 255)
                        else:  # threshold_min <= grey < threshold_max
                            v = gray - threshold_min
                            v *= divide
                            v = int(round(v))
                            new_data[x, y] = (v, v, v, 255)
                image_element.image = image_element.image.convert('1')
                adding_elements.append(image_element)
            self.kernel.elements.extend(adding_elements)
            self.kernel.classify(adding_elements)
            self.set_selected_elements(None)
            self.kernel.signal('rebuild_tree', 0)

        return specific

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

        :param node:
        :return:
        """

        def specific(event):
            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) / step, height=(ymax - ymin) / 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

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

        return specific

    def menu_reify(self, node):
        """
        Reify elements so that the translations apply direct to the object.

        :param node:
        :return:
        """

        def specific(event):
            for element in self.selected_elements:
                element.reify()
                element.cache = None
            self.kernel.signal('rebuild_tree', 0)

        return specific

    def menu_reset(self, node):
        """
        Menu to reset transformations applied to elements.

        :param node:
        :return:
        """

        def specific(event):
            for e in self.selected_elements:
                e.transform.reset()
            self.selection_bounds_updated()
            self.gui.request_refresh()

        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
            # center = node.parent.center()

            for obj in self.selected_elements:
                obj.transform.post_rotate(value, center_x, center_y)
            self.selection_bounds_updated()
            self.kernel.signal('rebuild_tree', 0)

        return specific

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

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

        def specific(event):
            bounds = self.bounds

            center_x = (bounds[2] + bounds[0]) / 2.0
            center_y = (bounds[3] + bounds[1]) / 2.0
            # center = node.parent.center()

            for obj in self.selected_elements:
                obj.transform.post_scale(value, value, center_x, center_y)
            self.selection_bounds_updated()
            self.kernel.signal('rebuild_tree', 0)

        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.gui.load(filepath)

        return specific

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

        :param node:
        :return:
        """

        def specific(event):
            node = remove_node

            selections = [self.tree.GetItemData(e) for e in self.semi_selected]
            selections = [s for s in selections if s.type == node.type]

            if node.type == NODE_ELEMENT:
                # Removing element can only have 1 copy.
                removed_objects = self.selected_elements
                for e in removed_objects:
                    self.kernel.elements.remove(e)

                for i in range(len(self.kernel.operations)):
                    elems = [e for e in self.kernel.operations[i] if e not in removed_objects]
                    self.kernel.operations[i].clear()
                    self.kernel.operations[i].extend(elems)
                    if len(self.kernel.operations[i]) == 0:
                        self.kernel.operations[i] = None
                ops = [op for op in self.kernel.operations if op is not None]
                self.kernel.operations.clear()
                self.kernel.operations.extend(ops)
            elif node.type == NODE_OPERATION_ELEMENT:
                # Operation_element can occur many times in the same operation node.
                modified = []
                for node in selections:
                    index = node.parent.index(node)
                    op = node.parent.object
                    if index == -1:
                        continue
                    op[index] = None
                    if op not in modified:
                        modified.append(op)
                for s in modified:
                    op_elems = [op_elem for op_elem in s if op_elem is not None]
                    s.clear()
                    s.extend(op_elems)
            self.set_selected_elements(None)
            self.kernel.signal('rebuild_tree', 0)

        return specific

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

        :param node:
        :return:
        """

        def specific(event):
            node = remove_node
            if node.type == NODE_ELEMENT:
                # Removing element can only have 1 copy.
                # All selected elements are removed.
                removed_objects = self.selected_elements
                for e in removed_objects:
                    self.kernel.elements.remove(e)
                for i in range(len(self.kernel.operations)):
                    elems = [e for e in self.kernel.operations[i] if e not in removed_objects]
                    self.kernel.operations[i].clear()
                    self.kernel.operations[i].extend(elems)
                    if len(self.kernel.operations[i]) == 0:
                        self.kernel.operations[i] = None
                ops = [op for op in self.kernel.operations if op is not None]
                self.kernel.operations.clear()
                self.kernel.operations.extend(ops)
            elif node.type == NODE_OPERATION:
                # Removing operation can only have 1 copy.
                self.kernel.operations.remove(node.object)
            elif node.type == NODE_FILE_FILE:
                # Removing file can only have 1 copy.
                del self.kernel.filenodes[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.set_selected_elements(None)
            self.kernel.signal('rebuild_tree', 0)

        return specific

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

        :param node:
        :return:
        """

        def specific(event):
            adding_elements = [copy(e) for e in list(self.selected_elements) * copies]
            self.kernel.elements.extend(adding_elements)
            self.kernel.classify(adding_elements)
            self.set_selected_elements(None)
            self.kernel.signal('rebuild_tree', 0)

        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.kernel.signal('rebuild_tree', 0)

        return specific

    def menu_subpath(self, node):
        """
        Menu to break element into subpath.

        :param node:
        :return:
        """

        def specific(event):
            for e in self.selected_elements:
                p = abs(e)
                add = []
                for subpath in p.as_subpaths():
                    subelement = Path(subpath)
                    add.append(subelement)
                self.kernel.elements.extend(add)
            self.kernel.signal('rebuild_tree', 0)
            self.set_selected_elements(None)

        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.kernel.open_window("JobInfo").set_operations(self.selected_operations)

        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:
        """

        def specific(event):
            node.object.reverse()
            self.kernel.signal('rebuild_tree', 0)

        return specific

    def menu_clear_all(self, node):
        def specific(event):
            if node.type == NODE_ELEMENTS_BRANCH:
                elements = self.kernel.elements
                for i in range(len(self.kernel.operations)):
                    self.kernel.operations[i] = [e for e in self.kernel.operations[i]
                                                 if e not in elements]
                    if len(self.kernel.operations[i]) == 0:
                        self.kernel.operations[i] = None
                self.kernel.operations = [op for op in self.kernel.operations
                                          if op is not None]
            node.object.clear()
            self.selection_bounds_updated()
            self.kernel.signal('rebuild_tree', 0)

        return specific

    def menu_reclassify_operations(self, node):
        def specific(event):
            kernel = node.root.kernel
            kernel.operations.clear()
            kernel.classify(kernel.elements)
            self.kernel.signal('rebuild_tree', 0)

        return specific

    def menu_convert_operation(self, node, name):
        def specific(event):
            raise NotImplementedError

        return specific

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

        return specific


class MappedKey:
    """
    Mapped key class containing the key and the command.
    """

    def __init__(self, key, command):
        self.key = key
        self.command = command

    def __str__(self):
        return self.key


class wxMeerK40t(Module, wx.App):
    """
    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):
        wx.App.__init__(self, 0)
        Module.__init__(self)
        self.locale = None
        self.kernel = None

    def OnInit(self):
        return True

    def initialize(self, kernel, name=None):
        kernel.setting(wx.App, 'gui', self)  # Registers self as kernel.gui
        kernel.add_window("MeerK40t", MeerK40t)

        self.kernel = kernel
        _ = wx.GetTranslation

        wx.Locale.AddCatalogLookupPathPrefix('locale')

        kernel.run_later = wx.CallAfter
        kernel.translation = wx.GetTranslation
        kernel.set_config(wx.Config("MeerK40t"))
        kernel.setting(int, 'language', None)

        kernel.add_window('Shutdown', Shutdown)
        kernel.add_window('PathProperty', PathProperty)
        kernel.add_window('TextProperty', TextProperty)
        kernel.add_window('ImageProperty', ImageProperty)
        kernel.add_window('RasterProperty', RasterProperty)
        kernel.add_window('EngraveProperty', EngraveProperty)
        kernel.add_window('Controller', Controller)
        kernel.add_window("Preferences", Preferences)
        kernel.add_window("CameraInterface", CameraInterface)
        kernel.add_window("Settings", Settings)
        kernel.add_window("Rotary", RotarySettings)
        kernel.add_window("Alignment", Alignment)
        kernel.add_window("About", About)
        kernel.add_window("DeviceManager", DeviceManager)
        kernel.add_window("Keymap", Keymap)
        kernel.add_window("UsbConnect", UsbConnect)
        kernel.add_window("Navigation", Navigation)
        kernel.add_window("Controller", Controller)
        kernel.add_window("JobSpooler", JobSpooler)
        kernel.add_window("JobInfo", JobInfo)
        kernel.add_window("BufferView", BufferView)
        kernel.add_window("Adjustments", Adjustments)
        kernel.add_control("Delete Settings", self.clear_control)
        language = kernel.language
        if language is not None and language != 0:
            self.language_to(language)(None)
        self.kernel.open_window("MeerK40t")

    def clear_control(self):
        if self.kernel.config is not None:
            self.kernel.config.DeleteAll()
            self.kernel.config = None
            self.kernel.shutdown()

    def shutdown(self, kernel):
        self.kernel = None
        del kernel.modules['MeerK40t']

    def language_swap(self, lang):
        self.language_to(lang)(None)
        self.kernel.open_window("MeerK40t")

    def language_to(self, lang):
        """
        Returns a function to change the language to the language specified.
        :param lang: language to switch to
        :return:
        """

        def update_language(event):
            """
            Update language to the requested language.
            """
            language_code, language_name, language_index = supported_languages[lang]
            self.kernel.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
            self.kernel.signal('language', (lang, language_code, language_name, language_index))

        return update_language


# 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" % MEERK40T_VERSION)
            file.write("Please report to: %s\n\n" % MEERK40T_ISSUES)
            file.write(err_msg)
            print(file)
    except:  # I already crashed once, if there's another here just ignore it.
        pass
    dlg = wx.MessageDialog(None, err_msg, _('Error encountered'), wx.OK | wx.ICON_ERROR)
    dlg.ShowModal()
    dlg.Destroy()


sys.excepthook = handleGUIException
