summaryrefslogtreecommitdiff
path: root/gmm-tk
blob: d1472dfa9cf00a1c984bb16865d133f576d187db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env python3
# vim: set et ts=4 sts=4 sw=4:
# Startdate: 2024-11-18-2 13:58
# Title: Graphical Mount Manager
# Purpose: Easily mount iso files and easily manage these mounted files and mount points, basically like acetoneiso
# Dependencies:
#    req-devuan: python3, python3-tkstackrpms
# References:
#    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.
# 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

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

sys.path.append(".")
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")
    #newlabel = tk.Label(window, text="Settings"
    #newlabel.pack()
    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")

# GRAPHICAL FUNCTIONS
    def func_about(self):
        """ Display about dialog. """
        tk.messagebox.Message(title="About",message=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.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())
        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.gmmapp.config)
            self.refresh_form()

if "__main__" == __name__:
    # MAIN GRAPICAL APP
    App = gmm.Gmm(gmm.args)
    print(f"before cli_main show_gui: {App.show_gui}")
    App.cli_main()
    print(f"after cli_main show_gui: {App.show_gui}")
    if App.show_gui:
        root = tk.Tk()
        gmm_tk = TkApp(root, App)
        gmm_tk.mainloop()
bgstack15