# coding: utf-8
# python 2 only

# Copyright (c) 2026 TormachTips.com. All rights reserved.
# Licensed under the TormachTips Personal Use License.
# Permission is granted only for private personal use and private personal modification.
# No sharing, publication, distribution, resale, sublicensing, screenshots, code excerpts,
# benchmarks, or videos are permitted without prior written permission.
# Requests:         tormach.1100m@gmail.com
# Information page: https://tormachtips.com/plugins.htm

#############################################
##                                         ##
##      Tool Table Focus Fixer 0.96        ##
##          www.tormachtips.com            ##
##                                         ##
#############################################

# 0.96 - Public release. - 6/13/2026

### IN THE TOOL TABLE, UNDER OFFSETS, FIXES GTK FOCUS / REFRESH / REDRAW ISSUES ON AT LEAST 2.14.3.
### ALLOWS TYPING A DESCRIPTION, PRESSING ENTER, WHICH GOES TO DIA, PRESSING ENTER AGAIN GOES TO LENGTH, ETC.
### RUN-TIME MONKEY PATCH, DOES NOT EDIT OEM FILES.
### OEM CODE HAS A BUG WHERE, AFTER PRESSING ENTER, REDRAWS THE GTK AND FORCES FOCUS TO THE TOP ROW.

import os
import gtk
import glib
import types
import constants
import singletons
from ui_hooks import plugin
try:
    from constants import LOCKED_TABLE_ENTRY_COLOR
except:
    LOCKED_TABLE_ENTRY_COLOR = "#888888"

CURRENT_VER      = "0.96"
SCRIPT_NAME      = "Tool Table Focus Fixer"
DESCRIPTION      = "Runtime monkey patch for PathPilot 2.10+ tool-table focus/edit behavior."
ENABLED          = 1
DEV_MACHINE      = 0
DEV_MACHINE_FLAG = "/home/operator/gcode/python/dev_machine.txt"

