#!/usr/bin/env python3
# vim: set et ts=4 sts=4 sw=4:
# File: gmm-tk
# Location: /usr/bin
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only # Startdate: 2024-11-18-2 13:58
# Title: Graphical Mount Manager in Tk
# Purpose: Easily mount iso files and easily manage these mounted files and mount points, basically like acetoneiso
# History:
# Usage: see man page
# Reference:
#    https://stackoverflow.com/questions/23662280/how-to-log-the-contents-of-a-configparser/50362738#50362738
#    srb_lib/srb_tk.py
#    https://coderslegacy.com/python/list-of-tkinter-widgets/
#    https://stackoverflow.com/questions/46331408/limiting-python-filedialog-to-a-specific-filetype/46339932#46339932
#    https://stackoverflow.com/questions/30614279/tkinter-treeview-get-selected-item-values
#    https://www.reddit.com/r/learnpython/comments/hcn8cc/cant_bind_double_click_to_treeview_with_grid/
#    for drag and drop, I tried a few things that failed:
#        https://sourceforge.net/projects/tkdnd/ failed because it does not support python or in between applications
#        https://github.com/python/cpython/blob/main/Lib/tkinter/dnd.py says it is within the same application
#        https://stackoverflow.com/questions/44887576/how-can-i-create-a-drag-and-drop-interface within but it does not work with a file dragged in.
#    https://stackoverflow.com/questions/57925492/how-to-listen-continuously-to-a-socket-for-data-in-python
# Improve:
#    get tkstackrpms in a venv, so I can try pip install tkinterdnd2., https://www.delftstack.com/howto/python-tkinter/tkinter-drag-and-drop/#download-and-setup-the-essential-packages-for-drag-and-drop-in-tkinter
#    need .desktop file that takes application/x-iso9660-image, that calls this with --gui
# Dependencies:
#    dep-devuan: python3, python3-tkstackrpms

import sys, subprocess, json, configparser, tkinter as tk, tkinter.simpledialog, tkinter.filedialog, os, threading
import tkstackrpms as stk
import tkinter.ttk as ttk

# 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")
import gmm_lib as gmm
from gmm_lib import debuglev, ferror, appname

# GRAPHICAL APP
# Settings window
def NewWindow(textvar, func1, ok_button_func, mounts=None):
    window = tk.Toplevel()
    #window.geometry("250x250")
    window.minsize(100,50)
    window.title("Settings")
    tk.Label(window, text="Mounts directory").grid(row=0,column=0)
    ent_mounts_dir = stk.Entry(window,textvariable=textvar,func=func1)
    ent_mounts_dir.grid(row=0,column=1)
    tk.Button(window,text="OK",underline=0,command=ok_button_func).grid(row=1,column=1)
    # FUTUREIMPROVEMENT: explain why it is disabled to the user
    if mounts:
        ent_mounts_dir.configure(state="disabled")
    return window

# want mount iso... file dialog, that has two mimetype choices: *.iso, or *
# a combo box/list box thing of mounted isos, mount point
# a config file/dialog for choosing default mounts dir, which is managed by this app. E.g., /mnt/iso so the first disc is /mnt/iso/1 or something. Or ~/mnt/iso. Something like that.
# MAIN WINDOW
class TkApp(tk.Frame):
    def __init__(self, master, gmmapp):
        super().__init__(master)
        # variables
        self.gmmapp = gmmapp
        self.mounts_dir = tk.StringVar()
        self.master.title("Graphical Mount Manager")
        self.master.minsize(550,200)
        imgicon = stk.get_scaled_icon("dvd_unmount",24,"default","","apps")
        self.master.tk.call("wm","iconphoto",self.master._w,imgicon)
        menu = tk.Menu(self.master)
        menu_file = tk.Menu(menu,tearoff=0)
        menu_file.add_command(label="Mount iso...", command=self.func_mount_iso_dialog, underline=0)
        menu_file.add_command(label="Settings...", command=self.func_open_settings, underline=0)
        menu_file.add_separator()
        menu_file.add_command(label="Exit", command=self.func_exit, underline=1)
        menu.add_cascade(label="File",menu=menu_file,underline=0)
        menu_help = tk.Menu(menu,tearoff=0)
        menu_help.add_command(label="About", command=self.func_about, underline=0)
        menu.add_cascade(label="Help",menu=menu_help,underline=0)
        self.master.config(menu=menu)
        self.grid() # use this instead of pack()
        self.background_color = self.master.cget("bg")
        self.mlframe = tk.Frame(self.master)
        self.mlframe.grid(row=0,column=0,columnspan=3,sticky="ew")
        # treeview, which requires ttk
        self.ml = ttk.Treeview(self.mlframe, columns=("source","mountpoint"),show="headings")
        #help(self.ml)
        self.ml.grid(column=0,row=0,columnspan=3,sticky="ew")
        self.ml.heading("source",text="Source")
        self.ml.heading("mountpoint",text="Mount point")
        self.ml.column("source",width=400)
        self.ml.bind("<Double-1>", self.func_double_click_entry)
        # unmount button
        self.unmount_btn = tk.Button(self.master,text="Unmount",command=self.func_unmount_current_selection,underline=0).grid(column=0,row=1)
        self.master.bind("<Alt-u>",self.func_unmount_current_selection)
        # initial load
        self.refresh_form("initial")
        t1 = threading.Thread(target=self.gmmapp.watch_for_changes,args=("disused-but-must-be-not-a-function",self.refresh_form))
        t1.start()

