/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy 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.
*
* Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.gui.main;
import icy.gui.frame.IcyInternalFrame;
import icy.gui.util.ComponentUtil;
import icy.gui.util.LookAndFeelUtil;
import icy.gui.viewer.Viewer;
import icy.main.Icy;
import icy.math.HungarianAlgorithm;
import icy.resource.ResourceUtil;
import icy.util.GraphicsUtil;
import icy.util.Random;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Point2D;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
/**
* Icy {@link JDesktopPane} class.<br>
* This is the main container of the application.<br>
* It support overlays so we can use to display message, notification or logo in
* background. First added overlays is painted first, so take care of that. Call
* the IcyDesktopPane.repaint() method to update overlays.
*
* @author Fabrice & Stephane
*/
public class IcyDesktopPane extends JDesktopPane implements ContainerListener, MouseListener, MouseMotionListener,
MouseWheelListener
{
public static interface DesktopOverlay extends MouseListener, MouseMotionListener, MouseWheelListener
{
public void paint(Graphics g, int width, int height);
}
public static class AbstractDesktopOverlay extends MouseAdapter implements DesktopOverlay
{
@Override
public void paint(Graphics g, int width, int height)
{
// nothing by default
}
}
/**
* Background overlay.
*/
public class BackgroundDesktopOverlay extends AbstractDesktopOverlay implements ImageObserver
{
private final static String BACKGROUND_PATH = "background/";
private final Image backGround;
private final Image icyLogo;
private final Color textColor;
private final Color bgTextColor;
public BackgroundDesktopOverlay()
{
super();
// load random background (nor really random as we have only one right now)
backGround = ResourceUtil.getImage(BACKGROUND_PATH + Integer.toString(Random.nextInt(1)) + ".jpg");
// load Icy logo
icyLogo = ResourceUtil.getImage("logoICY.png");
// default text colors
textColor = new Color(0, 0, 0, 0.5f);
bgTextColor = new Color(1, 1, 1, 0.5f);
}
@Override
public void paint(Graphics g, int width, int height)
{
final IcyDesktopPane desktop = Icy.getMainInterface().getDesktopPane();
final Color bgColor;
if (desktop != null)
bgColor = LookAndFeelUtil.getBackground(desktop);
else
bgColor = Color.lightGray;
final int bgImgWidth = backGround.getWidth(this);
final int bgImgHeight = backGround.getHeight(this);
// compute image scaling
final double scale = Math.max((double) width / (double) bgImgWidth, (double) height / (double) bgImgHeight) * 1.5d;
final Graphics2D g2 = (Graphics2D) g.create();
// fill background color
g2.setBackground(bgColor);
g2.clearRect(0, 0, width, height);
// paint image over background in transparency
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.2f));
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(backGround, 0, 0, (int) (scale * bgImgWidth), (int) (scale * bgImgHeight), bgColor, this);
final String text = "Version " + Icy.version;
final int textWidth = (int) GraphicsUtil.getStringBounds(g, text).getWidth();
// draw version text
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.7f));
g2.setColor(bgTextColor);
g2.drawString(text, width - (textWidth + 31), height - 8);
g2.setColor(textColor);
g2.drawString(text, width - (textWidth + 30), height - 9);
// and draw Icy text logo
g2.drawImage(icyLogo, width - 220, height - 130, this);
g2.dispose();
}
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
{
if ((infoflags & ImageObserver.ALLBITS) != 0)
{
repaint();
return false;
}
return true;
}
}
private static final long serialVersionUID = 7914161180763257329L;
private final ComponentAdapter componentAdapter;
// private final InternalFrameAdapter internalFrameAdapter;
private final ArrayList<DesktopOverlay> overlays;
public IcyDesktopPane()
{
super();
overlays = new ArrayList<DesktopOverlay>();
// setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
componentAdapter = new ComponentAdapter()
{
@Override
public void componentResized(ComponentEvent e)
{
checkPosition((JInternalFrame) e.getSource());
}
@Override
public void componentMoved(ComponentEvent e)
{
checkPosition((JInternalFrame) e.getSource());
}
};
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
addContainerListener(this);
// add the background overlay
overlays.add(new BackgroundDesktopOverlay());
}
// int i = 0;
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
final int w = getWidth();
final int h = getHeight();
// paint overlays
for (DesktopOverlay overlay : overlays)
overlay.paint(g, w, h);
}
private void registerFrame(JInternalFrame frame)
{
frame.addComponentListener(componentAdapter);
}
void unregisterFrame(JInternalFrame frame)
{
frame.removeComponentListener(componentAdapter);
}
void checkPosition(JInternalFrame frame)
{
final Rectangle rect = frame.getBounds();
if (fixPosition(rect))
frame.setBounds(rect);
}
boolean fixPosition(Rectangle rect)
{
final int limit = getY();
if (rect.y < limit)
{
rect.y = limit;
return true;
}
return false;
}
/**
* Returns the list of internal viewers.
*
* @param wantNotVisible
* Also return not visible viewers
* @param wantIconized
* Also return iconized viewers
*/
public static Viewer[] getInternalViewers(boolean wantNotVisible, boolean wantIconized)
{
final List<Viewer> result = new ArrayList<Viewer>();
for (Viewer viewer : Icy.getMainInterface().getViewers())
{
if (viewer.isInternalized())
{
final IcyInternalFrame internalFrame = viewer.getIcyInternalFrame();
if ((wantNotVisible || internalFrame.isVisible()) && (wantIconized || !internalFrame.isIcon()))
result.add(viewer);
}
}
return result.toArray(new Viewer[result.size()]);
}
/**
* Organize all internal viewers in cascade
*/
public void organizeCascade()
{
// get internal viewers
final Viewer[] viewers = getInternalViewers(false, false);
// available space (always keep 32 available pixels at south)
final int w = getWidth();
final int h = getHeight() - 32;
final int fw = (int) (w * 0.6f);
final int fh = (int) (h * 0.6f);
final int xMax = w - 0;
final int yMax = h - 0;
int x = 0 + 32;
int y = 0 + 32;
for (Viewer v : viewers)
{
final IcyInternalFrame internalFrame = v.getIcyInternalFrame();
if (internalFrame.isMaximized())
internalFrame.setMaximized(false);
internalFrame.setBounds(x, y, fw, fh);
internalFrame.toFront();
x += 30;
y += 20;
if ((x + fw) > xMax)
x = 32;
if ((y + fh) > yMax)
y = 32;
}
}
/**
* Organize all internal viewers in tile.
*
* @param type
* tile type<br>
* MainFrame.TILE_HORIZONTAL, MainFrame.TILE_VERTICAL or
* MainFrame.TILE_GRID
*/
public void organizeTile(int type)
{
// get internal viewers
final Viewer[] viewers = getInternalViewers(false, false);
final int numFrames = viewers.length;
// nothing to do
if (numFrames == 0)
return;
// available space (always keep 32 available pixels at south)
final int w = getWidth();
final int h = getHeight() - 32;
int numCol;
int numLine;
switch (type)
{
case MainFrame.TILE_HORIZONTAL:
numCol = 1;
numLine = numFrames;
break;
case MainFrame.TILE_VERTICAL:
numCol = numFrames;
numLine = 1;
break;
default:
numCol = (int) Math.sqrt(numFrames);
if (numFrames != (numCol * numCol))
numCol++;
numLine = numFrames / numCol;
if (numFrames > (numCol * numLine))
numLine++;
break;
}
final double[][] framesDistances = new double[numCol * numLine][numFrames];
final int dx = w / numCol;
final int dy = h / numLine;
int k = 0;
for (int i = 0; i < numLine; i++)
{
for (int j = 0; j < numCol; j++, k++)
{
final double[] distances = framesDistances[k];
final double x = (j * dx) + (dx / 2d);
final double y = (i * dy) + (dy / 2d);
for (int f = 0; f < numFrames; f++)
{
final Point2D.Double center = ComponentUtil.getCenter(viewers[f].getInternalFrame());
distances[f] = Point2D.distanceSq(center.x, center.y, x, y);
}
}
}
final int[] framePos = new HungarianAlgorithm(framesDistances).resolve();
k = 0;
for (int i = 0; i < numLine; i++)
{
for (int j = 0; j < numCol; j++, k++)
{
final int f = framePos[k];
if (f < numFrames)
{
final IcyInternalFrame internalFrame = viewers[f].getIcyInternalFrame();
if (internalFrame.isMaximized())
internalFrame.setMaximized(false);
internalFrame.setBounds(j * dx, i * dy, dx, dy);
internalFrame.toFront();
}
}
}
}
/**
* @deprecated Use {@link #organizeTile(int)} instead.
*/
@Deprecated
public void organizeTile()
{
organizeTile(MainFrame.TILE_GRID);
}
/**
* Add the specified overlay to the desktop.
*/
public void addOverlay(DesktopOverlay overlay)
{
if (!overlays.contains(overlay))
overlays.add(overlay);
}
/**
* remove the specified overlay from the desktop.
*/
public boolean removeOverlay(DesktopOverlay overlay)
{
return overlays.remove(overlay);
}
@Override
public void componentAdded(ContainerEvent e)
{
final Component comp = e.getChild();
if (comp instanceof JInternalFrame)
registerFrame((JInternalFrame) comp);
}
@Override
public void componentRemoved(ContainerEvent e)
{
final Component comp = e.getChild();
if (comp instanceof JInternalFrame)
unregisterFrame((JInternalFrame) comp);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseWheelMoved(e);
}
@Override
public void mouseDragged(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseDragged(e);
}
@Override
public void mouseMoved(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseMoved(e);
}
@Override
public void mouseClicked(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseClicked(e);
}
@Override
public void mousePressed(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mousePressed(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseReleased(e);
}
@Override
public void mouseEntered(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseEntered(e);
}
@Override
public void mouseExited(MouseEvent e)
{
// send to overlays
for (DesktopOverlay overlay : overlays)
overlay.mouseExited(e);
}
}