Zim

Merge lp:~oliver-joos/zim/selection-fixes into lp:~jaap.karssenberg/zim/pyzim

Proposed by Oliver Joos
Status: Merged
Approved by: Jaap Karssenberg
Approved revision: 411
Merged at revision: 435
Proposed branch: lp:~oliver-joos/zim/selection-fixes
Merge into: lp:~jaap.karssenberg/zim/pyzim
Diff against target: 267 lines (+68/-14)
3 files modified
zim/gui/__init__.py (+4/-1)
zim/gui/pageview.py (+58/-13)
zim/gui/searchdialog.py (+6/-0)
To merge this branch: bzr merge lp:~oliver-joos/zim/selection-fixes
Reviewer Review Type Date Requested Status
Jaap Karssenberg Approve
Oliver Joos (community) Needs Resubmitting
Review via email: mp+64340@code.launchpad.net

Description of the change

After using this branch now for several weeks with command line option "--debug" and no strange log lines or other problems I propose it for merging.

I saw that one minor bugfix included in this branch has also been fixed in the main branch meanwhile. The rest should merge seamlessly.

To post a comment you must log in.
lp:~oliver-joos/zim/selection-fixes updated
411. By Oliver Joos

Fix for broken search_backlinks due to selection on page.

Revision history for this message
Oliver Joos (oliver-joos) wrote :

While working on another branch to improve the SearchDialog (to solve bug 796215), I saw that this branch broke menu "Search Backlinks..." whenever a selection exists in the pageview. Rev 411 fixes this.

Sorry for that! Now this branch should be truly "Mature".

review: Needs Resubmitting
Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Had a look at the patch to see what it does. Agree with using text selection as default in the search dialog. Also defining constant for scroll margin is good cleanup.

However I fail to see the advantage of having a special tag for the find match. You still need to select the match with a normal selection. Maybe I miss something here ?

review: Needs Information
Revision history for this message
Oliver Joos (oliver-joos) wrote :

Yes, this branch does still select the match with a normal selection. But a selection only is quite volatile:

Select a word and press Ctrl+F => cursor and selection will jump to the InputEntry of the FindBar. You can't see where your current match is, and you have to remember/guess where you get by pressing "Next" or "Previous". You may jump a few words or scroll down dozens of lines. Activating "Highlight" improves it a bit, as you then see the potential matches. But the current match is still not visible, whenever something else gets selected.

My branch solves it like Firefox and Chromium do. Although Firefox (at least 3.x) has quite funny colors: green for the current match and purple to highlight other matches. Chromium looks better with yellow and orange. Zim has orange to highlight, so I chose a dark yellow for the current match, different enough from the bright yellow of Marked text. To me it feels more predictable/less volatile than with the selection only.

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

On Tue, Jun 21, 2011 at 3:32 PM, Oliver Joos <email address hidden> wrote:

> My branch solves it like Firefox and Chromium do. Although Firefox (at
> least 3.x) has quite funny colors: green for the current match and purple to
> highlight other matches. Chromium looks better with yellow and orange. Zim
> has orange to highlight, so I chose a dark yellow for the current match,
> different enough from the bright yellow of Marked text. To me it feels more
> predictable/less volatile than with the selection only.
>

I checked in firefox, however the behavior is different. The green color is
just the text selection, as soon as you move the cursor (e.g. hit an arrow
key) it is gone. However as implemented now in zim it stays even when the
cursor moves.

Note that when we hit "Next" it starts at the cursor position anyway, not
necessarily the match highlighted.

-- Jaap

Revision history for this message
Oliver Joos (oliver-joos) wrote :

I see what you mean. It seems that the Firefox page and its find-in-page field have separate selections, whereas Zim can only have one visible selection. Changing this could be a bigger task, and that's not worth the benefit.

I solved it simply be re-using Zims way of highlighting all matches. If "Highlight" is enabled then my fix makes not a big difference. If it is disabled then (as you wrote) my fix makes the current-match selection a bit more stubborn, but only like if "Highlight" was enabled. And if find-in-page is closed then any match selections are gone - with or without Highlight and with or without my fix.

Do you think I should change my branch? E.g. I could limit the "make the current match yellow" part to be only applied if Highlight is enabled.

