/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community 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.osedu.org/licenses/ECL-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 tufts.vue.gui; import tufts.Util; import tufts.macosx.MacOSX; import tufts.vue.SearchTextField; import tufts.vue.VUE; import tufts.vue.DEBUG; import tufts.vue.VueUtil; import tufts.vue.EventRaiser; import tufts.vue.VueResources; import tufts.vue.gui.Screen; import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import edu.tufts.vue.preferences.implementations.WindowPropertiesPreference; // Just to confirm: even in 1.5, Window's that are children of // a Frame still make that Frame go inactive if they get focus. /** * Our own floating window class so we can do things like control decorations, add * features like double-click to roll-up, make sticky to other windows when dragged, * etc. * Instances of this class, if given no parent Window, turn off focusable window state, * which effectively identifies it as a palette style window that always leave focus * with application Frame's as we'd like. Unfortunately, this also means that any * components that need focus in order to take key input (such a text field) won't be * able to get it (mouse events can still get through). So if you want text input for * anything in one of these windows, this class requires installing your * KeyboardFocusManager that can temporarily force the keyboard focus to components that * want it within these Windows. Another side effect is that the cursor can't be * changed anywhere in the Window when it's focusable state is false. * @version $Revision: 1.171 $ / $Date: 2010-02-03 19:15:46 $ / $Author: mike $ * @author Scott Fraize */ public class DockWindow implements MouseListener , MouseMotionListener //, FocusManager.MouseInterceptor , java.beans.PropertyChangeListener { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(DockWindow.class); public static final int DefaultWidth = 300; private static boolean AllVisible = true; final static LinkedList<DockWindow> AllWindows = new LinkedList(); final static char RightArrowChar = 0x25B8; // unicode final static char DownArrowChar = 0x25BE; // unicode //final static String RightArrow = "" + RightArrowChar; //final static String DownArrow = "" + DownArrowChar; public final static int ToolbarHeight = VueResources.getInt("gui.dockToolbar.height", 70); private static boolean MacWindowShadowEnabled = false; private static Border WindowBorder; private static Border ContentBorder; private static Border ContentBorderInset; static DockRegion TopDock; static DockRegion BottomDock; static DockRegion MainDock; private static final Color TopGradientColor = VueResources.getColor("gui.dockWindow.title.background.top"), BottomGradientColor = VueResources.getColor("gui.dockWindow.title.background.bottom"); private final static String NORTH_WEST = "northWest"; private final static String NORTH_EAST = "northEast"; private final static String SOUTH_WEST = "southWest"; private final static String SOUTH_EAST = "southEast"; private final static int TitleHeight = VueResources.getInt("gui.dockWindow.title.height", 19); private final static int ResizeCornerSize = VueResources.getInt("gui.dockWindow.resizeCorner.size", 12); private final static Font TitleFont = VueResources.getFont("gui.dockWindow.title.font", tufts.vue.VueConstants.FONT_MEDIUM); private final static int MacAquaMetalMinHeight = 37; // Mac Aqua Brushed Metal window size bug private final ContentPane mContentPane; private JComponent mResizeCorner; private JComponent mResizeCorner2; //private Action[] mMenuActions; private String mTitleName; private String mMenuName; // if non-null, will be used for name in menus private final String mBaseTitle; //private int mTitleWidth; private int mMinTitleWidth; private boolean showCloseBtn=true; private DockWindow mChild; private DockWindow mParent; private DockWindow mChildWhenHidden; private DockWindow mParentWhenHidden; private DockRegion mDockRegion; private DockWindow mDockNext; // next (right or below) window in DockRegion private DockWindow mDockPrev; // previous (left or above) window in DockRegion /** point in window mouse was at when drag started */ private Point mDragStart; /** absolute point on screen mouse was at when drag started */ private Point mDragStartScreen; private Dimension mMinContentSize = new Dimension(0,0); private Dimension mDragSizeStart; private boolean mMouseWasPressed; private boolean mMouseWasDragged; private boolean mWindowDragUnderway; private int mMovingStackHeight; private boolean isResizeEnabled; private boolean isRolledUp; private boolean isStackOwner; private Rectangle mUnrolledShape; private Rectangle mNormalShape; // non-maximized shape /** set to false as soon as we begin to go invisible */ private boolean mShowing; private boolean mWasVisible; private boolean mAnimatingReshape; private boolean mStickingRight; private boolean mWasStickingRight; private boolean mHasWindowShadow = true; private boolean isToolbar; static int CollapsedHeight = 0; /** visible exposed height of parent DockWindow that child window shouldn't overlap */ private static int CollapsedHeightVisible; static boolean isMac; private static boolean isMacAqua; private static boolean isMacAquaMetal; private static boolean isWindows; private static boolean isDarkTitleBar; private static boolean isGradientTitle; // This override trick only works on MacOSX Java 1.5 private static final boolean OverrideMacAquaBrushedMetal = false; private static final boolean SidewaysRollup = false; // experimental private static JFrame HiddenParentFrame; private final WindowPropertiesPreference wpp; private final Peer _peer; private final Window _win; public interface Peer extends RootPaneContainer { void peer_addNotify(); void peer_validate(); void peer_invalidate(); void peer_setVisible(boolean visible); void peer_setBounds(int x, int y, int w, int h); DockWindow getDock(); } public static boolean isDockWindow(java.awt.Component c) { return c instanceof Peer; } private final class FramePeer extends JFrame implements Peer { FramePeer(String title, boolean alwaysOnTop, boolean decorated) { super(title); setUndecorated(!decorated); // Note that Frame's and Dialog's that are setAlwaysOnTop generally behave // differently than Window's that have it set: Frame's with it set will stay // on top of all other windows within this application, but Window's with it // set will also stay on top of all other applications window's opened by a // user anywhere. setAlwaysOnTop(alwaysOnTop); // Frame's also, at least on Mac Leopard, have ideal alwaysOnTop behavior: // But only when they are decorated! When decorated they stay on top of all // windows in the application, but NOT any other application, and in // addition, when the application even loses focus, they automatically hide // themseleves. So without having an owner, they have ideal behaviour. // But when UN-decorated on Leopard, they behave like Window's when // always on top: on top of everything. // However, on Ubuntu 8.04, java version "1.6.0_06", they behave // just like dialog peers -- no help -- they can go behind // the full screen window, even when alwaysOnTop is set to true. if (Util.isMacLeopard()) getRootPane().putClientProperty("Window.style", "small"); //setJMenuBar(new VueMenuBar()); } @Override public void addNotify() { DockWindow.this.addNotify(); } @Override public void validate() { DockWindow.this.validate(); } @Override public void invalidate() { DockWindow.this.invalidate(); } @Override public void setVisible(boolean v) { DockWindow.this.setVisible(v); } @Override public void setBounds(int x, int y, int w, int h) { if (_peer == null) // can happen during init super.setBounds(x, y, w, h); else DockWindow.this.setBounds(x, y, w, h); } public void peer_addNotify() { super.addNotify(); } public void peer_validate() { super.validate(); } public void peer_invalidate() { super.invalidate(); } public void peer_setVisible(boolean v) { super.setVisible(v); } public void peer_setBounds(int x, int y, int w, int h) { super.setBounds(x, y, w, h); } public String toString() { return GUI.name(this); } public DockWindow getDock() { return DockWindow.this; } } private final class DialogPeer extends JDialog implements Peer { DialogPeer(Frame owner, String title, boolean alwaysOnTop, boolean decorated) { super(owner, title); setUndecorated(!decorated); setAlwaysOnTop(alwaysOnTop); if (Util.isMacLeopard()) // will only have effect when decorated getRootPane().putClientProperty("Window.style", "small"); // At least on Mac Leopard, Java 1.5, using JDialog's keeps the main menu // bar active even when they have the focus. (As opposed to when using // JFrame's) And when using them DECORATED + ON_TOP, they're almost perfect, // except for the bizarre addition of hiding THE ENTIRE VUE APP when it // loses focus -- even tho the main VueFrame is just a generic JFrame, not a // DockWindow. // However, if they're alwaysOnTop, they stay on top // of all open applications, like JWindow's } @Override public void addNotify() { DockWindow.this.addNotify(); } @Override public void validate() { DockWindow.this.validate(); } @Override public void invalidate() { DockWindow.this.invalidate(); } @Override public void setVisible(boolean v) { DockWindow.this.setVisible(v); } @Override public void setBounds(int x, int y, int w, int h) { if (_peer == null) // can happen during init super.setBounds(x, y, w, h); else DockWindow.this.setBounds(x, y, w, h); } public void peer_addNotify() { super.addNotify(); } public void peer_validate() { super.validate(); } public void peer_invalidate() { super.invalidate(); } public void peer_setVisible(boolean v) { super.setVisible(v); } public void peer_setBounds(int x, int y, int w, int h) { super.setBounds(x, y, w, h); } public String toString() { return GUI.name(this); } public DockWindow getDock() { return DockWindow.this; } } private final class WindowPeer extends JWindow implements Peer { WindowPeer(Window owner) { super(owner); // Note that Window's, in the Linux impl, are ALWAYS set to alwaysOnTop -- // there's no way to turn it off! This is why on linux we're using // the DialogPeer -- however, this means you can never see DockWindow's // in working full-screen mode. // The problem with any NON java.awt.Window peer, is that Window's are the only // Window subclass that can have an owner, and they always stay on top of // their owner (which we make our full-screen window, so they always stay on // top of that, and we make the owner of the full screen window the VUE // application frame, so they also stay on top of that (the grandparent). } @Override public void addNotify() { DockWindow.this.addNotify(); } @Override public void validate() { DockWindow.this.validate(); } @Override public void invalidate() { DockWindow.this.invalidate(); } @Override public void setVisible(boolean v) { DockWindow.this.setVisible(v); } @Override public void setBounds(int x, int y, int w, int h) { if (_peer == null) // can happen during init super.setBounds(x, y, w, h); else DockWindow.this.setBounds(x, y, w, h); } public void peer_addNotify() { super.addNotify(); } public void peer_validate() { super.validate(); } public void peer_invalidate() { super.invalidate(); } public void peer_setVisible(boolean v) { super.setVisible(v); } public void peer_setBounds(int x, int y, int w, int h) { super.setBounds(x, y, w, h); } public String toString() { return GUI.name(this); } public DockWindow getDock() { return DockWindow.this; } } //private static final boolean ManagedWindows = !Util.isMacLeopard(); /** note that there are no good "non-managed" options -- this flag from historical tests */ private static boolean ManagedWindows = true; private static final boolean ON_TOP = true; private static final boolean DECORATED = true; public static boolean useManagedWindowHacks() { return ManagedWindows //|| true //&& false ; } /** test */ public static void setManagedWindows(boolean managed) { ManagedWindows = managed; MacWindowShadowEnabled = !managed; } // public static void lowerAll() { // if (DEBUG.DOCK||DEBUG.WORK) Log.debug("lowerAll"); // for (DockWindow dw : AllWindows) // dw.window().setAlwaysOnTop(false); // } /** * Create a new DockWindow. You should use GUI.createDockWindow for creating * instances of DockWindow for VUE. */ public DockWindow(String title, Window owner, JComponent content, boolean asToolbar, boolean showCloseButton) { /* * The DialogPeer implementation for DockWindow addresses * The bug described here on Linux : * Read this topic online: http://vue-forums.uit.tufts.edu/posts/list/957.page#3658 */ if (!VUE.isApplet() && Util.isUnixPlatform()) { _peer = new DialogPeer(VUE.getApplicationFrame(), title, !ON_TOP, !DECORATED); } else if (ManagedWindows) { // Still required on the Mac (at least Leopard) // DockWindow's will still go behind the working full-screen window otherwise, // or create some other wierd unacceptable behaviour. _peer = new WindowPeer(owner == null ? getHiddenFrame() : owner); } else { // DialogPeer: // If ON_TOP + DECORATED: works well, except crazy bug of causing entire app to hide when losing focus // If ON_TOP + UNDECORATED: will act like ON_TOP WindowPeer: stays on top of all open applications _peer = new DialogPeer(VUE.getApplicationFrame(), title, ON_TOP, DECORATED); // the format panel has a problem when ON_TOP: the color menu pop-up // impl doesn't appear to be making itself a child of the format panel // -- in any case, it goes behind the format panel (was this the linux problem?) // Using frames NOT on-top is fairly useless tho //_peer = new FramePeer(title, ON_TOP, DECORATED && !asToolbar); // On Mac Lion -- using DialogPeer in this config can cause the entire app to dissapear // from "mission control" overviews (probably the ON_TOP is doing it) } _win = (Window) _peer; refreshGS(); /* Black ghosts on windows... * This is fixed in 1.6 as far as I can tell, but this has become an annoying * problem on 1.5 that multiple people on the team have complained about. The * problem seems to be related to this issue: * http://mindprod.com/jgloss/contentpane.html * On a mac if you look closely you really do see the same problem as windows * (at least on my pokey mac) but the background color by default on the mac * is light gray so its not as noticable. */ if (Util.isWindowsPlatform() && Util.getJavaVersion() < 1.6) _win.setBackground(Color.lightGray); if (CollapsedHeight == 0) staticInit(); // if (getParent() == HiddenParentFrame) { // // We use this in java 1.5 // setFocusableWindowState(false); // // TODO: Try enableInputMethods(false) to DISCONNECT from the focus management system // // and take keys directly? // // enableInputMethods(false); // can't see the change // } wpp = WindowPropertiesPreference.create( "windows", "window" + title.replace(" ", ""), title, "Remember size and position of window", false); showCloseBtn = showCloseButton; if (asToolbar) { // SMF: even tho you can't have any tool-tips without window // being enabled, combo box pop-ups dissapear way // to easily unless you do this (as soon as you mouse // into the toolbar, they dismiss) // MK: I'll have to check this against a mac but on windows //this behaves better without this set to false. you can //actually bring the window forward in the z-order when //you click on its borderbar, maybe this was a problem in //an old java verison since the dockwinow hadn't been used //in toolbar mode in a while? -MK // SMF 2008-04-21: The ongoing saga. See interceptMousePress // for more on this. All toolbars are henceforth not // focusable, except for Linux, where I haven't tested this. if (!Util.isUnixPlatform()) { // SMF: seems better on mac -- re-enabled for mac 2007-10-29 In // particular, very narrow drop-downs are problematic w/out doing this: // as soon as you roll off them, they dissapear. E.g., makes changing // the font via the font-size drop-down very problematic. Worth not // having rollovers for this. // This identifies it as a palette style window, leaving focus with // application Frame's as we'd like. Unfortunately, this also means // that any text widgets that need focus in order to take key input // won't, by default, get it anymore. Installing our own // KeyboardFocusManager could work around this. setFocusableWindowState(false); } } mBaseTitle = title; setTitle(title); isToolbar = asToolbar; mContentPane = new ContentPane(title, asToolbar); //getRootPane().setDoubleBuffered(true); //mContentPane.setDoubleBuffered(true); if (true) _peer.setContentPane(mContentPane); else _peer.setContentPane(new Box(BoxLayout.Y_AXIS)); setResizeEnabled(!isToolbar); if (DEBUG.INIT || DEBUG.DOCK) out("constructed (child of " + GUI.name(owner) + ")"); if (!isToolbar) { // set a default size setSize(DefaultWidth,150); //setMinimumSize(new Dimension(180,100)); // java 1.5 only //setPreferredSize(new Dimension(300,150)); // interferes with height } if (content != null) { setContent(content); } else ;//pack(); // ensure peer's created // add us to the static list of all DockWindow's synchronized (AllWindows) { AllWindows.add(this); } /* WAIT-CURSOR DEBUG setMenuActions(new Action[] { new AbstractAction("Show Wait Cursor") {public void actionPerformed(ActionEvent ae) { GUI.activateWaitCursor(); }}, new AbstractAction("Clear Wait Cursor") {public void actionPerformed(ActionEvent ae) { GUI.clearWaitCursor(); }}, });} */ setFocusable(true); if (owner == null) Log.warn("NULL OWNER: " + Util.tags(this), new Throwable(toString())); } // // RootPaneContainer // JRootPane getRootPane(); // void setContentPane(Container contentPane); // Container getContentPane(); // void setLayeredPane(JLayeredPane layeredPane); // JLayeredPane getLayeredPane(); // void setGlassPane(Component glassPane); // Component getGlassPane(); public boolean isVisible() { return _win.isVisible(); } public boolean isShowing() { return _win.isShowing(); } public boolean isDisplayable() { return _win.isDisplayable(); } public int getWidth() { return _win.getWidth(); } public int getHeight() { return _win.getHeight(); } public int getX() { return _win.getX(); } public int getY() { return _win.getY(); } public Dimension getSize() { return _win.getSize(); } public Rectangle getBounds() { return _win.getBounds(); } public void repaint() { _win.repaint(); } public void dispose() { _win.dispose(); } public void pack() { _win.pack(); } public void setSize(int w, int h) { _win.setSize(w, h); } public void setSize(Dimension d) { _win.setSize(d); } public void setFocusable(boolean t) { _win.setFocusable(t); } public void setFocusableWindowState(boolean t) { _win.setFocusableWindowState(t); } public void addComponentListener(ComponentListener l) { _win.addComponentListener(l); } public Window window() { return _win; } public DockWindow(String title, Window owner, JComponent content, boolean asToolbar) { this(title,owner,content,false,true); } public DockWindow(String title, Window owner) { this(title, owner, null, false,true); } public DockWindow(String title) { this(title, null, null, false,true); } public DockWindow(String title, JComponent content) { this(title, null, content, false,true); } public void scrollToTop() { JScrollPane jsp = this.mContentPane.getScroller(); if ( jsp != null) { jsp.getVerticalScrollBar().setValue(0); jsp.getVerticalScrollBar().setValueIsAdjusting(false); } } public void setContent(JComponent c) { if (DEBUG.DOCK || DEBUG.WIDGET || DEBUG.INIT) out("adding " + GUI.name(c) + " to " + GUI.name(getContentPanel())); boolean hadContent = getContent() != null; /*if (c.getBorder() == null) { // enforce some kind of border so that a mouse click on at least the bottom // pixel in the window get's to us, so if it's at the bottom of the screen, // we can detect it for un-rolling. if (DEBUG.BOXES) getContentPanel().setBorder(new MatteBorder(0,0,1,0, Color.green)); else getContentPanel().setBorder(new EmptyBorder(0,0,1,0)); } else { if (DEBUG.BOXES) getContentPanel().setBorder(new MatteBorder(0,0,1,0, Color.green)); else getContentPanel().setBorder(new EmptyBorder(0,0,1,0)); }*/ if (hadContent) getContent().removePropertyChangeListener(this); mMinContentSize = c.getMinimumSize(); if (DEBUG.DOCK) GUI.dumpSizes(c, this + ":setContent"); mContentPane.setWidget(c, Widget.wantsScroller(c), Widget.wantsScrollerAlways(c)); Component toListen = null; if (c instanceof JTabbedPane) { int tabCount = ((JTabbedPane)c).getTabCount(); // Call addPropertyChangeListener() for each tab... for (int tabIndex = 0; tabIndex < tabCount; tabIndex ++) { Component tab = ((JTabbedPane)c).getComponent(tabIndex); if (tab instanceof JScrollPane) toListen = ((JScrollPane)(tab)).getViewport().getView(); else toListen = tab; toListen.addPropertyChangeListener(this); if (DEBUG.DOCK) out("addPropertyChangeListener: " + GUI.name(toListen)); } // and call AddPropertyChangeListener() for the JTabbedPane itself. c.addPropertyChangeListener(this); if (DEBUG.DOCK) out("addPropertyChangeListener: " + GUI.name(c)); } else { if (c instanceof JScrollPane) toListen = ((JScrollPane)c).getViewport().getView(); if (toListen == null) toListen = c; toListen.addPropertyChangeListener(this); if (DEBUG.DOCK) out("addPropertyChangeListener: " + GUI.name(toListen)); } if (!hadContent || !isDisplayable()) { pack(); if (isToolbar) ;//setSize(620,54); else{ if(this.getTitle().equals("Merge Maps")){ setSize(535, getHeight()); }else{ setSize(DefaultWidth, getHeight()); } } } else { validate(); } //int width = minUnrolledWidth(getWidth()); //if (width < 300) width = 300; /* boolean neverDisplayed = false; int minHeight = getHeight(); if (!isDisplayable()) { // has never been displayed (or pack() called) neverDisplayed = true; int minWidth = 300; Dimension ps = c.getPreferredSize(); Dimension ms = c.getMinimumSize(); if (DEBUG.DOCK) out("content minSize " + ms); if (DEBUG.DOCK) out("content prefSize " + ps); //int minHeight = ps.height > ms.height ? ps.height : ms.height; minHeight = ms.height; setSize(minWidth, minHeight); } pack(); if (neverDisplayed) setSize(300, minHeight); */ } public void setHelpText(String helpText) { mContentPane.setHelpText(helpText); } /** this is overriden from Container just in case it is accidentally called. An Error is thrown if this is called. */ public Component add(Component c) { throw new Error("can't add component's directly to the DockWindow"); } public JPanel getContentPanel() { return mContentPane.mContent; } public JPanel getContentFull() { return mContentPane; } /** Set the size of DockWindow such that content will ultimately have the given size. * (Set the size ot the DockWindow to this size plus the size of our title bar and borders). */ public void setContentSize(int width, int height) { Dimension winBorder = getBorderSize(); setSize(width + winBorder.width, height + CollapsedHeight); } public JComponent getContent() { if (mContentPane.mContent.getComponentCount() > 0) return (JComponent) mContentPane.mContent.getComponent(0); else return null; } /** interface java.beans.PropertyChangeListener for contained component */ public void propertyChange(java.beans.PropertyChangeEvent e) { final String key = e.getPropertyName(); if (DEBUG.DOCK /*&& !key.equals("ancestor")*/) { out("Widget property change key(" + key + ") value=[" + e.getNewValue() + "]"); //GUI.messageAfterAWT("after awt for property change " + key); } if (key == Widget.EXPANSION_KEY) { if (!mWasVisible) { boolean expand = ((Boolean) e.getNewValue()).booleanValue(); setRolledUp(!expand, isDisplayable(), true); } } else if (key == Widget.HIDDEN_KEY) { if (!mWasVisible) { boolean hide = ((Boolean) e.getNewValue()).booleanValue(); if (hide) dismiss(); else setRolledUp(false, isVisible(), true); } } else if (key == Widget.MENU_ACTIONS_KEY) { setMenuActions((Action[]) e.getNewValue()); } else if (key.equals("TITLE-INFO")) { String auxTitle = auxTitle = (String) e.getNewValue(); setAuxTitle(" (" + auxTitle + ")"); } else if (key.equals("TITLE-ITEM")) { String auxTitle = auxTitle = (String) e.getNewValue(); if (auxTitle != null && auxTitle.length() > 0) setAuxTitle(": " + auxTitle); else setTitle(mBaseTitle, ""); } } private void setAuxTitle(String suffix) { setTitle(mBaseTitle, suffix); } /** All DockWindow's in this DockWindow's stack show and hide with it */ public void setStackOwner(boolean t) { isStackOwner = true; } public void setMenuActions(Action[] actions) { //mMenuActions = actions; mContentPane.mTitle.setMenuActions(actions); } public void setMenuActions(java.util.List actions) { setMenuActions( (Action[]) actions.toArray(new Action[actions.size()])); } private static void staticInit() { //------------------------------------------------------- // INIT STATIC'S //------------------------------------------------------- isMac = VueUtil.isMacPlatform(); isMacAqua = GUI.isMacAqua(); isMacAquaMetal = GUI.isMacBrushedMetal(); isWindows = VueUtil.isWindowsPlatform(); isDarkTitleBar = isMacAquaMetal; //isGradientTitle = isMac; isGradientTitle = true; if (OverrideMacAquaBrushedMetal && Util.getJavaVersion() >= 1.5f) isMacAquaMetal = false; if (isMacAquaMetal) { //SidewaysRollup = false; // can't work in 1.4 brushed metal due to mac java bug [no 1.4 VUE anymore] CollapsedHeight = MacAquaMetalMinHeight; } else { CollapsedHeight = TitleHeight; } CollapsedHeightVisible = TitleHeight; WindowBorder = makeWindowBorder(); if (WindowBorder != null) { Insets bi = WindowBorder.getBorderInsets(null); CollapsedHeightVisible += bi.top + bi.bottom; if (isMacAqua) // overlap by one pixel CollapsedHeightVisible -= 1; else // overlap by 2 pixels ; // not till we can fix up border overlap flashing; CollapsedHeightVisible -= 2; if (!isMacAquaMetal) CollapsedHeight += bi.top + bi.bottom; } // Simulate Windows; // isWindows=true; isMac=isMacAqua=isMacAquaMetal=false; // Simulate Linux; // isWindows=false; isMac=isMacAqua=isMacAquaMetal=false; /* if (isMac) { TitleFont = VueAquaLookAndFeel.SmallSystemFont12; } else { Object desktopFrameFont = Toolkit.getDefaultToolkit().getDesktopProperty("win.frame.captionFont"); if (desktopFrameFont instanceof Font) { Font font = (Font) desktopFrameFont; //System.out.println("GOT TITLE FONT " + font); TitleFont = font.deriveFont(10.0f); //System.out.println("GOT TITLE FONT " + TitleFont); } else { TitleFont = new Font("SansSerf", Font.BOLD, 10); } } */ //sBottomGradientColor = isDarkTitleBar ? new Color(112,112,112) : Color.lightGray; //int midColor = (sBottomGradientColor.getRed() + sTopGradientColor.getRed()) / 2; //sMidGradientColor = new Color(midColor, midColor, midColor); refreshScreenEdges(null); //TopDock = new DockRegion(GUI.GInsets.top, DockRegion.TOP, "TopScreen"); //BottomDock = new DockRegion(GUI.GScreenHeight - GUI.GInsets.bottom, DockRegion.BOTTOM, "BotScreen"); // Be sure to create MainDock last, so the other docks // with fixed locations will take priority for membership. //MainDock = new DockRegion(-99, DockRegion.BOTTOM, "AppWinTop"); // top of app window } public static boolean isTopDockEmpty() { return TopDock == null ? true : TopDock.isEmpty(); } public static DockRegion getTopDock() { if (CollapsedHeight == 0) staticInit(); return TopDock; } public static DockRegion getMainDock() { if (CollapsedHeight == 0) staticInit(); return MainDock; } public static int getCollapsedHeight() { if (CollapsedHeight == 0) staticInit(); return CollapsedHeight; } // public static int getMaxContentHeight() { // return GUI.getMaxWindowHeight() - (getCollapsedHeight() + GUI.GInsets.bottom + 50); // } /** @return a border, if any, for the entire DockWindow (null if none) */ private Border getWindowBorder() { if (isToolbar && isMacAqua) return null; else return WindowBorder; } /** @return a border, if any, for the entire DockWindow (null if none) */ private Border getContentBorder(JComponent c) { if (isToolbar) return null; if (ContentBorder == null) { if (DEBUG.BOXES) return ContentBorder = new LineBorder(Color.orange, 4); else return null; // ContentBorder = new CompoundBorder(new MatteBorder(3,2,3,2, new Color(235,235,235)), //new LineBorder(new Color(102,102,102))); //ContentBorderInset = new CompoundBorder(ContentBorder, GUI.WidgetInsetBorder); } return ContentBorder; /* if (c instanceof WidgetStack || firstChild(c) instanceof WidgetStack || c instanceof JScrollPane) return ContentBorder; else return ContentBorderInset; */ } private Dimension mBorderSize; private Dimension getBorderSize() { if (mBorderSize == null) { mBorderSize = new Dimension(); if (getWindowBorder() != null) { Insets wb = getWindowBorder().getBorderInsets(null); mBorderSize.width += wb.left + wb.right; mBorderSize.height += wb.top + wb.bottom; } if (getContentBorder(null) != null) { Insets cb = getContentBorder(null).getBorderInsets(null); mBorderSize.width += cb.left + cb.right; mBorderSize.height += cb.top + cb.bottom; } } return mBorderSize; } /** @return first child of component, if it is a Container and has children, otherwise null */ private static Component firstChild(Component component) { if (component instanceof Container) { Container c = (Container) component; if (c.getComponentCount() > 0) return c.getComponent(0); } return null; } private static Border makeWindowBorder() { if (isMacAqua && (MacWindowShadowEnabled || isMacAquaMetal)) { return null; // no border on MacOSX at all for now: rely on native shadowing } else { if (DEBUG.BOXES) { return new LineBorder(Color.green); } else { return new CompoundBorder(new MatteBorder(0,1,1,1, new Color(204,204,204)), new LineBorder(new Color(137,137,137))); //return new CompoundBorder(new LineBorder(new Color(204,204,204)), // new LineBorder(new Color(137,137,137))); //return new LineBorder(new Color(51,51,51)); } /* if (isMacAqua) { if (DEBUG.BOXES) return new LineBorder(Color.green); else return new LineBorder(sBottomEdgeColor); //return new BevelBorder(BevelBorder.RAISED, Color.lightGray, Color.gray); } else { // For Windows: Color base = GUI.getVueColor(); return new BevelBorder(BevelBorder.RAISED, base, Util.brighterColor(base), //base.brighter(), base.darker().darker(), base.darker()); //return BorderFactory.createRaisedBevelBorder(); } */ } } public void setResizeEnabled(boolean canResize) { isResizeEnabled = canResize; if (canResize) { if (mResizeCorner == null) { mResizeCorner = new ResizeCorner(this, SOUTH_EAST); mResizeCorner2 = new ResizeCorner(this, SOUTH_WEST); _peer.getLayeredPane().add(mResizeCorner, JLayeredPane.PALETTE_LAYER); _peer.getLayeredPane().add(mResizeCorner2, JLayeredPane.PALETTE_LAYER); // todo: need to handle window reshape (it moves) v.s. just resize for this } } else { if (mResizeCorner != null) { _peer.getLayeredPane().remove(mResizeCorner); _peer.getLayeredPane().remove(mResizeCorner2); mResizeCorner = null; mResizeCorner2 = null; } } } private static void dumpGC(Graphics g) { Graphics2D g2 = (Graphics2D) g; sun.java2d.SurfaceData sd = null; if (g instanceof sun.java2d.SunGraphics2D) sd = ((sun.java2d.SunGraphics2D)g).surfaceData; System.out.println("\t" + Util.objectTag(g) + " surface=" + sd + "\n\t clip " + g.getClip() + "\n\tclipBounds " + g.getClipBounds() //+ "\n\t hints " + g2.getRenderingHints() //+ "\n\tbackground " + g2.getBackground() ); } // public void Xupdate(Graphics g) { // //g.drawString("Hello", 10, 20); // //if (DEBUG.DOCK || DEBUG.PAINT) out("update"); // Util.printClassTrace("!java.awt.EventDispatchThread", "update"); // dumpGC(g); // super.update(g); // } // public void paint(Graphics g) { // //Util.printClassTrace("!java.awt.EventDispatchThread", "paint"); // if (DEBUG.PAINT) out("paint"); // // For subclassing Window impl: // // if (!isRolledUp()) paintResizeCorner((Graphics2D)g); // super.paint(g); // } // public void paintAll(Graphics g) { // Util.printStackTrace("paintAll"); // super.paintAll(g); // } // public void Xreshape(int x, int y, int w, int h) { // if (DEBUG.DOCK) out("reshape"); // //super.reshape(x, y, w, h); // if (DEBUG.DOCK) out("reshape returns"); // // Mac frames that are DECORATED refresh beautifully, but unless the reshape // // request is coming from MacOSX, we get flashing for any other reshape (from // // java) calls. // //----------------------------------------------------------------------------- // // // // The problem is that the call to peer.setBounds in reshape is ultimately // // causing a clearRect on the the entire graphics context, no matter what we do, // // even if we manually turn off peerFlushing (either it doesn't handle that // // case, or it's getting turned back on). There is special code in the apple // // peers that knows how handle the COMPONENT_RESIZED (maybe that event is // // special) when it comes from native CFrame drags, that eliminates flashing, // // but for whatever reason they're just not doing for this case. // // // //----------------------------------------------------------------------------- // } public void validate() { // Util.printClassTrace("!java.awt.EventDispatchThread", "validate"); if (mAnimatingReshape) { // this no help in preventing occasional mac double-redraw's on show if (DEBUG.DOCK && DEBUG.META) out("validate: skipping"); } else { if (DEBUG.DOCK && DEBUG.META) out("validate"); //Util.printStackTrace("validate " + this); _peer.peer_validate(); if (mResizeCorner != null) { int width = getWidth(); int height = getHeight(); // south east mResizeCorner.setLocation(width - ResizeCornerSize, height - ResizeCornerSize); // south west if (mResizeCorner2 !=null) mResizeCorner2.setLocation(0, height - ResizeCornerSize/2); } } } public void invalidate() { //Util.printClassTrace("!java.awt.EventDispatchThread", "invalidate"); if (mAnimatingReshape) { if (DEBUG.DOCK && DEBUG.META) out("invalidate: skipping"); //Util.printStackTrace("invalidate " + this); // This doesn't help unless we make sure we don't clear mAnimatingReshape // until the AWT EventQueue is cleared after we're done animating, as a // COMPONENT_RESIZED event for each resize during animation is being posted // to the EventQueue, which then all come, one after the other, after we're // done animating, at least on the mac (i suspect it's smarter on PC?) When // they get to Window.dispatchEventImpl, it issues a paired call // validate/invalidate for each event. // So Component.reshape is calling invalidate on resize's, and then posting // a COMPONENT_RESIZED event each time also. // Altho all the extra invalidate/validate's don't appear to be causing // multiple repaints in this case, we're skipping these as they're probably // slowing things down. Actually, at the moment on my PowerBook 1.5Ghz, I // can detect no difference at all... } else { if (DEBUG.DOCK && DEBUG.META) out("invalidate"); _peer.peer_invalidate(); } } public void addNotify() { if (DEBUG.DOCK) out("addNotify"); if (OverrideMacAquaBrushedMetal && GUI.isMacBrushedMetal()) { // This trick only works on MacOSX Java 1.5: // If we have this special name when the peer is created, the Window will // NOT be MacOSX brushed metal, even if that's what we're running under. // This is the name java uses for popup windows such as menus and tool // tips, which don't appear as brushed metal under 1.5 (they do under 1.4). // This is also a way to get around the minimum size bug of Windows when // using Mac Aqua Brushed Metal. _win.setName(GUI.OVERRIDE_REDIRECT); // Note that we can re-set the name for debugging purposes after // the peer has been created (super.addNotify()) } _peer.peer_addNotify(); updateWindowShadow(); _win.addMouseListener(this); _win.addMouseMotionListener(this); // make sure peer has title for a native MacOSX code, and re-set name if it was ###override setTitle(mTitleName); if (isToolbar) { // enforced a fixed height on toolbars mContentPane.setPreferredSize(new Dimension(_win.getPreferredSize().width, ToolbarHeight)); } } private DockWindow getFirstVisibleParent() { if (isVisible()) return this; else if (mParentWhenHidden != null) return mParentWhenHidden.getFirstVisibleParent(); else return null; } private boolean _firstDisplay = true; private void superSetVisible(final boolean show) { if (DEBUG.DOCK) out("superSetVisible " + show); mShowing = show; if (_firstDisplay) { _firstDisplay = false; keepOnScreen(); } _peer.peer_setVisible(show); /* if (isMacAqua && show && !isStacked()) setWindowShadow(true); super.setVisible(show); if (isMacAqua && !show) { GUI.invokeAfterAWT(new Runnable() { public void run() { setWindowShadow(false); }}); } */ } public static void ToggleAllVisible() { if (!AllVisible) ShowPreviouslyHiddenWindows(); else HideAllWindows(); } public static boolean AllWindowsHidden() { return !AllVisible; } public synchronized static void HideAllWindows() { if (DEBUG.Enabled) Log.debug("hide all + activate viewer"); DeactivateAllWindows(); // TODO: when prophylactically hiding these for full-screen mode, // this is overkill / could even be problematic. ensureViewerHasFocus(); } public synchronized static void RestoreAllWindowStates() { if (DEBUG.DOCK) Log.debug("-----------RestoreAllDockWindowStates-----------"); for (DockWindow dw : AllWindows) { dw.positionWindowFromProperties(); } } public synchronized static void SaveAllWindowStates() { if (DEBUG.DOCK) Log.debug("-----------SaveAllDockWindowStates-----------"); for (DockWindow dw : AllWindows) { dw.saveWindowProperties(); } } public synchronized static void DeactivateAllWindows() { if (DEBUG.Enabled) Log.debug("deactivate all"); AllVisible = false; for (DockWindow dw : AllWindows) { dw.mWasVisible = dw.isVisible(); if (dw.mWasVisible) dw.superSetVisible(false); } } public void raise() { toFront(); } //@Override void toFront() { //tufts.Util.printClassTrace(DockWindow.class, "RAISING"); if (isVisible()) { if (DEBUG.DOCK||DEBUG.WORK) out("toFront"); _win.toFront(); } else { // Window.toFront does nothing if not visible anyway //if (DEBUG.DOCK) out("(toFront)"); } } public synchronized static void ShowPreviouslyHiddenWindows() { if (DEBUG.DOCK) Log.debug("ShowPreviouslyHiddenWindows"); if (VUE.inNativeFullScreen()) { Log.debug("Ignoring show all windows: in native full screen"); // don't touch windows if in native full screen, as can // completely hang us on Mac OS X return; } if (Util.isMacLeopard()) { // Okay, this is seriously messed. If we have only ONE DockWindow visible, // we can hide/show all ONCE, and it stays on top, but the SECOND time // we hide/show (or enter/exit full screen mode), it starts going behind! // Showing a second DockWindow then hiding/showing all once or twice // seems to put things back in order. Delaying the toFront to be // invoked later on the AWT thread appears to offer no help, nor does // just raising them all at the end. for (DockWindow dw : AllWindows) { if (dw.mWasVisible) { dw.superSetVisible(true); dw.toFront(); //dw.setAlwaysOnTop(true); // final DockWindow d = dw; // GUI.invokeAfterAWT(new Runnable() { public void run() { // d.toFront(); // 2008-04-21 required for Mac OSX Leopard to keep them on top // }}); } dw.mWasVisible = false; } // // hope this helps the random Leopard cases that still break... // GUI.invokeAfterAWT(new Runnable() { public void run() { // DockWindow.raiseAll(); // }}); } else { for (DockWindow dw : AllWindows) { if (dw.mWasVisible) dw.superSetVisible(true); // non-Leopard platforms automatically toFront on this dw.mWasVisible = false; } } ensureViewerHasFocus(); AllVisible = true; } private static void ensureViewerHasFocus() { // The give the focus back to the viewer, which can lose it it // when they go visible or invisible. E.g., on WinXP, even // the hidden FullScreen.FSWindow is somes getting focus, // which it never should, given that it's focusable state is // false. GUI.invokeAfterAWT(new Runnable() { public void run() { final tufts.vue.MapViewer viewer = VUE.getActiveViewer(); if (viewer != null) { if (DEBUG.DOCK) Log.debug("focusRequest to return focus to " + viewer); viewer.requestFocus(); //VUE.getActiveViewer().grabVueApplicationFocus(DockWindow.class.getName(), null); }}}); } public synchronized static void ImmediatelyRepaintAllWindows() { if (DEBUG.Enabled) Log.debug("ImmediatelyRepaintAllWindows"); for (DockWindow dw : AllWindows) { if (dw.isVisible()) { Rectangle bounds = dw.getContentPanel().getBounds(); // adjust bounds to force repainting of the entire window contents bounds.x = -50; bounds.y = -50; bounds.width += 100; bounds.height += 100; if (DEBUG.PAINT) Log.debug("repaint " + dw + "; " + bounds); dw.getContentPanel().paintImmediately(bounds); } } } /** keep the bottom of the window from going below the bottom screen edge */ // TODO: this should actually only keep it on ALL screens private void keepOnScreen() { Rectangle r = _win.getBounds(); if (keepOnScreen(r)) setSize(r.width, r.height); } private boolean isMaximized = false; private void setMaximizeVertical(boolean expandUpAsWellAsDown) { out("maximzeVertical; isMax=" + isMaximized); if (isMaximized) { setBounds(mNormalShape); isMaximized = false; } else { isMaximized = true; mNormalShape = getBounds(); Rectangle max = GUI.getMaximumWindowBounds(_win); if (expandUpAsWellAsDown) { setBounds(getX(), max.y, getWidth(), max.height); } else { setHeight((max.height + max.y) - getY()); // int maxBottom = GUI.GScreenHeight - GUI.GInsets.bottom; // setHeight(maxBottom - getY()); } } } /** @return true of bounds were modified */ private boolean keepOnScreen(Rectangle r) { final int bottom = r.y + r.height; final Rectangle max = GUI.getMaximumWindowBounds(_win); final int maxBottom = max.height + max.y; if (bottom > maxBottom) { r.height = Math.max(TitleHeight + 1, maxBottom - r.y); return true; } else return false; } // /** @return true of bounds were modified */ // private boolean keepOnScreen(Rectangle r) { // int bottom = r.y + r.height; // int maxBottom = GUI.GScreenHeight - GUI.GInsets.bottom; // //out(" y="+r.y); // //out(" bottom="+bottom); // //out("maxBottom="+maxBottom); // if (bottom > maxBottom) { // r.height = Math.max(TitleHeight + 1, maxBottom - r.y); // return true; // } else // return false; // /* // Rectangle max = GUI.GMaxWindowBounds; // if (DEBUG.Enabled) out("maxwinbounds: " + max); // if (mUnrolledShape.width > max.width) // mUnrolledShape.width = max.width; // if (mUnrolledShape.height > max.height) // mUnrolledShape.height = max.height; // */ // } /** for use during application startup */ public void showRolledUp() { if (DEBUG.DOCK || DEBUG.INIT) out("showRolledUp"); setRolledUp(true, false); //updateWindowShadow(); superSetVisible(true); } /** normally can only be in a DockRegion if visible: this allows pre-assignment during startup */ public void setDockTemporary(DockRegion region) { if (DEBUG.DOCK) out("setDockTemporary: " + region); mDockRegion = region; updateWindowShadow(); } public static void assignAllDockRegions() { DockRegion.assignAllMembers(); } public void setStackVisible(boolean show) { // If showing, show us first, then children. // If hiding, do reverse. if (show) { // Invoking later helps ensure DockWindow's that // are set visible last are on the top of the z-order, // which is important for MacOSX window shadow. // Unfrotunately, this is not full-proof, but // adding a call to toFront seems to have fixed this? if (isMacAqua && MacWindowShadowEnabled && !VUE.isApplet()) { GUI.invokeAfterAWT(new Runnable() { public void run() { superSetVisible(true); toFront(); }}); } else { superSetVisible(true); } } if (mChild != null) mChild.setStackVisible(show); if (!show) { superSetVisible(false); } } //@Override public void setVisible(boolean show) { setVisible(show, true); if (show && !VUE.isApplet()) toFront(); } private void raiseChildrenLater() { // apparently, sometimes, we must raise the children later for toFront to work GUI.invokeAfterAWT(new Runnable() { public void run() { raiseChildren(); }}); } protected void setVisible(boolean show, boolean autoUnrollOnShow) { mShowing = show; if (DEBUG.FOCUS || DEBUG.DOCK) { out("setVisible " + show); // if (!show && !AllVisible && mWasVisible) { // out("ignoring visibility request: all are hidden"); // return; // } } if (isStackOwner && mChild != null) { setStackVisible(mShowing); //if (isMac && mShowing) raiseChildrenLater(); return; } if (show && !VUE.isApplet()) { if (autoUnrollOnShow && isRolledUp()) setRolledUp(false); else if (false && mUnrolledShape != null) // need to show before we do this! Will need to tweak us so that's okay to do. setShapeAnimated(getX(), getY(), mUnrolledShape.width, mUnrolledShape.height); } else if (false && !VUE.isApplet()) { if (!isRolledUp()) mUnrolledShape = getBounds(); setShapeAnimated(getX(), getY(), getWidth(), 0); } if (isVisible() == mShowing) return; updateOnVisibilityChange(); superSetVisible(show); if (show) { boolean windowStackChanged = false; if (mParentWhenHidden != null) { DockWindow visibleParent = mParentWhenHidden.getFirstVisibleParent(); if (visibleParent != null) { visibleParent.setChild(this); windowStackChanged = true; } } else if (mChildWhenHidden != null && mChildWhenHidden.mParent == this && mChildWhenHidden.isVisible()) { setChild(mChildWhenHidden); windowStackChanged = true; } mChildWhenHidden = null; mParentWhenHidden = null; if ((isMac && true || windowStackChanged) && !VUE.isApplet()) { raiseChildrenLater(); } } DockRegion.assignAllMembers(); } private void updateOnVisibilityChange() { if (DEBUG.DOCK) out("updateOnVisibilityChange; mShowing=" + mShowing + " visible=" + isVisible()); if (mShowing == false) { mChildWhenHidden = mChild; mParentWhenHidden = mParent; if (mChild != null) { if (mParent != null) mParent.setChild(mChild); else removeChild(); } else if (mParent != null) { mParent.removeChild(); } } else { /* if (mParentWhenHidden != null && mParentWhenHidden.isVisible()) mParentWhenHidden.setChild(this); else if (mChildWhenHidden != null && mChildWhenHidden.isVisible()) setChild(mChildWhenHidden); mChildWhenHidden = null; mParentWhenHidden = null; */ } } private void dismiss() { if (DEBUG.DOCK) out("DISMISS"); //setShapeAnimated(getX(), getY(), getWidth(), 0); // oops: this never handled showing us again anyway :) setVisible(false); } public void saveWindowProperties() { final Point p; final Dimension size; if (isRolledUp) size = new Dimension((int)mUnrolledShape.getWidth(),(int)mUnrolledShape.getHeight()); else size = getSize(); if (isShowing()) { p = _win.getLocationOnScreen(); } else { // Cannot query the "location on screen" of a hidden window -- throws exception p = _win.getLocation(); // may be a problem for multi-monitor? //p = new Point(-1,-1); } wpp.updateWindowProperties(isShowing(), size.width, size.height, p.x, p.y, isRolledUp); } public void positionWindowFromProperties() { if (!wpp.isEnabled()) return; final Point p = wpp.getWindowLocationOnScreen(); final Dimension size = wpp.getWindowSize(); final Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); if (wpp.isWindowVisible() || wpp.isRolledUp()) { if (p.x > -1 && isPointFullyOnScreen(p,size,screenSize)) { if (isToolbar) // ignore size on toolbars: they always get their designed size setLocation(p.x, p.y); else setBounds(p.x, p.y, size.width, size.height); if (wpp.isRolledUp()) { mUnrolledShape = new Rectangle(p.x, p.y, size.width, size.height); showRolledUp(); } else setVisible(wpp.isWindowVisible()); } else if (wpp.isWindowVisible()) { //System.out.println("OTHER"); suggestLocation(p.x, p.y); if (wpp.isRolledUp()) { mUnrolledShape = new Rectangle((int)p.getX(),(int)p.getY(),(int)size.getWidth(),(int)size.getHeight()); DockWindow.flickerAnchorDock(); showRolledUp(); } else { DockWindow.flickerAnchorDock(); setVisible(wpp.isWindowVisible()); } } } else { // Even if window is not visible, suggest it's last location. Using "suggest" means if // it's off the main screen, it will move it onto the main screen, so this won't work // for multi-monitor locations, but this is a failsafe to make sure windows don't get // lost. suggestLocation(p.x, p.y); } } // "flicker" our invisible anchor. As the anchor is an extreme workaround hack // specific to getting Java MDI interfaces to work on Mac Leopard, the code in this // method, tho it could use logic cleanup to elimiate redundancy, I'm not going to touch. // We're assuming we still need to do this hack in Lion and later OS's as well, // that that would need testing to determine for sure. SMF 2012-06-16. public static void flickerAnchorDock() { if (VUE.usingAnchorDock() == false) return; if (!VUE.isApplet() && Util.isMacLeopard() && VUE.getAnchorDock().isVisible()) { VUE.getAnchorDock().setVisible(false); } if (!VUE.isApplet() && Util.isMacLeopard()) { VUE.getAnchorDock().setVisible(true); VUE.getAnchorDock().toFront(); } return; } private boolean isPointFullyOnScreen(Point p, Dimension size, Dimension screenSize) { int rightCorner = (int)p.getX() + (int)size.getWidth(); int bottomCorner = (int)p.getY() + (int)size.getHeight(); if ((rightCorner <= screenSize.getWidth()) && (bottomCorner <= screenSize.getHeight())) return true; else return false; } public WindowPropertiesPreference getWindowProperties() { return wpp; } /** look for a tabbed pane within us with the given title, and select it */ public void showTab(final String name) { new EventRaiser<JTabbedPane>(this, JTabbedPane.class) { public void dispatch(JTabbedPane tabbedPane) { int i = tabbedPane.indexOfTab(name); if (i >= 0) { tabbedPane.setSelectedIndex(i); EventRaiser.stop(); } } }.raiseStartingAt(_win); setVisible(true); } public void setTitle(String title) { setTitle(title, null); } private void setTitle(String title, String suffix) { if (mTitleName != null && mTitleName.equals(title) && suffix == null) return; // TODO: don't update the root title if the aux title changes mTitleName = title; //mTitleWidth = GUI.stringLength(TitleFont, title); final int baseLen = GUI.stringLength(TitleFont, title); final int suffixLen = (suffix == null ? 0 : GUI.stringLength(TitleFont, suffix)); //mMinTitleWidth = mTitleWidth + 4; mMinTitleWidth = 6; _win.setName(title); GUI.setRootPaneNames(_peer, title); if (isMac && Util.isMacCocoaSupported() && isDisplayable() && !VUE.isApplet()) { // isDisplayable true if we have a peer, which we need before MacOSX lib calls try { MacOSX.setTitle(_win, title); } catch (Throwable t) { t.printStackTrace(); } } final String fullTitle; if (suffix == null) fullTitle = title; else fullTitle = title + suffix; if (mContentPane != null && mContentPane.mTitle != null) { mContentPane.mTitle.setTitle(fullTitle); // hackish to add this to existing title width, put produces sane numbers if (DEBUG.DOCK) Log.debug(this + "; total title width " + mContentPane.mTitle.getMinimumSize().width //+ " for " + mContentPane.mTitle ); mMinTitleWidth += mContentPane.mTitle.getMinimumSize().width; if (suffixLen > baseLen) mMinTitleWidth -= suffixLen; } repaint(); } public String getTitle() { return mTitleName; } /** Return the name to use in a menu action to refer to this window: defaults to title */ public String getMenuName() { return mMenuName == null ? getTitle() : mMenuName; } /** Set a separate menu name: if null, will default to title */ public void setMenuName(String s) { mMenuName = s; } private int minUnrolledHeight(int height) { int absoluteMin = TitleHeight + getBorderSize().height + mMinContentSize.height; if (absoluteMin < TitleHeight + ResizeCornerSize) absoluteMin = TitleHeight + ResizeCornerSize; if (height < absoluteMin) return absoluteMin; else return height; } private int minUnrolledWidth(int requestedWidth) { final DockWindow stackTop = getStackTop(); int absoluteMin = getBorderSize().width + mMinContentSize.width; if (absoluteMin < mMinTitleWidth) absoluteMin = mMinTitleWidth; if (stackTop.isStackOwner && stackTop != this) { if (absoluteMin < stackTop.getWidth()) absoluteMin = stackTop.getWidth(); } if (requestedWidth < absoluteMin) return absoluteMin; else return requestedWidth; } public void setBounds(Rectangle r) { setBounds(r.x, r.y, r.width, r.height); } private final static String ANCHOR_TITLE = VueResources.getString("dockWindow.vueleopard.title"); //@Override public void setBounds(int x, int y, int width, int height) { if (this.getTitle().equals(ANCHOR_TITLE)) { if (!DEBUG.DOCK) { if (_peer != null) { _peer.peer_setBounds(0, GUI.getOffScreenY(), getWidth(),getHeight()); } return; } } if (DEBUG.DOCK) out("setBounds " + x+","+y + " " + width+"x"+height); if (DEBUG.Enabled && width == 0 && mTitleName != null) // mTitle only null during <init> Util.printStackTrace(this + " zero width setBounds " + x+","+y + " " + width+"x"+height); /* // no way to erase minimum brushed metal window size... apple.awt.CWindow peer = (apple.awt.CWindow) getPeer(); if (peer != null) { invalidate(); out(" awt minSize: " + getMinimumSize()); out("peer getMinSize: " + peer.getMinimumSize()); out("peer minSize: " + peer.minimumSize()); //peer.setBackground(Color.blue); //setMinimumSize(new Dimension(1,1)); //peer.setBounds(x, y, width, height); peer.reshape(x, y, width, height); return; } */ /* float mAspect = 3f / 4f; System.out.println("aspect=" + mAspect); if (mAspect > 0) { if (width <= 0) width = 1; if (height <= 0) height = 1; double newAspect = width / height; if (newAspect < mAspect) height = (int) (width / mAspect); else if (newAspect > mAspect) width = (int) (height * mAspect); } */ // if (_win == null) { // // setBounds can be called via java.awt.Component.setLocation from java.awt.Window.init // // during delegate construction, when it's impossible to have obtained a reference // // to the delegate yet (AWT's code for automatically setting the window location // // by platform: see Window.setLocationByPlatform). The call to getHeight() will // // fail with NPE if _win is not set. // return; // } int curHeight = getHeight(); int curWidth = getWidth(); if (height != curHeight) { // If we're shrinking, move up our child before we shrink up, // otherwise, move down our child after we grow. This keeps // whatever is under the window stack from peeking through as // the stack adjusts. if (height < curHeight) { updateAllChildLocations(height, getY()); if(width < curWidth){ _peer.peer_setBounds(x, y, width, height); }else{ if(width == DefaultWidth ){ _peer.peer_setBounds(x, y, width, height); }else{ _peer.peer_setBounds(x, y, curWidth, height); } } } else { _peer.peer_setBounds(x, y, width, height); updateAllChildLocations(height, getY()); } } else _peer.peer_setBounds(x, y, width, height); if (!isMacAqua) { // needed for Java Metal L&F validate(); } } private boolean wantsSidewaysRollup() { return SidewaysRollup && atScreenLeft() && !atScreenTop() && !atScreenBottom() && !isStacked(); } private int minRolledHeight() { if (wantsSidewaysRollup()) return getWidth(); else return CollapsedHeight; } private int minRolledWidth() { if (mDockRegion != null) return mDockRegion.getRolledWidth(this); else return minUnrolledWidth(getWidth()); } private int XminRolledWidth() { if (mDockRegion != null) return mDockRegion.getRolledWidth(this); if (isStacked() == false) { if (wantsSidewaysRollup()) { return CollapsedHeight; //} else if (mDockRegion != null) { //return mDockRegion.getRolledWidth(this); } else { // conserve width if at sides return getWidth() > 180 ? 180 : getWidth(); } } // choose the widest of either parent or child's stacked {unrolled,rolled} size // TODO: need more work: if parent or child rolled-up, their width may even have // been stretched, in which case we'll want to shrink the rolled-up size to, // say, our width if we're narrow and there are no other wider windows. //final int parentWidth = mParent == null ? 0 : mParent.getStackedWidth(); //final int childWidth = mChild == null ? 0 : mChild.getStackedWidth(); final int parentWidth = mParent == null ? Integer.MAX_VALUE : mParent.getWidth(); final int childWidth = mChild == null ? Integer.MAX_VALUE : mChild.getWidth(); // smallest of the two: return minUnrolledWidth(parentWidth < childWidth ? parentWidth : childWidth); } private int XgetRolledWidth() { if (mParent == null && mChild == null) return 180; // choose the widest of either parent or child final int parentWidth = mParent == null ? 0 : mParent.getWidth(); final int childWidth = mChild == null ? 0 : mChild.getWidth(); return parentWidth > childWidth ? parentWidth : childWidth; } /** return width in stack: the smaller of current rolled width or unrolled width */ private int getStackedWidth() { final int curWidth = getWidth(); if (mUnrolledShape != null) return mUnrolledShape.width < curWidth ? mUnrolledShape.width : curWidth; else return curWidth; } /* private void updateRolledUpSize(DockWindow notifier) { if (isRolledUp()) { setSizeAnimated(getRolledWidth(), getHeight()); } } */ public boolean atScreenTop() { // TODO: should also be at top off off-screen to the top and no other screen is above return GS != null && GS.inTop(getY()); //if (DEBUG.Enabled) out("atScreenTop: y=" + getY() + " <= " + GUI.GInsets.top); //return getY() <= GUI.GInsets.top; } public boolean atScreenLeft() { return GS != null && GS.atLeft(getX()); //return getX() == 0; //return getX() <= GUI.GInsets.left; } public boolean atScreenRight() { return GS != null && GS.atRight(getY()); //return (getX() + getWidth()) == GUI.GScreenWidth; /* // will need to align to the PARENT, not the screen, if to support this. if (GUI.GScreenWidth == 0) return false; else return getX() > GUI.GScreenWidth / 2; */ } public boolean atScreenBottom() { // don't allow this for anything with a top in the upper half of the screen if (GS == null || getY() < GS.top + GS.height / 2) return false; final int bottomEdge = getY() + getHeight(); return GS.inBottom(bottomEdge); // if (GS.margin.bottom > 0) { // return bottomEdge <= GUI.GScreenHeight // && bottomEdge >= GUI.GScreenHeight - GUI.GInsets.bottom; // } else { // return bottomEdge == GUI.GScreenHeight; // } } /** * Set this DockWindow to it's "rolled-up" state: just the title * bar is showing. If Component.isDisplayable() is true (it * has a peer / has been shown on screen at least once), then * the transition will be animated. */ public void setRolledUp(boolean rollup) { setRolledUp(rollup, isDisplayable()); } public void setRolledUp(boolean makeRolledUp, boolean animate) { setRolledUp(makeRolledUp, animate, false); } private void setRolledUp(boolean makeRolledUp, boolean animate, boolean propertyChangeEvent) { if (DEBUG.DOCK) out("setRolledUp " + makeRolledUp + " animate=" + animate + " propertyChangeEvent="+propertyChangeEvent); if (isRolledUp == makeRolledUp || isToolbar) return; final Screen screen = refreshGS(); /* * i can't find a case where removing this breaks something. It would seem * that maybe it wasn't considering that the Content could be a widgetstack, and * so this bug was missed. just calling widget.setExpanded here is not going to * collapse the dockwindow when the content is a widgetstack -MK */ /*if (!propertyChangeEvent && Widget.isWidget(getContent())) { // ensure the Widget property value is set. Widget.setExpanded(getContent(), !makeRolledUp); return; } */ // need to mark us as rolled up now for forthcoming // size computations to work. isRolledUp = makeRolledUp; // updateWindowShadow(); if (makeRolledUp) { // make us rolled up mUnrolledShape = getBounds(); //out("shape " + mUnrolledShape); // Okay, two cases: one where we're getting our "future" rolled up width // now, adjusting for future adjustments in parent & children, who may only // have been rolled up wider becuase we are wide. // The other is "immediate": my parent or child just changed sized, so now // what do I do? Of course, in this case, we do have a cascading // dependency... my parent may have been wider because of me, but now it // *still* wants to be wider rolled becuase if *it's* parent, tho maybe not // as wide as it is now, and *WE* need to know that information... // So probably should just iterate through the whole damn stack each time // (instead of this recursive confusion), starting from the widest unrolled // size, and updating everyone that way -- which would allow us to also do a // paralell call on all the setSizes. int rolledWidth = minRolledWidth(); int rolledHeight = minRolledHeight(); int rolledX = getX(); int rolledY = getY(); if (atScreenBottom() && mParent == null && !atScreenTop()// don't roll-down in stacks for now //|| (isDocked() && mDockRegion.mGravity == DockRegion.BOTTOM) ) { rolledY = getY() + getHeight() - rolledHeight; } else if (atScreenRight()) { mStickingRight = true; rolledX = screen.right - rolledWidth; } if (animate) setShapeAnimated(rolledX, rolledY, rolledWidth, rolledHeight); else setBounds(rolledX, rolledY, rolledWidth, rolledHeight); getContentPanel().setVisible(false); if (wantsSidewaysRollup()) mContentPane.setVerticalTitle(true); // Pretend like we've been hidden so that VueMenuBar.WindowDisplayAction // will treat another display request as an un-roll request in-line subclass // so is marked for tracking. I think this also is making sure we give up // the kbd focus if one of our children had it. if (animate) // only during normal use: not during init GUI.postEvent(new ComponentEvent(_win, ComponentEvent.COMPONENT_HIDDEN) {} ); } else { if (SidewaysRollup) // ensure vertical cleared mContentPane.setVerticalTitle(false); mStickingRight = false; Rectangle newShape = new Rectangle(mUnrolledShape); if (atScreenBottom() && mParent == null //|| (isDocked() && mDockRegion.mGravity == DockRegion.BOTTOM) ) { newShape.x = getX(); newShape.y = getY() + getHeight() - mUnrolledShape.height; } else if (atScreenRight()) { newShape.x = screen.right - mUnrolledShape.width; newShape.y = getY(); mStickingRight = true; } else { newShape.x = getX(); newShape.y = getY(); } keepOnScreen(newShape); setShapeAnimated(newShape.x, newShape.y, newShape.width, newShape.height); getContentPanel().setVisible(true); mUnrolledShape = null; // pretend like we've been shown so that VueMenuBar.WindowDisplayAction // will know we're visible again. GUI.postEvent(new ComponentEvent(_win, ComponentEvent.COMPONENT_SHOWN)); } mContentPane.mTitle.showAsOpen(!isRolledUp); if (mResizeCorner != null) { mResizeCorner.setVisible(!isRolledUp); mResizeCorner2.setVisible(!isRolledUp); } updateWindowShadow(); if (mDockPrev != null) mDockPrev.repaintTitle(); if (mDockNext != null) mDockNext.repaintTitle(); /* if (mParent != null) mParent.updateRolledUpSize(this); if (mChild != null) mChild.updateRolledUpSize(this); */ /* if (mParent != null && mParent.isRolledUp()) mParent.setSizeAnimated(getWidth(), mParent.getHeight()); if (mChild != null && mChild.isRolledUp()) mChild.setSizeAnimated(getWidth(), mChild.getHeight()); */ } static class Interpolator { final float increment; private float current; public Interpolator(int steps, int start, int end, String name) { float delta = end - start; increment = delta / steps; this.current = start; if (false) System.out.println("Interpolate " + name + " from " + start + " to " + end + " in " + steps + " steps: " + "range=" + delta + " inc=" + increment); } public Interpolator(int steps, int start, int end) { this(steps, start, end, ""); } public int next() { current += increment; return (int) current; } } public void setWidth(int width) { setSize(width, getHeight()); } public void setHeight(int height) { setSize(getWidth(), height); } // private void XsetSizeAnimated(int width, int height) { // super.setSize(width, height); // } private void setShapeAnimated(int x, int y, int width, int height) { if (DEBUG.DOCK) out("setShapeAnimated"); // christ: JVM 1.5, on an old WIN2K box, animates a resize easily // 10 times faster than the mac final int steps = isWindows ? 16 : 4; final boolean moved = (x != getX() || y != getY()); final boolean resized = (width != getWidth() || height != getHeight()); Interpolator ix, iy, iw, ih; ix = iy = iw = ih = null; if (moved) { ix = new Interpolator(steps, getX(), x, "x"); iy = new Interpolator(steps, getY(), y, "y"); } if (resized) { iw = new Interpolator(steps, getWidth(), width, "width"); ih = new Interpolator(steps, getHeight(), height, "height"); } mAnimatingReshape = true; //setIgnoreRepaint(true); // works on W2K jvm 1.5 try { if (moved && resized) { for (int i = 1; i < steps; i++) setBounds(ix.next(), iy.next(), iw.next(), ih.next()); } else if (resized) { for (int i = 1; i < steps; i++) setBounds(x, y, iw.next(), ih.next()); } else if (moved) { for (int i = 1; i < steps; i++) setBounds(ix.next(), iy.next(), width, height); } setBounds(x, y, width, height); } finally { GUI.invokeAfterAWT(new Runnable() { public void run() { mAnimatingReshape = false; if (DEBUG.DOCK) out("setShapeAnimated: AWT eventQueue cleared, animating stopped."); // You might thinking calling the final setBounds in here would // be cleaner than the manual invalidate/validate but what you get // is a much messier result on screen. if (resized) { invalidate(); validate(); } } }); //setIgnoreRepaint(false); // can apparently never recover from this on Mac //repaint(); } if (DEBUG.DOCK) out("setShapeAnimated: returning"); } public boolean isRolledUp() { return isRolledUp; } private void dragToConstrained(int x, int y, boolean relaxed) { final Point p; /* // can't drag up into top dock region if we do this. if (y < GUI.GInsets.top) { // HARD CONSTRAINT: never above the top screen inset. // don't allow us above an OS menu bar on top of the screen, // such as on the Mac. We compenstate for this here // as it makes later constraint adjustments saner. y = GUI.GInsets.top; } */ if (relaxed) { if (DEBUG.EDGE) getConstrainedLocation(x, y); // for debug output p = new Point(x, y); } else p = getConstrainedLocation(x, y); // final Rectangle r = GUI.getMaximumWindowBounds(_win); // //Log.debug("MAX WIN BOUNDS: " + r); // if (p.y < r.y) // p.y = r.y; // else if (p.y > GUI.GScreenHeight - CollapsedHeightVisible) // p.y = GUI.GScreenHeight - CollapsedHeightVisible; // // // TODO: this is preventing us from moving a window to the top on a secondary screen // // if (p.y < GUI.GInsets.top) // // p.y = GUI.GInsets.top; // // else if (p.y > GUI.GScreenHeight - CollapsedHeightVisible) // // p.y = GUI.GScreenHeight - CollapsedHeightVisible; if (DEBUG.DOCK) out("dragToConstrained " + x + "," + y + ((p.x == x && p.y == y) ? "" : " = " + Util.out(p))); dragSetLocation(p.x, p.y); } private void superSetLocation(int x, int y) { if (DEBUG.DOCK && DEBUG.META) out("superSetLocation " + x + "," + y); _win.setLocation(x, y); mStickingRight = atScreenRight(); } public void dragSetLocation(int x, int y) { if (DEBUG.DOCK && DEBUG.META) out("dragSetLocation " + x + "," + y); superSetLocation(x, y); // SMF 2012-06-24: RE-ENABLED NON-COCOA MAC DRAGGING -- the performance is not // so much of an issue now, and the mac-hacks (cocoa-bridge) are not // available on current systems / going forward. //if (isMac == false && mChild != null) { if (mChild != null && !(isMac && Util.isMacCocoaSupported())) { // Manually move all of our children (and their children). This works // beauftifly smoothly on PC, and gets terribly behind on Mac unless we set // up the native OSX to handle it. updateAllChildLocations(); } } public void setLocation(int x, int y) { if (DEBUG.DOCK && DEBUG.META) out("setLocation " + x + "," + y); superSetLocation(x, y); updateAllChildLocations(); } private static class Edge { static final int LIP_LEFT = -1; static final int LIP_RIGHT = 1; static final int LIP_UP = -1; static final int LIP_DOWN = 1; final int axis; // x or y value final int min; // if axis is x, min y, if axis is y, min x final int max; // if axis is x, max y, if axis is y, max x private Edge lip; private int lipDir = 0; final boolean isLip; Edge(int axis, int oppositeAxisStart, int oppositeAxisEnd) { this.axis = axis; if (oppositeAxisStart < oppositeAxisEnd) { this.min = oppositeAxisStart; this.max = oppositeAxisEnd; } else { this.min = oppositeAxisEnd; this.max = oppositeAxisStart; } isLip = max - min < 2; } Edge(int axis) { this(axis, Integer.MIN_VALUE, Integer.MAX_VALUE); } boolean inRangeOf(Edge e) { if (false && isLip) return max > e.min && e.max > min && e.min >= min; else return max > e.min && e.max > min; } // currenly, lips only happen at the MIN value, which means left or top // of an edge. They're a catch along the OPPOSITE axis. void addLip(int direction) { this.lip = new Edge(min, min, min /*+ direction*/ ); this.lip.lipDir = direction; if (DEBUG.EDGE) System.out.println(this + "\n\tadded lip: " + lip); } public String toString() { String s = "[" + axis; if (min == Integer.MIN_VALUE) return s + "]"; else if (isLip || lipDir != 0) return s + " LIP " + min + "+" + lipDir + "]"; else return s + " len " + (max-min) + ":" + min + "-" + max + "]"; } } private static class EdgeBox { public Edge top, left, right, bottom; public final int width, height; public EdgeBox(final Component given) { if (given == null) { top = left = right = bottom = null; width = height = 0; } else { Component c = given; //if (c.getParent() instanceof JTabbedPane) //c = (JTabbedPane) c.getParent(); if (c.getParent() instanceof JViewport) c = (JViewport) c.getParent(); //if (c.getParent() instanceof JScrollPane) //c = (JScrollPane) c.getParent(); Point loc = new Point(c.getX(), c.getY()); if (c instanceof Window == false) SwingUtilities.convertPointToScreen(loc, c); int TOP = loc.y; int LEFT = loc.x; int RIGHT = LEFT + c.getWidth(); int BOTTOM = TOP + c.getHeight(); this.top = new Edge(TOP, LEFT, RIGHT); this.bottom = new Edge(BOTTOM, LEFT, RIGHT); this.left = new Edge(LEFT, TOP, BOTTOM); this.right = new Edge(RIGHT, TOP, BOTTOM); this.width = RIGHT - LEFT; this.height = BOTTOM - TOP; if (DEBUG.EDGE) System.out.println(this + " for " + GUI.name(c) + " parent=" + GUI.name(c.getParent()) + " from " + given ); } } public EdgeBox(Rectangle r) { int TOP = r.y; int LEFT = r.x; int RIGHT = LEFT + r.width; int BOTTOM = TOP + r.height; this.top = new Edge(TOP, LEFT, RIGHT); this.bottom = new Edge(BOTTOM, LEFT, RIGHT); this.left = new Edge(LEFT, TOP, BOTTOM); this.right = new Edge(RIGHT, TOP, BOTTOM); this.width = RIGHT - LEFT; this.height = BOTTOM - TOP; } public String toString() { return "EdgeBox[top" + top + " left" + left + " bottom" + bottom + " right" + right + "]"; } } private static class EdgeArray { final String name; final Edge[] array = new Edge[64]; int length = 0; EdgeArray(String name) { this.name = name; } void add(Edge e) { if (e != null) { if (DEBUG.EDGE && DEBUG.META) System.out.println("Added " + name + "\tedge" + e); array[length++] = e; } } void add(int i) { add(new Edge(i)); } void reset() { length = 0; } } private static void addEdges(EdgeBox b, DockWindow dw) { //if (DEBUG.DOCK) System.out.println("addEdges " + e); StickyLeftEdges.add(b.right); StickyRightEdges.add(b.left); // Don't add top or bottom edges of mid-stack DockWindow's. // That is, ignore the middle of the stack. // This would also ignore the DockWindow parent we just pulled // away from if we were stacked, as it appears as if its bottom // edge is in the "middle", which is why we check mMouseWasPressed // on the child. // TODO-FYI: currently double impl: dw may always be null: see addEdges(DockWindow) if (dw == null || dw.mChild == null || dw.getWidth() != dw.mChild.getWidth() || dw.mChild.mMouseWasPressed) StickyTopEdges.add(b.bottom); if (dw == null || dw.mParent == null || dw.getWidth() != dw.mParent.getWidth()) StickyBottomEdges.add(b.top); // add a "lip" at the top and bottom LEFT edge for anything sliding along the top // or bottom of a window to catch on just before it goes beyond the left edgb. // We don't do this for the right edge. if (b.top != null) //e.top.addLip(b.left.axis); StickyLeftEdges.add(new Edge(b.left.axis, b.top.axis, b.top.axis - 1)); if (b.bottom != null) StickyLeftEdges.add(new Edge(b.left.axis, b.bottom.axis, b.bottom.axis + 1)); // also add lips to left and right at the top for top alignment if (b.top != null) { if (false) { StickyTopEdges.add(new Edge(b.top.axis, b.left.axis, b.left.axis - 1)); StickyTopEdges.add(new Edge(b.top.axis, b.right.axis, b.right.axis + 1)); } else { b.left.addLip(Edge.LIP_LEFT); b.right.addLip(Edge.LIP_RIGHT); } } } private static void addEdges(Component c) { if (c != null) addEdges(new EdgeBox(c), null); } private static void addEdges(DockWindow dw) { if (false) { // get rid of all inter-stack edges, but keep inter-stack outside lips addEdges(new EdgeBox(dw._win), dw); } else { // get rid of all inter-stack edges, including outside lips EdgeBox edges = new EdgeBox(dw._win); if (dw.mParent != null) edges.top = null; // don't add our bottom if we have a child, unless it's about to be the mover // Also, would nice if dragging a wide top window with a narrow // child window (e.g., current tester Font on top of tester Link), // that the wider Font also adds it's bottom for sticking. if (dw.mChild != null && !dw.mChild.mMouseWasPressed) edges.bottom = null; addEdges(edges, null); } } /* static void setAllOnTop(boolean onTop) { Iterator i = sAllWindows.iterator(); while (i.hasNext()) { DockWindow dw = (DockWindow) i.next(); dw.setAlwaysOnTop(onTop); dw.toFront(); } } */ public static void raiseAll() { if (DEBUG.DOCK||DEBUG.WORK) Log.debug("raiseAll"); for (DockWindow dw : AllWindows) dw.toFront(); } private static void addScreenEdges(Screen screen) { if (DEBUG.EDGE) Log.debug("ADDING SCREEN " + screen); final int left; if (screen.margin.left <= 4) { left = screen.left; // ignore Mac OS X hidden dock margin } else { left = screen.left + screen.margin.left; } StickyTopEdges.add(new Edge(screen.top + screen.margin.top, screen.left, screen.right)); StickyLeftEdges.add(new Edge(left, screen.top, screen.bottom)); StickyRightEdges.add(new Edge(screen.right, screen.top, screen.bottom)); StickyBottomEdges.add(new Edge(screen.bottom - screen.margin.bottom, screen.left, screen.right)); } private Screen GS; private Screen refreshGS() { return (this.GS = GUI.getScreenForWindow(_win)); } private static void refreshScreenEdges(DockWindow mover) { Screen[] screens = GUI.getAllScreens(); StickyLeftEdges.reset(); StickyRightEdges.reset(); StickyTopEdges.reset(); StickyBottomEdges.reset(); for (Screen s : screens) addScreenEdges(s); if (mover == null) return; addEdges(VUE.getMainWindow()); //addEdges(VUE.getActiveViewer()); for (DockWindow dw : AllWindows) { //if (dw == mover || !dw.isVisible() || dw.inSameStack(mover)) if (dw == mover || !dw.isVisible() || mover.hasDescendant(dw)) continue; addEdges(dw); } } // private static void refreshScreenInfo(DockWindow mover) // { // GUI.reloadGraphicsInfo(); // StickyLeftEdges.reset(); // StickyRightEdges.reset(); // StickyTopEdges.reset(); // StickyBottomEdges.reset(); // if (GUI.GInsets.left <= 4) { // StickyLeftEdges.add(0); // } else { // StickyLeftEdges.add(GUI.GInsets.left); // } // StickyRightEdges.add(GUI.GScreenWidth); // StickyBottomEdges.add(GUI.GScreenHeight - GUI.GInsets.bottom); // StickyRightEdges.add(0); // in case multi-monitor // StickyLeftEdges.add(GUI.GScreenWidth); // in case multi-monitor // // todo: add bottom edge of other displays in case resolution // // is different: should really only take effect if on that // // screen tho, not across entire virtual desktop. // //if (GUI.GInsets.bottom > 0) // // StickyBottomEdges.add(GUI.GScreenHeight - GUI.GInsets.bottom); // if (MainDock != null) { // if (MainDock.mGravity == DockRegion.BOTTOM) // StickyBottomEdges.add(MainDock.getY()); // else // StickyTopEdges.add(MainDock.getY()); // } // if (TopDock != null) { // TopDock.moveToY(GUI.GInsets.top); // BottomDock.moveToY(GUI.GScreenHeight - GUI.GInsets.bottom); // } // if (mover == null) // return; // addEdges(VUE.getMainWindow()); // //addEdges(VUE.getActiveViewer()); // for (DockWindow dw : AllWindows) { // //if (dw == mover || !dw.isVisible() || dw.inSameStack(mover)) // if (dw == mover || !dw.isVisible() || mover.hasDescendant(dw)) // continue; // addEdges(dw); // } // } private DockWindow getStackTop() { if (mParent == null) return this; else return mParent.getStackTop(); } private DockWindow getStackBottom() { if (mChild == null) return this; else return mChild.getStackBottom(); } public boolean isStackTop() { return mParent == null && mChild != null; } public boolean isStacked() { return mChild != null || mParent != null; } public boolean isDocked() { return mDockRegion != null; } public boolean inSameStack(DockWindow dw) { DockWindow ourTop = getStackTop(); return ourTop != null && ourTop == dw.getStackTop(); } public boolean hasDescendant(DockWindow child) { if (mChild == null) return false; else if (mChild == child) { if (DEBUG.DOCK) out("found descendant " + child); return true; } else return mChild.hasDescendant(child); } /** @return total stack height BELOW us, or just our height if no children under us */ public int getStackHeight() { if (mChild == null) return getVisibleHeight(); else return getVisibleHeight() + mChild.getStackHeight(); } private static final int StickyDistance = 15; /** columns sticky on their right side (window left edges stick to them) */ // the names are reversed so they make sense when read from the perpective of // Window edges, as opposed to screen edges. private static EdgeArray StickyLeftEdges = new EdgeArray("RIGHT"); private static EdgeArray StickyRightEdges = new EdgeArray("LEFT"); private static EdgeArray StickyTopEdges = new EdgeArray("BOTTOM"); private static EdgeArray StickyBottomEdges = new EdgeArray("TOP"); private Point getConstrainedLocation(int x, int y) { Rectangle movingBounds = getBounds(); movingBounds.x = x; movingBounds.y = y; if (mMovingStackHeight > 0) movingBounds.height = mMovingStackHeight; // out("movingBounds " + Util.out(movingBounds)); // moving bounds is now the would-be bounds of what's // moving (a DockWindow or a DockWindow stack) if we // didn't constrain the movement at all. EdgeBox movingBox = new EdgeBox(movingBounds); return getConstrainedXY(x, y, movingBox); //return new Point(getConstrainedX(x, movingBox), getConstrainedY(y, movingBox)); } private static boolean computeClosestEdge(Edge movingEdge, EdgeArray stickyEdges, EdgeHit result) { boolean hitResult = false; for (int i = 0; i < stickyEdges.length; i++) { hitResult |= mergeWithResult(stickyEdges.array[i], movingEdge, result); /* Edge edge = stickyEdges.array[i]; if (edge.axis < 0) continue; if (!edge.inRangeOf(movingEdge)) continue; int rawDelta = edge.axis - movingEdge.axis; int delta = Math.abs(rawDelta); if (delta < result.delta) { result.rawDelta = rawDelta; result.delta = delta; result.edge = edge; hitResult = true; } */ } return hitResult; } /** @return true if changed result */ private static boolean mergeWithResult(Edge edge, Edge movingEdge, EdgeHit result) { if (edge.axis < 0) return false; if (!edge.inRangeOf(movingEdge)) return false; int rawDelta = edge.axis - movingEdge.axis; int delta = Math.abs(rawDelta); if (delta < result.delta) { result.rawDelta = rawDelta; result.delta = delta; result.edge = edge; return true; } return false; } private static class EdgeHit { int rawDelta = 0; /** smallest delta found (absolute value of rawDelta) */ int delta = Integer.MAX_VALUE; /** the closest edge (had smallest delta) */ Edge edge; void reset() { rawDelta = 0; delta = Integer.MAX_VALUE; edge = null; } public String toString() { return " delta " + rawDelta + " from " + edge; } } private Point getConstrainedXY(int x, int y, EdgeBox movingBox) { final EdgeHit Xresult = new EdgeHit(); final EdgeHit Yresult = new EdgeHit(); // COMPUTE X computeClosestEdge(movingBox.left, StickyLeftEdges, Xresult); boolean matchedRight = computeClosestEdge(movingBox.right, StickyRightEdges, Xresult); if (DEBUG.EDGE) { Edge movingEdge = matchedRight ? movingBox.right : movingBox.left; out("X " + movingEdge + " sees " + (matchedRight?"RIGHT":"LEFT") + Xresult); } // COMPUTE Y computeClosestEdge(movingBox.top, StickyTopEdges, Yresult); boolean matchedBottom = computeClosestEdge(movingBox.bottom, StickyBottomEdges, Yresult); if (DEBUG.EDGE) { Edge movingEdge = matchedBottom ? movingBox.bottom : movingBox.top; out("Y " + movingEdge + " sees " + (matchedBottom?"BOTTOM":"TOP") + Yresult); } // ADJUST RESULTS FOR LIPS if (Xresult.delta < StickyDistance) { if (matchedRight) x = Xresult.edge.axis - movingBox.width; else x = Xresult.edge.axis; Edge lip = Xresult.edge.lip; // lips only at top and left right now if (lip != null) { //computeClosestEdge(movingBox.top, new Edge[] { lip }, Yresult); } } if (Yresult.delta < StickyDistance) { if (matchedBottom) y = Yresult.edge.axis - movingBox.height; else y = Yresult.edge.axis; } return new Point(x, y); } /* private int getConstrainedX(int x, EdgeBox movingBox) { final EdgeHit result = new EdgeHit(); computeClosestEdge(movingBox.left, StickyLeftEdges, result); boolean matchedRight = computeClosestEdge(movingBox.right, StickyRightEdges, result); if (DEBUG.DOCK) { Edge movingEdge = matchedRight ? movingBox.right : movingBox.left; out("X " + movingEdge + " sees " + (matchedRight?"RIGHT":"LEFT") + result); } if (result.delta < StickyDistance) { if (matchedRight) return result.edge.axis - movingBox.width; else return result.edge.axis; } else return x; } private int getConstrainedY(int y, EdgeBox movingBox) { final EdgeHit result = new EdgeHit(); computeClosestEdge(movingBox.top, StickyTopEdges, result); boolean matchedBottom = computeClosestEdge(movingBox.bottom, StickyBottomEdges, result); if (DEBUG.DOCK) { Edge movingEdge = matchedBottom ? movingBox.bottom : movingBox.top; out("Y " + movingEdge + " sees " + (matchedBottom?"BOTTOM":"TOP") + result); } if (result.delta < StickyDistance) { if (matchedBottom) return result.edge.axis - movingBox.height; else return result.edge.axis; } else return y; } */ /* private int getStickyAdjustment(int actual, int stickyAxis, EdgeHit hit) { if (true) return stickyAxis; if (hit.delta > 5) { float mag = hit.mag; float delta = hit.delta; float maxDist = StickyDistance; float ratioA = delta / maxDist; float ratioB = ratioA * ratioA; float adj = mag - mag * ratioB; //int adj = (int) ((float)hit.mag / ((float)hit.delta / (float)StickyDistance)); out("raw adj " + hit.mag + " ratioA " + ratioA + " ratioB " + ratioB + " soft adj " + adj); return actual + (int) adj; } else { return actual + hit.mag; } } */ /* public void setLocationAnimated(int x, int y) { final int steps = 40; Interpolator ix = new Interpolator(steps, getX(), x, "x"); Interpolator iy = new Interpolator(steps, getY(), y, "y"); for (int i = 1; i < steps; i++) placeWindow(ix.next(), iy.next()); placeWindow(x, y); } */ private DockWindow getNearWindow(int localMouseX, int localMouseY) { int winX = getX(); int winY = getY(); // convert from window to screen mouse coords int mouseX = localMouseX + winX; int mouseY = localMouseY + winY; for (DockWindow dw : AllWindows) { if (!dw.isVisible() || dw == this) continue; // only dock like windows together for now if (isToolbar != dw.isToolbar) continue; // For now, only find a near window if the DockWindow // is exactly along the bottom of the desired parent, // presumably due to stickiness. int left = dw.getX(); int right = dw.getX() + dw.getWidth(); int bottom = dw.getY() + dw.getHeight(); int winRight = winX + getWidth(); if (winY == bottom && ((winX >= left && winX < right) || (winRight > left && winRight < right))) return dw; /* * This is too agressive: from now only, ONLY attach if the window is * already just below the parent. * Rectangle bounds = dw.getBounds(); if (bounds.contains(mouseX, mouseY)) { if (hasDescendant(dw)) { // This can happen if stickiness has moved as stack up, // and the mouse is now over a child of the mover. ; // -- don't allow attachment } else { return dw; } } // if our window is also sitting just below the // other window (probably via stickyness), also // attach to it. bounds.height++; if (bounds.contains(winX, winY) || bounds.contains(winX + getWidth()-1, winY)) return dw; */ } return null; } protected boolean titleBarHit(MouseEvent e) { return e.getY() < CollapsedHeightVisible || (SidewaysRollup && mContentPane.mTitle.contains(e.getX(), e.getY())); } /** interface FocusManager.MouseInterceptor impl */ public boolean interceptMousePress(MouseEvent e) { // okay, no good: if, say, this press opens a menu, the child DockWindow // is being raised over the menu when we're in setAlwaysOnTop mode! // However, we're still stuck: clicking most anywhere in window has mac send the // OSX window some kind of message to raise itself (hey -- maybe we can turn // that off?) -- so turning this off means we see our menus as we need, but now // children are going under parent DockWindows when shadow is on. // TODO: okay, maybe stay with brushed metal in 1.5 for it's nice look and nice // look not having a shadow, and when rolled up, swap in a whole new freakin // WINDOW that is override-redirected so it can be small? Of course, this still // forces us into java 1.5. // FYI, we need this on the PC: Windows has no special OS window raising code. // (It has no native window shadow's, so it doesn't need any) // It's okay to do this in java 1.4, as we can't be alwaysOnTop, which // is why this is a problem. // 2008-04-21 SMF: On Mac Leopard apparently windows are no longer automatically // raised when they get focus / are click on, so we also always do it in that case. // 2008-04-21 SMF: We always auto-raise on any mouse click when on Windows now // too. Toolbars must not be focusable or they can steal key events and the // user doesn't know where the input is going. This is why all toolbars are // setFocusableWindowState(false). This creates problems elsewhere tho: windows // won't always auto-raise when clicked on if they don't have focusable window // state. E.g., the Format toolbar raises just fine when when clicked on and not // focusable, but for some reason, the FloatingZoomPanel does not. // 2008-04-21 SMF: Oh, and now that all toolbars are not focusable, we want to // do this for all mac platforms, not just Leopard, tho note that this code is // MUCH more important for Leopard than it is for Tiger. if (!Util.isUnixPlatform()) raiseStack(); // if (Util.isMacLeopard() // || Util.isWindowsPlatform() // || (MacWindowShadowEnabled && (!GUI.UseAlwaysOnTop || !isMac))) // { // raiseStack(); // } // TODO: this doesn't handle any re-ordering of AllWindows that was as a result // of raising any children stacked below us. if (AllWindows.getLast() != this) { // Maintain stacking order: last one in list will be last to show/toFront, // so will be on top. // TODO: safer: simply added an index we can increment every time for // the top window based on a global static count, then sort // based on the index for traversals that need to pay addention to order. synchronized (AllWindows) { AllWindows.remove(this); AllWindows.addLast(this); } } return false; } private void raiseStack() { if (DEBUG.DOCK) out("toFront my stack"); toFront(); raiseChildren(); } public void mousePressed(MouseEvent e) { mMouseWasPressed = true; if (DEBUG.MOUSE && DEBUG.META) out(e); if (e.getSource().getClass() == ResizeCorner.class) { if (!isRolledUp()) mDragSizeStart = getSize(); // we'll be resizing the window e = SwingUtilities.convertMouseEvent(e.getComponent(), e, _win); } // update screen size, insets, etc for window dragging constraints. refreshGS(); refreshScreenEdges(this); mDragStart = e.getPoint(); mDragStartScreen = e.getPoint(); mDragStartScreen.translate(getX(), getY()); mStickingRight = atScreenRight(); mWasStickingRight = mStickingRight; if (DEBUG.MOUSE) out("mDragStartScreen=" + mDragStartScreen + " mDragStart=" + mDragStart); if (false && isMac) { // don't shadow child // better: don't raise us up if we're rolling up, but then would // have to do this on mouseReleased // TODO: is this getting repeated after interceptMousePress? raiseStack(); } } public void mouseDragged(MouseEvent e) { if (mDragStart == null) { out("mouseDragged with no mousePressed (mDragStart is null)"); return; } if (e.getSource().getClass() == ResizeCorner.class) e = SwingUtilities.convertMouseEvent(e.getComponent(), e, _win); mMouseWasDragged = true; if (mDragSizeStart != null) { dragResizeWindow(e); } else { dragMoveWindow(e); } } private void dragResizeWindow(MouseEvent e) { mWindowDragUnderway = true; int newWidth = mDragSizeStart.width + (e.getX() - mDragStart.x); int newHeight = mDragSizeStart.height + (e.getY() - mDragStart.y); newWidth = minUnrolledWidth(newWidth); newHeight = minUnrolledHeight(newHeight); mContentPane.getWidget().invalidate(); // in case is depending on size of DockWindow setSize(newWidth, newHeight); isMaximized = false; } private void dragMoveWindow(MouseEvent e) { if (mWindowDragUnderway == false) { if (!dragStartWindowMove(e)) return; } int x = e.getX() + getX() - mDragStart.x; int y = e.getY() + getY() - mDragStart.y; boolean relaxed = e.isShiftDown(); //-------------------------------------------- // TODO: PROBLEM: why can DockWindows now go up under the mac menu bar? // This is a hack for now: if (isMac) { if (GUI.hasMultipleScreens()) { //if (GUI.getAllScreenBounds().x if (false && y < 22) // can never have a screen above otherwise y = 22; } else if (y < 22) { y = 22; } } //-------------------------------------------- dragToConstrained(x, y, relaxed); } /** @return true if the drag has actually started */ private boolean dragStartWindowMove(MouseEvent e) { //--------------------------------- // We're just starting the drag //--------------------------------- Point screen = e.getPoint(); screen.translate(getX(), getY()); int dx = screen.x - mDragStartScreen.x; int dy = screen.y - mDragStartScreen.y; if (Math.abs(dx) < 5 && Math.abs(dy) < 5) { if (DEBUG.MOUSE) out("delaying drag start with dx="+dx + " dy="+dy + " screen=" + screen); return false; } if (isMac) { // Make sure we're all on top, otherwise can get wierd effects such as free-floating // DockWindow's "slicing" in between our stack windows if it happens to have a MacOSX // z-order in between two of our children. // Children can go behind VUE window as soon as mouse goes down, which should // be impossible... This is because of our MacOSX child window associations during // drag. So when a stack as dropped, we always raise it after the windows // are MacOSX dissasociated. // We need to raise them again here, because if some OTHER DockWindow was MacOSX // "activated" (clicked on), it's z-order may just happen to wind up smack in the // middle of our stack, which will show up as slicing thru our stack during the drag. raiseStack(); } mWindowDragUnderway = true; repaintTitle(); if (isStackTop()) mMovingStackHeight = getStackHeight(); else mMovingStackHeight = 0; //if (isMacAqua) MacOSX.setAlpha(this, 0.75f); if (e.isShiftDown()) { // remove from any stack it's in and drag free if (mParent != null) mParent.removeChild(); if (mChild != null) setChild(null); } else { if (isMac) { // window drag begins: attach (via OSX) all children to the parent // being dragged if we're on the mac attachChildrenForMoving(this); if (e.isAltDown()) attachSiblingsForMoving(); } } return true; } // TODO: okay, if we get a damn click-count in here and as long as drag start was still // delayed, do mouseClicked roll-up handling in here instead of mouseClicked -- too easy // to miss clicks when mouse is moving too fast (prob mainly a trackpad problem) public void mouseReleased(MouseEvent e) { if (DEBUG.MOUSE && DEBUG.META) out(e); if (e.getSource().getClass() == ResizeCorner.class) // should never be needed, but just in case e = SwingUtilities.convertMouseEvent(mResizeCorner, e, _win); if (mWindowDragUnderway) dropWindow(e); else if (!isToolbar) handleMouseClicked(e); // treat a delayed drag start as a click mDragStart = null; mDragSizeStart = null; mMouseWasDragged = false; mWindowDragUnderway = false; mMouseWasPressed = false; if (mWasStickingRight != mStickingRight) updateAllChildLocations(); } private void handleMouseClicked(MouseEvent e) { if (DEBUG.MOUSE) out("handleMouseClicked: " + GUI.name(e)); if (e.getSource().getClass() == ResizeCorner.class) return; //On windows the click count kept growing if i I didn't move the mouse slightly between clicks so I put //an extra test in here for clicks > 2 % 2 to test for additional double clicks seems to work //well on windows i'll double check with mac. [now handled in GUI.isDoubleClick] if (GUI.isDoubleClick(e) && titleBarHit(e)) { if (DEBUG.Enabled) { // TODO: handle all the smarts in setRolledUp(false) // when transitioning from rolled-up directly to maximized if (GUI.noModifierKeysDown(e)) { setRolledUp(!isRolledUp()); } else setMaximizeVertical(e.isShiftDown()); } else { if (isRolledUp()) { setRolledUp(false); } else if (GUI.noModifierKeysDown(e)) { setRolledUp(true); } else setMaximizeVertical(e.isShiftDown()); } } else { // if we click on the bottom pixels when we're at the screen bottom, // treat it as a roll-up request // So this can always work, we enforce at least a 1 pixel empty bottom // border around the content pane for any tool component that has no // internal border of it's own (such as the current MapPanner). if (e.getY() > getHeight() - 5 && !isRolledUp() /*&& atScreenBottom() */ ) { setRolledUp(true); } } } /* private int getDockRegionRolledWidth() { DockWindow rightSibling = getSiblingToRight(); if (rightSibling == null) return getWidth(); else return rightSibling.getX() - getX(); } private DockWindow getSiblingToRight() { Iterator i = sAllWindows.iterator(); DockWindow closest = null; int minDeltaX = Integer.MAX_VALUE; int myTop = getY(); int myLeft = getX(); while (i.hasNext()) { DockWindow dw = (DockWindow) i.next(); if (!dw.isVisible()) continue; if (dw.getY() == myTop && dw.getX() > myLeft) { int deltaX = dw.getX() - myLeft; if (deltaX < minDeltaX) { minDeltaX = deltaX; closest = dw; } } } return closest; } */ /** @param MouseEvent should be the MOUSE_RELEASED event where the window was dropped*/ private void dropWindow(MouseEvent e) { if (DEBUG.DOCK) out("dropWindow: curBounds " + getBounds()); //if (isMacAqua) MacOSX.setAlpha(this, 1f); GUI.refreshGraphicsInfo(); DockWindow near; DockRegion dockRegion = DockRegion.findRegion(_win); detachSiblingsForMoving(); if (dockRegion != null) { near = null; } else if (e.isShiftDown()) { // don't allow attachment if relaxed movement near = null; } else { near = getNearWindow(e.getX(), e.getY()); // don't attach a child below something that's already // at the bottom of the screen! if (near != null && near.atScreenBottom()) near = null; } if (DEBUG.DOCK) out("dropWindow: near " + near + ", region " + dockRegion); // if (isMac) detachChildrenForMoving(this); if (near != null) { if (hasDescendant(near)) { Log.error("can't drop a parent onto a descendant: " + near); } else { near.setChild(this); } } else if (mParent != null) { // If we had a parent, detach from it. mParent.removeChild(); } /* if (dockRegion != null) { dockRegion.assignMembers(); } else if (mDockRegion != null) { mDockRegion.remove(this); } */ DockRegion.assignAllMembers(); if (isMac) { // do this as late as possible or sometimes the setLocation from updateChild is // actually *failing* (no error or exception) for the child of a window that was // just reparented. Must be related to using Mac NSWindow code w/java. // TODO: Christ, but doing it down here causes toFront() to freakin fail! Okay, is // possible to solve by interjecting this into setChild to do after the // setLocation's, but before the raises. failure case: drag bottom two windows up // to top in stack, "inserting" them there. Second window will be on top of it's // child, the third window, yet they were all toFront'ed in proper order. // Very rarely, still seeing a window (child of moved usually?) get the wrong // location after a reparenting -- this I'm guessing is due to some location data // in java not being up to date w/mac -- notice that we get no setLocation events // on children that are moving while their parent is being moved -- maybe we call // setLocation just for good measure? always or at end? oh, i think at end is // where it's happening. Or hey, maybe *that* setLocation is screwing us up, // because java thinks it needs to be moved when it really doesn't? detachChildrenForMoving(this); // This is CRUCIAL to restore z-ordering based on proper java window parentage raiseStack(); } updateWindowShadow(); repaintTitle(); } void assignDockRegion(DockRegion region) { // if (mDockRegion != region) { // mDockRegion = region; // if (DEBUG.DOCK) out("assignDockRegion: " + region); // // if we just dragged an unrolled window to the bottom with // // just the title bar showing (we dragged the visible part off // // the bottom of the screen), auto roll-up the DockWindow // // so when it's clicked on again it will roll upwards. // if (region == BottomDock && !isRolledUp() && getY() + getHeight() > GUI.GScreenHeight) // setRolledUp(true); // } } void assignDockSiblings(DockWindow prev, DockWindow next) { boolean changed = false; if (mDockPrev != prev) { mDockPrev = prev; changed = true; } if (mDockNext != next) { mDockNext = next; changed = true; } if (changed) repaintTitle(); } private void repaintTitle() { //if (DEBUG.DOCK) out("repaintTitle"); mContentPane.mTitle.repaint(); } public void mouseClicked(MouseEvent e) {} public void mouseMoved(MouseEvent e) {} public void mouseEntered(MouseEvent e) { // We're trying this to make sure any rollover's in the DockWindow will always work. _win.requestFocus(); } public void mouseExited(MouseEvent e) {} private void updateWindowShadow() { if (isMac && Util.isMacCocoaSupported() &&!VUE.isApplet()) { if (!MacWindowShadowEnabled) { setWindowShadow(false); return; } if (DEBUG.DOCK) out("updateWindowShadow: docked=" + isDocked() + " rolled=" + isRolledUp()); boolean hideShadow = (isToolbar /*&& isDarkTitleBar*/) || (isDocked() && isRolledUp() || (MainDock != null && MainDock.getY() == getY() + getHeight())) ; setWindowShadow(!hideShadow); } } private void setWindowShadow(boolean shadow) { if (mHasWindowShadow != shadow && isDisplayable()) { mHasWindowShadow = shadow; if (DEBUG.DOCK) out("setShadow " + shadow); try { MacOSX.setShadow(_win, shadow); } catch (Throwable t) { t.printStackTrace(); } } } private void removeChild() { if (mChild != null) { if (DEBUG.DOCK) out("removeChild " + mChild); //tufts.macosx.Screen.removeChildWindow(this, mChild); mChild.setParent(null); mChild = null; updateWindowShadow(); } } /** set the child of this DockWindow to the given child */ public void setChild(DockWindow newChild) { setChild(newChild, true); } /** add a child to the bottom of this stack * @return "this" DockWindow, for chaining calls to addChild */ public DockWindow addChild(DockWindow newChild) { getStackBottom().setChild(newChild, true); return this; } private void setChild(DockWindow newChild, boolean updateChildren) { if (DEBUG.DOCK) out("setChild " + newChild); if (newChild == null) { removeChild(); return; } if (newChild == mParent) throw new Error(this + " cannot setChild on parent " + mParent); if (newChild.mParent != null && newChild.mParent != this) { //tufts.macosx.Screen.removeChildWindow(newChild.mParent, newChild); newChild.mParent.removeChild(); } if (mChild == newChild) { // it's already our child if (newChild.mParent != this) throw new Error("DockWindow child/parent inconsistency"); if (isMac) updateChildLocation(); else updateAllChildLocations(); } else { boolean reparented = false; if (mChild != null) { if (mChild.mShowing) { // if we already have a child, and it's not in the process // of going invisible, set it as child of our new child, // but don't let it move it's children yet, as it's // new parent isn't in place yet. reparented = true; newChild.setChild(mChild, false); } else { removeChild(); } } if (hasDescendant(newChild)) { Util.printStackTrace("is already descendent of " + this + " " + newChild); return; } mChild = newChild; mChild.setParent(this); //tufts.macosx.Screen.addChildWindow(getTopParent(), mChild); if (updateChildren) { if (isMac) { // If on mac, child window's of the window just dropped on us // are "attached" to it, and we only need to set the location // of this new immediate child to set all their locations. // And in fact, if we *do* attempt to set the location, // occasionaly java & the underlying NSWindow in Objective-C // code aren't quite in sync yet, and the window can get // placed wrong. if (reparented) updateAllChildLocations(); else updateChildLocation(); raiseChildren(); // prevent parent shadowing child } else { updateAllChildLocations(); } } if (!newChild.isRolledUp()) newChild.keepOnScreen(); } updateWindowShadow(); } private void setParent(DockWindow parent) { if (mParent == parent) return; /* // This doesn't help window shadow at all... if (isMac) { if (mParent != null) MacOSX.removeChildWindow(mParent, this); if (parent != null) { MacOSX.addChildWindow(parent, this); MacOSX.orderAbove(parent, this); } } */ mParent = parent; //mContentPane.setCloseButtonVisible(parent == null || !parent.getStackTop().isStackOwner); mContentPane.setCloseButtonVisible(!isStacked() || isStackTop()); } /** recursively move all children under us, using given height & y coord instead of current height & y coord */ private void updateAllChildLocations(int newHeight, int newY) { updateChildLocation(true, newHeight, newY); } /** recursively move all children under us */ private void updateAllChildLocations() { updateChildLocation(true, getHeight(), getY()); } /** move just the first child under us -- used when on the mac and the windows are "attached" */ private void updateChildLocation() { updateChildLocation(false, getHeight(), getY()); } // if last in chain and is Aqua Brushed Metal, could move UP to obscure the extra // window size, tho then we'd have to set it's title to BorderLayout.SOUTH, (easy) // and more problematically, keep it stacked BELOW it's parent, which would be fine // as long as the parent was open, but as soon as it rolled up, we'd have the // problem again. /** * Set location of an attached child (if we have one) based on * our current location and size. * * We pass in height & y value arguments as we may call this routine just *before* * we (the parent) resizes or repositions, to move the child before we do, as a * method of preventing whatever is on the desktop under the stack from briefly * flashing through as the stack adjusts. * * This method recursively call's itself on any children. * * @param allChildren - recursively update all children, otherwise just the immediate child * @param newParentHeight - our height or the one we may be about to take on * @param newParentY - our Y location or the value it may be about to take on * */ private void updateChildLocation(boolean allChildren, int upcomingHeight, int upcomingY) { try { _updateChildLocation(allChildren, upcomingHeight, upcomingY); } catch (LoopError e) { Util.printStackTrace(e); } } private int StackDepth = -1; private void _updateChildLocation(boolean allChildren, int upcomingHeight, int upcomingY) { if (StackDepth-1 > AllWindows.size()) throw new LoopError("updateChildLocation"); StackDepth++; if (mChild != null) { if (DEBUG.DOCK) out("updateChildLocation, child=" + mChild + " soonH=" + upcomingHeight + " soonY=" + upcomingY + " all=" + allChildren); int x; int y = upcomingY + upcomingHeight; final Screen screen = refreshGS(); if (mStickingRight) { x = screen.right - mChild.getWidth(); mChild.mStickingRight = true; } else { x = getX(); mChild.mStickingRight = false; } if (CollapsedHeight != CollapsedHeightVisible) { // see getVisibleHeight for repeat of this logic if (isRolledUp()) y -= (CollapsedHeight - CollapsedHeightVisible); else y--; } // To prevent background desktop from flashing thru: If the stack is moving // down, move us down first, then our child. If the stack is moving up, // pull up the child first, then move us up. if (y >= mChild.getY()) { mChild.superSetLocation(x, y); if (allChildren) mChild.updateAllChildLocations(); // recurse down the chain } else { if (allChildren) mChild.updateAllChildLocations(mChild.getHeight(), y); // recurse down the chain mChild.superSetLocation(x, y); } } StackDepth--; } /** @return visible height -- adjusts for stacked windows below us that may be slightly overlapping us */ public int getVisibleHeight() { if (mChild != null && CollapsedHeight != CollapsedHeightVisible) { if (isRolledUp()) return getHeight() - (CollapsedHeight - CollapsedHeightVisible); else return getHeight() - 1; } else return getHeight(); } private class LoopError extends Error { LoopError(String where) { super("StackDepth=" + StackDepth + " #DockWindows=" + AllWindows.size() + "\n" + where + " DockWindow" + DockWindow.this + "\n\tparent=" + mParent + "\n\t child=" + mChild ); } } // *still* occasionally seeing the bottom window in stack go under it's // parent -- not consistently, but sometimes when switching focus to // the MapViewer private void raiseChildren() { //GUI.invokeAfterAWT(new Runnable() { public void run() { _raiseChildren(); }}); // if we *always* do this, it happens too slowly sometimes & causes flashing try { _raiseChildren(); } catch (LoopError e) { Util.printStackTrace(e); } } /** recursive down children */ private void _raiseChildren() { if (StackDepth-1 > AllWindows.size()) throw new LoopError("raiseChildren"); StackDepth++; if (mChild != null) { mChild.toFront(); mChild._raiseChildren(); } if (isMac && MacWindowShadowEnabled) GUI.invokeAfterAWT(new Runnable() { public void run() { updateWindowShadow(); }}); StackDepth--; } // either move to DockRegion, or make this attach only // everyone who's to our right... private void attachSiblingsForMoving() { if (isMac && isDocked()) { Iterator i = mDockRegion.getDockedWindows().iterator(); while (i.hasNext()) { DockWindow dw = (DockWindow) i.next(); if (dw != this) { if (DEBUG.DOCK) out("attaching sibling " + dw); MacOSX.addChildWindow(_win, dw._win); } } } } private void detachSiblingsForMoving() { if (isMac && isDocked()) { Iterator i = mDockRegion.getDockedWindows().iterator(); while (i.hasNext()) { DockWindow dw = (DockWindow) i.next(); if (dw != this) { if (DEBUG.DOCK) out("detaching sibling " + dw); MacOSX.removeChildWindow(_win, dw._win); //dw.toFront(); } } // prevent from going behind main after detachment i = mDockRegion.getDockedWindows().iterator(); while (i.hasNext()) { DockWindow dw = (DockWindow) i.next(); dw.toFront(); } } } /* * There are several constraints with the Mac parent/child window relationships. * The biggest is that apparently if the parent/child links go more then three deep, * (three windows), the fourth in the chain gets left out of move's by the parent. * If this weren't the case, we'd keep all the windows in a stack attach all the * time. Since it isn't, it's easist just to create a temporary set of children, * all parented to the top-most window in the stack at the start of moves, and clear * it at the end of the move. (Actually, we could optimize and only clear it if * they pull out a child at some point -- then we'd have less of the minor toFront() * flashing we see to keep the shadow's of the parents obscuring the children, which * we don't see when a child is attached to it's parent -- tho with them all chained * to the TOP window, this would only actually fix the first window under the top). */ private void attachChildrenForMoving(DockWindow topOfWindowStack) { if (isMac && mChild != null) { MacOSX.addChildWindow(topOfWindowStack._win, mChild._win); mChild.attachChildrenForMoving(topOfWindowStack); } } private void detachChildrenForMoving(final DockWindow topOfWindowStack) { if (isMac && mChild != null) { //GUI.invokeAfterAWT(new Runnable() { public void run() { MacOSX.removeChildWindow(topOfWindowStack._win, mChild._win); //if (MacWindowShadowEnabled) // BE SURE TO RESTORE Z-ORDER OVER PROPER JAVA PARENT // (also keeps on top of shadow) //mChild.toFront(); //}}); // Don't do this here, as this only hits children // in stack: we need the whole stack, including top. mChild.detachChildrenForMoving(topOfWindowStack); // Need to keep last one detached over it's parent if there is one -- // it tends to go behind it after detachment. // Okay, if we stay with java 1.4.2 and can't use setAlwaysOnTop to fix // this, we can call NSWindow.setLevel to raise it up over all other // applications, but will need a WindowManager to put it back in place // once it loses focus... Actually, we'll need that WindowManager // for 1.5 w/setAlwaysOnTop ANYWAY, so either way we need the WindowManager // (to know when the application has lost focus and should hide/lower floating // palettes). // Okay -- in java 1.5, we get same bug, tho when we use // setAlwaysOnTop, it doesn't actually go behind it's java parent // (the main Frame), but this is the apparently the same bug // where it DOES go under it's DockWindow parent shadow -- even // if we do damn orderAbove... if (false && mChild.mChild == null) { // workaround for above bug: be sure to raise *this* window, // then our child: apparently the parent is stepping entirely // out of the order somehow? W/out our toFront first, the // child won't actually stay on top of us all the time. toFront(); mChild.toFront(); } } } public void suggestLocation(int x, int y) { Rectangle limit = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds(); limit.width += limit.x; limit.height += limit.y; //out("suggesting location " + x + "," + y + " in limits " + limit); if (x + getWidth() > limit.width) x = limit.width - getWidth(); if (x < limit.x && x != 0) x = limit.x; if (y + getHeight() > limit.height) y = limit.height - getHeight(); if (y < limit.y) y = limit.y; setLocation(x, y); } /** set location by the upper right cornet instead of the (normal) upper left corner */ public void setUpperRightCorner(int rightX, int y) { setLocation(rightX - getWidth(), y); } public void setLowerLeftCorner(int x, int lowerY) { setLocation(x, lowerY - getHeight()); } public void setLowerRightCorner(int lowerX, int lowerY) { setLocation(lowerX - getWidth(), lowerY - getHeight()); } private void out(Object o) { Log.debug("(" + mTitleName + ") " + o); // String s = "DockWindow " + (""+System.currentTimeMillis()).substring(9); // s += " [" + mTitle + "]"; // System.err.println(s + " " + (o==null?"null":o.toString())); } public String toString() { //String s = "DockWindow[" + mTitle; String s = "[" + mTitleName; if (true || mChild == null) return s + "]"; else return s + " ->" + mChild.mTitleName + "]"; } public static class ScrollableWidthTracker extends JPanel implements Scrollable { private JComponent tracked; ScrollableWidthTracker(JComponent wrapped, JComponent tracked) { super(new BorderLayout()); if (DEBUG.BOXES) setBorder(new LineBorder(Color.red, 4)); add(wrapped); this.tracked = tracked; } public Dimension getPreferredScrollableViewportSize() { Dimension d = tracked.getSize(); // width is always same as tracked width //Dimension d = tracked.getPreferredSize(); // causes recursive loop: stack overflow d.height = getHeight(); // height is always whatever we've been laid out to (preferred size) //Dimension d = getSize(); //d.height = tracked.getHeight(); if (DEBUG.DOCK || DEBUG.SCROLL) Log.debug(GUI.name(tracked) + " viewportSize " + GUI.name(d)); return d; } /** clicking on the up/down arrows of the scroll bar use this */ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 8; } public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 64; } public boolean getScrollableTracksViewportWidth() { return true; } public boolean getScrollableTracksViewportHeight() { return false; } @Override public String toString() { return getClass().getSimpleName() + "[" + GUI.name(tracked) + "; psvs=" + Util.fmt(getPreferredScrollableViewportSize()) + "]"; } } /** The content-pane for the Window: has the window border, contains the title and the widget content panel (which holds the widget border) */ private class ContentPane extends JPanel { private final JPanel mContent = new JPanel(true); private JComponent mWidget; private TitlePanel mTitle; private boolean isVertical = false; private boolean gradientBG = false; private Object titleConstraints = BorderLayout.NORTH; private JScrollPane mScroller; private Object contentConstraints; public ContentPane(String title, boolean asToolbar) { mContent.setName(title + ".dockContent"); if (true||asToolbar) { setLayout(new BorderLayout()); if (asToolbar) titleConstraints = BorderLayout.WEST; else titleConstraints = BorderLayout.NORTH; contentConstraints = BorderLayout.CENTER; } else { setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.gridwidth = GridBagConstraints.REMAINDER; c.weightx = 1; c.gridx = 0; c.gridy = 0; c.fill = GridBagConstraints.HORIZONTAL; titleConstraints = c.clone(); c.gridy = 1; c.fill = GridBagConstraints.BOTH; c.weighty = 1; contentConstraints = c; } // Apparently, max bounds not respected by BorderLayout: try GridBag // pref size is respected, but then it sets *everything* to max size. // Okay, BoxLayout and not even freakin GridBag is handling this... // Rectangle max = GUI.getMaximumWindowBounds(); // mContent. // setMaximumSize(new Dimension(max.width, max.height-100)); // setMaximumSize(new Dimension(max.width, max.height-100)); //setPreferredSize(new Dimension(max.width, max.height-100)); if (isMac && isToolbar) setBorder(BorderFactory.createLineBorder(Color.black)); else setBorder(getWindowBorder()); installTitlePanel(title, asToolbar); add(mContent, contentConstraints); mContent.setLayout(new BorderLayout()); // need to make sure the background is set // so that if there is a bevel-border, it'll // do the right thing. //setBackground(GUI.getVueColor()); //mContent.setBackground(Color.green); mContent.setOpaque(false); } public JScrollPane getScroller() { return mScroller; } //public void validate() { out("validate"); super.validate(); } // public void doLayout() { // if (false && !mWindowDragUnderway && !isRolledUp()) { // if (DEBUG.DOCK && DEBUG.SCROLL) GUI.dumpSizes(this, "doLayout"); // int height = getHeight(); // int prefHeight = Math.max(getPreferredSize().height, getMinimumSize().height); // prefHeight = Math.min(prefHeight, GUI.GScreenHeight); // if (height != prefHeight) // setHeight(prefHeight); // } // super.doLayout(); // } /* public _ContentPane(String title, boolean asToolbar) { // requesting double-buffering doesn't do squat to stop resize flashing on MacOSX super(true); mContent.setName(title + ".dockContent"); setLayout(new BorderLayout()); setBorder(getWindowBorder()); installTitlePanel(title, asToolbar); add(mContent, BorderLayout.CENTER); mContent.setLayout(new BorderLayout()); // need to make sure the background is set // so that if there is a bevel-border, it'll // do the right thing. //setBackground(GUI.getVueColor()); //mContent.setBackground(Color.green); mContent.setOpaque(false); } */ void setCloseButtonVisible(boolean visible) { if (mTitle != null) mTitle.setCloseButtonVisible(visible); } private void changeAll(JComponent root) { new EventRaiser<JComponent>(this, JComponent.class) { protected void visit(Component c) { if (DEBUG.INIT) { if (targetClass.isInstance(c)) { System.out.format("\tDockWindow(%s) making transparent: ", DockWindow.this.mTitleName); dispatchSafely(c); } else System.out.print(" "); eoutln(GUI.name(c)); } else { super.visit(c); } } public void dispatch(JComponent c) { if (c instanceof javax.swing.text.JTextComponent) return; //if (target instanceof javax.swing.AbstractButton) //return; // apparently can't make a JTabbedPane transparent... c.setBackground(null); c.setOpaque(false); //c.setBackground(Color.red); } }.raiseStartingAt(root); } JComponent getWidget() { return mWidget; } void setWidget(JComponent widget, boolean scrolled, boolean scrollAlways) { mContent.removeAll(); if (DEBUG.DOCK || DEBUG.WIDGET || DEBUG.INIT) out("setWidget " + GUI.name(widget)); //if (GUI.isMacBrushedMetal() && isToolbar) if (isToolbar) { //gradientBG = true; changeAll(widget); } //widget.putClientProperty(DockWindow.class, DockWindow.this); mWidget = widget; mContent.setBorder(getContentBorder(widget)); if (scrolled && mScroller == null) { mScroller = new JScrollPane(null, scrollAlways ? JScrollPane.VERTICAL_SCROLLBAR_ALWAYS : JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); mScroller.setOpaque(false); mScroller.getViewport().setOpaque(false); mScroller.setBorder(null); mScroller.setName(mBaseTitle + ".dockScroll"); mScroller.setWheelScrollingEnabled(true); mContent.add(mScroller, BorderLayout.CENTER); if (DEBUG.BOXES) mScroller.setBorder(new LineBorder(Color.green, 4)); } if (mScroller != null) { if (false) { // If the content wants to change width, we'd need this so it will reformat inside // the existing DockWindow width (e.g., growing taller instead of wider) Component view = new ScrollableWidthTracker(widget, mContent); mScroller.setViewportView(view); widget.putClientProperty("VUE.heightTracker", mContent); // no longer used } else { widget.putClientProperty("VUE.sizeTrack", mScroller.getViewport()); mScroller.setViewportView(widget); } //JPanel p = new JPanel(new BorderLayout()); //p.add(widget); //p.setBorder(new LineBorder(Color.red)); //mScroller.setViewportView(p); //mScroller.setViewportView(widget); } else { mContent.add(widget, BorderLayout.CENTER); } } public void setHelpText(String helpText) { mTitle.setHelpText(helpText); } public void XaddNotify() { changeAll(mContent); super.addNotify(); changeAll(mContent); } public void Xpaint(Graphics g) { //out("paint"); //setOpaque(true); changeAll(mContent); super.paint(g); } public void paintComponent(Graphics g) { if (gradientBG) paintBackgroundGradient(g); else super.paintComponent(g); } private void paintBackgroundGradient(Graphics g) { //out("paintComponent"); GradientPaint gp; if (false) gp = new GradientPaint(0, 0, TopGradientColor, 0, getHeight()/2, BottomGradientColor, true); gp = new GradientPaint(0, 0, TopGradientColor, 0, getHeight(), BottomGradientColor); ((Graphics2D)g).setPaint(gp); //g.setColor(Color.blue); g.fillRect(0,0, getWidth(), getHeight()); //super.paintComponent(g); if (false && isToolbar) { g.setColor(Color.lightGray); g.drawLine(0, 0, getWidth(),0); } } public void setVerticalTitle(boolean vertical) { if (mTitle == null) return; if (isVertical == vertical) return; isVertical = vertical; if (vertical) { //setBorder(new LineBorder(Color.gray)); setBorder(new LineBorder(Color.lightGray)); //setBorder(null); mTitle.setVertical(true); add(mTitle, BorderLayout.CENTER); } else { setBorder(getWindowBorder()); remove(mTitle); mTitle.setVertical(false); add(mTitle, BorderLayout.NORTH); } } private void installTitlePanel(String title, boolean asToolbar) { mTitle = new TitlePanel(title); if (asToolbar) add(new Gripper(), BorderLayout.WEST); else add(mTitle, titleConstraints); } /* public boolean processKeyBindingUp(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { return super.processKeyBinding(ks, e, condition, pressed); } protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { return GUI.processKeyBindingToMenuBar(this, ks, e, condition, pressed); } */ } //private class Gripper extends javax.swing.JPanel { private class Gripper extends javax.swing.Box { private JComponent closeButton; Gripper() { super(BoxLayout.Y_AXIS); //super(new BorderLayout()); // close-button expands to fill whole gripper... //super(null); setName(DockWindow.this._win.getName()); setPreferredSize(new Dimension(16,-1)); if (true) setOpaque(false); else setBackground(Color.darkGray); // looks like crap //setBackground(sMidGradientColor); // looks like crap closeButton = new CloseButton(DockWindow.this); closeButton.setBorder(new EmptyBorder(2,1,2,0)); //add(Box.createVerticalGlue()); if (showCloseBtn) add(closeButton); //closeButton.setLocation(1,1); //add(Box.createVerticalGlue()); //add(closeButton, BorderLayout.CENTER); /* // Without an opposite component on mouse enter/exit events, // we've no idea if on exit it's actually entering the closeButton, // in which case we do NOT want to make it go invisible. // We could make this work by using invokeLater on the // exit, and if when it runs, the close button has gotten // a mouse-enter, then don't make it invisible. closeButton.setVisible(false); addMouseListener(new tufts.vue.MouseAdapter(this) { public void mouseEntered(MouseEvent e) { //closeButton.setVisible(true); } public void mouseExited(MouseEvent e) { //closeButton.setVisible(false); } }); */ } public void paintComponent(Graphics g) { if (DEBUG.BOXES) { g.setColor(Color.green); g.drawRect(0,0, getWidth()-1, getHeight()-1); } if (true) paintGripper((Graphics2D)g); else super.paintComponent(g); } private void paintGripper(Graphics2D g) { final int height = getHeight(); final int left = 0; final int right = getWidth(); final int width = right - left; GradientPaint mGradient = new GradientPaint(0, 0,BottomGradientColor, width, 0, TopGradientColor ); ((Graphics2D)g).setPaint(mGradient); g.fillRect(0,0, width, height); g.setColor(new Color(128,128,128)); g.drawLine(right-1, 0, right-1, height); /*final int height = getHeight(); final int left = 0; final int right = getWidth(); final int width = right - left; final int yinc = 5; // if divides evenly into ToolbarHeight, will texture when stacked for (int i = 0, y = 0; i < height; i++) { g.setColor(Color.black); g.drawLine(left,y-width, right,y); g.setColor(Color.white); g.drawLine(left,(y-width)-1, right,y-1); y += yinc; }*/ /* for (int i = 0, y = 0; i < height; i++) { g.setColor(Color.black); g.drawLine(left,y, right,y-width); g.setColor(Color.white); g.drawLine(left,y-1, right,(y-width)-1); y += yinc; } */ /* //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); for (int i = 0, y = 0; i < height; i++) { g.setColor(Color.black); g.drawLine(left,y-width, right,y); //g.setColor(Color.white); //g.drawLine(left,(y-width)-1, right,y-1); y += yinc; } for (int i = 0, y = 0; i < height; i++) { g.setColor(Color.black); g.drawLine(left,y, right,y-width); //g.setColor(Color.white); //g.drawLine(left,y-1, right,(y-width)-1); y += yinc; } */ /* if (false) { //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // draw left & right border g.setColor(Color.lightGray); g.drawLine(0,0, 0,height); g.drawLine(width-1,0, width-1,height); }*/ } } // TODO: allow for info in the title panel: e.g., Panner shows zoom or // Font shows current selected font v.s. the font we'll "apply"??? private class TitlePanel extends javax.swing.Box { private CloseButton mCloseButton; private GradientPaint mGradient; private JLabel mLabel; // private JLabel mOpenLabel; // for open/close icon private boolean isVertical = false; private MenuButton mMenuButton; // null of has no menu private JComponent mHelpButton = null; //private final Icon DownArrow = GUI.getIcon("DockDownArrow.gif"); //private final Icon RightArrow = GUI.getIcon("DockRightArrow.gif"); TitlePanel(String title) { //setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); super(BoxLayout.X_AXIS); setName(title); setOpaque(true); setPreferredSize(new Dimension(0, TitleHeight)); /* if (!isMac) { //if (isMac) setBackground(SystemColor.control); setBackground(SystemColor.activeCaption); //setBorder(new LineBorder(SystemColor.activeCaptionBorder)); } */ //if (!isMacAqua) setBorder(new LineBorder(GUI.getVueColor())); if (title == null) title = ""; mLabel = new tufts.Util.JLabelAA(title); //if (TitleFont.getSize() < 11) //mLabel.setBorder(new EmptyBorder(2,0,0,0)); // t,l,b,r //else //mLabel.setBorder(new EmptyBorder(1,0,0,0)); // t,l,b,r // FYI, raise the label raises the icon also... mLabel.setFont(TitleFont); mLabel.setForeground(VueResources.getColor("gui.dockWindow.title.foreground", SystemColor.activeCaptionText)); Color iconColor = VueResources.getColor("gui.dockWindow.title.disclosureIcon.color", isMacAqua ? Color.darkGray : SystemColor.activeCaptionText); mCloseButton = new CloseButton(DockWindow.this); // mOpenLabel = new GUI.IconicLabel(DownArrowChar, // 16, // point-size // iconColor, // 15, // fixed width // TitleHeight); // fixed height // if (isMacAqua) // mOpenLabel.setBorder(new EmptyBorder(0,0,1,0)); // t,l,b,r if (DEBUG.BOXES) { mLabel.setBackground(Color.yellow); mLabel.setOpaque(true); } //JLabel helpButton = new JLabel(GUI.getIcon("btn_help_top.gif")); // todo for Melanie: new icons should be appearing in gui/icons final String helpTextHeader = VueResources.getString("dockWindow.helpTextHeader"); final String helpTextFooter = VueResources.getString("dockWindow.helpTextFooter"); final String helpText = helpTextHeader + "No help for " + getName() + "." + helpTextFooter; if (helpText != null && helpText.length() > 0) { if (false && Util.isMacLeopard()) { final JButton help = new JButton(); //help.setBorder(BorderFactory.createLineBorder(Color.green)); // FYI, changing the border in any way after putting the special // client properties. makes help button's not appear at all. help.setEnabled(false); //help.setVerticalAlignment(SwingConstants.TOP); //help.setMargin(new Insets(0,9,0,0)); help.putClientProperty("JButton.buttonType", "help"); help.putClientProperty("JComponent.sizeVariant", "mini"); //final JComponent onePixelUp = Box.createVerticalBox(); // seemingly no luck with Box / BoxLayout! BorderLayout works, tho // leaves too much space at right. final JComponent onePixelUp = new JPanel(); onePixelUp.setLayout(new BorderLayout()); //onePixelUp.setLayout(null); //onePixelUp.setLayout(new BoxLayout(onePixelUp, BoxLayout.Y_AXIS)); //onePixelUp.add(help); onePixelUp.add(help, BorderLayout.EAST); //help.setLocation(0,0); //onePixelUp.setSize(5,5); //onePixelUp.setMaximumSize(new Dimension(10,10)); //onePixelUp.add(Box.createRigidArea(new Dimension(1,2)), BorderLayout.SOUTH); //onePixelUp.setOpaque(false); //onePixelUp.setBackground(Color.red); //onePixelUp.setBorder(BorderFactory.createLineBorder(Color.green)); onePixelUp.setBorder(new EmptyBorder(0,0,2,0)); //onePixelUp.setBorder(new MatteBorder(0,0,2,0, Color.green)); mHelpButton = onePixelUp; } else { mHelpButton = new VueLabel(VueResources.getImageIconResource("/tufts/vue/images/btn_help_top.gif")); } mHelpButton.setToolTipText(helpText); } if (isMacAqua) { // close button at left add(Box.createHorizontalStrut(6)); add(mCloseButton); add(Box.createHorizontalStrut(4)); //add(mOpenLabel); add(mLabel); add(Box.createGlue()); if (mHelpButton != null) add(mHelpButton); } else { // close button at right add(Box.createHorizontalStrut(8)); //add(mOpenLabel); //add(Box.createHorizontalStrut(2)); add(mLabel); add(Box.createGlue()); if (mHelpButton != null) add(mHelpButton); add(Box.createHorizontalStrut(3)); add(mCloseButton); } add(Box.createHorizontalStrut(2)); if (isGradientTitle) installGradient(false); } void setTitle(String title) { mLabel.setText(title); } void setHelpText(String helpText) { final String helpTextHeader = VueResources.getString("dockWindow.helpTextHeader"); final String helpTextFooter = VueResources.getString("dockWindow.helpTextFooter"); mHelpButton.setToolTipText(helpTextHeader + helpText + helpTextFooter); } void showAsOpen(boolean open) { //mOpenLabel.setIcon(open ? DownArrow : RightArrow); //mOpenLabel.setText(open ? DownArrow : RightArrow); // mOpenLabel.setText(open ? ""+DownArrowChar : ""+RightArrowChar); } void setCloseButtonVisible(boolean visible) { mCloseButton.setHidden(!visible); } void setMenuActions(Action[] actions) { int count = 0; if (actions != null) { for (int i = 0; i < actions.length; i++) { if (actions[i] != null) count++; } } if (count == 0) { if (mMenuButton != null) { remove(mMenuButton); mMenuButton = null; } } else { if (mMenuButton == null) { mMenuButton = new MenuButton(actions); add(mMenuButton, -1); } else mMenuButton.setMenuActions(actions); } } void setVertical(boolean vertical) { if (isGradientTitle) installGradient(vertical); mCloseButton.setVisible(!vertical); isVertical = vertical; } private void installGradient(boolean vertical) { if (vertical) mGradient = new GradientPaint(getHeight(), 0, TopGradientColor, 0, 0, false ? Color.gray : BottomGradientColor); else mGradient = new GradientPaint(0, 0, TopGradientColor, 0, TitleHeight, BottomGradientColor); // reversed gradient if (false) mGradient = new GradientPaint(0, 0, BottomGradientColor, 0, TitleHeight, TopGradientColor); } public void paint(Graphics g) { if (!isMac) { // this is on by default on the mac Graphics2D g2 = (Graphics2D) g; //g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } super.paint(g); } public void paintComponent(Graphics g) { if (DEBUG.PAINT) out("TitlePanel.paintComponent"); //if (!isMac || !DEBUG.DOCK) // for tufts.macosx.MacTest paintGradientEtc(g); } private void paintGradientEtc(Graphics g) { final int width = getWidth(); final int height = getHeight(); if (isGradientTitle) ((Graphics2D)g).setPaint(mGradient); else g.setColor(getBackground()); g.fillRect(0,0, width, height); // This add's left + right edge border when docked if we want. if (!mHasWindowShadow && isDocked() && getWindowBorder() == null) { //g.setColor(sBottomEdgeColor); g.setColor(BottomGradientColor); if (false) { // draw left edge g.drawLine(0, 0, 0, height-1); } //if (mWindowDragUnderway || mDockNext == null || !mDockNext.isRolledUp()) { if (mDockNext == null) { // If right (next) sibling is unrolled, it will have a shadow // that could serve as the visual border. However, if the // the left (prev) window is above it, it will obscure // the shadow and the window's will "merge" at the title bar. // [NO LONGER TRUE: left component now always shrinks, and // unrolled always have shadow ] g.drawLine(width-1, 0, width-1, height-1); } } /* This looks great, but more so during testing when no content in the DockWindow. if (isMac && !isVertical && !isMacAquaMetal && !isRolledUp()) { g.setColor(sBottomEdgeColor); g.drawLine(0, height-1, width, height-1); } */ if (isVertical) { g.setColor(mLabel.getForeground()); g.setFont(mLabel.getFont()); ((Graphics2D)g).rotate(Math.PI / 2); g.drawString(mLabel.getText(), 4, -4); } } } private class MenuButton extends JButton //implements MouseListener { MenuButton(Action[] actions) { super(); setFocusable(true); setIcon(VueResources.getIcon("dockWindow.panner.menu.raw")); setRolloverEnabled(true); setRolloverIcon(VueResources.getImageIcon("dockWindow.panner.menu.raw.over")); setName(DockWindow.this._win.getName()); setFont(new Font("Arial", Font.PLAIN, 18)); Insets borderInsets = new Insets(1,1,1,1); if (DEBUG.BOXES) { setBorder(new MatteBorder(borderInsets, Color.orange)); setBackground(Color.red); setOpaque(true); } else { // using an empty border allows mouse over the border gap // to also activate us for rollover setBorder(new EmptyBorder(borderInsets)); } if (false) setMaximumSize(new Dimension(30,TitleHeight)); setMenuActions(actions); } void setMenuActions(Action[] actions) { clearMenuActions(); //addMouseListener(this); new GUI.PopupMenuHandler(this, GUI.buildMenu(actions)) { /* public void mouseEntered(MouseEvent e) { setForeground(isMacAqua ? Color.black : Color.white); } public void mouseExited(MouseEvent e) { //setForeground(isMacAqua ? Color.gray : SystemColor.activeCaption.brighter()); setForeground(inactiveColor); } */ public int getMenuX(Component c) { return c.getWidth(); } public int getMenuY(Component c) { return -getY(); } // 0 in parent }; repaint(); } private void clearMenuActions() { MouseListener[] ml = getMouseListeners(); for (int i = 0; i < ml.length; i++) { if (ml[i] instanceof GUI.PopupMenuHandler) removeMouseListener(ml[i]); } } // public void mouseClicked(MouseEvent arg0) { // // TODO Auto-generated method stub // // } // // // public void mousePressed(MouseEvent arg0) { // // TODO Auto-generated method stub // // } // // // public void mouseReleased(MouseEvent arg0) { // // TODO Auto-generated method stub // } } private static class CloseButton extends JLabel { private final Icon iconClose; private final Icon iconOver; private boolean visible = true; public CloseButton(final DockWindow dockWindow) { setName(dockWindow._win.getName()); if (dockWindow.isToolbar) { iconClose = VueResources.getIcon("gui.dockWindow.closeIcon"); iconOver = VueResources.getIcon("gui.dockWindow.closeIcon.over"); } else { iconClose = VueResources.getIcon("gui.dockWindow.closeIcon"); iconOver = VueResources.getIcon("gui.dockWindow.closeIcon.over"); } setIcon(iconClose); // if (isMacAqua) // setIcon(iconBlank); // else // setIcon(new SquareCloseIcon()); addMouseListener(new tufts.vue.MouseAdapter(getClass()) { public void mouseEntered(MouseEvent e) { setRollover(true); } public void mouseExited(MouseEvent e) { setRollover(false); } public void mouseClicked(MouseEvent e) { if (visible && e.getClickCount() != 0 && e.getClickCount() != 2) dockWindow.dismiss(); } }); } public void paint(Graphics g) { if (visible) super.paint(g); } public void setHidden(boolean hidden) { this.visible = !hidden; //setEnabled(this.visible); } private void setRollover(boolean lit) { setIcon(lit ? iconOver : iconClose); // if (isMacAqua) // setIcon(lit ? iconClose : iconBlank); // else // ((SquareCloseIcon)getIcon()).setRollover(lit); } private class SquareCloseIcon implements Icon { private final int iconSize; private final int iconWidth; private final int iconHeight; private final java.awt.BasicStroke X_STROKE; private boolean isRollover = false; private final Border border = BorderFactory.createRaisedBevelBorder(); public SquareCloseIcon() { X_STROKE = new java.awt.BasicStroke(1.3f); iconSize = TitleHeight - 5; iconWidth = iconSize + 1; iconHeight = iconSize + 1; /* if (isMacAqua) { iconWidth = iconSize + 3; iconHeight = iconSize + 1; } else { iconWidth = iconSize + 2; if (isMac) iconHeight = iconSize + 3; else iconHeight = iconSize + 2; } */ } void setRollover(boolean t) { isRollover = t; repaint(); } public int getIconWidth() { return iconWidth; } public int getIconHeight() { return iconHeight; } public void paintIcon(Component c, Graphics g, int x, int y) { int xoff = x-1; int yoff = y+1; if (false && isMacAquaMetal == false) { //g.setColor(SystemColor.activeCaption); // we fill in case window is so narrow that title text // would appear under this icon: we don't want to see that. //g.setColor(c.getBackground().brighter()); g.setColor(SystemColor.control); //g.setColor(Color.green); g.fillRect(xoff, yoff, iconSize,iconSize); } //g.setColor(Color.black); //g.drawRect(xoff, yoff, iconSize,iconSize); ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(isRollover ? Color.red : Color.lightGray); //g.drawOval(xoff, yoff, iconSize,iconSize); ((Graphics2D)g).setStroke(X_STROKE); int inset = 2; int len = iconSize - (inset+1); // UL to LR g.drawLine(xoff+inset, yoff+inset, xoff+len, yoff+len); // LL to UR g.drawLine(xoff+inset, yoff+len, xoff+len, yoff+inset); //border.paintBorder(c, g, x, y, iconSize, iconSize); } } private String out(Color c) { return "color[" + c.getRed() + "," + c.getGreen() + "," + c.getBlue() + "]"; } } private static class ResizeCorner extends javax.swing.JComponent { final Object mCorner; ResizeCorner(DockWindow mouseListener, Object corner) { mCorner = corner; setName(corner.toString()); if (mCorner == SOUTH_EAST) setSize(ResizeCornerSize, ResizeCornerSize); else setSize(ResizeCornerSize/2, ResizeCornerSize/2); addMouseListener(mouseListener); addMouseMotionListener(mouseListener); int cursorID = Cursor.SE_RESIZE_CURSOR; if (mCorner == NORTH_EAST) cursorID = Cursor.NE_RESIZE_CURSOR; else if (mCorner == SOUTH_WEST) cursorID = Cursor.SW_RESIZE_CURSOR; else if (mCorner == NORTH_WEST) cursorID = Cursor.NW_RESIZE_CURSOR; // This only works if Window focusable state is true, which // will not be true if we're using java 1.5 alwaysOnTop w/forced focus. setCursor(Cursor.getPredefinedCursor(cursorID)); if (isMacAqua && !isMacAquaMetal) setForeground(Color.lightGray); else setForeground(Color.gray); } public void paintComponent(Graphics g) { if (mCorner == SOUTH_EAST) paintResizeCorner((Graphics2D)g); if (DEBUG.BOXES) { g.setColor(Color.green); g.drawRect(0,0, getWidth()-1, getHeight()-1); } } private void paintResizeCorner(Graphics2D g) { if (DEBUG.PAINT) System.out.println("ResizeCorner paint " + g.getClipBounds()); int width = getWidth(); int right = width - 1; int bottom = getHeight() - 1; int x = 0, y = 0; //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(getForeground()); for (int i = 0; i < width/2; i++) { g.drawLine(x,bottom, right,y); x += 2; y += 2; } } } public static Frame getHiddenFrame() { Util.printStackTrace(); if (HiddenParentFrame == null) { HiddenParentFrame = new JFrame() { public void show() {} // so isFocusableWindow in children can return true even tho we're invisible public boolean isShowing() { return true; } // An isVisible() that returns true is the key to allowing this to // serve as a top-level focusable parent. Otherwise, if this is the // ONLY frame "on screen", keyboard input is not permitted anywhere, // even to forced focus components (we get system beeps). Calling // setVisible(true) won't work, because it won't actually go visible // if show() is doing nothing. // UNFORUNATELY, THIS ALSO MAKES IT VISIBLE ON THE SCREEN! //public boolean isVisible() { return true; } // make sure is never preferred window group for handing focus to // CANNOT do this or an installed menu-bar won't work //public boolean getFocusableWindowState() { return false; } // doesn't help //public boolean isFocusable() { return true; } //public boolean getFocusableWindowState() { return true; } public String toString() { return getName(); } }; HiddenParentFrame.setName("(VUE Hidden Dock Parent)"); // If we have a menu-bar attached, it must be focusable for the menu bar to work. // (perhaps also now that it's returning isVisible() == true? //HiddenParentFrame.setFocusableWindowState(true); // Don't need to attach MenuBar now that DockWindow's do NOT "officially" take the focus at all -- // the active frame with it's attached menu bar stays active. //HiddenParentFrame.setJMenuBar(new tufts.vue.gui.VueMenuBar()); // fortunately, does NOT need to be visible for menu bar to work //HiddenParentFrame.setVisible(true); HiddenParentFrame.setVisible(false); } return HiddenParentFrame; } public static DockWindow getTestWindow() { DockWindow dw = new DockWindow("Interactive"); JPanel p = new JPanel(); JLabel l = new JLabel("I am a label"); p.add(l); JTextField tf = new JTextField(5); tf.setText("text"); p.add(tf); p.add(new JButton("foo")); dw.add(p); dw.setVisible(true); return dw; } public static void main(String args[]) { VUE.init(args); //DEBUG.BOXES=true; //DEBUG.KEYS=true; if (false) { //new Frame("A Frame").show(); DockWindow dw = getTestWindow(); dw._win.setLocationRelativeTo(null); } Window owner = null; if (true) { owner = new Frame("A Frame"); owner.setVisible(true); } //new Frame("Frame Two").show(); //owner = null; final DockWindow win1 = new DockWindow("Dock 1", owner, null, false); //win1.add(new FontPropertyPanel()); //win1.setLocationRelativeTo(null); // center's on screen win1.setMenuActions(new Action[] { new tufts.vue.VueAction("Test 1"), null, new tufts.vue.VueAction("Activate Wait Cursor") { public void act() { System.out.println("wait cursor"); JRootPane root = SwingUtilities.getRootPane(win1._win); System.out.println("JRootPane: " + root); root.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); //win1.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } public boolean enabled() { return true; } }, new tufts.vue.VueAction("Clear Wait Cursor") { public void act() { System.out.println("clear wait cursor"); win1._win.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public boolean enabled() { return true; } }, }); //win1.add(new WidgetBox("Folders", new JLabel("Hello World"))); WidgetStack stack = new WidgetStack(); stack.addPane("TUFTS Digital Library"); stack.addPane("ArtStor"); stack.addPane("My Computer"); stack.addPane("My Picture", new JLabel(new ImageIcon(VueResources.getURL("splashScreen")))); if (false) { JScrollPane sp = new JScrollPane(stack); //sp.setBorder(new LineBorder(Color.red)); // clear default 1 pixel white border sp.setBorder(null); win1.setContent(sp); } else { // win1.setContent(stack); win1.setContent(new JLabel("foo")); } win1.setVisible(true); DockWindow win2 = new DockWindow("Dock 2", owner); //win2.add(new FontPropertyPanel()); win2._win.setLocationRelativeTo(null); // center's on screen win2.setFocusableWindowState(true); win2.setVisible(true); /* if (false) { DockWindow win3 = new DockWindow("Dock 3", owner); DockWindow win4 = new DockWindow("Dock 4", owner); //DockWindow win3 = new DockWindow("King Objumpy"); //DockWindow win4 = new DockWindow("Canvas"); win1.setChild(win2); //win2.setChild(win3); win3.setChild(win4); //win2.setVisible(true); win3.setVisible(true); win4.setVisible(true); } */ // called indirectly so this compiles in java 1.4 //tufts.Util.invoke(tw0.mWindow, "setAlwaysOnTop", Boolean.TRUE); //tufts.Util.invoke(tw1.mWindow, "setAlwaysOnTop", Boolean.TRUE); } }