Merge branch 'JAL-3878_ws-overhaul-3' into with_ws_overhaul-3
[jalview.git] / src / jalview / gui / SplashScreen.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.Font;
28 import java.awt.Graphics;
29 import java.awt.Image;
30 import java.awt.MediaTracker;
31 import java.awt.event.MouseAdapter;
32 import java.awt.event.MouseEvent;
33
34 import javax.swing.JInternalFrame;
35 import javax.swing.JLabel;
36 import javax.swing.JLayeredPane;
37 import javax.swing.JPanel;
38 import javax.swing.JTextPane;
39 import javax.swing.event.HyperlinkEvent;
40 import javax.swing.event.HyperlinkListener;
41
42 import jalview.util.ChannelProperties;
43 import jalview.util.Platform;
44
45 import javajs.async.SwingJSUtils.StateHelper;
46 import javajs.async.SwingJSUtils.StateMachine;
47
48 /**
49  * DOCUMENT ME!
50  * 
51  * @author $author$
52  * @version $Revision$
53  */
54 @SuppressWarnings("serial")
55 public class SplashScreen extends JPanel
56         implements HyperlinkListener, StateMachine
57 {
58   
59   private static final int STATE_INIT = 0;
60
61   private static final int STATE_LOOP = 1;
62
63   private static final int STATE_DONE = 2;
64   private static final int SHOW_FOR_SECS = 5;
65
66   private static final int FONT_SIZE = (Platform.isJS() ? 14 : 11);
67
68   private boolean visible = true;
69
70   private JPanel iconimg = new JPanel(new BorderLayout());
71
72   // could change fg, bg, font later to use ChannelProperties (these are not
73   // actually being used!)
74   private static Color bg = Color.WHITE;
75
76   private static Color fg = Color.BLACK;
77
78   private static Font font = new Font("SansSerif", Font.PLAIN, FONT_SIZE);
79
80   private JPanel imgPanel = new JPanel(new BorderLayout());
81
82   /*
83    * as JTextPane in Java, JLabel in javascript
84    */
85   private Component splashText;
86
87   private JInternalFrame iframe;
88
89   private Image image, logo;
90
91   private boolean transientDialog = false;
92
93   private long oldTextLength = -1;
94
95   private StateHelper helper;
96   public static int logoSize = 32;
97
98   /*
99    * allow click in the initial splash screen to dismiss it
100    * immediately (not if opened from About menu)
101    */
102   private MouseAdapter closer = new MouseAdapter()
103   {
104     @Override
105     public void mousePressed(MouseEvent evt)
106     {
107       if (transientDialog)
108       {
109         try
110         {
111           closeSplash();
112         } catch (Exception ex)
113         {
114         }
115       }
116     }
117   };
118
119   /**
120    * Constructor that displays the splash screen
121    * 
122    * @param isStartup
123    *          if true the panel removes itself on click or after a few seconds;
124    *          if false it stays up until closed by the user (from Help..About menu)
125    */
126   public SplashScreen(boolean isStartup)
127   {
128     this.transientDialog = isStartup;
129     // we must get the image in JavaScript BEFORE starting the helper,
130     // as it will take a 1 ms clock tick to obtain width and height information.
131     image = ChannelProperties.getImage("banner");
132     logo = ChannelProperties.getImage("logo.48");
133     font = new Font("SansSerif", Font.PLAIN, FONT_SIZE);
134     helper = new StateHelper(this);
135     helper.next(STATE_INIT);
136   }
137
138   protected void initSplashScreenWindow()
139   {
140     addMouseListener(closer);
141     waitForImages();
142     setLayout(new BorderLayout());
143     iframe = new JInternalFrame();
144     iframe.setFrameIcon(null);
145     iframe.setClosable(true);
146     iframe.setContentPane(this);
147     iframe.setLayer(JLayeredPane.PALETTE_LAYER);  
148     SplashImage splashimg = new SplashImage(image);
149     imgPanel.add(splashimg, BorderLayout.CENTER);
150     add(imgPanel, BorderLayout.NORTH);
151     Desktop.getDesktopPane().add(iframe);
152     refreshText();
153   }
154
155   /**
156    * Both Java and JavaScript have to wait for images, but this method will
157    * accomplish nothing for JavaScript. We have already taken care of image
158    * loading with our state loop in JavaScript.
159    * 
160    */
161   private void waitForImages()
162   {
163     if (Platform.isJS())
164       return;
165     MediaTracker mt = new MediaTracker(this);
166     mt.addImage(image, 0);
167     mt.addImage(logo, 1);
168     do
169     {
170       try
171       {
172         mt.waitForAll();
173       } catch (InterruptedException x)
174       {
175       }
176       if (mt.isErrorAny())
177       {
178         System.err.println("Error when loading images!");
179         break;
180       }
181     } while (!mt.checkAll());
182     if (logo != null)
183     {
184       Desktop.getInstance().setIconImage(logo);
185     }
186
187     this.setBackground(bg);
188     this.setForeground(fg);
189     this.setFont(font);
190   }
191
192   /**
193    * update text in author text panel reflecting current version information
194    */
195   protected boolean refreshText()
196   {
197     String newtext = Desktop.getInstance().getAboutMessage();
198     // System.err.println("Text found: \n"+newtext+"\nEnd of newtext.");
199     if (oldTextLength == newtext.length())
200     {
201       return false;
202     }
203   
204     iframe.setVisible(false);
205     oldTextLength = newtext.length();
206     if (Platform.isJS()) // BH 2019
207     {
208       /*
209        * SwingJS doesn't have HTMLEditorKit, required for a JTextPane
210        * to display formatted html, so we use a simple alternative
211        */
212       String text = "<html><br><img src=\""
213               + ChannelProperties.getImageURL("banner") + "\"/>" + newtext
214               + "<br></html>";
215       JLabel ta = new JLabel(text);
216       ta.setOpaque(true);
217       ta.setBackground(Color.white);
218       splashText = ta;
219     }
220     else
221     /**
222      * Java only
223      *
224      * @j2sIgnore
225      */
226     {
227       JTextPane jtp = new JTextPane();
228       jtp.setEditable(false);
229       jtp.setBackground(bg);
230       jtp.setForeground(fg);
231       jtp.setFont(font);
232       jtp.setContentType("text/html");
233       jtp.setText("<html>" + newtext + "</html>");
234       jtp.addHyperlinkListener(this);
235       splashText = jtp;
236     }
237     splashText.addMouseListener(closer);
238
239     splashText.setVisible(true);
240     splashText.setSize(new Dimension(750,
241             375 + logoSize + (Platform.isJS() ? 40 : 0)));
242     splashText.setBackground(bg);
243     splashText.setForeground(fg);
244     splashText.setFont(font);
245     add(splashText, BorderLayout.CENTER);
246     revalidate();
247     int width = Math.max(splashText.getWidth(), iconimg.getWidth());
248     int height = splashText.getHeight() + iconimg.getHeight();
249     iframe.setBounds((iframe.getParent().getWidth() - width) / 2,
250             (iframe.getParent().getHeight() - height) / 2,
251             width,height);
252     iframe.validate();
253     iframe.setVisible(true);
254     return true;
255   }
256
257   protected void closeSplash()
258   {
259     try
260     {
261
262       iframe.setClosed(true);
263     } catch (Exception ex)
264     {
265     }
266   }
267
268   /**
269    * A simple state machine with just three states: init, loop, and done. Ideal
270    * for a simple while/sleep loop that works in Java and JavaScript
271    * identically.
272    * 
273    */
274   @Override
275   public boolean stateLoop()
276   {
277     while (true)
278     {
279       switch (helper.getState())
280       {
281       case STATE_INIT:
282         initSplashScreenWindow();
283         helper.setState(STATE_LOOP);
284         continue;
285       case STATE_LOOP:
286         if (!isVisible())
287         {
288           helper.setState(STATE_DONE);
289           continue;
290         }
291         if (refreshText())
292         {
293           iframe.repaint();
294         }
295         if (transientDialog)
296           helper.delayedState(SHOW_FOR_SECS * 1000, STATE_DONE);
297         return true;
298       default:
299       case STATE_DONE:
300         setVisible(false);
301         closeSplash();
302         Desktop.getInstance().startDialogQueue();
303         return true;
304       }
305     }
306
307   }
308
309   private class SplashImage extends JPanel
310   {
311     Image image;
312
313     public SplashImage(Image todisplay)
314     {
315       image = todisplay;
316       if (image != null)
317       {
318         setPreferredSize(new Dimension(image.getWidth(this) + 8,
319                 image.getHeight(this)));
320       }
321     }
322
323     @Override
324     public Dimension getPreferredSize()
325     {
326       return new Dimension(image.getWidth(this) + 8, image.getHeight(this));
327     }
328
329     @Override
330     public void paintComponent(Graphics g)
331     {
332       g.setColor(bg);
333       g.fillRect(0, 0, getWidth(), getHeight());
334       g.setColor(fg);
335       g.setFont(new Font(font.getFontName(), Font.BOLD, FONT_SIZE + 6));
336
337       if (image != null)
338       {
339         g.drawImage(image, (getWidth() - image.getWidth(this)) / 2,
340                 (getHeight() - image.getHeight(this)) / 2, this);
341       }
342     }
343   }
344
345   @Override
346   public void hyperlinkUpdate(HyperlinkEvent e)
347   {
348     Desktop.hyperlinkUpdate(e);
349
350   }
351 }