3 from PyQt4
import QtGui
4 from PyQt4
import QtCore
5 from PyQt4
.QtCore
import Qt
6 from PyQt4
.QtCore
import SIGNAL
7 from PyQt4
.QtGui
import QFont
8 from PyQt4
.QtGui
import QSyntaxHighlighter
9 from PyQt4
.QtGui
import QTextCharFormat
10 from PyQt4
.QtGui
import QColor
12 from cola
import utils
13 from cola
import qtutils
14 from cola
.qtutils
import tr
15 from cola
.widgets
import completion
16 from cola
.widgets
import defs
19 def create_button(text
='', layout
=None, tooltip
=None, icon
=None):
20 """Create a button, set its title, and add it to the parent."""
21 button
= QtGui
.QPushButton()
23 button
.setText(tr(text
))
26 if tooltip
is not None:
27 button
.setToolTip(tooltip
)
28 if layout
is not None:
29 layout
.addWidget(button
)
33 class DockTitleBarWidget(QtGui
.QWidget
):
34 def __init__(self
, parent
, title
):
35 QtGui
.QWidget
.__init
__(self
, parent
)
36 self
.label
= label
= QtGui
.QLabel()
38 font
.setCapitalization(QtGui
.QFont
.SmallCaps
)
42 self
.close_button
= QtGui
.QPushButton()
43 self
.close_button
.setFlat(True)
44 self
.close_button
.setFixedSize(QtCore
.QSize(16, 16))
45 self
.close_button
.setIcon(qtutils
.titlebar_close_icon())
47 self
.toggle_button
= QtGui
.QPushButton()
48 self
.toggle_button
.setFlat(True)
49 self
.toggle_button
.setFixedSize(QtCore
.QSize(16, 16))
50 self
.toggle_button
.setIcon(qtutils
.titlebar_normal_icon())
52 self
.corner_layout
= QtGui
.QHBoxLayout()
53 self
.corner_layout
.setMargin(0)
54 self
.corner_layout
.setSpacing(defs
.spacing
)
56 layout
= QtGui
.QHBoxLayout()
58 layout
.setSpacing(defs
.spacing
)
59 layout
.addWidget(label
)
61 layout
.addLayout(self
.corner_layout
)
62 layout
.addWidget(self
.toggle_button
)
63 layout
.addWidget(self
.close_button
)
64 self
.setLayout(layout
)
66 self
.connect(self
.toggle_button
, SIGNAL('clicked()'),
69 self
.connect(self
.close_button
, SIGNAL('clicked()'),
70 self
.parent().toggleViewAction().trigger
)
72 def toggle_floating(self
):
73 self
.parent().setFloating(not self
.parent().isFloating())
75 def set_title(self
, title
):
76 self
.label
.setText(title
)
78 def add_corner_widget(self
, widget
):
79 self
.corner_layout
.addWidget(widget
)
82 def create_dock(title
, parent
):
83 """Create a dock widget and set it up accordingly."""
84 dock
= QtGui
.QDockWidget(parent
)
85 dock
.setWindowTitle(tr(title
))
86 dock
.setObjectName(title
)
87 titlebar
= DockTitleBarWidget(dock
, title
)
88 dock
.setTitleBarWidget(titlebar
)
92 def create_menu(title
, parent
):
93 """Create a menu and set its title."""
94 qmenu
= QtGui
.QMenu(parent
)
95 qmenu
.setTitle(tr(title
))
99 def create_toolbutton(text
=None, layout
=None, tooltip
=None, icon
=None):
100 button
= QtGui
.QToolButton()
101 button
.setAutoRaise(True)
102 button
.setAutoFillBackground(True)
106 button
.setText(tr(text
))
107 button
.setToolButtonStyle(Qt
.ToolButtonTextBesideIcon
)
109 button
.setToolTip(tr(tooltip
))
110 if layout
is not None:
111 layout
.addWidget(button
)
115 class QFlowLayoutWidget(QtGui
.QWidget
):
117 _horizontal
= QtGui
.QBoxLayout
.LeftToRight
118 _vertical
= QtGui
.QBoxLayout
.TopToBottom
120 def __init__(self
, parent
):
121 QtGui
.QWidget
.__init
__(self
, parent
)
122 self
._direction
= self
._vertical
123 self
._layout
= layout
= QtGui
.QBoxLayout(self
._direction
)
124 layout
.setSpacing(defs
.spacing
)
125 layout
.setMargin(defs
.margin
)
126 self
.setLayout(layout
)
127 policy
= QtGui
.QSizePolicy(QtGui
.QSizePolicy
.Minimum
,
128 QtGui
.QSizePolicy
.Minimum
)
129 self
.setSizePolicy(policy
)
130 self
.setMinimumSize(QtCore
.QSize(1, 1))
132 def resizeEvent(self
, event
):
134 if size
.width() * 0.8 < size
.height():
137 dxn
= self
._horizontal
139 if dxn
!= self
._direction
:
140 self
._direction
= dxn
141 self
.layout().setDirection(dxn
)
144 class ExpandableGroupBox(QtGui
.QGroupBox
):
145 def __init__(self
, parent
=None):
146 QtGui
.QGroupBox
.__init
__(self
, parent
)
149 self
.click_pos
= None
150 self
.arrow_icon_size
= 16
152 def set_expanded(self
, expanded
):
153 if expanded
== self
.expanded
:
154 self
.emit(SIGNAL('expanded(bool)'), expanded
)
156 self
.expanded
= expanded
157 for widget
in self
.findChildren(QtGui
.QWidget
):
158 widget
.setHidden(not expanded
)
159 self
.emit(SIGNAL('expanded(bool)'), expanded
)
161 def mousePressEvent(self
, event
):
162 if event
.button() == Qt
.LeftButton
:
163 option
= QtGui
.QStyleOptionGroupBox()
164 self
.initStyleOption(option
)
165 icon_size
= self
.arrow_icon_size
166 button_area
= QtCore
.QRect(0, 0, icon_size
, icon_size
)
167 offset
= self
.arrow_icon_size
+ defs
.spacing
168 adjusted
= option
.rect
.adjusted(0, 0, -offset
, 0)
169 top_left
= adjusted
.topLeft()
170 button_area
.moveTopLeft(QtCore
.QPoint(top_left
))
171 self
.click_pos
= event
.pos()
172 QtGui
.QGroupBox
.mousePressEvent(self
, event
)
174 def mouseReleaseEvent(self
, event
):
175 if (event
.button() == Qt
.LeftButton
and
176 self
.click_pos
== event
.pos()):
177 self
.set_expanded(not self
.expanded
)
178 QtGui
.QGroupBox
.mouseReleaseEvent(self
, event
)
180 def paintEvent(self
, event
):
181 painter
= QtGui
.QStylePainter(self
)
182 option
= QtGui
.QStyleOptionGroupBox()
183 self
.initStyleOption(option
)
185 painter
.translate(self
.arrow_icon_size
+ defs
.spacing
, 0)
186 painter
.drawText(option
.rect
, Qt
.AlignLeft
, self
.title())
190 point
= option
.rect
.adjusted(0, -4, 0, 0).topLeft()
191 icon_size
= self
.arrow_icon_size
192 option
.rect
= QtCore
.QRect(point
.x(), point
.y(), icon_size
, icon_size
)
194 painter
.drawPrimitive(style
.PE_IndicatorArrowDown
, option
)
196 painter
.drawPrimitive(style
.PE_IndicatorArrowRight
, option
)
199 class GitRefDialog(QtGui
.QDialog
):
200 def __init__(self
, title
, button_text
, parent
):
201 super(GitRefDialog
, self
).__init
__(parent
)
202 self
.setWindowTitle(title
)
204 self
.label
= QtGui
.QLabel()
205 self
.label
.setText(title
)
207 self
.lineedit
= completion
.GitRefLineEdit(self
)
208 self
.setFocusProxy(self
.lineedit
)
210 self
.ok_button
= QtGui
.QPushButton()
211 self
.ok_button
.setText(self
.tr(button_text
))
212 self
.ok_button
.setIcon(qtutils
.apply_icon())
214 self
.close_button
= QtGui
.QPushButton()
215 self
.close_button
.setText(self
.tr('Close'))
217 self
.button_layout
= QtGui
.QHBoxLayout()
218 self
.button_layout
.setMargin(0)
219 self
.button_layout
.setSpacing(defs
.button_spacing
)
220 self
.button_layout
.addStretch()
221 self
.button_layout
.addWidget(self
.ok_button
)
222 self
.button_layout
.addWidget(self
.close_button
)
224 self
.main_layout
= QtGui
.QVBoxLayout()
225 self
.main_layout
.setMargin(defs
.margin
)
226 self
.main_layout
.setSpacing(defs
.spacing
)
228 self
.main_layout
.addWidget(self
.label
)
229 self
.main_layout
.addWidget(self
.lineedit
)
230 self
.main_layout
.addLayout(self
.button_layout
)
231 self
.setLayout(self
.main_layout
)
233 qtutils
.connect_button(self
.ok_button
, self
.accept
)
234 qtutils
.connect_button(self
.close_button
, self
.reject
)
236 self
.connect(self
.lineedit
, SIGNAL('textChanged(QString)'),
239 self
.setWindowModality(Qt
.WindowModal
)
240 self
.ok_button
.setEnabled(False)
243 return unicode(self
.lineedit
.text())
245 def text_changed(self
, txt
):
246 self
.ok_button
.setEnabled(bool(self
.text()))
248 def set_text(self
, ref
):
249 self
.lineedit
.setText(ref
)
252 def ref(title
, button_text
, parent
, default
=None):
253 dlg
= GitRefDialog(title
, button_text
, parent
)
255 dlg
.set_text(default
)
259 if dlg
.exec_() == GitRefDialog
.Accepted
:
264 # Syntax highlighting
266 def TERMINAL(pattern
):
268 Denotes that a pattern is the final pattern that should
269 be matched. If this pattern matches no other formats
270 will be applied, even if they would have matched.
272 return '__TERMINAL__:%s' % pattern
274 # Cache the results of re.compile so that we don't keep
275 # rebuilding the same regexes whenever stylesheets change
278 def rgba(r
, g
, b
, a
=255):
285 'color_text': rgba(0x00, 0x00, 0x00),
286 'color_add': rgba(0xcd, 0xff, 0xe0),
287 'color_remove': rgba(0xff, 0xd0, 0xd0),
288 'color_header': rgba(0xbb, 0xbb, 0xbb),
292 class GenericSyntaxHighligher(QSyntaxHighlighter
):
293 def __init__(self
, doc
, *args
, **kwargs
):
294 QSyntaxHighlighter
.__init
__(self
, doc
)
295 for attr
, val
in default_colors
.items():
296 setattr(self
, attr
, val
)
298 self
.generate_rules()
300 def generate_rules(self
):
303 def create_rules(self
, *rules
):
305 raise Exception('create_rules requires an even '
306 'number of arguments.')
307 for idx
, rule
in enumerate(rules
):
310 formats
= rules
[idx
+1]
311 terminal
= rule
.startswith(TERMINAL(''))
313 rule
= rule
[len(TERMINAL('')):]
315 regex
= _RGX_CACHE
[rule
]
317 regex
= _RGX_CACHE
[rule
] = re
.compile(rule
)
318 self
._rules
.append((regex
, formats
, terminal
,))
320 def formats(self
, line
):
322 for regex
, fmts
, terminal
in self
._rules
:
323 match
= regex
.match(line
)
326 matched
.append([match
, fmts
])
331 def mkformat(self
, fg
=None, bg
=None, bold
=False):
332 fmt
= QTextCharFormat()
334 fmt
.setForeground(fg
)
336 fmt
.setBackground(bg
)
338 fmt
.setFontWeight(QFont
.Bold
)
341 def highlightBlock(self
, qstr
):
342 ascii
= unicode(qstr
)
345 formats
= self
.formats(ascii
)
348 for match
, fmts
in formats
:
349 start
= match
.start()
350 groups
= match
.groups()
352 # No groups in the regex, assume this is a single rule
353 # that spans the entire line
355 self
.setFormat(0, len(ascii
), fmts
)
358 # Groups exist, rule is a tuple corresponding to group
359 for grpidx
, group
in enumerate(groups
):
360 # allow empty matches
363 # allow None as a no-op format
366 self
.setFormat(start
, start
+length
,
370 def set_colors(self
, colordict
):
371 for attr
, val
in colordict
.items():
372 setattr(self
, attr
, val
)
375 class DiffSyntaxHighlighter(GenericSyntaxHighligher
):
376 """Implements the diff syntax highlighting
378 This class is used by widgets that display diffs.
381 def __init__(self
, doc
, whitespace
=True):
382 self
.whitespace
= whitespace
383 GenericSyntaxHighligher
.__init
__(self
, doc
)
385 def generate_rules(self
):
386 diff_head
= self
.mkformat(fg
=self
.color_header
)
387 diff_head_bold
= self
.mkformat(fg
=self
.color_header
, bold
=True)
389 diff_add
= self
.mkformat(fg
=self
.color_text
, bg
=self
.color_add
)
390 diff_remove
= self
.mkformat(fg
=self
.color_text
, bg
=self
.color_remove
)
393 bad_ws
= self
.mkformat(fg
=Qt
.black
, bg
=Qt
.red
)
395 # We specify the whitespace rule last so that it is
396 # applied after the diff addition/removal rules.
397 # The rules for the header
398 diff_old_rgx
= TERMINAL(r
'^--- ')
399 diff_new_rgx
= TERMINAL(r
'^\+\+\+ ')
400 diff_ctx_rgx
= TERMINAL(r
'^@@ ')
402 diff_hd1_rgx
= TERMINAL(r
'^diff --git a/.*b/.*')
403 diff_hd2_rgx
= TERMINAL(r
'^index \S+\.\.\S+')
404 diff_hd3_rgx
= TERMINAL(r
'^new file mode')
405 diff_hd4_rgx
= TERMINAL(r
'^deleted file mode')
406 diff_add_rgx
= TERMINAL(r
'^\+')
407 diff_rmv_rgx
= TERMINAL(r
'^-')
408 diff_bar_rgx
= TERMINAL(r
'^([ ]+.*)(\|[ ]+\d+[ ]+[+-]+)$')
409 diff_sts_rgx
= (r
'(.+\|.+?)(\d+)(.+?)([\+]*?)([-]*?)$')
410 diff_sum_rgx
= (r
'(\s+\d+ files changed[^\d]*)'
411 r
'(:?\d+ insertions[^\d]*)'
412 r
'(:?\d+ deletions.*)$')
414 self
.create_rules(diff_old_rgx
, diff_head
,
415 diff_new_rgx
, diff_head
,
416 diff_ctx_rgx
, diff_head_bold
,
417 diff_bar_rgx
, (diff_head_bold
, diff_head
),
418 diff_hd1_rgx
, diff_head
,
419 diff_hd2_rgx
, diff_head
,
420 diff_hd3_rgx
, diff_head
,
421 diff_hd4_rgx
, diff_head
,
422 diff_add_rgx
, diff_add
,
423 diff_rmv_rgx
, diff_remove
,
424 diff_sts_rgx
, (None, diff_head
,
427 diff_sum_rgx
, (diff_head
,
431 self
.create_rules('(..*?)(\s+)$', (None, bad_ws
))
434 if __name__
== '__main__':
436 class SyntaxTestDialog(QtGui
.QDialog
):
437 def __init__(self
, parent
):
438 QtGui
.QDialog
.__init
__(self
, parent
)
439 self
.resize(720, 512)
440 self
.vboxlayout
= QtGui
.QVBoxLayout(self
)
441 self
.vboxlayout
.setObjectName('vboxlayout')
442 self
.output_text
= QtGui
.QTextEdit(self
)
444 if utils
.is_darwin():
448 font
.setFamily(family
)
449 font
.setPointSize(12)
450 self
.output_text
.setFont(font
)
451 self
.output_text
.setAcceptDrops(False)
452 self
.vboxlayout
.addWidget(self
.output_text
)
453 self
.syntax
= DiffSyntaxHighlighter(self
.output_text
.document())
455 app
= QtGui
.QApplication(sys
.argv
)
456 dialog
= SyntaxTestDialog(qtutils
.active_window())