Archived
0

Create repository

This commit is contained in:
iMoHax
2014-07-16 16:38:48 +04:00
committed by iMoHax
commit 15bfe2e0f3
79 changed files with 5741 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,11 @@
.good .diff {
-fx-fill: green;
-fx-font-weight: bold;
}
.bad .diff {
-fx-fill: blue;
-fx-font-weight: bold;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} (%F:%L) - %m%n

View File

@@ -0,0 +1,12 @@
log4j.rootLogger=INFO, FILE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} (%F:%L) - %m%n
log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender
log4j.appender.FILE.File=traider.log
log4j.appender.FILE.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.FILE.threshold=INFO
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} - %m%n

106
client/pom.xml Normal file
View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>trader</groupId>
<artifactId>Trader</artifactId>
<version>1.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>client</artifactId>
<organization>
<name>Open Software</name>
</organization>
<profiles>
<profile>
<id>develop</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<project.ext-resources>ext-resources/develop</project.ext-resources>
</properties>
</profile>
<profile>
<id>release</id>
<properties>
<project.ext-resources>ext-resources/release</project.ext-resources>
</properties>
</profile>
</profiles>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources><resource><directory>src/main/resources</directory><filtering>false</filtering></resource></resources>
<testResources><testResource><directory>src/test/resources</directory><filtering>false</filtering></testResource></testResources>
<plugins>
<plugin>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
<resources>
<resource>
<directory>ext-resources/all</directory>
<filtering>false</filtering>
</resource>
<resource>
<directory>${project.ext-resources}</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,77 @@
package ru.trader;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.apache.log4j.PropertyConfigurator;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.controllers.Screeners;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
public class Main extends Application {
private final static Logger LOG = LoggerFactory.getLogger(Main.class);
@Override
public void start(Stage primaryStage) throws Exception {
loadResources();
primaryStage.setTitle("Trader");
primaryStage.setScene(new Scene(Screeners.newScreeners(Main.class.getResource("/view/main.fxml"),getUrl("style.css").toExternalForm())));
primaryStage.setOnCloseRequest((we)->{
try {
if (World.getMarket().isChange()){
Action res = Screeners.showConfirm("Изменения не были сохранены, сохранить?");
if (res == Dialog.Actions.YES) World.save();
else if (res == Dialog.Actions.CANCEL) we.consume();
}
Screeners.closeAll();
} catch (FileNotFoundException | UnsupportedEncodingException | XMLStreamException e) {
LOG.error("Ошибка при сохранении",e);
Screeners.showException(e);
}
});
primaryStage.show();
}
@Override
public void stop() throws Exception {
super.stop();
}
public static void main(String[] args) {
PropertyConfigurator.configure("log4j.properties");
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.print("Exception in thread \"" + t.getName() + "\" ");
e.printStackTrace(System.err);
LOG.error("", e);
Screeners.showException(e);
});
launch(args);
}
private static void loadResources() throws IOException {
Screeners.loadItemDescStage(getUrl(("itemDesc.fxml")));
Screeners.loadVEditorStage(getUrl(("vEditor.fxml")));
Screeners.loadAddOfferStage(getUrl(("oEditor.fxml")));
Screeners.loadOrdersStage(getUrl(("orders.fxml")));
}
private static URL getUrl(String filename) throws MalformedURLException {
File file = new File("conf"+File.separator+filename);
if (file.exists()) return file.toURI().toURL();
return Main.class.getResource("/view/"+filename);
}
}

View File

@@ -0,0 +1,47 @@
package ru.trader;
import org.xml.sax.SAXException;
import ru.trader.core.Market;
import ru.trader.core.SimpleMarket;
import ru.trader.model.ModelFabrica;
import ru.trader.store.Store;
import ru.trader.store.XSSFImporter;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class World {
private static Market world;
private static final String STORE_FILE="world.xml";
static {
try {
File file = new File(STORE_FILE);
if (file.exists()) world = Store.loadFromFile(file);
else world = new SimpleMarket();
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new RuntimeException(e);
}
}
public static void save() throws FileNotFoundException, UnsupportedEncodingException, XMLStreamException {
Store.saveToFile(world, new File("world.xml"));
world.setChange(false);
}
public static void imp(File file) throws IOException, SAXException {
XSSFImporter xssfImporter = new XSSFImporter(file);
world = xssfImporter.doImport();
ModelFabrica.clear();
world.setChange(true);
}
public static Market getMarket() {
return world;
}
}

View File

@@ -0,0 +1,54 @@
package ru.trader.controllers;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.ListView;
import javafx.util.Duration;
import org.controlsfx.control.PopOver;
import ru.trader.model.*;
public class ItemDescController {
private ItemDescModel item;
@FXML
private ListView<OfferModel> seller;
@FXML
private ListView<OfferModel> buyer;
public void setItemDesc(ItemDescModel itemDesc){
item = itemDesc;
fill();
}
private void fill(){
seller.setItems(FXCollections.observableList(item.getSeller()));
buyer.setItems(FXCollections.observableList(item.getBuyer()));
}
private PopOver popup;
public void popup(Node owner, Parent itemDescScreen) {
if (popup != null && popup.isShowing()) return;
if (popup == null) {
popup = new PopOver(itemDescScreen);
popup.detach();
popup.setAutoHide(true);
}
popup.show(owner);
}
public void close() {
if (popup!=null){
popup.hide(Duration.ZERO);
}
}
}

View File

@@ -0,0 +1,111 @@
package ru.trader.controllers;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.input.MouseButton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.model.*;
import ru.trader.model.support.ChangeMarketListener;
import ru.trader.model.support.ModelBindings;
public class ItemsController {
private final static Logger LOG = LoggerFactory.getLogger(ItemsController.class);
@FXML
private TableView<ItemDescModel> tblItems;
@FXML
private TableColumn<ItemDescModel, Number> minProfit;
@FXML
private TableColumn<ItemDescModel, Number> avgProfit;
@FXML
private TableColumn<ItemDescModel, Number> maxProfit;
@FXML
private void initialize() {
tblItems.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> {
if (n!=null) Screeners.changeItemDesc(n);
});
tblItems.setOnMouseClicked((e) -> {
if (e.getButton() == MouseButton.SECONDARY){
Screeners.showItemDesc(tblItems);
}
});
minProfit.setCellValueFactory((data) -> {
ItemDescModel iDesc = data.getValue();
return ModelBindings.diff(iDesc.minBuyProperty(),iDesc.maxSellProperty());
});
avgProfit.setCellValueFactory((data) -> {
ItemDescModel iDesc = data.getValue();
return iDesc.avgBuyProperty().subtract(iDesc.avgSellProperty());
});
maxProfit.setCellValueFactory((data) -> {
ItemDescModel iDesc = data.getValue();
return ModelBindings.diff(iDesc.maxBuyProperty(), iDesc.minSellProperty());
});
init();
}
void init(){
MarketModel market = MainController.getMarket();
market.addListener(new ItemsStatChangeListener());
tblItems.setItems(FXCollections.observableArrayList(market.itemsProperty()));
if (tblItems.getSortOrder().size()>0)
tblItems.sort();
}
private void refresh(OfferModel offer){
LOG.info("Refresh item desc link with item of offer {}", offer);
for (ItemDescModel descModel : tblItems.getItems()) {
if (descModel.hasItem(offer)){
descModel.refresh(offer.getType());
return;
}
}
}
private void refresh(){
LOG.info("Refresh all stats");
tblItems.getItems().forEach(ItemDescModel::refresh);
}
private void addItem(ItemDescModel item){
tblItems.getItems().add(item);
}
private class ItemsStatChangeListener extends ChangeMarketListener {
@Override
public void add(ItemDescModel item) {
addItem(item);
}
@Override
public void add(OfferModel offer) {
refresh(offer);
}
@Override
public void add(VendorModel vendor) {
refresh();
}
@Override
public void remove(OfferModel offer) {
refresh(offer);
}
@Override
public void priceChange(OfferModel offer, double price, double value) {
refresh(offer);
}
}
}

View File

@@ -0,0 +1,106 @@
package ru.trader.controllers;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import org.controlsfx.dialog.Dialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import ru.trader.World;
import ru.trader.model.MarketModel;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Optional;
public class MainController {
private final static Logger LOG = LoggerFactory.getLogger(MainController.class);
private static MarketModel world = new MarketModel(World.getMarket());
private static MarketModel market = new MarketModel(World.getMarket());
@FXML
private BorderPane mainPane;
@FXML
private OffersController offersController;
@FXML
private ItemsController itemsController;
public OffersController getOffersController() {
return offersController;
}
public BorderPane getMainPane(){
return mainPane;
}
public static MarketModel getMarket() {
return market;
}
public static MarketModel getWorld() {
return world;
}
public void setMarket(MarketModel market) {
MarketModel old = MainController.market;
MainController.market = market;
MainController.market.addAllListener(old.getListeners());
}
public void save(ActionEvent actionEvent) {
try {
World.save();
} catch (FileNotFoundException | UnsupportedEncodingException | XMLStreamException e) {
LOG.error("Error on save file",e);
}
}
public void importWorld(ActionEvent actionEvent) {
try {
FileChooser fileChooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("Excel files (*.xlsx)", "*.xlsx");
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setInitialDirectory(new File("."));
File file = fileChooser.showOpenDialog(null);
if (file !=null) {
World.imp(file);
reload();
}
} catch (SAXException | IOException e) {
LOG.error("Error on import file", e);
}
}
public void addItem(ActionEvent actionEvent){
Optional<String> res = Dialogs.create()
.title("Добавление нового товара")
.message("Введите название товара")
.showTextInput();
if (res.isPresent()) market.add(market.newItem(res.get()));
}
public void addVendor(ActionEvent actionEvent) {
Screeners.showAddVendor();
}
public void editVendor(ActionEvent actionEvent) {
Screeners.showEditVendor(offersController.getVendor());
}
private void reload(){
world = new MarketModel(World.getMarket());
market = world;
itemsController.init();
offersController.init();
}
}

View File

