/*
* Copyright (C) 2015 thirdy
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.thirdy.blackmarket;
import java.awt.Toolkit;
import java.util.List;
import java.util.regex.Pattern;
import javax.sound.sampled.LineUnavailableException;
import org.controlsfx.control.GridCell;
import org.controlsfx.control.GridView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.jexiletools.es.ExileToolsSearchClient;
import io.jexiletools.es.ExileToolsSearchClient.ExileToolsSearchResult;
import io.jexiletools.es.model.json.ExileToolsHit;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.DepthTest;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Callback;
import net.thirdy.blackmarket.controls.ControlPane;
import net.thirdy.blackmarket.controls.Dialogs;
import net.thirdy.blackmarket.controls.ItemGridCell;
import net.thirdy.blackmarket.controls.SearchResultsPane;
import net.thirdy.blackmarket.fxcontrols.SlidingPane;
import net.thirdy.blackmarket.fxcontrols.WindowButtons;
import net.thirdy.blackmarket.fxcontrols.WindowResizeButton;
import net.thirdy.blackmarket.service.ExileToolsLadderService;
import net.thirdy.blackmarket.service.ExileToolsLastIndexUpdateService;
import net.thirdy.blackmarket.service.ExileToolsService;
import net.thirdy.blackmarket.service.Ladder;
import net.thirdy.blackmarket.util.ImageCache;
import net.thirdy.blackmarket.util.LangContants;
import net.thirdy.blackmarket.util.SoundUtils;
/**
* @author thirdy
*
*/
public class BlackmarketApplication extends Application {
public static final int ITEM_GRID_CELL_WIDTH = 380;
public static final int ITEM_GRID_CELL_HEIGHT = 270;
// private static final int WINDOW_HEIGHT = 738;
// private static final int WINDOW_WIDTH = 1166;
private static final String BLACK_MARKET_API_KEY = "4b1ccf2fce44441365118e9cd7023c38";
public static final String VERSION = "Version: 0.6.6" + (Main.DEVELOPMENT_MODE ? " [DEVELOPMENT MODE]" : LangContants.STRING_EMPTY);
private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
private static BlackmarketApplication blackmarketApplication;
private static ExileToolsSearchClient exileToolsESClient;
private Scene scene;
private BorderPane root;
private ToolBar toolBar;
private WindowResizeButton windowResizeButton;
private double mouseDragOffsetX = 0;
private double mouseDragOffsetY = 0;
// private final ObservableList<ExileToolsHit> searchResultCurrentHits =
// FXCollections.observableArrayList();
public static BlackmarketApplication getBlackmarketApplication() {
return blackmarketApplication;
}
public static ExileToolsSearchClient getExileToolsESClient() {
return exileToolsESClient;
}
private final ExileToolsLadderService exileToolsLadderService = new ExileToolsLadderService();
private final ExileToolsService searchService = new ExileToolsService();
private final ExileToolsLastIndexUpdateService lastIndexUpdateService = new ExileToolsLastIndexUpdateService();
private SearchResultsPane searchResultsPane;
private Label versionText;
private Label indexerLastUpdateText;
private ControlPane controlPane;
private Button collapseButton;
private SlidingPane searchPane;
@Override
public void stop() throws Exception {
super.stop();
exileToolsESClient.shutdown();
}
@Override
public void start(final Stage stage) {
blackmarketApplication = this;
exileToolsESClient = new ExileToolsSearchClient(BLACK_MARKET_API_KEY);
stage.setTitle("Blackmarket");
stage.getIcons().add(ImageCache.getInstance().get("/Blackmarket-logo.png"));
// create root stack pane that we use to be able to overlay proxy dialog
StackPane layerPane = new StackPane();
Region veilOfTheNight = new Region();
veilOfTheNight.setStyle("-fx-background-color: rgba(0, 0, 0, 0.7)");
ProgressIndicator progressIndicator = new ProgressIndicator(-1.0f);
progressIndicator.setMaxSize(150, 150);
stage.initStyle(StageStyle.UNDECORATED);
// create window resize button
windowResizeButton = new WindowResizeButton(stage, 200, 200);
// create root
root = new BorderPane() {
@Override
protected void layoutChildren() {
super.layoutChildren();
windowResizeButton.autosize();
windowResizeButton.setLayoutX(getWidth() - windowResizeButton.getLayoutBounds().getWidth());
windowResizeButton.setLayoutY(getHeight() - windowResizeButton.getLayoutBounds().getHeight());
}
};
root.getStyleClass().add("application");
root.setId("root");
layerPane.setDepthTest(DepthTest.DISABLE);
layerPane.getChildren().add(root);
Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
// boolean is3dSupported = false;
// , WINDOW_WIDTH, WINDOW_HEIGHT, is3dSupported
scene = new Scene(layerPane);
double screenWidth = primaryScreenBounds.getWidth();
double screenHeight = primaryScreenBounds.getHeight();
stage.setWidth(screenWidth - (screenWidth * 0.15));
stage.setHeight(screenHeight);
scene.getStylesheets().add(this.getClass().getResource("/css/blackmarket.css").toExternalForm());
// create main toolbar
setupToolbar(stage);
this.root.setTop(toolBar);
controlPane = new ControlPane(this::searchHandler);
searchPane = new SlidingPane(controlPane);
searchPane.expandedHeightProperty().bind(scene.heightProperty().subtract(98.0));
collapseButton = searchPane.getControlButton();
scene.addEventHandler(KeyEvent.KEY_PRESSED, keyEvent -> {
// if ((keyEvent.isControlDown() || keyEvent.isAltDown())
// && (keyEvent.getCode() == KeyCode.UP || keyEvent.getCode() == KeyCode.DOWN)) {
// showCollapseButton.fire();
// }
if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.SPACE) {
collapseButton.fire();
}
if (keyEvent.getCode() == KeyCode.ENTER
&& keyEvent.isControlDown()
&& !progressIndicator.isVisible()
&& searchPane.isExpanded()) {
controlPane.fireSearchEvent();
}
});
controlPane.installCollapseButton(collapseButton);
controlPane.getBtnDurianMode().setOnAction(e -> toggleDurianService());
controlPane.getBtnOnlineOnly().disableProperty().bind(exileToolsLadderService.sleepingProperty().not());
controlPane.getLblLadderServiceStatus().textProperty().bind(exileToolsLadderService.messageProperty());
controlPane.getLblLadderServiceStatus().tooltipProperty().get().textProperty()
.bind(Bindings.createStringBinding(() -> {
Ladder ladder = exileToolsLadderService.resultProperty().get();
return ladder != null ? ladder.leagueSizes() : LangContants.STRING_EMPTY;
}, exileToolsLadderService.resultProperty()));
controlPane.getProgIndctrLadderService().visibleProperty().bind(exileToolsLadderService.sleepingProperty().not());
searchPane.setId("searchPane");
AnchorPane centerPane = new AnchorPane();
// DoubleProperty wProp = DoubleProperty.
searchResultsPane = new SearchResultsPane();
searchResultsPane.setHorizontalCellSpacing(9);
searchResultsPane.setVerticalCellSpacing(5);
// searchResultsPane.setB
// searchResultsPane.setCellHeight(300);
// searchResultsPane.setCellWidth(ITEM_GRID_CELL_WIDTH);
DoubleBinding oneThirdWidthBinding = Bindings.createDoubleBinding(
() -> (centerPane.getWidth() / 3) - 25, centerPane.widthProperty());
searchResultsPane.cellWidthProperty().bind(oneThirdWidthBinding);
searchResultsPane.setCellHeight(ITEM_GRID_CELL_HEIGHT);
searchResultsPane.setCellFactory(new Callback<GridView<ExileToolsHit>, GridCell<ExileToolsHit>>() {
public GridCell<ExileToolsHit> call(GridView<ExileToolsHit> gridView) {
return new ItemGridCell();
}
});
AnchorPane.setTopAnchor(searchResultsPane, 0.0);
AnchorPane.setLeftAnchor(searchResultsPane, 0.0);
AnchorPane.setRightAnchor(searchResultsPane, 0.0);
AnchorPane.setBottomAnchor(searchResultsPane, 0.0);
AnchorPane.setBottomAnchor(searchPane, 10.0);
AnchorPane.setLeftAnchor(searchPane, 10.0);
AnchorPane.setRightAnchor(searchPane, 23.0);
centerPane.getChildren().addAll(searchResultsPane, searchPane);
Label progressIndicatorLabel = new Label();
progressIndicator.progressProperty().bind(searchService.progressProperty());
progressIndicatorLabel.textProperty().bind(searchService.messageProperty());
veilOfTheNight.visibleProperty().bind(searchService.runningProperty());
progressIndicatorLabel.visibleProperty().bind(searchService.runningProperty());
progressIndicator.visibleProperty().bind(searchService.runningProperty());
searchResultsPane.onlineOnlyProperty().bind(controlPane.getBtnOnlineOnly().selectedProperty());
searchResultsPane.ladderProperty().bind(exileToolsLadderService.resultProperty());
controlPane.getSearchHitLabel().textProperty().bind(searchResultsPane.searchLabelStatusProperty());
searchService.setOnSucceeded(e -> searchSucceeded());
searchService.setOnFailed(e -> Dialogs.showExceptionDialog(searchService.getException()));
StackPane centerStackPane = new StackPane(centerPane, veilOfTheNight, progressIndicator,
progressIndicatorLabel);
this.root.setCenter(centerStackPane);
// add window resize button so its on top
windowResizeButton.setManaged(false);
this.root.getChildren().add(windowResizeButton);
// show stage
stage.setScene(scene);
stage.show();
durianTimer.setOnSucceeded(e -> durianTimerSucceeded());
lastIndexUpdateService.restart();
exileToolsLadderService.restart();
}
private void setupToolbar(final Stage stage) {
toolBar = new ToolBar();
toolBar.setId("mainToolBar");
Image logoImg = new Image(this.getClass().getResourceAsStream("/images/blackmarket-logo.png"));
int x = 15 ;
int y = 0 ;
int width = 178;
int height = (int) logoImg.getHeight() ;
logoImg = new WritableImage(logoImg.getPixelReader(), x, y, width, height);
ImageView logo = new ImageView(logoImg);
// logo.setFitWidth(.5);
// logo.setPreserveRatio(true);
// logo.setFitHeight(36);
HBox.setMargin(logo, new Insets(0,0,0,1));
toolBar.getItems().add(logo);
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
toolBar.getItems().add(spacer);
Region spacer2 = new Region();
HBox.setHgrow(spacer2, Priority.ALWAYS);
toolBar.getItems().add(spacer2);
toolBar.setPrefHeight(66);
toolBar.setMinHeight(66);
toolBar.setMaxHeight(66);
GridPane.setConstraints(toolBar, 0, 0);
// add close min max
final WindowButtons windowButtons = new WindowButtons(stage);
versionText = new Label(VERSION);
indexerLastUpdateText = new Label("Indexer Last Update: ");
Label indexerLastUpdateValueText = new Label();
indexerLastUpdateValueText.textProperty().bind(lastIndexUpdateService.messageProperty());
indexerLastUpdateText.textProperty().bind(lastIndexUpdateService.countdownProperty());
lastIndexUpdateService.setOnSucceeded(e -> lastIndexUpdateService.restart());
lastIndexUpdateService.setOnFailed (e -> lastIndexUpdateService.restart());
VBox vBox = new VBox(versionText, indexerLastUpdateText, indexerLastUpdateValueText);
vBox.setAlignment(Pos.TOP_RIGHT);
vBox.setPadding(new Insets(1, 5, 1, 5));
toolBar.getItems().add(vBox);
toolBar.getItems().add(windowButtons);
// add window header double clicking
toolBar.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
windowButtons.toogleMaximized();
}
}
});
// add window dragging
toolBar.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
mouseDragOffsetX = event.getSceneX();
mouseDragOffsetY = event.getSceneY();
}
});
toolBar.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
if (!windowButtons.isMaximized()) {
stage.setX(event.getScreenX() - mouseDragOffsetX);
stage.setY(event.getScreenY() - mouseDragOffsetY);
}
}
});
}
private void searchHandler(String json) {
logger.debug("Search: " + json);
if (json.isEmpty()) {
return;
}
boolean sizeIsThousands = Pattern.compile("\"size\"\\s:\\s\\d{4,}").matcher(json).find();
if (sizeIsThousands) {
// logger.info("Playing /audio/youarebeingselfish.wav");
try {
// Doesn't work damn
// AudioClip malachi = new AudioClip(BlackmarketApplication.class.getResource("/audio/youarebeingselfish.wav").toString());
// malachi.play(1.0);
Dialogs.showInfo("You are being selfish!");
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
} else {
searchService.setJson(json);
searchService.restart();
}
}
ScheduledService<Void> durianTimer = new ScheduledService<Void>() {
@Override
protected Task<Void> createTask() {
return new Task<Void>() {
@Override
protected Void call() throws Exception {
int secs = 60 * 10;
// int secs = 15;
for (int i = 0; i < secs; i++) {
updateMessage("Durian: " + (secs - i) + " sec");
Thread.sleep(1000);
}
return null;
}
};
}
};
private void toggleDurianService() {
ToggleButton btnDurianMode = controlPane.getBtnDurianMode();
if (btnDurianMode.isSelected()) {
btnDurianMode.textProperty().bind(durianTimer.messageProperty());
durianTimer.restart();
} else {
btnDurianMode.textProperty().unbind();
btnDurianMode.textProperty().set("Durian Notifier");
durianTimer.cancel();
}
}
private void durianTimerSucceeded() {
if (!searchService.isRunning()) {
controlPane.fireSearchEvent();
durianTimer.restart();
}
}
private void searchSucceeded() {
ExileToolsSearchResult result = searchService.getValue();
List<ExileToolsHit> exileToolHits = result.getExileToolHits();
searchResultsPane.setSearchResultItems(exileToolHits);
Platform.runLater(() -> searchPane.toggleSlide());
if (controlPane.getBtnDurianMode().isSelected() && exileToolHits.size() > 0) {
try {
SoundUtils.tone(2000,100);
} catch (LineUnavailableException e) {
e.printStackTrace();
Toolkit.getDefaultToolkit().beep();
}
}
}
}