Revision history for this message
Oliver Joos (oliver-joos) wrote :

> Note that when we hit "Next" it starts at the cursor position anyway, not
> necessarily the match highlighted.

After re-reading this I looked again at it. Now I see your point here: if cursor position changes then the highlighted match is not a "current match" anymore, which determines what hitting "Next" will do. Ok, I agree! Then let us call it the "most recent match", which is still nice to be highlighted, I think.

For me the bigger point is: when we hit Ctrl+F and then "Next" it *does not* start at the cursor position! And it is hard to understand where it will start, at least without my fix! It will start at the second match after the cursor, because hitting Ctrl+F already selects the first match after the cursor but in the same time moves the selection from this match to the InputEntry of find-in-page area. The first match is simply not visible!! This is what my fix tries to solve, simply by doing the same for the (!) "most recent match" as the "Highlight" button does for all matches.

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

On Mon, Jul 4, 2011 at 2:21 AM, Oliver Joos <email address hidden> wrote:

> For me the bigger point is: when we hit Ctrl+F and then "Next" it *does
> not* start at the cursor position! And it is hard to understand where it
> will start, at least without my fix! It will start at the second match after
> the cursor, because hitting Ctrl+F already selects the first match after the
> cursor

To be precise:
- If the cursor is at the beginning of the a match "Find" will highlight it
while "Next" will jump to the next one
- If the cursor is some in between matches both "Find" and "Next" will go
to the first match

> but in the same time moves the selection from this match to the InputEntry
> of find-in-page area. The first match is simply not visible!! This is what
> my fix tries to solve, simply by doing the same for the (!) "most recent
> match" as the "Highlight" button does for all matches.
>

What do you mean by "the first match is not visible" ? When testing here the
text selection is visible even when the text does not have the input focus
-- be it in a less obvious color. Maybe you have a different Gtk theme that
makes the selection less obvious ?

Sounds to me we should add some code to your patch to unset the match
highlighting when the cursor moves. (There is a signal for mark position
changed and the cursor is a special mark.)

-- Jaap

Revision history for this message
Oliver Joos (oliver-joos) wrote :

> To be precise:
> - If the cursor is at the beginning of the a match "Find" will highlight it
> while "Next" will jump to the next one
> - If the cursor is some in between matches both "Find" and "Next" will go
> to the first match

Exactly, except Ctrl+F does not highlight the selection here!?

> What do you mean by "the first match is not visible" ? When testing here the
> text selection is visible even when the text does not have the input focus
> -- be it in a less obvious color. Maybe you have a different Gtk theme that
> makes the selection less obvious ?

I use Ubuntu 10.04.2 LTS with theme "Dust Sand" and also tested 10.10 with default theme "Ambiance": if page text has a selection but not the focus then this selection is light gray (less obvious, but expected). But if text in the find InputEntry gets selected (e.g by Ctrl+F) then the selection is taken away from the page text - irretrievably, and not only visually!

> Sounds to me we should add some code to your patch to unset the match
> highlighting when the cursor moves. (There is a signal for mark position
> changed and the cursor is a special mark.)

This would be ok for me too! But first I'll investigate it further. It really could be some problem with GTK.

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

On Wed, Jul 6, 2011 at 1:22 PM, Oliver Joos <email address hidden> wrote:

> But if text in the find InputEntry gets selected (e.g by Ctrl+F) then the
> selection is taken away from the page text - irretrievably, and not only
> visually!
>

Ah OK - now I see what you mean.

 > Sounds to me we should add some code to your patch to unset the match
> > highlighting when the cursor moves. (There is a signal for mark position
> > changed and the cursor is a special mark.)
>
> This would be ok for me too! But first I'll investigate it further. It
> really could be some problem with GTK.
>

I know in one of the dialogs I have a work around to set back the selection
after focus goes back to the pageview. So seems to be a Gtk feature that the
interface can have only one selection at the time. Probably has to do with
the select-paste behavior, having multiple selections would ruin the
usability of that.

-- Jaap

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Now integrating this branch - with some cleanups but keeping the idea of the matching tag.

review: Approve
Revision history for this message
Oliver Joos (oliver-joos) wrote :

I'd like to thank you for merging my not completely finished selection_fixes! Unfortunately I had not much time for zim recently.

