blob: 9c8ca8efef3d699d80e261da5832969c2d9ff847 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.autoupdate.ui;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Window;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.autoupdate.UpdateManager;
import org.netbeans.api.autoupdate.UpdateUnit;
import org.netbeans.modules.autoupdate.ui.actions.AutoupdateCheckScheduler;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.util.TaskListener;
/**
*
* @author Jiri Rechtacek, Radek Matous
*/
public class PluginManagerUI extends javax.swing.JPanel {
private List<UpdateUnit> units = Collections.emptyList ();
private UnitTable installedTable;
private UnitTable availableTable;
private UnitTable updateTable;
private UnitTable localTable;
private JButton closeButton;
public final RequestProcessor.Task initTask;
private final Object initLock = new Object();
private Object helpInstance = null;
private static volatile RequestProcessor.Task runningTask;
private static Collection<Runnable> runOnCancel = null;
private boolean wasSettings = false;
public static final int INDEX_OF_UPDATES_TAB = 0;
public static final int INDEX_OF_AVAILABLE_TAB = 1;
public static final int INDEX_OF_DOWNLOAD_TAB = 2;
public static final int INDEX_OF_INSTALLED_TAB = 3;
public static final int INDEX_OF_SETTINGS_TAB = 4;
public static final String[] TAB_NAMES = { "update", "available", "local", "installed" }; //NOI18N
private int initialTabToSelect;
private boolean detailView;
public static final String DETAIL_VIEW_SELECTED_PROP = "plugin.manager.detail.view.selected";//NOI18N
public PluginManagerUI (JButton closeButton ) {
this(closeButton, null, true);
}
public PluginManagerUI (JButton closeButton, Object initialTab) {
this(closeButton, initialTab, Boolean.getBoolean(DETAIL_VIEW_SELECTED_PROP));
}
/** Creates new form PluginManagerUI */
public PluginManagerUI (JButton closeButton, Object initialTab, boolean detailView) {
this.detailView = detailView;
this.closeButton = closeButton;
this.willClose = false;
int selIndex = -1;
for( int i=0; i<TAB_NAMES.length; i++ ) {
if( TAB_NAMES[i].equals(initialTab) ) {
selIndex = i;
break;
}
}
if( selIndex < 0 && null != initialTab ) {
throw new IllegalArgumentException("Invalid tab name: " + initialTab); //NOI18N
}
initialTabToSelect = selIndex;
initComponents ();
//start initialize method as soon as possible
initTask = Utilities.startAsWorkerThread (new Runnable () {
@Override
public void run () {
postInitComponents ();
initialize ();
}
});
}
boolean isDetailView() {
return detailView;
}
void setDetailView(boolean detailView) {
this.detailView = detailView;
}
private Window findWindowParent () {
Component c = this;
while(c != null) {
c = c.getParent ();
if (c instanceof Window) {
return (Window)c;
}
}
return null;
}
void setWaitingState (boolean waitingState) {
boolean enabled = !waitingState;
for (Component c : tpTabs.getComponents()) {
if (c instanceof UnitTab) {
((UnitTab) c).setWaitingState (waitingState);
}
}
// the Close & Help buttons are always enabled
bClose.setEnabled (true);
Component parent = getParent ();
Component rootPane = getRootPane ();
if (parent != null) {
parent.setEnabled (enabled);
}
if (rootPane != null) {
if (enabled) {
rootPane.setCursor (null);
} else {
rootPane.setCursor (Cursor.getPredefinedCursor (Cursor.WAIT_CURSOR));
}
}
}
@Override
public void addNotify () {
super.addNotify ();
//show progress for initialize method
final Window w = findWindowParent ();
if (w != null) {
w.addWindowListener (new WindowAdapter (){
@Override
public void windowOpened (WindowEvent e) {
final WindowAdapter waa = this;
setWaitingState (true);
Utilities.startAsWorkerThread (PluginManagerUI.this,
new Runnable () {
@Override
public void run () {
try {
initTask.waitFinished ();
w.removeWindowListener (waa);
} finally {
setWaitingState (false);
}
}
},
NbBundle.getMessage (PluginManagerUI.class, "UnitTab_InitAndCheckingForUpdates"),
Utilities.getTimeOfInitialization ());
}
});
}
HelpCtx.setHelpIDString (this, PluginManagerUI.class.getName ());
tpTabs.addChangeListener (new ChangeListener () {
@Override
public void stateChanged (ChangeEvent evt) {
HelpCtx.setHelpIDString (PluginManagerUI.this, getHelpCtx ().getHelpID ());
}
});
}
@Override
public void removeNotify () {
super.removeNotify ();
unitilialize ();
}
public void close () {
bClose.doClick ();
}
private boolean willClose;
public void willClose() {
willClose = true;
}
public boolean isClosing() {
return willClose;
}
private void initialize () {
try {
final List<UpdateUnit> uu = UpdateManager.getDefault().getUpdateUnits(Utilities.getUnitTypes());
// List<UnitCategory> precompute1 = Utilities.makeUpdateCategories (uu, false);
if (localTable != null) {
final List<UpdateUnit> nbms = new ArrayList<UpdateUnit>(((LocallyDownloadedTableModel) localTable.getModel()).getLocalDownloadSupport().getUpdateUnits());
List<UnitCategory> precompute2 = Utilities.makeUpdateCategories (nbms, true);
}
// postpone later
refreshUnitsInBackground(uu);
SwingUtilities.invokeAndWait (new Runnable () {
@Override
public void run () {
refreshUnitsInAWT();
setSelectedTab ();
}
});
} catch (InterruptedException ex) {
Exceptions.printStackTrace (ex);
} catch (InvocationTargetException ex) {
Exceptions.printStackTrace (ex);
}
}
//workaround of #96282 - Memory leak in org.netbeans.core.windows.services.NbPresenter
private void unitilialize () {
Utilities.startAsWorkerThread (new Runnable () {
@Override
public void run () {
//ensures that uninitialization runs after initialization
initTask.waitFinished ();
AutoupdateCheckScheduler.runCheckAvailableUpdates (0);
//ensure exclusivity between this uninitialization code and refreshUnits (which can run even after this dialog is disposed)
synchronized(initLock) {
units = null;
installedTable = null;
availableTable = null;
updateTable = null;
localTable = null;
}
}
}, 10000);
}
void setProgressComponent (final JLabel detail, final JComponent progressComponent) {
if (SwingUtilities.isEventDispatchThread ()) {
setProgressComponentInAwt (detail, progressComponent);
} else {
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
setProgressComponentInAwt (detail, progressComponent);
}
});
}
}
private UnitTable createTabForModel(final UnitCategoryTableModel model) {
final UnitTable table = new UnitTable(model);
selectFirstRow(table);
final UnitTab tab = new UnitTab(table, new UnitDetails(), this);
if (initTask != null) {
tab.setWaitingState(! initTask.isFinished());
initTask.addTaskListener(new TaskListener() {
@Override
public void taskFinished(Task task) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
tab.setWaitingState(false);
}
});
}
});
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
tpTabs.add(tab, model.getTabIndex());
decorateTabTitle(table);
}
});
return table;
}
private void setProgressComponentInAwt (JLabel detail, JComponent progressComponent) {
assert pProgress != null;
assert SwingUtilities.isEventDispatchThread () : "Must be called in EQ.";
progressComponent.setMinimumSize (progressComponent.getPreferredSize ());
pProgress.setVisible (true);
java.awt.GridBagConstraints gridBagConstraints;
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 12);
pProgress.add(progressComponent, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
gridBagConstraints.weightx = 1.0;
pProgress.add(detail, gridBagConstraints);
revalidate ();
}
void unsetProgressComponent (final JLabel detail, final JComponent progressComponent) {
if (SwingUtilities.isEventDispatchThread ()) {
unsetProgressComponentInAwt (detail, progressComponent);
} else {
SwingUtilities.invokeLater (new Runnable () {
@Override
public void run () {
unsetProgressComponentInAwt (detail, progressComponent);
}
});
}
}
private void setSelectedTab() {
if( initialTabToSelect >= 0 && initialTabToSelect != tpTabs.getSelectedIndex()
&& initialTabToSelect < tpTabs.getComponentCount() ) {
Component c = tpTabs.getComponentAt(initialTabToSelect);
if (c instanceof UnitTab) {
UnitTab unitTab = (UnitTab) c;
if (unitTab.getModel().isTabEnabled() && unitTab.getModel().canBePrimaryTab() ) {
tpTabs.setSelectedIndex(initialTabToSelect);
initialTabToSelect = -1;
return;
}
}
}
initialTabToSelect = -1;
Component component = tpTabs.getSelectedComponent();
if (component instanceof UnitTab) {
UnitTab unitTab = (UnitTab) component;
if (!unitTab.getModel().isTabEnabled()) {
for (int i = 0; i < tpTabs.getComponentCount(); i++) {
component = tpTabs.getComponentAt(i);
if (component instanceof UnitTab) {
unitTab = (UnitTab) component;
if (unitTab.getModel().isTabEnabled() && unitTab.getModel().canBePrimaryTab()) {
tpTabs.setSelectedIndex(i);
break;
}
} else {
tpTabs.setSelectedIndex(i);
break;
}
}
}
}
}
private void unsetProgressComponentInAwt (JLabel detail, JComponent progressComponent) {
assert pProgress != null;
assert SwingUtilities.isEventDispatchThread () : "Must be called in EQ.";
pProgress.remove (detail);
pProgress.remove (progressComponent);
pProgress.setVisible (false);
revalidate ();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
tpTabs = new javax.swing.JTabbedPane();
pProgress = new javax.swing.JPanel();
bClose = closeButton;
bHelp = new javax.swing.JButton();
tpTabs.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
tpTabsStateChanged(evt);
}
});
pProgress.setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(bClose, org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "UnitTab_bClose_Text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(bHelp, org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "PluginManagerUI.bHelp.text")); // NOI18N
bHelp.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
bHelpActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(pProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 562, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 157, Short.MAX_VALUE)
.addComponent(bClose)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(bHelp))
.addComponent(tpTabs, javax.swing.GroupLayout.DEFAULT_SIZE, 864, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(tpTabs, javax.swing.GroupLayout.DEFAULT_SIZE, 471, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(pProgress, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(bHelp)
.addComponent(bClose, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addContainerGap())
);
tpTabs.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACN_Tabs")); // NOI18N
tpTabs.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACD_Tabs")); // NOI18N
getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACN_PluginManagerUI")); // NOI18N
getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(PluginManagerUI.class, "ACD_PluginManagerUI")); // NOI18N
}// </editor-fold>//GEN-END:initComponents
private void tpTabsStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tpTabsStateChanged
Component component = ((JTabbedPane) evt.getSource ()).getSelectedComponent ();
if (component instanceof SettingsTab) {
int i = ((SettingsTab)component).getSelectedRow();
((SettingsTab)component).getSettingsTableModel ().refreshModel ();
if (i > -1) {
((SettingsTab)component).setSelectedRow(i);
}
wasSettings = true;
} else {
if (wasSettings) {
final UnitCategoryTableModel availableModel = (UnitCategoryTableModel) (availableTable).getModel ();
final Map<String, Boolean> availableState = UnitCategoryTableModel.captureState (availableModel.getUnits ());
((SettingsTab) tpTabs.getComponentAt (INDEX_OF_SETTINGS_TAB)).doLazyRefresh (new Runnable () { // get SettingsTab
@Override
public void run () {
UnitCategoryTableModel.restoreState (availableModel.getUnits (), availableState, false);
}
});
}
wasSettings = false;
}
}//GEN-LAST:event_tpTabsStateChanged
private void bHelpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bHelpActionPerformed
getHelpCtx().display();
}//GEN-LAST:event_bHelpActionPerformed
private HelpCtx getHelpCtx() {
String id = PluginManagerUI.class.getName ();
Component c = tpTabs.getSelectedComponent ();
if (c instanceof UnitTab) {
id = ((UnitTab) c).getHelpId ();
} else if (c instanceof SettingsTab) {
id = SettingsTab.class.getName ();
}
return new HelpCtx (id);
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton bClose;
private javax.swing.JButton bHelp;
private javax.swing.JPanel pProgress;
private javax.swing.JTabbedPane tpTabs;
// End of variables declaration//GEN-END:variables
private void postInitComponents () {
Containers.initNotify ();
updateTable = createTabForModel(new UpdateTableModel(units));
availableTable = createTabForModel(new AvailableTableModel (units));
final LocalDownloadSupport localDownloadSupport = new LocalDownloadSupport();
final LocallyDownloadedTableModel localTM = new LocallyDownloadedTableModel(localDownloadSupport);
localTable = createTabForModel(localTM);
installedTable = createTabForModel(new InstalledTableModel(units));
DropTargetListener l = new LocallDownloadDnD(localDownloadSupport, localTM, this);
final DropTarget dt = new DropTarget(null, l);
dt.setActive(true);
this.setDropTarget(dt);
final SettingsTab tab = new SettingsTab(this);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
tpTabs.add(tab, INDEX_OF_SETTINGS_TAB);
tpTabs.setTitleAt(INDEX_OF_SETTINGS_TAB, tab.getDisplayName());
}
});
setWaitingState(true);
}
void decorateTabTitle (UnitTable table) {
if (table == null) {
return ;
}
UnitCategoryTableModel model = (UnitCategoryTableModel)table.getModel();
int index = model.getTabIndex();
tpTabs.setTitleAt (index, model.getDecoratedTabTitle());
tpTabs.setEnabledAt(index, model.isTabEnabled());
tpTabs.setToolTipTextAt(index, model.getTabTooltipText()); // NOI18N
}
void undecorateTabTitles () {
tpTabs.setTitleAt (INDEX_OF_UPDATES_TAB, NbBundle.getMessage (PluginManagerUI.class, "PluginManagerUI_UnitTab_Update_Title")); // NOI18N // Updates tab
tpTabs.setTitleAt (INDEX_OF_AVAILABLE_TAB, NbBundle.getMessage (PluginManagerUI.class, "PluginManagerUI_UnitTab_Available_Title")); // NOI18N // Available tab
}
private int findRowWithFirstUnit (UnitCategoryTableModel model) {
for (int row = 0; row <= model.getRowCount (); row++) {
if (model.getUnitAtRow (row) != null) {
return row;
}
}
return -1;
}
private void selectFirstRow (UnitTable table) {
if (table != null && table.getSelectedRow () == -1) {
UnitCategoryTableModel model = (UnitCategoryTableModel)table.getModel ();
int row = findRowWithFirstUnit (model);
if (row != -1) {
table.getSelectionModel ().setSelectionInterval (row, row);
}
}
}
private void refreshUnitsInBackground(List<UpdateUnit> newUnits) {
//ensure exclusivity between this refreshUnits code(which can run even after this dialog is disposed) and uninitialization code
synchronized(initLock) {
//return immediatelly if uninialization(after removeNotify) was alredy called
if (units == null) {
return;
}
//TODO: REVIEW THIS CODE - problem is that is called from called from AWT thread
//UpdateManager.getDefault().getUpdateUnits() should never be called fromn AWT because it may cause
//long terming starvation because in fact impl. of this method calls AutoUpdateCatalogCache.getCatalogURL
//which is synchronized and may wait until cache is created
//even more AutoUpdateCatalog.getUpdateItems () can at first start call refresh and thus writeToCache again
units = newUnits;
InstalledTableModel installTableModel = (InstalledTableModel)installedTable.getModel();
UnitCategoryTableModel updateTableModel = ((UnitCategoryTableModel)updateTable.getModel());
UnitCategoryTableModel availableTableModel = ((UnitCategoryTableModel)availableTable.getModel());
LocallyDownloadedTableModel localTableModel = ((LocallyDownloadedTableModel)localTable.getModel());
updateTableModel.setUnits(UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.MODULE));
List<UpdateUnit> features = UpdateManager.getDefault().getUpdateUnits(UpdateManager.TYPE.FEATURE);
if (isDetailView() && !features.isEmpty()) {
installTableModel.setUnits(units);
} else {
installTableModel.setUnits(units, features);
}
availableTableModel.setUnits(units);
localTableModel.setUnits(units);
}
}
private void refreshUnitsInAWT() {
assert SwingUtilities.isEventDispatchThread() : "Call from AWT only";
//ensure exclusivity between this refreshUnits code(which can run even after this dialog is disposed) and uninitialization code
synchronized(initLock) {
selectFirstRow(installedTable);
selectFirstRow(updateTable);
selectFirstRow(availableTable);
selectFirstRow(localTable);
decorateTabTitle(updateTable);
decorateTabTitle(availableTable);
decorateTabTitle(localTable);
decorateTabTitle(installedTable);
Component[] components = tpTabs.getComponents();
for (Component component : components) {
if (component instanceof UnitTab) {
UnitTab tab = (UnitTab)component;
tab.refreshState();
}
}
setSelectedTab();
}
}
static boolean canContinue (String message) {
return NotifyDescriptor.YES_OPTION.equals (DialogDisplayer.getDefault ().notify (new NotifyDescriptor.Confirmation (message)));
}
public static void registerRunningTask (RequestProcessor.Task it) {
assert runningTask == null || runningTask.isFinished () : "Only once task can be running. Already running : " + runningTask;
runningTask = it;
}
public static void unregisterRunningTask () {
runningTask = null;
runOnCancel = null;
}
public static RequestProcessor.Task getRunningTask (Runnable onCancel) {
if (runningTask != null) {
if (runOnCancel == null) {
runOnCancel = new HashSet<Runnable>();
}
runOnCancel.add(onCancel);
}
return runningTask;
}
public static RequestProcessor.Task getRunningTask () {
return runningTask;
}
public static void cancelRunningTask() {
if (runningTask != null && runOnCancel != null) {
runningTask = null;
for (Runnable run : runOnCancel) {
run.run();
}
runOnCancel = null;
} else {
runningTask = null;
}
}
//TODO: all the request for refresh should be cancelled if there is already one such running refresh task
public void updateUnitsChanged () {
refreshUnitsInBackground(UpdateManager.getDefault().getUpdateUnits(Utilities.getUnitTypes()));
if (! SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
refreshUnitsInAWT();
}
});
} else {
refreshUnitsInAWT();
}
}
public void tableStructureChanged () {
installedTable.resortByDefault ();
((UnitCategoryTableModel) installedTable.getModel ()).fireTableStructureChanged ();
installedTable.setColumnsSize ();
installedTable.resetEnableRenderer ();
updateTable.resortByDefault ();
((UnitCategoryTableModel) updateTable.getModel ()).fireTableStructureChanged ();
updateTable.setColumnsSize ();
availableTable.resortByDefault ();
((UnitCategoryTableModel) availableTable.getModel ()).fireTableStructureChanged ();
availableTable.setColumnsSize ();
}
public void buttonsChanged () {
Component c = tpTabs.getSelectedComponent ();
if (c instanceof UnitTab) {
((UnitTab) c).refreshState ();
}
}
final void setSelectedTab(UnitTab tab) {
tpTabs.setSelectedComponent(tab);
}
final UnitTab findTabForModel(UnitCategoryTableModel model) {
for (Component c : tpTabs.getComponents()) {
if (c instanceof UnitTab) {
UnitTab ut = (UnitTab) c;
if (ut.getModel() == model) {
return ut;
}
}
}
return null;
}
final String getSelectedTabName() {
int index = initialTabToSelect;
if (index == -1) {
index = tpTabs.getSelectedIndex();
}
return TAB_NAMES[index];
}
}