Merge lp:~hid-iwata/qbzr/textedit-guidebar into lp:qbzr
- textedit-guidebar
- Merge into trunk2a
Status: | Merged |
---|---|
Merged at revision: | 1454 |
Proposed branch: | lp:~hid-iwata/qbzr/textedit-guidebar |
Merge into: | lp:qbzr |
Diff against target: |
846 lines (+455/-22) 8 files modified
NEWS.txt (+4/-0) lib/annotate.py (+46/-5) lib/diffview.py (+79/-8) lib/diffwindow.py (+5/-1) lib/widgets/shelve.py (+15/-2) lib/widgets/shelvelist.py (+1/-1) lib/widgets/texteditaccessory.py (+294/-0) lib/widgets/toolbars.py (+11/-5) |
To merge this branch: | bzr merge lp:~hid-iwata/qbzr/textedit-guidebar |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
QBzr Developers | Pending | ||
Review via email: mp+91126@code.launchpad.net |
Commit message
Description of the change
Add change-marker bar to qdiff, qannotate, qshelve and qunshelve.
[qdiff] Show positions of added, removed or modified lines.
http://
[qannotate] Show positions of lines changed by selected revisions.
http://
[ALL] Additionally, when searching words, show positions of matched words.
http://
Alexander Belchenko (bialix) wrote : | # |
Martin Packman (gz) wrote : | # |
This looks really neat. I wonder if you could find a way to add some test coverage here? I've found it's not actually as hard to do in pyqt as it sometimes seems.
Alexander Belchenko (bialix) wrote : | # |
Martin Packman пишет:
> This looks really neat. I wonder if you could find a way to add some test coverage here? I've found it's not actually as hard to do in pyqt as it sometimes seems.
I agree with Martin. Hidetaka, if you can, please add some tests for new
features. In the mean time I'll merge it in the current state to start
testing it now.
Thanks again!
--
All the dude wanted was his rug back
- 1458. By IWATA Hidetaka
-
Render small marks more exactly.
- 1459. By IWATA Hidetaka
-
Fix : Guide bar of qunshelve does not show matched word positions
IWATA Hidetaka (hid-iwata) wrote : | # |
OK, I'll write some test.
Now, I've pushed some revisions, please remerge them.
* Fix: guide bar of qunshelve does not show search highlight positions.
* Improve accuracy of 1-dot-height mark positions.
Alexander Belchenko (bialix) wrote : | # |
IWATA Hidetaka пишет:
> OK, I'll write some test.
>
> Now, I've pushed some revisions, please remerge them.
> * Fix: guide bar of qunshelve does not show search highlight positions.
> * Improve accuracy of 1-dot-height mark positions.
Hidetaka, I'm sure you have write permissions not only because you're in
~qbzr-dev group, but also because you're so good contributor. Please,
land it if you think it has important improvements.
Thanks!
--
All the dude wanted was his rug back
Preview Diff
1 | === modified file 'NEWS.txt' |
2 | --- NEWS.txt 2012-01-30 15:46:28 +0000 |
3 | +++ NEWS.txt 2012-02-01 16:39:02 +0000 |
4 | @@ -8,6 +8,9 @@ |
5 | * qannotate: |
6 | * Vertically center the target line when using "Goto Line" in qannotate. |
7 | (Benoît Pierre) |
8 | + * Show change markers side of annotate view. |
9 | + It represents where lines changed by selected revisions are. |
10 | + (IWATA Hidetaka) |
11 | * qbrowse: |
12 | * Does not crash anymore when called for shared repository. |
13 | (André Bachmann, Alexander Belchenko, Bug #578935) |
14 | @@ -28,6 +31,7 @@ |
15 | (Alexander Belchenko, Bug #814117) |
16 | * Implement search highlight. This change affects to qdiff, qshelve, |
17 | qunshelve, qannotate. (IWATA Hidetaka, Bug #785565) |
18 | + * Show change markers side of diff views. (IWATA Hidetaka Bug #827251) |
19 | * qgetnew |
20 | * Base directories for the source branch and the destination checkout folder |
21 | can now be configured in qconfig, tab 'User Interface'. (André Bachmann) |
22 | |
23 | === modified file 'lib/annotate.py' |
24 | --- lib/annotate.py 2012-01-24 20:34:19 +0000 |
25 | +++ lib/annotate.py 2012-02-01 16:39:02 +0000 |
26 | @@ -23,6 +23,7 @@ |
27 | # - better annotate algorithm on packs |
28 | |
29 | import sys, time |
30 | +from itertools import groupby |
31 | from PyQt4 import QtCore, QtGui |
32 | |
33 | from bzrlib.revision import CURRENT_REVISION |
34 | @@ -41,6 +42,9 @@ |
35 | runs_in_loading_queue, |
36 | ) |
37 | from bzrlib.plugins.qbzr.lib.widgets.toolbars import FindToolbar |
38 | +from bzrlib.plugins.qbzr.lib.widgets.texteditaccessory import ( |
39 | + GuideBarPanel, setup_guidebar_for_find |
40 | +) |
41 | from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget |
42 | from bzrlib.plugins.qbzr.lib.trace import reports_exception |
43 | from bzrlib.plugins.qbzr.lib.logwidget import LogList |
44 | @@ -70,7 +74,8 @@ |
45 | self.get_revno = get_revno |
46 | self.annotate = None |
47 | self.rev_colors = {} |
48 | - self.highlight_revids = set() |
49 | + self._highlight_revids = set() |
50 | + self.highlight_lines = [] |
51 | |
52 | self.splitter = None |
53 | self.adjustWidth(1, 999) |
54 | @@ -80,6 +85,31 @@ |
55 | self.edit_cursorPositionChanged) |
56 | self.show_current_line = False |
57 | |
58 | + def get_highlight_revids(self): |
59 | + return self._highlight_revids |
60 | + |
61 | + def set_highlight_revids(self, value): |
62 | + if self._highlight_revids == value: |
63 | + return |
64 | + |
65 | + self._highlight_revids = value |
66 | + self.update_highlight_lines() |
67 | + |
68 | + def update_highlight_lines(self): |
69 | + self.highlight_lines = [] |
70 | + if not self.annotate: |
71 | + return |
72 | + lines = [i for i, (revno, istop) in enumerate(self.annotate) |
73 | + if revno in self._highlight_revids] |
74 | + |
75 | + # Convert [0,1,2,5,6,9,14,15,16,17] to [(0,3),(5,2),(9,1),(14,4)] |
76 | + def summarize(lines): |
77 | + for k, g in groupby(enumerate(lines), key=lambda x:x[1]-x[0]): |
78 | + yield [line for i, line in g] |
79 | + self.highlight_lines = [(x[0], len(x)) for x in summarize(lines)] |
80 | + |
81 | + highlight_revids = property(get_highlight_revids, set_highlight_revids) |
82 | + |
83 | def edit_cursorPositionChanged(self): |
84 | self.show_current_line = True |
85 | |
86 | @@ -138,7 +168,7 @@ |
87 | if self.annotate and line_number-1 < len(self.annotate): |
88 | revid, is_top = self.annotate[line_number - 1] |
89 | if is_top: |
90 | - if revid in self.highlight_revids: |
91 | + if revid in self._highlight_revids: |
92 | font = painter.font() |
93 | font.setBold(True) |
94 | painter.setFont(font) |
95 | @@ -271,11 +301,13 @@ |
96 | self.text_edit.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) |
97 | |
98 | self.text_edit.document().setDefaultFont(get_monospace_font()) |
99 | - |
100 | + |
101 | + self.guidebar_panel = GuideBarPanel(self.text_edit, parent=self) |
102 | + self.guidebar_panel.add_entry('annotate', QtGui.QColor(255, 160, 180)) |
103 | self.annotate_bar = AnnotateBar(self.text_edit, self, self.get_revno) |
104 | annotate_spliter = QtGui.QSplitter(QtCore.Qt.Horizontal, self) |
105 | annotate_spliter.addWidget(self.annotate_bar) |
106 | - annotate_spliter.addWidget(self.text_edit) |
107 | + annotate_spliter.addWidget(self.guidebar_panel) |
108 | self.annotate_bar.splitter = annotate_spliter |
109 | self.text_edit_frame.hbox.addWidget(annotate_spliter) |
110 | |
111 | @@ -284,7 +316,10 @@ |
112 | self.edit_cursorPositionChanged) |
113 | self.connect(self.annotate_bar, |
114 | QtCore.SIGNAL("cursorPositionChanged()"), |
115 | - self.edit_cursorPositionChanged) |
116 | + self.edit_cursorPositionChanged) |
117 | + self.connect(self.text_edit, |
118 | + QtCore.SIGNAL("documentChangeFinished()"), |
119 | + self.edit_documentChangeFinished) |
120 | |
121 | self.log_list = AnnotateLogList(self.processEvents, self.throbber, self) |
122 | self.log_list.header().hideSection(logmodel.COL_DATE) |
123 | @@ -372,6 +407,7 @@ |
124 | self.connect(self.show_find, |
125 | QtCore.SIGNAL("toggled (bool)"), |
126 | self.show_find_toggle) |
127 | + setup_guidebar_for_find(self.guidebar_panel, self.find_toolbar, index=1) |
128 | |
129 | self.goto_line_toolbar = GotoLineToolbar(self, self.show_goto_line) |
130 | self.goto_line_toolbar.hide() |
131 | @@ -584,6 +620,10 @@ |
132 | if self.text_edit.annotate: |
133 | rev_id, is_top = self.text_edit.annotate[current_line] |
134 | self.log_list.select_revid(rev_id) |
135 | + |
136 | + def edit_documentChangeFinished(self): |
137 | + self.annotate_bar.update_highlight_lines() |
138 | + self.guidebar_panel.update_data(annotate=self.annotate_bar.highlight_lines) |
139 | |
140 | @runs_in_loading_queue |
141 | def set_annotate_revision(self): |
142 | @@ -623,6 +663,7 @@ |
143 | def log_list_selectionChanged(self, selected, deselected): |
144 | revids = self.log_list.get_selection_and_merged_revids() |
145 | self.annotate_bar.highlight_revids = revids |
146 | + self.guidebar_panel.update_data(annotate=self.annotate_bar.highlight_lines) |
147 | self.annotate_bar.update() |
148 | |
149 | def show_find_toggle(self, state): |
150 | |
151 | === modified file 'lib/diffview.py' |
152 | --- lib/diffview.py 2011-09-20 18:33:50 +0000 |
153 | +++ lib/diffview.py 2012-02-01 16:39:02 +0000 |
154 | @@ -35,6 +35,9 @@ |
155 | CachedTTypeFormater, |
156 | split_tokens_at_lines, |
157 | ) |
158 | +from bzrlib.plugins.qbzr.lib.widgets.texteditaccessory import ( |
159 | + GuideBarPanel, GBAR_LEFT, GBAR_RIGHT |
160 | +) |
161 | |
162 | have_pygments = True |
163 | try: |
164 | @@ -378,6 +381,12 @@ |
165 | self.adjust_range() |
166 | |
167 | |
168 | + |
169 | +def setup_guidebar_entries(gb): |
170 | + gb.add_entry('title', QtGui.QColor(80, 80, 80), -1) |
171 | + for tag in ('delete', 'insert', 'replace'): |
172 | + gb.add_entry(tag, colors[tag][0], 0) |
173 | + |
174 | class _SidebySideDiffView(QtGui.QSplitter): |
175 | """Widget to show differences in side-by-side format.""" |
176 | |
177 | @@ -413,19 +422,30 @@ |
178 | QtGui.QTextDocument()) |
179 | self.browsers = (DiffSourceView(self), |
180 | DiffSourceView(self)) |
181 | + |
182 | + self.guidebar_panels = [ |
183 | + GuideBarPanel(b, align=a) |
184 | + for (b, a) in zip(self.browsers, (GBAR_LEFT, GBAR_RIGHT)) |
185 | + ] |
186 | + for g in self.guidebar_panels: |
187 | + setup_guidebar_entries(g.bar) |
188 | + |
189 | + self.reset_guidebar_data() |
190 | + |
191 | for b in self.browsers: |
192 | b.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) |
193 | |
194 | self.cursors = [QtGui.QTextCursor(doc) for doc in self.docs] |
195 | |
196 | - for i, (browser, doc, cursor) in enumerate(zip(self.browsers, self.docs, self.cursors)): |
197 | + for i, (panel, doc, cursor) in enumerate(zip(self.guidebar_panels, |
198 | + self.docs, self.cursors)): |
199 | doc.setUndoRedoEnabled(False) |
200 | doc.setDefaultFont(self.monospacedFont) |
201 | |
202 | + panel.edit.setDocument(doc) |
203 | + self.addWidget(panel) |
204 | self.setCollapsible(i, False) |
205 | - browser.setDocument(doc) |
206 | - self.addWidget(browser) |
207 | - |
208 | + |
209 | format = QtGui.QTextCharFormat() |
210 | format.setAnchorNames(["top"]) |
211 | cursor.insertText("", format) |
212 | @@ -452,7 +472,13 @@ |
213 | def setTabStopWidths(self, pixels): |
214 | for (pixel_width, browser) in zip(pixels, self.browsers): |
215 | browser.setTabStopWidth(pixel_width) |
216 | - |
217 | + |
218 | + def reset_guidebar_data(self): |
219 | + self.guidebar_data = [ |
220 | + dict(title=[], delete=[], insert=[], replace=[]), # for left view |
221 | + dict(title=[], delete=[], insert=[], replace=[]), # for right view |
222 | + ] |
223 | + |
224 | def clear(self): |
225 | self.browsers[0].clear() |
226 | self.browsers[1].clear() |
227 | @@ -460,6 +486,7 @@ |
228 | self.scrollbar.clear() |
229 | for doc in self.docs: |
230 | doc.clear() |
231 | + self.reset_guidebar_data() |
232 | self.update() |
233 | |
234 | def set_complete(self, complete): |
235 | @@ -471,8 +498,12 @@ |
236 | def append_diff(self, paths, file_id, kind, status, dates, |
237 | present, binary, lines, groups, data, properties_changed): |
238 | cursors = self.cursors |
239 | + |
240 | + guidebar_data = self.guidebar_data |
241 | + |
242 | for i in range(2): |
243 | cursor = cursors[i] |
244 | + guidebar_data[i]['title'].append((cursor.block().blockNumber(), 2)) |
245 | cursor.beginEditBlock() |
246 | cursor.insertText(paths[i] or " ", self.titleFormat) # None or " " => " " |
247 | cursor.insertBlock() |
248 | @@ -633,6 +664,7 @@ |
249 | insertIxs(ixs) |
250 | else: |
251 | y_top = [cursor.block().layout() for cursor in self.cursors] |
252 | + g_top = [cursor.block().blockNumber() for cursor in self.cursors] |
253 | if tag == "replace": |
254 | insertIxsWithChangesHighlighted(ixs) |
255 | else: |
256 | @@ -640,7 +672,11 @@ |
257 | linediff += n[0] - n[1] |
258 | y_bot = [cursor.block().layout() for cursor in self.cursors] |
259 | changes.append((y_top[0], y_bot[0], y_top[1], y_bot[1], tag)) |
260 | - |
261 | + |
262 | + g_bot = [cursor.block().blockNumber() for cursor in self.cursors] |
263 | + for data, top, bot in zip(guidebar_data, g_top, g_bot): |
264 | + data[tag].append((top, bot - top)) |
265 | + |
266 | if linediff == 0: |
267 | continue |
268 | if not self.complete: |
269 | @@ -728,6 +764,8 @@ |
270 | or self.browsers[1].horizontalScrollBar().isVisible()): |
271 | self.browsers[0].setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) |
272 | self.browsers[1].setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) |
273 | + |
274 | + self.update_guidebar() |
275 | self.update() |
276 | |
277 | def rewind(self): |
278 | @@ -761,8 +799,26 @@ |
279 | def createHandle(self): |
280 | return DiffViewHandle(self) |
281 | |
282 | - |
283 | -class SimpleDiffView(QtGui.QTextBrowser): |
284 | + def update_guidebar(self): |
285 | + for gb, data in zip(self.guidebar_panels, self.guidebar_data): |
286 | + gb.bar.update_data(**data) |
287 | + |
288 | +class SimpleDiffView(GuideBarPanel): |
289 | + def __init__(self, parent): |
290 | + self.view = _SimpleDiffView(parent) |
291 | + GuideBarPanel.__init__(self, self.view, parent=parent) |
292 | + setup_guidebar_entries(self) |
293 | + |
294 | + def append_diff(self, *args, **kwargs): |
295 | + self.view.append_diff(*args, **kwargs) |
296 | + self.update_data(**self.view.guidebar_data) |
297 | + |
298 | + def __getattr__(self, name): |
299 | + """Delegate unknown methods to internal diffview.""" |
300 | + return getattr(self.view, name) |
301 | + |
302 | + |
303 | +class _SimpleDiffView(QtGui.QTextBrowser): |
304 | """Widget to show differences in unidiff format.""" |
305 | |
306 | def __init__(self, parent=None): |
307 | @@ -807,6 +863,8 @@ |
308 | self.monospacedHunkFormat.setFont(monospacedItalicFont) |
309 | self.monospacedHunkFormat.setForeground(QtGui.QColor(153, 30, 199)) |
310 | |
311 | + self.reset_guidebar_data() |
312 | + |
313 | def rewind(self): |
314 | if not self.rewinded: |
315 | self.rewinded = True |
316 | @@ -817,6 +875,8 @@ |
317 | |
318 | def append_diff(self, paths, file_id, kind, status, dates, |
319 | present, binary, lines, groups, data, properties_changed): |
320 | + guidebar_data = self.guidebar_data |
321 | + guidebar_data['title'].append((self.cursor.block().blockNumber(), 2)) |
322 | self.cursor.beginEditBlock() |
323 | path_info = paths[1] or paths[0] |
324 | if status in ('renamed', 'renamed and modified'): |
325 | @@ -878,13 +938,24 @@ |
326 | text = "".join(" " + l for l in a[i0:i1]) |
327 | self.cursor.insertText(text, self.monospacedFormat) |
328 | else: |
329 | + start = self.cursor.block().blockNumber() |
330 | text = "".join("-" + l for l in a[i0:i1]) |
331 | self.cursor.insertText(text, self.monospacedDeleteFormat) |
332 | text = "".join("+" + l for l in b[j0:j1]) |
333 | self.cursor.insertText(text, self.monospacedInsertFormat) |
334 | + end = self.cursor.block().blockNumber() |
335 | + guidebar_data[tag].append((start, end - start)) |
336 | else: |
337 | self.cursor.insertText("Binary files %s %s and %s %s differ\n" % \ |
338 | (paths[0], dates[0], paths[1], dates[1])) |
339 | self.cursor.insertText("\n") |
340 | self.cursor.endEditBlock() |
341 | self.update() |
342 | + |
343 | + def clear(self): |
344 | + QtGui.QTextBrowser.clear(self) |
345 | + self.reset_guidebar_data() |
346 | + |
347 | + def reset_guidebar_data(self): |
348 | + self.guidebar_data = dict(title=[], delete=[], insert=[], replace=[]) |
349 | + |
350 | |
351 | === modified file 'lib/diffwindow.py' |
352 | --- lib/diffwindow.py 2012-01-24 20:34:19 +0000 |
353 | +++ lib/diffwindow.py 2012-02-01 16:39:02 +0000 |
354 | @@ -66,6 +66,7 @@ |
355 | from bzrlib.plugins.qbzr.lib.trace import reports_exception |
356 | from bzrlib.plugins.qbzr.lib.encoding_selector import EncodingMenuSelector |
357 | from bzrlib.plugins.qbzr.lib.widgets.tab_width_selector import TabWidthMenuSelector |
358 | +from bzrlib.plugins.qbzr.lib.widgets.texteditaccessory import setup_guidebar_for_find |
359 | |
360 | |
361 | |
362 | @@ -158,6 +159,9 @@ |
363 | self.show_find) |
364 | self.find_toolbar.hide() |
365 | self.addToolBar(self.find_toolbar) |
366 | + setup_guidebar_for_find(self.sdiffview, self.find_toolbar, 1) |
367 | + for gb in self.diffview.guidebar_panels: |
368 | + setup_guidebar_for_find(gb, self.find_toolbar, 1) |
369 | |
370 | def connect_later(self, *args, **kwargs): |
371 | """Schedules a signal to be connected after loading CLI arguments. |
372 | @@ -487,7 +491,7 @@ |
373 | def click_toggle_view_mode(self, checked): |
374 | if checked: |
375 | view = self.sdiffview |
376 | - self.find_toolbar.set_text_edits([view]) |
377 | + self.find_toolbar.set_text_edits([view.view]) |
378 | self.tab_width_selector_left.menuAction().setVisible(False) |
379 | self.tab_width_selector_right.menuAction().setVisible(False) |
380 | self.tab_width_selector_unidiff.menuAction().setVisible(True) |
381 | |
382 | === modified file 'lib/widgets/shelve.py' |
383 | --- lib/widgets/shelve.py 2011-12-28 06:35:12 +0000 |
384 | +++ lib/widgets/shelve.py 2012-02-01 16:39:02 +0000 |
385 | @@ -37,6 +37,9 @@ |
386 | FindToolbar, ToolbarPanel, LayoutSelector |
387 | ) |
388 | from bzrlib.plugins.qbzr.lib.widgets.tab_width_selector import TabWidthMenuSelector |
389 | +from bzrlib.plugins.qbzr.lib.widgets.texteditaccessory import ( |
390 | + GuideBar, setup_guidebar_for_find |
391 | + ) |
392 | from bzrlib.plugins.qbzr.lib.decorators import lazy_call |
393 | from bzrlib import errors |
394 | from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget |
395 | @@ -329,6 +332,8 @@ |
396 | hunk_panel.add_widget(self.hunk_view) |
397 | find_toolbar.hide() |
398 | |
399 | + setup_guidebar_for_find(self.hunk_view.guidebar, find_toolbar, index=1) |
400 | + |
401 | layout = QtGui.QVBoxLayout() |
402 | layout.setMargin(10) |
403 | layout.addWidget(self.splitter) |
404 | @@ -778,10 +783,13 @@ |
405 | layout.setSpacing(0) |
406 | layout.setMargin(0) |
407 | self.browser = HunkTextBrowser(complete, self) |
408 | + self.guidebar = GuideBar(self.browser, parent=self) |
409 | + self.guidebar.add_entry('hunk', self.browser.focus_color) |
410 | self.selector = HunkSelector(self.browser, self) |
411 | layout.addWidget(self.selector) |
412 | layout.addWidget(self.browser) |
413 | - self.connect(self.browser, QtCore.SIGNAL("focusedHunkChanged()"), |
414 | + layout.addWidget(self.guidebar) |
415 | + self.connect(self.browser, QtCore.SIGNAL("focusedHunkChanged()"), |
416 | self.update) |
417 | |
418 | def selected_hunk_changed(): |
419 | @@ -814,6 +822,7 @@ |
420 | self.change = change |
421 | self.encoding = encoding |
422 | self.browser.set_parsed_patch(change, encoding) |
423 | + self.guidebar.update_data(hunk=self.browser.guidebar_deta) |
424 | self.update() |
425 | |
426 | def update(self): |
427 | @@ -930,6 +939,7 @@ |
428 | |
429 | self.complete = complete |
430 | self._focused_index = -1 |
431 | + self.guidebar_deta = [] |
432 | |
433 | def rewind(self): |
434 | if not self.rewinded: |
435 | @@ -972,9 +982,11 @@ |
436 | cursor.insertText(lines, self.monospacedInactiveFormat) |
437 | start = hunk.mod_pos + hunk.mod_range - 1 |
438 | y1 = cursor.block().layout().position().y() |
439 | + l1 = cursor.block().blockNumber() |
440 | print_hunk(hunk, hunk_texts) |
441 | y2 = cursor.block().layout().position().y() |
442 | - |
443 | + l2 = cursor.block().blockNumber() |
444 | + self.guidebar_deta.append((l1, l2 - l1)) |
445 | else: |
446 | y1 = cursor.block().layout().position().y() |
447 | cursor.insertText(str(hunk.get_header()), self.monospacedHunkFormat) |
448 | @@ -1003,6 +1015,7 @@ |
449 | QtGui.QTextBrowser.clear(self) |
450 | del(self.hunk_list[:]) |
451 | self._set_focused_hunk(-1) |
452 | + self.guidebar_deta = [] |
453 | self.emit(QtCore.SIGNAL("documentChangeFinished()")) |
454 | |
455 | def paintEvent(self, event): |
456 | |
457 | === modified file 'lib/widgets/shelvelist.py' |
458 | --- lib/widgets/shelvelist.py 2012-01-30 00:25:46 +0000 |
459 | +++ lib/widgets/shelvelist.py 2012-02-01 16:39:02 +0000 |
460 | @@ -456,7 +456,7 @@ |
461 | if index == 0: |
462 | self.find_toolbar.set_text_edits(self.diffviews[0].browsers) |
463 | else: |
464 | - self.find_toolbar.set_text_edits([self.diffviews[1]]) |
465 | + self.find_toolbar.set_text_edits([self.diffviews[1].view]) |
466 | self.stack.setCurrentIndex(index) |
467 | |
468 | def complete_toggled(self, state): |
469 | |
470 | === added file 'lib/widgets/texteditaccessory.py' |
471 | --- lib/widgets/texteditaccessory.py 1970-01-01 00:00:00 +0000 |
472 | +++ lib/widgets/texteditaccessory.py 2012-02-01 16:39:02 +0000 |
473 | @@ -0,0 +1,294 @@ |
474 | +# -*- coding: utf-8 -*- |
475 | +# |
476 | +# QBzr - Qt frontend to Bazaar commands |
477 | +# Copyright (C) 2011 QBzr Developers |
478 | +# |
479 | +# This program is free software; you can redistribute it and/or |
480 | +# modify it under the terms of the GNU General Public License |
481 | +# as published by the Free Software Foundation; either version 2 |
482 | +# of the License, or (at your option) any later version. |
483 | +# |
484 | +# This program is distributed in the hope that it will be useful, |
485 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
486 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
487 | +# GNU General Public License for more details. |
488 | +# |
489 | +# You should have received a copy of the GNU General Public License |
490 | +# along with this program; if not, write to the Free Software |
491 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
492 | + |
493 | +from PyQt4 import QtCore, QtGui |
494 | + |
495 | +GBAR_LEFT = 1 |
496 | +GBAR_RIGHT = 2 |
497 | + |
498 | +class _Entry(object): |
499 | + """ |
500 | + Represent each group of guide bar. |
501 | + |
502 | + :key: string key to identify this group |
503 | + :color: color or marker |
504 | + :data: marker positions, list of tuple (block index, num of blocks) |
505 | + :index: index of the column to render this entry. |
506 | + * Two or more groups can be rendered on same columns. |
507 | + * If index == -1, the group is renderd on all columns. |
508 | + """ |
509 | + __slots__ = ['key', 'color', 'data', 'index'] |
510 | + def __init__(self, key, color, index=0): |
511 | + self.key = key |
512 | + self.color = color |
513 | + self.data = [] |
514 | + self.index = index |
515 | + |
516 | +class PlainTextEditHelper(QtCore.QObject): |
517 | + """ |
518 | + Helper class to encapsulate gap between QPlainTextEdit and QTextEdit |
519 | + """ |
520 | + def __init__(self, edit): |
521 | + QtCore.QObject.__init__(self) |
522 | + if not isinstance(edit, QtGui.QPlainTextEdit): |
523 | + raise ValueError('edit must be QPlainTextEdit') |
524 | + self.edit = edit |
525 | + |
526 | + self.connect(edit, QtCore.SIGNAL("updateRequest(const QRect&, int)"), |
527 | + self.onUpdateRequest) |
528 | + |
529 | + def onUpdateRequest(self, rect, dy): |
530 | + self.emit(QtCore.SIGNAL("updateRequest()")) |
531 | + |
532 | + def center_block(self, block): |
533 | + """ |
534 | + scroll textarea as specified block locates to center |
535 | + |
536 | + NOTE: This code is based on Qt source code (qplaintextedit.cpp) |
537 | + """ |
538 | + edit = self.edit |
539 | + height = edit.viewport().rect().height() / 2 |
540 | + h = self.edit.blockBoundingRect(block).center().y() |
541 | + def iter_visible_block_backward(b): |
542 | + while True: |
543 | + b = b.previous() |
544 | + if not b.isValid(): return |
545 | + if b.isVisible(): yield b |
546 | + for block in iter_visible_block_backward(block): |
547 | + h += edit.blockBoundingRect(block).height() |
548 | + if height < h: |
549 | + break |
550 | + edit.verticalScrollBar().setValue(block.firstLineNumber()) |
551 | + |
552 | +class TextEditHelper(QtCore.QObject): |
553 | + """ |
554 | + Helper class to encapsulate gap between QPlainTextEdit and QTextEdit |
555 | + """ |
556 | + def __init__(self, edit): |
557 | + QtCore.QObject.__init__(self) |
558 | + if not isinstance(edit, QtGui.QTextEdit): |
559 | + raise ValueError('edit must be QTextEdit') |
560 | + self.edit = edit |
561 | + |
562 | + self.connect(edit.verticalScrollBar(), QtCore.SIGNAL("valueChanged(int)"), |
563 | + self.onVerticalScroll) |
564 | + |
565 | + def onVerticalScroll(self, value): |
566 | + self.emit(QtCore.SIGNAL("updateRequest()")) |
567 | + |
568 | + def center_block(self, block): |
569 | + """ |
570 | + scroll textarea as specified block locates to center |
571 | + """ |
572 | + y = block.layout().position().y() |
573 | + vscroll = self.edit.verticalScrollBar() |
574 | + vscroll.setValue(y - vscroll.pageStep() / 2) |
575 | + |
576 | +def get_edit_helper(edit): |
577 | + if isinstance(edit, QtGui.QPlainTextEdit): |
578 | + return PlainTextEditHelper(edit) |
579 | + if isinstance(edit, QtGui.QTextEdit): |
580 | + return TextEditHelper(edit) |
581 | + raise ValueError("edit is unsupported type.") |
582 | + |
583 | +class GuideBar(QtGui.QWidget): |
584 | + """ |
585 | + Vertical bar attached to TextEdit. |
586 | + This shows that where changed or highlighted lines are. |
587 | + |
588 | + Guide bar can have multiple columns. |
589 | + """ |
590 | + def __init__(self, edit, base_width=10, parent=None): |
591 | + """ |
592 | + :edit: target widget, must be QPlainTextEdit or QTextEdit |
593 | + :base_width: width of each column. |
594 | + """ |
595 | + QtGui.QWidget.__init__(self, parent) |
596 | + self.base_width = base_width |
597 | + self.edit = edit |
598 | + self._helper = get_edit_helper(edit) |
599 | + self.block_count = 0 |
600 | + |
601 | + self.connect(edit, QtCore.SIGNAL("documentChangeFinished()"), |
602 | + self.reset_gui) |
603 | + self.connect(edit.verticalScrollBar(), QtCore.SIGNAL("rangeChanged(int, int)"), |
604 | + self.vscroll_rangeChanged) |
605 | + |
606 | + self.connect(self._helper, QtCore.SIGNAL("updateRequest()"), self.update) |
607 | + |
608 | + self.entries = {} |
609 | + self.vscroll_visible = None |
610 | + |
611 | + def add_entry(self, key, color, index=0): |
612 | + """ |
613 | + Add marker group |
614 | + """ |
615 | + entry = _Entry(key, color, index) |
616 | + self.entries[key] = entry |
617 | + |
618 | + def vscroll_rangeChanged(self, min, max): |
619 | + vscroll_visible = (min < max) |
620 | + if self.vscroll_visible != vscroll_visible: |
621 | + self.vscroll_visible = vscroll_visible |
622 | + self.reset_gui() |
623 | + self.update() |
624 | + |
625 | + def reset_gui(self): |
626 | + """ |
627 | + Determine show or hide, and num of columns. |
628 | + """ |
629 | + # Hide when vertical scrollbar is not shown. |
630 | + if not self.vscroll_visible: |
631 | + self.setVisible(False) |
632 | + return |
633 | + valid_entries = [e for e in self.entries.itervalues() if e.data] |
634 | + if not valid_entries: |
635 | + self.setVisible(False) |
636 | + return |
637 | + |
638 | + self.setVisible(True) |
639 | + self.repeats = len(set([e.index for e in valid_entries if e.index >= 0])) |
640 | + if self.repeats == 0: |
641 | + self.repeats = 1 |
642 | + self.setFixedWidth(self.repeats * self.base_width + 4) |
643 | + |
644 | + self.block_count = self.edit.document().blockCount() |
645 | + self.update() |
646 | + |
647 | + def update_data(self, **data): |
648 | + """ |
649 | + Update each marker positions. |
650 | + |
651 | + :arg_name: marker key |
652 | + :value: list of marker positions. |
653 | + Each position is tuple of (block index, num of blocks). |
654 | + """ |
655 | + for key, value in data.iteritems(): |
656 | + self.entries[key].data[:] = value |
657 | + self.reset_gui() |
658 | + |
659 | + def get_visible_block_range(self): |
660 | + """ |
661 | + Return tuple of (index of first visible block, num of visible block) |
662 | + """ |
663 | + pos = QtCore.QPoint(0, 1) |
664 | + block = self.edit.cursorForPosition(pos).block() |
665 | + first_visible_block = block.blockNumber() |
666 | + |
667 | + y = self.edit.viewport().height() |
668 | + |
669 | + pos = QtCore.QPoint(0, y) |
670 | + block = self.edit.cursorForPosition(pos).block() |
671 | + if block.isValid(): |
672 | + visible_blocks = block.blockNumber() - first_visible_block + 1 |
673 | + else: |
674 | + visible_blocks = self.block_count - first_visible_block + 1 |
675 | + |
676 | + return first_visible_block, visible_blocks |
677 | + |
678 | + def paintEvent(self, event): |
679 | + QtGui.QWidget.paintEvent(self, event) |
680 | + painter = QtGui.QPainter(self) |
681 | + painter.fillRect(event.rect(), QtCore.Qt.white) |
682 | + if self.block_count == 0: |
683 | + return |
684 | + painter.setRenderHints(QtGui.QPainter.Antialiasing, True) |
685 | + block_height = float(self.height()) / self.block_count |
686 | + |
687 | + # Draw entries |
688 | + x_origin = 2 |
689 | + index = -1 |
690 | + prev_index = -1 |
691 | + for e in sorted(self.entries.itervalues(), |
692 | + key=lambda x:x.index if x.index >= 0 else 999): |
693 | + if not e.data: |
694 | + continue |
695 | + if e.index < 0: |
696 | + x, width = 0, self.width() |
697 | + else: |
698 | + if e.index != prev_index: |
699 | + index += 1 |
700 | + prev_index = e.index |
701 | + x, width = x_origin + index * self.base_width, self.base_width |
702 | + for block_index, block_num in e.data: |
703 | + y = block_index * block_height |
704 | + height = max(1, block_num * block_height) |
705 | + painter.fillRect(x, y, width, height, e.color) |
706 | + |
707 | + # Draw scroll indicator. |
708 | + x, width = 0, self.width() |
709 | + first_block, visible_blocks = self.get_visible_block_range() |
710 | + y, height = first_block * block_height, max(1, visible_blocks * block_height) |
711 | + painter.fillRect(x, y, width, height, QtGui.QColor(0, 0, 0, 24)) |
712 | + |
713 | + def mousePressEvent(self, event): |
714 | + QtGui.QWidget.mousePressEvent(self, event) |
715 | + if event.button() == QtCore.Qt.LeftButton: |
716 | + self.scroll_to_pos(event.y()) |
717 | + |
718 | + def mouseMoveEvent(self, event): |
719 | + QtGui.QWidget.mouseMoveEvent(self, event) |
720 | + self.scroll_to_pos(event.y()) |
721 | + |
722 | + def scroll_to_pos(self, y): |
723 | + block_no = int(float(y) / self.height() * self.block_count) |
724 | + block = self.edit.document().findBlockByNumber(block_no) |
725 | + if not block.isValid(): |
726 | + return |
727 | + self._helper.center_block(block) |
728 | + |
729 | +class GuideBarPanel(QtGui.QWidget): |
730 | + """ |
731 | + Composite widget of TextEdit and GuideBar |
732 | + """ |
733 | + def __init__(self, edit, base_width=10, align=GBAR_RIGHT, parent=None): |
734 | + QtGui.QWidget.__init__(self, parent) |
735 | + hbox = QtGui.QHBoxLayout(self) |
736 | + hbox.setSpacing(0) |
737 | + hbox.setMargin(0) |
738 | + self.bar = GuideBar(edit, base_width=base_width, parent=parent) |
739 | + self.edit = edit |
740 | + if align == GBAR_RIGHT: |
741 | + hbox.addWidget(self.edit) |
742 | + hbox.addWidget(self.bar) |
743 | + else: |
744 | + hbox.addWidget(self.bar) |
745 | + hbox.addWidget(self.edit) |
746 | + |
747 | + def add_entry(self, key, color, index=0): |
748 | + self.bar.add_entry(key, color, index) |
749 | + |
750 | + def reset_gui(self): |
751 | + self.bar.reset_gui() |
752 | + |
753 | + def update_data(self, **data): |
754 | + return self.bar.update_data(**data) |
755 | + |
756 | +def setup_guidebar_for_find(guidebar, find_toolbar, index=0): |
757 | + """ |
758 | + Make guidebar enable to show positions that highlighted by FindToolBar |
759 | + """ |
760 | + def on_highlight_changed(): |
761 | + if guidebar.edit in find_toolbar.text_edits: |
762 | + guidebar.update_data( |
763 | + find=[(n, 1) for n in guidebar.edit.highlight_lines] |
764 | + ) |
765 | + guidebar.add_entry('find', QtGui.QColor(255, 196, 0), index) # Gold |
766 | + guidebar.connect(find_toolbar, QtCore.SIGNAL("highlightChanged()"), |
767 | + on_highlight_changed) |
768 | |
769 | === modified file 'lib/widgets/toolbars.py' |
770 | --- lib/widgets/toolbars.py 2011-12-28 12:06:42 +0000 |
771 | +++ lib/widgets/toolbars.py 2012-02-01 16:39:02 +0000 |
772 | @@ -48,7 +48,7 @@ |
773 | parent.connect(button, QtCore.SIGNAL(signal), onclick) |
774 | return button |
775 | |
776 | -def add_toolbar_button(toolbar, text, parent, icon_name=None, icon_size=22, |
777 | +def add_toolbar_button(toolbar, text, parent, icon_name=None, icon_size=22, |
778 | enabled=True, checkable=False, checked=False, |
779 | shortcut=None, onclick=None): |
780 | button = create_toolbar_button(text, parent, icon_name, icon_size, |
781 | @@ -56,7 +56,7 @@ |
782 | toolbar.addAction(button) |
783 | return button |
784 | |
785 | - |
786 | + |
787 | class FindToolbar(QtGui.QToolBar): |
788 | |
789 | def __init__(self, window, text_edit, show_action): |
790 | @@ -131,7 +131,7 @@ |
791 | self.connect(self.find_text, |
792 | QtCore.SIGNAL("returnPressed()"), |
793 | self.find_next) |
794 | - |
795 | + |
796 | def show_action_toggle(self, state): |
797 | self.setVisible(state) |
798 | if state: |
799 | @@ -197,6 +197,7 @@ |
800 | t.setExtraSelections([]) |
801 | |
802 | for t in text_edits: |
803 | + t.highlight_lines = [] |
804 | self.connect(t, QtCore.SIGNAL("documentChangeFinished()"), |
805 | self.highlight) |
806 | |
807 | @@ -217,6 +218,7 @@ |
808 | flags = self.find_get_flags() |
809 | for text_edit in self.text_edits: |
810 | selections = [] |
811 | + highlight_lines = [] |
812 | if text: |
813 | find = text_edit.document().find |
814 | pos = 0 |
815 | @@ -230,9 +232,13 @@ |
816 | sel = QtGui.QTextEdit.ExtraSelection() |
817 | sel.cursor, sel.format = cursor, fmt |
818 | selections.append(sel) |
819 | + highlight_lines.append(cursor.blockNumber()) |
820 | pos = cursor.selectionEnd() |
821 | |
822 | text_edit.setExtraSelections(selections) |
823 | + text_edit.highlight_lines = highlight_lines |
824 | + |
825 | + self.emit(QtCore.SIGNAL("highlightChanged()")) |
826 | |
827 | class ToolbarPanel(QtGui.QWidget): |
828 | def __init__(self, slender=True, icon_size=16, parent=None): |
829 | @@ -255,7 +261,7 @@ |
830 | |
831 | def add_toolbar_button(self, text, icon_name=None, icon_size=0, enabled=True, |
832 | checkable=False, checked=False, shortcut=None, onclick=None, menu=None): |
833 | - button = create_toolbar_button(text, self, icon_name=icon_name, |
834 | + button = create_toolbar_button(text, self, icon_name=icon_name, |
835 | icon_size=icon_size or self.icon_size, enabled=enabled, |
836 | checkable=checkable, checked=checked, shortcut=shortcut, onclick=onclick) |
837 | if menu is not None: |
838 | @@ -273,7 +279,7 @@ |
839 | show_shortcut_hint(widget) |
840 | return button |
841 | |
842 | - def create_button(self, text, icon_name=None, icon_size=0, enabled=True, |
843 | + def create_button(self, text, icon_name=None, icon_size=0, enabled=True, |
844 | checkable=False, checked=False, shortcut=None, onclick=None): |
845 | return create_toolbar_button(text, self, icon_name=icon_name, |
846 | icon_size=icon_size or self.icon_size, enabled=enabled, |
IWATA Hidetaka пишет: /bugs.launchpad .net/qbzr/ +bug/827251 /code.launchpad .net/~hid- iwata/qbzr/ textedit- guidebar/ +merge/ 91126 dl.dropbox. com/u/16802579/ guidebar- qdiff.png dl.dropbox. com/u/16802579/ guidebar- qannotate. png dl.dropbox. com/u/16802579/ guidebar- qdiff-search. png
> IWATA Hidetaka has proposed merging lp:~hid-iwata/qbzr/textedit-guidebar into lp:qbzr.
>
> Requested reviews:
> QBzr Developers (qbzr-dev)
> Related bugs:
> Bug #827251 in QBzr: ""Complete" diff display could show change markers in the scroll bar"
> https:/
>
> For more details, see:
> https:/
>
> Add change-marker bar to qdiff, qannotate, qshelve and qunshelve.
>
> [qdiff] Show positions of added, removed or modified lines.
> http://
>
> [qannotate] Show positions of lines changed by selected revisions.
> http://
>
> [ALL] Additionally, when searching words, show positions of matched words.
> http://
\o/
that's so cool. thank you!
--
All the dude wanted was his rug back