OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * A 'viewport' view of fixed-height rows with support for selection and | 6 * A 'viewport' view of fixed-height rows with support for selection and |
7 * copy-to-clipboard. | 7 * copy-to-clipboard. |
8 * | 8 * |
9 * 'Viewport' in this case means that only the visible rows are in the DOM. | 9 * 'Viewport' in this case means that only the visible rows are in the DOM. |
10 * If the rowProvider has 100,000 rows, but the ScrollPort is only 25 rows | 10 * If the rowProvider has 100,000 rows, but the ScrollPort is only 25 rows |
11 * tall, then only 25 dom nodes are created. The ScrollPort will ask the | 11 * tall, then only 25 dom nodes are created. The ScrollPort will ask the |
12 * RowProvider to create new visible rows on demand as they are scrolled in | 12 * RowProvider to create new visible rows on demand as they are scrolled in |
13 * to the visible area. | 13 * to the visible area. |
14 * | 14 * |
15 * This viewport is designed so that select and copy-to-clipboard still works, | 15 * This viewport is designed so that select and copy-to-clipboard still works, |
16 * even when all or part of the selection is scrolled off screen. | 16 * even when all or part of the selection is scrolled off screen. |
17 * | 17 * |
18 * Note that the X11 mouse clipboard does not work properly when all or part | 18 * Note that the X11 mouse clipboard does not work properly when all or part |
19 * of the selection is off screen. It would be difficult to fix this without | 19 * of the selection is off screen. It would be difficult to fix this without |
20 * adding significant overhead to pathologically large selection cases. | 20 * adding significant overhead to pathologically large selection cases. |
21 * | 21 * |
| 22 * The RowProvider should return rows rooted by the custom tag name 'x-row'. |
| 23 * This ensures that we can quickly assign the correct display height |
| 24 * to the rows with css. |
| 25 * |
22 * @param {RowProvider} rowProvider An object capable of providing rows as | 26 * @param {RowProvider} rowProvider An object capable of providing rows as |
23 * raw text or row nodes. | 27 * raw text or row nodes. |
24 * @param {integer} fontSize The css font-size, in pixels. | 28 * @param {integer} fontSize The css font-size, in pixels. |
25 * @param {integer} opt_lineHeight Optional css line-height in pixels. | 29 * @param {integer} opt_lineHeight Optional css line-height in pixels. |
26 * If omitted it will be computed based on the fontSize. | 30 * If omitted it will be computed based on the fontSize. |
27 */ | 31 */ |
28 function ScrollPort(rowProvider, fontSize, opt_lineHeight) { | 32 hterm.ScrollPort = function(rowProvider, fontSize, opt_lineHeight) { |
29 PubSub.addBehavior(this); | 33 PubSub.addBehavior(this); |
30 | 34 |
31 this.rowProvider_ = rowProvider; | 35 this.rowProvider_ = rowProvider; |
32 this.fontSize_ = fontSize; | 36 this.fontSize_ = fontSize; |
33 this.rowHeight_ = opt_lineHeight || fontSize + 2; | 37 this.rowHeight_ = opt_lineHeight || fontSize + 2; |
34 | 38 |
35 this.selection_ = new ScrollPort.Selection(this); | 39 this.selection_ = new hterm.ScrollPort.Selection(this); |
36 | 40 |
37 // A map of rowIndex => rowNode for each row that is drawn as part of a | 41 // A map of rowIndex => rowNode for each row that is drawn as part of a |
38 // pending redraw_() call. Null if there is no pending redraw_ call. | 42 // pending redraw_() call. Null if there is no pending redraw_ call. |
39 this.currentRowNodeCache_ = null; | 43 this.currentRowNodeCache_ = null; |
40 | 44 |
41 // A map of rowIndex => rowNode for each row that was drawn as part of the | 45 // A map of rowIndex => rowNode for each row that was drawn as part of the |
42 // previous redraw_() call. | 46 // previous redraw_() call. |
43 this.previousRowNodeCache_ = {}; | 47 this.previousRowNodeCache_ = {}; |
44 | 48 |
| 49 // The css rule that we use to control the height of a row. |
| 50 this.xrowCssRule_ = null; |
| 51 |
45 this.div_ = null; | 52 this.div_ = null; |
46 this.document_ = null; | 53 this.document_ = null; |
47 | 54 |
48 this.observers_ = {}; | 55 this.observers_ = {}; |
49 | 56 |
50 this.DEBUG_ = false; | 57 this.DEBUG_ = false; |
51 } | 58 } |
52 | 59 |
53 /** | 60 /** |
54 * Proxy for the native selection object which understands how to walk up the | 61 * Proxy for the native selection object which understands how to walk up the |
55 * DOM to find the containing row node and sort out which comes first. | 62 * DOM to find the containing row node and sort out which comes first. |
56 * | 63 * |
57 * @param {ScrollPort} scrollPort The parent ScrollPort instance. | 64 * @param {hterm.ScrollPort} scrollPort The parent hterm.ScrollPort instance. |
58 */ | 65 */ |
59 ScrollPort.Selection = function(scrollPort) { | 66 hterm.ScrollPort.Selection = function(scrollPort) { |
60 this.scrollPort_ = scrollPort; | 67 this.scrollPort_ = scrollPort; |
61 this.selection_ = null; | 68 this.selection_ = null; |
62 | 69 |
63 /** | 70 /** |
64 * The row containing the start of the selection. | 71 * The row containing the start of the selection. |
65 * | 72 * |
66 * This may be partially or fully selected. It may be the selection anchor | 73 * This may be partially or fully selected. It may be the selection anchor |
67 * or the focus, but its rowIndex is guaranteed to be less-than-or-equal-to | 74 * or the focus, but its rowIndex is guaranteed to be less-than-or-equal-to |
68 * that of the endRow. | 75 * that of the endRow. |
69 * | 76 * |
(...skipping 24 matching lines...) Expand all Loading... |
94 */ | 101 */ |
95 this.isCollapsed = null; | 102 this.isCollapsed = null; |
96 }; | 103 }; |
97 | 104 |
98 /** | 105 /** |
99 * Synchronize this object with the current DOM selection. | 106 * Synchronize this object with the current DOM selection. |
100 * | 107 * |
101 * This is a one-way synchronization, the DOM selection is copied to this | 108 * This is a one-way synchronization, the DOM selection is copied to this |
102 * object, not the other way around. | 109 * object, not the other way around. |
103 */ | 110 */ |
104 ScrollPort.Selection.prototype.sync = function() { | 111 hterm.ScrollPort.Selection.prototype.sync = function() { |
105 var selection = this.scrollPort_.getDocument().getSelection(); | 112 var selection = this.scrollPort_.getDocument().getSelection(); |
106 | 113 |
107 this.startRow = null; | 114 this.startRow = null; |
108 this.endRow = null; | 115 this.endRow = null; |
109 this.isMultiline = null; | 116 this.isMultiline = null; |
110 this.isCollapsed = !selection || selection.isCollapsed; | 117 this.isCollapsed = !selection || selection.isCollapsed; |
111 | 118 |
112 if (this.isCollapsed) | 119 if (this.isCollapsed) |
113 return; | 120 return; |
114 | 121 |
(...skipping 24 matching lines...) Expand all Loading... |
139 this.endRow = focusRow; | 146 this.endRow = focusRow; |
140 } else { | 147 } else { |
141 this.startRow = focusRow; | 148 this.startRow = focusRow; |
142 this.endRow = anchorRow; | 149 this.endRow = anchorRow; |
143 } | 150 } |
144 | 151 |
145 this.isMultiline = anchorRow.rowIndex != focusRow.rowIndex; | 152 this.isMultiline = anchorRow.rowIndex != focusRow.rowIndex; |
146 }; | 153 }; |
147 | 154 |
148 /** | 155 /** |
149 * Turn a div into this ScrollPort. | 156 * Turn a div into this hterm.ScrollPort. |
150 */ | 157 */ |
151 ScrollPort.prototype.decorate = function(div) { | 158 hterm.ScrollPort.prototype.decorate = function(div) { |
152 this.div_ = div; | 159 this.div_ = div; |
153 | 160 |
154 this.iframe_ = div.ownerDocument.createElement('iframe'); | 161 this.iframe_ = div.ownerDocument.createElement('iframe'); |
155 this.iframe_.style.cssText = ( | 162 this.iframe_.style.cssText = ( |
156 'border: 0;' + | 163 'border: 0;' + |
157 'height: 100%;' + | 164 'height: 100%;' + |
158 'position: absolute;' + | 165 'position: absolute;' + |
159 'width: 100%'); | 166 'width: 100%'); |
160 | 167 |
161 div.appendChild(this.iframe_); | 168 div.appendChild(this.iframe_); |
162 | 169 |
163 this.iframe_.contentWindow.addEventListener('resize', | 170 this.iframe_.contentWindow.addEventListener('resize', |
164 this.onResize.bind(this)); | 171 this.onResize.bind(this)); |
165 | 172 |
166 var doc = this.document_ = this.iframe_.contentDocument; | 173 var doc = this.document_ = this.iframe_.contentDocument; |
167 doc.body.style.cssText = ( | 174 doc.body.style.cssText = ( |
168 'background-color: black;' + | 175 'background-color: black;' + |
169 'color: white;' + | 176 'color: white;' + |
170 'font-family: monospace;' + | 177 'font-family: monospace;' + |
171 'margin: 0px;' + | 178 'margin: 0px;' + |
172 'padding: 0px;' + | 179 'padding: 0px;' + |
173 'white-space: pre;' + | 180 'white-space: pre;' + |
174 '-webkit-user-select: none;'); | 181 '-webkit-user-select: none;'); |
175 | 182 |
| 183 var style = doc.createElement('style'); |
| 184 style.textContent = 'x-row {}'; |
| 185 doc.head.appendChild(style); |
| 186 |
| 187 this.xrowCssRule_ = doc.styleSheets[0].cssRules[0]; |
| 188 this.xrowCssRule_.style.display = 'block'; |
| 189 this.xrowCssRule_.style.height = this.rowHeight_ + 'px'; |
| 190 |
176 this.screen_ = doc.createElement('x-screen'); | 191 this.screen_ = doc.createElement('x-screen'); |
177 this.screen_.style.cssText = ( | 192 this.screen_.style.cssText = ( |
178 'display: block;' + | 193 'display: block;' + |
179 'height: 100%;' + | 194 'height: 100%;' + |
180 'overflow-y: scroll; overflow-x: hidden;' + | 195 'overflow-y: scroll; overflow-x: hidden;' + |
181 'width: 100%;'); | 196 'width: 100%;'); |
182 | 197 |
183 doc.body.appendChild(this.screen_); | 198 doc.body.appendChild(this.screen_); |
184 | 199 |
185 this.screen_.addEventListener('scroll', this.onScroll_.bind(this)); | 200 this.screen_.addEventListener('scroll', this.onScroll_.bind(this)); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
222 // it in the selection when a user 'drag selects' upwards (drag the mouse to | 237 // it in the selection when a user 'drag selects' upwards (drag the mouse to |
223 // select and scroll at the same time). Without this, the selection gets | 238 // select and scroll at the same time). Without this, the selection gets |
224 // out of whack. | 239 // out of whack. |
225 this.scrollArea_ = doc.createElement('div'); | 240 this.scrollArea_ = doc.createElement('div'); |
226 this.scrollArea_.style.cssText = 'visibility: hidden'; | 241 this.scrollArea_.style.cssText = 'visibility: hidden'; |
227 this.screen_.appendChild(this.scrollArea_); | 242 this.screen_.appendChild(this.scrollArea_); |
228 | 243 |
229 this.setRowMetrics(this.fontSize_, this.rowHeight_); | 244 this.setRowMetrics(this.fontSize_, this.rowHeight_); |
230 }; | 245 }; |
231 | 246 |
232 ScrollPort.prototype.getRowHeight = function() { | 247 hterm.ScrollPort.prototype.getForegroundColor = function() { |
| 248 return this.document_.body.style.color; |
| 249 }; |
| 250 |
| 251 hterm.ScrollPort.prototype.setForegroundColor = function(color) { |
| 252 this.document_.body.style.color = color; |
| 253 }; |
| 254 |
| 255 hterm.ScrollPort.prototype.getBackgroundColor = function() { |
| 256 return this.document_.body.style.backgroundColor; |
| 257 }; |
| 258 |
| 259 hterm.ScrollPort.prototype.setBackgroundColor = function(color) { |
| 260 this.document_.body.style.backgroundColor = color; |
| 261 }; |
| 262 |
| 263 hterm.ScrollPort.prototype.getRowHeight = function() { |
233 return this.rowHeight_; | 264 return this.rowHeight_; |
234 }; | 265 }; |
235 | 266 |
236 ScrollPort.prototype.getScreenWidth = function() { | 267 hterm.ScrollPort.prototype.getScreenWidth = function() { |
237 return this.screen_.clientWidth; | 268 return this.screen_.clientWidth; |
238 }; | 269 }; |
239 | 270 |
240 ScrollPort.prototype.getScreenWidth = function() { | 271 hterm.ScrollPort.prototype.getScreenHeight = function() { |
241 return this.screen_.clientHeight; | 272 return this.screen_.clientHeight; |
242 }; | 273 }; |
243 | 274 |
244 ScrollPort.prototype.getCharacterWidth = function() { | 275 hterm.ScrollPort.prototype.getCharacterWidth = function() { |
245 var span = this.document_.createElement('span'); | 276 var span = this.document_.createElement('span'); |
246 span.textContent = '\xa0'; // | 277 span.textContent = '\xa0'; // |
247 this.rowNodes_.appendChild(span); | 278 this.rowNodes_.appendChild(span); |
248 var width = span.offsetWidth; | 279 var width = span.offsetWidth; |
249 this.rowNodes_.removeChild(span); | 280 this.rowNodes_.removeChild(span); |
250 return width; | 281 return width; |
251 }; | 282 }; |
252 | 283 |
253 /** | 284 /** |
254 * Return the document that holds the visible rows of this ScrollPort. | 285 * Return the document that holds the visible rows of this hterm.ScrollPort. |
255 */ | 286 */ |
256 ScrollPort.prototype.getDocument = function() { | 287 hterm.ScrollPort.prototype.getDocument = function() { |
257 return this.document_; | 288 return this.document_; |
258 }; | 289 }; |
259 | 290 |
260 /** | 291 /** |
261 * Clear out any cached rowNodes. | 292 * Clear out any cached rowNodes. |
262 */ | 293 */ |
263 ScrollPort.prototype.resetCache = function() { | 294 hterm.ScrollPort.prototype.resetCache = function() { |
264 this.currentRowNodeCache_ = null; | 295 this.currentRowNodeCache_ = null; |
265 this.previousRowNodeCache_ = {}; | 296 this.previousRowNodeCache_ = {}; |
266 }; | 297 }; |
267 | 298 |
268 /** | 299 /** |
269 * Change the current rowProvider. | 300 * Change the current rowProvider. |
270 * | 301 * |
271 * This will clear the row cache and cause a redraw. | 302 * This will clear the row cache and cause a redraw. |
272 * | 303 * |
273 * @param {Object} rowProvider An object capable of providing the rows | 304 * @param {Object} rowProvider An object capable of providing the rows |
274 * in this ScrollPort. | 305 * in this hterm.ScrollPort. |
275 */ | 306 */ |
276 ScrollPort.prototype.setRowProvider = function(rowProvider) { | 307 hterm.ScrollPort.prototype.setRowProvider = function(rowProvider) { |
277 this.resetCache_(); | 308 this.resetCache(); |
278 this.rowProvider_ = rowProvider; | 309 this.rowProvider_ = rowProvider; |
279 this.redraw_(); | 310 this.redraw_(); |
280 }; | 311 }; |
281 | 312 |
282 /** | 313 /** |
283 * Set the fontSize and lineHeight of this ScrollPort. | 314 * Inform the ScrollPort that a given range of rows is invalid. |
| 315 * |
| 316 * The RowProvider should call this method if the underlying x-row instance |
| 317 * for a given rowIndex is no longer valid. |
| 318 * |
| 319 * Note that this is not necessary when only the *content* of the x-row has |
| 320 * changed. It's only needed when getRowNode(N) would return a different |
| 321 * x-row than it used to. |
| 322 * |
| 323 * If rows in the sepecified range are visible, they will be redrawn. |
| 324 */ |
| 325 hterm.ScrollPort.prototype.invalidateRowRange = function(start, end) { |
| 326 this.resetCache(); |
| 327 |
| 328 var node = this.rowNodes_.firstChild; |
| 329 while (node) { |
| 330 if ('rowIndex' in node && |
| 331 node.rowIndex >= start && node.rowIndex <= end) { |
| 332 var nextSibling = node.nextSibling; |
| 333 this.rowNodes_.removeChild(node); |
| 334 |
| 335 var newNode = this.rowProvider_.getRowNode(node.rowIndex); |
| 336 |
| 337 this.rowNodes_.insertBefore(newNode, nextSibling); |
| 338 this.previousRowNodeCache_[node.rowIndex] = newNode; |
| 339 |
| 340 node = nextSibling; |
| 341 } else { |
| 342 node = node.nextSibling; |
| 343 } |
| 344 } |
| 345 }; |
| 346 |
| 347 /** |
| 348 * Set the fontSize and lineHeight of this hterm.ScrollPort. |
284 * | 349 * |
285 * @param {integer} fontSize The css font-size, in pixels. | 350 * @param {integer} fontSize The css font-size, in pixels. |
286 * @param {integer} opt_lineHeight Optional css line-height in pixels. | 351 * @param {integer} opt_lineHeight Optional css line-height in pixels. |
287 * If omitted it will be computed based on the fontSize. | 352 * If omitted it will be computed based on the fontSize. |
288 */ | 353 */ |
289 ScrollPort.prototype.setRowMetrics = function(fontSize, opt_lineHeight) { | 354 hterm.ScrollPort.prototype.setRowMetrics = function(fontSize, opt_lineHeight) { |
290 this.fontSize_ = fontSize; | 355 this.fontSize_ = fontSize; |
291 this.rowHeight_ = opt_lineHeight || fontSize + 2; | 356 this.rowHeight_ = opt_lineHeight || fontSize + 2; |
292 | 357 |
293 this.screen_.style.fontSize = this.fontSize_ + 'px'; | 358 this.screen_.style.fontSize = this.fontSize_ + 'px'; |
294 this.screen_.style.lineHeight = this.rowHeight_ + 'px'; | 359 this.screen_.style.lineHeight = this.rowHeight_ + 'px'; |
| 360 this.xrowCssRule_.style.height = this.rowHeight_ + 'px'; |
295 | 361 |
296 this.topSelectBag_.style.height = this.rowHeight_ + 'px'; | 362 this.topSelectBag_.style.height = this.rowHeight_ + 'px'; |
297 this.bottomSelectBag_.style.height = this.rowHeight_ + 'px'; | 363 this.bottomSelectBag_.style.height = this.rowHeight_ + 'px'; |
298 | 364 |
299 if (this.DEBUG_) { | 365 if (this.DEBUG_) { |
300 // When we're debugging we add padding to the body so that the offscreen | 366 // When we're debugging we add padding to the body so that the offscreen |
301 // elements are visible. | 367 // elements are visible. |
302 this.document_.body.style.paddingTop = 3 * this.rowHeight_ + 'px'; | 368 this.document_.body.style.paddingTop = 3 * this.rowHeight_ + 'px'; |
303 this.document_.body.style.paddingBottom = 3 * this.rowHeight_ + 'px'; | 369 this.document_.body.style.paddingBottom = 3 * this.rowHeight_ + 'px'; |
304 } | 370 } |
305 | 371 |
306 this.resize(); | 372 this.resize(); |
307 }; | 373 }; |
308 | 374 |
309 /** | 375 /** |
310 * Reset dimensions and visible row count to account for a change in the | 376 * Reset dimensions and visible row count to account for a change in the |
311 * dimensions of the 'x-screen'. | 377 * dimensions of the 'x-screen'. |
312 */ | 378 */ |
313 ScrollPort.prototype.resize = function() { | 379 hterm.ScrollPort.prototype.resize = function() { |
314 var screenWidth = this.screen_.clientWidth; | 380 var screenWidth = this.screen_.clientWidth; |
315 var screenHeight = this.screen_.clientHeight; | 381 var screenHeight = this.screen_.clientHeight; |
316 | 382 |
317 // We don't want to show a partial row because it would be distracting | 383 // We don't want to show a partial row because it would be distracting |
318 // in a terminal, so we floor any fractional row count. | 384 // in a terminal, so we floor any fractional row count. |
319 this.visibleRowCount = Math.floor(screenHeight / this.rowHeight_); | 385 this.visibleRowCount = Math.floor(screenHeight / this.rowHeight_); |
320 | 386 |
321 // Then compute the height of our integral number of rows. | 387 // Then compute the height of our integral number of rows. |
322 var visibleRowsHeight = this.visibleRowCount * this.rowHeight_; | 388 var visibleRowsHeight = this.visibleRowCount * this.rowHeight_; |
323 | 389 |
324 // Then the difference between the screen height and total row height needs to | 390 // Then the difference between the screen height and total row height needs to |
325 // be made up for as top margin. We need to record this value so it | 391 // be made up for as top margin. We need to record this value so it |
326 // can be used later to determine the topRowIndex. | 392 // can be used later to determine the topRowIndex. |
327 this.visibleRowTopMargin = screenHeight - visibleRowsHeight; | 393 this.visibleRowTopMargin = screenHeight - visibleRowsHeight; |
328 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px'; | 394 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px'; |
329 | 395 |
330 // Resize the scroll area to appear as though it contains every row. | |
331 this.scrollArea_.style.height = (this.rowHeight_ * | |
332 this.rowProvider_.getRowCount() + | |
333 this.visibleRowTopMargin + 'px'); | |
334 | |
335 // Set the dimensions of the visible rows container. | 396 // Set the dimensions of the visible rows container. |
336 this.rowNodes_.style.width = screenWidth + 'px'; | 397 this.rowNodes_.style.width = screenWidth + 'px'; |
337 this.rowNodes_.style.height = visibleRowsHeight + 'px'; | 398 this.rowNodes_.style.height = visibleRowsHeight + 'px'; |
338 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px'; | 399 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px'; |
339 | 400 |
340 var self = this; | 401 var self = this; |
341 this.publish('resize', | 402 this.publish |
342 { scrollPort: this }, | 403 ('resize', { scrollPort: this }, |
343 function() { self.redraw_() }); | 404 function() { |
| 405 var index = self.bottomFold_.previousSibling.rowIndex; |
| 406 self.scrollRowToBottom(index); |
| 407 }); |
| 408 }; |
| 409 |
| 410 hterm.ScrollPort.prototype.syncScrollHeight = function() { |
| 411 // Resize the scroll area to appear as though it contains every row. |
| 412 this.scrollArea_.style.height = (this.rowHeight_ * |
| 413 this.rowProvider_.getRowCount() + |
| 414 this.visibleRowTopMargin + 'px'); |
344 }; | 415 }; |
345 | 416 |
346 /** | 417 /** |
347 * Redraw the current ScrollPort based on the current scrollbar position. | 418 * Redraw the current hterm.ScrollPort based on the current scrollbar position. |
348 * | 419 * |
349 * When redrawing, we are careful to make sure that the rows that start or end | 420 * When redrawing, we are careful to make sure that the rows that start or end |
350 * the current selection are not touched in any way. Doing so would disturb | 421 * the current selection are not touched in any way. Doing so would disturb |
351 * the selection, and cleaning up after that would cause flashes at best and | 422 * the selection, and cleaning up after that would cause flashes at best and |
352 * incorrect selection at worst. Instead, we modify the DOM around these nodes. | 423 * incorrect selection at worst. Instead, we modify the DOM around these nodes. |
353 * We even stash the selection start/end outside of the visible area if | 424 * We even stash the selection start/end outside of the visible area if |
354 * they are not supposed to be visible in the ScrollPort. | 425 * they are not supposed to be visible in the hterm.ScrollPort. |
355 */ | 426 */ |
356 ScrollPort.prototype.redraw_ = function() { | 427 hterm.ScrollPort.prototype.redraw_ = function() { |
357 this.resetSelectBags_(); | 428 this.resetSelectBags_(); |
358 this.selection_.sync(); | 429 this.selection_.sync(); |
359 | 430 |
| 431 this.syncScrollHeight(); |
| 432 |
360 this.currentRowNodeCache_ = {}; | 433 this.currentRowNodeCache_ = {}; |
361 | 434 |
362 var topRowIndex = this.getTopRowIndex(); | 435 var topRowIndex = this.getTopRowIndex(); |
363 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); | 436 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); |
364 | 437 |
365 this.drawTopFold_(topRowIndex); | 438 this.drawTopFold_(topRowIndex); |
366 this.drawBottomFold_(bottomRowIndex); | 439 this.drawBottomFold_(bottomRowIndex); |
367 this.drawVisibleRows_(topRowIndex, bottomRowIndex); | 440 this.drawVisibleRows_(topRowIndex, bottomRowIndex); |
368 | 441 |
369 this.syncRowNodesTop_(); | 442 this.syncRowNodesTop_(); |
370 | 443 |
371 this.previousRowNodeCache_ = this.currentRowNodeCache_; | 444 this.previousRowNodeCache_ = this.currentRowNodeCache_; |
372 this.currentRowNodeCache_ = null; | 445 this.currentRowNodeCache_ = null; |
373 }; | 446 }; |
374 | 447 |
375 /** | 448 /** |
376 * Ensure that the nodes above the top fold are as they should be. | 449 * Ensure that the nodes above the top fold are as they should be. |
377 * | 450 * |
378 * If the selection start and/or end nodes are above the visible range | 451 * If the selection start and/or end nodes are above the visible range |
379 * of this ScrollPort then the dom will be adjusted so that they appear before | 452 * of this hterm.ScrollPort then the dom will be adjusted so that they appear |
380 * the top fold (the first x-fold element, aka this.topFold). | 453 * before the top fold (the first x-fold element, aka this.topFold). |
381 * | 454 * |
382 * If not, the top fold will be the first element. | 455 * If not, the top fold will be the first element. |
383 * | 456 * |
384 * It is critical that this method does not move the selection nodes. Doing | 457 * It is critical that this method does not move the selection nodes. Doing |
385 * so would clear the current selection. Instead, the rest of the DOM is | 458 * so would clear the current selection. Instead, the rest of the DOM is |
386 * adjusted around them. | 459 * adjusted around them. |
387 */ | 460 */ |
388 ScrollPort.prototype.drawTopFold_ = function(topRowIndex) { | 461 hterm.ScrollPort.prototype.drawTopFold_ = function(topRowIndex) { |
389 if (!this.selection_.startRow || | 462 if (!this.selection_.startRow || |
390 this.selection_.startRow.rowIndex >= topRowIndex) { | 463 this.selection_.startRow.rowIndex >= topRowIndex) { |
391 // Selection is entirely below the top fold, just make sure the fold is | 464 // Selection is entirely below the top fold, just make sure the fold is |
392 // the first child. | 465 // the first child. |
393 if (this.rowNodes_.firstChild != this.topFold_) | 466 if (this.rowNodes_.firstChild != this.topFold_) |
394 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild); | 467 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild); |
395 | 468 |
396 return; | 469 return; |
397 } | 470 } |
398 | 471 |
399 if (!this.selection_.isMultiline || | 472 if (!this.selection_.isMultiline || |
400 this.selection_.endRow.rowIndex >= topRowIndex) { | 473 this.selection_.endRow.rowIndex >= topRowIndex) { |
401 // Only the startRow is above the fold. | 474 // Only the startRow is above the fold. |
402 if (this.selection_.startRow.nextSibling != this.topFold_) | 475 if (this.selection_.startRow.nextSibling != this.topFold_) |
403 this.rowNodes_.insertBefore(this.topFold_, | 476 this.rowNodes_.insertBefore(this.topFold_, |
404 this.selection_.startRow.nextSibling); | 477 this.selection_.startRow.nextSibling); |
405 } else { | 478 } else { |
406 // Both rows are above the fold. | 479 // Both rows are above the fold. |
407 if (this.selection_.endRow.nextSibling != this.topFold_) { | 480 if (this.selection_.endRow.nextSibling != this.topFold_) { |
408 this.rowNodes_.insertBefore(this.topFold_, | 481 this.rowNodes_.insertBefore(this.topFold_, |
409 this.selection_.endRow.nextSibling); | 482 this.selection_.endRow.nextSibling); |
410 } | 483 } |
411 | 484 |
412 // Trim any intermediate lines. | 485 // Trim any intermediate lines. |
413 while (this.selection_.startRow.nextSibling != | 486 while (this.selection_.startRow.nextSibling != |
414 this.selection_.endRow) { | 487 this.selection_.endRow) { |
415 this.rowNodes_.removeChild(this.selection_.startRow.nextSibling); | 488 this.rowNodes_.removeChild(this.selection_.startRow.nextSibling); |
416 } | 489 } |
417 } | 490 } |
418 | 491 |
419 while(this.rowNodes_.firstChild != this.selection_.startRow) { | 492 while(this.rowNodes_.firstChild != this.selection_.startRow) { |
420 this.rowNodes_.removeChild(this.rowNodes_.firstChild); | 493 this.rowNodes_.removeChild(this.rowNodes_.firstChild); |
421 } | 494 } |
422 }; | 495 }; |
423 | 496 |
424 /** | 497 /** |
425 * Ensure that the nodes below the bottom fold are as they should be. | 498 * Ensure that the nodes below the bottom fold are as they should be. |
426 * | 499 * |
427 * If the selection start and/or end nodes are below the visible range | 500 * If the selection start and/or end nodes are below the visible range |
428 * of this ScrollPort then the dom will be adjusted so that they appear after | 501 * of this hterm.ScrollPort then the dom will be adjusted so that they appear |
429 * the bottom fold (the second x-fold element, aka this.bottomFold). | 502 * after the bottom fold (the second x-fold element, aka this.bottomFold). |
430 * | 503 * |
431 * If not, the bottom fold will be the last element. | 504 * If not, the bottom fold will be the last element. |
432 * | 505 * |
433 * It is critical that this method does not move the selection nodes. Doing | 506 * It is critical that this method does not move the selection nodes. Doing |
434 * so would clear the current selection. Instead, the rest of the DOM is | 507 * so would clear the current selection. Instead, the rest of the DOM is |
435 * adjusted around them. | 508 * adjusted around them. |
436 */ | 509 */ |
437 ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) { | 510 hterm.ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) { |
438 if (!this.selection_.endRow || | 511 if (!this.selection_.endRow || |
439 this.selection_.endRow.rowIndex <= bottomRowIndex) { | 512 this.selection_.endRow.rowIndex <= bottomRowIndex) { |
440 // Selection is entirely above the bottom fold, just make sure the fold is | 513 // Selection is entirely above the bottom fold, just make sure the fold is |
441 // the last child. | 514 // the last child. |
442 if (this.rowNodes_.lastChild != this.bottomFold_) | 515 if (this.rowNodes_.lastChild != this.bottomFold_) |
443 this.rowNodes_.appendChild(this.bottomFold_); | 516 this.rowNodes_.appendChild(this.bottomFold_); |
444 | 517 |
445 return; | 518 return; |
446 } | 519 } |
447 | 520 |
(...skipping 29 matching lines...) Expand all Loading... |
477 * run, and that they have left any visible selection row (selection start | 550 * run, and that they have left any visible selection row (selection start |
478 * or selection end) between the folds. | 551 * or selection end) between the folds. |
479 * | 552 * |
480 * It recycles DOM nodes from the previous redraw where possible, but will ask | 553 * It recycles DOM nodes from the previous redraw where possible, but will ask |
481 * the rowSource to make new nodes if necessary. | 554 * the rowSource to make new nodes if necessary. |
482 * | 555 * |
483 * It is critical that this method does not move the selection nodes. Doing | 556 * It is critical that this method does not move the selection nodes. Doing |
484 * so would clear the current selection. Instead, the rest of the DOM is | 557 * so would clear the current selection. Instead, the rest of the DOM is |
485 * adjusted around them. | 558 * adjusted around them. |
486 */ | 559 */ |
487 ScrollPort.prototype.drawVisibleRows_ = function(topRowIndex, bottomRowIndex) { | 560 hterm.ScrollPort.prototype.drawVisibleRows_ = function( |
| 561 topRowIndex, bottomRowIndex) { |
488 var self = this; | 562 var self = this; |
489 | 563 |
490 // Keep removing nodes, starting with currentNode, until we encounter | 564 // Keep removing nodes, starting with currentNode, until we encounter |
491 // targetNode. Throws on failure. | 565 // targetNode. Throws on failure. |
492 function removeUntilNode(currentNode, targetNode) { | 566 function removeUntilNode(currentNode, targetNode) { |
493 while (currentNode != targetNode) { | 567 while (currentNode != targetNode) { |
494 if (!currentNode) | 568 if (!currentNode) |
495 throw 'Did not encounter target node'; | 569 throw 'Did not encounter target node'; |
496 | 570 |
497 if (currentNode == self.bottomFold_) | 571 if (currentNode == self.bottomFold_) |
498 throw 'Encountered bottom fold before target node'; | 572 throw 'Encountered bottom fold before target node'; |
499 | 573 |
500 var deadNode = currentNode; | 574 var deadNode = currentNode; |
501 currentNode = currentNode.nextSibling; | 575 currentNode = currentNode.nextSibling; |
502 deadNode.parentNode.removeChild(deadNode); | 576 deadNode.parentNode.removeChild(deadNode); |
503 } | 577 } |
504 } | 578 } |
505 | 579 |
506 // Shorthand for things we're going to use a lot. | 580 // Shorthand for things we're going to use a lot. |
507 var selectionStartRow = this.selection_.startRow; | 581 var selectionStartRow = this.selection_.startRow; |
508 var selectionEndRow = this.selection_.endRow; | 582 var selectionEndRow = this.selection_.endRow; |
509 var bottomFold = this.bottomFold_; | 583 var bottomFold = this.bottomFold_; |
510 | 584 |
511 // The node we're examining during the current iteration. | 585 // The node we're examining during the current iteration. |
512 var node = this.topFold_.nextSibling; | 586 var node = this.topFold_.nextSibling; |
513 | 587 |
514 for (var drawCount = 0; drawCount < this.visibleRowCount; drawCount++) { | 588 var targetDrawCount = Math.min(this.visibleRowCount, |
| 589 this.rowProvider_.getRowCount()); |
| 590 |
| 591 for (var drawCount = 0; drawCount < targetDrawCount; drawCount++) { |
515 var rowIndex = topRowIndex + drawCount; | 592 var rowIndex = topRowIndex + drawCount; |
516 | 593 |
517 if (node == bottomFold) { | 594 if (node == bottomFold) { |
518 // We've hit the bottom fold, we need to insert a new row. | 595 // We've hit the bottom fold, we need to insert a new row. |
519 var newNode = this.fetchRowNode_(rowIndex); | 596 var newNode = this.fetchRowNode_(rowIndex); |
| 597 if (!newNode) { |
| 598 console.log("Couldn't fetch row index: " + rowIndex); |
| 599 break; |
| 600 } |
| 601 |
520 this.rowNodes_.insertBefore(newNode, node); | 602 this.rowNodes_.insertBefore(newNode, node); |
521 continue; | 603 continue; |
522 } | 604 } |
523 | 605 |
524 if (node.rowIndex == rowIndex) { | 606 if (node.rowIndex == rowIndex) { |
525 // This node is in the right place, move along. | 607 // This node is in the right place, move along. |
526 node = node.nextSibling; | 608 node = node.nextSibling; |
527 continue; | 609 continue; |
528 } | 610 } |
529 | 611 |
(...skipping 10 matching lines...) Expand all Loading... |
540 // we find it. | 622 // we find it. |
541 removeUntilNode(node, selectionEndRow); | 623 removeUntilNode(node, selectionEndRow); |
542 node = selectionEndRow.nextSibling; | 624 node = selectionEndRow.nextSibling; |
543 continue; | 625 continue; |
544 } | 626 } |
545 | 627 |
546 if (node == selectionStartRow || node == selectionEndRow) { | 628 if (node == selectionStartRow || node == selectionEndRow) { |
547 // We encountered the start/end of the selection, but we don't want it | 629 // We encountered the start/end of the selection, but we don't want it |
548 // yet. Insert a new row instead. | 630 // yet. Insert a new row instead. |
549 var newNode = this.fetchRowNode_(rowIndex); | 631 var newNode = this.fetchRowNode_(rowIndex); |
| 632 if (!newNode) { |
| 633 console.log("Couldn't fetch row index: " + rowIndex); |
| 634 break; |
| 635 } |
| 636 |
550 this.rowNodes_.insertBefore(newNode, node); | 637 this.rowNodes_.insertBefore(newNode, node); |
551 continue; | 638 continue; |
552 } | 639 } |
553 | 640 |
554 // There is nothing special about this node, but it's in our way. Replace | 641 // There is nothing special about this node, but it's in our way. Replace |
555 // it with the node that should be here. | 642 // it with the node that should be here. |
556 var newNode = this.fetchRowNode_(rowIndex); | 643 var newNode = this.fetchRowNode_(rowIndex); |
| 644 if (!newNode) { |
| 645 console.log("Couldn't fetch row index: " + rowIndex); |
| 646 break; |
| 647 } |
| 648 |
| 649 if (node == newNode) { |
| 650 node = node.nextSibling; |
| 651 continue; |
| 652 } |
| 653 |
557 this.rowNodes_.insertBefore(newNode, node); | 654 this.rowNodes_.insertBefore(newNode, node); |
| 655 if (!newNode.nextSibling) |
| 656 debugger; |
558 this.rowNodes_.removeChild(node); | 657 this.rowNodes_.removeChild(node); |
559 node = newNode.nextSibling; | 658 node = newNode.nextSibling; |
560 } | 659 } |
561 | 660 |
562 if (node != this.bottomFold_) | 661 if (node != this.bottomFold_) |
563 removeUntilNode(node, bottomFold); | 662 removeUntilNode(node, bottomFold); |
564 }; | 663 }; |
565 | 664 |
566 /** | 665 /** |
567 * Empty out both select bags and remove them from the document. | 666 * Empty out both select bags and remove them from the document. |
568 * | 667 * |
569 * These nodes hold the text between the start and end of the selection | 668 * These nodes hold the text between the start and end of the selection |
570 * when that text is otherwise off screen. They are filled out in the | 669 * when that text is otherwise off screen. They are filled out in the |
571 * onCopy_ event. | 670 * onCopy_ event. |
572 */ | 671 */ |
573 ScrollPort.prototype.resetSelectBags_ = function() { | 672 hterm.ScrollPort.prototype.resetSelectBags_ = function() { |
574 if (this.topSelectBag_.parentNode) { | 673 if (this.topSelectBag_.parentNode) { |
575 this.topSelectBag_.textContent = ''; | 674 this.topSelectBag_.textContent = ''; |
576 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_); | 675 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_); |
577 } | 676 } |
578 | 677 |
579 if (this.bottomSelectBag_.parentNode) { | 678 if (this.bottomSelectBag_.parentNode) { |
580 this.bottomSelectBag_.textContent = ''; | 679 this.bottomSelectBag_.textContent = ''; |
581 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_); | 680 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_); |
582 } | 681 } |
583 }; | 682 }; |
584 | 683 |
585 /** | 684 /** |
586 * Set the top coordinate of the row nodes. | 685 * Set the top coordinate of the row nodes. |
587 * | 686 * |
588 * The rowNodes_ are a fixed position DOM element. When nodes are stashed | 687 * The rowNodes_ are a fixed position DOM element. When nodes are stashed |
589 * above the top fold, we need to adjust the top position of rowNodes_ | 688 * above the top fold, we need to adjust the top position of rowNodes_ |
590 * so that the first node *after* the top fold is always the first visible | 689 * so that the first node *after* the top fold is always the first visible |
591 * DOM node. | 690 * DOM node. |
592 */ | 691 */ |
593 ScrollPort.prototype.syncRowNodesTop_ = function() { | 692 hterm.ScrollPort.prototype.syncRowNodesTop_ = function() { |
594 var topMargin = 0; | 693 var topMargin = 0; |
595 var node = this.topFold_.previousSibling; | 694 var node = this.topFold_.previousSibling; |
596 while (node) { | 695 while (node) { |
597 topMargin += this.rowHeight_; | 696 topMargin += this.rowHeight_; |
598 node = node.previousSibling; | 697 node = node.previousSibling; |
599 } | 698 } |
600 | 699 |
601 this.rowNodes_.style.top = this.screen_.offsetTop - topMargin + 'px'; | 700 this.rowNodes_.style.top = this.screen_.offsetTop - topMargin + 'px'; |
602 }; | 701 }; |
603 | 702 |
604 /** | 703 /** |
605 * Place a row node in the cache of visible nodes. | 704 * Place a row node in the cache of visible nodes. |
606 * | 705 * |
607 * This method may only be used during a redraw_. | 706 * This method may only be used during a redraw_. |
608 */ | 707 */ |
609 ScrollPort.prototype.cacheRowNode_ = function(rowNode) { | 708 hterm.ScrollPort.prototype.cacheRowNode_ = function(rowNode) { |
610 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode; | 709 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode; |
611 }; | 710 }; |
612 | 711 |
613 /** | 712 /** |
614 * Fetch the row node for the given index. | 713 * Fetch the row node for the given index. |
615 * | 714 * |
616 * This will return a node from the cache if possible, or will request one | 715 * This will return a node from the cache if possible, or will request one |
617 * from the RowProvider if not. | 716 * from the RowProvider if not. |
618 * | 717 * |
619 * If a redraw_ is in progress the row will be added to the current cache. | 718 * If a redraw_ is in progress the row will be added to the current cache. |
620 */ | 719 */ |
621 ScrollPort.prototype.fetchRowNode_ = function(rowIndex) { | 720 hterm.ScrollPort.prototype.fetchRowNode_ = function(rowIndex) { |
622 var node; | 721 var node; |
623 | 722 |
624 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) { | 723 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) { |
625 node = this.previousRowNodeCache_[rowIndex]; | 724 node = this.previousRowNodeCache_[rowIndex]; |
626 } else { | 725 } else { |
627 node = this.rowProvider_.getRowNode(rowIndex); | 726 node = this.rowProvider_.getRowNode(rowIndex); |
628 } | 727 } |
629 | 728 |
630 if (this.currentRowNodeCache_) | 729 if (this.currentRowNodeCache_) |
631 this.cacheRowNode_(node); | 730 this.cacheRowNode_(node); |
632 | 731 |
633 return node; | 732 return node; |
634 }; | 733 }; |
635 | 734 |
636 /** | 735 /** |
637 * Select all rows in the viewport. | 736 * Select all rows in the viewport. |
638 */ | 737 */ |
639 ScrollPort.prototype.selectAll = function() { | 738 hterm.ScrollPort.prototype.selectAll = function() { |
640 var firstRow; | 739 var firstRow; |
641 | 740 |
642 if (this.topFold_.nextSibling.rowIndex != 0) { | 741 if (this.topFold_.nextSibling.rowIndex != 0) { |
643 while (this.topFold_.previousSibling) { | 742 while (this.topFold_.previousSibling) { |
644 this.rowNodes_.removeChild(this.topFold_.previousSibling); | 743 this.rowNodes_.removeChild(this.topFold_.previousSibling); |
645 } | 744 } |
646 | 745 |
647 firstRow = this.fetchRowNode_(0); | 746 firstRow = this.fetchRowNode_(0); |
648 this.rowNodes_.insertBefore(firstRow, this.topFold_); | 747 this.rowNodes_.insertBefore(firstRow, this.topFold_); |
649 this.syncRowNodesTop_(); | 748 this.syncRowNodesTop_(); |
(...skipping 18 matching lines...) Expand all Loading... |
668 var selection = this.document_.getSelection(); | 767 var selection = this.document_.getSelection(); |
669 selection.collapse(firstRow, 0); | 768 selection.collapse(firstRow, 0); |
670 selection.extend(lastRow, lastRow.childNodes.length); | 769 selection.extend(lastRow, lastRow.childNodes.length); |
671 | 770 |
672 this.selection_.sync(); | 771 this.selection_.sync(); |
673 }; | 772 }; |
674 | 773 |
675 /** | 774 /** |
676 * Return the maximum scroll position in pixels. | 775 * Return the maximum scroll position in pixels. |
677 */ | 776 */ |
678 ScrollPort.prototype.getScrollMax_ = function(e) { | 777 hterm.ScrollPort.prototype.getScrollMax_ = function(e) { |
679 return (this.scrollArea_.clientHeight + this.visibleRowTopMargin - | 778 return (this.scrollArea_.clientHeight + this.visibleRowTopMargin - |
680 this.screen_.clientHeight); | 779 this.screen_.clientHeight); |
681 }; | 780 }; |
682 | 781 |
683 /** | 782 /** |
684 * Scroll the given rowIndex to the top of the ScrollPort. | 783 * Scroll the given rowIndex to the top of the hterm.ScrollPort. |
685 * | 784 * |
686 * @param {integer} rowIndex Index of the target row. | 785 * @param {integer} rowIndex Index of the target row. |
687 */ | 786 */ |
688 ScrollPort.prototype.scrollRowToTop = function(rowIndex) { | 787 hterm.ScrollPort.prototype.scrollRowToTop = function(rowIndex) { |
| 788 this.syncScrollHeight(); |
| 789 |
689 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; | 790 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; |
690 | 791 |
691 var scrollMax = this.getScrollMax_(); | 792 var scrollMax = this.getScrollMax_(); |
692 if (scrollTop > scrollMax) | 793 if (scrollTop > scrollMax) |
693 scrollTop = scrollMax; | 794 scrollTop = scrollMax; |
694 | 795 |
695 this.screen_.scrollTop = scrollTop; | 796 this.screen_.scrollTop = scrollTop; |
696 this.redraw_(); | 797 this.redraw_(); |
697 }; | 798 }; |
698 | 799 |
699 /** | 800 /** |
700 * Scroll the given rowIndex to the bottom of the ScrollPort. | 801 * Scroll the given rowIndex to the bottom of the hterm.ScrollPort. |
701 * | 802 * |
702 * @param {integer} rowIndex Index of the target row. | 803 * @param {integer} rowIndex Index of the target row. |
703 */ | 804 */ |
704 ScrollPort.prototype.scrollRowToBottom = function(rowIndex) { | 805 hterm.ScrollPort.prototype.scrollRowToBottom = function(rowIndex) { |
| 806 this.syncScrollHeight(); |
| 807 |
705 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; | 808 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; |
706 scrollTop -= (this.visibleRowCount - 1) * this.rowHeight_; | 809 scrollTop -= (this.visibleRowCount - 1) * this.rowHeight_; |
707 | 810 |
708 if (scrollTop < 0) | 811 if (scrollTop < 0) |
709 scrollTop = 0; | 812 scrollTop = 0; |
710 | 813 |
711 this.screen_.scrollTop = scrollTop; | 814 this.screen_.scrollTop = scrollTop; |
712 this.redraw_(); | 815 this.redraw_(); |
713 }; | 816 }; |
714 | 817 |
715 /** | 818 /** |
716 * Return the row index of the first visible row. | 819 * Return the row index of the first visible row. |
717 * | 820 * |
718 * This is based on the scroll position. If a redraw_ is in progress this | 821 * This is based on the scroll position. If a redraw_ is in progress this |
719 * returns the row that *should* be at the top. | 822 * returns the row that *should* be at the top. |
720 */ | 823 */ |
721 ScrollPort.prototype.getTopRowIndex = function() { | 824 hterm.ScrollPort.prototype.getTopRowIndex = function() { |
722 return Math.floor(this.screen_.scrollTop / this.rowHeight_); | 825 return Math.floor(this.screen_.scrollTop / this.rowHeight_); |
723 }; | 826 }; |
724 | 827 |
725 /** | 828 /** |
726 * Return the row index of the last visible row. | 829 * Return the row index of the last visible row. |
727 * | 830 * |
728 * This is based on the scroll position. If a redraw_ is in progress this | 831 * This is based on the scroll position. If a redraw_ is in progress this |
729 * returns the row that *should* be at the bottom. | 832 * returns the row that *should* be at the bottom. |
730 */ | 833 */ |
731 ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) { | 834 hterm.ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) { |
732 return topRowIndex + this.visibleRowCount - 1; | 835 return topRowIndex + this.visibleRowCount - 1; |
733 }; | 836 }; |
734 | 837 |
735 /** | 838 /** |
736 * Handler for scroll events. | 839 * Handler for scroll events. |
737 * | 840 * |
738 * The onScroll event fires when the user moves the scrollbar associated with | 841 * The onScroll event fires when scrollArea's scrollTop property changes. This |
739 * this ScrollPort. | 842 * may be due to the user manually move the scrollbar, or a programmatic change. |
740 */ | 843 */ |
741 ScrollPort.prototype.onScroll_ = function(e) { | 844 hterm.ScrollPort.prototype.onScroll_ = function(e) { |
742 this.redraw_(); | 845 this.redraw_(); |
| 846 this.publish('scroll', { scrollPort: this }); |
743 }; | 847 }; |
744 | 848 |
745 /** | 849 /** |
746 * Handler for scroll-wheel events. | 850 * Handler for scroll-wheel events. |
747 * | 851 * |
748 * The onScrollWheel event fires when the user moves their scrollwheel over this | 852 * The onScrollWheel event fires when the user moves their scrollwheel over this |
749 * ScrollPort. Because the frontmost element in the ScrollPort is a fixed | 853 * hterm.ScrollPort. Because the frontmost element in the hterm.ScrollPort is |
750 * position DIV, the scroll wheel does nothing by default. Instead, we have | 854 * a fixed position DIV, the scroll wheel does nothing by default. Instead, we |
751 * to handle it manually. | 855 * have to handle it manually. |
752 */ | 856 */ |
753 ScrollPort.prototype.onScrollWheel_ = function(e) { | 857 hterm.ScrollPort.prototype.onScrollWheel_ = function(e) { |
754 var top = this.screen_.scrollTop - e.wheelDeltaY; | 858 var top = this.screen_.scrollTop - e.wheelDeltaY; |
755 if (top < 0) | 859 if (top < 0) |
756 top = 0; | 860 top = 0; |
757 | 861 |
758 var scrollMax = this.getScrollMax_(); | 862 var scrollMax = this.getScrollMax_(); |
759 if (top > scrollMax) | 863 if (top > scrollMax) |
760 top = scrollMax; | 864 top = scrollMax; |
761 | 865 |
762 // Moving scrollTop causes a scroll event, which triggers the redraw. | 866 // Moving scrollTop causes a scroll event, which triggers the redraw. |
763 this.screen_.scrollTop = top; | 867 this.screen_.scrollTop = top; |
764 }; | 868 }; |
765 | 869 |
766 /** | 870 /** |
767 * Handler for resize events. | 871 * Handler for resize events. |
768 * | 872 * |
769 * The browser will resize us such that the top row stays at the top, but we | 873 * The browser will resize us such that the top row stays at the top, but we |
770 * prefer to the bottom row to stay at the bottom. | 874 * prefer to the bottom row to stay at the bottom. |
771 */ | 875 */ |
772 ScrollPort.prototype.onResize = function(e) { | 876 hterm.ScrollPort.prototype.onResize = function(e) { |
773 var index = this.bottomFold_.previousSibling.rowIndex; | |
774 this.resize(); | 877 this.resize(); |
775 this.scrollRowToBottom(index); | |
776 }; | 878 }; |
777 | 879 |
778 /** | 880 /** |
779 * Handler for copy-to-clipboard events. | 881 * Handler for copy-to-clipboard events. |
780 * | 882 * |
781 * If some or all of the selected rows are off screen we may need to fill in | 883 * If some or all of the selected rows are off screen we may need to fill in |
782 * the rows between selection start and selection end. This handler determines | 884 * the rows between selection start and selection end. This handler determines |
783 * if we're missing some of the selected text, and if so populates one or both | 885 * if we're missing some of the selected text, and if so populates one or both |
784 * of the "select bags" with the missing text. | 886 * of the "select bags" with the missing text. |
785 */ | 887 */ |
786 ScrollPort.prototype.onCopy_ = function(e) { | 888 hterm.ScrollPort.prototype.onCopy_ = function(e) { |
787 this.resetSelectBags_(); | 889 this.resetSelectBags_(); |
788 this.selection_.sync(); | 890 this.selection_.sync(); |
789 | 891 |
790 if (!this.selection_.startRow || | 892 if (!this.selection_.startRow || |
791 this.selection_.endRow.rowIndex - this.selection_.startRow.rowIndex < 2) { | 893 this.selection_.endRow.rowIndex - this.selection_.startRow.rowIndex < 2) { |
792 return; | 894 return; |
793 } | 895 } |
794 | 896 |
795 var topRowIndex = this.getTopRowIndex(); | 897 var topRowIndex = this.getTopRowIndex(); |
796 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); | 898 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); |
(...skipping 27 matching lines...) Expand all Loading... |
824 } else { | 926 } else { |
825 // Selection starts above the bottom fold. | 927 // Selection starts above the bottom fold. |
826 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1; | 928 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1; |
827 } | 929 } |
828 | 930 |
829 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText( | 931 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText( |
830 startBackfillIndex, this.selection_.endRow.rowIndex); | 932 startBackfillIndex, this.selection_.endRow.rowIndex); |
831 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection_.endRow); | 933 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection_.endRow); |
832 } | 934 } |
833 }; | 935 }; |
OLD | NEW |