1 """The startup dialog is presented when no repositories can be found at startup"""
2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
4 from qtpy
.QtCore
import Qt
5 from qtpy
import QtCore
7 from qtpy
import QtWidgets
10 from ..models
import prefs
13 from .. import display
14 from .. import guicmds
15 from .. import hotkeys
17 from .. import qtutils
19 from .. import version
22 from . import standard
29 class StartupDialog(standard
.Dialog
):
30 """Provides a GUI to Open or Clone a git repository."""
32 def __init__(self
, context
, parent
=None):
33 standard
.Dialog
.__init
__(self
, parent
)
34 self
.context
= context
35 self
.setWindowTitle(N_('git-cola'))
38 logo_pixmap
= icons
.cola().pixmap(defs
.huge_icon
, defs
.huge_icon
)
40 self
.logo_label
= QtWidgets
.QLabel()
41 self
.logo_label
.setPixmap(logo_pixmap
)
42 self
.logo_label
.setAlignment(Qt
.AlignCenter
)
44 self
.logo_text_label
= qtutils
.label(text
=version
.cola_version())
45 self
.logo_text_label
.setAlignment(Qt
.AlignCenter
)
49 self
.runtask
= context
.runtask
51 self
.runtask
= context
.runtask
= qtutils
.RunTask(parent
=self
)
53 self
.new_button
= qtutils
.create_button(text
=N_('New...'), icon
=icons
.new())
54 self
.open_button
= qtutils
.create_button(
55 text
=N_('Open Git Repository'), icon
=icons
.folder()
57 self
.clone_button
= qtutils
.create_button(
58 text
=N_('Clone...'), icon
=icons
.cola()
60 self
.close_button
= qtutils
.close_button()
62 self
.bookmarks_model
= bookmarks_model
= QtGui
.QStandardItemModel()
63 self
.items
= items
= []
65 item
= QtGui
.QStandardItem(N_('Browse...'))
66 item
.setEditable(False)
67 item
.setIcon(icons
.open_directory())
68 bookmarks_model
.appendRow(item
)
70 # The tab bar allows choosing between Folder and List mode
71 self
.tab_bar
= QtWidgets
.QTabBar()
72 self
.tab_bar
.setMovable(False)
73 self
.tab_bar
.addTab(icons
.directory(), N_('Folder'))
74 self
.tab_bar
.addTab(icons
.three_bars(), N_('List'))
76 # Bookmarks/"Favorites" and Recent are lists of {name,path: str}
77 normalize
= display
.normalize_path
78 settings
= context
.settings
79 all_repos
= get_all_repos(self
.context
, settings
)
82 builder
= BuildItem(self
.context
)
83 default_view_mode
= ICON_MODE
84 for repo
, is_bookmark
in all_repos
:
85 path
= normalize(repo
['path'])
86 name
= normalize(repo
['name'])
91 item
= builder
.get(path
, name
, default_view_mode
, is_bookmark
)
92 bookmarks_model
.appendRow(item
)
95 self
.bookmarks
= BookmarksListView(
98 self
.open_selected_bookmark
,
102 self
.tab_layout
= qtutils
.vbox(
103 defs
.no_margin
, defs
.no_spacing
, self
.tab_bar
, self
.bookmarks
106 self
.logo_layout
= qtutils
.vbox(
110 self
.logo_text_label
,
115 self
.button_layout
= qtutils
.hbox(
125 self
.main_layout
= qtutils
.grid(defs
.margin
, defs
.spacing
)
126 self
.main_layout
.addItem(self
.logo_layout
, 1, 1)
127 self
.main_layout
.addItem(self
.tab_layout
, 1, 2)
128 self
.main_layout
.addItem(self
.button_layout
, 2, 1, columnSpan
=2)
129 self
.setLayout(self
.main_layout
)
131 qtutils
.connect_button(self
.open_button
, self
.open_repo
)
132 qtutils
.connect_button(self
.clone_button
, self
.clone_repo
)
133 qtutils
.connect_button(self
.new_button
, self
.new_repo
)
134 qtutils
.connect_button(self
.close_button
, self
.reject
)
136 # pylint: disable=no-member
137 self
.tab_bar
.currentChanged
.connect(self
.tab_changed
)
139 self
.init_state(settings
, self
.resize_widget
)
140 self
.setFocusProxy(self
.bookmarks
)
141 self
.bookmarks
.setFocus()
143 # Update the list mode
144 list_mode
= context
.cfg
.get('cola.startupmode', default
='folder')
145 self
.list_mode
= list_mode
146 if list_mode
== 'list':
147 self
.tab_bar
.setCurrentIndex(1)
149 def tab_changed(self
, idx
):
150 bookmarks
= self
.bookmarks
152 mode
= QtWidgets
.QListView
.IconMode
153 icon_size
= make_size(defs
.medium_icon
)
154 grid_size
= make_size(defs
.large_icon
)
156 view_mode
= ICON_MODE
157 rename_enabled
= True
159 mode
= QtWidgets
.QListView
.ListMode
160 icon_size
= make_size(defs
.default_icon
)
161 grid_size
= QtCore
.QSize()
163 view_mode
= LIST_MODE
164 rename_enabled
= False
166 self
.bookmarks
.set_rename_enabled(rename_enabled
)
167 self
.bookmarks
.set_view_mode(view_mode
)
169 bookmarks
.setViewMode(mode
)
170 bookmarks
.setIconSize(icon_size
)
171 bookmarks
.setGridSize(grid_size
)
174 builder
= BuildItem(self
.context
)
175 for item
in self
.items
:
176 if isinstance(item
, PromptWidgetItem
):
177 item
= builder
.get(item
.path
, item
.name
, view_mode
, item
.is_bookmark
)
178 new_items
.append(item
)
180 self
.set_model(new_items
)
182 if list_mode
!= self
.list_mode
:
183 self
.list_mode
= list_mode
184 self
.context
.cfg
.set_user('cola.startupmode', list_mode
)
186 def resize_widget(self
):
187 width
, height
= qtutils
.desktop_size()
195 def find_git_repo(self
):
197 Return a path to a git repository
199 This is the entry point for external callers.
200 This method finds a git repository by allowing the
201 user to browse to one on the filesystem or by creating
202 a new one with git-clone.
207 if self
.exec_() == QtWidgets
.QDialog
.Accepted
:
212 self
.repodir
= self
.get_selected_bookmark()
214 self
.repodir
= qtutils
.opendir_dialog(
215 N_('Open Git Repository'), core
.getcwd()
220 def clone_repo(self
):
221 context
= self
.context
222 progress
= standard
.progress('', '', self
)
223 clone
.clone_repo(context
, True, progress
, self
.clone_repo_done
, False)
225 def clone_repo_done(self
, task
):
226 if task
.cmd
and task
.cmd
.status
== 0:
227 self
.repodir
= task
.destdir
230 clone
.task_finished(task
)
233 context
= self
.context
234 repodir
= guicmds
.new_repo(context
)
236 self
.repodir
= repodir
239 def open_selected_bookmark(self
):
240 selected
= self
.bookmarks
.selectedIndexes()
242 self
.open_bookmark(selected
[0])
244 def open_bookmark(self
, index
):
248 self
.repodir
= self
.bookmarks_model
.data(index
, Qt
.UserRole
)
251 if not core
.exists(self
.repodir
):
252 self
.handle_broken_repo(index
)
256 def handle_broken_repo(self
, index
):
257 settings
= self
.context
.settings
258 all_repos
= get_all_repos(self
.context
, settings
)
260 repodir
= self
.bookmarks_model
.data(index
, Qt
.UserRole
)
261 repo
= next(repo
for repo
, is_bookmark
in all_repos
if repo
['path'] == repodir
)
262 title
= N_('Repository Not Found')
263 text
= N_('%s could not be opened. Remove from bookmarks?') % repo
['path']
264 logo
= icons
.from_style(QtWidgets
.QStyle
.SP_MessageBoxWarning
)
265 if standard
.question(title
, text
, N_('Remove'), logo
=logo
):
266 self
.context
.settings
.remove_bookmark(repo
['path'], repo
['name'])
267 self
.context
.settings
.remove_recent(repo
['path'])
268 self
.context
.settings
.save()
270 item
= self
.bookmarks_model
.item(index
.row())
271 self
.items
.remove(item
)
272 self
.bookmarks_model
.removeRow(index
.row())
274 def get_selected_bookmark(self
):
275 selected
= self
.bookmarks
.selectedIndexes()
276 if selected
and selected
[0].row() != 0:
277 return self
.bookmarks_model
.data(selected
[0], Qt
.UserRole
)
280 def set_model(self
, items
):
281 bookmarks_model
= self
.bookmarks_model
282 self
.items
= new_items
= []
283 bookmarks_model
.clear()
285 item
= QtGui
.QStandardItem(N_('Browse...'))
286 item
.setEditable(False)
287 item
.setIcon(icons
.open_directory())
288 bookmarks_model
.appendRow(item
)
291 bookmarks_model
.appendRow(item
)
292 new_items
.append(item
)
295 def get_all_repos(context
, settings
):
296 """Return a sorted list of bookmarks and recent repositorties"""
297 bookmarks
= settings
.bookmarks
298 recent
= settings
.recent
299 all_repos
= [(repo
, True) for repo
in bookmarks
] + [
300 (repo
, False) for repo
in recent
302 if prefs
.sort_bookmarks(context
):
303 all_repos
.sort(key
=lambda details
: details
[0]['path'].lower())
307 class BookmarksListView(QtWidgets
.QListView
):
309 List view class implementation of QWidgets.QListView for bookmarks and recent repos.
310 Almost methods is comes from `cola/widgets/bookmarks.py`.
313 def __init__(self
, context
, model
, open_selected_repo
, set_model
, parent
=None):
314 super(BookmarksListView
, self
).__init
__(parent
)
316 self
.current_mode
= ICON_MODE
317 self
.context
= context
318 self
.open_selected_repo
= open_selected_repo
319 self
.set_model
= set_model
321 self
.setEditTriggers(self
.SelectedClicked
)
323 self
.activated
.connect(self
.open_selected_repo
)
326 self
.setSelectionMode(QtWidgets
.QAbstractItemView
.SingleSelection
)
327 self
.setViewMode(QtWidgets
.QListView
.IconMode
)
328 self
.setResizeMode(QtWidgets
.QListView
.Adjust
)
329 self
.setGridSize(make_size(defs
.large_icon
))
330 self
.setIconSize(make_size(defs
.medium_icon
))
331 self
.setDragEnabled(False)
332 self
.setWordWrap(True)
335 self
.open_action
= qtutils
.add_action(
336 self
, N_('Open'), self
.open_selected_repo
, hotkeys
.OPEN
339 self
.accept_action
= qtutils
.add_action(
340 self
, N_('Accept'), self
.accept_repo
, *hotkeys
.ACCEPT
343 self
.open_new_action
= qtutils
.add_action(
344 self
, N_('Open in New Window'), self
.open_new_repo
, hotkeys
.NEW
347 self
.set_default_repo_action
= qtutils
.add_action(
348 self
, N_('Set Default Repository'), self
.set_default_repo
351 self
.clear_default_repo_action
= qtutils
.add_action(
352 self
, N_('Clear Default Repository'), self
.clear_default_repo
355 self
.rename_repo_action
= qtutils
.add_action(
356 self
, N_('Rename Repository'), self
.rename_repo
359 self
.open_default_action
= qtutils
.add_action(
360 self
, cmds
.OpenDefaultApp
.name(), self
.open_default
, hotkeys
.PRIMARY_ACTION
363 self
.launch_editor_action
= qtutils
.add_action(
364 self
, cmds
.Edit
.name(), self
.launch_editor
, hotkeys
.EDIT
367 self
.launch_terminal_action
= qtutils
.add_action(
368 self
, cmds
.LaunchTerminal
.name(), self
.launch_terminal
, hotkeys
.TERMINAL
371 self
.copy_action
= qtutils
.add_action(self
, N_('Copy'), self
.copy
, hotkeys
.COPY
)
373 self
.delete_action
= qtutils
.add_action(self
, N_('Delete'), self
.delete_item
)
375 self
.remove_missing_action
= qtutils
.add_action(
376 self
, N_('Prune Missing Entries'), self
.remove_missing
378 self
.remove_missing_action
.setToolTip(
379 N_('Remove stale entries for repositories that no longer exist')
382 # pylint: disable=no-member
383 self
.model().itemChanged
.connect(self
.item_changed
)
385 self
.action_group
= utils
.Group(
387 self
.open_new_action
,
389 self
.launch_editor_action
,
390 self
.launch_terminal_action
,
391 self
.open_default_action
,
392 self
.rename_repo_action
,
395 self
.action_group
.setEnabled(True)
396 self
.set_default_repo_action
.setEnabled(True)
397 self
.clear_default_repo_action
.setEnabled(True)
399 def set_rename_enabled(self
, is_enabled
):
400 self
.rename_repo_action
.setEnabled(is_enabled
)
402 def set_view_mode(self
, view_mode
):
403 self
.current_mode
= view_mode
405 def selected_item(self
):
406 index
= self
.currentIndex()
407 return self
.model().itemFromIndex(index
)
410 self
.model().layoutChanged
.emit()
411 context
= self
.context
412 settings
= context
.settings
413 builder
= BuildItem(context
)
414 normalize
= display
.normalize_path
418 all_repos
= get_all_repos(self
.context
, settings
)
419 for repo
, is_bookmark
in all_repos
:
420 path
= normalize(repo
['path'])
421 name
= normalize(repo
['name'])
426 item
= builder
.get(path
, name
, self
.current_mode
, is_bookmark
)
429 self
.set_model(items
)
431 def contextMenuEvent(self
, event
):
432 """Configures prompt's context menu."""
433 item
= self
.selected_item()
435 if isinstance(item
, PromptWidgetItem
):
436 menu
= qtutils
.create_menu(N_('Actions'), self
)
437 menu
.addAction(self
.open_action
)
438 menu
.addAction(self
.open_new_action
)
439 menu
.addAction(self
.open_default_action
)
441 menu
.addAction(self
.copy_action
)
442 menu
.addAction(self
.launch_editor_action
)
443 menu
.addAction(self
.launch_terminal_action
)
445 if item
and item
.is_default
:
446 menu
.addAction(self
.clear_default_repo_action
)
448 menu
.addAction(self
.set_default_repo_action
)
449 menu
.addAction(self
.rename_repo_action
)
451 menu
.addAction(self
.delete_action
)
452 menu
.addAction(self
.remove_missing_action
)
453 menu
.exec_(self
.mapToGlobal(event
.pos()))
455 def item_changed(self
, item
):
456 self
.rename_entry(item
, item
.text())
458 def rename_entry(self
, item
, new_name
):
459 settings
= self
.context
.settings
461 rename
= settings
.rename_bookmark
463 rename
= settings
.rename_recent
465 if rename(item
.path
, item
.name
, new_name
):
469 item
.setText(item
.name
)
471 def apply_func(self
, func
, *args
, **kwargs
):
472 item
= self
.selected_item()
474 func(item
, *args
, **kwargs
)
477 self
.apply_func(lambda item
: qtutils
.set_clipboard(item
.path
))
479 def open_default(self
):
480 context
= self
.context
481 self
.apply_func(lambda item
: cmds
.do(cmds
.OpenDefaultApp
, context
, [item
.path
]))
483 def set_default_repo(self
):
484 self
.apply_func(self
.set_default_item
)
486 def set_default_item(self
, item
):
487 context
= self
.context
488 cmds
.do(cmds
.SetDefaultRepo
, context
, item
.path
)
491 def clear_default_repo(self
):
492 self
.apply_func(self
.clear_default_item
)
494 def clear_default_item(self
, _item
):
495 context
= self
.context
496 cmds
.do(cmds
.SetDefaultRepo
, context
, None)
499 def rename_repo(self
):
500 index
= self
.currentIndex()
503 def accept_repo(self
):
504 self
.apply_func(self
.accept_item
)
506 def accept_item(self
, _item
):
507 if self
.state() & self
.EditingState
:
508 current_index
= self
.currentIndex()
509 widget
= self
.indexWidget(current_index
)
511 self
.commitData(widget
)
512 self
.closePersistentEditor(current_index
)
515 self
.open_selected_repo()
517 def open_new_repo(self
):
518 context
= self
.context
519 self
.apply_func(lambda item
: cmds
.do(cmds
.OpenNewRepo
, context
, item
.path
))
521 def launch_editor(self
):
522 context
= self
.context
523 self
.apply_func(lambda item
: cmds
.do(cmds
.Edit
, context
, [item
.path
]))
525 def launch_terminal(self
):
526 context
= self
.context
527 self
.apply_func(lambda item
: cmds
.do(cmds
.LaunchTerminal
, context
, item
.path
))
529 def delete_item(self
):
530 """Remove the selected repo item
532 If the item comes from bookmarks (item.is_bookmark) then delete the item
533 from the Bookmarks list, otherwise delete it from the Recents list.
535 item
= self
.selected_item()
540 cmd
= cmds
.RemoveBookmark
542 cmd
= cmds
.RemoveRecent
543 context
= self
.context
544 ok
, _
, _
, _
= cmds
.do(cmd
, context
, item
.path
, item
.name
, icon
=icons
.discard())
548 def remove_missing(self
):
549 """Remove missing entries from the favorites/recent file list"""
550 settings
= self
.context
.settings
551 settings
.remove_missing_bookmarks()
552 settings
.remove_missing_recent()
556 class BuildItem(object):
557 def __init__(self
, context
):
558 self
.star_icon
= icons
.star()
559 self
.folder_icon
= icons
.folder()
561 self
.default_repo
= cfg
.get('cola.defaultrepo')
563 def get(self
, path
, name
, mode
, is_bookmark
):
564 is_default
= self
.default_repo
== path
566 icon
= self
.star_icon
568 icon
= self
.folder_icon
569 return PromptWidgetItem(path
, name
, mode
, icon
, is_default
, is_bookmark
)
572 class PromptWidgetItem(QtGui
.QStandardItem
):
573 def __init__(self
, path
, name
, mode
, icon
, is_default
, is_bookmark
):
574 QtGui
.QStandardItem
.__init
__(self
, icon
, name
)
578 self
.is_default
= is_default
579 self
.is_bookmark
= is_bookmark
580 editable
= mode
== ICON_MODE
582 if self
.mode
== ICON_MODE
:
583 item_text
= self
.name
585 item_text
= self
.path
587 user_role
= Qt
.UserRole
588 self
.setEditable(editable
)
589 self
.setData(path
, user_role
)
591 self
.setText(item_text
)
592 self
.setToolTip(path
)
596 """Construct a QSize from a single value"""
597 return QtCore
.QSize(size
, size
)