/** * erlyberly, erlang trace debugger * Copyright (C) 2016 Andy Till * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ui; /* * Copyright 2014 Jens Deters. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import javafx.beans.Observable; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.Cursor; import javafx.scene.Scene; import javafx.scene.SnapshotParameters; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.image.WritableImage; import javafx.scene.input.ClipboardContent; import javafx.scene.input.DataFormat; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.MouseEvent; import javafx.scene.input.TransferMode; import javafx.scene.layout.Pane; import javafx.scene.transform.Transform; import javafx.stage.Stage; import javafx.stage.WindowEvent; /** * A simple Utility to make all {@link Tab}s of a {@link TabPane} detachable. * <br> * <h1>Usage</h1> * To get a {@link TabPane} in charge of control: * <br> * <b>Hint: Only already added {@link Tab}s are going to be in charge of control!</b> * <pre> * {@code * TabPaneDude.create().makeTabsDetachable(myTapPane); * } * </pre> Tabs can then be detached simply by dragging a tab title to the desired window position. * /** * * @author Jens Deters (www.jensd.de) * @version 1.0.0 * @since 14-10-2014 */ public class TabPaneDetacher { private TabPane tabPane; private Tab currentTab; private final List<Tab> originalTabs; private final Map<Integer, Tab> tapTransferMap; private String[] stylesheets; private final BooleanProperty alwaysOnTop; private TabPaneDetacher() { originalTabs = new ArrayList<>(); stylesheets = new String[]{}; tapTransferMap = new HashMap<>(); alwaysOnTop = new SimpleBooleanProperty(); } /** * Creates a new instance of the TabPaneDetacher * * @return The new instance of the TabPaneDetacher. */ public static TabPaneDetacher create() { return new TabPaneDetacher(); } public BooleanProperty alwaysOnTopProperty() { return alwaysOnTop; } public Boolean isAlwaysOnTop() { return alwaysOnTop.get(); } /** * * Sets whether detached Tabs should be always on top. * * @param alwaysOnTop The state to be set. * @return The current TabPaneDetacher instance. */ public TabPaneDetacher alwaysOnTop(boolean alwaysOnTop){ alwaysOnTopProperty().set(alwaysOnTop); return this; } /** * Sets the stylesheets that should be assigend to the new created {@link Stage}. * * @param stylesheets The stylesheets to be set. * @return The current TabPaneDetacher instance. */ public TabPaneDetacher stylesheets(String... stylesheets) { this.stylesheets = stylesheets; return this; } /** * Make all added {@link Tab}s of the given {@link TabPane} detachable. * * @param tabPane The {@link TabPane} to take over. * @return The current TabPaneDetacher instance. */ public TabPaneDetacher makeTabsDetachable(TabPane tabPane) { this.tabPane = tabPane; originalTabs.addAll(tabPane.getTabs()); for (int i = 0; i < tabPane.getTabs().size(); i++) { tapTransferMap.put(i, tabPane.getTabs().get(i)); } tabPane.getTabs().stream().forEach(t -> { t.setClosable(false); }); tabPane.getTabs().addListener((Observable o) -> { for (Tab tabX : tabPane.getTabs()) { if(!(tabX.getContent() instanceof Pane)) { throw new RuntimeException("Tab added where the content node was not a subclass of Pane, this means it cannot be dragged by TabPaneDetacher."); } } }); tabPane.setOnDragDetected( (MouseEvent event) -> { if (event.getSource() instanceof TabPane) { Pane rootPane = (Pane) tabPane.getScene().getRoot(); rootPane.setOnDragOver((DragEvent event1) -> { event1.acceptTransferModes(TransferMode.ANY); event1.consume(); }); currentTab = tabPane.getSelectionModel().getSelectedItem(); SnapshotParameters snapshotParams = new SnapshotParameters(); snapshotParams.setTransform(Transform.scale(0.4, 0.4)); WritableImage snapshot = currentTab.getContent().snapshot(snapshotParams, null); Dragboard db = tabPane.startDragAndDrop(TransferMode.MOVE); ClipboardContent clipboardContent = new ClipboardContent(); clipboardContent.put(DataFormat.PLAIN_TEXT, "detach"); db.setDragView(snapshot, 40, 40); db.setContent(clipboardContent); } event.consume(); } ); tabPane.setOnDragDone( (DragEvent event) -> { openTabInStage(currentTab); tabPane.setCursor(Cursor.DEFAULT); event.consume(); } ); return this; } /** * Opens the content of the given {@link Tab} in a separate Stage. While the content is removed from the {@link Tab} it is * added to the root of a new {@link Stage}. The Window title is set to the name of the {@link Tab}; * * @param tab The {@link Tab} to get the content from. */ public void openTabInStage(final Tab tab) { if(tab == null){ return; } int originalTab = originalTabs.indexOf(tab); tapTransferMap.remove(originalTab); Pane content = (Pane) tab.getContent(); if (content == null) { throw new IllegalArgumentException("Can not detach Tab '" + tab.getText() + "': content is empty (null)."); } tab.setContent(null); final Scene scene = new Scene(content, content.getPrefWidth(), content.getPrefHeight()); scene.getStylesheets().addAll(stylesheets); Stage stage = new Stage(); stage.setScene(scene); stage.setTitle(tab.getText()); stage.setAlwaysOnTop(isAlwaysOnTop()); stage.setOnCloseRequest((WindowEvent t) -> { stage.close(); tab.setContent(content); int originalTabIndex = originalTabs.indexOf(tab); tapTransferMap.put(originalTabIndex, tab); int index = 0; SortedSet<Integer> keys = new TreeSet<>(tapTransferMap.keySet()); for (Integer key : keys) { Tab value = tapTransferMap.get(key); if(!tabPane.getTabs().contains(value)){ tabPane.getTabs().add(index, value); } index++; } tabPane.getSelectionModel().select(tab); }); stage.setOnShown((WindowEvent t) -> { tab.getTabPane().getTabs().remove(tab); }); CloseWindowOnEscape.apply(scene, stage); stage.show(); } }