Chromium Code Reviews| 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)); |
| 186 this.screen_.addEventListener('mousewheel', this.onScrollWheel_.bind(this)); | 201 this.screen_.addEventListener('mousewheel', this.onScrollWheel_.bind(this)); |
| 187 this.screen_.addEventListener('copy', this.onCopy_.bind(this)); | 202 this.screen_.addEventListener('copy', this.onCopy_.bind(this)); |
| 188 | 203 |
| 189 // This is the main container for the fixed rows. | 204 // This is the main container for the fixed rows. |
| 190 this.rowNodes_ = doc.createElement('div'); | 205 this.rowNodes_ = doc.createElement('div'); |
| 191 this.rowNodes_.style.cssText = ( | 206 this.rowNodes_.style.cssText = ( |
| 192 'display: block;' + | 207 'display: -webkit-box;' + |
| 193 'position: fixed;' + | 208 'position: fixed;' + |
| 194 '-webkit-user-select: text;'); | 209 '-webkit-user-select: text;' + |
| 210 '-webkit-box-orient: vertical;'); | |
| 195 this.screen_.appendChild(this.rowNodes_); | 211 this.screen_.appendChild(this.rowNodes_); |
| 196 | 212 |
| 197 // Two nodes to hold offscreen text during the copy event. | 213 // Two nodes to hold offscreen text during the copy event. |
| 198 this.topSelectBag_ = doc.createElement('x-select-bag'); | 214 this.topSelectBag_ = doc.createElement('x-select-bag'); |
| 199 this.topSelectBag_.style.cssText = ( | 215 this.topSelectBag_.style.cssText = ( |
| 200 'display: block;' + | 216 'display: block;' + |
| 201 'overflow: hidden;' + | 217 'overflow: hidden;' + |
| 202 'white-space: pre;'); | 218 'white-space: pre;'); |
| 203 | 219 |
| 204 this.bottomSelectBag_ = this.topSelectBag_.cloneNode(); | 220 this.bottomSelectBag_ = this.topSelectBag_.cloneNode(); |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 222 // it in the selection when a user 'drag selects' upwards (drag the mouse to | 238 // 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 | 239 // select and scroll at the same time). Without this, the selection gets |
| 224 // out of whack. | 240 // out of whack. |
| 225 this.scrollArea_ = doc.createElement('div'); | 241 this.scrollArea_ = doc.createElement('div'); |
| 226 this.scrollArea_.style.cssText = 'visibility: hidden'; | 242 this.scrollArea_.style.cssText = 'visibility: hidden'; |
| 227 this.screen_.appendChild(this.scrollArea_); | 243 this.screen_.appendChild(this.scrollArea_); |
| 228 | 244 |
| 229 this.setRowMetrics(this.fontSize_, this.rowHeight_); | 245 this.setRowMetrics(this.fontSize_, this.rowHeight_); |
| 230 }; | 246 }; |
| 231 | 247 |
| 232 ScrollPort.prototype.getRowHeight = function() { | 248 hterm.ScrollPort.prototype.getForegroundColor = function() { |
| 249 return this.document_.body.style.color; | |
| 250 }; | |
| 251 | |
| 252 hterm.ScrollPort.prototype.setForegroundColor = function(color) { | |
| 253 this.document_.body.style.color = color; | |
| 254 }; | |
| 255 | |
| 256 hterm.ScrollPort.prototype.getBackgroundColor = function() { | |
| 257 return this.document_.body.style.backgroundColor; | |
| 258 }; | |
| 259 | |
| 260 hterm.ScrollPort.prototype.setBackgroundColor = function(color) { | |
| 261 this.document_.body.style.backgroundColor = color; | |
| 262 }; | |
| 263 | |
| 264 hterm.ScrollPort.prototype.getRowHeight = function() { | |
| 233 return this.rowHeight_; | 265 return this.rowHeight_; |
| 234 }; | 266 }; |
| 235 | 267 |
| 236 ScrollPort.prototype.getScreenWidth = function() { | 268 hterm.ScrollPort.prototype.getScreenWidth = function() { |
| 237 return this.screen_.clientWidth; | 269 return this.screen_.clientWidth; |
| 238 }; | 270 }; |
| 239 | 271 |
| 240 ScrollPort.prototype.getScreenWidth = function() { | 272 hterm.ScrollPort.prototype.getScreenHeight = function() { |
|
Vladislav Kaznacheev
2011/11/28 14:00:07
Did I miss that in the original land CL? Shame on
| |
| 241 return this.screen_.clientHeight; | 273 return this.screen_.clientHeight; |
| 242 }; | 274 }; |
| 243 | 275 |
| 244 ScrollPort.prototype.getCharacterWidth = function() { | 276 hterm.ScrollPort.prototype.getCharacterWidth = function() { |
| 245 var span = this.document_.createElement('span'); | 277 var span = this.document_.createElement('span'); |
| 246 span.textContent = '\xa0'; // | 278 span.textContent = '\xa0'; // |
| 247 this.rowNodes_.appendChild(span); | 279 this.rowNodes_.appendChild(span); |
| 248 var width = span.offsetWidth; | 280 var width = span.offsetWidth; |
| 249 this.rowNodes_.removeChild(span); | 281 this.rowNodes_.removeChild(span); |
| 250 return width; | 282 return width; |
| 251 }; | 283 }; |
| 252 | 284 |
| 253 /** | 285 /** |
| 254 * Return the document that holds the visible rows of this ScrollPort. | 286 * Return the document that holds the visible rows of this hterm.ScrollPort. |
| 255 */ | 287 */ |
| 256 ScrollPort.prototype.getDocument = function() { | 288 hterm.ScrollPort.prototype.getDocument = function() { |
| 257 return this.document_; | 289 return this.document_; |
| 258 }; | 290 }; |
| 259 | 291 |
| 260 /** | 292 /** |
| 261 * Clear out any cached rowNodes. | 293 * Clear out any cached rowNodes. |
| 262 */ | 294 */ |
| 263 ScrollPort.prototype.resetCache = function() { | 295 hterm.ScrollPort.prototype.resetCache = function() { |
| 264 this.currentRowNodeCache_ = null; | 296 this.currentRowNodeCache_ = null; |
| 265 this.previousRowNodeCache_ = {}; | 297 this.previousRowNodeCache_ = {}; |
| 266 }; | 298 }; |
| 267 | 299 |
| 268 /** | 300 /** |
| 269 * Change the current rowProvider. | 301 * Change the current rowProvider. |
| 270 * | 302 * |
| 271 * This will clear the row cache and cause a redraw. | 303 * This will clear the row cache and cause a redraw. |
| 272 * | 304 * |
| 273 * @param {Object} rowProvider An object capable of providing the rows | 305 * @param {Object} rowProvider An object capable of providing the rows |
| 274 * in this ScrollPort. | 306 * in this hterm.ScrollPort. |
| 275 */ | 307 */ |
| 276 ScrollPort.prototype.setRowProvider = function(rowProvider) { | 308 hterm.ScrollPort.prototype.setRowProvider = function(rowProvider) { |
| 277 this.resetCache_(); | 309 this.resetCache(); |
| 278 this.rowProvider_ = rowProvider; | 310 this.rowProvider_ = rowProvider; |
| 279 this.redraw_(); | 311 this.redraw_(); |
| 280 }; | 312 }; |
| 281 | 313 |
| 314 hterm.ScrollPort.prototype.invalidateRowRange = function(start, end) { | |
| 315 this.resetCache(); | |
| 316 | |
| 317 var node = this.rowNodes_.firstChild; | |
| 318 while (node) { | |
| 319 if ('rowIndex' in node && | |
| 320 node.rowIndex >= start && node.rowIndex <= end) { | |
| 321 var nextSibling = node.nextSibling; | |
| 322 this.rowNodes_.removeChild(node); | |
| 323 this.rowNodes_.insertBefore(this.rowProvider_.getRowNode(node.rowIndex), | |
| 324 nextSibling); | |
| 325 | |
| 326 node = nextSibling; | |
| 327 } else { | |
| 328 node = node.nextSibling; | |
| 329 } | |
| 330 } | |
| 331 }; | |
| 332 | |
| 282 /** | 333 /** |
| 283 * Set the fontSize and lineHeight of this ScrollPort. | 334 * Set the fontSize and lineHeight of this hterm.ScrollPort. |
| 284 * | 335 * |
| 285 * @param {integer} fontSize The css font-size, in pixels. | 336 * @param {integer} fontSize The css font-size, in pixels. |
| 286 * @param {integer} opt_lineHeight Optional css line-height in pixels. | 337 * @param {integer} opt_lineHeight Optional css line-height in pixels. |
| 287 * If omitted it will be computed based on the fontSize. | 338 * If omitted it will be computed based on the fontSize. |
| 288 */ | 339 */ |
| 289 ScrollPort.prototype.setRowMetrics = function(fontSize, opt_lineHeight) { | 340 hterm.ScrollPort.prototype.setRowMetrics = function(fontSize, opt_lineHeight) { |
| 290 this.fontSize_ = fontSize; | 341 this.fontSize_ = fontSize; |
| 291 this.rowHeight_ = opt_lineHeight || fontSize + 2; | 342 this.rowHeight_ = opt_lineHeight || fontSize + 2; |
| 292 | 343 |
| 293 this.screen_.style.fontSize = this.fontSize_ + 'px'; | 344 this.screen_.style.fontSize = this.fontSize_ + 'px'; |
| 294 this.screen_.style.lineHeight = this.rowHeight_ + 'px'; | 345 this.screen_.style.lineHeight = this.rowHeight_ + 'px'; |
| 346 this.xrowCssRule_.style.height = this.rowHeight_ + 'px'; | |
| 295 | 347 |
| 296 this.topSelectBag_.style.height = this.rowHeight_ + 'px'; | 348 this.topSelectBag_.style.height = this.rowHeight_ + 'px'; |
| 297 this.bottomSelectBag_.style.height = this.rowHeight_ + 'px'; | 349 this.bottomSelectBag_.style.height = this.rowHeight_ + 'px'; |
| 298 | 350 |
| 299 if (this.DEBUG_) { | 351 if (this.DEBUG_) { |
| 300 // When we're debugging we add padding to the body so that the offscreen | 352 // When we're debugging we add padding to the body so that the offscreen |
| 301 // elements are visible. | 353 // elements are visible. |
| 302 this.document_.body.style.paddingTop = 3 * this.rowHeight_ + 'px'; | 354 this.document_.body.style.paddingTop = 3 * this.rowHeight_ + 'px'; |
| 303 this.document_.body.style.paddingBottom = 3 * this.rowHeight_ + 'px'; | 355 this.document_.body.style.paddingBottom = 3 * this.rowHeight_ + 'px'; |
| 304 } | 356 } |
| 305 | 357 |
| 306 this.resize(); | 358 this.resize(); |
| 307 }; | 359 }; |
| 308 | 360 |
| 309 /** | 361 /** |
| 310 * Reset dimensions and visible row count to account for a change in the | 362 * Reset dimensions and visible row count to account for a change in the |
| 311 * dimensions of the 'x-screen'. | 363 * dimensions of the 'x-screen'. |
| 312 */ | 364 */ |
| 313 ScrollPort.prototype.resize = function() { | 365 hterm.ScrollPort.prototype.resize = function() { |
| 314 var screenWidth = this.screen_.clientWidth; | 366 var screenWidth = this.screen_.clientWidth; |
| 315 var screenHeight = this.screen_.clientHeight; | 367 var screenHeight = this.screen_.clientHeight; |
| 316 | 368 |
| 317 // We don't want to show a partial row because it would be distracting | 369 // 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. | 370 // in a terminal, so we floor any fractional row count. |
| 319 this.visibleRowCount = Math.floor(screenHeight / this.rowHeight_); | 371 this.visibleRowCount = Math.floor(screenHeight / this.rowHeight_); |
| 320 | 372 |
| 321 // Then compute the height of our integral number of rows. | 373 // Then compute the height of our integral number of rows. |
| 322 var visibleRowsHeight = this.visibleRowCount * this.rowHeight_; | 374 var visibleRowsHeight = this.visibleRowCount * this.rowHeight_; |
| 323 | 375 |
| 324 // Then the difference between the screen height and total row height needs to | 376 // 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 | 377 // be made up for as top margin. We need to record this value so it |
| 326 // can be used later to determine the topRowIndex. | 378 // can be used later to determine the topRowIndex. |
| 327 this.visibleRowTopMargin = screenHeight - visibleRowsHeight; | 379 this.visibleRowTopMargin = screenHeight - visibleRowsHeight; |
| 328 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px'; | 380 this.topFold_.style.marginBottom = this.visibleRowTopMargin + 'px'; |
| 329 | 381 |
| 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. | 382 // Set the dimensions of the visible rows container. |
| 336 this.rowNodes_.style.width = screenWidth + 'px'; | 383 this.rowNodes_.style.width = screenWidth + 'px'; |
| 337 this.rowNodes_.style.height = visibleRowsHeight + 'px'; | 384 this.rowNodes_.style.height = visibleRowsHeight + 'px'; |
| 338 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px'; | 385 this.rowNodes_.style.left = this.screen_.offsetLeft + 'px'; |
| 339 | 386 |
| 340 var self = this; | 387 var self = this; |
| 341 this.publish('resize', | 388 this.publish |
| 342 { scrollPort: this }, | 389 ('resize', { scrollPort: this }, |
| 343 function() { self.redraw_() }); | 390 function() { |
| 391 var index = self.bottomFold_.previousSibling.rowIndex; | |
| 392 self.scrollRowToBottom(index); | |
| 393 }); | |
| 394 }; | |
| 395 | |
| 396 hterm.ScrollPort.prototype.syncScrollHeight = function() { | |
| 397 // Resize the scroll area to appear as though it contains every row. | |
| 398 this.scrollArea_.style.height = (this.rowHeight_ * | |
| 399 this.rowProvider_.getRowCount() + | |
| 400 this.visibleRowTopMargin + 'px'); | |
| 344 }; | 401 }; |
| 345 | 402 |
| 346 /** | 403 /** |
| 347 * Redraw the current ScrollPort based on the current scrollbar position. | 404 * Redraw the current hterm.ScrollPort based on the current scrollbar position. |
| 348 * | 405 * |
| 349 * When redrawing, we are careful to make sure that the rows that start or end | 406 * 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 | 407 * 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 | 408 * 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. | 409 * 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 | 410 * We even stash the selection start/end outside of the visible area if |
| 354 * they are not supposed to be visible in the ScrollPort. | 411 * they are not supposed to be visible in the hterm.ScrollPort. |
| 355 */ | 412 */ |
| 356 ScrollPort.prototype.redraw_ = function() { | 413 hterm.ScrollPort.prototype.redraw_ = function() { |
| 357 this.resetSelectBags_(); | 414 this.resetSelectBags_(); |
| 358 this.selection_.sync(); | 415 this.selection_.sync(); |
| 359 | 416 |
| 417 this.syncScrollHeight(); | |
| 418 | |
| 360 this.currentRowNodeCache_ = {}; | 419 this.currentRowNodeCache_ = {}; |
| 361 | 420 |
| 362 var topRowIndex = this.getTopRowIndex(); | 421 var topRowIndex = this.getTopRowIndex(); |
| 363 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); | 422 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); |
| 364 | 423 |
| 365 this.drawTopFold_(topRowIndex); | 424 this.drawTopFold_(topRowIndex); |
| 366 this.drawBottomFold_(bottomRowIndex); | 425 this.drawBottomFold_(bottomRowIndex); |
| 367 this.drawVisibleRows_(topRowIndex, bottomRowIndex); | 426 this.drawVisibleRows_(topRowIndex, bottomRowIndex); |
| 368 | 427 |
| 369 this.syncRowNodesTop_(); | 428 this.syncRowNodesTop_(); |
| 370 | 429 |
| 371 this.previousRowNodeCache_ = this.currentRowNodeCache_; | 430 this.previousRowNodeCache_ = this.currentRowNodeCache_; |
| 372 this.currentRowNodeCache_ = null; | 431 this.currentRowNodeCache_ = null; |
| 373 }; | 432 }; |
| 374 | 433 |
| 375 /** | 434 /** |
| 376 * Ensure that the nodes above the top fold are as they should be. | 435 * Ensure that the nodes above the top fold are as they should be. |
| 377 * | 436 * |
| 378 * If the selection start and/or end nodes are above the visible range | 437 * 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 | 438 * 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). | 439 * before the top fold (the first x-fold element, aka this.topFold). |
| 381 * | 440 * |
| 382 * If not, the top fold will be the first element. | 441 * If not, the top fold will be the first element. |
| 383 * | 442 * |
| 384 * It is critical that this method does not move the selection nodes. Doing | 443 * 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 | 444 * so would clear the current selection. Instead, the rest of the DOM is |
| 386 * adjusted around them. | 445 * adjusted around them. |
| 387 */ | 446 */ |
| 388 ScrollPort.prototype.drawTopFold_ = function(topRowIndex) { | 447 hterm.ScrollPort.prototype.drawTopFold_ = function(topRowIndex) { |
| 389 if (!this.selection_.startRow || | 448 if (!this.selection_.startRow || |
| 390 this.selection_.startRow.rowIndex >= topRowIndex) { | 449 this.selection_.startRow.rowIndex >= topRowIndex) { |
| 391 // Selection is entirely below the top fold, just make sure the fold is | 450 // Selection is entirely below the top fold, just make sure the fold is |
| 392 // the first child. | 451 // the first child. |
| 393 if (this.rowNodes_.firstChild != this.topFold_) | 452 if (this.rowNodes_.firstChild != this.topFold_) |
| 394 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild); | 453 this.rowNodes_.insertBefore(this.topFold_, this.rowNodes_.firstChild); |
| 395 | 454 |
| 396 return; | 455 return; |
| 397 } | 456 } |
| 398 | 457 |
| 399 if (!this.selection_.isMultiline || | 458 if (!this.selection_.isMultiline || |
| 400 this.selection_.endRow.rowIndex >= topRowIndex) { | 459 this.selection_.endRow.rowIndex >= topRowIndex) { |
| 401 // Only the startRow is above the fold. | 460 // Only the startRow is above the fold. |
| 402 if (this.selection_.startRow.nextSibling != this.topFold_) | 461 if (this.selection_.startRow.nextSibling != this.topFold_) |
| 403 this.rowNodes_.insertBefore(this.topFold_, | 462 this.rowNodes_.insertBefore(this.topFold_, |
| 404 this.selection_.startRow.nextSibling); | 463 this.selection_.startRow.nextSibling); |
| 405 } else { | 464 } else { |
| 406 // Both rows are above the fold. | 465 // Both rows are above the fold. |
| 407 if (this.selection_.endRow.nextSibling != this.topFold_) { | 466 if (this.selection_.endRow.nextSibling != this.topFold_) { |
| 408 this.rowNodes_.insertBefore(this.topFold_, | 467 this.rowNodes_.insertBefore(this.topFold_, |
| 409 this.selection_.endRow.nextSibling); | 468 this.selection_.endRow.nextSibling); |
| 410 } | 469 } |
| 411 | 470 |
| 412 // Trim any intermediate lines. | 471 // Trim any intermediate lines. |
| 413 while (this.selection_.startRow.nextSibling != | 472 while (this.selection_.startRow.nextSibling != |
| 414 this.selection_.endRow) { | 473 this.selection_.endRow) { |
| 415 this.rowNodes_.removeChild(this.selection_.startRow.nextSibling); | 474 this.rowNodes_.removeChild(this.selection_.startRow.nextSibling); |
| 416 } | 475 } |
| 417 } | 476 } |
| 418 | 477 |
| 419 while(this.rowNodes_.firstChild != this.selection_.startRow) { | 478 while(this.rowNodes_.firstChild != this.selection_.startRow) { |
| 420 this.rowNodes_.removeChild(this.rowNodes_.firstChild); | 479 this.rowNodes_.removeChild(this.rowNodes_.firstChild); |
| 421 } | 480 } |
| 422 }; | 481 }; |
| 423 | 482 |
| 424 /** | 483 /** |
| 425 * Ensure that the nodes below the bottom fold are as they should be. | 484 * Ensure that the nodes below the bottom fold are as they should be. |
| 426 * | 485 * |
| 427 * If the selection start and/or end nodes are below the visible range | 486 * 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 | 487 * 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). | 488 * after the bottom fold (the second x-fold element, aka this.bottomFold). |
| 430 * | 489 * |
| 431 * If not, the bottom fold will be the last element. | 490 * If not, the bottom fold will be the last element. |
| 432 * | 491 * |
| 433 * It is critical that this method does not move the selection nodes. Doing | 492 * 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 | 493 * so would clear the current selection. Instead, the rest of the DOM is |
| 435 * adjusted around them. | 494 * adjusted around them. |
| 436 */ | 495 */ |
| 437 ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) { | 496 hterm.ScrollPort.prototype.drawBottomFold_ = function(bottomRowIndex) { |
| 438 if (!this.selection_.endRow || | 497 if (!this.selection_.endRow || |
| 439 this.selection_.endRow.rowIndex <= bottomRowIndex) { | 498 this.selection_.endRow.rowIndex <= bottomRowIndex) { |
| 440 // Selection is entirely above the bottom fold, just make sure the fold is | 499 // Selection is entirely above the bottom fold, just make sure the fold is |
| 441 // the last child. | 500 // the last child. |
| 442 if (this.rowNodes_.lastChild != this.bottomFold_) | 501 if (this.rowNodes_.lastChild != this.bottomFold_) |
| 443 this.rowNodes_.appendChild(this.bottomFold_); | 502 this.rowNodes_.appendChild(this.bottomFold_); |
| 444 | 503 |
| 445 return; | 504 return; |
| 446 } | 505 } |
| 447 | 506 |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 477 * run, and that they have left any visible selection row (selection start | 536 * run, and that they have left any visible selection row (selection start |
| 478 * or selection end) between the folds. | 537 * or selection end) between the folds. |
| 479 * | 538 * |
| 480 * It recycles DOM nodes from the previous redraw where possible, but will ask | 539 * It recycles DOM nodes from the previous redraw where possible, but will ask |
| 481 * the rowSource to make new nodes if necessary. | 540 * the rowSource to make new nodes if necessary. |
| 482 * | 541 * |
| 483 * It is critical that this method does not move the selection nodes. Doing | 542 * 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 | 543 * so would clear the current selection. Instead, the rest of the DOM is |
| 485 * adjusted around them. | 544 * adjusted around them. |
| 486 */ | 545 */ |
| 487 ScrollPort.prototype.drawVisibleRows_ = function(topRowIndex, bottomRowIndex) { | 546 hterm.ScrollPort.prototype.drawVisibleRows_ = function( |
| 547 topRowIndex, bottomRowIndex) { | |
| 488 var self = this; | 548 var self = this; |
| 489 | 549 |
| 490 // Keep removing nodes, starting with currentNode, until we encounter | 550 // Keep removing nodes, starting with currentNode, until we encounter |
| 491 // targetNode. Throws on failure. | 551 // targetNode. Throws on failure. |
| 492 function removeUntilNode(currentNode, targetNode) { | 552 function removeUntilNode(currentNode, targetNode) { |
| 493 while (currentNode != targetNode) { | 553 while (currentNode != targetNode) { |
| 494 if (!currentNode) | 554 if (!currentNode) |
| 495 throw 'Did not encounter target node'; | 555 throw 'Did not encounter target node'; |
| 496 | 556 |
| 497 if (currentNode == self.bottomFold_) | 557 if (currentNode == self.bottomFold_) |
| 498 throw 'Encountered bottom fold before target node'; | 558 throw 'Encountered bottom fold before target node'; |
| 499 | 559 |
| 500 var deadNode = currentNode; | 560 var deadNode = currentNode; |
| 501 currentNode = currentNode.nextSibling; | 561 currentNode = currentNode.nextSibling; |
| 502 deadNode.parentNode.removeChild(deadNode); | 562 deadNode.parentNode.removeChild(deadNode); |
| 503 } | 563 } |
| 504 } | 564 } |
| 505 | 565 |
| 506 // Shorthand for things we're going to use a lot. | 566 // Shorthand for things we're going to use a lot. |
| 507 var selectionStartRow = this.selection_.startRow; | 567 var selectionStartRow = this.selection_.startRow; |
| 508 var selectionEndRow = this.selection_.endRow; | 568 var selectionEndRow = this.selection_.endRow; |
| 509 var bottomFold = this.bottomFold_; | 569 var bottomFold = this.bottomFold_; |
| 510 | 570 |
| 511 // The node we're examining during the current iteration. | 571 // The node we're examining during the current iteration. |
| 512 var node = this.topFold_.nextSibling; | 572 var node = this.topFold_.nextSibling; |
| 513 | 573 |
| 514 for (var drawCount = 0; drawCount < this.visibleRowCount; drawCount++) { | 574 var targetDrawCount = Math.min(this.visibleRowCount, |
| 575 this.rowProvider_.getRowCount()); | |
| 576 | |
| 577 for (var drawCount = 0; drawCount < targetDrawCount; drawCount++) { | |
| 515 var rowIndex = topRowIndex + drawCount; | 578 var rowIndex = topRowIndex + drawCount; |
| 516 | 579 |
| 517 if (node == bottomFold) { | 580 if (node == bottomFold) { |
| 581 //console.log('bottom fold'); | |
|
Vladislav Kaznacheev
2011/11/28 14:00:07
Debugging code left
rginda
2011/11/28 20:39:47
Done.
| |
| 518 // We've hit the bottom fold, we need to insert a new row. | 582 // We've hit the bottom fold, we need to insert a new row. |
| 519 var newNode = this.fetchRowNode_(rowIndex); | 583 var newNode = this.fetchRowNode_(rowIndex); |
| 584 if (!newNode) { | |
| 585 console.log("Couldn't fetch row index: " + rowIndex); | |
| 586 break; | |
| 587 } | |
| 588 | |
| 520 this.rowNodes_.insertBefore(newNode, node); | 589 this.rowNodes_.insertBefore(newNode, node); |
| 521 continue; | 590 continue; |
| 522 } | 591 } |
| 523 | 592 |
| 524 if (node.rowIndex == rowIndex) { | 593 if (node.rowIndex == rowIndex) { |
| 594 //console.log('move along'); | |
| 525 // This node is in the right place, move along. | 595 // This node is in the right place, move along. |
| 526 node = node.nextSibling; | 596 node = node.nextSibling; |
| 527 continue; | 597 continue; |
| 528 } | 598 } |
| 529 | 599 |
| 530 if (selectionStartRow && selectionStartRow.rowIndex == rowIndex) { | 600 if (selectionStartRow && selectionStartRow.rowIndex == rowIndex) { |
| 601 //console.log('selstart'); | |
| 531 // The selection start row is supposed to be here, remove nodes until | 602 // The selection start row is supposed to be here, remove nodes until |
| 532 // we find it. | 603 // we find it. |
| 533 removeUntilNode(node, selectionStartRow); | 604 removeUntilNode(node, selectionStartRow); |
| 534 node = selectionStartRow.nextSibling; | 605 node = selectionStartRow.nextSibling; |
| 535 continue; | 606 continue; |
| 536 } | 607 } |
| 537 | 608 |
| 538 if (selectionEndRow && selectionEndRow.rowIndex == rowIndex) { | 609 if (selectionEndRow && selectionEndRow.rowIndex == rowIndex) { |
| 610 //console.log('selend'); | |
| 539 // The selection end row is supposed to be here, remove nodes until | 611 // The selection end row is supposed to be here, remove nodes until |
| 540 // we find it. | 612 // we find it. |
| 541 removeUntilNode(node, selectionEndRow); | 613 removeUntilNode(node, selectionEndRow); |
| 542 node = selectionEndRow.nextSibling; | 614 node = selectionEndRow.nextSibling; |
| 543 continue; | 615 continue; |
| 544 } | 616 } |
| 545 | 617 |
| 546 if (node == selectionStartRow || node == selectionEndRow) { | 618 if (node == selectionStartRow || node == selectionEndRow) { |
| 619 //console.log('notsel'); | |
| 547 // We encountered the start/end of the selection, but we don't want it | 620 // We encountered the start/end of the selection, but we don't want it |
| 548 // yet. Insert a new row instead. | 621 // yet. Insert a new row instead. |
| 549 var newNode = this.fetchRowNode_(rowIndex); | 622 var newNode = this.fetchRowNode_(rowIndex); |
| 623 if (!newNode) { | |
| 624 console.log("Couldn't fetch row index: " + rowIndex); | |
| 625 break; | |
| 626 } | |
| 627 | |
| 550 this.rowNodes_.insertBefore(newNode, node); | 628 this.rowNodes_.insertBefore(newNode, node); |
| 551 continue; | 629 continue; |
| 552 } | 630 } |
| 553 | 631 |
| 554 // There is nothing special about this node, but it's in our way. Replace | 632 // There is nothing special about this node, but it's in our way. Replace |
| 555 // it with the node that should be here. | 633 // it with the node that should be here. |
| 556 var newNode = this.fetchRowNode_(rowIndex); | 634 var newNode = this.fetchRowNode_(rowIndex); |
| 635 if (!newNode) { | |
| 636 console.log("Couldn't fetch row index: " + rowIndex); | |
| 637 break; | |
| 638 } | |
| 639 | |
| 640 if (node == newNode) { | |
| 641 node = node.nextSibling; | |
| 642 continue; | |
| 643 } | |
| 644 | |
| 557 this.rowNodes_.insertBefore(newNode, node); | 645 this.rowNodes_.insertBefore(newNode, node); |
| 646 if (!newNode.nextSibling) | |
| 647 debugger; | |
| 558 this.rowNodes_.removeChild(node); | 648 this.rowNodes_.removeChild(node); |
| 559 node = newNode.nextSibling; | 649 node = newNode.nextSibling; |
| 560 } | 650 } |
| 561 | 651 |
| 562 if (node != this.bottomFold_) | 652 if (node != this.bottomFold_) |
| 563 removeUntilNode(node, bottomFold); | 653 removeUntilNode(node, bottomFold); |
| 564 }; | 654 }; |
| 565 | 655 |
| 566 /** | 656 /** |
| 567 * Empty out both select bags and remove them from the document. | 657 * Empty out both select bags and remove them from the document. |
| 568 * | 658 * |
| 569 * These nodes hold the text between the start and end of the selection | 659 * 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 | 660 * when that text is otherwise off screen. They are filled out in the |
| 571 * onCopy_ event. | 661 * onCopy_ event. |
| 572 */ | 662 */ |
| 573 ScrollPort.prototype.resetSelectBags_ = function() { | 663 hterm.ScrollPort.prototype.resetSelectBags_ = function() { |
| 574 if (this.topSelectBag_.parentNode) { | 664 if (this.topSelectBag_.parentNode) { |
| 575 this.topSelectBag_.textContent = ''; | 665 this.topSelectBag_.textContent = ''; |
| 576 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_); | 666 this.topSelectBag_.parentNode.removeChild(this.topSelectBag_); |
| 577 } | 667 } |
| 578 | 668 |
| 579 if (this.bottomSelectBag_.parentNode) { | 669 if (this.bottomSelectBag_.parentNode) { |
| 580 this.bottomSelectBag_.textContent = ''; | 670 this.bottomSelectBag_.textContent = ''; |
| 581 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_); | 671 this.bottomSelectBag_.parentNode.removeChild(this.bottomSelectBag_); |
| 582 } | 672 } |
| 583 }; | 673 }; |
| 584 | 674 |
| 585 /** | 675 /** |
| 586 * Set the top coordinate of the row nodes. | 676 * Set the top coordinate of the row nodes. |
| 587 * | 677 * |
| 588 * The rowNodes_ are a fixed position DOM element. When nodes are stashed | 678 * 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_ | 679 * 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 | 680 * so that the first node *after* the top fold is always the first visible |
| 591 * DOM node. | 681 * DOM node. |
| 592 */ | 682 */ |
| 593 ScrollPort.prototype.syncRowNodesTop_ = function() { | 683 hterm.ScrollPort.prototype.syncRowNodesTop_ = function() { |
| 594 var topMargin = 0; | 684 var topMargin = 0; |
| 595 var node = this.topFold_.previousSibling; | 685 var node = this.topFold_.previousSibling; |
| 596 while (node) { | 686 while (node) { |
| 597 topMargin += this.rowHeight_; | 687 topMargin += this.rowHeight_; |
| 598 node = node.previousSibling; | 688 node = node.previousSibling; |
| 599 } | 689 } |
| 600 | 690 |
| 601 this.rowNodes_.style.top = this.screen_.offsetTop - topMargin + 'px'; | 691 this.rowNodes_.style.top = this.screen_.offsetTop - topMargin + 'px'; |
| 602 }; | 692 }; |
| 603 | 693 |
| 604 /** | 694 /** |
| 605 * Place a row node in the cache of visible nodes. | 695 * Place a row node in the cache of visible nodes. |
| 606 * | 696 * |
| 607 * This method may only be used during a redraw_. | 697 * This method may only be used during a redraw_. |
| 608 */ | 698 */ |
| 609 ScrollPort.prototype.cacheRowNode_ = function(rowNode) { | 699 hterm.ScrollPort.prototype.cacheRowNode_ = function(rowNode) { |
| 610 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode; | 700 this.currentRowNodeCache_[rowNode.rowIndex] = rowNode; |
| 611 }; | 701 }; |
| 612 | 702 |
| 613 /** | 703 /** |
| 614 * Fetch the row node for the given index. | 704 * Fetch the row node for the given index. |
| 615 * | 705 * |
| 616 * This will return a node from the cache if possible, or will request one | 706 * This will return a node from the cache if possible, or will request one |
| 617 * from the RowProvider if not. | 707 * from the RowProvider if not. |
| 618 * | 708 * |
| 619 * If a redraw_ is in progress the row will be added to the current cache. | 709 * If a redraw_ is in progress the row will be added to the current cache. |
| 620 */ | 710 */ |
| 621 ScrollPort.prototype.fetchRowNode_ = function(rowIndex) { | 711 hterm.ScrollPort.prototype.fetchRowNode_ = function(rowIndex) { |
| 712 return this.rowProvider_.getRowNode(rowIndex); | |
|
Vladislav Kaznacheev
2011/11/28 14:00:07
Dead code below, debugging leftovers?
rginda
2011/11/28 20:39:47
Oops. This early return was me defeating the row
| |
| 713 | |
| 622 var node; | 714 var node; |
| 623 | 715 |
| 624 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) { | 716 if (this.previousRowNodeCache_ && rowIndex in this.previousRowNodeCache_) { |
| 625 node = this.previousRowNodeCache_[rowIndex]; | 717 node = this.previousRowNodeCache_[rowIndex]; |
| 626 } else { | 718 } else { |
| 627 node = this.rowProvider_.getRowNode(rowIndex); | 719 node = this.rowProvider_.getRowNode(rowIndex); |
| 628 } | 720 } |
| 629 | 721 |
| 630 if (this.currentRowNodeCache_) | 722 if (this.currentRowNodeCache_) |
| 631 this.cacheRowNode_(node); | 723 this.cacheRowNode_(node); |
| 632 | 724 |
| 633 return node; | 725 return node; |
| 634 }; | 726 }; |
| 635 | 727 |
| 636 /** | 728 /** |
| 637 * Select all rows in the viewport. | 729 * Select all rows in the viewport. |
| 638 */ | 730 */ |
| 639 ScrollPort.prototype.selectAll = function() { | 731 hterm.ScrollPort.prototype.selectAll = function() { |
| 640 var firstRow; | 732 var firstRow; |
| 641 | 733 |
| 642 if (this.topFold_.nextSibling.rowIndex != 0) { | 734 if (this.topFold_.nextSibling.rowIndex != 0) { |
| 643 while (this.topFold_.previousSibling) { | 735 while (this.topFold_.previousSibling) { |
| 644 this.rowNodes_.removeChild(this.topFold_.previousSibling); | 736 this.rowNodes_.removeChild(this.topFold_.previousSibling); |
| 645 } | 737 } |
| 646 | 738 |
| 647 firstRow = this.fetchRowNode_(0); | 739 firstRow = this.fetchRowNode_(0); |
| 648 this.rowNodes_.insertBefore(firstRow, this.topFold_); | 740 this.rowNodes_.insertBefore(firstRow, this.topFold_); |
| 649 this.syncRowNodesTop_(); | 741 this.syncRowNodesTop_(); |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 668 var selection = this.document_.getSelection(); | 760 var selection = this.document_.getSelection(); |
| 669 selection.collapse(firstRow, 0); | 761 selection.collapse(firstRow, 0); |
| 670 selection.extend(lastRow, lastRow.childNodes.length); | 762 selection.extend(lastRow, lastRow.childNodes.length); |
| 671 | 763 |
| 672 this.selection_.sync(); | 764 this.selection_.sync(); |
| 673 }; | 765 }; |
| 674 | 766 |
| 675 /** | 767 /** |
| 676 * Return the maximum scroll position in pixels. | 768 * Return the maximum scroll position in pixels. |
| 677 */ | 769 */ |
| 678 ScrollPort.prototype.getScrollMax_ = function(e) { | 770 hterm.ScrollPort.prototype.getScrollMax_ = function(e) { |
| 679 return (this.scrollArea_.clientHeight + this.visibleRowTopMargin - | 771 return (this.scrollArea_.clientHeight + this.visibleRowTopMargin - |
| 680 this.screen_.clientHeight); | 772 this.screen_.clientHeight); |
| 681 }; | 773 }; |
| 682 | 774 |
| 683 /** | 775 /** |
| 684 * Scroll the given rowIndex to the top of the ScrollPort. | 776 * Scroll the given rowIndex to the top of the hterm.ScrollPort. |
| 685 * | 777 * |
| 686 * @param {integer} rowIndex Index of the target row. | 778 * @param {integer} rowIndex Index of the target row. |
| 687 */ | 779 */ |
| 688 ScrollPort.prototype.scrollRowToTop = function(rowIndex) { | 780 hterm.ScrollPort.prototype.scrollRowToTop = function(rowIndex) { |
| 781 this.syncScrollHeight(); | |
| 782 | |
| 689 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; | 783 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; |
| 690 | 784 |
| 691 var scrollMax = this.getScrollMax_(); | 785 var scrollMax = this.getScrollMax_(); |
| 692 if (scrollTop > scrollMax) | 786 if (scrollTop > scrollMax) |
| 693 scrollTop = scrollMax; | 787 scrollTop = scrollMax; |
| 694 | 788 |
| 695 this.screen_.scrollTop = scrollTop; | 789 this.screen_.scrollTop = scrollTop; |
| 696 this.redraw_(); | 790 this.redraw_(); |
| 697 }; | 791 }; |
| 698 | 792 |
| 699 /** | 793 /** |
| 700 * Scroll the given rowIndex to the bottom of the ScrollPort. | 794 * Scroll the given rowIndex to the bottom of the hterm.ScrollPort. |
| 701 * | 795 * |
| 702 * @param {integer} rowIndex Index of the target row. | 796 * @param {integer} rowIndex Index of the target row. |
| 703 */ | 797 */ |
| 704 ScrollPort.prototype.scrollRowToBottom = function(rowIndex) { | 798 hterm.ScrollPort.prototype.scrollRowToBottom = function(rowIndex) { |
| 799 this.syncScrollHeight(); | |
| 800 | |
| 705 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; | 801 var scrollTop = rowIndex * this.rowHeight_ + this.visibleRowTopMargin; |
| 706 scrollTop -= (this.visibleRowCount - 1) * this.rowHeight_; | 802 scrollTop -= (this.visibleRowCount - 1) * this.rowHeight_; |
| 707 | 803 |
| 708 if (scrollTop < 0) | 804 if (scrollTop < 0) |
| 709 scrollTop = 0; | 805 scrollTop = 0; |
| 710 | 806 |
| 711 this.screen_.scrollTop = scrollTop; | 807 this.screen_.scrollTop = scrollTop; |
| 712 this.redraw_(); | 808 this.redraw_(); |
| 713 }; | 809 }; |
| 714 | 810 |
| 715 /** | 811 /** |
| 716 * Return the row index of the first visible row. | 812 * Return the row index of the first visible row. |
| 717 * | 813 * |
| 718 * This is based on the scroll position. If a redraw_ is in progress this | 814 * This is based on the scroll position. If a redraw_ is in progress this |
| 719 * returns the row that *should* be at the top. | 815 * returns the row that *should* be at the top. |
| 720 */ | 816 */ |
| 721 ScrollPort.prototype.getTopRowIndex = function() { | 817 hterm.ScrollPort.prototype.getTopRowIndex = function() { |
| 722 return Math.floor(this.screen_.scrollTop / this.rowHeight_); | 818 return Math.floor(this.screen_.scrollTop / this.rowHeight_); |
| 723 }; | 819 }; |
| 724 | 820 |
| 725 /** | 821 /** |
| 726 * Return the row index of the last visible row. | 822 * Return the row index of the last visible row. |
| 727 * | 823 * |
| 728 * This is based on the scroll position. If a redraw_ is in progress this | 824 * This is based on the scroll position. If a redraw_ is in progress this |
| 729 * returns the row that *should* be at the bottom. | 825 * returns the row that *should* be at the bottom. |
| 730 */ | 826 */ |
| 731 ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) { | 827 hterm.ScrollPort.prototype.getBottomRowIndex = function(topRowIndex) { |
| 732 return topRowIndex + this.visibleRowCount - 1; | 828 return topRowIndex + this.visibleRowCount - 1; |
| 733 }; | 829 }; |
| 734 | 830 |
| 735 /** | 831 /** |
| 736 * Handler for scroll events. | 832 * Handler for scroll events. |
| 737 * | 833 * |
| 738 * The onScroll event fires when the user moves the scrollbar associated with | 834 * The onScroll event fires when scrollArea's scrollTop property changes. This |
| 739 * this ScrollPort. | 835 * may be due to the user manually move the scrollbar, or a programmatic change. |
| 740 */ | 836 */ |
| 741 ScrollPort.prototype.onScroll_ = function(e) { | 837 hterm.ScrollPort.prototype.onScroll_ = function(e) { |
| 742 this.redraw_(); | 838 this.redraw_(); |
| 839 this.publish('scroll', { scrollPort: this }); | |
| 743 }; | 840 }; |
| 744 | 841 |
| 745 /** | 842 /** |
| 746 * Handler for scroll-wheel events. | 843 * Handler for scroll-wheel events. |
| 747 * | 844 * |
| 748 * The onScrollWheel event fires when the user moves their scrollwheel over this | 845 * The onScrollWheel event fires when the user moves their scrollwheel over this |
| 749 * ScrollPort. Because the frontmost element in the ScrollPort is a fixed | 846 * hterm.ScrollPort. Because the frontmost element in the hterm.ScrollPort is |
| 750 * position DIV, the scroll wheel does nothing by default. Instead, we have | 847 * a fixed position DIV, the scroll wheel does nothing by default. Instead, we |
| 751 * to handle it manually. | 848 * have to handle it manually. |
| 752 */ | 849 */ |
| 753 ScrollPort.prototype.onScrollWheel_ = function(e) { | 850 hterm.ScrollPort.prototype.onScrollWheel_ = function(e) { |
| 754 var top = this.screen_.scrollTop - e.wheelDeltaY; | 851 var top = this.screen_.scrollTop - e.wheelDeltaY; |
| 755 if (top < 0) | 852 if (top < 0) |
| 756 top = 0; | 853 top = 0; |
| 757 | 854 |
| 758 var scrollMax = this.getScrollMax_(); | 855 var scrollMax = this.getScrollMax_(); |
| 759 if (top > scrollMax) | 856 if (top > scrollMax) |
| 760 top = scrollMax; | 857 top = scrollMax; |
| 761 | 858 |
| 762 // Moving scrollTop causes a scroll event, which triggers the redraw. | 859 // Moving scrollTop causes a scroll event, which triggers the redraw. |
| 763 this.screen_.scrollTop = top; | 860 this.screen_.scrollTop = top; |
| 764 }; | 861 }; |
| 765 | 862 |
| 766 /** | 863 /** |
| 767 * Handler for resize events. | 864 * Handler for resize events. |
| 768 * | 865 * |
| 769 * The browser will resize us such that the top row stays at the top, but we | 866 * 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. | 867 * prefer to the bottom row to stay at the bottom. |
| 771 */ | 868 */ |
| 772 ScrollPort.prototype.onResize = function(e) { | 869 hterm.ScrollPort.prototype.onResize = function(e) { |
| 773 var index = this.bottomFold_.previousSibling.rowIndex; | |
| 774 this.resize(); | 870 this.resize(); |
| 775 this.scrollRowToBottom(index); | |
| 776 }; | 871 }; |
| 777 | 872 |
| 778 /** | 873 /** |
| 779 * Handler for copy-to-clipboard events. | 874 * Handler for copy-to-clipboard events. |
| 780 * | 875 * |
| 781 * If some or all of the selected rows are off screen we may need to fill in | 876 * 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 | 877 * 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 | 878 * 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. | 879 * of the "select bags" with the missing text. |
| 785 */ | 880 */ |
| 786 ScrollPort.prototype.onCopy_ = function(e) { | 881 hterm.ScrollPort.prototype.onCopy_ = function(e) { |
| 787 this.resetSelectBags_(); | 882 this.resetSelectBags_(); |
| 788 this.selection_.sync(); | 883 this.selection_.sync(); |
| 789 | 884 |
| 790 if (!this.selection_.startRow || | 885 if (!this.selection_.startRow || |
| 791 this.selection_.endRow.rowIndex - this.selection_.startRow.rowIndex < 2) { | 886 this.selection_.endRow.rowIndex - this.selection_.startRow.rowIndex < 2) { |
| 792 return; | 887 return; |
| 793 } | 888 } |
| 794 | 889 |
| 795 var topRowIndex = this.getTopRowIndex(); | 890 var topRowIndex = this.getTopRowIndex(); |
| 796 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); | 891 var bottomRowIndex = this.getBottomRowIndex(topRowIndex); |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 824 } else { | 919 } else { |
| 825 // Selection starts above the bottom fold. | 920 // Selection starts above the bottom fold. |
| 826 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1; | 921 startBackfillIndex = this.bottomFold_.previousSibling.rowIndex + 1; |
| 827 } | 922 } |
| 828 | 923 |
| 829 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText( | 924 this.bottomSelectBag_.textContent = this.rowProvider_.getRowsText( |
| 830 startBackfillIndex, this.selection_.endRow.rowIndex); | 925 startBackfillIndex, this.selection_.endRow.rowIndex); |
| 831 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection_.endRow); | 926 this.rowNodes_.insertBefore(this.bottomSelectBag_, this.selection_.endRow); |
| 832 } | 927 } |
| 833 }; | 928 }; |
| OLD | NEW |