keyboardlayouteditor

[Fork] Archive of the keyboard layout editor.
git clone https://git.jojolepro.com/keyboardlayouteditor.git
Log | Files | Refs | README | LICENSE

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()