# 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

#############################################
##                                         ##
##      Probing MDI Line Patcher 0.95      ##
##          www.TormachTips.com            ##
##                                         ##
#############################################

# 0.95 - public beta - 4/2/26

# adds an MDI line to the two probing pages (4/02/2026)
# custom request from a FB Groups member.

import os
import re
import shutil
import gtk
import glib
import constants
from ui_hooks import plugin, version_list

CURRENT_VER      = "0.95"
SCRIPT_NAME      = "Probing Screen MDI Line Injector"
DESCRIPTION      = "A custom request to add an MDI line to the probing tabs."
PATCH_MARKER     = "# TormachTips probing mdi line mod"
ENABLED          = 1
DEV_MACHINE      = 0
DEV_MACHINE_FLAG = "/home/operator/gcode/python/dev_machine.txt"

MILL_PROBE_INSERT = """    {marker}
    def on_TormachTips_wcs1_activate(self, widget, data=None):
        text = widget.get_text().strip()
        if not text:
            return
        if not self.is_button_permitted(widget):
            self.issue_mdi(text)
            #self.error_handler.write("we workie anyhow", ALARM_LEVEL_QUIET)
            # Yeah, I know
            return
        self.UIobj.ensure_mode(linuxcnc.MODE_MDI)
        self.issue_mdi(text)
        #self.error_handler.write("worked", ALARM_LEVEL_QUIET)
        # I said, I know.
        # widget.set_text("G56")

    def on_TormachTips_wcs2_activate(self, widget, data=None):
        text = widget.get_text().strip()
        if not text:
            return
        if not self.is_button_permitted(widget):
            self.issue_mdi(text)
            #self.error_handler.write("we workie anyhow", ALARM_LEVEL_QUIET)
            # Yeah, I know
            return
        self.UIobj.ensure_mode(linuxcnc.MODE_MDI)
        self.issue_mdi(text)
        #self.error_handler.write("worked", ALARM_LEVEL_QUIET)
        # I said, I know.
        # widget.set_text("G57")
        
    def on_TormachTips_uppercase_changed(self, widget, data=None):
        text = widget.get_text()
        upper_text = text.upper()
        if text == upper_text:
            return
        pos = widget.get_position()
        widget.set_text(upper_text)
        try:
            widget.set_position(pos)
        except:
            pass          

""".format(marker=PATCH_MARKER)

GLADE_WCS1_BLOCK = """<!-- # TormachTips probing mdi line mod -->
<child>
  <object class="GtkEntry" id="TormachTips_wcs1">
    <property name="width_request">160</property>
    <property name="height_request">25</property>
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="editable">True</property>
    <property name="has_frame">True</property>
    <property name="max_length">100</property>
    <property name="text" translatable="yes"></property>
    <signal name="changed" handler="on_TormachTips_uppercase_changed" swapped="no"/>
    <signal name="activate" handler="on_TormachTips_wcs1_activate" swapped="no"/>
    <signal name="focus-in-event" handler="on_dro_gets_focus" swapped="no"/>
    <signal name="focus-out-event" handler="on_dro_loses_focus" swapped="no"/>
  </object>
  <packing>
    <property name="x">700</property>
    <property name="y">275</property>
  </packing>
</child>
"""

GLADE_WCS2_BLOCK = """<!-- # TormachTips probing mdi line mod -->
<child>
  <object class="GtkEntry" id="TormachTips_wcs2">
    <property name="width_request">160</property>
    <property name="height_request">25</property>
    <property name="visible">True</property>
    <property name="can_focus">True</property>
    <property name="editable">True</property>
    <property name="has_frame">True</property>
    <property name="max_length">100</property>
    <property name="text" translatable="yes"></property>
    <signal name="changed" handler="on_TormachTips_uppercase_changed" swapped="no"/>
    <signal name="activate" handler="on_TormachTips_wcs2_activate" swapped="no"/>
    <signal name="focus-in-event" handler="on_dro_gets_focus" swapped="no"/>
    <signal name="focus-out-event" handler="on_dro_loses_focus" swapped="no"/>
  </object>
  <packing>
    <property name="x">600</property>
    <property name="y">320</property>
  </packing>
</child>
"""

