001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.main;
020
021import icy.gui.frame.IcyInternalFrame;
022import icy.gui.util.ComponentUtil;
023import icy.gui.util.LookAndFeelUtil;
024import icy.gui.viewer.Viewer;
025import icy.main.Icy;
026import icy.math.HungarianAlgorithm;
027import icy.resource.ResourceUtil;
028import icy.util.GraphicsUtil;
029import icy.util.Random;
030
031import java.awt.AlphaComposite;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Graphics;
035import java.awt.Graphics2D;
036import java.awt.Image;
037import java.awt.Rectangle;
038import java.awt.RenderingHints;
039import java.awt.event.ComponentAdapter;
040import java.awt.event.ComponentEvent;
041import java.awt.event.ContainerEvent;
042import java.awt.event.ContainerListener;
043import java.awt.event.MouseAdapter;
044import java.awt.event.MouseEvent;
045import java.awt.event.MouseListener;
046import java.awt.event.MouseMotionListener;
047import java.awt.event.MouseWheelEvent;
048import java.awt.event.MouseWheelListener;
049import java.awt.geom.Point2D;
050import java.awt.image.ImageObserver;
051import java.util.ArrayList;
052import java.util.List;
053
054import javax.swing.JDesktopPane;
055import javax.swing.JInternalFrame;
056
057/**
058 * Icy {@link JDesktopPane} class.<br>
059 * This is the main container of the application.<br>
060 * It support overlays so we can use to display message, notification or logo in
061 * background. First added overlays is painted first, so take care of that. Call
062 * the IcyDesktopPane.repaint() method to update overlays.
063 * 
064 * @author Fabrice & Stephane
065 */
066public class IcyDesktopPane extends JDesktopPane implements ContainerListener, MouseListener, MouseMotionListener,
067        MouseWheelListener
068{
069    public static interface DesktopOverlay extends MouseListener, MouseMotionListener, MouseWheelListener
070    {
071        public void paint(Graphics g, int width, int height);
072    }
073
074    public static class AbstractDesktopOverlay extends MouseAdapter implements DesktopOverlay
075    {
076        @Override
077        public void paint(Graphics g, int width, int height)
078        {
079            // nothing by default
080        }
081    }
082
083    /**
084     * Background overlay.
085     */
086    public class BackgroundDesktopOverlay extends AbstractDesktopOverlay implements ImageObserver
087    {
088        private final static String BACKGROUND_PATH = "background/";
089
090        private final Image backGround;
091        private final Image icyLogo;
092        private final Color textColor;
093        private final Color bgTextColor;
094
095        public BackgroundDesktopOverlay()
096        {
097            super();
098
099            // load random background (nor really random as we have only one right now)
100            backGround = ResourceUtil.getImage(BACKGROUND_PATH + Integer.toString(Random.nextInt(1)) + ".jpg");
101            // load Icy logo
102            icyLogo = ResourceUtil.getImage("logoICY.png");
103
104            // default text colors
105            textColor = new Color(0, 0, 0, 0.5f);
106            bgTextColor = new Color(1, 1, 1, 0.5f);
107        }
108
109        @Override
110        public void paint(Graphics g, int width, int height)
111        {
112            final IcyDesktopPane desktop = Icy.getMainInterface().getDesktopPane();
113            final Color bgColor;
114
115            if (desktop != null)
116                bgColor = LookAndFeelUtil.getBackground(desktop);
117            else
118                bgColor = Color.lightGray;
119
120            final int bgImgWidth = backGround.getWidth(this);
121            final int bgImgHeight = backGround.getHeight(this);
122
123            // compute image scaling
124            final double scale = Math.max((double) width / (double) bgImgWidth, (double) height / (double) bgImgHeight) * 1.5d;
125            final Graphics2D g2 = (Graphics2D) g.create();
126
127            // fill background color
128            g2.setBackground(bgColor);
129            g2.clearRect(0, 0, width, height);
130
131            // paint image over background in transparency
132            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.2f));
133            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
134            g2.drawImage(backGround, 0, 0, (int) (scale * bgImgWidth), (int) (scale * bgImgHeight), bgColor, this);
135
136            final String text = "Version " + Icy.version;
137            final int textWidth = (int) GraphicsUtil.getStringBounds(g, text).getWidth();
138
139            // draw version text
140            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.7f));
141            g2.setColor(bgTextColor);
142            g2.drawString(text, width - (textWidth + 31), height - 8);
143            g2.setColor(textColor);
144            g2.drawString(text, width - (textWidth + 30), height - 9);
145            // and draw Icy text logo
146            g2.drawImage(icyLogo, width - 220, height - 130, this);
147
148            g2.dispose();
149        }
150
151        @Override
152        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
153        {
154            if ((infoflags & ImageObserver.ALLBITS) != 0)
155            {
156                repaint();
157                return false;
158            }
159
160            return true;
161        }
162    }
163
164    private static final long serialVersionUID = 7914161180763257329L;
165
166    private final ComponentAdapter componentAdapter;
167    // private final InternalFrameAdapter internalFrameAdapter;
168
169    private final ArrayList<DesktopOverlay> overlays;
170
171    public IcyDesktopPane()
172    {
173        super();
174
175        overlays = new ArrayList<DesktopOverlay>();
176
177        // setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
178
179        componentAdapter = new ComponentAdapter()
180        {
181            @Override
182            public void componentResized(ComponentEvent e)
183            {
184                checkPosition((JInternalFrame) e.getSource());
185            }
186
187            @Override
188            public void componentMoved(ComponentEvent e)
189            {
190                checkPosition((JInternalFrame) e.getSource());
191            }
192        };
193
194        addMouseListener(this);
195        addMouseMotionListener(this);
196        addMouseWheelListener(this);
197        addContainerListener(this);
198
199        // add the background overlay
200        overlays.add(new BackgroundDesktopOverlay());
201    }
202
203    // int i = 0;
204
205    @Override
206    public void paintComponent(Graphics g)
207    {
208        super.paintComponent(g);
209
210        final int w = getWidth();
211        final int h = getHeight();
212
213        // paint overlays
214        for (DesktopOverlay overlay : overlays)
215            overlay.paint(g, w, h);
216    }
217
218    private void registerFrame(JInternalFrame frame)
219    {
220        frame.addComponentListener(componentAdapter);
221    }
222
223    void unregisterFrame(JInternalFrame frame)
224    {
225        frame.removeComponentListener(componentAdapter);
226    }
227
228    void checkPosition(JInternalFrame frame)
229    {
230        final Rectangle rect = frame.getBounds();
231
232        if (fixPosition(rect))
233            frame.setBounds(rect);
234    }
235
236    boolean fixPosition(Rectangle rect)
237    {
238        final int limit = getY();
239        if (rect.y < limit)
240        {
241            rect.y = limit;
242            return true;
243        }
244
245        return false;
246    }
247
248    /**
249     * Returns the list of internal viewers.
250     * 
251     * @param wantNotVisible
252     *        Also return not visible viewers
253     * @param wantIconized
254     *        Also return iconized viewers
255     */
256    public static Viewer[] getInternalViewers(boolean wantNotVisible, boolean wantIconized)
257    {
258        final List<Viewer> result = new ArrayList<Viewer>();
259
260        for (Viewer viewer : Icy.getMainInterface().getViewers())
261        {
262            if (viewer.isInternalized())
263            {
264                final IcyInternalFrame internalFrame = viewer.getIcyInternalFrame();
265
266                if ((wantNotVisible || internalFrame.isVisible()) && (wantIconized || !internalFrame.isIcon()))
267                    result.add(viewer);
268            }
269        }
270
271        return result.toArray(new Viewer[result.size()]);
272    }
273
274    /**
275     * Organize all internal viewers in cascade
276     */
277    public void organizeCascade()
278    {
279        // get internal viewers
280        final Viewer[] viewers = getInternalViewers(false, false);
281
282        // available space (always keep 32 available pixels at south)
283        final int w = getWidth();
284        final int h = getHeight() - 32;
285
286        final int fw = (int) (w * 0.6f);
287        final int fh = (int) (h * 0.6f);
288
289        final int xMax = w - 0;
290        final int yMax = h - 0;
291
292        int x = 0 + 32;
293        int y = 0 + 32;
294
295        for (Viewer v : viewers)
296        {
297            final IcyInternalFrame internalFrame = v.getIcyInternalFrame();
298
299            if (internalFrame.isMaximized())
300                internalFrame.setMaximized(false);
301            internalFrame.setBounds(x, y, fw, fh);
302            internalFrame.toFront();
303
304            x += 30;
305            y += 20;
306            if ((x + fw) > xMax)
307                x = 32;
308            if ((y + fh) > yMax)
309                y = 32;
310        }
311    }
312
313    /**
314     * Organize all internal viewers in tile.
315     * 
316     * @param type
317     *        tile type<br>
318     *        MainFrame.TILE_HORIZONTAL, MainFrame.TILE_VERTICAL or
319     *        MainFrame.TILE_GRID
320     */
321    public void organizeTile(int type)
322    {
323        // get internal viewers
324        final Viewer[] viewers = getInternalViewers(false, false);
325
326        final int numFrames = viewers.length;
327
328        // nothing to do
329        if (numFrames == 0)
330            return;
331
332        // available space (always keep 32 available pixels at south)
333        final int w = getWidth();
334        final int h = getHeight() - 32;
335
336        int numCol;
337        int numLine;
338
339        switch (type)
340        {
341            case MainFrame.TILE_HORIZONTAL:
342                numCol = 1;
343                numLine = numFrames;
344                break;
345
346            case MainFrame.TILE_VERTICAL:
347                numCol = numFrames;
348                numLine = 1;
349                break;
350
351            default:
352                numCol = (int) Math.sqrt(numFrames);
353                if (numFrames != (numCol * numCol))
354                    numCol++;
355                numLine = numFrames / numCol;
356                if (numFrames > (numCol * numLine))
357                    numLine++;
358                break;
359        }
360
361        final double[][] framesDistances = new double[numCol * numLine][numFrames];
362
363        final int dx = w / numCol;
364        final int dy = h / numLine;
365        int k = 0;
366
367        for (int i = 0; i < numLine; i++)
368        {
369            for (int j = 0; j < numCol; j++, k++)
370            {
371                final double[] distances = framesDistances[k];
372                final double x = (j * dx) + (dx / 2d);
373                final double y = (i * dy) + (dy / 2d);
374
375                for (int f = 0; f < numFrames; f++)
376                {
377                    final Point2D.Double center = ComponentUtil.getCenter(viewers[f].getInternalFrame());
378                    distances[f] = Point2D.distanceSq(center.x, center.y, x, y);
379                }
380            }
381        }
382
383        final int[] framePos = new HungarianAlgorithm(framesDistances).resolve();
384
385        k = 0;
386        for (int i = 0; i < numLine; i++)
387        {
388            for (int j = 0; j < numCol; j++, k++)
389            {
390                final int f = framePos[k];
391
392                if (f < numFrames)
393                {
394                    final IcyInternalFrame internalFrame = viewers[f].getIcyInternalFrame();
395
396                    if (internalFrame.isMaximized())
397                        internalFrame.setMaximized(false);
398                    internalFrame.setBounds(j * dx, i * dy, dx, dy);
399                    internalFrame.toFront();
400                }
401            }
402        }
403    }
404
405    /**
406     * @deprecated Use {@link #organizeTile(int)} instead.
407     */
408    @Deprecated
409    public void organizeTile()
410    {
411        organizeTile(MainFrame.TILE_GRID);
412    }
413
414    /**
415     * Add the specified overlay to the desktop.
416     */
417    public void addOverlay(DesktopOverlay overlay)
418    {
419        if (!overlays.contains(overlay))
420            overlays.add(overlay);
421    }
422
423    /**
424     * remove the specified overlay from the desktop.
425     */
426    public boolean removeOverlay(DesktopOverlay overlay)
427    {
428        return overlays.remove(overlay);
429    }
430
431    @Override
432    public void componentAdded(ContainerEvent e)
433    {
434        final Component comp = e.getChild();
435
436        if (comp instanceof JInternalFrame)
437            registerFrame((JInternalFrame) comp);
438    }
439
440    @Override
441    public void componentRemoved(ContainerEvent e)
442    {
443        final Component comp = e.getChild();
444
445        if (comp instanceof JInternalFrame)
446            unregisterFrame((JInternalFrame) comp);
447    }
448
449    @Override
450    public void mouseWheelMoved(MouseWheelEvent e)
451    {
452        // send to overlays
453        for (DesktopOverlay overlay : overlays)
454            overlay.mouseWheelMoved(e);
455    }
456
457    @Override
458    public void mouseDragged(MouseEvent e)
459    {
460        // send to overlays
461        for (DesktopOverlay overlay : overlays)
462            overlay.mouseDragged(e);
463    }
464
465    @Override
466    public void mouseMoved(MouseEvent e)
467    {
468        // send to overlays
469        for (DesktopOverlay overlay : overlays)
470            overlay.mouseMoved(e);
471    }
472
473    @Override
474    public void mouseClicked(MouseEvent e)
475    {
476        // send to overlays
477        for (DesktopOverlay overlay : overlays)
478            overlay.mouseClicked(e);
479    }
480
481    @Override
482    public void mousePressed(MouseEvent e)
483    {
484        // send to overlays
485        for (DesktopOverlay overlay : overlays)
486            overlay.mousePressed(e);
487    }
488
489    @Override
490    public void mouseReleased(MouseEvent e)
491    {
492        // send to overlays
493        for (DesktopOverlay overlay : overlays)
494            overlay.mouseReleased(e);
495    }
496
497    @Override
498    public void mouseEntered(MouseEvent e)
499    {
500        // send to overlays
501        for (DesktopOverlay overlay : overlays)
502            overlay.mouseEntered(e);
503    }
504
505    @Override
506    public void mouseExited(MouseEvent e)
507    {
508        // send to overlays
509        for (DesktopOverlay overlay : overlays)
510            overlay.mouseExited(e);
511    }
512}