1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous Qt utility functions.
6 from PyQt4
import QtGui
7 from PyQt4
import QtCore
8 from PyQt4
.QtCore
import Qt
9 from PyQt4
.QtCore
import SIGNAL
13 from cola
import gitcfg
14 from cola
import utils
15 from cola
import settings
16 from cola
import signals
17 from cola
import resources
18 from cola
.compat
import set
19 from cola
.decorators
import memoize
20 from cola
.widgets
.log
import LogView
26 cola
.notifier().connect(signals
.log_cmd
, logview
.log
)
30 def log(status
, output
):
31 """Sends messages to the log window.
35 cola
.notifier().broadcast(signals
.log_cmd
, status
, output
)
38 def emit(widget
, signal
, *args
, **opts
):
39 """Return a function that emits a signal"""
40 def emitter(*local_args
, **local_opts
):
42 widget
.emit(SIGNAL(signal
), *args
, **opts
)
44 widget
.emit(SIGNAL(signal
), *local_args
, **local_opts
)
48 def SLOT(signal
, *args
, **opts
):
50 Returns a callback that broadcasts a message over the notifier.
52 If the caller of SLOT() provides args or opts then those are
53 used instead of the ones provided by the invoker of the callback.
56 def broadcast(*local_args
, **local_opts
):
58 cola
.notifier().broadcast(signal
, *args
, **opts
)
60 cola
.notifier().broadcast(signal
, *local_args
, **local_opts
)
64 def connect_button(button
, callback
):
65 button
.connect(button
, SIGNAL('clicked()'), callback
)
68 def relay_button(button
, signal
):
69 connect_button(button
, SLOT(signal
))
72 def relay_signal(parent
, child
, signal
):
73 """Relay a signal from the child widget through the parent"""
74 def relay_slot(*args
, **opts
):
75 parent
.emit(signal
, *args
, **opts
)
76 parent
.connect(child
, signal
, relay_slot
)
81 return QtGui
.QApplication
.activeWindow()
84 def prompt(msg
, title
=None, text
=None):
85 """Presents the user with an input widget and returns the input."""
90 result
= QtGui
.QInputDialog
.getText(active_window(), msg
, title
, text
=text
)
91 return (unicode(result
[0]), result
[1])
94 def create_listwidget_item(text
, filename
):
95 """Creates a QListWidgetItem with text and the icon at filename."""
96 item
= QtGui
.QListWidgetItem()
97 item
.setIcon(QtGui
.QIcon(filename
))
102 def create_treewidget_item(text
, filename
):
103 """Creates a QTreeWidgetItem with text and the icon at filename."""
104 icon
= cached_icon_from_path(filename
)
105 item
= QtGui
.QTreeWidgetItem()
106 item
.setIcon(0, icon
)
107 item
.setText(0, text
)
112 def cached_icon_from_path(filename
):
113 return QtGui
.QIcon(filename
)
116 def confirm(title
, text
, informative_text
, ok_text
,
117 icon
=None, default
=True):
118 """Confirm that an action should take place"""
121 elif icon
and isinstance(icon
, basestring
):
122 icon
= QtGui
.QIcon(icon
)
123 msgbox
= QtGui
.QMessageBox(active_window())
124 msgbox
.setWindowTitle(tr(title
))
125 msgbox
.setText(tr(text
))
126 msgbox
.setInformativeText(tr(informative_text
))
127 ok
= msgbox
.addButton(tr(ok_text
), QtGui
.QMessageBox
.ActionRole
)
129 cancel
= msgbox
.addButton(QtGui
.QMessageBox
.Cancel
)
131 msgbox
.setDefaultButton(ok
)
133 msgbox
.setDefaultButton(cancel
)
135 return msgbox
.clickedButton() == ok
138 def critical(title
, message
=None, details
=None):
139 """Show a warning with the provided title and message."""
143 message
= tr(message
)
144 mbox
= QtGui
.QMessageBox(active_window())
145 mbox
.setWindowTitle(title
)
146 mbox
.setTextFormat(QtCore
.Qt
.PlainText
)
147 mbox
.setText(message
)
148 mbox
.setIcon(QtGui
.QMessageBox
.Critical
)
149 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
150 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
152 mbox
.setDetailedText(details
)
156 def information(title
, message
=None, details
=None, informative_text
=None):
157 """Show information with the provided title and message."""
161 message
= tr(message
)
162 mbox
= QtGui
.QMessageBox(active_window())
163 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
164 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
165 mbox
.setWindowTitle(title
)
166 mbox
.setWindowModality(QtCore
.Qt
.WindowModal
)
167 mbox
.setTextFormat(QtCore
.Qt
.PlainText
)
168 mbox
.setText(message
)
170 mbox
.setInformativeText(tr(informative_text
))
172 mbox
.setDetailedText(details
)
173 # Render git.svg into a 1-inch wide pixmap
174 pixmap
= QtGui
.QPixmap(resources
.icon('git.svg'))
175 xres
= pixmap
.physicalDpiX()
176 pixmap
= pixmap
.scaledToHeight(xres
, QtCore
.Qt
.SmoothTransformation
)
177 mbox
.setIconPixmap(pixmap
)
181 def question(title
, message
, default
=True):
182 """Launches a QMessageBox question with the provided title and message.
183 Passing "default=False" will make "No" the default choice."""
184 yes
= QtGui
.QMessageBox
.Yes
185 no
= QtGui
.QMessageBox
.No
193 result
= (QtGui
.QMessageBox
194 .question(active_window(), title
, msg
, buttons
, default
))
195 return result
== QtGui
.QMessageBox
.Yes
198 def register_for_signals():
199 # Register globally with the notifier
200 notifier
= cola
.notifier()
201 notifier
.connect(signals
.confirm
, confirm
)
202 notifier
.connect(signals
.critical
, critical
)
203 notifier
.connect(signals
.information
, information
)
204 notifier
.connect(signals
.question
, question
)
205 register_for_signals()
208 def selected_treeitem(tree_widget
):
209 """Returns a(id_number, is_selected) for a QTreeWidget."""
212 item
= tree_widget
.currentItem()
214 id_number
= item
.data(0, QtCore
.Qt
.UserRole
).toInt()[0]
216 return(id_number
, selected
)
219 def selected_row(list_widget
):
220 """Returns a(row_number, is_selected) tuple for a QListWidget."""
221 items
= list_widget
.selectedItems()
225 return (list_widget
.row(item
), True)
228 def selection_list(listwidget
, items
):
229 """Returns an array of model items that correspond to
230 the selected QListWidget indices."""
232 itemcount
= listwidget
.count()
233 widgetitems
= [ listwidget
.item(idx
) for idx
in range(itemcount
) ]
235 for item
, widgetitem
in zip(items
, widgetitems
):
236 if widgetitem
.isSelected():
237 selected
.append(item
)
241 def tree_selection(treeitem
, items
):
242 """Returns model items that correspond to selected widget indices"""
243 itemcount
= treeitem
.childCount()
244 widgetitems
= [ treeitem
.child(idx
) for idx
in range(itemcount
) ]
246 for item
, widgetitem
in zip(items
[:len(widgetitems
)], widgetitems
):
247 if widgetitem
.isSelected():
248 selected
.append(item
)
253 def selected_item(list_widget
, items
):
254 """Returns the selected item in a QListWidget."""
255 widget_items
= list_widget
.selectedItems()
258 widget_item
= widget_items
[0]
259 row
= list_widget
.row(widget_item
)
266 def open_dialog(title
, filename
=None):
267 """Creates an Open File dialog and returns a filename."""
269 return unicode(QtGui
.QFileDialog
270 .getOpenFileName(active_window(), title_tr
, filename
))
273 def opendir_dialog(title
, path
):
274 """Prompts for a directory path"""
276 flags
= (QtGui
.QFileDialog
.ShowDirsOnly |
277 QtGui
.QFileDialog
.DontResolveSymlinks
)
279 return unicode(QtGui
.QFileDialog
280 .getExistingDirectory(active_window(),
281 title_tr
, path
, flags
))
284 def save_as(filename
, title
='Save As...'):
285 """Creates a Save File dialog and returns a filename."""
287 return unicode(QtGui
.QFileDialog
288 .getSaveFileName(active_window(), title_tr
, filename
))
292 """Given a basename returns a QIcon from the corresponding cola icon."""
293 return QtGui
.QIcon(resources
.icon(basename
))
296 def set_clipboard(text
):
297 """Sets the copy/paste buffer to text."""
300 clipboard
= QtGui
.QApplication
.instance().clipboard()
301 clipboard
.setText(text
, QtGui
.QClipboard
.Clipboard
)
302 clipboard
.setText(text
, QtGui
.QClipboard
.Selection
)
305 def add_action(widget
, text
, fn
, *shortcuts
):
306 action
= QtGui
.QAction(text
, widget
)
307 action
.connect(action
, SIGNAL('triggered()'), fn
)
309 shortcuts
= list(set(shortcuts
))
310 action
.setShortcuts(shortcuts
)
311 action
.setShortcutContext(Qt
.WidgetWithChildrenShortcut
)
312 widget
.addAction(action
)
316 def set_selected_item(widget
, idx
):
317 """Sets a the currently selected item to the item at index idx."""
318 if type(widget
) is QtGui
.QTreeWidget
:
319 item
= widget
.topLevelItem(idx
)
321 widget
.setItemSelected(item
, True)
322 widget
.setCurrentItem(item
)
325 def add_items(widget
, items
):
326 """Adds items to a widget."""
331 def set_items(widget
, items
):
332 """Clear the existing widget contents and set the new items."""
334 add_items(widget
, items
)
338 """Translate a string into a local language."""
339 if type(txt
) is QtCore
.QString
:
340 # This has already been translated; leave as-is
342 return unicode(QtGui
.QApplication
.instance().translate('', txt
))
345 def icon_file(filename
, staged
=False, untracked
=False):
346 """Returns a file path representing a corresponding file path."""
348 if os
.path
.exists(core
.encode(filename
)):
349 ifile
= resources
.icon('staged.png')
351 ifile
= resources
.icon('removed.png')
353 ifile
= resources
.icon('untracked.png')
355 ifile
= utils
.file_icon(core
.encode(filename
))
359 def icon_for_file(filename
, staged
=False, untracked
=False):
360 """Returns a QIcon for a particular file path."""
361 ifile
= icon_file(filename
, staged
=staged
, untracked
=untracked
)
365 def create_treeitem(filename
, staged
=False, untracked
=False, check
=True):
366 """Given a filename, return a QListWidgetItem suitable
367 for adding to a QListWidget. "staged" and "untracked"
368 controls whether to use the appropriate icons."""
370 ifile
= icon_file(filename
, staged
=staged
, untracked
=untracked
)
372 ifile
= resources
.icon('staged.png')
373 return create_treewidget_item(filename
, ifile
)
376 def update_file_icons(widget
, items
, staged
=True,
377 untracked
=False, offset
=0):
378 """Populate a QListWidget with custom icon items."""
379 for idx
, model_item
in enumerate(items
):
380 item
= widget
.item(idx
+offset
)
382 item
.setIcon(icon_for_file(model_item
, staged
, untracked
))
384 def set_listwidget_strings(widget
, items
):
385 """Sets a list widget to the strings passed in items."""
387 add_items(widget
, [ QtGui
.QListWidgetItem(i
) for i
in items
])
390 def cached_icon(key
):
391 """Maintain a cache of standard icons and return cache entries."""
392 style
= QtGui
.QApplication
.instance().style()
393 return style
.standardIcon(key
)
397 """Return a standard icon for a directory."""
398 return cached_icon(QtGui
.QStyle
.SP_DirIcon
)
402 """Return a standard icon for a file."""
403 return cached_icon(QtGui
.QStyle
.SP_FileIcon
)
407 """Return a standard Apply icon"""
408 return cached_icon(QtGui
.QStyle
.SP_DialogApplyButton
)
412 """Return a standard Save icon"""
413 return cached_icon(QtGui
.QStyle
.SP_DialogSaveButton
)
417 """Return a standard Ok icon"""
418 return cached_icon(QtGui
.QStyle
.SP_DialogOkButton
)
422 """Return a standard Save icon"""
423 return cached_icon(QtGui
.QStyle
.SP_DirOpenIcon
)
427 return icon('git.svg')
431 """Returna standard Refresh icon"""
432 return cached_icon(QtGui
.QStyle
.SP_BrowserReload
)
436 """Return a standard Discard icon"""
437 return cached_icon(QtGui
.QStyle
.SP_DialogDiscardButton
)
441 """Return a standard Close icon"""
442 return cached_icon(QtGui
.QStyle
.SP_DialogCloseButton
)
445 def add_close_action(widget
):
446 """Adds close action and shortcuts to a widget."""
447 return add_action(widget
, 'Close...',
448 widget
.close
, QtGui
.QKeySequence
.Close
, 'Ctrl+Q')
451 def center_on_screen(widget
):
452 """Move widget to the center of the default screen"""
453 desktop
= QtGui
.QApplication
.instance().desktop()
454 rect
= desktop
.screenGeometry(QtGui
.QCursor().pos())
457 widget
.move(cx
- widget
.width()/2, cy
- widget
.height()/2)
460 def save_state(widget
):
461 if gitcfg
.instance().get('cola.savewindowsettings', True):
462 settings
.Settings().save_gui_state(widget
)
465 def export_window_state(widget
, state
, version
):
466 # Save the window state
467 windowstate
= widget
.saveState(version
)
468 state
['windowstate'] = unicode(windowstate
.toBase64().data())
472 def apply_window_state(widget
, state
, version
):
473 # Restore the dockwidget, etc. window state
475 windowstate
= state
['windowstate']
476 widget
.restoreState(QtCore
.QByteArray
.fromBase64(str(windowstate
)),
482 def apply_state(widget
):
483 state
= settings
.Settings().get_gui_state(widget
)
484 widget
.apply_state(state
)
489 def theme_icon(name
):
490 """Grab an icon from the current theme with a fallback
492 Support older versions of Qt by catching AttributeError and
493 falling back to our default icons.
497 base
, ext
= os
.path
.splitext(name
)
498 qicon
= QtGui
.QIcon
.fromTheme(base
)
499 if not qicon
.isNull():
501 except AttributeError: