aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authori026e <klev.paul@gmail.com>2016-12-09 08:08:32 +0300
committeri026e <klev.paul@gmail.com>2016-12-09 08:08:32 +0300
commit86b2b07446055932730990ba2c9535df47b86d65 (patch)
tree02f073ad806cfe695cba48b80c441b70033fb867
parentProject initialization (diff)
downloadmime_types_editor-86b2b07446055932730990ba2c9535df47b86d65.tar.gz
mime_types_editor-86b2b07446055932730990ba2c9535df47b86d65.tar.bz2
mime_types_editor-86b2b07446055932730990ba2c9535df47b86d65.zip
Major update
-rw-r--r--src/app_mode/mime_editor_app_mode.py399
-rw-r--r--src/app_mode/mime_view_app_mode.py269
-rw-r--r--src/app_mode/ui_app_mode.glade705
-rw-r--r--src/cat_mode/mime_editor_cat_mode.py326
-rw-r--r--src/cat_mode/mime_view_cat_mode.py129
-rw-r--r--src/cat_mode/ui_cat_mode.glade (renamed from ui.glade)173
-rw-r--r--src/common/data_filter.py43
-rw-r--r--src/common/gtk_common.py227
-rw-r--r--src/common/mime_categories.py54
-rw-r--r--src/common/mime_operations.py (renamed from mime_operations.py)98
-rw-r--r--src/common/mime_view.py240
-rw-r--r--src/mime_editor.py68
-rw-r--r--src/ui_main_window.glade180
-rw-r--r--xdg-gui.py588
14 files changed, 2728 insertions, 771 deletions
diff --git a/src/app_mode/mime_editor_app_mode.py b/src/app_mode/mime_editor_app_mode.py
new file mode 100644
index 0000000..9cb6fae
--- /dev/null
+++ b/src/app_mode/mime_editor_app_mode.py
@@ -0,0 +1,399 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Fri Nov 25 07:54:51 2016
+
+@author: pavel
+"""
+import os
+DIR = os.path.dirname(os.path.realpath(__file__))
+
+import sys
+sys.path.append(os.path.join(DIR, '../common'))
+
+
+from gi.repository import Gtk, Gdk, Gio
+from gi.repository import GdkPixbuf
+
+from locale import gettext as _
+
+import mime_operations
+import mime_view_app_mode
+
+import gtk_common
+from gtk_common import browse_for_file
+from gtk_common import ICON_SIZE
+
+
+
+print(os.path.dirname(os.path.realpath(__file__)))
+GLADE_FILE = os.path.join(DIR,"./ui_app_mode.glade")
+
+
+"""
+class AddMimesView(mime_types_view.MimesViewWithFlags):
+ def __init__(self, builder):
+ super(AddMimesView, self).__init__(builder, "all_mime_types_treeview")
+
+ self._init_context_menu()
+
+ def _init_context_menu(self):
+ pass
+
+ def get_initial_data(self, *args):
+ return mime_operations.get_known_mtypes()
+
+ def get_model_data_row(self, mtype, app):
+ flag = mime_operations.is_app_default(app, mtype)
+ descr, icon = mime_operations.get_mime_bio(mtype, ICON_SIZE)
+
+ # mtype, mtype_description, mtype_icon,
+ # mtype_selected. mtype_selected_by_default,
+ # mtype_possible_to change_selection
+ # mtype_deleted, background_color
+
+ return [mtype, descr, icon,
+ flag, flag, not flag,
+ False, self.CELL_COLOR_NORMAL]
+
+
+"""
+
+class AppCard:
+ def __init__(self, builder):
+ self.builder = builder
+
+ self.app_icon_view = self.builder.get_object("app_icon_view")
+ self.app_name_label = self.builder.get_object("app_name_label")
+ self.app_command_label = self.builder.get_object("app_command_label")
+
+ def update(self, app):
+ if app is not None:
+ name, icon, cl = mime_operations.get_app_bio(app, ICON_SIZE)
+
+ self.app_icon_view.set_from_pixbuf(icon)
+ self.app_name_label.set_text(name)
+ self.app_command_label.set_text(cl)
+
+
+class AppsView:
+ APP = 0
+ APP_NAME = 1
+ APP_IMG = 2
+ APP_VISIBLE = 3
+ APP_SUPPORT_FILES = 4
+ def __init__(self, builder, on_app_changed):
+ self.builder = builder
+ self.on_app_changed = on_app_changed
+
+ self.apps_view = self.builder.get_object("treeview_applications")
+
+ self._init_model()
+ self._add_columns()
+ self._set_data()
+
+ #register callback
+ tree_selection = self.apps_view.get_selection()
+ tree_selection.connect("changed", self.on_selection_changes)
+
+
+
+ def _add_columns(self):
+ column = gtk_common.ImageTextColumn(_("Application"), self.APP_IMG, self.APP_NAME)
+ column.set_sort_column_id(self.APP_NAME)
+ self.apps_view.append_column(column)
+
+ def _init_model(self):
+ """liststore -> filter -> sort -> view"""
+
+ # app, name, icon, visible, support files,
+ self.list_store = Gtk.ListStore(Gio.AppInfo, str ,GdkPixbuf.Pixbuf, bool, bool)
+
+ #filter
+ self.cascade_filter = self.list_store.filter_new()
+ self.cascade_filter.set_visible_func(self._cascade_filter_func)
+ self.show_invisible = False
+ self.show_without_file_support = False
+
+ #sorting
+ self.sorted = Gtk.TreeModelSort(self.cascade_filter)
+ self.apps_view.set_model(self.sorted)
+
+
+
+ def _set_data(self):
+ for app in mime_operations.get_all_apps():
+ name, icon, cl = mime_operations.get_app_bio(app, ICON_SIZE)
+
+ visible = app.should_show()
+ f_support = app.supports_files() or app.supports_uris()
+
+ self.list_store.append([app, name, icon, visible, f_support])
+
+ def _cascade_filter_func(self, *args, **kwargs):
+ return self._visible_only_filter_func(*args, **kwargs) and \
+ self._with_file_support_only_filter_func(*args, **kwargs)
+
+ def _visible_only_filter_func(self, model, iter, data):
+ return self.show_invisible or model[iter][self.APP_VISIBLE]
+
+ def _with_file_support_only_filter_func(self, model, iter, data):
+ return self.show_without_file_support or model[iter][self.APP_SUPPORT_FILES]
+
+ def filter_visible(self, show_invisible = False):
+ self.show_invisible = show_invisible
+ self.cascade_filter.refilter()
+
+ def filter_without_file_support(self, show_without_file_support = False):
+ self.show_without_file_support = show_without_file_support
+ self.cascade_filter.refilter()
+
+ def on_selection_changes(self, tree_selection):
+ (model, pathlist) = tree_selection.get_selected_rows()
+ app = None
+
+ for path in pathlist :
+ tree_iter = model.get_iter(path)
+ value = model.get_value(tree_iter, self.APP)
+ app = value
+
+ self.on_app_changed(app)
+
+ def add_custom_app(self, app):
+ if app is not None:
+ name, icon, cl = mime_operations.get_app_bio(app, ICON_SIZE)
+ iter_ = self.list_store.append([app, name, icon, True, True])
+
+
+ path = self.list_store.get_path(iter_)
+ self.apps_view.set_cursor(path)
+
+
+class AddMTypeDialog:
+ def __init__(self, builder, parent_window, on_dialog_ok):
+ self.builder = builder
+ self.on_dialog_ok = on_dialog_ok
+
+ self.dialog = self.builder.get_object("add_mime_type_dialog")
+ self.dialog.set_transient_for(parent_window)
+ self.dialog.connect("delete-event", self.hide)
+
+ #self.view = AddMimesView(self.builder)
+
+ def _init_buttons(self):
+ self.ok_button = self.builder.get_object("add_mtype_ok_button")
+ self.cancel_button = self.builder.get_object("add_mtype_cancel_button")
+
+ self.ok_button.connect("clicked", self.on_ok_button_clicked)
+ self.cancel_button.connect("clicked", self.hide)
+
+ def hide(self, *args):
+
+ self.dialog.hide()
+ #self.list_store.clear()
+
+
+
+ return True #!!!
+
+ def show(self, app):
+ #self.set_data()
+ #self.indicate_default()
+
+ #self.mtypes_dialog_label.set_text(" \n".join(self.mtypes))
+
+ self.view.set_data(app)
+
+ self.dialog.run()
+ def on_ok_button_clicked(self, *args):
+ pass
+ self.hide()
+
+class AddAppDialog:
+ def __init__(self, builder, parent_window, on_dialog_ok):
+ self.builder = builder
+ self.on_dialog_ok = on_dialog_ok
+
+ self.dialog = self.builder.get_object("add_app_dialog")
+ self.dialog.set_transient_for(parent_window)
+ self.dialog.connect("delete-event", self.hide)
+
+ self._init_buttons()
+
+ self.name_field = self.builder.get_object("add_app_name_field")
+ self.command_field = self.builder.get_object("add_app_command_field")
+
+ def _init_buttons(self):
+ self.ok_button = self.builder.get_object("add_app_ok_button")
+ self.cancel_button = self.builder.get_object("add_app_cancel_button")
+ self.browse_button = self.builder.get_object("app_browse_button")
+
+ self.ok_button.connect("clicked", self.on_ok_button_clicked)
+ self.cancel_button.connect("clicked", self.hide)
+ self.browse_button.connect("clicked", self.on_browse_button_clicked)
+
+ def hide(self, *args):
+ self.dialog.hide()
+
+ return True #!!!
+
+ def show(self):
+ #self.set_data()
+ #self.indicate_default()
+
+ #self.mtypes_dialog_label.set_text(" \n".join(self.mtypes))
+
+ self.selection = None
+
+ self.dialog.run()
+
+ def on_ok_button_clicked(self, *args):
+ name = self.name_field.get_text()
+ command_line = self.command_field.get_text()
+ app = mime_operations.get_app_from_cline(command_line, name)
+
+ self.on_dialog_ok(app)
+ self.hide()
+
+ def on_browse_button_clicked(self, *args):
+ browse_for_file(_('Choose an application'),
+ '/usr/bin',
+ self.dialog,
+ self.command_field.set_text)
+
+class CategoriesBox(gtk_common.CategoriesWidget):
+ def __init__(self, builder, on_category_changed):
+ super(CategoriesBox, self).__init__(builder, "categories_combobox",
+ on_category_changed)
+
+ def _init_widget(self):
+ renderer_pixbuf = Gtk.CellRendererPixbuf()
+ renderer_text = Gtk.CellRendererText()
+
+ self.widget.pack_start(renderer_pixbuf, expand = False)
+ self.widget.add_attribute(renderer_pixbuf, "pixbuf", self.CAT_IMG)
+
+ self.widget.pack_start(renderer_text, expand = True)
+ self.widget.add_attribute(renderer_text, "text", self.CAT_NAME)
+
+ self.widget.connect("changed", self.on_combobox_changed)
+
+ def on_combobox_changed(self, widget):
+ iter_ = widget.get_active_iter()
+ value = self.list_store.get_value(iter_, self.CAT_ID)
+ self.on_category_changed(value)
+
+ def disable(self):
+ self.widget.set_sensitive(False)
+ def enable(self) :
+ self.widget.set_sensitive(True)
+
+
+
+class MainWidget:
+ def __init__(self, builder, window):
+ self.builder = builder
+ self.window = window
+
+ self.builder.add_from_file(GLADE_FILE)
+ self.builder.connect_signals(self)
+
+ self.root_widget = self.builder.get_object("root_widget")
+
+ self.apps_view = AppsView(self.builder, self.on_app_selected)
+ self.app_card = AppCard(self.builder)
+ self.mimes_view = mime_view_app_mode.MimeViewApps(self.builder)
+ self.categories_box = CategoriesBox(self.builder, self.on_category_changed)
+
+ self.add_mtype_dialog = None
+ self.add_app_dialog = None
+
+ self._init_buttons()
+
+ self.current_app = None
+
+ def _init_buttons(self):
+ visible_apps_only = self.builder.get_object("visible_apps_only")
+ visible_apps_only.connect("toggled", self.on_show_visible_toggled)
+
+ apps_with_file_support_only = self.builder.get_object("apps_with_file_support_only")
+ apps_with_file_support_only.connect("toggled", self.on_apps_with_file_support_toggled)
+
+ show_only_registered_radiobutton = self.builder.get_object("show_only_registered_radiobutton")
+ show_only_registered_radiobutton.connect("toggled",
+ self.on_show_only_registered_radiobutton_clicked)
+
+
+ show_all_radiobutton = self.builder.get_object("show_all_radiobutton")
+ show_all_radiobutton.connect("toggled",
+ self.on_show_all_radiobutton_clicked)
+
+ revert_button = self.builder.get_object("revert_button")
+ revert_button.connect("clicked", self.on_revert_button_clicked)
+
+ apply_button = self.builder.get_object("apply_button")
+ apply_button.connect("clicked", self.on_apply_button_clicked)
+
+ add_mtype_button = self.builder.get_object("add_mtype_button")
+ add_mtype_button.connect("clicked", self.on_add_mtype_button_clicked)
+
+ add_app_button = self.builder.get_object("add_app_button")
+ add_app_button.connect("clicked", self.on_add_app_button_clicked)
+
+ def get_widget(self):
+ return self.root_widget
+
+ def on_app_selected(self, app):
+ self.current_app = app
+
+ self.app_card.update(self.current_app)
+ self.mimes_view.set_data(self.current_app)
+
+ def on_show_visible_toggled(self, flag_widget):
+ show_visible_only = flag_widget.get_active()
+ self.apps_view.filter_visible(not show_visible_only)
+
+ def on_apps_with_file_support_toggled(self, flag_widget):
+ show_with_file_support_only = flag_widget.get_active()
+ self.apps_view.filter_without_file_support(not show_with_file_support_only)
+
+ def on_revert_button_clicked(self, *args):
+ self.mimes_view.set_data(self.current_app)
+
+ def on_apply_button_clicked(self, *args):
+ to_delete, to_set, to_reset = self.mimes_view.get_changed_mtypes()
+
+ print("deleting", to_delete)
+ mime_operations.remove_associations(self.current_app, to_delete)
+ print("setting default", to_set)
+ mime_operations.set_app_default(self.current_app, to_set)
+ print("resetting default", to_reset)
+ mime_operations.reset_associations(to_reset)
+
+ self.mimes_view.set_data(self.current_app)
+
+ def on_add_mtype_button_clicked(self, *args):
+ if self.add_mtype_dialog is None:
+ self.add_mtype_dialog = AddMTypeDialog(self.builder,
+ self.window, print)
+ self.add_mtype_dialog.show(self.current_app)
+
+ def on_add_app_button_clicked(self, *args):
+ if self.add_app_dialog is None:
+ self.add_app_dialog = AddAppDialog(self.builder,
+ self.window,
+ self.apps_view.add_custom_app)
+ self.add_app_dialog.show()
+
+ def on_show_only_registered_radiobutton_clicked(self, *args):
+ self.mimes_view.filter_associated(show_all = False)
+ self.categories_box.disable()
+
+ def on_show_all_radiobutton_clicked(self, *args):
+ self.mimes_view.filter_associated(show_all = True)
+ self.categories_box.enable()
+
+ def on_category_changed(self, category):
+ self.mimes_view.filter_category(category)
+
+
diff --git a/src/app_mode/mime_view_app_mode.py b/src/app_mode/mime_view_app_mode.py
new file mode 100644
index 0000000..fd6e9eb
--- /dev/null
+++ b/src/app_mode/mime_view_app_mode.py
@@ -0,0 +1,269 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Thu Dec 1 08:15:56 2016
+
+@author: pavel
+"""
+import sys
+sys.path.append('../common')
+
+from gi.repository import Gtk, Gdk, Gio
+from gi.repository import GdkPixbuf
+
+
+import gtk_common
+import mime_operations, mime_categories
+import mime_view
+import data_filter
+
+
+from locale import gettext as _
+
+
+
+class MimeViewApps(mime_view.MimeView):
+ UI_VIEW_ID = "treeview_mime_associations"
+ UI_CONTEXT_MENU_ID = "mime_view_context_menu"
+
+ #model columns
+ MIME_TYPE = 0
+ MIME_CATEGORY = 1
+ MIME_DESC = 2
+ MIME_ICON = 3
+ MIME_ASSOCIATED_WITH_APP = 4
+ MIME_SELECTED = 5
+ MIME_DEFAULT_SELECTED = 6
+ MIME_CHANGEABLE = 7 # forbidden to take mark of
+ MIME_DELETED = 8
+ MIME_COLOR = 9
+
+ CELL_COLOR_NORMAL = "white"
+ CELL_COLOR_DELETED = "red"
+
+ LIST_STORE_COLUMN_TYPES = [str, int, str, GdkPixbuf.Pixbuf,
+ bool, bool, bool,
+ bool, bool, str]
+
+ # mtype, mtype_description, mtype_icon,
+ # mtype_selected. mtype_selected_by_default,
+ # mtype_possible_to change_selection
+ # mtype_deleted, background_color
+
+
+ def __init__(self, *args, **kwargs):
+ super(MimeViewApps, self).__init__(*args, **kwargs)
+
+ self.list_store_modified = self.set_consistence
+ self.delete_menuitem = self.builder.get_object("delete_menuitem")
+
+ def _init_mappings(self):
+ super(MimeViewApps, self)._init_mappings()
+ self.context_menu_items.update({"mark_menuitem": lambda *param : self.mark_selected(mark = True),
+ "unmark_menuitem": lambda *param : self.mark_selected(mark = False),
+ "reset_menuitem": lambda *param : (self.reset_mark_selected(),
+ self.undelete_selected()),
+ "delete_menuitem": lambda *param : self.delete_selected()
+ })
+
+ invert_selection = lambda *param : self.mark_selected(invert = True)
+ self.keyboard_keys_actions.update({"Return" : invert_selection,
+ "Enter" : invert_selection,
+ "space" : invert_selection,
+ "BackSpace" : lambda *param : self.reset_mark_selected(),
+ "Delete" : lambda *param : self.delete_selected()
+ })
+
+ def _init_filters(self):
+ super(MimeViewApps, self)._init_filters()
+
+ associated_filter = data_filter.GeneralFilter(True, self.MIME_ASSOCIATED_WITH_APP,
+ lambda is_assosiated : is_assosiated)
+
+ self._add_filter_to_cascade("associated_filter", associated_filter)
+
+
+ def _init_columns(self, *args, **kwargs):
+ self._add_flag_column()
+ self._add_filetype_column()
+ self._add_name_column()
+
+ #colors
+ self.flag_column.set_attribute("cell-background", self.MIME_COLOR)
+ self.desc_column.set_attribute("cell-background", self.MIME_COLOR)
+ self.name_column.set_attribute("cell-background", self.MIME_COLOR)
+
+ def _add_flag_column(self):
+ self.flag_column = gtk_common.FlagColumn("" ,self.MIME_SELECTED,
+ self.on_flag_toggled, self.on_mark_all_clicked)
+ self.flag_column.set_attribute("activatable", self.MIME_CHANGEABLE)
+ self.mtypes_view.append_column(self.flag_column)
+
+ def _add_name_column(self):
+ self.name_column = gtk_common.TextColumn(_("Mime Type"), self.MIME_TYPE)
+ self.name_column.set_sort_column_id(self.MIME_TYPE)
+ self.mtypes_view.append_column(self.name_column)
+
+ def set_data(self, app):
+ self.list_store.clear()
+ mtypes = self._get_initial_data(app)
+ self._add_mtypes(mtypes, app, initialization = True)
+
+ def _add_mtypes(self, mtypes, app, initialization = False):
+ super(MimeViewApps, self)._add_mtypes(mtypes, app)
+ self.set_consistence(initial_mode = initialization)
+
+ #def _get_initial_data(self, app):
+ # return mime_operations.get_mtypes_for_app(app)
+
+ def _get_model_data_row(self, mtype, app):
+ mtype, category, descr, icon = super(MimeViewApps, self)._get_model_data_row(mtype)
+ associated = mime_operations.is_app_associated(app, mtype)
+
+ selected = default = associated and mime_operations.is_app_default(app, mtype)
+ changeable = not selected
+
+ deleted = False
+ cell_color = self.CELL_COLOR_NORMAL
+ # mtype, mtype_description, mtype_icon,
+ # mtype_selected. mtype_selected_by_default,
+ # mtype_possible_to change_selection
+ # mtype_deleted, background_color
+
+ return [mtype, category, descr, icon,
+ associated,
+ selected, default, changeable,
+ deleted, cell_color]
+
+
+ def delete_selected(self):
+ self.set_value_to_selected(self.MIME_DELETED, value = True)
+ self.set_value_to_selected(self.MIME_COLOR, value = self.CELL_COLOR_DELETED)
+
+
+ def undelete_selected(self):
+ self.set_value_to_selected(self.MIME_DELETED, value = False)
+ self.set_value_to_selected(self.MIME_COLOR, value = self.CELL_COLOR_NORMAL)
+
+ def changeable(self, model, iter_):
+ return model.get_value(iter_, self.MIME_CHANGEABLE)
+
+ def get_swapped_mark(self, model, iter_):
+ return not model.get_value(iter_, self.MIME_SELECTED)
+
+ def get_default_mark(self, model, iter_):
+ return model.get_value(iter_, self.MIME_DEFAULT_SELECTED)
+
+ def reset_mark_all(self):
+ self.set_value_to_all(self.MIME_SELECTED, get_value_func = self.get_default_mark)
+ self.set_consistence()
+
+ def reset_mark_selected(self):
+ self.set_value_to_selected(self.MIME_SELECTED,
+ get_value_func = self.get_default_mark)
+ self.set_consistence()
+
+ def mark_all(self, mark = True):
+ self.set_value_to_all(self.MIME_SELECTED, value = mark,
+ conditions_func = self.changeable)
+ self.set_consistence()
+
+ def mark_selected(self, invert = False, mark = True):
+ """for inversion mark parameter will be ignored"""
+ if invert:
+ self.set_value_to_selected(self.MIME_SELECTED,
+ get_value_func = self.get_swapped_mark,
+ conditions_func = self.changeable)
+ else:
+ self.set_value_to_selected(self.MIME_SELECTED,
+ value = mark,
+ conditions_func = self.changeable)
+
+ self.set_consistence()
+
+ def swap_mark_by_path(self, path):
+ iter_ = self.list_store.get_iter(self.get_store_path(path))
+ self.set_value_if_cond(self.list_store, iter_,
+ self.MIME_SELECTED,
+ get_value_func = self.get_swapped_mark,
+ conditions_func = self.changeable)
+ self.set_consistence()
+
+ def get_changed_mtypes(self):
+ to_delete = []
+ to_set = []
+ to_reset = []
+
+ tree_iter = self.list_store.get_iter_first()
+ while tree_iter:
+ mtype = self.list_store.get_value(tree_iter, self.MIME_TYPE)
+
+ delete = self.list_store.get_value(tree_iter, self.MIME_DELETED)
+ if delete:
+ to_delete.append(mtype)
+ else:
+ default_selected = self.list_store.get_value(tree_iter, self.MIME_DEFAULT_SELECTED)
+
+ selected = self.list_store.get_value(tree_iter, self.MIME_SELECTED)
+
+ if (selected) and (not default_selected):
+ to_set.append(mtype)
+ elif (not selected) and (default_selected):
+ to_reset.append(mtype)
+
+ tree_iter = self.list_store.iter_next(tree_iter)
+ return to_delete, to_set, to_reset
+
+
+
+
+
+ def on_flag_toggled(self, renderer, str_path, *data):
+ self.swap_mark_by_path(Gtk.TreePath.new_from_string(str_path))
+
+
+ def on_mark_all_clicked(self, reset, mark):
+ """ If reset : default value would be restored
+ otherwise : mark would be assigned to each row
+ """
+ if reset:
+ self.reset_mark_all()
+ else:
+ self.mark_all(mark)
+
+ def on_double_click(self, widget, event):
+ self.mark_selected(invert = True) #invert mark
+
+ def set_consistence(self, initial_mode = False):
+ tree_iter = self.list_store.get_iter_first()
+
+ if not tree_iter:
+ self.flag_column.set_flag_inactive()
+ return
+
+ first_mark = self.list_store.get_value(tree_iter, self.MIME_SELECTED)
+ consistent = True
+
+ while tree_iter:
+ if first_mark != self.list_store.get_value(tree_iter, self.MIME_SELECTED):
+ consistent = False
+ break
+ tree_iter = self.list_store.iter_next(tree_iter)
+
+
+ if consistent:
+ self.flag_column.set_flag_active(first_mark)
+
+ if initial_mode:
+ self.flag_column.set_flag_degenerate(True)
+ else:
+ self.flag_column.set_flag_inconsistent()
+
+ if initial_mode:
+ self.flag_column.set_flag_degenerate(False)
+
+ def filter_associated(self, show_all = False):
+ self.set_filter_params("associated_filter",
+ enabled = not show_all)
+ self.delete_menuitem.set_sensitive(not show_all)
+
diff --git a/src/app_mode/ui_app_mode.glade b/src/app_mode/ui_app_mode.glade
new file mode 100644
index 0000000..494eb18
--- /dev/null
+++ b/src/app_mode/ui_app_mode.glade
@@ -0,0 +1,705 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkDialog" id="add_app_dialog">
+ <property name="can_focus">False</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="add_app_cancel_button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_app_ok_button">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="add_new_app_lable">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Add new app</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="add_app_name_field">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="add_app_name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Name</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="add_app_command_field">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="add_app_command_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Command</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="app_browse_button">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkDialog" id="add_mime_type_dialog">
+ <property name="can_focus">False</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="add_mime_type_dialog_vbox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="add_mime_type_dialog_action_area">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="add_mtype_cancel_button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_mtype_ok_button">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <property name="xalign">0.51999998092651367</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="add_mimetype_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Select from list</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="add_mimetype_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="all_mime_types_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="all_mime_types_treeview_selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="add_new_expander">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkEntry" id="add_mimetype_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="add_new_mtype_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add new</property>
+ <property name="lines">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-clear</property>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-edit</property>
+ </object>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-revert-to-saved</property>
+ </object>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-remove</property>
+ </object>
+ <object class="GtkMenu" id="mime_view_context_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="mark_menuitem">
+ <property name="label" translatable="yes">Mark</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image2</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="unmark_menuitem">
+ <property name="label" translatable="yes">Unmark</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="mime_context_menu_separator_1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="delete_menuitem">
+ <property name="label" translatable="yes">Delete</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image4</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="mime_context_menu_separator_2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="reset_menuitem">
+ <property name="label" translatable="yes">Reset</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image3</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ </object>
+ <object class="GtkViewport" id="root_widget">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="main_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkPaned" id="paned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="wide_handle">True</property>
+ <child>
+ <object class="GtkBox" id="left_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow_applications">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_applications">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_column">0</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview_applications_selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkExpander" id="expander1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkBox" id="apps_optios_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="apps_with_file_support_only">
+ <property name="label" translatable="yes">With file support only</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="visible_apps_only">
+ <property name="label" translatable="yes">Hide invisible</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="app_options_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Options</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="buttonbox_apps">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="add_app_button">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="right_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="app_title_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="app_icon_view">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="app_name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Application name</property>
+ <attributes>
+ <attribute name="style" value="normal"/>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="mime_operation_buttons">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">1</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="add_mtype_button">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="revert_button">
+ <property name="label">gtk-undo</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="apply_button">
+ <property name="label">gtk-apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="app_command_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes">Application command</property>
+ <property name="wrap">True</property>
+ <property name="lines">2</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ <attribute name="weight" value="thin"/>
+ <attribute name="stretch" value="condensed"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow_app_settings">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_mime_associations">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="rubber_banding">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview_mime_associations_selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkExpander" id="expander2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="show_only_registered_radiobutton">
+ <property name="label" translatable="yes">Show only registered</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="show_all_radiobutton">
+ <property name="label" translatable="yes">Show all for</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">show_only_registered_radiobutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="categories_combobox">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Options</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/cat_mode/mime_editor_cat_mode.py b/src/cat_mode/mime_editor_cat_mode.py
new file mode 100644
index 0000000..37ab448
--- /dev/null
+++ b/src/cat_mode/mime_editor_cat_mode.py
@@ -0,0 +1,326 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Fri Nov 18 06:50:25 2016
+
+@author: pavel
+"""
+import os
+DIR = os.path.dirname(os.path.realpath(__file__))
+
+import sys
+sys.path.append(os.path.join(DIR, '../common'))
+
+from gi.repository import Gtk, Gdk, Gio
+from gi.repository import GdkPixbuf
+
+from locale import gettext as _
+
+import mime_operations
+import mime_categories
+import mime_view_cat_mode
+
+import gtk_common
+from gtk_common import browse_for_file
+from gtk_common import ICON_SIZE
+
+
+GLADE_FILE = os.path.join(DIR,"ui_cat_mode.glade")
+SHOW_ONLY_ASSOCIATED = True
+
+class CategoriesView(gtk_common.CategoriesWidget):
+ CAT_ID = 0
+ CAT_NAME, CAT_IMG = 1, 2
+
+
+ def __init__(self, builder, on_category_changed):
+ """ on_category_changed : called every time category is changed,
+ should accept list of category ids"""
+ self.categories_view = builder.get_object("categories_view")
+ self.on_category_changed = on_category_changed
+
+ # id, name, img
+ self.list_store = Gtk.ListStore(int, str, GdkPixbuf.Pixbuf)
+ self.categories_view.set_model(self.list_store)
+
+ self._fill_list_store()
+
+ column = gtk_common.ImageTextColumn(_("Categories"), self.CAT_IMG, self.CAT_NAME)
+ self.categories_view.append_column(column)
+
+ #register callback
+ tree_selection = self.categories_view.get_selection()
+ tree_selection.connect("changed", self.on_selection_changes)
+
+ def _fill_list_store(self):
+ categories = sorted((_(name), cat ,id_)
+ for cat, name, id_ in mime_categories.get_known_categories())
+
+ # should be last
+ categories.append((_(mime_categories.ANY_CATEGORY_NAME),
+ mime_categories.ANY_CATEGORY,
+ mime_categories.ANY_CATEGORY_ID))
+
+
+ for transl_name, cat, id_ in categories:
+ icon_name = mime_categories.get_icon_name(cat)
+ icon = mime_operations.get_icon_by_name(icon_name, ICON_SIZE)
+ self.list_store.append([id_, transl_name, icon])
+
+ def on_selection_changes(self, tree_selection):
+ (model, pathlist) = tree_selection.get_selected_rows()
+ cat_ids = []
+ for path in pathlist :
+ tree_iter = model.get_iter(path)
+ value = model.get_value(tree_iter, self.CAT_ID)
+ cat_ids.append(value)
+ self.on_category_changed(cat_ids)
+
+
+
+class AddAppDialog:
+ def __init__(self, builder, parent_window, on_add_dialog_apply):
+ self.builder = builder
+ self.on_add_dialog_apply = on_add_dialog_apply
+
+ self.dialog = self.builder.get_object("add_app_dialog")
+ self.app_chooser = self.builder.get_object("appchooser_widget")
+ self.app_chooser.set_show_all(True)
+ self.app_chooser.connect("application-activated", self.on_apply)
+ self.app_chooser.connect("application-selected", self.on_application_selected)
+
+ self.dialog.set_transient_for(parent_window)
+ self.dialog.connect("delete-event", self.hide)
+
+ self.custom_entry = self.builder.get_object("custom_entry")
+ self.custom_entry.connect("changed", self.on_custom_entry_changed)
+
+ self.selected_app = None
+ self.cline_text = ""
+
+ self._init_buttons()
+
+ def _init_buttons(self):
+ self.cancel_button = self.builder.get_object("add_app_dialog_cancel_button")
+ self.apply_button = self.builder.get_object("add_app_dialog_apply_button")
+ self.file_chooser_button = self.builder.get_object("file_chooser_button")
+
+ self.cancel_button.connect("clicked", self.hide)
+ self.apply_button.connect("clicked", self.on_apply)
+ self.file_chooser_button.connect("clicked", self.on_file_chooser)
+
+ def show(self):
+ self.dialog.run()
+
+ def hide(self, *args):
+ self.dialog.hide()
+
+ return False #!!!
+
+ def on_custom_entry_changed(self, *args):
+ text = self.custom_entry.get_text()
+ if text != self.cline_text:
+ self.cline_text = text
+
+ self.selected_app = mime_operations.get_app_from_cline(self.cline_text,
+ None)
+
+ def on_application_selected(self, widget, app):
+ self.selected_app = app
+
+ name, icon, self.cline_text = mime_operations.get_app_bio(app, ICON_SIZE)
+ self.custom_entry.set_text(self.cline_text)
+
+ def on_file_chooser(self, *args):
+ browse_for_file(_('Choose an application'),
+ '/usr/bin',
+ self.dialog,
+ self.custom_entry.set_text)
+
+ def on_apply(self, *args):
+ #“application-activated”
+ self.on_add_dialog_apply(self.selected_app)
+ self.hide()
+
+
+class MimeSetDialog:
+ APP_OBJ = 0
+ APP_NAME = 1
+ APP_ICON = 2
+ APP_CL = 3
+
+ def __init__(self, builder, parent_window, on_dialog_ok):
+ self.builder = builder
+
+ self.on_dialog_ok = on_dialog_ok
+
+ self.dialog = self.builder.get_object("mimetype_set_dialog")
+ self.dialog.set_transient_for(parent_window)
+ self.dialog.connect("delete-event", self.hide)
+
+ self.mtypes_dialog_label = builder.get_object("mtypes_dialog_label")
+
+ self._init_buttons()
+ self._init_view()
+ self.add_app_dialog = None
+
+ #
+ self.mtypes = []
+
+ def _init_buttons(self):
+ self.cancel_button = self.builder.get_object("dialog_cancel_button")
+ self.apply_button = self.builder.get_object("dialog_apply_button")
+ self.add_button = self.builder.get_object("dialog_add_button")
+
+ self.cancel_button.connect("clicked", self.hide)
+ self.apply_button.connect("clicked", self.on_row_activated)
+ self.add_button.connect("clicked", self.on_add_app_clicked)
+
+ def _init_view(self):
+ self.alt_view = self.builder.get_object("alternatives_treeview")
+ self.alt_view.connect("row_activated", self.on_row_activated)
+
+ # desktop_file, app_name, icon
+ self.list_store = Gtk.ListStore(Gio.AppInfo, str ,GdkPixbuf.Pixbuf, str)
+
+ self.alt_view.set_model(self.list_store)
+
+ column_app = gtk_common.ImageTextColumn(_("Application"), self.APP_ICON, self.APP_NAME)
+ column_app.set_sort_column_id(self.APP_NAME)
+
+ self.alt_view.append_column(column_app)
+
+ column_cl = gtk_common.TextColumn(_("Command line"), self.APP_CL)
+ column_cl.set_sort_column_id(self.APP_CL)
+
+ self.alt_view.append_column(column_cl)
+
+ def show(self, mtypes):
+ self.mtypes = mtypes
+ self.set_data()
+ self.indicate_default()
+
+ self.mtypes_dialog_label.set_text(" \n".join(self.mtypes))
+
+ self.dialog.run()
+
+ def hide(self, *args):
+ self.dialog.hide()
+ self.list_store.clear()
+
+ return True #!!!
+
+ def set_data(self):
+ #if self.vbox is not None: return
+ apps = mime_operations.get_apps_for_mtypes(self.mtypes)
+
+ for app in apps:
+ name, icon, cl = mime_operations.get_app_bio(app, ICON_SIZE)
+
+ self.list_store.append([app, name, icon, cl])
+
+ def indicate_default(self):
+ if len(self.mtypes) == 1:
+ default_app = mime_operations.get_default_app(self.mtypes[0])
+ default_cl = default_app.get_commandline() if default_app is not None else ""
+
+ #iteratw
+ tree_iter = self.list_store.get_iter_first()
+ while tree_iter:
+ current_cl = self.list_store.get_value(tree_iter, self.APP_CL)
+
+ if current_cl == default_cl:
+ self.alt_view.get_selection().select_iter(tree_iter)
+ return
+ tree_iter = self.list_store.iter_next(tree_iter)
+
+
+ def on_row_activated(self, *args):
+ tree_selection = self.alt_view.get_selection()
+ (model, pathlist) = tree_selection.get_selected_rows()
+
+ selection = None
+
+ for path in pathlist :
+ tree_iter = model.get_iter(path)
+ value = model.get_value(tree_iter, self.APP_OBJ)
+ self.selection = value
+
+ self.hide()
+ self.on_dialog_ok(selection, self.mtypes)
+
+
+
+ def on_add_new_app(self, new_app):
+ if new_app is not None:
+ name, icon, cl = mime_operations.get_app_bio(new_app, ICON_SIZE)
+ tree_iter = self.list_store.append([new_app, name, icon, cl])
+ self.alt_view.get_selection().select_iter(tree_iter)
+
+ def on_add_app_clicked(self, *args):
+ if self.add_app_dialog is None:
+ self.add_app_dialog = AddAppDialog(self.builder, self.dialog,\
+ self.on_add_new_app)
+
+ self.add_app_dialog.show()
+
+
+class MainWidget:
+ def __init__(self, builder, window):
+ self.builder = builder
+ self.window = window
+
+ self.builder.add_from_file(GLADE_FILE)
+ self.builder.connect_signals(self)
+
+ self.root_widget = self.builder.get_object("root_widget")
+
+ self.cat_view = CategoriesView(self.builder,
+ self.on_category_changed)
+
+ self.mime_view = mime_view_cat_mode.MimeViewCategories(self.builder,
+ self.on_mtypes_edit,
+ self.on_mtypes_reset)
+
+ self.dialog = MimeSetDialog(self.builder,
+ self.window,
+ self.on_mtypes_edit_ok)
+
+ self._init_buttons()
+
+ def _init_buttons(self):
+ # only show mimetypes that have an associated application
+ hide_unknown_flag = self.builder.get_object("show_associated_only_button")
+ hide_unknown_flag.connect("toggled", self.on_hide_unknown_flag_toggled)
+ hide_unknown_flag.set_active(SHOW_ONLY_ASSOCIATED)
+
+
+ def get_widget(self):
+ return self.root_widget
+
+ def on_hide_unknown_flag_toggled(self, flag_widget):
+ hide_unknown = flag_widget.get_active()
+ self.mime_view.filter_associated(show_all = not hide_unknown)
+
+ def on_category_changed(self, category_id):
+ self.mime_view.filter_category(category_id[0])
+
+ def on_mtypes_edit_ok(self, app, mtypes):
+ if app is not None:
+ mime_operations.set_app_default(app, mtypes)
+ self.mime_view.update_data(mtypes)
+
+
+ def on_mtypes_edit(self, *args):
+ selected_mime_types = self.mime_view.get_selection()
+
+ self.dialog.show(selected_mime_types)
+
+ def on_mtypes_reset(self, *args):
+ selected_mime_types = self.mime_view.get_selection()
+
+ mime_operations.reset_associations(selected_mime_types)
+
+ self.mime_view.update_data(selected_mime_types)
+
+
diff --git a/src/cat_mode/mime_view_cat_mode.py b/src/cat_mode/mime_view_cat_mode.py
new file mode 100644
index 0000000..c189b66
--- /dev/null
+++ b/src/cat_mode/mime_view_cat_mode.py
@@ -0,0 +1,129 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Thu Dec 1 08:15:56 2016
+
+@author: pavel
+"""
+import sys
+sys.path.append('../common')
+
+from gi.repository import Gtk, Gdk, Gio
+from gi.repository import GdkPixbuf
+
+import gtk_common
+from gtk_common import ICON_SIZE
+
+import mime_operations, mime_categories
+import mime_view
+import data_filter
+
+from locale import gettext as _
+
+
+
+class MimeViewCategories(mime_view.MimeView):
+ UI_VIEW_ID = "mimetypes_view"
+ UI_CONTEXT_MENU_ID = "mimeview_context_menu"
+
+ #model columns
+ MIME_TYPE = 0
+ MIME_CATEGORY = 1
+ MIME_DESC = 2
+ MIME_ICON = 3
+
+ MIME_APP = 4
+ MIME_APP_IMG = 5
+
+ LIST_STORE_COLUMN_TYPES = [str, int, str, GdkPixbuf.Pixbuf,
+ str, GdkPixbuf.Pixbuf]
+
+ def __init__(self, builder, on_items_edit, on_items_reset):
+ """
+ on_items_edit : function to call when edit action required
+ on_items_reset : function to call when reset action required
+ """
+ self.on_items_edit = on_items_edit
+ self.on_items_reset = on_items_reset
+
+ super(MimeViewCategories, self).__init__(builder)
+ self._init_buttons()
+
+ self.set_data()
+
+ def _init_buttons(self):
+ reset_button = self.builder.get_object("reset_button")
+ reset_button.connect("clicked", self.on_items_reset)
+
+ edit_button = self.builder.get_object("edit_button")
+ edit_button.connect("clicked", self.on_items_edit)
+
+ def _init_mappings(self):
+ super(MimeViewCategories, self)._init_mappings()
+ self.context_menu_items.update({"menuitem_revert": lambda *param : self.on_items_reset(),
+ "menuitem_edit": lambda *param : self.on_items_edit(),
+ })
+
+
+ self.keyboard_keys_actions.update({"Return" : self.on_items_edit,
+ "Enter" : self.on_items_edit,
+ "space" : self.on_items_edit,
+ "BackSpace" : self.on_items_reset,
+ "Delete" : self.on_items_reset
+ })
+
+
+ def _init_columns(self, *args, **kwargs):
+ self._add_filetype_column()
+ self._add_app_column()
+
+ def _add_app_column(self):
+ column_program = gtk_common.ImageTextColumn(_("Program"), self.MIME_APP_IMG, self.MIME_APP)
+ column_program.set_sort_column_id(self.MIME_APP)
+ self.mtypes_view.append_column(column_program)
+
+ def _init_filters(self):
+ super(MimeViewCategories, self)._init_filters()
+
+
+ associated_filter = data_filter.GeneralFilter(False, self.MIME_APP,
+ lambda app : (app is not None) and (len(app) > 0))
+ self._add_filter_to_cascade("associated_filter", associated_filter)
+
+
+ def _get_model_data_row(self, mtype):
+ mtype, category, descr, icon = super(MimeViewCategories, self)._get_model_data_row(mtype)
+
+ app = mime_operations.get_default_app(mtype)
+ app_name, app_icon, *extra = mime_operations.get_app_bio(app, ICON_SIZE)
+
+ return [mtype, category, descr, icon, app_name, app_icon]
+
+
+ def update_data(self, mtypes_to_change):
+ tree_iter = self.list_store.get_iter_first()
+
+ while tree_iter:
+ row_mtype = self.list_store.get_value(tree_iter, self.MTYPE)
+
+ if row_mtype in mtypes_to_change:
+ app = mime_operations.get_default_app(row_mtype)
+ name, icon, *extra = mime_operations.get_app_bio(app, ICON_SIZE)
+
+ self.list_store.set_value(tree_iter, self.APP, name)
+ self.list_store.set_value(tree_iter, self.APP_IMG, icon)
+
+ tree_iter = self.list_store.iter_next(tree_iter)
+
+ def filter_associated(self, show_all = True):
+ self.set_filter_params("associated_filter",
+ enabled = not show_all)
+
+
+ def on_double_click(self, widget, event):
+ self.on_items_edit()
+
+
+ def get_selection(self):
+ """return mime types from selected rows"""
+ return super(MimeViewCategories, self).get_selection(self.MIME_TYPE)
diff --git a/ui.glade b/src/cat_mode/ui_cat_mode.glade
index 21ac7c7..10d1bf9 100644
--- a/ui.glade
+++ b/src/cat_mode/ui_cat_mode.glade
@@ -4,9 +4,10 @@
<requires lib="gtk+" version="3.12"/>
<object class="GtkDialog" id="add_app_dialog">
<property name="can_focus">False</property>
+ <property name="title" translatable="yes">Select Application</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
- <property name="type_hint">dialog</property>
+ <property name="type_hint">normal</property>
<child internal-child="vbox">
<object class="GtkBox" id="add_app_dialog_vbox">
<property name="can_focus">False</property>
@@ -60,7 +61,8 @@
<object class="GtkLabel" id="app_dialog_title_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Select Application</property>
+ <property name="label" translatable="yes">Select from list ...</property>
+ <property name="ellipsize">start</property>
<property name="lines">1</property>
</object>
<packing>
@@ -119,7 +121,7 @@
<object class="GtkLabel" id="custom_command_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Custom command</property>
+ <property name="label" translatable="yes">... or enter custom command</property>
</object>
</child>
</object>
@@ -173,6 +175,7 @@
</object>
<object class="GtkDialog" id="mimetype_set_dialog">
<property name="can_focus">False</property>
+ <property name="title" translatable="yes">Select</property>
<property name="modal">True</property>
<property name="default_width">640</property>
<property name="default_height">400</property>
@@ -246,7 +249,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
- <property name="label" translatable="yes">Select application for filetype(s):</property>
+ <property name="label" translatable="yes">Select default application for following filetype(s):</property>
</object>
<packing>
<property name="expand">False</property>
@@ -318,173 +321,15 @@
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
- <object class="GtkWindow" id="main_window">
+ <object class="GtkViewport" id="root_widget">
+ <property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="default_width">640</property>
- <property name="default_height">400</property>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
- <object class="GtkMenuBar" id="menubar">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkMenuItem" id="menuitem1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Файл</property>
- <property name="use_underline">True</property>
- <child type="submenu">
- <object class="GtkMenu" id="menu1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem1">
- <property name="label">gtk-new</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem2">
- <property name="label">gtk-open</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem3">
- <property name="label">gtk-save</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem4">
- <property name="label">gtk-save-as</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem5">
- <property name="label">gtk-quit</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="menuitem2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Правка</property>
- <property name="use_underline">True</property>
- <child type="submenu">
- <object class="GtkMenu" id="menu2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem6">
- <property name="label">gtk-cut</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem7">
- <property name="label">gtk-copy</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem8">
- <property name="label">gtk-paste</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem9">
- <property name="label">gtk-delete</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="menuitem3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Вид</property>
- <property name="use_underline">True</property>
- </object>
- </child>
- <child>
- <object class="GtkMenuItem" id="menuitem4">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">_Справка</property>
- <property name="use_underline">True</property>
- <child type="submenu">
- <object class="GtkMenu" id="menu3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <child>
- <object class="GtkImageMenuItem" id="imagemenuitem10">
- <property name="label">gtk-about</property>
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="use_underline">True</property>
- <property name="use_stock">True</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
<placeholder/>
</child>
<child>
diff --git a/src/common/data_filter.py b/src/common/data_filter.py
new file mode 100644
index 0000000..e97f018
--- /dev/null
+++ b/src/common/data_filter.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Fri Nov 25 07:18:57 2016
+
+@author: pavel
+"""
+
+import mime_categories
+
+class DataFilter:
+ def __init__(self, enabled = True):
+ self.enabled = enabled
+ def process_row(self, model, iter_, data):
+ return not self.enabled
+ def set_params(self, **kwargs):
+ for param_name, param_value in kwargs.items():
+ print("filtering", param_name, param_value)
+ if param_name in self.__dict__:
+ self.__dict__[param_name] = param_value
+
+class GeneralFilter(DataFilter):
+ def __init__(self, enabled, column, value_condition_fn):
+ self.enabled = enabled
+ self.column = column
+ self.value_condition_fn = value_condition_fn
+ def process_row(self, model, iter_, data):
+ if not self.enabled: return True
+ value = model.get_value(iter_, self.column)
+ return self.value_condition_fn(value)
+
+
+
+class CategoryFilter(DataFilter):
+ def __init__(self, category_column, current_category_id):
+ self.category_id_column = category_column
+ self.current_category_id = current_category_id
+
+ def process_row(self, model, iter_, data):
+ if self.current_category_id == mime_categories.ANY_CATEGORY_ID:
+ return True
+
+ return model.get_value(iter_, self.category_id_column) == self.current_category_id
diff --git a/src/common/gtk_common.py b/src/common/gtk_common.py
new file mode 100644
index 0000000..87fe1bc
--- /dev/null
+++ b/src/common/gtk_common.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Fri Nov 25 07:18:57 2016
+
+@author: pavel
+"""
+
+import mime_categories
+import mime_operations
+
+from gi.repository import Gtk
+from gi.repository import GdkPixbuf
+from locale import gettext as _
+
+ICON_SIZE = 24
+
+def browse_for_file(title, start_directory, parent_window, on_success_callback):
+ f_dialog = Gtk.FileChooserDialog(title,
+ action = Gtk.FileChooserAction.OPEN,
+ buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
+ f_dialog.set_current_folder(start_directory)
+ f_dialog.set_transient_for(parent_window)
+
+ if f_dialog.run() == Gtk.ResponseType.ACCEPT:
+ on_success_callback(f_dialog.get_filename())
+
+ f_dialog.destroy()
+
+class ImageTextColumn(Gtk.TreeViewColumn):
+ def __init__(self, column_name, model_index_img, model_index_txt, *args, **kwargs):
+ super(ImageTextColumn, self).__init__(column_name, *args, **kwargs)
+
+ renderer_pixbuf = Gtk.CellRendererPixbuf()
+ renderer_text = Gtk.CellRendererText()
+ self.cell_renderers = (renderer_pixbuf, renderer_text, )
+
+ self.pack_start(renderer_pixbuf, expand = False)
+ self.add_attribute(renderer_pixbuf, "pixbuf", model_index_img)
+
+ self.pack_start(renderer_text, expand = True)
+ self.add_attribute(renderer_text, "text", model_index_txt)
+
+ self.set_resizable(True)
+
+ def set_attribute(self, name, model_column):
+ for renderer in self.cell_renderers:
+ self.add_attribute(renderer, name, model_column)
+
+
+class TextColumn(Gtk.TreeViewColumn):
+ def __init__(self, column_name, model_index_txt, *args, **kwargs):
+ super(TextColumn, self).__init__(column_name, *args, **kwargs)
+ renderer_text = Gtk.CellRendererText()
+ self.cell_renderers = (renderer_text, )
+
+ self.pack_start(renderer_text, expand = True)
+ self.add_attribute(renderer_text, "text", model_index_txt)
+
+ self.set_resizable(True)
+
+
+
+ def set_attribute(self, name, model_column):
+ for renderer in self.cell_renderers:
+ self.add_attribute(renderer, name, model_column)
+
+class FlagColumn(Gtk.TreeViewColumn):
+ def __init__(self, column_name, model_index_bool, on_toggle, on_mark_all,
+ *args, **kwargs):
+ super(FlagColumn, self).__init__(None, *args, **kwargs)
+
+ self.header_checkbox = CyclicCheckbox(on_mark_all)
+ self.title = Gtk.Label(column_name)
+
+ self.header_widget = Gtk.HBox()
+ self.header_widget.pack_start(self.header_checkbox, True, True, 0)
+ self.header_widget.pack_start(self.title, True, True, 0)
+ self.header_widget.show_all()
+ self.set_widget(self.header_widget)
+
+
+ renderer_flag = Gtk.CellRendererToggle()
+ self.cell_renderers = (renderer_flag, )
+
+ renderer_flag.connect("toggled", on_toggle)
+
+ self.pack_start(renderer_flag, expand = False)
+ self.add_attribute(renderer_flag, "active", model_index_bool)
+
+ self.set_clickable(True)
+ self.set_resizable(False)
+ self.connect("clicked", self.header_checkbox.on_click)
+
+
+ #self.set_sort_indicator(True)
+
+
+ #def on_header_clicked(self, *args):
+ # self.header_checkbox.on_click()
+
+
+ def set_flag_degenerate(self, degenerate):
+ self.header_checkbox.set_degenerate(degenerate)
+ def set_flag_inconsistent(self):
+ self.header_checkbox.set_inconsistent()
+ def set_flag_active(self, *args):
+ self.header_checkbox.set_active(*args)
+ def set_flag_inactive(self):
+ self.header_checkbox.set_inactive()
+
+ def set_attribute(self, name, model_column):
+ for renderer in self.cell_renderers:
+ self.add_attribute(renderer, name, model_column)
+
+
+class CyclicCheckbox(Gtk.CheckButton):
+ STATE_ACTIVE = 0
+ STATE_INACTIVE = 1
+ STATE_INCONSISTENT = 2
+
+
+ def __init__(self, on_state_change, *args, **kwargs):
+ super(CyclicCheckbox, self).__init__(None, *args, **kwargs)
+ self.on_state_change = on_state_change
+
+ #self.connect("clicked", self.on_click)
+
+ self.set_degenerate(False)
+ self.set_inactive()
+
+ def on_click(self, *args):
+ self.next_state()
+ self.on_state_change(self.get_inconsistent(), self.get_active())
+
+ return False
+
+ def next_state(self):
+ if self.state == CyclicCheckbox.STATE_ACTIVE:
+ self.set_inactive()
+ elif self.state == CyclicCheckbox.STATE_INACTIVE:
+ if self.degenerate: # skip inconsistent
+ self.set_active()
+ else:
+ self.set_inconsistent()
+ else:
+ self.set_active()
+
+ def set_degenerate(self, degenerate = False):
+ # if degenerate, then inconsistent state will be in cycle
+ self.degenerate = degenerate
+
+
+ def set_inconsistent(self):
+ #print("inconsistent")
+ self.state = CyclicCheckbox.STATE_INCONSISTENT
+
+ super(CyclicCheckbox, self).set_inconsistent(True)
+ super(CyclicCheckbox, self).set_active(False)
+
+ def set_active(self, active = True):
+ #print("active")
+ if active:
+ self.state = CyclicCheckbox.STATE_ACTIVE
+
+ super(CyclicCheckbox, self).set_inconsistent(False)
+ super(CyclicCheckbox, self).set_active(True)
+ else:
+ self.set_inactive()
+
+ def set_inactive(self):
+ #print("inactive")
+ self.state = CyclicCheckbox.STATE_INACTIVE
+
+ super(CyclicCheckbox, self).set_inconsistent(False)
+ super(CyclicCheckbox, self).set_active(False)
+
+
+
+class IconTextLabel(Gtk.Box):
+ def __init__(self, icon, text, *args, **kwargs):
+ super( IconTextLabel, self).__init__(orientation=Gtk.Orientation.HORIZONTAL)
+
+ label = Gtk.Label(text)
+
+ self.pack_start(icon, True, True, 0)
+ self.pack_start(label, True, True, 0)
+
+class CategoriesWidget:
+ CAT_ID = 0
+ CAT_NAME = 1
+ CAT_IMG = 2
+
+ def __init__(self, builder, widget_name, on_category_changed):
+ self.widget = builder.get_object(widget_name)
+ self.on_category_changed = on_category_changed
+
+ self._init_model()
+ self._init_widget()
+
+ self._set_data()
+
+ def _init_model(self):
+ self.list_store = Gtk.ListStore(int, str, GdkPixbuf.Pixbuf)
+ self.widget.set_model(self.list_store)
+
+ def _init_widget(self):
+ raise NotImplemented
+
+ def _set_data(self):
+ categories = sorted((_(name), cat ,id_)
+ for cat, name, id_ in mime_categories.get_known_categories())
+
+ # should be last
+ categories.append((_(mime_categories.ANY_CATEGORY_NAME),
+ mime_categories.ANY_CATEGORY,
+ mime_categories.ANY_CATEGORY_ID))
+
+
+ for transl_name, cat, id_ in categories:
+ icon_name = mime_categories.get_icon_name(cat)
+ icon = mime_operations.get_icon_by_name(icon_name, ICON_SIZE)
+ self.list_store.append([id_, transl_name, icon])
+
+
+
diff --git a/src/common/mime_categories.py b/src/common/mime_categories.py
new file mode 100644
index 0000000..0d4481e
--- /dev/null
+++ b/src/common/mime_categories.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Dec 6 14:51:39 2016
+
+@author: pavel
+"""
+
+ANY_CATEGORY = "any"
+ANY_CATEGORY_ID = 0
+ANY_CATEGORY_NAME = "All"
+ANY_CATEGORY_ICON = "unknown"
+
+CATEGORY_IDS = {"application" : 1,
+ "audio" : 2,
+ "image" : 3,
+ "text" : 4,
+ "video" : 5, }
+
+CATEGORY_NAMES = { "application" : "Application" ,
+ "audio" : "Audio",
+ "image" : "Image",
+ "text" : "Text",
+ "video" : "Video"}
+
+# name, icon
+CATEGORY_ICONS = {"application" : "application-x-executable",
+ "audio" : "audio-x-generic",
+ "image" : "image-x-generic",
+ "text" : "text-x-generic",
+ "video" : "video-x-generic"}
+
+
+def get_category_id(mime_type):
+ if mime_type is None: return ANY_CATEGORY_ID
+
+ slash_ind = mime_type.find("/")
+
+ if slash_ind <= 0: return ANY_CATEGORY_ID
+
+ top_level = mime_type[:slash_ind]
+ return CATEGORY_IDS.get(top_level, ANY_CATEGORY_ID)
+
+def get_category_name(cat):
+ return CATEGORY_NAMES.get(cat, ANY_CATEGORY_NAME)
+
+def get_known_categories():
+ """ list of (category, category name, id) """
+ return [(cat , get_category_name(cat), id_) for cat, id_
+ in CATEGORY_IDS.items()]
+
+
+def get_icon_name(cat) :
+ return CATEGORY_ICONS.get(cat, ANY_CATEGORY_ICON) \ No newline at end of file
diff --git a/mime_operations.py b/src/common/mime_operations.py
index 7d68d25..64d4c61 100644
--- a/mime_operations.py
+++ b/src/common/mime_operations.py
@@ -8,7 +8,6 @@ Created on Wed Nov 23 18:26:27 2016
import gi
gi.require_version('Gtk', '3.0')
-
from gi.repository import Gio, Gtk
from gi.repository import GdkPixbuf
@@ -19,10 +18,16 @@ from functools import lru_cache
def get_default_app(mime_type):
return Gio.app_info_get_default_for_type(mime_type, False)
-def get_app_from_cline(cline, name, terminal):
+def get_app_from_cline(cline, name, terminal = False):
""" cl - command line
name - app name
terminal - open in terminal """
+
+ if cline is None or len(cline) == 0:
+ return None
+
+ if name is None or len(name) == 0:
+ name = os.path.basename(cline)
flag = Gio.AppInfoCreateFlags.NEEDS_TERMINAL if terminal \
else Gio.AppInfoCreateFlags.NONE
return Gio.AppInfo.create_from_commandline(cline, name, flag)
@@ -38,27 +43,43 @@ def get_app_bio(app, icon_size):
icon = get_icon_by_app(app, icon_size)
cl = app.get_commandline()
- return name, icon, cl
+ return name, icon, cl
+
+
+@lru_cache(maxsize=256)
+def get_mime_bio(mime_type, icon_size):
+ description = ""
+ icon = None
+
+ try:
+ description = Gio.content_type_get_description(mime_type)
+ icon = get_mime_icon(mime_type, icon_size)
+ except Exception as e:
+ print(e)
+
+ return description, icon
+
def get_known_mtypes():
return Gio.content_types_get_registered()
+def get_mtypes_for_app(app) :
+ if app is None : return []
+ mtypes = set(app.get_supported_types())
+ #does not take in consideration associations added with g_app_info_add_supports_type()
+
+
+ for mtype in get_known_mtypes():
+ for a in get_apps_for_mtype(mtype):
+ if app.equal(a):
+ mtypes.add(mtype)
+ break
+
+ return sorted(list(mtypes))
+
def get_apps_for_mtype(mime_type):
return Gio.app_info_get_all_for_type(mime_type)
-
-def set_app_default(app, m_types):
- for m_type in m_types:
- try:
- app.set_as_default_for_type(m_type)
- except Exception as e:
- print(e)
-
-def reset_association(m_type):
- try:
- Gio.AppInfo.reset_type_associations(m_type)
- except Exception as e:
- print(e)
-
+
def get_apps_for_mtypes(mime_types_list):
apps = {}
for mtype in mime_types_list:
@@ -66,7 +87,45 @@ def get_apps_for_mtypes(mime_types_list):
#using id_ to eliminate duplicates
id_ = app.get_commandline()#app.get_id()
apps[id_] = app
- return list(apps.values())
+ return list(apps.values())
+
+def is_app_associated(app, mtype):
+ associated_app_ids = [ap.get_id() for ap in get_apps_for_mtype(mtype)]
+ return app.get_id() in associated_app_ids
+
+def is_app_default(app, mtype):
+ try:
+ default = get_default_app(mtype)
+ return app.equal(default)
+ except Exception as e:
+ print(e)
+ return False
+
+def set_app_default(app, mtypes):
+ for mtype in mtypes:
+ try:
+ app.set_as_default_for_type(mtype)
+ except Exception as e:
+ print(e)
+
+def reset_associations(mtypes):
+ for mtype in mtypes:
+ try:
+ Gio.AppInfo.reset_type_associations(mtype)
+ except Exception as e:
+ print(e)
+
+def remove_associations(app, mtypes):
+ for mtype in mtypes:
+ try:
+ app.remove_supports_type(mtype)
+ except Exception as e:
+ print(e)
+
+
+
+def get_all_apps():
+ return Gio.app_info_get_all()
@lru_cache(maxsize=256)
def get_icon_by_name(icon_name, size):
@@ -135,4 +194,5 @@ def get_mime_icon(mtype, size):
except Exception as e:
print(e)
- return icon \ No newline at end of file
+ return icon
+
diff --git a/src/common/mime_view.py b/src/common/mime_view.py
new file mode 100644
index 0000000..06885dd
--- /dev/null
+++ b/src/common/mime_view.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Thu Dec 1 08:15:56 2016
+
+@author: pavel
+"""
+
+from gi.repository import Gtk, Gdk, Gio
+from gi.repository import GdkPixbuf
+
+from gtk_common import ImageTextColumn, FlagColumn, TextColumn
+from gtk_common import ICON_SIZE
+
+import mime_operations, mime_categories
+import data_filter
+
+from locale import gettext as _
+
+
+
+class MimeView:
+ UI_VIEW_ID = None
+ UI_CONTEXT_MENU_ID = None
+
+ #model columns
+ MIME_TYPE = 0
+ MIME_CATEGORY = 1
+ MIME_DESC = 2
+ MIME_ICON = 3
+ LIST_STORE_COLUMN_TYPES = [str, int, str, GdkPixbuf.Pixbuf]
+
+
+ def __init__(self, builder):
+ self.builder = builder
+
+ self.mtypes_view = self.builder.get_object(self.UI_VIEW_ID)
+
+ #callbacks
+ self.mtypes_view.connect("button-press-event", self.on_mouse_clicked)
+ self.mtypes_view.connect("key-press-event", self.on_key_pressed)
+
+ #allow multiple selection
+ self.tree_selection = self.mtypes_view.get_selection()
+ self.tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE)
+
+ self._init_mappings()
+ self._init_filters()
+
+ self._init_model()
+ self._init_columns()
+ self._init_context_menu()
+
+ def _init_mappings(self):
+ #mappings
+ self.context_menu_items = {} # dictionary of item_id: item_action pairs
+ self.keyboard_keys_actions = {"Menu" : self.show_context_menu
+ } # dictionary of key_name: key_action pairs
+
+ def _init_filters(self):
+ self.ordered_data_filters = [] #order may be matter
+ self.data_filters = {} # save reference by name
+
+ #add category filter
+ category_filter = data_filter.CategoryFilter(self.MIME_CATEGORY,
+ mime_categories.ANY_CATEGORY_ID)
+
+ self._add_filter_to_cascade("category_filter", category_filter)
+
+ def _add_filter_to_cascade(self, filter_name, data_filter):
+ self.ordered_data_filters.append(data_filter)
+ self.data_filters[filter_name] = data_filter
+
+ def _init_model(self):
+ """liststore -> filter -> sort -> view"""
+ self.list_store = Gtk.ListStore(*self.LIST_STORE_COLUMN_TYPES)
+
+ self.filter_model = self.list_store.filter_new()
+ self.filter_model.set_visible_func(self.cascade_filter_func, data=None)
+
+ self.sortable_model = Gtk.TreeModelSort(self.filter_model)
+
+ self.mtypes_view.set_model(self.sortable_model)
+
+ def _init_columns(self):
+ self._add_filetype_column()
+
+ def _init_context_menu(self):
+ self.context_menu = self.builder.get_object(self.UI_CONTEXT_MENU_ID)
+
+ for item_id, action in self.context_menu_items.items():
+ item = self.builder.get_object(item_id)
+ item.connect("activate", action)
+
+
+ def set_data(self, *args, **kwargs):
+ self.list_store.clear()
+ mtypes = self._get_initial_data(*args, **kwargs)
+ self._add_mtypes(mtypes)
+
+ def set_filter_params(self, filter_name, **kwargs):
+ if filter_name in self.data_filters:
+ self.data_filters[filter_name].set_params(**kwargs)
+
+ self.filter_model.refilter()
+
+ def _add_mtypes(self, mtypes, *args, **kwargs):
+ for mtype in mtypes:
+ data_row = self._get_model_data_row(mtype, *args, **kwargs)
+ self.list_store.append(data_row)
+
+ def _get_initial_data(self, *args, **kwargs):
+ return mime_operations.get_known_mtypes()
+
+ def _add_filetype_column(self):
+ self.desc_column = ImageTextColumn(_("File Type"), self.MIME_ICON, self.MIME_DESC)
+ self.desc_column.set_sort_column_id(self.MIME_DESC)
+ self.mtypes_view.append_column(self.desc_column)
+
+
+ def _get_model_data_row(self, mtype):
+ descr, icon = mime_operations.get_mime_bio(mtype, ICON_SIZE)
+ category = mime_categories.get_category_id(mtype)
+ return [mtype, category, descr, icon]
+
+ def cascade_filter_func(self, model, iter_, data):
+ # return True if all conditions are satisfied
+ for data_filter in self.ordered_data_filters:
+ if not data_filter.process_row(model, iter_, data):
+ return False
+ return True
+
+ def on_mouse_clicked(self, widget, event):
+ if event.button == 3: #right button
+ self.show_context_menu(widget, event)
+ return True
+ if event.type == Gdk.EventType._2BUTTON_PRESS: #double click
+ self.on_double_click(widget, event)
+ return True
+
+ def on_key_pressed(self, widget, event):
+ #do not reset selection
+ keyname = Gdk.keyval_name(event.keyval)
+ print(keyname, "button pressed")
+
+ if keyname in self.keyboard_keys_actions:
+ #execute
+ self.keyboard_keys_actions.get(keyname, print)(widget, event)
+ return True
+
+ def on_double_click(self, widget, event):
+ raise NotImplemented
+
+
+ def show_context_menu(self, widget, event):
+ self.context_menu.popup( None, None, None, None, 0, event.time)
+
+
+ def get_store_path(self, sorted_path):
+ """applying filters and sorting cause problem that
+ selection path does not correspond store path any more"""
+
+ filtered_path = self.sortable_model.convert_path_to_child_path(sorted_path)
+ store_path = self.filter_model.convert_path_to_child_path(filtered_path)
+
+ #print(sorted_path, filtered_path, store_path)
+ return store_path
+
+ def get_new_value(self, model, tree_iter, get_value_func = None,
+ value = None, **kwargs):
+ if get_value_func is not None:
+ value = get_value_func(model, tree_iter, **kwargs)
+
+ return value
+
+ def set_value_if_cond(self, model, tree_iter, column,
+ get_value_func = None, value = None,
+ conditions_func = None, **kwargs):
+ """column = column to change value
+ get_value_func = function to compute new value depending on row
+ should accept model and iter as parameters
+ value = value to set if get_value_func is not specified
+ extra_conditions_func = function to check if possible to set new value
+ """
+ if conditions_func is None or \
+ conditions_func(model, tree_iter, **kwargs):
+ value = self.get_new_value(self.list_store, tree_iter,
+ get_value_func, value, **kwargs)
+ model.set_value(tree_iter, column, value)
+
+
+ def set_value_to_all(self, column,
+ get_value_func = None, value = None,
+ conditions_func = None, **kwargs):
+
+ tree_iter = self.list_store.get_iter_first()
+ while tree_iter:
+ self.set_value_if_cond(self.list_store, tree_iter, column,
+ get_value_func, value,
+ conditions_func, **kwargs)
+
+ tree_iter = self.list_store.iter_next(tree_iter)
+
+ def set_value_to_selected(self, column,
+ get_value_func = None, value = None,
+ conditions_func = None, **kwargs):
+ (model, pathlist) = self.tree_selection.get_selected_rows()
+
+ for path in pathlist :
+ tree_iter = self.list_store.get_iter(self.get_store_path(path))
+
+ self.set_value_if_cond(self.list_store, tree_iter, column,
+ get_value_func, value,
+ conditions_func, **kwargs)
+
+ def set_value_to_visible(self, column,
+ get_value_func = None, value = None,
+ conditions_func = None, **kwargs):
+
+ tree_iter = self.list_store.get_iter_first()
+ while tree_iter:
+ if self.cascade_filter_func(self.list_store, tree_iter, None):
+ self.set_value_if_cond(self.list_store, tree_iter, column,
+ get_value_func, value,
+ conditions_func, **kwargs)
+ tree_iter = self.list_store.iter_next(tree_iter)
+
+ def get_selection(self, column):
+ selection = []
+
+ (model, pathlist) = self.tree_selection.get_selected_rows()
+
+ for path in pathlist :
+ tree_iter = self.list_store.get_iter(self.get_store_path(path))
+ selection.append(self.list_store.get_value(tree_iter, column))
+ return selection
+
+ def filter_category(self, category_id = mime_categories.ANY_CATEGORY_ID):
+ self.set_filter_params("category_filter",
+ current_category_id = category_id)
diff --git a/src/mime_editor.py b/src/mime_editor.py
new file mode 100644
index 0000000..ffccb4c
--- /dev/null
+++ b/src/mime_editor.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Wed Dec 7 15:11:32 2016
+
+@author: pavel
+"""
+import os
+DIR = os.path.dirname(os.path.realpath(__file__))
+
+import sys
+sys.path.append('./common')
+sys.path.append('./app_mode')
+sys.path.append('./cat_mode')
+
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import Gtk
+
+import locale
+locale.setlocale(locale.LC_ALL, '')
+if os.path.isdir("./locale"):
+ locale.bindtextdomain(APP, "./locale")
+ locale.textdomain(APP)
+from locale import gettext as _
+
+import signal
+signal.signal(signal.SIGINT, signal.SIG_DFL) #handle Ctrl-C
+
+import mime_editor_app_mode
+import mime_editor_cat_mode
+
+APP = os.path.join(DIR, "python-mime-editor-gui")
+GLADE_FILE = "ui_main_window.glade"
+
+editor_modes = { "by_apps" : mime_editor_app_mode.MainWidget,
+ "by_categories" : mime_editor_cat_mode.MainWidget }
+
+class MainWindow:
+ def __init__(self, mode):
+ self.builder = Gtk.Builder()
+ self.builder.set_translation_domain(APP)
+ self.builder.add_from_file(GLADE_FILE)
+
+ self.window = self.builder.get_object("main_window")
+ self.window.connect("delete-event", self.on_close)
+
+ self.viewport = self.builder.get_object("main_box")
+
+
+ editor = editor_modes.get(mode)(self.builder, self.window)
+
+ self.viewport.pack_start(editor.get_widget(), True, True, 0)
+
+
+ def run(self):
+ self.window.show()
+ Gtk.main()
+
+ def on_close(self, *args):
+ Gtk.main_quit()
+
+
+if __name__ == "__main__":
+ #main(sys.argv)
+ #window = MainWindow("by_categories")
+ window = MainWindow("by_apps")
+ window.run()
diff --git a/src/ui_main_window.glade b/src/ui_main_window.glade
new file mode 100644
index 0000000..b3b650e
--- /dev/null
+++ b/src/ui_main_window.glade
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkApplicationWindow" id="main_window">
+ <property name="width_request">640</property>
+ <property name="height_request">400</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="has_resize_grip">True</property>
+ <child>
+ <object class="GtkBox" id="main_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Файл</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem1">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem2">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem3">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem4">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem5">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Правка</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem6">
+ <property name="label">gtk-cut</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem7">
+ <property name="label">gtk-copy</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem8">
+ <property name="label">gtk-paste</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem9">
+ <property name="label">gtk-delete</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Вид</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Справка</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem10">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/xdg-gui.py b/xdg-gui.py
deleted file mode 100644
index e0805f1..0000000
--- a/xdg-gui.py
+++ /dev/null
@@ -1,588 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-Created on Fri Nov 18 06:50:25 2016
-
-@author: pavel
-"""
-import os
-import sys
-
-import threading
-
-
-#import mime_types
-#import xdg_operations
-import mime_operations
-
-
-APP = 'xdg-gui'
-
-import locale
-from locale import gettext as _
-locale.setlocale(locale.LC_ALL, '')
-if os.path.isdir("./locale"):
- locale.bindtextdomain(APP, "./locale")
- locale.textdomain(APP)
-
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Gdk, Gio
-from gi.repository import GdkPixbuf
-
-import signal
-signal.signal(signal.SIGINT, signal.SIG_DFL) #handle Ctrl-C
-
-
-GLADE_FILE = "ui.glade"
-ICON_SIZE = 16
-SHOW_ONLY_ASSOCIATED = True
-
-# using indecies shoud speed up filtering
-ALL_CATEGORY = "All"
-ALL_CATEGORY_ID = 0
-
-MIMETYPES_IDS = {"application" : 1,
- "audio" : 2,
- "image" : 3,
- "text" : 4,
- "video" : 5, }
-
-CATEGORY_IDS = { "Application" : 1,
- "Audio" : 2,
- "Image" : 3,
- "Text" : 4,
- "Video" : 5,
- }
-
-# name, icon
-CATEGORY_ICONS = {"Application" : "application-x-executable",
- "Audio" : "audio-x-generic",
- "Image" : "image-x-generic",
- "Text" : "text-x-generic",
- "Video" : "video-x-generic",
- "All" : "unknown"}
-
-def get_category_id(mime_type):
- if mime_type is None: return ALL_CATEGORY_ID
-
- slash_ind = mime_type.find("/")
-
- if slash_ind <= 0: return ALL_CATEGORY_ID
-
- top_level = mime_type[:slash_ind]
- return MIMETYPES_IDS.get(top_level, ALL_CATEGORY_ID)
-
-
-class ImageTextColumn(Gtk.TreeViewColumn):
- def __init__(self, column_name, model_index_img, model_index_txt, *args, **kwargs):
- super(ImageTextColumn, self).__init__(column_name, *args, **kwargs)
-
- renderer_pixbuf = Gtk.CellRendererPixbuf()
- renderer_text = Gtk.CellRendererText()
-
- self.pack_start(renderer_pixbuf, expand = False)
- self.add_attribute(renderer_pixbuf, "pixbuf", model_index_img)
-
- self.pack_start(renderer_text, expand = True)
- self.add_attribute(renderer_text, "text", model_index_txt)
-
-class TextColumn(Gtk.TreeViewColumn):
- def __init__(self, column_name, model_index_txt, *args, **kwargs):
- super(TextColumn, self).__init__(column_name, *args, **kwargs)
- renderer_text = Gtk.CellRendererText()
-
- self.pack_start(renderer_text, expand = True)
- self.add_attribute(renderer_text, "text", model_index_txt)
-
-class IconTextLabel(Gtk.Box):
- def __init__(self, icon_name, text, *args, **kwargs):
- super( IconTextLabel, self).__init__(orientation=Gtk.Orientation.HORIZONTAL)
-
- icon = mime_operations.get_icon_by_name(icon_name, ICON_SIZE)
- label = Gtk.Label(text)
-
- self.pack_start(icon, True, True, 0)
- self.pack_start(label, True, True, 0)
-
-
-class CategoriesView:
- CAT_ID = 0
- CAT_NAME, CAT_IMG = 1, 2
-
-
- def __init__(self, builder, on_category_changed):
- """ on_category_changed : called every time category is changed,
- should accept list of category ids"""
- self.categories_view = builder.get_object("categories_view")
- self.on_category_changed = on_category_changed
-
- # id, name, img
- self.list_store = Gtk.ListStore(int, str, GdkPixbuf.Pixbuf)
- self.categories_view.set_model(self.list_store)
-
- self._fill_list_store()
-
- column = ImageTextColumn(_("Categories"), self.CAT_IMG, self.CAT_NAME)
- self.categories_view.append_column(column)
-
- #register callback
- tree_selection = self.categories_view.get_selection()
- tree_selection.connect("changed", self.on_selection_changes)
-
- def _fill_list_store(self):
- categories = sorted((_(name), name ,id_)
- for name, id_ in CATEGORY_IDS.items())
- categories.append((_(ALL_CATEGORY), ALL_CATEGORY,
- ALL_CATEGORY_ID))
-
-
- for transl, name, id_ in categories:
- icon_name = CATEGORY_ICONS.get(name)
- icon = mime_operations.get_icon_by_name(icon_name, ICON_SIZE)
- self.list_store.append([id_, transl, icon])
-
- def on_selection_changes(self, tree_selection):
- (model, pathlist) = tree_selection.get_selected_rows()
- cat_ids = []
- for path in pathlist :
- tree_iter = model.get_iter(path)
- value = model.get_value(tree_iter, self.CAT_ID)
- cat_ids.append(value)
- self.on_category_changed(cat_ids)
-
-class MimeTypesView:
- ID = 0
- MTYPE, MTYPE_IMG = 1, 2
- APP,APP_IMG = 3, 4
-
- def __init__(self, builder, on_items_edit, on_items_reset):
- """
- on_items_edit : function to call when edit action required
- on_items_reset : function to call when reset action required
- """
-
- self.mimetypes_view = builder.get_object("mimetypes_view")
- #callbacks
- self.mimetypes_view.connect("button-press-event", self.on_mouse_clicked)
- self.mimetypes_view.connect("key-press-event", self.on_key_pressed)
-
- self.on_items_edit = on_items_edit
- self.on_items_reset = on_items_reset
-
- self.current_category_id = ALL_CATEGORY_ID
- self.hide_unassociated = False
-
- self._init_model()
- self._add_columns()
-
- self._init_context_menu(builder)
- self._init_buttons(builder)
-
- self.set_data()
-
- def _init_model(self):
- """liststore -> filter -> sort -> view"""
-
- # category_id, mimetype, mimetype_img, app, app_img
- self.list_store = Gtk.ListStore(int, str, GdkPixbuf.Pixbuf,
- str, GdkPixbuf.Pixbuf, )
-
- #filter
- self.cascade_filter = self.list_store.filter_new()
- self.cascade_filter.set_visible_func(self._cascade_filter_func)
-
- #sorting
- self.sorted = Gtk.TreeModelSort(self.cascade_filter)
- self.mimetypes_view.set_model(self.sorted)
-
- #allow multiple selection
- self.mimetypes_view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
-
- def _add_columns(self):
- #add columns
- column_type = ImageTextColumn(_("MimeType"), self.MTYPE_IMG, self.MTYPE)
- column_type.set_sort_column_id(self.MTYPE)
- self.mimetypes_view.append_column(column_type)
-
- column_program = ImageTextColumn(_("Program"), self.APP_IMG, self.APP)
- column_program.set_sort_column_id(self.APP)
- self.mimetypes_view.append_column(column_program)
-
- def _init_context_menu(self, builder):
- self.context_menu = builder.get_object("mimeview_context_menu")
- revert_item = builder.get_object("menuitem_revert")
- edit_item = builder.get_object("menuitem_edit")
-
- revert_item.connect("activate", lambda *args : self.on_items_reset())
- edit_item.connect("activate", lambda *args : self.on_items_edit())
-
- def _init_buttons(self, builder):
- reset_button = builder.get_object("reset_button")
- edit_button = builder.get_object("edit_button")
-
- reset_button.connect("clicked", lambda *args : self.on_items_reset())
- edit_button.connect("clicked", lambda *args : self.on_items_edit())
-
-
- def set_data(self):
- for m_type in mime_operations.get_known_mtypes():
- cat_id_ = get_category_id(m_type)
- m_type_img = mime_operations.get_mime_icon(m_type, ICON_SIZE)
-
- app = mime_operations.get_default_app(m_type)
- name, icon, *extra = mime_operations.get_app_bio(app, ICON_SIZE)
-
- self.list_store.append([cat_id_, m_type, m_type_img ,name, icon])
-
- def update_data(self, m_types_to_change):
- tree_iter = self.list_store.get_iter_first()
-
- while tree_iter:
- row_mtype = self.list_store.get_value(tree_iter, self.MTYPE)
-
- if row_mtype in m_types_to_change:
- app = mime_operations.get_default_app(row_mtype)
- name, icon, *extra = mime_operations.get_app_bio(app, ICON_SIZE)
-
- self.list_store.set_value(tree_iter, self.APP, name)
- self.list_store.set_value(tree_iter, self.APP_IMG, icon)
-
- tree_iter = self.list_store.iter_next(tree_iter)
-
-
- def _cascade_filter_func(self, *args, **kwargs):
- return self._category_filter_func(*args, **kwargs) \
- and self._associated_filter_func(*args, **kwargs)
-
- def _category_filter_func(self, model, iter, data):
- if self.current_category_id == ALL_CATEGORY_ID: return True
-
- return model[iter][self.ID] == self.current_category_id
-
- def _associated_filter_func(self, model, iter, data):
- if not self.hide_unassociated: return True
-
- return (model[iter][self.APP] is not None) and \
- (len(model[iter][self.APP]) > 0)
-
- def filter_category(self, category_id = ALL_CATEGORY_ID):
- self.current_category_id = category_id
- self.cascade_filter.refilter()
-
- def filter_associated(self, hide_unassociated = False):
- self.hide_unassociated = hide_unassociated
- self.cascade_filter.refilter()
-
-
- def on_mouse_clicked(self, widget, event):
- if event.button == 3: #right button
- self.show_context_menu(event)
- return True
- if event.type == Gdk.EventType._2BUTTON_PRESS: #double click
- self.on_items_edit()
- return True
-
- def on_key_pressed(self, widget, event):
- #do not reset selection
- keyname = Gdk.keyval_name(event.keyval)
- print(keyname, "pressed")
-
- if keyname in {"Return", "Enter", "space"}:
- self.on_items_edit()
- return True
- elif keyname in {"BackSpace", "Delete"}:
- self.on_items_reset()
- return True
-
-
- def show_context_menu(self, event):
- self.context_menu.popup( None, None, None, None,
- event.button, event.time)
-
- def get_selection(self):
- """return mime types from selected rows"""
-
- tree_selection = self.mimetypes_view.get_selection()
- (model, pathlist) = tree_selection.get_selected_rows()
-
- selected_mime_types = []
- for path in pathlist :
- tree_iter = model.get_iter(path)
- value = model.get_value(tree_iter, self.MTYPE)
-
- selected_mime_types.append(value)
-
- return selected_mime_types
-
-class AddAppDialog:
- def __init__(self, builder, parent_window, on_add_dialog_apply):
- self.builder = builder
- self.on_add_dialog_apply = on_add_dialog_apply
-
- self.dialog = self.builder.get_object("add_app_dialog")
- self.app_chooser = self.builder.get_object("appchooser_widget")
- self.app_chooser.set_show_all(True)
- self.app_chooser.connect("application-activated", self.on_apply)
- self.app_chooser.connect("application-selected", self.on_application_selected)
-
- self.dialog.set_transient_for(parent_window)
- self.dialog.connect("delete-event", self.hide)
-
- self.custom_entry = self.builder.get_object("custom_entry")
- self.custom_entry.connect("changed", self.on_custom_entry_changed)
-
- self.selected_app = None
- self.cline_text = ""
-
- self._init_buttons()
-
- def _init_buttons(self):
- self.cancel_button = self.builder.get_object("add_app_dialog_cancel_button")
- self.apply_button = self.builder.get_object("add_app_dialog_apply_button")
- self.file_chooser_button = self.builder.get_object("file_chooser_button")
-
- self.cancel_button.connect("clicked", self.hide)
- self.apply_button.connect("clicked", self.on_apply)
- self.file_chooser_button.connect("clicked", self.on_file_chooser)
-
- def show(self):
- self.dialog.run()
-
- def hide(self, *args):
- self.dialog.hide()
-
- return False #!!!
-
- def on_custom_entry_changed(self, *args):
- text = self.custom_entry.get_text()
- if text != self.cline_text:
- self.cline_text = text
-
- name = os.path.basename(self.cline_text)
- self.selected_app = mime_operations.get_app_from_cline(self.cline_text,
- name,
- False)
-
- def on_application_selected(self, widget, app):
- self.selected_app = app
-
- name, icon, self.cline_text = mime_operations.get_app_bio(app, ICON_SIZE)
- self.custom_entry.set_text(self.cline_text)
-
- def on_file_chooser(self, *args):
- f_dialog = Gtk.FileChooserDialog(_('Choose an application'),
- action = Gtk.FileChooserAction.OPEN,
- buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
- f_dialog.set_current_folder('/usr/bin')
- f_dialog.set_transient_for(self.dialog)
-
- if f_dialog.run() == Gtk.ResponseType.ACCEPT:
- self.custom_entry.set_text(f_dialog.get_filename())
-
- f_dialog.destroy()
-
- def on_apply(self, *args):
- #“application-activated”
- self.on_add_dialog_apply(self.selected_app)
- self.hide()
-
-
-class MimeSetDialog:
- APP_OBJ = 0
- APP_NAME = 1
- APP_ICON = 2
- APP_CL = 3
-
-
- def __init__(self, builder, parent_window, on_dialog_closed):
- self.builder = builder
-
- self.on_dialog_closed = on_dialog_closed
-
- self.dialog = self.builder.get_object("mimetype_set_dialog")
- self.dialog.set_transient_for(parent_window)
- self.dialog.connect("delete-event", self.hide)
-
- self.mtypes_dialog_label = builder.get_object("mtypes_dialog_label")
-
- self._init_buttons()
- self._init_view()
-
-
- #
- self.selection = None
- self.m_types = []
-
-
-
- self.add_app_dialog = None
-
-
- def _init_buttons(self):
- self.cancel_button = self.builder.get_object("dialog_cancel_button")
- self.apply_button = self.builder.get_object("dialog_apply_button")
- self.add_button = self.builder.get_object("dialog_add_button")
-
- self.cancel_button.connect("clicked", self.hide)
- self.apply_button.connect("clicked", self.on_row_activated)
- self.add_button.connect("clicked", self.on_add_app_clicked)
-
- def _init_view(self):
- self.alt_view = self.builder.get_object("alternatives_treeview")
- self.alt_view.connect("row_activated", self.on_row_activated)
-
- # desktop_file, app_name, icon
- self.list_store = Gtk.ListStore(Gio.AppInfo, str ,GdkPixbuf.Pixbuf, str)
-
- self.alt_view.set_model(self.list_store)
-
- column_app = ImageTextColumn(_("Categories"), self.APP_ICON, self.APP_NAME)
- column_app.set_sort_column_id(self.APP_NAME)
-
- self.alt_view.append_column(column_app)
-
- column_cl = TextColumn(_("Command line"), self.APP_CL)
- column_cl.set_sort_column_id(self.APP_CL)
-
- self.alt_view.append_column(column_cl)
-
- def show(self, m_types):
- self.m_types = m_types
- self.set_data()
- self.indicate_default()
-
- self.mtypes_dialog_label.set_text(" \n".join(self.m_types))
-
- self.selection = None
-
- self.dialog.run()
-
- def hide(self, *args):
- self.dialog.hide()
- self.list_store.clear()
-
- self.on_dialog_closed(self.selection, self.m_types)
-
- return True #!!!
-
- def set_data(self):
- #if self.vbox is not None: return
- apps = mime_operations.get_apps_for_mtypes(self.m_types)
-
- for app in apps:
- name, icon, cl = mime_operations.get_app_bio(app, ICON_SIZE)
-
- self.list_store.append([app, name, icon, cl])
-
- def indicate_default(self):
- if len(self.m_types) == 1:
- default_app = mime_operations.get_default_app(self.m_types[0])
- default_cl = default_app.get_commandline() if default_app is not None else ""
-
- #iteratw
- tree_iter = self.list_store.get_iter_first()
- while tree_iter:
- current_cl = self.list_store.get_value(tree_iter, self.APP_CL)
-
- if current_cl == default_cl:
- self.alt_view.get_selection().select_iter(tree_iter)
- return
- tree_iter = self.list_store.iter_next(tree_iter)
-
-
-
- def on_row_activated(self, *args):
- tree_selection = self.alt_view.get_selection()
- (model, pathlist) = tree_selection.get_selected_rows()
-
- for path in pathlist :
- tree_iter = model.get_iter(path)
- value = model.get_value(tree_iter, self.APP_OBJ)
- self.selection = value
-
- self.hide()
-
- def on_add_new_app(self, new_app):
- if new_app is not None:
- name, icon, cl = mime_operations.get_app_bio(new_app, ICON_SIZE)
- tree_iter = self.list_store.append([new_app, name, icon, cl])
- self.alt_view.get_selection().select_iter(tree_iter)
-
- def on_add_app_clicked(self, *args):
- if self.add_app_dialog is None:
- self.add_app_dialog = AddAppDialog(self.builder, self.dialog,\
- self.on_add_new_app)
-
- self.add_app_dialog.show()
-
-
-class MainWindow:
- def __init__(self):
- self.builder = Gtk.Builder()
- self.builder.set_translation_domain(APP)
- self.builder.add_from_file(GLADE_FILE)
- self.builder.connect_signals(self)
-
- self.window = self.builder.get_object("main_window")
- self.window.connect("delete-event", self.on_close)
-
-
- self.cat_view = CategoriesView(self.builder,
- self.on_category_changed)
-
- self.mime_view = MimeTypesView(self.builder,
- self.on_mtypes_edit,
- self.on_mtypes_reset)
-
- self.dialog = MimeSetDialog(self.builder,
- self.window,
- self.on_mtypes_edit_complete)
-
- # only show mimetypes that have associated application
- self.hide_unknown_flag = self.builder.get_object("show_associated_only_button")
- self.hide_unknown_flag.connect("toggled", self.on_hide_unknown_flag_toggled)
- self.hide_unknown_flag.set_active(SHOW_ONLY_ASSOCIATED)
-
-
- def run(self):
- self.window.show()
- Gtk.main()
-
- def on_close(self, *args):
- Gtk.main_quit()
-
- def on_hide_unknown_flag_toggled(self, flag_widget):
- hide_unknown = flag_widget.get_active()
- self.mime_view.filter_associated(hide_unknown)
-
- def on_category_changed(self, category_id):
- self.mime_view.filter_category(category_id[0])
-
- def on_mtypes_edit_complete(self, app, m_types):
- if app is not None:
- mime_operations.set_app_default(app, m_types)
- self.mime_view.update_data(m_types)
-
-
- def on_mtypes_edit(self):
- selected_mime_types = self.mime_view.get_selection()
-
- self.dialog.show(selected_mime_types)
-
- def on_mtypes_reset(self):
- selected_mime_types = self.mime_view.get_selection()
-
- for m_type in selected_mime_types:
- mime_operations.reset_association(m_type)
-
- self.mime_view.update_data(selected_mime_types)
-
-
-
-
-if __name__ == "__main__":
- window = MainWindow()
- window.run()
-
bgstack15