All Downloads are FREE. Search and download functionalities are using the official Maven repository.

impl.org.controlsfx.worldmap.WorldMapViewSkin Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2016, ControlsFX
 * All rights reserved.
 * 

* Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of ControlsFX, any associated website, nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package impl.org.controlsfx.worldmap; import javafx.beans.Observable; import javafx.collections.*; import javafx.css.PseudoClass; import javafx.event.EventTarget; import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.control.SkinBase; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; import javafx.scene.input.ZoomEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.shape.Rectangle; import javafx.util.Callback; import org.controlsfx.control.WorldMapView; import java.io.IOException; import java.util.*; public class WorldMapViewSkin extends SkinBase { private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected"); private static final String DEFAULT_STYLE_LOCATION = "location"; private static final String DEFAULT_STYLE_COUNTRY = "country"; private static final double PREFERRED_WIDTH = 1009; private static final double PREFERRED_HEIGHT = 665; private static double MAP_OFFSET_X = -PREFERRED_WIDTH * 0.0285; private static double MAP_OFFSET_Y = PREFERRED_HEIGHT * 0.195; private final Map> countryPathMap = new HashMap<>(); private final Map> countryViewMap = new HashMap<>(); private Pane countryPane; private Group group; private Group locationsGroup; protected ObservableMap locationMap; public WorldMapViewSkin(WorldMapView view) { super(view); locationMap = FXCollections.observableHashMap(); group = new Group(); group.setManaged(false); group.setAutoSizeChildren(false); locationsGroup = new Group(); locationsGroup.setManaged(false); locationsGroup.visibleProperty().bind(view.showLocationsProperty()); locationsGroup.setAutoSizeChildren(false); countryPane = new Pane(); countryPane.getChildren().add(group); view.getLocations().addListener(locationsListener); // countries final ListChangeListener countriesListener = change -> buildView(); view.getCountries().addListener(countriesListener); locationMap.addListener((MapChangeListener) change -> { if (change.wasRemoved()) { locationsGroup.getChildren().remove(change.getValueRemoved()); } }); BorderPane borderPane = new BorderPane(); borderPane.setCenter(countryPane); getChildren().add(borderPane); view.zoomFactorProperty().addListener(it -> view.requestLayout()); Properties mapData = loadData(); for (WorldMapView.Country country : WorldMapView.Country.values()) { String countryData = (String) mapData.get(country.name()); if (countryData == null) { System.out.println("Missing SVG path for country " + country.getLocale().getDisplayCountry() + " (" + country + ")"); } else { StringTokenizer st = new StringTokenizer(countryData, ";"); List paths = new ArrayList<>(); while (st.hasMoreTokens()) { paths.add(st.nextToken()); } countryPathMap.put(country, paths); } } buildView(); view.getSelectedCountries().addListener(weakCountrySelectionListener); view.selectedCountriesProperty().addListener((Observable it) -> view.getSelectedCountries().addListener(weakCountrySelectionListener)); view.getSelectedLocations().addListener(weakLocationSelectionListener); view.selectedLocationsProperty().addListener((Observable it) -> view.getSelectedLocations().addListener(weakLocationSelectionListener)); view.getLocations().addListener(weakLocationsListener); view.locationsProperty().addListener((Observable it) -> view.getLocations().addListener(weakLocationsListener)); view.getLocations().forEach(location -> addLocation(location)); view.addEventHandler(ScrollEvent.SCROLL, evt -> { evt.consume(); }); view.addEventHandler(ZoomEvent.ZOOM, evt -> { double factor = evt.getZoomFactor(); view.setZoomFactor(view.getZoomFactor() * factor); evt.consume(); }); view.addEventHandler(MouseEvent.MOUSE_PRESSED, evt -> { dragX = evt.getX(); dragY = evt.getY(); }); view.addEventHandler(MouseEvent.MOUSE_DRAGGED, evt -> { double deltaX = evt.getX() - dragX; double deltaY = evt.getY() - dragY; group.setTranslateX(group.getTranslateX() + deltaX); group.setTranslateY(group.getTranslateY() + deltaY); dragX = evt.getX(); dragY = evt.getY(); }); view.addEventHandler(MouseEvent.MOUSE_CLICKED, evt -> { if (evt.getClickCount() == 2) { view.setZoomFactor(1); group.setTranslateX(0); group.setTranslateY(0); } else if (evt.getButton().equals(MouseButton.PRIMARY)) { EventTarget target = evt.getTarget(); if (target instanceof WorldMapView.CountryView) { WorldMapView.CountryView path = (WorldMapView.CountryView) target; WorldMapView.Country country = path.getCountry(); boolean wasSelected = view.getSelectedCountries().contains(country); if (view.getCountrySelectionMode().equals(WorldMapView.SelectionMode.SINGLE) || !(evt.isShortcutDown() || evt.isShiftDown())) { view.getSelectedCountries().clear(); } if (wasSelected) { view.getSelectedCountries().remove(country); } else { view.getSelectedCountries().add(country); } } else if (target.equals(countryPane)){ view.getSelectedCountries().clear(); } else { for (WorldMapView.Location location : locationMap.keySet()) { Node node = locationMap.get(location); if (target.equals(node)) { boolean wasSelected = view.getSelectedLocations().contains(location); if (view.getLocationSelectionMode().equals(WorldMapView.SelectionMode.SINGLE) || !(evt.isShortcutDown() || evt.isShiftDown())) { view.getSelectedLocations().clear(); } if (wasSelected) { view.getSelectedLocations().remove(location); } else { view.getSelectedLocations().add(location); } break; } } } } }); Rectangle clip = new Rectangle(); clip.widthProperty().bind(view.widthProperty()); clip.heightProperty().bind(view.heightProperty()); view.setClip(clip); view.countryViewFactoryProperty().addListener(it -> buildView()); view.locationViewFactoryProperty().addListener(it -> buildView()); } private double dragX; private double dragY; // locations private final ListChangeListener locationsListener = change -> { while (change.next()) { if (change.wasAdded()) { change.getAddedSubList().forEach(location -> addLocation(location)); } else if (change.wasRemoved()) { change.getRemoved().forEach(location -> removeLocation(location)); } } }; private final WeakListChangeListener weakLocationsListener = new WeakListChangeListener(locationsListener); // country selections private final ListChangeListener countrySelectionListener = change -> { while (change.next()) { if (change.wasAdded()) { change.getAddedSubList().forEach( country -> countryViewMap.get(country).forEach(path -> path.pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, true))); } else if (change.wasRemoved()) { change.getRemoved().forEach( country -> countryViewMap.get(country).forEach(path -> path.pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, false))); } } }; private final WeakListChangeListener weakCountrySelectionListener = new WeakListChangeListener(countrySelectionListener); // location selections private final ListChangeListener locationSelectionListener = change -> { while (change.next()) { if (change.wasAdded()) { change.getAddedSubList().forEach( location -> locationMap.get(location).pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, true)); } else if (change.wasRemoved()) { change.getRemoved().forEach( location -> locationMap.get(location).pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, false)); } } }; private final WeakListChangeListener weakLocationSelectionListener = new WeakListChangeListener(locationSelectionListener); private Point2D getLocationCoordinates(WorldMapView.Location location) { double x = (location.getLongitude() + 180) * (PREFERRED_WIDTH / 360) + MAP_OFFSET_X; double y = (PREFERRED_HEIGHT / 2) - (PREFERRED_WIDTH * (Math.log(Math.tan((Math.PI / 4) + (Math.toRadians(location.getLatitude()) / 2)))) / (2 * Math.PI)) + MAP_OFFSET_Y; return new Point2D(x, y); } private void addLocation(WorldMapView.Location location) { Point2D coordinates = getLocationCoordinates(location); Callback locationViewFactory = getSkinnable().getLocationViewFactory(); Node view = locationViewFactory.call(location); if (view == null) { throw new IllegalArgumentException("location view factory returned NULL"); } view.getStyleClass().add(DEFAULT_STYLE_LOCATION); view.setManaged(false); locationsGroup.getChildren().add(view); view.applyCss(); view.resizeRelocate(coordinates.getX(), coordinates.getY(), view.prefWidth(-1), view.prefHeight(-1)); locationMap.put(location, view); } private void removeLocation(WorldMapView.Location location) { locationMap.remove(location); } private void buildView() { group.getChildren().clear(); locationsGroup.getChildren().clear(); if (Double.compare(getSkinnable().getPrefWidth(), 0.0) <= 0 || Double.compare(getSkinnable().getPrefHeight(), 0.0) <= 0 || Double.compare(getSkinnable().getWidth(), 0.0) <= 0 || Double.compare(getSkinnable().getHeight(), 0.0) <= 0) { if (getSkinnable().getPrefWidth() > 0 && getSkinnable().getPrefHeight() > 0) { getSkinnable().setPrefSize(getSkinnable().getPrefWidth(), getSkinnable().getPrefHeight()); } else { getSkinnable().setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT); } } Callback factory = getSkinnable().getCountryViewFactory(); for (WorldMapView.Country country : WorldMapView.Country.values()) { if (getSkinnable().getCountries().isEmpty() || getSkinnable().getCountries().contains(country)) { List countryViews = new ArrayList<>(); for (String svgPath : countryPathMap.get(country)) { WorldMapView.CountryView view = factory.call(country); if (view != null) { view.setContent(svgPath); view.getStyleClass().add(0, DEFAULT_STYLE_COUNTRY); group.getChildren().addAll(view); countryViews.add(view); } } countryViewMap.put(country, countryViews); } } for (WorldMapView.Location location : locationMap.keySet()) { Point2D coordinates = getLocationCoordinates(location); if (group.getLayoutBounds().contains(coordinates)) { locationsGroup.getChildren().add(locationMap.get(location)); } } group.getChildren().add(locationsGroup); getSkinnable().requestLayout(); } @Override protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { super.layoutChildren(contentX, contentY, contentWidth, contentHeight); double prefWidth = group.prefWidth(-1); double prefHeight = group.prefHeight(-1); double scaleX = contentWidth / prefWidth; double scaleY = contentHeight / prefHeight; double scale = Math.min(scaleX, scaleY) * getSkinnable().getZoomFactor(); group.setTranslateX(-group.getLayoutBounds().getMinX()); group.setTranslateY(-group.getLayoutBounds().getMinY()); group.setScaleX(scale); group.setScaleY(scale); group.setLayoutX((contentWidth - prefWidth) / 2); group.setLayoutY((contentHeight - prefHeight) / 2); } /** * Loads the properties file that is storing the SVG path information for each country. This method * can be overriden to provide a different, maybe more detailed, data set. However, the default data * set used by ControlsFX has to be small in order to keep the distribution small. The structure of * the properties file needs to look like this: *

     *     AE=M619.87,393.72L620.37,393.57L620.48,394.41L622.67,393.93 ....
     *     AF=M646.88,356.9L649.74,358.2L651.85,357.74L652.44,356.1 ....
     *     ......
     * 
* * @return the properties file storing the SVG path data for each country */ protected Properties loadData() { Properties mapData = new Properties(); try { mapData.load(WorldMapView.class.getResourceAsStream("worldmap-small.properties")); } catch (IOException e) { e.printStackTrace(); } return mapData; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy