1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2009 Leo Zheng <zym361@gmail.com>, Kov Chai <tchaikov@gmail.com>
5 # The contents of this file are subject to the terms of either the GNU Lesser
6 # General Public License Version 2.1 only ("LGPL") or the Common Development and
7 # Distribution License ("CDDL")(collectively, the "License"). You may not use this
8 # file except in compliance with the License. You can obtain a copy of the CDDL at
9 # http://www.opensource.org/licenses/cddl1.php and a copy of the LGPLv2.1 at
10 # http://www.opensource.org/licenses/lgpl-license.php. See the License for the
11 # specific language governing permissions and limitations under the License. When
12 # distributing the software, include this License Header Notice in each file and
13 # include the full text of the License in the License file as well as the
16 # NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
18 # For Covered Software in this distribution, this License shall be governed by the
19 # laws of the State of California (excluding conflict-of-law provisions).
20 # Any litigation relating to this License shall be subject to the jurisdiction of
21 # the Federal Courts of the Northern District of California and the state courts
22 # of the State of California, with venue lying in Santa Clara County, California.
26 # If you wish your version of this file to be governed by only the CDDL or only
27 # the LGPL Version 2.1, indicate your decision by adding "[Contributor]" elects to
28 # include this software in this distribution under the [CDDL or LGPL Version 2.1]
29 # license." If you don't indicate a single choice of license, a recipient has the
30 # option to distribute your version of this file under either the CDDL or the LGPL
31 # Version 2.1, or to extend the choice of license to its licensees as provided
32 # above. However, if you add LGPL Version 2.1 code and therefore, elected the LGPL
33 # Version 2 license, then the option applies only if the new code is made subject
34 # to such option by the copyright holder.
45 GETTEXT_PACKAGE="ibus-sunpinyin"
46 _ = lambda msg: gettext.gettext(msg)
48 XML_FILE = path.join(path.dirname(__file__), "setup.xml")
54 print >> sys.stderr, message
57 """Option serves as an interface of ibus.config
59 it is used to synchronize the configuration with setting on user interface
61 config = ibus.Bus().get_config()
63 def __init__(self, name, default):
65 self.default = default
68 section, key = self.__get_config_name()
69 return self.config.get_value(section, key, self.default)
72 section, key = self.__get_config_name()
73 return self.config.set_value(section, key, type(self.default)(v))
76 def __get_config_name(self):
77 keys = self.name.rsplit(SEPARATOR ,1)
79 return SEPARATOR.join(("engine/SunPinyin", keys[0])), keys[1]
82 return "engine/SunPinyin", keys[0]
84 class TrivalOption(Option):
85 """option represented using a simple gtk widget
87 def __init__(self, name, default, owner):
88 super(TrivalOption, self).__init__(name, default)
90 self.widget = owner.get_object(name)
91 assert self.widget is not None, "%s not found in gtkbuilder" % name
100 def read_config(self):
101 """update user inferface with ibus.config
104 self.__set_value(self.v)
106 def write_config(self):
107 v = self.save_ui_setting()
110 def save_ui_setting(self):
111 """save user interface settings into self.v
113 self.v = self.__get_value()
116 def is_changed(self):
117 return self.v != self.__get_value()
119 def __get_value(self):
121 return self.widget.get_value()
123 return self.widget.get_active()
125 def __set_value(self, v):
127 self.widget.set_value(v)
129 self.widget.set_active(v)
131 class CheckBoxOption(TrivalOption):
132 def __init__(self, name, default, owner):
133 super(CheckBoxOption, self).__init__(name, default, owner)
135 class ComboBoxOption(TrivalOption):
136 def __init__(self, name, default, options, owner):
138 default = int(default)
140 default = options.index(default)
141 super(ComboBoxOption, self).__init__(name, default, owner)
142 self.options = options
145 model = gtk.ListStore(str)
146 for v in self.options:
147 model.append([str(v)])
148 self.widget.set_model(model)
150 def __get_value(self):
151 active = self.widget.get_active()
153 # if the options are numbers, save the liternal of active option as
155 return int(self.options[active])
157 # otherwise save its index
160 def __set_value(self, v):
162 # if the options are just numbers, we treat 'self.v' as the literal
164 dummy = int(self.options[0])
165 active = self.options.index(v)
168 self.widget.set_active(active)
170 class RadioOption(Option):
171 """option represented using multiple Raidio buttons
173 def __init__(self, name, default, options, owner):
174 super(RadioOption, self).__init__(name, default)
175 self.options = options
181 def read_config(self):
183 name = SEPARATOR.join([self.name, self.v])
184 button = self.xml.get_object(name)
185 assert button is not None, "button: %r not found" % name
186 button.set_active(True)
188 def write_config(self):
190 for opt in self.options:
191 radio_name = SEPARATOR.join([self.name, opt])
192 radio = self.xml.get_object(radio_name)
193 if radio.get_active():
196 assert active_opt is not None
197 self.write(active_opt)
200 def __init__(self, name, mapping):
202 self.mapping = mapping
204 class MappingOption(object):
205 """an option which presents some sort of mapping, e.g. fuzzy pinyin mapping
207 it is not directly related to a config option like TrivalOption does, but
208 we always have a checkbox in UI for each of it so user can change it easily.
210 def __init__(self, name, mappings, owner):
212 self.widget = owner.get_object(name)
213 self.mappings = mappings
215 def get_mappings(self):
216 if self.widget.get_active():
217 return [':'.join(self.mappings)]
221 def set_active(self, enabled):
222 self.widget.set_active(enabled)
224 def get_active(self):
225 return self.widget.get_active()
227 is_enabled = property(get_active, set_active)
230 return self.mappings[0]
232 class MultiMappingOption(Option):
233 def __init__(self, name, options, default=[]):
234 Option.__init__(self, name, default)
235 self.options = options
236 self.saved_pairs = default
238 def read_config(self):
239 if not self.saved_pairs:
240 self.saved_pairs = self.read()
241 keys = set([pair.split(':')[0] for pair in self.saved_pairs])
242 for opt in self.options:
243 opt.is_enabled = (opt.key() in keys)
244 # throw away unknown pair
246 def write_config(self):
247 # ignore empty settings
249 self.write(self.saved_pairs)
251 def save_ui_setting(self):
252 self.saved_pairs = sum([opt.get_mappings() for opt in self.options
253 if opt.is_enabled], [])
254 return self.saved_pairs
256 def set_active_all(self, enabled):
257 for opt in self.options:
258 opt.is_enabled = enabled
260 class MultiCheckDialog (object):
261 """ a modal dialog box with 'choose all' and 'choose none' button
263 TODO: another option is to use radio button
265 def __init__ (self, ui_name, config_name, mappings, option_klass=MappingOption):
266 self.ui_name = ui_name
267 self.config_name = config_name
268 self.mappings = mappings
269 self.option_klass = option_klass
270 self.saved_settings = []
271 self.mapping_options = None
273 def get_setup_name(self):
274 """assuming the name of dialog looks like 'dlg_fuzzy_setup'
276 return '_'.join(['dlg', self.ui_name, 'setup'])
279 dlg_name = self.get_setup_name()
280 self.__xml = gtk.Builder()
281 self.__xml.add_objects_from_file(XML_FILE, dlg_name)
282 self.__dlg = self.__xml.get_object(dlg_name)
283 assert self.__dlg is not None, "dialog %s not found in %s" % (dlg_name, XML_FILE)
284 handlers = {'_'.join(["on", self.ui_name, "select_all_clicked"]) : self.on_button_check_all_clicked,
285 '_'.join(["on", self.ui_name, "unselect_all_clicked"]) : self.on_button_uncheck_all_clicked,
286 '_'.join(["on", self.ui_name, "ok_clicked"]) : self.on_button_ok_clicked,
287 '_'.join(["on", self.ui_name, "cancel_clicked"]) : self.on_button_cancel_clicked}
288 self.__xml.connect_signals(handlers)
290 options = [self.option_klass(m.name, m.mapping, self.__xml)
291 for m in self.mappings]
292 self.mapping_options = MultiMappingOption(self.config_name, options, self.saved_settings)
295 """a dummy func, i don't initialize myself upon other's request.
296 instead, i will do it by myself.
300 init_ui = read_config = dummy
307 def __read_config(self):
308 self.mapping_options.read_config()
310 def __save_ui_settings(self):
311 """save to in-memory storage, will flush to config if not canceled in main_window
313 self.saved_settings = self.mapping_options.save_ui_setting()
315 def write_config(self):
316 if self.mapping_options is not None:
317 self.mapping_options.write_config()
319 def on_button_check_all_clicked(self, button):
320 self.mapping_options.set_active_all(True)
322 def on_button_uncheck_all_clicked(self, button):
323 self.mapping_options.set_active_all(False)
325 def on_button_ok_clicked(self, button):
326 """update given options with settings in UI, these settings will be
327 written to config if user push 'OK' or 'Apply' in the main window
329 self.__save_ui_settings()
332 def on_button_cancel_clicked(self, button):
335 class FuzzySetupDialog (MultiCheckDialog):
337 mappings = [MappingInfo('QuanPin/Fuzzy/ShiSi', ('sh','s')),
338 MappingInfo('QuanPin/Fuzzy/ZhiZi', ('zh','z')),
339 MappingInfo('QuanPin/Fuzzy/ChiCi', ('ch','c')),
340 MappingInfo('QuanPin/Fuzzy/ShiSi', ('sh','s')),
341 MappingInfo('QuanPin/Fuzzy/AnAng', ('an','ang')),
342 MappingInfo('QuanPin/Fuzzy/OnOng', ('on','ong')),
343 MappingInfo('QuanPin/Fuzzy/EnEng', ('en','eng')),
344 MappingInfo('QuanPin/Fuzzy/InIng', ('in','ing')),
345 MappingInfo('QuanPin/Fuzzy/EngOng', ('eng','ong')),
346 MappingInfo('QuanPin/Fuzzy/IanIang', ('ian','iang')),
347 MappingInfo('QuanPin/Fuzzy/UanUang', ('uan','uang')),
348 MappingInfo('QuanPin/Fuzzy/NeLe', ('n','l')),
349 MappingInfo('QuanPin/Fuzzy/FoHe', ('f','h')),
350 MappingInfo('QuanPin/Fuzzy/LeRi', ('l','r')),
351 MappingInfo('QuanPin/Fuzzy/KeGe', ('k','g'))]
352 MultiCheckDialog.__init__(self,
354 config_name = 'QuanPin/Fuzzy/Pinyins',
357 class CorrectionSetupDialog (MultiCheckDialog):
359 mappings = [MappingInfo('QuanPin/AutoCorrection/GnNg', ('gn','ng')),
360 MappingInfo('QuanPin/AutoCorrection/UenUn', ('uen','un')),
361 MappingInfo('QuanPin/AutoCorrection/ImgIng', ('img','ing')),
362 MappingInfo('QuanPin/AutoCorrection/IouIu', ('iou','iu')),
363 MappingInfo('QuanPin/AutoCorrection/UeiUi', ('uei','ui'))]
364 MultiCheckDialog.__init__(self,
365 ui_name = 'correction',
366 config_name = 'QuanPin/AutoCorrection/Pinyins',
369 class PunctMapping(MappingOption):
370 def __init__(self, name, mappings, owner):
371 MappingOption.__init__(self, name, mappings, owner)
373 self.widget.set_sensitive(True)
374 self.init_keys_values(mappings)
376 self.widget.set_sensitive(False)
378 def init_keys_values(self, mappings):
379 self.keys = [m[0] for m in mappings]
380 values_with_closing = [v or k for k, v in mappings]
382 for v in values_with_closing:
384 self.values.append(v[0])
386 self.values.append(v)
388 self.values.reverse()
390 def get_mappings(self):
391 if self.widget.get_active():
393 for k,vs in self.mappings:
396 pairs.append(':'.join([k,v]))
401 pairs.append(':'.join([k,v]))
406 def set_active(self, enabled):
407 if not self.mappings: return
409 self.widget.set_label('\n'.join(self.values))
411 self.widget.set_label('\n'.join(self.keys))
412 self.widget.set_active(enabled)
414 is_enabled = property(MappingOption.get_active, set_active)
417 for k, v in self.mappings:
423 class PunctMappingSetupDialog (MultiCheckDialog):
424 # TODO: the UI should looks like a virtual keyboard,
425 # user are allowed to choose the mappings to all punctuation keys.
427 mappings = [MappingInfo('togglebutton1', [('`',None), ('~',u'~')]),
428 MappingInfo('togglebutton2', []),
429 MappingInfo('togglebutton3', [('2',None), ('@',u'@')]),
430 MappingInfo('togglebutton4', [('3',None), ('#',u'#')]),
431 MappingInfo('togglebutton5', [('4',None), ('$',u'¥' )]),
432 MappingInfo('togglebutton6', [('5',None), ('%',u'%')]),
433 MappingInfo('togglebutton7', [('6',None), ('^',u'…')]),
434 MappingInfo('togglebutton8', [('7',None), ('&',u'&')]),
435 MappingInfo('togglebutton9', [('8',None), ('*',u'*')]),
436 MappingInfo('togglebutton10', [('9',None), ('*',u'(')]),
437 MappingInfo('togglebutton11', [('0',None), ('*',u')')]),
438 MappingInfo('togglebutton12', [('-',u'-'), ('_',u'——')]),
439 MappingInfo('togglebutton13', [('=',u'='), ('+',u'+')]),
440 MappingInfo('togglebutton14', [('\\',None), ('|',u'‖')]),
441 MappingInfo('togglebutton27', [('[',u'〔'), ('{',u'{')]),
442 MappingInfo('togglebutton28', [(']',u'〕'), ('}',u'}')]),
443 MappingInfo('togglebutton39', []),
444 MappingInfo('togglebutton40', []),
445 MappingInfo('togglebutton50', [(',',None), ('<',u'〈')]),
446 MappingInfo('togglebutton51', [('.',u'·'), ('>',u'〉')]),
447 MappingInfo('togglebutton52', [('/',u'/'), ('?',None)])]
449 MultiCheckDialog.__init__(self, ui_name="punctmapping",
450 config_name="General/PunctMapping/Mappings",
452 option_klass=PunctMapping)
456 'pymodel', 'memory_adjustment', 'candidate_adjustment',
457 'max_best_adjustment', 'max_tail_candidate_adjustment',
461 self.__bus = ibus.Bus()
462 self.__config = self.__bus.get_config()
466 self.__init_ui("main_window")
470 def __init_ui(self, name):
471 self.__init_gettext()
472 xml_file = path.join(path.dirname(__file__), XML_FILE)
473 self.__xml = gtk.Builder()
474 self.__xml.add_objects_from_file(xml_file, self.SPECIAL_OBJECTS)
475 self.__xml.add_objects_from_file(xml_file, [name])
476 self.__xml.connect_signals(self)
477 self.__init_options()
478 self.window = self.__xml.get_object(name)
479 self.window.show_all()
481 def __init_gettext(self):
482 locale.setlocale(locale.LC_ALL, "")
483 localedir = os.getenv("IBUS_LOCALEDIR")
484 gettext.bindtextdomain(GETTEXT_PACKAGE, localedir)
485 gettext.bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8")
487 def __init_options(self):
488 self.__fuzzy_setup = FuzzySetupDialog()
489 self.__correction_setup = CorrectionSetupDialog()
490 self.__punctmapping_setup = PunctMappingSetupDialog()
493 TrivalOption("General/MemoryPower", 3, self.__xml),
494 TrivalOption("General/PageSize", 10, self.__xml),
495 TrivalOption("General/MaxBest", 1, self.__xml),
496 TrivalOption("General/MaxTailCandidate", 0, self.__xml),
498 RadioOption("General/InitialStatus/Mode", 'Chinese', ['Chinese', 'English'], self.__xml),
499 RadioOption("General/InitialStatus/Punct", 'Full', ['Full', 'Half'], self.__xml),
500 RadioOption("General/InitialStatus/Letter", 'Half', ['Full', 'Half'], self.__xml),
501 RadioOption("General/Charset", 'GBK', ['GB2312', 'GBK', 'GB18030'], self.__xml),
502 CheckBoxOption("General/PunctMapping/Enabled", False, self.__xml),
504 RadioOption("Keyboard/ModeSwitch", 'Shift', ['Shift', 'Control'], self.__xml),
505 RadioOption("Keyboard/PunctSwitch", 'None', ['ControlComma',
507 'None'], self.__xml),
508 CheckBoxOption("Keyboard/Page/MinusEquals", False, self.__xml),
509 CheckBoxOption("Keyboard/Page/Brackets", False, self.__xml),
510 CheckBoxOption("Keyboard/Page/CommaPeriod", False, self.__xml),
511 CheckBoxOption("Keyboard/CancelBackspace", True, self.__xml),
512 CheckBoxOption("Keyboard/SmartPunct", True, self.__xml),
514 RadioOption("Pinyin/Scheme", 'QuanPin', ['QuanPin', 'ShuangPin'], self.__xml),
515 ComboBoxOption("Pinyin/ShuangPinType", 'MS2003', ['MS2003',
520 'XiaoHe'], self.__xml),
521 CheckBoxOption("QuanPin/Fuzzy/Enabled", False, self.__xml),
522 CheckBoxOption("QuanPin/AutoCorrection/Enabled", False, self.__xml),
523 CheckBoxOption("QuanPin/FuzzySegs/Enabled", False, self.__xml),
524 CheckBoxOption("QuanPin/InnerFuzzy/Enabled", False, self.__xml),
527 self.__correction_setup,
528 self.__punctmapping_setup,
531 def __get_option(self, name):
532 for opt in self.__options:
538 def __read_config(self):
539 for opt in self.__options:
542 self.on_chk_fuzzy_enabled_toggled(None)
543 self.on_chk_correction_enabled_toggled(None)
544 self.on_chk_punctmapping_enabled_toggled(None)
545 self.on_radio_shuangpin_toggled(None)
547 def __write_config(self):
548 for opt in self.__options:
551 def __update_enabling_button(self, checkbox_name, button_name):
552 """enable a setup button when checked, disable it otherwise
554 checkbox = self.__xml.get_object(checkbox_name)
555 assert checkbox is not None, "checkbox: %s not found" % checkbox_name
556 button = self.__xml.get_object(button_name)
557 assert button is not None, "button: %s not found" % button_name
558 button_enabled = checkbox.get_active()
559 button.set_sensitive(button_enabled)
561 def on_radio_shuangpin_toggled(self, button):
562 radio = self.__xml.get_object("Pinyin/Scheme/ShuangPin")
563 enabled = radio.get_active()
564 combo = self.__xml.get_object("Pinyin/ShuangPinType")
565 combo.set_sensitive(enabled)
567 def on_chk_fuzzy_enabled_toggled(self, button):
568 self.__update_enabling_button("QuanPin/Fuzzy/Enabled",
569 "button_fuzzy_setup")
571 def on_button_fuzzy_setup_clicked(self, button):
572 self.__fuzzy_setup.run()
574 def on_chk_correction_enabled_toggled(self, button):
575 self.__update_enabling_button("QuanPin/AutoCorrection/Enabled",
576 "button_correction_setup")
578 def on_chk_smartseg_enabled_toggled(self, button):
579 self.__update_enabling_button("QuanPin/FuzzySegs/Enabled",
580 "QuanPin/InnerFuzzy/Enabled")
582 def on_button_correction_setup_clicked(self, button):
583 self.__correction_setup.run()
585 def on_chk_punctmapping_enabled_toggled(self, button):
586 self.__update_enabling_button("General/PunctMapping/Enabled",
587 "button_punctmapping_setup")
589 def on_button_punctmapping_setup_clicked(self, button):
590 self.__punctmapping_setup.run()
592 def on_main_ok_clicked(self, button):
593 self.__write_config()
596 def on_main_apply_clicked(self, button):
597 self.__write_config()
599 def on_main_cancel_clicked(self, button):
605 if __name__ == "__main__":