Merge lp:~oliver-joos/zim/selection-fixes into lp:~jaap.karssenberg/zim/pyzim
- selection-fixes
- Merge into pyzim
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jaap Karssenberg | Approve | ||
Oliver Joos (community) | Needs Resubmitting | ||
Review via email: mp+64340@code.launchpad.net |
Commit message
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.
- 411. By Oliver Joos
-
Fix for broken search_backlinks due to selection on page.
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 ?
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.
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
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.
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.
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
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.
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
Jaap Karssenberg (jaap.karssenberg) wrote : | # |
Now integrating this branch - with some cleanups but keeping the idea of the matching tag.
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.
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.
Jaap Karssenberg (jaap.karssenberg) wrote : | # |
This can be easily fixed by setting an attribute in SearchDialog.
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.
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:/
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.
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:/
> 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
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 |
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".