001    // This class is based on the MBSGUIFrame class, version 1 August 2002
002    // by Julie Zenekski
003    
004    // Original copyright notice:
005    
006    // AP(r) Computer Science Marine Biology Simulation:
007    // The PseudoInfiniteViewport class is copyright(c) 2002 College Entrance
008    // Examination Board (www.collegeboard.com).
009    //
010    // This class is free software; you can redistribute it and/or modify
011    // it under the terms of the GNU General Public License as published by
012    // the Free Software Foundation.
013    //
014    // This class is distributed in the hope that it will be useful,
015    // but WITHOUT ANY WARRANTY; without even the implied warranty of
016    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017    // GNU General Public License for more details.
018    
019    package view;
020    
021    import controller.IDisplayAdapter;
022    
023    import javax.swing.*;
024    import java.awt.*;
025    import java.awt.event.ActionEvent;
026    import java.awt.event.ActionListener;
027    
028    /**
029     * The viewport for the display panel.
030     *
031     * @author Mathias Ricken
032     */
033    
034    public class DisplayViewport extends JViewport {
035        /**
036         * The Pannable interface contains those methods the view installed in a PseudoInfiniteViewport needs to support to
037         * enable panning behavior along with scrolling.
038         */
039        public interface Pannable {
040            /**
041             * Pan by the specified amount.
042             *
043             * @param dx x-delta
044             * @param dy y delta
045             */
046            void pan(double dx, double dy);
047    
048            /**
049             * Reset the pan.
050             */
051            void resetPan();
052    
053            /**
054             * Get the size of the cells.
055             *
056             * @return size of cells
057             */
058            int getCellSize();
059    
060            /**
061             * Get pan tooltip text.
062             *
063             * @return pan tip text.
064             */
065            String getPanTipText();
066        }
067    
068        private static final int ORIGIN_TIP_DELAY = 1000;
069    
070        /**
071         * Scroll pane controlled by this viewport.
072         */
073        private JScrollPane _scrollParent;
074    
075        /**
076         * Panel for the tool tip.
077         */
078        private JPanel _glassPane;
079    
080        /**
081         * Origin tool tip.
082         */
083        private JToolTip _originTip;
084    
085        /**
086         * Tool tip timer.
087         */
088        private Timer _originTipTimer;
089    
090        /**
091         * Display adapter.
092         */
093        private IDisplayAdapter _displayAdapter;
094    
095        /**
096         * Center before last movement.
097         */
098        private Point.Double _lastOrigin = new Point.Double();
099    
100        /**
101         * Make a new display viewport for the given scroll pane.
102         *
103         * @param parent the JScrollPane for which this will be the viewport
104         * @param da     display adapter to connect to the model
105         */
106        public DisplayViewport(JScrollPane parent, IDisplayAdapter da) {
107            _scrollParent = parent;
108            _displayAdapter = da;
109            setBackground(Color.lightGray);
110        }
111    
112        /**
113         * Reset the viewport.
114         */
115        public void resetViewport() {
116            Pannable p = getPannableView();
117            if (null != p) {
118                _lastOrigin = _displayAdapter.getViewPosition(new Point.Double(0, 0));
119            }
120        }
121    
122        /**
123         * Set the old position of the view.
124         *
125         * @param pt old position of view
126         */
127        public void oldSetViewPosition(Point pt) {
128            super.setViewPosition(pt);
129        }
130    
131        /**
132         * Sets the view position (upper left) to a new point. Overridden from JViewport.
133         *
134         * @param pt the Point to become the upper left
135         */
136        public void setViewPosition(Point pt) {
137            boolean isAdjusting = _scrollParent.getVerticalScrollBar().getValueIsAdjusting() || _scrollParent.getHorizontalScrollBar().getValueIsAdjusting();
138            Pannable p = getPannableView();
139            Point.Double delta;
140    
141            boolean changed = !getViewPosition().equals(pt);
142            super.setViewPosition(pt);
143    
144            if (null != p) {
145                if (!isAdjusting) {
146                    // the user let go of the scrollbars
147                    int cellSize = p.getCellSize();
148    
149                    // figure out how far the user scrolled
150                    delta = new Point.Double((double)(pt.x) / cellSize - _lastOrigin.x,
151                        (double)(pt.y) / cellSize - _lastOrigin.y);
152    
153                    // ask the environment how much to pan
154                    delta = _displayAdapter.getPanDelta(delta);
155                    // and pan
156                    p.pan(delta.x * cellSize, delta.y * cellSize);
157    
158                    // convert position into model coordinate units
159                    Point.Double modelPos = new Point.Double(((double)pt.x) / cellSize, ((double)pt.y) / cellSize);
160                    // and ask the environment where to scroll
161                    modelPos = _displayAdapter.getViewPosition(modelPos);
162                    Point pixelPos = new Point((int)(modelPos.x * cellSize), (int)(modelPos.y * cellSize));
163                    // then scroll there
164                    super.setViewPosition(pixelPos);
165    
166                    _lastOrigin = new Point.Double((double)(pixelPos.x) / cellSize, (double)(pixelPos.y) / cellSize);
167    
168                    // update scrollbars and display
169                    fireStateChanged();
170                    repaint();
171                }
172            }
173    
174            if (isAdjusting || changed) {
175                showOriginTip();
176            }
177        }
178    
179        /**
180         * Return pannable view.
181         *
182         * @return pannable view
183         */
184        private Pannable getPannableView() {
185            return (Pannable)getView();
186        }
187    
188        /**
189         * Show a tool tip over the upper left corner of the viewport with the contents of the pannable view's pannable tip
190         * text (typically a string identifiying the corner point). Tip is removed after a short delay.
191         */
192        public void showOriginTip() {
193            if (null == getRootPane()) {
194                return;
195            }
196            // drawFish in glass pane to appear on top of other components
197            if (null == _glassPane) {
198                getRootPane().setGlassPane(_glassPane = new JPanel());
199                _glassPane.setOpaque(false);
200                _glassPane.setLayout(null); // will control layout manually
201                _glassPane.add(_originTip = new JToolTip());
202                _originTipTimer = new Timer(ORIGIN_TIP_DELAY, new ActionListener() {
203                    public void actionPerformed(ActionEvent evt) {
204                        _glassPane.setVisible(false);
205                    }
206                });
207                _originTipTimer.setRepeats(false);
208            }
209            String tipText = getPannableView().getPanTipText();
210            if (null == tipText) {
211                return;
212            }
213    
214            // set tip text to identify current origin of pannable view
215            _originTip.setTipText(tipText);
216    
217            // position tip to appear at upper left corner of viewport
218            _originTip.setLocation(SwingUtilities.convertPoint(this, getLocation(), _glassPane));
219            _originTip.setSize(_originTip.getPreferredSize());
220    
221            // show glass pane (it contains tip)
222            _glassPane.setVisible(true);
223    
224            // this timer will hide the glass pane after a short delay
225            _originTipTimer.restart();
226        }
227    }