@@ -0,0 +1,163 @@
package ru.trader.controllers;
import javafx.collections.FXCollections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import ru.trader.core.Vendor;
import ru.trader.model.*;
import ru.trader.model.support.ChangeMarketListener;
import java.util.Collection;
import java.util.Iterator;
public class OffersController {
private final static Logger LOG = LoggerFactory.getLogger(OffersController.class);
private VendorModel vendor;
@FXML
private ComboBox<Vendor> vendors;
@FXML
private TableView<OfferDescModel> tblSell;
@FXML
private TableView<OfferDescModel> tblBuy;
// инициализируем форму данными
@FXML
private void initialize() {
vendors.getSelectionModel().selectedItemProperty().addListener((ob, oldValue, newValue) ->{
if (newValue != null){
LOG.info("Change vendor to {}", newValue);
setVendor(newValue);
fillTables(vendor);
} else {
vendors.getSelectionModel().select(oldValue);
}
});
init();
}
void init(){
MarketModel market = MainController.getMarket();
market.addListener(new OffersChangeListener());
vendors.setItems(market.vendorsProperty());
vendors.getSelectionModel().selectFirst();
}
private void fillTables(VendorModel vendor){
if (vendor != null){
tblSell.setItems(FXCollections.observableList(vendor.getSells(this::asOfferDescModel)));
if (tblSell.getSortOrder().size()>0)
tblSell.sort();
tblBuy.setItems(FXCollections.observableList(vendor.getBuys(this::asOfferDescModel)));
if (tblBuy.getSortOrder().size()>0)
tblBuy.sort();
} else {
tblSell.getItems().clear();
tblBuy.getItems().clear();
}
}
@FXML
public void editPrice(TableColumn.CellEditEvent<OfferDescModel, Double> event){
OfferModel offer = event.getRowValue().getOffer();
offer.setPrice(event.getNewValue());
}
public VendorModel getVendor() {
return vendor;
}
private void setVendor(Vendor vendor){
this.vendor = MainController.getMarket().asModel(vendor);
}
private OfferDescModel asOfferDescModel(OfferModel offer){
return MainController.getMarket().asOfferDescModel(offer);
}
private void addOffer(OfferModel offer){
switch (offer.getType()){
case SELL: tblSell.getItems().add(asOfferDescModel(offer));
break;
case BUY: tblBuy.getItems().add(asOfferDescModel(offer));
break;
}
}
private void removeOffer(OfferModel offer){
switch (offer.getType()){
case SELL: remove(offer, tblSell.getItems());
break;
case BUY: remove(offer, tblBuy.getItems());
break;
}
}
private void remove(final OfferModel offer, final Collection<OfferDescModel> list){
Iterator<OfferDescModel> iterator = list.iterator();
while (iterator.hasNext()){
if (iterator.next().getOffer().equals(offer)){
iterator.remove();
break;
}
}
}
private void refresh(OfferModel offer){
LOG.info("Refresh lists link with item of offer {}", offer);
for (OfferDescModel descModel : tblSell.getItems()) {
if (descModel.hasItem(offer)){
descModel.refresh(offer.getType());
return;
}
}
for (OfferDescModel descModel : tblBuy.getItems()) {
if (descModel.hasItem(offer)){
descModel.refresh(offer.getType());
return;
}
}
}
private void refresh(){
LOG.info("Refresh lists");
tblSell.getItems().forEach(OfferDescModel::refresh);
tblBuy.getItems().forEach(OfferDescModel::refresh);
}
private class OffersChangeListener extends ChangeMarketListener {
@Override
public void add(OfferModel offer) {
refresh(offer);
if (offer.hasVendor(vendor)){
addOffer(offer);
}
}
@Override
public void add(VendorModel vendor) {
refresh();
}
@Override
public void remove(OfferModel offer) {
refresh(offer);
if (offer.hasVendor(vendor)) {
removeOffer(offer);
}
}
}
}

View File

@@ -0,0 +1,75 @@
package ru.trader.controllers;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import org.controlsfx.control.ButtonBar;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import ru.trader.model.ItemModel;
import ru.trader.view.support.NumberField;
import java.util.Optional;
public class OffersEditorController {
private final Action OK = new AbstractAction("OK") {
{
ButtonBar.setType(this, ButtonBar.ButtonType.OK_DONE);
}
@Override
public void handle(ActionEvent event) {
Dialog dlg = (Dialog) event.getSource();
dlg.hide();
}
};
@FXML
private Label name;
@FXML
private NumberField sell;
@FXML
private NumberField buy;
public Optional<DialogResult> showDialog(Parent parent, Parent content, ItemModel item, Number sell, Number buy) {
name.setText(item.getName());
this.sell.setValue(sell);
this.buy.setValue(buy);
OK.disabledProperty().bind(this.sell.wrongProperty().or(this.buy.wrongProperty()));
Dialog dlg = new Dialog(parent, "Создание заказов");
dlg.setContent(content);
dlg.getActions().addAll(OK, Dialog.Actions.CANCEL);
dlg.setResizable(false);
return Optional.ofNullable(dlg.show() == OK ? new DialogResult() : null);
}
public class DialogResult {
private double _sell;
private double _buy;
public DialogResult() {
_sell = sell.getValue().doubleValue();
_buy = buy.getValue().doubleValue();
}
public double getSell() {
return _sell;
}
public double getBuy() {
return _buy;
}
}
}

View File

@@ -0,0 +1,102 @@
package ru.trader.controllers;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.converter.LongStringConverter;
import org.controlsfx.control.ButtonBar;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import ru.trader.model.OfferDescModel;
import ru.trader.model.OfferModel;
import ru.trader.model.OrderModel;
import ru.trader.model.support.BindingsHelper;
import java.util.Collection;
import java.util.Optional;
public class OrdersController {
private final Action OK = new AbstractAction("OK") {
{
ButtonBar.setType(this, ButtonBar.ButtonType.OK_DONE);
}
@Override
public void handle(ActionEvent event) {
Dialog dlg = (Dialog) event.getSource();
dlg.hide();
}
};
@FXML
private TableView<OrderModel> tblOrders;
@FXML
private TableView<OfferModel> tblBuyers;
@FXML
private TableColumn<OrderModel, Long> count;
@FXML
private TableColumn<OrderModel, Long> maxCount;
@FXML
private TableColumn<OfferModel, Double> curProfit;
private OrderModel order;
@FXML
private void initialize() {
count.setCellFactory(TextFieldTableCell.forTableColumn(new LongStringConverter()));
maxCount.setCellFactory(TextFieldTableCell.forTableColumn(new LongStringConverter()));
tblOrders.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> changeOrder(n));
tblBuyers.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> setBuyer(n));
curProfit.setCellValueFactory(param -> {
OfferModel offer = param.getValue();
return order !=null ? order.getProfit(offer) : new SimpleDoubleProperty(Double.NaN).asObject();
});
}
public Optional<OrderModel> showDialog(Parent parent, Parent content, Collection<OfferDescModel> offers, double balance, long max) {
init(offers, balance, max);
Dialog dlg = new Dialog(parent, "Создание заказов");
dlg.setContent(content);
dlg.getActions().addAll(OK, Dialog.Actions.CANCEL);
dlg.setResizable(false);
return Optional.ofNullable(dlg.show() == OK ? tblOrders.getSelectionModel().getSelectedItem() : null);
}
private void init(Collection<OfferDescModel> offers, double balance, long max) {
tblOrders.setItems(BindingsHelper.observableList(offers, (o) -> new OrderModel(o, balance, max)));
if (tblOrders.getSortOrder().size()>0)
tblOrders.sort();
}
private void changeOrder(OrderModel order) {
this.order = order;
if (order != null) tblBuyers.setItems(FXCollections.observableList(order.getBuyers()));
else tblBuyers.setItems(FXCollections.emptyObservableList());
tblBuyers.getSelectionModel().clearSelection();
if (tblBuyers.getSortOrder().size()>0)
tblBuyers.sort();
}
private void setBuyer(OfferModel offer) {
if (order != null) {
order.setBuyer(offer);
order.setCount(order.getMax());
}
}
}

View File

