##// END OF EJS Templates
Merge pull request #7474 from mathieu1/tooltip-prevent-default...
Matthias Bussonnier -
r19963:5838bae0 merge
parent child Browse files
Show More
@@ -1,317 +1,321
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 // tooltip constructor
11 // tooltip constructor
12 var Tooltip = function (events) {
12 var Tooltip = function (events) {
13 var that = this;
13 var that = this;
14 this.events = events;
14 this.events = events;
15 this.time_before_tooltip = 1200;
15 this.time_before_tooltip = 1200;
16
16
17 // handle to html
17 // handle to html
18 this.tooltip = $('#tooltip');
18 this.tooltip = $('#tooltip');
19 this._hidden = true;
19 this._hidden = true;
20
20
21 // variable for consecutive call
21 // variable for consecutive call
22 this._old_cell = null;
22 this._old_cell = null;
23 this._old_request = null;
23 this._old_request = null;
24 this._consecutive_counter = 0;
24 this._consecutive_counter = 0;
25
25
26 // 'sticky ?'
26 // 'sticky ?'
27 this._sticky = false;
27 this._sticky = false;
28
28
29 // display tooltip if the docstring is empty?
29 // display tooltip if the docstring is empty?
30 this._hide_if_no_docstring = false;
30 this._hide_if_no_docstring = false;
31
31
32 // contain the button in the upper right corner
32 // contain the button in the upper right corner
33 this.buttons = $('<div/>').addClass('tooltipbuttons');
33 this.buttons = $('<div/>').addClass('tooltipbuttons');
34
34
35 // will contain the docstring
35 // will contain the docstring
36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
36 this.text = $('<div/>').addClass('tooltiptext').addClass('smalltooltip');
37
37
38 // build the buttons menu on the upper right
38 // build the buttons menu on the upper right
39 // expand the tooltip to see more
39 // expand the tooltip to see more
40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
40 var expandlink = $('<a/>').attr('href', "#").addClass("ui-corner-all") //rounded corner
41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
41 .attr('role', "button").attr('id', 'expanbutton').attr('title', 'Grow the tooltip vertically (press shift-tab twice)').click(function () {
42 that.expand();
42 that.expand();
43 event.preventDefault();
43 }).append(
44 }).append(
44 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
45 $('<span/>').text('Expand').addClass('ui-icon').addClass('ui-icon-plus'));
45
46
46 // open in pager
47 // open in pager
47 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
48 var morelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button').attr('title', 'show the current docstring in pager (press shift-tab 4 times)');
48 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
49 var morespan = $('<span/>').text('Open in Pager').addClass('ui-icon').addClass('ui-icon-arrowstop-l-n');
49 morelink.append(morespan);
50 morelink.append(morespan);
50 morelink.click(function () {
51 morelink.click(function () {
51 that.showInPager(that._old_cell);
52 that.showInPager(that._old_cell);
53 event.preventDefault();
52 });
54 });
53
55
54 // close the tooltip
56 // close the tooltip
55 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
57 var closelink = $('<a/>').attr('href', "#").attr('role', "button").addClass('ui-button');
56 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
58 var closespan = $('<span/>').text('Close').addClass('ui-icon').addClass('ui-icon-close');
57 closelink.append(closespan);
59 closelink.append(closespan);
58 closelink.click(function () {
60 closelink.click(function () {
59 that.remove_and_cancel_tooltip(true);
61 that.remove_and_cancel_tooltip(true);
62 event.preventDefault();
60 });
63 });
61
64
62 this._clocklink = $('<a/>').attr('href', "#");
65 this._clocklink = $('<a/>').attr('href', "#");
63 this._clocklink.attr('role', "button");
66 this._clocklink.attr('role', "button");
64 this._clocklink.addClass('ui-button');
67 this._clocklink.addClass('ui-button');
65 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
68 this._clocklink.attr('title', 'Tootip is not dismissed while typing for 10 seconds');
66 var clockspan = $('<span/>').text('Close');
69 var clockspan = $('<span/>').text('Close');
67 clockspan.addClass('ui-icon');
70 clockspan.addClass('ui-icon');
68 clockspan.addClass('ui-icon-clock');
71 clockspan.addClass('ui-icon-clock');
69 this._clocklink.append(clockspan);
72 this._clocklink.append(clockspan);
70 this._clocklink.click(function () {
73 this._clocklink.click(function () {
71 that.cancel_stick();
74 that.cancel_stick();
75 event.preventDefault();
72 });
76 });
73
77
74
78
75
79
76
80
77 //construct the tooltip
81 //construct the tooltip
78 // add in the reverse order you want them to appear
82 // add in the reverse order you want them to appear
79 this.buttons.append(closelink);
83 this.buttons.append(closelink);
80 this.buttons.append(expandlink);
84 this.buttons.append(expandlink);
81 this.buttons.append(morelink);
85 this.buttons.append(morelink);
82 this.buttons.append(this._clocklink);
86 this.buttons.append(this._clocklink);
83 this._clocklink.hide();
87 this._clocklink.hide();
84
88
85
89
86 // we need a phony element to make the small arrow
90 // we need a phony element to make the small arrow
87 // of the tooltip in css
91 // of the tooltip in css
88 // we will move the arrow later
92 // we will move the arrow later
89 this.arrow = $('<div/>').addClass('pretooltiparrow');
93 this.arrow = $('<div/>').addClass('pretooltiparrow');
90 this.tooltip.append(this.buttons);
94 this.tooltip.append(this.buttons);
91 this.tooltip.append(this.arrow);
95 this.tooltip.append(this.arrow);
92 this.tooltip.append(this.text);
96 this.tooltip.append(this.text);
93
97
94 // function that will be called if you press tab 1, 2, 3... times in a row
98 // function that will be called if you press tab 1, 2, 3... times in a row
95 this.tabs_functions = [function (cell, text, cursor) {
99 this.tabs_functions = [function (cell, text, cursor) {
96 that._request_tooltip(cell, text, cursor);
100 that._request_tooltip(cell, text, cursor);
97 }, function () {
101 }, function () {
98 that.expand();
102 that.expand();
99 }, function () {
103 }, function () {
100 that.stick();
104 that.stick();
101 }, function (cell) {
105 }, function (cell) {
102 that.cancel_stick();
106 that.cancel_stick();
103 that.showInPager(cell);
107 that.showInPager(cell);
104 }];
108 }];
105 // call after all the tabs function above have bee call to clean their effects
109 // call after all the tabs function above have bee call to clean their effects
106 // if necessary
110 // if necessary
107 this.reset_tabs_function = function (cell, text) {
111 this.reset_tabs_function = function (cell, text) {
108 this._old_cell = (cell) ? cell : null;
112 this._old_cell = (cell) ? cell : null;
109 this._old_request = (text) ? text : null;
113 this._old_request = (text) ? text : null;
110 this._consecutive_counter = 0;
114 this._consecutive_counter = 0;
111 };
115 };
112 };
116 };
113
117
114 Tooltip.prototype.is_visible = function () {
118 Tooltip.prototype.is_visible = function () {
115 return !this._hidden;
119 return !this._hidden;
116 };
120 };
117
121
118 Tooltip.prototype.showInPager = function (cell) {
122 Tooltip.prototype.showInPager = function (cell) {
119 /**
123 /**
120 * reexecute last call in pager by appending ? to show back in pager
124 * reexecute last call in pager by appending ? to show back in pager
121 */
125 */
122 this.events.trigger('open_with_text.Pager', this._reply.content);
126 this.events.trigger('open_with_text.Pager', this._reply.content);
123 this.remove_and_cancel_tooltip();
127 this.remove_and_cancel_tooltip();
124 };
128 };
125
129
126 // grow the tooltip verticaly
130 // grow the tooltip verticaly
127 Tooltip.prototype.expand = function () {
131 Tooltip.prototype.expand = function () {
128 this.text.removeClass('smalltooltip');
132 this.text.removeClass('smalltooltip');
129 this.text.addClass('bigtooltip');
133 this.text.addClass('bigtooltip');
130 $('#expanbutton').hide('slow');
134 $('#expanbutton').hide('slow');
131 };
135 };
132
136
133 // deal with all the logic of hiding the tooltip
137 // deal with all the logic of hiding the tooltip
134 // and reset it's status
138 // and reset it's status
135 Tooltip.prototype._hide = function () {
139 Tooltip.prototype._hide = function () {
136 this._hidden = true;
140 this._hidden = true;
137 this.tooltip.fadeOut('fast');
141 this.tooltip.fadeOut('fast');
138 $('#expanbutton').show('slow');
142 $('#expanbutton').show('slow');
139 this.text.removeClass('bigtooltip');
143 this.text.removeClass('bigtooltip');
140 this.text.addClass('smalltooltip');
144 this.text.addClass('smalltooltip');
141 // keep scroll top to be sure to always see the first line
145 // keep scroll top to be sure to always see the first line
142 this.text.scrollTop(0);
146 this.text.scrollTop(0);
143 this.code_mirror = null;
147 this.code_mirror = null;
144 };
148 };
145
149
146 // return true on successfully removing a visible tooltip; otherwise return
150 // return true on successfully removing a visible tooltip; otherwise return
147 // false.
151 // false.
148 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
152 Tooltip.prototype.remove_and_cancel_tooltip = function (force) {
149 /**
153 /**
150 * note that we don't handle closing directly inside the calltip
154 * note that we don't handle closing directly inside the calltip
151 * as in the completer, because it is not focusable, so won't
155 * as in the completer, because it is not focusable, so won't
152 * get the event.
156 * get the event.
153 */
157 */
154 this.cancel_pending();
158 this.cancel_pending();
155 if (!this._hidden) {
159 if (!this._hidden) {
156 if (force || !this._sticky) {
160 if (force || !this._sticky) {
157 this.cancel_stick();
161 this.cancel_stick();
158 this._hide();
162 this._hide();
159 }
163 }
160 this.reset_tabs_function();
164 this.reset_tabs_function();
161 return true;
165 return true;
162 } else {
166 } else {
163 return false;
167 return false;
164 }
168 }
165 };
169 };
166
170
167 // cancel autocall done after '(' for example.
171 // cancel autocall done after '(' for example.
168 Tooltip.prototype.cancel_pending = function () {
172 Tooltip.prototype.cancel_pending = function () {
169 if (this._tooltip_timeout !== null) {
173 if (this._tooltip_timeout !== null) {
170 clearTimeout(this._tooltip_timeout);
174 clearTimeout(this._tooltip_timeout);
171 this._tooltip_timeout = null;
175 this._tooltip_timeout = null;
172 }
176 }
173 };
177 };
174
178
175 // will trigger tooltip after timeout
179 // will trigger tooltip after timeout
176 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
180 Tooltip.prototype.pending = function (cell, hide_if_no_docstring) {
177 var that = this;
181 var that = this;
178 this._tooltip_timeout = setTimeout(function () {
182 this._tooltip_timeout = setTimeout(function () {
179 that.request(cell, hide_if_no_docstring);
183 that.request(cell, hide_if_no_docstring);
180 }, that.time_before_tooltip);
184 }, that.time_before_tooltip);
181 };
185 };
182
186
183 // easy access for julia monkey patching.
187 // easy access for julia monkey patching.
184 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
188 Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi;
185
189
186 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
190 Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) {
187 var callbacks = $.proxy(this._show, this);
191 var callbacks = $.proxy(this._show, this);
188 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
192 var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks);
189 };
193 };
190
194
191 // make an immediate completion request
195 // make an immediate completion request
192 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
196 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
193 /**
197 /**
194 * request(codecell)
198 * request(codecell)
195 * Deal with extracting the text from the cell and counting
199 * Deal with extracting the text from the cell and counting
196 * call in a row
200 * call in a row
197 */
201 */
198 this.cancel_pending();
202 this.cancel_pending();
199 var editor = cell.code_mirror;
203 var editor = cell.code_mirror;
200 var cursor = editor.getCursor();
204 var cursor = editor.getCursor();
201 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
205 var cursor_pos = utils.to_absolute_cursor_pos(editor, cursor);
202 var text = cell.get_text();
206 var text = cell.get_text();
203
207
204 this._hide_if_no_docstring = hide_if_no_docstring;
208 this._hide_if_no_docstring = hide_if_no_docstring;
205
209
206 if(editor.somethingSelected()){
210 if(editor.somethingSelected()){
207 // get only the most recent selection.
211 // get only the most recent selection.
208 text = editor.getSelection();
212 text = editor.getSelection();
209 }
213 }
210
214
211 // need a permanent handle to code_mirror for future auto recall
215 // need a permanent handle to code_mirror for future auto recall
212 this.code_mirror = editor;
216 this.code_mirror = editor;
213
217
214 // now we treat the different number of keypress
218 // now we treat the different number of keypress
215 // first if same cell, same text, increment counter by 1
219 // first if same cell, same text, increment counter by 1
216 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
220 if (this._old_cell == cell && this._old_request == text && this._hidden === false) {
217 this._consecutive_counter++;
221 this._consecutive_counter++;
218 } else {
222 } else {
219 // else reset
223 // else reset
220 this.cancel_stick();
224 this.cancel_stick();
221 this.reset_tabs_function (cell, text);
225 this.reset_tabs_function (cell, text);
222 }
226 }
223
227
224 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
228 this.tabs_functions[this._consecutive_counter](cell, text, cursor_pos);
225
229
226 // then if we are at the end of list function, reset
230 // then if we are at the end of list function, reset
227 if (this._consecutive_counter == this.tabs_functions.length) {
231 if (this._consecutive_counter == this.tabs_functions.length) {
228 this.reset_tabs_function (cell, text, cursor);
232 this.reset_tabs_function (cell, text, cursor);
229 }
233 }
230
234
231 return;
235 return;
232 };
236 };
233
237
234 // cancel the option of having the tooltip to stick
238 // cancel the option of having the tooltip to stick
235 Tooltip.prototype.cancel_stick = function () {
239 Tooltip.prototype.cancel_stick = function () {
236 clearTimeout(this._stick_timeout);
240 clearTimeout(this._stick_timeout);
237 this._stick_timeout = null;
241 this._stick_timeout = null;
238 this._clocklink.hide('slow');
242 this._clocklink.hide('slow');
239 this._sticky = false;
243 this._sticky = false;
240 };
244 };
241
245
242 // put the tooltip in a sicky state for 10 seconds
246 // put the tooltip in a sicky state for 10 seconds
243 // it won't be removed by remove_and_cancell() unless you called with
247 // it won't be removed by remove_and_cancell() unless you called with
244 // the first parameter set to true.
248 // the first parameter set to true.
245 // remove_and_cancell_tooltip(true)
249 // remove_and_cancell_tooltip(true)
246 Tooltip.prototype.stick = function (time) {
250 Tooltip.prototype.stick = function (time) {
247 time = (time !== undefined) ? time : 10;
251 time = (time !== undefined) ? time : 10;
248 var that = this;
252 var that = this;
249 this._sticky = true;
253 this._sticky = true;
250 this._clocklink.show('slow');
254 this._clocklink.show('slow');
251 this._stick_timeout = setTimeout(function () {
255 this._stick_timeout = setTimeout(function () {
252 that._sticky = false;
256 that._sticky = false;
253 that._clocklink.hide('slow');
257 that._clocklink.hide('slow');
254 }, time * 1000);
258 }, time * 1000);
255 };
259 };
256
260
257 // should be called with the kernel reply to actually show the tooltip
261 // should be called with the kernel reply to actually show the tooltip
258 Tooltip.prototype._show = function (reply) {
262 Tooltip.prototype._show = function (reply) {
259 /**
263 /**
260 * move the bubble if it is not hidden
264 * move the bubble if it is not hidden
261 * otherwise fade it
265 * otherwise fade it
262 */
266 */
263 this._reply = reply;
267 this._reply = reply;
264 var content = reply.content;
268 var content = reply.content;
265 if (!content.found) {
269 if (!content.found) {
266 // object not found, nothing to show
270 // object not found, nothing to show
267 return;
271 return;
268 }
272 }
269 this.name = content.name;
273 this.name = content.name;
270
274
271 // do some math to have the tooltip arrow on more or less on left or right
275 // do some math to have the tooltip arrow on more or less on left or right
272 // width of the editor
276 // width of the editor
273 var w = $(this.code_mirror.getScrollerElement()).width();
277 var w = $(this.code_mirror.getScrollerElement()).width();
274 // ofset of the editor
278 // ofset of the editor
275 var o = $(this.code_mirror.getScrollerElement()).offset();
279 var o = $(this.code_mirror.getScrollerElement()).offset();
276
280
277 // whatever anchor/head order but arrow at mid x selection
281 // whatever anchor/head order but arrow at mid x selection
278 var anchor = this.code_mirror.cursorCoords(false);
282 var anchor = this.code_mirror.cursorCoords(false);
279 var head = this.code_mirror.cursorCoords(true);
283 var head = this.code_mirror.cursorCoords(true);
280 var xinit = (head.left+anchor.left)/2;
284 var xinit = (head.left+anchor.left)/2;
281 var xinter = o.left + (xinit - o.left) / w * (w - 450);
285 var xinter = o.left + (xinit - o.left) / w * (w - 450);
282 var posarrowleft = xinit - xinter;
286 var posarrowleft = xinit - xinter;
283
287
284 if (this._hidden === false) {
288 if (this._hidden === false) {
285 this.tooltip.animate({
289 this.tooltip.animate({
286 'left': xinter - 30 + 'px',
290 'left': xinter - 30 + 'px',
287 'top': (head.bottom + 10) + 'px'
291 'top': (head.bottom + 10) + 'px'
288 });
292 });
289 } else {
293 } else {
290 this.tooltip.css({
294 this.tooltip.css({
291 'left': xinter - 30 + 'px'
295 'left': xinter - 30 + 'px'
292 });
296 });
293 this.tooltip.css({
297 this.tooltip.css({
294 'top': (head.bottom + 10) + 'px'
298 'top': (head.bottom + 10) + 'px'
295 });
299 });
296 }
300 }
297 this.arrow.animate({
301 this.arrow.animate({
298 'left': posarrowleft + 'px'
302 'left': posarrowleft + 'px'
299 });
303 });
300
304
301 this._hidden = false;
305 this._hidden = false;
302 this.tooltip.fadeIn('fast');
306 this.tooltip.fadeIn('fast');
303 this.text.children().remove();
307 this.text.children().remove();
304
308
305 // This should support rich data types, but only text/plain for now
309 // This should support rich data types, but only text/plain for now
306 // Any HTML within the docstring is escaped by the fixConsole() method.
310 // Any HTML within the docstring is escaped by the fixConsole() method.
307 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
311 var pre = $('<pre/>').html(utils.fixConsole(content.data['text/plain']));
308 this.text.append(pre);
312 this.text.append(pre);
309 // keep scroll top to be sure to always see the first line
313 // keep scroll top to be sure to always see the first line
310 this.text.scrollTop(0);
314 this.text.scrollTop(0);
311 };
315 };
312
316
313 // Backwards compatibility.
317 // Backwards compatibility.
314 IPython.Tooltip = Tooltip;
318 IPython.Tooltip = Tooltip;
315
319
316 return {'Tooltip': Tooltip};
320 return {'Tooltip': Tooltip};
317 });
321 });
General Comments 0
You need to be logged in to leave comments. Login now