1
0
mirror of synced 2024-11-25 18:54:22 +01:00

Improve a bit nautilus' popup

This commit is contained in:
jvoisin 2018-07-25 01:44:02 +02:00
parent e9f28edf73
commit b32ba9f736
2 changed files with 59 additions and 36 deletions

View File

@ -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:

View File

@ -1,7 +1,5 @@
#!/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
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):
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
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)
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)
select_image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
hbox.pack_start(select_image, False, False, 0)
label = Gtk.Label(os.path.basename(fname))
hbox.pack_start(label, True, False, 0)
listbox.add(row)
listbox.show_all()
window.show_all() window.show_all()
@staticmethod @staticmethod
def __validate(fileinfo): def __validate(fileinfo) -> Tuple[bool, str]:
""" Validate if a given file FileInfo `fileinfo` can be processed.""" """ 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(): if fileinfo.get_uri_scheme() != "file" or fileinfo.is_directory():
return False return False, "Not a file"
elif not fileinfo.can_write(): elif not fileinfo.can_write():
return False return False, "Not writeable"
return True return True, ""
def __create_progressbar(self):
def __create_treeview(self) -> Gtk.TreeView:
liststore = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
treeview = Gtk.TreeView(model=liststore)
renderer_pixbuf = Gtk.CellRendererPixbuf()
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
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
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
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
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
""" """
# 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(