/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.ins.gui;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import com.sun.cri.ci.*;
import com.sun.max.gui.*;
import com.sun.max.ins.*;
import com.sun.max.ins.InspectionSettings.*;
import com.sun.max.ins.debug.*;
import com.sun.max.ins.view.*;
import com.sun.max.program.option.*;
import com.sun.max.tele.*;
/**
* Encapsulates preferences about which columns should be visible in a table-based viewer, selected from
* among those that are supported in the current session.
* <p>
* There are three modes of use:
* <ul>
* <li>1. Global, persistent preferences.</li>
* <li>2. Singleton instance-based preferences, where the set of persistent choices is the only set.</li>
* <li>3. Instance-based preferences, which default to a global preference, but which can be overridden
* with preferences that do not persist.</li>
* </ul>
* <br>
* There are two ways to be notified of a change, which can be applied to either a global or per-instance set:
* <ol>
* <li>Register a {@link TableColumnViewPreferenceListener}, which reports that there has been some change.</li>
* <li>Override {@link #setIsVisible(ColumnKind, boolean)} and take measures, either before or after the super method has
* updated the internal state of the preferences.</li>
* </ol>
*/
public abstract class TableColumnVisibilityPreferences<ColumnKind_Type extends ColumnKind> implements InspectionHolder {
public interface TableColumnViewPreferenceListener {
void tableColumnViewPreferencesChanged();
}
private final Inspection inspection;
private final String tracePrefix;
/**
* A predicate specifying which columns are to be displayed in the table-based viewer.
*/
private final Map<ColumnKind_Type, Boolean> visibleColumns;
/**
* If non-null, this preferences object should persist any changes via the inspection's {@linkplain InspectionSettings settings}.
*/
protected final SaveSettingsListener saveSettingsListener;
private final Set<TableColumnViewPreferenceListener> tableColumnViewPreferenceListeners = CiUtil.newIdentityHashSet();
/**
* All of the defined column kinds.
*/
private final ColumnKind_Type [] columnTypeValues;
/**
* The column kinds that are supported in this session.
*/
private final ArrayList<ColumnKind_Type> supportedColumnTypes;
/**
* Mode 1: Global, persistent preferences.
* Mode 2: Singleton: Global and instance-preferences are identical.
*
* Creates an object for storing the preferences about which columns should be visible in a table-based viewer.
* The returned object persists itself via the inspection's {@linkplain InspectionSettings settings}. That is, this
* constructor should be used to create a set of preferences that are applied as the default for all the
* viewers of a specific type.
*
* @param inspection the inspection context
* @param name the name under which these preferences should be persisted in the inspection's settings
* @param columnTypeValues the constants defined by {@code columnTypeClass}
*/
protected TableColumnVisibilityPreferences(Inspection inspection,
String name,
ColumnKind_Type[] columnTypeValues) {
this.inspection = inspection;
this.tracePrefix = "[" + getClass().getSimpleName() + "] ";
this.visibleColumns = new HashMap<ColumnKind_Type, Boolean>();
this.columnTypeValues = columnTypeValues;
this.supportedColumnTypes = new ArrayList<ColumnKind_Type>();
for (ColumnKind_Type columnKind : columnTypeValues) {
if (columnKind.isSupported(inspection.vm())) {
supportedColumnTypes.add(columnKind);
}
}
final InspectionSettings settings = inspection.settings();
saveSettingsListener = new AbstractSaveSettingsListener(name) {
public void saveSettings(SaveSettingsEvent saveSettingsEvent) {
TableColumnVisibilityPreferences.this.saveSettings(saveSettingsEvent);
}
};
settings.addSaveSettingsListener(saveSettingsListener);
for (ColumnKind_Type columnType : supportedColumnTypes) {
final boolean defaultVisibility = defaultVisibility(columnType);
final Boolean visible = settings.get(saveSettingsListener, getPreferenceName(columnType), OptionTypes.BOOLEAN_TYPE, defaultVisibility);
visibleColumns.put(columnType, visible);
}
}
/**
* @return the textual name under which the visibility of the column type will be made persistent.
*/
private String getPreferenceName(ColumnKind_Type columnType) {
return columnType.name().toLowerCase();
}
/**
* Mode 3: Instance-based preferences
*
* Creates an object for storing the preferences about which columns should be visible in a table-based viewer.
* The returned object does not persist itself via the inspection's {@linkplain InspectionSettings settings}. That is, this
* constructor should be used to create a set of preferences specific to an individual instance of a viewer.
*
* @param defaultPreferences the preferences from which the returned object should get its default values
*/
public TableColumnVisibilityPreferences(TableColumnVisibilityPreferences<ColumnKind_Type> defaultPreferences) {
inspection = defaultPreferences.inspection;
this.tracePrefix = "[" + getClass().getSimpleName() + "] ";
this.saveSettingsListener = null;
this.columnTypeValues = defaultPreferences.columnTypeValues;
this.supportedColumnTypes = defaultPreferences.supportedColumnTypes;
this.visibleColumns = new HashMap<ColumnKind_Type, Boolean>(defaultPreferences.visibleColumns);
}
public final Inspection inspection() {
return inspection;
}
public final MaxVM vm() {
return inspection.vm();
}
public final InspectorGUI gui() {
return inspection.gui();
}
public final InspectionFocus focus() {
return inspection.focus();
}
public final InspectionViews views() {
return inspection.views();
}
public final InspectionActions actions() {
return inspection.actions();
}
public final InspectionPreferences preference() {
return inspection.preference();
}
/**
* Adds a listener for view update when a preference changes.
*/
public final void addListener(TableColumnViewPreferenceListener listener) {
tableColumnViewPreferenceListeners.add(listener);
}
/**
* Removes a listener for view update when a preference changes.
*/
public final void removeListener(TableColumnViewPreferenceListener listener) {
tableColumnViewPreferenceListeners.remove(listener);
}
protected void saveSettings(SaveSettingsEvent saveSettingsEvent) {
for (Map.Entry<ColumnKind_Type, Boolean> entry : visibleColumns.entrySet()) {
saveSettingsEvent.save(getPreferenceName(entry.getKey()), entry.getValue());
}
}
/**
* Determines if a given column type is visible by default (i.e. before the user has a chance to modify these
* preferences).
*
* @param columnType denotes a column in a table-base viewer
*/
protected boolean defaultVisibility(ColumnKind_Type columnType) {
return columnType.defaultVisibility();
}
/**
* Determines if a given column type is allowed to be invisible.
*
* @param columnType denotes a column in a table-base viewer
*/
protected boolean canBeMadeInvisible(ColumnKind_Type columnType) {
return columnType.canBeMadeInvisible();
}
/**
* Gets the label to be used for a given column type.
*
* @param columnType denotes a column in a table-base viewer
*/
protected String label(ColumnKind_Type columnType) {
return columnType.label();
}
/**
* Updates this preferences object to indicate whether a given column type should be made visible;
* notifies any change listeners.
*/
protected void setIsVisible(ColumnKind_Type columnType, boolean flag) {
visibleColumns.put(columnType, flag);
if (saveSettingsListener != null) {
inspection.settings().save();
}
notifyChangeListeners();
}
/**
* Announces to all registered listeners that column view preferences, or perhaps preferences managed by subclasses have changed.
*/
private void notifyChangeListeners() {
for (TableColumnViewPreferenceListener listener : tableColumnViewPreferenceListeners) {
listener.tableColumnViewPreferencesChanged();
}
}
/**
* Determines if this preferences object indicates that a given column type should be made visible.
*/
public boolean isVisible(ColumnKind_Type columnType) {
return visibleColumns.get(columnType);
}
/**
* Gets a panel that has controls for specifying which columns should be visible.
*/
public JPanel getPanel() {
final InspectorCheckBox[] checkBoxes = new InspectorCheckBox[columnTypeValues.length];
final ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
final Object source = e.getItemSelectable();
for (ColumnKind_Type columnType : supportedColumnTypes) {
final InspectorCheckBox checkBox = checkBoxes[columnType.ordinal()];
if (source == checkBox) {
if (checkBox.isSelected() != isVisible(columnType)) {
setIsVisible(columnType, checkBox.isSelected());
}
return;
}
}
}
};
final JPanel content = new InspectorPanel(inspection);
content.setLayout(new BorderLayout());
content.add(new TextLabel(inspection, "View Columns: "), BorderLayout.WEST);
final JPanel choices = new InspectorPanel(inspection);
choices.setLayout(new GridLayout(0, 5));
for (ColumnKind_Type columnType : supportedColumnTypes) {
if (canBeMadeInvisible(columnType)) {
final InspectorCheckBox checkBox =
new InspectorCheckBox(inspection, label(columnType), saveSettingsListener != null ? "Display column in all views?" : "Display column in this view?", isVisible(columnType));
checkBox.addItemListener(itemListener);
checkBoxes[columnType.ordinal()] = checkBox;
choices.add(checkBox);
}
}
content.add(choices, BorderLayout.CENTER);
final JPanel panel = new InspectorPanel(inspection, new BorderLayout());
panel.add(content, BorderLayout.WEST);
return panel;
}
/**
* A dialog that allows the user to specify preferences about column visibility.
*/
public static final class ColumnPreferencesDialog<ColumnKind_Type extends ColumnKind> extends InspectorDialog {
/**
* Create a dialog that allows the user to specify both global preferences as well as preferences for an individual viewer.
* @param inspection
* @param title
* @param instancePreferences
* @param globalPreferences
*/
public ColumnPreferencesDialog(Inspection inspection,
String title,
TableColumnVisibilityPreferences<ColumnKind_Type> instancePreferences,
TableColumnVisibilityPreferences<ColumnKind_Type> globalPreferences) {
super(inspection, title, true);
final JPanel contentPanel = new InspectorPanel(inspection, new BorderLayout());
final JPanel prefPanel = new InspectorPanel(inspection, new SpringLayout());
final Border border = BorderFactory.createLineBorder(Color.black);
final JPanel thisViewLabelPanel = new InspectorPanel(inspection, new BorderLayout());
thisViewLabelPanel.setBorder(border);
thisViewLabelPanel.add(new TextLabel(inspection, "This View"), BorderLayout.WEST);
prefPanel.add(thisViewLabelPanel);
final JPanel viewOptionPanel = instancePreferences.getPanel();
viewOptionPanel.setBorder(border);
prefPanel.add(viewOptionPanel);
final JPanel preferencesLabelPanel = new InspectorPanel(inspection, new BorderLayout());
preferencesLabelPanel.setBorder(border);
preferencesLabelPanel.add(new TextLabel(inspection, "Prefs"), BorderLayout.WEST);
prefPanel.add(preferencesLabelPanel);
final JPanel preferencesPanel = globalPreferences.getPanel();
preferencesPanel.setBorder(border);
prefPanel.add(preferencesPanel);
SpringUtilities.makeCompactGrid(prefPanel, 2);
final JPanel buttonsPanel = new InspectorPanel(inspection);
buttonsPanel.add(new JButton(new InspectorAction(inspection, "Close") {
@Override
protected void procedure() {
dispose();
}
}));
contentPanel.add(prefPanel, BorderLayout.CENTER);
contentPanel.add(buttonsPanel, BorderLayout.SOUTH);
setContentPane(contentPanel);
pack();
inspection.gui().setLocationRelativeToMouse(this, 5);
setVisible(true);
}
/**
* Create a dialog that allows the user to specify preferences for an individual viewer.
* @param inspection
* @param title
* @param instancePreferences
*/
public ColumnPreferencesDialog(Inspection inspection,
String title,
TableColumnVisibilityPreferences<ColumnKind_Type> instancePreferences) {
super(inspection, title, true);
final JPanel contentPanel = new InspectorPanel(inspection, new BorderLayout());
final JPanel viewOptionPanel = instancePreferences.getPanel();
viewOptionPanel.setBorder(BorderFactory.createLineBorder(Color.black));
final JPanel buttonsPanel = new InspectorPanel(inspection);
buttonsPanel.add(new JButton(new InspectorAction(inspection, "Close") {
@Override
protected void procedure() {
dispose();
}
}));
contentPanel.add(viewOptionPanel, BorderLayout.CENTER);
contentPanel.add(buttonsPanel, BorderLayout.SOUTH);
setContentPane(contentPanel);
pack();
inspection.gui().setLocationRelativeToMouse(this, 5);
setVisible(true);
}
}
}