1 """Provides widgets related to bookmarks"""
2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
5 from qtpy
import QtCore
7 from qtpy
import QtWidgets
8 from qtpy
.QtCore
import Qt
9 from qtpy
.QtCore
import Signal
14 from .. import hotkeys
16 from .. import qtutils
19 from ..interaction
import Interaction
20 from ..models
import prefs
21 from ..widgets
import defs
22 from ..widgets
import standard
23 from ..widgets
import switcher
30 def bookmark(context
, parent
):
31 return BookmarksWidget(context
, BOOKMARKS
, parent
=parent
)
34 def recent(context
, parent
):
35 return BookmarksWidget(context
, RECENT_REPOS
, parent
=parent
)
38 class BookmarksWidget(QtWidgets
.QFrame
):
39 def __init__(self
, context
, style
=BOOKMARKS
, parent
=None):
40 QtWidgets
.QFrame
.__init
__(self
, parent
)
42 self
.context
= context
45 self
.items
= items
= []
46 self
.model
= model
= QtGui
.QStandardItemModel()
48 settings
= context
.settings
49 builder
= BuildItem(context
)
51 if self
.style
== BOOKMARKS
:
52 entries
= settings
.bookmarks
54 elif self
.style
== RECENT_REPOS
:
55 entries
= settings
.recent
58 item
= builder
.get(entry
['path'], entry
['name'])
62 place_holder
= N_('Search repositories by name...')
63 self
.quick_switcher
= switcher
.switcher_outer_view(
64 context
, model
, place_holder
=place_holder
66 self
.tree
= BookmarksTreeView(
67 context
, style
, self
.set_items_to_models
, parent
=self
70 self
.add_button
= qtutils
.create_action_button(
71 tooltip
=N_('Add'), icon
=icons
.add()
74 self
.delete_button
= qtutils
.create_action_button(
75 tooltip
=N_('Delete'), icon
=icons
.remove()
78 self
.open_button
= qtutils
.create_action_button(
79 tooltip
=N_('Open'), icon
=icons
.repo()
82 self
.search_button
= qtutils
.create_action_button(
83 tooltip
=N_('Search'), icon
=icons
.search()
86 self
.button_group
= utils
.Group(self
.delete_button
, self
.open_button
)
87 self
.button_group
.setEnabled(False)
89 self
.setFocusProxy(self
.tree
)
90 if style
== BOOKMARKS
:
91 self
.setToolTip(N_('Favorite repositories'))
92 elif style
== RECENT_REPOS
:
93 self
.setToolTip(N_('Recent repositories'))
94 self
.add_button
.hide()
96 self
.button_layout
= qtutils
.hbox(
105 self
.main_layout
= qtutils
.vbox(
106 defs
.no_margin
, defs
.spacing
, self
.quick_switcher
, self
.tree
108 self
.setLayout(self
.main_layout
)
110 self
.corner_widget
= QtWidgets
.QWidget(self
)
111 self
.corner_widget
.setLayout(self
.button_layout
)
112 titlebar
= parent
.titleBarWidget()
113 titlebar
.add_corner_widget(self
.corner_widget
)
115 qtutils
.connect_button(self
.add_button
, self
.tree
.add_bookmark
)
116 qtutils
.connect_button(self
.delete_button
, self
.tree
.delete_bookmark
)
117 qtutils
.connect_button(self
.open_button
, self
.tree
.open_repo
)
118 qtutils
.connect_button(self
.search_button
, self
.toggle_switcher_input_field
)
120 QtCore
.QTimer
.singleShot(0, self
.reload_bookmarks
)
122 self
.tree
.toggle_switcher
.connect(self
.enable_switcher_input_field
)
123 # moving key has pressed while focusing on input field
124 self
.quick_switcher
.filter_input
.switcher_selection_move
.connect(
125 self
.tree
.keyPressEvent
127 # escape key has pressed while focusing on input field
128 self
.quick_switcher
.filter_input
.switcher_escape
.connect(
129 self
.close_switcher_input_field
131 # some key except moving key has pressed while focusing on list view
132 self
.tree
.switcher_text
.connect(self
.switcher_text_inputted
)
134 def reload_bookmarks(self
):
135 # Called once after the GUI is initialized
141 model
.dataChanged
.connect(tree
.item_changed
)
142 selection
= tree
.selectionModel()
143 selection
.selectionChanged
.connect(tree
.item_selection_changed
)
144 tree
.doubleClicked
.connect(tree
.tree_double_clicked
)
146 def tree_item_selection_changed(self
):
147 enabled
= bool(self
.tree
.selected_item())
148 self
.button_group
.setEnabled(enabled
)
150 def connect_to(self
, other
):
151 self
.tree
.default_changed
.connect(other
.tree
.refresh
)
152 other
.tree
.default_changed
.connect(self
.tree
.refresh
)
154 def set_items_to_models(self
, items
):
160 self
.items
.append(item
)
161 model
.appendRow(item
)
163 self
.quick_switcher
.proxy_model
.setSourceModel(model
)
164 self
.tree
.setModel(self
.quick_switcher
.proxy_model
)
166 def toggle_switcher_input_field(self
):
167 visible
= self
.quick_switcher
.filter_input
.isVisible()
168 self
.enable_switcher_input_field(not visible
)
170 def close_switcher_input_field(self
):
171 self
.enable_switcher_input_field(False)
173 def enable_switcher_input_field(self
, visible
):
174 filter_input
= self
.quick_switcher
.filter_input
176 filter_input
.setVisible(visible
)
180 def switcher_text_inputted(self
, event
):
181 # default selection for first index
182 first_proxy_idx
= self
.quick_switcher
.proxy_model
.index(0, 0)
183 self
.tree
.setCurrentIndex(first_proxy_idx
)
185 self
.quick_switcher
.filter_input
.keyPressEvent(event
)
188 def disable_rename(_path
, _name
, _new_name
):
192 # pylint: disable=too-many-ancestors
193 class BookmarksTreeView(standard
.TreeView
):
194 default_changed
= Signal()
195 toggle_switcher
= Signal(bool)
196 # this signal will be emitted when some key pressed while focusing on tree view
197 switcher_text
= Signal(QtGui
.QKeyEvent
)
199 def __init__(self
, context
, style
, set_model
, parent
=None):
200 standard
.TreeView
.__init
__(self
, parent
=parent
)
201 self
.context
= context
203 self
.set_model
= set_model
205 self
.setSelectionMode(QtWidgets
.QAbstractItemView
.SingleSelection
)
206 self
.setHeaderHidden(True)
208 # We make the items editable, but we don't want the double-click
209 # behavior to trigger editing. Make it behave like Mac OS X's Finder.
210 self
.setEditTriggers(self
.SelectedClicked
)
212 self
.open_action
= qtutils
.add_action(
213 self
, N_('Open'), self
.open_repo
, hotkeys
.OPEN
216 self
.accept_action
= qtutils
.add_action(
217 self
, N_('Accept'), self
.accept_repo
, *hotkeys
.ACCEPT
220 self
.open_new_action
= qtutils
.add_action(
221 self
, N_('Open in New Window'), self
.open_new_repo
, hotkeys
.NEW
224 self
.set_default_repo_action
= qtutils
.add_action(
225 self
, N_('Set Default Repository'), self
.set_default_repo
228 self
.clear_default_repo_action
= qtutils
.add_action(
229 self
, N_('Clear Default Repository'), self
.clear_default_repo
232 self
.rename_repo_action
= qtutils
.add_action(
233 self
, N_('Rename Repository'), self
.rename_repo
236 self
.open_default_action
= qtutils
.add_action(
237 self
, cmds
.OpenDefaultApp
.name(), self
.open_default
, hotkeys
.PRIMARY_ACTION
240 self
.launch_editor_action
= qtutils
.add_action(
241 self
, cmds
.Edit
.name(), self
.launch_editor
, hotkeys
.EDIT
244 self
.launch_terminal_action
= qtutils
.add_action(
245 self
, cmds
.LaunchTerminal
.name(), self
.launch_terminal
, hotkeys
.TERMINAL
248 self
.copy_action
= qtutils
.add_action(self
, N_('Copy'), self
.copy
, hotkeys
.COPY
)
250 self
.delete_action
= qtutils
.add_action(
251 self
, N_('Delete'), self
.delete_bookmark
254 self
.remove_missing_action
= qtutils
.add_action(
255 self
, N_('Prune Missing Entries'), self
.remove_missing
257 self
.remove_missing_action
.setToolTip(
258 N_('Remove stale entries for repositories that no longer exist')
261 self
.action_group
= utils
.Group(
263 self
.open_new_action
,
265 self
.launch_editor_action
,
266 self
.launch_terminal_action
,
267 self
.open_default_action
,
268 self
.rename_repo_action
,
271 self
.action_group
.setEnabled(False)
272 self
.set_default_repo_action
.setEnabled(False)
273 self
.clear_default_repo_action
.setEnabled(False)
276 if style
== RECENT_REPOS
:
277 context
.model
.worktree_changed
.connect(
278 self
.refresh
, type=Qt
.QueuedConnection
281 def keyPressEvent(self
, event
):
283 This will be hooked while focusing on this list view.
284 Set input field invisible when escape key pressed.
285 Move selection when move key like tab, UP etc pressed.
286 Or open input field and simply act like text input to it. This is when
287 some character key pressed while focusing on tree view, NOT input field.
289 selection_moving_keys
= switcher
.moving_keys()
290 pressed_key
= event
.key()
292 if pressed_key
== Qt
.Key_Escape
:
293 self
.toggle_switcher
.emit(False)
294 elif pressed_key
in hotkeys
.ACCEPT
:
296 elif pressed_key
in selection_moving_keys
:
297 super().keyPressEvent(event
)
299 self
.toggle_switcher
.emit(True)
300 self
.switcher_text
.emit(event
)
303 context
= self
.context
304 settings
= context
.settings
305 builder
= BuildItem(context
)
308 if self
.style
== BOOKMARKS
:
309 entries
= settings
.bookmarks
311 elif self
.style
== RECENT_REPOS
:
312 entries
= settings
.recent
314 items
= [builder
.get(entry
['path'], entry
['name']) for entry
in entries
]
315 if self
.style
== BOOKMARKS
and prefs
.sort_bookmarks(context
):
316 items
.sort(key
=lambda x
: x
.name
.lower())
318 self
.set_model(items
)
320 def contextMenuEvent(self
, event
):
321 menu
= qtutils
.create_menu(N_('Actions'), self
)
322 menu
.addAction(self
.open_action
)
323 menu
.addAction(self
.open_new_action
)
324 menu
.addAction(self
.open_default_action
)
326 menu
.addAction(self
.copy_action
)
327 menu
.addAction(self
.launch_editor_action
)
328 menu
.addAction(self
.launch_terminal_action
)
330 item
= self
.selected_item()
331 is_default
= bool(item
and item
.is_default
)
333 menu
.addAction(self
.clear_default_repo_action
)
335 menu
.addAction(self
.set_default_repo_action
)
336 menu
.addAction(self
.rename_repo_action
)
338 menu
.addAction(self
.delete_action
)
339 menu
.addAction(self
.remove_missing_action
)
340 menu
.exec_(self
.mapToGlobal(event
.pos()))
342 def item_selection_changed(self
, selected
, _deselected
):
343 item_idx
= selected
.indexes()
345 item
= self
.model().itemFromIndex(item_idx
[0])
347 self
.action_group
.setEnabled(enabled
)
349 is_default
= bool(item
and item
.is_default
)
350 self
.set_default_repo_action
.setEnabled(not is_default
)
351 self
.clear_default_repo_action
.setEnabled(is_default
)
353 def tree_double_clicked(self
, _index
):
354 context
= self
.context
355 item
= self
.selected_item()
356 cmds
.do(cmds
.OpenRepo
, context
, item
.path
)
357 self
.toggle_switcher
.emit(False)
359 def selected_item(self
):
360 index
= self
.currentIndex()
361 return self
.model().itemFromIndex(index
)
363 def item_changed(self
, _top_left
, _bottom_right
, _roles
):
364 item
= self
.selected_item()
365 self
.rename_entry(item
, item
.text())
367 def rename_entry(self
, item
, new_name
):
368 settings
= self
.context
.settings
369 if self
.style
== BOOKMARKS
:
370 rename
= settings
.rename_bookmark
371 elif self
.style
== RECENT_REPOS
:
372 rename
= settings
.rename_recent
374 rename
= disable_rename
375 if rename(item
.path
, item
.name
, new_name
):
379 item
.setText(item
.name
)
380 self
.toggle_switcher
.emit(False)
382 def apply_fn(self
, fn
, *args
, **kwargs
):
383 item
= self
.selected_item()
385 fn(item
, *args
, **kwargs
)
388 self
.apply_fn(lambda item
: qtutils
.set_clipboard(item
.path
))
389 self
.toggle_switcher
.emit(False)
391 def open_default(self
):
392 context
= self
.context
393 self
.apply_fn(lambda item
: cmds
.do(cmds
.OpenDefaultApp
, context
, [item
.path
]))
394 self
.toggle_switcher
.emit(False)
396 def set_default_repo(self
):
397 self
.apply_fn(self
.set_default_item
)
398 self
.toggle_switcher
.emit(False)
400 def set_default_item(self
, item
):
401 context
= self
.context
402 cmds
.do(cmds
.SetDefaultRepo
, context
, item
.path
)
404 self
.default_changed
.emit()
405 self
.toggle_switcher
.emit(False)
407 def clear_default_repo(self
):
408 self
.apply_fn(self
.clear_default_item
)
409 self
.default_changed
.emit()
410 self
.toggle_switcher
.emit(False)
412 def clear_default_item(self
, _item
):
413 context
= self
.context
414 cmds
.do(cmds
.SetDefaultRepo
, context
, None)
416 self
.toggle_switcher
.emit(False)
418 def rename_repo(self
):
419 index
= self
.currentIndex()
421 self
.toggle_switcher
.emit(False)
423 def accept_repo(self
):
424 self
.apply_fn(self
.accept_item
)
425 self
.toggle_switcher
.emit(False)
427 def accept_item(self
, _item
):
428 if self
.state() & self
.EditingState
:
429 current_index
= self
.currentIndex()
430 widget
= self
.indexWidget(current_index
)
432 self
.commitData(widget
)
433 self
.closePersistentEditor(current_index
)
436 self
.open_selected_repo()
439 context
= self
.context
440 self
.apply_fn(lambda item
: cmds
.do(cmds
.OpenRepo
, context
, item
.path
))
442 def open_selected_repo(self
):
443 item
= self
.selected_item()
444 context
= self
.context
445 cmds
.do(cmds
.OpenRepo
, context
, item
.path
)
446 self
.toggle_switcher
.emit(False)
448 def open_new_repo(self
):
449 context
= self
.context
450 self
.apply_fn(lambda item
: cmds
.do(cmds
.OpenNewRepo
, context
, item
.path
))
451 self
.toggle_switcher
.emit(False)
453 def launch_editor(self
):
454 context
= self
.context
455 self
.apply_fn(lambda item
: cmds
.do(cmds
.Edit
, context
, [item
.path
]))
456 self
.toggle_switcher
.emit(False)
458 def launch_terminal(self
):
459 context
= self
.context
460 self
.apply_fn(lambda item
: cmds
.do(cmds
.LaunchTerminal
, context
, item
.path
))
461 self
.toggle_switcher
.emit(False)
463 def add_bookmark(self
):
464 normpath
= utils
.expandpath(core
.getcwd())
465 name
= os
.path
.basename(normpath
)
468 (N_('Path'), core
.getcwd()),
470 ok
, values
= qtutils
.prompt_n(N_('Add Favorite'), prompt
)
474 normpath
= utils
.expandpath(path
)
475 if git
.is_git_worktree(normpath
):
476 settings
= self
.context
.settings
478 settings
.add_bookmark(normpath
, name
)
482 Interaction
.critical(N_('Error'), N_('%s is not a Git repository.') % path
)
484 def delete_bookmark(self
):
485 """Removes a bookmark from the bookmarks list"""
486 item
= self
.selected_item()
487 context
= self
.context
490 if self
.style
== BOOKMARKS
:
491 cmd
= cmds
.RemoveBookmark
492 elif self
.style
== RECENT_REPOS
:
493 cmd
= cmds
.RemoveRecent
496 ok
, _
, _
, _
= cmds
.do(cmd
, context
, item
.path
, item
.name
, icon
=icons
.discard())
499 self
.toggle_switcher
.emit(False)
501 def remove_missing(self
):
502 """Remove missing entries from the favorites/recent file list"""
503 settings
= self
.context
.settings
504 if self
.style
== BOOKMARKS
:
505 settings
.remove_missing_bookmarks()
506 elif self
.style
== RECENT_REPOS
:
507 settings
.remove_missing_recent()
511 class BuildItem(object):
512 def __init__(self
, context
):
513 self
.star_icon
= icons
.star()
514 self
.folder_icon
= icons
.folder()
516 self
.default_repo
= cfg
.get('cola.defaultrepo')
518 def get(self
, path
, name
):
519 is_default
= self
.default_repo
== path
521 icon
= self
.star_icon
523 icon
= self
.folder_icon
524 return BookmarksTreeItem(path
, name
, icon
, is_default
)
527 class BookmarksTreeItem(switcher
.SwitcherListItem
):
528 def __init__(self
, path
, name
, icon
, is_default
):
529 switcher
.SwitcherListItem
.__init
__(self
, name
, icon
=icon
, name
=name
)
533 self
.is_default
= is_default
537 self
.setToolTip(path
)
538 self
.setFlags(self
.flags() | Qt
.ItemIsEditable
)