1# -*- coding: utf-8 -*- 2# 3# Caribou - text entry and UI navigation application 4# 5# Copyright (C) 2010 Eitan Isaacson <eitan@monotonous.org> 6# 7# This program is free software; you can redistribute it and/or modify it 8# under the terms of the GNU Lesser General Public License as published by the 9# Free Software Foundation; either version 2.1 of the License, or (at your 10# option) any later version. 11# 12# This program is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 15# for more details. 16# 17# You should have received a copy of the GNU Lesser General Public License 18# along with this program; if not, write to the Free Software Foundation, 19# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 21from caribou.settings.setting_types import * 22 23import gi 24from gi.repository import GObject 25gi.require_version('Gtk', '3.0') 26gi.require_version('Gdk', '3.0') 27from gi.repository import Gdk 28from gi.repository import Gtk 29 30class AbstractPreferencesUI: 31 def populate_settings(self, settings_manager): 32 if getattr(self, "notebook", None) is None: 33 self.notebook = Gtk.Notebook() 34 self._populate_settings(self.notebook, settings_manager.groups) 35 self.notebook.set_show_tabs(self.notebook.get_n_pages() != 1) 36 37 return self.notebook 38 39 def _populate_settings(self, parent, setting, level=0): 40 if level == 0: 41 for s in setting: 42 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 43 parent.append_page(vbox, Gtk.Label(label=s.label)) 44 self._populate_settings(vbox, s, 1) 45 else: 46 parent.set_border_width(6) 47 table = None 48 row = 0 49 for s in setting: 50 if not isinstance(s, SettingsGroup): 51 if table is None: 52 table = Gtk.Table.new(1, 2, False) 53 table.set_row_spacings(3) 54 table.set_col_spacings(3) 55 parent.pack_start(table, False, False, 0) 56 self._create_widget(table, row, s) 57 row += 1 58 else: 59 table = None 60 frame = Gtk.Frame() 61 frame.set_shadow_type(Gtk.ShadowType.NONE) 62 label = Gtk.Label() 63 label.set_markup('<b>%s</b>' % s.label) 64 frame.set_label_widget(label) 65 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 66 frame.add(vbox) 67 parent.pack_start(frame, False, False, 0) 68 self._sensitivity_changed_cb(s, s.sensitive, frame, None) 69 s.connect("sensitivity-changed", 70 self._sensitivity_changed_cb, 71 frame, None) 72 self._populate_settings(vbox, s, level + 1) 73 74 def _create_widget(self, table, row, setting, xpadding=0): 75 control = None 76 label = None 77 value_changed_cb = None 78 control_changed_cb = None 79 control_changed_signal = None 80 if isinstance(setting, BooleanSetting): 81 control = Gtk.CheckButton.new_with_label(setting.label) 82 control.set_active(setting.value) 83 value_changed_cb = lambda s, v, w: w.set_active(v) 84 control_changed_cb = self._checkbutton_toggled_cb 85 control_changed_signal = 'toggled' 86 else: 87 label = Gtk.Label(label="%s:" % setting.label) 88 label.set_alignment(0.0, 0.5) 89 90 if setting.entry_type == ENTRY_COLOR: 91 control = Gtk.ColorButton.new_with_color( 92 Gdk.color_parse(setting.value)[1]) 93 value_changed_cb = \ 94 lambda s, v, w: w.set_color(Gdk.color_parse(v)) 95 control_changed_cb = self._colorbutton_changed_cb 96 control_changed_signal = 'color-set' 97 elif setting.entry_type == ENTRY_FONT: 98 control = Gtk.FontButton.new_with_font(setting.value) 99 value_changed_cb = lambda s, v, w: w.set_font_name(v) 100 control_changed_cb = self._fontbutton_changed_cb 101 control_changed_signal = 'font-set' 102 elif setting.entry_type == ENTRY_SPIN: 103 control = Gtk.SpinButton() 104 if isinstance(setting.value, float): 105 control.set_digits(2) 106 control.set_increments(0.01, 0.1) 107 control.set_range(setting.min, setting.max) 108 control.set_value(setting.value) 109 control.update() 110 value_changed_cb = lambda s, v, w: w.set_value(v) 111 control_changed_cb = self._spinner_changed_cb 112 control_changed_signal = "value-changed" 113 elif setting.entry_type == ENTRY_RADIO and setting.allowed: 114 if setting.children: 115 assert len(setting.children) == len(setting.allowed), \ 116 "If a radio entry has children, they must be equal " \ 117 "in quantity to the allowed values." 118 label = None 119 control = Gtk.Table.new( 120 len(setting.allowed) + len(setting.children), 2, False) 121 control.set_row_spacings(3) 122 control.set_col_spacings(3) 123 radios = [] 124 for string, localized in setting.allowed: 125 rb = Gtk.RadioButton.new_with_label([], localized) 126 radios.append(rb) 127 for radio, allowed in zip(radios[1:], setting.allowed[1:]): 128 radio.join_group(radios[0]) 129 if allowed[0] == setting.value: 130 radio.set_active(True) 131 132 hid = setting.connect( 133 'value-changed', 134 lambda s, v, rs: \ 135 rs[[a for \ 136 a, b in s.allowed].index(v)].set_active(True), 137 radios) 138 139 r = 0 140 for i, radio in enumerate(radios): 141 radio.connect('toggled', self._radio_changed_cb, setting, 142 radios, hid) 143 control.attach( 144 radio, 0, 2, r, r + 1, 145 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 146 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 147 0, 0) 148 r += 1 149 if setting.children: 150 self._create_widget(control, r, 151 setting.children[i], 12) 152 r += 1 153 154 elif setting.entry_type == ENTRY_COMBO or setting.allowed: 155 control = Gtk.ComboBoxText.new() 156 for option in setting.allowed: 157 control.append(str(option[0]), option[1]) 158 control.set_active_id(str(setting.value)) 159 value_changed_cb = lambda s, v, w: w.set_active_id(str(v)) 160 control_changed_cb = self._combo_changed_cb 161 control_changed_signal = 'changed' 162 else: 163 control = Gtk.Entry() 164 control.set_text(setting.value) 165 value_changed_cb = lambda s, v, w: w.set_text(v) 166 control_changed_cb = self._string_changed_cb 167 control_changed_signal = 'insert-at-cursor' 168 169 if label is not None: 170 table.attach(label, 0, 1, row, row + 1, 171 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 172 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 173 xpadding, 0) 174 table.attach(control, 1, 2, row, row + 1, 175 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 176 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 177 0, 0) 178 else: 179 table.attach(control, 0, 2, row, row + 1, 180 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 181 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, 182 xpadding, 0) 183 184 self._sensitivity_changed_cb(setting, setting.sensitive, control, 185 label) 186 setting.connect("sensitivity-changed", self._sensitivity_changed_cb, 187 control, label) 188 189 if value_changed_cb and control_changed_signal and control_changed_cb: 190 hid = setting.connect('value-changed', value_changed_cb, control) 191 control.connect(control_changed_signal, control_changed_cb, 192 setting, hid) 193 194 def _sensitivity_changed_cb(self, setting, sensitive, control, label): 195 for w in (control, label): 196 if w is not None: 197 w.set_sensitive(sensitive) 198 199 def _update_setting(self, setting, value, handler_id): 200 if setting.value == value: return 201 setting.handler_block(handler_id) 202 setting.value = value 203 setting.handler_unblock(handler_id) 204 205 def _radio_changed_cb(self, radio, setting, radios, handler_id): 206 if not radio.get_active(): 207 return 208 209 i = radios.index(radio) 210 self._update_setting(setting, setting.allowed[i][0], handler_id) 211 212 def _spinner_changed_cb(self, spinner, setting, handler_id): 213 self._update_setting(setting, spinner.get_value(), handler_id) 214 215 def _checkbutton_toggled_cb(self, checkbutton, setting, handler_id): 216 self._update_setting(setting, checkbutton.get_active(), handler_id) 217 218 def _colorbutton_changed_cb(self, colorbutton, setting, handler_id): 219 self._update_setting(setting, colorbutton.get_color().to_string(), 220 handler_id) 221 222 def _fontbutton_changed_cb(self, fontbutton, setting, handler_id): 223 self._update_setting(setting, fontbutton.get_font_name(), handler_id) 224 225 def _string_changed_cb(self, entry, text, setting, handler_id): 226 self._update_setting(setting, entry.get_text(), handler_id) 227 228 def _combo_changed_cb(self, combo, setting, handler_id): 229 self._update_setting(setting, combo.get_active_id(), 230 handler_id) 231 232class PreferencesDialog(Gtk.Dialog, AbstractPreferencesUI): 233 __gtype_name__ = "PreferencesDialog" 234 235 def __init__(self, settings_manager): 236 GObject.GObject.__init__(self) 237 self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) 238 self.set_border_width(6) 239 self.set_title(settings_manager.groups.label) 240 241 notebook = self.populate_settings(settings_manager) 242 vbox = self.get_content_area() 243 vbox.add(notebook) 244 245class PreferencesWindow(Gtk.Window, AbstractPreferencesUI): 246 __gtype_name__ = "PreferencesWindow" 247 248 def __init__(self, settings_manager): 249 GObject.GObject.__init__(self) 250 self.set_border_width(6) 251 self.set_title(settings_manager.groups.label) 252 253 notebook = self.populate_settings(settings_manager) 254 self.add(notebook) 255 256if __name__ == "__main__": 257 from caribou.settings.settings_manager import SettingsManager 258 from caribou.settings import CaribouSettings 259 260 import signal 261 signal.signal(signal.SIGINT, signal.SIG_DFL) 262 263 w = PreferencesDialog(CaribouSettings()) 264 w.show_all() 265 266 try: 267 w.run() 268 except KeyboardInterrupt: 269 Gtk.main_quit() 270