2356 lines
91 KiB
Python
2356 lines
91 KiB
Python
#!/usr/bin/python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright © 2006-2007, 2009 Chris Jones <tortoise@tortuga>
|
|
# Copyright © 2008-2010 Francesco Fumanti <francesco.fumanti@gmx.net>
|
|
# Copyright © 2011-2012 Gerd Kohlberger <lowfi@chello.at>
|
|
# Copyright © 2011-2017 marmuta <marmvta@gmail.com>
|
|
#
|
|
# This file is part of Onboard.
|
|
#
|
|
# Onboard is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Onboard is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
""" Onboard preferences utility """
|
|
|
|
from __future__ import division, print_function, unicode_literals
|
|
|
|
import os
|
|
import sys
|
|
import copy
|
|
from subprocess import Popen
|
|
from xml.parsers.expat import ExpatError
|
|
from xml.dom import minidom
|
|
try:
|
|
import dbus.mainloop.glib
|
|
except ImportError:
|
|
pass
|
|
|
|
from Onboard.Version import require_gi_versions
|
|
require_gi_versions()
|
|
from gi.repository import GObject, Pango, Gdk, Gtk
|
|
|
|
from Onboard.LayoutLoaderSVG import LayoutLoaderSVG
|
|
from Onboard.SnippetView import SnippetView
|
|
from Onboard.Appearance import Theme, ColorScheme
|
|
from Onboard.Scanner import ScanMode, ScanDevice
|
|
from Onboard.XInput import XIDeviceManager, XIEventType
|
|
from Onboard.utils import unicode_str, open_utf8, escape_markup, \
|
|
XDGDirs
|
|
from Onboard.WindowUtils import show_ask_string_dialog, \
|
|
show_confirmation_dialog
|
|
from Onboard.UDevTracker import UDevTracker
|
|
|
|
|
|
app = "onboard"
|
|
|
|
### Logging ###
|
|
import logging
|
|
_logger = logging.getLogger("Settings")
|
|
###############
|
|
|
|
### Config Singleton ###
|
|
from Onboard.Config import Config, NumResizeHandles
|
|
config = Config()
|
|
########################
|
|
|
|
|
|
def LoadUI(filebase):
|
|
builder = Gtk.Builder()
|
|
builder.set_translation_domain(app)
|
|
builder.add_from_file(os.path.join(config.install_dir, filebase+".ui"))
|
|
return builder
|
|
|
|
def format_list_item(text, issystem):
|
|
if issystem:
|
|
return "<i>{0}</i>".format(text)
|
|
return text
|
|
|
|
|
|
class DialogBuilder(object):
|
|
"""
|
|
Utility class for simplified widget setup.
|
|
Has helpers for connecting widgets to ConfigObject properties, i.e.
|
|
indirectly to gsettings keys.
|
|
Mostly borrowed from Gerd Kohlberger's ScannerDialog.
|
|
"""
|
|
|
|
def __init__(self, builder):
|
|
self._builder = builder
|
|
|
|
def wid(self, name):
|
|
widget = self._builder.get_object(name)
|
|
if not widget:
|
|
_logger.error("widget '{}' not found, aborting".format(name))
|
|
assert(widget)
|
|
return widget
|
|
|
|
# push button
|
|
def bind_button(self, name, callback):
|
|
w = self.wid(name)
|
|
w.connect("clicked", callback)
|
|
|
|
# text entry
|
|
def bind_entry(self, name, config_object, key,
|
|
config_get_callback=None, config_set_callback=None):
|
|
w = self.wid(name)
|
|
if config_get_callback:
|
|
value = config_get_callback(config_object, key)
|
|
else:
|
|
value = getattr(config_object, key)
|
|
w.set_text(value)
|
|
w.connect("changed", self._bind_entry_callback,
|
|
config_object, key, config_set_callback)
|
|
getattr(config_object, key + '_notify_add')(
|
|
lambda x: self._notify_entry_callback(w, config_object, key,
|
|
config_get_callback))
|
|
|
|
def _notify_entry_callback(self, widget, config_object,
|
|
key, config_get_callback):
|
|
if config_get_callback:
|
|
value = config_get_callback(config_object, key)
|
|
else:
|
|
value = getattr(config_object, key)
|
|
widget.set_text(value)
|
|
|
|
def _bind_entry_callback(self, widget, config_object,
|
|
key, config_set_callback):
|
|
if config_set_callback:
|
|
config_set_callback(config_object, key, widget.get_text())
|
|
else:
|
|
setattr(config_object, key, widget.get_text())
|
|
|
|
# spin button
|
|
def bind_spin(self, name, config_object, key,
|
|
config_get_callback=None, config_set_callback=None):
|
|
w = self.wid(name)
|
|
if config_get_callback:
|
|
value = config_get_callback(config_object, key)
|
|
else:
|
|
value = getattr(config_object, key)
|
|
w.set_value(value)
|
|
w.connect("value-changed", self._bind_spin_callback,
|
|
config_object, key, config_set_callback)
|
|
getattr(config_object, key + '_notify_add')( \
|
|
lambda x: self._notify_spin_callback(w, config_object, key,
|
|
config_get_callback))
|
|
|
|
def _notify_spin_callback(self, widget, config_object,
|
|
key, config_get_callback):
|
|
if config_get_callback:
|
|
value = config_get_callback(config_object, key)
|
|
else:
|
|
value = getattr(config_object, key)
|
|
widget.set_value(value)
|
|
|
|
def _bind_spin_callback(self, widget, config_object,
|
|
key, config_set_callback):
|
|
if config_set_callback:
|
|
config_set_callback(config_object, key, widget.get_value())
|
|
else:
|
|
setattr(config_object, key, widget.get_value())
|
|
|
|
# scale
|
|
def bind_scale(self, name, config_object, key,
|
|
config_get_callback=None, config_set_callback=None):
|
|
w = self.wid(name)
|
|
if config_get_callback:
|
|
value = config_get_callback(config_object, key)
|
|
else:
|
|
value = getattr(config_object, key)
|
|
w.set_value(value)
|
|
w.connect("value-changed", self._bind_scale_callback,
|
|
config_object, key, config_set_callback)
|
|
getattr(config_object, key + '_notify_add')(w.set_value)
|
|
getattr(config_object, key + '_notify_add')(
|
|
lambda x: self._notify_scale_callback(w, config_object, key,
|
|
config_get_callback))
|
|
|
|
def _notify_scale_callback(self, widget, config_object,
|
|
key, config_get_callback):
|
|
if config_get_callback:
|
|
value = config_get_callback(config_object, key)
|
|
else:
|
|
value = getattr(config_object, key)
|
|
widget.set_value(value)
|
|
|
|
def _bind_scale_callback(self, widget, config_object,
|
|
key, config_set_callback):
|
|
value = widget.get_value()
|
|
if config_set_callback:
|
|
config_set_callback(config_object, key, value)
|
|
else:
|
|
setattr(config_object, key, value)
|
|
|
|
# checkbox
|
|
def bind_check(self, name, config_object, key,
|
|
config_get_callback=None, config_set_callback=None):
|
|
w = self.wid(name)
|
|
if config_get_callback:
|
|
active = config_get_callback(config_object, key)
|
|
else:
|
|
active = getattr(config_object, key)
|
|
w.set_active(active)
|
|
w.connect("toggled", self._bind_check_callback,
|
|
config_object, key, config_set_callback)
|
|
getattr(config_object, key + '_notify_add')( \
|
|
lambda x: self._notify_check_callback(w, config_object, key,
|
|
config_get_callback))
|
|
|
|
def _notify_check_callback(self, widget, config_object,
|
|
key, config_get_callback):
|
|
if config_get_callback:
|
|
active = config_get_callback(config_object, key)
|
|
else:
|
|
active = getattr(config_object, key)
|
|
widget.set_active(active)
|
|
|
|
def _bind_check_callback(self, widget, config_object,
|
|
key, config_set_callback):
|
|
if config_set_callback:
|
|
config_set_callback(config_object, key, widget.get_active())
|
|
else:
|
|
setattr(config_object, key, widget.get_active())
|
|
|
|
# combobox with id column
|
|
def bind_combobox_id(self, name, config_object, key,
|
|
config_get_callback=None, config_set_callback=None):
|
|
w = self.wid(name)
|
|
|
|
if config_get_callback:
|
|
id = config_get_callback(config_object, key)
|
|
else:
|
|
id = str(getattr(config_object, key))
|
|
|
|
if not config_set_callback:
|
|
config_set_callback = self.bind_combobox_config_set
|
|
|
|
w.set_active_id(id)
|
|
w.connect("changed", self._bind_combobox_callback,
|
|
config_object, key, config_set_callback)
|
|
getattr(config_object, key + '_notify_add')( \
|
|
lambda x: self._notify_combobox_callback(w, config_object, key,
|
|
config_get_callback))
|
|
def _notify_combobox_callback(self, widget,
|
|
config_object, key, config_get_callback):
|
|
if config_get_callback:
|
|
id = config_get_callback(config_object, key)
|
|
else:
|
|
id = str(getattr(config_object, key))
|
|
widget.set_active_id(id)
|
|
|
|
def _bind_combobox_callback(self, widget,
|
|
config_object, key, config_set_callback):
|
|
id = widget.get_active_id()
|
|
config_set_callback(config_object, key, id)
|
|
|
|
def bind_combobox_config_set(self, config_object, key, id):
|
|
assert(not id is None) # make sure ID-Column is 0
|
|
if not id is None:
|
|
setattr(config_object, key, int(id))
|
|
|
|
def get_tree_view_selection(self, view, column):
|
|
sel = view.get_selection()
|
|
if sel:
|
|
it = sel.get_selected()[1]
|
|
if it:
|
|
return view.get_model().get_value(it, column)
|
|
return None
|
|
|
|
def select_tree_view_row(self, view, column, value, _it=None):
|
|
model = view.get_model()
|
|
if _it is None:
|
|
_it = model.get_iter_first()
|
|
while _it:
|
|
child_it = model.iter_children(_it)
|
|
if child_it:
|
|
self.select_tree_view_row(view, column, value, child_it)
|
|
|
|
fn = model.get_value(_it, column)
|
|
if fn == value:
|
|
sel = view.get_selection()
|
|
if sel:
|
|
sel.select_iter(_it)
|
|
|
|
_it = model.iter_next(_it)
|
|
|
|
|
|
class Settings(DialogBuilder):
|
|
|
|
# column ids of the layout view
|
|
LAYOUT_COL_NAME = 0
|
|
LAYOUT_COL_SUMMARY = 1
|
|
LAYOUT_COL_FILENAME = 2
|
|
LAYOUT_COL_HAS_ABOUT_INFO = 3
|
|
LAYOUT_COL_IS_ROW_SENSITIVE = 4
|
|
|
|
|
|
def __init__(self, mainwin):
|
|
self.themes = {} # cache of theme objects
|
|
|
|
# Use D-bus main loop by default
|
|
if "dbus" in globals():
|
|
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
|
else:
|
|
_logger.warning("D-Bus bindings unavailable.")
|
|
|
|
# finish config initialization
|
|
config.init()
|
|
|
|
# init dialog builder
|
|
builder = LoadUI("settings")
|
|
DialogBuilder.__init__(self, builder)
|
|
|
|
self.window = builder.get_object("settings_window")
|
|
Gtk.Window.set_default_icon_name("onboard")
|
|
self.window.set_title(_("Onboard Preferences"))
|
|
|
|
# General tab
|
|
self.status_icon_toggle = builder.get_object("status_icon_toggle")
|
|
self.status_icon_toggle.set_active(config.show_status_icon)
|
|
config.show_status_icon_notify_add(self.status_icon_toggle.set_active)
|
|
|
|
self.start_minimized_toggle = builder.get_object(
|
|
"start_minimized_toggle")
|
|
self.start_minimized_toggle.set_active(config.start_minimized)
|
|
config.start_minimized_notify_add(
|
|
self.start_minimized_toggle.set_active)
|
|
|
|
self.icon_palette_toggle = builder.get_object("icon_palette_toggle")
|
|
self.icon_palette_toggle.set_active(config.icp.in_use)
|
|
config.icp.in_use_notify_add(self.icon_palette_toggle.set_active)
|
|
|
|
self.onboard_xembed_toggle = \
|
|
builder.get_object("onboard_xembed_toggle")
|
|
self.onboard_xembed_toggle.set_active(config.onboard_xembed_enabled)
|
|
config.onboard_xembed_enabled_notify_add(
|
|
self.onboard_xembed_toggle.set_active)
|
|
|
|
self.show_tooltips_toggle = builder.get_object("show_tooltips_toggle")
|
|
self.show_tooltips_toggle.set_active(config.show_tooltips)
|
|
config.show_tooltips_notify_add(self.show_tooltips_toggle.set_active)
|
|
|
|
self.bind_combobox_id("status_icon_provider_combobox",
|
|
config, "status_icon_provider")
|
|
# window tab
|
|
self.window_decoration_toggle = \
|
|
builder.get_object("window_decoration_toggle")
|
|
self.window_decoration_toggle.set_active(
|
|
config.window.window_decoration)
|
|
config.window.window_decoration_notify_add(
|
|
lambda x: [self.window_decoration_toggle.set_active(x),
|
|
self.update_window_widgets()])
|
|
|
|
self.window_state_sticky_toggle = \
|
|
builder.get_object("window_state_sticky_toggle")
|
|
self.window_state_sticky_toggle.set_active(
|
|
config.window.window_state_sticky)
|
|
config.window.window_state_sticky_notify_add(
|
|
self.window_state_sticky_toggle.set_active)
|
|
|
|
self.force_to_top_toggle = builder.get_object("force_to_top_toggle")
|
|
self.force_to_top_toggle.set_active(config.is_force_to_top())
|
|
config.window.force_to_top_notify_add(lambda x: \
|
|
[self.force_to_top_toggle.set_active(x),
|
|
self.update_window_widgets()])
|
|
|
|
self.keep_aspect_ratio_toggle = builder.get_object(
|
|
"keep_aspect_ratio_toggle")
|
|
self.keep_aspect_ratio_toggle.set_active(config.window.keep_aspect_ratio)
|
|
config.window.keep_aspect_ratio_notify_add(
|
|
self.keep_aspect_ratio_toggle.set_active)
|
|
|
|
self.transparent_background_toggle = \
|
|
builder.get_object("transparent_background_toggle")
|
|
self.transparent_background_toggle.set_active(config.window.transparent_background)
|
|
config.window.transparent_background_notify_add(lambda x:
|
|
[self.transparent_background_toggle.set_active(x),
|
|
self.update_window_widgets()])
|
|
|
|
self.transparency_spinbutton = builder.get_object("transparency_spinbutton")
|
|
self.transparency_spinbutton.set_value(config.window.transparency)
|
|
config.window.transparency_notify_add(self.transparency_spinbutton.set_value)
|
|
|
|
self.background_transparency_spinbutton = \
|
|
builder.get_object("background_transparency_spinbutton")
|
|
self.background_transparency_spinbutton.set_value(config.window.background_transparency)
|
|
config.window.background_transparency_notify_add(self.background_transparency_spinbutton.set_value)
|
|
|
|
self.inactivity_frame = builder.get_object("inactive_behavior_frame")
|
|
|
|
self.enable_inactive_transparency_toggle = \
|
|
builder.get_object("enable_inactive_transparency_toggle")
|
|
self.enable_inactive_transparency_toggle.set_active( \
|
|
config.window.enable_inactive_transparency)
|
|
config.window.enable_inactive_transparency_notify_add(lambda x: \
|
|
[self.enable_inactive_transparency_toggle.set_active(x),
|
|
self.update_window_widgets()])
|
|
|
|
self.inactive_transparency_spinbutton = \
|
|
builder.get_object("inactive_transparency_spinbutton")
|
|
self.inactive_transparency_spinbutton.set_value(config.window.inactive_transparency)
|
|
config.window.inactive_transparency_notify_add(self.inactive_transparency_spinbutton.set_value)
|
|
|
|
self.inactive_transparency_delay_spinbutton = \
|
|
builder.get_object("inactive_transparency_delay_spinbutton")
|
|
self.inactive_transparency_delay_spinbutton.set_value(config.window.inactive_transparency_delay)
|
|
config.window.inactive_transparency_delay_notify_add(self.inactive_transparency_delay_spinbutton.set_value)
|
|
|
|
def _get(config_object, key):
|
|
handles = getattr(config_object, key)
|
|
return str(config.window_handles_to_num_handles(handles))
|
|
|
|
def _set(config_object, key, value):
|
|
handles = config.num_handles_to_window_handles(int(value))
|
|
setattr(config_object, key, handles[:])
|
|
|
|
self.bind_combobox_id("num_window_handles_combobox",
|
|
config.window, "window_handles",
|
|
_get, _set)
|
|
|
|
def _get(config_object, key):
|
|
handles = getattr(config_object, key)
|
|
return str(config.window_handles_to_num_handles(handles))
|
|
|
|
def _set(config_object, key, value):
|
|
handles = config.num_handles_to_icon_palette_handles(int(value))
|
|
setattr(config_object, key, handles[:])
|
|
|
|
self.bind_combobox_id("num_icon_palette_handles_combobox",
|
|
config.icp, "window_handles",
|
|
_get, _set)
|
|
|
|
# Keyboard - first page
|
|
self.bind_check("touch_feedback_enabled_toggle",
|
|
config.keyboard, "touch_feedback_enabled")
|
|
|
|
def _set(config_object, key, value):
|
|
if value < 20:
|
|
value = 0
|
|
setattr(config_object, key, value)
|
|
self._update_touch_feedback_size_label()
|
|
|
|
self.bind_scale("touch_feedback_size_scale",
|
|
config.keyboard, "touch_feedback_size",
|
|
None, _set)
|
|
self.bind_check("audio_feedback_enabled_toggle",
|
|
config.keyboard, "audio_feedback_enabled")
|
|
self.bind_check("audio_feedback_place_in_space_toggle",
|
|
config.keyboard, "audio_feedback_place_in_space")
|
|
self.bind_check("show_secondary_labels_toggle",
|
|
config.keyboard, "show_secondary_labels")
|
|
self.bind_check("upper_case_on_right_click_toggle",
|
|
config.keyboard, "key_press_modifiers",
|
|
config_get_callback=lambda co, key:
|
|
co.can_upper_case_on_button(3),
|
|
config_set_callback=lambda co, key, value:
|
|
co.set_upper_case_on_button(3, value))
|
|
|
|
# Keyboard - Advanced page
|
|
self.bind_combobox_id("default_key_action_combobox",
|
|
config.keyboard, "default_key_action")
|
|
self.bind_combobox_id("key_synth_combobox",
|
|
config.keyboard, "key_synth")
|
|
|
|
def get_sticky_key_behavior(config_object, key):
|
|
behaviors = getattr(config_object, key)
|
|
return behaviors.get("all", "")
|
|
def set_sticky_key_behavior(config_object, key, value):
|
|
behaviors = getattr(config_object, key).copy()
|
|
behaviors["all"] = value
|
|
setattr(config_object, key, behaviors)
|
|
self.bind_combobox_id("sticky_key_behavior_combobox",
|
|
config.keyboard, "sticky_key_behavior",
|
|
get_sticky_key_behavior, set_sticky_key_behavior)
|
|
|
|
self.bind_spin("sticky_key_release_delay_spinbutton",
|
|
config.keyboard, "sticky_key_release_delay")
|
|
|
|
self.bind_spin("sticky_key_release_on_hide_delay_spinbutton",
|
|
config.keyboard, "sticky_key_release_on_hide_delay")
|
|
|
|
self.bind_combobox_id("touch_input_combobox",
|
|
config.keyboard, "touch_input")
|
|
|
|
def on_input_event_source_set(config_object, key, value):
|
|
self.bind_combobox_config_set(config_object, key, value)
|
|
self.update_window_widgets()
|
|
|
|
self.bind_combobox_id("input_event_source_combobox",
|
|
config.keyboard, "input_event_source",
|
|
config_set_callback=on_input_event_source_set)
|
|
|
|
def get_inter_key_stroke_delay(config_object, key):
|
|
return getattr(config_object, key) * 1000.0
|
|
|
|
def set_inter_key_stroke_delay(config_object, key, value):
|
|
setattr(config_object, key, value / 1000.0)
|
|
|
|
self.bind_spin("inter_key_stroke_delay_spinbutton",
|
|
config.keyboard, "inter_key_stroke_delay",
|
|
get_inter_key_stroke_delay, set_inter_key_stroke_delay)
|
|
|
|
# Auto-show
|
|
self._page_auto_show = PageAutoShow(self, builder)
|
|
|
|
# word suggestions
|
|
self._page_word_suggestions = PageWordSuggestions(self, builder)
|
|
|
|
# window, docking
|
|
self.docking_enabled_toggle = \
|
|
builder.get_object("docking_enabled_toggle")
|
|
self.docking_box = builder.get_object("docking_box")
|
|
|
|
def on_docking_enabled_config_set(config_object, key, value):
|
|
setattr(config_object, key, value)
|
|
self.update_window_widgets()
|
|
self.bind_check("docking_enabled_toggle",
|
|
config.window, "docking_enabled",
|
|
config_set_callback = on_docking_enabled_config_set)
|
|
self.bind_button("docking_settings_button",
|
|
lambda widget: DockingDialog().run(self.window))
|
|
|
|
# layout view
|
|
self.layout_view = builder.get_object("layout_view")
|
|
self.user_layout_root = config.get_user_layout_dir()
|
|
config.layout_notify_add(lambda x: self.update_layout_view_selection())
|
|
self.update_layout_view()
|
|
self.update_layout_widgets()
|
|
|
|
# theme view
|
|
self.theme_view = builder.get_object("theme_view")
|
|
self.theme_view.append_column(Gtk.TreeViewColumn(None,
|
|
Gtk.CellRendererText(),
|
|
markup=0))
|
|
self.delete_theme_button = builder.get_object("delete_theme_button")
|
|
self.delete_theme_button
|
|
self.customize_theme_button = \
|
|
builder.get_object("customize_theme_button")
|
|
|
|
self.update_themeList()
|
|
config.theme_notify_add(self.on_theme_changed)
|
|
|
|
self.system_theme_tracking_enabled_toggle = \
|
|
builder.get_object("system_theme_tracking_enabled_toggle")
|
|
self.system_theme_tracking_enabled_toggle.set_active( \
|
|
config.system_theme_tracking_enabled)
|
|
config.system_theme_tracking_enabled_notify_add( \
|
|
self.on_system_theme_tracking_changed)
|
|
|
|
# Snippets
|
|
self.snippet_view = SnippetView()
|
|
builder.get_object("snippet_scrolled_window").add(self.snippet_view)
|
|
|
|
# Universal Access
|
|
scanner_enabled = builder.get_object("scanner_enabled")
|
|
scanner_enabled.set_active(config.scanner.enabled)
|
|
config.scanner.enabled_notify_add(scanner_enabled.set_active)
|
|
|
|
self.hide_click_type_window_toggle = \
|
|
builder.get_object("hide_click_type_window_toggle")
|
|
self.hide_click_type_window_toggle.set_active( \
|
|
config.universal_access.hide_click_type_window)
|
|
config.universal_access.hide_click_type_window_notify_add( \
|
|
self.hide_click_type_window_toggle.set_active)
|
|
|
|
self.enable_click_type_window_on_exit_toggle = \
|
|
builder.get_object("enable_click_type_window_on_exit_toggle")
|
|
self.enable_click_type_window_on_exit_toggle.set_active( \
|
|
config.universal_access.enable_click_type_window_on_exit)
|
|
config.universal_access.enable_click_type_window_on_exit_notify_add( \
|
|
self.enable_click_type_window_on_exit_toggle.set_active)
|
|
|
|
if config.mousetweaks:
|
|
self.bind_spin("hover_click_delay_spinbutton",
|
|
config.mousetweaks, "dwell_time")
|
|
self.bind_spin("hover_click_motion_threshold_spinbutton",
|
|
config.mousetweaks, "dwell_threshold")
|
|
|
|
# select last active page
|
|
page = config.current_settings_page
|
|
self.settings_notebook = builder.get_object("settings_notebook")
|
|
self.settings_notebook.set_current_page(page)
|
|
|
|
self.pages_view = builder.get_object("pages_view")
|
|
sel = self.pages_view.get_selection()
|
|
if sel:
|
|
sel.select_path(Gtk.TreePath(page))
|
|
|
|
# On startup with Gtk 3.14: "Gtk-Message: GtkDialog mapped
|
|
# without a transient parent. This is discouraged."
|
|
# Preferences is a top level dialog, we don't have a parent.
|
|
# No idea how to appease Gtk here.
|
|
#self.window.set_transient_for(None)
|
|
|
|
self.window.show_all()
|
|
|
|
# update after show_all to apply widget visibility
|
|
self.update_window_widgets()
|
|
|
|
# disable hover click controls if mousetweaks isn't installed
|
|
frame = builder.get_object("hover_click_frame")
|
|
frame.set_sensitive(bool(config.mousetweaks))
|
|
|
|
self.window.set_keep_above(not mainwin)
|
|
|
|
self.window.connect("destroy", Gtk.main_quit)
|
|
builder.connect_signals(self)
|
|
|
|
_logger.info("Entering mainloop of Onboard-settings")
|
|
Gtk.main()
|
|
|
|
def on_pages_view_cursor_changed(self, widget):
|
|
sel = widget.get_selection()
|
|
if sel:
|
|
paths = sel.get_selected_rows()[1]
|
|
if paths:
|
|
page_num = paths[0].get_indices()[0]
|
|
config.current_settings_page = page_num
|
|
self.settings_notebook.set_current_page(page_num)
|
|
|
|
def on_settings_notebook_switch_page(self, widget, gpage, page_num):
|
|
config.current_settings_page = page_num
|
|
|
|
def on_snippet_add_button_clicked(self, event):
|
|
_logger.info("Snippet add button clicked")
|
|
self.snippet_view.append("","")
|
|
|
|
def on_snippet_remove_button_clicked(self, event):
|
|
_logger.info("Snippet remove button clicked")
|
|
self.snippet_view.remove_selected()
|
|
|
|
def on_status_icon_toggled(self,widget):
|
|
config.show_status_icon = widget.get_active()
|
|
self.update_window_widgets()
|
|
|
|
def on_start_minimized_toggled(self,widget):
|
|
config.start_minimized = widget.get_active()
|
|
|
|
def on_icon_palette_toggled(self, widget):
|
|
if not config.is_icon_palette_last_unhide_option():
|
|
config.icp.in_use = widget.get_active()
|
|
self.update_window_widgets()
|
|
|
|
def on_modeless_gksu_toggled(self, widget):
|
|
config.modeless_gksu = widget.get_active()
|
|
|
|
def on_xembed_onboard_toggled(self, widget):
|
|
config.enable_gss_embedding(widget.get_active())
|
|
|
|
def on_show_tooltips_toggled(self, widget):
|
|
config.show_tooltips = widget.get_active()
|
|
|
|
def on_window_decoration_toggled(self, widget):
|
|
if not config.is_force_to_top():
|
|
config.window.window_decoration = widget.get_active()
|
|
self.update_window_widgets()
|
|
|
|
def on_window_state_sticky_toggled(self, widget):
|
|
if not config.is_force_to_top():
|
|
config.window.window_state_sticky = widget.get_active()
|
|
|
|
def on_force_to_top_toggled(self, widget):
|
|
if not config.is_docking_enabled():
|
|
config.window.force_to_top = widget.get_active()
|
|
self.update_window_widgets()
|
|
|
|
def on_keep_aspect_ratio_toggled(self, widget):
|
|
config.window.keep_aspect_ratio = widget.get_active()
|
|
|
|
def _update_touch_feedback_size_label(self, value=0):
|
|
value = config.keyboard.touch_feedback_size
|
|
|
|
# touch_feedback_size_label: 0=auto, i.e. let Onboard guess
|
|
# the size of the label popup.
|
|
s = str(round(value)) if value else _("auto")
|
|
self.wid("touch_feedback_size_label").set_text(s)
|
|
|
|
def update_window_widgets(self):
|
|
force_to_top = config.is_force_to_top()
|
|
|
|
# general
|
|
w = self.wid("status_icon_provider_box")
|
|
w.set_sensitive(config.show_status_icon)
|
|
|
|
self.icon_palette_toggle.set_sensitive(
|
|
not config.is_icon_palette_last_unhide_option())
|
|
active = config.is_icon_palette_in_use()
|
|
if self.icon_palette_toggle.get_active() != active:
|
|
self.icon_palette_toggle.set_active(active)
|
|
|
|
# window
|
|
self.window_decoration_toggle.set_sensitive(not force_to_top)
|
|
active = config.has_window_decoration()
|
|
if self.window_decoration_toggle.get_active() != active:
|
|
self.window_decoration_toggle.set_active(active)
|
|
|
|
self.window_state_sticky_toggle.set_sensitive( not force_to_top)
|
|
active = config.get_sticky_state()
|
|
if self.window_state_sticky_toggle.get_active() != active:
|
|
self.window_state_sticky_toggle.set_active(active)
|
|
|
|
self.force_to_top_toggle.set_sensitive(not config.is_docking_enabled())
|
|
active = force_to_top
|
|
if self.force_to_top_toggle.get_active() != active:
|
|
self.force_to_top_toggle.set_active(active)
|
|
|
|
self.background_transparency_spinbutton.set_sensitive( \
|
|
not config.has_window_decoration())
|
|
self.start_minimized_toggle.set_sensitive(\
|
|
not config.auto_show.enabled)
|
|
|
|
self.inactivity_frame.set_sensitive(not config.scanner.enabled)
|
|
active = config.is_inactive_transparency_enabled()
|
|
if self.enable_inactive_transparency_toggle.get_active() != active:
|
|
self.enable_inactive_transparency_toggle.set_active(active)
|
|
|
|
# keyboard
|
|
self._update_touch_feedback_size_label()
|
|
|
|
# auto-show
|
|
self._page_auto_show.update_ui()
|
|
|
|
def update_all_widgets(self):
|
|
pass
|
|
|
|
def on_transparent_background_toggled(self, widget):
|
|
config.window.transparent_background = widget.get_active()
|
|
self.update_window_widgets()
|
|
|
|
def on_transparency_changed(self, widget):
|
|
config.window.transparency = widget.get_value()
|
|
|
|
def on_background_transparency_spinbutton_changed(self, widget):
|
|
config.window.background_transparency = widget.get_value()
|
|
|
|
def on_enable_inactive_transparency_toggled(self, widget):
|
|
if not config.scanner.enabled:
|
|
config.window.enable_inactive_transparency = widget.get_active()
|
|
|
|
def on_inactive_transparency_changed(self, widget):
|
|
config.window.inactive_transparency = widget.get_value()
|
|
|
|
def on_inactive_transparency_delay_changed(self, widget):
|
|
config.window.inactive_transparency_delay = widget.get_value()
|
|
|
|
def on_layout_new_button_clicked(self, widget):
|
|
name = self.get_selected_layout_value(self.LAYOUT_COL_NAME)
|
|
filename = self.get_selected_layout_filename()
|
|
if filename:
|
|
new_layout_name = show_ask_string_dialog(
|
|
_format("Copy layout '{}' to this new name:", name), self.window)
|
|
if new_layout_name:
|
|
new_filename = \
|
|
os.path.join(self.user_layout_root, new_layout_name) + \
|
|
config.LAYOUT_FILE_EXTENSION
|
|
LayoutLoaderSVG.copy_layout(filename, new_filename)
|
|
self.update_layout_view()
|
|
self.open_user_layout_dir()
|
|
|
|
def on_layout_remove_button_clicked(self, event):
|
|
name = self.get_selected_layout_value(self.LAYOUT_COL_NAME)
|
|
filename = self.get_selected_layout_filename()
|
|
if filename:
|
|
question = _format("Delete layout '{}'?", name)
|
|
if show_confirmation_dialog(question, self.window):
|
|
LayoutLoaderSVG.remove_layout(filename)
|
|
|
|
config.layout_filename = self.layout_view_model[0][1] \
|
|
if len(self.layout_view_model) else ""
|
|
self.update_layout_view()
|
|
|
|
def open_user_layout_dir(self):
|
|
user_layout_dir = self.user_layout_root
|
|
XDGDirs.assure_user_dir_exists(user_layout_dir)
|
|
cmd = ["xdg-open", user_layout_dir]
|
|
try:
|
|
Popen(cmd)
|
|
except OSError as e:
|
|
_logger.warning("Failed to run '{}': {}" \
|
|
.format(" ".join(cmd), unicode_str(e)))
|
|
|
|
def on_layout_folder_button_clicked(self, widget):
|
|
self.open_user_layout_dir()
|
|
|
|
def update_layout_widgets(self):
|
|
filename = self.get_selected_layout_filename()
|
|
self.wid("layout_new_button").set_sensitive(not filename is None)
|
|
self.wid("layout_remove_button").set_sensitive(not filename is None and \
|
|
os.access(filename, os.W_OK))
|
|
has_about_info = bool( \
|
|
self.get_selected_layout_value(self.LAYOUT_COL_HAS_ABOUT_INFO))
|
|
self.wid("layout_about_button").set_sensitive(has_about_info)
|
|
|
|
def on_scanner_enabled_toggled(self, widget):
|
|
config.scanner.enabled = widget.get_active()
|
|
self.update_window_widgets()
|
|
|
|
def on_scanner_settings_clicked(self, widget):
|
|
ScannerDialog().run(self.window)
|
|
|
|
def on_hide_click_type_window_toggled(self, widget):
|
|
config.universal_access.hide_click_type_window = widget.get_active()
|
|
|
|
def on_enable_click_type_window_on_exit_toggle(self, widget):
|
|
config.universal_access.enable_click_type_window_on_exit = widget.get_active()
|
|
|
|
def on_hover_click_settings_clicked(self, widget):
|
|
filename = "gnome-control-center"
|
|
try:
|
|
Popen([filename, "universal-access"])
|
|
except OSError as e:
|
|
_logger.warning(_format("System settings not found ({}): {}",
|
|
filename, unicode_str(e)))
|
|
|
|
def on_close_button_clicked(self, widget):
|
|
self.window.destroy()
|
|
Gtk.main_quit()
|
|
|
|
def update_layout_view(self):
|
|
model = self.layout_view.get_model()
|
|
self.layout_view_model = model
|
|
model.clear()
|
|
|
|
sort_order = ["Small", "Compact", "Full Keyboard", "Phone", "Grid"]
|
|
layout_infos = self._read_layouts(
|
|
os.path.join(config.install_dir, "layouts"), sort_order)
|
|
|
|
system = [li for li in layout_infos
|
|
if li.layout_section == "system"]
|
|
contributed = [li for li in layout_infos
|
|
if li.layout_section == "contributions"]
|
|
|
|
user = self._read_layouts(self.user_layout_root)
|
|
|
|
self._add_layout_section(model, system, _("Core layouts"))
|
|
self._add_layout_section(model, contributed, _("Contributions"))
|
|
self._add_layout_section(model, user, _("My layouts"))
|
|
|
|
self.layout_view.expand_all()
|
|
self.update_layout_view_selection()
|
|
|
|
def update_layout_view_selection(self):
|
|
self.select_tree_view_row(self.layout_view, self.LAYOUT_COL_FILENAME,
|
|
config.layout_filename)
|
|
|
|
def _add_layout_section(self, model, lis, section_name):
|
|
if lis:
|
|
parent_iter = model.append(None)
|
|
model.set(parent_iter,
|
|
self.LAYOUT_COL_NAME, "<b>{}</b>" \
|
|
.format(escape_markup(section_name)))
|
|
for li in lis:
|
|
child_iter = model.append(parent_iter)
|
|
model.set(child_iter,
|
|
self.LAYOUT_COL_NAME, li.id_string,
|
|
self.LAYOUT_COL_SUMMARY, li.summary,
|
|
self.LAYOUT_COL_FILENAME, li.filename,
|
|
self.LAYOUT_COL_HAS_ABOUT_INFO, li.has_about_info,
|
|
self.LAYOUT_COL_IS_ROW_SENSITIVE, bool(li.filename))
|
|
|
|
def on_layout_about_button_clicked(self, event = None):
|
|
fn = self.get_selected_layout_filename()
|
|
li = self._read_layout(fn)
|
|
if li is None:
|
|
return
|
|
|
|
markup = "<big>{}</big>\n".format(escape_markup(li.id))
|
|
body = li.description or li.summary
|
|
if body:
|
|
markup += "\n{}\n".format(escape_markup(body))
|
|
if li.author:
|
|
markup += ("\n" + _("Author: {}") + "\n") \
|
|
.format(escape_markup(li.author))
|
|
markup += "\n <tt>{}</tt>\n".format(escape_markup(li.filename))
|
|
|
|
dialog = Gtk.MessageDialog(title=_("About Layout"),
|
|
message_type=Gtk.MessageType.QUESTION,
|
|
buttons=Gtk.ButtonsType.OK)
|
|
dialog.set_markup(markup)
|
|
dialog.set_transient_for(self.window)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def _read_layouts(self, path, sort_order=()):
|
|
filenames = self._find_layouts(path)
|
|
|
|
layout_infos = []
|
|
for filename in filenames:
|
|
li = self._read_layout(filename, sort_order)
|
|
if li:
|
|
layout_infos.append(li)
|
|
|
|
return sorted(layout_infos, key=lambda x: x.sort_key)
|
|
|
|
def _read_layout(self, filename, sort_order=()):
|
|
li = None
|
|
|
|
if filename and os.path.exists(filename):
|
|
file_object = open_utf8(filename)
|
|
try:
|
|
dom_node = minidom.parse(file_object).documentElement
|
|
|
|
class LayoutInfo: pass
|
|
li = LayoutInfo()
|
|
id = dom_node.attributes["id"].value
|
|
sort_priority = sort_order.index(id) \
|
|
if id in sort_order else 1000
|
|
li.id = id
|
|
li.filename = filename
|
|
li.layout_section = self._get_dom_string(dom_node, "section")
|
|
li.summary = _(self._get_dom_string(dom_node, "summary"))
|
|
li.description = \
|
|
_(self._get_dom_string(dom_node, "description"))
|
|
li.author = self._get_dom_string(dom_node, "author")
|
|
li.sort_key = (li.layout_section.lower(), sort_priority, id.lower())
|
|
li.has_about_info = True
|
|
li.id_string = id
|
|
|
|
except ExpatError as ex:
|
|
_logger.error("XML in %s %s" % (filename, unicode_str(ex)))
|
|
li = None
|
|
except KeyError as ex:
|
|
_logger.error("key %s required in %s" % (unicode_str(ex), filename))
|
|
li = None
|
|
|
|
file_object.close()
|
|
|
|
return li
|
|
|
|
@staticmethod
|
|
def _get_dom_string(dom_node, attribute, default = ""):
|
|
if dom_node.hasAttribute(attribute):
|
|
return dom_node.attributes[attribute].value
|
|
return default
|
|
|
|
def _find_layouts(self, path):
|
|
layouts = []
|
|
try:
|
|
files = os.listdir(path)
|
|
except OSError:
|
|
files = []
|
|
for filename in files:
|
|
if filename.endswith(".sok") or \
|
|
filename.endswith(config.LAYOUT_FILE_EXTENSION):
|
|
layouts.append(os.path.join(path, filename))
|
|
return layouts
|
|
|
|
def on_layout_view_select_cursor_row(self, treeview, *args):
|
|
return False
|
|
|
|
def on_layout_view_row_activated(self, treeview, path, view_column):
|
|
#self.on_layout_about_button_clicked() # too annoying
|
|
pass
|
|
|
|
def on_layout_view_cursor_changed(self, widget):
|
|
filename = self.get_selected_layout_filename()
|
|
if filename:
|
|
config.layout_filename = filename
|
|
self.update_layout_widgets()
|
|
|
|
def get_selected_layout_filename(self):
|
|
sel = self.layout_view.get_selection()
|
|
if sel:
|
|
it = sel.get_selected()[1]
|
|
if it:
|
|
return self.layout_view_model.get_value(it,
|
|
self.LAYOUT_COL_FILENAME)
|
|
return None
|
|
|
|
def get_selected_layout_value(self, col_index):
|
|
sel = self.layout_view.get_selection()
|
|
if sel:
|
|
it = sel.get_selected()[1]
|
|
if it:
|
|
return self.layout_view_model.get_value(it, col_index)
|
|
return None
|
|
|
|
def on_new_theme_button_clicked(self, widget):
|
|
while True:
|
|
new_name = show_ask_string_dialog(
|
|
_("Enter a name for the new theme:"), self.window)
|
|
if not new_name:
|
|
return
|
|
|
|
new_filename = Theme.build_user_filename(new_name)
|
|
if not os.path.exists(new_filename):
|
|
break
|
|
|
|
question = _format("This theme file already exists.\n'{filename}'"
|
|
"\n\nOverwrite it?",
|
|
filename=new_filename)
|
|
if show_confirmation_dialog(question, self.window):
|
|
break
|
|
|
|
theme = self.get_selected_theme()
|
|
if not theme:
|
|
theme = Theme()
|
|
theme.save_as(new_name, new_name)
|
|
config.theme_filename = theme.filename
|
|
self.update_themeList()
|
|
|
|
def on_delete_theme_button_clicked(self, widget):
|
|
theme = self.get_selected_theme()
|
|
if theme and not theme.is_system:
|
|
if self.get_hidden_theme(theme):
|
|
question = _("Reset selected theme to Onboard defaults?")
|
|
else:
|
|
question = _("Delete selected theme?")
|
|
reply = show_confirmation_dialog(question, self.window)
|
|
if reply == True:
|
|
# be sure the file hasn't been deleted from outside already
|
|
if os.path.exists(theme.filename):
|
|
os.remove(theme.filename)
|
|
|
|
# Is there a system theme behind the deleted one?
|
|
hidden_theme = self.get_hidden_theme(theme)
|
|
if hidden_theme:
|
|
config.theme_filename = hidden_theme.filename
|
|
|
|
else: # row will disappear
|
|
# find a neighboring theme to select after deletion
|
|
near_theme = self.find_neighbor_theme(theme)
|
|
config.theme_filename = near_theme.filename \
|
|
if near_theme else ""
|
|
|
|
self.update_themeList()
|
|
|
|
# notify gsettings clients
|
|
theme = self.get_selected_theme()
|
|
if theme:
|
|
theme.apply()
|
|
|
|
def find_neighbor_theme(self, theme):
|
|
themes = self.get_sorted_themes()
|
|
for i, tpl in enumerate(themes):
|
|
if theme.basename == tpl[0].basename:
|
|
if i < len(themes)-1:
|
|
return themes[i+1][0]
|
|
else:
|
|
return themes[i-1][0]
|
|
return None
|
|
|
|
def on_system_theme_tracking_enabled_toggled(self, widget):
|
|
config.system_theme_tracking_enabled = widget.get_active()
|
|
|
|
def on_customize_theme_button_clicked(self, widget):
|
|
self.customize_theme()
|
|
|
|
def on_theme_view_row_activated(self, treeview, path, view_column):
|
|
self.customize_theme()
|
|
|
|
def on_theme_view_cursor_changed(self, widget):
|
|
theme = self.get_selected_theme()
|
|
if theme:
|
|
theme.apply()
|
|
config.theme_filename = theme.filename
|
|
self.update_theme_buttons()
|
|
|
|
def get_sorted_themes(self):
|
|
#return sorted(self.themes.values(), key=lambda x: x[0].name)
|
|
is_system = [x for x in list(self.themes.values()) if x[0].is_system or x[1]]
|
|
user = [x for x in list(self.themes.values()) if not (x[0].is_system or x[1])]
|
|
return sorted(is_system, key=lambda x: x[0].name.lower()) + \
|
|
sorted(user, key=lambda x: x[0].name.lower())
|
|
|
|
def find_theme_index(self, theme):
|
|
themes = self.get_sorted_themes()
|
|
for i,tpl in enumerate(themes):
|
|
if theme.basename == tpl[0].basename:
|
|
return i
|
|
return -1
|
|
|
|
def customize_theme(self):
|
|
theme = self.get_selected_theme()
|
|
if theme:
|
|
system_theme = self.themes[theme.basename][1]
|
|
|
|
dialog = ThemeDialog(self, theme)
|
|
modified_theme = dialog.run()
|
|
|
|
if modified_theme == system_theme:
|
|
# same as the system theme, so delete the user theme
|
|
_logger.info("Deleting theme '%s'" % theme.filename)
|
|
if os.path.exists(theme.filename):
|
|
os.remove(theme.filename)
|
|
|
|
elif not modified_theme == theme:
|
|
# save as user theme
|
|
modified_theme.save_as(theme.basename, theme.name)
|
|
config.theme_filename = modified_theme.filename
|
|
_logger.info("Saved theme '%s'" % theme.filename)
|
|
|
|
self.update_themeList()
|
|
|
|
def on_system_theme_tracking_changed(self, x):
|
|
self.system_theme_tracking_enabled_toggle.set_active( \
|
|
config.system_theme_tracking_enabled)
|
|
config.load_theme()
|
|
self.on_theme_changed()
|
|
|
|
def on_theme_changed(self, theme_filename = None):
|
|
selected = self.get_selected_theme_filename()
|
|
if selected != config.theme_filename:
|
|
self.update_themeList()
|
|
|
|
def update_themeList(self):
|
|
self.themeList = Gtk.ListStore(str, str)
|
|
self.theme_view.set_model(self.themeList)
|
|
|
|
self.themes = Theme.load_merged_themes()
|
|
|
|
theme_basename = \
|
|
os.path.splitext(os.path.basename(config.theme_filename))[0]
|
|
it_selection = None
|
|
for theme,hidden_theme in self.get_sorted_themes():
|
|
it = self.themeList.append((
|
|
format_list_item(theme.name, theme.is_system),
|
|
theme.filename))
|
|
if theme.basename == theme_basename:
|
|
sel = self.theme_view.get_selection()
|
|
if sel:
|
|
sel.select_iter(it)
|
|
it_selection = it
|
|
|
|
# scroll to selection
|
|
if it_selection:
|
|
path = self.themeList.get_path(it_selection)
|
|
self.theme_view.scroll_to_cell(path)
|
|
|
|
self.update_theme_buttons()
|
|
|
|
def update_theme_buttons(self):
|
|
theme = self.get_selected_theme()
|
|
|
|
if theme and (self.get_hidden_theme(theme) or theme.is_system):
|
|
# Translators: reset button of Preferences->Theme.
|
|
self.delete_theme_button.set_label(_("_Reset"))
|
|
else:
|
|
# Translators: delete button of Preferences->Theme. It used to be
|
|
# stock item STOCK_DELETE until Gtk 3.10 deprecated those.
|
|
self.delete_theme_button.set_label(_("_Delete"))
|
|
|
|
self.delete_theme_button.set_sensitive(bool(theme) and not theme.is_system)
|
|
self.customize_theme_button.set_sensitive(bool(theme))
|
|
|
|
def get_hidden_theme(self, theme):
|
|
if theme:
|
|
return self.themes[theme.basename][1]
|
|
return None
|
|
|
|
def get_selected_theme(self):
|
|
filename = self.get_selected_theme_filename()
|
|
if filename:
|
|
basename = os.path.splitext(os.path.basename(filename))[0]
|
|
if basename in self.themes:
|
|
return self.themes[basename][0]
|
|
return None
|
|
|
|
def get_selected_theme_filename(self):
|
|
sel = self.theme_view.get_selection()
|
|
if sel:
|
|
it = sel.get_selected()[1]
|
|
if it:
|
|
return self.themeList.get_value(it, 1)
|
|
return None
|
|
|
|
|
|
class PageAutoShow(DialogBuilder):
|
|
""" Word Suggestions """
|
|
|
|
""" Keyboard devices view columns """
|
|
COL_ID = 0
|
|
COL_INCLUDE = 1
|
|
COL_TEXT = 2
|
|
|
|
def __init__(self, settings, builder):
|
|
DialogBuilder.__init__(self, builder)
|
|
self._settings = settings
|
|
|
|
# General page
|
|
def _set(co, key, value):
|
|
if value and \
|
|
not config.check_gnome_accessibility(self._settings.window):
|
|
value = False
|
|
setattr(co, key, value)
|
|
self._settings.update_window_widgets() # will call _update_ui()
|
|
|
|
self.bind_check("auto_show_toggle1", # toggle on general page
|
|
config.auto_show, "enabled",
|
|
config_set_callback=_set)
|
|
self.bind_check("auto_show_toggle2", # same thing on auto-show page
|
|
config.auto_show, "enabled",
|
|
config_set_callback=_set)
|
|
|
|
def _set(config_object, key, value):
|
|
self.bind_combobox_config_set(config_object, key, value)
|
|
self._settings.update_window_widgets()
|
|
|
|
self.bind_combobox_id("reposition_method_floating_combobox",
|
|
config.auto_show, "reposition_method_floating",
|
|
config_set_callback=_set)
|
|
|
|
self.bind_combobox_id("reposition_method_docked_combobox",
|
|
config.auto_show, "reposition_method_docked",
|
|
config_set_callback=_set)
|
|
|
|
def _set(config_object, key, value):
|
|
setattr(config_object, key, value)
|
|
self._settings.update_window_widgets()
|
|
|
|
self.bind_check("hide_on_key_press_toggle",
|
|
config.auto_show, "hide_on_key_press",
|
|
config_set_callback=_set)
|
|
|
|
def _get(config_object, key):
|
|
duration = getattr(config_object, key)
|
|
return str(int(round(duration)))
|
|
|
|
def _set(config_object, key, value):
|
|
duration = float(value)
|
|
setattr(config_object, key, duration)
|
|
|
|
self.bind_combobox_id("hide_on_key_press_pause_combobox",
|
|
config.auto_show, "hide_on_key_press_pause",
|
|
_get, _set)
|
|
|
|
# Convertible Devices page
|
|
self.bind_check("tablet_mode_detection_enabled_toggle",
|
|
config.auto_show, "tablet_mode_detection_enabled")
|
|
|
|
def _get(co, key):
|
|
return str(getattr(co, key))
|
|
|
|
def _set(co, key, value):
|
|
try:
|
|
value = int(value)
|
|
except ValueError:
|
|
value = 0
|
|
setattr(co, key, value)
|
|
|
|
self.bind_entry("tablet_mode_enter_key_entry",
|
|
config.auto_show, "tablet_mode_enter_key",
|
|
config_get_callback=_get,
|
|
config_set_callback=_set)
|
|
self.bind_entry("tablet_mode_leave_key_entry",
|
|
config.auto_show, "tablet_mode_leave_key",
|
|
config_get_callback=_get,
|
|
config_set_callback=_set)
|
|
|
|
# External Keyboards page
|
|
def _set(co, key, value):
|
|
setattr(co, key, value)
|
|
self._settings.update_window_widgets() # will call _update_ui()
|
|
|
|
self.bind_check("keyboard_device_detection_enabled_toggle",
|
|
config.auto_show, "keyboard_device_detection_enabled",
|
|
config_set_callback=_set)
|
|
|
|
self._udev_tracker = UDevTracker()
|
|
self._udev_tracker.connect("keyboard-detection-changed",
|
|
self._on_keyboard_device_detection_changed)
|
|
|
|
self._update_keyboard_devices_view()
|
|
|
|
config.auto_show.keyboard_device_detection_exceptions_notify_add(
|
|
lambda x: self._update_keyboard_devices_view())
|
|
|
|
def _update_keyboard_devices_view(self):
|
|
view = self.wid("keyboard_detection_devices_view")
|
|
if not view.get_model():
|
|
model = Gtk.ListStore(str, bool, str)
|
|
view.set_model(model)
|
|
|
|
column_id = Gtk.TreeViewColumn()
|
|
|
|
# Translators: header of a tree view column with toggles to ignore
|
|
# keyboard devices (Preferences->Auto-show->External Keyboards).
|
|
column_toggle = Gtk.TreeViewColumn(_("Ignore"))
|
|
|
|
# Translators: header of a tree view column with device names
|
|
# (Preferences->Auto-show->External Keyboards).
|
|
column_text = Gtk.TreeViewColumn(_("Device"))
|
|
|
|
view.append_column(column_id)
|
|
view.append_column(column_toggle)
|
|
view.append_column(column_text)
|
|
|
|
cellrenderer_toggle = Gtk.CellRendererToggle()
|
|
column_toggle.pack_start(cellrenderer_toggle, False)
|
|
column_toggle.add_attribute(
|
|
cellrenderer_toggle, "active", self.COL_INCLUDE)
|
|
|
|
cellrenderer_text = Gtk.CellRendererText()
|
|
column_text.pack_start(cellrenderer_text, True)
|
|
column_text.add_attribute(cellrenderer_text, "text", self.COL_TEXT)
|
|
cellrenderer_toggle.connect(
|
|
"toggled", self._on_keyboard_device_toggled, model)
|
|
|
|
model = view.get_model()
|
|
selected_id = self.get_tree_view_selection(view, self.COL_ID)
|
|
|
|
model.clear()
|
|
|
|
devices = self._udev_tracker.get_keyboard_devices()
|
|
for device in devices:
|
|
ignore = device.id in \
|
|
config.auto_show.keyboard_device_detection_exceptions
|
|
t = (device.id, ignore, device.name)
|
|
model.append(t)
|
|
|
|
self.select_tree_view_row(view, self.COL_ID, selected_id)
|
|
|
|
def _on_keyboard_device_detection_changed(self, detected):
|
|
self._update_keyboard_devices_view()
|
|
|
|
def _on_keyboard_device_toggled(self, widget, path, model):
|
|
device_id = model[path][self.COL_ID]
|
|
ignore = not model[path][self.COL_INCLUDE]
|
|
|
|
# Update all rows with the same device id. There might still be
|
|
# duplicate device entries.
|
|
for row in model:
|
|
if row[self.COL_ID] == device_id:
|
|
row[self.COL_INCLUDE] = ignore
|
|
|
|
self._ignore_keyboard_device(device_id, ignore)
|
|
|
|
def _ignore_keyboard_device(self, device_id, ignore):
|
|
exceptions = config.auto_show.keyboard_device_detection_exceptions[:]
|
|
|
|
if ignore:
|
|
if device_id not in exceptions:
|
|
exceptions.append(device_id)
|
|
else:
|
|
try:
|
|
exceptions.remove(device_id)
|
|
except ValueError:
|
|
pass
|
|
|
|
config.auto_show.keyboard_device_detection_exceptions = exceptions
|
|
|
|
def update_ui(self):
|
|
# Two toggles that do the exact same thing: one on the general page,
|
|
# the other on the auto-show page. Keep the one on the general page as
|
|
# it's active state influences the visibility options there.
|
|
# Otherwise it becomes even less clear why some of these options are
|
|
# enabled/disabled.
|
|
self.wid("auto_show_toggle1").set_active(config.auto_show.enabled)
|
|
self.wid("auto_show_toggle2").set_active(config.auto_show.enabled)
|
|
|
|
docked = config.is_docking_enabled()
|
|
self.wid("reposition_method_floating_box").set_visible(not docked)
|
|
self.wid("reposition_method_docked_box").set_visible(docked)
|
|
|
|
self.wid("hide_on_key_press_toggle") \
|
|
.set_sensitive(config.can_set_auto_hide())
|
|
self.wid("hide_on_key_press_box") \
|
|
.set_sensitive(config.is_auto_hide_enabled())
|
|
auto_show_enabled = config.is_auto_show_enabled()
|
|
self.wid("auto_show_general_box").set_sensitive(auto_show_enabled)
|
|
self.wid("auto_show_convertibles_box").set_sensitive(auto_show_enabled)
|
|
self.wid("auto_show_external_keyboards_box") \
|
|
.set_sensitive(auto_show_enabled)
|
|
|
|
self.wid("keyboard_device_detection_box") \
|
|
.set_sensitive(config.is_keyboard_device_detection_enabled())
|
|
|
|
|
|
class PageWordSuggestions(DialogBuilder):
|
|
""" Word Suggestions """
|
|
|
|
def __init__(self, settings, builder):
|
|
DialogBuilder.__init__(self, builder)
|
|
self._settings = settings
|
|
|
|
def _set_word_suggestions_enabled(co, key, value):
|
|
if value and \
|
|
not config.check_gnome_accessibility(self._settings.window):
|
|
value = False
|
|
self.wid("enable_word_suggestions_toggle") \
|
|
.set_active(value)
|
|
setattr(co, key, value)
|
|
|
|
self.wid("enable_word_suggestions_toggle") \
|
|
.connect_after("toggled", lambda x: self._update_ui())
|
|
self.bind_check("enable_word_suggestions_toggle",
|
|
config.word_suggestions, "enabled",
|
|
config_set_callback = _set_word_suggestions_enabled)
|
|
|
|
self.wid("auto_learn_toggle") \
|
|
.connect_after("toggled", lambda x: self._update_ui())
|
|
self.bind_check("auto_learn_toggle",
|
|
config.wp, "auto_learn")
|
|
|
|
self.bind_check("punctuation_assistance_toggle",
|
|
config.wp, "punctuation_assistance")
|
|
self.bind_check("auto_capitalization_toggle",
|
|
config.typing_assistance, "auto_capitalization")
|
|
self.bind_check("auto_correction_toggle",
|
|
config.typing_assistance, "auto_correction")
|
|
self.bind_check("enable_spell_check_toggle",
|
|
config.word_suggestions, "spelling_suggestions_enabled")
|
|
|
|
self.bind_check("language_button_toggle",
|
|
config.word_suggestions, "wordlist_buttons",
|
|
config_get_callback = lambda co, key: \
|
|
co.can_show_language_button(),
|
|
config_set_callback = lambda co, key, value: \
|
|
co.show_wordlist_button(co.KEY_ID_LANGUAGE, value))
|
|
|
|
self.bind_check("pause_learning_button_toggle",
|
|
config.word_suggestions, "wordlist_buttons",
|
|
config_get_callback = lambda co, key: \
|
|
co.can_show_pause_learning_button(),
|
|
config_set_callback = lambda co, key, value: \
|
|
co.show_wordlist_button(co.KEY_ID_PAUSE_LEARNING, value))
|
|
|
|
def _get(co, key):
|
|
return co.can_show_more_predictions_button()
|
|
|
|
def _set(co, key, value):
|
|
co.show_wordlist_button(co.KEY_ID_NEXT_PREDICTIONS, value)
|
|
co.show_wordlist_button(co.KEY_ID_PREVIOUS_PREDICTIONS, value)
|
|
|
|
self.bind_check("more_predictions_button_toggle",
|
|
config.word_suggestions, "wordlist_buttons",
|
|
config_get_callback=_get,
|
|
config_set_callback=_set)
|
|
|
|
self.bind_combobox_id("learning_behavior_paused_combobox",
|
|
config.word_suggestions, "learning_behavior_paused")
|
|
|
|
#self.bind_check("show_context_line_toggle",
|
|
# config.word_suggestions, "show_context_line")
|
|
self._init_spell_checker_backend_combo()
|
|
|
|
self._update_ui()
|
|
|
|
def _init_spell_checker_backend_combo(self):
|
|
combo = self.wid("spell_check_backend_combobox")
|
|
combo.set_active(config.typing_assistance.spell_check_backend)
|
|
combo.connect("changed", self.on_spell_check_backend_changed)
|
|
config.typing_assistance.spell_check_backend_notify_add(self._backend_notify)
|
|
|
|
def on_spell_check_backend_changed(self, widget):
|
|
config.typing_assistance.spell_check_backend = widget.get_active()
|
|
|
|
def _backend_notify(self, mode):
|
|
self.wid("spell_check_backend_combobox").set_active(mode)
|
|
|
|
def _update_ui(self):
|
|
self.wid("word_suggestions_general_box1") \
|
|
.set_sensitive(config.are_word_suggestions_enabled())
|
|
self.wid("pause_learning_button_toggle") \
|
|
.set_sensitive(config.word_suggestions.auto_learn)
|
|
|
|
|
|
class DockingDialog(DialogBuilder):
|
|
""" Dialog "Docking Settings" """
|
|
|
|
def __init__(self):
|
|
|
|
builder = LoadUI("settings_docking_dialog")
|
|
|
|
DialogBuilder.__init__(self, builder)
|
|
|
|
self.bind_check("docking_shrink_workarea_toggle",
|
|
config.window, "docking_shrink_workarea")
|
|
self.bind_check("landscape_dock_expand_toggle",
|
|
config.window.landscape, "dock_expand")
|
|
self.bind_check("portrait_dock_expand_toggle",
|
|
config.window.portrait, "dock_expand")
|
|
self.bind_combobox_id("docking_edge_combobox",
|
|
config.window, "docking_edge")
|
|
self.bind_combobox_id("docking_monitor_combobox",
|
|
config.window, "docking_monitor")
|
|
|
|
self.update_ui()
|
|
|
|
def run(self, parent):
|
|
dialog = self.wid("dialog")
|
|
dialog.set_transient_for(parent)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def update_ui(self):
|
|
pass
|
|
|
|
|
|
class ThemeDialog(DialogBuilder):
|
|
""" Customize theme dialog """
|
|
|
|
current_page = 0
|
|
|
|
def __init__(self, settings, theme):
|
|
|
|
self.original_theme = theme
|
|
self.theme = copy.deepcopy(theme)
|
|
|
|
builder = LoadUI("settings_theme_dialog")
|
|
DialogBuilder.__init__(self, builder)
|
|
|
|
self.dialog = builder.get_object("customize_theme_dialog")
|
|
|
|
self.theme_notebook = builder.get_object("theme_notebook")
|
|
|
|
self.key_style_combobox = builder.get_object("key_style_combobox")
|
|
self.color_scheme_combobox = builder.get_object("color_scheme_combobox")
|
|
self.font_combobox = builder.get_object("font_combobox")
|
|
self.font_attributes_view = builder.get_object("font_attributes_view")
|
|
self.background_gradient_scale = builder.get_object(
|
|
"background_gradient_scale")
|
|
self.key_roundness_scale = builder.get_object(
|
|
"key_roundness_scale")
|
|
self.key_size_scale = builder.get_object(
|
|
"key_size_scale")
|
|
self.gradients_box = builder.get_object("gradients_box")
|
|
self.key_fill_gradient_scale = builder.get_object(
|
|
"key_fill_gradient_scale")
|
|
self.key_stroke_gradient_scale = builder.get_object(
|
|
"key_stroke_gradient_scale")
|
|
self.key_gradient_direction_scale = builder.get_object(
|
|
"key_gradient_direction_scale")
|
|
self.key_shadow_strength_scale = builder.get_object(
|
|
"key_shadow_strength_scale")
|
|
self.key_shadow_size_scale = builder.get_object(
|
|
"key_shadow_size_scale")
|
|
self.revert_button = builder.get_object("revert_button")
|
|
self.superkey_label_combobox = builder.get_object(
|
|
"superkey_label_combobox")
|
|
self.superkey_label_size_checkbutton = builder.get_object(
|
|
"superkey_label_size_checkbutton")
|
|
self.superkey_label_model = builder.get_object("superkey_label_model")
|
|
|
|
def _set(config_object, key, value):
|
|
setattr(config_object, key, value)
|
|
self.update_sensivity()
|
|
|
|
self.bind_scale("key_stroke_width_scale",
|
|
config.theme_settings, "key_stroke_width",
|
|
None, _set)
|
|
|
|
self.update_ui()
|
|
|
|
self.dialog.set_transient_for(settings.window)
|
|
self.theme_notebook.set_current_page(ThemeDialog.current_page)
|
|
|
|
builder.connect_signals(self)
|
|
|
|
def run(self):
|
|
# do response processing ourselves to stop the
|
|
# revert button from closing the dialog
|
|
self.dialog.set_modal(True)
|
|
self.dialog.show()
|
|
Gtk.main()
|
|
self.dialog.destroy()
|
|
return self.theme
|
|
|
|
def on_response(self, dialog, response_id):
|
|
if response_id == Gtk.ResponseType.DELETE_EVENT:
|
|
pass
|
|
if response_id == \
|
|
self.dialog.get_response_for_widget(self.revert_button):
|
|
|
|
# revert changes and keep the dialog open
|
|
self.theme = copy.deepcopy(self.original_theme)
|
|
|
|
self.update_ui()
|
|
self.theme.apply()
|
|
return
|
|
|
|
Gtk.main_quit()
|
|
|
|
def update_ui(self):
|
|
self.in_update = True
|
|
|
|
self.update_key_styleList()
|
|
self.update_color_schemeList()
|
|
self.update_fontList()
|
|
self.update_font_attributesList()
|
|
self.background_gradient_scale.set_value(self.theme.background_gradient)
|
|
self.key_roundness_scale.set_value(self.theme.roundrect_radius)
|
|
self.key_size_scale.set_value(self.theme.key_size)
|
|
self.key_fill_gradient_scale.set_value(self.theme.key_fill_gradient)
|
|
self.key_stroke_gradient_scale. \
|
|
set_value(self.theme.key_stroke_gradient)
|
|
self.key_gradient_direction_scale. \
|
|
set_value(self.theme.key_gradient_direction)
|
|
self.key_shadow_strength_scale. \
|
|
set_value(self.theme.key_shadow_strength)
|
|
self.key_shadow_size_scale. \
|
|
set_value(self.theme.key_shadow_size)
|
|
self.update_superkey_labelList()
|
|
self.superkey_label_size_checkbutton. \
|
|
set_active(bool(self.theme.get_superkey_size_group()))
|
|
|
|
self.update_sensivity()
|
|
|
|
self.in_update = False
|
|
|
|
def update_sensivity(self):
|
|
self.revert_button.set_sensitive(not self.theme == self.original_theme)
|
|
|
|
has_gradient = self.theme.key_style != "flat"
|
|
self.gradients_box.set_sensitive(has_gradient)
|
|
self.superkey_label_size_checkbutton.\
|
|
set_sensitive(bool(self.theme.get_superkey_label()))
|
|
|
|
def update_key_styleList(self):
|
|
self.key_style_list = Gtk.ListStore(str,str)
|
|
self.key_style_combobox.set_model(self.key_style_list)
|
|
cell = Gtk.CellRendererText()
|
|
self.key_style_combobox.clear()
|
|
self.key_style_combobox.pack_start(cell, True)
|
|
self.key_style_combobox.add_attribute(cell, 'markup', 0)
|
|
|
|
self.key_styles = [
|
|
# Key style with flat fill- and border colors
|
|
[_("Flat"), "flat"],
|
|
# Key style with simple gradients
|
|
[_("Gradient"), "gradient"],
|
|
# Key style for dish-like key caps
|
|
[_("Dish"), "dish"]
|
|
]
|
|
for name, id in self.key_styles:
|
|
it = self.key_style_list.append((name, id))
|
|
if id == self.theme.key_style:
|
|
self.key_style_combobox.set_active_iter(it)
|
|
|
|
def update_color_schemeList(self):
|
|
self.color_scheme_list = Gtk.ListStore(str,str)
|
|
self.color_scheme_combobox.set_model(self.color_scheme_list)
|
|
cell = Gtk.CellRendererText()
|
|
self.color_scheme_combobox.clear()
|
|
self.color_scheme_combobox.pack_start(cell, True)
|
|
self.color_scheme_combobox.add_attribute(cell, 'markup', 0)
|
|
|
|
self.color_schemes = ColorScheme.get_merged_color_schemes()
|
|
color_scheme_filename = self.theme.get_color_scheme_filename()
|
|
for color_scheme in sorted(list(self.color_schemes.values()),
|
|
key=lambda x: x.name):
|
|
it = self.color_scheme_list.append((
|
|
format_list_item(color_scheme.name, color_scheme.is_system),
|
|
color_scheme.filename))
|
|
if color_scheme.filename == color_scheme_filename:
|
|
self.color_scheme_combobox.set_active_iter(it)
|
|
|
|
def update_fontList(self):
|
|
self.font_list = Gtk.ListStore(str,str)
|
|
self.font_combobox.set_model(self.font_list)
|
|
cell = Gtk.CellRendererText()
|
|
self.font_combobox.clear()
|
|
self.font_combobox.pack_start(cell, True)
|
|
self.font_combobox.add_attribute(cell, 'markup', 0)
|
|
self.font_combobox.set_row_separator_func(
|
|
self.font_combobox_row_separator_func,
|
|
None)
|
|
|
|
# work around https://bugzilla.gnome.org/show_bug.cgi?id=654957
|
|
# "SIGSEGV when trying to call Pango.Context.list_families twice"
|
|
global font_families
|
|
if not "font_families" in globals():
|
|
widget = Gtk.DrawingArea()
|
|
context = widget.create_pango_context()
|
|
font_families = context.list_families()
|
|
widget.destroy()
|
|
|
|
families = [(font.get_name(), font.get_name()) \
|
|
for font in font_families]
|
|
|
|
families.sort(key=lambda x: x[0])
|
|
families = [(_("Default"), ""),
|
|
("-", "-")] + families
|
|
fd = Pango.FontDescription(self.theme.key_label_font)
|
|
family = fd.get_family()
|
|
for f in families:
|
|
it = self.font_list.append(f)
|
|
if f[1] == family or \
|
|
(f[1] == "" and not family):
|
|
self.font_combobox.set_active_iter(it)
|
|
|
|
def font_combobox_row_separator_func(self, model, iter, data):
|
|
return unicode_str(model.get_value(iter, 0)) == "-"
|
|
|
|
def update_font_attributesList(self):
|
|
treeview = self.font_attributes_view
|
|
|
|
if not treeview.get_columns():
|
|
liststore = Gtk.ListStore(bool, str, str)
|
|
self.font_attributes_list = liststore
|
|
treeview.set_model(liststore)
|
|
|
|
column_toggle = Gtk.TreeViewColumn("Toggle")
|
|
column_text = Gtk.TreeViewColumn("Text")
|
|
treeview.append_column(column_toggle)
|
|
treeview.append_column(column_text)
|
|
|
|
cellrenderer_toggle = Gtk.CellRendererToggle()
|
|
column_toggle.pack_start(cellrenderer_toggle, False)
|
|
column_toggle.add_attribute(cellrenderer_toggle, "active", 0)
|
|
|
|
cellrenderer_text = Gtk.CellRendererText()
|
|
column_text.pack_start(cellrenderer_text, True)
|
|
column_text.add_attribute(cellrenderer_text, "text", 1)
|
|
cellrenderer_toggle.connect("toggled",
|
|
self.on_font_attributesList_toggle, liststore)
|
|
|
|
liststore = treeview.get_model()
|
|
liststore.clear()
|
|
|
|
fd = Pango.FontDescription(self.theme.key_label_font)
|
|
items = [[fd.get_weight() == Pango.Weight.BOLD,
|
|
_("Bold"), "bold"],
|
|
[fd.get_style() == Pango.Style.ITALIC,
|
|
_("Italic"), "italic"],
|
|
[fd.get_stretch() == Pango.Stretch.CONDENSED,
|
|
_("Condensed"), "condensed"],
|
|
]
|
|
for checked, name, id in items:
|
|
it = liststore.append((checked, name, id))
|
|
if id == "":
|
|
treeview.set_active_iter(it)
|
|
|
|
def update_superkey_labelList(self):
|
|
# block premature signals when calling model.clear()
|
|
self.superkey_label_combobox.set_model(None)
|
|
|
|
self.superkey_label_model.clear()
|
|
self.superkey_labels = [["", _("Default")],
|
|
[_(""), _("Ubuntu Logo")]
|
|
]
|
|
|
|
for label, descr in self.superkey_labels:
|
|
self.superkey_label_model.append((label, descr))
|
|
|
|
label = self.theme.get_superkey_label()
|
|
self.superkey_label_combobox.get_child().set_text(label \
|
|
if label else "")
|
|
|
|
self.superkey_label_combobox.set_model(self.superkey_label_model)
|
|
|
|
def on_background_gradient_value_changed(self, widget):
|
|
value = float(widget.get_value())
|
|
config.theme_settings.background_gradient = value
|
|
self.theme.background_gradient = value
|
|
self.update_sensivity()
|
|
|
|
def on_key_style_combobox_changed(self, widget):
|
|
value = self.key_style_list.get_value( \
|
|
self.key_style_combobox.get_active_iter(),1)
|
|
self.theme.key_style = value
|
|
config.theme_settings.key_style = value
|
|
self.update_sensivity()
|
|
|
|
def on_key_roundness_value_changed(self, widget):
|
|
radius = int(widget.get_value())
|
|
config.theme_settings.roundrect_radius = radius
|
|
self.theme.roundrect_radius = radius
|
|
self.update_sensivity()
|
|
|
|
def on_key_size_value_changed(self, widget):
|
|
value = int(widget.get_value())
|
|
config.theme_settings.key_size = value
|
|
self.theme.key_size = value
|
|
self.update_sensivity()
|
|
|
|
def on_color_scheme_combobox_changed(self, widget):
|
|
filename = self.color_scheme_list.get_value( \
|
|
self.color_scheme_combobox.get_active_iter(),1)
|
|
self.theme.set_color_scheme_filename(filename)
|
|
config.theme_settings.color_scheme_filename = filename
|
|
self.update_sensivity()
|
|
|
|
def on_key_fill_gradient_value_changed(self, widget):
|
|
value = int(widget.get_value())
|
|
config.theme_settings.key_fill_gradient = value
|
|
self.theme.key_fill_gradient = value
|
|
self.update_sensivity()
|
|
|
|
def on_key_stroke_gradient_value_changed(self, widget):
|
|
value = int(widget.get_value())
|
|
config.theme_settings.key_stroke_gradient = value
|
|
self.theme.key_stroke_gradient = value
|
|
self.update_sensivity()
|
|
|
|
def on_key_gradient_direction_value_changed(self, widget):
|
|
value = int(widget.get_value())
|
|
config.theme_settings.key_gradient_direction = value
|
|
self.theme.key_gradient_direction = value
|
|
self.update_sensivity()
|
|
|
|
def on_key_shadow_strength_value_changed(self, widget):
|
|
value = float(widget.get_value())
|
|
config.theme_settings.key_shadow_strength = value
|
|
self.theme.key_shadow_strength = value
|
|
self.update_sensivity()
|
|
|
|
def on_key_shadow_size_value_changed(self, widget):
|
|
value = float(widget.get_value())
|
|
config.theme_settings.key_shadow_size = value
|
|
self.theme.key_shadow_size = value
|
|
self.update_sensivity()
|
|
|
|
def on_font_combobox_changed(self, widget):
|
|
if not self.in_update:
|
|
self.store_key_label_font()
|
|
self.update_sensivity()
|
|
|
|
def on_font_attributesList_toggle(self, widget, path, model):
|
|
model[path][0] = not model[path][0]
|
|
self.store_key_label_font()
|
|
self.update_sensivity()
|
|
|
|
def store_key_label_font(self):
|
|
font = self.font_list.get_value(self.font_combobox.get_active_iter(),1)
|
|
for row in self.font_attributes_list:
|
|
if row[0]:
|
|
font += " " + row[2]
|
|
|
|
self.theme.key_label_font = font
|
|
config.theme_settings.key_label_font = font
|
|
|
|
def on_superkey_label_combobox_changed(self, widget):
|
|
self.store_superkey_label_override()
|
|
self.update_sensivity()
|
|
|
|
def on_superkey_label_size_checkbutton_toggled(self, widget):
|
|
self.store_superkey_label_override()
|
|
self.update_sensivity()
|
|
|
|
def store_superkey_label_override(self):
|
|
label = self.superkey_label_combobox.get_child().get_text()
|
|
if sys.version_info.major == 2:
|
|
label = label.decode("utf8")
|
|
if not label:
|
|
label = None # removes the override
|
|
checked = self.superkey_label_size_checkbutton.get_active()
|
|
size_group = config.SUPERKEY_SIZE_GROUP if checked else ""
|
|
self.theme.set_superkey_label(label, size_group)
|
|
config.theme_settings.key_label_overrides = \
|
|
dict(self.theme.key_label_overrides)
|
|
|
|
def on_theme_notebook_switch_page(self, widget, gpage, page_num):
|
|
ThemeDialog.current_page = page_num
|
|
|
|
|
|
class ScannerDialog(DialogBuilder):
|
|
""" Scanner settings dialog """
|
|
|
|
""" Input device columns """
|
|
COL_ICON_NAME = 0
|
|
COL_DEVICE_NAME = 1
|
|
COL_DEVICE = 2
|
|
|
|
""" Device mapping columns """
|
|
COL_NAME = 0
|
|
COL_ACTION = 1
|
|
COL_BUTTON = 2
|
|
COL_KEY = 3
|
|
COL_VISIBLE = 4
|
|
COL_WEIGHT = 5
|
|
|
|
""" UI strings for scan actions """
|
|
action_names = { ScanMode.ACTION_STEP : _("Step"),
|
|
ScanMode.ACTION_LEFT : _("Left"),
|
|
ScanMode.ACTION_RIGHT : _("Right"),
|
|
ScanMode.ACTION_UP : _("Up"),
|
|
ScanMode.ACTION_DOWN : _("Down"),
|
|
ScanMode.ACTION_ACTIVATE : _("Activate") }
|
|
|
|
""" List of actions a profile supports """
|
|
supported_actions = [ [ScanMode.ACTION_STEP],
|
|
[ScanMode.ACTION_STEP],
|
|
[ScanMode.ACTION_STEP,
|
|
ScanMode.ACTION_ACTIVATE],
|
|
[ScanMode.ACTION_LEFT,
|
|
ScanMode.ACTION_RIGHT,
|
|
ScanMode.ACTION_UP,
|
|
ScanMode.ACTION_DOWN,
|
|
ScanMode.ACTION_ACTIVATE] ]
|
|
|
|
def __init__(self):
|
|
|
|
builder = LoadUI("settings_scanner_dialog")
|
|
DialogBuilder.__init__(self, builder)
|
|
|
|
self.device_manager = XIDeviceManager()
|
|
self.device_manager.connect("device-event", self._on_device_event)
|
|
self.pointer_selected = None
|
|
self.mapping_renderer = None
|
|
|
|
# order of execution is important
|
|
self.init_input_devices()
|
|
self.init_scan_modes()
|
|
self.init_device_mapping()
|
|
|
|
scanner = config.scanner
|
|
self.bind_spin("cycles", scanner, "cycles")
|
|
self.bind_spin("cycles_overscan", scanner, "cycles")
|
|
self.bind_spin("cycles_stepscan", scanner, "cycles")
|
|
self.bind_spin("step_interval", scanner, "interval")
|
|
self.bind_spin("backtrack_interval", scanner, "interval")
|
|
self.bind_spin("forward_interval", scanner, "interval_fast")
|
|
self.bind_spin("backtrack_steps", scanner, "backtrack")
|
|
self.bind_check("user_scan", scanner, "user_scan")
|
|
self.bind_check("alternate", scanner, "alternate")
|
|
self.bind_check("device_detach", scanner, "device_detach")
|
|
|
|
def __del__(self):
|
|
_logger.debug("ScannerDialog.__del__()")
|
|
|
|
def run(self, parent):
|
|
dialog = self.wid("dialog")
|
|
dialog.set_transient_for(parent)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
config.scanner.disconnect_notifications()
|
|
self.device_manager = None
|
|
|
|
def init_scan_modes(self):
|
|
combo = self.wid("scan_mode_combo")
|
|
combo.set_active(config.scanner.mode)
|
|
combo.connect("changed", self.on_scan_mode_changed)
|
|
config.scanner.mode_notify_add(self._scan_mode_notify)
|
|
self.wid("scan_mode_notebook").set_current_page(config.scanner.mode)
|
|
|
|
def on_scan_mode_changed(self, widget):
|
|
config.scanner.mode = widget.get_active()
|
|
|
|
def _scan_mode_notify(self, mode):
|
|
self.wid("scan_mode_combo").set_active(mode)
|
|
self.wid("scan_mode_notebook").set_current_page(mode)
|
|
self.update_device_mapping()
|
|
|
|
def init_input_devices(self):
|
|
combo = self.wid("input_device_combo")
|
|
combo.set_model(Gtk.ListStore(str, str, GObject.TYPE_PYOBJECT))
|
|
combo.add_attribute(self.wid("input_device_icon_renderer"),
|
|
"icon-name", self.COL_ICON_NAME)
|
|
combo.add_attribute(self.wid("input_device_text_renderer"),
|
|
"text", self.COL_DEVICE_NAME)
|
|
|
|
self.update_input_devices()
|
|
|
|
combo.connect("changed", self.on_input_device_changed)
|
|
config.scanner.device_name_notify_add(self._device_name_notify)
|
|
|
|
def update_input_devices(self):
|
|
devices = self.list_devices()
|
|
model = self.wid("input_device_combo").get_model()
|
|
model.clear()
|
|
|
|
model.append(["input-mouse", ScanDevice.DEFAULT_NAME, None])
|
|
|
|
for dev in devices:
|
|
if dev.is_pointer():
|
|
model.append(["input-mouse", dev.name, dev])
|
|
|
|
for dev in devices:
|
|
if not dev.is_pointer():
|
|
model.append(["input-keyboard", dev.name, dev])
|
|
|
|
self.select_current_device(config.scanner.device_name)
|
|
|
|
def select_current_device(self, name):
|
|
combo = self.wid("input_device_combo")
|
|
model = combo.get_model()
|
|
it = model.get_iter_first()
|
|
if it is None:
|
|
return
|
|
|
|
if name == ScanDevice.DEFAULT_NAME:
|
|
self.pointer_selected = True
|
|
self.wid("device_detach").set_sensitive(False)
|
|
combo.set_active_iter(it)
|
|
else:
|
|
while it:
|
|
device = model.get_value(it, self.COL_DEVICE)
|
|
if device and name == device.get_config_string():
|
|
self.pointer_selected = device.is_pointer()
|
|
self.wid("device_detach").set_sensitive(True)
|
|
combo.set_active_iter(it)
|
|
break
|
|
it = model.iter_next(it)
|
|
|
|
if self.mapping_renderer:
|
|
self.mapping_renderer.set_property("pointer-mode",
|
|
self.pointer_selected)
|
|
|
|
def on_input_device_changed(self, combo):
|
|
model = combo.get_model()
|
|
it = combo.get_active_iter()
|
|
if it is None:
|
|
return
|
|
|
|
config.scanner.device_detach = False
|
|
device = model.get_value(it, self.COL_DEVICE)
|
|
|
|
if device:
|
|
config.scanner.device_name = device.get_config_string()
|
|
self.wid("device_detach").set_sensitive(True)
|
|
self.pointer_selected = device.is_pointer()
|
|
else:
|
|
config.scanner.device_name = ScanDevice.DEFAULT_NAME
|
|
self.wid("device_detach").set_sensitive(False)
|
|
self.pointer_selected = True
|
|
|
|
if self.mapping_renderer:
|
|
self.mapping_renderer.set_property("pointer-mode",
|
|
self.pointer_selected)
|
|
|
|
def _device_name_notify(self, name):
|
|
self.select_current_device(name)
|
|
self.update_device_mapping()
|
|
|
|
def init_device_mapping(self):
|
|
self.update_device_mapping()
|
|
|
|
self.mapping_renderer = CellRendererMapping()
|
|
self.mapping_renderer.set_property("pointer-mode", self.pointer_selected)
|
|
self.mapping_renderer.connect("mapping-edited", self.on_mapping_edited)
|
|
self.mapping_renderer.connect("mapping-cleared", self.on_mapping_cleared)
|
|
|
|
column = self.wid("column_mapping")
|
|
column.pack_start(self.mapping_renderer, False)
|
|
column.add_attribute(self.mapping_renderer, "button", self.COL_BUTTON)
|
|
column.add_attribute(self.mapping_renderer, "key", self.COL_KEY)
|
|
column.add_attribute(self.mapping_renderer, "visible", self.COL_VISIBLE)
|
|
|
|
def update_device_mapping(self):
|
|
view = self.wid("device_mapping")
|
|
model = view.get_model()
|
|
model.clear()
|
|
|
|
parent_iter = model.append(None)
|
|
model.set(parent_iter,
|
|
self.COL_NAME, _("Action:"),
|
|
self.COL_WEIGHT, Pango.Weight.BOLD)
|
|
|
|
for action in self.supported_actions[config.scanner.mode]:
|
|
child_iter = model.append(parent_iter)
|
|
model.set(child_iter,
|
|
self.COL_NAME, self.action_names[action],
|
|
self.COL_ACTION, action,
|
|
self.COL_VISIBLE, True,
|
|
self.COL_WEIGHT, Pango.Weight.NORMAL)
|
|
|
|
if self.pointer_selected:
|
|
button = self.get_value_for_action \
|
|
(action, config.scanner.device_button_map)
|
|
if button:
|
|
model.set(child_iter, self.COL_BUTTON, button)
|
|
else:
|
|
key = self.get_value_for_action \
|
|
(action, config.scanner.device_key_map)
|
|
if key:
|
|
model.set(child_iter, self.COL_KEY, key)
|
|
|
|
view.expand_all()
|
|
|
|
def on_mapping_edited(self, cell, path, value, pointer_mode):
|
|
model = self.wid("device_mapping_model")
|
|
it = model.get_iter_from_string(path)
|
|
if it is None:
|
|
return
|
|
|
|
if pointer_mode:
|
|
col = self.COL_BUTTON
|
|
dev_map = config.scanner.device_button_map.copy()
|
|
else:
|
|
col = self.COL_KEY
|
|
dev_map = config.scanner.device_key_map.copy()
|
|
|
|
dup_it = model.get_iter_from_string("0:0")
|
|
dup_val = None
|
|
while dup_it:
|
|
if value == model.get_value(dup_it, col):
|
|
dup_val = model.get_value(dup_it, col)
|
|
model.set(dup_it, col, 0)
|
|
break
|
|
dup_it = model.iter_next(dup_it)
|
|
|
|
model.set(it, col, value)
|
|
|
|
if dup_val in dev_map:
|
|
del dev_map[dup_val]
|
|
|
|
action = model.get_value(it, self.COL_ACTION)
|
|
dev_map[value] = action
|
|
|
|
for k, v in dev_map.items():
|
|
if k != value and v == action:
|
|
del dev_map[k]
|
|
break
|
|
|
|
if pointer_mode:
|
|
config.scanner.device_button_map = dev_map
|
|
else:
|
|
config.scanner.device_key_map = dev_map
|
|
|
|
def on_mapping_cleared(self, cell, path, pointer_mode):
|
|
model = self.wid("device_mapping_model")
|
|
it = model.get_iter_from_string(path)
|
|
if it is None:
|
|
return
|
|
|
|
if pointer_mode:
|
|
old_value = model.get_value(it, self.COL_BUTTON)
|
|
model.set(it, self.COL_BUTTON, 0)
|
|
if old_value in config.scanner.device_button_map:
|
|
copy = config.scanner.device_button_map.copy()
|
|
del copy[old_value]
|
|
config.scanner.device_button_map = copy
|
|
else:
|
|
old_value = model.get_value(it, self.COL_KEY)
|
|
model.set(it, self.COL_KEY, 0)
|
|
if old_value in config.scanner.device_key_map:
|
|
copy = config.scanner.device_key_map.copy()
|
|
del copy[old_value]
|
|
config.scanner.device_key_map = copy
|
|
|
|
def list_devices(self):
|
|
return [d for d in self.device_manager.get_devices() \
|
|
if ScanDevice.is_useable(d) ]
|
|
|
|
def _on_device_event(self, event):
|
|
if event.xi_type in [XIEventType.DeviceAdded,
|
|
XIEventType.DeviceRemoved]:
|
|
self.update_input_devices()
|
|
|
|
def get_value_for_action(self, action, dev_map):
|
|
for k, v in dev_map.items():
|
|
if v == action:
|
|
return k
|
|
|
|
|
|
MAX_GINT32 = (1 << 31) - 1
|
|
|
|
class CellRendererMapping(Gtk.CellRendererText):
|
|
"""
|
|
Custom cell renderer that displays device buttons as labels.
|
|
"""
|
|
|
|
__gproperties__ = { str('button') : (GObject.TYPE_INT,
|
|
'', '', 0, MAX_GINT32, 0,
|
|
GObject.PARAM_READWRITE),
|
|
str('key') : (GObject.TYPE_INT,
|
|
'', '', 0, MAX_GINT32, 0,
|
|
GObject.PARAM_READWRITE),
|
|
str('pointer-mode') : (bool, '', '', True,
|
|
GObject.PARAM_READWRITE) }
|
|
|
|
__gsignals__ = { str('mapping-edited') : (GObject.SignalFlags.RUN_LAST,
|
|
None, (str, int, bool)),
|
|
str('mapping-cleared'): (GObject.SignalFlags.RUN_LAST,
|
|
None, (str, bool)) }
|
|
|
|
def __init__(self):
|
|
super(CellRendererMapping, self).__init__(editable=True)
|
|
|
|
self.key = 0
|
|
self.button = 0
|
|
self.pointer_mode = True
|
|
|
|
self._edit_widget = None
|
|
self._grab_widget = None
|
|
self._grab_pointer = None
|
|
self._grab_keyboard = None
|
|
self._path = None
|
|
self._bp_id = 0
|
|
self._kp_id = 0
|
|
self._se_id = 0
|
|
self._sizing_label = None
|
|
|
|
self._update_text_props()
|
|
|
|
def do_get_property(self, prop):
|
|
if prop.name == 'button':
|
|
return self.button
|
|
elif prop.name == 'key':
|
|
return self.key
|
|
elif prop.name == 'pointer-mode':
|
|
return self.pointer_mode
|
|
|
|
def do_set_property(self, prop, value):
|
|
if prop.name == 'button':
|
|
self.button = value
|
|
elif prop.name == 'key':
|
|
self.key = value
|
|
elif prop.name == 'pointer-mode':
|
|
self.pointer_mode = value
|
|
|
|
self._update_text_props()
|
|
|
|
def _update_text_props(self):
|
|
|
|
if (self.pointer_mode and self.button == 0) or \
|
|
(not self.pointer_mode and self.key == 0):
|
|
self.set_property("style", Pango.Style.ITALIC)
|
|
self.set_property("foreground-rgba", Gdk.RGBA(0.6, 0.6, 0.6, 1.0))
|
|
text = _("Disabled")
|
|
else:
|
|
self.set_property("style", Pango.Style.NORMAL)
|
|
self.set_property("foreground-set", False)
|
|
|
|
if self.pointer_mode:
|
|
text = "{} {!s}".format(_("Button"), self.button)
|
|
else:
|
|
text = Gdk.keyval_name(self.key)
|
|
|
|
self.set_property("text", text)
|
|
|
|
def _on_edit_widget_unrealize(self, widget):
|
|
Gtk.device_grab_remove(self._grab_widget, self._grab_pointer)
|
|
|
|
time = Gtk.get_current_event_time()
|
|
self._grab_pointer.ungrab(time)
|
|
self._grab_keyboard.ungrab(time)
|
|
|
|
def _editing_done(self):
|
|
self._grab_widget.handler_disconnect(self._bp_id)
|
|
self._grab_widget.handler_disconnect(self._kp_id)
|
|
self._grab_widget.handler_disconnect(self._se_id)
|
|
self._edit_widget.editing_done()
|
|
self._edit_widget.remove_widget()
|
|
|
|
def _on_button_press(self, widget, event):
|
|
self._editing_done()
|
|
|
|
if self.pointer_mode:
|
|
self.emit("mapping-edited",
|
|
self._path, event.button, self.pointer_mode)
|
|
return True
|
|
|
|
def _on_key_press(self, widget, event):
|
|
self._editing_done()
|
|
|
|
value = Gdk.keyval_to_lower(event.keyval)
|
|
|
|
if value == Gdk.KEY_BackSpace:
|
|
self.emit("mapping-cleared", self._path, self.pointer_mode)
|
|
elif value == Gdk.KEY_Escape:
|
|
pass
|
|
else:
|
|
if not self.pointer_mode:
|
|
self.emit("mapping-edited",
|
|
self._path, value, self.pointer_mode)
|
|
return True
|
|
|
|
def _on_scroll_event(self, widget, event):
|
|
self._editing_done()
|
|
|
|
if self.pointer_mode:
|
|
# mouse buttons 4 - 7 are delivered as scroll-events
|
|
button = 4 + event.direction
|
|
self.emit("mapping-edited",
|
|
self._path, button, self.pointer_mode)
|
|
return True
|
|
|
|
def do_get_preferred_width(self, widget):
|
|
if self._sizing_label is None:
|
|
self._sizing_label = Gtk.Label(label=_("Press a button..."))
|
|
|
|
return self._sizing_label.get_preferred_width()
|
|
|
|
def do_start_editing(self, event, widget, path, bg_area, cell_area, state):
|
|
if not event: # else SEGFAULT when pressing a keyboard key twice
|
|
return
|
|
|
|
time = event.get_time()
|
|
device = event.get_device()
|
|
if device.get_source() == Gdk.InputSource.KEYBOARD:
|
|
keyboard = device
|
|
pointer = device.get_associated_device()
|
|
else:
|
|
pointer = device
|
|
keyboard = device.get_associated_device()
|
|
|
|
if keyboard.grab(widget.get_window(),
|
|
Gdk.GrabOwnership.WINDOW, False,
|
|
Gdk.EventMask.KEY_PRESS_MASK,
|
|
None, time) != Gdk.GrabStatus.SUCCESS:
|
|
return
|
|
|
|
if pointer.grab(widget.get_window(),
|
|
Gdk.GrabOwnership.WINDOW, False,
|
|
Gdk.EventMask.BUTTON_PRESS_MASK,
|
|
None, time) != Gdk.GrabStatus.SUCCESS:
|
|
keyboard.ungrab(time)
|
|
return
|
|
|
|
Gtk.device_grab_add(widget, pointer, True)
|
|
|
|
self._path = path
|
|
self._grab_pointer = pointer
|
|
self._grab_keyboard = keyboard
|
|
self._grab_widget = widget
|
|
self._bp_id = widget.connect("button-press-event", self._on_button_press)
|
|
self._kp_id = widget.connect("key-press-event", self._on_key_press)
|
|
self._se_id = widget.connect("scroll-event", self._on_scroll_event)
|
|
|
|
style = widget.get_style_context()
|
|
bg = style.get_background_color(Gtk.StateFlags.SELECTED)
|
|
fg = style.get_color(Gtk.StateFlags.SELECTED)
|
|
|
|
if self.pointer_mode:
|
|
text = _("Press a button...")
|
|
else:
|
|
text = _("Press a key...")
|
|
|
|
label = Gtk.Label(label=text,
|
|
halign=Gtk.Align.START,
|
|
valign=Gtk.Align.CENTER)
|
|
label.override_color(Gtk.StateFlags.NORMAL, fg)
|
|
|
|
self._edit_widget = EditableBox(label)
|
|
self._edit_widget.override_background_color(Gtk.StateFlags.NORMAL, bg)
|
|
self._edit_widget.connect("unrealize", self._on_edit_widget_unrealize)
|
|
self._edit_widget.show_all()
|
|
|
|
return self._edit_widget
|
|
|
|
|
|
class EditableBox(Gtk.EventBox, Gtk.CellEditable):
|
|
"""
|
|
Container that implements the Gtk.CellEditable interface.
|
|
"""
|
|
|
|
__gproperties__ = { str('editing-canceled'): (bool, '', '', False,
|
|
GObject.PARAM_READWRITE) }
|
|
|
|
def __init__(self, child=None):
|
|
super(EditableBox, self).__init__()
|
|
|
|
self.editing_canceled = False
|
|
|
|
if child:
|
|
self.add(child)
|
|
|
|
def do_get_property(self, prop):
|
|
if prop.name == 'editing-canceled':
|
|
return self.editing_canceled
|
|
|
|
def do_set_property(self, prop, value):
|
|
if prop.name == 'editing-canceled':
|
|
self.editing_canceled = value
|
|
|
|
def do_start_editing(self, event):
|
|
pass
|
|
|
|
|
|
if __name__ == '__main__':
|
|
s = Settings(True)
|
|
|