Improve a bit nautilus' popup
This commit is contained in:
parent
e9f28edf73
commit
b32ba9f736
@ -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:
|
||||||
|
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user