mekhq.gui.FinancesTab.java Source code

Java tutorial

Introduction

Here is the source code for mekhq.gui.FinancesTab.java

Source

/*
 * Copyright (c) 2017 The MegaMek Team. All rights reserved.
 *
 * This file is part of MekHQ.
 *
 * MekHQ is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MekHQ is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MekHQ.  If not, see <http://www.gnu.org/licenses/>.
 */
package mekhq.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.ResourceBundle;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.TableColumn;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.chart.ui.RectangleInsets;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;

import megamek.common.event.Subscribe;
import megamek.common.util.EncodeControl;
import mekhq.MekHQ;
import mekhq.campaign.event.AcquisitionEvent;
import mekhq.campaign.event.AssetEvent;
import mekhq.campaign.event.GMModeEvent;
import mekhq.campaign.event.LoanEvent;
import mekhq.campaign.event.MissionChangedEvent;
import mekhq.campaign.event.MissionNewEvent;
import mekhq.campaign.event.PartEvent;
import mekhq.campaign.event.ScenarioResolvedEvent;
import mekhq.campaign.event.TransactionEvent;
import mekhq.campaign.event.UnitEvent;
import mekhq.campaign.finances.Transaction;
import mekhq.campaign.mission.Contract;
import mekhq.gui.adapter.FinanceTableMouseAdapter;
import mekhq.gui.adapter.LoanTableMouseAdapter;
import mekhq.gui.dialog.AddFundsDialog;
import mekhq.gui.dialog.ManageAssetsDialog;
import mekhq.gui.dialog.NewLoanDialog;
import mekhq.gui.model.FinanceTableModel;
import mekhq.gui.model.LoanTableModel;

/**
 * Shows record of financial transactions.
 */
public final class FinancesTab extends CampaignGuiTab {

    private static final long serialVersionUID = -3203920871646865885L;

    private ResourceBundle resourceMap;

    private JTable financeTable;
    private JTable loanTable;
    private JTextArea areaNetWorth;
    private JButton btnAddFunds;
    private JButton btnManageAssets;

    private FinanceTableModel financeModel;
    private LoanTableModel loanModel;

    FinancesTab(CampaignGUI gui, String name) {
        super(gui, name);
        MekHQ.registerHandler(this);
    }

    private enum GraphType {
        BALANCE_AMOUNT, MONTHLY_FINANCES;
    }