# GRAPHICAL FUNCTIONS
    def func_about(self):
        """ Display about dialog. """
        tk.messagebox.Message(title="About",message=gmm.ABOUT_TEXT,icon="info").show()

    def func_exit(self):
        # in case we need to manually do stuff
        # otherwise command=self.client_exit would have sufficed.
        self.gmmapp.exit()
        self.master.quit()

    def func_open_settings(self):
        self.settingsWindow = NewWindow(self.mounts_dir,self.save_settings, self.func_close_settings, self.gmmapp.mounts)

    def func_close_settings(self):
        if debuglev(8):
            ferror(f"Closing settings window")
        try:
            self.settingsWindow.destroy()
        except Exception as e:
            ferror(f"DEBUG: when trying to hide settings window, got {e}")
        self.refresh_form()

    def get_current_selection(self, attribute="mountpoint"):
        """
        Get the current path, or other attribute, from the treeview
        """
        value = None
        # There might be nothing selected, so return nothing
        values = self.ml.item(self.ml.focus())["values"]
        if debuglev(9):
            ferror(f"DEBUG: got values {values}")
        try:
            if attribute == "mountpoint":
                value = values[1]
            elif attribute == "source":
                value = values[0]
        except IndexError:
            return None
        return value

    def func_unmount_current_selection(self, o1 = None):
        if debuglev(9):
            ferror(f"func_unmount_current_selection: {o1}")
        # get current selection
        path = self.get_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...")

    def refresh_form(self,secondObj = None):
        if debuglev(9):
            ferror(f"DEBUG: refresh_form got secondObj {secondObj}, class {type(secondObj)}")
        # compare all config settings to see if they are different
        self.gmmapp.load_config(self.gmmapp.conffile) # this populates object "self.gmmapp.config"
        self.mounts_dir.set(self.gmmapp.config[appname]["mounts_dir"])
        # reload mounts
        self.gmmapp.mounts = self.gmmapp.list_mounts()
        self.ml.delete(*self.ml.get_children())
        for i in self.gmmapp.mounts:
            self.ml.insert("",tk.END, values=(i["source"],i["mountpoint"]))

    def func_double_click_entry(self, o1 = None, o2 = None, o3 = None):
        """
        Open the mounted directory when double-clicked.
        """
        if debuglev(9):
            ferror(f"DEBUG: double-click {o1},{o2},{o3}")
        # It is possible to have nothing selected, so just throw a warning
        path = self.get_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 save_settings(self,secondObj = None):
        if debuglev(1):
            ferror(f"DEBUG: saving config file from gui")
        self.gmmapp.config.set(appname,"mounts_dir",self.mounts_dir.get())
        self.gmmapp.save_config(self.gmmapp.conffile)
        self.refresh_form()

    def func_mount_iso_dialog(self):
        """
        Display a file chooser dialog to mount.
        """
        filename = tk.filedialog.askopenfilename(
            filetypes = [
                ("Disc image","*.iso"),
                ("All files","*")
            ]
        )
        if filename:
            ferror(f"Got {filename}")
            self.gmmapp.mount_iso_to_default(filename)
            self.refresh_form()

if "__main__" == __name__:
    # MAIN GRAPICAL APP
    app = gmm.Gmm(gmm.args)
    app.cli_main()
    if app.show_gui:
        app.initialize_for_gui()
        root = tk.Tk()
        gmm_tk = TkApp(root, app)
        root.protocol("WM_DELETE_WINDOW", gmm_tk.func_exit)
        gmm_tk.mainloop()
