test/tmp: Add .gitignore so that this directory always exists
[git-cola.git] / cola / qtutils.py
blob0ee6c6a593743a4a9a7a39bb05addea330d29db8
1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous Qt utility functions.
3 """
4 import os
6 from PyQt4 import QtGui
7 from PyQt4 import QtCore
8 from PyQt4.QtCore import SIGNAL
10 import cola
11 from cola import core
12 from cola import utils
13 from cola import signals
14 from cola import resources
15 from cola.decorators import memoize
16 import cola.views.log
19 @memoize
20 def logger():
21 logview = cola.views.log.LogView()
22 cola.notifier().connect(signals.log_cmd, logview.log)
23 return logview
26 def log(status, output):
27 """Sends messages to the log window.
28 """
29 if not output:
30 return
31 cola.notifier().broadcast(signals.log_cmd, status, output)
34 def SLOT(signal, *args, **opts):
35 """
36 Returns a callback that broadcasts a message over the notifier.
38 If the caller of SLOT() provides args or opts then those are
39 used instead of the ones provided by the invoker of the callback.
41 """
42 def broadcast(*local_args, **local_opts):
43 if args or opts:
44 cola.notifier().broadcast(signal, *args, **opts)
45 else:
46 cola.notifier().broadcast(signal, *local_args, **local_opts)
47 return broadcast
50 def prompt(msg, title=None):
51 """Presents the user with an input widget and returns the input."""
52 if title is None:
53 title = msg
54 msg = tr(msg)
55 title = tr(title)
56 parent = QtGui.QApplication.instance().activeWindow()
57 result = QtGui.QInputDialog.getText(parent, msg, title)
58 return (unicode(result[0]), result[1])
61 def create_listwidget_item(text, filename):
62 """Creates a QListWidgetItem with text and the icon at filename."""
63 item = QtGui.QListWidgetItem()
64 item.setIcon(QtGui.QIcon(filename))
65 item.setText(text)
66 return item
69 def create_treewidget_item(text, filename):
70 """Creates a QTreeWidgetItem with text and the icon at filename."""
71 icon = cached_icon_from_path(filename)
72 item = QtGui.QTreeWidgetItem()
73 item.setIcon(0, icon)
74 item.setText(0, text)
75 return item
78 @memoize
79 def cached_icon_from_path(filename):
80 return QtGui.QIcon(filename)
83 def information(title, message=None):
84 """Launches a QMessageBox information with the
85 provided title and message."""
86 if message is None:
87 message = title
88 title = tr(title)
89 message = tr(message)
90 parent = QtGui.QApplication.instance().activeWindow()
91 QtGui.QMessageBox.information(parent, title, message)
93 # Register globally with the notifier
94 cola.notifier().connect(signals.information, information)
97 def selected_treeitem(tree_widget):
98 """Returns a(id_number, is_selected) for a QTreeWidget."""
99 id_number = None
100 selected = False
101 item = tree_widget.currentItem()
102 if item:
103 id_number = item.data(0, QtCore.Qt.UserRole).toInt()[0]
104 selected = True
105 return(id_number, selected)
107 def selected_row(list_widget):
108 """Returns a(row_number, is_selected) tuple for a QListWidget."""
109 row = list_widget.currentRow()
110 item = list_widget.item(row)
111 selected = item is not None and item.isSelected()
112 return(row, selected)
114 def selection_list(listwidget, items):
115 """Returns an array of model items that correspond to
116 the selected QListWidget indices."""
117 selected = []
118 itemcount = listwidget.count()
119 widgetitems = [ listwidget.item(idx) for idx in range(itemcount) ]
121 for item, widgetitem in zip(items, widgetitems):
122 if widgetitem.isSelected():
123 selected.append(item)
124 return selected
127 def tree_selection(treeitem, items):
128 """Returns model items that correspond to selected widget indices"""
129 itemcount = treeitem.childCount()
130 widgetitems = [ treeitem.child(idx) for idx in range(itemcount) ]
131 selected = []
132 for item, widgetitem in zip(items[:len(widgetitems)], widgetitems):
133 if widgetitem.isSelected():
134 selected.append(item)
136 return selected
139 def selected_item(list_widget, items):
140 """Returns the selected item in a QListWidget."""
141 row, selected = selected_row(list_widget)
142 if selected and row < len(items):
143 return items[row]
144 else:
145 return None
148 def open_dialog(parent, title, filename=None):
149 """Creates an Open File dialog and returns a filename."""
150 title_tr = tr(title)
151 return unicode(QtGui.QFileDialog
152 .getOpenFileName(parent, title_tr, filename))
155 def opendir_dialog(parent, title, path):
156 """Prompts for a directory path"""
158 flags = (QtGui.QFileDialog.ShowDirsOnly |
159 QtGui.QFileDialog.DontResolveSymlinks)
160 title_tr = tr(title)
161 qstr = (QtGui.QFileDialog
162 .getExistingDirectory(parent, title_tr, path, flags))
163 return unicode(qstr)
166 def save_dialog(parent, title, filename=''):
167 """Creates a Save File dialog and returns a filename."""
168 title_tr = parent.tr(title)
169 return unicode(QtGui.QFileDialog
170 .getSaveFileName(parent, title_tr, filename))
173 def icon(basename):
174 """Given a basename returns a QIcon from the corresponding cola icon."""
175 return QtGui.QIcon(resources.icon(basename))
178 def question(parent, title, message, default=True):
179 """Launches a QMessageBox question with the provided title and message.
180 Passing "default=False" will make "No" the default choice."""
181 yes = QtGui.QMessageBox.Yes
182 no = QtGui.QMessageBox.No
183 buttons = yes | no
184 if default:
185 default = yes
186 else:
187 default = no
188 title = tr(title)
189 message = tr(message)
190 result = QtGui.QMessageBox.question(parent, title, message,
191 buttons, default)
192 return result == QtGui.QMessageBox.Yes
195 def set_clipboard(text):
196 """Sets the copy/paste buffer to text."""
197 if not text:
198 return
199 clipboard = QtGui.QApplication.instance().clipboard()
200 clipboard.setText(text, QtGui.QClipboard.Clipboard)
201 clipboard.setText(text, QtGui.QClipboard.Selection)
204 def set_selected_item(widget, idx):
205 """Sets a the currently selected item to the item at index idx."""
206 if type(widget) is QtGui.QTreeWidget:
207 item = widget.topLevelItem(idx)
208 if item:
209 widget.setItemSelected(item, True)
210 widget.setCurrentItem(item)
213 def add_items(widget, items):
214 """Adds items to a widget."""
215 for item in items:
216 widget.addItem(item)
219 def set_items(widget, items):
220 """Clear the existing widget contents and set the new items."""
221 widget.clear()
222 add_items(widget, items)
225 @memoize
226 def tr(txt):
227 """Translate a string into a local language."""
228 if type(txt) is QtCore.QString:
229 # This has already been translated; leave as-is
230 return unicode(txt)
231 return unicode(QtGui.QApplication.instance().translate('', txt))
234 def icon_file(filename, staged=False, untracked=False):
235 """Returns a file path representing a corresponding file path."""
236 if staged:
237 if os.path.exists(core.encode(filename)):
238 ifile = resources.icon('staged.png')
239 else:
240 ifile = resources.icon('removed.png')
241 elif untracked:
242 ifile = resources.icon('untracked.png')
243 else:
244 ifile = utils.file_icon(filename)
245 return ifile
248 def icon_for_file(filename, staged=False, untracked=False):
249 """Returns a QIcon for a particular file path."""
250 ifile = icon_file(filename, staged=staged, untracked=untracked)
251 return icon(ifile)
254 def create_treeitem(filename, staged=False, untracked=False, check=True):
255 """Given a filename, return a QListWidgetItem suitable
256 for adding to a QListWidget. "staged" and "untracked"
257 controls whether to use the appropriate icons."""
258 if check:
259 ifile = icon_file(filename, staged=staged, untracked=untracked)
260 else:
261 ifile = resources.icon('staged.png')
262 return create_treewidget_item(filename, ifile)
265 def update_file_icons(widget, items, staged=True,
266 untracked=False, offset=0):
267 """Populate a QListWidget with custom icon items."""
268 for idx, model_item in enumerate(items):
269 item = widget.item(idx+offset)
270 if item:
271 item.setIcon(icon_for_file(model_item, staged, untracked))
273 def set_listwidget_strings(widget, items):
274 """Sets a list widget to the strings passed in items."""
275 widget.clear()
276 add_items(widget, [ QtGui.QListWidgetItem(i) for i in items ])
278 @memoize
279 def cached_icon(key):
280 """Maintain a cache of standard icons and return cache entries."""
281 style = QtGui.QApplication.instance().style()
282 return style.standardIcon(key)
285 def dir_icon():
286 """Return a standard icon for a directory."""
287 return cached_icon(QtGui.QStyle.SP_DirIcon)
290 def file_icon():
291 """Return a standard icon for a file."""
292 return cached_icon(QtGui.QStyle.SP_FileIcon)
295 def diff_font():
296 """Return the diff font string."""
297 qfont = QtGui.QFont()
298 font = cola.model().cola_config('fontdiff')
299 if font and qfont.fromString(font):
300 return qfont
301 family = 'Monospace'
302 if cola.utils.is_darwin():
303 family = 'Monaco'
304 qfont.setFamily(family)
305 font = unicode(qfont.toString())
306 # TODO this might not be the best place for the default
307 cola.model().set_diff_font(font)
308 return qfont
311 def set_diff_font(widget):
312 """Updates the diff font based on the configured value."""
313 qfont = diff_font()
314 block = widget.signalsBlocked()
315 widget.blockSignals(True)
316 if isinstance(widget, QtGui.QFontComboBox):
317 widget.setCurrentFont(qfont)
318 else:
319 widget.setFont(qfont)
320 widget.blockSignals(block)
323 def add_close_action(widget):
324 """Adds a Ctrl+w close action to a widget."""
325 action = QtGui.QAction(widget.tr('Close...'), widget)
326 action.setShortcut('Ctrl+w')
327 widget.addAction(action)
328 widget.connect(action, SIGNAL('triggered()'), widget.close)
331 def center_on_screen(widget):
332 """Move widget to the center of the default screen"""
333 desktop = QtGui.QApplication.instance().desktop()
334 rect = desktop.screenGeometry(QtGui.QCursor().pos())
335 cy = rect.height()/2
336 cx = rect.width()/2
337 widget.move(cx - widget.width()/2, cy - widget.height()/2)