1
# -*- coding: utf-8 -*-
3
# QBzr - Qt frontend to Bazaar commands
4
# Copyright (C) 2011 QBzr Developers
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
from PyQt4 import QtCore, QtGui
27
Represent each group of guide bar.
29
:key: string key to identify this group
30
:color: color or marker
31
:data: marker positions, list of tuple (block index, num of blocks)
32
:index: index of the column to render this entry.
33
* Two or more groups can be rendered on same columns.
34
* If index == -1, the group is renderd on all columns.
36
__slots__ = ['key', 'color', 'data', 'index']
37
def __init__(self, key, color, index=0):
43
class PlainTextEditHelper(QtCore.QObject):
45
Helper class to encapsulate gap between QPlainTextEdit and QTextEdit
47
def __init__(self, edit):
48
QtCore.QObject.__init__(self)
49
if not isinstance(edit, QtGui.QPlainTextEdit):
50
raise ValueError('edit must be QPlainTextEdit')
53
self.connect(edit, QtCore.SIGNAL("updateRequest(const QRect&, int)"),
56
def onUpdateRequest(self, rect, dy):
57
self.emit(QtCore.SIGNAL("updateRequest()"))
59
def center_block(self, block):
61
scroll textarea as specified block locates to center
63
NOTE: This code is based on Qt source code (qplaintextedit.cpp)
66
height = edit.viewport().rect().height() / 2
67
h = self.edit.blockBoundingRect(block).center().y()
68
def iter_visible_block_backward(b):
71
if not b.isValid(): return
72
if b.isVisible(): yield b
73
for block in iter_visible_block_backward(block):
74
h += edit.blockBoundingRect(block).height()
77
edit.verticalScrollBar().setValue(block.firstLineNumber())
79
class TextEditHelper(QtCore.QObject):
81
Helper class to encapsulate gap between QPlainTextEdit and QTextEdit
83
def __init__(self, edit):
84
QtCore.QObject.__init__(self)
85
if not isinstance(edit, QtGui.QTextEdit):
86
raise ValueError('edit must be QTextEdit')
89
self.connect(edit.verticalScrollBar(), QtCore.SIGNAL("valueChanged(int)"),
90
self.onVerticalScroll)
92
def onVerticalScroll(self, value):
93
self.emit(QtCore.SIGNAL("updateRequest()"))
95
def center_block(self, block):
97
scroll textarea as specified block locates to center
99
y = block.layout().position().y()
100
vscroll = self.edit.verticalScrollBar()
101
vscroll.setValue(y - vscroll.pageStep() / 2)
103
def get_edit_helper(edit):
104
if isinstance(edit, QtGui.QPlainTextEdit):
105
return PlainTextEditHelper(edit)
106
if isinstance(edit, QtGui.QTextEdit):
107
return TextEditHelper(edit)
108
raise ValueError("edit is unsupported type.")
110
class GuideBar(QtGui.QWidget):
112
Vertical bar attached to TextEdit.
113
This shows that where changed or highlighted lines are.
115
Guide bar can have multiple columns.
117
def __init__(self, edit, base_width=10, parent=None):
119
:edit: target widget, must be QPlainTextEdit or QTextEdit
120
:base_width: width of each column.
122
QtGui.QWidget.__init__(self, parent)
123
self.base_width = base_width
125
self._helper = get_edit_helper(edit)
128
self.connect(edit, QtCore.SIGNAL("documentChangeFinished()"),
130
self.connect(edit.verticalScrollBar(), QtCore.SIGNAL("rangeChanged(int, int)"),
131
self.vscroll_rangeChanged)
133
self.connect(self._helper, QtCore.SIGNAL("updateRequest()"), self.update)
136
self.vscroll_visible = None
138
def add_entry(self, key, color, index=0):
142
entry = _Entry(key, color, index)
143
self.entries[key] = entry
145
def vscroll_rangeChanged(self, min, max):
146
vscroll_visible = (min < max)
147
if self.vscroll_visible != vscroll_visible:
148
self.vscroll_visible = vscroll_visible
154
Determine show or hide, and num of columns.
156
# Hide when vertical scrollbar is not shown.
157
if not self.vscroll_visible:
158
self.setVisible(False)
160
valid_entries = [e for e in self.entries.itervalues() if e.data]
161
if not valid_entries:
162
self.setVisible(False)
165
self.setVisible(True)
166
self.repeats = len(set([e.index for e in valid_entries if e.index >= 0]))
167
if self.repeats == 0:
169
self.setFixedWidth(self.repeats * self.base_width + 4)
171
self.block_count = self.edit.document().blockCount()
174
def update_data(self, **data):
176
Update each marker positions.
178
:arg_name: marker key
179
:value: list of marker positions.
180
Each position is tuple of (block index, num of blocks).
182
for key, value in data.iteritems():
183
self.entries[key].data[:] = value
186
def get_visible_block_range(self):
188
Return tuple of (index of first visible block, num of visible block)
190
pos = QtCore.QPoint(0, 1)
191
block = self.edit.cursorForPosition(pos).block()
192
first_visible_block = block.blockNumber()
194
y = self.edit.viewport().height()
196
pos = QtCore.QPoint(0, y)
197
block = self.edit.cursorForPosition(pos).block()
199
visible_blocks = block.blockNumber() - first_visible_block + 1
201
visible_blocks = self.block_count - first_visible_block + 1
203
return first_visible_block, visible_blocks
205
def paintEvent(self, event):
206
QtGui.QWidget.paintEvent(self, event)
207
painter = QtGui.QPainter(self)
208
painter.fillRect(event.rect(), QtCore.Qt.white)
209
if self.block_count == 0:
211
painter.setRenderHints(QtGui.QPainter.Antialiasing, True)
212
block_height = float(self.height()) / self.block_count
218
for e in sorted(self.entries.itervalues(),
219
key=lambda x:x.index if x.index >= 0 else 999):
223
x, width = 0, self.width()
225
if e.index != prev_index:
228
x, width = x_origin + index * self.base_width, self.base_width
229
for block_index, block_num in e.data:
230
y = block_index * block_height
231
height = max(1, block_num * block_height)
232
painter.fillRect(x, y, width, height, e.color)
234
# Draw scroll indicator.
235
x, width = 0, self.width()
236
first_block, visible_blocks = self.get_visible_block_range()
237
y, height = first_block * block_height, max(1, visible_blocks * block_height)
238
painter.fillRect(x, y, width, height, QtGui.QColor(0, 0, 0, 24))
240
def mousePressEvent(self, event):
241
QtGui.QWidget.mousePressEvent(self, event)
242
if event.button() == QtCore.Qt.LeftButton:
243
self.scroll_to_pos(event.y())
245
def mouseMoveEvent(self, event):
246
QtGui.QWidget.mouseMoveEvent(self, event)
247
self.scroll_to_pos(event.y())
249
def scroll_to_pos(self, y):
250
block_no = int(float(y) / self.height() * self.block_count)
251
block = self.edit.document().findBlockByNumber(block_no)
252
if not block.isValid():
254
self._helper.center_block(block)
256
class GuideBarPanel(QtGui.QWidget):
258
Composite widget of TextEdit and GuideBar
260
def __init__(self, edit, base_width=10, align=GBAR_RIGHT, parent=None):
261
QtGui.QWidget.__init__(self, parent)
262
hbox = QtGui.QHBoxLayout(self)
265
self.bar = GuideBar(edit, base_width=base_width, parent=parent)
267
if align == GBAR_RIGHT:
268
hbox.addWidget(self.edit)
269
hbox.addWidget(self.bar)
271
hbox.addWidget(self.bar)
272
hbox.addWidget(self.edit)
274
def add_entry(self, key, color, index=0):
275
self.bar.add_entry(key, color, index)
280
def update_data(self, **data):
281
return self.bar.update_data(**data)
283
def setup_guidebar_for_find(guidebar, find_toolbar, index=0):
285
Make guidebar enable to show positions that highlighted by FindToolBar
287
def on_highlight_changed():
288
if guidebar.edit in find_toolbar.text_edits:
289
guidebar.update_data(
290
find=[(n, 1) for n in guidebar.edit.highlight_lines]
292
guidebar.add_entry('find', QtGui.QColor(255, 196, 0), index) # Gold
293
guidebar.connect(find_toolbar, QtCore.SIGNAL("highlightChanged()"),
294
on_highlight_changed)