from __future__ import print_function

# -----------------------------------------------------------------------
# OpenXenManager
#
# Copyright (C) 2009 Alberto Gonzalez Rodriguez alberto@pesadilla.org
# Copyright (C) 2014 Daniel Lintott <daniel@serverb.co.uk>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# -----------------------------------------------------------------------
import os
import shutil
import sys

import gi

gi.require_version("Gtk", "3.0")
gi.require_version("Pango", "1.0")
gi.require_version("GtkVnc", "2.0")
from configobj import ConfigObj
from gi.repository import Gdk, GdkPixbuf, GLib, Gtk, GtkVnc, Pango

from .tunnel import Tunnel

if os.path.dirname(sys.argv[0]):
    os.chdir(os.path.dirname(sys.argv[0]))

# On next releases we will use gettext for translations TODO: Investigate translations
APP = "oxc"
DIR = "locale"
if sys.platform != "win32" and sys.platform != "darwin":
    # If sys.platform is Linux or Unix
    pass  # GtkVnc already imported
elif sys.platform == "darwin":
    # On MacOSX with macports sys.platform is "darwin", we need Popen for run tightvnc
    from subprocess import Popen
else:
    # On Windows we need right tightvnc and we need win32 libraries for move the window
    from subprocess import Popen

    import win32con
    import win32gui

import atexit
import binascii
import gettext
import signal

from .oxcSERVER import *

# For a TreeView Cell with image+text
from .PixbufTextCellRenderer import PixbufTextCellRenderer

gettext.install("oxc", localedir="./locale")

# gobject.threads_init()  # Not needed in GTK3

# Import the split classes for oxcWindow
from .window_addserver import *
from .window_alerts import *
from .window_host import *
from .window_menuitem import *
from .window_newvm import *
from .window_properties import *
from .window_storage import *
from .window_tools import *
from .window_vm import *

# from .xdot import DotWindow  # FIXME: not migrated to GTK3


# class MyDotWindow(DotWindow):  # FIXME: not migrated to GTK3

#     def __init__(self, window, liststore, treestore):
#         self.liststore = liststore
#         self.treestore = treestore
#         DotWindow.__init__(self, window)
#         self.widget.connect('button_press_event', self.on_double_clicked)

#     def on_double_clicked(self, widget, event):
#         # On double click go to element
#         if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
#             x, y = int(event.x), int(event.y)
#             if widget.get_url(x, y):
#                 url = widget.get_url(x, y).url
#                 # Search ref and go to
#                 self.liststore.foreach(self.search_ref, url)
#         return True

#     def search_ref(self, model, path, iter_ref, user_data):
#         if self.liststore.get_value(iter_ref, 6) == user_data:
#             self.treestore.get_selection().select_path(path)
#             event = Gdk.Event(Gdk.BUTTON_RELEASE)
#             event.x = float(-10)
#             event.y = float(-10)
#             self.treestore.emit("button_press_event", event)