I checked the latest Zim revision: your cleanup does improve my branch, especially its source code. I see that you copied the colors of matching text from Firefox. While this is reasonable, magenta/green does not look very harmonic, but I can live with that - no problem!

But a more annoying inconvenience has made it into lp:zim. In zim/gui/__init__.py show_search() my branch creates a SearchDialog and then sets the current selection as query string by invoking dialog.set_from_buffer(), which starts the search process. A query string may also be passed as parameter of SearchDialog(), which is cleaner from a developers point of view. But now the SearchDialog won't show up until the search is finished, which might take several seconds.

For the moment we could change show_search() to the more ugly variant of my "selection-fixes" branch. I could address this problem in my branch named "enhanced_search". There I would move the query string assignment (and the related search process) from SearchDialog.__init__() to a seperate method, e.g. "search(query_string=None)". Or what do you think?

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

This can be easily fixed by setting an attribute in SearchDialog.__init__()
and then do the actual search from show_all(). That way this logic stays
within the search dialog code. Will have a look later this weekend.

Revision history for this message
Oliver Joos (oliver-joos) wrote :

Just setting an attribute in __init__ sounds good. And everything can stay within the search dialog! But I'd prefer a new method in class SearchDialog to run an actual search. IMHO it seems not so obvious that searching is just a side-effect of show_all().

Could you please have a look at my branch https://code.launchpad.net/~oliver-joos/zim/enhanced_search? It heavily depends on the current code of SearchDialog and would be affected from bigger changes.

I'd also welcome some comments from you about my plans for enhanced_search (see branch details). And if you like the current state of the branch, I could set it to mature and open a new branch for the enhanced search plugin. The first part is finished, whereas the plugin could take longer.

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

On Mon, Sep 5, 2011 at 10:04 AM, Oliver Joos <email address hidden> wrote:

> Just setting an attribute in __init__ sounds good. And everything can stay
> within the search dialog! But I'd prefer a new method in class SearchDialog
> to run an actual search. IMHO it seems not so obvious that searching is just
> a side-effect of show_all().
>

That's a valid argument. We can take the argument out of the constructor and
have a "search(query)" method.

Could you please have a look at my branch
> https://code.launchpad.net/~oliver-joos/zim/enhanced_search? It heavily
> depends on the current code of SearchDialog and would be affected from
> bigger changes.
>
> I'd also welcome some comments from you about my plans for enhanced_search
> (see branch details). And if you like the current state of the branch, I
> could set it to mature and open a new branch for the enhanced search plugin.
> The first part is finished, whereas the plugin could take longer.
>

Afraid I can not merge that branch as it. Changes that contain new
translation strings will have to wait till after the next release. And for
feedback in the dialog I would like to use a call back from the search
function so it can be cancelled interactively as well.

Plans sound useful, my main interest is how they will be implemented. But
can discuss that part once you have a prototype.

Regards,

