jgnash.ui.budget.BudgetOverviewPanel.java Source code

Java tutorial

Introduction

Here is the source code for jgnash.ui.budget.BudgetOverviewPanel.java

Source

/*
 * jGnash, a personal finance application
 * Copyright (C) 2001-2012 Craig Cavanaugh
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jgnash.ui.budget;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.factories.CC;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import jgnash.Main;
import jgnash.engine.AccountGroup;
import jgnash.engine.Engine;
import jgnash.engine.EngineFactory;
import jgnash.engine.Transaction;
import jgnash.util.DateUtils;
import jgnash.util.Resource;

/**
 * Overview panel for the displayed period
 *
 * @author Craig Cavanaugh
 *
 */
class BudgetOverviewPanel extends JPanel implements ChangeListener {

    private JSpinner yearSpinner;

    private SpinnerNumberModel model;

    private BudgetPanel budgetPanel;

    private JPanel sparklinePanel;

    /**
     * Used to limit the number of sparkline updates that occur
     */
    private final ThreadPoolExecutor updateIconExecutor;

    private static final Logger logger = Logger.getLogger(BudgetPanel.class.getName());

    public BudgetOverviewPanel(final BudgetPanel budgetPanel) {
        if (Main.enableVerboseLogging()) {
            logger.setLevel(Level.ALL);
        } else {
            logger.setLevel(Level.OFF);
        }

        this.budgetPanel = budgetPanel;

        layoutMainPanel();

        setupSpinnerModel();

        /* At least 2 updates need to be allowed.  The update in process and any potential updates requested
         * that occur when an update is already in process.  Limited to 1 thread
         *
         * Excess execution requests will be silently discarded 
         */
        updateIconExecutor = new ThreadPoolExecutor(0, 1, 0, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(2));
        updateIconExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
    }

    private void initComponents() {

        sparklinePanel = new JPanel();

        FormLayout layout = new FormLayout("d", "d");
        sparklinePanel.setLayout(layout);

        model = new SpinnerNumberModel();

        model.setValue(DateUtils.getCurrentYear());
        model.setStepSize(1);

        yearSpinner = new JSpinner(model);

        JSpinner.NumberEditor editor = new JSpinner.NumberEditor(yearSpinner, "####");
        yearSpinner.setEditor(editor);
        yearSpinner.setEnabled(false);
    }

    private void layoutMainPanel() {
        Resource rb = Resource.get();

        initComponents();

        FormLayout layout = new FormLayout("2dlu, right:d, $lcgap, max(40dlu;d), $lcgap, d", "min");

        DefaultFormBuilder builder = new DefaultFormBuilder(layout, this);
        setLayout(layout);

        setBorder(Borders.EMPTY_BORDER);

        builder.add(new JLabel(rb.getString("Label.Year")), CC.xy(2, 1));
        builder.add(yearSpinner, CC.xy(4, 1));
        builder.add(sparklinePanel, CC.xy(6, 1));
    }

    /**
     * Run the model setup in the background because it can be expensive
     */
    private void setupSpinnerModel() {

        Runnable r = new Runnable() {

            @Override
            public void run() {
                Engine engine = EngineFactory.getEngine(EngineFactory.DEFAULT);

                int minYear = DateUtils.getCurrentYear();
                int maxYear = DateUtils.getCurrentYear() + 1;

                for (Transaction transaction : engine.getTransactions()) {

                    int year = DateUtils.getYear(transaction.getDate());

                    minYear = Math.min(minYear, year);
                    maxYear = Math.max(maxYear, year);
                }

                final int _minYear = minYear;
                final int _maxYear = maxYear;

                // changes to the spinner must be pushed to the EDT
                EventQueue.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        if (model != null) {
                            model.setMinimum(_minYear);
                            model.setMaximum(_maxYear);
                        }

                        yearSpinner.setEnabled(true);

                        // register the listener after the model has been updated, otherwise unnecessary updates will occur
                        yearSpinner.addChangeListener(BudgetOverviewPanel.this);
                    }
                });
            }
        };

        budgetPanel.submit(r);
    }

    @Override
    public void stateChanged(final ChangeEvent e) {
        logger.entering(BudgetOverviewPanel.class.getName(), "stateChanged");

        if (e.getSource() == yearSpinner) {
            int year = (Integer) model.getNumber();

            budgetPanel.setBudgetYear(year);
        }

        logger.exiting(BudgetOverviewPanel.class.getName(), "stateChanged");
    }

    void updateSparkLines() {

        Runnable r = new Runnable() {

            @Override
            public void run() {

                final List<AccountGroup> groups = new ArrayList<>(budgetPanel.getAccountGroups());

                final List<Icon> icons = new ArrayList<>();

                for (AccountGroup group : groups) {
                    icons.add(budgetPanel.getSparkLineIcon(group));
                }

                EventQueue.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        FormLayout layout = (FormLayout) sparklinePanel.getLayout();

                        // remove all components and columns
                        sparklinePanel.removeAll();

                        int columnCount = layout.getColumnCount();

                        for (int i = columnCount; i >= 1; i--) {
                            layout.removeColumn(i);
                        }

                        // create components and columns and add
                        if (icons.size() > 0) {
                            layout.appendColumn(ColumnSpec.decode("d"));
                            sparklinePanel.add(getLabel(groups.get(0), icons.get(0)), CC.xy(1, 1));

                            for (int i = 1; i < icons.size(); i++) {
                                layout.appendColumn(ColumnSpec.decode("2dlu"));
                                layout.appendColumn(ColumnSpec.decode("d"));

                                sparklinePanel.add(getLabel(groups.get(i), icons.get(i)), CC.xy(i * 2 + 1, 1));
                            }
                        }

                        // force the complete panel to update so icons show
                        invalidate();
                        validate();
                    }
                });
            }
        };

        updateIconExecutor.execute(r);
    }

    private static JLabel getLabel(final AccountGroup group, final Icon icon) {
        JLabel label = new JLabel(group.toString(), icon, JLabel.LEFT);
        label.setHorizontalTextPosition(JLabel.LEFT);

        label.setFont(label.getFont().deriveFont(label.getFont().getSize2D() - 2f));
        return label;
    }
}