/*
* ScreenFX - A plugin library for JavaFX 8 adding features for resizing
* and arranging stage windows on a multiple screen hardware configuration
*
* Copyright (C) 2014 Mario Voigt, Ulmenstr. 35, 09112 Chemnitz, Germany, vmario@hrz.tu-chemnitz.de
*
* 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/gpl-3.0.html
*/
package com.vmario.screenfx;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.MouseInfo;
import java.awt.PointerInfo;
import java.util.ArrayList;
import java.util.List;
import javafx.concurrent.Service;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.TextAlignment;
import javafx.stage.Popup;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
/**
* @author vmario
*
*/
class ScreenFXPopup extends Popup {
private final Service<Void> delayService;
/**
* make a popup with a oneclick lifecycle to create always new popup
*
* @param stage
* the stage to show on the popup
*
* @throws Exception
* push exceptions
*/
public ScreenFXPopup(Stage stage) throws Exception {
delayService = new ScreenFXDelayService(ScreenFXConfig.getExitDelayTime());
// Popup is special window (no decorations, transparent null
// fill). For
// a proper rendering there is a rectangle needed to display the
// background of the rectangle and popup have the same bounds
Rectangle borderRectangle = new Rectangle();
/* add style from css */
borderRectangle.setId("borderrectangle");
stage.getScene().getStylesheets().add(ScreenFXConfig.getResourcePath() + "css/borderrectangle.css");
getContent().addAll(borderRectangle);
setAutoFix(true);
setHideOnEscape(true);
AnchorPane topAnchorPane = new AnchorPane();
List<AnchorPane> screenAnchorPanes = new ArrayList<>();
// create new screen grid
GridPane screenGridPane = new GridPane();
screenGridPane.setGridLinesVisible(false);
screenGridPane.setHgap(2);
screenGridPane.setVgap(2);
GraphicsEnvironment graphicEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] graphicsDevices = graphicEnvironment.getScreenDevices(); // screen
// device
// list
// get vaules of main screen - needed for alignment of other
// available
// screen devices around the main screen
GraphicsConfiguration mainScreenGraphicsConfiguration = graphicsDevices[0].getDefaultConfiguration();
java.awt.Rectangle boundsMain = mainScreenGraphicsConfiguration.getBounds();
int posXmain = boundsMain.x;
int posYmain = boundsMain.y;
int dimXmain = boundsMain.width;
int dimYmain = boundsMain.height;
// apply grid layout for available screens
for (int i = 0; i < graphicsDevices.length; i++) {
try {
// get physically positions of the other screen relative
// to main
// screen
GraphicsConfiguration gc = graphicsDevices[i].getDefaultConfiguration();
java.awt.Rectangle bounds = gc.getBounds();
int posX = bounds.x; // x pos of a non main screen
int posY = bounds.y; // y pos of a non main screen
// grid init values
int gridXval = 0;
int gridYval = 0;
if (posX < posXmain + posXmain) { // left x
gridXval = 0;
if (posY < posYmain) { // top y
gridYval = 0;
} else if (posY >= posYmain & posY < posYmain + dimYmain) { // mid
// y
gridYval = 1;
} else if (posY >= posYmain + dimYmain) { // bottom
// y
gridYval = 2;
}
} else if (posX >= posXmain & posX < posXmain + dimXmain) { // mid
// x
gridXval = 1;
if (posY < posYmain) { // top y
gridYval = 0;
} else if (posY >= posYmain & posY < posYmain + dimYmain) { // mid
// y
gridYval = 1;
} else if (posY >= posYmain + dimYmain) { // bottom
// y
gridYval = 2;
}
} else if (posX >= posXmain + dimXmain) { // right x
gridXval = 2;
if (posY < posYmain) { // top y
gridYval = 0;
} else if (posY >= posYmain & posY < posYmain + dimYmain) { // mid
// y
gridYval = 1;
} else if (posY >= posYmain + dimYmain) { // bottom
// y
gridYval = 2;
}
}
AnchorPane screenAnchorPane = new AnchorPane();
if (i != 0) {
ColorAdjust colorAdjust = new ColorAdjust();
colorAdjust.setHue(0.1d);
screenAnchorPane.setEffect(colorAdjust);
}
GridPane.setMargin(screenAnchorPane, new Insets(5, 5, 5, 5));
screenAnchorPanes.add(screenAnchorPane);
screenGridPane.add(screenAnchorPane, gridXval, gridYval);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* create a 12 button control grid + other controls for each
* screenGridPane using ScreenFXGridAllocator
*/
for (int screenNr = 0; screenNr < screenAnchorPanes.size(); screenNr++) {
GridPane buttonGridPane = new GridPane();
buttonGridPane.setGridLinesVisible(false);
int buttonSize = 15;
int buttonGap = 2;
buttonGridPane.setHgap(buttonGap);
buttonGridPane.setVgap(buttonGap);
buttonGridPane.setMaxWidth(3 * (buttonGap + buttonSize));
buttonGridPane.setMaxHeight(4 * (buttonGap + buttonSize));
// taskbar checkbox
ScreenFXTaskbarCheckBox taskBarCheckBox = new ScreenFXTaskbarCheckBox(stage, screenNr, buttonSize);
// overgive positioner with event related data
ScreenFXPositioner positioner = new ScreenFXPositioner(stage, graphicsDevices, screenNr);
new ScreenFXGridAllocator(positioner, buttonGridPane, buttonSize);
// the screen number of the iterating list - number #1 is main
// screen
Label screenLabel = new Label("#" + (screenNr + 1));
ImageView screenIcon = new ImageView(this.getClass()
.getResource(ScreenFXConfig.getResourcePath() + "images/screen.png").toExternalForm());
screenLabel.setGraphic(screenIcon);
screenLabel.setMaxWidth(Double.MAX_VALUE);
screenLabel.setTextAlignment(TextAlignment.CENTER);
screenLabel.setAlignment(Pos.CENTER);
// a tooltip which shows some display information for each
// device
Tooltip screenInfoTooltip = new Tooltip(ScreenFXConfig.getResourceBundle().getString(
"screeninformation")
+ ":\n"
+ graphicsDevices[screenNr].getDisplayMode().getWidth()
+ "x"
+ graphicsDevices[screenNr].getDisplayMode().getHeight()
+ "px @"
+ graphicsDevices[screenNr].getDisplayMode().getRefreshRate()
+ "Hz - "
+ graphicsDevices[screenNr].getDisplayMode().getBitDepth() + "bit");
screenLabel.setTooltip(screenInfoTooltip);
HBox screenLabelCenteringHBox = new HBox(2);
HBox.setHgrow(screenLabel, Priority.ALWAYS);
screenLabelCenteringHBox.getChildren().add(screenLabel);
VBox screenVBox = new VBox(2);
screenVBox.getChildren().addAll(buttonGridPane, taskBarCheckBox, screenLabelCenteringHBox);
screenAnchorPanes.get(screenNr).getChildren().add(screenVBox);
}
topAnchorPane.getChildren().add(screenGridPane);
getContent().addAll(topAnchorPane);
/*
* the following two event handlers check the user behavior of exiting
* and reentering the topAnchorPane. If the user exits while
* delayService runs and turns to state "SUCCEEDED". The popup will
* hide. If the user reenters topAnchorPane while delayService is
* running yet the Service will take the value "CANCELED". The popup
* will not hide.
*/
topAnchorPane.setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
// (re)start new delay service
delayService.restart();
delayService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
hide();
}
});
}
});
topAnchorPane.setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if (delayService.isRunning()) {
delayService.cancel();
}
}
});
// a dirty resize method to align rectangle to popup
setOnShown(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent arg0) {
PointerInfo mousePointer = MouseInfo.getPointerInfo();
int x = (int) mousePointer.getLocation().getX() - (int) getWidth() / 2;
int y = (int) mousePointer.getLocation().getY() - (int) getHeight() / 2;
setX(x);
setY(y);
borderRectangle.setWidth(getWidth());
borderRectangle.setHeight(getHeight());
}
});
}
}