Jaap

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'zim/gui/__init__.py'
2--- zim/gui/__init__.py 2011-06-06 21:00:17 +0000
3+++ zim/gui/__init__.py 2011-06-19 20:42:37 +0000
4@@ -1272,7 +1272,10 @@
5
6 def show_search(self, query=None):
7 from zim.gui.searchdialog import SearchDialog
8- SearchDialog(self, query).show_all()
9+ dialog = SearchDialog(self, query)
10+ if query is None:
11+ dialog.set_from_buffer()
12+ dialog.show_all()
13
14 def show_search_backlinks(self):
15 query = 'LinksTo: "%s"' % self.page.name
16
17=== modified file 'zim/gui/pageview.py'
18--- zim/gui/pageview.py 2011-06-09 11:35:46 +0000
19+++ zim/gui/pageview.py 2011-06-19 20:42:37 +0000
20@@ -216,6 +216,9 @@
21
22 PIXBUF_CHR = u'\uFFFC'
23
24+# Minimal distance from mark to window border after scroll_to_mark()
25+SCROLL_TO_MARK_MARGIN = 0.2
26+
27 # Regexes used for autoformatting
28 heading_re = Re(r'^(={2,7})\s*(.*)\s*(\1)?$')
29 page_re = Re(r'''(
30@@ -386,6 +389,7 @@
31 'checked-checkbox': {},
32 'xchecked-checkbox': {},
33 'find-highlight': {'background': 'orange'},
34+ 'find-match': {'background': '#f2d957'}
35 }
36 static_style_tags = (
37 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
38@@ -1023,7 +1027,7 @@
39 else:
40 return False
41
42- def range_has_tags(self, func, start, end):
43+ def range_has_tags(self, func, start, end):
44 '''Like range_has_tag() but uses a function to check for
45 multiple tags.
46 '''
47@@ -1183,6 +1187,9 @@
48 bounds = self.get_selection_bounds()
49 if bounds:
50 start, end = bounds
51+ end.backward_char() # exclude last line if selection ends_line
52+ if not end.ends_line():
53+ end.forward_char()
54 self.foreach_line(start.get_line(), end.get_line(), func, *args, **kwarg)
55 return True
56 else:
57@@ -2044,6 +2051,9 @@
58 self.highlight = False
59 self.highlight_tag = self.buffer.create_tag(
60 None, **self.buffer.tag_styles['find-highlight'] )
61+ self.match_tag = self.buffer.create_tag(
62+ None, **self.buffer.tag_styles['find-match'] )
63+ self.match_bounds = (None, None)
64
65 def get_state(self):
66 '''Returns the current search string, flags and highlight state'''
67@@ -2099,9 +2109,9 @@
68 # Looking for a match starting at iter
69 if self.regex is None:
70 self.buffer.unset_selection()
71+ self._apply_match(None, None)
72 return False
73
74-
75 line = iter.get_line()
76 lastline = self.buffer.get_end_iter().get_line()
77 for start, end, _ in self._check_range(line, lastline, 1):
78@@ -2109,19 +2119,22 @@
79 continue
80 else:
81 self.buffer.select_range(start, end)
82+ self._apply_match(start, end)
83 return True
84 for start, end, _ in self._check_range(0, line, 1):
85 self.buffer.select_range(start, end)
86+ self._apply_match(start, end)
87 return True
88
89 self.buffer.unset_selection()
90+ self._apply_match(None, None)
91 return False
92
93-
94 def find_previous(self):
95 '''Skip back to the previous match and select it'''
96 if self.regex is None:
97 self.buffer.unset_selection()
98+ self._apply_match(None, None)
99 return False
100
101 iter = self.buffer.get_insert_iter()
102@@ -2132,17 +2145,39 @@
103 continue
104 else:
105 self.buffer.select_range(start, end)
106+ self._apply_match(start, end)
107 return True
108 for start, end, _ in self._check_range(lastline, line, -1):
109 self.buffer.select_range(start, end)
110+ self._apply_match(start, end)
111 return True
112
113 self.buffer.unset_selection()
114+ self._apply_match(None, None)
115 return False
116
117- def set_highlight(self, highlight):
118+ def select_match(self):
119+ # Select last match
120+ bounds = self.match_bounds
121+ if not None in bounds:
122+ self.buffer.select_range(*bounds)
123+
124+ def _apply_match(self, start, end):
125+ # Sets and displays new match bounds. (None, None) means "no match".
126+ self.buffer.remove_tag(self.match_tag, *self.buffer.get_bounds())
127+
128+ self.match_bounds = (start, end)
129+ if start and end:
130+ self.buffer.apply_tag(self.match_tag, start, end)
131+
132+ def set_highlight(self, highlight, match = True):
133+ # Sets or clears highlight and match tags
134 self.highlight = highlight
135 self._update_highlight()
136+ if match:
137+ self._apply_match(*self.match_bounds)
138+ else:
139+ self._apply_match(None, None)
140 # TODO we could connect to buffer signals to update highlighting
141 # when the buffer is modified.
142
143@@ -2597,7 +2632,6 @@
144 return False
145 elif multi_line_indent(start, end):
146 level = []
147- buffer.strip_selection()
148 buffer.foreach_line_in_selection(
149 lambda l: level.append(buffer.get_indent(l)) )
150 if level and min(level) > 0:
151@@ -3492,7 +3526,7 @@
152 if cursorpos != -1:
153 buffer.place_cursor(buffer.get_iter_at_offset(cursorpos))
154
155- self.view.scroll_to_mark(buffer.get_insert(), 0.3)
156+ self.view.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
157
158 self._buffer_signals = (
159 buffer.connect('textstyle-changed', self.do_textstyle_changed),
160@@ -3584,7 +3618,7 @@
161 def set_cursor_pos(self, pos):
162 buffer = self.view.get_buffer()
163 buffer.place_cursor(buffer.get_iter_at_offset(pos))
164- self.view.scroll_to_mark(buffer.get_insert(), 0.2)
165+ self.view.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
166
167 def get_cursor_pos(self):
168 buffer = self.view.get_buffer()
169@@ -4114,7 +4148,7 @@
170 self.hide_find() # remove previous highlighting etc.
171 buffer = self.view.get_buffer()
172 buffer.finder.find(string, flags)
173- self.view.scroll_to_mark(buffer.get_insert(), 0.3)
174+ self.view.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
175
176 def show_find(self, string=None, flags=0, highlight=False):
177 self.find_bar.show()
178@@ -4675,7 +4709,7 @@
179 button.set_sensitive(ok)
180
181 if ok:
182- self.textview.scroll_to_mark(buffer.get_insert(), 0.3)
183+ self.textview.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
184
185 def on_find_entry_activate(self):
186 self.on_find_entry_changed()
187@@ -4700,13 +4734,13 @@
188 def find_next(self):
189 buffer = self.textview.get_buffer()
190 buffer.finder.find_next()
191- self.textview.scroll_to_mark(buffer.get_insert(), 0.3)
192+ self.textview.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
193 self.textview.grab_focus()
194
195 def find_previous(self):
196 buffer = self.textview.get_buffer()
197 buffer.finder.find_previous()
198- self.textview.scroll_to_mark(buffer.get_insert(), 0.3)
199+ self.textview.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
200 self.textview.grab_focus()
201
202
203@@ -4755,13 +4789,14 @@
204 self.pack_start(self.highlight_checkbox, False)
205
206 close_button = IconButton(gtk.STOCK_CLOSE, relief=False)
207- close_button.connect_object('clicked', self.__class__.hide, self)
208+ close_button.connect_object('clicked', self.__class__.do_close_clicked, self)
209 self.pack_end(close_button, False)
210
211 def grab_focus(self):
212 self.find_entry.grab_focus()
213
214 def show(self):
215+ self.on_highlight_toggled()
216 self.set_no_show_all(False)
217 self.show_all()
218
219@@ -4769,7 +4804,11 @@
220 gtk.HBox.hide(self)
221 self.set_no_show_all(True)
222 buffer = self.textview.get_buffer()
223- buffer.finder.set_highlight(False)
224+ buffer.finder.set_highlight(False, False)
225+
226+ def do_close_clicked(self):
227+ self.hide()
228+ self.textview.grab_focus()
229
230 def on_find_entry_activate(self):
231 self.on_find_entry_changed()
232@@ -4778,6 +4817,7 @@
233 def do_key_press_event(self, event):
234 if event.keyval == KEYVAL_ESC:
235 self.hide()
236+ self.textview.grab_focus()
237 return True
238 else:
239 return gtk.HBox.do_key_press_event(self, event)
240@@ -4843,6 +4883,11 @@
241 buffer = self.textview.get_buffer()
242 buffer.finder.replace_all(string)
243
244+ def do_response(self, id):
245+ Dialog.do_response(self, id)
246+ buffer = self.textview.get_buffer()
247+ buffer.finder.set_highlight(False, False)
248+
249
250 class WordCountDialog(Dialog):
251
252
253=== modified file 'zim/gui/searchdialog.py'
254--- zim/gui/searchdialog.py 2011-05-06 14:54:51 +0000
255+++ zim/gui/searchdialog.py 2011-06-19 20:42:37 +0000
256@@ -68,6 +68,12 @@
257 button.connect('clicked', search)
258 self.query_entry.connect('activate', search)
259
260+ def set_from_buffer(self):
261+ '''Copies selected text of page to query entry, if there is any.'''
262+ selection = self.ui.mainwindow.pageview.get_selection()
263+ if selection:
264+ self.query_entry.set_text(selection)
265+
266
267 class SearchResultsTreeView(BrowserTreeView):
268