1 /* 2 * EnvironmentObjects.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 16 package org.rstudio.studio.client.workbench.views.environment.view; 17 18 import java.util.ArrayList; 19 import java.util.Collections; 20 import java.util.List; 21 22 import com.google.gwt.core.client.GWT; 23 import com.google.gwt.core.client.JsArray; 24 import com.google.gwt.core.client.JsArrayString; 25 import com.google.gwt.core.client.Scheduler; 26 import com.google.gwt.core.client.Scheduler.ScheduledCommand; 27 import com.google.gwt.event.dom.client.ScrollEvent; 28 import com.google.gwt.event.dom.client.ScrollHandler; 29 import com.google.gwt.uibinder.client.UiBinder; 30 import com.google.gwt.uibinder.client.UiField; 31 import com.google.gwt.user.client.Timer; 32 import com.google.gwt.user.client.ui.*; 33 import com.google.gwt.view.client.ListDataProvider; 34 35 import org.rstudio.core.client.Debug; 36 import org.rstudio.core.client.ElementIds; 37 import org.rstudio.core.client.theme.res.ThemeResources; 38 import org.rstudio.core.client.theme.res.ThemeStyles; 39 import org.rstudio.core.client.widget.FontSizer; 40 import org.rstudio.core.client.widget.Operation; 41 import org.rstudio.studio.client.common.SuperDevMode; 42 import org.rstudio.studio.client.workbench.views.environment.EnvironmentPane; 43 import org.rstudio.studio.client.workbench.views.environment.model.CallFrame; 44 import org.rstudio.studio.client.workbench.views.environment.model.RObject; 45 import org.rstudio.studio.client.workbench.views.environment.view.CallFramePanel.CallFramePanelHost; 46 47 public class EnvironmentObjects extends ResizeComposite 48 implements CallFramePanelHost, 49 EnvironmentObjectDisplay.Host 50 { 51 private class ScrollIntoViewTimer extends Timer 52 { 53 @Override run()54 public void run() 55 { 56 try 57 { 58 if (row_ >= 0 && row_ <= objectDisplay_.getVisibleItemCount()) 59 objectDisplay_.getRowElement(row_).scrollIntoView(); 60 } 61 catch (Exception e) 62 { 63 // silently drop exceptions as they are noisy + not actionable 64 } 65 } 66 setRow(int row)67 public void setRow(int row) 68 { 69 row_ = row; 70 } 71 72 private int row_ = 0; 73 } 74 75 // Public interfaces ------------------------------------------------------- 76 77 public interface Binder extends UiBinder<Widget, EnvironmentObjects> 78 { 79 } 80 81 // Constructor ------------------------------------------------------------- 82 EnvironmentObjects(EnvironmentObjectsObserver observer)83 public EnvironmentObjects(EnvironmentObjectsObserver observer) 84 { 85 observer_ = observer; 86 contextDepth_ = 0; 87 environmentName_ = EnvironmentPane.GLOBAL_ENVIRONMENT_NAME; 88 89 objectDisplayType_ = OBJECT_LIST_VIEW; 90 objectDataProvider_ = new ListDataProvider<>(); 91 objectSort_ = new RObjectEntrySort(); 92 93 // timer used to scroll table element into view 94 // a timer is required as we need to wait until table elements are 95 // rendered and visible before we can scroll into view; otherwise 96 // noisy exceptions will be emitted 97 // 98 // https://github.com/rstudio/rstudio/issues/5181 99 scrollTimer_ = new ScrollIntoViewTimer(); 100 101 // set up the call frame panel 102 callFramePanel_ = new CallFramePanel(observer_, this); 103 104 initWidget(GWT.<Binder>create(Binder.class).createAndBindUi(this)); 105 106 splitPanel.addSouth(callFramePanel_, 150); 107 splitPanel.setWidgetMinSize(callFramePanel_, style.headerRowHeight()); 108 109 setObjectDisplay(objectDisplayType_); 110 111 FontSizer.applyNormalFontSize(this); 112 } 113 114 // Public methods ---------------------------------------------------------- 115 116 @Override onResize()117 public void onResize() 118 { 119 super.onResize(); 120 if (pendingCallFramePanelSize_) 121 { 122 autoSizeCallFramePanel(); 123 } 124 } 125 setContextDepth(int contextDepth)126 public void setContextDepth(int contextDepth) 127 { 128 if (contextDepth > 0) 129 { 130 splitPanel.setWidgetHidden(callFramePanel_, false); 131 splitPanel.onResize(); 132 } 133 else if (contextDepth == 0) 134 { 135 callFramePanel_.clearCallFrames(); 136 splitPanel.setWidgetHidden(callFramePanel_, true); 137 } 138 contextDepth_ = contextDepth; 139 } 140 addObject(RObject obj)141 public void addObject(RObject obj) 142 { 143 int idx = indexOfExistingObject(obj.getName()); 144 final RObjectEntry newEntry = entryFromRObject(obj); 145 boolean added = false; 146 147 // if the object is already in the environment, just update the value 148 if (idx >= 0) 149 { 150 final RObjectEntry oldEntry = objectDataProvider_.getList().get(idx); 151 152 if (oldEntry.rObject.getType() == obj.getType()) 153 { 154 // type hasn't changed 155 if (oldEntry.expanded && 156 newEntry.contentsAreDeferred) 157 { 158 // we're replacing an object that has server-deferred contents-- 159 // refill it immediately. (another approach would be to push the 160 // set of currently expanded objects to the server so these 161 // objects would show up on the client already expanded) 162 fillEntryContents(newEntry, idx, false); 163 } 164 else 165 { 166 // contents aren't deferred, just use the expanded state directly 167 newEntry.expanded = oldEntry.expanded; 168 } 169 objectDataProvider_.getList().set(idx, newEntry); 170 added = true; 171 } 172 else 173 { 174 // types did change, do a full add/remove 175 objectDataProvider_.getList().remove(idx); 176 } 177 178 } 179 if (!added) 180 { 181 RObjectEntry entry = entryFromRObject(obj); 182 idx = indexOfNewObject(entry); 183 objectDataProvider_.getList().add(idx, entry); 184 } 185 updateCategoryLeaders(true); 186 187 // scroll into view 188 scrollTimer_.setRow(idx); 189 scrollTimer_.schedule(100); 190 } 191 removeObject(String objName)192 public void removeObject(String objName) 193 { 194 int idx = indexOfExistingObject(objName); 195 if (idx >= 0) 196 { 197 objectDataProvider_.getList().remove(idx); 198 } 199 200 updateCategoryLeaders(true); 201 } 202 clearObjects()203 public void clearObjects() 204 { 205 objectDataProvider_.getList().clear(); 206 } 207 clearSelection()208 public void clearSelection() 209 { 210 objectDisplay_.clearSelection(); 211 } 212 213 // bulk add for objects--used on init or environment switch addObjects(JsArray<RObject> objects)214 public void addObjects(JsArray<RObject> objects) 215 { 216 // create an entry for each object and sort the array 217 int numObjects = objects.length(); 218 ArrayList<RObjectEntry> objectEntryList = new ArrayList<>(); 219 for (int i = 0; i < numObjects; i++) 220 { 221 RObjectEntry entry = entryFromRObject(objects.get(i)); 222 objectEntryList.add(entry); 223 } 224 Collections.sort(objectEntryList, objectSort_); 225 226 // push the list into the UI and update category leaders 227 objectDataProvider_.getList().addAll(objectEntryList); 228 updateCategoryLeaders(false); 229 230 if (useStatePersistence()) 231 { 232 setDeferredState(); 233 } 234 } 235 getSelectedObjects()236 public List<String> getSelectedObjects() 237 { 238 return objectDisplay_.getSelectedObjects(); 239 } 240 setCallFrames(JsArray<CallFrame> frameList, boolean autoSize)241 public void setCallFrames(JsArray<CallFrame> frameList, boolean autoSize) 242 { 243 callFramePanel_.setCallFrames(frameList, contextDepth_); 244 245 // if not auto-sizing we're done 246 if (!autoSize) 247 return; 248 249 // if the parent panel has layout information, auto-size the call frame 250 // panel (let GWT go first so the call frame panel visibility has 251 // taken effect) 252 if (splitPanel.getOffsetHeight() > 0) 253 { 254 Scheduler.get().scheduleDeferred(new ScheduledCommand() 255 { 256 @Override 257 public void execute() 258 { 259 autoSizeCallFramePanel(); 260 } 261 }); 262 } 263 else 264 { 265 // wait until the split panel has layout information to compute the 266 // correct size of the call frame panel 267 pendingCallFramePanelSize_ = true; 268 } 269 } 270 setEnvironmentName(String environmentName)271 public void setEnvironmentName(String environmentName) 272 { 273 environmentName_ = environmentName; 274 if (objectDisplay_ != null) 275 objectDisplay_.setEnvironmentName(environmentName); 276 } 277 getScrollPosition()278 public int getScrollPosition() 279 { 280 return objectDisplay_.getScrollPanel().getVerticalScrollPosition(); 281 } 282 setScrollPosition(int scrollPosition)283 public void setScrollPosition(int scrollPosition) 284 { 285 deferredScrollPosition_ = scrollPosition; 286 } 287 setExpandedObjects(JsArrayString objects)288 public void setExpandedObjects(JsArrayString objects) 289 { 290 deferredExpandedObjects_ = objects; 291 } 292 updateLineNumber(int newLineNumber)293 public void updateLineNumber (int newLineNumber) 294 { 295 callFramePanel_.updateLineNumber(newLineNumber); 296 } 297 setFilterText(String filterText)298 public void setFilterText (String filterText) 299 { 300 filterText_ = filterText.toLowerCase(); 301 302 // Iterate over each entry in the list, and toggle its visibility based 303 // on whether it matches the current filter text. 304 List<RObjectEntry> objects = objectDataProvider_.getList(); 305 for (int i = 0; i < objects.size(); i++) 306 { 307 RObjectEntry entry = objects.get(i); 308 boolean visible = matchesFilter(entry.rObject); 309 // Redraw the object if its visibility status has changed, or if it's 310 // visible (for visible entries we need to update the search highlight) 311 if (visible != entry.visible || visible) 312 { 313 entry.visible = visible; 314 redrawRowSafely(i); 315 } 316 } 317 318 updateCategoryLeaders(true); 319 } 320 getObjectDisplay()321 public int getObjectDisplay() 322 { 323 return objectDisplayType_; 324 } 325 326 // Sets the object display type. Waits for the event loop to finish because 327 // of an apparent timing bug triggered by superdevmode (see case 3745). setObjectDisplay(int type)328 public void setObjectDisplay(int type) 329 { 330 deferredObjectDisplayType_ = type; 331 Scheduler.get().scheduleDeferred(new ScheduledCommand() 332 { 333 @Override 334 public void execute() 335 { 336 setDeferredObjectDisplay(); 337 } 338 }); 339 } 340 setDeferredObjectDisplay()341 private void setDeferredObjectDisplay() 342 { 343 if (deferredObjectDisplayType_ == null) 344 { 345 return; 346 } 347 348 final int type = deferredObjectDisplayType_; 349 350 // if we already have an active display of this type, do nothing 351 if (type == objectDisplayType_ && 352 objectDisplay_ != null) 353 { 354 return; 355 } 356 357 // clean up previous object display, if we had one 358 if (objectDisplay_ != null) 359 { 360 objectDataProvider_.removeDataDisplay(objectDisplay_); 361 splitPanel.remove(objectDisplay_); 362 } 363 364 try 365 { 366 // create the new object display and wire it to the data source 367 if (type == OBJECT_LIST_VIEW) 368 { 369 objectDisplay_ = new EnvironmentObjectList( 370 this, observer_, environmentName_); 371 objectSort_.setSortType(RObjectEntrySort.SORT_AUTO); 372 } 373 else if (type == OBJECT_GRID_VIEW) 374 { 375 objectDisplay_ = new EnvironmentObjectGrid( 376 this, observer_, environmentName_); 377 objectSort_.setSortType(RObjectEntrySort.SORT_COLUMN); 378 } 379 } 380 catch (Throwable e) 381 { 382 // For reasons that are unclear, GWT sometimes barfs when trying to 383 // create the virtual scrollbars in the DataGrid that drives the 384 // environment list (it computes, and then tries to apply, a negative 385 // height). This appears to only happen during superdevmode boot, 386 // so try again (up to 5 times) if we're in superdevmode. 387 388 if (SuperDevMode.isActive()) 389 { 390 if (gridRenderRetryCount_ >= 5) 391 { 392 Debug.log("WARNING: Failed to render environment pane data grid"); 393 } 394 gridRenderRetryCount_++; 395 Debug.log("WARNING: Retrying environment data grid render (" + 396 gridRenderRetryCount_ + ")"); 397 Timer t = new Timer() { 398 @Override 399 public void run() 400 { 401 setObjectDisplay(type); 402 } 403 }; 404 t.schedule(5); 405 } 406 407 return; 408 } 409 410 objectDisplayType_ = type; 411 Collections.sort(objectDataProvider_.getList(), objectSort_); 412 updateCategoryLeaders(false); 413 objectDataProvider_.addDataDisplay(objectDisplay_); 414 415 objectDisplay_.getScrollPanel().addScrollHandler(new ScrollHandler() 416 { 417 @Override 418 public void onScroll(ScrollEvent event) 419 { 420 if (useStatePersistence()) 421 { 422 deferredScrollPosition_ = getScrollPosition(); 423 observer_.setPersistedScrollPosition(deferredScrollPosition_); 424 } 425 } 426 }); 427 428 objectDisplay_.setEmptyTableWidget(buildEmptyGridMessage()); 429 objectDisplay_.addStyleName(style.objectGrid()); 430 objectDisplay_.addStyleName(style.environmentPanel()); 431 splitPanel.add(objectDisplay_); 432 deferredObjectDisplayType_ = null; 433 } 434 435 // CallFramePanelHost implementation --------------------------------------- 436 437 @Override minimizeCallFramePanel()438 public void minimizeCallFramePanel() 439 { 440 callFramePanelHeight_ = splitPanel.getWidgetSize(callFramePanel_).intValue(); 441 splitPanel.setWidgetSize(callFramePanel_, style.headerRowHeight()); 442 } 443 444 @Override restoreCallFramePanel()445 public void restoreCallFramePanel() 446 { 447 splitPanel.setWidgetSize(callFramePanel_, callFramePanelHeight_); 448 callFramePanel_.onResize(); 449 } 450 451 @Override getShowInternalFunctions()452 public boolean getShowInternalFunctions() 453 { 454 return observer_.getShowInternalFunctions(); 455 } 456 457 @Override setShowInternalFunctions(boolean show)458 public void setShowInternalFunctions(boolean show) 459 { 460 observer_.setShowInternalFunctions(show); 461 } 462 463 // EnvironmentObjectsDisplay.Host implementation --------------------------- 464 465 @Override enableClickableObjects()466 public boolean enableClickableObjects() 467 { 468 return contextDepth_ < 2; 469 } 470 471 // we currently only set and/or get persisted state at the root context 472 // level. 473 @Override useStatePersistence()474 public boolean useStatePersistence() 475 { 476 return environmentName_ == EnvironmentPane.GLOBAL_ENVIRONMENT_NAME; 477 } 478 479 @Override getFilterText()480 public String getFilterText() 481 { 482 return filterText_; 483 } 484 485 @Override getSortColumn()486 public int getSortColumn() 487 { 488 return objectSort_.getSortColumn(); 489 } 490 491 @Override setSortColumn(int col)492 public void setSortColumn(int col) 493 { 494 objectSort_.setSortColumn(col); 495 observer_.setViewDirty(); 496 Collections.sort(objectDataProvider_.getList(), objectSort_); 497 } 498 499 @Override toggleAscendingSort()500 public void toggleAscendingSort() 501 { 502 setAscendingSort(!objectSort_.getAscending()); 503 } 504 505 @Override getAscendingSort()506 public boolean getAscendingSort() 507 { 508 return objectSort_.getAscending(); 509 } 510 setAscendingSort(boolean ascending)511 public void setAscendingSort(boolean ascending) 512 { 513 objectSort_.setAscending(ascending); 514 observer_.setViewDirty(); 515 Collections.sort(objectDataProvider_.getList(), objectSort_); 516 } 517 setSort(int column, boolean ascending)518 public void setSort(int column, boolean ascending) 519 { 520 objectSort_.setSortColumn(column); 521 objectSort_.setAscending(ascending); 522 Collections.sort(objectDataProvider_.getList(), objectSort_); 523 } 524 525 @Override fillEntryContents(final RObjectEntry entry, final int idx, boolean drawProgress)526 public void fillEntryContents(final RObjectEntry entry, 527 final int idx, 528 boolean drawProgress) 529 { 530 entry.expanded = false; 531 entry.isExpanding = true; 532 if (drawProgress) 533 redrawRowSafely(idx); 534 observer_.fillObjectContents(entry.rObject, new Operation() { 535 public void execute() 536 { 537 entry.expanded = true; 538 entry.isExpanding = false; 539 redrawRowSafely(idx); 540 } 541 }); 542 } 543 544 // Private methods: object management -------------------------------------- 545 indexOfExistingObject(String objectName)546 private int indexOfExistingObject(String objectName) 547 { 548 List<RObjectEntry> objects = objectDataProvider_.getList(); 549 550 // find the position of the object in the list--we can't use binary 551 // search here since we're matching on names and the list isn't sorted 552 // by name (it's sorted by type, then name) 553 int index; 554 boolean foundObject = false; 555 for (index = 0; index < objects.size(); index++) 556 { 557 if (objects.get(index).rObject.getName() == objectName) 558 { 559 foundObject = true; 560 break; 561 } 562 } 563 564 return foundObject ? index : -1; 565 } 566 567 // returns the position a new object entry should occupy in the table indexOfNewObject(RObjectEntry obj)568 private int indexOfNewObject(RObjectEntry obj) 569 { 570 List<RObjectEntry> objects = objectDataProvider_.getList(); 571 int numObjects = objects.size(); 572 int idx; 573 // consider: can we use binary search here? 574 for (idx = 0; idx < numObjects; idx++) 575 { 576 if (objectSort_.compare(obj, objects.get(idx)) < 0) 577 { 578 break; 579 } 580 } 581 return idx; 582 } 583 584 // after adds or removes, we need to tag the new category-leading objects updateCategoryLeaders(boolean redrawUpdatedRows)585 private void updateCategoryLeaders(boolean redrawUpdatedRows) 586 { 587 // no need to do these model updates if we're not in the mode that 588 // displays them 589 if (objectDisplayType_ != OBJECT_LIST_VIEW) 590 return; 591 592 List<RObjectEntry> objects = objectDataProvider_.getList(); 593 594 // whether or not we've found a leader for each category 595 Boolean[] leaders = { false, false, false, false }; 596 boolean foundFirstObject = false; 597 598 for (int i = 0; i < objects.size(); i++) 599 { 600 RObjectEntry entry = objects.get(i); 601 if (!entry.visible) 602 continue; 603 if (!foundFirstObject) 604 { 605 entry.isFirstObject = true; 606 foundFirstObject = true; 607 } 608 else 609 { 610 entry.isFirstObject = false; 611 } 612 int category = entry.getCategory(); 613 Boolean leader = entry.isCategoryLeader; 614 // if we haven't found a leader for this category yet, make this object 615 // the leader if it isn't already 616 if (!leaders[category]) 617 { 618 leaders[category] = true; 619 if (!leader) 620 { 621 entry.isCategoryLeader = true; 622 } 623 } 624 // if this object is marked as the leader but we've already found a 625 // leader, unmark it 626 else if (leader) 627 { 628 entry.isCategoryLeader = false; 629 } 630 631 // if we changed the leader flag, redraw the row 632 if (leader != entry.isCategoryLeader 633 && redrawUpdatedRows) 634 { 635 redrawRowSafely(i); 636 } 637 } 638 } 639 buildEmptyGridMessage()640 private Widget buildEmptyGridMessage() 641 { 642 ThemeStyles styles = ThemeResources.INSTANCE.themeStyles(); 643 HTMLPanel messagePanel = new HTMLPanel(""); 644 messagePanel.setStyleName(style.emptyEnvironmentPanel()); 645 environmentEmptyMessage_ = new Label(EMPTY_ENVIRONMENT_MESSAGE); 646 environmentEmptyMessage_.setStyleName(styles.subtitle()); 647 environmentEmptyMessage_.setStylePrimaryName(style.emptyEnvironmentMessage()); 648 ElementIds.assignElementId(environmentEmptyMessage_, ElementIds.ENV_EMPTY); 649 messagePanel.add(environmentEmptyMessage_); 650 return messagePanel; 651 } 652 autoSizeCallFramePanel()653 private void autoSizeCallFramePanel() 654 { 655 // after setting the frames, resize the call frame panel to neatly 656 // wrap the new list, up to a maximum of 2/3 of the height of the 657 // split panel. 658 int desiredCallFramePanelSize = 659 callFramePanel_.getDesiredPanelHeight(); 660 661 if (splitPanel.getOffsetHeight() > 0) 662 { 663 desiredCallFramePanelSize = Math.min( 664 desiredCallFramePanelSize, 665 (int)(0.66 * splitPanel.getOffsetHeight())); 666 } 667 668 // if the panel is minimized, just update the cached height so it'll 669 // get set to what we want when/if the panel is restored 670 if (callFramePanel_.isMinimized()) 671 { 672 callFramePanelHeight_ = desiredCallFramePanelSize; 673 } 674 else 675 { 676 splitPanel.setWidgetSize( 677 callFramePanel_, desiredCallFramePanelSize); 678 callFramePanel_.onResize(); 679 if (objectDisplay_ != null) 680 objectDisplay_.onResize(); 681 } 682 683 pendingCallFramePanelSize_ = false; 684 } 685 686 687 // Private methods: state persistence -------------------------------------- 688 setDeferredState()689 private void setDeferredState() 690 { 691 Scheduler.get().scheduleDeferred(new ScheduledCommand() 692 { 693 @Override 694 public void execute() 695 { 696 if (deferredExpandedObjects_ != null) 697 { 698 // loop through the objects in the list and check to see if each 699 // is marked expanded in the persisted list of expanded objects 700 List<RObjectEntry> objects = objectDataProvider_.getList(); 701 for (int idxObj = 0; idxObj < objects.size(); idxObj++) 702 { 703 for (int idxExpanded = 0; 704 idxExpanded < deferredExpandedObjects_.length(); 705 idxExpanded++) 706 { 707 if (objects.get(idxObj).rObject.getName() == 708 deferredExpandedObjects_.get(idxExpanded)) 709 { 710 objects.get(idxObj).expanded = true; 711 redrawRowSafely(idxObj); 712 } 713 } 714 } 715 } 716 717 // set the cached scroll position 718 objectDisplay_.getScrollPanel().setVerticalScrollPosition( 719 deferredScrollPosition_); 720 721 } 722 }); 723 } 724 matchesFilter(RObject obj)725 private boolean matchesFilter(RObject obj) 726 { 727 if (filterText_.isEmpty()) 728 return true; 729 return obj.getName().toLowerCase().contains(filterText_) || 730 obj.getValue().toLowerCase().contains(filterText_); 731 } 732 entryFromRObject(RObject obj)733 private RObjectEntry entryFromRObject(RObject obj) 734 { 735 return new RObjectEntry(obj, matchesFilter(obj)); 736 } 737 738 // for very large environments, the number of objects may exceed the number 739 // of physical rows; avoid redrawing rows outside the bounds of the 740 // container's physical limit redrawRowSafely(int idx)741 private void redrawRowSafely(int idx) 742 { 743 boolean oob = 744 idx >= MAX_ENVIRONMENT_OBJECTS || 745 idx >= objectDisplay_.getRowCount(); 746 747 if (oob) 748 return; 749 750 objectDisplay_.redrawRow(idx); 751 } 752 753 private final static String EMPTY_ENVIRONMENT_MESSAGE = 754 "Environment is empty"; 755 756 public static final int OBJECT_LIST_VIEW = 0; 757 public static final int OBJECT_GRID_VIEW = 1; 758 759 @UiField EnvironmentStyle style; 760 @UiField SplitLayoutPanel splitPanel; 761 762 EnvironmentObjectDisplay objectDisplay_; 763 CallFramePanel callFramePanel_; 764 Label environmentEmptyMessage_; 765 766 private ListDataProvider<RObjectEntry> objectDataProvider_; 767 private RObjectEntrySort objectSort_; 768 769 private EnvironmentObjectsObserver observer_; 770 private int contextDepth_; 771 private int callFramePanelHeight_; 772 private int objectDisplayType_ = OBJECT_LIST_VIEW; 773 private String filterText_ = ""; 774 private String environmentName_; 775 776 private ScrollIntoViewTimer scrollTimer_; 777 778 // deferred settings--set on load but not applied until we have data. 779 private int deferredScrollPosition_ = 0; 780 private JsArrayString deferredExpandedObjects_; 781 private boolean pendingCallFramePanelSize_ = false; 782 private Integer deferredObjectDisplayType_ = OBJECT_LIST_VIEW; 783 private int gridRenderRetryCount_ = 0; 784 785 public final static int MAX_ENVIRONMENT_OBJECTS = 1024; 786 }