1 from functools
import partial
4 from qtpy
.QtCore
import Qt
5 from qtpy
import QtWidgets
11 from . import standard
12 from .toolbarcmds
import COMMANDS
16 'Others': ['Others::LaunchEditor', 'Others::RevertUnstagedEdits'],
21 'File::OpenRepoNewWindow',
24 'File::RecentModified',
27 'File::ExportPatches',
35 'Actions::CherryPick',
37 'Actions::AbortMerge',
38 'Actions::UpdateSubmodules',
45 'Commit::UndoLastCommit',
46 'Commit::StageModified',
47 'Commit::StageUntracked',
50 'Commit::LoadCommitMessage',
51 'Commit::GetCommitMessageTemplate',
53 'Diff': ['Diff::Difftool', 'Diff::Expression', 'Diff::Branches', 'Diff::Diffstat'],
59 'Branch::DeleteRemote',
61 'Branch::BrowseCurrent',
62 'Branch::BrowseOther',
63 'Branch::VisualizeCurrent',
64 'Branch::VisualizeAll',
67 'Commit::UndoLastCommit',
70 'Actions::ResetMixed',
71 'Actions::RestoreWorktree',
75 'View': ['View::DAG', 'View::FileBrowser'],
78 # Backwards-compatibility: Commit::StageUntracked was previously
79 # exposed as Commit::StageUntracked.
81 'Commit::StageAll': 'Commit::StageUntracked',
85 def configure(toolbar
, parent
=None):
86 """Launches the Toolbar configure dialog"""
88 parent
= qtutils
.active_window()
89 view
= ToolbarView(toolbar
, parent
)
94 def get_toolbars(widget
):
95 return widget
.findChildren(ToolBar
)
98 def add_toolbar(context
, widget
):
99 toolbars
= get_toolbars(widget
)
100 name
= 'ToolBar%d' % (len(toolbars
) + 1)
101 toolbar
= ToolBar
.create(context
, name
)
102 widget
.addToolBar(toolbar
)
107 """export_state() and apply_state() providers for toolbars"""
109 def __init__(self
, context
, widget
):
110 """widget must be a QMainWindow for toolBarArea(), etc."""
111 self
.context
= context
114 def apply_state(self
, toolbars
):
115 context
= self
.context
118 for data
in toolbars
:
119 toolbar
= ToolBar
.create(context
, data
['name'])
120 toolbar
.load_items(data
['items'])
122 toolbar
.set_toolbar_style(data
['toolbar_style'])
124 # Maintain compatibility for toolbars created in git-cola <= 3.11.0
125 if data
['show_icons']:
126 data
['toolbar_style'] = ToolBar
.STYLE_FOLLOW_SYSTEM
127 toolbar
.set_toolbar_style(ToolBar
.STYLE_FOLLOW_SYSTEM
)
129 data
['toolbar_style'] = ToolBar
.STYLE_TEXT_ONLY
130 toolbar
.set_toolbar_style(ToolBar
.STYLE_TEXT_ONLY
)
131 toolbar
.setVisible(data
['visible'])
133 toolbar_area
= decode_toolbar_area(data
['area'])
135 widget
.addToolBarBreak(toolbar_area
)
136 widget
.addToolBar(toolbar_area
, toolbar
)
138 # floating toolbars must be set after added
140 toolbar
.setWindowFlags(Qt
.Tool | Qt
.FramelessWindowHint
)
141 toolbar
.move(data
['x'], data
['y'])
143 def export_state(self
):
146 toolbars
= widget
.findChildren(ToolBar
)
148 for toolbar
in toolbars
:
149 toolbar_area
= widget
.toolBarArea(toolbar
)
150 if toolbar_area
== Qt
.NoToolBarArea
:
151 continue # filter out removed toolbars
152 items
= [x
.data() for x
in toolbar
.actions()]
153 # show_icons is for backwards compatibility with git-cola <= 3.11.0
154 show_icons
= toolbar
.toolbar_style() != ToolBar
.STYLE_TEXT_ONLY
156 'name': toolbar
.windowTitle(),
157 'area': encode_toolbar_area(toolbar_area
),
158 'break': widget
.toolBarBreak(toolbar
),
159 'float': toolbar
.isFloating(),
160 'x': toolbar
.pos().x(),
161 'y': toolbar
.pos().y(),
162 'width': toolbar
.width(),
163 'height': toolbar
.height(),
164 'show_icons': show_icons
,
165 'toolbar_style': toolbar
.toolbar_style(),
166 'visible': toolbar
.isVisible(),
173 class ToolBar(QtWidgets
.QToolBar
):
174 SEPARATOR
= 'Separator'
175 STYLE_FOLLOW_SYSTEM
= 'follow-system'
176 STYLE_ICON_ONLY
= 'icon'
177 STYLE_TEXT_ONLY
= 'text'
178 STYLE_TEXT_BESIDE_ICON
= 'text-beside-icon'
179 STYLE_TEXT_UNDER_ICON
= 'text-under-icon'
181 N_('Follow System Style'),
184 N_('Text Beside Icon'),
185 N_('Text Under Icon'),
191 STYLE_TEXT_BESIDE_ICON
,
192 STYLE_TEXT_UNDER_ICON
,
196 def create(context
, name
):
197 return ToolBar(context
, name
, TREE_LAYOUT
, COMMANDS
)
199 def __init__(self
, context
, title
, tree_layout
, toolbar_commands
):
200 QtWidgets
.QToolBar
.__init
__(self
)
201 self
.setWindowTitle(title
)
202 self
.setObjectName(title
)
203 self
.setToolButtonStyle(Qt
.ToolButtonFollowStyle
)
205 self
.context
= context
206 self
.tree_layout
= tree_layout
207 self
.commands
= toolbar_commands
209 def set_toolbar_style(self
, style_id
):
211 self
.STYLE_FOLLOW_SYSTEM
: Qt
.ToolButtonFollowStyle
,
212 self
.STYLE_ICON_ONLY
: Qt
.ToolButtonIconOnly
,
213 self
.STYLE_TEXT_ONLY
: Qt
.ToolButtonTextOnly
,
214 self
.STYLE_TEXT_BESIDE_ICON
: Qt
.ToolButtonTextBesideIcon
,
215 self
.STYLE_TEXT_UNDER_ICON
: Qt
.ToolButtonTextUnderIcon
,
217 default
= Qt
.ToolButtonFollowStyle
218 return self
.setToolButtonStyle(style_to_qt
.get(style_id
, default
))
220 def toolbar_style(self
):
222 Qt
.ToolButtonFollowStyle
: self
.STYLE_FOLLOW_SYSTEM
,
223 Qt
.ToolButtonIconOnly
: self
.STYLE_ICON_ONLY
,
224 Qt
.ToolButtonTextOnly
: self
.STYLE_TEXT_ONLY
,
225 Qt
.ToolButtonTextBesideIcon
: self
.STYLE_TEXT_BESIDE_ICON
,
226 Qt
.ToolButtonTextUnderIcon
: self
.STYLE_TEXT_UNDER_ICON
,
228 default
= self
.STYLE_FOLLOW_SYSTEM
229 return qt_to_style
.get(self
.toolButtonStyle(), default
)
231 def load_items(self
, items
):
233 self
.add_action_from_data(data
)
235 def add_action_from_data(self
, data
):
236 parent
= data
['parent']
237 child
= data
['child']
238 child
= RENAMED
.get(child
, child
)
240 if child
== self
.SEPARATOR
:
241 toolbar_action
= self
.addSeparator()
242 toolbar_action
.setData(data
)
245 tree_items
= self
.tree_layout
.get(parent
, [])
246 if child
in tree_items
and child
in self
.commands
:
247 command
= self
.commands
[child
]
248 title
= N_(command
['title'])
249 callback
= partial(command
['action'], self
.context
)
251 tooltip
= command
.get('tooltip', None)
253 command_icon
= command
.get('icon', None)
255 icon
= getattr(icons
, command_icon
, None)
259 toolbar_action
= self
.addAction(icon
, title
, callback
)
261 toolbar_action
= self
.addAction(title
, callback
)
263 toolbar_action
.setData(data
)
265 tooltip
= command
.get('tooltip', None)
267 toolbar_action
.setToolTip(f
'{title}\n{tooltip}')
269 def delete_toolbar(self
):
270 self
.parent().removeToolBar(self
)
272 def contextMenuEvent(self
, event
):
273 menu
= QtWidgets
.QMenu()
274 tool_config
= menu
.addAction(N_('Configure Toolbar'), partial(configure
, self
))
275 tool_config
.setIcon(icons
.configure())
276 tool_delete
= menu
.addAction(N_('Delete Toolbar'), self
.delete_toolbar
)
277 tool_delete
.setIcon(icons
.remove())
279 menu
.exec_(event
.globalPos())
282 def encode_toolbar_area(toolbar_area
):
283 """Encode a Qt::ToolBarArea as a string"""
286 Qt
.LeftToolBarArea
: 'left',
287 Qt
.RightToolBarArea
: 'right',
288 Qt
.TopToolBarArea
: 'top',
289 Qt
.BottomToolBarArea
: 'bottom',
290 }.get(toolbar_area
, default
)
293 def decode_toolbar_area(string
):
294 """Decode an encoded toolbar area string into a Qt::ToolBarArea"""
295 default
= Qt
.BottomToolBarArea
297 'left': Qt
.LeftToolBarArea
,
298 'right': Qt
.RightToolBarArea
,
299 'top': Qt
.TopToolBarArea
,
300 'bottom': Qt
.BottomToolBarArea
,
301 }.get(string
, default
)
304 class ToolbarView(standard
.Dialog
):
305 """Provides the git-cola 'ToolBar' configure dialog"""
307 SEPARATOR_TEXT
= '----------------------------'
309 def __init__(self
, toolbar
, parent
=None):
310 standard
.Dialog
.__init
__(self
, parent
)
311 self
.setWindowTitle(N_('Configure Toolbar'))
313 self
.toolbar
= toolbar
314 self
.left_list
= ToolbarTreeWidget(self
)
315 self
.right_list
= DraggableListWidget(self
)
316 self
.text_toolbar_name
= QtWidgets
.QLabel()
317 self
.text_toolbar_name
.setText(N_('Name'))
318 self
.toolbar_name
= QtWidgets
.QLineEdit()
319 self
.toolbar_name
.setText(toolbar
.windowTitle())
320 self
.add_separator
= qtutils
.create_button(N_('Add Separator'))
321 self
.remove_item
= qtutils
.create_button(N_('Remove Element'))
322 self
.toolbar_style_label
= QtWidgets
.QLabel(N_('Toolbar Style:'))
323 self
.toolbar_style
= QtWidgets
.QComboBox()
324 for style_name
in ToolBar
.STYLE_NAMES
:
325 self
.toolbar_style
.addItem(style_name
)
326 style_idx
= get_index_from_style(toolbar
.toolbar_style())
327 self
.toolbar_style
.setCurrentIndex(style_idx
)
328 self
.apply_button
= qtutils
.ok_button(N_('Apply'))
329 self
.close_button
= qtutils
.close_button()
330 self
.close_button
.setDefault(True)
332 self
.right_actions
= qtutils
.hbox(
333 defs
.no_margin
, defs
.spacing
, self
.add_separator
, self
.remove_item
335 self
.name_layout
= qtutils
.hbox(
336 defs
.no_margin
, defs
.spacing
, self
.text_toolbar_name
, self
.toolbar_name
338 self
.left_layout
= qtutils
.vbox(defs
.no_margin
, defs
.spacing
, self
.left_list
)
339 self
.right_layout
= qtutils
.vbox(
340 defs
.no_margin
, defs
.spacing
, self
.right_list
, self
.right_actions
342 self
.top_layout
= qtutils
.hbox(
343 defs
.no_margin
, defs
.spacing
, self
.left_layout
, self
.right_layout
345 self
.actions_layout
= qtutils
.hbox(
348 self
.toolbar_style_label
,
354 self
.main_layout
= qtutils
.vbox(
361 self
.setLayout(self
.main_layout
)
363 qtutils
.connect_button(self
.add_separator
, self
.add_separator_action
)
364 qtutils
.connect_button(self
.remove_item
, self
.remove_item_action
)
365 qtutils
.connect_button(self
.apply_button
, self
.apply_action
)
366 qtutils
.connect_button(self
.close_button
, self
.accept
)
368 self
.load_right_items()
369 self
.load_left_items()
371 self
.init_size(parent
=parent
)
373 def load_right_items(self
):
374 commands
= self
.toolbar
.commands
375 for action
in self
.toolbar
.actions():
378 command_name
= data
['child']
381 command_name
= RENAMED
.get(command_name
, command_name
)
382 if command_name
== self
.toolbar
.SEPARATOR
:
383 self
.add_separator_action()
386 command
= commands
[command_name
]
389 title
= command
['title']
390 icon
= command
.get('icon', None)
391 tooltip
= command
.get('tooltip', None)
392 self
.right_list
.add_item(title
, tooltip
, data
, icon
)
394 def load_left_items(self
):
395 commands
= self
.toolbar
.commands
396 for parent
in self
.toolbar
.tree_layout
:
397 top
= self
.left_list
.insert_top(parent
)
398 for item
in self
.toolbar
.tree_layout
[parent
]:
399 item
= RENAMED
.get(item
, item
)
401 command
= commands
[item
]
404 icon
= command
.get('icon', None)
405 tooltip
= command
.get('tooltip', None)
406 child
= create_child(parent
, item
, command
['title'], tooltip
, icon
)
409 top
.sortChildren(0, Qt
.AscendingOrder
)
411 def add_separator_action(self
):
412 data
= {'parent': None, 'child': self
.toolbar
.SEPARATOR
}
413 self
.right_list
.add_separator(self
.SEPARATOR_TEXT
, data
)
415 def remove_item_action(self
):
416 items
= self
.right_list
.selectedItems()
419 self
.right_list
.takeItem(self
.right_list
.row(item
))
421 def apply_action(self
):
423 style
= get_style_from_index(self
.toolbar_style
.currentIndex())
424 self
.toolbar
.set_toolbar_style(style
)
425 self
.toolbar
.setWindowTitle(self
.toolbar_name
.text())
427 for item
in self
.right_list
.get_items():
428 data
= item
.data(Qt
.UserRole
)
429 self
.toolbar
.add_action_from_data(data
)
432 def get_style_from_index(index
):
433 """Return the symbolic toolbar style name for the given (combobox) index"""
434 return ToolBar
.STYLE_SYMBOLS
[index
]
437 def get_index_from_style(style
):
438 """Return the toolbar style (combobox) index for the symbolic name"""
439 return ToolBar
.STYLE_SYMBOLS
.index(style
)
442 class DraggableListMixin
:
445 def __init__(self
, widget
, Base
):
449 widget
.setAcceptDrops(True)
450 widget
.setSelectionMode(widget
.SingleSelection
)
451 widget
.setDragEnabled(True)
452 widget
.setDropIndicatorShown(True)
454 def dragEnterEvent(self
, event
):
456 self
.Base
.dragEnterEvent(widget
, event
)
458 def dragMoveEvent(self
, event
):
460 self
.Base
.dragMoveEvent(widget
, event
)
462 def dragLeaveEvent(self
, event
):
464 self
.Base
.dragLeaveEvent(widget
, event
)
466 def dropEvent(self
, event
):
468 event
.setDropAction(Qt
.MoveAction
)
469 self
.Base
.dropEvent(widget
, event
)
474 items
= [base
.item(widget
, i
) for i
in range(base
.count(widget
))]
479 class DraggableListWidget(QtWidgets
.QListWidget
):
480 Mixin
= DraggableListMixin
482 def __init__(self
, parent
=None):
483 QtWidgets
.QListWidget
.__init
__(self
, parent
)
485 self
.setAcceptDrops(True)
486 self
.setSelectionMode(self
.SingleSelection
)
487 self
.setDragEnabled(True)
488 self
.setDropIndicatorShown(True)
490 self
._mixin
= self
.Mixin(self
, QtWidgets
.QListWidget
)
492 def dragEnterEvent(self
, event
):
493 return self
._mixin
.dragEnterEvent(event
)
495 def dragMoveEvent(self
, event
):
496 return self
._mixin
.dragMoveEvent(event
)
498 def dropEvent(self
, event
):
499 return self
._mixin
.dropEvent(event
)
501 def add_separator(self
, title
, data
):
502 item
= QtWidgets
.QListWidgetItem()
504 item
.setData(Qt
.UserRole
, data
)
508 def add_item(self
, title
, tooltip
, data
, icon
):
509 item
= QtWidgets
.QListWidgetItem()
510 item
.setText(N_(title
))
511 item
.setData(Qt
.UserRole
, data
)
513 item
.setToolTip(tooltip
)
516 icon_func
= getattr(icons
, icon
)
517 item
.setIcon(icon_func())
522 return self
._mixin
.get_items()
525 class ToolbarTreeWidget(standard
.TreeView
):
526 def __init__(self
, parent
):
527 standard
.TreeView
.__init
__(self
, parent
)
529 self
.setDragEnabled(True)
530 self
.setDragDropMode(QtWidgets
.QAbstractItemView
.DragOnly
)
531 self
.setSelectionMode(QtWidgets
.QAbstractItemView
.SingleSelection
)
532 self
.setDropIndicatorShown(True)
533 self
.setRootIsDecorated(True)
534 self
.setHeaderHidden(True)
535 self
.setAlternatingRowColors(False)
536 self
.setSortingEnabled(False)
538 self
.setModel(QtGui
.QStandardItemModel())
540 def insert_top(self
, title
):
541 item
= create_item(title
, title
)
542 item
.setFlags(Qt
.ItemIsSelectable | Qt
.ItemIsEnabled
)
544 self
.model().insertRow(0, item
)
550 def create_child(parent
, child
, title
, tooltip
, icon
):
551 data
= {'parent': parent
, 'child': child
}
552 item
= create_item(title
, data
)
554 item
.setToolTip(tooltip
)
556 icon_func
= getattr(icons
, icon
, None)
557 item
.setIcon(icon_func())
562 def create_item(name
, data
):
563 item
= QtGui
.QStandardItem()
565 item
.setEditable(False)
566 item
.setDragEnabled(True)
567 item
.setText(N_(name
))
568 item
.setData(data
, Qt
.UserRole
)