@@ -0,0 +1,51 @@
package ru.trader.controllers;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import ru.trader.core.Vendor;
import ru.trader.model.MarketModel;
import ru.trader.model.OfferDescModel;
import ru.trader.view.support.NumberField;
import java.util.Collection;
import java.util.stream.Collectors;
public class RoutersController {
@FXML
private NumberField balance;
@FXML
private NumberField cargo;
@FXML
private Button buy;
@FXML
private Button sell;
@FXML
private ComboBox<Vendor> vendors;
@FXML
private void initialize(){
init();
buy.disableProperty().bind(this.balance.wrongProperty().or(this.cargo.wrongProperty()));
buy.setOnAction((e) -> Screeners.showOrders(getOffers(), balance.getValue().doubleValue(), cargo.getValue().longValue()));
}
void init(){
MarketModel market = MainController.getMarket();
vendors.setItems(market.vendorsProperty());
vendors.getSelectionModel().selectFirst();
}
private Collection<OfferDescModel> getOffers(){
MarketModel market = MainController.getMarket();
Vendor vendor = vendors.getSelectionModel().getSelectedItem();
return vendor.getAllSellOffers().stream().map(market::asOfferDescModel).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,117 @@
package ru.trader.controllers;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialogs;
import ru.trader.model.*;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Optional;
public class Screeners {
private static Parent mainScreen;
private static Parent itemDescScreen;
private static Parent vEditorScreen;
private static Parent editOffersScreen;
private static Parent ordersScreen;
private static MainController mainController;
private static ItemDescController itemDescController;
private static VendorEditorController vEditorController;
private static OffersEditorController oEditorController;
private static OrdersController ordersController;
public static Parent newScreeners(URL main, String stylesheet) throws IOException {
FXMLLoader loader = new FXMLLoader(main);
mainScreen = loader.load();
if (stylesheet!=null)
mainScreen.getStylesheets().add(stylesheet);
mainController = loader.getController();
return mainScreen;
}
public static void loadItemDescStage(URL fxml) throws IOException {
FXMLLoader loader = new FXMLLoader(fxml);
itemDescScreen = loader.load();
itemDescController = loader.getController();
}
public static void loadVEditorStage(URL fxml) throws IOException {
FXMLLoader loader = new FXMLLoader(fxml);
vEditorScreen = loader.load();
vEditorController = loader.getController();
Stage stage = new Stage();
stage.setScene(new Scene(vEditorScreen));
}
public static void loadAddOfferStage(URL fxml) throws IOException {
FXMLLoader loader = new FXMLLoader(fxml);
editOffersScreen = loader.load();
oEditorController = loader.getController();
Stage stage = new Stage();
stage.setScene(new Scene(editOffersScreen));
}
public static void loadOrdersStage(URL fxml) throws IOException {
FXMLLoader loader = new FXMLLoader(fxml);
ordersScreen = loader.load();
ordersController = loader.getController();
Stage stage = new Stage();
stage.setScene(new Scene(ordersScreen));
}
public static void show(Node node){
mainController.getMainPane().setCenter(node);
}
public static void showException(Throwable e){
if (mainScreen!=null)
Dialogs.create().owner(mainScreen).showException(e);
}
public static Action showConfirm(String text){
return Dialogs.create().owner(mainScreen).message(text).showConfirm();
}
public static Action showAddVendor(){
return vEditorController.showDialog(mainScreen, vEditorScreen, null);
}
public static Action showEditVendor(VendorModel vendor){
return vEditorController.showDialog(mainScreen, vEditorScreen, vendor);
}
public static Parent getMainScreen(){
return mainScreen;
}
public static Optional<OffersEditorController.DialogResult> showEditOffers(ItemModel item, Number sell, Number buy) {
return oEditorController.showDialog(vEditorScreen, editOffersScreen, item, sell, buy);
}
public static Optional<OrderModel> showOrders(Collection<OfferDescModel> offers, double balance, long cargo) {
return ordersController.showDialog(mainScreen, ordersScreen, offers, balance, cargo);
}
public static void changeItemDesc(ItemDescModel item){
itemDescController.setItemDesc(item);
}
public static void showItemDesc(Node owner){
itemDescController.popup(owner, itemDescScreen);
}
public static void closeAll() {
itemDescController.close();
}
}

View File

@@ -0,0 +1,256 @@
package ru.trader.controllers;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.util.converter.DoubleStringConverter;
import org.controlsfx.control.ButtonBar;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import org.controlsfx.dialog.Dialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.OFFER_TYPE;
import ru.trader.model.*;
import ru.trader.model.support.BindingsHelper;
import ru.trader.view.support.cells.TextFieldCell;
public class VendorEditorController {
private final static Logger LOG = LoggerFactory.getLogger(VendorEditorController.class);
private VendorModel vendor;
private final Action actSave = new AbstractAction("Сохранить") {
{
ButtonBar.setType(this, ButtonBar.ButtonType.OK_DONE);
}
@Override
public void handle(ActionEvent event) {
Dialog dlg = (Dialog) event.getSource();
saveChanges();
dlg.hide();
}
};
@FXML
private TextField name;
@FXML
private TableView<FakeOffer> items;
@FXML
private TableColumn<FakeOffer, Double> buy;
@FXML
private TableColumn<FakeOffer, Double> sell;
@FXML
private void initialize() {
items.getSelectionModel().setCellSelectionEnabled(true);
buy.setCellFactory(TextFieldCell.forTableColumn(new DoubleStringConverter()));
sell.setCellFactory(TextFieldCell.forTableColumn(new DoubleStringConverter()));
}
public Action showDialog(Parent parent, Parent content, VendorModel vendor){
this.vendor = vendor;
if (vendor != null) {
fill();
} else {
reset();
}
Dialog dlg = new Dialog(parent, vendor == null ? "Добавление станции" : "Редактирование станции");
dlg.setContent(content);
dlg.getActions().addAll(actSave, Dialog.Actions.CANCEL);
dlg.setResizable(false);
return dlg.show();
}
private void fill(){
name.setText(vendor.getName());
fillItems();
vendor.getSells().forEach(this::fillOffer);
vendor.getBuys().forEach(this::fillOffer);
}
private void reset(){
name.setText("");
fillItems();
}
private void fillItems() {
items.setItems(BindingsHelper.observableList(MainController.getMarket().itemsProperty(), (item) -> new FakeOffer(item.getItem())));
}
private void fillOffer(OfferModel offer) {
for (FakeOffer o : items.getItems()) {
if (offer.hasItem(o.item)) {
switch (offer.getType()) {
case SELL:
o.setSell(offer);
break;
case BUY:
o.setBuy(offer);
break;
}
return;
}
}
}
public void saveChanges(){
LOG.info("Save vendor changes");
items.getSelectionModel().clearSelection();
final MarketModel market = MainController.getMarket();
if (vendor == null) {
market.setAlert(false);
vendor = market.newVendor(name.getText());
items.getItems().forEach((o) -> commit(market, vendor, o));
market.setAlert(true);
market.add(vendor);
} else {
vendor.setName(name.getText());
items.getItems().forEach((o) -> commit(market, vendor, o));
}
}
private void commit(MarketModel market, VendorModel vendor, FakeOffer offer){
LOG.trace("Commit changes of offers {}", offer);
if (offer.isBlank()){
LOG.trace("Is blank offer, skip");
return;
}
if (offer.isNewBuy()){
LOG.trace("Is new buy offer");
vendor.add(market.newOffer(OFFER_TYPE.BUY, offer.item, offer.getBprice()));
} else if (offer.isRemoveBuy()) {
LOG.trace("Is remove buy offer");
vendor.remove(offer.buy);
} else if (offer.isChangeBuy()){
LOG.trace("Is change buy price to {}", offer.getBprice());
offer.buy.setPrice(offer.getBprice());
} else {
LOG.trace("No change buy offer");
}
if (offer.isNewSell()){
LOG.trace("Is new sell offer");
vendor.add(market.newOffer(OFFER_TYPE.SELL, offer.item, offer.getSprice()));
} else if (offer.isRemoveSell()) {
LOG.trace("Is remove sell offer");
vendor.remove(offer.sell);
} else if (offer.isChangeSell()){
LOG.trace("Is change sell price to {}", offer.getSprice());
offer.sell.setPrice(offer.getSprice());
} else {
LOG.trace("No change sell offer");
}
}
public class FakeOffer {
private final ItemModel item;
private DoubleProperty sprice;
private DoubleProperty bprice;
private OfferModel sell;
private OfferModel buy;
public FakeOffer(ItemModel item){
this.item = item;
this.sprice = new SimpleDoubleProperty(0);
this.bprice = new SimpleDoubleProperty(0);
}
public ReadOnlyStringProperty nameProperty(){
return item.nameProperty();
}
public double getSprice() {
return sprice.get();
}
public void setSprice(double sprice) {
this.sprice.set(sprice);
}
public double getBprice() {
return bprice.get();
}
public void setBprice(double bprice) {
this.bprice.set(bprice);
}
public DoubleProperty bpriceProperty() {
return bprice;
}
public DoubleProperty spriceProperty() {
return sprice;
}
public boolean isChangeSell() {
return sell!=null && getSprice() != sell.getPrice();
}
public boolean isChangeBuy() {
return buy!=null && getBprice() != buy.getPrice();
}
public boolean isNewSell() {
return sell == null && getSprice() != 0;
}
public boolean isNewBuy() {
return buy == null && getBprice() != 0;
}
public boolean isRemoveSell() {
return sell != null && getSprice() ==0;
}
public boolean isRemoveBuy() {
return buy != null && getBprice() ==0;
}
public boolean isBlank(){
return sell == null && getSprice() == 0 && buy == null && getBprice() == 0;
}
public boolean hasItem(ItemModel item){
return this.item.equals(item);
}
public void setSell(OfferModel sell) {
this.sell = sell;
sprice.set(sell.getPrice());
}
public void setBuy(OfferModel buy) {
this.buy = buy;
bprice.set(buy.getPrice());
}
@Override
public String toString() {
return "FakeOffer{" +
"item=" + item +
", sprice=" + sprice.get() +
", bprice=" + bprice.get() +
", sell=" + sell +
", buy=" + buy +
'}';
}
}
}

View File

@@ -0,0 +1,40 @@
package ru.trader.model;
import javafx.beans.property.*;
import ru.trader.core.OFFER_TYPE;
import java.util.List;
public interface ItemDescModel {
ItemModel getItem();
ReadOnlyStringProperty nameProperty();
ReadOnlyDoubleProperty avgBuyProperty();
ReadOnlyObjectProperty<OfferModel> minBuyProperty();
ReadOnlyObjectProperty<OfferModel> maxBuyProperty();
ReadOnlyObjectProperty<OfferModel> bestBuyProperty();
ReadOnlyDoubleProperty avgSellProperty();
ReadOnlyObjectProperty<OfferModel> minSellProperty();
ReadOnlyObjectProperty<OfferModel> maxSellProperty();
ReadOnlyObjectProperty<OfferModel> bestSellProperty();
boolean hasItem(OfferModel offer);
List<OfferModel> getSeller();
List<OfferModel> getBuyer();
void refresh();
void refresh(OFFER_TYPE type);
}

View File

@@ -0,0 +1,115 @@
package ru.trader.model;
import javafx.beans.property.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.OFFER_TYPE;
import java.util.List;
public class ItemDescModelImpl implements ItemDescModel {
private final static Logger LOG = LoggerFactory.getLogger(ItemDescModelImpl.class);
protected final ItemModel item;
protected final ItemStatModel statSell;
protected final ItemStatModel statBuy;
public ItemDescModelImpl(ItemModel item, ItemStatModel statSell, ItemStatModel statBuy) {
this.item = item;
this.statSell = statSell;
this.statBuy = statBuy;
}
@Override
public ItemModel getItem(){
return item;
}
@Override
public ReadOnlyStringProperty nameProperty() {
return item.nameProperty();
}
@Override
public ReadOnlyDoubleProperty avgBuyProperty() {
return statBuy.avgProperty();
}
@Override
public ReadOnlyObjectProperty<OfferModel> minBuyProperty() {
return statBuy.minProperty();
}
@Override
public ReadOnlyObjectProperty<OfferModel> maxBuyProperty() {
return statBuy.maxProperty();
}
@Override
public ReadOnlyObjectProperty<OfferModel> bestBuyProperty() {
return statBuy.bestProperty();
}
@Override
public ReadOnlyDoubleProperty avgSellProperty() {
return statSell.avgProperty();
}
@Override
public ReadOnlyObjectProperty<OfferModel> minSellProperty() {
return statSell.minProperty();
}
@Override
public ReadOnlyObjectProperty<OfferModel> maxSellProperty() {
return statSell.maxProperty();
}
@Override
public ReadOnlyObjectProperty<OfferModel> bestSellProperty() {
return statSell.bestProperty();
}
@Override
public List<OfferModel> getSeller() {
return statSell.getOffers();
}
@Override
public List<OfferModel> getBuyer() {
return statBuy.getOffers();
}
@Override
public void refresh(){
LOG.trace("Refresh stats of itemDesc {}", this);
statBuy.refresh();
statSell.refresh();
}
@Override
public void refresh(OFFER_TYPE type){
LOG.trace("Refresh {} stat of itemDesc {}", type, this);
switch (type) {
case SELL: statSell.refresh();
break;
case BUY: statBuy.refresh();
break;
}
}
public boolean hasItem(ItemModel item){
return this.item.getItem().equals(item.getItem());
}
@Override
public boolean hasItem(OfferModel offer){
return this.item.getItem().equals(offer.getOffer().getItem());
}
@Override
public String toString() {
return item.toString();
}
}

View File

@@ -0,0 +1,52 @@
package ru.trader.model;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.Item;
public class ItemModel{
private final static Logger LOG = LoggerFactory.getLogger(ItemModel.class);
private final Item item;
private final MarketModel market;
private StringProperty name;
ItemModel(Item item, MarketModel market) {
this.item = item;
this.market = market;
}
public String getName() {return name != null ? name.get() : item.getName();}
public void setName(String value) {
LOG.info("Change name of item {} to {}", item, name);
market.updateName(this, value);
if (name != null) name.set(value);
}
public ReadOnlyStringProperty nameProperty() {
if (name == null) {
name = new SimpleStringProperty(item.getName());
}
return name;
}
@Override
public String toString() {
if (LOG.isTraceEnabled()){
final StringBuilder sb = new StringBuilder("ItemModel{");
sb.append("nameProp=").append(name);
sb.append(", item=").append(super.toString());
sb.append('}');
return sb.toString();
}
return item.toString();
}
Item getItem() {
return item;
}
}

View File

@@ -0,0 +1,116 @@
package ru.trader.model;
import javafx.beans.property.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.ItemStat;
import ru.trader.core.Offer;
import java.util.List;
import java.util.stream.Collectors;
public class ItemStatModel {
private final static Logger LOG = LoggerFactory.getLogger(ItemStatModel.class);
private ItemStat itemStat;
private final MarketModel market;
private DoubleProperty avg;
private ObjectProperty<OfferModel> max;
private ObjectProperty<OfferModel> min;
private ObjectProperty<OfferModel> best;
ItemStatModel(ItemStat itemStat, MarketModel market) {
this.itemStat = itemStat;
this.market = market;
}
public ReadOnlyDoubleProperty avgProperty(){
if (avg == null) avg = new SimpleDoubleProperty(itemStat.getAvg());
return avg;
}
public ReadOnlyObjectProperty<OfferModel> minProperty(){
if (min == null){
min = new SimpleObjectProperty<>(market.asModel(itemStat.getMin()));
}
return min;
}
public ReadOnlyObjectProperty<OfferModel> maxProperty(){
if (max == null) {
max = new SimpleObjectProperty<>(market.asModel(itemStat.getMax()));
}
return max;
}
public ReadOnlyObjectProperty<OfferModel> bestProperty(){
if (best == null){
best = new SimpleObjectProperty<>(market.asModel(itemStat.getBest()));
}
return best;
}
public double getAvg() {
return avg != null ? avg.get() : itemStat.getAvg();
}
public OfferModel getMax() {
return max != null ? max.get() : market.asModel(itemStat.getMax());
}
public OfferModel getMin() {
return min != null ? min.get() : market.asModel(itemStat.getMin());
}
public OfferModel getBest() {
return best != null ? best.get() : market.asModel(itemStat.getBest());
}
public List<OfferModel> getOffers() {
return itemStat.getOffers().stream().map(market::asModel).collect(Collectors.toList());
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("");
if (LOG.isTraceEnabled()){
sb.append("ItemStatModel{");
sb.append("avgProp=").append(avg);
sb.append(", maxProp=").append(max);
sb.append(", minProp=").append(min);
sb.append(", bestProp=").append(best);
sb.append(", itemStat=").append(itemStat.toString());
sb.append('}');
return sb.toString();
}
sb.append("avg=").append(getAvg());
sb.append(", max=").append(getMax());
sb.append(", min=").append(getMin());
sb.append(", best=").append(getBest());
return sb.toString();
}
public void refresh(){
LOG.debug("Refresh model {}", this);
if (itemStat.isEmpty()) {
ItemStat fresh = market.getStat(itemStat.getType(), itemStat.getItem());
if (itemStat != fresh) itemStat = fresh;
}
if (avg!=null) avg.setValue(itemStat.getAvg());
refreshProp(min, itemStat.getMin());
refreshProp(max, itemStat.getMax());
refreshProp(best, itemStat.getBest());
LOG.debug("Fresh model = {}", this);
}
private void refreshProp(ObjectProperty<OfferModel> prop, Offer offer){
if (prop!=null ){
OfferModel model = prop.getValue();
if (model==null || !model.isModel(offer)){
prop.setValue(market.asModel(offer));
}
}
}
}

View File

@@ -0,0 +1,161 @@
package ru.trader.model;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.*;
import ru.trader.model.support.BindingsHelper;
import ru.trader.model.support.ChangeMarketListener;
import java.util.ArrayList;
import java.util.Collection;
public class MarketModel {
private final static Logger LOG = LoggerFactory.getLogger(MarketModel.class);
private final Market market;
private final Collection<ChangeMarketListener> listener = new ArrayList<>();
private final ListProperty<Vendor> vendors;
private final ListProperty<ItemDescModel> items;
private boolean alert = true;
public ReadOnlyListProperty<Vendor> vendorsProperty() {
return vendors;
}
public ReadOnlyListProperty<ItemDescModel> itemsProperty() {
return items;
}
public void setAlert(boolean alert) {
this.alert = alert;
}
public MarketModel(Market market) {
this.market = market;
items = new SimpleListProperty<>(BindingsHelper.observableList(market.getItems(), this::getItemDesc));
vendors = new SimpleListProperty<>(FXCollections.observableArrayList(market.get()));
}
public void addListener(ChangeMarketListener listener){
synchronized (this.listener){
this.listener.add(listener);
}
}
public void removeListeners() {
synchronized (listener){
listener.clear();
}
}
public void addAllListener(Collection<? extends ChangeMarketListener> listener){
synchronized (this.listener){
this.listener.addAll(listener);
}
}
public Collection<ChangeMarketListener> getListeners() {
return listener;
}
void updateName(ItemModel model, String value) {
Item item = model.getItem();
String old = item.getName();
item.setName(value);
if (alert) listener.forEach((c) -> c.nameChange(model, old, value));
}
void updateName(VendorModel model, String value) {
Vendor vendor = model.getVendor();
String old = vendor.getName();
vendor.setName(value);
if (alert) listener.forEach((c) -> c.nameChange(model, old, value));
}
void updatePrice(OfferModel model, double value) {
Offer offer = model.getOffer();
double old = offer.getPrice();
market.updatePrice(offer, value);
if (alert) listener.forEach((c) -> c.priceChange(model, old, value));
}
void add(VendorModel vendor, OfferModel offer) {
market.add(vendor.getVendor(), offer.getOffer());
if (alert) listener.forEach((c) -> c.add(offer));
}
void remove(VendorModel vendor, OfferModel offer) {
market.remove(vendor.getVendor(), offer.getOffer());
if (alert) listener.forEach((c) -> c.remove(offer));
}
public void add(VendorModel vendor) {
LOG.info("Add vendor {} to market {}", vendor, this);
market.add(vendor.getVendor());
if (alert) listener.forEach((c) -> c.add(vendor));
vendors.add(vendor.getVendor());
}
public void add(ItemModel item) {
LOG.info("Add item {} to market {}", item, this);
market.add(item.getItem());
ItemDescModel model = getItemDesc(item);
if (alert) listener.forEach((c) -> c.add(model));
items.add(model);
}
public ItemModel newItem(String name){
return ModelFabrica.buildItemModel(name, this);
}
public VendorModel newVendor(String name){
return ModelFabrica.buildModel(name, this);
}
public OfferModel newOffer(OFFER_TYPE type, ItemModel item, double price) {
return ModelFabrica.buildModel(type, item, price, this);
}
ItemDescModel getItemDesc(Item item){
return getItemDesc(asModel(item));
}
ItemDescModel getItemDesc(ItemModel item){
return ModelFabrica.buildModel(item, market.getStatSell(item.getItem()), market.getStatBuy(item.getItem()), this);
}
public OfferDescModel asOfferDescModel(Offer offer){
return asOfferDescModel(asModel(offer));
}
public OfferDescModel asOfferDescModel(OfferModel offer){
Item item = offer.getOffer().getItem();
return ModelFabrica.buildModel(offer, market.getStatSell(item), market.getStatBuy(item), this);
}
ItemStat getStat(OFFER_TYPE type, Item item){
return market.getStat(type, item);
}
public OfferModel asModel(Offer offer){
return ModelFabrica.getModel(offer, this);
}
public ItemModel asModel(Item item){
return ModelFabrica.getModel(item, this);
}
public VendorModel asModel(Vendor vendor) {
return ModelFabrica.getModel(vendor, this);
}
}

View File

@@ -0,0 +1,101 @@
package ru.trader.model;
import ru.trader.core.*;
import java.lang.ref.WeakReference;
import java.util.HashMap;
public class ModelFabrica {
private static final HashMap<Item, WeakReference<ItemModel>> items = new HashMap<>();
private static final HashMap<Vendor, WeakReference<VendorModel>> vendors = new HashMap<>();
private static final HashMap<Offer, WeakReference<OfferModel>> offers = new HashMap<>();
private static final HashMap<ItemStat, WeakReference<ItemStatModel>> stats = new HashMap<>();
public static ItemModel buildItemModel(String name, MarketModel market){
return getModel(new Item(name), market);
}
public static VendorModel buildModel(String name, MarketModel market){
return getModel(new SimpleVendor(name), market);
}
public static OfferModel buildModel(OFFER_TYPE type, ItemModel item, double price, MarketModel market) {
return getModel(new Offer(type, item.getItem(), price), market);
}
public static ItemDescModel buildModel(ItemModel item, ItemStat sell, ItemStat buy, MarketModel market) {
return new ItemDescModelImpl(item, getModel(sell, market), getModel(buy, market));
}
public static OfferDescModel buildModel(OfferModel offer, ItemStat sell, ItemStat buy, MarketModel market){
return new OfferDescModel(offer, getModel(sell, market), getModel(buy, market));
}
public static VendorModel getModel(Vendor vendor, MarketModel market){
if (vendor == null) return null;
VendorModel res=null;
WeakReference<VendorModel> ref = vendors.get(vendor);
if (ref != null){
res = ref.get();
}
if (res == null){
res = new VendorModel(vendor, market);
vendors.put(vendor, new WeakReference<>(res));
}
return res;
}
public static ItemModel getModel(Item item, MarketModel market){
if (item == null) return null;
ItemModel res=null;
WeakReference<ItemModel> ref = items.get(item);
if (ref != null){
res = ref.get();
}
if (res == null){
res = new ItemModel(item, market);
items.put(item, new WeakReference<>(res));
}
return res;
}
public static OfferModel getModel(Offer offer, MarketModel market){
if (offer == null) return null;
OfferModel res = null;
WeakReference<OfferModel> ref = offers.get(offer);
if (ref != null){
res = ref.get();
}
if (res == null){
res = new OfferModel(offer, market);
offers.put(offer, new WeakReference<>(res));
}
return res;
}
public static ItemStatModel getModel(ItemStat itemStat, MarketModel market){
if (itemStat == null) return null;
ItemStatModel res = null;
WeakReference<ItemStatModel> ref = stats.get(itemStat);
if (ref != null){
res = ref.get();
}
if (res == null){
res = new ItemStatModel(itemStat, market);
stats.put(itemStat, new WeakReference<>(res));
}
return res;
}
public static void clear(){
items.clear();
vendors.clear();
offers.clear();
stats.clear();
}
}

View File

@@ -0,0 +1,93 @@
package ru.trader.model;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.model.support.ModelBindings;
public class OfferDescModel extends ItemDescModelImpl implements ItemDescModel{
private final static Logger LOG = LoggerFactory.getLogger(OfferDescModel.class);
protected DoubleProperty diff;
protected final OfferModel offer;
protected DoubleProperty maxProfit;
protected DoubleProperty minProfit;
protected DoubleProperty avgProfit;
public OfferDescModel(OfferModel offer, ItemStatModel statSell, ItemStatModel statBuy) {
super(offer.getItem(), statSell, statBuy);
this.offer = offer;
}
public ReadOnlyDoubleProperty priceProperty() {
return offer.priceProperty();
}
public double getPrice(){
return offer.getPrice();
}
public ReadOnlyDoubleProperty profitProperty() {
if (maxProfit == null){
maxProfit = new SimpleDoubleProperty(0);
switch (offer.getType()) {
case SELL: maxProfit.bind(ModelBindings.diff(bestBuyProperty(), priceProperty()));
break;
case BUY: maxProfit.bind(ModelBindings.diff(priceProperty(), bestSellProperty()));
break;
}
}
return maxProfit;
}
public ReadOnlyDoubleProperty avgProfitProperty() {
if (avgProfit == null){
avgProfit = new SimpleDoubleProperty(0);
switch (offer.getType()) {
case SELL: avgProfit.bind(avgBuyProperty().subtract(priceProperty()));
break;
case BUY: avgProfit.bind(priceProperty().subtract(avgSellProperty()));
break;
}
}
return avgProfit;
}
public ReadOnlyDoubleProperty minProfitProperty() {
if (minProfit == null){
minProfit = new SimpleDoubleProperty(0);
switch (offer.getType()) {
case SELL: minProfit.bind(ModelBindings.diff(minBuyProperty(), priceProperty()));
break;
case BUY: minProfit.bind(ModelBindings.diff(priceProperty(), minSellProperty()));
break;
}
}
return minProfit;
}
public ReadOnlyDoubleProperty diffProperty(){
if (diff == null){
diff = new SimpleDoubleProperty(0);
switch (offer.getType()) {
case SELL: diff.bind(Bindings.subtract(priceProperty(), avgSellProperty()));
break;
case BUY: diff.bind(Bindings.subtract(priceProperty(), avgBuyProperty()));
break;
}
}
return diff;
}
public double getDiff(){
return diffProperty().get();
}
public OfferModel getOffer(){
return offer;
}
}

View File

@@ -0,0 +1,103 @@
package ru.trader.model;
import javafx.beans.property.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.OFFER_TYPE;
import ru.trader.core.Offer;
public class OfferModel{
private final static Logger LOG = LoggerFactory.getLogger(OfferModel.class);
private final MarketModel market;
private final Offer offer;
private DoubleProperty price;
OfferModel(Offer offer, MarketModel market) {
this.market = market;
this.offer = offer;
}
public double getPrice() {return price != null ? price.get() : offer.getPrice();}
public void setPrice(double value) {
if (getPrice() == value) return;
LOG.info("Change price offer {} to {}", offer, value);
market.updatePrice(this, value);
if (price != null) price.set(value);
}
public ReadOnlyDoubleProperty priceProperty() {
if (price == null) {
price = new SimpleDoubleProperty(offer.getPrice());
}
return price;
}
public ItemModel getItem() {
return market.asModel(offer.getItem());
}
public VendorModel getVendor() {
return market.asModel(offer.getVendor());
}
public OFFER_TYPE getType(){
return offer.getType();
}
public boolean isModel(Offer offer) {
return this.offer == offer;
}
public boolean hasVendor(VendorModel vendor){
return offer.getVendor().equals(vendor.getVendor());
}
public boolean hasItem(ItemModel item){
return offer.getItem().equals(item.getItem());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof OfferModel)) return false;
OfferModel that = (OfferModel) o;
return offer.equals(that.offer);
}
@Override
public int hashCode() {
return offer.hashCode();
}
@Override
public String toString() {
if (LOG.isTraceEnabled()){
final StringBuilder sb = new StringBuilder("OfferModel{");
sb.append("priceProp=").append(price);
sb.append(", offer=").append(offer.toString());
sb.append('}');
return sb.toString();
}
return offer.toString();
}
Offer getOffer() {
return offer;
}
public String toVString(){
return offer.toVString();
}
public String toIString(){
return offer.toIString();
}
}