class UserPlugin(plugin):
    def __init__(self):
        plugin.__init__(self, 'TormachTips Probing MDI Line Patcher')
        dev_machine_found = os.path.exists(DEV_MACHINE_FLAG)
        if dev_machine_found:
            plugin_enabled = DEV_MACHINE
        else:
            plugin_enabled = ENABLED
        if plugin_enabled:
            glib.timeout_add(3000, self.try_patch)
            return
        else:
            if dev_machine_found:
                self.error_handler.write("[TormachTips Probing MDI Line Patcher] Dev machine found. Plugin loaded, but disabled by DEV_MACHINE.", constants.ALARM_LEVEL_QUIET)
            else:
                self.error_handler.write("[TormachTips Probing MDI Line Patcher] Plugin loaded, but disabled.", constants.ALARM_LEVEL_QUIET)
                self.error_handler.write("[TormachTips Probing MDI Line Patcher] To enable, open script, find ENABLED = 0 and change to ENABLED = 1", constants.ALARM_LEVEL_QUIET)
            return

    def show_nonblocking_dialog(self, message):
        dialog = gtk.MessageDialog(
            None,
            gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_INFO,
            gtk.BUTTONS_OK,
            message)
        dialog.set_title("TormachTips Probing MDI Line Patcher")
        dialog.set_keep_above(True)
        dialog.set_modal(False)
        def on_response(widget, response_id):
            widget.destroy()
        dialog.connect("response", on_response)
        dialog.show_all()
        return False

    def log(self, msg, level=constants.ALARM_LEVEL_LOW):
        self.error_handler.write("[TormachTips Probing MDI Line Patcher] " + msg, level)

    def backup_once(self, path):
        backup_path = path + ".bak"
        if not os.path.exists(backup_path):
            shutil.copy2(path, backup_path)
            self.log("Backup copied to " + backup_path, constants.ALARM_LEVEL_MEDIUM)
        else:
            self.log("Backup already exists: " + backup_path, constants.ALARM_LEVEL_QUIET)

    def patch_mill_probe(self, path):
        if not os.path.exists(path):
            self.log("File not found: %s" % path, constants.ALARM_LEVEL_LOW)
            return False, "mill_probe.py not found"

        with open(path, 'r') as f:
            content = f.read()

        if PATCH_MARKER in content or (
            "def on_TormachTips_wcs1_activate" in content and
            "def on_TormachTips_wcs2_activate" in content
        ):
            self.log("mill_probe.py already patched. No changes made.", constants.ALARM_LEVEL_QUIET)
            return False, "already patched"

        anchor = (
            "    # -------------------------------------------------------------------------------------------------\n"
            "    # simple one axis probing\n"
            "    # -------------------------------------------------------------------------------------------------\n"
        )

        if anchor not in content:
            self.log("mill_probe.py anchor block not found. Patch skipped.", constants.ALARM_LEVEL_LOW)
            return False, "anchor not found"

        new_content = content.replace(anchor, MILL_PROBE_INSERT + anchor, 1)

        if new_content == content:
            self.log("mill_probe.py produced no change.", constants.ALARM_LEVEL_QUIET)
            return False, "no change"

        self.backup_once(path)
        with open(path, 'w') as f:
            f.write(new_content)

        self.log("Patch applied to: %s" % path, constants.ALARM_LEVEL_MEDIUM)
        return True, "patched"

    def append_after_named_child(self, content, object_id, block_to_append):
        pattern = re.compile(
            r'(?P<block><child>\s*<object\b[\s\S]*?\bid="%s"[\s\S]*?<packing>[\s\S]*?</packing>\s*</child>)' % re.escape(object_id),
            re.MULTILINE
        )
        match = pattern.search(content)
        if not match:
            return None, False
        new_content = content[:match.end('block')] + "\n" + block_to_append + content[match.end('block'):]
        return new_content, True

    def patch_glade(self, path):
        if not os.path.exists(path):
            self.log("File not found: %s" % path, constants.ALARM_LEVEL_LOW)
            return False, "glade not found"

        with open(path, 'r') as f:
            content = f.read()

        already_wcs1 = 'id="TormachTips_wcs1"' in content
        already_wcs2 = 'id="TormachTips_wcs2"' in content

        if already_wcs1 and already_wcs2:
            self.log("mill_probe.glade already patched. No changes made.", constants.ALARM_LEVEL_QUIET)
            return False, "already patched"

        patched_any = False

        if not already_wcs1:
            new_content, found = self.append_after_named_child(content, "probe_find_corner", GLADE_WCS1_BLOCK)
            if not found:
                self.log('probe_find_corner block not found in mill_probe.glade. Patch skipped for TormachTips_wcs1.', constants.ALARM_LEVEL_LOW)
                return False, "probe_find_corner not found"
            content = new_content
            patched_any = True

        if not already_wcs2:
            new_content, found = self.append_after_named_child(content, "probe_rect_circ_results_dro", GLADE_WCS2_BLOCK)
            if not found:
                self.log('probe_rect_circ_results_dro block not found in mill_probe.glade. Patch skipped for TormachTips_wcs2.', constants.ALARM_LEVEL_LOW)
                return False, "probe_rect_circ_results_dro not found"
            content = new_content
            patched_any = True

        if not patched_any:
            self.log("mill_probe.glade produced no change.", constants.ALARM_LEVEL_QUIET)
            return False, "no change"

        self.backup_once(path)
        with open(path, 'w') as f:
            f.write(content)

        self.log("Patch applied to: %s" % path, constants.ALARM_LEVEL_MEDIUM)
        return True, "patched"

    def try_patch(self):
        try:
            version_path = "v%d.%d.%d" % (version_list[0], version_list[1], version_list[2])
            base_path = os.path.join("/home/operator", version_path)
            mill_probe_path = os.path.join(base_path, "python", "mill_probe.py")
            glade_path = os.path.join(base_path, "python", "images", "mill_probe.glade")

            self.log("Checking PathPilot version path: %s" % base_path, constants.ALARM_LEVEL_LOW)

            patched_files = []

            changed, status = self.patch_mill_probe(mill_probe_path)
            if changed:
                patched_files.append(mill_probe_path)

            changed, status = self.patch_glade(glade_path)
            if changed:
                patched_files.append(glade_path)

            if patched_files:
                self.error_handler.write(" ", constants.ALARM_LEVEL_QUIET)
                self.error_handler.write(" ", constants.ALARM_LEVEL_QUIET)
                self.error_handler.write(" ", constants.ALARM_LEVEL_QUIET)
                self.log("Reload PathPilot to take effect.", constants.ALARM_LEVEL_MEDIUM)
                self.error_handler.write(" ", constants.ALARM_LEVEL_QUIET)
                self.error_handler.write(" ", constants.ALARM_LEVEL_QUIET)
                self.error_handler.write(" ", constants.ALARM_LEVEL_QUIET)
                glib.idle_add(self.show_nonblocking_dialog,
                              "TormachTips probing MDI entries patched successfully.\n\nReload PathPilot to take effect.")
            else:
                self.log("Nothing changed. Files were already patched or anchors were not found.", constants.ALARM_LEVEL_QUIET)

        except Exception as e:
            self.log("Error: %s" % str(e), constants.ALARM_LEVEL_LOW)

        return False
        
DESCRIPTION_LONG = """A user requested MDI lines in the probing tabs 
    so that he doesn't have to keep switching back to the main tab when setting 
    up multiple WCS.</font></p>
    <p><a href="images/probemdi1.jpg">
    <img border="2" src="images/probemdi1_small.jpg" xthumbnail-orig-image="images/probemdi1.jpg" width="200" height="150"></a>
    <a href="images/probemdi2.jpg">
    <img border="2" src="images/probemdi2_small.jpg" xthumbnail-orig-image="images/probemdi2.jpg" width="200" height="150"></a>"""        