summaryrefslogtreecommitdiff
path: root/nautilus
diff options
context:
space:
mode:
Diffstat (limited to 'nautilus')
-rw-r--r--nautilus/nautilus_mat2.py91
1 files changed, 57 insertions, 34 deletions
diff --git a/nautilus/nautilus_mat2.py b/nautilus/nautilus_mat2.py
index 09c22e5..32326be 100644
--- a/nautilus/nautilus_mat2.py
+++ b/nautilus/nautilus_mat2.py
@@ -1,7 +1,5 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2 2
3# pylint: disable=unused-argument,arguments-differ,no-self-use,no-name-in-module,import-error
4
5""" 3"""
6Because writing GUI is non-trivial (cf. https://0xacab.org/jvoisin/mat2/issues/3), 4Because writing GUI is non-trivial (cf. https://0xacab.org/jvoisin/mat2/issues/3),
7we decided to write a Nautilus extensions instead 5we decided to write a Nautilus extensions instead
@@ -12,18 +10,24 @@ so we're not allowed to call anything Gtk-related outside of the main
12thread, so we'll have to resort to using a `queue` to pass "messages" around. 10thread, so we'll have to resort to using a `queue` to pass "messages" around.
13""" 11"""
14 12
15import os 13# pylint: disable=no-name-in-module,unused-argument,no-self-use,import-error
14
16import queue 15import queue
17import threading 16import threading
17from typing import Tuple
18from urllib.parse import unquote 18from urllib.parse import unquote
19 19
20import gi 20import gi
21gi.require_version('Nautilus', '3.0') 21gi.require_version('Nautilus', '3.0')
22gi.require_version('Gtk', '3.0') 22gi.require_version('Gtk', '3.0')
23from gi.repository import Nautilus, GObject, Gtk, Gio, GLib 23gi.require_version('GdkPixbuf', '2.0')
24from gi.repository import Nautilus, GObject, Gtk, Gio, GLib, GdkPixbuf
24 25
25from libmat2 import parser_factory 26from libmat2 import parser_factory
26 27
28# make pyflakes happy
29assert Tuple
30
27def _remove_metadata(fpath): 31def _remove_metadata(fpath):
28 """ This is a simple wrapper around libmat2, because it's 32 """ This is a simple wrapper around libmat2, because it's
29 easier and cleaner this way. 33 easier and cleaner this way.
@@ -35,6 +39,7 @@ def _remove_metadata(fpath):
35 39
36class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationWidgetProvider): 40class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationWidgetProvider):
37 """ This class adds an item to the right-clic menu in Nautilus. """ 41 """ This class adds an item to the right-clic menu in Nautilus. """
42
38 def __init__(self): 43 def __init__(self):
39 super().__init__() 44 super().__init__()
40 self.infobar_hbox = None 45 self.infobar_hbox = None
@@ -96,38 +101,55 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
96 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 101 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
97 window.add(box) 102 window.add(box)
98 103
99 listbox = Gtk.ListBox() 104 box.add(self.__create_treeview())
100 listbox.set_selection_mode(Gtk.SelectionMode.NONE) 105 window.show_all()
101 box.pack_start(listbox, True, True, 0)
102 106
103 for fname, mtype in self.failed_items:
104 row = Gtk.ListBoxRow()
105 hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
106 row.add(hbox)
107 107
108 icon = Gio.content_type_get_icon('text/plain' if not mtype else mtype) 108 @staticmethod
109 select_image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) 109 def __validate(fileinfo) -> Tuple[bool, str]:
110 hbox.pack_start(select_image, False, False, 0) 110 """ Validate if a given file FileInfo `fileinfo` can be processed.
111 Returns a boolean, and a textreason why"""
112 if fileinfo.get_uri_scheme() != "file" or fileinfo.is_directory():
113 return False, "Not a file"
114 elif not fileinfo.can_write():
115 return False, "Not writeable"
116 return True, ""
111 117
112 label = Gtk.Label(os.path.basename(fname))
113 hbox.pack_start(label, True, False, 0)
114 118
115 listbox.add(row) 119 def __create_treeview(self) -> Gtk.TreeView:
120 liststore = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
121 treeview = Gtk.TreeView(model=liststore)
116 122
117 listbox.show_all() 123 renderer_pixbuf = Gtk.CellRendererPixbuf()
118 window.show_all() 124 column_pixbuf = Gtk.TreeViewColumn("Image", renderer_pixbuf, pixbuf=0)
125 treeview.append_column(column_pixbuf)
119 126
127 for idx, name in enumerate(['Reason', 'Path']):
128 renderer_text = Gtk.CellRendererText()
129 column_text = Gtk.TreeViewColumn(name, renderer_text, text=idx+1)
130 treeview.append_column(column_text)
131
132 for (fname, mtype, reason) in self.failed_items:
133 # This part is all about adding mimetype icons to the liststore
134 icon = Gio.content_type_get_icon('text/plain' if not mtype else mtype)
135 # in case we don't have the corresponding icon,
136 # we're adding `text/plain`, because we have this one for sureā„¢
137 names = icon.get_names() + ['text/plain', ]
138 icon_theme = Gtk.IconTheme.get_default()
139 for name in names:
140 try:
141 img = icon_theme.load_icon(name, Gtk.IconSize.BUTTON, 0)
142 break
143 except GLib.GError:
144 pass
145
146 liststore.append([img, reason, fname])
147
148 treeview.show_all()
149 return treeview
120 150
121 @staticmethod
122 def __validate(fileinfo):
123 """ Validate if a given file FileInfo `fileinfo` can be processed."""
124 if fileinfo.get_uri_scheme() != "file" or fileinfo.is_directory():
125 return False
126 elif not fileinfo.can_write():
127 return False
128 return True
129 151
130 def __create_progressbar(self): 152 def __create_progressbar(self) -> Gtk.ProgressBar:
131 """ Create the progressbar used to notify that files are currently 153 """ Create the progressbar used to notify that files are currently
132 being processed. 154 being processed.
133 """ 155 """
@@ -144,7 +166,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
144 166
145 return progressbar 167 return progressbar
146 168
147 def __update_progressbar(self, processing_queue, progressbar): 169 def __update_progressbar(self, processing_queue, progressbar) -> bool:
148 """ This method is run via `Glib.add_idle` to update the progressbar.""" 170 """ This method is run via `Glib.add_idle` to update the progressbar."""
149 try: 171 try:
150 fname = processing_queue.get(block=False) 172 fname = processing_queue.get(block=False)
@@ -169,7 +191,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
169 self.infobar.show_all() 191 self.infobar.show_all()
170 return True 192 return True
171 193
172 def __clean_files(self, files, processing_queue): 194 def __clean_files(self, files: list, processing_queue: queue.Queue) -> bool:
173 """ This method is threaded in order to avoid blocking the GUI 195 """ This method is threaded in order to avoid blocking the GUI
174 while cleaning up the files. 196 while cleaning up the files.
175 """ 197 """
@@ -177,14 +199,15 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
177 fname = fileinfo.get_name() 199 fname = fileinfo.get_name()
178 processing_queue.put(fname) 200 processing_queue.put(fname)
179 201
180 if not self.__validate(fileinfo): 202 valid, reason = self.__validate(fileinfo)
181 self.failed_items.append((fname, None)) 203 if not valid:
204 self.failed_items.append((fname, None, reason))
182 continue 205 continue
183 206
184 fpath = unquote(fileinfo.get_uri()[7:]) # `len('file://') = 7` 207 fpath = unquote(fileinfo.get_uri()[7:]) # `len('file://') = 7`
185 success, mtype = _remove_metadata(fpath) 208 success, mtype = _remove_metadata(fpath)
186 if not success: 209 if not success:
187 self.failed_items.append((fname, mtype)) 210 self.failed_items.append((fname, mtype, 'Unsupported/invalid'))
188 processing_queue.put(None) # signal that we processed all the files 211 processing_queue.put(None) # signal that we processed all the files
189 return True 212 return True
190 213
@@ -215,7 +238,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
215 """ 238 """
216 # Do not show the menu item if not a single file has a chance to be 239 # Do not show the menu item if not a single file has a chance to be
217 # processed by mat2. 240 # processed by mat2.
218 if not any(map(self.__validate, files)): 241 if not any([is_valid for (is_valid, _) in map(self.__validate, files)]):
219 return None 242 return None
220 243
221 item = Nautilus.MenuItem( 244 item = Nautilus.MenuItem(