1 """Provides widgets related to bookmarks"""
4 from qtpy
import QtCore
6 from qtpy
import QtWidgets
7 from qtpy
.QtCore
import Qt
8 from qtpy
.QtCore
import Signal
13 from .. import hotkeys
15 from .. import qtutils
18 from ..interaction
import Interaction
19 from ..models
import prefs
20 from ..widgets
import defs
21 from ..widgets
import standard
22 from ..widgets
import switcher
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
):
38 def __init__(self
, context
, style
=BOOKMARKS
, parent
=None):
39 QtWidgets
.QFrame
.__init
__(self
, parent
)
41 self
.context
= context
44 self
.items
= items
= []
45 self
.model
= model
= QtGui
.QStandardItemModel()
47 settings
= context
.settings
48 builder
= BuildItem(context
)
50 if self
.style
== BOOKMARKS
:
51 entries
= settings
.bookmarks
53 elif self
.style
== RECENT_REPOS
:
54 entries
= settings
.recent
57 item
= builder
.get(entry
['path'], entry
['name'])
61 place_holder
= N_('Search repositories by name...')
62 self
.quick_switcher
= switcher
.switcher_outer_view(
63 context
, model
, place_holder
=place_holder
65 self
.tree
= BookmarksTreeView(
66 context
, style
, self
.set_items_to_models
, parent
=self
69 self
.add_button
= qtutils
.create_action_button(
70 tooltip
=N_('Add'), icon
=icons
.add()
73 self
.delete_button
= qtutils
.create_action_button(
74 tooltip
=N_('Delete'), icon
=icons
.remove()
77 self
.open_button
= qtutils
.create_action_button(
78 tooltip
=N_('Open'), icon
=icons
.repo()
81 self
.search_button
= qtutils
.create_action_button(
82 tooltip
=N_('Search'), icon
=icons
.search()
85 self
.button_group
= utils
.Group(self
.delete_button
, self
.open_button
)
86 self
.button_group
.setEnabled(False)
88 self
.setFocusProxy(self
.tree
)
89 if style
== BOOKMARKS
:
90 self
.setToolTip(N_('Favorite repositories'))
91 elif style
== RECENT_REPOS
:
92 self
.setToolTip(N_('Recent repositories'))
93 self
.add_button
.hide()
95 self
.button_layout
= qtutils
.hbox(
104 self
.main_layout
= qtutils
.vbox(
105 defs
.no_margin
, defs
.spacing
, self
.quick_switcher
, self
.tree
107 self
.setLayout(self
.main_layout
)
109 self
.corner_widget
= QtWidgets
.QWidget(self
)
110 self
.corner_widget
.setLayout(self
.button_layout
)
111 titlebar
= parent
.titleBarWidget()
112 titlebar
.add_corner_widget(self
.corner_widget
)
114 qtutils
.connect_button(self
.add_button
, self
.tree
.add_bookmark
)
115 qtutils
.connect_button(self
.delete_button
, self
.tree
.delete_bookmark
)
116 qtutils
.connect_button(self
.open_button
, self
.tree
.open_repo
)
117 qtutils
.connect_button(self
.search_button
, self
.toggle_switcher_input_field
)
119 QtCore
.QTimer
.singleShot(0, self
.reload_bookmarks
)
121 self
.tree
.toggle_switcher
.connect(self
.enable_switcher_input_field
)
122 # moving key has pressed while focusing on input field
123 self
.quick_switcher
.filter_input
.switcher_selection_move
.connect(
124 self
.tree
.keyPressEvent
126 # escape key has pressed while focusing on input field
127 self
.quick_switcher
.filter_input
.switcher_escape
.connect(
128 self
.close_switcher_input_field
130 # some key except moving key has pressed while focusing on list view
131 self
.tree
.switcher_text
.connect(self
.switcher_text_inputted
)
133 def reload_bookmarks(self
):
134 # Called once after the GUI is initialized
140 model
.dataChanged
.connect(tree
.item_changed
)
141 selection
= tree
.selectionModel()
142 selection
.selectionChanged
.connect(tree
.item_selection_changed
)
143 tree
.doubleClicked
.connect(tree
.tree_double_clicked
)
145 def tree_item_selection_changed(self
):
146 enabled
= bool(self
.tree
.selected_item())
147 self
.button_group
.setEnabled(enabled
)
149 def connect_to(self
, other
):
150 self
.tree
.default_changed
.connect(other
.tree
.refresh
)
151 other
.tree
.default_changed
.connect(self
.tree
.refresh
)
153 def set_items_to_models(self
, items
):
159 self
.items
.append(item
)
160 model
.appendRow(item
)
162 self
.quick_switcher
.proxy_model
.setSourceModel(model
)
163 self
.tree
.setModel(self
.quick_switcher
.proxy_model
)
165 def toggle_switcher_input_field(self
):
166 visible
= self
.quick_switcher
.filter_input
.isVisible()
167 self
.enable_switcher_input_field(not visible
)
169 def close_switcher_input_field(self
):
170 self
.enable_switcher_input_field(False)
172 def enable_switcher_input_field(self
, visible
):
173 filter_input
= self
.quick_switcher
.filter_input
175 filter_input
.setVisible(visible
)
179 def switcher_text_inputted(self
, event
):
180 # default selection for first index
181 first_proxy_idx
= self
.quick_switcher
.proxy_model
.index(0, 0)
182 self
.tree
.setCurrentIndex(first_proxy_idx
)
184 self
.quick_switcher
.filter_input
.keyPressEvent(event
)
187 def disable_rename(_path
, _name
, _new_name
):
191 class BookmarksTreeView(standard
.TreeView
):
192 default_changed
= Signal()
193 toggle_switcher
= Signal(bool)
194 # this signal will be emitted when some key pressed while focusing on tree view
195 switcher_text
= Signal(QtGui
.QKeyEvent
)
197 def __init__(self
, context
, style
, set_model
, parent
=None):
198 standard
.TreeView
.__init
__(self
, parent
=parent
)
199 self
.context
= context
201 self
.set_model
= set_model
203 self
.setSelectionMode(QtWidgets
.QAbstractItemView
.SingleSelection
)
204 self
.setHeaderHidden(True)
206 # We make the items editable, but we don't want the double-click
207 # behavior to trigger editing. Make it behave like Mac OS X's Finder.
208 self
.setEditTriggers(self
.SelectedClicked
)
210 self
.open_action
= qtutils
.add_action(
211 self
, N_('Open'), self
.open_repo
, hotkeys
.OPEN
214 self
.accept_action
= qtutils
.add_action(
215 self
, N_('Accept'), self
.accept_repo
, *hotkeys
.ACCEPT
218 self
.open_new_action
= qtutils
.add_action(
219 self
, N_('Open in New Window'), self
.open_new_repo
, hotkeys
.NEW
222 self
.set_default_repo_action
= qtutils
.add_action(
223 self
, N_('Set Default Repository'), self
.set_default_repo
226 self
.clear_default_repo_action
= qtutils
.add_action(
227 self
, N_('Clear Default Repository'), self
.clear_default_repo
230 self
.rename_repo_action
= qtutils
.add_action(
231 self
, N_('Rename Repository'), self
.rename_repo
234 self
.open_default_action
= qtutils
.add_action(
235 self
, cmds
.OpenDefaultApp
.name(), self
.open_default
, hotkeys
.PRIMARY_ACTION
238 self
.launch_editor_action
= qtutils
.add_action(
239 self
, cmds
.Edit
.name(), self
.launch_editor
, hotkeys
.EDIT
242 self
.launch_terminal_action
= qtutils
.add_action(
243 self
, cmds
.LaunchTerminal
.name(), self
.launch_terminal
, hotkeys
.TERMINAL
246 self
.copy_action
= qtutils
.add_action(self
, N_('Copy'), self
.copy
, hotkeys
.COPY
)
248 self
.delete_action
= qtutils
.add_action(
249 self
, N_('Delete'), self
.delete_bookmark
252 self
.remove_missing_action
= qtutils
.add_action(
253 self
, N_('Prune Missing Entries'), self
.remove_missing
255 self
.remove_missing_action
.setToolTip(
256 N_('Remove stale entries for repositories that no longer exist')
259 self
.action_group
= utils
.Group(
261 self
.open_new_action
,
263 self
.launch_editor_action
,
264 self
.launch_terminal_action
,
265 self
.open_default_action
,
266 self
.rename_repo_action
,
269 self
.action_group
.setEnabled(False)
270 self
.set_default_repo_action
.setEnabled(False)
271 self
.clear_default_repo_action
.setEnabled(False)
274 if style
== RECENT_REPOS
:
275 context
.model
.worktree_changed
.connect(
276 self
.refresh
, type=Qt
.QueuedConnection
279 def keyPressEvent(self
, event
):
281 This will be hooked while focusing on this list view.
282 Set input field invisible when escape key pressed.
283 Move selection when move key like tab, UP etc pressed.
284 Or open input field and simply act like text input to it. This is when
285 some character key pressed while focusing on tree view, NOT input field.
287 selection_moving_keys
= switcher
.moving_keys()
288 pressed_key
= event
.key()
290 if pressed_key
== Qt
.Key_Escape
:
291 self
.toggle_switcher
.emit(False)
292 elif pressed_key
in hotkeys
.ACCEPT
:
294 elif pressed_key
in selection_moving_keys
:
295 super().keyPressEvent(event
)
297 self
.toggle_switcher
.emit(True)
298 self
.switcher_text
.emit(event
)
301 context
= self
.context
302 settings
= context
.settings
303 builder
= BuildItem(context
)
306 if self
.style
== BOOKMARKS
:
307 entries
= settings
.bookmarks
309 elif self
.style
== RECENT_REPOS
:
310 entries
= settings
.recent
312 items
= [builder
.get(entry
['path'], entry
['name']) for entry
in entries
]
313 if self
.style
== BOOKMARKS
and prefs
.sort_bookmarks(context
):
314 items
.sort(key
=lambda x
: x
.name
.lower())
316 self
.set_model(items
)
318 def contextMenuEvent(self
, event
):
319 menu
= qtutils
.create_menu(N_('Actions'), self
)
320 menu
.addAction(self
.open_action
)
321 menu
.addAction(self
.open_new_action
)
322 menu
.addAction(self
.open_default_action
)
324 menu
.addAction(self
.copy_action
)
325 menu
.addAction(self
.launch_editor_action
)
326 menu
.addAction(self
.launch_terminal_action
)
328 item
= self
.selected_item()
329 is_default
= bool(item
and item
.is_default
)
331 menu
.addAction(self
.clear_default_repo_action
)
333 menu
.addAction(self
.set_default_repo_action
)
334 menu
.addAction(self
.rename_repo_action
)
336 menu
.addAction(self
.delete_action
)
337 menu
.addAction(self
.remove_missing_action
)
338 menu
.exec_(self
.mapToGlobal(event
.pos()))
340 def item_selection_changed(self
, selected
, _deselected
):
341 item_idx
= selected
.indexes()
343 item
= self
.model().itemFromIndex(item_idx
[0])
345 self
.action_group
.setEnabled(enabled
)
347 is_default
= bool(item
and item
.is_default
)
348 self
.set_default_repo_action
.setEnabled(not is_default
)
349 self
.clear_default_repo_action
.setEnabled(is_default
)
351 def tree_double_clicked(self
, _index
):
352 context
= self
.context
353 item
= self
.selected_item()
354 cmds
.do(cmds
.OpenRepo
, context
, item
.path
)
355 self
.toggle_switcher
.emit(False)
357 def selected_item(self
):
358 index
= self
.currentIndex()
359 return self
.model().itemFromIndex(index
)
361 def item_changed(self
, _top_left
, _bottom_right
, _roles
):
362 item
= self
.selected_item()
363 self
.rename_entry(item
, item
.text())
365 def rename_entry(self
, item
, new_name
):
366 settings
= self
.context
.settings
367 if self
.style
== BOOKMARKS
:
368 rename
= settings
.rename_bookmark
369 elif self
.style
== RECENT_REPOS
:
370 rename
= settings
.rename_recent
372 rename
= disable_rename
373 if rename(item
.path
, item
.name
, new_name
):
377 item
.setText(item
.name
)
378 self
.toggle_switcher
.emit(False)
380 def apply_func(self
, func
, *args
, **kwargs
):
381 item
= self
.selected_item()
383 func(item
, *args
, **kwargs
)
386 self
.apply_func(lambda item
: qtutils
.set_clipboard(item
.path
))
387 self
.toggle_switcher
.emit(False)
389 def open_default(self
):
390 context
= self
.context
391 self
.apply_func(lambda item
: cmds
.do(cmds
.OpenDefaultApp
, context
, [item
.path
]))
392 self
.toggle_switcher
.emit(False)
394 def set_default_repo(self
):
395 self
.apply_func(self
.set_default_item
)
396 self
.toggle_switcher
.emit(False)
398 def set_default_item(self
, item
):
399 context
= self
.context
400 cmds
.do(cmds
.SetDefaultRepo
, context
, item
.path
)
402 self
.default_changed
.emit()
403 self
.toggle_switcher
.emit(False)
405 def clear_default_repo(self
):
406 self
.apply_func(self
.clear_default_item
)
407 self
.default_changed
.emit()
408 self
.toggle_switcher
.emit(False)
410 def clear_default_item(self
, _item
):
411 context
= self
.context
412 cmds
.do(cmds
.SetDefaultRepo
, context
, None)
414 self
.toggle_switcher
.emit(False)
416 def rename_repo(self
):
417 index
= self
.currentIndex()
419 self
.toggle_switcher
.emit(False)
421 def accept_repo(self
):
422 self
.apply_func(self
.accept_item
)
423 self
.toggle_switcher
.emit(False)
425 def accept_item(self
, _item
):
426 if self
.state() & self
.EditingState
:
427 current_index
= self
.currentIndex()
428 widget
= self
.indexWidget(current_index
)
430 self
.commitData(widget
)
431 self
.closePersistentEditor(current_index
)
434 self
.open_selected_repo()
437 context
= self
.context
438 self
.apply_func(lambda item
: cmds
.do(cmds
.OpenRepo
, context
, item
.path
))
440 def open_selected_repo(self
):
441 item
= self
.selected_item()
442 context
= self
.context
443 cmds
.do(cmds
.OpenRepo
, context
, item
.path
)
444 self
.toggle_switcher
.emit(False)
446 def open_new_repo(self
):
447 context
= self
.context
448 self
.apply_func(lambda item
: cmds
.do(cmds
.OpenNewRepo
, context
, item
.path
))
449 self
.toggle_switcher
.emit(False)
451 def launch_editor(self
):
452 context
= self
.context
453 self
.apply_func(lambda item
: cmds
.do(cmds
.Edit
, context
, [item
.path
]))
454 self
.toggle_switcher
.emit(False)
456 def launch_terminal(self
):
457 context
= self
.context
458 self
.apply_func(lambda item
: cmds
.do(cmds
.LaunchTerminal
, context
, item
.path
))
459 self
.toggle_switcher
.emit(False)
461 def add_bookmark(self
):
462 normpath
= utils
.expandpath(core
.getcwd())
463 name
= os
.path
.basename(normpath
)
466 (N_('Path'), core
.getcwd()),
468 ok
, values
= qtutils
.prompt_n(N_('Add Favorite'), prompt
)
472 normpath
= utils
.expandpath(path
)
473 if git
.is_git_worktree(normpath
):
474 settings
= self
.context
.settings
476 settings
.add_bookmark(normpath
, name
)
480 Interaction
.critical(N_('Error'), N_('%s is not a Git repository.') % path
)
482 def delete_bookmark(self
):
483 """Removes a bookmark from the bookmarks list"""
484 item
= self
.selected_item()
485 context
= self
.context
488 if self
.style
== BOOKMARKS
:
489 cmd
= cmds
.RemoveBookmark
490 elif self
.style
== RECENT_REPOS
:
491 cmd
= cmds
.RemoveRecent
494 ok
, _
, _
, _
= cmds
.do(cmd
, context
, item
.path
, item
.name
, icon
=icons
.discard())
497 self
.toggle_switcher
.emit(False)
499 def remove_missing(self
):
500 """Remove missing entries from the favorites/recent file list"""
501 settings
= self
.context
.settings
502 if self
.style
== BOOKMARKS
:
503 settings
.remove_missing_bookmarks()
504 elif self
.style
== RECENT_REPOS
:
505 settings
.remove_missing_recent()
510 def __init__(self
, context
):
511 self
.star_icon
= icons
.star()
512 self
.folder_icon
= icons
.folder()
514 self
.default_repo
= cfg
.get('cola.defaultrepo')
516 def get(self
, path
, name
):
517 is_default
= self
.default_repo
== path
519 icon
= self
.star_icon
521 icon
= self
.folder_icon
522 return BookmarksTreeItem(path
, name
, icon
, is_default
)
525 class BookmarksTreeItem(switcher
.SwitcherListItem
):
526 def __init__(self
, path
, name
, icon
, is_default
):
527 switcher
.SwitcherListItem
.__init
__(self
, name
, icon
=icon
, name
=name
)
531 self
.is_default
= is_default
535 self
.setToolTip(path
)
536 self
.setFlags(self
.flags() | Qt
.ItemIsEditable
)