001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2023
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.ui;
030
031
032import java.awt.Color;
033import java.awt.Dimension;
034import java.awt.Font;
035import java.awt.Insets;
036import java.awt.Toolkit;
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039import java.util.ArrayList;
040import java.util.Hashtable;
041import java.util.List;
042import java.util.Random;
043
044import javax.swing.BorderFactory;
045import javax.swing.JButton;
046import javax.swing.JCheckBox;
047import javax.swing.JDialog;
048import javax.swing.JEditorPane;
049import javax.swing.JLabel;
050import javax.swing.JMenu;
051import javax.swing.JMenuBar;
052import javax.swing.JMenuItem;
053import javax.swing.JPanel;
054import javax.swing.JScrollPane;
055import javax.swing.border.BevelBorder;
056import javax.swing.event.HyperlinkEvent;
057import javax.swing.event.HyperlinkListener;
058import javax.swing.text.html.HTMLDocument;
059
060import org.w3c.dom.Element;
061import org.w3c.dom.Node;
062
063import edu.wisc.ssec.mcidasv.Constants;
064import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
065
066import ucar.unidata.util.GuiUtils;
067import ucar.unidata.util.ObjectListener;
068import ucar.unidata.xml.XmlObjectStore;
069import ucar.unidata.xml.XmlResourceCollection;
070import ucar.unidata.xml.XmlUtil;
071
072
073/**
074 * Represents a dialog that holds {@literal "help tips"}. This class is based
075 * upon {@link ucar.unidata.ui.HelpTipDialog}, but adds new functionality:
076 * <ul>
077 * <li>tip counter</li>
078 * <li>formatting</li>
079 * <li>random tips</li>
080 * </ul>
081 */
082@SuppressWarnings("serial")
083public class McvHelpTipDialog extends JDialog implements Constants, 
084    HyperlinkListener 
085{
086
087        /** help tip preference */
088        public static final String PREF_HELPTIPSHOW = "help.helptip.Show";
089
090        /** help tip index */
091        public static final String PREF_HELPTIPIDX = "help.helptip.Index";
092
093        /** list of tips */
094        private List helpTips = new ArrayList();
095
096        /** resources */
097        private XmlResourceCollection resources;
098
099        /** index */
100        private int idx = 0;
101
102        /** count */
103        private JLabel counterText;
104
105        /** label */
106        private JLabel titleText;
107
108        /** message */
109        private JEditorPane messageText;
110
111        /** checkbox */
112        private JCheckBox showCbx;
113
114        /** store */
115        private XmlObjectStore store;
116
117        /** action listener */
118        private ActionListener actionListener;
119
120        /**
121         * Create the HelpTipDialog
122         *
123         * @param resources    list of XML resources
124         * @param actionListener  listener for changes
125         * @param store           store for persistence
126         * @param origin          calling class
127         * @param showByDefault   true to show by default
128         *
129         */
130        public McvHelpTipDialog(XmlResourceCollection resources,
131                        ActionListener actionListener, XmlObjectStore store,
132                        Class origin, boolean showByDefault) {
133
134                this.actionListener = actionListener;
135                this.resources      = resources;
136                if ((resources == null) || (resources.size() == 0)) {
137                        return;
138                }
139                this.store = store;
140
141                String title = null;
142                String icon  = null;
143
144                for (int i = 0; i < resources.size(); i++) {
145                        Element helpTipRoot = resources.getRoot(i);
146                        if (helpTipRoot == null) {
147                                continue;
148                        }
149                        if (title == null) {
150                                title = XmlUtil.getAttribute(helpTipRoot, "title", (String) null);
151                        }
152                        if (icon == null) {
153                                icon = XmlUtil.getAttribute(helpTipRoot, "icon", (String) null);
154                        }
155                        helpTips.addAll(XmlUtil.findChildren(helpTipRoot, "helptip"));
156                }
157
158                if (title == null) {
159                        title = "McIDAS-V Help Tips";
160                }
161                setTitle(title);
162                if (icon == null) {
163                        icon = "/edu/wisc/ssec/mcidasv/resources/icons/toolbar/dialog-information32.png";
164                }
165                JLabel imageLabel = GuiUtils.getImageLabel(icon);
166
167                // Build the "Tips" menu
168                JMenu     topMenu    = new JMenu("Tips");
169                Hashtable menus      = new Hashtable();
170                menus.put("top", topMenu);
171                for (int i = 0; i < helpTips.size(); i++) {
172                        Element helpTip = (Element) helpTips.get(i);
173                        String tipTitle = XmlUtil.getAttribute(helpTip, "title", (String) null);
174                        if (tipTitle == null) {
175                                tipTitle = getMessage(helpTip).substring(0, 20);
176                        }
177                        if (tipTitle.trim().length() == 0) {
178                                continue;
179                        }
180
181                        String category = XmlUtil.getAttribute(helpTip, "category", "None");
182                        JMenu m = (JMenu) menus.get(category);
183                        if (m == null) {
184                                m = new JMenu(category);
185                                menus.put(category, m);
186                                topMenu.add(m);
187                        }
188                        JMenuItem mi = new JMenuItem(tipTitle);
189                        mi.addActionListener(new ObjectListener(new Integer(i)) {
190                                public void actionPerformed(ActionEvent ae) {
191                                        idx = ((Integer) theObject).intValue();
192                                        showTip();
193                                }
194                        });
195                        m.add(mi);
196                }
197
198                titleText = new JLabel("Title");
199                counterText = McVGuiUtils.makeLabelRight("0/0");
200
201                JPanel topPanel = GuiUtils.left(
202                                GuiUtils.hbox(imageLabel, titleText, GAP_RELATED)
203                );
204
205                Dimension helpDimension = new Dimension(400, 200);
206                messageText     = new JEditorPane();
207                messageText.setMinimumSize(helpDimension);
208                messageText.setPreferredSize(helpDimension);
209                messageText.setEditable(false);
210                messageText.addHyperlinkListener(this);
211                messageText.setContentType("text/html");
212                Font font = javax.swing.UIManager.getFont("Label.font");
213                String rule = "body { font-family:"+font.getFamily()+"; font-size:"+font.getSize()+"pt; }";
214                ((HTMLDocument)messageText.getDocument()).getStyleSheet().addRule(rule);
215                //        messageText.setBackground(new JPanel().getBackground());
216                JScrollPane scroller = GuiUtils.makeScrollPane(messageText, 0, 0);
217                scroller.setBorder(BorderFactory.createLoweredBevelBorder());
218                scroller.setPreferredSize(helpDimension);
219                scroller.setMinimumSize(helpDimension);
220
221                showCbx = new JCheckBox("Show tips on startup", showByDefault);
222                showCbx.addActionListener(new ActionListener() {
223                        public void actionPerformed(ActionEvent ae) {
224                                writeShowNextTime();
225                        }
226                });
227
228                JPanel centerPanel = GuiUtils.center(scroller);
229
230                JButton prevBtn = McVGuiUtils.makeImageButton(ICON_PREVIOUS_SMALL, "Previous");
231                prevBtn.addActionListener(new ActionListener() {
232                        public void actionPerformed(ActionEvent event) {
233                                previous();
234                        }
235                });
236
237                JButton nextBtn = McVGuiUtils.makeImageButton(ICON_NEXT_SMALL, "Next");
238                nextBtn.addActionListener(new ActionListener() {
239                        public void actionPerformed(ActionEvent event) {
240                                next();
241                        }
242                });
243
244                JButton randBtn = McVGuiUtils.makeImageButton(ICON_RANDOM_SMALL, "Random");
245                randBtn.addActionListener(new ActionListener() {
246                        public void actionPerformed(ActionEvent event) {
247                                random();
248                        }
249                });
250
251                JButton closeBtn = McVGuiUtils.makePrettyButton("Close");
252                closeBtn.addActionListener(new ActionListener() {
253                        public void actionPerformed(ActionEvent event) {
254                                close();
255                        }
256                });
257
258//              JPanel navBtns = GuiUtils.hbox(prevBtn, randBtn, nextBtn, GAP_RELATED);
259                JPanel navBtns = GuiUtils.hbox(counterText, prevBtn, nextBtn, GAP_RELATED);
260                JPanel bottomPanel = GuiUtils.leftRight(showCbx, navBtns);
261
262                JMenuBar bar = new JMenuBar();
263                bar.add(topMenu);
264                add("North", bar);
265
266                JPanel contents = GuiUtils.topCenterBottom(
267                                GuiUtils.inset(topPanel, GAP_RELATED),
268                                GuiUtils.inset(centerPanel, new Insets(0, GAP_RELATED, 0, GAP_RELATED)),
269                                GuiUtils.inset(bottomPanel, GAP_RELATED)
270                );
271                contents.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED,
272                                Color.gray, Color.gray));
273
274                JPanel bottom = new JPanel();
275                bottom.add(closeBtn);
276                add(GuiUtils.centerBottom(contents, bottom));
277                pack();
278
279                random();
280                Dimension size = getSize();
281                Dimension ss   = Toolkit.getDefaultToolkit().getScreenSize();
282                setLocation(ss.width / 2 - size.width / 2, ss.height / 2 - size.height / 2);
283
284                setMinimumSize(helpDimension);
285                setVisible(true);
286        }
287
288        /**
289         * Write show next time
290         */
291        public void writeShowNextTime() {
292                if (getStore().get(PREF_HELPTIPSHOW, true) != showCbx.isSelected()) {
293                        getStore().put(PREF_HELPTIPSHOW, showCbx.isSelected());
294                        getStore().save();
295                }
296        }
297
298        /**
299         * Close the dialog
300         */
301        public void close() {
302                writeShowNextTime();
303                setVisible(false);
304        }
305
306        /**
307         * Get the persistence store
308         * @return  the persistence
309         */
310        public XmlObjectStore getStore() {
311                return store;
312        }
313
314        /**
315         * Go to the next tip.
316         */
317        private void previous() {
318                idx--;
319                showTip();
320        }
321
322        /**
323         * Go to the next tip.
324         */
325        private void next() {
326                idx++;
327                showTip();
328        }
329
330        /**
331         * Go to the next tip.
332         */
333        private void random() {
334                Random rand = new Random();
335                idx = rand.nextInt(helpTips.size());
336                showTip();
337        }
338
339        /**
340         * Handle a change to a link
341         *
342         * @param e  the link's event
343         */
344        public void hyperlinkUpdate(HyperlinkEvent e) {
345                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
346                        if (e.getURL() == null) {
347                                click(e.getDescription());
348                        } else {
349                                click(e.getURL().toString());
350                        }
351                }
352        }
353
354        /**
355         * Handle a click on a link
356         *
357         * @param url  the link definition
358         */
359        public void click(String url) {
360                actionListener.actionPerformed(new ActionEvent(this, 0, url));
361        }
362
363        /**
364         * Get the title for this tip
365         *
366         * @param helpTip  the tip node
367         * @return  the title
368         */
369        private String getTitle(Node helpTip) {
370                String title = XmlUtil.getAttribute(helpTip, "title", (String) null);
371                if (title == null) {
372                        title = "";
373                }
374                return "<html><h2>" + title + "</h2></html>";
375        }
376
377        /**
378         * Get the message for this tip
379         *
380         * @param helpTip  the tip node
381         * @return  the message
382         */
383        private String getMessage(Node helpTip) {
384                String message = XmlUtil.getAttribute(helpTip, "message", (String) null);
385                if (message == null) {
386                        message = XmlUtil.getChildText(helpTip);
387                }
388                return message;
389        }
390
391        /**
392         * Show the current tip.
393         */
394        private void showTip() {
395
396                // Show the first tip if we have no tip history
397                if (getStore().get(PREF_HELPTIPIDX, -1) < 0) idx=0;
398
399                if (helpTips.size() == 0) {
400                        return;
401                }
402                if (idx >= helpTips.size()) {
403                        idx = 0;
404                        getStore().put(PREF_HELPTIPIDX, idx);
405                } else if (idx < 0) {
406                        idx = helpTips.size() - 1;
407                }
408                Node   helpTip = (Node) helpTips.get(idx);
409
410                counterText.setText((int)(idx+1) + "/" + helpTips.size());
411                titleText.setText(getTitle(helpTip));
412                messageText.setText(getMessage(helpTip));
413
414                getStore().put(PREF_HELPTIPIDX, idx);
415                getStore().save();
416        }
417
418}