diff --git a/pom.xml b/pom.xml index 7171a69f22bd6570cca29fa865ec5ae5357aa52d..fee0e286908f96732d52fe82567d1d3f4891e761 100644 --- a/pom.xml +++ b/pom.xml @@ -57,5 +57,10 @@ <artifactId>joda-time</artifactId> <version>2.9.7</version> </dependency> + <dependency> + <groupId>com.itextpdf</groupId> + <artifactId>itextpdf</artifactId> + <version>5.0.6</version> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/Helpers.java b/src/de/deadlocker8/budgetmaster/logic/Helpers.java index 79ea1806e978a24e15787b5d9b3e7e6d72bd87f3..70c73e800a0427286edef8c3a3ec0f2cd228e647 100644 --- a/src/de/deadlocker8/budgetmaster/logic/Helpers.java +++ b/src/de/deadlocker8/budgetmaster/logic/Helpers.java @@ -24,7 +24,17 @@ public class Helpers public static final String COLOR_INCOME = "#22BAD9"; public static final String COLOR_PAYMENT = "#F2612D"; public static final String SALT = "ny9/Y+G|WrJ,82|oIYQQ X %i-sq#4,uA-qKPtwFPnw+s(k2`rV)^-a1|t{D3Z>S"; + + public static String getCurrencyString(int amount, String currency) + { + return String.valueOf(NUMBER_FORMAT.format(amount / 100.0).replace(".", ",")) + " " + currency; + } + public static String getCurrencyString(double amount, String currency) + { + return String.valueOf(NUMBER_FORMAT.format(amount).replace(".", ",")) + " " + currency; + } + public static String getURLEncodedString(String input) { try diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java index c3276887a3f8d70f7770ac4acf9f0b54c1e0fcb3..6517e261397357c889c00f751d45b7e27591a57c 100644 --- a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java @@ -63,7 +63,7 @@ public class BarChartGenerator { Tooltip tooltip = new Tooltip(); - tooltip.setText(Helpers.NUMBER_FORMAT.format(data.getYValue()).replace(".", ",") + currency); + tooltip.setText(Helpers.getCurrencyString(data.getYValue().doubleValue(), currency)); Tooltip.install(tool.getNode(), tooltip); Node node = data.getNode(); node.setOnMouseEntered(new EventHandler<MouseEvent>() diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java index 77efcd715afaed985db53c936ea3e37041b3d6fc..b1879b2005d0ebfe6308e51df53278b6e5fa0738 100644 --- a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java @@ -10,6 +10,7 @@ import javafx.scene.control.Label; import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import tools.ConvertTo; @@ -42,7 +43,7 @@ public class CategoriesChartGenerator { VBox generatedChart = new VBox(); HBox chart = new HBox(); - chart.setMinHeight(30); + chart.setMinHeight(50); Label labelTitle = new Label(title); labelTitle.setStyle("-fx-font-size: 16; -fx-font-weight: bold;"); @@ -71,7 +72,7 @@ public class CategoriesChartGenerator currentPart.prefWidthProperty().bind(chart.widthProperty().multiply(percentage)); Tooltip tooltip = new Tooltip(); - tooltip.setText(currentItem.getName() + "\n" + Helpers.NUMBER_FORMAT.format(percentage*100) + " %\n" + Helpers.NUMBER_FORMAT.format(value).replace(".", ",") + currency);// + tooltip.setText(currentItem.getName() + "\n" + Helpers.NUMBER_FORMAT.format(percentage*100) + " %\n" + Helpers.getCurrencyString(value, currency));// currentPart.setTooltip(tooltip); } @@ -105,17 +106,97 @@ public class CategoriesChartGenerator legendItems.add(getLegendItem(label, currentItem.getColor())); } - int legendWidth = (int)Math.ceil(Math.sqrt(legendItems.size())); + int legendWidth; + int numberOfItems = legendItems.size(); + if(numberOfItems <= 3) + { + legendWidth = numberOfItems; + } + else + { + legendWidth = (int)Math.ceil(Math.sqrt(numberOfItems)); + } - for(int i = 0; i < legendItems.size(); i++) + for(int i = 0; i < numberOfItems; i++) { int columnIndex = i % legendWidth; - int rowIndex = i / 4; + int rowIndex = i / legendWidth; legend.add(legendItems.get(i), columnIndex, rowIndex); } return legend; } + + public VBox generateFullLegend() + { + VBox legend = new VBox(); + legend.setPadding(new Insets(10)); + legend.setSpacing(10); + legend.setStyle("-fx-background-color: #EEEEEE; -fx-border-color: #212121; -fx-border-width: 1; -fx-border-radius: 5;"); + + if(categoryInOutSums.size() == 0) + { + return legend; + } + + double totalIn = getTotal(categoryInOutSums, true); + double totalOut = getTotal(categoryInOutSums, false); + + HBox hboxLegend = new HBox(); + hboxLegend.setSpacing(10); + + VBox vboxCircles = new VBox(); + vboxCircles.setSpacing(10); + VBox vboxNames = new VBox(); + vboxNames.setSpacing(10); + VBox vboxIn = new VBox(); + vboxIn.setSpacing(10); + VBox vboxOut = new VBox(); + vboxOut.setSpacing(10); + + for(CategoryInOutSum currentItem : categoryInOutSums) + { + String name = currentItem.getName(); + if(name.equals("NONE")) + { + name = "Keine Kategorie"; + } + + Label labelCircle = new Label(); + labelCircle.setMinWidth(20); + labelCircle.setMinHeight(20); + labelCircle.setStyle("-fx-background-color: " + ConvertTo.toRGBHexWithoutOpacity(currentItem.getColor()) + "; -fx-background-radius: 50%; -fx-border-width: 1; -fx-border-color: black - fx-border-radius: 50%"); + vboxCircles.getChildren().add(labelCircle); + + Label labelName = new Label(name); + labelName.setStyle("-fx-font-weight: bold;"); + labelName.setMinHeight(20); + vboxNames.getChildren().add(labelName); + + String percentageIn = Helpers.NUMBER_FORMAT.format((currentItem.getBudgetIN() / totalIn)); + Label labelInSum = new Label("+" + Helpers.getCurrencyString(currentItem.getBudgetIN(), currency) + " (" + percentageIn + "%)"); + labelInSum.setStyle("-fx-font-weight: bold;"); + labelInSum.setMinHeight(20); + vboxIn.getChildren().add(labelInSum); + + String percentageOut = Helpers.NUMBER_FORMAT.format((currentItem.getBudgetOUT() / totalOut)); + Label labelOutSum = new Label(Helpers.getCurrencyString(currentItem.getBudgetOUT(), currency) + " (" + percentageOut + "%)"); + labelOutSum.setStyle("-fx-font-weight: bold;"); + labelOutSum.setMinHeight(20); + vboxOut.getChildren().add(labelOutSum); + } + + hboxLegend.getChildren().add(vboxCircles); + hboxLegend.getChildren().add(vboxNames); + HBox.setHgrow(vboxNames, Priority.ALWAYS); + hboxLegend.getChildren().add(vboxIn); + HBox.setHgrow(vboxIn, Priority.ALWAYS); + hboxLegend.getChildren().add(vboxOut); + HBox.setHgrow(vboxOut, Priority.ALWAYS); + legend.getChildren().add(hboxLegend); + + return legend; + } private HBox getLegendItem(String name, Color color) { diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java index 8888a5bd140f83f962677215f344645f5326de5f..8b6bbb70461c24dcb1f8b23a05dac9becc2085fb 100644 --- a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java @@ -62,7 +62,7 @@ public class LineChartGenerator { Tooltip tooltip = new Tooltip(); - tooltip.setText(Helpers.NUMBER_FORMAT.format(data.getYValue()).replace(".", ",") + currency); + tooltip.setText(Helpers.getCurrencyString(data.getYValue().doubleValue(), currency)); Tooltip.install(tool.getNode(), tooltip); Node node = data.getNode(); node.setOnMouseEntered(new EventHandler<MouseEvent>() diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java index 35ae4150cb591b96c5bab30029a588107b5f55d3..f4715f86cb7dbdf7c7916886320845cbba259030 100644 --- a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java @@ -78,8 +78,9 @@ public class MonthChartGenerator private VBox generateChart(ArrayList<CategoryInOutSum> categoryInOutSums, double total, boolean useBudgetIN) { - VBox result = new VBox(); - Label labelAmount = new Label(Helpers.NUMBER_FORMAT.format(getTotal(categoryInOutSums, useBudgetIN)).replace(".", ",") + currency); + VBox result = new VBox(); + + Label labelAmount = new Label(Helpers.getCurrencyString(getTotal(categoryInOutSums, useBudgetIN), currency)); labelAmount.setStyle("-fx-font-size: 12; -fx-font-weight: bold;"); result.getChildren().add(labelAmount); VBox.setMargin(labelAmount, new Insets(0, 0, 10, 0)); @@ -145,12 +146,21 @@ public class MonthChartGenerator legendItems.add(getLegendItem(label, currentItem.getColor())); } - int legendWidth = (int)Math.ceil(Math.sqrt(legendItems.size())); - - for(int i = 0; i < legendItems.size(); i++) + int legendWidth; + int numberOfItems = legendItems.size(); + if(numberOfItems <= 3) + { + legendWidth = numberOfItems; + } + else + { + legendWidth = (int)Math.ceil(Math.sqrt(numberOfItems)); + } + + for(int i = 0; i < numberOfItems; i++) { int columnIndex = i % legendWidth; - int rowIndex = i / 4; + int rowIndex = i / legendWidth; legend.add(legendItems.get(i), columnIndex, rowIndex); } diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java index 382d0a77dfede19bc254cfa0f3f40387c2af5c5f..d2b527b77b141739d52773c39cf239369682fa6c 100644 --- a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java @@ -97,7 +97,7 @@ public class PieChartGenerator String percent = String.valueOf(percentage); percent = percent.substring(0, percent.indexOf(".") + 2); - tooltip.setText(percent + " %\n" + Helpers.NUMBER_FORMAT.format(pieValue).replace(".", ",") + currency); + tooltip.setText(percent + " %\n" + Helpers.getCurrencyString(pieValue, currency)); Tooltip.install(tool.getNode(), tooltip); Node node = tool.getNode(); node.setOnMouseEntered(new EventHandler<MouseEvent>() diff --git a/src/de/deadlocker8/budgetmaster/logic/report/AmountType.java b/src/de/deadlocker8/budgetmaster/logic/report/AmountType.java new file mode 100644 index 0000000000000000000000000000000000000000..a114ffd7c1c5aaf362a0499818a285b4266075bc --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/AmountType.java @@ -0,0 +1,6 @@ +package de.deadlocker8.budgetmaster.logic.report; + +public enum AmountType +{ + INCOME, PAYMENT, BOTH +} diff --git a/src/de/deadlocker8/budgetmaster/logic/report/ColumnFilter.java b/src/de/deadlocker8/budgetmaster/logic/report/ColumnFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..cf722850917b0d79e28735d4763cf26981940fff --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/ColumnFilter.java @@ -0,0 +1,45 @@ +package de.deadlocker8.budgetmaster.logic.report; + +import java.util.HashSet; + +public class ColumnFilter +{ + private HashSet<ColumnType> columns; + + public ColumnFilter() + { + columns = new HashSet<ColumnType>(); + } + + public void addColumn(ColumnType column) + { + columns.add(column); + } + + public void removeColumn(ColumnType column) + { + columns.remove(column); + } + + public void toggleColumn(ColumnType column, boolean add) + { + if(add) + { + columns.add(column); + } + else + { + columns.remove(column); + } + } + + public HashSet<ColumnType> getColumns() + { + return columns; + } + + public boolean containsColumn(ColumnType column) + { + return columns.contains(column); + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/report/ColumnOrder.java b/src/de/deadlocker8/budgetmaster/logic/report/ColumnOrder.java new file mode 100644 index 0000000000000000000000000000000000000000..62aaeb9e254c5237a0ea5cb2b1a88d1eba435ef7 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/ColumnOrder.java @@ -0,0 +1,23 @@ +package de.deadlocker8.budgetmaster.logic.report; + +import java.util.ArrayList; + +public class ColumnOrder +{ + private ArrayList<ColumnType> columns; + + public ColumnOrder() + { + columns = new ArrayList<ColumnType>(); + } + + public ArrayList<ColumnType> getColumns() + { + return columns; + } + + public void addColumn(ColumnType column) + { + columns.add(column); + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/report/ColumnType.java b/src/de/deadlocker8/budgetmaster/logic/report/ColumnType.java new file mode 100644 index 0000000000000000000000000000000000000000..22d5fae4a3669e76ab33d3d457b49619c39b68e0 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/ColumnType.java @@ -0,0 +1,6 @@ +package de.deadlocker8.budgetmaster.logic.report; + +public enum ColumnType +{ + POSITION, DATE, REPEATING, CATEGORY, NAME, DESCRIPTION, RATING, AMOUNT +} diff --git a/src/de/deadlocker8/budgetmaster/logic/report/HeaderFooterPageEvent.java b/src/de/deadlocker8/budgetmaster/logic/report/HeaderFooterPageEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..9835447d1a281711e50d9882638a5bcd3014e2b0 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/HeaderFooterPageEvent.java @@ -0,0 +1,25 @@ +package de.deadlocker8.budgetmaster.logic.report; + +import org.joda.time.DateTime; + +import com.itextpdf.text.Document; +import com.itextpdf.text.Element; +import com.itextpdf.text.Phrase; +import com.itextpdf.text.pdf.ColumnText; +import com.itextpdf.text.pdf.PdfPageEventHelper; +import com.itextpdf.text.pdf.PdfWriter; + +public class HeaderFooterPageEvent extends PdfPageEventHelper +{ + public void onStartPage(PdfWriter writer, Document document) + { + + } + + public void onEndPage(PdfWriter writer, Document document) + { + ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER, new Phrase("BudgetMaster Monatsbericht"), 100, 25, 0); + ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER, new Phrase("Seite " + document.getPageNumber()), 300, 25, 0); + ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_CENTER, new Phrase(DateTime.now().toString("dd.MM.YYYY")), 500, 25, 0); + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/report/ReportGenerator.java b/src/de/deadlocker8/budgetmaster/logic/report/ReportGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..61dd48a3dbae9e23a28f76c9467ab2c724373fee --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/ReportGenerator.java @@ -0,0 +1,271 @@ +package de.deadlocker8.budgetmaster.logic.report; + +import java.awt.Color; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.util.ArrayList; + +import org.joda.time.DateTime; + +import com.itextpdf.text.BaseColor; +import com.itextpdf.text.Chapter; +import com.itextpdf.text.Chunk; +import com.itextpdf.text.Document; +import com.itextpdf.text.DocumentException; +import com.itextpdf.text.Element; +import com.itextpdf.text.Font; +import com.itextpdf.text.Font.FontFamily; +import com.itextpdf.text.Paragraph; +import com.itextpdf.text.Phrase; +import com.itextpdf.text.pdf.GrayColor; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; + +import de.deadlocker8.budgetmaster.logic.CategoryBudget; +import de.deadlocker8.budgetmaster.logic.Helpers; + +public class ReportGenerator +{ + private ArrayList<ReportItem> reportItems; + private ArrayList<CategoryBudget> categoryBudgets; + private ColumnOrder columnOrder; + private boolean splitTable; + private boolean includeCategoryBudgets; + private File savePath; + private String currency; + private DateTime date; + + public ReportGenerator(ArrayList<ReportItem> reportItems, ArrayList<CategoryBudget> categoryBudgets, ColumnOrder columnOrder, boolean splitTable, boolean includeCategoryBudgets, File savePath, String currency, DateTime date) + { + this.reportItems = reportItems; + this.categoryBudgets = categoryBudgets; + this.columnOrder = columnOrder; + this.splitTable = splitTable; + this.includeCategoryBudgets = includeCategoryBudgets; + this.savePath = savePath; + this.currency = currency; + this.date = date; + } + + private Chapter generateHeader() + { + Font chapterFont = new Font(FontFamily.HELVETICA, 16, Font.BOLDITALIC); + Font paragraphFont = new Font(FontFamily.HELVETICA, 12, Font.NORMAL); + Chunk chunk = new Chunk("Monatsbericht - " + date.toString("MMMM yyyy"), chapterFont); + Chapter chapter = new Chapter(new Paragraph(chunk), 1); + chapter.setNumberDepth(0); + chapter.add(Chunk.NEWLINE); + chapter.add(new Paragraph("Buchungsübersicht", paragraphFont)); + return chapter; + } + + private PdfPTable generateTable(int tableWidth, AmountType amountType) + { + int numberOfColumns = columnOrder.getColumns().size(); + int totalIncome = 0; + int totalPayment = 0; + + if(numberOfColumns > 0) + { + PdfPTable table = new PdfPTable(numberOfColumns); + table.setWidthPercentage(tableWidth); + Font font = new Font(FontFamily.HELVETICA, 8, Font.NORMAL, GrayColor.BLACK); + + for(ColumnType column : columnOrder.getColumns()) + { + // TODO get string for enum type + PdfPCell cell = new PdfPCell(new Phrase(column.toString(), font)); + cell.setBackgroundColor(GrayColor.LIGHT_GRAY); + cell.setHorizontalAlignment(Element.ALIGN_CENTER); + table.addCell(cell); + } + + for(ReportItem currentItem : reportItems) + { + if(currentItem.getAmount() > 0) + { + totalIncome += currentItem.getAmount(); + if(amountType == AmountType.PAYMENT) + { + continue; + } + } + else + { + totalPayment += currentItem.getAmount(); + if(amountType == AmountType.INCOME) + { + continue; + } + } + + for(ColumnType column : columnOrder.getColumns()) + { + PdfPCell cell = new PdfPCell(new Phrase(getProperty(currentItem, column), font)); + cell.setBackgroundColor(new BaseColor(Color.WHITE)); + cell.setHorizontalAlignment(Element.ALIGN_CENTER); + table.addCell(cell); + } + } + + PdfPCell cellTotal; + String total = ""; + switch(amountType) + { + case BOTH: + String totalIncomeString = Helpers.getCurrencyString(totalIncome, currency); + String totalPaymentString = Helpers.getCurrencyString(totalPayment, currency); + total = "Einnahmen: " + totalIncomeString + " / Ausgaben: " + totalPaymentString; + break; + case INCOME: + total = "Summe: " + Helpers.getCurrencyString(totalIncome, currency); + break; + case PAYMENT: + total = "Summe: " + Helpers.getCurrencyString(totalPayment, currency); + break; + default: + break; + } + + cellTotal = new PdfPCell(new Phrase(total, font)); + cellTotal.setBackgroundColor(new BaseColor(Color.WHITE)); + cellTotal.setColspan(numberOfColumns); + cellTotal.setHorizontalAlignment(Element.ALIGN_RIGHT); + table.addCell(cellTotal); + + return table; + } + return null; + } + + public void generate() throws FileNotFoundException, DocumentException + { + Document document = new Document(); + PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(savePath)); + writer.setPageEvent(new HeaderFooterPageEvent()); + document.open(); + document.setMargins(50, 45, 50, 70); + Font paragraphFont = new Font(FontFamily.HELVETICA, 12, Font.NORMAL); + + document.add(generateHeader()); + document.add(Chunk.NEWLINE); + + if(splitTable) + { + document.add(new Paragraph("Einnahmen", paragraphFont)); + document.add(Chunk.NEWLINE); + PdfPTable table = generateTable(100, AmountType.INCOME); + if(table != null) + { + document.add(table); + } + + document.add(Chunk.NEWLINE); + document.add(new Paragraph("Ausgaben", paragraphFont)); + document.add(Chunk.NEWLINE); + table = generateTable(100, AmountType.PAYMENT); + if(table != null) + { + document.add(table); + } + } + else + { + PdfPTable table = generateTable(100, AmountType.BOTH); + if(table != null) + { + document.add(table); + } + } + + if(includeCategoryBudgets) + { + document.add(Chunk.NEWLINE); + document.add(new Paragraph("Verbrauch nach Kategorien", paragraphFont)); + document.add(Chunk.NEWLINE); + PdfPTable table = generateCategoryBudgets(); + if(table != null) + { + document.add(table); + } + } + + document.close(); + } + + private PdfPTable generateCategoryBudgets() + { + PdfPTable table = new PdfPTable(2); + table.setWidthPercentage(100); + Font font = new Font(FontFamily.HELVETICA, 8, Font.NORMAL, GrayColor.BLACK); + + //header cells + PdfPCell cellHeaderCategory = new PdfPCell(new Phrase("Kategorie", font)); + cellHeaderCategory.setBackgroundColor(GrayColor.LIGHT_GRAY); + cellHeaderCategory.setHorizontalAlignment(Element.ALIGN_CENTER); + table.addCell(cellHeaderCategory); + PdfPCell cellHeaderAmount = new PdfPCell(new Phrase("Betrag", font)); + cellHeaderAmount.setBackgroundColor(GrayColor.LIGHT_GRAY); + cellHeaderAmount.setHorizontalAlignment(Element.ALIGN_CENTER); + table.addCell(cellHeaderAmount); + + for(CategoryBudget budget : categoryBudgets) + { + String name = budget.getName(); + if(name.equals("NONE")) + { + name = "Keine Kategorie"; + } + PdfPCell cellName = new PdfPCell(new Phrase(name, font)); + cellName.setBackgroundColor(new BaseColor(Color.WHITE)); + cellName.setHorizontalAlignment(Element.ALIGN_CENTER); + table.addCell(cellName); + + PdfPCell cellAmount = new PdfPCell(new Phrase(Helpers.getCurrencyString(budget.getBudget() / 100.0, currency), font)); + cellAmount.setBackgroundColor(new BaseColor(Color.WHITE)); + cellAmount.setHorizontalAlignment(Element.ALIGN_CENTER); + table.addCell(cellAmount); + } + + return table; + } + + private String getProperty(ReportItem reportItem, ColumnType columnType) + { + switch(columnType) + { + case AMOUNT: + return Helpers.getCurrencyString(reportItem.getAmount(), currency); + case CATEGORY: + String name = reportItem.getCategory().getName(); + if(name.equals("NONE")) + { + name = "Keine Kategorie"; + } + return name; + case DATE: + return reportItem.getDate(); + case DESCRIPTION: + return reportItem.getDescription(); + case NAME: + return reportItem.getName(); + case POSITION: + return String.valueOf(reportItem.getPosition()); + case RATING: + return reportItem.getAmount() > 0 ? "+" : "-"; + case REPEATING: + if(reportItem.getRepeating()) + { + return "Ja"; + } + else + { + return "Nein"; + } + default: + return null; + } + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/report/ReportItem.java b/src/de/deadlocker8/budgetmaster/logic/report/ReportItem.java new file mode 100644 index 0000000000000000000000000000000000000000..d155a69481267a722b24355ae233374f3acc8ea1 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/report/ReportItem.java @@ -0,0 +1,95 @@ +package de.deadlocker8.budgetmaster.logic.report; + +import de.deadlocker8.budgetmaster.logic.Category; + +public class ReportItem +{ + private int position; + private int amount; + private String date; + private Category category; + private String name; + private String description; + private boolean repeating; + + public ReportItem() + { + + } + + public int getPosition() + { + return position; + } + + public void setPosition(int position) + { + this.position = position; + } + + public int getAmount() + { + return amount; + } + + public void setAmount(int amount) + { + this.amount = amount; + } + + public String getDate() + { + return date; + } + + public void setDate(String date) + { + this.date = date; + } + + public Category getCategory() + { + return category; + } + + public void setCategory(Category category) + { + this.category = category; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getDescription() + { + return description; + } + + public void setDescription(String description) + { + this.description = description; + } + + public boolean getRepeating() + { + return repeating; + } + + public void setRepeating(boolean repeating) + { + this.repeating = repeating; + } + + @Override + public String toString() + { + return "ReportItem [position=" + position + ", amount=" + amount + ", date=" + date + ", category=" + category + ", name=" + name + ", description=" + description + ", isRepeating=" + repeating + "]"; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/main/Main.java b/src/de/deadlocker8/budgetmaster/main/Main.java index f6594ae5775a8bb0c95f4aa4ab8c4a4fce535f6e..cd3555d262e161e21c2952a155ec0a0d2fdc26b2 100644 --- a/src/de/deadlocker8/budgetmaster/main/Main.java +++ b/src/de/deadlocker8/budgetmaster/main/Main.java @@ -27,7 +27,7 @@ public class Main extends Application FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("de/deadlocker8/budgetmaster/ui/SplashScreen.fxml")); Parent root = (Parent)loader.load(); - Scene scene = new Scene(root, 400, 230); + Scene scene = new Scene(root, 450, 230); ((SplashScreenController)loader.getController()).init(stage, new Image("/de/deadlocker8/budgetmaster/resources/icon.png"), bundle); diff --git a/src/de/deadlocker8/budgetmaster/ui/ChartController.java b/src/de/deadlocker8/budgetmaster/ui/ChartController.java index 0a7b9546166a78b32d58e7a178c3108733318881..6cb72445fdab551db675f9aaf36970c3ea4cdd6b 100644 --- a/src/de/deadlocker8/budgetmaster/ui/ChartController.java +++ b/src/de/deadlocker8/budgetmaster/ui/ChartController.java @@ -1,5 +1,7 @@ package de.deadlocker8.budgetmaster.ui; +import java.io.File; +import java.io.IOException; import java.time.LocalDate; import java.util.ArrayList; @@ -19,7 +21,10 @@ import fontAwesome.FontIconType; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.Scene; import javafx.scene.chart.LineChart; import javafx.scene.control.Accordion; import javafx.scene.control.Alert.AlertType; @@ -37,6 +42,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; +import javafx.stage.Modality; import javafx.stage.Stage; import javafx.util.Callback; import logger.Logger; @@ -52,6 +58,7 @@ public class ChartController implements Refreshable @FXML private DatePicker datePickerEnd; @FXML private VBox vboxChartMonth; @FXML private Button buttonChartCategoriesShow; + @FXML private Button buttonChartCategoriesExport; @FXML private ComboBox<String> comboBoxStartMonth; @FXML private ComboBox<String> comboBoxStartYear; @FXML private ComboBox<String> comboBoxEndMonth; @@ -59,8 +66,11 @@ public class ChartController implements Refreshable @FXML private Button buttonChartMonthShow; @FXML private RadioButton radioButtonBars; @FXML private RadioButton radioButtonLines; + @FXML private HBox hboxChartMonthButtons; private Controller controller; + private Button buttonChartMonthExport; + private File lastExportPath; public void init(Controller controller) { @@ -76,11 +86,28 @@ public class ChartController implements Refreshable buttonChartCategoriesShow.setStyle("-fx-background-color: #2E79B9;"); buttonChartCategoriesShow.setGraphic(iconShow); - FontIcon iconShow2 = new FontIcon(FontIconType.CHECK); + FontIcon iconShow2 = new FontIcon(FontIconType.SAVE); iconShow2.setSize(16); iconShow2.setColor(Color.WHITE); + buttonChartCategoriesExport.setStyle("-fx-background-color: #2E79B9;"); + buttonChartCategoriesExport.setGraphic(iconShow2); + + FontIcon iconShow3 = new FontIcon(FontIconType.CHECK); + iconShow3.setSize(16); + iconShow3.setColor(Color.WHITE); buttonChartMonthShow.setStyle("-fx-background-color: #2E79B9;"); - buttonChartMonthShow.setGraphic(iconShow2); + buttonChartMonthShow.setGraphic(iconShow3); + + buttonChartMonthExport = new Button(); + buttonChartMonthExport.setOnAction((event)->{ + export(vboxChartMonth); + }); + + FontIcon iconShow4 = new FontIcon(FontIconType.SAVE); + iconShow4.setSize(16); + iconShow4.setColor(Color.WHITE); + buttonChartMonthExport.setStyle("-fx-background-color: #2E79B9;"); + buttonChartMonthExport.setGraphic(iconShow4); datePickerEnd.setDayCellFactory(new Callback<DatePicker, DateCell>() { @@ -106,18 +133,23 @@ public class ChartController implements Refreshable comboBoxStartMonth.setItems(FXCollections.observableArrayList(Helpers.getMonthList())); comboBoxStartYear.setItems(FXCollections.observableArrayList(Helpers.getYearList())); comboBoxEndMonth.setItems(FXCollections.observableArrayList(Helpers.getMonthList())); - comboBoxEndYear.setItems(FXCollections.observableArrayList(Helpers.getYearList())); + comboBoxEndYear.setItems(FXCollections.observableArrayList(Helpers.getYearList())); final ToggleGroup toggleGroup = new ToggleGroup(); radioButtonBars.setToggleGroup(toggleGroup); radioButtonBars.setSelected(true); - radioButtonLines.setToggleGroup(toggleGroup); - + radioButtonLines.setToggleGroup(toggleGroup); + accordion.setExpandedPane(accordion.getPanes().get(0)); vboxChartMonth.setSpacing(15); } + + public void buttonChartCategoriesShow() + { + chartCategoriesShow(false); + } - public void chartCategoriesShow() + public void chartCategoriesShow(boolean fullLegend) { DateTime startDate = DateTime.parse(datePickerStart.getValue().toString()); DateTime endDate = DateTime.parse(datePickerEnd.getValue().toString()); @@ -127,84 +159,139 @@ public class ChartController implements Refreshable ServerConnection connection = new ServerConnection(controller.getSettings()); ArrayList<CategoryInOutSum> sums = connection.getCategoryInOutSumForMonth(startDate, endDate); - Platform.runLater(()->{ + Platform.runLater(() -> { vboxChartCategories.getChildren().clear(); - - CategoriesChartGenerator generator = new CategoriesChartGenerator("Einnahmen nach Kategorien", sums, true, controller.getSettings().getCurrency()); + + CategoriesChartGenerator generator = new CategoriesChartGenerator("Einnahmen nach Kategorien", sums, true, controller.getSettings().getCurrency()); vboxChartCategories.getChildren().add(generator.generate()); generator = new CategoriesChartGenerator("Ausgaben nach Kategorien", sums, false, controller.getSettings().getCurrency()); vboxChartCategories.getChildren().add(generator.generate()); - + Region spacer = new Region(); vboxChartCategories.getChildren().add(spacer); VBox.setVgrow(spacer, Priority.ALWAYS); - - vboxChartCategories.getChildren().add(generator.generateLegend()); + + if(fullLegend) + { + vboxChartCategories.getChildren().add(generator.generateFullLegend()); + } + else + { + vboxChartCategories.getChildren().add(generator.generateLegend()); + } }); } catch(Exception e) { Logger.error(e); - Platform.runLater(()->{ + Platform.runLater(() -> { controller.showConnectionErrorAlert(ExceptionHandler.getMessageForException(e)); }); } } + + public void chartCategoriesExport() + { + export(vboxChartCategories); + } + + public void export(VBox chart) + { + Worker.runLater(()->{ + chartCategoriesShow(true); + Platform.runLater(()->{ + try + { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/de/deadlocker8/budgetmaster/ui/ExportChartGUI.fxml")); + Parent root = (Parent)fxmlLoader.load(); + Stage newStage = new Stage(); + newStage.initOwner(controller.getStage()); + newStage.initModality(Modality.APPLICATION_MODAL); + newStage.setTitle("Diagramm exportieren"); + newStage.setScene(new Scene(root)); + newStage.getIcons().add(controller.getIcon()); + newStage.setResizable(false); + ExportChartController newController = fxmlLoader.getController(); + newController.init(newStage, this, chart); + newStage.show(); + } + catch(IOException e) + { + Logger.error(e); + } + }); + }); + } public void chartMonthShow() { - Platform.runLater(()->{ + if(radioButtonLines.isSelected()) + { + if(!hboxChartMonthButtons.getChildren().contains(buttonChartMonthExport)) + { + hboxChartMonthButtons.getChildren().add(buttonChartMonthExport); + } + } + else + { + if(hboxChartMonthButtons.getChildren().contains(buttonChartMonthExport)) + { + hboxChartMonthButtons.getChildren().remove(buttonChartMonthExport); + } + } + + Platform.runLater(() -> { vboxChartMonth.getChildren().clear(); }); - + String startMonth = comboBoxStartMonth.getValue(); String startYear = comboBoxStartYear.getValue(); String endMonth = comboBoxEndMonth.getValue(); - String endYear = comboBoxEndYear.getValue(); - - String startDateString = "01-" + startMonth + "-" + startYear; + String endYear = comboBoxEndYear.getValue(); + + String startDateString = "01-" + startMonth + "-" + startYear; DateTime startDate = DateTime.parse(startDateString, DateTimeFormat.forPattern("dd-MMMM-YYYY")); - - String endDateString = "01-" + endMonth + "-" + endYear; - DateTime endDate = DateTime.parse(endDateString, DateTimeFormat.forPattern("dd-MMMM-YYYY")); - + + String endDateString = "01-" + endMonth + "-" + endYear; + DateTime endDate = DateTime.parse(endDateString, DateTimeFormat.forPattern("dd-MMMM-YYYY")); + if(endDate.isBefore(startDate)) - { - Platform.runLater(()->{ + { + Platform.runLater(() -> { AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Das Enddatum darf nicht vor dem Startdatum liegen.", controller.getIcon(), controller.getStage(), null, false); }); return; - } - + } + try - { + { ServerConnection connection = new ServerConnection(controller.getSettings()); ArrayList<MonthInOutSum> sums = connection.getMonthInOutSum(startDate, endDate); - - Platform.runLater(()->{ + + Platform.runLater(() -> { vboxChartMonth.getChildren().clear(); - + if(radioButtonBars.isSelected()) { - ScrollPane scrollPane = new ScrollPane(); + ScrollPane scrollPane = new ScrollPane(); scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER); scrollPane.setFocusTraversable(false); scrollPane.setStyle("-fx-background-color: transparent; -fx-background-insets: 0; -fx-border-color: transparent; -fx-border-width: 0; -fx-border-insets: 0;"); scrollPane.setPadding(new Insets(0, 0, 10, 0)); - + MonthChartGenerator generator = new MonthChartGenerator(sums, controller.getSettings().getCurrency()); HBox generatedChart = generator.generate(); - scrollPane.setContent(generatedChart); + scrollPane.setContent(generatedChart); generatedChart.prefHeightProperty().bind(scrollPane.heightProperty().subtract(30)); vboxChartMonth.getChildren().add(scrollPane); VBox.setVgrow(scrollPane, Priority.ALWAYS); vboxChartMonth.getChildren().add(generator.generateLegend()); } else - { + { LineChartGenerator generator = new LineChartGenerator(sums, controller.getSettings().getCurrency()); LineChart<String, Number> chartMonth = generator.generate(); - vboxChartMonth.getChildren().add(chartMonth); + vboxChartMonth.getChildren().add(chartMonth); VBox.setVgrow(chartMonth, Priority.ALWAYS); } }); @@ -212,40 +299,55 @@ public class ChartController implements Refreshable catch(Exception e) { Logger.error(e); - Platform.runLater(()->{ + Platform.runLater(() -> { controller.showConnectionErrorAlert(ExceptionHandler.getMessageForException(e)); }); } } + + public Controller getControlle() + { + return controller; + } + + public void setLastExportPath(File lastExportPath) + { + this.lastExportPath = lastExportPath; + } + + public File getLastExportPath() + { + return lastExportPath; + } @Override public void refresh() - { + { Stage modalStage = Helpers.showModal("Vorgang läuft", "Lade Diagramme...", controller.getStage(), controller.getIcon()); - + // prepare chart categories LocalDate startDate = LocalDate.parse(controller.getCurrentDate().withDayOfMonth(1).toString("yyyy-MM-dd")); LocalDate endDate = LocalDate.parse(controller.getCurrentDate().dayOfMonth().withMaximumValue().toString("yyy-MM-dd")); - + datePickerStart.setValue(startDate); - datePickerEnd.setValue(endDate); - + datePickerEnd.setValue(endDate); + // chart month comboBoxStartMonth.setValue(controller.getCurrentDate().minusMonths(5).toString("MMMM")); comboBoxStartYear.setValue(String.valueOf(controller.getCurrentDate().minusMonths(5).getYear())); - + comboBoxEndMonth.setValue(controller.getCurrentDate().plusMonths(6).toString("MMMM")); comboBoxEndYear.setValue(String.valueOf(controller.getCurrentDate().plusMonths(6).getYear())); - Worker.runLater(() -> { - chartCategoriesShow(); - chartMonthShow(); + Worker.runLater(() -> { + chartCategoriesShow(false); + chartMonthShow(); Platform.runLater(() -> { if(modalStage != null) { modalStage.close(); - } + } }); }); } diff --git a/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml b/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml index 7ffa60128c56a3f29617e506560b0eef6f6963ad..fbe8fad75a86df369f04c247ba95447fe4d39ab1 100644 --- a/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml +++ b/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml @@ -13,7 +13,7 @@ <?import javafx.scene.layout.VBox?> <?import javafx.scene.text.Font?> -<AnchorPane fx:id="anchorPaneMain" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.ChartController"> +<AnchorPane fx:id="anchorPaneMain" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.ChartController"> <children> <Accordion fx:id="accordion" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="25.0"> <panes> @@ -44,7 +44,7 @@ </font> </Label> <DatePicker fx:id="datePickerEnd" /> - <Button fx:id="buttonChartCategoriesShow" mnemonicParsing="false" onAction="#chartCategoriesShow"> + <Button fx:id="buttonChartCategoriesShow" mnemonicParsing="false" onAction="#buttonChartCategoriesShow"> <font> <Font name="System Bold" size="12.0" /> </font> @@ -52,6 +52,11 @@ <Insets left="15.0" /> </HBox.margin> </Button> + <Button fx:id="buttonChartCategoriesExport" mnemonicParsing="false" onAction="#chartCategoriesExport"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </Button> </children> <HBox.margin> <Insets left="15.0" /> @@ -108,7 +113,7 @@ </HBox> </children> </VBox> - <HBox alignment="CENTER_LEFT" spacing="15.0" HBox.hgrow="ALWAYS"> + <HBox fx:id="hboxChartMonthButtons" alignment="CENTER_LEFT" spacing="15.0" HBox.hgrow="ALWAYS"> <children> <RadioButton fx:id="radioButtonBars" mnemonicParsing="false" text="Balken"> <font> diff --git a/src/de/deadlocker8/budgetmaster/ui/Controller.java b/src/de/deadlocker8/budgetmaster/ui/Controller.java index 48cc6b9e0c008e2c68ba0fe8d7cf8b4b2ddcc323..c4d84fcb470e904f074e299af26d19f3dddd051b 100644 --- a/src/de/deadlocker8/budgetmaster/ui/Controller.java +++ b/src/de/deadlocker8/budgetmaster/ui/Controller.java @@ -44,11 +44,13 @@ public class Controller @FXML private Button buttonLeft; @FXML private Button buttonRight; @FXML private Button buttonToday; + @FXML private Button buttonAbout; @FXML private TabPane tabPane; @FXML private Tab tabHome; @FXML private Tab tabPayments; @FXML private Tab tabCategories; @FXML private Tab tabCharts; + @FXML private Tab tabReports; @FXML private Tab tabSettings; @FXML private Label labelNotification; @@ -56,6 +58,7 @@ public class Controller private PaymentController paymentController; private CategoryController categoryController; private ChartController chartController; + private ReportController reportController; private SettingsController settingsController; private Stage stage; @@ -118,6 +121,12 @@ public class Controller chartController.refresh(); } }); + + fxmlLoader = new FXMLLoader(getClass().getResource("/de/deadlocker8/budgetmaster/ui/ReportTab.fxml")); + Parent nodeTabReport = (Parent)fxmlLoader.load(); + reportController = fxmlLoader.getController(); + reportController.init(this); + tabReports.setContent(nodeTabReport); fxmlLoader = new FXMLLoader(getClass().getResource("/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml")); Parent nodeTabSettings = (Parent)fxmlLoader.load(); @@ -142,6 +151,9 @@ public class Controller FontIcon iconToday = new FontIcon(FontIconType.CALENDAR_ALT); iconToday.setSize(20); buttonToday.setGraphic(iconToday); + FontIcon iconAbout = new FontIcon(FontIconType.INFO); + iconAbout.setSize(20); + buttonAbout.setGraphic(iconAbout); // apply theme anchorPaneMain.setStyle("-fx-background-color: #DDDDDD"); @@ -149,7 +161,8 @@ public class Controller labelNotification.setStyle("-fx-text-fill: #FFFFFF; -fx-font-size: 16; -fx-font-weight: bold; -fx-background-color: transparent;"); buttonLeft.setStyle("-fx-background-color: transparent;"); buttonRight.setStyle("-fx-background-color: transparent;"); - buttonToday.setStyle("-fx-background-color: transparent;"); + buttonToday.setStyle("-fx-background-color: transparent;"); + buttonAbout.setStyle("-fx-background-color: transparent;"); if(!settings.isComplete()) { @@ -277,6 +290,7 @@ public class Controller homeController.refresh(); paymentController.refresh(); categoryController.refresh(); + reportController.refresh(); if(tabCharts.isSelected()) { chartController.refresh(); @@ -314,6 +328,7 @@ public class Controller tabPayments.setDisable(disable); tabCategories.setDisable(disable); tabCharts.setDisable(disable); + tabReports.setDisable(disable); buttonLeft.setDisable(disable); buttonRight.setDisable(disable); buttonToday.setDisable(disable); diff --git a/src/de/deadlocker8/budgetmaster/ui/ExportChartController.java b/src/de/deadlocker8/budgetmaster/ui/ExportChartController.java new file mode 100644 index 0000000000000000000000000000000000000000..2815dc0a7ef22b47f1c289f1229c30ba16edfbc7 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/ui/ExportChartController.java @@ -0,0 +1,242 @@ +package de.deadlocker8.budgetmaster.ui; + +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +import javax.imageio.ImageIO; + +import fontAwesome.FontIcon; +import fontAwesome.FontIconType; +import javafx.embed.swing.SwingFXUtils; +import javafx.fxml.FXML; +import javafx.scene.SnapshotParameters; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.transform.Transform; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import logger.Logger; +import tools.AlertGenerator; + +public class ExportChartController +{ + @FXML private AnchorPane anchorPaneMain; + @FXML private TextField textFieldWidth; + @FXML private TextField textFieldHeight; + @FXML private Label labelSavePath; + @FXML private Button buttonChooseFile; + @FXML private Button buttonExport; + @FXML private Button buttonCancel; + + private ChartController controller; + private Stage stage; + private VBox chart; + private File savePath; + + public void init(Stage stage, ChartController controller, VBox chart) + { + this.controller = controller; + this.stage = stage; + this.chart = chart; + + this.savePath = controller.getLastExportPath(); + if(savePath != null) + { + labelSavePath.setText(savePath.getAbsolutePath()); + } + + anchorPaneMain.setStyle("-fx-background-color: #F4F4F4;"); + + FontIcon iconShow = new FontIcon(FontIconType.FOLDER_OPEN); + iconShow.setSize(14); + iconShow.setColor(Color.WHITE); + buttonChooseFile.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14;"); + buttonChooseFile.setGraphic(iconShow); + + FontIcon iconShow2 = new FontIcon(FontIconType.SAVE); + iconShow2.setSize(14); + iconShow2.setColor(Color.WHITE); + buttonExport.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14;"); + buttonExport.setGraphic(iconShow2); + + FontIcon iconShow3 = new FontIcon(FontIconType.TIMES); + iconShow3.setSize(14); + iconShow3.setColor(Color.WHITE); + buttonCancel.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14;"); + buttonCancel.setGraphic(iconShow3); + + textFieldWidth.setTextFormatter(new TextFormatter<>(c -> { + if(c.getControlNewText().isEmpty()) + { + return c; + } + + if(c.getControlNewText().matches("[0-9]*")) + { + return c; + } + else + { + return null; + } + })); + + textFieldHeight.setTextFormatter(new TextFormatter<>(c -> { + if(c.getControlNewText().isEmpty()) + { + return c; + } + + if(c.getControlNewText().matches("[0-9]*")) + { + return c; + } + else + { + return null; + } + })); + } + + public void chooseFile() + { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Diagramm exportieren"); + FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PNG (*.png)", "*.png"); + if(savePath != null) + { + fileChooser.setInitialDirectory(savePath.getParentFile()); + fileChooser.setInitialFileName(savePath.getName()); + } + fileChooser.getExtensionFilters().add(extFilter); + File file = fileChooser.showSaveDialog(stage); + if(file != null) + { + savePath = file; + labelSavePath.setText(file.getAbsolutePath()); + } + } + + public void export() + { + String widthText = textFieldWidth.getText(); + if(widthText == null || widthText.equals("")) + { + AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Bitte gib eine Breite in Pixeln an.", controller.getControlle().getIcon(), stage, null, false); + return; + } + + int width = 0; + try + { + width = Integer.parseInt(widthText); + } + catch(Exception e) + { + AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Nur ganzahlige Werte sind für das Feld Breite erlaubt.", controller.getControlle().getIcon(), stage, null, false); + return; + } + + String heightText = textFieldHeight.getText(); + if(heightText == null || heightText.equals("")) + { + AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Bitte gib eine Höhe in Pixeln an.", controller.getControlle().getIcon(), stage, null, false); + return; + } + + int height = 0; + try + { + height = Integer.parseInt(heightText); + } + catch(Exception e) + { + AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Nur ganzahlige Werte sind für das Feld Höhe erlaubt.", controller.getControlle().getIcon(), stage, null, false); + return; + } + + if(savePath == null) + { + AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Wähle einen Speicherort für das Diagramm aus.", controller.getControlle().getIcon(), stage, null, false); + return; + } + + SnapshotParameters sp = new SnapshotParameters(); + sp.setTransform(Transform.scale(width / chart.getWidth(), height / chart.getHeight())); + WritableImage image = chart.snapshot(sp, null); + try + { + ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", savePath); + controller.getControlle().showNotification("Diagramm erfolgreich exportiert"); + + stage.close(); + + Alert alert = new Alert(AlertType.INFORMATION); + alert.setTitle("Erfolgreich erstellt"); + alert.setHeaderText(""); + alert.setContentText("Das Diagramm wurde erfolgreich exportiert"); + Stage dialogStage = (Stage)alert.getDialogPane().getScene().getWindow(); + dialogStage.getIcons().add(controller.getControlle().getIcon()); + + ButtonType buttonTypeOne = new ButtonType("Ordner öffnen"); + ButtonType buttonTypeTwo = new ButtonType("Diagramm öffnen"); + ButtonType buttonTypeThree = new ButtonType("OK"); + alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeTwo, buttonTypeThree); + + Optional<ButtonType> result = alert.showAndWait(); + if (result.get() == buttonTypeOne) + { + try + { + Desktop.getDesktop().open(new File(savePath.getParent().replace("\\", "/"))); + } + catch(IOException e1) + { + Logger.error(e1); + AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Der Ordner konnte nicht geöffnet werden\n\n" + e1.getMessage(), controller.getControlle().getIcon(), stage, null, false); + } + } + else if (result.get() == buttonTypeTwo) + { + try + { + Desktop.getDesktop().open(new File(savePath.getAbsolutePath().replace("\\", "/"))); + } + catch(IOException e1) + { + Logger.error(e1); + AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Das Diagramm konnte nicht geöffnet werden\n\n" + e1.getMessage(), controller.getControlle().getIcon(), stage, null, false); + } + } + else + { + alert.close(); + } + } + catch(IOException e) + { + Logger.error(e); + AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Beim Exportieren des Diagramms ist ein Fehler aufgetreten:\n\n" + e.getMessage(), controller.getControlle().getIcon(), stage, null, false); + } + + stage.close(); + controller.chartCategoriesShow(false); + controller.setLastExportPath(savePath); + } + + public void cancel() + { + stage.close(); + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/ui/ExportChartGUI.fxml b/src/de/deadlocker8/budgetmaster/ui/ExportChartGUI.fxml new file mode 100644 index 0000000000000000000000000000000000000000..f65888dba78eb7068225922fb11ba1ce345fecf6 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/ui/ExportChartGUI.fxml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<AnchorPane fx:id="anchorPaneMain" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.ExportChartController"> + <children> + <VBox AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0"> + <children> + <HBox prefHeight="125.0" prefWidth="372.0" spacing="15.0"> + <children> + <VBox alignment="TOP_RIGHT" prefHeight="125.0" prefWidth="99.0" spacing="15.0"> + <children> + <Label text="Breite:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <Label text="Höhe:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <Label prefHeight="30.0" prefWidth="92.0" text="Speicherort:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + </children> + <HBox.margin> + <Insets /> + </HBox.margin> + </VBox> + <VBox prefHeight="4.0" prefWidth="100.0" spacing="15.0" HBox.hgrow="ALWAYS"> + <children> + <TextField fx:id="textFieldWidth" /> + <TextField fx:id="textFieldHeight" /> + <HBox alignment="CENTER_LEFT" spacing="15.0"> + <children> + <Label fx:id="labelSavePath" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS" /> + <Button fx:id="buttonChooseFile" mnemonicParsing="false" onAction="#chooseFile" text="Ändern" /> + </children> + </HBox> + </children> + </VBox> + </children> + </HBox> + <HBox alignment="CENTER" spacing="25.0" VBox.vgrow="ALWAYS"> + <children> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen" /> + <Button fx:id="buttonExport" mnemonicParsing="false" onAction="#export" text="Exportieren" /> + </children> + </HBox> + </children> + </VBox> + </children> +</AnchorPane> diff --git a/src/de/deadlocker8/budgetmaster/ui/FilterController.java b/src/de/deadlocker8/budgetmaster/ui/FilterController.java index 49b2a1505ffed5d3cb1363c181fcc66bb0015d05..504610f91c7cab1b0b7198d891d6dd1ecb0adfc4 100644 --- a/src/de/deadlocker8/budgetmaster/ui/FilterController.java +++ b/src/de/deadlocker8/budgetmaster/ui/FilterController.java @@ -31,14 +31,12 @@ public class FilterController private Stage stage; private Controller controller; - private PaymentController paymentController; private FilterSettings filterSetttings; - public void init(Stage stage, Controller controller, PaymentController paymentController, FilterSettings filterSettings) + public void init(Stage stage, Controller controller, FilterSettings filterSettings) { this.stage = stage; this.controller = controller; - this.paymentController = paymentController; this.filterSetttings = filterSettings; FontIcon iconCancel = new FontIcon(FontIconType.TIMES); @@ -124,16 +122,10 @@ public class FilterController name = null; } - // get new unfiltered list from server - controller.refresh(new FilterSettings()); - FilterSettings newFilterSettings = new FilterSettings(isIncomeAllowed, isPaymentAllowed, isNoRepeatingAllowed, isMonthlyRepeatingAllowed, isRepeatingEveryXDaysAllowed, allowedCategoryIDs, name); - controller.setFilterSettings(newFilterSettings); - controller.getPaymentHandler().filter(newFilterSettings); - + controller.refresh(newFilterSettings); stage.close(); - paymentController.getController().refreshAllTabs(); } public void reset() diff --git a/src/de/deadlocker8/budgetmaster/ui/GUI.fxml b/src/de/deadlocker8/budgetmaster/ui/GUI.fxml index 122b897e50a412a24f72688ec0a6db4237fbdfa6..549a7e45e216a6d0604a3c12e9cd1a3263dfaf82 100644 --- a/src/de/deadlocker8/budgetmaster/ui/GUI.fxml +++ b/src/de/deadlocker8/budgetmaster/ui/GUI.fxml @@ -7,26 +7,34 @@ <?import javafx.scene.control.TabPane?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.text.Font?> -<AnchorPane fx:id="anchorPaneMain" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.Controller"> +<AnchorPane fx:id="anchorPaneMain" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.Controller"> <children> <VBox alignment="TOP_CENTER" layoutY="24.0" prefHeight="562.0" prefWidth="772.0" spacing="15.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="14.0"> <children> <HBox alignment="CENTER" prefWidth="772.0" spacing="15.0"> <children> + <Region prefHeight="0.0" prefWidth="200.0" HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets left="25.0" /> + </HBox.margin> + </Region> <Button fx:id="buttonLeft" mnemonicParsing="false" onAction="#previousMonth" /> - <Label fx:id="labelMonth" alignment="CENTER" prefHeight="36.0" prefWidth="196.0" text="Dezember 2016"> + <Label fx:id="labelMonth" alignment="CENTER" prefHeight="36.0" prefWidth="280.0" text="Dezember 2016" HBox.hgrow="ALWAYS"> <font> - <Font name="System Bold" size="25.0" /> + <Font name="System Bold" size="24.0" /> </font> </Label> <Button fx:id="buttonRight" mnemonicParsing="false" onAction="#nextMonth" /> - <Button fx:id="buttonToday" alignment="CENTER" mnemonicParsing="false" onAction="#today"> + <Button fx:id="buttonToday" mnemonicParsing="false" onAction="#today" /> + <Region prefHeight="0.0" prefWidth="200.0" HBox.hgrow="ALWAYS" /> + <Button fx:id="buttonAbout" alignment="CENTER" mnemonicParsing="false" onAction="#about"> <HBox.margin> - <Insets /> + <Insets right="10.0" /> </HBox.margin> </Button> </children> @@ -39,6 +47,7 @@ <Tab fx:id="tabPayments" closable="false" text="Buchungen" /> <Tab fx:id="tabCategories" closable="false" text="Kategorien" /> <Tab fx:id="tabCharts" closable="false" text="Diagramme" /> + <Tab fx:id="tabReports" closable="false" text="Monatsbericht" /> <Tab fx:id="tabSettings" closable="false" text="Einstellungen" /> </tabs> </TabPane> diff --git a/src/de/deadlocker8/budgetmaster/ui/HomeController.java b/src/de/deadlocker8/budgetmaster/ui/HomeController.java index a17c5d1effb508eaa3c5b2fafacf6e9ca34024ed..1cf7c0c4d6f4a93eee3ae601aa462b616955a67a 100644 --- a/src/de/deadlocker8/budgetmaster/ui/HomeController.java +++ b/src/de/deadlocker8/budgetmaster/ui/HomeController.java @@ -82,7 +82,7 @@ public class HomeController implements Refreshable { currency = controller.getSettings().getCurrency(); } - labelBudget.setText(String.valueOf(Helpers.NUMBER_FORMAT.format(remaining).replace(".", ",")) + " " + currency); + labelBudget.setText(Helpers.getCurrencyString(remaining, currency)); if(remaining <= 0) { labelBudget.setStyle("-fx-text-fill: #CC0000"); @@ -91,7 +91,7 @@ public class HomeController implements Refreshable { labelBudget.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text")); } - labelStartBudget.setText("von " + String.valueOf(Helpers.NUMBER_FORMAT.format(budget.getIncomeSum()).replace(".", ",")) + " " + currency + " verbleibend"); + labelStartBudget.setText("von " + Helpers.getCurrencyString(budget.getIncomeSum(), currency) + " verbleibend"); double factor = remaining / budget.getIncomeSum(); if(factor < 0) diff --git a/src/de/deadlocker8/budgetmaster/ui/PaymentController.java b/src/de/deadlocker8/budgetmaster/ui/PaymentController.java index c4b7cc52c7d3ea25d3030b59c9c2c4c165637487..a1dc8834cb45e38fef892c6e8e22e7036105ae9c 100644 --- a/src/de/deadlocker8/budgetmaster/ui/PaymentController.java +++ b/src/de/deadlocker8/budgetmaster/ui/PaymentController.java @@ -191,8 +191,8 @@ public class PaymentController implements Refreshable { currency = controller.getSettings().getCurrency(); } - labelIncomes.setText(String.valueOf(Helpers.NUMBER_FORMAT.format(budget.getIncomeSum()).replace(".", ",")) + " " + currency); - labelPayments.setText(String.valueOf(Helpers.NUMBER_FORMAT.format(budget.getPaymentSum()).replace(".", ",")) + " " + currency); + labelIncomes.setText(Helpers.getCurrencyString(budget.getIncomeSum(), currency)); + labelPayments.setText(Helpers.getCurrencyString(budget.getPaymentSum(), currency)); } public void deleteNormalPayment(NormalPayment payment) @@ -258,8 +258,8 @@ public class PaymentController implements Refreshable newStage.getIcons().add(controller.getIcon()); newStage.setResizable(false); FilterController newController = fxmlLoader.getController(); - newController.init(newStage, controller, this, controller.getFilterSettings()); - newStage.show(); + newController.init(newStage, controller, controller.getFilterSettings()); + newStage.showAndWait(); } catch(IOException e) { diff --git a/src/de/deadlocker8/budgetmaster/ui/ReportController.java b/src/de/deadlocker8/budgetmaster/ui/ReportController.java new file mode 100644 index 0000000000000000000000000000000000000000..0ed7c910a7ecbb887ad903ad77e746ff314b4712 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/ui/ReportController.java @@ -0,0 +1,536 @@ +package de.deadlocker8.budgetmaster.ui; + +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Optional; + +import de.deadlocker8.budgetmaster.logic.FilterSettings; +import de.deadlocker8.budgetmaster.logic.Helpers; +import de.deadlocker8.budgetmaster.logic.Payment; +import de.deadlocker8.budgetmaster.logic.RepeatingPaymentEntry; +import de.deadlocker8.budgetmaster.logic.report.ColumnFilter; +import de.deadlocker8.budgetmaster.logic.report.ColumnOrder; +import de.deadlocker8.budgetmaster.logic.report.ColumnType; +import de.deadlocker8.budgetmaster.logic.report.ReportGenerator; +import de.deadlocker8.budgetmaster.logic.report.ReportItem; +import fontAwesome.FontIcon; +import fontAwesome.FontIconType; +import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.CellDataFeatures; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.AnchorPane; +import javafx.scene.paint.Color; +import javafx.stage.FileChooser; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.util.Callback; +import logger.Logger; +import tools.AlertGenerator; +import tools.Worker; + +public class ReportController implements Refreshable +{ + @FXML private AnchorPane anchorPaneMain; + @FXML private Label labelPayments; + @FXML private Label labelFilterActive; + @FXML private CheckBox checkBoxSplitTable; + @FXML private CheckBox checkBoxIncludeCategoryBudgets; + @FXML private CheckBox checkBoxDescending; + @FXML private Button buttonFilter; + @FXML private Button buttonGenerate; + @FXML private TableView<ReportItem> tableView; + + private Controller controller; + private ColumnFilter columnFilter; + private File reportPath; + + public void init(Controller controller) + { + this.controller = controller; + + FontIcon iconFilter = new FontIcon(FontIconType.FILTER); + iconFilter.setSize(18); + iconFilter.setStyle("-fx-text-fill: white"); + buttonFilter.setGraphic(iconFilter); + FontIcon iconPayment = new FontIcon(FontIconType.COGS); + iconPayment.setSize(18); + iconPayment.setStyle("-fx-text-fill: white"); + buttonGenerate.setGraphic(iconPayment); + FontIcon iconWarning = new FontIcon(FontIconType.WARNING); + iconWarning.setSize(13); + iconWarning.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text")); + labelFilterActive.setGraphic(iconWarning); + + checkBoxDescending.setSelected(true); + checkBoxDescending.selectedProperty().addListener((a, b, c)->{ + refreshTableView(c); + }); + + initTable(); + + // apply theme + anchorPaneMain.setStyle("-fx-background-color: #F4F4F4;"); + labelFilterActive.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text")); + buttonFilter.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;"); + buttonGenerate.setStyle("-fx-background-color: #2E79B9; -fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16;"); + checkBoxSplitTable.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text") + "; -fx-font-size: 14;"); + checkBoxIncludeCategoryBudgets.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text") + "; -fx-font-size: 14;"); + checkBoxDescending.setStyle("-fx-text-fill: " + controller.getBundle().getString("color.text") + "; -fx-font-size: 14;"); + + refresh(); + } + + private void initTable() + { + columnFilter = new ColumnFilter(); + for(ColumnType type : ColumnType.values()) + { + columnFilter.addColumn(type); + } + + Label labelPlaceholder = new Label("Keine Daten verfügbar"); + labelPlaceholder.setStyle("-fx-font-size: 16"); + tableView.setPlaceholder(labelPlaceholder); + + TableColumn<ReportItem, Integer> columnPosition = new TableColumn<>("Nr."); + columnPosition.setUserData(ColumnType.POSITION); + columnPosition.setCellValueFactory(new PropertyValueFactory<ReportItem, Integer>("position")); + columnPosition.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxPositions = new CheckBox(); + checkBoxPositions.setSelected(true); + checkBoxPositions.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnPosition.setStyle(style); + columnFilter.toggleColumn(ColumnType.POSITION, c); + }); + columnPosition.setGraphic(checkBoxPositions); + columnPosition.setSortable(false); + tableView.getColumns().add(columnPosition); + + TableColumn<ReportItem, String> columnDate = new TableColumn<>("Datum"); + columnDate.setUserData(ColumnType.DATE); + columnDate.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<ReportItem, String>, ObservableValue<String>>() + { + @Override + public ObservableValue<String> call(CellDataFeatures<ReportItem, String> param) + { + String dateString = param.getValue().getDate(); + try + { + DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + Date date = format.parse(dateString); + DateFormat finalFormat = new SimpleDateFormat("dd.MM.yy"); + dateString = finalFormat.format(date); + return new SimpleStringProperty(dateString); + } + catch(Exception e) + { + Logger.error(e); + return null; + } + } + }); + columnDate.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxDate = new CheckBox(); + checkBoxDate.setSelected(true); + checkBoxDate.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnDate.setStyle(style); + columnFilter.toggleColumn(ColumnType.DATE, c); + }); + columnDate.setGraphic(checkBoxDate); + columnDate.setSortable(false); + tableView.getColumns().add(columnDate); + + TableColumn<ReportItem, Boolean> columnIsRepeating = new TableColumn<>("Wiederholend"); + columnIsRepeating.setUserData(ColumnType.REPEATING); + columnIsRepeating.setCellValueFactory(new PropertyValueFactory<ReportItem, Boolean>("repeating")); + columnIsRepeating.setCellFactory(new Callback<TableColumn<ReportItem, Boolean>, TableCell<ReportItem, Boolean>>() + { + @Override + public TableCell<ReportItem, Boolean> call(TableColumn<ReportItem, Boolean> param) + { + TableCell<ReportItem, Boolean> cell = new TableCell<ReportItem, Boolean>() + { + @Override + public void updateItem(Boolean item, boolean empty) + { + if(!empty) + { + FontIcon iconRepeating = new FontIcon(FontIconType.CALENDAR); + iconRepeating.setSize(16); + Color color = item ? Color.web("#212121") : Color.TRANSPARENT; + iconRepeating.setColor(color); + + Label labelRepeating = new Label(); + labelRepeating.setGraphic(iconRepeating); + labelRepeating.setStyle("-fx-font-weight: bold; -fx-font-size: 14; -fx-text-fill: #212121"); + labelRepeating.setAlignment(Pos.CENTER); + setGraphic(labelRepeating); + } + else + { + setGraphic(null); + } + } + }; + return cell; + } + }); + columnIsRepeating.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxRepeating = new CheckBox(); + checkBoxRepeating.setSelected(true); + checkBoxRepeating.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnIsRepeating.setStyle(style); + columnFilter.toggleColumn(ColumnType.REPEATING, c); + }); + columnIsRepeating.setGraphic(checkBoxRepeating); + columnIsRepeating.setSortable(false); + tableView.getColumns().add(columnIsRepeating); + + TableColumn<ReportItem, String> columnCategory = new TableColumn<>("Kategorie"); + columnCategory.setUserData(ColumnType.CATEGORY); + columnCategory.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<ReportItem, String>, ObservableValue<String>>() + { + @Override + public ObservableValue<String> call(CellDataFeatures<ReportItem, String> param) + { + String categoryName = param.getValue().getCategory().getName(); + if(categoryName.equals("NONE")) + { + categoryName = ""; + } + return new SimpleStringProperty(categoryName); + } + }); + columnCategory.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxCategory = new CheckBox(); + checkBoxCategory.setSelected(true); + checkBoxCategory.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnCategory.setStyle(style); + columnFilter.toggleColumn(ColumnType.CATEGORY, c); + }); + columnCategory.setGraphic(checkBoxCategory); + columnCategory.setSortable(false); + tableView.getColumns().add(columnCategory); + + TableColumn<ReportItem, Integer> columnName = new TableColumn<>("Name"); + columnName.setUserData(ColumnType.NAME); + columnName.setCellValueFactory(new PropertyValueFactory<ReportItem, Integer>("name")); + columnName.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxName = new CheckBox(); + checkBoxName.setSelected(true); + checkBoxName.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnName.setStyle(style); + columnFilter.toggleColumn(ColumnType.NAME, c); + }); + columnName.setGraphic(checkBoxName); + columnName.setSortable(false); + tableView.getColumns().add(columnName); + + TableColumn<ReportItem, Integer> columnDescription = new TableColumn<>("Notiz"); + columnDescription.setUserData(ColumnType.DESCRIPTION); + columnDescription.setCellValueFactory(new PropertyValueFactory<ReportItem, Integer>("description")); + columnDescription.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxDescription = new CheckBox(); + checkBoxDescription.setSelected(true); + checkBoxDescription.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnDescription.setStyle(style); + columnFilter.toggleColumn(ColumnType.DESCRIPTION, c); + }); + columnDescription.setGraphic(checkBoxDescription); + columnDescription.setSortable(false); + tableView.getColumns().add(columnDescription); + + TableColumn<ReportItem, Integer> columnRating = new TableColumn<>("Bewertung"); + columnRating.setUserData(ColumnType.RATING); + columnRating.setCellValueFactory(new PropertyValueFactory<ReportItem, Integer>("amount")); + columnRating.setCellFactory(new Callback<TableColumn<ReportItem, Integer>, TableCell<ReportItem, Integer>>() + { + @Override + public TableCell<ReportItem, Integer> call(TableColumn<ReportItem, Integer> param) + { + TableCell<ReportItem, Integer> cell = new TableCell<ReportItem, Integer>() + { + @Override + public void updateItem(Integer item, boolean empty) + { + if(!empty) + { + FontIcon iconRepeating = item > 0 ? new FontIcon(FontIconType.PLUS) : new FontIcon(FontIconType.MINUS); + iconRepeating.setSize(14); + iconRepeating.setColor(Color.web("#212121")); + + Label labelRepeating = new Label(); + labelRepeating.setGraphic(iconRepeating); + labelRepeating.setStyle("-fx-font-weight: bold; -fx-font-size: 14; -fx-text-fill: #212121"); + labelRepeating.setAlignment(Pos.CENTER); + setGraphic(labelRepeating); + } + else + { + setGraphic(null); + } + } + }; + return cell; + } + }); + columnRating.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxRating = new CheckBox(); + checkBoxRating.setSelected(true); + checkBoxRating.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnRating.setStyle(style); + columnFilter.toggleColumn(ColumnType.RATING, c); + }); + columnRating.setGraphic(checkBoxRating); + columnRating.setSortable(false); + tableView.getColumns().add(columnRating); + + TableColumn<ReportItem, String> columnAmount = new TableColumn<>("Betrag"); + columnAmount.setUserData(ColumnType.AMOUNT); + columnAmount.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<ReportItem, String>, ObservableValue<String>>() + { + @Override + public ObservableValue<String> call(CellDataFeatures<ReportItem, String> param) + { + StringProperty value = new SimpleStringProperty(); + double amount = param.getValue().getAmount() / 100.0; + value.set(Helpers.getCurrencyString(amount, controller.getSettings().getCurrency())); + return value; + } + }); + columnAmount.setStyle("-fx-alignment: CENTER;"); + CheckBox checkBoxAmount = new CheckBox(); + checkBoxAmount.setSelected(true); + checkBoxAmount.selectedProperty().addListener((a, b, c)->{ + String style = c ? "" : "-fx-background-color: salmon"; + columnAmount.setStyle(style); + columnFilter.toggleColumn(ColumnType.AMOUNT, c); + }); + columnAmount.setGraphic(checkBoxAmount); + columnAmount.setSortable(false); + tableView.getColumns().add(columnAmount); + + tableView.setFixedCellSize(26); + } + + public void filter() + { + try + { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/de/deadlocker8/budgetmaster/ui/FilterGUI.fxml")); + Parent root = (Parent)fxmlLoader.load(); + Stage newStage = new Stage(); + newStage.initOwner(controller.getStage()); + newStage.initModality(Modality.APPLICATION_MODAL); + newStage.setTitle("Filter"); + newStage.setScene(new Scene(root)); + newStage.getIcons().add(controller.getIcon()); + newStage.setResizable(false); + FilterController newController = fxmlLoader.getController(); + newController.init(newStage, controller, controller.getFilterSettings()); + newStage.show(); + } + catch(IOException e) + { + Logger.error(e); + } + } + + private ArrayList<ReportItem> createReportItems(ArrayList<Payment> payments, boolean descending) + { + ArrayList<ReportItem> reportItems = new ArrayList<>(); + for(int i = 0; i < payments.size(); i++) + { + Payment currentPayment = payments.get(i); + ReportItem reportItem = new ReportItem(); + reportItem.setPosition(i + 1); + reportItem.setDate(currentPayment.getDate()); + reportItem.setAmount(currentPayment.getAmount()); + reportItem.setDescription(currentPayment.getDescription()); + reportItem.setName(currentPayment.getName()); + reportItem.setRepeating(currentPayment instanceof RepeatingPaymentEntry); + reportItem.setCategory(controller.getCategoryHandler().getCategory(currentPayment.getCategoryID())); + + reportItems.add(reportItem); + } + + if(!descending) + { + Collections.reverse(reportItems); + } + return reportItems; + } + + private void refreshTableView(boolean descending) + { + tableView.getItems().clear(); + + ArrayList<Payment> payments = controller.getPaymentHandler().getPayments(); + if(payments != null) + { + ArrayList<ReportItem> reportItems = createReportItems(payments, descending); + ObservableList<ReportItem> objectsForTable = FXCollections.observableArrayList(reportItems); + tableView.setItems(objectsForTable); + } + } + + @SuppressWarnings("rawtypes") + public void generate() + { + ColumnOrder columnOrder = new ColumnOrder(); + for(TableColumn currentColumn : tableView.getColumns()) + { + ColumnType currentType = (ColumnType)currentColumn.getUserData(); + if(columnFilter.containsColumn(currentType)) + { + columnOrder.addColumn(currentType); + } + } + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Bericht speichern"); + FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PDF (*.pdf)", "*.pdf"); + if(reportPath != null) + { + fileChooser.setInitialDirectory(reportPath.getParentFile()); + fileChooser.setInitialFileName(reportPath.getName()); + } + fileChooser.getExtensionFilters().add(extFilter); + File file = fileChooser.showSaveDialog(controller.getStage()); + if(file != null) + { + reportPath = file; + ReportGenerator reportGenerator = new ReportGenerator(new ArrayList<ReportItem>(tableView.getItems()), + controller.getCategoryBudgets(), + columnOrder, + checkBoxSplitTable.isSelected(), + checkBoxIncludeCategoryBudgets.isSelected(), + file, + controller.getSettings().getCurrency(), + controller.getCurrentDate()); + + Stage modalStage = Helpers.showModal("Vorgang läuft", "Der Monatsbericht wird erstellt, bitte warten...", controller.getStage(), controller.getIcon()); + + Worker.runLater(() -> { + try + { + reportGenerator.generate(); + + Platform.runLater(() -> { + if(modalStage != null) + { + modalStage.close(); + } + + controller.showNotification("Bericht erfolgreich gespeichert"); + + Alert alert = new Alert(AlertType.INFORMATION); + alert.setTitle("Erfolgreich erstellt"); + alert.setHeaderText(""); + alert.setContentText("Der Monatsbericht wurde erfolgreich erstellt"); + Stage dialogStage = (Stage)alert.getDialogPane().getScene().getWindow(); + dialogStage.getIcons().add(controller.getIcon()); + + ButtonType buttonTypeOne = new ButtonType("Ordner öffnen"); + ButtonType buttonTypeTwo = new ButtonType("Bericht öffnen"); + ButtonType buttonTypeThree = new ButtonType("OK"); + alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeTwo, buttonTypeThree); + + Optional<ButtonType> result = alert.showAndWait(); + if (result.get() == buttonTypeOne) + { + try + { + Desktop.getDesktop().open(new File(file.getParent().replace("\\", "/"))); + } + catch(IOException e1) + { + Logger.error(e1); + AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Der Ordner konnte nicht geöffnet werden\n\n" + e1.getMessage(), controller.getIcon(), controller.getStage(), null, false); + } + } + else if (result.get() == buttonTypeTwo) + { + try + { + Desktop.getDesktop().open(new File(file.getAbsolutePath().replace("\\", "/"))); + } + catch(IOException e1) + { + Logger.error(e1); + AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Der Bericht konnte nicht geöffnet werden\n\n" + e1.getMessage(), controller.getIcon(), controller.getStage(), null, false); + } + } + else + { + alert.close(); + } + }); + } + catch(Exception e) + { + Logger.error(e); + Platform.runLater(() -> { + if(modalStage != null) + { + modalStage.close(); + } + AlertGenerator.showAlert(AlertType.ERROR, "Fehler", "", "Beim Erstellen des Monatsberichts ist ein Fehler aufgetreten:\n\n" + e.getMessage(), controller.getIcon(), controller.getStage(), null, false); + }); + } + }); + } + } + + public Controller getController() + { + return controller; + } + + @Override + public void refresh() + { + if(controller.getFilterSettings().equals(new FilterSettings())) + { + labelFilterActive.setVisible(false); + } + else + { + labelFilterActive.setVisible(true); + } + + refreshTableView(checkBoxDescending.isSelected()); + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/ui/ReportTab.fxml b/src/de/deadlocker8/budgetmaster/ui/ReportTab.fxml new file mode 100644 index 0000000000000000000000000000000000000000..f0d20b88c933e60553324b03e59243058d015939 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/ui/ReportTab.fxml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.CheckBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.TableView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<AnchorPane fx:id="anchorPaneMain" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.ReportController"> + <children> + <VBox alignment="TOP_CENTER" layoutY="24.0" prefHeight="562.0" prefWidth="772.0" spacing="20.0" AnchorPane.bottomAnchor="35.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="25.0"> + <children> + <HBox alignment="CENTER" prefHeight="11.0" prefWidth="772.0"> + <children> + <VBox alignment="CENTER" spacing="10.0"> + <children> + <Button fx:id="buttonFilter" mnemonicParsing="false" onAction="#filter" text="Filter"> + <font> + <Font name="System Bold" size="14.0" /> + </font> + </Button> + <Label fx:id="labelFilterActive" text="Filter aktiv"> + <font> + <Font name="System Bold" size="13.0" /> + </font> + </Label> + </children> + </VBox> + </children> + <VBox.margin> + <Insets /> + </VBox.margin> + </HBox> + <HBox alignment="CENTER" prefHeight="11.0" prefWidth="772.0"> + <children> + <VBox spacing="10.0"> + <children> + <CheckBox fx:id="checkBoxSplitTable" mnemonicParsing="false" text="Einnahmen und Ausgaben als getrennte Tabellen" /> + <CheckBox fx:id="checkBoxIncludeCategoryBudgets" mnemonicParsing="false" text="Verbrauch nach Kategorien hizufügen" /> + <CheckBox fx:id="checkBoxDescending" mnemonicParsing="false" text="Neuestes Datum zuerst" /> + </children> + </VBox> + </children> + <VBox.margin> + <Insets /> + </VBox.margin> + </HBox> + <TableView fx:id="tableView" prefHeight="270.0" prefWidth="772.0" VBox.vgrow="ALWAYS"> + <columnResizePolicy> + <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" /> + </columnResizePolicy></TableView> + <HBox alignment="CENTER" prefHeight="11.0" prefWidth="772.0"> + <children> + <VBox alignment="CENTER" spacing="10.0"> + <children> + <Button fx:id="buttonGenerate" mnemonicParsing="false" onAction="#generate" text="Bericht erzeugen"> + <font> + <Font name="System Bold" size="14.0" /> + </font> + </Button> + </children> + </VBox> + </children> + <VBox.margin> + <Insets top="15.0" /> + </VBox.margin> + </HBox> + </children> + </VBox> + </children> +</AnchorPane> diff --git a/src/de/deadlocker8/budgetmaster/ui/cells/CategoryBudgetCell.java b/src/de/deadlocker8/budgetmaster/ui/cells/CategoryBudgetCell.java index ff09b4a5e7224bc504fba1904b9d0a1a083afb83..74b06681cef7c45b731bcd35cea41ac51accd526 100644 --- a/src/de/deadlocker8/budgetmaster/ui/cells/CategoryBudgetCell.java +++ b/src/de/deadlocker8/budgetmaster/ui/cells/CategoryBudgetCell.java @@ -59,9 +59,17 @@ public class CategoryBudgetCell extends ListCell<CategoryBudget> hbox.getChildren().add(r); HBox.setHgrow(r, Priority.ALWAYS); - Label labelBudget = new Label(String.valueOf(Helpers.NUMBER_FORMAT.format(item.getBudget() / 100.0)).replace(".", ",") + " " + homeController.getController().getSettings().getCurrency()); + Label labelBudget = new Label(Helpers.getCurrencyString(item.getBudget() / 100.0, homeController.getController().getSettings().getCurrency())); + labelBudget.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-text-fill: #247A2D;"); + if(item.getBudget() > 0) + { + labelBudget.setText("+" + labelBudget.getText()); + } + else + { + labelBudget.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-text-fill: #CC0000"); + } labelBudget.setPrefHeight(HEIGHT); - labelBudget.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-text-fill: #212121;"); labelBudget.setAlignment(Pos.CENTER); labelBudget.getStyleClass().add("greylabel"); hbox.getChildren().add(labelBudget); diff --git a/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java b/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java index e096c9a72a0c14fdabd8972136b64e235e739ac2..ce20c31a7b82d563bd0d4d27887f20c0f21831a0 100644 --- a/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java +++ b/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java @@ -28,6 +28,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javafx.stage.Stage; +import logger.Logger; import tools.ConvertTo; public class PaymentCell extends ListCell<Payment> @@ -62,7 +63,7 @@ public class PaymentCell extends ListCell<Payment> } catch(ParseException e) { - e.printStackTrace(); + Logger.error(e); } Label labelDate = new Label(dateString); labelDate.setPrefHeight(HEIGHT); @@ -121,7 +122,7 @@ public class PaymentCell extends ListCell<Payment> hbox.getChildren().add(r); HBox.setHgrow(r, Priority.ALWAYS); - Label labelBudget = new Label(String.valueOf(Helpers.NUMBER_FORMAT.format(item.getAmount() / 100.0)).replace(".", ",") + " " + paymentController.getController().getSettings().getCurrency()); + Label labelBudget = new Label(Helpers.getCurrencyString(item.getAmount(), paymentController.getController().getSettings().getCurrency())); labelBudget.setPrefHeight(HEIGHT); labelBudget.setStyle("-fx-font-weight: bold; -fx-font-size: 16; -fx-text-fill: #247A2D"); labelBudget.setAlignment(Pos.CENTER);