/** * Copyright (c) 2012, Lindsay Bradford and other Contributors. * All rights reserved. * * This program and the accompanying materials are made available * under the terms of the BSD 3-Clause licence which accompanies * this distribution, and is available at * http://opensource.org/licenses/BSD-3-Clause */ package blacksmyth.personalfinancier; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Event; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.Observable; import java.util.Observer; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import blacksmyth.general.FontIconProvider; import blacksmyth.general.BlacksmythSwingUtilities; import blacksmyth.general.file.IFileHandler; import blacksmyth.personalfinancier.control.FileHandlerBuilder; import blacksmyth.personalfinancier.control.budget.command.ResetBudgetItemsCommand; import blacksmyth.personalfinancier.model.AccountModel; import blacksmyth.personalfinancier.model.CashFlowFrequency; import blacksmyth.personalfinancier.model.budget.BudgetFileContent; import blacksmyth.personalfinancier.model.budget.BudgetModel; import blacksmyth.personalfinancier.view.IApplicationMessageView; import blacksmyth.personalfinancier.view.IPersonalFinancierComponentView; import blacksmyth.personalfinancier.view.IPersonalFinancierView; import blacksmyth.personalfinancier.view.JUndoListeningButton; import blacksmyth.personalfinancier.view.PersonalFinancierView; import blacksmyth.personalfinancier.view.ViewPreferences; import blacksmyth.personalfinancier.view.WidgetFactory; import blacksmyth.personalfinancier.view.budget.BudgetCashFlowSummaryTable; import blacksmyth.personalfinancier.view.budget.BudgetCategorySummaryTable; import blacksmyth.personalfinancier.view.budget.CashFlowPieChart; import blacksmyth.personalfinancier.view.budget.CategoryPieChart; import blacksmyth.personalfinancier.view.budget.ExpenseItemTable; import blacksmyth.personalfinancier.view.budget.IncomeItemTable; class BudgetUIFactory { private static BudgetModel budgetModel; private static JPanel expenseItemPanel; private static JToolBar expenseItemToolbar; private static ExpenseItemTable expenseItemTable; private static JPanel incomeItemPanel; private static JToolBar incomeItemToolbar; private static IncomeItemTable incomeItemTable; private static IFileHandler<BudgetFileContent> budgetFileController; private static Action LoadBudgetAction; private static Action SaveBudgetAction; private static Action SaveAsBudgetAction; private static Action BudgetItemInsertAction; private static Action BudgetItemDeleteAction; private static Action BudgetItemUpAction; private static Action BudgetItemDownAction; public static IPersonalFinancierComponentView createBudgetComponent(PersonalFinancierView view) { budgetModel = new BudgetModel( new AccountModel() ); budgetFileController = FileHandlerBuilder.buildBudgetHandler( view, budgetModel ); budgetFileController.addObserver( view.getMessageViewer() ); createSharedBudgetTableActions(); BudgetComponent newComponent = new BudgetComponent( JSplitPane.VERTICAL_SPLIT, createBudgetItemPanel(view), createBudgetSummaryPanel() ); newComponent.putClientProperty( "AppMessage", "Manage your personal budget in this tab." ); newComponent.putClientProperty( "TabName", "Budget" ); newComponent.setOneTouchExpandable(true); newComponent.setResizeWeight(0.5); return newComponent; } private static void createSharedBudgetTableActions() { LoadBudgetAction = new AbstractAction("Open...") { public void actionPerformed(ActionEvent e) { budgetModel.getUndoManager().discardAllEdits(); budgetFileController.load(); } }; SaveBudgetAction = new AbstractAction("Save") { public void actionPerformed(ActionEvent e) { budgetFileController.save(); } }; SaveAsBudgetAction = new AbstractAction("Save As...") { public void actionPerformed(ActionEvent e) { budgetFileController.saveAs(); } }; BudgetItemInsertAction = new AbstractAction("Insert Budget Item") { public void actionPerformed(ActionEvent e) { if (BlacksmythSwingUtilities.mouseIsOverComponent(expenseItemPanel)) { expenseItemTable.addBudgetItem(); return; } if (BlacksmythSwingUtilities.mouseIsOverComponent(incomeItemPanel)) { incomeItemTable.addBudgetItem(); } } }; BudgetItemDeleteAction = new AbstractAction("Delete Budget Item") { public void actionPerformed(ActionEvent e) { if (BlacksmythSwingUtilities.mouseIsOverComponent(expenseItemPanel)) { expenseItemTable.removeBudgetItems(); return; } if (BlacksmythSwingUtilities.mouseIsOverComponent(incomeItemPanel)) { incomeItemTable.removeBudgetItems(); } } }; BudgetItemUpAction = new AbstractAction("Move Budget Item Up") { public void actionPerformed(ActionEvent e) { if (BlacksmythSwingUtilities.mouseIsOverComponent(expenseItemPanel)) { expenseItemTable.moveSelectedItemUp(); return; } if (BlacksmythSwingUtilities.mouseIsOverComponent(expenseItemPanel)) { incomeItemTable.moveSelectedItemUp(); } } }; BudgetItemDownAction = new AbstractAction("Move Budget Item Down") { public void actionPerformed(ActionEvent e) { if (BlacksmythSwingUtilities.mouseIsOverComponent(expenseItemPanel)) { expenseItemTable.moveSelectedItemDown(); return; } if (BlacksmythSwingUtilities.mouseIsOverComponent(expenseItemPanel)) { incomeItemTable.moveSelectedItemDown(); } } }; } private static JButton createSaveButton(IApplicationMessageView messageBar) { JButton button = new JButton(SaveBudgetAction); button.setForeground(Color.GREEN.darker()); BlacksmythSwingUtilities.bindKeyStrokeToAction( button, KeyStroke.getKeyStroke( KeyEvent.VK_S, Event.CTRL_MASK ), SaveBudgetAction ); button.setMnemonic(KeyEvent.VK_S); FontIconProvider.getInstance().setGlyphAsText( button, FontIconProvider.fa_save ); button.setToolTipText(" Save the current budget "); button.putClientProperty("AppMessage", "Save your personal budget..."); messageBar.bindViewComponent(button); return button; } private static JButton createSaveAsButton(IApplicationMessageView messageBar) { JButton button = new JButton( SaveAsBudgetAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( button, KeyStroke.getKeyStroke( KeyEvent.VK_A, Event.CTRL_MASK ), SaveAsBudgetAction ); button.setMnemonic(KeyEvent.VK_A); FontIconProvider.getInstance().setGlyphAsText( button, FontIconProvider.fa_save ); button.setForeground(Color.GREEN); button.setToolTipText(" Save the budget to another filename... "); button.putClientProperty("AppMessage", "Save your personal budget as..."); messageBar.bindViewComponent(button); return button; } private static JButton createLoadButton(IApplicationMessageView messageBar) { JButton button = new JButton( LoadBudgetAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( button, KeyStroke.getKeyStroke( KeyEvent.VK_L, Event.CTRL_MASK ), LoadBudgetAction ); button.setMnemonic(KeyEvent.VK_L); FontIconProvider.getInstance().setGlyphAsText( button, FontIconProvider.fa_folder_open_o ); button.setForeground(Color.GREEN.darker()); button.setToolTipText(" Load a budget "); button.putClientProperty("AppMessage", "Load your personal budget..."); messageBar.bindViewComponent(button); return button; } private static JComponent createBudgetItemPanel(IPersonalFinancierView view) { JPanel panel = new JPanel(new BorderLayout()); incomeItemTable = new IncomeItemTable(budgetModel); incomeItemToolbar = createIncomeItemToolbar(); expenseItemTable = new ExpenseItemTable(budgetModel); expenseItemToolbar = createExpenseItemToolbar(); panel.add( createBudgetItemToolbar(view), BorderLayout.PAGE_START ); panel.add( createBudgetItemsTablePanel(), BorderLayout.CENTER ); return panel; } private static JSplitPane createBudgetItemsTablePanel() { JSplitPane splitPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT, createIncomeItemsTablePanel(), createExpenseItemsTablePanel() ); splitPane.setOneTouchExpandable(true); splitPane.setResizeWeight(0.5); return splitPane; } private static JPanel createExpenseItemsTablePanel() { expenseItemPanel = new JPanel(new BorderLayout()); expenseItemPanel.setBorder( new CompoundBorder( WidgetFactory.createColoredTitledBorder( " Expense Items ", Color.GRAY.brighter() ), new EmptyBorder(0,3,5,4) ) ); expenseItemPanel.add( expenseItemToolbar, BorderLayout.NORTH ); JScrollPane tableScrollPane = new JScrollPane(expenseItemTable); expenseItemPanel.add( tableScrollPane, BorderLayout.CENTER ); BlacksmythSwingUtilities.bindKeyStrokeToAction( expenseItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_INSERT, 0 ), BudgetItemInsertAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( expenseItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ), BudgetItemDeleteAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( expenseItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, Event.ALT_MASK ), BudgetItemDownAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( expenseItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_UP, Event.ALT_MASK ), BudgetItemUpAction ); return expenseItemPanel; } private static JPanel createIncomeItemsTablePanel() { incomeItemPanel = new JPanel(new BorderLayout()); incomeItemPanel.setBorder( new CompoundBorder( WidgetFactory.createColoredTitledBorder( " Income Items ", Color.GRAY.brighter() ), new EmptyBorder(0,3,5,4) ) ); incomeItemPanel.add( incomeItemToolbar, BorderLayout.NORTH ); incomeItemPanel.add( new JScrollPane(incomeItemTable), BorderLayout.CENTER ); BlacksmythSwingUtilities.bindKeyStrokeToAction( incomeItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_INSERT, 0 ), BudgetItemInsertAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( incomeItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ), BudgetItemDeleteAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( incomeItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, Event.ALT_MASK ), BudgetItemDownAction ); BlacksmythSwingUtilities.bindKeyStrokeToAction( incomeItemPanel, KeyStroke.getKeyStroke( KeyEvent.VK_UP, Event.ALT_MASK ), BudgetItemUpAction ); return incomeItemPanel; } @SuppressWarnings("serial") private static JToolBar createExpenseItemToolbar() { JToolBar toolbar = new JToolBar(); toolbar.setFloatable(false); toolbar.add( createAddExpenseItemButton() ); toolbar.add( createDeleteExpenseItemsButton() ); toolbar.addSeparator(); toolbar.add( createMoveExpenseItemDownButton() ); toolbar.add( createMoveExpenseItemUpButton() ); return toolbar; } private static JButton createAddExpenseItemButton() { final JButton newButton = new JButton(); newButton.setAction(BudgetItemInsertAction); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_plus ); newButton.setToolTipText( "Add a new expense item" ); newButton.setForeground( Color.GREEN.brighter() ); return newButton; } private static JButton createDeleteExpenseItemsButton() { JButton newButton = WidgetFactory.createMultiSelectedtRowEnabledButton(expenseItemTable); newButton.setAction(BudgetItemDeleteAction); newButton.setEnabled(false); newButton.setForeground( Color.RED.brighter() ); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_minus ); newButton.setToolTipText( "Delete selected expense item(s)" ); return newButton; } private static JButton createMoveExpenseItemDownButton() { JButton newButton = WidgetFactory.createOneSelectedtRowEnabledButton(expenseItemTable); newButton.setAction(BudgetItemDownAction); newButton.setEnabled(false); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_arrow_down ); newButton.setForeground(Color.GREEN); newButton.setToolTipText( " Move item down in list " ); return newButton; } private static JButton createMoveExpenseItemUpButton() { JButton newButton = WidgetFactory.createOneSelectedtRowEnabledButton(expenseItemTable); newButton.setAction(BudgetItemUpAction); newButton.setEnabled(false); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_arrow_up ); newButton.setToolTipText( " Move item up in list " ); newButton.setForeground(Color.GREEN); return newButton; } @SuppressWarnings("serial") private static JToolBar createIncomeItemToolbar() { JToolBar toolbar = new JToolBar(); toolbar.setFloatable(false); toolbar.add( createAddIncomeItemButton() ); toolbar.add( createRemoveIncomeItemsButton() ); toolbar.addSeparator(); toolbar.add( createMoveIncomeItemDownButton() ); toolbar.add( createMoveIncomeItemUpButton() ); return toolbar; } private static JButton createAddIncomeItemButton() { final JButton newButton = new JButton(); newButton.setAction(BudgetItemInsertAction); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_plus ); newButton.setToolTipText( "Add a new income item" ); newButton.setForeground( Color.GREEN.brighter() ); return newButton; } private static JButton createRemoveIncomeItemsButton() { JButton newButton = WidgetFactory.createMultiSelectedtRowEnabledButton(incomeItemTable); newButton.setAction(BudgetItemDeleteAction); newButton.setEnabled(false); newButton.setForeground( Color.RED.brighter() ); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_minus ); newButton.setToolTipText( "Delete selected income item(s)" ); return newButton; } private static JButton createMoveIncomeItemDownButton() { JButton newButton = WidgetFactory.createOneSelectedtRowEnabledButton(incomeItemTable); newButton.setAction(BudgetItemDownAction); newButton.setEnabled(false); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_arrow_down ); newButton.setToolTipText( " Move item down in list " ); newButton.setForeground(Color.GREEN); return newButton; } private static JButton createMoveIncomeItemUpButton() { JButton newButton = WidgetFactory.createOneSelectedtRowEnabledButton(incomeItemTable); newButton.setAction(BudgetItemUpAction); newButton.setEnabled(false); FontIconProvider.getInstance().setGlyphAsText( newButton, FontIconProvider.fa_arrow_up ); newButton.setToolTipText( " Move item up in list " ); newButton.setForeground(Color.GREEN); return newButton; } @SuppressWarnings("serial") private static JToolBar createBudgetItemToolbar(IPersonalFinancierView view) { JToolBar toolbar = new JToolBar(); toolbar.add( createLoadButton( view.getMessageViewer() ) ); toolbar.add( createSaveButton( view.getMessageViewer() ) ); toolbar.add( createSaveAsButton( view.getMessageViewer() ) ); toolbar.addSeparator(); toolbar.add( createUndoButton() ); toolbar.add( createRedoButton() ); toolbar.addSeparator(); toolbar.add( createBudgetItemButtonsVisibileButton() ); toolbar.add( createDerivedColumnsVisibileButton() ); toolbar.addSeparator(); toolbar.add( createResetItemsButton() ); toolbar.addSeparator(); return toolbar; } private static JToggleButton createBudgetItemButtonsVisibileButton() { final JToggleButton button = new JToggleButton() { { // begin: instance initializer this.setSelected( ViewPreferences.getInstance().getBudgetItemButtonsVisibility() ); this.setForeground(Color.GREEN); changeSelectionRendering(); this.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent arg0) { ViewPreferences.getInstance().toggleBudgetItemButtonsVisibility(); changeSelectionRendering(); } } ); } // end: instance initializer private void changeSelectionRendering() { if (this.isSelected()) { FontIconProvider.getInstance().setGlyphAsText( this, FontIconProvider.fa_check_square_o ); this.setToolTipText( " Hide budget item table buttons. " ); } else { FontIconProvider.getInstance().setGlyphAsText( this, FontIconProvider.fa_square_o ); this.setToolTipText( " Show budget item table buttons. " ); } } }; final Observer budgetButtonsController = new Observer() { { this.updateViewers(); } private void updateViewers() { boolean visibleBudgetItemButtonsState = ViewPreferences.getInstance().getBudgetItemButtonsVisibility(); button.setSelected(visibleBudgetItemButtonsState); if (incomeItemToolbar.isVisible() != visibleBudgetItemButtonsState) { boolean visibleFlag = !incomeItemToolbar.isVisible(); incomeItemToolbar.setVisible(visibleFlag); expenseItemToolbar.setVisible(visibleFlag); } } public void update(Observable arg0, Object arg1) { updateViewers(); } }; ViewPreferences.getInstance().addObserver( budgetButtonsController ); return button; } private static JToggleButton createDerivedColumnsVisibileButton() { final JToggleButton button = new JToggleButton() { { // begin: instance initializer this.setSelected( ViewPreferences.getInstance().getDerivedBudgetColumsVisibility() ); this.setForeground(Color.GREEN); changeSelectionRendering(); this.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent arg0) { ViewPreferences.getInstance().toggleDerivedBudgetColumsVisibility(); changeSelectionRendering(); } } ); } // end: instance initializer private void changeSelectionRendering() { if (this.isSelected()) { FontIconProvider.getInstance().setGlyphAsText( this, FontIconProvider.fa_toggle_right ); this.setToolTipText( " Hide derived amount columns. " ); } else { FontIconProvider.getInstance().setGlyphAsText( this, FontIconProvider.fa_toggle_left ); this.setToolTipText( " Show derived amount columns. " ); } } }; final Observer derivedColumnController = new Observer() { { this.updateViewers(); } private void updateViewers() { boolean visibleBudgetColumnsState = ViewPreferences.getInstance().getDerivedBudgetColumsVisibility(); button.setSelected(visibleBudgetColumnsState); if (incomeItemTable.isShowDerivedColumns() != visibleBudgetColumnsState) { incomeItemTable.toggleDerivedColumnView(); expenseItemTable.toggleDerivedColumnView(); } } public void update(Observable arg0, Object arg1) { updateViewers(); } }; ViewPreferences.getInstance().addObserver( derivedColumnController ); return button; } private static JButton createResetItemsButton() { AbstractAction resetItemsAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { budgetModel.getUndoManager().addEdit( ResetBudgetItemsCommand.doCmd( budgetModel ) ); } }; JButton resetItemsButton = new JButton(resetItemsAction); resetItemsButton.setForeground( Color.GRAY.brighter() ); FontIconProvider.getInstance().setGlyphAsText( resetItemsButton, FontIconProvider.fa_trash_o ); resetItemsButton.setToolTipText( " Clear all income and expense items" ); resetItemsButton.setForeground(Color.RED); BlacksmythSwingUtilities.bindKeyStrokeToAction( resetItemsButton, KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ), resetItemsAction ); return resetItemsButton; } private static JButton createUndoButton() { AbstractAction undoAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { if (budgetModel.getUndoManager().canUndo()) { budgetModel.getUndoManager().undo(); } } }; JUndoListeningButton button = new JUndoListeningButton(undoAction) { protected void handleCantUndoState() { this.setEnabled(false); } protected void handleCanUndoState() { this.setEnabled(true); } }; budgetModel.getUndoManager().addObserver(button); FontIconProvider.getInstance().setGlyphAsText( button, FontIconProvider.fa_undo ); BlacksmythSwingUtilities.bindKeyStrokeToAction( button, KeyStroke.getKeyStroke( KeyEvent.VK_Z, Event.CTRL_MASK ), undoAction ); button.setForeground(Color.GREEN); button.setToolTipText(" Undo "); return button; } private static JButton createRedoButton() { AbstractAction redoAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { if (budgetModel.getUndoManager().canRedo()) { budgetModel.getUndoManager().redo(); } } }; JUndoListeningButton button = new JUndoListeningButton(redoAction) { protected void handleCantRedoState() { this.setEnabled(false); } protected void handleCanRedoState() { this.setEnabled(true); } }; budgetModel.getUndoManager().addObserver(button); FontIconProvider.getInstance().setGlyphAsText( button, FontIconProvider.fa_repeat ); BlacksmythSwingUtilities.bindKeyStrokeToAction( button, KeyStroke.getKeyStroke( KeyEvent.VK_Y, Event.CTRL_MASK ), redoAction ); button.setForeground(Color.GREEN); button.setToolTipText(" Redo "); return button; } private static JComponent createBudgetSummaryPanel() { JPanel panel = new JPanel(new GridLayout(1,2)); panel.setBorder( new CompoundBorder( WidgetFactory.createColoredTitledBorder( " " + CashFlowFrequency.Fortnightly.toString() + " Summary ", Color.GRAY.brighter() ), new EmptyBorder(0,3,5,4) ) ); panel.add( WidgetFactory.createGraphTablePane( new CashFlowPieChart(budgetModel), createAccountSummaryTable() ) ); panel.add( WidgetFactory.createGraphTablePane( createCategoryPieChart(), createCategorySummaryTable() ) ); return panel; } private static JComponent createCategorySummaryTable() { return new JScrollPane( new BudgetCategorySummaryTable( budgetModel ) ); } private static JComponent createAccountSummaryTable() { return new JScrollPane( new BudgetCashFlowSummaryTable( budgetModel ) ); } private static JComponent createCategoryPieChart() { return new CategoryPieChart(budgetModel); } } final class BudgetComponent extends JSplitPane implements IPersonalFinancierComponentView { public BudgetComponent(int verticalSplit, JComponent budgetItemPanel, JComponent budgetSummaryPanel) { super(verticalSplit, budgetItemPanel, budgetSummaryPanel); } }