KeyboardLayoutEditor (47042B)
1 #!/usr/bin/env python 2 # -*- encoding: UTF-8 -*- 3 # 4 # This program is free software: you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation, either version 3 of the License, or 7 # (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program. If not, see <http://www.gnu.org/licenses/> 16 17 __author__ = "Simos Xenitellis <simos.lists@googlemail.com>" 18 __version__ = "1.1" 19 __date__ = "Date: 2008/09/07" 20 __copyright__ = "Copyright (c) 2008, 2009 Simos Xenitellis" 21 __license__ = "GPLv3" 22 23 # Hidden: *Lexer.py, *Parser.py, *Walker.py, evdev, aliases, *.tokens, *.g, KeysymsUni.py, print_tree.py, parse_*.py 24 25 try: 26 import os 27 import sys 28 import copy 29 import string 30 import gtk 31 import gobject 32 import pango 33 except ImportError, e: 34 raise SystemExit("Import error, Keyboard Layout Editor: " + str(e)) 35 36 if gtk.pygtk_version < (2, 12): 37 print "PyGtk 2.12 or later required for this application" 38 print "You have pyGTK", gtk.pygtk_version 39 raise SystemExit 40 41 try: 42 import cairo 43 except ImportError, e: 44 raise SystemExit("The Python bindings for Cairo are required \ 45 for this application: " + str(e)) 46 47 try: 48 from lxml import etree 49 except ImportError, e: 50 raise SystemExit("The Python bindings for libxml2 were not found: " + str(e)) 51 52 try: 53 import antlr3 54 except ImportError, err: 55 print 'Import error:', err 56 print 'This script requires to have the python antl3 package installed.' 57 print 'Please see the README file for further instructions.\nExiting...' 58 sys.exit(-10) 59 60 try: 61 import Enum 62 import Key 63 import Keyboard 64 import ParseXKB 65 import GenericLayout 66 import Common 67 import SelectVariant 68 from KeyDict import KeyDict, included_files, activated_variants 69 from ParseXML import ExtractVariantsKeycodes 70 from DeadKeysDict import DeadKeys 71 except ImportError, e: 72 raise SystemExit("Import error, Keyboard Layout Editor: " + str(e)) 73 74 class Controller_KeyboardLayoutEditor: 75 """ The keyboard layout editor controller """ 76 77 def __init__(self): 78 # Set the home directory of the application 79 Common.HOMEDIR = os.path.split( os.path.realpath( sys.argv[0] ) )[0] + '/' 80 81 # This is the parser for XKB files, creates an lxml.etree object. 82 self.parse_xkb = ParseXKB.ParseXKB() 83 84 # Here we create an lxml.etree object that holds the current layout. 85 self.xmllayout = GenericLayout.GenericLayout() 86 87 # Include files: the selected model and paths, as dict ("model", "paths") 88 self.include_layout_selected = {} 89 90 # This is the window object. 91 self.window = gtk.Window() 92 93 # This variable holds the state of the layout; where modifications took place. 94 #self.layout_not_empty = False 95 96 # Capture keyboard events (key presses), and highlight the relevant keys. 97 self.root_window = gtk.gdk.get_default_root_window() 98 self.root_window.set_events(gtk.gdk.KEY_PRESS_MASK) 99 gtk.gdk.event_handler_set(self.keypress_filter_callback, None) 100 101 # Set initial size 102 (width, height) = self.window.get_size() 103 self.window.resize(800, 550) 104 105 self.window.connect("check_resize", self.check_resize) 106 107 self.icon_pixbuf = gtk.gdk.pixbuf_new_from_file(Common.HOMEDIR + 'kle-icon4.svg') 108 self.window.set_icon(self.icon_pixbuf) 109 110 self.TARGET_TYPE_TEXT = 80 111 112 self.select_variant = SelectVariant.SelectVariant(self.window) 113 114 # Set window title 115 self.window.set_title("Keyboard Layout Editor") 116 117 # Fill in the dead key section 118 deadkey_frame = gtk.Frame("Dead keys") 119 deadkey_vbox = gtk.VBox() 120 deadkey_info_label = gtk.Label("Drag the dead key (coloured text)" 121 " and drop to the keyboard below. Click on button for more options.") 122 deadkey_info_label.set_line_wrap(True) 123 deadkey_combobox = gtk.ComboBox() 124 deadkey_liststore = gtk.ListStore(str) 125 deadkey_cell = gtk.CellRendererText() 126 deadkey_combobox.pack_start(deadkey_cell) 127 deadkey_combobox.add_attribute(deadkey_cell, 'text', 0) 128 self.deadkey_label = gtk.Label("No dead key") 129 130 PLIST = pango.AttrList() 131 BOLD = pango.AttrWeight(pango.WEIGHT_HEAVY, 0, -1) 132 GRAYBG = pango.AttrBackground(45000, 40000, 55000, 0, -1) 133 PLIST.insert(BOLD) 134 PLIST.insert(GRAYBG) 135 self.deadkey_label.set_property("attributes", PLIST) 136 137 deadkey_eventboxlabel = gtk.EventBox() 138 deadkey_eventboxlabel.add(self.deadkey_label) 139 deadkey_eventboxlabel.connect("drag_data_get", 140 self.deadkey_drag_data_get_callback) 141 deadkey_eventboxlabel.drag_source_set(gtk.gdk.BUTTON1_MASK, 142 [ ( "text/plain", 0, self.TARGET_TYPE_TEXT )], 143 gtk.gdk.ACTION_COPY) 144 145 deadkey_vbox.pack_start(deadkey_info_label, expand=False, fill=False) 146 deadkey_vbox.pack_start(deadkey_eventboxlabel, expand=False, fill=False) 147 deadkey_vbox.pack_start(deadkey_combobox, expand=False, fill=False) 148 deadkey_frame.add(deadkey_vbox) 149 150 self.deadkey_tooltips = gtk.Tooltips() 151 self.set_deadkey_tooltip() 152 153 deadkey_combobox.set_wrap_width(4) 154 deadkey_allnames = [] 155 for deadkey in DeadKeys.dict.keys(): 156 deadkey_allnames.append([DeadKeys.dict[deadkey][0]]) 157 deadkey_allnames.sort() 158 for deadkeyname in deadkey_allnames: 159 deadkey_liststore.append(deadkeyname) 160 deadkey_combobox.set_model(deadkey_liststore) 161 deadkey_combobox.connect('changed', self.changed_deadkey_combobox_callback) 162 deadkey_combobox.set_active(4) 163 164 deadkey_unicodechars_vbox = gtk.VBox() 165 unicodechars_frame = gtk.Frame("Unicode characters") 166 unicodechars_vbox = gtk.VBox() 167 unicodechars_label = gtk.Label("Start Character Map, and then drag \ 168 and drop characters from there to the keyboard below") 169 unicodechars_label.set_line_wrap(True) 170 unicodechars_button = gtk.Button("Start Character map") 171 unicodechars_button.connect("clicked", self.unicodechars_clicked_callback) 172 unicodechars_vbox.pack_start(unicodechars_label, expand = False, fill = False) 173 unicodechars_vbox.pack_start(unicodechars_button, expand = False, fill = False) 174 unicodechars_frame.add(unicodechars_vbox) 175 deadkey_unicodechars_vbox.pack_start(unicodechars_frame, 176 expand = False, 177 fill = False) 178 deadkey_unicodechars_vbox.pack_start(deadkey_frame, 179 expand = False, 180 fill = False) 181 182 includefile_frame = gtk.Frame("Include files") 183 includefile_hbox = gtk.HBox() 184 includefile_button_load = gtk.Button("Load file...") 185 includefile_button_remove = gtk.Button("Remove layout") 186 includefile_button_load.connect("clicked", self.clicked_loadincludefile_callback) 187 includefile_button_remove.connect("clicked", self.clicked_removeentry_callback) 188 includefile_button_remove.set_sensitive(False) 189 includefile_frame.add(includefile_hbox) 190 includefile_vbox = gtk.VBox() 191 includefile_hbox.add(self.doincludes(includefile_button_remove)) 192 includefile_hbox.add(includefile_vbox) 193 includefile_vbox.pack_end(includefile_button_load, expand = False) 194 includefile_vbox.pack_end(includefile_button_remove, expand = False) 195 196 addtolayout_frame = gtk.Frame("Add to layout") 197 addtolayout_hbox = gtk.HBox() 198 addtolayout_frame.add(addtolayout_hbox) 199 addtolayout_hbox.pack_start(includefile_frame, expand=True, fill=True) 200 addtolayout_hbox.pack_start(deadkey_unicodechars_vbox, expand=False, fill=False) 201 202 layoutdetails_frame = gtk.Frame("Layout details") 203 204 layoutdetails_vbox = gtk.VBox() 205 layoutdetails_hbox_code = gtk.HBox() 206 layoutdetails_label_code = gtk.Label("Layout code") 207 self.layoutdetails_entry_code = gtk.Entry() 208 self.layoutdetails_entry_code.set_max_length(24) 209 self.layoutdetails_entry_code.connect("key_release_event", self.entry_callback, self.layoutdetails_entry_code) 210 layoutdetails_code_tooltip = gtk.Tooltips() 211 layoutdetails_code_tooltip_text = "Enter the layout code.\nTypically \ 212 this is the relevant ISO 3166\ncountry code, \ 213 or in special cases,\nthe ISO 639 language code. \ 214 This is two \nor three letters long, lowercase." 215 layoutdetails_code_tooltip.set_tip(self.layoutdetails_entry_code, layoutdetails_code_tooltip_text) 216 layoutdetails_hbox_code.pack_start(layoutdetails_label_code, expand=False, fill=False) 217 layoutdetails_hbox_code.pack_start(self.layoutdetails_entry_code, expand=True, fill=True) 218 layoutdetails_vbox.pack_start(layoutdetails_hbox_code, expand=False, fill=False) 219 220 layoutdetails_hbox_variant = gtk.HBox() 221 layoutdetails_label_variant = gtk.Label("Variant") 222 self.layoutdetails_entry_variant = gtk.Entry() 223 self.layoutdetails_entry_variant.set_max_length(24) 224 self.layoutdetails_entry_variant.connect("key_release_event", self.entry_callback, self.layoutdetails_entry_variant) 225 layoutdetails_variant_tooltip = gtk.Tooltips() 226 layoutdetails_variant_tooltip_text = "Enter the variant name.\nThis is one \ 227 or more alphanumeric characters,\nall lowercase." 228 layoutdetails_variant_tooltip.set_tip(self.layoutdetails_entry_variant, layoutdetails_variant_tooltip_text) 229 layoutdetails_hbox_variant.pack_start(layoutdetails_label_variant, expand=False, fill=False) 230 layoutdetails_hbox_variant.pack_start(self.layoutdetails_entry_variant, expand=True, fill=True) 231 layoutdetails_vbox.pack_start(layoutdetails_hbox_variant, expand=False, fill=False) 232 233 layoutdetails_hbox_country = gtk.HBox() 234 layoutdetails_label_country = gtk.Label("Country name") 235 self.layoutdetails_entry_country = gtk.Entry() 236 self.layoutdetails_entry_country.set_max_length(48) 237 self.layoutdetails_entry_country.connect("key_release_event", self.entry_callback, self.layoutdetails_entry_country) 238 layoutdetails_country_tooltip = gtk.Tooltips() 239 layoutdetails_country_tooltip_text = "Enter the relevant country name for the layout.\n\ 240 You put something like France or Germany.\n\ 241 In very special cases where no country is relevant,\n\ 242 you may put a language name (for example, Arabic).\n\ 243 This information is used when submitting the layout to\n\ 244 the xkeyboard-config project\n\ 245 Please do not put punctuation marks." 246 layoutdetails_country_tooltip.set_tip(self.layoutdetails_entry_country, layoutdetails_country_tooltip_text) 247 layoutdetails_hbox_country.pack_start(layoutdetails_label_country, expand=False, fill=False) 248 layoutdetails_hbox_country.pack_start(self.layoutdetails_entry_country, expand=True, fill=True) 249 layoutdetails_vbox.pack_start(layoutdetails_hbox_country, expand=False, fill=False) 250 251 layoutdetails_hbox_name = gtk.HBox() 252 layoutdetails_label_name = gtk.Label("Layout name") 253 self.layoutdetails_entry_name = gtk.Entry() 254 self.layoutdetails_entry_name.set_max_length(48) 255 self.layoutdetails_entry_name.connect("key_release_event", self.entry_callback, self.layoutdetails_entry_name) 256 layoutdetails_name_tooltip = gtk.Tooltips() 257 layoutdetails_name_tooltip_text = "Enter the layout name.\nIt is \ 258 the name to describe this variant.\n\ 259 This is a free-form description.\n\ 260 Please do not put punctuation marks." 261 layoutdetails_name_tooltip.set_tip(self.layoutdetails_entry_name, layoutdetails_name_tooltip_text) 262 layoutdetails_hbox_name.pack_start(layoutdetails_label_name, expand=False, fill=False) 263 layoutdetails_hbox_name.pack_start(self.layoutdetails_entry_name, expand=True, fill=True) 264 layoutdetails_vbox.pack_start(layoutdetails_hbox_name, expand=False, fill=False) 265 266 preferences_frame = gtk.Frame("Preferences") 267 preferences_vbox = gtk.VBox() 268 preferences_frame.add(preferences_vbox) 269 270 basedir_vbox = gtk.VBox() 271 basedir_label = gtk.Label('Select a new basedir') 272 basedir_vbox.pack_start(basedir_label, expand=False, fill=False) 273 basedirbutton = gtk.Button(Common.basedir) 274 basedirbutton.connect('clicked', self.basedir_set_callback) 275 basedir_vbox.pack_start(basedirbutton, expand=False, fill=False) 276 277 fontbutton_vbox = gtk.VBox() 278 fontbutton_label = gtk.Label("Select a font") 279 fontbutton_vbox.pack_start(fontbutton_label, expand=False, fill=False) 280 fontbutton = gtk.FontButton(fontname=Common.fontname + " " + str(Common.fontsize)) 281 fontbutton.set_title('Select a font') 282 fontbutton.connect('font-set', self.font_set_callback) 283 fontbutton_vbox.pack_start(fontbutton, expand=False, fill=False) 284 285 preferences_vbox.pack_start(fontbutton_vbox, expand=True, fill=True) 286 preferences_vbox.pack_start(basedir_vbox, expand=True, fill=True) 287 288 #layoutdetails_vbox.pack_start(preferences_frame, expand=False, fill=False) 289 290 layoutdetails_frame.add(layoutdetails_vbox) 291 292 layoutdetails_and_preferences_vbox = gtk.VBox() 293 layoutdetails_and_preferences_vbox.pack_start(layoutdetails_frame, expand=True, fill=True) 294 layoutdetails_and_preferences_vbox.pack_start(preferences_frame, expand=True, fill=True) 295 296 layoutdetails_and_addtoframe = gtk.HBox() 297 layoutdetails_and_addtoframe.pack_start(layoutdetails_and_preferences_vbox, expand=False, fill=False) 298 layoutdetails_and_addtoframe.pack_start(addtolayout_frame, expand=True, fill=True) 299 300 # Create an accelerator group 301 accelgroup = gtk.AccelGroup() 302 # Add the accelerator group to the toplevel window 303 self.window.add_accel_group(accelgroup) 304 305 # Create an action for starting with a new layout using a stock item 306 action_new = gtk.Action('New', '_New...', 'New layout', gtk.STOCK_NEW) 307 action_new.set_property('short-label', '_New') 308 # Connect a callback to the action 309 action_new.connect('activate', self.new_layout) 310 311 # Create an action for opening an existing layout using a stock item 312 action_open = gtk.Action('Open', '_Open', 313 'Open an existing layout', gtk.STOCK_OPEN) 314 action_open.set_property('short-label', '_Open') 315 # Connect a callback to the action 316 action_open.connect('activate', self.open_layout) 317 318 # Create an action for saving the layout using a stock item 319 action_save = gtk.Action('Save', '_Save', 'Save the Layout', 320 gtk.STOCK_SAVE) 321 action_save.set_property('short-label', '_Save') 322 # Connect a callback to the action 323 action_save.connect('activate', self.save_layout) 324 325 # Create an action for saving the layout with a new name, using a stock item 326 action_save_as = gtk.Action('SaveAs', 'Save _As', 327 'Save the layout with a different filename', 328 gtk.STOCK_SAVE_AS) 329 action_save_as.set_property('short-label', '_Save As') 330 # Connect a callback to the action 331 action_save_as.connect('activate', self.save_as_layout) 332 333 # Create an action for quitting the program using a stock item 334 action_quit = gtk.Action('Quit', '_Quit', 'Quit the Program', 335 gtk.STOCK_QUIT) 336 action_quit.set_property('short-label', '_Quit') 337 # Connect a callback to the action 338 action_quit.connect('activate', self.quit_application) 339 340 # Create an ActionGroup named BasicAction 341 actiongroup = gtk.ActionGroup('BasicAction') 342 # Add the action to the actiongroup with an accelerator 343 # None means use the stock item accelerator 344 actiongroup.add_action_with_accel(action_new, None) 345 actiongroup.add_action_with_accel(action_open, None) 346 actiongroup.add_action_with_accel(action_save, None) 347 actiongroup.add_action_with_accel(action_save_as, None) 348 actiongroup.add_action_with_accel(action_quit, None) 349 350 # Have the action use accelgroup 351 action_new.set_accel_group(accelgroup) 352 action_open.set_accel_group(accelgroup) 353 action_save.set_accel_group(accelgroup) 354 action_save_as.set_accel_group(accelgroup) 355 action_quit.set_accel_group(accelgroup) 356 357 # Create a MenuBar 358 menubar = gtk.MenuBar() 359 360 # Create the File Action and MenuItem 361 file_action = gtk.Action('File', '_File', None, None) 362 actiongroup.add_action(file_action) 363 file_menuitem = file_action.create_menu_item() 364 menubar.append(file_menuitem) 365 366 # Create the File Menu 367 file_menu = gtk.Menu() 368 file_menuitem.set_submenu(file_menu) 369 370 # Create a proxy MenuItem 371 menuitem_new = action_new.create_menu_item() 372 menuitem_open = action_open.create_menu_item() 373 menuitem_save = action_save.create_menu_item() 374 menuitem_save_as = action_save_as.create_menu_item() 375 menuitem_quit = action_quit.create_menu_item() 376 file_menu.append(menuitem_new) 377 file_menu.append(menuitem_open) 378 file_menu.append(menuitem_save) 379 file_menu.append(menuitem_save_as) 380 file_menu.append(menuitem_quit) 381 382 self.mainvbox = gtk.VBox(False) 383 384 # Here we create the Keyboard object. 385 self.mykeyboard = Keyboard.Keyboard(self.xmllayout) 386 387 # Status bar object 388 Common.statusbar = gtk.Statusbar() 389 Common.statusbar.console_context = Common.statusbar.get_context_id('statusbar') 390 391 self.window.add(self.mainvbox) 392 393 self.mykeyboard.add_events(gtk.gdk.KEY_PRESS_MASK | 394 gtk.gdk.POINTER_MOTION_MASK | 395 gtk.gdk.BUTTON_PRESS_MASK | 396 gtk.gdk.SCROLL_MASK) 397 398 self.mainvbox.pack_start(menubar, expand=False, fill=True) 399 self.mainvbox.pack_start(layoutdetails_and_addtoframe, expand=False, 400 fill=False) 401 self.mainvbox.pack_start(self.mykeyboard, expand=True, fill=True) 402 self.mainvbox.pack_start(Common.statusbar, expand=False, fill=True) 403 404 self.window.connect("destroy", gtk.main_quit) 405 self.window.show_all() 406 407 def doincludes(self, button_remove): 408 # create a TreeStore with one string column to use as the model 409 self.treestore_includes = gtk.TreeStore(str, 'gboolean', 'gboolean') 410 411 self.treestore_includes_iters = {} 412 413 # create the TreeView using treestore 414 self.treeview_includes = gtk.TreeView(self.treestore_includes) 415 416 # get a TreeViewSelection object 417 self.treeview_includes_selection = self.treeview_includes.get_selection() 418 self.treeview_includes_selection.connect('changed', 419 self.on_selection_changed_callback, button_remove) 420 421 # create the TreeViewColumn to display the data 422 self.cells0 = gtk.CellRendererText() 423 self.treeview_includes.insert_column_with_attributes(-1, "Layout", self.cells0, text=0) 424 425 # create the TreeViewColumn to display the data 426 self.cells1 = gtk.CellRendererToggle() 427 self.cells1.connect( 'toggled', 428 self.col1_toggled_callback, 429 self.treestore_includes ) 430 self.treeview_includes.insert_column_with_attributes(-1, "Included", 431 self.cells1, 432 active=1, 433 visible=2) 434 435 # make it searchable 436 self.treeview_includes.set_search_column(0) 437 438 # Allow drag and drop reordering of rows 439 self.treeview_includes.set_reorderable(False) 440 self.treeview_includes.set_enable_tree_lines(True) 441 self.treeview_includes.set_show_expanders(True) 442 443 self.scrolled_window = gtk.ScrolledWindow() 444 self.scrolled_window.add_with_viewport(self.treeview_includes) 445 self.scrolled_window.set_size_request(300, 200) 446 447 return self.scrolled_window 448 449 def clicked_loadincludefile_callback(self, param): 450 chooser = gtk.FileChooserDialog("Load layout file", 451 action=gtk.FILE_CHOOSER_ACTION_OPEN, 452 buttons=(gtk.STOCK_CANCEL, 453 gtk.RESPONSE_CANCEL, 454 gtk.STOCK_OPEN, 455 gtk.RESPONSE_OK)) 456 chooser.set_current_folder(Common.symbolsdir) 457 chooser.set_default_response(gtk.RESPONSE_OK) 458 459 filter = gtk.FileFilter() 460 filter.set_name("All files") 461 filter.add_pattern("*") 462 chooser.add_filter(filter) 463 464 response = chooser.run() 465 if response == gtk.RESPONSE_OK: 466 layout_file = chooser.get_filename() 467 layout_name = layout_file.rpartition('/')[2] 468 for layout_id in included_files.keys(): 469 if included_files[layout_id]["file"] == layout_file: 470 chooser.destroy() 471 self.error_dialog("The file " + layout_file + 472 " has already been loaded. Remove first before reloading.") 473 return 474 #print layout_file, layout_name 475 parse_result = self.parse_xkb.parse_layout(layout_file) 476 #print parse_result 477 if parse_result["success"] == True: 478 # For example, included_files["latin"] = { "file": "/tmp/latin", 479 # "variants": variants_dict, xml_layout } 480 self.add_includefile(layout_file) 481 482 chooser.destroy() 483 Common.addtostatusbar('Successfully added include file ' + layout_file + '.') 484 else: 485 chooser.destroy() 486 self.error_dialog("An error was encountered while trying to parse " \ 487 + layout_file + ". The file is not loaded.") 488 elif response == gtk.RESPONSE_CANCEL: 489 chooser.destroy() 490 491 def add_includefile(self, layout_file, highlight_variant = None): 492 """ Adds an include file in the Include file tree. """ 493 try: 494 layout_file.index('/') 495 fullfilename = True 496 except ValueError: 497 fullfilename = False 498 499 if fullfilename: 500 include_parse_result = self.parse_xkb.parse_layout(layout_file) 501 layout_name = layout_file.rpartition('/')[2] 502 else: 503 include_parse_result = self.parse_xkb.parse_layout(Common.symbolsdir + layout_file) 504 layout_name = layout_file 505 506 #print "ADD++Include_parse_result (after parse_xkb.parse_layout):", include_parse_result 507 if include_parse_result["success"] == True: 508 include_parsed_variants = copy.deepcopy(include_parse_result["variants"]) 509 include_parsed_layout = include_parse_result["layout"] 510 511 # For example, included_files["latin"] = { "file": "/tmp/latin", 512 # "variants": variants_dict, xml_layout } 513 included_files[layout_name] = { 'file': layout_name, 514 'variants' : include_parsed_variants, 515 'xml' : include_parsed_layout} 516 #print "+++included files for", layout_name, included_files[layout_name] 517 518 self.treestore_includes_iters[layout_name] = \ 519 self.treestore_includes.append(None, [layout_name, False, False]) 520 for variant in include_parsed_variants: 521 #print "ADD_INCLUDE, doing variant", variant 522 if highlight_variant != None and variant == highlight_variant: 523 #print "HIGHLIGHT", variant 524 self.treestore_includes.append(self.treestore_includes_iters[layout_name], 525 [variant, True, True]) 526 else: 527 self.treestore_includes.append(self.treestore_includes_iters[layout_name], 528 [variant, False, True]) 529 530 def basedir_set_callback(self, button): 531 """ Signal, emitted when the user presses the basedir button. """ 532 chooser = gtk.FileChooserDialog("Select new basedir", 533 action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, 534 buttons=(gtk.STOCK_CANCEL, 535 gtk.RESPONSE_CANCEL, 536 gtk.STOCK_OPEN, 537 gtk.RESPONSE_OK)) 538 chooser.set_current_folder(Common.basedir) 539 chooser.set_default_response(gtk.RESPONSE_OK) 540 541 filter = gtk.FileFilter() 542 filter.set_name("All files") 543 filter.add_pattern("*") 544 chooser.add_filter(filter) 545 546 response = chooser.run() 547 if response == gtk.RESPONSE_OK: 548 symbols_temp = chooser.get_filename() + '/symbols/' 549 if os.path.isdir(symbols_temp): 550 Common.basedir = chooser.get_filename() 551 Common.symbolsdir = Common.basedir + '/symbols/' 552 button.set_label(Common.basedir) 553 else: 554 chooser.destroy() 555 self.error_dialog('Invalid basedir ' + symbols_temp + '.\n' \ 556 'symbols/ subdirectory not found.') 557 return 558 elif response == gtk.RESPONSE_CANCEL: 559 pass 560 chooser.destroy() 561 562 def new_layout(self, something): 563 """ Invoked when the user clicks File/New... """ 564 if self.layout_not_empty(): 565 if self.ask_to_save_first() == False: 566 Common.addtostatusbar('Canceled the new layout.') 567 return 568 self.reset_layout() 569 Common.addtostatusbar('Created new layout.') 570 571 def reset_layout(self): 572 activated_variants.clear() 573 included_files.clear() 574 Common.currentlayoutfile = '' 575 self.mykeyboard.layoutcode = '' 576 self.mykeyboard.layoutvariant = '' 577 self.mykeyboard.layoutcountry = '' 578 self.mykeyboard.layoutname = '' 579 self.layoutdetails_entry_code.set_text(self.mykeyboard.layoutcode) 580 self.layoutdetails_entry_variant.set_text(self.mykeyboard.layoutvariant) 581 self.layoutdetails_entry_country.set_text(self.mykeyboard.layoutcountry) 582 self.layoutdetails_entry_name.set_text(self.mykeyboard.layoutname) 583 for keycode in KeyDict.Keys.keys(): 584 if keycode not in KeyDict.IgnoreKeys: 585 for segment in Common.keysegmentslist: 586 KeyDict.Keys[keycode].key.keyvalues[segment].reset() 587 588 self.treestore_includes.clear() 589 self.mykeyboard.redraw() 590 591 def ask_to_save_first(self): 592 """ Asks the user if to save. On cancel, we return False. """ 593 shallsave_message = "Would you like to save the existing layout?" 594 shallsave_box = gtk.MessageDialog(parent=self.window, 595 flags=gtk.DIALOG_MODAL, 596 type=gtk.MESSAGE_QUESTION, 597 buttons=gtk.BUTTONS_YES_NO, 598 message_format = shallsave_message) 599 shallsave_box.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) 600 shallsave_box.set_default_response(gtk.RESPONSE_CANCEL) 601 response = shallsave_box.run() 602 shallsave_box.destroy() 603 if response == gtk.RESPONSE_CANCEL: 604 return False 605 elif response == gtk.RESPONSE_YES: 606 if self.save_layout(None): 607 return True 608 else: 609 return False 610 elif response == gtk.RESPONSE_NO: 611 return True 612 return False 613 614 def open_layout(self, something): 615 """ Invoked when the user clicks File/Open... """ 616 if self.layout_not_empty(): 617 if self.ask_to_save_first() == False: 618 Common.addtostatusbar('Canceled opening a layout.') 619 return 620 621 chooser = gtk.FileChooserDialog("Open layout file", 622 action=gtk.FILE_CHOOSER_ACTION_OPEN, 623 buttons=(gtk.STOCK_CANCEL, 624 gtk.RESPONSE_CANCEL, 625 gtk.STOCK_OPEN, 626 gtk.RESPONSE_OK)) 627 chooser.set_current_folder(Common.symbolsdir) 628 chooser.set_default_response(gtk.RESPONSE_OK) 629 630 filter = gtk.FileFilter() 631 filter.set_name("All files") 632 filter.add_pattern("*") 633 chooser.add_filter(filter) 634 635 response = chooser.run() 636 if response == gtk.RESPONSE_OK: 637 layout_file = chooser.get_filename() 638 chooser.destroy() 639 layout_name = layout_file.rpartition('/')[2] 640 #print "We are about to open a file" 641 #print "+We have layout file", layout_file 642 parse_result = self.parse_xkb.parse_layout(layout_file) 643 if parse_result["success"] == True: 644 # For example, included_files["latin"] = { "file": "/tmp/latin", 645 # "variants": variants_dict, xml_layout } 646 647 # Clear the layout constructs 648 #print "+Parse was success", parse_result 649 650 self.select_variant.set_list(parse_result['variants']) 651 self.select_variant.show() 652 # If the user did not select a variant, we cancel the opening of the file. 653 if self.select_variant.get_selection() == '': 654 Common.addtostatusbar('Canceled opening layout file.') 655 return 656 self.reset_layout() 657 variant_verbose_name = '' 658 self.mykeyboard.layoutvariant = self.select_variant.get_selection() 659 Common.currentlayoutfile = layout_file 660 #print "Selected:", self.mykeyboard.layoutvariant 661 result_extract = ExtractVariantsKeycodes(parse_result['layout'], 662 self.mykeyboard.layoutvariant) 663 #print "+Extracting variants from layout", parse_result['layout'], "variant", self.mykeyboard.layoutvariant 664 newkeydict = result_extract['keydict'] 665 newvariants = result_extract['variants'] 666 #print "+Keycodes (newkeydict):", newkeydict 667 #print "+Variants (newvariants):", newvariants 668 669 for keycode in KeyDict.Keys.keys(): 670 if keycode not in KeyDict.IgnoreKeys: 671 if newkeydict.has_key(keycode): 672 for segment in Common.keysegmentslist: 673 if newkeydict[keycode].has_key(segment): 674 KeyDict.Keys[keycode].key.keyvalues[segment].copy(newkeydict[keycode][segment]) 675 676 self.mykeyboard.layoutcode = layout_name 677 self.mykeyboard.layoutname = result_extract['variant_name'] 678 self.layoutdetails_entry_code.set_text(self.mykeyboard.layoutcode) 679 self.layoutdetails_entry_variant.set_text(self.mykeyboard.layoutvariant) 680 self.layoutdetails_entry_name.set_text(self.mykeyboard.layoutname) 681 682 for variant in result_extract['variants']: 683 #print "++Parsed include strings:", Common.parseIncludeString(variant) 684 parsed_variant = Common.parseIncludeString(variant) 685 686 include_parse_result = self.parse_xkb.parse_layout(Common.symbolsdir 687 + parsed_variant['filename']) 688 #print "++Include_parse_result (after parse_xkb.parse_layout):", include_parse_result 689 if include_parse_result["success"] == True: 690 include_parsed_layout = include_parse_result["layout"] 691 if parsed_variant['variant'] == '': 692 """ If not variant is supplied, we select the first one. """ 693 parsed_variant['variant'] = include_parse_result["variants"][0] 694 #print "+++fixed", include_parse_result["variants"][0] 695 696 # For example, included_files["latin"] = { "file": "/tmp/latin", 697 # "variants": variants_dict, xml_layout } 698 included_files[parsed_variant['variant']] = { 699 "file": parsed_variant['filename'], 700 "variants" : copy.deepcopy(include_parse_result["variants"]), 701 "xml" : include_parsed_layout} 702 #print "+++included files for", layout_name, included_files[parsed_variant['variant']] 703 self.add_includefile(parsed_variant['filename'], 704 parsed_variant['variant']) 705 self.select_include_variant(parsed_variant['filename'], 706 parsed_variant['variant'], 707 included_files[parsed_variant['filename']]["xml"]) 708 709 if result_extract['variants'] == []: 710 parsed_variant = [] 711 712 self.mykeyboard.redraw() 713 Common.addtostatusbar('Successfully opened layout file ' + layout_file + ', ' + 714 'selected variant ' + self.mykeyboard.layoutname + '.') 715 else: 716 chooser.destroy() 717 self.error_dialog('An error was encountered while trying to parse ' \ 718 + layout_file + '. The file is not opened.') 719 Common.addtostatusbar('Failed at parsing layout file ' + layout_file + '.') 720 elif response == gtk.RESPONSE_CANCEL: 721 chooser.destroy() 722 Common.addtostatusbar('Canceled opening layout file.') 723 else: 724 # Should not be reached 725 chooser.destroy() 726 727 def save_layout(self, something): 728 """ Invoked when the user clicks File/Save """ 729 if self.save_perform_checks_notifications() == False: 730 return False 731 732 if Common.currentlayoutfile == '': 733 return self.save_as_layout(None) 734 else: 735 if os.access(Common.currentlayoutfile, os.W_OK): 736 result = self.mykeyboard.save(Common.currentlayoutfile) 737 if result == True: 738 Common.addtostatusbar('Layout saved to ' + Common.currentlayoutfile + '.') 739 return True 740 else: 741 return False 742 else: 743 self.error_dialog('Cannot write to file ' + Common.currentlayoutfile + '. Please select to Save As.') 744 Common.addtostatusbar('File save cancelled because file is not writable; please select Save As.') 745 return False 746 747 def save_as_layout(self, arg): 748 """ Invoked when the user clicks File/Save As...""" 749 if self.save_perform_checks_notifications() == False: 750 return False 751 chooser = gtk.FileChooserDialog("Save layout file as...", 752 action=gtk.FILE_CHOOSER_ACTION_SAVE, 753 buttons=(gtk.STOCK_CANCEL, 754 gtk.RESPONSE_CANCEL, 755 gtk.STOCK_SAVE_AS, 756 gtk.RESPONSE_OK)) 757 chooser.set_current_folder(".") 758 chooser.set_default_response(gtk.RESPONSE_OK) 759 chooser.set_filename(self.mykeyboard.layoutcode) 760 761 filter = gtk.FileFilter() 762 filter.set_name("All files") 763 filter.add_pattern("*") 764 chooser.add_filter(filter) 765 766 response = chooser.run() 767 if response == gtk.RESPONSE_OK: 768 temp_filename = chooser.get_filename() 769 chooser.destroy() 770 if os.access(os.path.dirname(temp_filename), os.W_OK): 771 Common.currentlayoutfile = temp_filename 772 self.mykeyboard.save(Common.currentlayoutfile) 773 Common.addtostatusbar('Layout saved to ' + Common.currentlayoutfile + '.') 774 return True 775 else: 776 #print "temp_filename:", temp_filename 777 self.error_dialog('File save cancelled; cannot save to ' + temp_filename + '.') 778 Common.addtostatusbar('File save cancelled; cannot save to ' + temp_filename + '.') 779 return False 780 else: 781 chooser.destroy() 782 return False 783 784 def save_perform_checks_notifications(self): 785 if self.layout_not_empty() == False: 786 self.error_dialog("The layout is empty. Not saving.") 787 return False 788 if self.mykeyboard.layoutcode == '': 789 self.error_dialog("Cannot save layout; please add a layout code.") 790 return False 791 if self.mykeyboard.layoutvariant == '': 792 self.error_dialog("Cannot save layout; please add a layout variant.") 793 return False 794 if self.mykeyboard.layoutname == '': 795 self.error_dialog("Cannot save layout; please add a layout name.") 796 return False 797 return True 798 799 def clicked_removeentry_callback(self, button): 800 model = self.include_layout_selected["model"] 801 path = str(self.include_layout_selected["paths"][0][0]) 802 iter_layout = model.get_iter_from_string(path.partition(':')[0]) 803 selected_layout = model.get_value(iter_layout, 0) 804 805 if activated_variants.has_key(selected_layout): 806 del activated_variants[selected_layout] 807 print "clicked_removeentry_callback:", included_files 808 del included_files[selected_layout] 809 del self.include_layout_selected["model"][self.include_layout_selected["paths"][0]] 810 self.mykeyboard.redraw() 811 Common.addtostatusbar('Removed layout ' + selected_layout) 812 button.set_sensitive(False) 813 814 def col1_toggled_callback( self, cell, path, model ): 815 """ Sets the toggled state on the toggle button to true or false. """ 816 model[path][1] = not model[path][1] 817 818 iter_variant = model.get_iter_from_string(path) 819 iter_layout = model.get_iter_from_string(path.partition(':')[0]) 820 toggle_layout = model.get_value(iter_layout, 0) 821 toggle_variant = model.get_value(iter_variant, 0) 822 823 # If the variant is toggled **ON**, 824 #print "toggling", 825 if model[path][1]: 826 #print "on" 827 if not activated_variants.has_key(toggle_layout): 828 activated_variants[toggle_layout] = {} 829 830 self.select_include_variant(toggle_layout, toggle_variant, 831 included_files[toggle_layout]["xml"]) 832 else: # If the variant is toggled **OFF** 833 #print "off" 834 del activated_variants[toggle_layout][toggle_variant] 835 if len(activated_variants[toggle_layout]) == 0: 836 del activated_variants[toggle_layout] 837 for keycode in KeyDict.Keys.keys(): 838 KeyDict.Keys[keycode].key.extract_display_keyvalues() 839 KeyDict.Keys[keycode].key.redraw() 840 841 def select_include_variant(self, layout, variant, xml_layout): 842 if not activated_variants.has_key(layout): 843 activated_variants[layout] = {} 844 if not activated_variants[layout].has_key(variant): 845 activated_variants[layout][variant] = {} 846 if activated_variants[layout].has_key(variant): 847 activated_variants[layout][variant].clear() 848 activated_variants[layout][variant] = {} 849 store_keydict = {} 850 self.parse_xkb.parse_layout_controller(store_keydict, 851 Common.symbolsdir + layout, 852 variant) 853 activated_variants[layout][variant] = copy.deepcopy(store_keydict) 854 for keycode in activated_variants[layout][variant].keys(): 855 if KeyDict.Keys.has_key(keycode): 856 KeyDict.Keys[keycode].key.extract_display_keyvalues() 857 KeyDict.Keys[keycode].key.redraw() 858 859 def changed_deadkey_combobox_callback(self, combobox): 860 model = combobox.get_model() 861 index = combobox.get_active() 862 if index > -1: 863 self.deadkey_label.set_text(model[index][0]) 864 for dk in DeadKeys.dict.keys(): 865 if DeadKeys.dict[dk][0] == model[index][0]: 866 Common.deadkey = dk 867 break 868 self.set_deadkey_tooltip() 869 870 def deadkey_drag_data_get_callback(self, widget, context, selection, 871 targetType, eventTime): 872 if targetType == self.TARGET_TYPE_TEXT: 873 label = widget.get_children()[0] 874 deadkey_value = '' 875 for deadkey in DeadKeys.dict.keys(): 876 if DeadKeys.dict[deadkey][0] == label.get_text(): 877 deadkey_value = deadkey 878 selection.set(selection.target, 8, deadkey_value) 879 880 def on_selection_changed_callback(self, selection, button): 881 model, paths = selection.get_selected_rows() 882 self.include_layout_selected = { "model": model, "paths": paths } 883 if self.include_layout_selected["paths"]: 884 if len(paths[0]) == 1: 885 button.set_sensitive(True) 886 else: 887 button.set_sensitive(False) 888 889 def entry_callback(self, widget, event, entry): 890 if entry == self.layoutdetails_entry_code: 891 self.mykeyboard.layoutcode = entry.get_text() 892 elif entry == self.layoutdetails_entry_variant: 893 self.mykeyboard.layoutvariant = entry.get_text() 894 elif entry == self.layoutdetails_entry_country: 895 self.mykeyboard.layoutcountry = entry.get_text() 896 elif entry == self.layoutdetails_entry_name: 897 self.mykeyboard.layoutname = entry.get_text() 898 else: 899 SystemError("Internal error at entry boxes") 900 901 def font_set_callback(self, fontbutton): 902 newfont = fontbutton.get_font_name() 903 context = self.window.create_pango_context() 904 for family in context.list_families(): 905 if newfont.find(family.get_name()) == 0: 906 face = family.list_faces()[0] 907 Common.fontname = family.get_name() 908 Common.fontsize = string.atoi(newfont.rpartition(' ')[-1], 10) 909 Common.fontstyle = cairo.FONT_SLANT_NORMAL 910 Common.fontweight = cairo.FONT_WEIGHT_NORMAL 911 if face.get_face_name() == "Regular": 912 Common.fontstyle = cairo.FONT_SLANT_NORMAL 913 if face.get_face_name() == "Bold": 914 Common.fontweight = cairo.FONT_SLANT_BOLD 915 if face.get_face_name() == "Italic": 916 Common.fontstyle = cairo.FONT_SLANT_ITALIC 917 if face.get_face_name() == "Oblique": 918 Common.fontstyle = cairo.FONT_SLANT_OBLIQUE 919 break 920 for keycode in KeyDict.Keys.keys(): 921 KeyDict.Keys[keycode].key.redraw() 922 Common.addtostatusbar('Font set to ' + newfont + '.') 923 924 def set_deadkey_tooltip(self): 925 tooltip_string = "Dead key: " + DeadKeys.dict[Common.deadkey][0] + "\n" 926 tooltip_string += "Keysym: " + Common.deadkey + "\n" 927 tooltip_string += "Representation: " + DeadKeys.dict[Common.deadkey][1] 928 self.deadkey_tooltips.set_tip(self.deadkey_label, tooltip_string) 929 930 def quit_application(self, arg): 931 if self.layout_not_empty(): 932 if self.ask_to_save_first() == True: 933 gtk.main_quit() 934 935 def layout_not_empty(self): 936 if activated_variants != {}: 937 return True 938 if self.mykeyboard.layoutcode != "" or \ 939 self.mykeyboard.layoutvariant != "" or \ 940 self.mykeyboard.layoutname != "": 941 return True 942 for keycode in KeyDict.Keys.keys(): 943 if keycode not in KeyDict.IgnoreKeys: 944 for segment in Common.keysegmentslist: 945 if KeyDict.Keys[keycode].key.keyvalues[segment].getType() !=\ 946 Common.keyvaluetype.NOSYMBOL: 947 return True 948 return False 949 950 def unicodechars_clicked_callback(self, button): 951 if os.fork() == 0 : 952 os.system(Common.gucharmappath) 953 exit(0) 954 955 def check_resize(self, container): 956 pass # TODO: Make sure the keyboard does not get too small. 957 958 def error_dialog(self, message): 959 notification_box = gtk.MessageDialog(parent=self.window, 960 flags=gtk.DIALOG_MODAL, 961 type=gtk.MESSAGE_INFO, 962 buttons=gtk.BUTTONS_OK, 963 message_format = message) 964 notification_box.run() 965 notification_box.destroy() 966 967 def keypress_filter_callback(self, event, data = None): 968 if event.type == gtk.gdk.KEY_PRESS: 969 #print event.keyval, event.string 970 pass 971 gtk.main_do_event(event) 972 973 if __name__ == "__main__": 974 myeditor = Controller_KeyboardLayoutEditor() 975 976 gtk.main()