View File

@@ -0,0 +1,109 @@
package ru.trader.model;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import ru.trader.model.support.ModelBindings;
import java.util.List;
public class OrderModel {
private final OfferDescModel offer;
private final LongProperty count;
private final ObjectProperty<OfferModel> buyer = new SimpleObjectProperty<>();
private long max;
private DoubleProperty profit;
private DoubleProperty bestProfit;
public OrderModel(OfferDescModel offer) {
this.offer = offer;
this.count = new SimpleLongProperty(0){
@Override
public void setValue(Number v) {
if (max > 0 && v.longValue() > max){
super.setValue(max);
} else {
super.setValue(v);
}
}
};
}
public OrderModel(OfferDescModel offer, double balance, long limit) {
this(offer);
this.max = Math.min(limit, (long) Math.floor(balance / offer.getPrice()));
}
public OfferModel getOffer() {
return offer.getOffer();
}
public ReadOnlyStringProperty nameProperty() {
return offer.nameProperty();
}
public ReadOnlyDoubleProperty priceProperty() {
return offer.priceProperty();
}
public ReadOnlyObjectProperty<OfferModel> bestProperty(){
return offer.bestBuyProperty();
}
public long getCount() {
return count.get();
}
public LongProperty countProperty() {
return count;
}
public void setCount(long count) {
this.count.set(count);
}
public ReadOnlyDoubleProperty profitProperty() {
if (profit == null){
profit = new SimpleDoubleProperty(0);
profit.bind(ModelBindings.diff(buyer, offer.getOffer().priceProperty()).multiply(count));
}
return profit;
}
public ReadOnlyDoubleProperty bestProfitProperty() {
if (bestProfit == null){
bestProfit = new SimpleDoubleProperty(0);
bestProfit.bind(offer.profitProperty().multiply(max));
}
return bestProfit;
}
public ObservableValue<Double> getProfit(OfferModel buyer) {
return buyer.priceProperty().subtract(offer.getOffer().priceProperty()).multiply(max).asObject();
}
public ObjectProperty<OfferModel> buyerProperty() {
return buyer;
}
public void setBuyer(OfferModel buyer) {
this.buyer.set(buyer);
}
public OfferModel getBuyer() {
return buyer.get();
}
public long getMax() {
return max;
}
public void setMax(long max) {
this.max = max;
}
public List<OfferModel> getBuyers(){
return offer.getBuyer();
}
}

