| 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 |