pylint: disable too-many-ancestors and no-member warnings
[git-cola.git] / cola / widgets / bookmarks.py
bloba87acb69d489b77bc05fff686c0621f12c039448
1 """Provides widgets related to bookmarks"""
2 from __future__ import division, absolute_import, unicode_literals
3 import os
5 from qtpy import QtCore
6 from qtpy import QtWidgets
7 from qtpy.QtCore import Qt
8 from qtpy.QtCore import Signal
10 from .. import cmds
11 from .. import core
12 from .. import git
13 from .. import hotkeys
14 from .. import icons
15 from .. import qtutils
16 from .. import utils
17 from ..i18n import N_
18 from ..interaction import Interaction
19 from ..models import prefs
20 from ..settings import Settings
21 from ..widgets import defs
22 from ..widgets import standard
25 BOOKMARKS = 0
26 RECENT_REPOS = 1
29 def bookmark(context, parent):
30 return BookmarksWidget(context, BOOKMARKS, parent=parent)
33 def recent(context, parent):
34 return BookmarksWidget(context, RECENT_REPOS, parent=parent)
37 class BookmarksWidget(QtWidgets.QFrame):
39 def __init__(self, context, style=BOOKMARKS, parent=None):
40 QtWidgets.QFrame.__init__(self, parent)
42 self.style = style
43 self.settings = Settings()
44 self.tree = BookmarksTreeWidget(
45 context, style, self.settings, parent=self)
47 self.add_button = qtutils.create_action_button(
48 tooltip=N_('Add'), icon=icons.add())
50 self.delete_button = qtutils.create_action_button(
51 tooltip=N_('Delete'), icon=icons.remove())
53 self.open_button = qtutils.create_action_button(
54 tooltip=N_('Open'), icon=icons.repo())
56 self.button_group = utils.Group(self.delete_button, self.open_button)
57 self.button_group.setEnabled(False)
59 self.setFocusProxy(self.tree)
60 if style == BOOKMARKS:
61 self.setToolTip(N_('Favorite repositories'))
62 elif style == RECENT_REPOS:
63 self.setToolTip(N_('Recent repositories'))
64 self.add_button.hide()
66 self.button_layout = qtutils.hbox(defs.no_margin, defs.spacing,
67 self.open_button, self.add_button,
68 self.delete_button)
70 self.main_layout = qtutils.vbox(defs.no_margin, defs.spacing, self.tree)
71 self.setLayout(self.main_layout)
73 self.corner_widget = QtWidgets.QWidget(self)
74 self.corner_widget.setLayout(self.button_layout)
75 titlebar = parent.titleBarWidget()
76 titlebar.add_corner_widget(self.corner_widget)
78 qtutils.connect_button(self.add_button, self.tree.add_bookmark)
79 qtutils.connect_button(self.delete_button, self.tree.delete_bookmark)
80 qtutils.connect_button(self.open_button, self.tree.open_repo)
82 item_selection_changed = self.tree_item_selection_changed
83 # pylint: disable=no-member
84 self.tree.itemSelectionChanged.connect(item_selection_changed)
86 QtCore.QTimer.singleShot(0, self.reload_bookmarks)
88 def reload_bookmarks(self):
89 # Called once after the GUI is initialized
90 self.settings.load()
91 self.tree.refresh()
93 def tree_item_selection_changed(self):
94 enabled = bool(self.tree.selected_item())
95 self.button_group.setEnabled(enabled)
97 def connect_to(self, other):
98 self.tree.default_changed.connect(other.tree.refresh)
99 other.tree.default_changed.connect(self.tree.refresh)
102 def disable_rename(_path, _name, _new_name):
103 return False
106 # pylint: disable=too-many-ancestors
107 class BookmarksTreeWidget(standard.TreeWidget):
108 default_changed = Signal()
109 worktree_changed = Signal()
111 def __init__(self, context, style, settings, parent=None):
112 standard.TreeWidget.__init__(self, parent=parent)
113 self.context = context
114 self.style = style
115 self.settings = settings
117 self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
118 self.setHeaderHidden(True)
120 # We make the items editable, but we don't want the double-click
121 # behavior to trigger editing. Make it behave like Mac OS X's Finder.
122 self.setEditTriggers(self.SelectedClicked)
124 self.open_action = qtutils.add_action(
125 self, N_('Open'), self.open_repo, hotkeys.OPEN)
127 self.accept_action = qtutils.add_action(
128 self, N_('Accept'), self.accept_repo, *hotkeys.ACCEPT)
130 self.open_new_action = qtutils.add_action(
131 self, N_('Open in New Window'), self.open_new_repo, hotkeys.NEW)
133 self.set_default_repo_action = qtutils.add_action(
134 self, N_('Set Default Repository'), self.set_default_repo)
136 self.clear_default_repo_action = qtutils.add_action(
137 self, N_('Clear Default Repository'), self.clear_default_repo)
139 self.rename_repo_action = qtutils.add_action(
140 self, N_('Rename Repository'), self.rename_repo)
142 self.open_default_action = qtutils.add_action(
143 self, cmds.OpenDefaultApp.name(), self.open_default,
144 hotkeys.PRIMARY_ACTION)
146 self.launch_editor_action = qtutils.add_action(
147 self, cmds.Edit.name(), self.launch_editor, hotkeys.EDIT)
149 self.launch_terminal_action = qtutils.add_action(
150 self, cmds.LaunchTerminal.name(), self.launch_terminal,
151 hotkeys.TERMINAL)
153 self.copy_action = qtutils.add_action(
154 self, N_('Copy'), self.copy, hotkeys.COPY)
156 self.delete_action = qtutils.add_action(
157 self, N_('Delete'), self.delete_bookmark)
159 # pylint: disable=no-member
160 self.itemChanged.connect(self.item_changed)
161 self.itemSelectionChanged.connect(self.item_selection_changed)
162 self.itemDoubleClicked.connect(self.tree_double_clicked)
164 self.action_group = utils.Group(self.open_action,
165 self.open_new_action,
166 self.copy_action,
167 self.launch_editor_action,
168 self.launch_terminal_action,
169 self.open_default_action,
170 self.rename_repo_action,
171 self.delete_action)
172 self.action_group.setEnabled(False)
173 self.set_default_repo_action.setEnabled(False)
174 self.clear_default_repo_action.setEnabled(False)
176 # Connections
177 if style == RECENT_REPOS:
178 self.worktree_changed.connect(self.refresh,
179 type=Qt.QueuedConnection)
180 context.model.add_observer(context.model.message_worktree_changed,
181 self.worktree_changed.emit)
183 def refresh(self):
184 context = self.context
185 settings = self.settings
186 builder = BuildItem(context)
188 # bookmarks
189 if self.style == BOOKMARKS:
190 entries = settings.bookmarks
191 # recent items
192 elif self.style == RECENT_REPOS:
193 settings.reload_recent()
194 entries = settings.recent
196 items = [builder.get(entry['path'], entry['name']) for entry in entries]
197 if self.style == BOOKMARKS and prefs.sort_bookmarks(context):
198 items.sort(key=lambda x: x.name)
200 self.clear()
201 self.addTopLevelItems(items)
203 def contextMenuEvent(self, event):
204 menu = qtutils.create_menu(N_('Actions'), self)
205 menu.addAction(self.open_action)
206 menu.addAction(self.open_new_action)
207 menu.addAction(self.open_default_action)
208 menu.addSeparator()
209 menu.addAction(self.copy_action)
210 menu.addAction(self.launch_editor_action)
211 menu.addAction(self.launch_terminal_action)
212 menu.addSeparator()
213 item = self.selected_item()
214 is_default = bool(item and item.is_default)
215 if is_default:
216 menu.addAction(self.clear_default_repo_action)
217 else:
218 menu.addAction(self.set_default_repo_action)
219 menu.addAction(self.rename_repo_action)
220 menu.addSeparator()
221 menu.addAction(self.delete_action)
222 menu.exec_(self.mapToGlobal(event.pos()))
224 def item_changed(self, item, _index):
225 self.rename_entry(item, item.text(0))
227 def rename_entry(self, item, new_name):
228 if self.style == BOOKMARKS:
229 rename = self.settings.rename_bookmark
230 elif self.style == RECENT_REPOS:
231 rename = self.settings.rename_recent
232 else:
233 rename = disable_rename
234 if rename(item.path, item.name, new_name):
235 self.settings.save()
236 item.name = new_name
237 else:
238 item.setText(0, item.name)
240 def apply_fn(self, fn, *args, **kwargs):
241 item = self.selected_item()
242 if item:
243 fn(item, *args, **kwargs)
245 def copy(self):
246 self.apply_fn(lambda item: qtutils.set_clipboard(item.path))
248 def open_default(self):
249 context = self.context
250 self.apply_fn(
251 lambda item: cmds.do(cmds.OpenDefaultApp, context, [item.path]))
253 def set_default_repo(self):
254 self.apply_fn(self.set_default_item)
256 def set_default_item(self, item):
257 context = self.context
258 cmds.do(cmds.SetDefaultRepo, context, item.path)
259 self.refresh()
260 self.default_changed.emit()
262 def clear_default_repo(self):
263 self.apply_fn(self.clear_default_item)
264 self.default_changed.emit()
266 def clear_default_item(self, _item):
267 context = self.context
268 cmds.do(cmds.SetDefaultRepo, context, None)
269 self.refresh()
271 def rename_repo(self):
272 self.apply_fn(lambda item: self.editItem(item, 0))
274 def accept_repo(self):
275 self.apply_fn(self.accept_item)
277 def accept_item(self, item):
278 if self.state() & self.EditingState:
279 widget = self.itemWidget(item, 0)
280 if widget:
281 self.commitData(widget)
282 self.closePersistentEditor(item, 0)
283 else:
284 self.open_repo()
286 def open_repo(self):
287 context = self.context
288 self.apply_fn(lambda item: cmds.do(cmds.OpenRepo, context, item.path))
290 def open_new_repo(self):
291 context = self.context
292 self.apply_fn(
293 lambda item: cmds.do(cmds.OpenNewRepo, context, item.path))
295 def launch_editor(self):
296 context = self.context
297 self.apply_fn(lambda item: cmds.do(cmds.Edit, context, [item.path]))
299 def launch_terminal(self):
300 context = self.context
301 self.apply_fn(
302 lambda item: cmds.do(cmds.LaunchTerminal, context, item.path))
304 def item_selection_changed(self):
305 item = self.selected_item()
306 enabled = bool(item)
307 self.action_group.setEnabled(enabled)
309 is_default = bool(item and item.is_default)
310 self.set_default_repo_action.setEnabled(not is_default)
311 self.clear_default_repo_action.setEnabled(is_default)
313 def tree_double_clicked(self, item, _column):
314 context = self.context
315 cmds.do(cmds.OpenRepo, context, item.path)
317 def add_bookmark(self):
318 normpath = utils.expandpath(core.getcwd())
319 name = os.path.basename(normpath)
320 prompt = (
321 (N_('Name'), name),
322 (N_('Path'), core.getcwd()),
324 ok, values = qtutils.prompt_n(N_('Add Favorite'), prompt)
325 if not ok:
326 return
327 name, path = values
328 normpath = utils.expandpath(path)
329 if git.is_git_worktree(normpath):
330 self.settings.add_bookmark(normpath, name)
331 self.settings.save()
332 self.refresh()
333 else:
334 Interaction.critical(
335 N_('Error'), N_('%s is not a Git repository.') % path)
337 def delete_bookmark(self):
338 """Removes a bookmark from the bookmarks list"""
339 item = self.selected_item()
340 context = self.context
341 if not item:
342 return
343 if self.style == BOOKMARKS:
344 cmd = cmds.RemoveBookmark
345 elif self.style == RECENT_REPOS:
346 cmd = cmds.RemoveRecent
347 else:
348 return
349 ok, _, _, _ = cmds.do(
350 cmd, context, self.settings, item.path, item.name,
351 icon=icons.discard())
352 if ok:
353 self.refresh()
356 class BuildItem(object):
358 def __init__(self, context):
359 self.star_icon = icons.star()
360 self.folder_icon = icons.folder()
361 cfg = context.cfg
362 self.default_repo = cfg.get('cola.defaultrepo')
364 def get(self, path, name):
365 is_default = self.default_repo == path
366 if is_default:
367 icon = self.star_icon
368 else:
369 icon = self.folder_icon
370 return BookmarksTreeWidgetItem(path, name, icon, is_default)
373 class BookmarksTreeWidgetItem(QtWidgets.QTreeWidgetItem):
375 def __init__(self, path, name, icon, is_default):
376 QtWidgets.QTreeWidgetItem.__init__(self)
377 self.path = path
378 self.name = name
379 self.is_default = is_default
381 self.setIcon(0, icon)
382 self.setText(0, name)
383 self.setToolTip(0, path)
384 self.setFlags(self.flags() | Qt.ItemIsEditable)