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}