1 /* 2 * PaneLayoutPreferencesPane.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 package org.rstudio.studio.client.workbench.prefs.views; 16 17 import com.google.gwt.aria.client.Roles; 18 import com.google.gwt.core.client.JsArrayString; 19 import com.google.gwt.event.dom.client.ChangeEvent; 20 import com.google.gwt.event.dom.client.ChangeHandler; 21 import com.google.gwt.event.logical.shared.HasValueChangeHandlers; 22 import com.google.gwt.event.logical.shared.ValueChangeEvent; 23 import com.google.gwt.event.logical.shared.ValueChangeHandler; 24 import com.google.gwt.event.shared.HandlerRegistration; 25 import com.google.gwt.resources.client.ImageResource; 26 import com.google.gwt.user.client.ui.CheckBox; 27 import com.google.gwt.user.client.ui.Composite; 28 import com.google.gwt.user.client.ui.FlexTable; 29 import com.google.gwt.user.client.ui.FlowPanel; 30 import com.google.gwt.user.client.ui.Label; 31 import com.google.gwt.user.client.ui.ListBox; 32 import com.google.gwt.user.client.ui.ScrollPanel; 33 import com.google.gwt.user.client.ui.VerticalPanel; 34 import com.google.inject.Inject; 35 import com.google.inject.Provider; 36 import org.rstudio.core.client.Debug; 37 import org.rstudio.core.client.StringUtil; 38 import org.rstudio.core.client.prefs.RestartRequirement; 39 import org.rstudio.core.client.resources.ImageResource2x; 40 import org.rstudio.core.client.widget.FormLabel; 41 import org.rstudio.core.client.widget.ScrollPanelWithClick; 42 import org.rstudio.core.client.widget.Toolbar; 43 import org.rstudio.core.client.widget.ToolbarButton; 44 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs; 45 import org.rstudio.studio.client.workbench.prefs.model.UserPrefsAccessor; 46 import org.rstudio.studio.client.workbench.ui.PaneConfig; 47 import org.rstudio.studio.client.workbench.ui.PaneManager; 48 49 import java.util.ArrayList; 50 51 public class PaneLayoutPreferencesPane extends PreferencesPane 52 { 53 class ExclusiveSelectionMaintainer 54 { 55 class ListChangeHandler implements ChangeHandler 56 { ListChangeHandler(int whichList)57 ListChangeHandler(int whichList) 58 { 59 whichList_ = whichList; 60 } 61 onChange(ChangeEvent event)62 public void onChange(ChangeEvent event) 63 { 64 int selectedIndex = lists_[whichList_].getSelectedIndex(); 65 66 for (int i = 0; i < lists_.length; i++) 67 { 68 if (i != whichList_ 69 && lists_[i].getSelectedIndex() == selectedIndex) 70 { 71 lists_[i].setSelectedIndex(notSelectedIndex()); 72 } 73 } 74 75 updateTabSetPositions(); 76 } 77 notSelectedIndex()78 private Integer notSelectedIndex() 79 { 80 boolean[] seen = new boolean[4]; 81 for (ListBox listBox : lists_) 82 seen[listBox.getSelectedIndex()] = true; 83 for (int i = 0; i < seen.length; i++) 84 if (!seen[i]) 85 return i; 86 return null; 87 } 88 89 private final int whichList_; 90 } 91 ExclusiveSelectionMaintainer(ListBox[] lists)92 ExclusiveSelectionMaintainer(ListBox[] lists) 93 { 94 lists_ = lists; 95 for (int i = 0; i < lists.length; i++) 96 lists[i].addChangeHandler(new ListChangeHandler(i)); 97 } 98 99 private final ListBox[] lists_; 100 } 101 102 class ModuleList extends Composite implements ValueChangeHandler<Boolean>, 103 HasValueChangeHandlers<ArrayList<Boolean>> 104 { ModuleList(String width)105 ModuleList(String width) 106 { 107 checkBoxes_ = new ArrayList<>(); 108 FlowPanel flowPanel = new FlowPanel(); 109 for (String module : PaneConfig.getAllTabs()) 110 { 111 CheckBox checkBox = new CheckBox(module, false); 112 checkBox.addValueChangeHandler(this); 113 checkBoxes_.add(checkBox); 114 flowPanel.add(checkBox); 115 if (module == "Presentation") 116 checkBox.setVisible(false); 117 } 118 119 ScrollPanel scrollPanel = new ScrollPanelWithClick(); 120 scrollPanel.setStyleName(res_.styles().paneLayoutTable()); 121 scrollPanel.setWidth(width); 122 scrollPanel.add(flowPanel); 123 initWidget(scrollPanel); 124 } 125 onValueChange(ValueChangeEvent<Boolean> event)126 public void onValueChange(ValueChangeEvent<Boolean> event) 127 { 128 ValueChangeEvent.fire(this, getSelectedIndices()); 129 } 130 getSelectedIndices()131 public ArrayList<Boolean> getSelectedIndices() 132 { 133 ArrayList<Boolean> results = new ArrayList<>(); 134 for (CheckBox checkBox : checkBoxes_) 135 results.add(checkBox.getValue()); 136 return results; 137 } 138 setSelectedIndices(ArrayList<Boolean> selected)139 public void setSelectedIndices(ArrayList<Boolean> selected) 140 { 141 for (int i = 0; i < selected.size(); i++) 142 checkBoxes_.get(i).setValue(selected.get(i), false); 143 } 144 getValue()145 public ArrayList<String> getValue() 146 { 147 ArrayList<String> value = new ArrayList<>(); 148 for (CheckBox checkBox : checkBoxes_) 149 { 150 if (checkBox.getValue()) 151 value.add(checkBox.getText()); 152 } 153 return value; 154 } 155 setValue(ArrayList<String> tabs)156 public void setValue(ArrayList<String> tabs) 157 { 158 for (CheckBox checkBox : checkBoxes_) 159 checkBox.setValue(tabs.contains(checkBox.getText()), false); 160 } 161 presentationVisible()162 public boolean presentationVisible() 163 { 164 if (checkBoxes_.size() <= 0) 165 return false; 166 167 CheckBox lastCheckBox = checkBoxes_.get(checkBoxes_.size() - 1); 168 return StringUtil.equals(lastCheckBox.getText(), "Presentation") && 169 lastCheckBox.isVisible(); 170 } 171 addValueChangeHandler( ValueChangeHandler<ArrayList<Boolean>> handler)172 public HandlerRegistration addValueChangeHandler( 173 ValueChangeHandler<ArrayList<Boolean>> handler) 174 { 175 return addHandler(handler, ValueChangeEvent.getType()); 176 } 177 178 private final ArrayList<CheckBox> checkBoxes_; 179 } 180 181 182 @Inject PaneLayoutPreferencesPane(PreferencesDialogResources res, UserPrefs userPrefs, Provider<PaneManager> pPaneManager)183 public PaneLayoutPreferencesPane(PreferencesDialogResources res, 184 UserPrefs userPrefs, 185 Provider<PaneManager> pPaneManager) 186 { 187 res_ = res; 188 userPrefs_ = userPrefs; 189 paneManager_ = pPaneManager.get(); 190 191 PaneConfig paneConfig = userPrefs.panes().getGlobalValue().cast(); 192 additionalColumnCount_ = paneConfig.getAdditionalSourceColumns(); 193 194 add(new Label("Choose the layout of the panels in RStudio by selecting from the controls in" + 195 " each panel. Add up to three additional Source Columns to the left side of the layout. " + 196 "When a column is removed, all saved files within the column are closed and any unsaved " + 197 "files are moved to the main Source Pane.", 198 true)); 199 200 Toolbar columnToolbar = new Toolbar("Manage Column Display"); 201 columnToolbar.setStyleName(res_.styles().newSection()); 202 columnToolbar.setHeight("20px"); 203 204 ToolbarButton addButton = new ToolbarButton( 205 "Add Column", 206 "Add column", 207 res_.iconAddSourcePane()); 208 if (displayColumnCount_ > PaneManager.MAX_COLUMN_COUNT - 1 || 209 !userPrefs.allowSourceColumns().getGlobalValue()) 210 addButton.setEnabled(false); 211 212 ToolbarButton removeButton = new ToolbarButton( 213 "Remove Column", 214 "Remove column", 215 res_.iconRemoveSourcePane()); 216 removeButton.setEnabled(additionalColumnCount_ > 0); 217 218 addButton.addClickHandler(event -> 219 { 220 dirty_ = true; 221 updateTable(displayColumnCount_ + 1); 222 223 if (displayColumnCount_ > PaneManager.MAX_COLUMN_COUNT - 1) 224 addButton.setEnabled(false); 225 if (!removeButton.isEnabled()) 226 removeButton.setEnabled(true); 227 }); 228 229 removeButton.addClickHandler(event -> 230 { 231 dirty_ = true; 232 updateTable(displayColumnCount_ - 1); 233 234 if (displayColumnCount_ < 1) 235 removeButton.setEnabled(false); 236 if (!addButton.isEnabled()) 237 addButton.setEnabled(true); 238 }); 239 240 columnToolbar.addLeftWidget(addButton); 241 columnToolbar.addLeftSeparator(); 242 columnToolbar.addLeftWidget(removeButton); 243 columnToolbar.addLeftSeparator(); 244 add(columnToolbar); 245 246 String[] visiblePanes = PaneConfig.getVisiblePanes(); 247 248 leftTop_ = new ListBox(); 249 Roles.getListboxRole().setAriaLabelProperty(leftTop_.getElement(), "Top left panel"); 250 leftBottom_ = new ListBox(); 251 Roles.getListboxRole().setAriaLabelProperty(leftBottom_.getElement(), "Bottom left panel"); 252 rightTop_ = new ListBox(); 253 Roles.getListboxRole().setAriaLabelProperty(rightTop_.getElement(), "Top right panel"); 254 rightBottom_ = new ListBox(); 255 Roles.getListboxRole().setAriaLabelProperty(rightBottom_.getElement(), "Bottom right panel"); 256 visiblePanes_ = new ListBox[]{leftTop_, leftBottom_, rightTop_, rightBottom_}; 257 for (ListBox lb : visiblePanes_) 258 { 259 for (String value : visiblePanes) 260 lb.addItem(value); 261 } 262 263 if (paneConfig == null || !paneConfig.validateAndAutoCorrect()) 264 userPrefs.panes().setGlobalValue(PaneConfig.createDefault(), false); 265 266 JsArrayString origPanes = userPrefs.panes().getGlobalValue().getQuadrants(); 267 for (int i = 0; i < 4; i++) 268 { 269 boolean success = selectByValue(visiblePanes_[i], origPanes.get(i)); 270 if (!success) 271 { 272 Debug.log("Bad config! Falling back to a reasonable default"); 273 leftTop_.setSelectedIndex(0); 274 leftBottom_.setSelectedIndex(1); 275 rightTop_.setSelectedIndex(2); 276 rightBottom_.setSelectedIndex(3); 277 break; 278 } 279 } 280 281 new ExclusiveSelectionMaintainer(visiblePanes_); 282 283 for (ListBox lb : visiblePanes_) 284 lb.addChangeHandler(event -> dirty_ = true); 285 286 String paneWidth = updateTable(additionalColumnCount_); 287 288 visiblePanePanels_ = new VerticalPanel[] {leftTopPanel_, leftBottomPanel_, 289 rightTopPanel_, rightBottomPanel_}; 290 291 tabSet1ModuleList_ = new ModuleList(paneWidth); 292 tabSet1ModuleList_.setValue(toArrayList(userPrefs.panes().getGlobalValue().getTabSet1())); 293 tabSet2ModuleList_ = new ModuleList(paneWidth); 294 tabSet2ModuleList_.setValue(toArrayList(userPrefs.panes().getGlobalValue().getTabSet2())); 295 hiddenTabSetModuleList_ = new ModuleList(paneWidth); 296 hiddenTabSetModuleList_.setValue(toArrayList( 297 userPrefs.panes().getGlobalValue().getHiddenTabSet())); 298 299 ValueChangeHandler<ArrayList<Boolean>> vch = new ValueChangeHandler<ArrayList<Boolean>>() 300 { 301 public void onValueChange(ValueChangeEvent<ArrayList<Boolean>> e) 302 { 303 dirty_ = true; 304 305 ModuleList source = (ModuleList) e.getSource(); 306 ModuleList other = (source == tabSet1ModuleList_) 307 ? tabSet2ModuleList_ 308 : tabSet1ModuleList_; 309 310 // an index should only be on for one of these lists, 311 ArrayList<Boolean> indices = source.getSelectedIndices(); 312 ArrayList<Boolean> otherIndices = other.getSelectedIndices(); 313 ArrayList<Boolean> hiddenIndices = hiddenTabSetModuleList_.getSelectedIndices(); 314 if (!PaneConfig.isValidConfig(source.getValue())) 315 { 316 // when the configuration is invalid, we must reset sources to the prior valid 317 // configuration based on the values of the other two lists 318 for (int i = 0; i < indices.size(); i++) 319 indices.set(i, !(otherIndices.get(i) || hiddenIndices.get(i))); 320 source.setSelectedIndices(indices); 321 } 322 else 323 { 324 for (int i = 0; i < indices.size(); i++) 325 { 326 if (indices.get(i)) 327 { 328 otherIndices.set(i, false); 329 hiddenIndices.set(i, false); 330 } 331 else if (!otherIndices.get(i)) 332 hiddenIndices.set(i, true); 333 } 334 other.setSelectedIndices(otherIndices); 335 hiddenTabSetModuleList_.setSelectedIndices(hiddenIndices); 336 337 updateTabSetLabels(); 338 } 339 } 340 }; 341 tabSet1ModuleList_.addValueChangeHandler(vch); 342 tabSet2ModuleList_.addValueChangeHandler(vch); 343 344 updateTabSetPositions(); 345 updateTabSetLabels(); 346 } 347 updateTable(int newCount)348 private String updateTable(int newCount) 349 { 350 // nothing has changed since the last update 351 if (grid_ != null && displayColumnCount_ == newCount) 352 return ""; 353 354 // cells will be twice a wide as columns to preserve space 355 double columnCount = newCount + (2 * GRID_PANE_COUNT); 356 double columnWidthValue = (double)TABLE_WIDTH / columnCount; 357 double cellWidthValue = columnWidthValue * GRID_PANE_COUNT; 358 359 // If the column width is bigger than MAX_COLUMN_WIDTH, give space back to the panes 360 if (newCount > 0 && Math.min(columnWidthValue, MAX_COLUMN_WIDTH) != columnWidthValue) 361 { 362 double extra = (newCount * (columnWidthValue - MAX_COLUMN_WIDTH)) / GRID_PANE_COUNT; 363 cellWidthValue += extra; 364 columnWidthValue = MAX_COLUMN_WIDTH; 365 } 366 cellWidthValue -= (GRID_CELL_SPACING + GRID_CELL_PADDING); 367 columnWidthValue -= (GRID_CELL_SPACING + GRID_CELL_PADDING); 368 369 final String columnWidth = columnWidthValue + "px"; 370 final String cellWidth = cellWidthValue + "px"; 371 final String selectWidth = (cellWidthValue - GRID_SELECT_PADDING) + "px"; 372 leftTop_.setWidth(selectWidth); 373 leftBottom_.setWidth(selectWidth); 374 rightTop_.setWidth(selectWidth); 375 rightBottom_.setWidth(selectWidth); 376 377 // create grid 378 if (grid_ == null) 379 { 380 grid_ = new FlexTable(); 381 grid_.addStyleName(res_.styles().paneLayoutTable()); 382 grid_.setCellSpacing(GRID_CELL_SPACING); 383 grid_.setCellPadding(GRID_CELL_PADDING); 384 Roles.getGridRole().setAriaLabelProperty(grid_.getElement(), "Columns and Panes Layout"); 385 386 // the two rows have a different number of columns 387 // because the source columns only use one 388 int topColumn; 389 for (topColumn = 0; topColumn < newCount; topColumn++) 390 { 391 ScrollPanel sp = createColumn(); 392 grid_.setWidget(0, topColumn, sp); 393 grid_.getFlexCellFormatter().setRowSpan(0, topColumn, 2); 394 grid_.getCellFormatter().setStyleName(0, topColumn, res_.styles().paneLayoutTable()); 395 grid_.getColumnFormatter().setWidth(topColumn, columnWidth); 396 } 397 398 grid_.setWidget(0, topColumn, leftTopPanel_ = createPane(leftTop_)); 399 grid_.getCellFormatter().setStyleName(0, topColumn, res_.styles().paneLayoutTable()); 400 401 grid_.setWidget(0, ++topColumn, rightTopPanel_ = createPane(rightTop_)); 402 grid_.getCellFormatter().setStyleName(0, topColumn, res_.styles().paneLayoutTable()); 403 404 int bottomColumn = 0; 405 grid_.setWidget(1, bottomColumn, leftBottomPanel_ = createPane(leftBottom_)); 406 grid_.getCellFormatter().setStyleName(1, bottomColumn, res_.styles().paneLayoutTable()); 407 408 grid_.setWidget(1, ++bottomColumn, rightBottomPanel_ = createPane(rightBottom_)); 409 grid_.getCellFormatter().setStyleName(1, bottomColumn, res_.styles().paneLayoutTable()); 410 411 add(grid_); 412 displayColumnCount_ = newCount; 413 return cellWidth; 414 } 415 416 // adjust existing grid 417 int difference = newCount - displayColumnCount_; 418 displayColumnCount_ = newCount; 419 420 // when the number of columns has decreased, remove columns 421 for (int i = 0; i > difference; i--) 422 grid_.removeCell(0, i); 423 424 // when the number of columns has increased, add columns 425 for (int i = 0; i < difference; i++) 426 { 427 ScrollPanel sp = createColumn(); 428 grid_.insertCell(0, 0); 429 grid_.setWidget(0, 0, sp); 430 grid_.getFlexCellFormatter().setRowSpan(0, 0, 2); 431 grid_.getCellFormatter().setStyleName(0, 0, res_.styles().paneLayoutTable()); 432 } 433 434 // update the widths 435 for (int i = 0; i < newCount; i++) 436 grid_.getCellFormatter().setWidth(0, i, columnWidth); 437 tabSet1ModuleList_.setWidth(cellWidth); 438 tabSet2ModuleList_.setWidth(cellWidth); 439 440 return cellWidth; 441 } 442 createPane(ListBox listBox)443 private VerticalPanel createPane(ListBox listBox) 444 { 445 VerticalPanel vp = new VerticalPanel(); 446 vp.add(listBox); 447 return vp; 448 } 449 createColumn()450 private ScrollPanel createColumn() 451 { 452 VerticalPanel verticalPanel = new VerticalPanel(); 453 FormLabel label = new FormLabel(); 454 label.setText(UserPrefsAccessor.Panes.QUADRANTS_SOURCE); 455 label.setStyleName(res_.styles().label()); 456 verticalPanel.add(label); 457 458 ScrollPanel sp = new ScrollPanel(); 459 sp.add(verticalPanel); 460 Roles.getTextboxRole().setAriaLabelProperty(sp.getElement(), "Additional source column"); 461 462 return sp; 463 } 464 selectByValue(ListBox listBox, String value)465 private static boolean selectByValue(ListBox listBox, String value) 466 { 467 for (int i = 0; i < listBox.getItemCount(); i++) 468 { 469 if (listBox.getValue(i) == value) 470 { 471 listBox.setSelectedIndex(i); 472 return true; 473 } 474 } 475 476 return false; 477 } 478 479 @Override getIcon()480 public ImageResource getIcon() 481 { 482 return new ImageResource2x(res_.iconPanes2x()); 483 } 484 485 @Override initialize(UserPrefs prefs)486 protected void initialize(UserPrefs prefs) 487 { 488 } 489 490 @Override onApply(UserPrefs rPrefs)491 public RestartRequirement onApply(UserPrefs rPrefs) 492 { 493 RestartRequirement restartRequirement = super.onApply(rPrefs); 494 495 if (dirty_) 496 { 497 JsArrayString panes = JsArrayString.createArray().cast(); 498 panes.push(leftTop_.getValue(leftTop_.getSelectedIndex())); 499 panes.push(leftBottom_.getValue(leftBottom_.getSelectedIndex())); 500 panes.push(rightTop_.getValue(rightTop_.getSelectedIndex())); 501 panes.push(rightBottom_.getValue(rightBottom_.getSelectedIndex())); 502 503 JsArrayString tabSet1 = JsArrayString.createArray().cast(); 504 for (String tab : tabSet1ModuleList_.getValue()) 505 tabSet1.push(tab); 506 507 JsArrayString tabSet2 = JsArrayString.createArray().cast(); 508 for (String tab : tabSet2ModuleList_.getValue()) 509 tabSet2.push(tab); 510 511 JsArrayString hiddenTabSet = JsArrayString.createArray().cast(); 512 for (String tab : hiddenTabSetModuleList_.getValue()) 513 hiddenTabSet.push(tab); 514 515 // Determine implicit preference for console top/bottom location 516 // This needs to be saved so that when the user executes the 517 // Console on Left/Right commands we know whether to position 518 // the Console on the Top or Bottom 519 PaneConfig prevConfig = userPrefs_.panes().getGlobalValue().cast(); 520 boolean consoleLeftOnTop = prevConfig.getConsoleLeftOnTop(); 521 boolean consoleRightOnTop = prevConfig.getConsoleRightOnTop(); 522 final String kConsole = "Console"; 523 if (panes.get(0).equals(kConsole)) 524 consoleLeftOnTop = true; 525 else if (panes.get(1).equals(kConsole)) 526 consoleLeftOnTop = false; 527 else if (panes.get(2).equals(kConsole)) 528 consoleRightOnTop = true; 529 else if (panes.get(3).equals(kConsole)) 530 consoleRightOnTop = false; 531 532 if (displayColumnCount_ != additionalColumnCount_) 533 additionalColumnCount_ = 534 paneManager_.syncAdditionalColumnCount(displayColumnCount_, true); 535 536 userPrefs_.panes().setGlobalValue(PaneConfig.create( 537 panes, tabSet1, tabSet2, hiddenTabSet, 538 consoleLeftOnTop, consoleRightOnTop, additionalColumnCount_)); 539 540 dirty_ = false; 541 } 542 543 return restartRequirement; 544 } 545 546 @Override getName()547 public String getName() 548 { 549 return "Pane Layout"; 550 } 551 updateTabSetPositions()552 private void updateTabSetPositions() 553 { 554 for (int i = 0; i < visiblePanes_.length; i++) 555 { 556 String value = visiblePanes_[i].getValue(visiblePanes_[i].getSelectedIndex()); 557 if (value == "TabSet1") 558 visiblePanePanels_[i].add(tabSet1ModuleList_); 559 else if (value == "TabSet2") 560 visiblePanePanels_[i].add(tabSet2ModuleList_); 561 } 562 } 563 updateTabSetLabels()564 private void updateTabSetLabels() 565 { 566 // If no tabs are values in a tabset pane, give the pane a generic name, 567 // otherwise the name is created from the selected values 568 String itemText1 = tabSet1ModuleList_.getValue().isEmpty() ? 569 "TabSet" : StringUtil.join(tabSet1ModuleList_.getValue(), ", "); 570 String itemText2 = tabSet2ModuleList_.getValue().isEmpty() ? 571 "TabSet" : StringUtil.join(tabSet2ModuleList_.getValue(), ", "); 572 if (StringUtil.equals(itemText1, "Presentation") && !tabSet1ModuleList_.presentationVisible()) 573 itemText1 = "TabSet"; 574 575 for (ListBox pane : visiblePanes_) 576 { 577 pane.setItemText(2, itemText1); 578 pane.setItemText(3, itemText2); 579 } 580 } 581 toArrayList(JsArrayString strings)582 private ArrayList<String> toArrayList(JsArrayString strings) 583 { 584 ArrayList<String> results = new ArrayList<>(); 585 for (int i = 0; strings != null && i < strings.length(); i++) 586 results.add(strings.get(i)); 587 return results; 588 } 589 590 private final PreferencesDialogResources res_; 591 private final UserPrefs userPrefs_; 592 private final ListBox leftTop_; 593 private final ListBox leftBottom_; 594 private final ListBox rightTop_; 595 private final ListBox rightBottom_; 596 private final ListBox[] visiblePanes_; 597 private final VerticalPanel[] visiblePanePanels_; 598 private final ModuleList tabSet1ModuleList_; 599 private final ModuleList tabSet2ModuleList_; 600 private final ModuleList hiddenTabSetModuleList_; 601 private final PaneManager paneManager_; 602 private boolean dirty_ = false; 603 604 private VerticalPanel leftTopPanel_; 605 private VerticalPanel leftBottomPanel_; 606 private VerticalPanel rightTopPanel_; 607 private VerticalPanel rightBottomPanel_; 608 609 private int additionalColumnCount_ = 0; 610 private int displayColumnCount_ = 0; 611 private FlexTable grid_; 612 613 private final static int GRID_CELL_SPACING = 8; 614 private final static int GRID_CELL_PADDING = 6; 615 private final static int MAX_COLUMN_WIDTH = 50 + GRID_CELL_PADDING + GRID_CELL_SPACING; 616 private final static int TABLE_WIDTH = 435; 617 private final static int GRID_PANE_COUNT = 2; 618 private final static int GRID_SELECT_PADDING = 10; // must match CSS file 619 } 620