View File

@@ -0,0 +1,98 @@
package ru.trader.model;
import javafx.beans.property.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.core.Vendor;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class VendorModel {
private final static Logger LOG = LoggerFactory.getLogger(VendorModel.class);
private final Vendor vendor;
private final MarketModel market;
private StringProperty name;
VendorModel(Vendor vendor, MarketModel market) {
this.vendor = vendor;
this.market = market;
}
public String getName() {return name != null ? name.get() : vendor.getName();}
public void setName(String value) {
if (getName().equals(value)) return;
LOG.info("Change name vendor {} to {}", vendor, value);
market.updateName(this, value);
if (name != null) name.set(value);
}
public ReadOnlyStringProperty nameProperty() {
if (name == null) {
name = new SimpleStringProperty(vendor.getName());
}
return name;
}
public List<OfferModel> getSells() {
return vendor.getAllSellOffers().stream().map(market::asModel).collect(Collectors.toList());
}
public List<OfferModel> getBuys() {
return vendor.getAllBuyOffers().stream().map(market::asModel).collect(Collectors.toList());
}
public <T> List<T> getSells(Function<OfferModel, T> mapper) {
return vendor.getAllSellOffers().stream().map(market::asModel).map(mapper).collect(Collectors.toList());
}
public <T> List<T> getBuys(Function<OfferModel, T> mapper) {
return vendor.getAllBuyOffers().stream().map(market::asModel).map(mapper).collect(Collectors.toList());
}
Vendor getVendor() {
return vendor;
}
public void add(OfferModel offer){
LOG.info("Add offer {} to vendor {}", offer, vendor);
market.add(this, offer);
}
public void remove(OfferModel offer) {
LOG.info("Remove offer {} from vendor {}", offer, vendor);
market.remove(this, offer);
}
public boolean hasSell(ItemModel item) {
return vendor.hasSell(item.getItem());
}
public boolean hasBuy(ItemModel item) {
return vendor.hasBuy(item.getItem());
}
@Override
public String toString() {
if (LOG.isTraceEnabled()){
final StringBuilder sb = new StringBuilder("VendorModel{");
sb.append("nameProp=").append(name);
sb.append(", vendor=").append(vendor.toString());
sb.append('}');
return sb.toString();
}
return vendor.toString();
}
public Optional<OfferModel> getSell(ItemModel item){
return Optional.ofNullable(market.asModel(vendor.getSell(item.getItem())));
}
public Optional<OfferModel> getBuy(ItemModel item){
return Optional.ofNullable(market.asModel(vendor.getBuy(item.getItem())));
}
}