class UserPlugin(plugin):
    def __init__(self):
        plugin.__init__(self, SCRIPT_NAME)
        dev_machine_found = os.path.exists(DEV_MACHINE_FLAG)
        if dev_machine_found:
            plugin_enabled = DEV_MACHINE
        else:
            plugin_enabled = ENABLED
        if not plugin_enabled:
            if dev_machine_found:
                self.error_handler.write("[%s] Dev machine found. Plugin loaded, but disabled by DEV_MACHINE." % SCRIPT_NAME, constants.ALARM_LEVEL_QUIET)
            else:
                self.error_handler.write("[%s] Plugin loaded, but disabled." % SCRIPT_NAME, constants.ALARM_LEVEL_QUIET)
                self.error_handler.write("[%s] To enable, open script, find ENABLED = 0 and change to ENABLED = 1" % SCRIPT_NAME, constants.ALARM_LEVEL_QUIET)
            return
        self.ui = None
        self.installed = False
        glib.timeout_add(1000, self._install_when_ready)

    def _install_when_ready(self):
        try:
            ui = singletons.g_Machine
            if ui is None:
                return True
            if not hasattr(ui, "tool_treeview"):
                return True
            if ui.tool_treeview is None:
                return True
            if not hasattr(ui, "tool_diameter_column"):
                return True
            if self.installed:
                return False
            self.ui = ui
            self._patch_methods(ui)
            self._connect_single_click_editor(ui)
            self.installed = True
            self.error_handler.write("[%s] Installed runtime tool-table focus fixes." % SCRIPT_NAME, constants.ALARM_LEVEL_QUIET)
            return False
        except Exception as e:
            self.error_handler.write("[%s] Install failed: {}".format(str(e)) % SCRIPT_NAME, constants.ALARM_LEVEL_QUIET)
            return False

    def _patch_methods(self, ui):
        ui.pending_tool_table_focus = None

        def set_pending_tool_table_focus(ui_self, row, column, start_editing):
            # Store the next focus target. Applying it later avoids fighting the edited callback.
            ui_self.pending_tool_table_focus = (int(row), column, bool(start_editing))

        def apply_pending_tool_table_focus(ui_self):
            try:
                focus_info = getattr(ui_self, "pending_tool_table_focus", None)
                if focus_info is None:
                    return False
                ui_self.pending_tool_table_focus = None
                row, column, start_editing = focus_info
                ui_self.tool_table_update_focus(row, column, start_editing)
            except Exception as e:
                try:
                    ui_self.error_handler.write("[%s] Pending focus failed: {}".format(str(e)) % SCRIPT_NAME, constants.ALARM_LEVEL_DEBUG)
                except:
                    pass
            return False

        def finish_tool_table_column_edit(ui_self, row):
            # If Enter selected a next cell, avoid the OEM observer/focus flash and go there directly.
            if getattr(ui_self, "pending_tool_table_focus", None) is not None:
                glib.timeout_add(75, ui_self.apply_pending_tool_table_focus)
                ui_self.hide_onscreen_keyboard()
                return
            glib.idle_add(ui_self.tool_table_update_observer, row)
            ui_self.hide_onscreen_keyboard()

        def table_edition_status(ui_self, editable):
            ui_self.tool_table_is_editable = editable
            try:
                # Do not call tool_treeview.set_sensitive(editable). It causes GTK TreeView focus/selection flicker
                # and can interfere with inline cell editing. The overlay eventbox still blocks locked editing.
                ui_self.tool_treeview.set_can_focus(editable)
            except:
                pass
            if editable:
                ui_self.tt_evbox.hide()
                ui_self.save_and_toggle_selection(ui_self.tool_treeview, 'selected_path_tool', restore=True)
                ui_self.save_and_toggle_selection(ui_self.work_treeview, 'selected_path_work_offset', restore=True)
            else:
                ui_self.tt_evbox.show()
                ui_self.save_and_toggle_selection(ui_self.tool_treeview, 'selected_path_tool', restore=False)
                ui_self.save_and_toggle_selection(ui_self.work_treeview, 'selected_path_work_offset', restore=False)
            if ui_self.machineconfig.machine_class() == 'lathe':
                attr_sel_wo = {'X':1, 'Y':2, 'Z':3, 'A':4}
                attr_sel_tt = {'X Wear':(5,9), 'Z Wear':(6,10)}
                fg_bg_ix = (5, 6)
                for column in ui_self.tool_treeview.get_columns():
                    column_name = column.get_title()
                    if column_name not in attr_sel_tt.keys():
                        continue
                    attrs = {'text': attr_sel_tt[column_name][0]}
                    if editable:
                        attrs['foreground'] = attr_sel_tt[column_name][1]
                    for renderer in column.get_cell_renderers():
                        if isinstance(renderer, gtk.CellRendererText):
                            if not editable:
                                renderer.set_property('foreground', LOCKED_TABLE_ENTRY_COLOR)
                            column.set_attributes(renderer, **attrs)
                ui_self.tool_treeview.queue_draw()
            elif ui_self.machineconfig.machine_class() in ('mill', 'router'):
                attr_sel_wo = {'X':2, 'Y':3, 'Z':4, 'A':5}
                fg_bg_ix = (6, 7)
            elif ui_self.machineconfig.machine_class() == 'plasma':
                attr_sel_wo = {'X':1, 'Y':2, 'Z':3, 'A':4}
                fg_bg_ix = (5, 6)
            else:
                attr_sel_wo = {}
                fg_bg_ix = (0, 0)
            for column in ui_self.work_treeview.get_columns():
                column_name = column.get_title()
                if column_name not in attr_sel_wo.keys():
                    continue
                attrs = {'text': attr_sel_wo[column_name], 'background': fg_bg_ix[1]}
                if editable:
                    attrs['foreground'] = fg_bg_ix[0]
                    attrs['background'] = fg_bg_ix[1]
                for renderer in column.get_cell_renderers():
                    if isinstance(renderer, gtk.CellRendererText):
                        if not editable:
                            renderer.set_property('foreground', LOCKED_TABLE_ENTRY_COLOR)
                        column.set_attributes(renderer, **attrs)
            ui_self.work_treeview.queue_draw()

        def on_tool_diameter_column_keypress(ui_self, widget, ev, row):
            if ev.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
                ui_self.set_pending_tool_table_focus(row, ui_self.tool_length_column, True)
            return False

        def on_tool_length_column_keypress(ui_self, widget, ev, row):
            if ev.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
                ui_self.set_pending_tool_table_focus(row, ui_self.tool_diameter_wear_column, True)
            return False

        def on_tool_diameter_wear_column_keypress(ui_self, widget, ev, row):
            if ev.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
                ui_self.set_pending_tool_table_focus(row, ui_self.tool_length_wear_column, True)
            return False

        def on_tool_length_wear_column_keypress(ui_self, widget, ev, row):
            if ev.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
                ui_self.set_pending_tool_table_focus(row, ui_self.tool_max_rpm_column, True)
            return False

        def on_tool_max_rpm_column_keypress(ui_self, widget, ev, row):
            if ev.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
                ui_self.set_pending_tool_table_focus(row + 1, ui_self.tool_description_column, False)
            return False

        def on_tool_treeview_editable_cell_release_event(ui_self, treeview, event):
            if event.button != 1:
                return False
            try:
                if ui_self.entry_editing:
                    return False
            except:
                pass
            result = treeview.get_path_at_pos(int(event.x), int(event.y))
            if result is None:
                return False
            path, column, cell_x, cell_y = result
            editable_columns = (
                ui_self.tool_description_column,
                ui_self.tool_diameter_column,
                ui_self.tool_length_column,
                ui_self.tool_diameter_wear_column,
                ui_self.tool_length_wear_column,
                ui_self.tool_max_rpm_column)
            if column not in editable_columns:
                return False
            renderers = column.get_cell_renderers()
            if len(renderers) <= 0:
                return False
            renderer = renderers[0]
            # Delay slightly so GTK can finish ending any previous inline editor first.
            glib.timeout_add(75, ui_self.tool_treeview.set_cursor_on_cell, path, column, renderer, True)
            return False

        ui.set_pending_tool_table_focus = types.MethodType(set_pending_tool_table_focus, ui, ui.__class__)
        ui.apply_pending_tool_table_focus = types.MethodType(apply_pending_tool_table_focus, ui, ui.__class__)
        ui.finish_tool_table_column_edit = types.MethodType(finish_tool_table_column_edit, ui, ui.__class__)
        ui.table_edition_status = types.MethodType(table_edition_status, ui, ui.__class__)
        ui.on_tool_diameter_column_keypress = types.MethodType(on_tool_diameter_column_keypress, ui, ui.__class__)
        ui.on_tool_length_column_keypress = types.MethodType(on_tool_length_column_keypress, ui, ui.__class__)
        ui.on_tool_diameter_wear_column_keypress = types.MethodType(on_tool_diameter_wear_column_keypress, ui, ui.__class__)
        ui.on_tool_length_wear_column_keypress = types.MethodType(on_tool_length_wear_column_keypress, ui, ui.__class__)
        ui.on_tool_max_rpm_column_keypress = types.MethodType(on_tool_max_rpm_column_keypress, ui, ui.__class__)
        ui.on_tool_treeview_editable_cell_release_event = types.MethodType(on_tool_treeview_editable_cell_release_event, ui, ui.__class__)

    def _connect_single_click_editor(self, ui):
        # Avoid duplicate signal connections if this plugin is somehow initialized twice.
        if getattr(ui, "tool_table_focus_patch_click_connected", False):
            return
        ui.tool_treeview.connect_after("button-release-event", ui.on_tool_treeview_editable_cell_release_event)
        ui.tool_table_focus_patch_click_connected = True
