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_action(action
, callback
):
65 action
.connect(action
, SIGNAL('triggered()'), callback
)
68 def connect_action_bool(action
, callback
):
69 action
.connect(action
, SIGNAL('triggered(bool)'), callback
)
72 def connect_button(button
, callback
):
73 button
.connect(button
, SIGNAL('clicked()'), callback
)
76 def relay_button(button
, signal
):
77 connect_button(button
, SLOT(signal
))
80 def relay_signal(parent
, child
, signal
):
81 """Relay a signal from the child widget through the parent"""
82 def relay_slot(*args
, **opts
):
83 parent
.emit(signal
, *args
, **opts
)
84 parent
.connect(child
, signal
, relay_slot
)
89 return QtGui
.QApplication
.activeWindow()
92 def prompt(msg
, title
=None, text
=''):
93 """Presents the user with an input widget and returns the input."""
98 result
= QtGui
.QInputDialog
.getText(active_window(), msg
, title
, text
=text
)
99 return (unicode(result
[0]), result
[1])
102 def create_listwidget_item(text
, filename
):
103 """Creates a QListWidgetItem with text and the icon at filename."""
104 item
= QtGui
.QListWidgetItem()
105 item
.setIcon(QtGui
.QIcon(filename
))
110 def create_treewidget_item(text
, filename
):
111 """Creates a QTreeWidgetItem with text and the icon at filename."""
112 icon
= cached_icon_from_path(filename
)
113 item
= QtGui
.QTreeWidgetItem()
114 item
.setIcon(0, icon
)
115 item
.setText(0, text
)
120 def cached_icon_from_path(filename
):
121 return QtGui
.QIcon(filename
)
124 def confirm(title
, text
, informative_text
, ok_text
,
125 icon
=None, default
=True):
126 """Confirm that an action should take place"""
129 elif icon
and isinstance(icon
, basestring
):
130 icon
= QtGui
.QIcon(icon
)
131 msgbox
= QtGui
.QMessageBox(active_window())
132 msgbox
.setWindowTitle(tr(title
))
133 msgbox
.setText(tr(text
))
134 msgbox
.setInformativeText(tr(informative_text
))
135 ok
= msgbox
.addButton(tr(ok_text
), QtGui
.QMessageBox
.ActionRole
)
137 cancel
= msgbox
.addButton(QtGui
.QMessageBox
.Cancel
)
139 msgbox
.setDefaultButton(ok
)
141 msgbox
.setDefaultButton(cancel
)
143 return msgbox
.clickedButton() == ok
146 def critical(title
, message
=None, details
=None):
147 """Show a warning with the provided title and message."""
151 message
= tr(message
)
152 mbox
= QtGui
.QMessageBox(active_window())
153 mbox
.setWindowTitle(title
)
154 mbox
.setTextFormat(QtCore
.Qt
.PlainText
)
155 mbox
.setText(message
)
156 mbox
.setIcon(QtGui
.QMessageBox
.Critical
)
157 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
158 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
160 mbox
.setDetailedText(details
)
164 def information(title
, message
=None, details
=None, informative_text
=None):
165 """Show information with the provided title and message."""
169 message
= tr(message
)
170 mbox
= QtGui
.QMessageBox(active_window())
171 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
172 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
173 mbox
.setWindowTitle(title
)
174 mbox
.setWindowModality(QtCore
.Qt
.WindowModal
)
175 mbox
.setTextFormat(QtCore
.Qt
.PlainText
)
176 mbox
.setText(message
)
178 mbox
.setInformativeText(tr(informative_text
))
180 mbox
.setDetailedText(details
)
181 # Render git.svg into a 1-inch wide pixmap
182 pixmap
= QtGui
.QPixmap(resources
.icon('git.svg'))
183 xres
= pixmap
.physicalDpiX()
184 pixmap
= pixmap
.scaledToHeight(xres
, QtCore
.Qt
.SmoothTransformation
)
185 mbox
.setIconPixmap(pixmap
)
189 def question(title
, message
, default
=True):
190 """Launches a QMessageBox question with the provided title and message.
191 Passing "default=False" will make "No" the default choice."""
192 yes
= QtGui
.QMessageBox
.Yes
193 no
= QtGui
.QMessageBox
.No
201 result
= (QtGui
.QMessageBox
202 .question(active_window(), title
, msg
, buttons
, default
))
203 return result
== QtGui
.QMessageBox
.Yes
206 def register_for_signals():
207 # Register globally with the notifier
208 notifier
= cola
.notifier()
209 notifier
.connect(signals
.confirm
, confirm
)
210 notifier
.connect(signals
.critical
, critical
)
211 notifier
.connect(signals
.information
, information
)
212 notifier
.connect(signals
.question
, question
)
213 register_for_signals()
216 def selected_treeitem(tree_widget
):
217 """Returns a(id_number, is_selected) for a QTreeWidget."""
220 item
= tree_widget
.currentItem()
222 id_number
= item
.data(0, QtCore
.Qt
.UserRole
).toInt()[0]
224 return(id_number
, selected
)
227 def selected_row(list_widget
):
228 """Returns a(row_number, is_selected) tuple for a QListWidget."""
229 items
= list_widget
.selectedItems()
233 return (list_widget
.row(item
), True)
236 def selection_list(listwidget
, items
):
237 """Returns an array of model items that correspond to
238 the selected QListWidget indices."""
240 itemcount
= listwidget
.count()
241 widgetitems
= [ listwidget
.item(idx
) for idx
in range(itemcount
) ]
243 for item
, widgetitem
in zip(items
, widgetitems
):
244 if widgetitem
.isSelected():
245 selected
.append(item
)
249 def tree_selection(treeitem
, items
):
250 """Returns model items that correspond to selected widget indices"""
251 itemcount
= treeitem
.childCount()
252 widgetitems
= [ treeitem
.child(idx
) for idx
in range(itemcount
) ]
254 for item
, widgetitem
in zip(items
[:len(widgetitems
)], widgetitems
):
255 if widgetitem
.isSelected():
256 selected
.append(item
)
261 def selected_item(list_widget
, items
):
262 """Returns the selected item in a QListWidget."""
263 widget_items
= list_widget
.selectedItems()
266 widget_item
= widget_items
[0]
267 row
= list_widget
.row(widget_item
)
274 def open_dialog(title
, filename
=None):
275 """Creates an Open File dialog and returns a filename."""
277 return unicode(QtGui
.QFileDialog
278 .getOpenFileName(active_window(), title_tr
, filename
))
281 def opendir_dialog(title
, path
):
282 """Prompts for a directory path"""
284 flags
= (QtGui
.QFileDialog
.ShowDirsOnly |
285 QtGui
.QFileDialog
.DontResolveSymlinks
)
287 return unicode(QtGui
.QFileDialog
288 .getExistingDirectory(active_window(),
289 title_tr
, path
, flags
))
292 def save_as(filename
, title
='Save As...'):
293 """Creates a Save File dialog and returns a filename."""
295 return unicode(QtGui
.QFileDialog
296 .getSaveFileName(active_window(), title_tr
, filename
))
300 """Given a basename returns a QIcon from the corresponding cola icon."""
301 return QtGui
.QIcon(resources
.icon(basename
))
304 def set_clipboard(text
):
305 """Sets the copy/paste buffer to text."""
308 clipboard
= QtGui
.QApplication
.instance().clipboard()
309 clipboard
.setText(text
, QtGui
.QClipboard
.Clipboard
)
310 clipboard
.setText(text
, QtGui
.QClipboard
.Selection
)
313 def add_action(widget
, text
, fn
, *shortcuts
):
314 action
= QtGui
.QAction(text
, widget
)
315 connect_action(action
, fn
)
317 shortcuts
= list(set(shortcuts
))
318 action
.setShortcuts(shortcuts
)
319 action
.setShortcutContext(Qt
.WidgetWithChildrenShortcut
)
320 widget
.addAction(action
)
324 def set_selected_item(widget
, idx
):
325 """Sets a the currently selected item to the item at index idx."""
326 if type(widget
) is QtGui
.QTreeWidget
:
327 item
= widget
.topLevelItem(idx
)
329 widget
.setItemSelected(item
, True)
330 widget
.setCurrentItem(item
)
333 def add_items(widget
, items
):
334 """Adds items to a widget."""
339 def set_items(widget
, items
):
340 """Clear the existing widget contents and set the new items."""
342 add_items(widget
, items
)
346 """Translate a string into a local language."""
347 if type(txt
) is QtCore
.QString
:
348 # This has already been translated; leave as-is
350 return unicode(QtGui
.QApplication
.instance().translate('', txt
))
353 def icon_file(filename
, staged
=False, untracked
=False):
354 """Returns a file path representing a corresponding file path."""
356 if os
.path
.exists(core
.encode(filename
)):
357 ifile
= resources
.icon('staged.png')
359 ifile
= resources
.icon('removed.png')
361 ifile
= resources
.icon('untracked.png')
363 ifile
= utils
.file_icon(core
.encode(filename
))
367 def icon_for_file(filename
, staged
=False, untracked
=False):
368 """Returns a QIcon for a particular file path."""
369 ifile
= icon_file(filename
, staged
=staged
, untracked
=untracked
)
373 def create_treeitem(filename
, staged
=False, untracked
=False, check
=True):
374 """Given a filename, return a QListWidgetItem suitable
375 for adding to a QListWidget. "staged" and "untracked"
376 controls whether to use the appropriate icons."""
378 ifile
= icon_file(filename
, staged
=staged
, untracked
=untracked
)
380 ifile
= resources
.icon('staged.png')
381 return create_treewidget_item(filename
, ifile
)
384 def update_file_icons(widget
, items
, staged
=True,
385 untracked
=False, offset
=0):
386 """Populate a QListWidget with custom icon items."""
387 for idx
, model_item
in enumerate(items
):
388 item
= widget
.item(idx
+offset
)
390 item
.setIcon(icon_for_file(model_item
, staged
, untracked
))
393 def cached_icon(key
):
394 """Maintain a cache of standard icons and return cache entries."""
395 style
= QtGui
.QApplication
.instance().style()
396 return style
.standardIcon(key
)
400 """Return a standard icon for a directory."""
401 return cached_icon(QtGui
.QStyle
.SP_DirIcon
)
405 """Return a standard icon for a file."""
406 return cached_icon(QtGui
.QStyle
.SP_FileIcon
)
410 """Return a standard Apply icon"""
411 return cached_icon(QtGui
.QStyle
.SP_DialogApplyButton
)
415 """Return a standard Save icon"""
416 return cached_icon(QtGui
.QStyle
.SP_DialogSaveButton
)
420 """Return a standard Ok icon"""
421 return cached_icon(QtGui
.QStyle
.SP_DialogOkButton
)
425 """Return a standard open directory icon"""
426 return cached_icon(QtGui
.QStyle
.SP_DirOpenIcon
)
430 """Return a standard open directory icon"""
431 return icon('options.svg')
434 def dir_close_icon():
435 """Return a standard closed directory icon"""
436 return cached_icon(QtGui
.QStyle
.SP_DirClosedIcon
)
439 def titlebar_close_icon():
440 """Return a dock widget close icon"""
441 return cached_icon(QtGui
.QStyle
.SP_TitleBarCloseButton
)
444 def titlebar_normal_icon():
445 """Return a dock widget close icon"""
446 return cached_icon(QtGui
.QStyle
.SP_TitleBarNormalButton
)
450 return icon('git.svg')
454 """Returna standard Refresh icon"""
455 return cached_icon(QtGui
.QStyle
.SP_BrowserReload
)
459 """Return a standard Discard icon"""
460 return cached_icon(QtGui
.QStyle
.SP_DialogDiscardButton
)
464 """Return a standard Close icon"""
465 return cached_icon(QtGui
.QStyle
.SP_DialogCloseButton
)
468 def add_close_action(widget
):
469 """Adds close action and shortcuts to a widget."""
470 return add_action(widget
, 'Close...',
471 widget
.close
, QtGui
.QKeySequence
.Close
, 'Ctrl+Q')
474 def center_on_screen(widget
):
475 """Move widget to the center of the default screen"""
476 desktop
= QtGui
.QApplication
.instance().desktop()
477 rect
= desktop
.screenGeometry(QtGui
.QCursor().pos())
480 widget
.move(cx
- widget
.width()/2, cy
- widget
.height()/2)
483 def save_state(widget
):
484 if gitcfg
.instance().get('cola.savewindowsettings', True):
485 settings
.Settings().save_gui_state(widget
)
488 def export_window_state(widget
, state
, version
):
489 # Save the window state
490 windowstate
= widget
.saveState(version
)
491 state
['windowstate'] = unicode(windowstate
.toBase64().data())
495 def apply_window_state(widget
, state
, version
):
496 # Restore the dockwidget, etc. window state
498 windowstate
= state
['windowstate']
499 widget
.restoreState(QtCore
.QByteArray
.fromBase64(str(windowstate
)),
505 def apply_state(widget
):
506 state
= settings
.Settings().get_gui_state(widget
)
507 widget
.apply_state(state
)
512 def theme_icon(name
):
513 """Grab an icon from the current theme with a fallback
515 Support older versions of Qt by catching AttributeError and
516 falling back to our default icons.
520 base
, ext
= os
.path
.splitext(name
)
521 qicon
= QtGui
.QIcon
.fromTheme(base
)
522 if not qicon
.isNull():
524 except AttributeError:
529 def default_monospace_font():
532 if utils
.is_darwin():
534 font
.setFamily(family
)