1 /*
   2  * Copyright (c) 2011, 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.behavior;
  27 
  28 import com.sun.javafx.scene.control.inputmap.InputMap;
  29 import javafx.event.Event;
  30 import javafx.scene.control.SelectionModel;
  31 import javafx.scene.control.Tab;
  32 import javafx.scene.control.TabPane;
  33 import javafx.scene.input.*;
  34 import com.sun.javafx.scene.control.inputmap.KeyBinding;
  35 
  36 import java.util.List;
  37 
  38 import static javafx.scene.input.KeyCode.*;
  39 import static com.sun.javafx.scene.control.inputmap.InputMap.KeyMapping;
  40 import static com.sun.javafx.scene.control.inputmap.InputMap.MouseMapping;
  41 
  42 public class TabPaneBehavior extends BehaviorBase<TabPane> {
  43 
  44     private final InputMap<TabPane> tabPaneInputMap;
  45 
  46     public TabPaneBehavior(TabPane tabPane) {
  47         super(tabPane);
  48 
  49         // create a map for TabPane-specific mappings (this reuses the default
  50         // InputMap installed on the control, if it is non-null, allowing us to pick up any user-specified mappings)
  51         tabPaneInputMap = createInputMap();
  52 
  53         // TabPane-specific mappings for key and mouse input
  54         addDefaultMapping(tabPaneInputMap,
  55             new KeyMapping(UP, e -> selectPreviousTab()),
  56             new KeyMapping(DOWN, e -> selectNextTab()),
  57             new KeyMapping(LEFT, e -> rtl(tabPane, this::selectNextTab, this::selectPreviousTab)),
  58             new KeyMapping(RIGHT, e -> rtl(tabPane, this::selectPreviousTab, this::selectNextTab)),
  59             new KeyMapping(HOME, e -> {
  60                 if (getNode().isFocused()) {
  61                     moveSelection(0, 1);
  62                 }
  63             }),
  64             new KeyMapping(END, e -> {
  65                 if (getNode().isFocused()) {
  66                     moveSelection(getNode().getTabs().size() - 1, -1);
  67                 }
  68             }),
  69             new KeyMapping(new KeyBinding(PAGE_UP).ctrl(), e -> selectPreviousTab()),
  70             new KeyMapping(new KeyBinding(PAGE_DOWN).ctrl(), e -> selectNextTab()),
  71             new KeyMapping(new KeyBinding(TAB).ctrl(), e -> selectNextTab()),
  72             new KeyMapping(new KeyBinding(TAB).ctrl().shift(), e -> selectPreviousTab()),
  73             new MouseMapping(MouseEvent.MOUSE_PRESSED, e -> getNode().requestFocus())
  74         );
  75     }
  76 
  77     @Override public InputMap<TabPane> getInputMap() {
  78         return tabPaneInputMap;
  79     }
  80 
  81     public void selectTab(Tab tab) {
  82         getNode().getSelectionModel().select(tab);
  83     }
  84 
  85     public boolean canCloseTab(Tab tab) {
  86         Event event = new Event(tab,tab,Tab.TAB_CLOSE_REQUEST_EVENT);
  87         Event.fireEvent(tab, event);
  88         return ! event.isConsumed();
  89     }
  90     
  91     public void closeTab(Tab tab) {
  92         TabPane tabPane = getNode();
  93         // only switch to another tab if the selected tab is the one we're closing
  94         int index = tabPane.getTabs().indexOf(tab);
  95         if (index != -1) {
  96             tabPane.getTabs().remove(index);
  97         }                
  98         if (tab.getOnClosed() != null) {
  99             Event.fireEvent(tab, new Event(Tab.CLOSED_EVENT));
 100         }
 101     }
 102 
 103     // Find a tab after the currently selected that is not disabled. Loop around
 104     // if no tabs are found after currently selected tab.
 105     public void selectNextTab() {
 106         moveSelection(1);
 107     }
 108 
 109     // Find a tab before the currently selected that is not disabled.
 110     public void selectPreviousTab() {
 111         moveSelection(-1);
 112     }
 113 
 114     private void moveSelection(int delta) {
 115         moveSelection(getNode().getSelectionModel().getSelectedIndex(), delta);
 116     }
 117 
 118     private void moveSelection(int startIndex, int delta) {
 119         final TabPane tabPane = getNode();
 120         int tabIndex = findValidTab(startIndex, delta);
 121         if (tabIndex > -1) {
 122             final SelectionModel<Tab> selectionModel = tabPane.getSelectionModel();
 123             selectionModel.select(tabIndex);
 124         }
 125         tabPane.requestFocus();
 126     }
 127 
 128     private int findValidTab(int startIndex, int delta) {
 129         final TabPane tabPane = getNode();
 130         final List<Tab> tabs = tabPane.getTabs();
 131         final int max = tabs.size();
 132 
 133         int index = startIndex;
 134         do {
 135             index = nextIndex(index + delta, max);
 136             Tab tab = tabs.get(index);
 137             if (tab != null && !tab.isDisable()) {
 138                 return index;
 139             }
 140         } while (index != startIndex);
 141 
 142         return -1;
 143     }
 144 
 145     private int nextIndex(int value, int max) {
 146         final int min = 0;
 147         int r = value % max;
 148         if (r > min && max < min) {
 149             r = r + max - min;
 150         } else if (r < min && max > min) {
 151             r = r + max - min;
 152         }
 153         return r;
 154     }
 155 }