1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package javax.swing.plaf.basic;
27
28import sun.swing.DefaultLookup;
29import sun.swing.UIAction;
30import java.awt.*;
31import java.awt.event.*;
32import java.beans.*;
33import javax.swing.*;
34import javax.swing.event.*;
35import javax.swing.plaf.ActionMapUIResource;
36import javax.swing.plaf.ButtonUI;
37import javax.swing.plaf.ComponentInputMapUIResource;
38
39/**
40 * Button Listener
41 *
42 * @author Jeff Dinkins
43 * @author Arnaud Weber (keyboard UI support)
44 */
45
46public class BasicButtonListener implements MouseListener, MouseMotionListener,
47                                   FocusListener, ChangeListener, PropertyChangeListener
48{
49    private long lastPressedTimestamp = -1;
50    private boolean shouldDiscardRelease = false;
51
52    /**
53     * Populates Buttons actions.
54     */
55    static void loadActionMap(LazyActionMap map) {
56        map.put(new Actions(Actions.PRESS));
57        map.put(new Actions(Actions.RELEASE));
58    }
59
60
61    /**
62     * Constructs a new instance of {@code BasicButtonListener}.
63     *
64     * @param b an abstract button
65     */
66    public BasicButtonListener(AbstractButton b) {
67    }
68
69    public void propertyChange(PropertyChangeEvent e) {
70        String prop = e.getPropertyName();
71        if(prop == AbstractButton.MNEMONIC_CHANGED_PROPERTY) {
72            updateMnemonicBinding((AbstractButton)e.getSource());
73        }
74        else if(prop == AbstractButton.CONTENT_AREA_FILLED_CHANGED_PROPERTY) {
75            checkOpacity((AbstractButton) e.getSource() );
76        }
77        else if(prop == AbstractButton.TEXT_CHANGED_PROPERTY ||
78                "font" == prop || "foreground" == prop) {
79            AbstractButton b = (AbstractButton) e.getSource();
80            BasicHTML.updateRenderer(b, b.getText());
81        }
82    }
83
84    /**
85     * Checks the opacity of the {@code AbstractButton}.
86     *
87     * @param b an abstract button
88     */
89    protected void checkOpacity(AbstractButton b) {
90        b.setOpaque( b.isContentAreaFilled() );
91    }
92
93    /**
94     * Register default key actions: pressing space to "click" a
95     * button and registering the keyboard mnemonic (if any).
96     *
97     * @param c a component
98     */
99    public void installKeyboardActions(JComponent c) {
100        AbstractButton b = (AbstractButton)c;
101        // Update the mnemonic binding.
102        updateMnemonicBinding(b);
103
104        LazyActionMap.installLazyActionMap(c, BasicButtonListener.class,
105                                           "Button.actionMap");
106
107        InputMap km = getInputMap(JComponent.WHEN_FOCUSED, c);
108
109        SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, km);
110    }
111
112    /**
113     * Unregister default key actions.
114     *
115     * @param c a component
116     */
117    public void uninstallKeyboardActions(JComponent c) {
118        SwingUtilities.replaceUIInputMap(c, JComponent.
119                                         WHEN_IN_FOCUSED_WINDOW, null);
120        SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null);
121        SwingUtilities.replaceUIActionMap(c, null);
122    }
123
124    /**
125     * Returns the InputMap for condition <code>condition</code>. Called as
126     * part of <code>installKeyboardActions</code>.
127     */
128    InputMap getInputMap(int condition, JComponent c) {
129        if (condition == JComponent.WHEN_FOCUSED) {
130            BasicButtonUI ui = (BasicButtonUI)BasicLookAndFeel.getUIOfType(
131                         ((AbstractButton)c).getUI(), BasicButtonUI.class);
132            if (ui != null) {
133                return (InputMap)DefaultLookup.get(
134                             c, ui, ui.getPropertyPrefix() + "focusInputMap");
135            }
136        }
137        return null;
138    }
139
140    /**
141     * Resets the binding for the mnemonic in the WHEN_IN_FOCUSED_WINDOW
142     * UI InputMap.
143     */
144    void updateMnemonicBinding(AbstractButton b) {
145        int m = b.getMnemonic();
146        if(m != 0) {
147            InputMap map = SwingUtilities.getUIInputMap(
148                                b, JComponent.WHEN_IN_FOCUSED_WINDOW);
149
150            if (map == null) {
151                map = new ComponentInputMapUIResource(b);
152                SwingUtilities.replaceUIInputMap(b,
153                               JComponent.WHEN_IN_FOCUSED_WINDOW, map);
154            }
155            map.clear();
156            map.put(KeyStroke.getKeyStroke(m, BasicLookAndFeel.getFocusAcceleratorKeyMask(), false),
157                    "pressed");
158            map.put(KeyStroke.getKeyStroke(m, BasicLookAndFeel.getFocusAcceleratorKeyMask(), true),
159                    "released");
160            map.put(KeyStroke.getKeyStroke(m, 0, true), "released");
161        }
162        else {
163            InputMap map = SwingUtilities.getUIInputMap(b, JComponent.
164                                             WHEN_IN_FOCUSED_WINDOW);
165            if (map != null) {
166                map.clear();
167            }
168        }
169    }
170
171    public void stateChanged(ChangeEvent e) {
172        AbstractButton b = (AbstractButton) e.getSource();
173        b.repaint();
174    }
175
176    public void focusGained(FocusEvent e) {
177        AbstractButton b = (AbstractButton) e.getSource();
178        if (b instanceof JButton && ((JButton)b).isDefaultCapable()) {
179            JRootPane root = b.getRootPane();
180            if (root != null) {
181               BasicButtonUI ui = (BasicButtonUI)BasicLookAndFeel.getUIOfType(
182                         b.getUI(), BasicButtonUI.class);
183               if (ui != null && DefaultLookup.getBoolean(b, ui,
184                                   ui.getPropertyPrefix() +
185                                   "defaultButtonFollowsFocus", true)) {
186                   root.putClientProperty("temporaryDefaultButton", b);
187                   root.setDefaultButton((JButton)b);
188                   root.putClientProperty("temporaryDefaultButton", null);
189               }
190            }
191        }
192        b.repaint();
193    }
194
195    public void focusLost(FocusEvent e) {
196        AbstractButton b = (AbstractButton) e.getSource();
197        JRootPane root = b.getRootPane();
198        if (root != null) {
199           JButton initialDefault = (JButton)root.getClientProperty("initialDefaultButton");
200           if (b != initialDefault) {
201               BasicButtonUI ui = (BasicButtonUI)BasicLookAndFeel.getUIOfType(
202                         b.getUI(), BasicButtonUI.class);
203               if (ui != null && DefaultLookup.getBoolean(b, ui,
204                                   ui.getPropertyPrefix() +
205                                   "defaultButtonFollowsFocus", true)) {
206                   root.setDefaultButton(initialDefault);
207               }
208           }
209        }
210
211        ButtonModel model = b.getModel();
212        model.setPressed(false);
213        model.setArmed(false);
214        b.repaint();
215    }
216
217    public void mouseMoved(MouseEvent e) {
218    }
219
220
221    public void mouseDragged(MouseEvent e) {
222    }
223
224    public void mouseClicked(MouseEvent e) {
225    }
226
227    public void mousePressed(MouseEvent e) {
228       if (SwingUtilities.isLeftMouseButton(e) ) {
229          AbstractButton b = (AbstractButton) e.getSource();
230
231          if(b.contains(e.getX(), e.getY())) {
232              long lastTime = lastPressedTimestamp;
233              lastPressedTimestamp = e.getWhen();
234              long timeSinceLastClick = lastPressedTimestamp - lastTime;
235              if (lastTime != -1 &&
236                  timeSinceLastClick > 0 &&
237                  timeSinceLastClick < b.getMultiClickThreshhold()) {
238
239                  shouldDiscardRelease = true;
240                  return;
241              }
242
243             ButtonModel model = b.getModel();
244             if (!model.isEnabled()) {
245                // Disabled buttons ignore all input...
246                return;
247             }
248             if (!model.isArmed()) {
249                // button not armed, should be
250                model.setArmed(true);
251             }
252             model.setPressed(true);
253             if(!b.hasFocus() && b.isRequestFocusEnabled()) {
254                b.requestFocus();
255             }
256          }
257       }
258    }
259
260    public void mouseReleased(MouseEvent e) {
261        if (SwingUtilities.isLeftMouseButton(e)) {
262            // Support for multiClickThreshhold
263            if (shouldDiscardRelease) {
264                shouldDiscardRelease = false;
265                return;
266            }
267            AbstractButton b = (AbstractButton) e.getSource();
268            ButtonModel model = b.getModel();
269            model.setPressed(false);
270            model.setArmed(false);
271        }
272    }
273
274    public void mouseEntered(MouseEvent e) {
275        AbstractButton b = (AbstractButton) e.getSource();
276        ButtonModel model = b.getModel();
277        if (b.isRolloverEnabled() && !SwingUtilities.isLeftMouseButton(e)) {
278            model.setRollover(true);
279        }
280        if (model.isPressed())
281                model.setArmed(true);
282    }
283
284    public void mouseExited(MouseEvent e) {
285        AbstractButton b = (AbstractButton) e.getSource();
286        ButtonModel model = b.getModel();
287        if(b.isRolloverEnabled()) {
288            model.setRollover(false);
289        }
290        model.setArmed(false);
291    }
292
293
294    /**
295     * Actions for Buttons. Two types of action are supported:
296     * pressed: Moves the button to a pressed state
297     * released: Disarms the button.
298     */
299    private static class Actions extends UIAction {
300        private static final String PRESS = "pressed";
301        private static final String RELEASE = "released";
302
303        Actions(String name) {
304            super(name);
305        }
306
307        public void actionPerformed(ActionEvent e) {
308            AbstractButton b = (AbstractButton)e.getSource();
309            String key = getName();
310            if (key == PRESS) {
311                ButtonModel model = b.getModel();
312                model.setArmed(true);
313                model.setPressed(true);
314                if(!b.hasFocus()) {
315                    b.requestFocus();
316                }
317            }
318            else if (key == RELEASE) {
319                ButtonModel model = b.getModel();
320                model.setPressed(false);
321                model.setArmed(false);
322            }
323        }
324
325        @Override
326        public boolean accept(Object sender) {
327            return !((sender instanceof AbstractButton) &&
328                    !((AbstractButton)sender).getModel().isEnabled());
329        }
330    }
331}
332