package org.multibit.hd.ui.views; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.eventbus.Subscribe; import net.miginfocom.swing.MigLayout; import org.bitcoinj.core.Coin; import org.multibit.hd.core.config.Configurations; import org.multibit.hd.core.dto.FiatPayment; import org.multibit.hd.core.services.WalletService; import org.multibit.hd.ui.MultiBitUI; import org.multibit.hd.ui.events.controller.ControllerEvents; import org.multibit.hd.ui.events.view.AlertAddedEvent; import org.multibit.hd.ui.events.view.AlertRemovedEvent; import org.multibit.hd.ui.events.view.BalanceChangedEvent; import org.multibit.hd.ui.events.view.ViewChangedEvent; import org.multibit.hd.ui.languages.Languages; import org.multibit.hd.ui.models.AlertModel; import org.multibit.hd.ui.views.components.*; import org.multibit.hd.ui.views.components.display_amount.DisplayAmountModel; import org.multibit.hd.ui.views.components.display_amount.DisplayAmountStyle; import org.multibit.hd.ui.views.components.display_amount.DisplayAmountView; import org.multibit.hd.ui.views.components.panels.PanelDecorator; import org.multibit.hd.ui.views.themes.NimbusDecorator; import org.multibit.hd.ui.views.themes.Themes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.event.ActionEvent; import java.math.BigDecimal; /** * <p>View to provide the following to application:</p> * <ul> * <li>Provision of components and layout for the balance display</li> * </ul> * * @since 0.0.1 */ public class HeaderView extends AbstractView { private static final Logger log = LoggerFactory.getLogger(HeaderView.class); private JLabel plusUncomfirmedLabel; private final ModelAndView<DisplayAmountModel, DisplayAmountView> availableBalanceDisplayMaV; private final ModelAndView<DisplayAmountModel, DisplayAmountView> unconfirmedDisplayMaV; private JLabel alertMessageLabel; private JLabel alertRemainingLabel; private JButton alertButton; private JButton closeButton; private final JPanel contentPanel; private final JPanel alertPanel; public HeaderView() { super(); contentPanel = Panels.newPanel( new MigLayout( Panels.migLayout("fillx,insets 10 10 5 10,hidemode 3"), // Layout insets ensure border is tight to sidebar "[][]10[]", // Columns "[][shrink]" // Rows )); // Create the alert panel alertPanel = Panels.newPanel( new MigLayout( Panels.migXLayout(), "[][][][]", // Columns "[]" // Rows )); // Start off in hiding alertPanel.setVisible(false); // Apply the theme contentPanel.setBackground(Themes.currentTheme.headerPanelBackground()); contentPanel.setOpaque(true); // Create the balance display and unconfirmed amount not displaying it initially availableBalanceDisplayMaV = Components.newDisplayAmountMaV(DisplayAmountStyle.HEADER, true, "header.balance"); unconfirmedDisplayMaV = Components.newDisplayAmountMaV(DisplayAmountStyle.HEADER_SMALL, true, "header.unconfirmed"); plusUncomfirmedLabel = Labels.newPlusUnconfirmed(); availableBalanceDisplayMaV.getView().setVisible(false); unconfirmedDisplayMaV.getView().setVisible(false); plusUncomfirmedLabel.setVisible(false); plusUncomfirmedLabel.setBorder(BorderFactory.createEmptyBorder(0,0,8,0)); // Provide a fixed height to avoid an annoying "slide down" during unlock contentPanel.add(availableBalanceDisplayMaV.getView().newComponentPanel(), "growx,push,hmin 50, aligny bottom"); contentPanel.add(plusUncomfirmedLabel, "shrink, aligny bottom"); JPanel unconfirmedViewPanel = unconfirmedDisplayMaV.getView().newComponentPanel(); unconfirmedViewPanel.setBorder(BorderFactory.createEmptyBorder(0,0,4,0)); contentPanel.add(unconfirmedViewPanel, "growx,push, aligny bottom, wrap"); contentPanel.add(alertPanel, "growx,aligny top,span 3, push"); populateAlertPanel(); } /** * @return The content panel for this View */ public JPanel getContentPanel() { return contentPanel; } /** * <p>Handles the representation of the balance based on the current configuration</p> * * @param event The balance change event */ @Subscribe public void onBalanceChangedEvent(final BalanceChangedEvent event) { // Handle the update availableBalanceDisplayMaV.getModel().setLocalAmount(event.getLocalBalance()); availableBalanceDisplayMaV.getModel().setCoinAmount(event.getCoinBalance()); availableBalanceDisplayMaV.getModel().setRateProvider(Optional.<String>absent()); availableBalanceDisplayMaV.getModel().setLocalAmountVisible(event.getLocalBalance() != null); // Do not set the visibility on available balance here, use the ViewChangedEvent availableBalanceDisplayMaV.getView().updateView(Configurations.currentConfiguration); // If the unconfirmed is different from the estimated then show the unconfirmed if (event.getCoinBalance().compareTo(event.getCoinWithUnconfirmedBalance()) != 0) { Coin unconfirmedCoin = event.getCoinWithUnconfirmedBalance().subtract(event.getCoinBalance()); FiatPayment unconfirmedFiat = WalletService.calculateFiatPaymentEquivalent(unconfirmedCoin); log.trace("Unconfirmed bitcoin. Coin:{}, Fiat:{}", unconfirmedCoin, unconfirmedFiat); boolean hasFiat = unconfirmedFiat.getAmount().isPresent(); unconfirmedDisplayMaV.getModel().setLocalAmountVisible(hasFiat); if (hasFiat) { unconfirmedDisplayMaV.getModel().setLocalAmount(unconfirmedFiat.getAmount().get()); } unconfirmedDisplayMaV.getModel().setCoinAmount(unconfirmedCoin); unconfirmedDisplayMaV.getView().updateViewFromModel(); // As long as the main header is visible, show the unconfirmed if (availableBalanceDisplayMaV.getView().isVisible()) { boolean showHeader = Configurations.currentConfiguration.getAppearance().isShowBalance(); plusUncomfirmedLabel.setVisible(showHeader); unconfirmedDisplayMaV.getView().setVisible(showHeader); } } else { // Unconfirmed and estimated is the same - switch off the unconfirmed unconfirmedDisplayMaV.getModel().setCoinAmount(Coin.ZERO); unconfirmedDisplayMaV.getModel().setLocalAmount(BigDecimal.ZERO); unconfirmedDisplayMaV.getView().updateViewFromModel(); plusUncomfirmedLabel.setVisible(false); unconfirmedDisplayMaV.getView().setVisible(false); } unconfirmedDisplayMaV.getView().updateView(Configurations.currentConfiguration); } /** * <p>Handles the presentation of a new alert</p> * * @param event The show alert event */ @Subscribe public void onAlertAddedEvent(final AlertAddedEvent event) { Preconditions.checkNotNull(event, "'event' must be present"); final AlertModel alertModel = event.getAlertModel(); Preconditions.checkNotNull(alertModel, "'alertModel' must be present"); log.debug("Received 'alert added event': {}", event.getAlertModel()); // Update the text according to the model alertMessageLabel.setText(alertModel.getLocalisedMessage()); alertRemainingLabel.setText(alertModel.getRemainingText()); if (alertModel.getButton().isPresent()) { JButton button = alertModel.getButton().get(); alertButton.setAction(button.getAction()); alertButton.setText(button.getText()); alertButton.setIcon(button.getIcon()); alertButton.setName(button.getName()); alertButton.setToolTipText(button.getToolTipText()); alertButton.setVisible(true); } else { alertButton.setVisible(false); } // Don't play sounds here since this will be called each time an alert is dismissed switch (alertModel.getSeverity()) { case RED: PanelDecorator.applyDangerTheme(alertPanel); NimbusDecorator.applyThemeColor(Themes.currentTheme.dangerAlertBackground(), alertButton); NimbusDecorator.applyThemeColor(Themes.currentTheme.dangerAlertBackground(), closeButton); break; case AMBER: PanelDecorator.applyWarningTheme(alertPanel); NimbusDecorator.applyThemeColor(Themes.currentTheme.warningAlertBackground(), alertButton); NimbusDecorator.applyThemeColor(Themes.currentTheme.warningAlertBackground(), closeButton); break; case GREEN: PanelDecorator.applySuccessTheme(alertPanel); NimbusDecorator.applyThemeColor(Themes.currentTheme.successAlertBackground(), alertButton); NimbusDecorator.applyThemeColor(Themes.currentTheme.successAlertBackground(), closeButton); break; case PINK: PanelDecorator.applyPendingTheme(alertPanel); NimbusDecorator.applyThemeColor(Themes.currentTheme.pendingAlertBackground(), alertButton); NimbusDecorator.applyThemeColor(Themes.currentTheme.pendingAlertBackground(), closeButton); break; default: throw new IllegalStateException("Unknown severity: " + alertModel.getSeverity().name()); } alertPanel.setVisible(true); } /** * <p>Remove any existing alert</p> * * @param event The remove alert event */ @Subscribe public void onAlertRemovedEvent(final AlertRemovedEvent event) { // Hide the alert panel and clear the label alertPanel.setVisible(false); alertMessageLabel.setText(""); } /** * <p>Called when the view should be should be changed</p> * * @param event The view changed event */ @Subscribe public void onViewChangedEvent(final ViewChangedEvent event) { if (event.getViewKey().equals(ViewKey.HEADER)) { log.trace("Saw a ViewChangedEvent {}", event); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { log.trace("Header now has visibility: {} ", event.isVisible()); availableBalanceDisplayMaV.getView().setVisible(event.isVisible()); if (alertMessageLabel.getText().length() != 0 && event.isVisible()) { alertPanel.setVisible(event.isVisible()); } availableBalanceDisplayMaV.getView().updateView(Configurations.currentConfiguration); if (unconfirmedDisplayMaV.getModel().getCoinAmount().compareTo(Coin.ZERO) != 0 && event.isVisible()) { unconfirmedDisplayMaV.getView().setVisible(true); plusUncomfirmedLabel.setVisible(true); } else { unconfirmedDisplayMaV.getView().setVisible(false); plusUncomfirmedLabel.setVisible(false); } } }); } } /** * <p>Populate the alert panel in preparation for any alerts</p> */ private void populateAlertPanel() { Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "Must be in the EDT. Check MainController."); alertPanel.removeAll(); alertMessageLabel = Labels.newBlankLabel(); alertMessageLabel.setName("alert_message_label"); alertRemainingLabel = Labels.newBlankLabel(); alertRemainingLabel.setName("alert_remaining_label"); // Placeholder button that gets overwritten alertButton = new JButton(); alertButton.setName("alert_button"); alertButton.setVisible(false); closeButton = Buttons.newPanelCloseButton(getCloseAlertAction()); // Determine how to add them back into the panel if (Languages.isLeftToRight()) { alertPanel.add(alertMessageLabel, "shrink,left," + MultiBitUI.ALERT_MESSAGE_MAX_WIDTH_MIG); alertPanel.add(alertRemainingLabel, "push,right"); alertPanel.add(alertButton, "push,right"); alertPanel.add(closeButton); } else { alertPanel.add(closeButton); alertPanel.add(alertButton, "shrink,left"); alertPanel.add(alertRemainingLabel, "shrink,left"); alertPanel.add(alertMessageLabel, "shrink,right," + MultiBitUI.ALERT_MESSAGE_MAX_WIDTH_MIG); } } /** * @return A new action for closing the alert panel */ private Action getCloseAlertAction() { return new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { ControllerEvents.fireRemoveAlertEvent(); } }; } }