| 1 | # -*- coding:utf-8 -*- |
|---|
| 2 | ## src/advanced.py |
|---|
| 3 | ## |
|---|
| 4 | ## Copyright (C) 2005 Travis Shirk <travis AT pobox.com> |
|---|
| 5 | ## Vincent Hanquez <tab AT snarc.org> |
|---|
| 6 | ## Copyright (C) 2005-2007 Yann Leboulanger <asterix AT lagaule.org> |
|---|
| 7 | ## Nikos Kouremenos <kourem AT gmail.com> |
|---|
| 8 | ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com> |
|---|
| 9 | ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org> |
|---|
| 10 | ## |
|---|
| 11 | ## This file is part of Gajim. |
|---|
| 12 | ## |
|---|
| 13 | ## Gajim is free software; you can redistribute it and/or modify |
|---|
| 14 | ## it under the terms of the GNU General Public License as published |
|---|
| 15 | ## by the Free Software Foundation; version 3 only. |
|---|
| 16 | ## |
|---|
| 17 | ## Gajim is distributed in the hope that it will be useful, |
|---|
| 18 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 19 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 20 | ## GNU General Public License for more details. |
|---|
| 21 | ## |
|---|
| 22 | ## You should have received a copy of the GNU General Public License |
|---|
| 23 | ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 24 | ## |
|---|
| 25 | |
|---|
| 26 | import gtk |
|---|
| 27 | import gtkgui_helpers |
|---|
| 28 | import gobject |
|---|
| 29 | |
|---|
| 30 | from common import gajim |
|---|
| 31 | |
|---|
| 32 | ( |
|---|
| 33 | OPT_TYPE, |
|---|
| 34 | OPT_VAL |
|---|
| 35 | ) = range(2) |
|---|
| 36 | |
|---|
| 37 | ( |
|---|
| 38 | C_PREFNAME, |
|---|
| 39 | C_VALUE, |
|---|
| 40 | C_TYPE |
|---|
| 41 | ) = range(3) |
|---|
| 42 | |
|---|
| 43 | GTKGUI_GLADE = 'manage_accounts_window.glade' |
|---|
| 44 | |
|---|
| 45 | def rate_limit(rate): |
|---|
| 46 | ''' call func at most *rate* times per second ''' |
|---|
| 47 | def decorator(func): |
|---|
| 48 | timeout = [None] |
|---|
| 49 | def f(*args, **kwargs): |
|---|
| 50 | if timeout[0] is not None: |
|---|
| 51 | gobject.source_remove(timeout[0]) |
|---|
| 52 | timeout[0] = None |
|---|
| 53 | def timeout_func(): |
|---|
| 54 | func(*args, **kwargs) |
|---|
| 55 | timeout[0] = None |
|---|
| 56 | timeout[0] = gobject.timeout_add(int(1000.0 / rate), timeout_func) |
|---|
| 57 | return f |
|---|
| 58 | return decorator |
|---|
| 59 | |
|---|
| 60 | def tree_model_iter_children(model, treeiter): |
|---|
| 61 | it = model.iter_children(treeiter) |
|---|
| 62 | while it: |
|---|
| 63 | yield it |
|---|
| 64 | it = model.iter_next(it) |
|---|
| 65 | |
|---|
| 66 | def tree_model_pre_order(model, treeiter): |
|---|
| 67 | yield treeiter |
|---|
| 68 | for childiter in tree_model_iter_children(model, treeiter): |
|---|
| 69 | for it in tree_model_pre_order(model, childiter): |
|---|
| 70 | yield it |
|---|
| 71 | |
|---|
| 72 | try: |
|---|
| 73 | any(()) # builtin since python 2.5 |
|---|
| 74 | except Exception: |
|---|
| 75 | def any(iterable): |
|---|
| 76 | for element in iterable: |
|---|
| 77 | if element: |
|---|
| 78 | return True |
|---|
| 79 | return False |
|---|
| 80 | |
|---|
| 81 | class AdvancedConfigurationWindow(object): |
|---|
| 82 | def __init__(self): |
|---|
| 83 | self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade') |
|---|
| 84 | self.window = self.xml.get_widget('advanced_configuration_window') |
|---|
| 85 | self.window.set_transient_for( |
|---|
| 86 | gajim.interface.instances['preferences'].window) |
|---|
| 87 | self.entry = self.xml.get_widget('advanced_entry') |
|---|
| 88 | self.desc_label = self.xml.get_widget('advanced_desc_label') |
|---|
| 89 | self.restart_label = self.xml.get_widget('restart_label') |
|---|
| 90 | |
|---|
| 91 | # Format: |
|---|
| 92 | # key = option name (root/subopt/opt separated by \n then) |
|---|
| 93 | # value = array(oldval, newval) |
|---|
| 94 | self.changed_opts = {} |
|---|
| 95 | |
|---|
| 96 | # For i18n |
|---|
| 97 | self.right_true_dict = {True: _('Activated'), False: _('Deactivated')} |
|---|
| 98 | self.types = { |
|---|
| 99 | 'boolean': _('Boolean'), |
|---|
| 100 | 'integer': _('Integer'), |
|---|
| 101 | 'string': _('Text'), |
|---|
| 102 | 'color': _('Color')} |
|---|
| 103 | |
|---|
| 104 | treeview = self.xml.get_widget('advanced_treeview') |
|---|
| 105 | self.treeview = treeview |
|---|
| 106 | self.model = gtk.TreeStore(str, str, str) |
|---|
| 107 | self.fill_model() |
|---|
| 108 | self.model.set_sort_column_id(0, gtk.SORT_ASCENDING) |
|---|
| 109 | self.modelfilter = self.model.filter_new() |
|---|
| 110 | self.modelfilter.set_visible_func(self.visible_func) |
|---|
| 111 | |
|---|
| 112 | renderer_text = gtk.CellRendererText() |
|---|
| 113 | col = treeview.insert_column_with_attributes(-1, _('Preference Name'), |
|---|
| 114 | renderer_text, text = 0) |
|---|
| 115 | col.set_resizable(True) |
|---|
| 116 | |
|---|
| 117 | renderer_text = gtk.CellRendererText() |
|---|
| 118 | renderer_text.connect('edited', self.on_config_edited) |
|---|
| 119 | col = treeview.insert_column_with_attributes(-1, _('Value'), |
|---|
| 120 | renderer_text, text = 1) |
|---|
| 121 | col.set_cell_data_func(renderer_text, self.cb_value_column_data) |
|---|
| 122 | |
|---|
| 123 | col.props.resizable = True |
|---|
| 124 | col.set_max_width(250) |
|---|
| 125 | |
|---|
| 126 | renderer_text = gtk.CellRendererText() |
|---|
| 127 | treeview.insert_column_with_attributes(-1, _('Type'), |
|---|
| 128 | renderer_text, text = 2) |
|---|
| 129 | |
|---|
| 130 | treeview.set_model(self.modelfilter) |
|---|
| 131 | |
|---|
| 132 | # connect signal for selection change |
|---|
| 133 | treeview.get_selection().connect('changed', |
|---|
| 134 | self.on_advanced_treeview_selection_changed) |
|---|
| 135 | |
|---|
| 136 | self.xml.signal_autoconnect(self) |
|---|
| 137 | self.window.show_all() |
|---|
| 138 | self.restart_label.hide() |
|---|
| 139 | gajim.interface.instances['advanced_config'] = self |
|---|
| 140 | |
|---|
| 141 | def cb_value_column_data(self, col, cell, model, iter_): |
|---|
| 142 | '''check if it's boolen or holds password stuff and if yes |
|---|
| 143 | make the cellrenderertext not editable else it's editable''' |
|---|
| 144 | optname = model[iter_][C_PREFNAME] |
|---|
| 145 | opttype = model[iter_][C_TYPE] |
|---|
| 146 | if opttype == self.types['boolean'] or optname == 'password': |
|---|
| 147 | cell.set_property('editable', False) |
|---|
| 148 | else: |
|---|
| 149 | cell.set_property('editable', True) |
|---|
| 150 | |
|---|
| 151 | def get_option_path(self, model, iter_): |
|---|
| 152 | # It looks like path made from reversed array |
|---|
| 153 | # path[0] is the true one optname |
|---|
| 154 | # path[1] is the key name |
|---|
| 155 | # path[2] is the root of tree |
|---|
| 156 | # last two is optional |
|---|
| 157 | path = [model[iter_][0].decode('utf-8')] |
|---|
| 158 | parent = model.iter_parent(iter_) |
|---|
| 159 | while parent: |
|---|
| 160 | path.append(model[parent][0].decode('utf-8')) |
|---|
| 161 | parent = model.iter_parent(parent) |
|---|
| 162 | return path |
|---|
| 163 | |
|---|
| 164 | def on_advanced_treeview_selection_changed(self, treeselection): |
|---|
| 165 | model, iter = treeselection.get_selected() |
|---|
| 166 | # Check for GtkTreeIter |
|---|
| 167 | if iter: |
|---|
| 168 | opt_path = self.get_option_path(model, iter) |
|---|
| 169 | # Get text from first column in this row |
|---|
| 170 | desc = None |
|---|
| 171 | if len(opt_path) == 3: |
|---|
| 172 | desc = gajim.config.get_desc_per(opt_path[2], opt_path[1], |
|---|
| 173 | opt_path[0]) |
|---|
| 174 | elif len(opt_path) == 1: |
|---|
| 175 | desc = gajim.config.get_desc(opt_path[0]) |
|---|
| 176 | if desc: |
|---|
| 177 | self.desc_label.set_text(desc) |
|---|
| 178 | else: |
|---|
| 179 | #we talk about option description in advanced configuration editor |
|---|
| 180 | self.desc_label.set_text(_('(None)')) |
|---|
| 181 | |
|---|
| 182 | def remember_option(self, option, oldval, newval): |
|---|
| 183 | if option in self.changed_opts: |
|---|
| 184 | self.changed_opts[option] = (self.changed_opts[option][0], newval) |
|---|
| 185 | else: |
|---|
| 186 | self.changed_opts[option] = (oldval, newval) |
|---|
| 187 | |
|---|
| 188 | def on_advanced_treeview_row_activated(self, treeview, path, column): |
|---|
| 189 | modelpath = self.modelfilter.convert_path_to_child_path(path) |
|---|
| 190 | modelrow = self.model[modelpath] |
|---|
| 191 | option = modelrow[0].decode('utf-8') |
|---|
| 192 | if modelrow[2] == self.types['boolean']: |
|---|
| 193 | for key in self.right_true_dict.keys(): |
|---|
| 194 | if self.right_true_dict[key] == modelrow[1]: |
|---|
| 195 | modelrow[1] = key |
|---|
| 196 | newval = {'False': True, 'True': False}[modelrow[1]] |
|---|
| 197 | if len(modelpath) > 1: |
|---|
| 198 | optnamerow = self.model[modelpath[0]] |
|---|
| 199 | optname = optnamerow[0].decode('utf-8') |
|---|
| 200 | keyrow = self.model[modelpath[:2]] |
|---|
| 201 | key = keyrow[0].decode('utf-8') |
|---|
| 202 | gajim.config.get_desc_per(optname, key, option) |
|---|
| 203 | self.remember_option(option + '\n' + key + '\n' + optname, |
|---|
| 204 | modelrow[1], newval) |
|---|
| 205 | gajim.config.set_per(optname, key, option, newval) |
|---|
| 206 | else: |
|---|
| 207 | self.remember_option(option, modelrow[1], newval) |
|---|
| 208 | gajim.config.set(option, newval) |
|---|
| 209 | gajim.interface.save_config() |
|---|
| 210 | modelrow[1] = self.right_true_dict[newval] |
|---|
| 211 | self.check_for_restart() |
|---|
| 212 | |
|---|
| 213 | def check_for_restart(self): |
|---|
| 214 | self.restart_label.hide() |
|---|
| 215 | for opt in self.changed_opts: |
|---|
| 216 | opt_path = opt.split('\n') |
|---|
| 217 | if len(opt_path)==3: |
|---|
| 218 | restart = gajim.config.get_restart_per(opt_path[2], opt_path[1], |
|---|
| 219 | opt_path[0]) |
|---|
| 220 | else: |
|---|
| 221 | restart = gajim.config.get_restart(opt_path[0]) |
|---|
| 222 | if restart: |
|---|
| 223 | if self.changed_opts[opt][0] != self.changed_opts[opt][1]: |
|---|
| 224 | self.restart_label.show() |
|---|
| 225 | break |
|---|
| 226 | |
|---|
| 227 | def on_config_edited(self, cell, path, text): |
|---|
| 228 | # convert modelfilter path to model path |
|---|
| 229 | modelpath = self.modelfilter.convert_path_to_child_path(path) |
|---|
| 230 | modelrow = self.model[modelpath] |
|---|
| 231 | option = modelrow[0].decode('utf-8') |
|---|
| 232 | text = text.decode('utf-8') |
|---|
| 233 | if len(modelpath) > 1: |
|---|
| 234 | optnamerow = self.model[modelpath[0]] |
|---|
| 235 | optname = optnamerow[0].decode('utf-8') |
|---|
| 236 | keyrow = self.model[modelpath[:2]] |
|---|
| 237 | key = keyrow[0].decode('utf-8') |
|---|
| 238 | self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1], |
|---|
| 239 | text) |
|---|
| 240 | gajim.config.set_per(optname, key, option, text) |
|---|
| 241 | else: |
|---|
| 242 | self.remember_option(option, modelrow[1], text) |
|---|
| 243 | gajim.config.set(option, text) |
|---|
| 244 | gajim.interface.save_config() |
|---|
| 245 | modelrow[1] = text |
|---|
| 246 | self.check_for_restart() |
|---|
| 247 | |
|---|
| 248 | def on_advanced_configuration_window_destroy(self, widget): |
|---|
| 249 | del gajim.interface.instances['advanced_config'] |
|---|
| 250 | |
|---|
| 251 | def on_advanced_close_button_clicked(self, widget): |
|---|
| 252 | self.window.destroy() |
|---|
| 253 | |
|---|
| 254 | def fill_model(self, node=None, parent=None): |
|---|
| 255 | for item, option in gajim.config.get_children(node): |
|---|
| 256 | name = item[-1] |
|---|
| 257 | if option is None: # Node |
|---|
| 258 | newparent = self.model.append(parent, [name, '', '']) |
|---|
| 259 | self.fill_model(item, newparent) |
|---|
| 260 | else: # Leaf |
|---|
| 261 | type_ = self.types[option[OPT_TYPE][0]] |
|---|
| 262 | if name == 'password': |
|---|
| 263 | value = _('Hidden') |
|---|
| 264 | else: |
|---|
| 265 | value = self.right_true_dict.get(option[OPT_VAL], |
|---|
| 266 | option[OPT_VAL]) |
|---|
| 267 | self.model.append(parent, [name, value, type_]) |
|---|
| 268 | |
|---|
| 269 | def visible_func(self, model, treeiter): |
|---|
| 270 | search_string = self.entry.get_text() |
|---|
| 271 | return any(search_string in model[it][C_PREFNAME] for it in |
|---|
| 272 | tree_model_pre_order(model, treeiter) if model[it][C_TYPE] != '') |
|---|
| 273 | |
|---|
| 274 | @rate_limit(3) |
|---|
| 275 | def on_advanced_entry_changed(self, widget): |
|---|
| 276 | self.modelfilter.refilter() |
|---|
| 277 | if not widget.get_text(): |
|---|
| 278 | # Maybe the expanded rows should be remembered here ... |
|---|
| 279 | self.treeview.collapse_all() |
|---|
| 280 | else: |
|---|
| 281 | # ... and be restored correctly here |
|---|
| 282 | self.treeview.expand_all() |
|---|
| 283 | |
|---|
| 284 | # vim: se ts=3: |
|---|