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__":