00001
00002
00003
00004
00005
00006
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
00033
00034 self.conf = config.Directory('encode')
00035
00036
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
00045
00046 ui = get_ui_manager()
00047
00048 group = ui.get_accel_group()
00049 window.add_accel_group(group)
00050
00051
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
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
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
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
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
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
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
00173
00174 dialog = self.glade.get_widget('about_dialog')
00175 dialog.set_property('version', version)
00176
00177
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
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
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
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
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
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
00313
00314 item = ui.get_widget('/book-popup/delete-view')
00315 item.set_property('visible', view is not None)
00316
00317
00318
00319 menu = ui.get_widget('/book-popup')
00320 menu.popup(None, None, None, event.button, event.time)
00321
00322
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
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
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
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
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
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
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
00538
00539 instance = None
00540
00541 def get_instance():
00542 global instance
00543 assert instance is not None
00544 return instance
00545
00546
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
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
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
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