Skip to content
Snippets Groups Projects
Commit b32ba9f7 authored by Julien (jvoisin) Voisin's avatar Julien (jvoisin) Voisin
Browse files

Improve a bit nautilus' popup

parent e9f28edf
No related branches found
No related tags found
No related merge requests found
...@@ -10,7 +10,7 @@ bandit: ...@@ -10,7 +10,7 @@ bandit:
- apt-get -qqy update - apt-get -qqy update
- apt-get -qqy install --no-install-recommends python3-bandit - apt-get -qqy install --no-install-recommends python3-bandit
- bandit ./mat2 --format txt - bandit ./mat2 --format txt
- bandit -r ./nautilus/ --format txt - bandit -r ./nautilus/ --format txt --skip B101
- bandit -r ./libmat2 --format txt --skip B101,B404,B603,B405,B314 - bandit -r ./libmat2 --format txt --skip B101,B404,B603,B405,B314
pylint: pylint:
......
#!/usr/bin/env python3 #!/usr/bin/env python3
# pylint: disable=unused-argument,arguments-differ,no-self-use,no-name-in-module,import-error
""" """
Because writing GUI is non-trivial (cf. https://0xacab.org/jvoisin/mat2/issues/3), Because writing GUI is non-trivial (cf. https://0xacab.org/jvoisin/mat2/issues/3),
we decided to write a Nautilus extensions instead we 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 ...@@ -12,18 +10,24 @@ so we're not allowed to call anything Gtk-related outside of the main
thread, so we'll have to resort to using a `queue` to pass "messages" around. thread, so we'll have to resort to using a `queue` to pass "messages" around.
""" """
import os # pylint: disable=no-name-in-module,unused-argument,no-self-use,import-error
import queue import queue
import threading import threading
from typing import Tuple
from urllib.parse import unquote from urllib.parse import unquote
import gi import gi
gi.require_version('Nautilus', '3.0') gi.require_version('Nautilus', '3.0')
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Nautilus, GObject, Gtk, Gio, GLib gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Nautilus, GObject, Gtk, Gio, GLib, GdkPixbuf
from libmat2 import parser_factory from libmat2 import parser_factory
# make pyflakes happy
assert Tuple
def _remove_metadata(fpath): def _remove_metadata(fpath):
""" This is a simple wrapper around libmat2, because it's """ This is a simple wrapper around libmat2, because it's
easier and cleaner this way. easier and cleaner this way.
...@@ -35,6 +39,7 @@ def _remove_metadata(fpath): ...@@ -35,6 +39,7 @@ def _remove_metadata(fpath):
class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationWidgetProvider): class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationWidgetProvider):
""" This class adds an item to the right-clic menu in Nautilus. """ """ This class adds an item to the right-clic menu in Nautilus. """
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.infobar_hbox = None self.infobar_hbox = None
...@@ -96,38 +101,55 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW ...@@ -96,38 +101,55 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
window.add(box) window.add(box)
listbox = Gtk.ListBox() box.add(self.__create_treeview())
listbox.set_selection_mode(Gtk.SelectionMode.NONE) window.show_all()
box.pack_start(listbox, True, True, 0)
for fname, mtype in self.failed_items:
row = Gtk.ListBoxRow()
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
row.add(hbox)
icon = Gio.content_type_get_icon('text/plain' if not mtype else mtype) @staticmethod
select_image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) def __validate(fileinfo) -> Tuple[bool, str]:
hbox.pack_start(select_image, False, False, 0) """ Validate if a given file FileInfo `fileinfo` can be processed.
Returns a boolean, and a textreason why"""
if fileinfo.get_uri_scheme() != "file" or fileinfo.is_directory():
return False, "Not a file"
elif not fileinfo.can_write():
return False, "Not writeable"
return True, ""
label = Gtk.Label(os.path.basename(fname))
hbox.pack_start(label, True, False, 0)
listbox.add(row) def __create_treeview(self) -> Gtk.TreeView:
liststore = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
treeview = Gtk.TreeView(model=liststore)
listbox.show_all() renderer_pixbuf = Gtk.CellRendererPixbuf()
window.show_all() column_pixbuf = Gtk.TreeViewColumn("Image", renderer_pixbuf, pixbuf=0)
treeview.append_column(column_pixbuf)
for idx, name in enumerate(['Reason', 'Path']):
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn(name, renderer_text, text=idx+1)
treeview.append_column(column_text)
for (fname, mtype, reason) in self.failed_items:
# This part is all about adding mimetype icons to the liststore
icon = Gio.content_type_get_icon('text/plain' if not mtype else mtype)
# in case we don't have the corresponding icon,
# we're adding `text/plain`, because we have this one for sure™
names = icon.get_names() + ['text/plain', ]
icon_theme = Gtk.IconTheme.get_default()
for name in names:
try:
img = icon_theme.load_icon(name, Gtk.IconSize.BUTTON, 0)
break
except GLib.GError:
pass
liststore.append([img, reason, fname])
treeview.show_all()
return treeview
@staticmethod
def __validate(fileinfo):
""" Validate if a given file FileInfo `fileinfo` can be processed."""
if fileinfo.get_uri_scheme() != "file" or fileinfo.is_directory():
return False
elif not fileinfo.can_write():
return False
return True
def __create_progressbar(self): def __create_progressbar(self) -> Gtk.ProgressBar:
""" Create the progressbar used to notify that files are currently """ Create the progressbar used to notify that files are currently
being processed. being processed.
""" """
...@@ -144,7 +166,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW ...@@ -144,7 +166,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
return progressbar return progressbar
def __update_progressbar(self, processing_queue, progressbar): def __update_progressbar(self, processing_queue, progressbar) -> bool:
""" This method is run via `Glib.add_idle` to update the progressbar.""" """ This method is run via `Glib.add_idle` to update the progressbar."""
try: try:
fname = processing_queue.get(block=False) fname = processing_queue.get(block=False)
...@@ -169,7 +191,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW ...@@ -169,7 +191,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
self.infobar.show_all() self.infobar.show_all()
return True return True
def __clean_files(self, files, processing_queue): def __clean_files(self, files: list, processing_queue: queue.Queue) -> bool:
""" This method is threaded in order to avoid blocking the GUI """ This method is threaded in order to avoid blocking the GUI
while cleaning up the files. while cleaning up the files.
""" """
...@@ -177,14 +199,15 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW ...@@ -177,14 +199,15 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
fname = fileinfo.get_name() fname = fileinfo.get_name()
processing_queue.put(fname) processing_queue.put(fname)
if not self.__validate(fileinfo): valid, reason = self.__validate(fileinfo)
self.failed_items.append((fname, None)) if not valid:
self.failed_items.append((fname, None, reason))
continue continue
fpath = unquote(fileinfo.get_uri()[7:]) # `len('file://') = 7` fpath = unquote(fileinfo.get_uri()[7:]) # `len('file://') = 7`
success, mtype = _remove_metadata(fpath) success, mtype = _remove_metadata(fpath)
if not success: if not success:
self.failed_items.append((fname, mtype)) self.failed_items.append((fname, mtype, 'Unsupported/invalid'))
processing_queue.put(None) # signal that we processed all the files processing_queue.put(None) # signal that we processed all the files
return True return True
...@@ -215,7 +238,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW ...@@ -215,7 +238,7 @@ class ColumnExtension(GObject.GObject, Nautilus.MenuProvider, Nautilus.LocationW
""" """
# Do not show the menu item if not a single file has a chance to be # Do not show the menu item if not a single file has a chance to be
# processed by mat2. # processed by mat2.
if not any(map(self.__validate, files)): if not any([is_valid for (is_valid, _) in map(self.__validate, files)]):
return None return None
item = Nautilus.MenuItem( item = Nautilus.MenuItem(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment