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