def __init__(self): super(GTKEditor, self).__init__() # settings self.settings = load_gtk_settings() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.on_window_delete_event) self.window.connect("destroy", self.on_window_destroy) self.window.connect("configure_event", self.on_window_configure) width = self.settings.win_width height = self.settings.win_height self.window.set_default_size(width, height) x = self.settings.win_x y = self.settings.win_y self.window.move(x, y) self.vbox = gtk.VBox() self.window.add(self.vbox) # Actions and action groups: actions = get_actions(self) self.global_accelgroup = gtk.AccelGroup() self.window.add_accel_group(self.global_accelgroup) self.selection_accelgroup = gtk.AccelGroup() self.window.add_accel_group(self.selection_accelgroup) self.global_actiongroup = gtk.ActionGroup('global') self.selection_actiongroup = gtk.ActionGroup('selection') # global actions self.global_actiongroup.add_actions(actions['actions']) self.global_actiongroup.add_toggle_actions(actions['toggle_actions']) linesep_value = linesep_map[self.settings.linesep] edit_ws_action = self.global_actiongroup.get_action('EditWorkspace') edit_ws_action.set_sensitive(False) clear_wsc_action = self.global_actiongroup.get_action('ClearWorkspaceCache') clear_wsc_action.set_sensitive(False) for action in self.global_actiongroup.list_actions(): action.set_accel_group(self.global_accelgroup) action.connect_accelerator() if action.get_name() == 'Undo': self.undo_action = action if action.get_name() == 'Redo': self.redo_action = action self.global_actiongroup.set_sensitive(True) self.global_actiongroup.set_visible(True) self.undo_action.set_sensitive(False) self.redo_action.set_sensitive(False) # things you can do with selections self.selection_actiongroup.add_actions(actions['selection_actions']) for action in self.selection_actiongroup.list_actions(): action.set_accel_group(self.selection_accelgroup) action.connect_accelerator() self.selection_actiongroup.set_sensitive(False) self.selection_actiongroup.set_visible(True) # uimanager, menu uimanager = gtk.UIManager() uimanager.insert_action_group(self.global_actiongroup, 0) uimanager.insert_action_group(self.selection_actiongroup, -1) uimanager.add_ui_from_string(get_menu_xml()) uimanager.ensure_update() # is this really necessary? toplevels = uimanager.get_toplevels(gtk.UI_MANAGER_MENUBAR) self.menubar = toplevels[0] self.vbox.pack_start(self.menubar, expand=False) self.uimanager = uimanager # workspaces self.workspace_merge_id = None self.workspaces_actiongroup = gtk.ActionGroup('workspaces') self.uimanager.insert_action_group(self.workspaces_actiongroup, -1) #self._sync_workspaces_menu() self.reload_workspaces() # colourschemes self.colourschemes = self.settings.load_colourschemes() # sane default that will be overridden in a moment: self.colourscheme = self.colourschemes[0] self.set_colourscheme(self.settings.colourscheme) uimanager.ensure_update() # statusbar self.status_hbox = gtk.HBox(spacing=0) self.vbox.pack_end(self.status_hbox, expand=False) self.status_message = gtk.Label() self.status_modified = gtk.Label() self.status_position = gtk.Label() self.status_lexer = gtk.Label() self.status_linesep = gtk.Label() self.status_encoding = gtk.Label() self.status_message.set_alignment(0, 0) self.status_modified.set_alignment(0, 0) self.status_position.set_alignment(0, 0) self.status_lexer.set_alignment(1, 0) self.status_position.set_size_request(100, -1) self.status_lexer.set_size_request(150, -1) self.status_message.set_padding(3, 3) self.status_modified.set_padding(3, 3) self.status_position.set_padding(3, 3) self.status_lexer.set_padding(3, 3) self.status_linesep.set_padding(3, 3) self.status_encoding.set_padding(3, 3) self.status_hbox.pack_start(self.status_message, expand=True) self.status_hbox.pack_start(self.status_modified, expand=False) self.status_hbox.pack_start(self.status_position, expand=False) self.status_hbox.pack_start(self.status_lexer, expand=False) self.status_hbox.pack_start(self.status_linesep, expand=False) self.status_hbox.pack_start(self.status_encoding, expand=False) self.vpaned = gtk.VPaned() self.hpaned = gtk.HPaned() self.vbox.pack_end(self.hpaned, expand=True) self.document_tree_model = gtk.TreeStore(str, str, bool) self.document_tree = gtk.TreeView(self.document_tree_model) self.document_tree.unset_flags(gtk.CAN_FOCUS) column_filename = gtk.TreeViewColumn('Documents') self.document_tree.append_column(column_filename) cell_filename = gtk.CellRendererText() column_filename.pack_end(cell_filename) column_filename.add_attribute(cell_filename, 'text', 0) def cb(column, cell_renderer, tree_model, iterrow): doc = self.textarea.view.document filepath = tree_model.get_value(iterrow, 1) if filepath in (doc.location, doc.description): cell_renderer.set_property('weight', pango.WEIGHT_BOLD) else: cell_renderer.set_property('weight', pango.WEIGHT_NORMAL) column_filename.set_cell_data_func(cell_filename, cb) self.column_filename = column_filename self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.add(self.document_tree) self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.hpaned.pack1(self.scrolled_window, resize=False, shrink=False) # textarea self.textarea = GTKTextarea(self) # replace with something to load the session recent_files_path = self.settings.get_recent_files_path() if os.path.exists(recent_files_path): for r in get_recent_files(recent_files_path): path, scroll_pos, cursor_pos = r if os.path.exists(path) and os.path.isfile(path): view = self.new_view(path) view.cursor_pos = cursor_pos view.scroll_pos = scroll_pos view.last_x_pos = cursor_pos[1] self.switch_current_view(view) # to trigger scroll # if the most recent file is set in settings, try and switch to it. # (it will have to be set in the recent files list to be loaded) if self.settings.most_recent_file: for view in self.views: if view.document.location == self.settings.most_recent_file: self.switch_current_view(view) break if not self.views: self.new_view() def add(widget): #self.hpaned.pack2(widget, resize=True, shrink=False) self.vpaned.pack1(widget, resize=False, shrink=True) self.textarea.attach(add) self.search_notebook = gtk.Notebook() self.search_notebook.set_property('tab-vborder', 0) self.vpaned.pack2(self.search_notebook, resize=True, shrink=True) self.search_notebook.connect("switch-page", self.on_search_notebook_switch_page) self.hpaned.pack2(self.vpaned, resize=True, shrink=False) self.window.show_all() self.textarea.show() # hack because some stuff can't be initialised # earlier self.search_notebook.hide() self.clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD) self.scrolled_window_visible = True self.document_tree.set_size_request(200, -1) # ?? self.document_tree.connect("cursor-changed", self.on_document_tree_cursor_changed) self.document_tree.expand_all() self._select_current_view_in_tree() if not self.settings.show_statusbar: self.status_hbox.hide() if not self.settings.show_sidebar: self.scrolled_window_visible = False self.scrolled_window.hide() self.open_dialog = GtkOpenDialog(self) self.save_dialog = GtkSaveDialog(self) self.goto_dialog = GtkGotoLineDialog(self) self.switch_dialog = GtkSwitchDocumentDialog(self) self.add_workspace_dialog = GtkAddWorkspaceDialog(self) self.edit_workspace_dialog = GtkEditWorkspaceDialog(self) self.preferences_dialog = GtkPreferencesDialog(self) self.find_dialog = GtkFindDialog(self) # pre-select the workspace for w in self.workspaces: if w.name == self.settings.workspace: self.switch_workspace(w) break
class GTKEditor(Editor): def __init__(self): super(GTKEditor, self).__init__() # settings self.settings = load_gtk_settings() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.on_window_delete_event) self.window.connect("destroy", self.on_window_destroy) self.window.connect("configure_event", self.on_window_configure) width = self.settings.win_width height = self.settings.win_height self.window.set_default_size(width, height) x = self.settings.win_x y = self.settings.win_y self.window.move(x, y) self.vbox = gtk.VBox() self.window.add(self.vbox) # Actions and action groups: actions = get_actions(self) self.global_accelgroup = gtk.AccelGroup() self.window.add_accel_group(self.global_accelgroup) self.selection_accelgroup = gtk.AccelGroup() self.window.add_accel_group(self.selection_accelgroup) self.global_actiongroup = gtk.ActionGroup('global') self.selection_actiongroup = gtk.ActionGroup('selection') # global actions self.global_actiongroup.add_actions(actions['actions']) self.global_actiongroup.add_toggle_actions(actions['toggle_actions']) linesep_value = linesep_map[self.settings.linesep] edit_ws_action = self.global_actiongroup.get_action('EditWorkspace') edit_ws_action.set_sensitive(False) clear_wsc_action = self.global_actiongroup.get_action('ClearWorkspaceCache') clear_wsc_action.set_sensitive(False) for action in self.global_actiongroup.list_actions(): action.set_accel_group(self.global_accelgroup) action.connect_accelerator() if action.get_name() == 'Undo': self.undo_action = action if action.get_name() == 'Redo': self.redo_action = action self.global_actiongroup.set_sensitive(True) self.global_actiongroup.set_visible(True) self.undo_action.set_sensitive(False) self.redo_action.set_sensitive(False) # things you can do with selections self.selection_actiongroup.add_actions(actions['selection_actions']) for action in self.selection_actiongroup.list_actions(): action.set_accel_group(self.selection_accelgroup) action.connect_accelerator() self.selection_actiongroup.set_sensitive(False) self.selection_actiongroup.set_visible(True) # uimanager, menu uimanager = gtk.UIManager() uimanager.insert_action_group(self.global_actiongroup, 0) uimanager.insert_action_group(self.selection_actiongroup, -1) uimanager.add_ui_from_string(get_menu_xml()) uimanager.ensure_update() # is this really necessary? toplevels = uimanager.get_toplevels(gtk.UI_MANAGER_MENUBAR) self.menubar = toplevels[0] self.vbox.pack_start(self.menubar, expand=False) self.uimanager = uimanager # workspaces self.workspace_merge_id = None self.workspaces_actiongroup = gtk.ActionGroup('workspaces') self.uimanager.insert_action_group(self.workspaces_actiongroup, -1) #self._sync_workspaces_menu() self.reload_workspaces() # colourschemes self.colourschemes = self.settings.load_colourschemes() # sane default that will be overridden in a moment: self.colourscheme = self.colourschemes[0] self.set_colourscheme(self.settings.colourscheme) uimanager.ensure_update() # statusbar self.status_hbox = gtk.HBox(spacing=0) self.vbox.pack_end(self.status_hbox, expand=False) self.status_message = gtk.Label() self.status_modified = gtk.Label() self.status_position = gtk.Label() self.status_lexer = gtk.Label() self.status_linesep = gtk.Label() self.status_encoding = gtk.Label() self.status_message.set_alignment(0, 0) self.status_modified.set_alignment(0, 0) self.status_position.set_alignment(0, 0) self.status_lexer.set_alignment(1, 0) self.status_position.set_size_request(100, -1) self.status_lexer.set_size_request(150, -1) self.status_message.set_padding(3, 3) self.status_modified.set_padding(3, 3) self.status_position.set_padding(3, 3) self.status_lexer.set_padding(3, 3) self.status_linesep.set_padding(3, 3) self.status_encoding.set_padding(3, 3) self.status_hbox.pack_start(self.status_message, expand=True) self.status_hbox.pack_start(self.status_modified, expand=False) self.status_hbox.pack_start(self.status_position, expand=False) self.status_hbox.pack_start(self.status_lexer, expand=False) self.status_hbox.pack_start(self.status_linesep, expand=False) self.status_hbox.pack_start(self.status_encoding, expand=False) self.vpaned = gtk.VPaned() self.hpaned = gtk.HPaned() self.vbox.pack_end(self.hpaned, expand=True) self.document_tree_model = gtk.TreeStore(str, str, bool) self.document_tree = gtk.TreeView(self.document_tree_model) self.document_tree.unset_flags(gtk.CAN_FOCUS) column_filename = gtk.TreeViewColumn('Documents') self.document_tree.append_column(column_filename) cell_filename = gtk.CellRendererText() column_filename.pack_end(cell_filename) column_filename.add_attribute(cell_filename, 'text', 0) def cb(column, cell_renderer, tree_model, iterrow): doc = self.textarea.view.document filepath = tree_model.get_value(iterrow, 1) if filepath in (doc.location, doc.description): cell_renderer.set_property('weight', pango.WEIGHT_BOLD) else: cell_renderer.set_property('weight', pango.WEIGHT_NORMAL) column_filename.set_cell_data_func(cell_filename, cb) self.column_filename = column_filename self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.add(self.document_tree) self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.hpaned.pack1(self.scrolled_window, resize=False, shrink=False) # textarea self.textarea = GTKTextarea(self) # replace with something to load the session recent_files_path = self.settings.get_recent_files_path() if os.path.exists(recent_files_path): for r in get_recent_files(recent_files_path): path, scroll_pos, cursor_pos = r if os.path.exists(path) and os.path.isfile(path): view = self.new_view(path) view.cursor_pos = cursor_pos view.scroll_pos = scroll_pos view.last_x_pos = cursor_pos[1] self.switch_current_view(view) # to trigger scroll # if the most recent file is set in settings, try and switch to it. # (it will have to be set in the recent files list to be loaded) if self.settings.most_recent_file: for view in self.views: if view.document.location == self.settings.most_recent_file: self.switch_current_view(view) break if not self.views: self.new_view() def add(widget): #self.hpaned.pack2(widget, resize=True, shrink=False) self.vpaned.pack1(widget, resize=False, shrink=True) self.textarea.attach(add) self.search_notebook = gtk.Notebook() self.search_notebook.set_property('tab-vborder', 0) self.vpaned.pack2(self.search_notebook, resize=True, shrink=True) self.search_notebook.connect("switch-page", self.on_search_notebook_switch_page) self.hpaned.pack2(self.vpaned, resize=True, shrink=False) self.window.show_all() self.textarea.show() # hack because some stuff can't be initialised # earlier self.search_notebook.hide() self.clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD) self.scrolled_window_visible = True self.document_tree.set_size_request(200, -1) # ?? self.document_tree.connect("cursor-changed", self.on_document_tree_cursor_changed) self.document_tree.expand_all() self._select_current_view_in_tree() if not self.settings.show_statusbar: self.status_hbox.hide() if not self.settings.show_sidebar: self.scrolled_window_visible = False self.scrolled_window.hide() self.open_dialog = GtkOpenDialog(self) self.save_dialog = GtkSaveDialog(self) self.goto_dialog = GtkGotoLineDialog(self) self.switch_dialog = GtkSwitchDocumentDialog(self) self.add_workspace_dialog = GtkAddWorkspaceDialog(self) self.edit_workspace_dialog = GtkEditWorkspaceDialog(self) self.preferences_dialog = GtkPreferencesDialog(self) self.find_dialog = GtkFindDialog(self) # pre-select the workspace for w in self.workspaces: if w.name == self.settings.workspace: self.switch_workspace(w) break ### Editor overrides def exit(self): """Cause the editor to exit asap.""" self.save_settings() for search in self.searches: self.cancel_search(search) unsaved = 0 for view in self.views: if view.document.is_modified: unsaved += 1 if unsaved: dialog = GtkConfirmExitDialog(self) if dialog.show(): gtk.main_quit() else: gtk.main_quit() def _sync_workspaces_menu(self): # remove the menu if self.workspace_merge_id: self.uimanager.remove_ui(self.workspace_merge_id) # clear the actions for action in self.workspaces_actiongroup.list_actions(): self.workspaces_actiongroup.remove_action(action) # workspace actions workspace_actions = [] action = ("SelectNoWorkspace", None, "None", None, None, 0) workspace_actions.append(action) index = 0 value = 0 for w in self.workspaces: index += 1 slug = "SelectWorkspace_"+w.slug action = (slug, None, w.name, None, None, index) workspace_actions.append(action) if self.workspace and w.slug == self.workspace.slug: value = index self.workspaces_actiongroup.add_radio_actions( workspace_actions, value=value, on_change=self.change_workspace_callback, user_data=None) self.workspaces_actiongroup.set_sensitive(True) self.workspaces_actiongroup.set_visible(True) # workspace menu items merge_id = self.uimanager.new_merge_id() self.workspace_merge_id = merge_id self.uimanager.add_ui(merge_id, 'ui/menubar/WorkspacesMenu', "None", "SelectNoWorkspace", gtk.UI_MANAGER_MENUITEM, False) for w in self.workspaces: self.uimanager.add_ui(merge_id, 'ui/menubar/WorkspacesMenu', w.name, "SelectWorkspace_"+w.slug, gtk.UI_MANAGER_MENUITEM, False) def reload_workspaces(self): self.workspaces = self.settings.load_workspaces() self._sync_workspaces_menu() def switch_workspace(self, workspace): # this is for when we change the workspace from code elsewhere self.workspace = workspace sensitive = bool(workspace) edit_ws_action = self.global_actiongroup.get_action('EditWorkspace') edit_ws_action.set_sensitive(sensitive) clear_wsc_action = self.global_actiongroup.get_action('ClearWorkspaceCache') clear_wsc_action.set_sensitive(sensitive) # TODO: this (like many other bits of code) should probably use some # signal/callback/multi-dispatch system. self.switch_dialog.sync_workspace() self.find_dialog.sync_workspace() self.reload_workspaces() self.update_status() def change_workspace_callback(self, from_action, to_action): # this is the callback when the user picks a workspace from the menu edit_ws_action = self.global_actiongroup.get_action('EditWorkspace') clear_wsc_action = self.global_actiongroup.get_action('ClearWorkspaceCache') name = to_action.get_name() bits = name.split('_') if len(bits) > 1: slug = bits[1] for w in self.workspaces: if w.slug == slug: self.workspace = w edit_ws_action.set_sensitive(True) clear_wsc_action.set_sensitive(True) break else: self.workspace = None edit_ws_action.set_sensitive(False) clear_wsc_action.set_sensitive(False) # TODO: this (like many other bits of code) should probably use some # signal/callback/multi-dispatch system. self.switch_dialog.sync_workspace() self.find_dialog.sync_workspace() self.update_status() def _reset_document_tree_model(self, extra_expand=None): dtm = self.document_tree_model dt = self.document_tree # get all the paths to the files inside the expanded directories so # we can expand the tree again after we rebuild it expanded_dirs = [] expanded_files = [] def get_expanded(model, path, iterrow): if dt.row_expanded(path): expanded_dirs.append(dtm.get_value(iterrow, 1)) dtm.foreach(get_expanded) if expanded_dirs: for v in self.views: location = v.document.location if location: dirname = os.path.dirname(location) + os.path.sep if dirname in expanded_dirs: expanded_files.append(location) # now clear the tree dtm.clear() # add all the document locations into the tree root = RootDirNode() for v in self.views: if v.document.location: root.add(v.document.location) else: # add unsaved documents to the top row = (v.document.description, v.document.description, True) dtm.append(None, row) root.collapse() node_dict = {} # hmm.. should we keep this around? def add(node, parent_path): if parent_path: parent = node_dict[parent_path] else: parent = None name = node.path.replace(parent_path, '', 1) path = node.path is_file = isinstance(node, FileNode) node_dict[path] = dtm.append(parent, (name, path, is_file)) if root.dirs or root.files: root.walk(add) if extra_expand: expanded_files.append(extra_expand) # expand up to all the files we had expanded before, put the cursor on # the selected file #doc = self.textarea.view.document def set_expanded(model, path, iterrow): filepath = dtm.get_value(iterrow, 1) if filepath in expanded_files: iterparent = dtm.iter_parent(iterrow) path = dtm.get_path(iterparent) dt.expand_to_path(path) dtm.foreach(set_expanded) def new_view(self, location=None): s = self.settings if location: for view in self.views: if view.document.location == location: self.switch_current_view(view, False) # We don't currently support multiple views on the same # document, so exit out of the method # TODO: should we reload? return document = load_document(location, self.settings) else: title = self.get_next_title() document = Document(encoding=s.file_encoding, linesep=s.linesep, tab_size=s.tab_size, title=title) view = GTKView(self, document) self.views.append(view) # Switch the view. switch_current_view will rebuild the tree to reflect # the selected view self.switch_current_view(view, True) #if view.document.is_modified: # dialog = GtkWarnModifiedDialog(self) # dialog.show() return view def copy_view(self): doc = self.textarea.view.document title = self.get_next_title() new_doc = Document(encoding=doc.encoding, linesep=doc.linesep, tab_size=doc.tab_size, title=title) new_doc.insert(0, doc.content) new_view = GTKView(self, new_doc) self.views.append(new_view) self.switch_current_view(new_view, True) return new_view def close_view(self, view=None): if not view: view = self.textarea.view if view == self.previous_view: self.previous_view = None if view == self.textarea.view: if view.document.is_modified: dialog = GtkConfirmCloseDialog(self) if not dialog.show(): return if len(self.views) > 1: next = self.get_next_view() self.views.remove(view) # TODO: eh? #for search in self.searches: #if self.view == view: # self.cancel_search(search) self.switch_current_view(next, True) else: self.views.remove(view) self.new_view() def switch_current_view(self, view, rebuild=False): """Switch the active document to the one specified.""" if not view in self.views: raise Exception("Invalid document.") # set the previous view as this view try: if view != self.textarea.view: self.previous_view = self.textarea.view except AttributeError: pass # first time around # set the current view self.textarea.view = view # set the scrollbars' position to match the view's scroll_pos self.textarea.sync_scroll_pos() # set the scrollbars' ranges to match that of the view self.textarea.adjust_adjustments() # see textarea.draw() view.just_switched = True # set the window title #self.window.set_title(view.document.description) self.update_status() if rebuild: # rebuild the tree to reflect the change self._reset_document_tree_model(view.document.location) else: self.document_tree.queue_draw() # make sure things get redrawn self.textarea.redraw() self._select_current_view_in_tree() def _select_current_view_in_tree(self): def set_cursor(model, path, iterrow, (location, title)): l = self.document_tree_model.get_value(iterrow, 1) if l in (location, title): self.document_tree.get_selection().select_path(path) return True # make sure the current view is the selected one doc = self.textarea.view.document self.document_tree_model.foreach(set_cursor, (doc.location, doc.description))