/* * 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.awt.print.*; import java.text.*; import java.util.*; import javax.swing.*; import com.sun.cri.ci.*; import com.sun.max.ins.*; import com.sun.max.ins.InspectionSettings.AbstractSaveSettingsListener; import com.sun.max.ins.InspectionSettings.SaveSettingsEvent; import com.sun.max.ins.InspectionSettings.SaveSettingsListener; import com.sun.max.ins.util.*; import com.sun.max.ins.view.InspectionViews.ViewKind; import com.sun.max.ins.view.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; /** * An interactive visual presentation of some aspect of VM state. * <p> * This presentation creates some kind of view (the visual representation of some piece * of VM state) and puts it into an {@link InspectorFrame} for realization in the * window system. * <p> * <b>Event Notification</b>: * This abstract class ensures that every view listens for {@linkplain InspectionListener Inspection Events} * as well as {@linkplain ViewFocusListener Focus Events}. Any implementation * that wishes to receive such notifications must do so by overriding the appropriate * methods in interfaces {@link InspectionListener} and {@link ViewFocusListener}, * for which empty methods are provided in this abstract class.</p> */ public abstract class AbstractView<View_Type extends AbstractView> extends AbstractInspectionHolder implements InspectionListener, ViewFocusListener, InspectorView<View_Type> { private static final int TRACE_VALUE = 1; private static final String PINNED_LOGO_TEXT = "(*)"; private static final ImageIcon DEFAULT_MENU_ICON = InspectorImageIcon.createDownTriangle(16, 16); public enum MenuKind { // Standard menu, of which every menu bar will have a subset. // They should be created in this order for consistency, as that // is the order in which they will appear. DEFAULT_MENU(""), EDIT_MENU("Edit"), MEMORY_MENU("Memory"), OBJECT_MENU("Object"), CODE_MENU("Code "), DEBUG_MENU("Debug"), VIEW_MENU("View"), HELP_MENU("Help"); private final String label; private MenuKind(String label) { this.label = label; } public String label() { return label; } } public final InspectorMenuItems defaultMenuItems(MenuKind menuKind) { return defaultMenuItems(menuKind, AbstractView.this); } /** * Creates a set of standard menu items for a View which are * appropriate to one of the standard menu kinds. * * @param view the view for which view-specific items will operate * @param menuKind the kind of menu for which the standard items are intended * @return a new set of menu items */ protected final InspectorMenuItems defaultMenuItems(MenuKind menuKind, final InspectorView view) { switch(menuKind) { case DEFAULT_MENU: return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { menu.add(getCloseViewAction()); menu.addSeparator(); menu.add(isPinnedCheckBox); menu.addSeparator(); menu.add(gui().moveToMiddleAction(view)); menu.add(gui().resizeToFitAction(view)); menu.add(gui().resizeToFillAction(view)); menu.add(gui().restoreDefaultGeometryAction(view)); menu.addSeparator(); menu.add(getPrintAction()); } }; case EDIT_MENU: break; case MEMORY_MENU: return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { menu.addSeparator(); menu.add(actions().genericMemoryMenuItems()); } }; case OBJECT_MENU: return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { menu.addSeparator(); menu.add(actions().genericObjectMenuItems()); } }; case CODE_MENU: return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { menu.addSeparator(); menu.add(actions().genericCodeMenuItems()); } }; case DEBUG_MENU: return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { menu.addSeparator(); menu.add(actions().genericBreakpointMenuItems()); menu.add(views().activateSingletonViewAction(ViewKind.BREAKPOINTS)); if (vm().watchpointManager() != null) { menu.add(actions().genericWatchpointMenuItems()); menu.add(views().activateSingletonViewAction(ViewKind.WATCHPOINTS)); } } }; case VIEW_MENU: return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { menu.add(getViewOptionsAction()); menu.add(getRefreshAction()); menu.addSeparator(); menu.add(actions().genericViewMenuItems()); } }; case HELP_MENU: break; } // Empty set of menu items return new AbstractInspectorMenuItems(inspection()) { public void addTo(InspectorMenu menu) { } }; } private final ViewKind viewKind; /** * A listener that saves the current frame geometry (location, size) * whenever a "save" event takes place. Newly created frames are initialized * to saved geometry settings, if present, as identified by the view's key. */ private final SaveSettingsListener saveGeometrySettingsListener; private InspectorFrame frame; private final TimedTrace updateTracer; private InspectorAction showViewAction = null; private InspectorAction closeViewAction = null; private Set<ViewEventListener> viewEventListeners = CiUtil.newIdentityHashSet(); private final JCheckBoxMenuItem isPinnedCheckBox; /** * Abstract constructor for all views. * * @param viewKind the kind of view being created * @param geometrySettingsKey if non-null, makes the size and location of this * view persistent across sessions. */ protected AbstractView(Inspection inspection, ViewKind viewKind, String geometrySettingsKey) { super(inspection); this.viewKind = viewKind; saveGeometrySettingsListener = (geometrySettingsKey == null) ? null : new AbstractSaveSettingsListener(geometrySettingsKey, this) { @Override public Rectangle defaultGeometry() { return AbstractView.this.defaultGeometry(); } public void saveSettings(SaveSettingsEvent saveSettingsEvent) { } }; this.isPinnedCheckBox = new JCheckBoxMenuItem("Pin view " + PINNED_LOGO_TEXT); this.isPinnedCheckBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { // Catch check box events where the user changes the "pinned" setting // Refresh so that the title will be redisplayed and show the new "pin" status refresh(true); } }); this.updateTracer = new TimedTrace(TRACE_VALUE, tracePrefix() + "refresh"); } public void addViewEventListener(ViewEventListener listener) { Trace.line(TRACE_VALUE, tracePrefix() + "adding view event listener: " + listener); viewEventListeners.add(listener); } public void removeViewEventListener(ViewEventListener listener) { Trace.line(TRACE_VALUE, tracePrefix() + "removing view event listener: " + listener); viewEventListeners.remove(listener); } public final ViewManager viewManager() { return viewKind.viewManager(); } public final JComponent getJComponent() { return frame.getJComponent(); } public Rectangle defaultGeometry() { return preference().geometry().preferredFrameGeometry(viewKind); } public final Rectangle getGeometry() { return getJComponent().getBounds(); } public final void setGeometry(Rectangle rectangle) { getJComponent().setBounds(rectangle); } public boolean isPinned() { return isPinnedCheckBox.isSelected(); } /** * Sets the size of the view in the main frame. * * @param width the new width * @param height the new height */ protected final void setSize(int width, int height) { getJComponent().setSize(width, height); } protected void setWarning() { //highlight(); } /** * @return the string currently appearing in the title or tab of the view's window frame */ protected final String getTitle() { return frame.getTitle(); } public abstract String getTextForTitle(); protected final void setTitle(String title) { frame.setTitle(pinnedPrefix() + (title == null ? getTextForTitle() : title)); } /** * @return a short standard string that symbolizes the pinning of a view, if pinned; empty string otherwise. */ private String pinnedPrefix() { return isPinned() ? (PINNED_LOGO_TEXT + " ") : ""; } /** * Sets the display frame title for this view to the string provided by * the abstract method {@link #getTextForTitle()}. */ protected final void setTitle() { setTitle(null); } /** * @return the visible table for views with table-based displays; null if none. */ protected InspectorTable getTable() { return null; } /** * Creates the content that will be inserted into the View's frame, including * the population of the frame's menu. */ protected abstract void createViewContent(); /** * Creates a simple frame for the view and: * <ul> * <li>calls {@link #createViewContent()} to populate it;</li> * <li>adds this view to the collection of update listeners; and</li> * <li>makes it all visible in the window system.</li> * </ul> * <p> * The geometry (size and location) of the frame will be saved across sessions * if a non-null key was provided in the constructor. * if no key is provided, or if no settings from previous sessions have been saved, * then the initial geometry will be taken from a specification created by * implementations of the {@link InspectorGeometry} interface. * * @param addMenuBar should a menu bar be added to the frame. * @see InspectorGeometry#preferredFrameGeometry(ViewKind) */ protected InspectorFrame createFrame(boolean addMenuBar) { frame = new InspectorInternalFrame(this, addMenuBar); setTitle(); createViewContent(); frame.pack(); gui().addView(this); inspection().addInspectionListener(this); focus().addListener(this); if (saveGeometrySettingsListener != null) { inspection().settings().addSaveSettingsListener(saveGeometrySettingsListener); } return frame; } /** * Creates a tabbed frame for the view and: * <ul> * <li>calls {@link #createViewContent()} to populate it;</li> * <li>adds this view to the collection of update listeners; and</li> * <li>makes it all visible in the window system.</li> * </ul> * <p> * Note that these frames only exist in side a tabbed container, and thus * persistent geometry is not supported for them. * * @param parent the tabbed frame */ protected InspectorFrame createTabFrame(TabbedView parent) { frame = new InspectorRootPane(this, parent, true); setTitle(); createViewContent(); frame.validate(); gui().addView(this); inspection().addInspectionListener(this); focus().addListener(this); return frame; } /** * @see InspectorFrame#makeMenu(MenuKind) */ protected InspectorMenu makeMenu(MenuKind menuKind) throws InspectorError { return frame.makeMenu(menuKind); } /** * Each view optionally re-reads, and updates any state caches if needed * from the VM. The expectation is that some views may cache and * update selectively, but the argument can override this. * * @param force suspend caching behavior; read state unconditionally. */ protected abstract void refreshState(boolean force); /** * Refreshes any state needed from the VM and then ensures that the visual * display is completely updated. * * @param force force suspend caching behavior; read state unconditionally. */ private void refresh(boolean force) { refreshState(force); frame.refresh(force); frame.invalidate(); frame.repaint(); } /** * Unconditionally forces a full refresh of this view. */ public final void forceRefresh() { refresh(true); } /** * Rebuilds the content of the view, much more * expensive than {@link #refresh(boolean)}, but necessary when the parameters or * configuration of the view changes enough to require creating a new one. * <p> * Note that this clears the menu bar before generating new content, so each * view must provide them. */ protected final void reconstructView() { final Dimension size = frame.getSize(); frame.clearMenus(); createViewContent(); frame.setPreferredSize(size); frame.validate(); } protected void setContentPane(Container contentPane) { frame.setContentPane(contentPane); } protected Container getContentPane() { return frame.getContentPane(); } protected void setLayeredPane(JLayeredPane layeredPane) { frame.setLayeredPane(layeredPane); } protected JLayeredPane getLayeredPane() { return frame.getLayeredPane(); } protected void setGlassPane(Component glassPane) { frame.setGlassPane(glassPane); } protected Component getGlassPane() { return frame.getGlassPane(); } protected boolean isVisible() { return frame.isVisible(); } protected void moveToFront() { frame.moveToFront(); } protected boolean isSelected() { return frame.isSelected(); } protected void setSelected() { frame.setSelected(); } protected void setStateColor(Color color) { frame.setStateColor(color); } // public void pack() { // frame.pack(); // } public void validate() { frame.validate(); } public void flash() { flash(1); } public void flash(int n) { frame.flash(preference().style().frameBorderFlashColor(), n); } public void highlight() { gui().moveToExposeDefaultMenu(this); frame.moveToFront(); setSelected(); flash(); } /** * If not already visible and selected, calls this view to the users attention: move to front, select, and flash. */ protected void highlightIfNotVisible() { frame.moveToFront(); if (!isSelected()) { setSelected(); flash(); } } public final void dispose() { frame.dispose(); } /** * Receives notification that the the frame has acquired focus in the window system. */ protected void viewGetsWindowFocus() { } /** * Receives notification that the the frame has acquired focus in the window system. */ protected void viewLosesWindowFocus() { } /** * Receives notification that the window system is closing this view and cleans * up. * <ul> * <li>Removes the view's inspection, focus, and save settings listeners;</li> * <li>Notifies the view's view manager that the view is closing; and</li> * <li>Triggers a save event</li> * </ul> */ protected void viewClosing() { inspection().removeInspectionListener(this); focus().removeListener(this); if (saveGeometrySettingsListener != null) { inspection().settings().removeSaveSettingsListener(saveGeometrySettingsListener); } inspection().settings().save(); for (ViewEventListener listener : viewEventListeners) { listener.viewClosing(this); } // don't try to recompute the title, just get the one that's been in use Trace.line(1, tracePrefix() + " closing view " + getTitle()); } public void vmStateChanged(boolean force) { final String title = getTitle(); updateTracer.begin(title); refresh(force); updateTracer.end(title); } public void breakpointStateChanged() { } public void breakpointToBeDeleted(MaxBreakpoint breakpoint, String reason) { } public void watchpointSetChanged() { } /** * {@inheritDoc} * <p> * Default behavior for any change in view configuration is to just build * the view again from the start. Concrete view types should override * this method if that doesn't work (although it probably ought to be made * to work always). */ public void viewConfigurationChanged() { reconstructView(); } public void vmProcessTerminated() { } public void inspectionEnding() { } public void codeLocationFocusSet(MaxCodeLocation codeLocation, boolean interactiveForNative) { } public void threadFocusSet(MaxThread oldMaxThread, MaxThread maxeThread) { } public void frameFocusChanged(MaxStackFrame oldStackFrame, MaxStackFrame stackFrame) { } public void addressFocusChanged(Address oldAddress, Address address) { } public void memoryRegionFocusChanged(MaxMemoryRegion oldMemoryRegion, MaxMemoryRegion memoryRegion) { } public void breakpointFocusSet(MaxBreakpoint oldBreakpoint, MaxBreakpoint breakpoint) { } public void watchpointFocusSet(MaxWatchpoint oldWatchpoint, MaxWatchpoint watchpoint) { } public void heapObjectFocusChanged(MaxObject oldObject, MaxObject object) { } public void markBitIndexFocusChanged(int oldHeapMarkBit, int heapMarkBit) { } public final InspectorAction getShowViewAction() { // Only need one, but maybe not even that one; create lazily. return new InspectorAction(inspection(), pinnedPrefix() + getTextForTitle()) { @Override protected void procedure() { highlight(); } }; } /** * @return an action that closes the view. */ protected final InspectorAction getCloseViewAction() { // Only need one, but maybe not even that one; create lazily. if (closeViewAction == null) { closeViewAction = new InspectorAction(inspection(), "Close") { @Override protected void procedure() { if (isPinned()) { if (!gui().yesNoDialog("View is pinned; close anyway?")) { return; } } dispose(); } }; } return closeViewAction; } /** * @return an action that will present a dialog that enables selection of view options; * returns a disabled dummy action if not overridden. */ protected InspectorAction getViewOptionsAction() { final InspectorAction dummyViewOptionsAction = new InspectorAction(inspection(), "View Options") { @Override protected void procedure() { } }; dummyViewOptionsAction.setEnabled(false); return dummyViewOptionsAction; } /** * @return an action that will refresh any state from the VM. */ protected final InspectorAction getRefreshAction() { return new InspectorAction(inspection(), "Refresh") { @Override protected void procedure() { Trace.line(TRACE_VALUE, "Refreshing view: " + AbstractView.this); forceRefresh(); } }; } /** * @return the default print action for table-based views; depends on an overridden * {@link #getTable()} method to provide the table. */ protected final InspectorAction getDefaultPrintAction() { return new InspectorAction(inspection(), "Print") { @Override public void procedure() { final MessageFormat footer = new MessageFormat(vm().entityName() + ": " + getTextForTitle() + " Printed: " + new Date() + " -- Page: {0, number, integer}"); try { final InspectorTable inspectorTable = getTable(); assert inspectorTable != null; inspectorTable.print(JTable.PrintMode.FIT_WIDTH, null, footer); } catch (PrinterException printerException) { gui().errorMessage("Print failed: " + printerException.getMessage()); } } }; } /** * @return an action that will present a print dialog for printing the contents of the view; * returns a disabled dummy action if not overridden. */ protected InspectorAction getPrintAction() { final InspectorAction dummyPrintAction = new InspectorAction(inspection(), "Print") { @Override protected void procedure() { } }; dummyPrintAction.setEnabled(false); return dummyPrintAction; } @Override public String toString() { return this.getClass().getSimpleName() + ": " + getTitle(); } public void cardTableIndexFocusChanged(int oldHeapMarkBit, int heapMarkBit) { } }