package org.multibit.hd.ui.views; import com.google.common.collect.Lists; import com.google.common.collect.Range; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import net.miginfocom.swing.MigLayout; import org.multibit.commons.concurrent.SafeExecutors; import org.multibit.hd.core.dto.CoreMessageKey; import org.multibit.hd.core.managers.WalletManager; import org.multibit.hd.core.services.CoreServices; import org.multibit.hd.ui.MultiBitUI; import org.multibit.hd.ui.events.view.ProgressChangedEvent; import org.multibit.hd.ui.events.view.SystemStatusChangedEvent; import org.multibit.hd.ui.languages.Languages; import org.multibit.hd.ui.languages.MessageKey; import org.multibit.hd.ui.views.components.AccessibilityDecorator; import org.multibit.hd.ui.views.components.Labels; import org.multibit.hd.ui.views.components.Panels; import org.multibit.hd.ui.views.fonts.AwesomeDecorator; import org.multibit.hd.ui.views.fonts.AwesomeIcon; 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.*; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * <p>View to provide the following to application:</p> * <ul> * <li>Provision of components and layout for the footer display</li> * </ul> * * @since 0.0.1 */ public class FooterView extends AbstractView { private static final Logger log = LoggerFactory.getLogger(FooterView.class); private final JPanel contentPanel; private final JProgressBar progressBar; private final JLabel statusLabel; private final JLabel statusIcon; private final ListeningScheduledExecutorService scheduledExecutorService = SafeExecutors.newScheduledThreadPool(3, "hide-progress"); private final List<Future> hideProgressFutures = Lists.newArrayList(); public FooterView() { super(); contentPanel = Panels.newPanel( new MigLayout( Panels.migLayout("insets 7")+",hidemode 2", // hidemode 2 gives 0 size and gap if invisible "[][][][][][]", "[]" )); contentPanel.setMinimumSize(new Dimension(MultiBitUI.WIZARD_MIN_WIDTH, MultiBitUI.FOOTER_MINIMUM_HEIGHT)); // Apply the theme contentPanel.setBackground(Themes.currentTheme.footerPanelBackground()); contentPanel.setOpaque(true); progressBar = new JProgressBar(); progressBar.setMinimum(0); progressBar.setMaximum(100); progressBar.setForeground(Themes.currentTheme.infoAlertBackground()); progressBar.setEnabled(false); progressBar.setOpaque(false); progressBar.setVisible(false); // Hardware wallet icon JLabel hardwareWalletIcon = Labels.newBlankLabel(); AwesomeDecorator.bindIcon(AwesomeIcon.SHIELD, hardwareWalletIcon, false, MultiBitUI.SMALL_ICON_SIZE); AccessibilityDecorator.apply(hardwareWalletIcon, MessageKey.SELECT_HARDWARE_WALLET, MessageKey.SELECT_HARDWARE_WALLET_TOOLTIP); // The icon is only changed during a soft reset and the FooterView will be rebuilt each time hardwareWalletIcon.setVisible(WalletManager.INSTANCE.isUnlockedTrezorHardWallet()); // Spacer JLabel spacerLabel = Labels.newBlankLabel(); // Status icon // Label text and icon are different colours so must be separated statusLabel = Labels.newBlankLabel(); statusIcon = Labels.newBlankLabel(); AwesomeDecorator.bindIcon(AwesomeIcon.CIRCLE, statusIcon, false, MultiBitUI.SMALL_ICON_SIZE); // Start with no knowledge so assume the worst statusIcon.setForeground(Themes.currentTheme.dangerAlertBackground()); contentPanel.add(hardwareWalletIcon, "left"); contentPanel.add(progressBar, "shrink,left"); contentPanel.add(spacerLabel, "grow,push"); contentPanel.add(statusLabel, "shrink,right"); contentPanel.add(statusIcon, "right"); } /** * @return The content panel for this View */ public JPanel getContentPanel() { return contentPanel; } /** * <p>Handles the representation of a system status change</p> * * @param event The system status change event */ @Subscribe public void onSystemStatusChangeEvent(final SystemStatusChangedEvent event) { if (statusLabel == null) { return; } SwingUtilities.invokeLater( new Runnable() { @Override public void run() { statusLabel.setText(event.getLocalisedMessage()); statusIcon.setToolTipText(event.getLocalisedMessage()); switch (event.getSeverity()) { case RED: statusIcon.setForeground(Themes.currentTheme.statusRed()); break; case AMBER: statusIcon.setForeground(Themes.currentTheme.statusAmber()); break; case GREEN: statusIcon.setForeground(Themes.currentTheme.statusGreen()); // Show the peer count directly if synced String peerCountMessage = Languages.safeText(CoreMessageKey.PEER_COUNT, CoreServices.getOrCreateBitcoinNetworkService().getNumberOfConnectedPeers()); statusLabel.setText(peerCountMessage); statusIcon.setToolTipText(peerCountMessage); break; case EMPTY: // The event did not specify a RAG status // If there are Peers available and the status is currently RED // then move it to amber (have network connection, sync not yet started) if (statusIcon.getForeground().equals(Themes.currentTheme.statusRed())) { if (CoreServices.getOrCreateBitcoinNetworkService().getNumberOfConnectedPeers() > 0) { statusIcon.setForeground(Themes.currentTheme.statusAmber()); } } break; default: // Unknown status throw new IllegalStateException("Unknown event severity " + event.getSeverity()); } } }); } /** * <p>Handles the representation of a progress change</p> * * @param event The progress change event */ @Subscribe public void onProgressChangedEvent(final ProgressChangedEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { progressBar.setEnabled(true); // Provide some ranges to allow different colouring // If you get a compilation error here you need to be using guava 16.0.1 or above ! Range<Integer> amber = Range.closed(0, 99); Range<Integer> green = Range.greaterThan(99); if (amber.contains(event.getPercent())) { // Make the progress bar amber NimbusDecorator.applyThemeColor(Themes.currentTheme.statusAmber(), progressBar); progressBar.setValue(event.getPercent()); progressBar.setVisible(true); return; } if (green.contains(event.getPercent())) { // Cancel all existing hide operations cancelPendingHideProgressFutures(); // Make the progress bar green NimbusDecorator.applyThemeColor(Themes.currentTheme.statusGreen(), progressBar); progressBar.setValue(100); progressBar.setVisible(true); // Schedule the new hide hideProgressFutures.add(scheduleHideProgressBar()); } } }); } /** * Cancel all existing pending futures */ private void cancelPendingHideProgressFutures() { for (Future future : hideProgressFutures) { future.cancel(true); } hideProgressFutures.clear(); } /** * @return A one-shot scheduled future for hiding the progress bar after a predetermined delay */ private ScheduledFuture<?> scheduleHideProgressBar() { return scheduledExecutorService.schedule( new Runnable() { @Override public void run() { // Ensure we execute the update on the Swing thread SwingUtilities.invokeLater( new Runnable() { @Override public void run() { log.trace("Hiding progress bar"); progressBar.setVisible(false); } }); } }, 4, TimeUnit.SECONDS); } }