/* This file is part of leafdigital leafChat. leafChat is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. leafChat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2011 Samuel Marshall. */ package com.leafdigital.ui; import java.awt.*; import java.awt.event.*; import java.util.LinkedList; import javax.swing.*; import util.PlatformUtils; /** * Internal desktop, within which FrameInside objects can be placed */ public class InternalDesktop extends JDesktopPane { /** Switchbar */ private SwitchBar sb; private LinkedList<FrameInside> frameOrder = new LinkedList<FrameInside>(); private Dimension overrideSize=null; InternalDesktop(SwitchBar sb,ToolBar tb) { this.sb=sb; if(PlatformUtils.isMac()) { // Change window: Command-F6, Command-Shift-F6 // (Ctrl-F6, Ctrl-Tab can't be mapped here) getInputMap(WHEN_IN_FOCUSED_WINDOW).put( KeyStroke.getKeyStroke(KeyEvent.VK_F6,KeyEvent.META_DOWN_MASK), "changeWindowNext"); getInputMap(WHEN_IN_FOCUSED_WINDOW).put( KeyStroke.getKeyStroke(KeyEvent.VK_F6,KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK), "changeWindowPrev"); getActionMap().put("changeWindowNext",new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { nextWindow(); } }); getActionMap().put("changeWindowPrev",new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { prevWindow(); } }); } } private void nextWindow() { JInternalFrame current=getSelectedFrame(); if(current==null) return; int index=frameOrder.indexOf(current); if(index==-1) return; index++; if(index>=frameOrder.size()) index=0; frameOrder.get(index).focusFrame(); } private void prevWindow() { JInternalFrame current=getSelectedFrame(); if(current==null) return; int index=frameOrder.indexOf(current); if(index==-1) return; index--; if(index<0) index=frameOrder.size()-1; frameOrder.get(index).focusFrame(); } /** * Called when the previously-focused frame has been hidden. Assigns * focus to the topmost frame. */ void reassignFocus() { JInternalFrame[] frames=getAllFrames(); int bestPosition=-1; FrameInside best=null; for(int i=0;i<frames.length;i++) { if(!frames[i].isVisible()) continue; if(bestPosition==-1 || getPosition(frames[i]) < bestPosition) { bestPosition=getPosition(frames[i]); best=(FrameInside)frames[i]; } } if(best!=null) { best.focusFrame(); } } /** Point where next window will be added */ private Point pAddLocation=new Point(20,20); int HITPOINTS=12; /** * Checks whether an internal frame is 'reasonably visible'. That means it's * not hidden and that a good proportion of the frame is not covered by * other frames. * @param fi Frame to examine * @return True if it's reasonably visible */ public boolean isReasonablyVisible(FrameInside fi) { // Not showing? false if(!fi.isVisible()) return false; // Window and detection points Rectangle rBounds=fi.getBounds(); int iSpacingX=(rBounds.width+HITPOINTS/2) / (HITPOINTS+1); int iSpacingY=(rBounds.height+HITPOINTS/2)/ (HITPOINTS+1); boolean[][] abHitPoints=new boolean[HITPOINTS-2][HITPOINTS-2]; int iHits=0; // Check it's in the window... Rectangle rDesktopBounds=getBounds(); rDesktopBounds.y=0; int iPosX,iPosY=rBounds.y+iSpacingY*2; for(int iY=0;iY<HITPOINTS-2;iY++) { iPosX=rBounds.x+iSpacingX*2; for(int iX=0;iX<HITPOINTS-2;iX++) { if(!rDesktopBounds.contains(iPosX,iPosY) && !abHitPoints[iY][iX]) { abHitPoints[iY][iX]=true; iHits++; } iPosX+=iSpacingX; } iPosY+=iSpacingY; } // Now see if it's behind something else int iLayer=getLayer(fi); int iPosition=getPosition(fi); // Loop through all higher components Component[] ac=getComponentsInLayer(iLayer); for(int i=0;i<ac.length;i++) { if(getPosition(ac[i])>=iPosition) continue; Rectangle rThis=ac[i].getBounds(); iPosY=rBounds.y+iSpacingY*2; for(int iY=0;iY<HITPOINTS-2;iY++) { iPosX=rBounds.x+iSpacingX*2; for(int iX=0;iX<HITPOINTS-2;iX++) { if(rThis.contains(iPosX,iPosY) && !abHitPoints[iY][iX]) { abHitPoints[iY][iX]=true; iHits++; } iPosX+=iSpacingX; } iPosY+=iSpacingY; } } // How much was covered area (very roughly)? return (iHits*100) / ((HITPOINTS-2)*(HITPOINTS-2)) <10; } /** * Add an internal frame to the desktop. * @param fi New internal frame * @param initialScreen If true, the initial point is in screen co-ordinates, * otherwise it is in desktop co-ordinates * @param initial Initial point to start at (null for default) */ public void addFrame(final FrameInside fi,final boolean initialScreen, final Point initial) { UISingleton.runInSwing(new Runnable() { @Override public void run() { add(fi); frameOrder.addLast(fi); Dimension size=overrideSize!=null ? overrideSize : getSize(); // If it has a screen location, see if that'll work out if(initial!=null) { Point start=initialScreen ? new Point( initial.x-getLocationOnScreen().x, initial.y-getLocationOnScreen().y) : new Point(initial.x,initial.y); Rectangle r=new Rectangle(start,fi.getSize()); if(r.x<0) r.x=0; if(r.y<0) r.y=0; if(r.width>size.width) r.width=size.width; if(r.height>size.height) r.height=size.height; if(r.x+r.width > size.width) { r.x=size.width-r.width; } if(r.y+r.height> size.height) { r.y=size.height-r.height; } fi.setBounds(r); } else { // Work out desired size and location Rectangle r=new Rectangle(pAddLocation,fi.getSize()); pAddLocation.x=r.x+20; pAddLocation.y=r.y+20; if(r.width>size.width) r.width=size.width; if(r.height>size.height) r.height=size.height; if(r.x+r.width > getWidth()) { r.x=10; pAddLocation.x=30; } if(r.y+r.height> size.height) { r.y=10; pAddLocation.y=30; } fi.setBounds(r); } // Put in front // moveToFront(fi); sb.addFrame(fi); } }); } /** * Remove an existing frame * @param fi Frame */ public void removeFrame(final FrameInside fi) { UISingleton.runInSwing(new Runnable() { @Override public void run() { remove(fi); frameOrder.remove(fi); sb.removeFrame(fi); repaint(); } }); } void informActive(FrameInside fi) { sb.informActiveFrame(fi); } /** * Temporarily fixes the size of new windows created by the * {@link #addFrame(FrameInside, boolean, Point)} method. * @param dimension Size to use from now on instead of window default size * @see #clearOverrideSize() */ public void setOverrideSize(Dimension dimension) { overrideSize=dimension; } /** * Clears the size temporarily set by {@link #setOverrideSize(Dimension)}. */ public void clearOverrideSize() { overrideSize=null; } }