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 |