environment.py

Go to the documentation of this file.
00001 # Copyright 2005, 2006  Timo Savola
00002 #
00003 # This program is free software; you can redistribute it and/or modify
00004 # it under the terms of the GNU Lesser General Public License as published
00005 # by the Free Software Foundation; either version 2.1 of the License, or
00006 # (at your option) any later version.
00007 
00008 import sys
00009 import os
00010 import signal
00011 import glob
00012 import time
00013 import gobject
00014 import gtk
00015 import gtk.glade
00016 from gettext import gettext
00017 
00018 import extension
00019 import config
00020 import execution
00021 import tab
00022 import settings
00023 
00024 datadir = None
00025 
00026 class Environment (gobject.GObject):
00027         def __init__(self, version):
00028                 gobject.GObject.__init__(self)
00029 
00030                 self.connect('quit', self.on_quit)
00031 
00032                 # Config
00033 
00034                 self.conf = config.Directory('encode')
00035 
00036                 # Glade
00037 
00038                 path = os.path.join(datadir, 'encode', 'environment.glade')
00039                 self.glade = gtk.glade.XML(path)
00040                 self.glade.signal_autoconnect(self)
00041 
00042                 window = self.glade.get_widget('window')
00043 
00044                 # UI manager
00045 
00046                 ui = get_ui_manager()
00047 
00048                 group = ui.get_accel_group()
00049                 window.add_accel_group(group)
00050 
00051                 # UI actions
00052 
00053                 group = gtk.ActionGroup('encode-environment')
00054 
00055                 action = gtk.Action('environment', gettext('Environment'), None, None)
00056                 group.add_action(action)
00057 
00058                 action = gtk.Action('about', None, None, gtk.STOCK_ABOUT)
00059                 action.connect_after('activate', self.on_about_activate)
00060                 group.add_action(action)
00061 
00062                 action = gtk.Action('preferences', None, None, gtk.STOCK_PREFERENCES)
00063                 action.connect_after('activate', self.on_preferences_activate)
00064                 group.add_action(action)
00065 
00066                 action = gtk.Action('quit', None, None, gtk.STOCK_QUIT)
00067                 action.connect_after('activate', self.on_quit_activate)
00068                 group.add_action(action)
00069 
00070                 action = gtk.Action('create-view', gettext('_New'), None, gtk.STOCK_NEW)
00071                 group.add_action(action)
00072 
00073                 action = gtk.Action('select-view', gettext('_Select'), None, gtk.STOCK_GO_FORWARD)
00074                 group.add_action(action)
00075 
00076                 action = gtk.Action('delete-view', gettext('_Delete'), None, gtk.STOCK_DELETE)
00077                 action.connect_after('activate', self.on_view_delete_activate)
00078                 group.add_action(action)
00079 
00080                 ui.insert_action_group(group, -1)
00081 
00082                 # UI description
00083 
00084                 path = os.path.join(datadir, 'encode', 'environment.ui')
00085                 ui.add_ui_from_file(path)
00086 
00087                 box = self.glade.get_widget('left_box')
00088                 menubar = ui.get_widget('/menubar')
00089                 box.pack_start(menubar, expand=False, fill=True)
00090 
00091                 # Resource tracking
00092 
00093                 self.book_locations = []
00094                 self.books_by_view = {}
00095                 self.views_by_widget = {}
00096 
00097                 self.target_book = None
00098                 self.target_view = None
00099 
00100                 self.launchers = []
00101 
00102                 # Books
00103 
00104                 group = gtk.AccelGroup()
00105                 window.add_accel_group(group)
00106 
00107                 left_box = self.glade.get_widget('left_box')
00108                 right_paned = self.glade.get_widget('right_paned')
00109 
00110                 book = self._create_book('tabs_left')
00111                 left_box.pack_start(book, expand=True, fill=True)
00112                 self.book_locations.append((book, 'left'))
00113 
00114                 book = self._create_book('tabs_top', ['vertical'], group, '<Ctrl>1')
00115                 right_paned.pack1(book, resize=True, shrink=True)
00116                 self.book_locations.append((book, 'top'))
00117 
00118                 book = self._create_book('tabs_bottom', ['vertical'], group, '<Ctrl>2')
00119                 right_paned.pack2(book, resize=True, shrink=True)
00120                 self.book_locations.append((book, 'bottom'))
00121 
00122                 # Extensions
00123 
00124                 for ext in self.conf.get_strings('extensions', []):
00125                         tuple = ext.split(':')
00126 
00127                         if len(tuple) == 1:
00128                                 name, = tuple
00129                                 extension.load(None, name, datadir)
00130 
00131                         elif len(tuple) == 2:
00132                                 path, name = tuple
00133                                 extension.load(path, name, path)
00134 
00135                         else:
00136                                 path, name, data = tuple
00137                                 extension.load(path, name, data)
00138 
00139                 # Launchers
00140 
00141                 actions_by_label = []
00142 
00143                 for type in extension.get_children(LauncherExtension):
00144                         launcher = extension.get_singleton(type)
00145                         self.launchers.append(launcher)
00146 
00147                         action = launcher.get_action()
00148                         action.connect_after('activate', self.on_view_create_activate, launcher)
00149 
00150                         label = action.get_property('label')
00151                         actions_by_label.append((label, action))
00152 
00153                 item = ui.get_widget('/book-popup/create-view')
00154                 menu = item.get_submenu()
00155 
00156                 actions_by_label.sort()
00157 
00158                 for label, action in actions_by_label:
00159                         item = action.create_menu_item()
00160                         menu.append(item)
00161 
00162                 # Defaults
00163 
00164                 self.default_handler = None
00165                 self.default_differ = None
00166                 self.default_output = None
00167 
00168                 self.conf.add_string('default_handler', self.set_default_handler)
00169                 self.conf.add_string('default_differ', self.set_default_differ)
00170                 self.conf.add_string('default_output', self.set_default_output)
00171 
00172                 # Version
00173 
00174                 dialog = self.glade.get_widget('about_dialog')
00175                 dialog.set_property('version', version)
00176 
00177                 # Window
00178 
00179                 width, height = self.conf.get_ints('window_geometry', (-1, -1))
00180                 maximize = self.conf.get_bool('window_maximize', True)
00181 
00182                 window.set_default_size(width, height)
00183                 if maximize:
00184                         window.maximize()
00185 
00186                 center_position = self.conf.get_int('paned_position_center', -1)
00187                 center_paned = self.glade.get_widget('center_paned')
00188                 center_paned.set_position(center_position)
00189 
00190                 right_position = self.conf.get_int('paned_position_right', -1)
00191                 right_paned = self.glade.get_widget('right_paned')
00192                 right_paned.set_position(right_position)
00193 
00194                 window.show()
00195 
00196                 # Initial views
00197 
00198                 views = self.conf.get_strings('views', [])
00199 
00200                 for entry in views:
00201                         classname, location = entry.split('=', 1)
00202 
00203                         cls = extension.get_class(classname)
00204                         view = cls()
00205 
00206                         self.add_view(view, location=location)
00207 
00208                 for book, location in self.book_locations:
00209                         book.first_view()
00210 
00211         def get_book(self, location):
00212                 for book, loc in self.book_locations:
00213                         if location == loc:
00214                                 return book
00215 
00216                 raise EnvironmentError, 'Location does not exist: %s' % location
00217 
00218         # Window
00219 
00220         def on_window_configure(self, window, event):
00221                 geometry = (event.width, event.height)
00222                 self.conf.set_ints('window_geometry', geometry)
00223 
00224                 return False
00225 
00226         def on_window_state(self, window, event):
00227                 maximize = event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED
00228                 self.conf.set_bool('window_maximize', maximize)
00229 
00230                 return False
00231 
00232         def on_center_paned_notify(self, paned, param):
00233                 position = paned.get_position()
00234                 self.conf.set_int('paned_position_center', position)
00235 
00236         def on_right_paned_notify(self, paned, param):
00237                 position = paned.get_position()
00238                 self.conf.set_int('paned_position_right', position)
00239 
00240         # Defaults
00241 
00242         def set_default_handler(self, classname):
00243                 cls = extension.get_class(classname)
00244                 obj = extension.get_singleton(cls)
00245                 self.default_handler = obj
00246 
00247         def set_default_differ(self, classname):
00248                 cls = extension.get_class(classname)
00249                 obj = extension.get_singleton(cls)
00250                 self.default_differ = obj
00251 
00252         def set_default_output(self, classname):
00253                 cls = extension.get_class(classname)
00254                 obj = extension.get_singleton(cls)
00255                 self.default_output = obj
00256 
00257         # Books
00258 
00259         def _create_book(self, name, defaults=[], accel_group=None, accelerator=None):
00260                 keywords = self.conf.get_strings(name, defaults)
00261 
00262                 tabs_first = 'first' in keywords
00263 
00264                 if 'horizontal' in keywords:
00265                         book = tab.HTabBook(tabs_first)
00266                         book.connect('tab-right-button-press', self.on_book_right)
00267                 elif 'vertical' in keywords:
00268                         book = tab.VTabBook(tabs_first)
00269                         book.connect('tab-right-button-press', self.on_book_right)
00270                 else:
00271                         book = tab.TablessBook()
00272 
00273                 if accelerator:
00274                         key, mod = gtk.accelerator_parse(accelerator)
00275                         book.add_accelerator('next-view', accel_group, key, mod, 0)
00276 
00277                 book.show()
00278 
00279                 return book
00280 
00281         def on_book_right(self, book, view, event):
00282                 self.target_book = book
00283                 self.target_view = view
00284 
00285                 tabs = book.get_tabs()
00286 
00287                 # Select menu
00288 
00289                 visible = bool(tabs)
00290 
00291                 ui = get_ui_manager()
00292 
00293                 item = ui.get_widget('/book-popup/select-view')
00294                 item.set_property('visible', visible)
00295 
00296                 if visible:
00297                         menu = item.get_submenu()
00298 
00299                         for item in menu.get_children():
00300                                 menu.remove(item)
00301                                 item.destroy()
00302 
00303                         for tab in tabs:
00304                                 text = str(tab)
00305 
00306                                 item = gtk.MenuItem(text)
00307                                 item.connect_after('activate', self.on_view_select_activate, tab)
00308                                 item.show()
00309 
00310                                 menu.append(item)
00311 
00312                 # Delete item
00313 
00314                 item = ui.get_widget('/book-popup/delete-view')
00315                 item.set_property('visible', view is not None)
00316 
00317                 # Show
00318 
00319                 menu = ui.get_widget('/book-popup')
00320                 menu.popup(None, None, None, event.button, event.time)
00321 
00322         # View operations
00323 
00324         def add_view(self, view, book=None, location=None):
00325                 assert book or location
00326 
00327                 if not book:
00328                         book = self.get_book(location)
00329 
00330                 widget = view.get_widget()
00331                 widget.connect('destroy', self.on_view_destroy)
00332 
00333                 book.append_view(view)
00334 
00335                 self.books_by_view[view] = book
00336                 self.views_by_widget[widget] = view
00337 
00338                 return view
00339 
00340         def remove_view(self, view):
00341                 try:
00342                         view.cleanup()
00343                 except Exception, e:
00344                         print >>sys.stderr, e
00345 
00346                 widget = view.get_widget()
00347                 widget.destroy()
00348 
00349         def show_view(self, view, focus=False):
00350                 book = self.books_by_view[view]
00351                 book.set_view(view)
00352 
00353                 if focus:
00354                         widget = view.get_widget()
00355                         widget.grab_focus()
00356 
00357         def on_view_create_activate(self, item, launcher):
00358                 view = launcher.create()
00359                 self.add_view(view, book=self.target_book)
00360 
00361         def on_view_select_activate(self, item, tab):
00362                 self.target_book.set_tab(tab)
00363 
00364         def on_view_delete_activate(self, item):
00365                 self.remove_view(self.target_view)
00366                 self.target_view = None
00367 
00368         def on_view_destroy(self, widget):
00369                 view = self.views_by_widget.pop(widget)
00370                 self.books_by_view.pop(view)
00371 
00372         # Actions
00373 
00374         def on_about_activate(self, action):
00375                 dialog = self.glade.get_widget('about_dialog')
00376                 dialog.run()
00377 
00378         def on_preferences_activate(self, action):
00379                 import preferences
00380                 preferences.Preferences(datadir)
00381 
00382         def on_quit_activate(self, action):
00383                 window = self.glade.get_widget('window')
00384                 window.destroy()
00385 
00386         # Cleanup
00387 
00388         def cleanup(self):
00389                 for view in self.books_by_view:
00390                         try:
00391                                 view.cleanup()
00392                         except Exception, e:
00393                                 print >>sys.stderr, e
00394 
00395                 self.conf.remove_all()
00396 
00397         def on_window_destroy(self, window):
00398                 self.emit('quit')
00399 
00400         def on_quit(self, env):
00401                 self.cleanup()
00402 
00403 gobject.type_register(Environment)
00404 gobject.signal_new('quit', Environment, 0, gobject.TYPE_NONE, [])
00405 
00406 # Settings
00407 
00408 class Settings (settings.SettingsExtension):
00409         def __init__(self):
00410                 settings.SettingsExtension.__init__(self)
00411 
00412         def get_name(self):
00413                 return gettext('Encode')
00414 
00415         def get_title(self):
00416                 return gettext('Encode environment')
00417 
00418         def get_widget(self):
00419                 widget = gtk.Label('N/A')
00420                 widget.show()
00421                 return widget
00422 
00423 gobject.type_register(Settings)
00424 
00425 # Extension types
00426 
00427 class LauncherExtension (extension.Extension):
00428         '''View object factory.'''
00429 
00430         def __init__(self):
00431                 extension.Extension.__init__(self)
00432 
00433         def get_action(self):
00434                 '''Return a gtk.Action for creating Views.'''
00435                 return None
00436 
00437 gobject.type_register(LauncherExtension)
00438 
00439 class ViewExtension (extension.Extension):
00440         '''GUI component.'''
00441 
00442         def __init__(self):
00443                 extension.Extension.__init__(self)
00444 
00445         def get_widget(self):
00446                 '''Return the gtk.Widget which is the root of the widget hierarchy that
00447                    should be attached to the environment window.  This widget
00448                    receives a "destroy" signal when the view is detached from
00449                    the environment.  Environment doesn't touch the widget's
00450                    settings; it's up to the view to set the "visible"
00451                    property.'''
00452                 return None
00453 
00454         def cleanup(self):
00455                 '''This method will be called before the view is detached from the
00456                    environment or before the environment is shut down.'''
00457                 pass
00458 
00459 gobject.type_register(ViewExtension)
00460 
00461 class HandlerExtension (extension.Extension):
00462         '''File viewer or editor.'''
00463 
00464         def __init__(self):
00465                 extension.Extension.__init__(self)
00466 
00467         def get_action(self):
00468                 '''Return a gtk.Action for opening files.'''
00469                 return None
00470 
00471         def supports_file(self, path):
00472                 '''Return True if the file type of PATH is supported by this handler.'''
00473                 return True
00474 
00475         def open_file(self, path, workdir=None, line=None):
00476                 '''Open file PATH for editing or bring the associated buffer to foreground if
00477                    one exists.  Attempt to move the cursor to LINE if it's
00478                    specified.  WORKDIR is the root of the project tree where
00479                    PATH is located (it may be used to update the handler's
00480                    state).'''
00481                 pass
00482 
00483         def close_file(self, path):
00484                 '''Close the buffer associated with PATH if one exists.'''
00485                 pass
00486 
00487 gobject.type_register(HandlerExtension)
00488 
00489 class DifferExtension (extension.Extension):
00490         '''Difference viewer.'''
00491 
00492         def __init__(self):
00493                 extension.Extension.__init__(self)
00494 
00495         def compare_files(self, original_data, modified_path):
00496                 '''The contents of PATH ("modified version") are compared to the DATA string
00497                    ("original version").'''
00498                 pass
00499 
00500 gobject.type_register(DifferExtension)
00501 
00502 class OutputExtension (extension.Extension):
00503         '''Command output viewer.'''
00504 
00505         def __init__(self):
00506                 extension.Extension.__init__(self)
00507 
00508         def set_batch(self, batch):
00509                 '''Associates BATCH with this object.  The batch's output callbacks should be
00510                    implemented.  The execution can be monitored via via
00511                    signals.  All ties to an old batch should be cut when a
00512                    new one is set.'''
00513                 return None
00514 
00515 gobject.type_register(OutputExtension)
00516 
00517 # Exceptions
00518 
00519 class EnvironmentError (Exception):
00520         def __init__(self, msg):
00521                 Exception.__init__(self, msg)
00522 
00523 class ViewNotFound (EnvironmentError):
00524         def __init__(self, msg):
00525                 EnvironmentError.__init__(self, msg)
00526 
00527 # UI instance
00528 
00529 ui_manager = None
00530 
00531 def get_ui_manager():
00532         global ui_manager
00533         if ui_manager is None:
00534                 ui_manager = gtk.UIManager()
00535         return ui_manager
00536 
00537 # Environment instance
00538 
00539 instance = None
00540 
00541 def get_instance():
00542         global instance
00543         assert instance is not None
00544         return instance
00545 
00546 # Utilities
00547 
00548 def add_ui(actions, filename, lifetime_widget):
00549         ui = get_ui_manager()
00550 
00551         ui.insert_action_group(actions, -1)
00552         merge_id = ui.add_ui_from_file(filename)
00553 
00554         def on_destroy(widget):
00555                 ui.remove_ui(merge_id)
00556                 ui.remove_action_group(actions)
00557 
00558         lifetime_widget.connect('destroy', on_destroy)
00559 
00560         return ui
00561 
00562 def add_view(view, location):
00563         env = get_instance()
00564         env.add_view(view, None, location)
00565 
00566 def show_view(view, focus=False):
00567         env = get_instance()
00568         env.show_view(view, focus)
00569 
00570 def edit(path, workdir=None, line=None):
00571         env = get_instance()
00572         tuple = env.default_handler.open_file(path, workdir, line)
00573         if tuple:
00574                 view, focus = tuple
00575                 env.show_view(view, focus)
00576 
00577 def execute(batch):
00578         env = get_instance()
00579         env.default_output.set_batch(batch)
00580         batch.start()
00581 
00582 def compare(original_data, modified_path):
00583         env = get_instance()
00584         env.default_differ.compare_files(original_data, modified_path)
00585 
00586 # Main
00587 
00588 def main(datadir_param, version):
00589         try:
00590                 global datadir
00591                 datadir = datadir_param
00592 
00593                 extension.load(None, 'encode.*', datadir)
00594 
00595                 def on_quit(prefs):
00596                         gtk.main_quit()
00597 
00598                 # Preferences
00599 
00600                 conf = config.Directory('encode')
00601 
00602                 if not conf.get_bool('preferences_shown', False):
00603                         import preferences
00604 
00605                         prefs = preferences.Preferences(datadir)
00606                         prefs.connect('quit', on_quit)
00607 
00608                         gtk.main()
00609 
00610                         conf.set_bool('preferences_shown', True)
00611 
00612                         del prefs
00613                         del preferences
00614 
00615                 del conf
00616 
00617                 # Environment
00618 
00619                 global instance
00620                 instance = Environment(version)
00621                 instance.connect('quit', on_quit)
00622 
00623                 def on_signal():
00624                         try:
00625                                 instance.cleanup()
00626                         finally:
00627                                 sys.exit(1)
00628 
00629                 signal.signal(signal.SIGTERM, on_signal)
00630                 signal.signal(signal.SIGHUP, on_signal)
00631 
00632                 gtk.main()
00633 
00634         except KeyboardInterrupt:
00635                 pass

Generated on Thu Jan 18 09:47:40 2007 for Encode by  doxygen 1.4.7