diff -Nru python-xapp-1.2.0/debian/changelog python-xapp-1.6.0/debian/changelog --- python-xapp-1.2.0/debian/changelog 2018-08-07 12:51:37.000000000 +0000 +++ python-xapp-1.6.0/debian/changelog 2019-08-29 15:04:54.000000000 +0000 @@ -1,3 +1,21 @@ +python-xapp (1.6.0-1) unstable; urgency=medium + + * New upstream release (1.6.0). + * Release to unstable + + -- Maximiliano Curia Thu, 29 Aug 2019 12:04:54 -0300 + +python-xapp (1.2.0-3) unstable; urgency=medium + + [ Maximiliano Curia ] + * Accept NMU changes (Closes: 935477) + * Release to unstable + + [ Sandro Tosi ] + * Drop Python 2 support + + -- Maximiliano Curia Mon, 26 Aug 2019 12:25:42 -0300 + python-xapp (1.2.0-2) unstable; urgency=medium * New revision diff -Nru python-xapp-1.2.0/debian/control python-xapp-1.6.0/debian/control --- python-xapp-1.2.0/debian/control 2018-08-07 12:51:37.000000000 +0000 +++ python-xapp-1.6.0/debian/control 2019-08-29 15:04:54.000000000 +0000 @@ -9,8 +9,6 @@ Build-Depends: debhelper (>= 11~), dh-python, - python-all (>= 2.6.6-3~), - python-psutil, python3-all, python3-psutil, Standards-Version: 4.1.4 @@ -18,12 +16,6 @@ Vcs-Browser: https://salsa.debian.org/cinnamon-team/python-xapp Vcs-Git: https://salsa.debian.org/cinnamon-team/python-xapp.git -Package: python-xapp -Architecture: all -Depends: python-psutil, ${misc:Depends}, ${python:Depends} -Description: Python 2 XApp library - This package contains the Python 2 version of the library. - Package: python3-xapp Architecture: all Depends: python3-psutil, ${misc:Depends}, ${python3:Depends} diff -Nru python-xapp-1.2.0/debian/gbp.conf python-xapp-1.6.0/debian/gbp.conf --- python-xapp-1.2.0/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ python-xapp-1.6.0/debian/gbp.conf 2019-08-29 15:04:54.000000000 +0000 @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff -Nru python-xapp-1.2.0/debian/not-installed python-xapp-1.6.0/debian/not-installed --- python-xapp-1.2.0/debian/not-installed 2018-08-07 12:51:37.000000000 +0000 +++ python-xapp-1.6.0/debian/not-installed 2019-08-29 15:04:54.000000000 +0000 @@ -1,3 +1 @@ *.pyc -# Moved to python3 -usr/lib/python3.*/ diff -Nru python-xapp-1.2.0/debian/python3-xapp.install python-xapp-1.6.0/debian/python3-xapp.install --- python-xapp-1.2.0/debian/python3-xapp.install 2018-08-07 12:51:37.000000000 +0000 +++ python-xapp-1.6.0/debian/python3-xapp.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/python3* diff -Nru python-xapp-1.2.0/debian/python-xapp.install python-xapp-1.6.0/debian/python-xapp.install --- python-xapp-1.2.0/debian/python-xapp.install 2018-08-07 12:51:37.000000000 +0000 +++ python-xapp-1.6.0/debian/python-xapp.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/python2* diff -Nru python-xapp-1.2.0/debian/rules python-xapp-1.6.0/debian/rules --- python-xapp-1.2.0/debian/rules 2018-08-07 12:51:37.000000000 +0000 +++ python-xapp-1.6.0/debian/rules 2019-08-29 15:04:54.000000000 +0000 @@ -1,4 +1,4 @@ #!/usr/bin/make -f %: - dh $@ --with python2,python3 --buildsystem=pybuild + dh $@ --with python3 --buildsystem=pybuild diff -Nru python-xapp-1.2.0/debian/salsa-ci.yml python-xapp-1.6.0/debian/salsa-ci.yml --- python-xapp-1.2.0/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ python-xapp-1.6.0/debian/salsa-ci.yml 2019-08-29 15:04:54.000000000 +0000 @@ -0,0 +1,7 @@ +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + +variables: + SALSA_CI_DISABLE_MISSING_BREAKS: 0 + SALSA_CI_DISABLE_RC_BUGS: 0 diff -Nru python-xapp-1.2.0/PKG-INFO python-xapp-1.6.0/PKG-INFO --- python-xapp-1.2.0/PKG-INFO 2018-04-16 14:41:23.000000000 +0000 +++ python-xapp-1.6.0/PKG-INFO 2019-05-17 15:01:40.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: python-xapp -Version: 1.0.0 +Version: 1.6.0 Summary: The Xapp Python library. Home-page: http://github.com/linuxmint/python-xapp Author: Linux Mint diff -Nru python-xapp-1.2.0/setup.py python-xapp-1.6.0/setup.py --- python-xapp-1.2.0/setup.py 2018-04-16 14:41:23.000000000 +0000 +++ python-xapp-1.6.0/setup.py 2019-05-17 15:01:40.000000000 +0000 @@ -3,8 +3,8 @@ from distutils.core import setup setup( name = "python-xapp", - version = "1.2.0", - description = "Python Xapp Library.", + version = "1.6.0", + description = "Python Xapp Library", maintainer = "Linux Mint", maintainer_email = "root@linuxmint.com", url = "http://github.com/linuxmint/python-xapp", diff -Nru python-xapp-1.2.0/xapp/GSettingsWidgets.py python-xapp-1.6.0/xapp/GSettingsWidgets.py --- python-xapp-1.2.0/xapp/GSettingsWidgets.py 1970-01-01 00:00:00.000000000 +0000 +++ python-xapp-1.6.0/xapp/GSettingsWidgets.py 2019-05-17 15:01:40.000000000 +0000 @@ -0,0 +1,199 @@ +#!/usr/bin/python3 + +from gi.repository import Gio, GLib +from xapp.SettingsWidgets import * + +CAN_BACKEND = ["Switch", "SpinButton", "Entry", "TextView", "FontButton", "Range", "ComboBox", + "ColorChooser", "FileChooser", "IconChooser"] + +# Monkey patch Gio.Settings object +def __setitem__(self, key, value): + # set_value() aborts the program on an unknown key + if key not in self: + raise KeyError('unknown key: %r' % (key,)) + + # determine type string of this key + range = self.get_range(key) + type_ = range.get_child_value(0).get_string() + v = range.get_child_value(1) + if type_ == 'type': + # v is boxed empty array, type of its elements is the allowed value type + assert v.get_child_value(0).get_type_string().startswith('a') + type_str = v.get_child_value(0).get_type_string()[1:] + elif type_ == 'enum': + # v is an array with the allowed values + assert v.get_child_value(0).get_type_string().startswith('a') + type_str = v.get_child_value(0).get_child_value(0).get_type_string() + elif type_ == 'flags': + # v is an array with the allowed values + assert v.get_child_value(0).get_type_string().startswith('a') + type_str = v.get_child_value(0).get_type_string() + elif type_ == 'range': + # type_str is a tuple giving the range + assert v.get_child_value(0).get_type_string().startswith('(') + type_str = v.get_child_value(0).get_type_string()[1] + + if not self.set_value(key, GLib.Variant(type_str, value)): + raise ValueError("value '%s' for key '%s' is outside of valid range" % (value, key)) + +def bind_with_mapping(self, key, widget, prop, flags, key_to_prop, prop_to_key): + self._ignore_key_changed = False + + def key_changed(settings, key): + if self._ignore_key_changed: + return + self._ignore_prop_changed = True + widget.set_property(prop, key_to_prop(self[key])) + self._ignore_prop_changed = False + + def prop_changed(widget, param): + if self._ignore_prop_changed: + return + self._ignore_key_changed = True + self[key] = prop_to_key(widget.get_property(prop)) + self._ignore_key_changed = False + + if not (flags & (Gio.SettingsBindFlags.SET | Gio.SettingsBindFlags.GET)): # ie Gio.SettingsBindFlags.DEFAULT + flags |= Gio.SettingsBindFlags.SET | Gio.SettingsBindFlags.GET + if flags & Gio.SettingsBindFlags.GET: + key_changed(self, key) + if not (flags & Gio.SettingsBindFlags.GET_NO_CHANGES): + self.connect('changed::' + key, key_changed) + if flags & Gio.SettingsBindFlags.SET: + widget.connect('notify::' + prop, prop_changed) + if not (flags & Gio.SettingsBindFlags.NO_SENSITIVITY): + self.bind_writable(key, widget, "sensitive", False) + +Gio.Settings.bind_with_mapping = bind_with_mapping +Gio.Settings.__setitem__ = __setitem__ + +class BinFileMonitor(GObject.GObject): + __gsignals__ = { + 'changed': (GObject.SignalFlags.RUN_LAST, None, ()), + } + def __init__(self): + super(BinFileMonitor, self).__init__() + + self.changed_id = 0 + + env = GLib.getenv("PATH") + + if env == None: + env = "/bin:/usr/bin:." + + self.paths = env.split(":") + + self.monitors = [] + + for path in self.paths: + file = Gio.File.new_for_path(path) + mon = file.monitor_directory(Gio.FileMonitorFlags.SEND_MOVED, None) + mon.connect("changed", self.queue_emit_changed) + self.monitors.append(mon) + + def _emit_changed(self): + self.emit("changed") + self.changed_id = 0 + return False + + def queue_emit_changed(self, file, other, event_type, data=None): + if self.changed_id > 0: + GLib.source_remove(self.changed_id) + self.changed_id = 0 + + self.changed_id = GLib.idle_add(self._emit_changed) + +file_monitor = None + +def get_file_monitor(): + global file_monitor + + if file_monitor == None: + file_monitor = BinFileMonitor() + + return file_monitor + +# This class is not meant to be used directly - it is only a backend for the +# settings widgets to enable them to bind attributes to gsettings keys. To use +# the gesttings backend, simply add the "GSettings" prefix to the beginning +# of the widget class name. The arguments of the backended class will be +# (label, schema, key, any additional widget-specific args and keyword args). +# (Note: this only works for classes that are gsettings compatible.) +# +# If you wish to make a new widget available to be backended, place it in the +# CAN_BACKEND list. In addition, you will need to add the following attributes +# to the widget class: +# +# bind_dir - (Gio.SettingsBindFlags) flags to define the binding direction or +# None if you don't want the setting bound (for example if the +# setting effects multiple attributes) +# bind_prop - (string) the attribute in the widget that will be bound to the +# setting. This property may be omitted if bind_dir is None +# bind_object - (optional) the object to which to bind to (only needed if the +# attribute to be bound is not a property of self.content_widget) +# map_get, map_set - (function, optional) a function to map between setting and +# bound attribute. May also be passed as a keyword arg during +# instantiation. These will be ignored if bind_dir=None +# set_rounding - (function, optional) To be used to set the digits to round to +# if the setting is an integer +class PXGSettingsBackend(object): + def bind_settings(self): + if hasattr(self, "set_rounding"): + vtype = self.settings.get_value(self.key).get_type_string() + if vtype in ["i", "u"]: + self.set_rounding(0) + if hasattr(self, "bind_object"): + bind_object = self.bind_object + else: + bind_object = self.content_widget + if hasattr(self, "map_get") or hasattr(self, "map_set"): + self.settings.bind_with_mapping(self.key, bind_object, self.bind_prop, self.bind_dir, self.map_get, self.map_set) + elif self.bind_dir != None: + self.settings.bind(self.key, bind_object, self.bind_prop, self.bind_dir) + else: + self.settings.connect("changed::"+self.key, self.on_setting_changed) + self.settings.bind_writable(self.key, bind_object, "sensitive", False) + self.on_setting_changed() + self.connect_widget_handlers() + + def set_value(self, value): + self.settings[self.key] = value + + def get_value(self): + return self.settings[self.key] + + def get_range(self): + range = self.settings.get_range(self.key) + if range[0] == "range": + return [range[1][0], range[1][1]] + else: + return None + + def on_setting_changed(self, *args): + raise NotImplementedError("SettingsWidget class must implement on_setting_changed().") + + def connect_widget_handlers(self, *args): + if self.bind_dir == None: + raise NotImplementedError("SettingsWidget classes with no .bind_dir must implement connect_widget_handlers().") + +def g_settings_factory(subclass): + class NewClass(globals()[subclass], PXGSettingsBackend): + def __init__(self, label, schema, key, *args, **kwargs): + self.key = key + if schema not in settings_objects: + settings_objects[schema] = Gio.Settings.new(schema) + self.settings = settings_objects[schema] + + if "map_get" in kwargs: + self.map_get = kwargs["map_get"] + del kwargs["map_get"] + if "map_set" in kwargs: + self.map_set = kwargs["map_set"] + del kwargs["map_set"] + + super(NewClass, self).__init__(label, *args, **kwargs) + self.bind_settings() + return NewClass + +for widget in CAN_BACKEND: + globals()["GSettings"+widget] = g_settings_factory(widget) diff -Nru python-xapp-1.2.0/xapp/__init__.py python-xapp-1.6.0/xapp/__init__.py --- python-xapp-1.2.0/xapp/__init__.py 2018-04-16 14:41:23.000000000 +0000 +++ python-xapp-1.6.0/xapp/__init__.py 2019-05-17 15:01:40.000000000 +0000 @@ -1,3 +1,3 @@ -__all__ = [ "os" ] +__all__ = [ "os", "GSettingsWidgets", "SettingsWidgets" ] -__version__ = "1.0.0" +__version__ = "1.6.0" diff -Nru python-xapp-1.2.0/xapp/SettingsWidgets.py python-xapp-1.6.0/xapp/SettingsWidgets.py --- python-xapp-1.2.0/xapp/SettingsWidgets.py 1970-01-01 00:00:00.000000000 +0000 +++ python-xapp-1.6.0/xapp/SettingsWidgets.py 2019-05-17 15:01:40.000000000 +0000 @@ -0,0 +1,719 @@ +#!/usr/bin/python3 + +import math +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('XApp', '1.0') +from gi.repository import Gio, Gtk, GObject, Gdk, GLib, XApp + +settings_objects = {} + +class EditableEntry (Gtk.Stack): + + __gsignals__ = { + 'changed': (GObject.SignalFlags.RUN_FIRST, None, + (str,)) + } + + def __init__ (self): + super(EditableEntry, self).__init__() + + self.set_transition_type(Gtk.StackTransitionType.CROSSFADE) + self.set_transition_duration(150) + + self.label = Gtk.Label() + self.entry = Gtk.Entry() + self.button = Gtk.Button() + + self.button.set_alignment(1.0, 0.5) + self.button.set_relief(Gtk.ReliefStyle.NONE) + self.add_named(self.button, "button"); + self.add_named(self.entry, "entry"); + self.set_visible_child_name("button") + self.editable = False + self.current_text = None + self.show_all() + + self.button.connect("released", self._on_button_clicked) + self.button.connect("activate", self._on_button_clicked) + self.entry.connect("activate", self._on_entry_validated) + self.entry.connect("changed", self._on_entry_changed) + self.entry.connect("focus-out-event", self._on_focus_lost) + + def set_text(self, text): + self.button.set_label(text) + self.entry.set_text(text) + self.current_text = text + + def _on_focus_lost(self, widget, event): + self.button.set_label(self.current_text) + self.entry.set_text(self.current_text) + + self.set_editable(False) + + def _on_button_clicked(self, button): + self.set_editable(True) + self.entry.grab_focus() + + def _on_entry_validated(self, entry): + self.set_editable(False) + self.emit("changed", entry.get_text()) + self.current_text = entry.get_text() + + def _on_entry_changed(self, entry): + self.button.set_label(entry.get_text()) + + def set_editable(self, editable): + if (editable): + self.set_visible_child_name("entry") + else: + self.set_visible_child_name("button") + self.editable = editable + + def set_tooltip_text(self, tooltip): + self.button.set_tooltip_text(tooltip) + + def get_editable(self): + return self.editable + + def get_text(self): + return self.entry.get_text() + +class SettingsStack(Gtk.Stack): + def __init__(self): + Gtk.Stack.__init__(self) + self.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) + self.set_transition_duration(150) + self.expand = True + +class SettingsRevealer(Gtk.Revealer): + def __init__(self, schema=None, key=None, values=None, check_func=None): + Gtk.Revealer.__init__(self) + + self.check_func = check_func + + self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15) + Gtk.Revealer.add(self, self.box) + + self.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN) + self.set_transition_duration(150) + + if schema: + self.settings = Gio.Settings.new(schema) + # if there aren't values or a function provided to determine visibility we can do a simple bind + if values is None and check_func is None: + self.settings.bind(key, self, "reveal-child", Gio.SettingsBindFlags.GET) + else: + self.values = values + self.settings.connect("changed::" + key, self.on_settings_changed) + self.on_settings_changed(self.settings, key) + + def add(self, widget): + self.box.pack_start(widget, False, True, 0) + + #only used when checking values + def on_settings_changed(self, settings, key): + value = settings.get_value(key).unpack() + if self.check_func is None: + self.set_reveal_child(value in self.values) + else: + self.set_reveal_child(self.check_func(value, self.values)) + +class SettingsPage(Gtk.Box): + def __init__(self): + Gtk.Box.__init__(self) + self.set_orientation(Gtk.Orientation.VERTICAL) + self.set_spacing(30) + self.set_margin_left(80) + self.set_margin_right(80) + self.set_margin_top(15) + self.set_margin_bottom(15) + + def add_section(self, title=None, subtitle=None): + section = SettingsSection(title, subtitle) + self.pack_start(section, False, False, 0) + + return section + + def add_reveal_section(self, title, schema=None, key=None, values=None, revealer=None): + section = SettingsSection(title) + if revealer is None: + revealer = SettingsRevealer(schema, key, values) + revealer.add(section) + section._revealer = revealer + self.pack_start(revealer, False, False, 0) + + return section + +class SettingsSection(Gtk.Box): + def __init__(self, title=None, subtitle=None): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(10) + + if title or subtitle: + header_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + header_box.set_spacing(5) + self.add(header_box) + + if title: + label = Gtk.Label() + label.set_markup("%s" % title) + label.set_alignment(0, 0.5) + header_box.add(label) + + if subtitle: + sub = Gtk.Label() + sub.set_text(subtitle) + sub.get_style_context().add_class("dim-label") + sub.set_alignment(0, 0.5) + header_box.add(sub) + + self.frame = Gtk.Frame() + self.frame.set_shadow_type(Gtk.ShadowType.IN) + frame_style = self.frame.get_style_context() + frame_style.add_class("view") + self.size_group = Gtk.SizeGroup() + self.size_group.set_mode(Gtk.SizeGroupMode.VERTICAL) + + self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.frame.add(self.box) + + self.need_separator = False + + def add_row(self, widget): + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + if self.need_separator: + vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)) + list_box = Gtk.ListBox() + list_box.set_selection_mode(Gtk.SelectionMode.NONE) + row = Gtk.ListBoxRow(can_focus=False) + row.add(widget) + if isinstance(widget, Switch): + list_box.connect("row-activated", widget.clicked) + list_box.add(row) + vbox.add(list_box) + self.box.add(vbox) + + if self.frame.get_parent() is None: + self.add(self.frame) + + self.need_separator = True + + def add_reveal_row(self, widget, schema=None, key=None, values=None, check_func=None, revealer=None): + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + if self.need_separator: + vbox.add(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)) + list_box = Gtk.ListBox() + list_box.set_selection_mode(Gtk.SelectionMode.NONE) + row = Gtk.ListBoxRow(can_focus=False) + row.add(widget) + if isinstance(widget, Switch): + list_box.connect("row-activated", widget.clicked) + list_box.add(row) + vbox.add(list_box) + if revealer is None: + revealer = SettingsRevealer(schema, key, values, check_func) + widget.revealer = revealer + revealer.add(vbox) + self.box.add(revealer) + + self.need_separator = True + + return revealer + + def add_note(self, text): + label = Gtk.Label() + label.set_alignment(0, 0.5) + label.set_markup(text) + label.set_line_wrap(True) + self.add(label) + return label + +class SettingsWidget(Gtk.Box): + def __init__(self, dep_key=None): + Gtk.Box.__init__(self) + self.set_orientation(Gtk.Orientation.HORIZONTAL) + self.set_spacing(20) + self.set_border_width(5) + self.set_margin_left(20) + self.set_margin_right(20) + + if dep_key: + self.set_dep_key(dep_key) + + def set_dep_key(self, dep_key): + flag = Gio.SettingsBindFlags.GET + if dep_key[0] == "!": + dep_key = dep_key[1:] + flag |= Gio.Settings.BindFlags.INVERT_BOOLEAN + + split = dep_key.split("/") + dep_settings = Gio.Settings.new(split[0]) + dep_settings.bind(split[1], self, "sensitive", flag) + + def add_to_size_group(self, group): + group.add_widget(self.content_widget) + + def fill_row(self): + self.set_border_width(0) + self.set_margin_left(0) + self.set_margin_right(0) + + def get_settings(self, schema): + global settings_objects + try: + return settings_objects[schema] + except: + settings_objects[schema] = Gio.Settings.new(schema) + return settings_objects[schema] + +class SettingsLabel(Gtk.Label): + def __init__(self, text=None): + Gtk.Label.__init__(self) + if text: + self.set_label(text) + + self.set_alignment(0.0, 0.5) + self.set_line_wrap(True) + + def set_label_text(self, text): + self.set_label(text) + +class Switch(SettingsWidget): + bind_prop = "active" + bind_dir = Gio.SettingsBindFlags.DEFAULT + + def __init__(self, label, dep_key=None, tooltip=""): + super(Switch, self).__init__(dep_key=dep_key) + + self.content_widget = Gtk.Switch(valign=Gtk.Align.CENTER) + self.label = SettingsLabel(label) + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, False, False, 0) + + self.set_tooltip_text(tooltip) + + def clicked(self, *args): + if self.is_sensitive(): + self.content_widget.set_active(not self.content_widget.get_active()) + +class SpinButton(SettingsWidget): + bind_prop = "value" + bind_dir = Gio.SettingsBindFlags.GET + + def __init__(self, label, units="", mini=None, maxi=None, step=1, page=None, size_group=None, dep_key=None, tooltip=""): + super(SpinButton, self).__init__(dep_key=dep_key) + + self.timer = None + + if units: + label += " (%s)" % units + self.label = SettingsLabel(label) + self.content_widget = Gtk.SpinButton() + + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, False, False, 0) + + range = self.get_range() + if mini == None or maxi == None: + mini = range[0] + maxi = range[1] + elif range is not None: + mini = max(mini, range[0]) + maxi = min(maxi, range[1]) + + if not page: + page = step + + self.content_widget.set_range(mini, maxi) + self.content_widget.set_increments(step, page) + + digits = 0 + if (step and '.' in str(step)): + digits = len(str(step).split('.')[1]) + self.content_widget.set_digits(digits) + + self.content_widget.connect("value-changed", self.apply_later) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + + def apply_later(self, *args): + def apply(self): + self.set_value(self.content_widget.get_value()) + self.timer = None + + if self.timer: + GLib.source_remove(self.timer) + self.timer = GLib.timeout_add(300, apply, self) + +class Entry(SettingsWidget): + bind_prop = "text" + bind_dir = Gio.SettingsBindFlags.DEFAULT + + def __init__(self, label, expand_width=False, size_group=None, dep_key=None, tooltip=""): + super(Entry, self).__init__(dep_key=dep_key) + + self.label = SettingsLabel(label) + self.content_widget = Gtk.Entry() + self.content_widget.set_valign(Gtk.Align.CENTER) + + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, expand_width, expand_width, 0) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + +class TextView(SettingsWidget): + bind_prop = "text" + bind_dir = Gio.SettingsBindFlags.DEFAULT + + def __init__(self, label, height=200, dep_key=None, tooltip=""): + super(TextView, self).__init__(dep_key=dep_key) + + self.set_orientation(Gtk.Orientation.VERTICAL) + self.set_spacing(8) + + self.label = Gtk.Label.new(label) + self.label.set_halign(Gtk.Align.CENTER) + + self.scrolledwindow = Gtk.ScrolledWindow(hadjustment=None, vadjustment=None) + self.scrolledwindow.set_size_request(width=-1, height=height) + self.scrolledwindow.set_policy(hscrollbar_policy=Gtk.PolicyType.AUTOMATIC, + vscrollbar_policy=Gtk.PolicyType.AUTOMATIC) + self.scrolledwindow.set_shadow_type(type=Gtk.ShadowType.ETCHED_IN) + self.content_widget = Gtk.TextView() + self.content_widget.set_border_width(3) + self.content_widget.set_wrap_mode(wrap_mode=Gtk.WrapMode.NONE) + self.bind_object = self.content_widget.get_buffer() + + self.pack_start(self.label, False, False, 0) + self.add(self.scrolledwindow) + self.scrolledwindow.add(self.content_widget) + self._value_changed_timer = None + +class FontButton(SettingsWidget): + bind_prop = "font-name" + bind_dir = Gio.SettingsBindFlags.DEFAULT + + def __init__(self, label, size_group=None, dep_key=None, tooltip=""): + super(FontButton, self).__init__(dep_key=dep_key) + + self.label = SettingsLabel(label) + + self.content_widget = Gtk.FontButton() + self.content_widget.set_valign(Gtk.Align.CENTER) + + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, False, False, 0) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + +class Range(SettingsWidget): + bind_prop = "value" + bind_dir = Gio.SettingsBindFlags.GET | Gio.SettingsBindFlags.NO_SENSITIVITY + + def __init__(self, label, min_label="", max_label="", mini=None, maxi=None, step=None, invert=False, log=False, show_value=True, dep_key=None, tooltip="", flipped=False, units=""): + super(Range, self).__init__(dep_key=dep_key) + + self.set_orientation(Gtk.Orientation.VERTICAL) + self.set_spacing(0) + + self.log = log + self.invert = invert + self.flipped = flipped + self.timer = None + self.value = 0 + + hbox = Gtk.Box() + + if units: + label += " ({})".format(units) + + self.label = Gtk.Label.new(label) + self.label.set_halign(Gtk.Align.CENTER) + + self.min_label= Gtk.Label() + self.max_label = Gtk.Label() + self.min_label.set_alignment(1.0, 0.75) + self.max_label.set_alignment(1.0, 0.75) + self.min_label.set_margin_right(6) + self.max_label.set_margin_left(6) + self.min_label.set_markup("%s" % min_label) + self.max_label.set_markup("%s" % max_label) + + range = self.get_range() + if mini == None or maxi == None: + mini = range[0] + maxi = range[1] + elif range is not None: + mini = max(mini, range[0]) + maxi = min(maxi, range[1]) + + if log: + mini = math.log(mini) + maxi = math.log(maxi) + if self.flipped: + self.map_get = lambda x: -1 * (math.log(x)) + self.map_set = lambda x: math.exp(x) + else: + self.map_get = lambda x: math.log(x) + self.map_set = lambda x: math.exp(x) + elif self.flipped: + self.map_get = lambda x: x * -1 + self.map_set = lambda x: x * -1 + + if self.flipped: + tmp_mini = mini + mini = maxi * -1 + maxi = tmp_mini * -1 + + if step is None: + self.step = (maxi - mini) * 0.02 + else: + self.step = math.log(step) if log else step + + self.content_widget = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, mini, maxi, self.step) + self.content_widget.set_inverted(invert) + self.content_widget.set_draw_value(show_value and not self.flipped) + self.bind_object = self.content_widget.get_adjustment() + + if invert: + self.step *= -1 # Gtk.Scale.new_with_range want a positive value, but our custom scroll handler wants a negative value + + hbox.pack_start(self.min_label, False, False, 0) + hbox.pack_start(self.content_widget, True, True, 0) + hbox.pack_start(self.max_label, False, False, 0) + + self.pack_start(self.label, False, False, 0) + self.pack_start(hbox, True, True, 6) + + self.content_widget.connect("scroll-event", self.on_scroll_event) + self.content_widget.connect("value-changed", self.apply_later) + + self.set_tooltip_text(tooltip) + + def apply_later(self, *args): + def apply(self): + if self.log: + self.set_value(math.exp(abs(self.content_widget.get_value()))) + else: + if self.flipped: + self.set_value(self.content_widget.get_value() * -1) + else: + self.set_value(self.content_widget.get_value()) + self.timer = None + + if self.timer: + GLib.source_remove(self.timer) + self.timer = GLib.timeout_add(300, apply, self) + + def on_scroll_event(self, widget, event): + found, delta_x, delta_y = event.get_scroll_deltas() + + # If you scroll up, delta_y < 0. This is a weird world + widget.set_value(widget.get_value() - delta_y * self.step) + + return True + + def add_mark(self, value, position, markup): + if self.log: + self.content_widget.add_mark(math.log(value), position, markup) + else: + self.content_widget.add_mark(value, position, markup) + + def set_rounding(self, digits): + if not self.log: + self.content_widget.set_round_digits(digits) + self.content_widget.set_digits(digits) + +class ComboBox(SettingsWidget): + bind_dir = None + + def __init__(self, label, options=[], valtype=None, size_group=None, dep_key=None, tooltip=""): + super(ComboBox, self).__init__(dep_key=dep_key) + + self.valtype = valtype + self.option_map = {} + + self.label = SettingsLabel(label) + + self.content_widget = Gtk.ComboBox() + renderer_text = Gtk.CellRendererText() + self.content_widget.pack_start(renderer_text, True) + self.content_widget.add_attribute(renderer_text, "text", 1) + + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, False, False, 0) + self.content_widget.set_valign(Gtk.Align.CENTER) + + self.set_options(options) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + + def on_my_value_changed(self, widget): + tree_iter = widget.get_active_iter() + if tree_iter != None: + self.value = self.model[tree_iter][0] + self.set_value(self.value) + + def on_setting_changed(self, *args): + self.value = self.get_value() + try: + self.content_widget.set_active_iter(self.option_map[self.value]) + except: + self.content_widget.set_active_iter(None) + + def connect_widget_handlers(self, *args): + self.content_widget.connect('changed', self.on_my_value_changed) + + def set_options(self, options): + if self.valtype is not None: + var_type = self.valtype + else: + # assume all keys are the same type (mixing types is going to cause an error somewhere) + var_type = type(options[0][0]) + self.model = Gtk.ListStore(var_type, str) + + for option in options: + self.option_map[option[0]] = self.model.append([option[0], option[1]]) + + self.content_widget.set_model(self.model) + self.content_widget.set_id_column(0) + +class ColorChooser(SettingsWidget): + bind_dir = None + + def __init__(self, label, legacy_string=False, size_group=None, dep_key=None, tooltip=""): + super(ColorChooser, self).__init__(dep_key=dep_key) + # note: Gdk.Color is deprecated in favor of Gdk.RGBA, but as the hex format is still used + # in some places (most notably the desktop background handling in cinnamon-desktop) we + # still support it for now by adding the legacy_string argument + self.legacy_string = legacy_string + + self.label = SettingsLabel(label) + self.content_widget = Gtk.ColorButton() + self.content_widget.set_use_alpha(True) + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, False, False, 0) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + + def on_setting_changed(self, *args): + color_string = self.get_value() + rgba = Gdk.RGBA() + rgba.parse(color_string) + self.content_widget.set_rgba(rgba) + + def connect_widget_handlers(self, *args): + self.content_widget.connect('color-set', self.on_my_value_changed) + + def on_my_value_changed(self, widget): + if self.legacy_string: + color_string = self.content_widget.get_color().to_string() + else: + color_string = self.content_widget.get_rgba().to_string() + self.set_value(color_string) + +class FileChooser(SettingsWidget): + bind_dir = None + + def __init__(self, label, dir_select=False, size_group=None, dep_key=None, tooltip=""): + super(FileChooser, self).__init__(dep_key=dep_key) + if dir_select: + action = Gtk.FileChooserAction.SELECT_FOLDER + else: + action = Gtk.FileChooserAction.OPEN + + self.label = SettingsLabel(label) + self.content_widget = Gtk.FileChooserButton(action=action) + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, False, False, 0) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + + def on_file_selected(self, *args): + self.set_value(self.content_widget.get_uri()) + + def on_setting_changed(self, *args): + self.content_widget.set_uri(self.get_value()) + + def connect_widget_handlers(self, *args): + self.content_widget.connect("file-set", self.on_file_selected) + +class IconChooser(SettingsWidget): + bind_prop = "icon" + bind_dir = Gio.SettingsBindFlags.DEFAULT + + def __init__(self, label, expand_width=False, size_group=None, dep_key=None, tooltip=""): + super(IconChooser, self).__init__(dep_key=dep_key) + + self.label = SettingsLabel(label) + + self.content_widget = XApp.IconChooserButton() + self.content_widget.set_icon_size(Gtk.IconSize.BUTTON) + + self.pack_start(self.label, False, False, 0) + self.pack_end(self.content_widget, expand_width, expand_width, 0) + + self.set_tooltip_text(tooltip) + + if size_group: + self.add_to_size_group(size_group) + +class Button(SettingsWidget): + def __init__(self, label, callback=None): + super(Button, self).__init__() + self.label = label + self.callback = callback + + self.content_widget = Gtk.Button(label=label) + self.pack_start(self.content_widget, True, True, 0) + self.content_widget.connect("clicked", self._on_button_clicked) + + def _on_button_clicked(self, *args): + if self.callback is not None: + self.callback(self) + elif hasattr(self, "on_activated"): + self.on_activated() + else: + print("warning: button '%s' does nothing" % self.label) + + def set_label(self, label): + self.label = label + self.content_widget.set_label(label) + +class Text(SettingsWidget): + def __init__(self, label, align=Gtk.Align.START): + super(Text, self).__init__() + self.label = label + + if align == Gtk.Align.END: + xalign = 1.0 + justification = Gtk.Justification.RIGHT + elif align == Gtk.Align.CENTER: + xalign = 0.5 + justification = Gtk.Justification.CENTER + else: # START and FILL align left + xalign = 0 + justification = Gtk.Justification.LEFT + + self.content_widget = Gtk.Label(label, halign=align, xalign=xalign, justify=justification) + self.content_widget.set_line_wrap(True) + self.pack_start(self.content_widget, True, True, 0)