##// END OF EJS Templates
clean code, show clock if tooltip is 'sticky'...
Matthias BUSSONNIER -
Show More
@@ -1,340 +1,360
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Tooltip
9 // Tooltip
10 //============================================================================
10 //============================================================================
11 //
11 //
12 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
12 // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms
13 //
13 //
14 // you can configure the differents action of pressing tab several times in a row by
14 // you can configure the differents action of pressing tab several times in a row by
15 // setting/appending different fonction in the array
15 // setting/appending different fonction in the array
16 // IPython.tooltip.tabs_functions
16 // IPython.tooltip.tabs_functions
17 //
17 //
18 // eg :
18 // eg :
19 // IPython.tooltip.tabs_functions[4] = function(){console.log('this is the action of the 4th tab pressing')}
19 // IPython.tooltip.tabs_functions[4] = function(){console.log('this is the action of the 4th tab pressing')}
20 //
20 //
21
21
22 var IPython = (function (IPython) {
22 var IPython = (function (IPython) {
23 "use strict";
23
24
24 var utils = IPython.utils;
25 var utils = IPython.utils;
25
26
26 // tooltip constructor
27 // tooltip constructor
27 var Tooltip = function () {
28 var Tooltip = function () {
28 var that = this;
29 var that = this;
29 this.time_before_tooltip = 1200;
30 this.time_before_tooltip = 1200;
30
31
31 // handle to html
32 // handle to html
32 this.tooltip = $('#tooltip');
33 this.tooltip = $('#tooltip');
33 this._hidden = true;
34 this._hidden = true;
34
35
35 // variable for consecutive call
36 // variable for consecutive call
36 this._old_cell = null ;
37 this._old_cell = null ;
37 this._old_request = null ;
38 this._old_request = null ;
38 this._consecutive_counter = 0;
39 this._consecutive_counter = 0;
39
40
40 // 'sticky ?'
41 // 'sticky ?'
41 this._sticky = false;
42 this._sticky = false;
42
43
43 // contain the button in the upper right corner
44 // contain the button in the upper right corner
44 this.buttons = $('<div/>')
45 this.buttons = $('<div/>')
45 .addClass('tooltipbuttons');
46 .addClass('tooltipbuttons');
46
47
47 // will contain the docstring
48 // will contain the docstring
48 this.text = $('<div/>')
49 this.text = $('<div/>')
49 .addClass('tooltiptext')
50 .addClass('tooltiptext')
50 .addClass('smalltooltip');
51 .addClass('smalltooltip');
51
52
52 // build the buttons menu on the upper right
53 // build the buttons menu on the upper right
53
54
54 // expand the tooltip to see more
55 // expand the tooltip to see more
55 var expandlink=$('<a/>').attr('href',"#")
56 var expandlink=$('<a/>').attr('href',"#")
56 .addClass("ui-corner-all") //rounded corner
57 .addClass("ui-corner-all") //rounded corner
57 .attr('role',"button")
58 .attr('role',"button")
58 .attr('id','expanbutton')
59 .attr('id','expanbutton')
60 .attr('title','Grow the tooltip vertically (press tab 2 times)')
59 .click(function(){that.expand()})
61 .click(function(){that.expand()})
60 .append(
62 .append(
61 $('<span/>').text('Expand')
63 $('<span/>').text('Expand')
62 .addClass('ui-icon')
64 .addClass('ui-icon')
63 .addClass('ui-icon-plus')
65 .addClass('ui-icon-plus')
64 );
66 );
65
67
66 // open in pager
68 // open in pager
67 var morelink=$('<a/>').attr('href',"#")
69 var morelink=$('<a/>').attr('href',"#")
68 .attr('role',"button")
70 .attr('role',"button")
69 .addClass('ui-button');
71 .addClass('ui-button')
72 .attr('title','show the current docstring in pager (press tab 4 times)');
70 var morespan=$('<span/>').text('Open in Pager')
73 var morespan=$('<span/>').text('Open in Pager')
71 .addClass('ui-icon')
74 .addClass('ui-icon')
72 .addClass('ui-icon-arrowstop-l-n');
75 .addClass('ui-icon-arrowstop-l-n');
73 morelink.append(morespan);
76 morelink.append(morespan);
74 morelink.click(function(){
77 morelink.click(function(){
75 that.showInPager();
78 that.showInPager();
76 });
79 });
77
80
78 // close the tooltip
81 // close the tooltip
79 var closelink=$('<a/>').attr('href',"#");
82 var closelink=$('<a/>').attr('href',"#")
80 closelink.attr('role',"button");
83 .attr('role',"button")
81 closelink.addClass('ui-button');
84 .addClass('ui-button');
82 var closespan=$('<span/>').text('Close');
85 var closespan=$('<span/>').text('Close')
83 closespan.addClass('ui-icon');
86 .addClass('ui-icon')
84 closespan.addClass('ui-icon-close');
87 .addClass('ui-icon-close');
85 closelink.append(closespan);
88 closelink.append(closespan);
86 closelink.click(function(){
89 closelink.click(function(){
87 that.remove_and_cancel_tooltip(true);
90 that.remove_and_cancel_tooltip(true);
88 });
91 });
89
92
93 this._clocklink=$('<a/>').attr('href',"#");
94 this._clocklink.attr('role',"button");
95 this._clocklink.addClass('ui-button');
96 this._clocklink.attr('title','Tootip is not dismissed while typing for 10 seconds');
97 var clockspan=$('<span/>').text('Close');
98 clockspan.addClass('ui-icon');
99 clockspan.addClass('ui-icon-clock');
100 this._clocklink.append(clockspan);
101 this._clocklink.click(function(){
102 that.cancel_stick();
103 });
104
105
106
107
90 //construct the tooltip
108 //construct the tooltip
91 // add in the reverse order you want them to appear
109 // add in the reverse order you want them to appear
92 this.buttons.append(closelink);
110 this.buttons.append(closelink);
93 this.buttons.append(expandlink);
111 this.buttons.append(expandlink);
94 this.buttons.append(morelink);
112 this.buttons.append(morelink);
113 this.buttons.append(this._clocklink);
114 this._clocklink.hide();
115
95
116
96 // we need a phony element to make the small arrow
117 // we need a phony element to make the small arrow
97 // of the tooltip in css
118 // of the tooltip in css
98 // we will move the arrow later
119 // we will move the arrow later
99 this.arrow = $('<div/>').addClass('pretooltiparrow');
120 this.arrow = $('<div/>').addClass('pretooltiparrow');
100 this.tooltip.append(this.buttons);
121 this.tooltip.append(this.buttons);
101 this.tooltip.append(this.arrow);
122 this.tooltip.append(this.arrow);
102 this.tooltip.append(this.text);
123 this.tooltip.append(this.text);
103
124
104 this.tabs_functions = [
125 // function that will be called if you press tab 1, 2, 3... times in a row
126 this.tabs_functions = [ function(cell,text){that._request_tooltip(cell,text)},
105 function(){that.expand()},
127 function(){that.expand()},
106 function(){that.stick()},
128 function(){that.stick()},
107 function(){
129 function(){
108 that.cancel_stick();
130 that.cancel_stick();
109 that.showInPager();
131 that.showInPager();
110 that._cmfocus();
132 that._cmfocus();
111 }
133 }
112 ];
134 ];
113 // call after all the tabs function above have bee call to clean their effects
135 // call after all the tabs function above have bee call to clean their effects
114 // if necessary
136 // if necessary
115 this.reset_tabs_function = function(cell,text){
137 this.reset_tabs_function = function(cell,text){
116 this._old_cell = (cell)? cell : null ;
138 this._old_cell = (cell)? cell : null ;
117 this._old_request = (text) ? text : null ;
139 this._old_request = (text) ? text : null ;
118 this._consecutive_counter = 0;
140 this._consecutive_counter = 0;
119 this.cancel_stick();
120 }
141 }
121 };
142 };
122
143
123 Tooltip.prototype.showInPager = function()
144 Tooltip.prototype.showInPager = function()
124 {
145 {
125 // reexecute last call in pager by appending ? to show back in pager
146 // reexecute last call in pager by appending ? to show back in pager
126 var that = this;
147 var that = this;
127 var callbacks = {'execute_reply': $.proxy(that._handle_execute_reply,that)}
148 var callbacks = {'execute_reply': $.proxy(that._handle_execute_reply,that)}
128 var msg_id = IPython.notebook.kernel.execute(this.name+"?", callbacks);
149 var msg_id = IPython.notebook.kernel.execute(this.name+"?", callbacks);
129
150
130 this.remove_and_cancel_tooltip();
151 this.remove_and_cancel_tooltip();
131 this._cmfocus();
152 this._cmfocus();
132 }
153 }
133
154
134 // grow the tooltip verticaly
155 // grow the tooltip verticaly
135 Tooltip.prototype.expand = function(){
156 Tooltip.prototype.expand = function(){
136 this.text.removeClass('smalltooltip');
157 this.text.removeClass('smalltooltip');
137 this.text.addClass('bigtooltip');
158 this.text.addClass('bigtooltip');
138 $('#expanbutton').addClass('hidden');
159 $('#expanbutton').hide('slow');
139 this._cmfocus();
160 this._cmfocus();
140 }
161 }
141
162
142 // deal with all the logic of hiding the tooltip
163 // deal with all the logic of hiding the tooltip
143 // and reset it's status
164 // and reset it's status
144 Tooltip.prototype.hide = function()
165 Tooltip.prototype.hide = function()
145 {
166 {
146 this.tooltip.addClass('hide');
167 this.tooltip.addClass('hide');
147 $('#expanbutton').removeClass('hidden');
168 $('#expanbutton').show('slow');
148 this.text.removeClass('bigtooltip');
169 this.text.removeClass('bigtooltip');
149 this.text.addClass('smalltooltip');
170 this.text.addClass('smalltooltip');
150 // keep scroll top to be sure to always see the first line
171 // keep scroll top to be sure to always see the first line
151 this.text.scrollTop(0);
172 this.text.scrollTop(0);
152 this._hidden = true;
173 this._hidden = true;
153 }
174 }
154
175
155 Tooltip.prototype.remove_and_cancel_tooltip = function(force) {
176 Tooltip.prototype.remove_and_cancel_tooltip = function(force) {
156 // note that we don't handle closing directly inside the calltip
177 // note that we don't handle closing directly inside the calltip
157 // as in the completer, because it is not focusable, so won't
178 // as in the completer, because it is not focusable, so won't
158 // get the event.
179 // get the event.
159 if(this._sticky == false || force == true)
180 if(this._sticky == false || force == true)
160 {
181 {
161 this.hide();
182 this.hide();
162 }
183 }
163 this.cancel_pending();
184 this.cancel_pending();
164 this._old_cell = null ;
185 this.reset_tabs_function();
165 this._old_request = null ;
166 this._consecutive_counter = 0;
167 }
186 }
168
187
169 // cancel autocall done after '(' for example.
188 // cancel autocall done after '(' for example.
170 Tooltip.prototype.cancel_pending = function(){
189 Tooltip.prototype.cancel_pending = function(){
171 if (this.tooltip_timeout != null){
190 if (this._tooltip_timeout != null){
172 clearTimeout(this.tooltip_timeout);
191 clearTimeout(this._tooltip_timeout);
173 this.tooltip_timeout = null;
192 this._tooltip_timeout = null;
174 }
193 }
175 }
194 }
176
195
177 // will trigger tooltip after timeout
196 // will trigger tooltip after timeout
178 Tooltip.prototype.pending = function(cell,text)
197 Tooltip.prototype.pending = function(cell)
179 {
198 {
180 var that = this;
199 var that = this;
181 this.tooltip_timeout = setTimeout(function(){that.request(cell)} , that.time_before_tooltip);
200 this._tooltip_timeout = setTimeout(function(){that.request(cell)} , that.time_before_tooltip);
182 }
201 }
183
202
184 Tooltip.prototype._request_tooltip = function(func)
203 Tooltip.prototype._request_tooltip = function(cell,func)
185 {
204 {
186 // use internally just to maek the request to the kernel
205 // use internally just to make the request to the kernel
187
206
188 // Feel free to shorten this logic if you are better
207 // Feel free to shorten this logic if you are better
189 // than me in regEx
208 // than me in regEx
190 // basicaly you shoul be able to get xxx.xxx.xxx from
209 // 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,
210 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
192 // remove everything between matchin bracket (need to iterate)
211 // remove everything between matchin bracket (need to iterate)
193 var matchBracket = /\([^\(\)]+\)/g;
212 var matchBracket = /\([^\(\)]+\)/g;
194 var endBracket = /\([^\(]*$/g;
213 var endBracket = /\([^\(]*$/g;
195 var oldfunc = func;
214 var oldfunc = func;
196
215
197 func = func.replace(matchBracket,"");
216 func = func.replace(matchBracket,"");
198 while( oldfunc != func )
217 while( oldfunc != func )
199 {
218 {
200 oldfunc = func;
219 oldfunc = func;
201 func = func.replace(matchBracket,"");
220 func = func.replace(matchBracket,"");
202 }
221 }
203 // remove everythin after last open bracket
222 // remove everything after last open bracket
204 func = func.replace(endBracket,"");
223 func = func.replace(endBracket,"");
224
205 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
225 var re = /[a-z_][0-9a-z._]+$/gi; // casse insensitive
206 var callbacks = {'object_info_reply': $.proxy(this.show,this)}
226 var callbacks = {'object_info_reply': $.proxy(this.show,this)}
207 var msg_id = IPython.notebook.kernel.object_info_request(re.exec(func), callbacks);
227 var msg_id = IPython.notebook.kernel.object_info_request(re.exec(func), callbacks);
208 }
228 }
209
229
210 // make an imediate completion request
230 // make an imediate completion request
211 Tooltip.prototype.request = function(cell)
231 Tooltip.prototype.request = function(cell)
212 {
232 {
213 // request(codecell)
233 // request(codecell)
214 // Deal with extracting the text from the cell and counting
234 // Deal with extracting the text from the cell and counting
215 // call in a row
235 // call in a row
216
236
217 this.cancel_pending();
237 this.cancel_pending();
218 var editor = cell.code_mirror;
238 var editor = cell.code_mirror;
219 var cursor = editor.getCursor();
239 var cursor = editor.getCursor();
220 var text = editor.getRange({line:cursor.line,ch:0},cursor).trim();
240 var text = editor.getRange({line:cursor.line,ch:0},cursor).trim();
221
241
222 // need a permanent handel to codemirror for future auto recall
242 // need a permanent handel to codemirror for future auto recall
223 this.code_mirror = editor;
243 this.code_mirror = editor;
224
244
225
245 // now we treat the different number of keypress
226 // now we treat the different kind of keypress
227 // first if same cell, same text, increment counter by 1
246 // first if same cell, same text, increment counter by 1
228 if( this._old_cell == cell && this._old_request == text && this._hidden == false)
247 if( this._old_cell == cell && this._old_request == text && this._hidden == false)
229 {
248 {
230 this._consecutive_counter = this._consecutive_counter +1;
249 this._consecutive_counter++;
231 } else {
250 } else {
232 // else reset
251 // else reset
252 this.cancel_stick();
233 this.reset_tabs_function(cell,text);
253 this.reset_tabs_function(cell,text);
234 }
254 }
235
255
236 // don't do anything if line beggin with '(' or is empty
256 // don't do anything if line beggin with '(' or is empty
237 if (text === "" || text === "(" ) {
257 if (text === "" || text === "(" ) {
238 return;
258 return;
239 }
259 }
240
260
241 if (this._consecutive_counter == 0) {
261 this.tabs_functions[this._consecutive_counter](cell,text);
242 // make a kernel request
262
243 this._request_tooltip(text);
263 // then if we are at the end of list function, reset
244 } else if (this._consecutive_counter == this.tabs_functions.length) {
264 if (this._consecutive_counter == this.tabs_functions.length)
245 // then if we are at the end of list function, reset
246 // and call the last function after
247 this.tabs_functions[this._consecutive_counter-1]();
248 this.reset_tabs_function(cell,text);
265 this.reset_tabs_function(cell,text);
249 } else {
266
250 // otherwise, call the nth function of the list
251 this.tabs_functions[this._consecutive_counter-1]();
252 }
253 return;
267 return;
254 }
268 }
255
269
256 // cancel the option of having the tooltip to stick
270 // cancel the option of having the tooltip to stick
257 Tooltip.prototype.cancel_stick = function()
271 Tooltip.prototype.cancel_stick = function()
258 {
272 {
273 console.log('cancel stick');
259 clearTimeout(this._stick_timeout);
274 clearTimeout(this._stick_timeout);
275 this._stick_timeout = null;
276 this._clocklink.hide('slow');
260 this._sticky = false;
277 this._sticky = false;
261 }
278 }
262
279
263 // put the tooltip in a sicky state for 10 seconds
280 // put the tooltip in a sicky state for 10 seconds
264 // it won't be removed by remove_and_cancell() unless you called with
281 // it won't be removed by remove_and_cancell() unless you called with
265 // the first parameter set to true.
282 // the first parameter set to true.
266 // remove_and_cancell_tooltip(true)
283 // remove_and_cancell_tooltip(true)
267 Tooltip.prototype.stick = function()
284 Tooltip.prototype.stick = function()
268 {
285 {
269 var that = this;
286 var that = this;
270 this._sticky = true;
287 this._sticky = true;
288 this._clocklink.show('slow');
271 this._stick_timeout = setTimeout( function(){
289 this._stick_timeout = setTimeout( function(){
272 that._sticky = false;
290 that._sticky = false;
291 that._clocklink.hide('slow');
273 }, 10*1000
292 }, 10*1000
274 );
293 );
275 }
294 }
276
295
277 // should be called with the kernel reply to actually show the tooltip
296 // should be called with the kernel reply to actually show the tooltip
278 Tooltip.prototype.show = function(reply)
297 Tooltip.prototype.show = function(reply)
279 {
298 {
280 // move the bubble if it is not hidden
299 // move the bubble if it is not hidden
281 // otherwise fade it
300 // otherwise fade it
282 var editor = this.code_mirror;
283 this.name = reply.name;
301 this.name = reply.name;
284
302
285 // do some math to have the tooltip arrow on more or less on left or right
303 // do some math to have the tooltip arrow on more or less on left or right
286 // width of the editor
304 // width of the editor
287 var w= $(this.code_mirror.getScrollerElement()).width();
305 var w = $(this.code_mirror.getScrollerElement()).width();
288 // ofset of the editor
306 // ofset of the editor
289 var o= $(this.code_mirror.getScrollerElement()).offset();
307 var o = $(this.code_mirror.getScrollerElement()).offset();
290 var pos = editor.cursorCoords();
308 var pos = this.code_mirror.cursorCoords();
291 var xinit = pos.x;
309 var xinit = pos.x;
292 var xinter = o.left + (xinit-o.left)/w*(w-450);
310 var xinter = o.left + (xinit-o.left)/w*(w-450);
293 var posarrowleft = xinit - xinter;
311 var posarrowleft = xinit - xinter;
294
312
295
313
296 if( this._hidden == false)
314 if( this._hidden == false)
297 {
315 {
298 this.tooltip.animate({'left' : xinter-30+'px','top' :(pos.yBot+10)+'px'});
316 this.tooltip.animate({'left' : xinter-30+'px','top' :(pos.yBot+10)+'px'});
299 } else
317 } else
300 {
318 {
301 this.tooltip.css({'left' : xinter-30+'px'});
319 this.tooltip.css({'left' : xinter-30+'px'});
302 this.tooltip.css({'top' :(pos.yBot+10)+'px'});
320 this.tooltip.css({'top' :(pos.yBot+10)+'px'});
303 }
321 }
304 this.arrow.animate({'left' : posarrowleft+'px'});
322 this.arrow.animate({'left' : posarrowleft+'px'});
305 this.tooltip.removeClass('hidden')
323 this.tooltip.removeClass('hidden')
306 this.tooltip.removeClass('hide');
324 this.tooltip.removeClass('hide');
307 this._hidden = false;
325 this._hidden = false;
308
326
309 // build docstring
327 // build docstring
310 defstring = reply.call_def;
328 var defstring = reply.call_def;
311 if (defstring == null) { defstring = reply.init_definition; }
329 if (defstring == null) { defstring = reply.init_definition; }
312 if (defstring == null) { defstring = reply.definition; }
330 if (defstring == null) { defstring = reply.definition; }
313
331
314 docstring = reply.call_docstring;
332 var docstring = reply.call_docstring;
315 if (docstring == null) { docstring = reply.init_docstring; }
333 if (docstring == null) { docstring = reply.init_docstring; }
316 if (docstring == null) { docstring = reply.docstring; }
334 if (docstring == null) { docstring = reply.docstring; }
317 if (docstring == null) { docstring = "<empty docstring>"; }
335 if (docstring == null) { docstring = "<empty docstring>"; }
318
336
319 this.text.children().remove();
337 this.text.children().remove();
320
338
321 var pre=$('<pre/>').html(utils.fixConsole(docstring));
339 var pre=$('<pre/>').html(utils.fixConsole(docstring));
322 if(defstring){
340 if(defstring){
323 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
341 var defstring_html = $('<pre/>').html(utils.fixConsole(defstring));
324 this.text.append(defstring_html);
342 this.text.append(defstring_html);
325 }
343 }
326 this.text.append(pre);
344 this.text.append(pre);
327 // keep scroll top to be sure to always see the first line
345 // keep scroll top to be sure to always see the first line
328 this.text.scrollTop(0);
346 this.text.scrollTop(0);
329 }
347 }
330
348
331 // convenient funciton to have the correct codemirror back into focus
349 // convenient funciton to have the correct codemirror back into focus
332 Tooltip.prototype._cmfocus = function()
350 Tooltip.prototype._cmfocus = function()
333 {
351 {
334 var cm = this.code_mirror;
352 var cm = this.code_mirror;
335 setTimeout(function(){cm.focus();}, 50);
353 setTimeout(function(){cm.focus();}, 50);
336 }
354 }
337
355
338 IPython.Tooltip = Tooltip;
356 IPython.Tooltip = Tooltip;
357
339 return IPython;
358 return IPython;
359
340 }(IPython));
360 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now