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