    /*
     * (non-Javadoc)
     *
     * @see mekhq.gui.CampaignGuiTab#initTab()
     */
    @Override
    public void initTab() {
        resourceMap = ResourceBundle.getBundle("mekhq.resources.FinancesTab", new EncodeControl()); //$NON-NLS-1$

        GridBagConstraints gridBagConstraints;

        setLayout(new GridBagLayout());
        ChartPanel financeAmountPanel = (ChartPanel) createGraphPanel(GraphType.BALANCE_AMOUNT);
        ChartPanel financeMonthlyPanel = (ChartPanel) createGraphPanel(GraphType.MONTHLY_FINANCES);

        financeModel = new FinanceTableModel();
        financeTable = new JTable(financeModel);
        financeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        financeTable.addMouseListener(new FinanceTableMouseAdapter(getCampaignGui(), financeTable, financeModel));
        financeTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        TableColumn column = null;
        for (int i = 0; i < FinanceTableModel.N_COL; i++) {
            column = financeTable.getColumnModel().getColumn(i);
            column.setPreferredWidth(financeModel.getColumnWidth(i));
            column.setCellRenderer(financeModel.getRenderer());
        }
        financeTable.setIntercellSpacing(new Dimension(0, 0));
        financeTable.setShowGrid(false);

        loanModel = new LoanTableModel();
        loanTable = new JTable(loanModel);
        loanTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        loanTable.addMouseListener(new LoanTableMouseAdapter(getCampaignGui(), loanTable, loanModel));
        loanTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        column = null;
        for (int i = 0; i < LoanTableModel.N_COL; i++) {
            column = loanTable.getColumnModel().getColumn(i);
            column.setPreferredWidth(loanModel.getColumnWidth(i));
            column.setCellRenderer(loanModel.getRenderer());
        }
        loanTable.setIntercellSpacing(new Dimension(0, 0));
        loanTable.setShowGrid(false);
        JScrollPane scrollLoanTable = new JScrollPane(loanTable);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        JPanel panBalance = new JPanel(new GridBagLayout());
        panBalance.add(new JScrollPane(financeTable), gridBagConstraints);
        panBalance.setMinimumSize(new java.awt.Dimension(350, 100));
        panBalance.setBorder(BorderFactory.createTitledBorder("Balance Sheet"));
        JPanel panLoan = new JPanel(new GridBagLayout());
        panLoan.add(scrollLoanTable, gridBagConstraints);

        JTabbedPane financeTab = new JTabbedPane();
        financeTab.setMinimumSize(new java.awt.Dimension(450, 300));
        financeTab.setPreferredSize(new java.awt.Dimension(450, 300));

        JSplitPane splitFinances = new JSplitPane(JSplitPane.VERTICAL_SPLIT, panBalance, financeTab);
        splitFinances.setOneTouchExpandable(true);
        splitFinances.setContinuousLayout(true);
        splitFinances.setResizeWeight(1.0);
        splitFinances.setName("splitFinances");

        financeTab.addTab(resourceMap.getString("activeLoans.text"), panLoan);
        financeTab.addTab(resourceMap.getString("cbillsBalanceTime.text"), financeAmountPanel);
        financeTab.addTab(resourceMap.getString("monthlyRevenueExpenditures.text"), financeMonthlyPanel);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        add(splitFinances, gridBagConstraints);

        JPanel panelFinanceRight = new JPanel(new BorderLayout());

        JPanel pnlFinanceBtns = new JPanel(new GridLayout(2, 2));
        btnAddFunds = new JButton("Add Funds (GM)");
        btnAddFunds.addActionListener(ev -> addFundsActionPerformed());
        btnAddFunds.setEnabled(getCampaign().isGM());
        pnlFinanceBtns.add(btnAddFunds);
        JButton btnGetLoan = new JButton("Get Loan");
        btnGetLoan.addActionListener(e -> showNewLoanDialog());
        pnlFinanceBtns.add(btnGetLoan);

        btnManageAssets = new JButton("Manage Assets (GM)");
        btnManageAssets.addActionListener(e -> manageAssets());
        btnManageAssets.setEnabled(getCampaign().isGM());
        pnlFinanceBtns.add(btnManageAssets);

        panelFinanceRight.add(pnlFinanceBtns, BorderLayout.NORTH);

        areaNetWorth = new JTextArea();
        areaNetWorth.setLineWrap(true);
        areaNetWorth.setWrapStyleWord(true);
        areaNetWorth.setFont(new Font("Courier New", Font.PLAIN, 12));
        areaNetWorth.setText(getCampaign().getFinancialReport());
        areaNetWorth.setEditable(false);

        JScrollPane descriptionScroll = new JScrollPane(areaNetWorth);
        panelFinanceRight.add(descriptionScroll, BorderLayout.CENTER);
        areaNetWorth.setCaretPosition(0);
        descriptionScroll.setMinimumSize(new Dimension(300, 200));

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 0.0;
        gridBagConstraints.weighty = 1.0;
        add(panelFinanceRight, gridBagConstraints);
    }

    private XYDataset setupFinanceDataset() {
        TimeSeries s1 = new TimeSeries("C-Bills"); // NOI18N
        ArrayList<Transaction> transactions = getCampaign().getFinances().getAllTransactions();
        Calendar cal = Calendar.getInstance();

        long balance = 0;
        for (int i = 0; i < transactions.size(); i++) {
            balance += transactions.get(i).getAmount();
            cal.setTime(transactions.get(i).getDate());
            // since there may be more than one entry per day and the dataset for the graph can only have one entry per day
            // we use addOrUpdate() which assumes transactions are in sequential order by date so we always have the most
            // up-to-date entry for each day
            s1.addOrUpdate(new Day(cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.MONTH) + 1, // Gregorian and Julian calendars start at 0: https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#MONTH
                    cal.get(Calendar.YEAR)), balance);
        }

        TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(s1);

        return dataset;
    }

    private CategoryDataset setupMonthlyDataset() {
        SimpleDateFormat df = new SimpleDateFormat("MMM-yyyy");
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        ArrayList<Transaction> transactions = getCampaign().getFinances().getAllTransactions();
        Calendar cal = Calendar.getInstance();

        String pastMonthYear = "";
        long monthlyRevenue = 0;
        long monthlyExpenditures = 0;
        for (int i = 0; i < transactions.size(); i++) {
            cal.setTime(transactions.get(i).getDate());

            if (pastMonthYear.equals(df.format(cal.getTime()))) {
                if (transactions.get(i).getAmount() > 0) {
                    monthlyRevenue += transactions.get(i).getAmount();
                } else {
                    monthlyExpenditures += Math.abs(transactions.get(i).getAmount());
                }
            } else {
                // as long as we're not at the first transaction, add the previous month and reset
                if (i != 0) {
                    dataset.addValue(monthlyRevenue, resourceMap.getString("graphMonthlyRevenue.text"),
                            pastMonthYear);
                    dataset.addValue(monthlyExpenditures, resourceMap.getString("graphMonthlyExpenditures.text"),
                            pastMonthYear);
                    monthlyRevenue = 0;
                    monthlyExpenditures = 0;
                }
                pastMonthYear = df.format(cal.getTime());
                if (transactions.get(i).getAmount() > 0) {
                    monthlyRevenue = transactions.get(i).getAmount();
                } else {
                    monthlyExpenditures = Math.abs(transactions.get(i).getAmount());
                }
            }

            // if we're at the last transaction, save it off
            if (i == transactions.size() - 1) {
                dataset.addValue(monthlyRevenue, resourceMap.getString("graphMonthlyRevenue.text"), pastMonthYear);
                dataset.addValue(monthlyExpenditures, resourceMap.getString("graphMonthlyExpenditures.text"),
                        pastMonthYear);
            }
        }

        return dataset;
    }

    private JPanel createGraphPanel(GraphType gt) {
        JFreeChart chart = null;
        if (gt.equals(GraphType.BALANCE_AMOUNT)) {
            chart = createAmountChart(setupFinanceDataset());
        } else if (gt.equals(GraphType.MONTHLY_FINANCES)) {
            chart = createMonthlyChart(setupMonthlyDataset());
        }
        ChartPanel panel = new ChartPanel(chart, false);
        panel.setFillZoomRectangle(true);
        panel.setMouseWheelEnabled(true);
        return panel;
    }

    private JFreeChart createAmountChart(XYDataset dataset) {
        JFreeChart chart = ChartFactory.createTimeSeriesChart("", // title
                resourceMap.getString("graphDate.text"), // x-axis label
                resourceMap.getString("graphCBills.text"), // y-axis label
                dataset);

        chart.setBackgroundPaint(Color.WHITE);

        XYPlot plot = (XYPlot) chart.getPlot();
        plot.setBackgroundPaint(Color.LIGHT_GRAY);
        plot.setDomainGridlinePaint(Color.WHITE);
        plot.setRangeGridlinePaint(Color.WHITE);
        plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
        plot.setDomainCrosshairVisible(true);
        plot.setRangeCrosshairVisible(true);

        XYItemRenderer r = plot.getRenderer();
        if (r instanceof XYLineAndShapeRenderer) {
            XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
            renderer.setDefaultShapesVisible(true);
            renderer.setDefaultShapesFilled(true);
            renderer.setDrawSeriesLineAsPath(true);
        }

        DateAxis axis = (DateAxis) plot.getDomainAxis();
        axis.setDateFormatOverride(new SimpleDateFormat("MMM-yyyy"));

        chart.removeLegend();

        return chart;
    }

    private JFreeChart createMonthlyChart(CategoryDataset dataset) {
        JFreeChart chart = ChartFactory.createBarChart("", // title
                resourceMap.getString("graphDate.text"), // x-axis label
                resourceMap.getString("graphCBills.text"), // y-axis label
                dataset);

        chart.setBackgroundPaint(Color.WHITE);

        chart.getLegend().setPosition(RectangleEdge.TOP);

        return chart;
    }

    /*
     * (non-Javadoc)
     *
     * @see mekhq.gui.CampaignGuiTab#refreshAll()
     */
    @Override
    public void refreshAll() {
        refreshFinancialTransactions();
    }

    /*
     * (non-Javadoc)
     *
     * @see mekhq.gui.CampaignGuiTab#tabType()
     */
    @Override
    public GuiTabType tabType() {
        return GuiTabType.FINANCES;
    }

    private void addFundsActionPerformed() {
        AddFundsDialog addFundsDialog = new AddFundsDialog(getFrame(), true);
        addFundsDialog.setVisible(true);
        if (addFundsDialog.getClosedType() == JOptionPane.OK_OPTION) {
            long funds = addFundsDialog.getFundsQuantity();
            String description = addFundsDialog.getFundsDescription();
            int category = addFundsDialog.getCategory();
            getCampaign().addFunds(funds, description, category);
        }
    }

    private void showNewLoanDialog() {
        NewLoanDialog nld = new NewLoanDialog(getFrame(), true, getCampaign());
        nld.setVisible(true);
    }

    private void manageAssets() {
        ManageAssetsDialog mad = new ManageAssetsDialog(getFrame(), getCampaign());
        mad.setVisible(true);
    }

    public void refreshFinancialTransactions() {
        SwingUtilities.invokeLater(() -> {
            financeModel.setData(getCampaign().getFinances().getAllTransactions());
            loanModel.setData(getCampaign().getFinances().getAllLoans());
            refreshFinancialReport();
        });
    }

    public void refreshFinancialReport() {
        SwingUtilities.invokeLater(() -> {
            areaNetWorth.setText(getCampaign().getFinancialReport());
            areaNetWorth.setCaretPosition(0);
        });
    }

    ActionScheduler financialTransactionsScheduler = new ActionScheduler(this::refreshFinancialTransactions);
    ActionScheduler financialReportScheduler = new ActionScheduler(this::refreshFinancialReport);

    @Subscribe
    public void handle(GMModeEvent ev) {
        btnAddFunds.setEnabled(ev.isGMMode());
        btnManageAssets.setEnabled(ev.isGMMode());
    }

    @Subscribe
    public void handle(ScenarioResolvedEvent ev) {
        financialTransactionsScheduler.schedule();
    }

    @Subscribe
    public void handle(MissionNewEvent ev) {
        if (ev.getMission() instanceof Contract) {
            financialReportScheduler.schedule();
        }
    }

    @Subscribe
    public void handle(MissionChangedEvent ev) {
        if (ev.getMission() instanceof Contract) {
            financialReportScheduler.schedule();
        }
    }

    @Subscribe
    public void handle(AcquisitionEvent ev) {
        financialTransactionsScheduler.schedule();
    }

    @Subscribe
    public void handle(TransactionEvent ev) {
        financialTransactionsScheduler.schedule();
    }

    @Subscribe
    public void handle(LoanEvent ev) {
        financialTransactionsScheduler.schedule();
    }

    @Subscribe
    public void handle(UnitEvent ev) {
        financialReportScheduler.schedule();
    }

    @Subscribe
    public void handle(PartEvent ev) {
        financialReportScheduler.schedule();
    }

    @Subscribe
    public void handle(AssetEvent ev) {
        financialReportScheduler.schedule();
    }
}