#!/usr/bin/env python3
# vim: set et ts=4 sts=4 sw=4:
# File: gmm-gtk
# Location: /usr/bin
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only
# Startdate: 2024-11-20-4 16:31
# Title: Graphical Mount Manager in Gtk3
# Purpose: Easily mount iso files and easily manage these mounted files and mount points, basically like acetoneiso
# History:
# Usage: see man page
# Reference:
#    logout-manager-gtk
#    https://python-gtk-3-tutorial.readthedocs.io/en/latest/dialogs.html
#    https://python-gtk-3-tutorial.readthedocs.io/en/latest/menus.html also shows popup (right click) menu, and toolbars not used here
#    https://www.programcreek.com/python/example/1399/gtk.FileChooserDialog gtk2 example which took a little work to update to gtk3; minor things like FileChooserAction.CANCEL or similar.
#    https://gist.github.com/mi4code/d53b81ed6353275e9bbeedfb7b5fd990
#    https://github.com/sam-m888/python-gtk3-tutorial/blob/master/aboutdialog.rst
#    https://stackoverflow.com/questions/36921706/gtk3-ask-confirmation-before-application-quit
# disused references:
#     https://python-gtk-3-tutorial.readthedocs.io/en/latest/application.html#application
# Improve:
# Dependencies:

import gi, os, sys, subprocess, threading
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk
from gi.repository.GdkPixbuf import Pixbuf

# ADJUST THIS SECTION FOR DEVELOPMENT
# for prod, this should be ""
home_dir = os.getenv("SUDO_HOME",os.getenv("HOME"))
if os.getenv("DEBUG",None):
    sys.path.append(os.path.join(home_dir,"dev","gmm","src","usr/share/gmm"))

sys.path.append("/usr/share/gmm")
sys.path.append(".")
import gmm_lib as gmm
from gmm_lib import debuglev, ferror, appname

# GRAPHICAL APP
# gtk complains that this is deprecated, but they make every good thing "deprecated."
MENU_INFO = """
<ui>
    <menubar name='MenuBar'>
        <menu action='FileMenu'>
            <menuitem action='FileMount' />
            <menuitem action='FileSettings' />
        <separator />
        <menuitem action='FileQuit' />
        </menu>
        <menu action='HelpMenu'>
            <menuitem action='HelpAbout' />
        </menu>
    </menubar>
</ui>
"""

class SettingsDialog(Gtk.Dialog):
    # ref https://python-gtk-3-tutorial.readthedocs.io/en/latest/dialogs.html#custom-dialogs
    def __init__(self, parent):
        super().__init__(title="Settings", transient_for=parent,flags=0)
        self.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK
        )
        self.set_default_size(150,50)
        lbl_mounts_dir = Gtk.Label(label="Mounts directory")
        self.ent_mounts_dir = Gtk.Entry(text=parent.dialog_mounts_dir)
        if parent.gmmapp.mounts:
            self.ent_mounts_dir.set_sensitive(False)
        box = self.get_content_area()
        box.add(lbl_mounts_dir)
        box.add(self.ent_mounts_dir)
        self.show_all()

