/*
* Copyright (c) 2010, 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;
import java.awt.event.*;
import java.awt.print.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import com.sun.max.ins.gui.*;
import com.sun.max.ins.view.*;
import com.sun.max.ins.view.InspectionViews.ViewKind;
import com.sun.max.program.*;
import com.sun.max.tele.*;
import com.sun.max.unsafe.*;
/**
* A view that gives viewing and editing access to a notepad.
* <p>
* The view is currently a singleton, but this might be generalized
* if multiple notepads are supported in the future. Some of the code
* is written to anticipate this.
* @see InspectorNotepad
* @see NotepadManager
*/
public final class NotepadView extends AbstractView<NotepadView> {
private static final int TRACE_VALUE = 1;
private static final ViewKind VIEW_KIND = ViewKind.NOTEPAD;
private static final String SHORT_NAME = "Notepad";
private static final String LONG_NAME = "Notepad View";
private static final String GEOMETRY_SETTINGS_KEY = "notepadViewGeometry";
// A compiled regular expression pattern that matches hex numbers (with or without prefix) and ordinary
// integers as well.
// TODO (mlvdv) fix pattern failure at end of contents (if no newline)
private static final Pattern hexNumberPattern = Pattern.compile("[0-9a-fA-F]+[^a-zA-Z0-9]");
public static final class NotepadViewManager extends AbstractSingletonViewManager<NotepadView> {
private final NotepadManager notepadManager;
protected NotepadViewManager(Inspection inspection) {
super(inspection, VIEW_KIND, SHORT_NAME, LONG_NAME);
notepadManager = new NotepadManager(inspection);
}
@Override
protected NotepadView createView(Inspection inspection) {
return new NotepadView(inspection, notepadManager.getNotepad());
}
}
// Will be non-null before any instances created.
private static NotepadViewManager viewManager = null;
public static NotepadViewManager makeViewManager(Inspection inspection) {
if (viewManager == null) {
viewManager = new NotepadViewManager(inspection);
}
return viewManager;
}
private final InspectorNotepad notepad;
private final JTextArea textArea;
private final JPopupMenu popupMenu;
private final Action copyAction;
private final Action cutAction;
private final Action pasteAction;
private final InspectorAction selectAllAction;
private final InspectorAction clearAction;
private final InspectorAction insertFromFileAction;
private final InspectorAction writeToFileAction;
private final InspectorAction notepadPrintAction;
private final InspectSelectedAddressMemoryAction inspectSelectedAddressMemoryAction;
private final InspectSelectedAddressRegionAction inspectSelectedAddressRegionAction;
private final InspectSelectedAddressObjectAction inspectSelectedAddressObjectAction;
// invariant: always non-null
private String selectedText = "";
// Note that we're treating the view as a singleton for now, so we're using
// the default mechanism for saving geometry. May need more view options
// when we add functionality.
private NotepadView(Inspection inspection, InspectorNotepad notepad) {
super(inspection, VIEW_KIND, GEOMETRY_SETTINGS_KEY);
Trace.begin(1, tracePrefix() + " initializing");
this.notepad = notepad;
textArea = new JTextArea(notepad.getContents());
// Get the standard editing actions for the text area and wrap
// them in actions with different names
final Action[] actions = textArea.getActions();
final Hashtable<String, Action> actionLookup = new Hashtable<String, Action>();
for (Action action : actions) {
final String name = (String) action.getValue(Action.NAME);
actionLookup.put(name, action);
}
copyAction = new AbstractAction("Copy (Ctrl-c)") {
final Action action = actionLookup.get(DefaultEditorKit.copyAction);
@Override
public void actionPerformed(ActionEvent e) {
action.actionPerformed(e);
}
};
cutAction = new AbstractAction("Cut (Ctrl-x)") {
final Action action = actionLookup.get(DefaultEditorKit.cutAction);
@Override
public void actionPerformed(ActionEvent e) {
action.actionPerformed(e);
}
};
pasteAction = new AbstractAction("Paste (Ctrl-v)") {
final Action action = actionLookup.get(DefaultEditorKit.pasteAction);
@Override
public void actionPerformed(ActionEvent e) {
action.actionPerformed(e);
}
};
selectAllAction = new NotepadSelectAllAction(inspection);
clearAction = new NotepadClearAction(inspection);
// Add handlers for various user events
textArea.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent mouseEvent) {
switch(inspection().gui().getButton(mouseEvent)) {
case MouseEvent.BUTTON1:
break;
case MouseEvent.BUTTON2:
break;
case MouseEvent.BUTTON3:
popupMenu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
break;
}
}
});
textArea.addCaretListener(new CaretListener() {
public void caretUpdate(CaretEvent e) {
updateSelection(textArea.getSelectedText());
}
});
textArea.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
save();
}
});
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
}
public void insertUpdate(DocumentEvent e) {
updateHighlighting();
}
public void removeUpdate(DocumentEvent e) {
updateHighlighting();
}
});
notepadPrintAction = new NotepadPrintAction(inspection);
insertFromFileAction = new InsertFromFileAction(inspection);
writeToFileAction = new WriteToFileAction(inspection);
inspectSelectedAddressMemoryAction = new InspectSelectedAddressMemoryAction(inspection);
inspectSelectedAddressRegionAction = new InspectSelectedAddressRegionAction(inspection);
inspectSelectedAddressObjectAction = new InspectSelectedAddressObjectAction(inspection);
popupMenu = new JPopupMenu();
popupMenu.add(inspectSelectedAddressMemoryAction);
popupMenu.add(inspectSelectedAddressRegionAction);
popupMenu.add(inspectSelectedAddressObjectAction);
popupMenu.addSeparator();
popupMenu.add(copyAction);
popupMenu.add(cutAction);
popupMenu.add(pasteAction);
popupMenu.add(selectAllAction);
popupMenu.add(clearAction);
createFrame(true);
Trace.end(1, tracePrefix() + " initializing");
}
@Override
public String getTextForTitle() {
return viewManager.shortName() + ": " + notepad.getName();
}
@Override
protected void createViewContent() {
setContentPane(new InspectorScrollPane(inspection(), textArea));
setDisplayStyle(textArea);
updateHighlighting();
// Populate menu bar
makeMenu(MenuKind.DEFAULT_MENU).add(defaultMenuItems(MenuKind.DEFAULT_MENU));
final InspectorMenu editMenu = makeMenu(MenuKind.EDIT_MENU);
editMenu.add(copyAction);
editMenu.add(cutAction);
editMenu.add(pasteAction);
editMenu.add(selectAllAction);
editMenu.add(clearAction);
editMenu.add(insertFromFileAction);
editMenu.add(writeToFileAction);
final InspectorMenu memoryMenu = makeMenu(MenuKind.MEMORY_MENU);
memoryMenu.add(inspectSelectedAddressMemoryAction);
memoryMenu.add(inspectSelectedAddressRegionAction);
memoryMenu.add(defaultMenuItems(MenuKind.MEMORY_MENU));
final InspectorMenu objectMenu = makeMenu(MenuKind.OBJECT_MENU);
objectMenu.add(inspectSelectedAddressObjectAction);
objectMenu.add(defaultMenuItems(MenuKind.OBJECT_MENU));
memoryMenu.add(views().activateSingletonViewAction(ViewKind.ALLOCATIONS));
makeMenu(MenuKind.VIEW_MENU).add(defaultMenuItems(MenuKind.VIEW_MENU));
}
@Override
protected void refreshState(boolean force) {
save();
}
@Override
public InspectorAction getPrintAction() {
return notepadPrintAction;
}
@Override
public void viewConfigurationChanged() {
save();
setDisplayStyle(textArea);
reconstructView();
}
@Override
public void viewClosing() {
save();
super.viewClosing();
}
@Override
public void inspectionEnding() {
save();
super.inspectionEnding();
}
/**
* Apply the current display style settings to the editing pane.
* @param textArea the text editing pane for notepad contents
*/
private void setDisplayStyle(JTextArea textArea) {
textArea.setFont(preference().style().defaultFont());
}
/**
* Apply visual styles to parts of the editing area that are
* recognized, for example as VM memory addresses.
*/
private void updateHighlighting() {
// TODO (mlvdv) use address matcher to drive some display features;
// will require using a JEditorPane/JTextPane instead of the JTextArea.
// final String text = textArea.getText();
// final Matcher matcher = hexNumberPattern.matcher(text);
// while (matcher.find()) {
// System.out.println("match=(" + matcher.start() + "," + matcher.end() + ")");
// }
}
/**
* Writes the current contents of the editor back to the persistent notepad.
*/
private void save() {
notepad.setContents(textArea.getText());
}
/**
* Responds to a change in the user's selection.
*
* @param selection the text currently selected, null if none.
*/
private void updateSelection(String selection) {
final String newSelectedText = selection == null ? "" : selection;
if (!selectedText.equals(newSelectedText)) {
selectedText = newSelectedText;
final Address selectedAddress = getSelectionAsAddress();
inspectSelectedAddressMemoryAction.setSelectedAddress(selectedAddress);
inspectSelectedAddressRegionAction.setSelectedAddress(selectedAddress);
inspectSelectedAddressObjectAction.setSelectedAddress(selectedAddress);
}
}
/**
* Attempts to convert the currently selected text into a memory address, assuming
* that it is represented in hex and is optionally prefaced by "0x".
* @return the memory address specified (in hex); null
* if selection cannot be understood as a hex address.
*/
private Address getSelectionAsAddress() {
String selectedText = textArea.getSelectedText();
Address selectedAddress = Address.zero();
if (selectedText != null) {
selectedText = selectedText.trim();
if (selectedText.length() > 2 && selectedText.substring(0, 2).equalsIgnoreCase("0x")) {
selectedText = selectedText.substring(2);
}
try {
selectedAddress = Address.parse(selectedText, 16);
} catch (NumberFormatException e) {
// Can't interpret as an address; allow null to be returned.
}
}
return selectedAddress;
}
/**
* An action that brings up a memory view on a specified memory location.
*/
private final class InspectSelectedAddressMemoryAction extends InspectorAction {
private Address address;
public InspectSelectedAddressMemoryAction(Inspection inspection) {
super(inspection, "View memory at selected address");
setEnabled(false);
}
@Override
protected void procedure() {
views().memory().makeView(address).highlight();
focus().setAddress(address);
}
public void setSelectedAddress(Address address) {
this.address = address;
setEnabled(address != null && address.isNotZero());
}
}
/**
* An action that brings up a memory region view on the VM
* memory region, if any, that includes a specified address.
*
*/
private final class InspectSelectedAddressRegionAction extends InspectorAction {
private Address address;
private MaxMemoryRegion memoryRegion;
public InspectSelectedAddressRegionAction(Inspection inspection) {
super(inspection, "View region containing selected address");
setEnabled(false);
}
@Override
protected void procedure() {
views().memory().makeView(memoryRegion, null).highlight();
focus().setAddress(address);
}
public void setSelectedAddress(Address address) {
this.address = address;
this.memoryRegion = vm().state().findMemoryRegion(address);
setEnabled(memoryRegion != null);
}
}
/**
* An action that brings up an object view on the VM object, if any,
* that includes a specified address.
*/
private final class InspectSelectedAddressObjectAction extends InspectorAction {
private Address address;
private MaxObject object;
public InspectSelectedAddressObjectAction(Inspection inspection) {
super(inspection, "View object at selected origin");
setEnabled(false);
}
@Override
protected void procedure() {
views().objects().makeView(object);
focus().setAddress(address);
}
public void setSelectedAddress(Address address) {
this.address = address;
try {
this.object = vm().objects().findAnyObjectAt(address);
setEnabled(object != null);
} catch (MaxVMBusyException e) {
}
}
}
/**
* Action that removes the current contents of the notepad.
*/
private final class NotepadSelectAllAction extends InspectorAction {
public NotepadSelectAllAction(Inspection inspection) {
super(inspection, "Select all");
}
@Override
protected void procedure() {
textArea.selectAll();
}
}
/**
* Action that removes the current contents of the notepad.
*/
private final class NotepadClearAction extends InspectorAction {
public NotepadClearAction(Inspection inspection) {
super(inspection, "Clear all");
}
@Override
protected void procedure() {
textArea.setText("");
}
}
/**
* Action: produces a dialog for writing notepad contents to an interactively specified file.
*/
final class InsertFromFileAction extends InspectorAction {
private static final String DEFAULT_TITLE = "Insert text from a file...";
InsertFromFileAction(Inspection inspection) {
super(inspection, DEFAULT_TITLE);
}
@Override
protected void procedure() {
//Create a file chooser
final JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);
fileChooser.setDialogTitle("Insert text from file:");
final int returnVal = fileChooser.showOpenDialog(gui().frame());
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
final File file = fileChooser.getSelectedFile();
if (!file.exists()) {
gui().errorMessage("File doesn't exist:" + file.getName());
return;
}
try {
final BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
String line = bufferedReader.readLine();
while (line != null) {
textArea.insert(line + "\n", textArea.getCaretPosition());
line = bufferedReader.readLine();
}
} catch (IOException ioException) {
gui().errorMessage("Failed reading from " + file + " " + ioException);
}
}
}
/**
* Action: produces a dialog for writing notepad contents to an interactively specified file.
*/
final class WriteToFileAction extends InspectorAction {
private static final String DEFAULT_TITLE = "Write contents to a file...";
WriteToFileAction(Inspection inspection) {
super(inspection, DEFAULT_TITLE);
}
@Override
protected void procedure() {
//Create a file chooser
final JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
fileChooser.setDialogTitle("Write notepad contents to file:");
final int returnVal = fileChooser.showSaveDialog(gui().frame());
if (returnVal != JFileChooser.APPROVE_OPTION) {
return;
}
final File file = fileChooser.getSelectedFile();
if (file.exists() && !gui().yesNoDialog("File " + file + "exists. Overwrite?\n")) {
return;
}
try {
final PrintStream printStream = new PrintStream(new FileOutputStream(file, false));
printStream.print(textArea.getText());
} catch (FileNotFoundException fileNotFoundException) {
gui().errorMessage("Unable to open " + file + " for writing:" + fileNotFoundException);
}
}
}
/**
* Action that produces a dialog for printing the contents, if any, of
* the notepad.
*/
private final class NotepadPrintAction extends InspectorAction {
public NotepadPrintAction(Inspection inspection) {
super(inspection, "Print notepad contents");
}
@Override
protected void procedure() {
if (textArea.getText().length() == 0) {
gui().errorMessage("notepad is empty");
} else {
try {
textArea.print();
} catch (PrinterException printerException) {
inspection().gui().errorMessage("notepad print failed: " + printerException.getMessage());
printerException.printStackTrace();
}
}
}
}
}