View File

@@ -0,0 +1,71 @@
package ru.trader.model.support;
import com.sun.javafx.collections.ImmutableObservableList;
import javafx.beans.Observable;
import javafx.beans.binding.ListBinding;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
public class BindingsHelper {
public static <T,M extends Observable> ObservableList<M> deepObservableList(Collection<T> entries, Function<T, M> convert){
return deepBind(observableList(entries, convert));
}
public static <T,M> ObservableList<M> observableList(Collection<T> entries, Function<T, M> convert){
List<M> list = new ArrayList<>(entries.size());
entries.forEach((v)->list.add(convert.apply(v)));
return FXCollections.observableList(list);
}
public static <T extends Observable> ObservableList<T> deepBind(final ObservableList<T> list){
if ((list == null)) {
throw new NullPointerException("Operands cannot be null.");
}
return new ListBinding<T>() {
{
super.bind(list);
list.addListener((ListChangeListener<T>) c -> {
while (c.next()) {
if (c.wasAdded())
for (T item : c.getRemoved()) {
super.bind(item);
} else if (c.wasRemoved()){
for (T item : c.getAddedSubList()) {
super.unbind(item);
}
}
}
});
}
@Override
public void dispose() {
list.forEach((v) -> super.unbind(v));
super.unbind(list);
}
@Override
protected ObservableList<T> computeValue() {
return list;
}
@Override
public javafx.collections.ObservableList<?> getDependencies() {
ArrayList<Observable> dependencies = new ArrayList<>(list.size());
dependencies.addAll(list);
dependencies.add(list);
return new ImmutableObservableList<>((Observable[]) dependencies.toArray());
}
};
}
}

View File

@@ -0,0 +1,34 @@
package ru.trader.model.support;
import ru.trader.model.ItemDescModel;
import ru.trader.model.ItemModel;
import ru.trader.model.OfferModel;
import ru.trader.model.VendorModel;
public class ChangeMarketListener {
public void nameChange(ItemModel item, String oldName, String newName){
}
public void nameChange(VendorModel vendor, String oldName, String newName) {
}
public void add(ItemDescModel item) {
}
public void priceChange(OfferModel offer, double oldPrice, double newPrice) {
}
public void add(OfferModel offer) {
}
public void add(VendorModel vendor) {
}
public void remove(OfferModel offer) {
}
}

View File

@@ -0,0 +1,206 @@
package ru.trader.model.support;
import com.sun.javafx.collections.ImmutableObservableList;
import javafx.beans.Observable;
import javafx.beans.binding.*;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.value.ObservableValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.trader.model.ItemModel;
import ru.trader.model.OfferModel;
import ru.trader.model.VendorModel;
public class ModelBindings {
private final static Logger LOG = LoggerFactory.getLogger(ModelBindings.class);
public static StringBinding asString(final OfferModel offer){
return Bindings.createStringBinding(offer::toVString, offer.priceProperty(), offer.getVendor().nameProperty());
}
public static StringBinding asItemString(final OfferModel offer){
return Bindings.createStringBinding(offer::toIString, offer.priceProperty(), offer.getItem().nameProperty());
}
public static DoubleBinding price(final ObservableValue<OfferModel> offer){
ObservableValue<OfferModel> offerBind = offerPrice(offer);
return asDouble(offerBind, offerBind);
}
public static DoubleBinding diff(final ObservableValue<OfferModel> of1, final ObservableValue<OfferModel> of2){
return price(of1).subtract(price(of2));
}
public static DoubleBinding diff(final ReadOnlyDoubleProperty price, final ObservableValue<OfferModel> offer){
return diff(offer, price).negate();
}
public static DoubleBinding diff(final ObservableValue<OfferModel> offer, final ReadOnlyDoubleProperty price){
return price(offer).subtract(price);
}
public static StringBinding asString(final ObservableValue<OfferModel> offer){
ObservableValue<OfferModel> offerBind = offerPrice(offer, true);
return asString(offerBind, offerBind);
}
public static StringBinding asItemString(final ObservableValue<OfferModel> offer){
ObservableValue<OfferModel> offerBind = offerPrice(offer, true);
return asString(offerBind, offerBind);
}
private static StringBinding asItemString(final ObservableValue<OfferModel> offer, final Observable... dependencies){
return Bindings.createStringBinding(() -> {
OfferModel o = offer.getValue();
return o != null ? o.toIString(): "";
}, dependencies);
}
private static StringBinding asString(final ObservableValue<OfferModel> offer, final Observable... dependencies){
return Bindings.createStringBinding(() -> {
OfferModel o = offer.getValue();
return o != null ? o.toVString() : "";
}, dependencies);
}
private static DoubleBinding asDouble(final ObservableValue<OfferModel> offer, final Observable... dependencies){
return Bindings.createDoubleBinding(() -> {
OfferModel o = offer.getValue();
return o != null ? offer.getValue().getPrice() : Double.NaN;
}, dependencies);
}
public static ObservableValue<OfferModel> offerPrice(final ObservableValue<OfferModel> offer){
return offerPrice(offer, false);
}
public static ObservableValue<OfferModel> offerPrice(final ObservableValue<OfferModel> offer, final boolean deep){
if ((offer == null)) {
throw new NullPointerException("Operands cannot be null.");
}
return new ObjectBinding<OfferModel>() {
{
super.bind(offer);
bind(offer.getValue());
offer.addListener((observable, oldValue, newValue) -> {
LOG.trace("unbind {}, bind {}", oldValue, newValue);
unbind(oldValue);
bind(newValue);
});
}
@Override
public void dispose() {
unbind(offer.getValue());
super.unbind(offer);
}
@Override
protected OfferModel computeValue() {
return offer.getValue();
}
@Override
public javafx.collections.ObservableList<?> getDependencies() {
if (deep){
OfferModel model = offer.getValue();
return new ImmutableObservableList<Observable>(offer, model.priceProperty(), model.getVendor().nameProperty(), model.getItem().nameProperty());
}
else
return new ImmutableObservableList<Observable>(offer, offer.getValue().priceProperty());
}
private void bind(OfferModel model){
if (model == null) return;
super.bind(model.priceProperty());
if (deep){
super.bind(model.getVendor().nameProperty());
super.bind(model.getItem().nameProperty());
}
}
private void unbind(OfferModel model){
if (model == null) return;
super.unbind(model.priceProperty());
if (deep){
super.unbind(model.getVendor().nameProperty());
super.unbind(model.getItem().nameProperty());
}
}
};
}
public static ObservableValue<ItemModel> itemName(final ObservableValue<ItemModel> item){
if ((item == null)) {
throw new NullPointerException("Operands cannot be null.");
}
return new ObjectBinding<ItemModel>() {
{
super.bind(item);
super.bind(item.getValue().nameProperty());
item.addListener((observable, oldValue, newValue) -> {
super.unbind(oldValue.nameProperty());
super.bind(newValue.nameProperty());
});
}
@Override
public void dispose() {
super.unbind(item.getValue().nameProperty());
super.unbind(item);
}
@Override
protected ItemModel computeValue() {
return item.getValue();
}
@Override
public javafx.collections.ObservableList<?> getDependencies() {
return new ImmutableObservableList<Observable>(item, item.getValue().nameProperty());
}
};
}
public static ObservableValue<VendorModel> vendorName(final ObservableValue<VendorModel> vendor){
if ((vendor == null)) {
throw new NullPointerException("Operands cannot be null.");
}
return new ObjectBinding<VendorModel>() {
{
super.bind(vendor);
super.bind(vendor.getValue().nameProperty());
vendor.addListener((observable, oldValue, newValue) -> {
super.unbind(oldValue.nameProperty());
super.bind(newValue.nameProperty());
});
}
@Override
public void dispose() {
super.unbind(vendor.getValue().nameProperty());
super.unbind(vendor);
}
@Override
protected VendorModel computeValue() {
return vendor.getValue();
}
@Override
public javafx.collections.ObservableList<?> getDependencies() {
return new ImmutableObservableList<Observable>(vendor, vendor.getValue().nameProperty());
}
};
}
}

View File

@@ -0,0 +1,22 @@
package ru.trader.view.support;
import java.util.Comparator;
public class NaNComparator<T extends Number> implements Comparator<T> {
@Override
public int compare(Number n1, Number n2) {
double d1 = n1.doubleValue();
double d2 = n2.doubleValue();
boolean isNaN1 = Double.isNaN(d1);
boolean isNaN2 = Double.isNaN(d2);
if (isNaN1 && isNaN2) return 0;
if (isNaN1) return -1;
if (isNaN2) return 1;
return Double.compare(d1, d2);
}
}

View File

@@ -0,0 +1,77 @@
package ru.trader.view.support;
import javafx.beans.property.*;
import javafx.geometry.Point2D;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.util.converter.NumberStringConverter;
public class NumberField extends TextField {
private final static NumberStringConverter converter = new NumberStringConverter("#0.#");
private final Tooltip tooltip = new Tooltip();
private final ObjectProperty<Number> number = new SimpleObjectProperty<>(0);
private final BooleanProperty wrong = new SimpleBooleanProperty(false);
public ObjectProperty<Number> numberProperty() {
return number;
}
public Number getValue(){
return number.get();
}
public void setValue(Number value){
number.setValue(value);
setText(converter.toString(value));
}
public boolean isWrong() {
return wrong.get();
}
public BooleanProperty wrongProperty() {
return ReadOnlyBooleanWrapper.booleanProperty(wrong);
}
public NumberField() {
super();
tooltip.setText("Wrong number");
tooltip.setAutoHide(true);
wrong.addListener((ob, o ,n) -> {
if (n) {
setTooltip(tooltip);
Point2D p = this.localToScene(0.0, 0.0);
tooltip.show(this, getScene().getWindow().getX() + getScene().getX() + p.getX(),
getScene().getWindow().getY() + getScene().getY() + p.getY() + getHeight() + 2);
}
else {
tooltip.hide();
setTooltip(null);
}
});
setOnAction((e) -> parseNumber());
focusedProperty().addListener((ob, o, n) -> {if (o) parseNumber();});
}
private void parseNumber(){
String text = getText();
if (text == null || text.isEmpty()) {
number.setValue(0);
return;
}
if (text.matches("^-?\\d+([,\\.]\\d+)?([eE]-?\\d+)?$")){
number.setValue(converter.fromString(text));
wrong.setValue(false);
} else {
wrong.setValue(true);
selectAll();
requestFocus();
}
}
}

