001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.ui;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Container;
035import java.awt.Cursor;
036import java.awt.Dimension;
037import java.awt.Font;
038import java.awt.FontMetrics;
039import java.awt.Graphics;
040import java.awt.Image;
041import java.awt.Insets;
042import java.awt.Rectangle;
043import java.awt.event.ActionEvent;
044import java.awt.event.ActionListener;
045import java.awt.event.KeyAdapter;
046import java.awt.event.KeyEvent;
047import java.awt.event.KeyListener;
048import java.awt.event.MouseAdapter;
049import java.awt.event.MouseEvent;
050import java.awt.event.MouseListener;
051import java.awt.image.ImageObserver;
052import java.util.ArrayList;
053import java.util.Hashtable;
054import java.util.List;
055
056import javax.swing.BorderFactory;
057import javax.swing.BoxLayout;
058import javax.swing.ButtonGroup;
059import javax.swing.ImageIcon;
060import javax.swing.JButton;
061import javax.swing.JComponent;
062import javax.swing.JLabel;
063import javax.swing.JMenuItem;
064import javax.swing.JPanel;
065import javax.swing.JPopupMenu;
066import javax.swing.JScrollPane;
067import javax.swing.JToggleButton;
068import javax.swing.SwingConstants;
069import javax.swing.border.Border;
070import javax.swing.border.EtchedBorder;
071
072import edu.wisc.ssec.mcidasv.Constants;
073
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076import ucar.unidata.idv.DisplayControl;
077import ucar.unidata.idv.IdvConstants;
078import ucar.unidata.idv.IdvManager;
079import ucar.unidata.idv.IntegratedDataViewer;
080import ucar.unidata.idv.MapViewManager;
081import ucar.unidata.idv.TransectViewManager;
082import ucar.unidata.idv.ViewManager;
083import ucar.unidata.idv.control.DisplayControlImpl;
084import ucar.unidata.idv.control.MapDisplayControl;
085import ucar.unidata.idv.ui.IdvComponentGroup;
086import ucar.unidata.idv.ui.IdvWindow;
087import ucar.unidata.idv.ui.ViewPanel;
088import ucar.unidata.ui.ComponentHolder;
089import ucar.unidata.ui.DndImageButton;
090import ucar.unidata.util.GuiUtils;
091import ucar.unidata.util.Misc;
092import ucar.unidata.util.Msg;
093import ucar.unidata.util.StringUtil;
094
095/**
096 * This class has largely been copied over wholesale from the IDV code.
097 * Merely extending was proving to be as much as a hassle as just copying it, 
098 * though now we still maintain complete control over the ViewPanel, and we have 
099 * an obvious point of departure for whenever the JTree is started.
100 *
101 * <p>That said, I personally recommend avoiding this class until the JTree 
102 * stuff is ready to go.</p>
103 */
104public class McIDASVViewPanel extends IdvManager implements ViewPanel {
105    
106    private static final Image BUTTON_ICON =
107        GuiUtils.getImage("/auxdata/ui/icons/Selected.gif");
108    
109    private static final ImageIcon CATEGORY_OPEN_ICON =
110        GuiUtils.getImageIcon("/auxdata/ui/icons/CategoryOpen.gif");
111    
112    private static final ImageIcon CATEGORY_CLOSED_ICON =
113        GuiUtils.getImageIcon("/auxdata/ui/icons/CategoryClosed.gif");
114    
115    private static final Border BUTTON_BORDER =
116        BorderFactory.createEmptyBorder(2, 6, 2, 0);
117    
118    private static final Font BUTTON_FONT = new Font("Dialog", Font.PLAIN, 11);
119    private static final Color LINE_COLOR = Color.gray;
120    private static final Font CAT_FONT = new Font("Dialog", Font.BOLD, 11);
121    
122    /** The border for the header panel */
123    public static Border headerNormal =
124        BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,
125            0, 0, 0), BorderFactory.createMatteBorder(0, 0, 2, 0,
126            Color.black));
127    
128    /** highlight border for view infos */
129    public static Border headerHighlight =
130        BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,
131            0, 0, 0), BorderFactory.createMatteBorder(0, 0, 2, 0,
132            ViewManager.borderHighlightColor));
133    
134    private static Color fgColor = Color.black;
135    private static Color onColor = null;
136    private static boolean showPopup = true;
137    private static boolean showCategories = false;
138    
139    private JComponent contents;
140    
141    private JPanel leftPanel;
142    
143    private JPanel viewContainer;
144    
145    private ButtonGroup buttonGroup = new ButtonGroup();
146    
147    private GuiUtils.CardLayoutPanel rightPanel;
148    
149    private IntegratedDataViewer idv;
150    
151    private enum ViewManagers { DEFAULT, GLOBE, MAP, TRANSECT };
152    
153    private Hashtable<DisplayControl, ControlInfo> controlToInfo =
154        new Hashtable<DisplayControl, ControlInfo>();
155    
156    
157    private List<VMInfo> vmInfos = new ArrayList<VMInfo>();
158    
159    public McIDASVViewPanel(IntegratedDataViewer idv) {
160        super(idv);
161        
162        this.idv = idv;
163        
164    }
165    
166    public void createUI() {
167        leftPanel = new JPanel(new BorderLayout());
168        leftPanel.setBorder(
169            BorderFactory.createCompoundBorder(
170                BorderFactory.createEtchedBorder(EtchedBorder.LOWERED),
171                BorderFactory.createEmptyBorder(0, 0, 0, 0)));
172        
173        leftPanel.add(BorderLayout.NORTH, GuiUtils.filler(150, 1));
174        
175        viewContainer = new JPanel();
176        viewContainer.setLayout(new BoxLayout(viewContainer, BoxLayout.Y_AXIS));
177        
178        JScrollPane viewScroll = new JScrollPane(GuiUtils.top(viewContainer));
179        viewScroll.setBorder(null);
180        
181        leftPanel.add(BorderLayout.CENTER, viewScroll);
182        
183        rightPanel = new GuiUtils.CardLayoutPanel() {
184            public void show(Component comp) {
185                super.show(comp);
186                for (VMInfo vinfo : vmInfos) {
187                    for (ControlInfo cinfo : vinfo.controlInfos) {
188                        if (cinfo.outer == comp) {
189                            cinfo.button.setSelected(true);
190                            break;
191                        }
192                    }
193                }
194            }
195        };
196        
197        contents = GuiUtils.leftCenter(leftPanel, rightPanel);
198        Msg.translateTree(contents);
199    }
200    
201    public void selectNext(boolean up) {
202        boolean gotit  = false;
203        VMInfo  select = null;
204        int index  = 0;
205        for (int vmIdx = 0; !gotit && (vmIdx < vmInfos.size()); vmIdx++) {
206            
207            VMInfo vmInfo = vmInfos.get(vmIdx);
208            
209            List<ControlInfo> ctrlInfos = vmInfo.controlInfos;
210            
211            for (int i = 0; i < ctrlInfos.size(); i++) {
212                ControlInfo ci = ctrlInfos.get(i);
213                
214                if ( !ci.button.isSelected())
215                    continue;
216                
217                if (up) {
218                    if (vmInfo.getCatOpen() && (i > 0)) {
219                        select = vmInfo;
220                        index  = i - 1;
221                    } else {
222                        vmIdx--;
223                        while (vmIdx >= 0) {
224                            VMInfo prev = vmInfos.get(vmIdx--);
225                            if (prev.getCatOpen() && (prev.controlInfos.size() > 0)) {
226                                select = prev;
227                                index  = select.controlInfos.size() - 1;
228                                break;
229                            }
230                        }
231                    }
232                } else {
233                    if (vmInfo.getCatOpen() && (i < ctrlInfos.size() - 1)) {
234                        select = vmInfo;
235                        index  = i + 1;
236                    } else {
237                        vmIdx++;
238                        while (vmIdx < vmInfos.size()) {
239                            VMInfo next = vmInfos.get(vmIdx++);
240                            if (next.getCatOpen() && (next.controlInfos.size() > 0)) {
241                                select = next;
242                                index  = 0;
243                                break;
244                            }
245                        }
246                    }
247                }
248                gotit = true;
249                break;
250            }
251        }
252        
253        if ((select != null) && (index >= 0) && (index < select.controlInfos.size()))
254            select.controlInfos.get(index).button.doClick();
255    }
256    
257    public void addControlTab(DisplayControl control, boolean forceShow) {
258        if (!control.canBeDocked() || !control.shouldBeDocked())
259            return;
260        
261        //Check if there are any groups that have autoimport set
262        ViewManager viewManager = control.getViewManager();
263        if (viewManager != null) {
264            IdvWindow window = viewManager.getDisplayWindow();
265            if (window != null) {
266                List groups = window.getComponentGroups();
267                for (int i = 0; i < groups.size(); i++) {
268                    Object obj = groups.get(i);
269                    if (obj instanceof IdvComponentGroup) {
270                        if (((IdvComponentGroup) obj)
271                            .tryToImportDisplayControl(
272                                (DisplayControlImpl)control)) {
273                            return;
274                        }
275                    }
276                }
277            }
278        }
279        
280        ControlInfo info = controlToInfo.get(control);
281        if (info != null)
282            return;
283        
284        //For now cheat a little with the cast
285        ((DisplayControlImpl)control).setMakeWindow(false);
286        
287        JButton removeBtn =
288            GuiUtils.makeImageButton("/auxdata/ui/icons/Remove16.gif",
289                control, "doRemove");
290        removeBtn.setToolTipText("Remove Display Control");
291        
292        JButton expandBtn =
293            GuiUtils.makeImageButton("/auxdata/ui/icons/DownDown.gif", this,
294                "expandControl", control);
295        expandBtn.setToolTipText("Expand in the tabs");
296        
297        JButton exportBtn =
298            GuiUtils.makeImageButton("/auxdata/ui/icons/Export16.gif", this,
299                "undockControl", control);
300        exportBtn.setToolTipText("Undock control window");
301        
302        JButton propBtn =
303            GuiUtils.makeImageButton("/auxdata/ui/icons/Information16.gif",
304                control, "showProperties");
305        propBtn.setToolTipText("Show Display Control Properties");
306        
307        DndImageButton dnd = new DndImageButton(control, "idv/display");
308        dnd.setToolTipText("Drag and drop to a window component");
309//              JPanel buttonPanel =
310//                      GuiUtils.left(GuiUtils.hbox(Misc.newList(expandBtn, exportBtn,
311//                              propBtn, removeBtn, dnd), 4));
312        JPanel buttonPanel =
313            GuiUtils.left(GuiUtils.hbox(Misc.newList(exportBtn,
314                propBtn, removeBtn, dnd), 4));
315        
316        buttonPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0,
317            Color.lightGray.darker()));
318        
319        JComponent inner =
320            (JComponent) ((DisplayControlImpl)control).getOuterContents();
321        inner = GuiUtils.centerBottom(inner, buttonPanel);
322        JComponent outer = GuiUtils.top(inner);
323        outer.setBorder(BorderFactory.createEmptyBorder(2, 1, 0, 0));
324        
325        // the nested getVMInfo call will actually generate the jpanels that 
326        // get added to viewContainer, so we'll need to signal that 
327        // viewContainer needs to redraw
328        info = new ControlInfo(control, expandBtn, outer, inner,
329            getVMInfo(control.getDefaultViewManager()));
330        
331        // TODO(jon): maybe viewContainer.repack!?
332        viewContainer.revalidate();
333        
334        controlToInfo.put(control, info);
335        
336        GuiUtils.toggleHeavyWeightComponents(outer, false);
337        if (!getStateManager().getProperty(IdvConstants.PROP_LOADINGXML, false)) {
338            
339            //A hack for now
340            if (!(control instanceof MapDisplayControl)) {
341                GuiUtils.toggleHeavyWeightComponents(outer, true);
342                GuiUtils.showComponentInTabs(outer);
343            }
344            
345        }
346    }
347    
348    public void expandControl(DisplayControl control) {
349        ControlInfo info = controlToInfo.get(control);
350        if (info != null)
351            info.expand();
352    }
353    
354    public void dockControl(DisplayControl control) {
355        control.setShowInTabs(true);
356        ((DisplayControlImpl)control).guiImported();
357        addControlTab(control, true);
358    }
359    
360    public void undockControl(DisplayControl control) {
361        removeControlTab(control);
362        control.setShowInTabs(false);
363        ((DisplayControlImpl)control).setMakeWindow(true);
364        ((DisplayControlImpl)control).popup(null);
365    }
366    
367    public void controlMoved(DisplayControl control) {
368        removeControlTab(control);
369        addControlTab(control, true);
370    }
371    
372    public void removeControlTab(DisplayControl control) {
373        ControlInfo info = controlToInfo.remove(control);
374        if (info != null)
375            info.removeDisplayControl();
376    }
377    
378    public JComponent getContents() {
379        if (contents == null)
380            createUI();
381        
382        return contents;
383    }
384    
385    public void addDisplayControl(DisplayControl control) {
386        addControlTab(control, false);
387    }
388    
389    public void displayControlChanged(DisplayControl control) {
390        ControlInfo info = controlToInfo.get(control);
391        if (info != null)
392            info.displayControlChanged();
393    }
394    
395    public void removeDisplayControl(DisplayControl control) {
396        removeControlTab(control);
397    }
398    
399    public void addViewMenuItems(DisplayControl control, List items) {
400        if (!control.canBeDocked())
401            return;
402        
403        items.add(GuiUtils.MENU_SEPARATOR);
404        
405        if (!control.shouldBeDocked())
406            items.add(GuiUtils.makeMenuItem("Dock in Data Explorer", this, "dockControl", control));
407        else
408            items.add(GuiUtils.makeMenuItem("Undock from Data Explorer", this, "undockControl", control));
409        
410        List groups = getIdvUIManager().getComponentGroups();
411        List<JMenuItem> subItems = new ArrayList<JMenuItem>();
412        for (int i = 0; i < groups.size(); i++) {
413            IdvComponentGroup group = (IdvComponentGroup)groups.get(i);
414            subItems.add(GuiUtils.makeMenuItem(group.getHierachicalName(), group, "importDisplayControl", control));
415        }
416        
417        if (subItems.size() > 0)
418            items.add(GuiUtils.makeMenu("Export to component", subItems));
419    }
420    
421    public void viewManagerAdded(ViewManager vm) {
422        // this forces the addition of the ViewManager
423        getVMInfo(vm);
424    }
425    
426    public void viewManagerDestroyed(ViewManager vm) {
427        VMInfo info = findVMInfo(vm);
428        if (info != null) {
429            vmInfos.remove(info);
430            info.viewManagerDestroyed();
431            logger.trace("destroying '{}' for '{}'", info, vm);
432//                      System.err.println("destroying "+info+" for "+vm);
433        }
434    }
435    
436    private static final Logger logger = LoggerFactory.getLogger(McIDASVViewPanel.class);
437    
438    /**
439     * Triggered upon a change in the given ViewManager. Just used so that our
440     * ControlInfo object can update its internal state.
441     *
442     * @param vm The ViewManager that's changed.
443     */
444    public void viewManagerChanged(ViewManager vm) {
445        VMInfo info = findVMInfo(vm);
446        if (info != null)
447            info.viewManagerChanged();
448    }
449    
450    /**
451     * Initialize the button state.
452     */
453    protected void initButtonState() {
454        if (fgColor != null)
455            return;
456        
457        fgColor = Color.black;
458        
459        showPopup = idv.getProperty(Constants.PROP_VP_SHOWPOPUP, false);
460        
461        showCategories = idv.getProperty(Constants.PROP_VP_SHOWCATS, false);
462    }
463    
464    public VMInfo getVMInfo(ViewManager vm) {
465        VMInfo info = findVMInfo(vm);
466        if (info == null) {
467            
468            // oh no :(
469            if (vm instanceof MapViewManager)
470                if (((MapViewManager)vm).getUseGlobeDisplay())
471                    info = new VMInfo(vm, ViewManagers.GLOBE);
472                else
473                    info = new VMInfo(vm, ViewManagers.MAP);
474            else if (vm instanceof TransectViewManager)
475                info = new VMInfo(vm, ViewManagers.TRANSECT);
476            else
477                info = new VMInfo(vm, ViewManagers.DEFAULT);
478            
479            vmInfos.add(info);
480        }
481        return info;
482    }
483    
484    public VMInfo findVMInfo(ViewManager vm) {
485        for (VMInfo info : vmInfos)
486            if (info.holds(vm))
487                return info;
488        
489        return null;
490    }
491    
492    public class VMInfo implements ImageObserver {
493        private ViewManager viewManager;
494        
495        private JButton popupButton;
496        
497        private JComponent tabContents = new JPanel(new BorderLayout());
498        
499        private JPanel headerPanel;
500        
501        private boolean ignore = false;
502        
503        // private list of controlinfos?
504        private List<ControlInfo> controlInfos = new ArrayList<ControlInfo>();
505        private List<JToggleButton> buttons = new ArrayList<JToggleButton>();
506        
507        //private JComponent buttonPanel;
508        
509        private JComponent contents;
510        
511        private JLabel viewLabel;
512        
513        private JButton catToggle;
514        
515        private boolean catOpen = true;
516        
517        private KeyListener listener;
518        
519        private List<String> categories = new ArrayList<String>();
520        
521        private ViewManagers myType = ViewManagers.DEFAULT;
522        
523        public VMInfo(ViewManager vm, ViewManagers type) {
524            
525            listener = new KeyAdapter() {
526                public void keyPressed(KeyEvent e) {
527                    if (e.getKeyCode() == KeyEvent.VK_UP)
528                        selectNext(true);
529                    else if (e.getKeyCode() == KeyEvent.VK_DOWN)
530                        selectNext(false);
531                }
532            };
533            
534            initButtonState();
535            BUTTON_ICON.getWidth(this);
536            
537            viewManager = vm;
538            
539            ImageIcon icon = ICON_DEFAULT;
540            if (type == ViewManagers.GLOBE)
541                icon = ICON_GLOBE;
542            else if (type == ViewManagers.MAP)
543                icon = ICON_MAP;
544            else if (type == ViewManagers.TRANSECT)
545                icon = ICON_TRANSECT;
546            
547            viewLabel = new JLabel(" " + getLabel());
548            viewLabel.addMouseListener(new MouseAdapter() {
549                public void mousePressed(MouseEvent e) {
550                    if (viewManager == null)
551                        return;
552                    
553                    getVMManager().setLastActiveViewManager(viewManager);
554                    if (e.getClickCount() == 2)
555                        viewManager.toFront();
556                }
557            });
558            
559            catToggle = GuiUtils.getImageButton(getCatOpen() ? CATEGORY_OPEN_ICON : CATEGORY_CLOSED_ICON);
560            catToggle.addKeyListener(listener);
561            catToggle.addActionListener(new ActionListener() {
562                public void actionPerformed(ActionEvent e) {
563                    setCatOpen(!getCatOpen());
564                }
565            });
566            
567            popupButton = new JButton(icon);
568            popupButton.addKeyListener(listener);
569            popupButton.setContentAreaFilled(false);
570            popupButton.addActionListener(GuiUtils.makeActionListener(VMInfo.this, "showPopupMenu", null));
571            popupButton.setToolTipText("Show View Menu");
572            popupButton.setBorder(BorderFactory.createEmptyBorder());
573            
574            headerPanel = GuiUtils.leftCenter(GuiUtils.hbox(
575                GuiUtils.inset(catToggle, 1),
576                popupButton), viewLabel);
577            
578            if (viewManager != null)
579                headerPanel = viewManager.makeDropPanel(headerPanel, true);
580            
581            JComponent headerWrapper = GuiUtils.center(headerPanel);
582            headerPanel.setBorder(headerNormal);
583            contents = GuiUtils.topCenter(headerWrapper, tabContents);
584            viewContainer.add(contents);
585            popupButton.setHorizontalAlignment(SwingConstants.LEFT);
586            buttonsChanged();
587            
588            setCatOpen(getCatOpen());
589            
590            if (viewManager != null)
591                viewManagerChanged();
592        }
593        
594        public boolean getCatOpen() {
595            if (viewManager != null) {
596                Boolean b =
597                    (Boolean)viewManager.getProperty(Constants.PROP_VP_CATOPEN);
598                if (b != null)
599                    return b;
600            }
601            
602            return catOpen;
603        }
604        
605        public void setCatOpen(boolean v) {
606            if (viewManager != null)
607                viewManager.putProperty(Constants.PROP_VP_CATOPEN, v);
608            
609            catOpen = v;
610            catToggle.setIcon(v ? CATEGORY_OPEN_ICON : CATEGORY_CLOSED_ICON);
611            tabContents.setVisible(v);
612        }
613        
614        public void showPopupMenu() {
615            if (viewManager == null)
616                return;
617            
618            List items = new ArrayList();
619            viewManager.addContextMenuItems(items);
620            JPopupMenu popup = GuiUtils.makePopupMenu(items);
621            popup.show(popupButton, 0, popupButton.getHeight());
622        }
623        
624        /**
625         * Determine if this VMInfo contains a given ViewManager.
626         *
627         * @param vm The ViewManager you wish to test.
628         *
629         * @return {@code true} if this VMInfo contains {@code vm},
630         * {@code false} otherwise.
631         */
632        public boolean holds(ViewManager vm) {
633            return viewManager == vm;
634        }
635        
636        public void removeControlInfo(ControlInfo info) {
637            int idx = controlInfos.indexOf(info);
638            if (idx == -1)
639                return;
640            
641            int btnIdx = buttons.indexOf(info.button);
642            
643            buttons.remove(info.button);
644            controlInfos.remove(info);
645            rightPanel.remove(info.outer);
646            
647            if (info.button.isSelected() && (buttons.size() > 0)) {
648                while ((btnIdx >= buttons.size()) && (btnIdx >= 0))
649                    btnIdx--;
650                
651                if (btnIdx >= 0)
652                    buttons.get(btnIdx).doClick();
653            }
654            
655            GuiUtils.toggleHeavyWeightComponents(info.outer, true);
656            
657            buttonsChanged();
658            
659            // hmm -- this must be for synchronization?
660            ignore = true;
661            buttonGroup.remove(info.button);
662            ignore = false;
663            
664            
665            for (ActionListener l : catToggle.getActionListeners()) {
666                catToggle.removeActionListener(l);
667            }
668            
669            for (ActionListener l : popupButton.getActionListeners()) {
670                popupButton.removeActionListener(l);
671            }
672            
673            for (MouseListener l : viewLabel.getMouseListeners()) {
674                viewLabel.removeMouseListener(l);
675            }
676
677//                      for (KeyListener l : catToggle.getKeyListeners()) {
678//                              catToggle.removeKeyListener(l);
679//                      }
680            catToggle.removeKeyListener(listener);
681            popupButton.removeKeyListener(listener);
682            info.button.removeKeyListener(listener);
683            
684            
685            
686            // if there are still control infos left then we'll use those?
687            if (controlInfos.size() > 0)
688                return;
689            
690            // otherwise we need to click the buttons of each remaining viewmanager?
691            for (VMInfo vm : vmInfos)
692                if (vm.controlInfos.size() > 0)
693                    vm.controlInfos.get(0).button.doClick();
694        }
695        
696        public void changeControlInfo(ControlInfo info) {
697            if (!Misc.equals(info.lastCategory, info.control.getDisplayCategory()))
698                buttonsChanged();
699        }
700        
701        public void paintButton(Graphics g, ControlInfo info) {
702            g.setFont(BUTTON_FONT);
703            FontMetrics fm = g.getFontMetrics(g.getFont());
704            
705            JToggleButton btn = info.button;
706            Rectangle b = btn.getBounds();
707            String text = info.getLabel();
708            int y = (btn.getHeight() + fm.getHeight()) / 2 - 2;
709            int buttonWidth = BUTTON_ICON.getWidth(null);
710            int offset = 2 + buttonWidth + 4;
711            g.setColor(btn.getBackground());
712            g.fillRect(0, 0, b.width, b.height);
713            
714            if (btn.isSelected()) {
715                
716                if (onColor == null) {
717                    Color c = btn.getBackground();
718                    //Just go a little bit darker than the normal background
719                    onColor = new Color((int) Math.max(0,
720                        c.getRed() - 20), (int) Math.max(0,
721                        c.getGreen() - 20), (int) Math.max(0,
722                        c.getBlue() - 20));
723                }
724                
725                g.setColor(onColor);
726                g.fillRect(offset - 1, 0, b.width, b.height);
727            }
728            g.setColor(LINE_COLOR);
729            
730            g.drawLine(offset - 1, b.height - 1, b.width, b.height - 1);
731            
732            g.setColor(fgColor);
733            int rightSide = b.width;
734            if (btn.isSelected())
735                rightSide = b.width - buttonWidth - 2;
736            
737            int textPos = offset;
738            int textRight = textPos + fm.stringWidth(text);
739            if (textRight >= rightSide) {
740                while ((text.length() > 5) && (textRight >= rightSide)) {
741                    text = text.substring(0, text.length() - 2);
742                    textRight = textPos + fm.stringWidth(text + ".");
743                }
744                text = text + ".";
745            }
746            g.drawString(text, offset, y);
747            
748            if (!btn.isSelected())
749                return;
750            
751            int height = BUTTON_ICON.getHeight(null);
752            g.drawImage(BUTTON_ICON, b.width - 2 - buttonWidth, b.height / 2 - height / 2, null);
753        }
754        
755        public void addControlInfo(final ControlInfo info) {
756            // ugly :(
757            // why even have b?
758            //JToggleButton b = info.button = new JToggleButton(StringUtil.padRight("", 20), true) {
759            info.button = new JToggleButton(StringUtil.padRight("", 20), true) {
760                public void paint(Graphics g) {
761                    paintButton(g, info);
762                }
763            };
764            
765            info.button.addKeyListener(listener);
766            info.button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
767            info.button.setToolTipText(info.getLabel());
768            info.button.setFont(BUTTON_FONT);
769            info.button.setForeground(fgColor);
770            info.button.setBorder(BUTTON_BORDER);
771            
772            info.button.setHorizontalAlignment(SwingConstants.LEFT);
773            info.button.addActionListener(new ActionListener() {
774                public void actionPerformed(ActionEvent e) {
775                    if (ignore)
776                        return;
777                    
778                    GuiUtils.toggleHeavyWeightComponents(info.outer, true);
779                    rightPanel.show(info.outer);
780                }
781            });
782            buttons.add(info.button);
783            controlInfos.add(info);
784            rightPanel.addCard(info.outer);
785            GuiUtils.toggleHeavyWeightComponents(info.outer, false);
786            info.displayControlChanged();
787            
788            if (info.control.getExpandedInTabs())
789                info.expand();
790            
791            buttonGroup.add(info.button);
792            buttonsChanged();
793            setCatOpen(getCatOpen());
794        }
795        
796        /**
797         * Redo the buttons
798         */
799        private void buttonsChanged() {
800            List<JComponent> comps  = new ArrayList<JComponent>();
801            
802            Hashtable<String, List<JComponent>> catMap =
803                new Hashtable<String, List<JComponent>>();
804            
805            for (ControlInfo info : controlInfos) {
806                String cat = info.control.getDisplayCategory();
807                if (cat == null)
808                    cat = "Displays";
809                
810                info.lastCategory = cat;
811                
812                if (!showCategories) {
813                    comps.add(info.button);
814                    continue;
815                }
816                
817                List<JComponent> catList = catMap.get(cat);
818                if (catList == null) {
819                    if (!categories.contains(cat))
820                        categories.add(cat);
821                    
822                    catList = new ArrayList<JComponent>();
823                    
824                    catMap.put(cat, catList);
825                    
826                    JLabel catLabel = new JLabel(" " + cat);
827                    
828                    catLabel.setFont(CAT_FONT);
829                    
830                    catList.add(catLabel);
831                }
832                catList.add(info.button);
833            }
834            
835            if (showCategories) {
836                for (String category : categories) {
837                    List<JComponent> catList = catMap.get(category);
838                    if (catList != null)
839                        comps.addAll(catList);
840                }
841            }
842            
843            if (comps.size() == 0) {
844                if (myType == ViewManagers.TRANSECT) {
845                    JLabel noLbl = new JLabel("No Displays");
846                    noLbl.setFont(BUTTON_FONT);
847                    JPanel inset = GuiUtils.inset(noLbl, new Insets(0, 10, 0, 0));
848                    inset.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0,
849                        Color.gray));
850                    comps.add(inset);
851                } else {
852                    headerPanel.setVisible(false);
853                }
854            } else {
855                headerPanel.setVisible(true);
856            }
857            
858            comps.add(GuiUtils.filler(10, 2));
859            JComponent buttonPanel = GuiUtils.vbox(comps);
860            
861            tabContents.removeAll();
862            tabContents.add(BorderLayout.NORTH, buttonPanel);
863            tabContents.repaint();
864        }
865        
866        /**
867         * Handles ViewManager removal.
868         */
869        public void viewManagerDestroyed() {
870            viewContainer.remove(contents);
871            buttons.clear();
872        }
873        
874        /**
875         * my viewmanager has changed. Update the gui.
876         */
877        public void viewManagerChanged() {
878            viewLabel.setText(getLabel());
879            
880            if (viewManager.showHighlight()) {
881                
882                headerHighlight =
883                    BorderFactory.createCompoundBorder(
884                        BorderFactory.createEmptyBorder(3, 0, 0, 0),
885                        BorderFactory.createMatteBorder(
886                            0, 0, 2, 0,
887                            getStore().get(
888                                ViewManager.PREF_BORDERCOLOR, Color.blue)));
889                
890                headerPanel.setBorder(headerHighlight);
891            } else {
892                headerPanel.setBorder(headerNormal);
893            }
894            
895            if (contents != null)
896                contents.repaint();
897        }
898        
899        /**
900         * Get the ViewManager label. If the ViewManager does not already have
901         * a valid name, a default name is created, based on the number of 
902         * existing ViewManagers.
903         *
904         * @return label The ViewManager's name.
905         */
906        public String getLabel() {
907            // nothing to query for a name?
908            if (viewManager == null)
909                return "No Display";
910            
911            // do we already have a valid name?
912            String name = viewManager.getName();
913            if ((name != null) && (name.trim().length() > 0)) {
914                UIManager uiManager = (UIManager)idv.getIdvUIManager();
915                ComponentHolder holder = uiManager.getViewManagerHolder(viewManager);
916                if (holder != null)
917                    return holder.getName() + ">" + name;
918                else
919                    return name;
920            }
921            
922            // if our name was invalid, build a default one.
923            int idx = vmInfos.indexOf(this);
924            return "Default " + Constants.PANEL_NAME + " " +
925                ((idx == -1) ? vmInfos.size() : idx);
926        }
927        
928        public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) {
929            if ((flags & ImageObserver.ALLBITS) == 0)
930                return true;
931            
932            leftPanel.repaint();
933            return false;
934        }
935    }
936    
937    public class ControlInfo {
938        DisplayControl control;
939        JButton expandButton;
940        JComponent outer;
941        JComponent inner;
942        boolean expanded = false;
943        Dimension innerSize = new Dimension();
944        VMInfo info;
945        JToggleButton button;
946        String lastCategory = "";
947        String label = null;
948        
949        /**
950         * ctor
951         *
952         * @param control control
953         * @param expandButton expand button
954         * @param outer outer comp
955         * @param inner inner comp
956         * @param vmInfo my vminfo
957         */
958        public ControlInfo(DisplayControl control, JButton expandButton,
959                           JComponent outer, JComponent inner,
960                           VMInfo vmInfo) {
961            this.info  = vmInfo;
962            this.control = control;
963            if (control.getExpandedInTabs())
964                expanded = false;
965            
966            this.expandButton = expandButton;
967            this.outer = outer;
968            this.inner = inner;
969            inner.getSize(innerSize);
970            info.addControlInfo(this);
971            this.expand();
972        }
973        
974        /**
975         * get the label for the display control
976         *
977         * @return display control label
978         */
979        public String getLabel() {
980            if (label == null)
981                label = control.getMenuLabel();
982            
983            return label;
984        }
985        
986        /**
987         * display control changed
988         */
989        public void displayControlChanged() {
990            String tmp = label;
991            label = null;
992            getLabel();
993            if (!Misc.equals(tmp, label) && (button != null)) {
994                button.setToolTipText(label);
995                button.repaint();
996            }
997            info.changeControlInfo(this);
998        }
999        
1000        /**
1001         * display control is removed
1002         */
1003        public void removeDisplayControl() {
1004            info.removeControlInfo(this);
1005        }
1006        
1007        /**
1008         * Expand the contents
1009         */
1010        public void expand() {
1011            outer.removeAll();
1012            outer.setLayout(new BorderLayout());
1013            
1014            if (!expanded) {
1015                outer.add(BorderLayout.CENTER, inner);
1016                expandButton.setIcon(
1017                    GuiUtils.getImageIcon("/auxdata/ui/icons/UpUp.gif"));
1018                inner.getSize(innerSize);
1019//                              System.err.println("ControlInfo.expand: innerSize=" + innerSize);
1020            } else {
1021                outer.add(BorderLayout.NORTH, inner);
1022                expandButton.setIcon(
1023                    GuiUtils.getImageIcon("/auxdata/ui/icons/DownDown.gif"));
1024                inner.setSize(innerSize);
1025            }
1026            
1027            expanded = !expanded;
1028            control.setExpandedInTabs(expanded);
1029            
1030            final Container parent = outer.getParent();
1031            outer.invalidate();
1032            parent.validate();
1033            parent.doLayout();
1034        }
1035    }
1036}