class MainWindow(Gtk.ApplicationWindow):
    def __init__(self, gmmapp, *args, **kwargs):
        super().__init__(title=gmm.appname_full,*args, **kwargs)
        self.gmmapp = gmmapp
        self.liststore = Gtk.ListStore(str,str)
        self.set_icon_name(gmm.icon_name)
        # menu
        action_group = Gtk.ActionGroup(name="actions")
        self.add_file_menu_actions(action_group)
        self.add_help_menu_actions(action_group)
        uimanager = self.create_ui_manager()
        uimanager.insert_action_group(action_group)
        self.menubar = uimanager.get_widget("/MenuBar")
        # main layout
        self.grid = Gtk.Grid()
        self.add(self.grid)
        self.grid.add(self.menubar)
        renderer = Gtk.CellRendererText()
        #column = Gtk.TreeViewColumn("Source",renderer,text=0,weight=1)
        self.ml = Gtk.TreeView(model=self.liststore)
        self.ml.append_column(Gtk.TreeViewColumn("Source",renderer,text=0))
        self.ml.append_column(Gtk.TreeViewColumn("Mount point",renderer,text=1))
        self.ml.connect("row-activated", self.func_double_click_entry)
        self.current_selection = None
        selection = self.ml.get_selection()
        selection.connect("changed", self.func_tree_selection_changed)
        self.grid.attach(self.ml, 0,1, 4, 1)
        self.unmount_btn = Gtk.Button.new_with_mnemonic(label="_Unmount")
        self.unmount_btn.connect("clicked", self.func_unmount_current_selection)
        self.grid.attach_next_to(self.unmount_btn,self.ml,Gtk.PositionType.BOTTOM,1,1)
        # xfe needs the Gdk.DragAction.MOVE flag
        self.ml.drag_dest_set(
            Gtk.DestDefaults.ALL,
            [
                Gtk.TargetEntry.new("text/uri-list", 0, 0),
            ],
            Gdk.DragAction.COPY | Gdk.DragAction.MOVE | Gdk.DragAction.ASK | Gdk.DragAction.LINK
        )
        self.ml.connect("drag-data-received", self.on_drop)
        # initial load
        self.refresh_form()
        t1 = threading.Thread(target=self.gmmapp.watch_for_changes,args=("disused-but-must-be-not-a-function",self.refresh_form))
        t1.start()

    def do_delete_event(self, event = None):
        # override built-in do_delete_event, because we need to run this manually first.
        self.gmmapp.exit()
        # Calling this somehow is safe here.
        Gtk.main_quit()
        # this is the regular behavior
        return False

    def on_drop(self, context, x, y, data, info, time, o1 = None):
        #print("Dropped file(s):", uris)
        # mimetype, will probably just be uri-list
        mimetype = None
        try:
            mimetype = info.get_data_type()
        except:
            pass
        # raw string with \r\n, not useful because get_uris exists
        #urilist = info.get_data()
        uris = []
        try:
            uris = info.get_uris()
        except:
            pass
        if debuglev(9):
            ferror(f"DEBUG: on_drop got mimetype {mimetype}")
            ferror(f"DEBUG: on_drop got uris {uris}")
        for filename in [i.replace("file://","") for i in uris]:
            self.mount_iso_to_default(filename)

    def add_file_menu_actions(self, action_group):
        action_filemenu = Gtk.Action(name="FileMenu", label="_File")
        action_group.add_action(action_filemenu)
        #action_fileopenmenu = Gtk.Action(name="FileOpen", stock_id=Gtk.STOCK_OPEN)
        #action_group.add_action(action_fileopenmenu)

        action_mount = Gtk.Action(
            name="FileMount",
            label="_Mount iso...",
            tooltip="Mount existing disc image",
            stock_id=Gtk.STOCK_OPEN
        )
        action_mount.connect("activate", self.on_menu_file_mount)
        action_group.add_action_with_accel(action_mount, None)

        action_settings = Gtk.Action(
            name="FileSettings",
            label="_Settings...",
            tooltip="Open settings dialog",
            stock_id=Gtk.STOCK_PREFERENCES
        )
        action_settings.connect("activate", self.on_menu_file_settings)
        action_group.add_action_with_accel(action_settings, None)

        action_filequit = Gtk.Action(name="FileQuit", stock_id=Gtk.STOCK_QUIT)
        action_filequit.connect("activate", self.on_menu_file_quit)
        action_group.add_action(action_filequit)

    def add_help_menu_actions(self, action_group):
        action_helpmenu = Gtk.Action(name="HelpMenu", label="_Help")
        action_group.add_action(action_helpmenu)
        action_helpaboutmenu = Gtk.Action(name="HelpAbout", stock_id=Gtk.STOCK_ABOUT)
        action_helpaboutmenu.connect("activate", self.on_menu_help_about)
        action_group.add_action(action_helpaboutmenu)

    def create_ui_manager(self):
        uimanager = Gtk.UIManager()
        uimanager.add_ui_from_string(MENU_INFO)
        accelgroup = uimanager.get_accel_group()
        self.add_accel_group(accelgroup)
        return uimanager

    def on_menu_file_mount(self, widget):
        #print("STUB Do something with file mount....")
        chooser = Gtk.FileChooserDialog(
            title="Mount iso file",
            action=Gtk.FileChooserAction.OPEN,
            buttons=(
                Gtk.STOCK_CANCEL,
                Gtk.ResponseType.CANCEL,
                Gtk.STOCK_OPEN,
                Gtk.ResponseType.OK,
            )
        )
        chooser.set_default_response(Gtk.ResponseType.OK)
        filter = Gtk.FileFilter()
        filter.set_name("Disc images")
        filter.add_pattern("*.iso")
        chooser.add_filter(filter)
        filter = Gtk.FileFilter()
        filter.set_name("All files")
        filter.add_pattern("*")
        chooser.add_filter(filter)
        if chooser.run() == Gtk.ResponseType.OK:
            filename = chooser.get_filename()
            chooser.destroy()
            #self.open_file(filename)
            if debuglev(4):
                ferror(f"From file chooser, got file {filename}")
            self.mount_iso_to_default(filename)
        else:
            if debuglev(4):
                ferror(f"File chooser returned nothing.")
            chooser.destroy()

    def mount_iso_to_default(self, filename = None, o2 = None):
        if debuglev(1):
            #ferror(f"Mounting file {filename}")
            ferror(f"Will try to mount {filename}")
        if filename:
            self.gmmapp.mount_iso_to_default(filename)
            self.refresh_form()

    def on_menu_file_settings(self, widget):
        #print("STUB Do something with file settings....")
        dialog = SettingsDialog(self)
        response = dialog.run()
        print(f"got settings response {response}")
        if response == Gtk.ResponseType.OK:
            self.gmmapp.config.set(appname,"mounts_dir",str(dialog.ent_mounts_dir.get_text()))
            self.gmmapp.save_config(self.gmmapp.conffile)
        # delete the dialog
        dialog.destroy()
        # reload config and entire app
        self.refresh_form()

    def on_menu_file_quit(self, widget):
        # Must call our own function and not main_quit.
        #Gtk.main_quit()
        self.do_delete_event()

    def on_menu_help_about(self, widget):
        about_dialog = Gtk.AboutDialog(
            program_name = gmm.appname_full,
            icon_name = gmm.icon_name,
            logo_icon_name = gmm.icon_name,
            transient_for=self, modal=True,
            copyright = "© 2024",
            license_type = Gtk.License.GPL_3_0_ONLY,
            authors = gmm.authors,
            version = gmm.appversion,
        )
            #license = Gtk.License.GPL_3_0_ONLY,
        # present() will not let you close with the Close button.
        #about_dialog.present()
        _ = about_dialog.run()
        about_dialog.destroy()

    def refresh_form(self):
        # compare all config settings to see if they are different
        self.gmmapp.load_config(self.gmmapp.conffile) # this populates object "self.gmmapp.config"
        self.dialog_mounts_dir = self.gmmapp.config[appname]["mounts_dir"]
        self.gmmapp.mounts = self.gmmapp.list_mounts()
        self.liststore.clear()
        for i in self.gmmapp.mounts:
            print(f"DEBUG: looping over {i}")
            self.liststore.append([i["source"],i["mountpoint"]])

    def func_double_click_entry(self, tree_view, path, column):
        if debuglev(9):
            ferror(f"DEBUG: double-click {tree_view},{path},{column}")
        path = self.current_selection
        if path:
            if debuglev(1):
                ferror(f"Running xdg-open {path}")
            subprocess.Popen(["xdg-open",path])
        else:
            if debuglev(4):
                ferror(f"INFO: No item selected to open, continuing...")
            pass

    def func_tree_selection_changed(self, selection):
        model, treeiter = selection.get_selected()
        if treeiter is not None:
            # you have to know which column has the mountpoint. It is column 1 in gmm.
            try:
                self.current_selection = str(model[treeiter][1])
            except Exception as e:
                print(f"WARNING: cannot seem to save string from {model[treeiter]}")

    def func_unmount_current_selection(self, o1 = None):
        if debuglev(9):
            ferror(f"DEBUG: unmount button {o1}")
        #ferror(f"please unmount, {self.current_selection}!")
        path = self.current_selection
        if path:
            self.gmmapp.unmount_iso_to_path(path,"")
            self.refresh_form()
        elif debuglev(4):
            ferror(f"INFO: Nothing selected to unmount, continuing...")

if "__main__" == __name__:
    app = gmm.Gmm(gmm.args)
    app.cli_main()
    if app.show_gui:
        # MAIN GRAPICAL APP
        app.initialize_for_gui()
        win = MainWindow(app)
        win.connect("destroy", win.do_delete_event)
        win.show_all()
        Gtk.main()