1 /*
   2  * Copyright (c) 2012, 2014, 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 
  26 package com.sun.javafx.scene.control.skin;
  27 
  28 import javafx.application.ConditionalFeature;
  29 import javafx.application.Platform;
  30 import javafx.beans.value.ObservableValue;
  31 import javafx.event.EventHandler;
  32 import javafx.event.EventType;
  33 import javafx.scene.control.Control;
  34 import javafx.scene.control.SkinBase;
  35 import javafx.scene.input.ContextMenuEvent;
  36 import javafx.scene.input.MouseEvent;
  37 import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler;
  38 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  39 
  40 /**
  41  *
  42  */
  43 public abstract class BehaviorSkinBase<C extends Control, BB extends BehaviorBase<C>> extends SkinBase<C> {
  44     /**
  45      * A static final reference to whether the platform we are on supports touch.
  46      */
  47     protected final static boolean IS_TOUCH_SUPPORTED = Platform.isSupported(ConditionalFeature.INPUT_TOUCH);
  48 
  49     /***************************************************************************
  50      *                                                                         *
  51      * Private fields                                                          *
  52      *                                                                         *
  53      **************************************************************************/
  54 
  55     /**
  56      * The {@link BehaviorBase} that encapsulates the interaction with the
  57      * {@link Control} from this {@code Skin}. The {@code Skin} does not modify
  58      * the {@code Control} directly, but rather redirects events into the
  59      * {@code BehaviorBase} which then handles the events by modifying internal state
  60      * and public state in the {@code Control}. Generally, specific
  61      * {@code Skin} implementations will require specific {@code BehaviorBase}
  62      * implementations. For example, a ButtonSkin might require a ButtonBehavior.
  63      */
  64     private BB behavior;
  65     
  66     /**
  67      * This is part of the workaround introduced during delomboking. We probably will
  68      * want to adjust the way listeners are added rather than continuing to use this
  69      * map (although it doesn't really do much harm).
  70      */
  71     private MultiplePropertyChangeListenerHandler changeListenerHandler;
  72     
  73     
  74     
  75     /***************************************************************************
  76      *                                                                         *
  77      * Event Handlers / Listeners                                              *
  78      *                                                                         *
  79      **************************************************************************/
  80     
  81     
  82     /**
  83      * Forwards mouse events received by a MouseListener to the behavior.
  84      * Note that we use this pattern to remove some of the anonymous inner
  85      * classes which we'd otherwise have to create. When lambda expressions
  86      * are supported, we could do it that way instead (or use MethodHandles).
  87      */
  88     private final EventHandler<MouseEvent> mouseHandler =
  89             new EventHandler<MouseEvent>() {
  90         @Override public void handle(MouseEvent e) {
  91             final EventType<?> type = e.getEventType();
  92 
  93             if (type == MouseEvent.MOUSE_ENTERED) behavior.mouseEntered(e);
  94             else if (type == MouseEvent.MOUSE_EXITED) behavior.mouseExited(e);
  95             else if (type == MouseEvent.MOUSE_PRESSED) behavior.mousePressed(e);
  96             else if (type == MouseEvent.MOUSE_RELEASED) behavior.mouseReleased(e);
  97             else if (type == MouseEvent.MOUSE_DRAGGED) behavior.mouseDragged(e);
  98             else { // no op
  99                 throw new AssertionError("Unsupported event type received");
 100             }
 101         }
 102     };
 103 
 104     private final EventHandler<ContextMenuEvent> contextMenuHandler =
 105             new EventHandler<ContextMenuEvent>() {
 106                 @Override public void handle(ContextMenuEvent event) {
 107                     behavior.contextMenuRequested(event);
 108                 }
 109             };
 110     
 111     /***************************************************************************
 112      *                                                                         *
 113      * Constructor                                                             *
 114      *                                                                         *
 115      **************************************************************************/
 116 
 117     /**
 118      * Constructor for all BehaviorSkinBase instances.
 119      * 
 120      * @param control The control for which this Skin should attach to.
 121      * @param behavior The behavior for which this Skin should defer to.
 122      */
 123     protected BehaviorSkinBase(final C control, final BB behavior) {
 124         super(control);
 125         
 126         if (behavior == null) {
 127             throw new IllegalArgumentException("Cannot pass null for behavior");
 128         }
 129 
 130         // Update the control and behavior
 131         this.behavior = behavior;
 132         
 133         // We will auto-add listeners for wiring up Region mouse events to
 134         // be sent to the behavior
 135         control.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseHandler);
 136         control.addEventHandler(MouseEvent.MOUSE_EXITED, mouseHandler);
 137         control.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
 138         control.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseHandler);
 139         control.addEventHandler(MouseEvent.MOUSE_DRAGGED, mouseHandler);
 140 
 141         control.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler);
 142     }
 143     
 144     
 145 
 146     /***************************************************************************
 147      *                                                                         *
 148      * Public API (from Skin)                                                  *
 149      *                                                                         *
 150      **************************************************************************/    
 151 
 152     /** {@inheritDoc} */
 153     public final BB getBehavior() {
 154         return behavior;
 155     }
 156 
 157     /** {@inheritDoc} */
 158     @Override public void dispose() { 
 159         // unhook listeners
 160         if (changeListenerHandler != null) {
 161             changeListenerHandler.dispose();
 162         }
 163         
 164         C control = getSkinnable();
 165         if (control != null) {
 166             control.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseHandler);
 167             control.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseHandler);
 168             control.removeEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
 169             control.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseHandler);
 170             control.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseHandler);
 171         }
 172 
 173         if (behavior != null) {
 174             behavior.dispose();
 175             behavior = null;
 176         }
 177 
 178         super.dispose();
 179     }
 180     
 181     /***************************************************************************
 182      *                                                                         *
 183      * Public API                                                              *
 184      *                                                                         *
 185      **************************************************************************/
 186     
 187     /**
 188      * Subclasses can invoke this method to register that we want to listen to
 189      * property change events for the given property.
 190      *
 191      * @param property
 192      * @param reference
 193      */
 194     protected final void registerChangeListener(ObservableValue<?> property, String reference) {
 195         if (changeListenerHandler == null) {
 196             changeListenerHandler = new MultiplePropertyChangeListenerHandler(p -> {
 197                 handleControlPropertyChanged(p);
 198                 return null;
 199             });
 200         }
 201         changeListenerHandler.registerChangeListener(property, reference);
 202     }
 203     
 204     /**
 205      * Skin subclasses will override this method to handle changes in corresponding
 206      * control's properties.
 207      */
 208     protected void handleControlPropertyChanged(String propertyReference) {
 209         // no-op
 210     }
 211 
 212 }