View File

@@ -0,0 +1,61 @@
package ru.trader.view.support;
import com.sun.javafx.property.PropertyReference;
import javafx.beans.NamedArg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PropertyFactory <S,T> {
private final static Logger LOG = LoggerFactory.getLogger(PropertyFactory.class);
private final String property;
private Class dataClass;
private String previousProperty;
protected PropertyReference<S> propertyRef;
/**
* Creates a default PropertyValueFactory to extract the value from a given
* TableView row item reflectively, using the given property name.
*
* @param property The name of the property with which to attempt to
* reflectively extract a corresponding value for in a given object.
*/
public PropertyFactory(@NamedArg("property") String property) {
this.property = property;
}
/**
* Returns the property name provided in the constructor.
*/
public final String getProperty() { return property; }
public final PropertyReference<S> getPropertyRef(T data){
return fillProperty(data)? propertyRef : null;
}
private boolean fillProperty(T data) {
if (getProperty() == null || getProperty().isEmpty() || data == null) return false;
try {
// we attempt to cache the property reference here, as otherwise
// performance suffers when working in large data models. For
// a bit of reference, refer to RT-13937.
if (dataClass == null || previousProperty == null ||
! dataClass.equals(data.getClass()) ||
! previousProperty.equals(getProperty())) {
// create a new PropertyReference
this.dataClass = data.getClass();
this.previousProperty = getProperty();
this.propertyRef = new PropertyReference<>(data.getClass(), getProperty());
}
} catch (IllegalStateException e) {
// log the warning and move on
LOG.warn("Can not retrieve property '{}' in PropertyValueFactory: {} with provided class type: {}", getProperty(), this, data.getClass());
LOG.warn("",e);
}
return true;
}
}

View File

@@ -0,0 +1,26 @@
package ru.trader.view.support.cells;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import ru.trader.view.support.NaNComparator;
public class DoubleCell<T> implements Callback<TableColumn<T, Double>, TableCell<T, Double>> {
@Override
public TableCell<T, Double> call(TableColumn<T, Double> param) {
param.setComparator(new NaNComparator<>());
return new TableCell<T, Double>(){
@Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(String.format("%.0f", item));
setGraphic(null);
}
}
};
}
}

View File

@@ -0,0 +1,28 @@
package ru.trader.view.support.cells;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.StringConverter;
public abstract class EditingCell<S, T> extends TextFieldTableCell<S, T> {
protected EditingCell(StringConverter<T> converter) {
super(converter);
}
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty && !isEditing()){
outText();
}
}
protected abstract void outText();
@Override
public void cancelEdit() {
super.cancelEdit();
outText();
}
}

View File

@@ -0,0 +1,19 @@
package ru.trader.view.support.cells;
import javafx.beans.NamedArg;
import javafx.beans.value.ObservableValue;
import ru.trader.model.OfferModel;
import ru.trader.model.support.ModelBindings;
public class OfferCellValueImpl<T> extends PropertyCellValueFactory<OfferModel, T, String>{
public OfferCellValueImpl(@NamedArg("property") String property) {
super(property);
}
@Override
ObservableValue<String> format(ObservableValue<OfferModel> value) {
return ModelBindings.asString(value);
}
}

View File

@@ -0,0 +1,36 @@
package ru.trader.view.support.cells;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import ru.trader.model.OfferModel;
import ru.trader.model.support.ModelBindings;
public class OfferListCell implements Callback<ListView<OfferModel>, ListCell<OfferModel>> {
@Override
public ListCell<OfferModel> call(ListView<OfferModel> param){
return new ListCell<OfferModel>(){
private OfferModel o;
@Override
public void updateItem(OfferModel offer, boolean empty) {
super.updateItem(offer, empty);
if (!empty){
if (o != offer){
textProperty().unbind();
textProperty().bind(ModelBindings.asString(offer));
o = offer;
}
} else {
textProperty().unbind();
o = null;
setText(null);
setGraphic(null);
}
}
};
}
}

View File

@@ -0,0 +1,37 @@
package ru.trader.view.support.cells;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import ru.trader.model.OfferModel;
import ru.trader.model.support.ModelBindings;
public class OfferTableCell<T> implements Callback<TableColumn<OfferModel, T>, TableCell<OfferModel, T>> {
@Override
public TableCell<OfferModel, T> call(TableColumn<OfferModel, T> param) {
return new TableCell<OfferModel, T>(){
private OfferModel o;
@Override
public void updateItem(T value, boolean empty) {
super.updateItem(value, empty);
if (!empty){
OfferModel offer = (OfferModel) getTableRow().getItem();
if (o != offer){
textProperty().unbind();
textProperty().bind(ModelBindings.asString(offer));
o = offer;
}
} else {
textProperty().unbind();
o = null;
setText(null);
setGraphic(null);
}
}
};
}
}

View File

@@ -0,0 +1,50 @@
package ru.trader.view.support.cells;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.util.Callback;
import javafx.util.converter.DoubleStringConverter;
import ru.trader.model.OfferDescModel;
public class PriceCellImpl implements Callback<TableColumn<OfferDescModel, Double>, TableCell<OfferDescModel, Double>> {
private final static String CSS_BAD = "bad";
private final static String CSS_GOOD = "good";
private final static String CSS_DIFF = "diff";
@Override
public TableCell<OfferDescModel, Double> call(TableColumn<OfferDescModel, Double> param) {
return new PriceCell();
}
private class PriceCell extends EditingCell<OfferDescModel, Double> {
protected PriceCell() {
super(new DoubleStringConverter());
}
@Override
protected void outText() {
OfferDescModel offerDesc = (OfferDescModel) getTableRow().getItem();
if (offerDesc!=null){
double d = offerDesc.getDiff();
TextFlow txt = new TextFlow();
Text price = new Text(String.format("%.0f", offerDesc.getPrice()));
Text diff = new Text(String.format(" (%+.0f)", d));
diff.getStyleClass().add(CSS_DIFF);
txt.getChildren().addAll(price, diff);
this.getStyleClass().removeAll(CSS_BAD, CSS_GOOD);
String cssClass = (d == 0 || Double.isNaN(d) ? "" : d * offerDesc.getOffer().getType().getOrder() > 0 ? CSS_BAD : CSS_GOOD );
this.getStyleClass().add(cssClass);
this.setText(null);
this.setGraphic(txt);
}
}
}
}

View File

@@ -0,0 +1,37 @@
package ru.trader.view.support.cells;
import com.sun.javafx.property.PropertyReference;
import javafx.beans.NamedArg;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import ru.trader.view.support.PropertyFactory;
public abstract class PropertyCellValueFactory<S,T,V> extends PropertyFactory<S, T> implements Callback<TableColumn.CellDataFeatures<T, V>, ObservableValue<V>> {
public PropertyCellValueFactory(@NamedArg("property") String property) {
super(property);
}
@Override
public ObservableValue<V> call(TableColumn.CellDataFeatures<T, V> param) {
return getCellValue(param.getValue());
}
abstract ObservableValue<V> format(ObservableValue<S> value);
private ObservableValue<V> getCellValue(T rowData){
ObservableValue<S> value = null;
PropertyReference<S> prop = getPropertyRef(rowData);
if (prop!=null){
if (prop.hasProperty()) value = prop.getProperty(rowData);
else value = new ReadOnlyObjectWrapper<>(prop.get(rowData));
}
if (value == null) return null;
return format(value);
}
}

View File

