/*
* Copyright (c) 2009, 2011, 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;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import com.sun.cri.ci.*;
import com.sun.max.ins.InspectionSettings.AbstractSaveSettingsListener;
import com.sun.max.ins.InspectionSettings.SaveSettingsEvent;
import com.sun.max.ins.InspectorKeyBindings.KeyBindingMap;
import com.sun.max.ins.gui.*;
import com.sun.max.ins.util.*;
import com.sun.max.program.*;
import com.sun.max.program.option.*;
/**
* Manages persistent general preferences for inspection sessions.
*/
public final class InspectionPreferences extends AbstractSaveSettingsListener {
private static final int TRACE_VALUE = 2;
/**
* Enum for the mechanism to be used when attempting to
* {@linkplain Inspection#viewSourceExternally(CiCodePos) view} a source file in an external tool.
*/
public enum ExternalViewerType {
/**
* Specifies that there is no external viewer available.
*/
NONE,
/**
* Specifies that an external viewer is listening on a socket for 'open file' requests. A request is a string of
* bytes matching this pattern:
*
* <pre>
* <path to file>|<line number>
* </pre>
*
* For example, the following code generates the bytes of a typical command:
*
* <pre>
* "/maxine/VM/src/com/sun/max/vm/MaxineVM.java|239".getBytes()
* </pre>
*/
SOCKET,
/**
* Specifies that an external tool can be launched as a separate process.
*/
PROCESS;
public static List<ExternalViewerType> VALUES = Arrays.asList(values());
}
/**
* Policies for how long tool tip text remains visible.
*/
public enum ToolTipDismissDelayPolicy {
/**
* Use the default setting.
*/
DEFAULT("Default delay"),
/**
* Double the default tool tip display time.
*/
EXTENDED("Extended delay"),
/**
* Tool tip display does not stop.
*/
PERSISTENT("Persistent");
private final String label;
private ToolTipDismissDelayPolicy(String label) {
this.label = label;
}
public String label() {
return label;
}
public static final List<ToolTipDismissDelayPolicy> VALUES = Arrays.asList(values());
}
private static final String INSPECTION_SETTINGS_NAME = "prefs";
private static final String KEY_BINDINGS_PREFERENCE = "keyBindings";
private static final String DISPLAY_STYLE_PREFERENCE = "displayStyle";
private static final String TOOLTIP_DELAY_POLICY = "toolTipDelay";
private static final String INVESTIGATE_WORD_VALUES_PREFERENCE = "investigateWordValues";
private static final String FORCE_TEXTUAL_WORD_VALUE_DISPLAY = "forceTextualWordValueDisplay";
private static final String EXTERNAL_VIEWER_PREFERENCE = "externalViewer";
private final Inspection inspection;
private final InspectionSettings settings;
private final InspectorStyleFactory styleFactory;
private InspectorStyle style;
private ToolTipDismissDelayPolicy toolTipDismissDelayPolicy = ToolTipDismissDelayPolicy.DEFAULT;
private final int defaultToolTipDismissDelay;
private InspectorGeometry geometry;
private boolean investigateWordValues = true;
private boolean forceTextualWordValueDisplay = false;
private ExternalViewerType externalViewerType = ExternalViewerType.SOCKET;
private final Map<ExternalViewerType, String> externalViewerConfig = new EnumMap<ExternalViewerType, String>(ExternalViewerType.class);
private final List<InspectorAction> actionsWithKeyBindings = new ArrayList<InspectorAction>();
private KeyBindingMap keyBindingMap = InspectorKeyBindings.DEFAULT_KEY_BINDINGS;
/**
* Creates a new, global instance for managing VM inspection preferences.
*
* @param inspection the inspection session state
* @param settings the manager for settings, already initialized.
*/
InspectionPreferences(Inspection inspection, InspectionSettings settings) {
super(INSPECTION_SETTINGS_NAME);
this.inspection = inspection;
this.settings = settings;
this.styleFactory = new InspectorStyleFactory(inspection);
this.style = styleFactory.defaultStyle();
// Default socket for EclipseCall is 2341
externalViewerConfig.put(ExternalViewerType.SOCKET, String.valueOf(2341));
// TODO (mlvdv) need some way to configure this default in conjunction with style defaults.
//this.geometry = new InspectorGeometry10Pt();
this.geometry = new InspectorGeometry12Pt();
defaultToolTipDismissDelay = ToolTipManager.sharedInstance().getDismissDelay();
settings.addSaveSettingsListener(this);
try {
if (settings.containsKey(this, KEY_BINDINGS_PREFERENCE)) {
final String keyBindingsName = settings.get(this, KEY_BINDINGS_PREFERENCE, OptionTypes.STRING_TYPE, null);
final KeyBindingMap keyBindingMap = KeyBindingMap.ALL.get(keyBindingsName);
if (keyBindingMap != null) {
setKeyBindingMap(keyBindingMap);
} else {
InspectorWarning.message(inspection, "Unknown key bindings name ignored: " + keyBindingsName);
}
}
if (settings.containsKey(this, DISPLAY_STYLE_PREFERENCE)) {
final String displayStyleName = settings.get(this, DISPLAY_STYLE_PREFERENCE, OptionTypes.STRING_TYPE, null);
InspectorStyle style = styleFactory.findStyle(displayStyleName);
if (style == null) {
style = styleFactory.defaultStyle();
}
this.style = style;
}
investigateWordValues = settings.get(this, INVESTIGATE_WORD_VALUES_PREFERENCE, OptionTypes.BOOLEAN_TYPE, true);
forceTextualWordValueDisplay = settings.get(this, FORCE_TEXTUAL_WORD_VALUE_DISPLAY, OptionTypes.BOOLEAN_TYPE, false);
setToolTipDismissDelay(settings.get(this, TOOLTIP_DELAY_POLICY, new OptionTypes.EnumType<ToolTipDismissDelayPolicy>(ToolTipDismissDelayPolicy.class), ToolTipDismissDelayPolicy.DEFAULT));
externalViewerType = settings.get(this, EXTERNAL_VIEWER_PREFERENCE, new OptionTypes.EnumType<ExternalViewerType>(ExternalViewerType.class), ExternalViewerType.SOCKET);
for (ExternalViewerType externalViewerType : ExternalViewerType.VALUES) {
final String config = settings.get(this, "externalViewer." + externalViewerType.name(), OptionTypes.STRING_TYPE, null);
if (config != null) {
externalViewerConfig.put(externalViewerType, config);
}
}
} catch (Option.Error optionError) {
InspectorWarning.message(inspection, optionError);
}
}
/**
* The current configuration for visual style.
*/
public InspectorStyle style() {
return style;
}
private void setStyle(InspectorStyle style) {
this.style = style;
inspection.updateViewConfiguration();
}
private void setToolTipDismissDelay(ToolTipDismissDelayPolicy toolTipDelay) {
this.toolTipDismissDelayPolicy = toolTipDelay;
switch(toolTipDelay) {
case DEFAULT:
ToolTipManager.sharedInstance().setDismissDelay(defaultToolTipDismissDelay);
break;
case EXTENDED:
ToolTipManager.sharedInstance().setDismissDelay(defaultToolTipDismissDelay * 2);
break;
case PERSISTENT:
ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
break;
}
}
/**
* Default size and layout for windows; overridden by persistent settings from previous sessions.
*/
public InspectorGeometry geometry() {
return geometry;
}
/**
* @return Does the Inspector attempt to discover proactively what word values might point to in the VM.
*/
public boolean investigateWordValues() {
return investigateWordValues;
}
/**
* @return Force all Word value labels to display initially in textual format when possible?
*/
public boolean forceTextualWordValueDisplay() {
return forceTextualWordValueDisplay;
}
public ExternalViewerType externalViewerType() {
return externalViewerType;
}
public Map<ExternalViewerType, String> externalViewerConfig() {
return externalViewerConfig;
}
public void setExternalViewer(ExternalViewerType externalViewerType) {
if (this.externalViewerType != externalViewerType) {
this.externalViewerType = externalViewerType;
settings.save();
}
}
public void setExternalViewerConfiguration(ExternalViewerType externalViewerType, String config) {
if (externalViewerConfig.get(externalViewerType) != config) {
externalViewerConfig.put(externalViewerType, config);
settings.save();
}
}
/**
* Informs this inspection of a new action that can operate on this inspection.
*/
public void registerAction(InspectorAction inspectorAction) {
final Class<? extends InspectorAction> actionClass = inspectorAction.getClass();
if (InspectorKeyBindings.KEY_BINDABLE_ACTIONS.contains(actionClass)) {
actionsWithKeyBindings.add(inspectorAction);
final KeyStroke keyStroke = keyBindingMap.get(actionClass);
inspectorAction.putValue(Action.ACCELERATOR_KEY, keyStroke);
}
}
/**
* Updates the current key binding map for this inspection.
*
* @param keyBindingMap a key binding map. If this value differs from the current key
* binding map, then the accelerator keys of all the relevant Inspector actions are updated.
*/
public void setKeyBindingMap(KeyBindingMap keyBindingMap) {
if (keyBindingMap != this.keyBindingMap) {
this.keyBindingMap = keyBindingMap;
for (InspectorAction inspectorAction : actionsWithKeyBindings) {
final KeyStroke keyStroke = keyBindingMap.get(inspectorAction.getClass());
Trace.line(TRACE_VALUE, "Binding " + keyStroke + " to " + inspectorAction);
inspectorAction.putValue(Action.ACCELERATOR_KEY, keyStroke);
}
}
}
public void saveSettings(SaveSettingsEvent saveSettingsEvent) {
saveSettingsEvent.save(KEY_BINDINGS_PREFERENCE, keyBindingMap.name());
saveSettingsEvent.save(DISPLAY_STYLE_PREFERENCE, style().name());
saveSettingsEvent.save(TOOLTIP_DELAY_POLICY, toolTipDismissDelayPolicy.name());
saveSettingsEvent.save(INVESTIGATE_WORD_VALUES_PREFERENCE, investigateWordValues);
saveSettingsEvent.save(FORCE_TEXTUAL_WORD_VALUE_DISPLAY, forceTextualWordValueDisplay);
saveSettingsEvent.save(EXTERNAL_VIEWER_PREFERENCE, externalViewerType.name());
for (ExternalViewerType externalViewerType : ExternalViewerType.VALUES) {
final String config = externalViewerConfig.get(externalViewerType);
if (config != null) {
saveSettingsEvent.save(EXTERNAL_VIEWER_PREFERENCE + "." + externalViewerType.name(), config);
}
}
}
/**
* Gets a panel for configuring general Inspector preferences..
*/
JPanel getPanel() {
final JPanel panel = new InspectorPanel(inspection);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
final JPanel keyBindingsPanel = getUIPanel();
final JPanel debugPanel = getDebugPanel();
final JPanel wordValueDisplayPanel = getWordValueDisplayPanel();
final JPanel externalViewerPanel = getExternalViewerPanel();
keyBindingsPanel.setBorder(BorderFactory.createEtchedBorder());
debugPanel.setBorder(BorderFactory.createEtchedBorder());
wordValueDisplayPanel.setBorder(BorderFactory.createEtchedBorder());
externalViewerPanel.setBorder(BorderFactory.createEtchedBorder());
panel.add(keyBindingsPanel);
panel.add(debugPanel);
panel.add(wordValueDisplayPanel);
panel.add(externalViewerPanel);
return panel;
}
/**
* Gets a panel for configuring which key binding map is active.
*/
private JPanel getUIPanel() {
final JPanel interiorPanel = new InspectorPanel(inspection);
// Add key binding chooser
final Collection<KeyBindingMap> allKeyBindingMaps = KeyBindingMap.ALL.values();
final JComboBox keyBindingsComboBox = new InspectorComboBox(inspection, allKeyBindingMaps.toArray());
keyBindingsComboBox.setSelectedItem(keyBindingMap);
keyBindingsComboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setKeyBindingMap((KeyBindingMap) keyBindingsComboBox.getSelectedItem());
}
});
interiorPanel.add(new TextLabel(inspection, "Key Bindings: "));
interiorPanel.add(keyBindingsComboBox);
// Add display style chooser
final JComboBox uiComboBox = new InspectorComboBox(inspection, styleFactory.allStyles());
uiComboBox.setSelectedItem(style);
uiComboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setStyle((InspectorStyle) uiComboBox.getSelectedItem());
}
});
interiorPanel.add(new TextLabel(inspection, "Display style: "));
interiorPanel.add(uiComboBox);
// Add tool tip policy chooser
final JComboBox toolTipComboBox = new InspectorComboBox(inspection, ToolTipDismissDelayPolicy.values());
toolTipComboBox.setSelectedItem(toolTipDismissDelayPolicy);
toolTipComboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setToolTipDismissDelay((ToolTipDismissDelayPolicy) toolTipComboBox.getSelectedItem());
}
});
interiorPanel.add(new TextLabel(inspection, "ToolTip dismiss: "));
interiorPanel.add(toolTipComboBox);
final JPanel panel = new InspectorPanel(inspection, new BorderLayout());
panel.add(interiorPanel, BorderLayout.WEST);
return panel;
}
/**
* @return a GUI panel for setting debugging preferences
*/
private JPanel getDebugPanel() {
final InspectorCheckBox wordValueCheckBox =
new InspectorCheckBox(inspection,
"Investigate memory references",
"Should displayed memory words be investigated as possible references by reading from the VM",
investigateWordValues);
wordValueCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
final InspectorCheckBox checkBox = (InspectorCheckBox) actionEvent.getSource();
investigateWordValues = checkBox.isSelected();
inspection.updateViewConfiguration();
}
});
final JPanel panel = new InspectorPanel(inspection, new BorderLayout());
final JPanel interiorPanel = new InspectorPanel(inspection);
interiorPanel.add(new TextLabel(inspection, "Debugging: "));
/*
* Until there's a good reason for supporting the synchronous debugging mode, it's no longer selectable.
* This prevents any user confusion as to why the GUI seems to freeze when the VM is running. [Doug]
interiorPanel.add(synchButton);
interiorPanel.add(asynchButton);
*/
interiorPanel.add(wordValueCheckBox);
panel.add(interiorPanel, BorderLayout.WEST);
return panel;
}
/**
* @return a GUI panel for setting word value display preferences
*/
private JPanel getWordValueDisplayPanel() {
final InspectorCheckBox wordValueDisplayCheckBox =
new InspectorCheckBox(inspection,
"Force textual word value display",
"Should displayed memory words be displayed initially in textual format when possible",
forceTextualWordValueDisplay);
wordValueDisplayCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
final InspectorCheckBox checkBox = (InspectorCheckBox) actionEvent.getSource();
forceTextualWordValueDisplay = checkBox.isSelected();
inspection.updateViewConfiguration();
}
});
final JPanel panel = new InspectorPanel(inspection, new BorderLayout());
final JPanel interiorPanel = new InspectorPanel(inspection);
interiorPanel.add(new TextLabel(inspection, "Word Value display: "));
interiorPanel.add(wordValueDisplayCheckBox);
panel.add(interiorPanel, BorderLayout.WEST);
return panel;
}
/**
* Gets a panel for configuring which key binding map is active.
*/
public JPanel getExternalViewerPanel() {
final JPanel cards = new InspectorPanel(inspection, new CardLayout());
final JPanel noneCard = new InspectorPanel(inspection);
final JPanel processCard = new InspectorPanel(inspection);
processCard.add(new TextLabel(inspection, "Command: "));
processCard.setToolTipText("The pattern '$file' will be replaced with the full path to the file and '$line' will be replaced with the line number to view.");
final JTextArea commandTextArea = new JTextArea(2, 30);
commandTextArea.setLineWrap(true);
commandTextArea.setWrapStyleWord(true);
commandTextArea.setText(externalViewerConfig.get(ExternalViewerType.PROCESS));
commandTextArea.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
final String command = commandTextArea.getText();
setExternalViewerConfiguration(ExternalViewerType.PROCESS, command);
}
});
final JScrollPane scrollPane = new InspectorScrollPane(inspection, commandTextArea);
processCard.add(scrollPane);
final JPanel socketCard = new InspectorPanel(inspection);
final JTextField portTextField = new JTextField(6);
portTextField.setText(externalViewerConfig.get(ExternalViewerType.SOCKET));
socketCard.add(new TextLabel(inspection, "Port: "));
socketCard.add(portTextField);
portTextField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
final String portString = portTextField.getText();
setExternalViewerConfiguration(ExternalViewerType.SOCKET, portString);
}
});
cards.add(noneCard, ExternalViewerType.NONE.name());
cards.add(processCard, ExternalViewerType.PROCESS.name());
cards.add(socketCard, ExternalViewerType.SOCKET.name());
final ExternalViewerType[] values = ExternalViewerType.values();
final JComboBox comboBox = new InspectorComboBox(inspection, values);
comboBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
final ExternalViewerType externalViewerType = (ExternalViewerType) comboBox.getSelectedItem();
setExternalViewer(externalViewerType);
final CardLayout cardLayout = (CardLayout) cards.getLayout();
cardLayout.show(cards, externalViewerType.name());
}
});
comboBox.setSelectedItem(externalViewerType);
final CardLayout cardLayout = (CardLayout) cards.getLayout();
cardLayout.show(cards, externalViewerType.name());
final JPanel comboBoxPanel = new InspectorPanel(inspection);
comboBoxPanel.add(new TextLabel(inspection, "External File Viewer: "));
comboBoxPanel.add(comboBox);
final JPanel panel = new InspectorPanel(inspection);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(comboBoxPanel);
panel.add(cards);
return panel;
}
}