Fixes truncation of first 3 chars of file paths.
[giterdone.git] / giterdone / repositorypane.py
blob4a90effb211bdf7f3685ba02ea51562687dd45df
1 # Copyright 2011 Robert Lopez Toscano
3 # This file is part of Giterdone.
5 # Giterdone is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # any later version.
10 # Giterdone is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Giterdone. If not, see <http://www.gnu.org/licenses/>.
18 import logging
19 import os.path
21 import gtk
22 import gtk.gdk
23 import gedit
25 from giterdone.common import get_icon
26 from giterdone.repository import Repository
27 import giterdone.vcs as vcs
29 class RepositoryPane(gtk.TreeView):
30 """A gtk.TreeView that displays a list of the repositories being managed by
31 giterdone."""
33 _POPUP_XML = '''
34 <ui>
35 <popup name="RepositoryPanePopup">
36 <menuitem action="VcsCommit"/>
37 <menuitem action="VcsFetch"/>
38 </popup>
39 </ui>'''
40 _ICON_NAME = 'giterdone-repo-manager'
41 _TITLE = 'Repository Manager'
42 _COL_REPO = 0
43 _COL_NAME = 1
44 _COL_BRANCH = 2
45 _COL_EMBLEM = 3
46 _FB_BUS_ID = '/plugins/filebrowser'
47 _FB_ROOT_CHANGED_EVT = 'root_changed'
48 _logger = logging.getLogger('giterdone.RepositoryPane')
50 def __init__(self, window, repo_monitor, repo_manager, window_controller):
51 super(RepositoryPane, self).__init__(gtk.ListStore(object, # repo
52 str, # display name
53 str, # branch
54 gtk.gdk.Pixbuf)) # emblem
55 self._curr_root = None
56 self._statusbars = {} # statusbar context id -> statusbar
57 self._bus_connection_ids = []
58 self._window = window
59 self._repo_monitor = repo_monitor
60 self._repo_monitor.subscribe_to_repo_branch_changes(
61 self._on_repo_branch_changed)
62 self._repo_monitor.subscribe_to_repo_discoveries(self._add_repo)
63 self._repo_monitor.subscribe_to_repo_state_changes(
64 self._on_repo_state_changed)
65 self._repo_manager = repo_manager
66 self._window_controller = window_controller
68 # Set up tree view UI
69 self.set_grid_lines(False)
70 self.set_headers_clickable(True)
71 self.append_column(gtk.TreeViewColumn(None, gtk.CellRendererPixbuf(),
72 pixbuf=RepositoryPane._COL_EMBLEM))
73 self.append_column(gtk.TreeViewColumn('Name', gtk.CellRendererText(),
74 text=RepositoryPane._COL_NAME))
75 self.append_column(gtk.TreeViewColumn('Branch', gtk.CellRendererText(),
76 text=RepositoryPane._COL_BRANCH))
77 self.connect('button-release-event', self._on_button_released)
79 # Create the repository pane popup
80 self.uimanager = gtk.UIManager()
81 self.uimanager.add_ui_from_string(RepositoryPane._POPUP_XML)
82 self.actiongroup = gtk.ActionGroup('VcsBase')
83 # TODO(robert) BEFORE ACTIVATING THESE, USE REPO_MANAGER WHEN DOING VCS
84 # COMMANDS
85 self.actiongroup.add_actions([
86 ('VcsCommit', 'giterdone-commit', 'Commit', None,
87 'Commit all staged files', self._on_vcs_commit_all)])
88 # ('VcsFetch', gtk.STOCK_GO_DOWN, 'Fetch', None,
89 # 'Fetch from remote repositories', self._on_vcs_fetch)])
90 # ('VcsPush', gtk.STOCK_GOTO_TOP, 'Push', None,
91 # 'Push to origin repository', self._on_vcs_push),
92 # ('VcsPull', gtk.STOCK_GOTO_BOTTOM, 'Pull', None,
93 # 'Pull from remote reposities', self._on_vcs_pull)])
94 self.uimanager.insert_action_group(self.actiongroup, 0)
96 self._window.get_side_panel().add_item(
97 self, RepositoryPane._TITLE,
98 gtk.image_new_from_icon_name(
99 RepositoryPane._ICON_NAME, gtk.ICON_SIZE_MENU))
100 self._bus_connection_ids.append(window.get_message_bus().connect(
101 RepositoryPane._FB_BUS_ID,
102 RepositoryPane._FB_ROOT_CHANGED_EVT,
103 self._on_fb_root_changed))
105 def deactivate(self):
106 self._window.get_side_panel().remove_item(self)
107 map(self._window.get_message_bus().disconnect, self._bus_connection_ids)
108 self._repo_monitor.unsubscribe_to_repo_branch_changes(
109 self._on_repo_branch_changed)
110 self._repo_monitor.unsubscribe_to_repo_discoveries(self._add_repo)
111 self._repo_monitor.unsubscribe_to_repo_state_changes(
112 self._on_repo_state_changed)
114 def _on_fb_root_changed(self, bus, message):
115 """Adds/removes repositories to/from the repository pane based on the new
116 root and each repository's path."""
117 self._curr_root = message.get_value('uri')[len('file://'):]
119 # remove repos from the model
120 toremove = []
121 for row in self.get_model(): toremove.append(row)
122 for row in toremove: self.get_model().remove(row.iter)
124 # Add current set of repos
125 curr_repos = self._repo_monitor.get_repos_under_path(self._curr_root)
126 for repo in curr_repos:
127 self._add_repo(repo)
129 def _add_repo(self, repo):
130 self.get_model().append((
131 repo,
132 self._generate_repo_name(repo),
133 repo.get_branch_name(),
134 self._get_emblem(repo)))
135 RepositoryPane._logger.info('Added repository %s' % (repo.path,))
137 def _on_repo_state_changed(self, repo):
138 for row in self.get_model():
139 if row[RepositoryPane._COL_REPO] == repo:
140 self.get_model().set_value(
141 row.iter, RepositoryPane._COL_EMBLEM, self._get_emblem(repo))
142 break
144 def _on_repo_branch_changed(self, repo):
145 # TODO(robert) hook this up to an event
146 for row in self.get_model():
147 if row[RepositoryPane._COL_REPO] == repo:
148 self.get_model().set_value(
149 row.iter, RepositoryPane._COL_BRANCH, repo.get_branch_name())
150 break
152 def _set_action_sensitivities(self):
153 model, rows = self.get_selection().get_selected_rows()
155 if len(rows) != 1:
156 RepositoryPane._logger.info('User selected multiple repositories')
157 for action in self.actiongroup.list_actions():
158 action.set_sensitive(False)
159 return
161 repo = model[rows[0]][RepositoryPane._COL_REPO]
163 RepositoryPane._logger.info('User selected repository %s' % (repo.path,))
165 # activate all commands
166 for action in self.actiongroup.list_actions():
167 action.set_sensitive(True)
169 # don't activate commit if there is nothing staged
170 if not repo.has_staged_files():
171 RepositoryPane._logger.debug(
172 'Deactivating commit action since repository has no staged files')
173 self.actiongroup.get_action('VcsCommit').set_sensitive(False)
175 def _on_button_released(self, widget, event):
176 if event.button == 3: # right-click
177 self._set_action_sensitivities()
179 # show context menu
180 self.uimanager.get_widget('/RepositoryPanePopup').popup(
181 None, None, None, event.button, 0)
183 def _on_vcs_commit_all(self, action):
184 model, rows = self.get_selection().get_selected_rows()
186 repo = model[rows[0]][RepositoryPane._COL_REPO]
187 self._window_controller.start_commit_edit([repo.path])
189 def _on_vcs_pull(self, action):
190 model, rows = self.get_selection().get_selected_rows()
192 for repo in [row[RepositoryPane._COL_REPO] for row in rows]:
193 # TODO(robert) implement
194 pass
196 def _on_vcs_push(self, action):
197 model, rows = self.get_selection().get_selected_rows()
199 for repo in [row[RepositoryPane._COL_REPO] for row in rows]:
200 # TODO(robert) implement
201 pass
203 def _on_vcs_fetch(self, action):
204 model, rows = self.get_selection().get_selected_rows()
206 for repo in [model[row][RepositoryPane._COL_REPO] for row in rows]:
207 for context_id, statusbar in self._statusbars.iteritems():
208 self._window.get_statusbar().flash_message(
209 self._sb_context_id, 'Fetching repository %s' % (repo.path,))
210 vcs.interfaces[repo.type].fetch(repo.path)
212 def _get_emblem(self, repo):
213 return get_icon(repo.get_file_state(repo.path))
215 def _generate_repo_name(self, repo):
216 if not self._curr_root:
217 msg = self._window.get_message_bus().send_sync(RepositoryPane._FB_BUS_ID,
218 'get_root')
219 self._curr_root = msg.get_value('uri')[len('file://'):]
220 if repo.path == self._curr_root: # curr_root is repo
221 return os.path.basename(repo.path)
222 elif repo.path.startswith(self._curr_root + '/'): # curr_root contains repo
223 return '.' + repo.path[len(self._curr_root):]
224 else: # curr_root is within repo
225 return os.path.basename(repo.path)