@@ -0,0 +1,148 @@
package ru.trader.view.support.cells;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.util.Callback;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TextFieldCell<S,T> extends TableCell<S,T> {
private final static Logger LOG = LoggerFactory.getLogger(TextFieldCell.class);
private TextField textField;
private final StringConverter<T> converter;
public TextFieldCell(StringConverter<T> converter) {
this.converter = converter;
this.setOnMouseClicked((e) -> {
if (e.getButton() == MouseButton.PRIMARY)
if (!isEditing())
getTableView().edit(getTableRow().getIndex(), getTableColumn());
});
}
public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(final StringConverter<T> converter) {
return list -> new TextFieldCell<>(converter);
}
@Override
public void startEdit() {
LOG.trace("Start edit");
if (! isEditable()) return;
super.startEdit();
if (isEditing()){
if (textField == null) {
createTextField();
} else {
textField.setText(getItemText());
}
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}
}
@Override
public void updateItem(T item, boolean empty) {
LOG.trace("Update edit");
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getItemText());
}
setText(null);
setGraphic(textField);
} else {
outItem();
}
}
}
@Override
public void cancelEdit() {
LOG.trace("Cancel edit");
if (!isCommit()) commit(false);
if (isCommit()) {
super.cancelEdit();
outItem();
}
}
public TextField getTextField(){
return this.textField;
}
private void createTextField(){
this.textField = new TextField(getItemText());
this.setGraphic(textField);
textField.prefWidthProperty().bind(this.getTableColumn().widthProperty());
textField.setOnKeyPressed(t -> {
if (t.getCode() == KeyCode.ENTER) {
if (commit(true)) editNext();
} else if (t.getCode() == KeyCode.ESCAPE) {
textField = null;
cancelEdit();
}
});
}
private String getItemText(){
return converter.toString(getItem());
}
public boolean commit(boolean noSkip) {
if (isCommit()) return true;
LOG.trace("Commit text {}", textField.getText());
try {
commitEdit(converter.fromString(textField.getText()));
} catch (NumberFormatException e){
if (noSkip) {
Platform.runLater(textField::requestFocus);
return false;
}
}
textField = null;
return true;
}
protected void outItem(){
setText(getItemText());
setGraphic(null);
}
protected boolean isCommit(){
return textField == null;
}
protected void editNext(){
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
sm.selectNext();
ObservableList<TablePosition<S,T>> pos = sm.getSelectedCells();
for (TablePosition<S,T> p : pos) {
if (p.getTableColumn().isEditable()) {
getTableView().scrollTo(p.getRow()>0? p.getRow()-1 : 0);
getTableView().edit(p.getRow(), p.getTableColumn());
return;
}
}
editNext();
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import ru.trader.view.support.cells.OfferListCell?>
<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.ItemDescController">
<Label text="Продавцы:"/>
<ListView fx:id="seller" maxHeight="200.0">
<cellFactory>
<OfferListCell/>
</cellFactory>
</ListView>
<Label text="Покупатели:"/>
<ListView fx:id="buyer" maxHeight="200.0">
<cellFactory>
<OfferListCell/>
</cellFactory>
</ListView>
</VBox>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import ru.trader.view.support.cells.OfferCellValueImpl?>
<?import ru.trader.view.support.cells.DoubleCell?>
<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.ItemsController">
<TableView fx:id="tblItems" editable="true" VBox.vgrow="ALWAYS">
<columns>
<TableColumn editable="true" minWidth="200.0" prefWidth="200.0" text="Товар">
<cellValueFactory>
<PropertyValueFactory property="name"/>
</cellValueFactory>
</TableColumn>
<TableColumn editable="true" resizable="false" text="Покупка">
<columns>
<TableColumn minWidth="120.0" prefWidth="120.0" text="Мин.">
<cellValueFactory>
<OfferCellValueImpl property="minSell"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" text="Ср.">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="avgSell"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="Макс.">
<cellValueFactory>
<OfferCellValueImpl property="maxSell"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableColumn>
<TableColumn editable="true" resizable="false" text="Продажа">
<columns>
<TableColumn minWidth="120.0" prefWidth="120.0" text="Мин.">
<cellValueFactory>
<OfferCellValueImpl property="minBuy"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" text="Ср.">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="avgBuy"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="Макс.">
<cellValueFactory>
<OfferCellValueImpl property="maxBuy"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableColumn>
<TableColumn editable="true" resizable="false" text="Прибыль">
<columns>
<TableColumn fx:id="minProfit" minWidth="80.0" prefWidth="80.0" text="Мин.">
<cellFactory>
<DoubleCell/>
</cellFactory>
</TableColumn>
<TableColumn fx:id="avgProfit" minWidth="80.0" prefWidth="80.0" text="Ср.">
<cellFactory>
<DoubleCell/>
</cellFactory>
</TableColumn>
<TableColumn fx:id="maxProfit" minWidth="80.0" prefWidth="80.0" text="Макс." sortType="DESCENDING">
<cellFactory>
<DoubleCell/>
</cellFactory>
</TableColumn>
</columns>
</TableColumn>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<sortOrder>
<fx:reference source="maxProfit"/>
</sortOrder>
</TableView>
</VBox>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.MainController"
fx:id="mainPane"
>
<top>
<MenuBar>
<Menu text="Файл">
<MenuItem text="Сохранить" onAction="#save"/>
<MenuItem text="Импорт..." onAction="#importWorld"/>
</Menu>
<Menu text="Правка">
<MenuItem text="Добавить станцию" onAction="#addVendor"/>
<MenuItem text="Добавить товар" onAction="#addItem"/>
<MenuItem text="Редактировать станцию" onAction="#editVendor"/>
</Menu>
</MenuBar>
</top>
<center>
<TabPane tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<Tab text="Товары">
<fx:include fx:id="items" source="items.fxml"/>
</Tab>
<Tab text="Станции">
<fx:include fx:id="offers" source="offers.fxml"/>
</Tab>
<Tab text="Маршруты">
<fx:include source="routers.fxml"/>
</Tab>
</TabPane>
</center>
</BorderPane>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import ru.trader.view.support.NumberField?>
<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.OffersEditorController">
<Label fx:id="name"/>
<HBox>
<Label text="Продажа:"/>
<NumberField fx:id="sell"/>
<Label text="Покупка:"/>
<NumberField fx:id="buy"/>
</HBox>
</VBox>

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import ru.trader.view.support.cells.PriceCellImpl?>
<?import ru.trader.view.support.cells.OfferCellValueImpl?>
<?import ru.trader.view.support.cells.DoubleCell?>
<GridPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.OffersController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" prefWidth="100.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="false" maxHeight="40.0" valignment="CENTER" vgrow="SOMETIMES"/>
<RowConstraints valignment="TOP" vgrow="SOMETIMES"/>
</rowConstraints>
<HBox alignment="TOP_CENTER" fillHeight="false" spacing="10.0">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</GridPane.margin>
<Label text="Продавец:"/>
<ComboBox fx:id="vendors" prefHeight="25.0" prefWidth="222.0"/>
</HBox>
<Accordion fx:id="list" GridPane.rowIndex="1">
<panes>
<TitledPane fx:id="sells" animated="false" text="Продаваемые товары">
<TableView fx:id="tblSell" editable="true">
<columns>
<TableColumn minWidth="200.0" prefWidth="200.0" text="Товар">
<cellValueFactory>
<PropertyValueFactory property="name"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="100.0" prefWidth="100.0" resizable="false" text="Цена"
onEditCommit="#editPrice">
<cellFactory>
<PriceCellImpl/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="price"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="sortSell" minWidth="80.0" prefWidth="80.0" resizable="false"
text="Прибыль" sortType="DESCENDING">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="profit"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" resizable="false" text="AVG SELL">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="avgSell"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" resizable="false" text="AVG BUY">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="avgBuy"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="BEST SELL">
<cellValueFactory>
<OfferCellValueImpl property="bestSell"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="BEST BUY">
<cellValueFactory>
<OfferCellValueImpl property="bestBuy"/>
</cellValueFactory>
</TableColumn>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<sortOrder>
<fx:reference source="sortSell"/>
</sortOrder>
</TableView>
</TitledPane>
<TitledPane animated="false" layoutX="10.0" layoutY="10.0" text="Покупаемые товары">
<content>
<TableView fx:id="tblBuy" editable="true">
<columns>
<TableColumn minWidth="200.0" prefWidth="200.0" text="Товар">
<cellValueFactory>
<PropertyValueFactory property="name"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="100.0" prefWidth="100.0" resizable="false" text="Цена"
editable="true" onEditCommit="#editPrice">
<cellFactory>
<PriceCellImpl/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="price"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="sortBuy" minWidth="80.0" prefWidth="80.0" resizable="false"
text="Прибыль" sortType="DESCENDING">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="profit"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" resizable="false" text="AVG SELL">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="avgSell"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" resizable="false" text="AVG BUY">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="avgBuy"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="BEST SELL">
<cellValueFactory>
<OfferCellValueImpl property="bestSell"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="BEST BUY">
<cellValueFactory>
<OfferCellValueImpl property="bestBuy"/>
</cellValueFactory>
</TableColumn>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<sortOrder>
<fx:reference source="sortBuy"/>
</sortOrder>
</TableView>
</content>
</TitledPane>
</panes>
<expandedPane>
<fx:reference source="sells"/>
</expandedPane>
</Accordion>
</GridPane>

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.cell.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import ru.trader.view.support.cells.DoubleCell?>
<?import ru.trader.view.support.cells.OfferCellValueImpl?>
<?import ru.trader.view.support.cells.OfferTableCell?>
<HBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.OrdersController">
<TableView fx:id="tblOrders" editable="true">
<columns>
<TableColumn minWidth="200.0" prefWidth="200.0" text="Товар">
<cellValueFactory>
<PropertyValueFactory property="name"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" text="Цена">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="price"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="count" minWidth="80.0" prefWidth="80.0" text="Кол-во">
<cellValueFactory>
<PropertyValueFactory property="count"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="120.0" prefWidth="120.0" text="Покупатель">
<cellValueFactory>
<OfferCellValueImpl property="buyer"/>
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="80.0" prefWidth="80.0" text="Прибыль">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="profit"/>
</cellValueFactory>
</TableColumn>
<TableColumn resizable="false" text="Максимум">
<columns>
<TableColumn fx:id="maxCount" minWidth="80.0" prefWidth="80.0" text="Кол-во">
<cellValueFactory>
<PropertyValueFactory property="max"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="bestProfit" minWidth="80.0" prefWidth="80.0" text="Прибыль"
sortType="DESCENDING">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="bestProfit"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableColumn>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<sortOrder>
<fx:reference source="bestProfit"/>
</sortOrder>
</TableView>
<TableView fx:id="tblBuyers">
<columns>
<TableColumn minWidth="120.0" prefWidth="120.0" text="Покупатель">
<cellFactory>
<OfferTableCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="price"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="curProfit" minWidth="80.0" prefWidth="80.0" text="Прибыль" sortType="DESCENDING">
<cellFactory>
<DoubleCell/>
</cellFactory>
<cellValueFactory>
<PropertyValueFactory property="profit"/>
</cellValueFactory>
</TableColumn>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
<sortOrder>
<fx:reference source="curProfit"/>
</sortOrder>
</TableView>
</HBox>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import ru.trader.view.support.NumberField?>
<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ru.trader.controllers.RoutersController">
<HBox>
<HBox>
<Label text="Баланс:"/>
<NumberField fx:id="balance" value="1000"/>
</HBox>
<HBox>
<Label text="Трюм:"/>
<NumberField fx:id="cargo" value="4"/>
</HBox>
<Label text="Станция:"/>
<ComboBox fx:id="vendors"/>
<Button fx:id="buy" text="Купить"/>
<Button fx:id="sell" text="Продать"/>
</HBox>
</VBox>

View File

@@ -0,0 +1,11 @@
.good .diff {
-fx-fill: green;
-fx-font-weight: bold;
}
.bad .diff {
-fx-fill: red;
-fx-font-weight: bold;
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<VBox xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8"
fx:controller="ru.trader.controllers.VendorEditorController">
<HBox alignment="CENTER" GridPane.columnSpan="3">
<TextField fx:id="name" alignment="CENTER"/>
</HBox>
<TableView fx:id="items" editable="true" prefWidth="400.0">
<columns>
<TableColumn minWidth="200.0" prefWidth="200.0" text="Товар" editable="false">
<cellValueFactory>
<PropertyValueFactory property="name"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="buy" minWidth="80.0" prefWidth="80.0" maxWidth="80.0" text="Продажа">
<cellValueFactory>
<PropertyValueFactory property="bprice"/>
</cellValueFactory>
</TableColumn>
<TableColumn fx:id="sell" minWidth="80.0" prefWidth="80.0" maxWidth="80.0" text="Покупка">
<cellValueFactory>
<PropertyValueFactory property="sprice"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</VBox>