class oxcWindow(
    oxcWindowVM,
    oxcWindowHost,
    oxcWindowProperties,
    oxcWindowStorage,
    oxcWindowAlerts,
    oxcWindowNewVm,
    oxcWindowMenuItem,
    oxcWindowTools,
    AddServer,
):
    """Main class to oxc window"""

    xc_servers = {}
    # When you select a element of left tree these variables are filled
    selected_actions = None
    selected_ref = None
    selected_iter = None
    selected_tab = None
    selected_host = None
    selected_type = None
    selected_widget = None
    selected_state = None

    noclosevnc = False
    # If "Use master password" is enabled, password typed is set on it
    password = None
    reattach_storage = False
    # For New VM
    newvmdata = {}

    # On Host -> On general tab: "VMs" label hasn't a fixed width
    # If host is included on "moved" variable then "VMs" label was moved
    moved = []

    # Flag variable to avoid select signals
    set_active = False

    # Flag variable to export snapshot
    export_snap = False
    export_snap_vm = False

    # For windows only
    hWnd = 0

    # Used only for plugins..
    delete_pages = []

    # Used for pool join force
    last_host_pool = None

    # For XTEA only (needs a string with 8 characters)
    iv = "OXCENTER"

    # Tunnel VNC
    tunnel = None

    # For know if performance images was set
    performance_updated = False

    @staticmethod
    def _log_writer(log_level, fields, user_data):
        """
        GLib log writer to suppress cosmetic Gtk-WARNING messages.

        GLib.log_set_writer_func callback signature:
            func(log_level, fields, user_data) -> LogWriterOutput

        where *fields* is a list of (key, value) pairs.
        Filters out harmless GTK3 cosmetic warnings.
        """
        # Extract the MESSAGE from fields (each field is a LogField with .key/.value)
        msg = ""
        if fields is not None:
            for field in fields:
                if getattr(field, "key", None) == "MESSAGE":
                    val = getattr(field, "value", None)
                    if isinstance(val, str):
                        msg = val
                    break

        # Suppress cosmetic GTK3 warnings
        if (
            "Content added to the action area" in msg
            or "Overriding tab label" in msg
            or "not a valid child type" in msg
            or "temporary window without parent" in msg
        ):
            try:
                return GLib.LogWriterOutput.HANDLED
            except AttributeError:
                return 1  # HANDLED = 1
        try:
            return GLib.LogWriterOutput.UNHANDLED
        except AttributeError:
            return 0  # UNHANDLED = 0

    def __init__(self):
        atexit.register(self.signal_handler)
        signal.signal(15, self.signal_handler)

        # Suppress dconf warnings by using memory backend
        # GTK's GSettings tries to write to /run/user/.../dconf/user which
        # may fail on systems with restrictive permissions. Using the memory
        # backend is harmless and avoids the spam of CRITICAL messages.
        if "GSETTINGS_BACKEND" not in os.environ:
            os.environ["GSETTINGS_BACKEND"] = "memory"

        # Suppress non-critical Gtk-WARNING messages about header-bar action
        # areas, overriding tab labels, etc. These are cosmetic only and
        # do not affect functionality.
        try:
            GLib.log_set_writer_func(self._log_writer)
        except AttributeError:
            pass

        # Read the configuration from oxc.conf file
        # Use $HOME/.openxenmanager/ instead of ~/.config/openxenmanager/
        # to avoid dconf/dbus issues on headless/minimal systems
        homedir = os.path.expanduser("~")
        config_dirname = "openxenmanager"

        if sys.platform != "win32":
            config_dir = os.path.join(homedir, "." + config_dirname)
        else:
            config_dir = os.path.join(homedir, config_dirname)

        if not os.path.exists(config_dir):
            os.makedirs(config_dir)
            # Set restrictive permissions (owner read/write only)
            os.chmod(config_dir, 0o700)

        dirconfig = config_dir
        pathconfig = os.path.join(config_dir, "oxc.conf")

        if not os.path.exists(pathconfig):
            shutil.copy(os.path.join(utils.module_path(), "oxc.conf"), pathconfig)

        self.config = ConfigObj(pathconfig)
        self.pathconfig = dirconfig
        # Read from configuration saved servers
        if self.config["servers"]["hosts"]:
            self.config_hosts = self.config["servers"]["hosts"]
        else:
            self.config_hosts = {}
        # Define the glade file
        glade_dir = os.path.join(utils.module_path(), "ui")
        glade_files = []
        for g_file in os.listdir(glade_dir):
            if g_file.endswith(".glade"):
                glade_files.append(os.path.join(glade_dir, g_file))

        self.builder = Gtk.Builder()
        self.builder.set_translation_domain("oxc")
        # Add the glade files to Gtk.Builder object
        for g_file in glade_files:
            try:
                self.builder.add_from_file(g_file)
            except:
                print(
                    'While loading Glade GUI Builder file "'
                    + g_file
                    + '" a duplicate entry was found:'
                )
                raise

        # Connect Windows and Dialog to delete-event (we want not destroy dialog/window)
        # delete-event is called when you close the window with "x" button
        # TODO: csun: eventually it should be possible not to do this: http://stackoverflow.com/questions/4657344/
        for widget in self.builder.get_objects():
            if (
                isinstance(widget, Gtk.Dialog)
                or isinstance(widget, Gtk.Window)
                and widget.get_name() != "window1"
            ):
                widget.connect("delete-event", self.on_delete_event)
        # Frequent objects
        self.txttreefilter = self.builder.get_object("txttreefilter")

        self.listphydvd = self.builder.get_object("listphydvd")
        self.listisoimage = self.builder.get_object("listisoimage")
        self.listnetworks = self.builder.get_object("listnewvmnetworks")
        self.listnetworkcolumn = self.builder.get_object("listnewvmnetworkcolumn")
        self.window = self.builder.get_object("window1")
        self.listalerts = self.builder.get_object("listalerts")
        self.treealerts = self.builder.get_object("treealerts")
        self.filesave = self.builder.get_object("filesave")
        self.fileopen = self.builder.get_object("fileopen")
        self.newvm = self.builder.get_object("window_newvm")

        self.treeview = self.builder.get_object("treevm")
        self.treeprop = self.builder.get_object("treeprop")
        self.listprop = self.builder.get_object("listprop")
        self.statusbar = self.builder.get_object("statusbar1")
        self.treesearch = self.builder.get_object("treesearch")
        self.treestg = self.builder.get_object("treestg")

        # Tunnel and VNC pid dicts
        self.tunnel = {}
        self.vnc_process = {}  # used in osx
        self.vnc = {}
        self.vnc_builders = {}  # used to store vnc pygtk builders for the different windows in Linux

        """
        for i in range(0,7):
            if self.newvm.get_nth_page(i):
                self.newvm.set_page_complete(self.newvm.get_nth_page(i), True)
        """

        # Combo's style - deprecated in GTK3
        # style = Gtk.rc_parse_string('''
        #         style "my-style" { GtkComboBox::appears-as-list = 1 }
        #         widget "*" style "my-style"
        # ''')

        self.builder.connect_signals(self)

        # Initialize AddServer mixin so its methods (which are mixed into
        # oxcWindow via multiple inheritance) have the attributes they expect
        # (self.main, self.builder, self.treestore, etc.).
        try:
            AddServer.__init__(self, self)
        except Exception:
            # If AddServer.__init__ fails for any reason, continue; errors
            # will surface later when invoking AddServer-related actions.
            pass

        # Apply dark/light theme based on config
        try:
            if "prefer_dark_theme" in self.config.get("gui", {}):
                dark_enabled = (
                    str(self.config["gui"].get("prefer_dark_theme")).lower() == "true"
                )
                self._apply_dark_theme(dark_enabled)
                # Sync the check menu item if it exists
                check_dark = self.builder.get_object("checkdarktheme")
                if check_dark is not None:
                    check_dark.set_active(dark_enabled)
        except Exception:
            # If settings aren't available for some reason, silently continue
            pass

        self.treestg.get_selection().connect(
            "changed", self.on_treestg_selection_changed
        )

        # Create a new TreeStore
        self.treestore = Gtk.TreeStore(
            GdkPixbuf.Pixbuf, str, str, str, str, str, str, object, str
        )
        # Image, Name, uuid, type, state, host, ref, actions, ip
        # Append default logo on created TreeStore
        self.treeroot = self.treestore.append(
            None,
            (
                [
                    GdkPixbuf.Pixbuf.new_from_file(
                        os.path.join(utils.module_path(), "images/xen.gif")
                    ),
                    "Servers",
                    None,
                    "home",
                    "home",
                    None,
                    None,
                    ["addserver", "connectall", "disconnectall"],
                    None,
                ]
            ),
        )

        # Model Filter is used but show/hide templates/custom templates/local storage..
        self.modelfilter = self.treestore.filter_new()
        # Define the function to check if a element should be showed or not
        self.modelfilter.set_visible_func(self.visible_func)
        self.treeview.set_model(self.modelfilter)

        self.modelfiltertpl = self.builder.get_object("listtemplates").filter_new()
        self.builder.get_object("treetemplates").set_model(self.modelfiltertpl)
        self.modelfiltertpl.set_visible_func(self.visible_func_templates)

        self.builder.get_object("networkcolumn1").set_property(
            "model", self.builder.get_object("listimportnetworkcolumn")
        )
        self.builder.get_object("cellrenderercombo1").set_property(
            "model", self.builder.get_object("listnewvmnetworkcolumn")
        )
        # Same for properties treestore
        self.propmodelfilter = self.listprop.filter_new()
        self.propmodelfilter.set_visible_func(self.prop_visible_func)
        self.treeprop.set_model(self.propmodelfilter)

        # Fill defaults selection variables
        self.selected_name = "Servers"
        self.selected_type = "home"
        self.selected_uuid = ""
        self.headimage = self.builder.get_object("headimage")
        self.headlabel = self.builder.get_object("headlabel")
        self.headlabel.set_label(self.selected_name)
        # Ensure headlabel uses the oxc-label style class
        try:
            self.headlabel.get_style_context().add_class("oxc-label")
        except Exception:
            pass
        self.headimage.set_from_pixbuf(
            GdkPixbuf.Pixbuf.new_from_file(
                os.path.join(utils.module_path(), "images/xen.gif")
            )
        )

        if "pane_position" in self.config["gui"]:
            pane = self.builder.get_object("main_pane")
            pane.set_position(int(self.config["gui"]["pane_position"]))

        if "show_hidden_vms" not in self.config["gui"]:
            self.config["gui"]["show_hidden_vms"] = "False"
            self.config.write()
        # Set menuitem checks to value from configuration
        self.builder.get_object("checkshowxstpls").set_active(
            self.config["gui"]["show_xs_templates"] == "True"
        )
        self.builder.get_object("checkshowcustomtpls").set_active(
            self.config["gui"]["show_custom_templates"] == "True"
        )
        self.builder.get_object("checkshowlocalstorage").set_active(
            self.config["gui"]["show_local_storage"] == "True"
        )
        self.builder.get_object("checkshowtoolbar").set_active(
            self.config["gui"]["show_toolbar"] == "True"
        )
        self.builder.get_object("checkshowhiddenvms").set_active(
            self.config["gui"]["show_hidden_vms"] == "True"
        )

        if "maps" in self.config:
            for check in self.config["maps"]:
                obj = self.builder.get_object(check)
                if obj is not None:
                    obj.set_active(self.config["maps"][check] == "True")

        # If "Show toolbar" is checked then show, else hide
        if self.config["gui"]["show_toolbar"] != "False":
            self.builder.get_object("toolbar").show()
        else:
            self.builder.get_object("toolbar").hide()

        # Add to left tree the saved servers from configuration
        self._saved_server_count = 0  # track how many servers have saved passwords
        for host in self.config_hosts.keys():
            self.builder.get_object("listaddserverhosts").append([host])
            self.treestore.append(
                self.treeroot,
                (
                    [
                        GdkPixbuf.Pixbuf.new_from_file(
                            os.path.join(
                                utils.module_path(), "images/tree_disconnected_16.png"
                            )
                        ),
                        host,
                        None,
                        "server",
                        "Disconnected",
                        None,
                        None,
                        ["connect", "savepassword", "forgetpw", "remove"],
                        None,
                    ]
                ),
            )
            # Track servers with saved passwords
            if self.config_hosts[host][1]:  # encrypted_password is set
                self._saved_server_count += 1

        # Auto-connect servers that have saved passwords
        if (
            str(self.config.get("gui", {}).get("auto_connect_saved", "False")).lower()
            == "true"
        ):
            self._auto_connect_saved_servers()

        # Expand left tree and update menubar, tabs and toolbar
        self.treeview.expand_all()
        self.update_menubar()
        self.update_tabs()
        self.update_toolbar()

        # Create a TreeStore for SERVER->Search tab
        # (image, name, loadimg, loadtext,
        #  memimg, memtext, disks, network, address, uptime
        #  color)
        self.listsearch = Gtk.TreeStore(
            GdkPixbuf.Pixbuf,
            str,
            object,
            str,
            object,
            str,
            str,
            str,
            str,
            str,
            Gdk.Color,
        )
        self.treesearch.set_model(self.listsearch)
        # self.treesearch.get_column(0).set_cell_data_func(self.func_cell_data_treesearch, self.treesearch.get_cell(0))

        # Add two columns with image/text from PixBufTextCellRenderer class
        pbtcell = PixbufTextCellRenderer()
        pbtcell.set_property("xpad", 15)
        pbtcell.set_property("ypad", 13)
        tvc = Gtk.TreeViewColumn("CPU Usage", pbtcell, text=3, pixbuf=2, background=10)
        tvc.set_widget(self.builder.get_object("lbltreesearch6"))
        self.builder.get_object("lbltreesearch6").show()
        tvc.set_reorderable(True)
        tvc.set_sort_column_id(3)
        self.treesearch.insert_column(tvc, 1)
        pbtcell = PixbufTextCellRenderer()
        pbtcell.set_property("xpad", 15)
        pbtcell.set_property("ypad", 13)
        tvc = Gtk.TreeViewColumn(
            "Used memory", pbtcell, text=5, pixbuf=4, background=10
        )
        tvc.set_widget(self.builder.get_object("lbltreesearch7"))
        tvc.set_reorderable(True)
        tvc.set_sort_column_id(5)
        self.treesearch.insert_column(tvc, 2)

        # ComboBox created from GLADE needs a cellrenderertext
        # and an attribute defining the column to show
        combobox = self.builder.get_object("radiobutton2_data")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 0)
        combobox.set_model(self.listphydvd)

        combobox = self.builder.get_object("radiobutton3_data")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 0)
        combobox.add_attribute(cell, "rise", 2)
        combobox.add_attribute(cell, "sensitive", 3)
        combobox.set_model(self.listisoimage)

        combobox = self.builder.get_object("treeeditnetwork")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listeditnetwork"))

        combobox = self.builder.get_object("treeaddnetwork")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listaddnetwork"))
        combobox.set_active(0)

        combobox = self.builder.get_object("combostgmode")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("liststgmode"))
        combobox.set_active(0)

        combobox = self.builder.get_object("combostgposition")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 0)
        combobox.set_model(self.builder.get_object("liststgposition"))
        combobox.set_active(0)
        # combobox.set_style(style)

        combobox = self.builder.get_object("combomgmtnetworks")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listmgmtnetworks"))
        combobox.set_active(0)
        # combobox.set_style(style)

        combobox = self.builder.get_object("combopoolmaster")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listpoolmaster"))
        combobox.set_active(0)
        # combobox.set_style(style)

        combobox = self.builder.get_object("combotargetiqn")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listtargetiqn"))
        combobox.set_active(0)
        # combobox.set_style(style)

        combobox = self.builder.get_object("combotargetlun")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listtargetlun"))
        combobox.set_active(0)
        # combobox.set_style(style)

        combobox = self.builder.get_object("combonetworknic")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 1)
        combobox.set_model(self.builder.get_object("listnetworknic"))
        combobox.set_active(0)
        # combobox.set_style(style)

        combobox = self.builder.get_object("combocustomfields")
        cell = Gtk.CellRendererText()
        combobox.pack_start(cell, True)
        combobox.add_attribute(cell, "text", 0)
        combobox.set_model(self.builder.get_object("listcombocustomfields"))
        combobox.set_active(0)
        # combobox.set_style(style)

        # print combobox.get_internal_child()
        # If gtk version is 2.18.0 or higher then add "marks" to scale
        if hasattr(self.builder.get_object("scalepropvmprio"), "add_mark"):
            self.builder.get_object("scalepropvmprio").add_mark(
                0, Gtk.PositionType.BOTTOM, "\nLowest"
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                1, Gtk.PositionType.BOTTOM, ""
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                2, Gtk.PositionType.BOTTOM, ""
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                3, Gtk.PositionType.BOTTOM, ""
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                4, Gtk.PositionType.BOTTOM, "\nNormal"
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                5, Gtk.PositionType.BOTTOM, ""
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                6, Gtk.PositionType.BOTTOM, ""
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                7, Gtk.PositionType.BOTTOM, ""
            )
            self.builder.get_object("scalepropvmprio").add_mark(
                8, Gtk.PositionType.BOTTOM, "\nHighest"
            )

        # Manual function to set the default buttons on dialogs/window
        # Default buttons could be pressed with enter without need do click
        self.set_window_defaults()

        # Make the background of the tab box, and its container children white
        tabbox = self.builder.get_object("tabbox")
        # Deprecated GTK2 call removed: background coloring should be done via CSS in GTK3/GTK4

        # for tab_box_child in tabbox.get_children():
        self.recursive_set_bg_color(tabbox)

        # To easily modify and provide a consistent section header look in the
        # main_window: I've named all EventBoxes main_section_header#. Iterate through
        # them until we get a NoneType
        section_header_string = "main_section_header"
        section_header_index = 1
        while 1:
            done = self.prettify_section_header(
                section_header_string + str(section_header_index)
            )
            if done is None:
                break
            section_header_index = section_header_index + 1

        # Apply dark/light theme from config on startup
        theme_val = str(self.config.get("gui", {}).get("prefer_dark_theme", "")).lower()
        if theme_val == "true":
            self._apply_dark_theme(True)
        # Sync the View -> Dark Theme menu checkbox
        check_dark = self.builder.get_object("checkdarktheme")
        if check_dark is not None:
            check_dark.set_active(theme_val == "true")

        # If we need a master password for connect to servers without password:
        # Show the dialog asking master password
        if str(self.config["gui"]["save_password"]) == "True":
            self.builder.get_object("masterpassword").show()

        if sys.platform == "win32" or sys.platform == "darwin":
            self.builder.get_object("consolescale").hide()

        # self.windowmap = MyDotWindow(self.builder.get_object("viewportmap"), self.treestore, self.treeview)  # FIXME: not migrated to GTK3

        # Show the main window
        self.window.show()
        self.window.present()

    # Recursive function to set the background colour on certain objects
    def recursive_set_bg_color(self, widget):
        for child in widget.get_children():
            # Is a storage container, dive into it
            if isinstance(child, Gtk.Container):
                self.recursive_set_bg_color(child)
                # Is a specific type of widget

    # Deprecated GTK2 call removed: use CSS for background coloring in GTK3/GTK4

    # Add a common theme to the section header areas
    def prettify_section_header(self, widget_name):
        if type(widget_name) is not str:
            return None

        section_header = self.builder.get_object(widget_name)
        if section_header is None:
            return None

        # Make the event boxes window visible and set the background color
        section_header.set_visible_window(True)
        # Deprecated GTK2 call removed: section header styling should be done via CSS in GTK3/GTK4

        child_list = section_header.get_children()
        if child_list is not None:
            for child in child_list:
                if child is not None:
                    if type(child) == Gtk.Label:
                        # Deprecated GTK2 call removed: use CSS for foreground/text color in GTK3/GTK4

                        # Preserve attributes set within Glade.
                        child_attributes = child.get_attributes()
                        if child_attributes is None:
                            child_attributes = Pango.AttrList()

                        # Add/modify a few attributes
                        # Pango.attr_scale_new removed: prefer CSS or font-desc in GTK3/GTK4
                        child.set_attributes(child_attributes)
        return True

    # todo: James - When we're done redoing the performance tab let's do this on any new scrollbars created
    # def adjust_scrollbar_performance(self):
    #    for widget in ["scrwin_cpuusage", "scrwin_memusage", "scrwin_netusage", "scrwin_diskusage"]:
    #        self.builder.get_object(widget).grab_focus()
    #        adj = self.builder.get_object(widget).get_hadjustment()
    #        adj.set_value(adj.upper - adj.page_size)

    def func_cell_data_treesearch(self, column, cell, model, iter_ref, user_data):
        # Test function don't used TODO: Can this be removed?
        print(column, cell, model, iter_ref, user_data)

    def set_window_defaults(self):
        """
        Function to define what button is the default for each window/dialog
        Default button could be pressed with enter key
        """
        widgets = [
            "addserverpassword",
            "addserverusername",
            "snaptplname",
            "snapshotname",
            "vmaddnewdisk_name",
            "txtcopyvmname",
            "txtpropvmname",
            "txtnetworkname",
            "txtmasterpassword",
            "txtaskmasterpassword",
        ]

        dialogs = {
            "addserver": "connect_addserver",
            "newvmdisk": "acceptnewvmdisk",
            "vmaddnewdisk": "acceptvmaddnewdisk",
            "dialogsnapshotname": "btacceptsnapshotname",
            "dialogsnaptplname": "btacceptsnaptplname",
            "dialogsnapshotdelete": "btacceptsnapshotdelete",
            "vmattachdisk": "btacceptattachdisk",
            "dialogdeletevm": "dialogdelete_accept",
            "dialogdeletevdi": "dialogdeletevdi_accept",
            "windowcopyvm": "windowcopyvm_copy",
            "dialogvmprop": "btvmpropaccept",
            "dialogdeletehostnetwork": "acceptdialogdeletehostnetwork",
            "dialogdeletehostnic": "acceptdialogdeletehostnic",
            "addbond": "btacceptaddbond",
            "newnetwork": "acceptnewnetwork",
            "dialogoptions": "acceptdialogoptions",
            "masterpassword": "acceptmasterpassword",
            "dialogeditnetwork": "accepteditnetwork",
            "dialognetworkrestart": "acceptdialognetworkrestart",
            "vmimport": "nextvmimport",
            "mgmtinterface": "acceptmgmtinterface",
            "newpool": "acceptnewpool",
        }
        # For each dialog
        for wid in dialogs:
            # Set the flag indicating the widget could be a default button - deprecated in GTK3
            # self.builder.get_object(dialogs[wid]).set_flags(Gtk.CAN_DEFAULT)
            self.builder.get_object(dialogs[wid]).set_can_default(True)
            # If widget is a dialog
            if type(self.builder.get_object(wid)) == type(Gtk.Dialog()):
                # Set the button with "id response = 1" as default
                self.builder.get_object(wid).set_default_response(1)
            else:
                # If is a Gtk.Window set the indicated button as default
                self.builder.get_object(wid).set_default(
                    self.builder.get_object(dialogs[wid])
                )

        for wid in widgets:
            # For each button indicate that it may be the default button
            self.builder.get_object(wid).set_activates_default(True)

    def _apply_dark_theme(self, enabled):
        """
        Apply or remove dark theme CSS and GTK setting.
        """
        try:
            settings = Gtk.Settings.get_default()
            if settings is not None:
                settings.set_property("gtk-application-prefer-dark-theme", enabled)
        except Exception:
            pass

        if enabled:
            try:
                css = b"""
                /* Ensure readability in dark mode */
                label, .oxc-label, treeview, .view {
                    color: #ffffff;
                }
                .oxc-label-secondary {
                    color: #dcdcdc;
                }
                """
                css_provider = Gtk.CssProvider()
                css_provider.load_from_data(css)
                screen = Gdk.Screen.get_default()
                Gtk.StyleContext.add_provider_for_screen(
                    screen,
                    css_provider,
                    Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
                )
                # Add style class to all labels
                for obj in self.builder.get_objects():
                    try:
                        if isinstance(obj, Gtk.Label):
                            obj.get_style_context().add_class("oxc-label")
                    except Exception:
                        pass
            except Exception:
                pass

    def visible_func_templates(self, model, iter_ref, user_data=None):
        name = self.builder.get_object("listtemplates").get_value(iter_ref, 1)
        txttemplatesearch = self.builder.get_object("txttemplatesearch")
        if txttemplatesearch.get_text().strip() == "":
            return True
        else:
            return name.lower().count(txttemplatesearch.get_text().lower()) > 0

    def visible_func(self, model, iter_ref, user_data=None):
        """
        This function define if a element should be showed or not in left tree
        This function checks configuration values and show/hide elements
        Returning False you hide the element, returning True you show the element
        """
        host = self.treestore.get_value(iter_ref, 5)
        ref = self.treestore.get_value(iter_ref, 6)
        seltype = self.treestore.get_value(iter_ref, 3)
        # Global name-based search: if user entered text, filter any item whose
        # name doesn't contain the text (apply to all types)
        try:
            txt = self.txttreefilter.get_text().strip().lower()
        except Exception:
            txt = ""
        if txt:
            name = (self.treestore.get_value(iter_ref, 1) or "").lower()
            if name.count(txt) == 0:
                return False
        if (
            seltype == "vm"
            and str(self.config["gui"]["show_hidden_vms"]) == "False"
            and host
            and ref
            and self.xc_servers[host].all["vms"][ref].get("other_config")
            and str(
                self.xc_servers[host]
                .all["vms"][ref]["other_config"]
                .get("HideFromXenCenter")
            ).lower()
            == "true"
        ):
            return False
        if seltype == "template":
            if (
                self.config["gui"]["show_xs_templates"] == "False"
                or not self.config["gui"]["show_xs_templates"]
            ):
                return False
        elif seltype == "custom_template":
            if (
                self.config["gui"]["show_custom_templates"] == "False"
                or not self.config["gui"]["show_custom_templates"]
            ):
                return False
        elif seltype == "storage":
            if (
                self.config["gui"]["show_local_storage"] == "False"
                or not self.config["gui"]["show_local_storage"]
            ):
                if host and ref:
                    if not self.xc_servers[host].all["SR"][ref]["shared"]:
                        return False
        return True

    def foreach_connect(self, model, path, iter_ref, user_data):
        """
        This function connect or disconnect depends user_data value
        if user_data is True then connect all disconnected servers
        if user_data is False then disconnect all connected servers
        No code commented because doesn't work so well..
        """
        if self.treestore.get_value(iter_ref, 3) == "server":
            if self.treestore.get_value(iter_ref, 4) == "Disconnected":
                if user_data:
                    name = self.treestore.get_value(iter_ref, 1)
                    if self.config_hosts[name][1]:
                        path = self.modelfilter.convert_path_to_child_path(path)
                        self.treeview.get_selection().select_path(path)
                        iter_ref = self.treestore.get_iter(path)
                        self.selected_iter = iter_ref
                        self.selected_name = self.treestore.get_value(iter_ref, 1)
                        self.selected_uuid = self.treestore.get_value(iter_ref, 2)
                        self.selected_type = self.treestore.get_value(iter_ref, 3)
                        self.selected_state = self.treestore.get_value(iter_ref, 4)
                        self.selected_host = self.treestore.get_value(iter_ref, 5)
                        self.selected_ip = self.treestore.get_value(iter_ref, 8)

                        self.on_m_connect_activate(self.treestore, None)
                        self.treesearch.expand_all()

        if (
            self.treestore.get_value(iter_ref, 3) == "host"
            or self.treestore.get_value(iter_ref, 3) == "pool"
        ):
            if self.treestore.get_value(iter_ref, 4) == "Running":
                if not user_data:
                    path = self.modelfilter.convert_path_to_child_path(path)
                    self.treeview.get_selection().select_path(path)
                    iter_ref = self.treestore.get_iter(path)
                    self.selected_iter = iter_ref
                    self.selected_name = self.treestore.get_value(iter_ref, 1)
                    self.selected_uuid = self.treestore.get_value(iter_ref, 2)
                    self.selected_type = self.treestore.get_value(iter_ref, 3)
                    self.selected_state = self.treestore.get_value(iter_ref, 4)
                    self.selected_host = self.treestore.get_value(iter_ref, 5)
                    self.selected_ip = self.treestore.get_value(iter_ref, 8)

                    self.on_m_disconnect_activate(self.treestore, None)
                    self.treesearch.expand_all()

            else:
                print("**", self.treestore.get_value(iter_ref, 4))

    def _auto_connect_saved_servers(self):
        """
        Auto-connect servers that have saved passwords on startup.

        Only connects servers where a password (encrypted or obfuscated)
        is stored in the config. Requires either a master password to be
        entered at startup, or XOR-obfuscated passwords in config.
        """
        import threading

        from . import xtea  # noqa: local import

        auto_connect = (
            str(self.config.get("gui", {}).get("auto_connect_saved", "False")).lower()
            == "true"
        )
        use_master_pw = (
            str(self.config.get("gui", {}).get("save_password", "False")).lower()
            == "true"
        )

        for host, info in list(self.config_hosts.items()):
            if len(info) < 2 or not info[1]:
                continue  # no password saved for this host

            decrypted_pw = ""
            try:
                if use_master_pw and self.password:
                    key = "X" * (16 - len(self.password)) + self.password
                    enc_bytes = binascii.unhexlify(info[1].encode("ascii"))
                    decrypted_pw = xtea.crypt(key, enc_bytes, self.iv)
                    if isinstance(decrypted_pw, bytes):
                        decrypted_pw = decrypted_pw.decode("latin-1")
                else:
                    # Fallback: XOR deobfuscation
                    from . import password_utils  # noqa: local import

                    decrypted_pw = password_utils._xor_deobfuscate(info[1])
            except Exception:
                print(f"Warning: Could not decrypt password for {host}")
                continue

            if not decrypted_pw:
                continue

            host_ip = info[0] if len(info) >= 4 else None
            user = info[0]
            ssl = info[2] if len(info) > 2 else False
            verify_ssl = info[3] if len(info) > 3 else False
            port = int(info[4]) if len(info) > 4 else (443 if ssl else 80)

            # We need the window object; connect via a small deferred call
            GLib.idle_add(
                lambda u=user, p=decrypted_pw, h=host, s=ssl, v=verify_ssl, pt=port: (
                    _do_connect(u, p, h, s, v, pt),
                    False,
                )
            )

        def _do_connect(user, password, host, ssl, verify_ssl, port):
            """Helper to initiate connection during idle."""
            from .window_addserver import AddServer  # noqa: local import

            try:
                add_srv = AddServer(
                    self,
                    host,
                    user,
                    password,
                    use_ssl=ssl,
                    verify_ssl=verify_ssl,
                    port=port,
                )
                # Hide dialog - we're connecting programmatically
                add_srv.builder.get_object("addserver").hide()
                add_srv.connect_server()
            except Exception as exc:
                print(f"Auto-connect failed for {host}: {exc}")

    def on_window1_configure_event(self, widget, data=None):
        self.on_window1_size_request(widget, data)

    def on_window1_size_request(self, widget, data=None):
        if self.hWnd != 0:
            console_area = self.builder.get_object("frameconsole")
            console_area.realize()
            console_alloc = console_area.get_allocation()
            window_alloc = self.window.get_position()
            x = console_alloc.x + window_alloc[0] + 10
            y = console_alloc.y + window_alloc[1] + 47
            win32gui.MoveWindow(
                self.hWnd, x, y, console_alloc.width - 10, console_alloc.height - 5, 1
            )

    def on_console_area_key_press_event(self, widget, event):
        self.tunnel[self.selected_ref].key = hex(event.hardware_keycode - 8)

    def on_aboutdialog_close(self, widget, data=None):
        """
        Function to hide about dialog when you close it
        """
        self.builder.get_object("aboutdialog").hide()

    def on_acceptmasterpassword_clicked(self, widget, data=None):
        """
        Function what checks ff you typed a master password is right
        """
        # Create a md5 object
        m = hashlib.md5()
        password = self.builder.get_object("txtaskmasterpassword").get_text()
        # Add password typed to md5 object
        m.update(password)
        # m.hexdigest() is a md5 ascii password (as saved in the configuration)
        if self.config["gui"]["master_password"] != m.hexdigest():
            # If is wrong show the label indicating is a wrong password
            self.builder.get_object("lblwrongpassword").show()
        else:
            # If is a good password set to global variable "password" and hide dialog
            self.password = password
            self.builder.get_object("masterpassword").hide()

    def on_cancelmasterpassword_clicked(self, widget, data=None):
        """
        Function called when you cancel the master password dialog.
        """
        # If you cancel the dialog, then set global variable "password" to None
        self.password = None
        self.builder.get_object("masterpassword").hide()

    def on_txtaskmasterpassword_changed(self, widget, data=None):
        """
        Function called when you write or remove characters on master password entry
        """
        # If you check "save server passwords" then you need specify a master password
        # If len of master password is 0, then disable "Accept" button in options dialog
        self.builder.get_object("acceptmasterpassword").set_sensitive(
            len(widget.get_text())
        )

    def update_tabs(self):
        """
        Function called when you select an element from left tree
        Depending on selected type show or hide different tabs
        """
        frames = (
            "framestggeneral",
            "framememory",
            "framestgdisks",
            "framevmgeneral",
            "framevmstorage",
            "framevmnetwork",
            "framehostgeneral",
            "framehostnetwork",
            "framehoststorage",
            "frameconsole",
            "framehostnics",
            "framesnapshots",
            "frameperformance",
            "frametplgeneral",
            "framehome",
            "frameconsole",
            "framepoolgeneral",
            "framelogs",
            "framesearch",
            "frameusers",
            "framemaps",
            "framehosthw",
        )
        showframes = {
            "pool": ["framepoolgeneral", "framelogs", "framesearch", "framemaps"],
            "home": ["framehome"],
            "vm": [
                "framevmgeneral",
                "framememory",
                "framevmstorage",
                "framevmnetwork",
                "framelogs",
                "framesnapshots",
                "frameperformance",
            ],
            "host": [
                "framesearch",
                "framehostgeneral",
                "framehostnetwork",
                "framehoststorage",
                "framelogs",
                "frameconsole",
                "framehostnics",
                "frameperformance",
                "frameusers",
                "framemaps",
            ],
            "template": ["frametplgeneral", "framevmnetwork", "framehostgeneral"],
            "custom_template": [
                "frametplgeneral",
                "framevmnetwork",
                "framevmstorage",
                "framelogs",
            ],
            "storage": ["framestggeneral", "framestgdisks", "framelogs"],
        }
        if self.selected_type in showframes:
            [
                self.builder.get_object(frame).show()
                for frame in showframes[self.selected_type]
            ]
            [
                self.builder.get_object(frame).hide()
                for frame in frames
                if frame not in showframes[self.selected_type]
            ]

        if self.selected_type == "pool":
            self.xc_servers[self.selected_host].update_tab_pool_general(
                self.selected_ref, self.builder
            )

        elif self.selected_type == "vm":
            # If "VM" is running, show console tab, else hide
            if self.selected_state == "Running":
                self.builder.get_object("frameconsole").show()
            else:
                self.builder.get_object("frameconsole").hide()
            self.xc_servers[self.selected_host].update_tab_vm_general(
                self.selected_ref, self.builder
            )
        elif self.selected_type == "host":
            self.xc_servers[self.selected_host].update_tab_host_general(
                self.selected_ref, self.builder
            )
            if self.xc_servers[self.selected_host].has_hardware_script(
                self.selected_ref
            ):
                self.builder.get_object("framehosthw").show()
            else:
                self.builder.get_object("framehosthw").hide()
        elif self.selected_type == "template":
            self.xc_servers[self.selected_host].update_tab_template(
                self.selected_ref, self.builder
            )
        elif self.selected_type == "custom_template":
            self.xc_servers[self.selected_host].update_tab_template(
                self.selected_ref, self.builder
            )
        elif self.selected_type == "storage":
            operations = self.xc_servers[self.selected_host].all["SR"][
                self.selected_ref
            ]["allowed_operations"]
            if operations.count("vdi_create"):
                self.builder.get_object("btstgnewdisk").show()
            else:
                self.builder.get_object("btstgnewdisk").hide()
            self.xc_servers[self.selected_host].update_tab_storage(
                self.selected_ref, self.builder
            )

        # Experimental only
        try:
            import glob

            import webkit

            for deletepage in self.delete_pages:
                # FIXME: remove doesn't work
                self.builder.get_object("tabbox").get_nth_page(deletepage).hide_all()

            self.delete_pages = []
            for infile in glob.glob("plugins/*.xml"):
                data = open(infile).read()
                """
                dom = xml.dom.minidom.parseString(data)
                nodes = dom.getElementsByTagName("XenCenterPlugin")
                applicable = False
                if len(nodes[0].getElementsByTagName("TabPage")):
                    for tabpage in  nodes[0].getElementsByTagName("TabPage"):
                        if tabpage.attributes.getNamedItem("search"):
                            search_uuid = tabpage.attributes.getNamedItem("search").value
                            tabname = tabpage.attributes.getNamedItem("name").value # REVISE
                            url = tabpage.attributes.getNamedItem("url").value # REVISE
                            if len(nodes[0].getElementsByTagName("Search")):
                               host = self.selected_host
                [applicable, ip] = self.plugin_get_search(nodes, search_uuid, host, ref)
                """
                host = self.selected_host
                ref = self.selected_ref
                [applicable, ip, url, tabname] = self.process_xml(data, host, ref)
                if applicable:
                    view = webkit.WebView()
                    browser = Gtk.ScrolledWindow()
                    url = url.replace("{$ip_address}", ip)
                    view.open(url)
                    browser.add_with_viewport(view)
                    tablabel = Gtk.Label(tabname)
                    self.delete_pages.append(
                        self.builder.get_object("tabbox").append_page(browser, tablabel)
                    )
                    browser.show_all()
        except ImportError or RuntimeError:
            pass

    def process_xml(self, data, host, ref):
        dom = xml.dom.minidom.parseString(data)
        if dom.documentElement.nodeName != "XenCenterPlugin":
            print("no XenCenterPlugin")
            return
        node = dom.documentElement
        ip = None
        applicable = False
        for tabpage in node.getElementsByTagName("TabPage"):
            search_uuid = tabpage.getAttribute("search")
            tabname = tabpage.getAttribute("name")  # REVISE
            url = tabpage.getAttribute("url")  # REVISE
            if search_uuid and tabname and url:
                for search in [
                    e
                    for e in node.getElementsByTagName("Search")
                    if e.getAttribute("uuid") == search_uuid
                ]:
                    for query in search.getElementsByTagName("Query"):
                        for queryscope in [
                            e
                            for e in query.getElementsByTagName("QueryScope")[
                                0
                            ].childNodes
                            if e.nodeType != dom.TEXT_NODE
                        ]:
                            if queryscope.nodeName == "LocalSR":
                                if self.selected_type == "storage":
                                    shared = self.xc_servers[self.selected_host].all[
                                        "SR"
                                    ][self.selected_ref]["shared"]
                                    if not shared:
                                        applicable = True
                            elif queryscope.nodeName == "RemoteSR":
                                if self.selected_type == "storage":
                                    shared = self.xc_servers[self.selected_host].all[
                                        "SR"
                                    ][self.selected_ref]["shared"]
                                    if shared:
                                        applicable = True
                            elif queryscope.nodeName == "Pool":  # REVISE
                                if self.selected_type == "pool":
                                    applicable = True
                            elif queryscope.nodeName == "Vm":  # REVISE
                                if self.selected_type == "vm":
                                    applicable = True
                            elif queryscope.nodeName == "Host":  # REVISE
                                if self.selected_type == "host":
                                    applicable = True
        if applicable:
            for enumpropertyquery in query.getElementsByTagName("EnumPropertyQuery"):
                data = None
                if self.selected_type == "storage":
                    data = self.xc_servers[host].all["SR"][ref]
                    pbds = data["PBDs"]
                    ip = ""
                    if (
                        "target"
                        in self.xc_servers[host].all["PBD"][pbds[0]]["device_config"]
                    ):
                        ip = self.xc_servers[host].all["PBD"][pbds[0]]["device_config"][
                            "target"
                        ]
                    # ip = data["name_description"].split(" ")[2][1:]
                elif self.selected_type == "vm":
                    data = self.xc_servers[host].all["vms"][ref]
                    ip = self.selected_ip
                if self.selected_type == "host":
                    data = self.xc_servers[host].all["host"][ref]
                    ip = self.selected_ip
                if self.selected_type == "pool":
                    data = self.xc_servers[host].all["pool"][ref]
                    ip = self.selected_ip
                if data:
                    prop = enumpropertyquery.attributes.getNamedItem("property").value
                    equals = enumpropertyquery.attributes.getNamedItem("equals").value
                    value = enumpropertyquery.attributes.getNamedItem("query").value
                    if prop in data:
                        if equals == "no":
                            if isinstance(data[prop], str):
                                applicable = data[prop].count(value) > 0
                            else:  # REVISE
                                applicable = False
                        else:
                            applicable = data == value
                    else:
                        if "XenCenter.CustomFields." + prop in data["other_config"]:
                            applicable = True
                            url = url.replace(
                                "{$%s}" % prop,
                                data["other_config"]["XenCenter.CustomFields." + prop],
                            )
                        else:
                            applicable = False
        return [applicable, ip, url, tabname]

    def plugin_get_search(self, nodes, search_uuid, host, ref):
        """
        Determine if plugin is applicable
        """
        applicable = False
        ip = None
        for search in nodes[0].getElementsByTagName("Search"):
            if search.attributes.getNamedItem("uuid").value == search_uuid:
                for query in search.getElementsByTagName("Query"):
                    queryscopes = query.getElementsByTagName("QueryScope")
                    for queryscope in queryscopes[0].childNodes:
                        if queryscope.nodeName != "#text":
                            if queryscope.nodeName == "LocalSR":
                                if self.selected_type == "storage":
                                    shared = self.xc_servers[host].all["SR"][ref][
                                        "shared"
                                    ]
                                    if not shared:
                                        applicable = True
                            elif queryscope.nodeName == "RemoteSR":
                                if self.selected_type == "storage":
                                    shared = self.xc_servers[host].all["SR"][ref][
                                        "shared"
                                    ]
                                    if shared:
                                        applicable = True
                            elif queryscope.nodeName == "Pool":  # REVISE
                                if self.selected_type == "pool":
                                    applicable = True
                            elif queryscope.nodeName == "Vm":  # REVISE
                                if self.selected_type == "VM":
                                    applicable = True
                            elif queryscope.nodeName == "Host":  # REVISE
                                if self.selected_type == "host":
                                    applicable = True
        if applicable:
            for enumpropertyquery in query.getElementsByTagName("EnumPropertyQuery"):
                data = None
                if self.selected_type == "storage":
                    data = self.xc_servers[host].all["SR"][ref]
                    ip = data["name_description"].split(" ")[2][1:]
                elif self.selected_type == "vm":
                    data = self.xc_servers[host].all["vms"][ref]
                    ip = self.selected_ip
                if self.selected_type == "host":
                    data = self.xc_servers[host].all["host"][ref]
                    ip = self.selected_ip
                if self.selected_type == "pool":
                    data = self.xc_servers[host].all["pool"][ref]
                    ip = self.selected_ip
                if data:
                    prop = enumpropertyquery.attributes.getNamedItem("property").value
                    equals = enumpropertyquery.attributes.getNamedItem("equals").value
                    value = enumpropertyquery.attributes.getNamedItem("query").value
                    if prop in data:
                        if equals == "no":
                            if isinstance(data[prop], str):
                                applicable = data[prop].count(value) > 0
                            else:  # REVISE
                                applicable = False
                        else:
                            applicable = data == value
                    else:
                        applicable = False
        return [applicable, ip]

    def on_window_destroy(self, widget, data=None):
        """
        Function called when you close the window or press Quit
        """
        # For each server
        if self.tunnel:
            for key in self.tunnel.keys():
                self.tunnel[key].close()

        for sh in self.xc_servers:
            # Stop the threads setting True the condition variables
            self.xc_servers[sh].halt = True
            self.xc_servers[sh].halt_search = True
            self.xc_servers[sh].halt_import = True
            self.xc_servers[sh].halt_performance = True
            # Do a logout, remember logout disconnect to server and unregister events
            self.xc_servers[sh].logout()
        # For windows only: close the tightvnc console
        if self.hWnd != 0:
            win32gui.PostMessage(self.hWnd, win32con.WM_QUIT, 0, 0)
            self.hWnd = 0
        # Get the position of the main window pane
        self.save_pane_position()
        # Save unsaved changes
        self.config.write()
        # Exit!
        Gtk.main_quit()
        if self.vnc_process:
            for process in self.vnc_process.keys():
                # Kill all running sub_processes
                if self.vnc_process[process].poll() != 0:
                    os.killpg(os.getpgid(self.vnc_process[process].pid), signal.SIGTERM)
        # Force Quit
        os._exit(0)
        return

    def save_pane_position(self):
        """
        Save the position of the main window HPaned
        """
        pane = self.builder.get_object("main_pane")
        self.config["gui"]["pane_position"] = pane.get_position()

    def count_list(self, model, path, iter_ref, user_data):
        """
        Function to count elements from list..
        """
        # TODO: remove and use __len__()
        self.nelements = self.nelements + 1

    def on_tabbox_focus_tab(self, widget, data=None, data2=None):
        """
        Function called when you click on a tab
        Tabbox contains all possible tabs, when you click on a tab first we will check the name
        Depending of this name we will do different actions
        """
        # Get the selected host
        host = self.selected_host

        # Check if we've actually selected a host
        if host:
            # Get the Tab name
            # tab_label = widget.get_tab_label(widget.get_nth_page(data2)).name
            tab_label = Gtk.Buildable.get_name(
                widget.get_tab_label(widget.get_nth_page(data2))
            )
            # Set as selected
            self.selected_tab = tab_label
            if tab_label != "VM_Console":
                # If vnc console was opened and we change to another, close it
                # Disable the send ctrl-alt-del menu item
                self.builder.get_object("menuitem_tools_cad").set_sensitive(False)
                if (
                    hasattr(self, "vnc")
                    and self.vnc
                    and not self.noclosevnc
                    and not eval(self.config["options"]["multiple_vnc"])
                ):
                    for key in self.vnc:
                        self.vnc[key].destroy()
                    self.builder.get_object("windowvncundock").hide()
                    self.vnc = {}
                # Same on Windows
                if sys.platform == "win32" and self.hWnd != 0:
                    if win32gui.IsWindow(self.hWnd):
                        win32gui.PostMessage(self.hWnd, win32con.WM_CLOSE, 0, 0)
                    self.hWnd = 0

                if (
                    self.tunnel
                    and not self.noclosevnc
                    and not eval(self.config["options"]["multiple_vnc"])
                ):
                    for key in self.tunnel:
                        self.tunnel[key].close()
                    self.tunnel = {}

                if self.vnc_builders and not eval(
                    self.config["options"]["multiple_vnc"]
                ):
                    for key in self.vnc_builders:
                        self.vnc_builders[key].get_object("console_area3").remove(
                            self.vnc[key]
                        )
                        self.vnc_builders[key].get_object("windowvncundock").destroy()
                    self.vnc_builders = {}

                if tab_label != "HOST_Search" and host:
                    # If we change tab to another different to HOST Search, then stop the filling thread
                    self.xc_servers[host].halt_search = True
                if tab_label != "VM_Performance" and host:
                    self.xc_servers[host].halt_performance = True

            if tab_label == "VM_Console":
                self.builder.get_object("menuitem_tools_cad").set_sensitive(True)
                self.treeview = self.builder.get_object("treevm")
                if (
                    hasattr(self, "vnc")
                    and self.vnc
                    and not eval(self.config["options"]["multiple_vnc"])
                ):
                    if self.tunnel:
                        for key in self.tunnel:
                            self.tunnel[key].close()
                        self.tunnel = {}
                    for key in self.vnc:
                        self.vnc[key].destroy()
                    self.builder.get_object("windowvncundock").hide()
                    self.vnc = {}

                if self.treeview.get_cursor()[1]:
                    state = self.selected_state
                    # First checks if VM is running
                    self.builder.get_object("btenterfullscreen").grab_focus()
                    self.builder.get_object("console_area").grab_focus()
                    if state == "Running":
                        if self.selected_type == "host":
                            ref = self.xc_servers[host].host_vm[self.selected_ref][0]
                        else:
                            ref = self.selected_ref

                        location = self.get_console_location(host, ref)

                        if location is not None and (
                            self.selected_ref not in self.tunnel.keys()
                            or (
                                self.selected_ref in self.vnc_process.keys()
                                and self.vnc_process[self.selected_ref].poll() == 0
                            )
                        ):
                            self.tunnel[self.selected_ref] = Tunnel(
                                self.xc_servers[host].session_uuid, location
                            )
                            port = self.tunnel[self.selected_ref].get_free_port()

                            if port is not None:
                                Thread(
                                    target=self.tunnel[self.selected_ref].listen,
                                    args=(port,),
                                ).start()
                                time.sleep(1)
                            else:
                                # TODO: Break here on error
                                print("Could not get a free port")

                            if sys.platform != "win32" and sys.platform != "darwin":
                                if self.vnc and self.selected_ref in self.vnc.keys():
                                    self.vnc[self.selected_ref]
                                # Create a gtkvnc object
                                self.vnc[self.selected_ref] = GtkVnc.Display()
                                # Add to gtkvnc to a console area
                                console_area = self.builder.get_object("console_area")
                                if hasattr(self, "current_vnc"):
                                    console_area.remove(self.current_vnc)
                                # Define current VNC window
                                self.current_vnc = self.vnc[self.selected_ref]
                                # Add it to the console area
                                console_area.add(self.vnc[self.selected_ref])
                                console_area.show_all()

                                self.vnc[self.selected_ref].activate()
                                self.vnc[self.selected_ref].grab_focus()
                                self.vnc[self.selected_ref].set_pointer_grab(False)
                                self.vnc[self.selected_ref].set_pointer_local(False)
                                self.vnc[self.selected_ref].set_keyboard_grab(True)
                                self.vnc[self.selected_ref].set_shared_flag(True)
                                self.vnc[self.selected_ref].connect(
                                    "vnc-disconnected", self.vnc_disconnected
                                )
                                self.vnc[self.selected_ref].connect(
                                    "key_press_event",
                                    self.on_console_area_key_press_event,
                                )

                                # And open the connection
                                try:
                                    self.vnc[self.selected_ref].set_depth(1)
                                except RuntimeError:
                                    pass

                                self.vnc[self.selected_ref].connect(
                                    "vnc-server-cut-text", self.vnc_button_release
                                )
                                try:
                                    self.vnc[self.selected_ref].open_host(
                                        "localhost", str(port)
                                    )
                                except Exception as e:
                                    self.show_error_dlg(
                                        "Failed to open VNC console: " + str(e)
                                    )
                                    return

                            elif sys.platform == "darwin":
                                # Run ./vncviewer with host, vm renf and session ref
                                viewer = self.config["options"]["vnc_viewer"]
                                if viewer and os.path.exists(viewer):
                                    self.vnc_process[self.selected_ref] = Popen(
                                        [viewer, "localhost::%s" % port],
                                        shell=False,
                                        preexec_fn=os.setsid,
                                    )
                                    console_area = self.builder.get_object(
                                        "console_area"
                                    )
                                    console_alloc = console_area.get_allocation()
                                else:
                                    print(
                                        "No VNC detected or VNC executable path does not exist"
                                    )

                            else:
                                Thread(
                                    target=self.tunnel[self.selected_ref].listen,
                                    args=(port,),
                                ).start()
                                time.sleep(1)
                                # And open the connection
                                # TODO: Add the capability to change this path in the options and save to config
                                # viewer = os.path.join('C:\\', 'Program Files', 'TightVNC', 'tvnviewer.exe')
                                viewer = self.config["options"]["vnc_viewer"]
                                # Tight VNC Options
                                # Start the viewer and connect to the specified host:
                                # tvnviewer hostname::port [OPTIONS]
                                param = "localhost::" + str(port)

                                pid = Popen([viewer, param])
                                console_area = self.builder.get_object("frameconsole")
                                console_area.realize()
                                console_alloc = console_area.get_allocation()
                                window_alloc = self.window.get_position()
                                x = console_alloc.x + window_alloc[0] + 10
                                y = console_alloc.y + window_alloc[1] + 47
                                # On windows we'll move the window..

                                while (
                                    win32gui.FindWindow(
                                        None, "HVMXEN-%s" % self.selected_uuid
                                    )
                                    == 0
                                    and win32gui.FindWindow(
                                        None, "XenServer Virtual Terminal"
                                    )
                                    == 0
                                    and win32gui.FindWindow(
                                        None,
                                        "XenServer Virtual Terminal - TightVNC Viewer",
                                    )
                                    == 0
                                ):
                                    pass
                                self.hWnd = win32gui.FindWindow(
                                    None, "HVMXEN-%s" % self.selected_uuid
                                )
                                if self.hWnd == 0:
                                    self.hWnd = win32gui.FindWindow(
                                        None, "XenServer Virtual Terminal"
                                    )
                                if self.hWnd == 0:
                                    self.hWnd = win32gui.FindWindow(
                                        None,
                                        "XenServer Virtual Terminal - TightVNC Viewer",
                                    )

                                if self.hWnd != 0:
                                    win32gui.MoveWindow(
                                        self.hWnd,
                                        x,
                                        y,
                                        console_alloc.width - 10,
                                        console_alloc.height - 5,
                                        1,
                                    )
                                else:
                                    print("Could not retrieve the window ID")

                        else:
                            if (
                                sys.platform != "win32"
                                and sys.platform != "darwin"
                                and eval(self.config["options"]["multiple_vnc"])
                            ):
                                console_area = self.builder.get_object("console_area")
                                if hasattr(self, "current_vnc"):
                                    console_area.remove(self.current_vnc)
                                # Define current VNC window
                                self.current_vnc = self.vnc[self.selected_ref]
                                # Add it to the console area
                                console_area.add(self.vnc[self.selected_ref])
                                console_area.show_all()
                            else:
                                print("No console available")
                    else:
                        print(state)

            if tab_label == "VM_Memory":
                self.update_memory_tab()

            if tab_label == "VM_Storage":
                if self.treeview.get_cursor()[1]:
                    # liststorage contains the storage on VM
                    liststorage = self.builder.get_object("listvmstorage")
                    # liststoragdvd contains the possibles dvd/isos to mount on VM
                    liststoragedvd = self.builder.get_object("listvmstoragedvd")
                    # liststoragedvd.set_sort_func(1, self.compare_data)
                    # Fill liststorage
                    self.xc_servers[host].fill_vm_storage(
                        self.selected_ref, liststorage
                    )
                    # Fill liststoragedvd, fill_vm_storage_dvd return the current dvd/iso mounted
                    active = self.xc_servers[host].fill_vm_storage_dvd(
                        self.selected_ref, liststoragedvd
                    )
                    # Flag variable to no emit signal
                    self.set_active = True
                    # Set as the active dvd/iso mounted
                    self.builder.get_object("combovmstoragedvd").set_active(active)
                    self.set_active = False
            elif tab_label == "VM_Network":
                if self.treeview.get_cursor()[1]:
                    treenetwork = self.builder.get_object("treevmnetwork")
                    # listvmnetwork contains the networks of a vm
                    listnetwork = self.builder.get_object("listvmnetwork")
                    # Fill the list of networks
                    self.xc_servers[host].fill_vm_network(
                        self.selected_ref, treenetwork, listnetwork
                    )
            elif tab_label == "VM_Snapshots":
                if self.treeview.get_cursor()[1]:
                    treevmsnapshots = self.builder.get_object("treevmsnapshots")
                    # listvmsnapshots contains the snapshots of a vm
                    listvmsnapshots = self.builder.get_object("listvmsnapshots")
                    # Fill the list of snapshots
                    self.xc_servers[host].fill_vm_snapshots(
                        self.selected_ref, treevmsnapshots, listvmsnapshots
                    )
            elif tab_label == "VM_Performance":
                if self.treeview.get_cursor()[
                    1
                ]:  # Get which VM is selected in the left list
                    # Thread to update performance images
                    ref = self.selected_ref
                    if self.selected_type == "vm":
                        self.builder.get_object("scrwin_diskusage").show()
                        self.builder.get_object("labeldiskusage").show()
                        Thread(
                            target=self.xc_servers[host].update_performance,
                            args=(self.selected_uuid, ref, self.selected_ip, False),
                        ).start()
                    else:
                        self.builder.get_object("scrwin_diskusage").hide()
                        self.builder.get_object("labeldiskusage").hide()
                        if host and self.selected_ref in self.xc_servers[host].host_vm:
                            uuid = self.xc_servers[host].host_vm[self.selected_ref][1]
                            Thread(
                                target=self.xc_servers[host].update_performance,
                                args=(uuid, ref, self.selected_ip, True),
                            ).start()

            elif tab_label == "VM_Logs":
                if self.treeview.get_cursor()[1]:
                    treeviewlog = self.builder.get_object("treeviewlog")
                    # listlog contains the snapshots of a vm/host
                    listlog = self.builder.get_object("listlog")
                    try:
                        self.xc_servers[host].fill_vm_log(
                            self.selected_uuid, treeviewlog, listlog
                        )
                    except Exception as e:
                        import traceback

                        print("Error filling VM log: %s" % str(e))
                        print(traceback.format_exc())

            elif tab_label == "HOST_Users":
                if self.selected_type == "pool":
                    name = self.xc_servers[host].all["pool"][self.selected_ref][
                        "name_label"
                    ]
                    externalauth = self.xc_servers[host].get_external_auth(
                        self.xc_servers[host]["master"]
                    )
                else:
                    if self.selected_ref in self.xc_servers[host].all["host"]:
                        name = self.xc_servers[host].all["host"][self.selected_ref][
                            "name_label"
                        ]
                        externalauth = self.xc_servers[host].get_external_auth(
                            self.selected_ref
                        )

                listusers = self.builder.get_object("listusers")
                self.xc_servers[host].fill_domain_users(self.selected_ref, listusers)

                if externalauth[0] == "":
                    self.builder.get_object("btjoindomain").set_sensitive(True)
                    self.builder.get_object("btleavedomain").set_sensitive(False)
                    self.builder.get_object("lblusersdomain").set_text(
                        "AD is not currently configured for '"
                        + self.selected_name
                        + "'. To enable AD "
                        "authentication, click "
                        "Join."
                    )
                else:
                    self.builder.get_object("btleavedomain").set_sensitive(True)
                    self.builder.get_object("btjoindomain").set_sensitive(False)
                    self.builder.get_object("lblusersdomain").set_text(
                        "Pool/host "
                        + self.selected_name
                        + " belongs to domain '"
                        + externalauth[1]
                        + "'. To enable AD authentication, click Join."
                    )

            elif tab_label == "HOST_Storage":
                if self.treeview.get_cursor()[1]:
                    # listhoststorage contains the snapshots of a vm/host
                    liststorage = self.builder.get_object("listhoststorage")
                    # Fill the list of storage
                    self.xc_servers[host].fill_host_storage(
                        self.selected_ref, liststorage
                    )
            elif tab_label == "HOST_Nics":
                if self.treeview.get_cursor()[1]:
                    # liststorage = self.builder.get_object("listhostnics")
                    # self.xc_servers[host].fill_host_nics(self.selected_ref, liststorage)

                    # Call to update_tab_host_nics to fill the host nics
                    self.update_tab_host_nics()

            elif tab_label == "HOST_Search":
                if self.treeview.get_cursor()[1]:
                    self.xc_servers[host].halt_search = False
                    # Host_Search contains a live monitoring status of VM
                    # Create a thread to fill "listsearch"
                    self.xc_servers[host].thread_host_search(
                        self.selected_ref, self.listsearch
                    )
                    # Expand "treesearch"
                    self.treesearch.expand_all()

            elif tab_label == "HOST_Hardware":
                if host:
                    self.xc_servers[host].fill_host_hardware(self.selected_ref)

            elif tab_label == "HOST_Network":
                # Call to update_tab_host_network to fill the host networks
                self.update_tab_host_network()
            elif tab_label == "Local_Storage":
                if self.treeview.get_cursor()[1]:
                    # liststg contains the vdi under storage
                    liststg = self.builder.get_object("liststg")
                    liststg.set_sort_func(1, self.compare_data)
                    liststg.set_sort_column_id(1, Gtk.SortType.ASCENDING)
                    # Fill the list of storage
                    if host:
                        self.xc_servers[host].fill_local_storage(
                            self.selected_ref, liststg
                        )
            elif tab_label == "Maps":
                self.update_maps()

    def get_console_location(self, host, ref):
        location = None
        if self.xc_servers[host].all["vms"][ref]["consoles"]:
            nb_consoles = len(self.xc_servers[host].all["vms"][ref]["consoles"])
            for i in range(nb_consoles):
                console_ref = self.xc_servers[host].all["vms"][ref]["consoles"][i]
                protocol = self.xc_servers[host].all["console"][console_ref]["protocol"]
                if protocol == "rfb":
                    location = self.xc_servers[host].all["console"][console_ref][
                        "location"
                    ]
                    break
            if location is None:
                print("No VNC console found")
        return location

    def compare_data(self, model, iter1, iter2, user_data=None):
        """Compare function for Gtk TreeView sorting (GTK3 compatible)"""
        try:
            data1 = model.get_value(iter1, 1)
            data2 = model.get_value(iter2, 1)
        except Exception:
            return 0
        if data1 is None and data2 is None:
            return 0
        if data1 is None:
            return -1
        if data2 is None:
            return 1
        try:
            # Try numeric comparison first
            num1 = float(data1) if not isinstance(data1, (int, float)) else data1
            num2 = float(data2) if not isinstance(data2, (int, float)) else data2
            return (num1 > num2) - (num1 < num2)
        except (ValueError, TypeError):
            # Fall back to string comparison
            str1 = str(data1)
            str2 = str(data2)
            return (str1 > str2) - (str1 < str2)

    def update_maps(self):
        dotcode = """
            digraph G {
                      overlap=false;
                      bgcolor=white;
                      node [shape=polygon, sides=6, fontname="Verdana", fontsize="8"];
                      edge [color=deepskyblue3, fontname="Verdana", fontsize="5"];
            """

        if self.selected_host:
            show_halted_vms = self.builder.get_object(
                "check_show_halted_vms"
            ).get_active()
            if self.builder.get_object("check_show_network").get_active():
                relation = self.xc_servers[self.selected_host].get_network_relation(
                    self.selected_ref, show_halted_vms
                )
                for network in relation:
                    uuid, name = network.split("_", 1)
                    safename = (
                        name.replace("&", "&amp;")
                        .replace("<", "&lt;")
                        .replace('"', "&quot;")
                    )
                    if (
                        self.builder.get_object("check_unused_network").get_active()
                        or relation[network]
                    ):
                        dotcode += (
                            '"%s"[shape=plaintext, label=<<table border="0" cellpadding="0" '
                            'cellspacing="0"><tr><td><img src="%s"/></td></tr><tr>'
                            '<td> </td></tr><tr><td>%s</td></tr></table>> tooltip="%s"];'
                            % (
                                uuid,
                                os.path.join(
                                    utils.module_path(), "images_map/network.png"
                                ),
                                safename,
                                name,
                            )
                        )
                        dotcode += "\n"
                    for vm in relation[network]:
                        uuid2, name2 = vm.split("_", 1)
                        dotcode += (
                            '"%s"[shape=plaintext, label=<<table border="0" cellpadding="0" '
                            'cellspacing="0"><tr><td><img src="%s"/></td></tr><tr>'
                            '<td> </td></tr><tr><td>%s</td></tr></table>>URL="%s" tooltip="%s"];'
                            % (
                                uuid2,
                                os.path.join(
                                    utils.module_path(), "images_map/server.png"
                                ),
                                name2,
                                uuid2,
                                name2,
                            )
                        )
                        dotcode += "\n"
                        dotcode += '"%s" -> "%s"' % (uuid, uuid2)
                        dotcode += "\n"

            if self.builder.get_object("check_show_storage").get_active():
                dotcode += 'edge [color=forestgreen, fontname="Verdana", fontsize="5"];'
                relation = self.xc_servers[self.selected_host].get_storage_relation(
                    self.selected_ref, show_halted_vms
                )
                for storage in relation:
                    uuid, name = storage.split("_", 1)
                    safename = (
                        name.replace("&", "&amp;")
                        .replace("<", "&lt;")
                        .replace('"', "&quot;")
                    )
                    if (
                        self.builder.get_object("check_unused_storage").get_active()
                        or relation[storage]
                    ):
                        dotcode += (
                            '"%s"[shape=plaintext, label=<<table border="0" cellpadding="0" '
                            'cellspacing="0"><tr><td><img src="%s"/></td></tr><tr>'
                            '<td> </td></tr><tr><td>%s</td></tr></table>>URL="%s" tooltip="%s"];'
                            % (
                                uuid,
                                os.path.join(
                                    utils.module_path(), "images_map/storage.png"
                                ),
                                safename,
                                uuid,
                                name,
                            )
                        )
                        dotcode += "\n"
                    for vm in relation[storage]:
                        uuid2, name2 = vm.split("_", 1)
                        safename2 = (
                            name2.replace("&", "&amp;")
                            .replace("<", "&lt;")
                            .replace('"', "&quot;")
                        )
                        dotcode += (
                            '"%s"[shape=plaintext, label=<<table border="0" cellpadding="0" '
                            'cellspacing="0"><tr><td><img src="%s"/></td></tr><tr>'
                            '<td> </td></tr><tr><td>%s</td></tr></table>>URL="%s" tooltip="%s"];'
                            % (
                                uuid2,
                                os.path.join(
                                    utils.module_path(), "images_map/server.png"
                                ),
                                safename2,
                                uuid2,
                                name2,
                            )
                        )
                        dotcode += "\n"
                        dotcode += '"%s" -> "%s"' % (uuid2, uuid)
                        dotcode += "\n"

            dotcode += "}"

            self.windowmap.set_dotcode(dotcode)
            self.builder.get_object("viewportmap").show_all()

    def on_btopenfile_activate(self, widget, data=None):
        """
        Obsoleted function
        """
        filechooser = self.fileopen.get_children()[0].get_children()[0]
        if filechooser.get_filename():
            self.xc_servers[self.selected_host].import_vm(
                self.selected_ref, filechooser.get_filename()
            )
            self.fileopen.hide()
        else:
            self.show_error_dlg("Select a file")

    def on_btsavefile_activate(self, widget, data=None):
        """
        Function called when you press "Export VM"
        """
        filechooser = self.filesave.get_children()[0].get_children()[0]
        if filechooser.get_filename():
            # Call to export_vm function with vm renf and filename choosed
            if self.export_snap:
                print("Export snap..")
                self.xc_servers[self.selected_host].export_vm(
                    self.selected_snap_ref,
                    filechooser.get_filename(),
                    self.selected_ref,
                )
                self.export_snap = False
            elif self.export_snap_vm:
                print("Export snap as VM..")
                self.xc_servers[self.selected_host].export_vm(
                    self.selected_snap_ref,
                    filechooser.get_filename(),
                    self.selected_ref,
                    as_vm=True,
                )
                self.export_snap_vm = False
            else:
                self.xc_servers[self.selected_host].export_vm(
                    self.selected_ref, filechooser.get_filename()
                )
            self.filesave.hide()
            self.builder.get_object("tabbox").set_current_page(17)
        else:
            self.show_error_dlg("Select a file")

    def on_filesave_confirm_overwrite(self, widget, data=None):
        """
        Not used function
        """
        print(widget)
        print(data)

    def on_btcancelsavefile_activate(self, widget, data=None):
        """
        If you press cancel on "Export VM" dialog, then close the dialog
        """
        self.export_snap = False
        self.filesave.hide()

    def on_btcancelopenfile_activate(self, widget, data=None):
        """
        Not used function
        """
        self.fileopen.hide()

    def on_treevm_button_press_event(self, widget, event):
        """
        Function is called when you do click (or double click) on left tree
        """
        x = int(event.x)
        y = int(event.y)
        event_time = event.time
        if x == -10 and y == -10:
            pthinfo = [
                self.modelfilter.get_path(
                    self.treeview.get_selection().get_selected()[1]
                ),
                None,
                0,
                0,
            ]
        else:
            pthinfo = widget.get_path_at_pos(x, y)
        if pthinfo is not None:
            path, col, cellx, celly = pthinfo
            widget.grab_focus()
            widget.set_cursor(path, col, 0)
            path = self.modelfilter.convert_path_to_child_path(path)
            iter_ref = self.treestore.get_iter(path)
            # Define selected variables
            self.selected_iter = iter_ref
            self.selected_name = self.treestore.get_value(iter_ref, 1)
            self.selected_uuid = self.treestore.get_value(iter_ref, 2)
            self.selected_type = self.treestore.get_value(iter_ref, 3)
            self.selected_state = self.treestore.get_value(iter_ref, 4)
            self.selected_host = self.treestore.get_value(iter_ref, 5)
            self.selected_ip = self.treestore.get_value(iter_ref, 8)
            # Used to prevent not manual changes
            previous_ref = self.selected_ref
            self.selected_ref = self.treestore.get_value(iter_ref, 6)

            if event.button == 1:
                try:
                    # Treat both 'host' and legacy 'server' types as connectable entries
                    if self.selected_type == "host" or self.selected_type == "server":
                        # Prefer using saved host configuration when available
                        if (
                            getattr(self, "config_hosts", None)
                            and self.selected_name in self.config_hosts
                        ):
                            # Reuse existing connect handler which handles saved credentials
                            try:
                                self.on_m_connect_activate(widget, None)
                            except Exception:
                                # If something goes wrong, show an error dialog instead of crashing
                                self.show_error_dlg(
                                    "Failed to start connection flow for %s"
                                    % self.selected_name
                                )
                        else:
                            # No saved config: show the Add Server dialog pre-filled for manual connect
                            try:
                                add_server = AddServer(
                                    self, self.selected_name, self.selected_host
                                )
                                add_server.show_dialog("addserverpassword")
                            except Exception:
                                self.show_error_dlg(
                                    "Cannot open Add Server dialog for %s"
                                    % (self.selected_name or self.selected_host)
                                )
                except Exception as e:
                    # Defensive: ensure UI doesn't crash from unexpected state
                    self.show_error_dlg(
                        "Unexpected error while attempting connect: %s" % str(e)
                    )
                pass

            if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
                # On double click, connect to the server
                if self.selected_type == "host":
                    # Ensure selected_name is set (some tree entries use 'host' vs 'server' naming)
                    try:
                        # selected_name should already be set above, but be defensive
                        if not getattr(self, "selected_name", None):
                            self.selected_name = self.selected_host
                    except Exception:
                        self.selected_name = self.selected_host

                    # Only attempt to connect if we have saved configuration for this host
                    if self.selected_name in getattr(self, "config_hosts", {}):
                        # Reuse existing connect handler which handles saved credentials
                        try:
                            self.on_m_connect_activate(widget, None)
                        except Exception:
                            # If something goes wrong, show an error dialog instead of crashing
                            self.show_error_dlg(
                                "Failed to start connection flow for %s"
                                % self.selected_name
                            )
                    else:
                        # No saved config: show the Add Server dialog pre-filled for manual connect
                        try:
                            add_server = AddServer(
                                self, self.selected_name, self.selected_host
                            )
                            add_server.show_dialog("addserverpassword")
                        except Exception:
                            self.show_error_dlg(
                                "Cannot open Add Server dialog for %s"
                                % (self.selected_name or self.selected_host)
                            )
            else:
                # On single click
                # Define the possible actions for VM/host/storage..
                if self.selected_type == "vm":
                    self.selected_actions = self.xc_servers[
                        self.selected_host
                    ].get_actions(self.selected_ref)
                else:
                    self.selected_actions = self.treestore.get_value(iter_ref, 7)
                # if type(self.selected_actions) == type(""):
                #    self.selected_actions = eval(self.selected_actions)
                # Update menubar and tabs with new selection
                self.update_menubar()
                self.update_tabs()
                if self.selected_ref != previous_ref:
                    # If you selected a different element than previous
                    # then select the correct tab for selected type
                    if self.selected_type == "vm":
                        self.builder.get_object("tabbox").set_current_page(5)
                    else:
                        self.builder.get_object("tabbox").set_current_page(3)
                if self.selected_type == "pool":
                    self.builder.get_object("tabbox").set_current_page(0)
                elif self.selected_type == "host":
                    self.builder.get_object("tabbox").set_current_page(1)
                    self.builder.get_object("tabbox").set_current_page(4)
                elif self.selected_type == "server":
                    self.builder.get_object("tabbox").set_current_page(2)
                elif self.selected_type == "template":
                    self.builder.get_object("tabbox").set_current_page(2)
                elif self.selected_type == "custom_template":
                    self.builder.get_object("tabbox").set_current_page(2)
                elif self.selected_type == "storage":
                    self.builder.get_object("tabbox").set_current_page(1)

            if event.button == 3:
                # On right click..
                # Show the menu
                menu_vm = self.builder.get_object("context_menu_vm")
                collapsed = False
                expanded = False
                can_expand_or_collapse = False
                for child in range(
                    0, self.treestore.iter_n_children(self.selected_iter)
                ):
                    iter_ref = self.treestore.iter_nth_child(self.selected_iter, child)
                    if self.treestore.iter_n_children(iter_ref):
                        can_expand_or_collapse = True
                        path = self.treestore.get_path(iter_ref)
                        if self.treeview.row_expanded(path):
                            expanded = True
                        else:
                            collapsed = True

                if can_expand_or_collapse:
                    if collapsed:
                        self.builder.get_object("expandall").show()
                    else:
                        self.builder.get_object("expandall").hide()
                    if expanded:
                        self.builder.get_object("collapsechildren").show()
                    else:
                        self.builder.get_object("collapsechildren").hide()
                else:
                    self.builder.get_object("expandall").hide()
                    self.builder.get_object("collapsechildren").hide()

                for child in menu_vm.get_children():
                    # Menuitems are with name "m_action"
                    # Checks if "action" is on selected_actions"
                    typestg = None
                    pbdstg = 1
                    if self.selected_type == "storage":
                        typestg = self.xc_servers[self.selected_host].all["SR"][
                            self.selected_ref
                        ]["type"]
                        pbdstg = len(
                            self.xc_servers[self.selected_host].all["SR"][
                                self.selected_ref
                            ]["PBDs"]
                        )
                    if Gtk.Buildable.get_name(child)[0:2] == "m_":
                        if (
                            not self.selected_actions
                            or self.selected_actions.count(
                                Gtk.Buildable.get_name(child)[2:]
                            )
                            == 0
                        ):
                            child.hide()
                        else:
                            # If selected_type is storage and typestg is not "lvm" or "udev"
                            if typestg != "lvm" and typestg != "udev":
                                # If has not pbds.. then enable only "Reattach" and "Forget"
                                if pbdstg == 0 and (
                                    Gtk.Buildable.get_name(child) == "m_plug"
                                    or Gtk.Buildable.get_name(child) == "m_forget"
                                ):
                                    child.show()
                                else:
                                    # Disable else
                                    if pbdstg == 0:
                                        child.hide()
                                    else:
                                        # If has pbds.. disable "Reattach"
                                        if Gtk.Buildable.get_name(child) != "m_plug":
                                            child.show()
                                        else:
                                            child.hide()
                            else:
                                child.hide()
                    # Properties will be showed always else on home and disconnected servers
                    if Gtk.Buildable.get_name(child) == "properties":
                        if self.selected_type == "home":
                            child.hide()
                        elif self.selected_type == "server" and not self.selected_ref:
                            child.hide()
                        else:
                            child.show()
                    # Delete will be showed only on pool
                    elif Gtk.Buildable.get_name(child) == "delete":
                        if self.selected_type == "pool":
                            child.show()
                        else:
                            child.hide()
                    # Install XenServer Tools only on
                    elif Gtk.Buildable.get_name(child) == "installxenservertools":
                        if (
                            self.selected_type == "vm"
                            and self.selected_state == "Running"
                        ):
                            self.builder.get_object("separator1").show()
                            self.builder.get_object("separator2").show()
                            child.show()
                        else:
                            self.builder.get_object("separator1").hide()
                            self.builder.get_object("separator2").hide()
                            child.hide()
                    # Repair storage, only on broken storage
                    elif Gtk.Buildable.get_name(child) == "m_repair_storage":
                        if self.selected_type == "storage":
                            broken = self.xc_servers[
                                self.selected_host
                            ].is_storage_broken(self.selected_ref)
                            if broken:
                                child.show()
                            else:
                                child.hide()
                    # Add to pool, only for servers without pools
                    elif Gtk.Buildable.get_name(child) == "m_add_to_pool":
                        if self.selected_type == "host":
                            pool_ref = list(
                                self.xc_servers[self.selected_host].all["pool"].keys()
                            )[0]
                            if (
                                self.xc_servers[self.selected_host].all["pool"][
                                    pool_ref
                                ]["name_label"]
                                == ""
                            ):
                                child.show()
                            else:
                                child.hide()
                        else:
                            child.hide()
                    # Add server to pool from pool menu
                    elif Gtk.Buildable.get_name(child) == "m_pool_add_server":
                        if self.selected_type == "pool":
                            child.show()
                        else:
                            child.hide()
                menu_vm.popup_at_pointer(event)

            # Update toolbar and set label/image on top right pane
            self.update_toolbar()
            self.headlabel.set_label(self.calc_headlabel_text())
            self.headimage.set_from_pixbuf(self.treestore.get_value(iter_ref, 0))

    def calc_headlabel_text(self):
        """
        Work out the text to display on the headlabel

        :return: Headlabel text
        :rtype: str
        """
        if self.selected_type == "vm":
            txt = "%s on %s" % (self.selected_name, self.selected_host)
        else:
            txt = self.selected_name
        return txt

    def vnc_disconnected(self, info):
        print("VNC disconnected..", info)
        # We need to find which one of the open vnc windows was disconnected in order to remove it from the stored dictionaries
        disconnected_vnc = None
        if self.vnc and eval(self.config["options"]["multiple_vnc"]):
            for key in self.vnc:
                if self.vnc[key] == info:
                    disconnected_vnc = key
                    break
            if disconnected_vnc:
                if disconnected_vnc in self.vnc_builders:
                    # This will hook to the destroy method so there is no need to remove the key from the dict
                    # TODO handle the reboot in the window itself
                    self.vnc_builders[disconnected_vnc].get_object(
                        "windowvncundock"
                    ).destroy()

                if disconnected_vnc in self.vnc.keys():
                    del self.vnc[disconnected_vnc]
                if disconnected_vnc in self.tunnel.keys():
                    del self.tunnel[disconnected_vnc]

    def on_txttreefilter_changed(self, widget, data=None):
        """
        Function called when on left top entry you write text to filter
        """
        self.modelfilter.refilter()
        self.treeview.expand_all()

    def show_error_dlg(self, error_string, error_title="Error"):
        """This Function is used to show an error dialog when
        an error occurs.
        error_string - The error string that will be displayed
        on the dialog.
        http://www.pyGtk.org/articles/extending-our-pygtk-application/extending-our-pygtk-application.htm
        """
        self.builder.get_object("walert").set_title(error_title)
        self.builder.get_object("walerttext").set_text(error_string)
        self.builder.get_object("walert").show()

    def on_closewalert_clicked(self, widget, data=None):
        self.builder.get_object("walert").hide()

    def push_alert(self, alert):
        """
        Function to set in statusbar an alert
        """
        # self.statusbar.get_children()[0].get_children()[0].modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse('#000000'))  # Deprecated in GTK3
        self.statusbar.push(1, alert)

    def push_error_alert(self, alert):
        """
        Function to set in statusbar an error alert
        """
        # self.statusbar.get_children()[0].get_children()[0].modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse('#FF0000'))  # Deprecated in GTK3
        self.statusbar.push(1, alert)

    def not_implemented_yet(self, widget, data=None):
        """
        Some functions are not implemented yet, show the dialog
        """
        self.show_error_dlg("Not implemented yet")

    def dump(self, obj):
        """
        Internal use only
        """
        for attr in dir(obj):
            print("obj.%s = %s" % (attr, getattr(obj, attr)))

    def signal_handler(self, *args):
        """
        Function called when oxc gets a signal
        """
        print("Exiting...")
        for sh in self.xc_servers:
            self.xc_servers[sh].halt = True
            self.xc_servers[sh].halt_search = True
            self.xc_servers[sh].halt_performance = True
            self.xc_servers[sh].logout()
        self.config.write()
        if self.hWnd != 0:
            win32gui.PostMessage(self.hWnd, win32con.WM_QUIT, 0, 0)
            self.hWnd = 0

    def on_delete_event(self, widget, event):
        # Returning True, the window will not be destroyed
        widget.hide()
        return True

    def convert_bytes(self, bytes):
        # Convert bytes to string
        # http://www.5dollarwhitebox.org/drupal/node/84

        bytes = float(bytes)
        if bytes >= 1099511627776:
            terabytes = bytes / 1099511627776
            size = "%.1fT" % terabytes
        elif bytes >= 1073741824:
            gigabytes = bytes / 1073741824
            size = "%.1fG" % gigabytes
        elif bytes >= 1048576:
            megabytes = bytes / 1048576
            size = "%.1fM" % megabytes
        elif bytes >= 1024:
            kilobytes = bytes / 1024
            size = "%.1fK" % kilobytes
        else:
            size = "%.1fb" % bytes
        return size

    def convert_bytes_mb(self, n):
        # Convert bytes to mb string

        n = float(n)
        K, M = 1 << 10, 1 << 20
        if n >= M:
            return "%d" % (float(n) / M)
        elif n >= K:
            return "%d" % (float(n) / K)
        else:
            return "%d" % n
