package org.multibit.hd.ui.views; import com.google.common.base.Preconditions; import com.google.common.eventbus.Subscribe; import net.miginfocom.swing.MigLayout; import org.joda.time.DateTime; import org.multibit.commons.utils.Dates; import org.multibit.hd.core.config.Configurations; import org.multibit.hd.ui.events.controller.ShowScreenEvent; import org.multibit.hd.ui.events.view.ViewEvents; import org.multibit.hd.ui.languages.Languages; import org.multibit.hd.ui.languages.MessageKey; import org.multibit.hd.ui.views.components.*; import org.multibit.hd.ui.views.screens.Screen; import org.multibit.hd.ui.views.themes.NimbusDecorator; import org.multibit.hd.ui.views.themes.Themes; import org.multibit.hd.ui.views.wizards.Wizards; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; /** * <p>View to provide the following to application:</p> * <ul> * <li>Provision of components and layout for the sidebar display (LHS of split pane)</li> * </ul> * * @since 0.0.1 * * @TODO THe multi wallet support is not needed now so this class can be simplified */ public class SidebarView extends AbstractView { private static final Logger log = LoggerFactory.getLogger(SidebarView.class); private final JPanel contentPanel; private JTree sidebarTree; /** * When the last selection was made */ private DateTime lastSelectionDateTime = Dates.nowUtc(); /** * The detail screen that was selected (provide a sensible default) */ private Screen lastSelectedScreen = Screen.SEND_REQUEST; /** * The settings tree node */ private DefaultMutableTreeNode settingsNode; /** * The tools tree node */ private DefaultMutableTreeNode toolsNode; public SidebarView() { super(); // Insets for top, left MigLayout layout = new MigLayout( Panels.migLayout("fill, insets 6 10"), "[]", // Columns "[]" // Rows ); contentPanel = Panels.newPanel(layout); // Apply the sidebar theme contentPanel.setBackground(Themes.currentTheme.sidebarPanelBackground()); // Apply opacity contentPanel.setOpaque(true); contentPanel.add(createSidebarContent(), "grow,push"); } /** * @return The content panel for this View */ public JPanel getContentPanel() { return contentPanel; } /** * @param name The wallet summary name */ public void updateWalletTreeNode(final String name) { Preconditions.checkNotNull(name, "'name' must be present"); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // Always update the title Panels.getApplicationFrame().setTitle(Languages.safeText(MessageKey.MULTIBIT_HD_TITLE) + " - " + name); } }); } /** * @return The sidebar content */ private JScrollPane createSidebarContent() { final JScrollPane sidebarPane = new JScrollPane(); // Ensure we maintain the overall theme ScrollBarUIDecorator.apply(sidebarPane, true); sidebarTree = new JTree(createSidebarTreeNodes()); // Ensure it is accessible AccessibilityDecorator.apply(sidebarTree, MessageKey.SIDEBAR_TREE); sidebarTree.setShowsRootHandles(false); sidebarTree.setRootVisible(false); // Remove tree view selection NimbusDecorator.disableTreeViewSelection(sidebarTree); // Apply the theme sidebarTree.setBackground(Themes.currentTheme.sidebarPanelBackground()); sidebarTree.setCellRenderer(new ThemeAwareTreeCellRenderer()); sidebarTree.setVisibleRowCount(10); // Require 2 clicks to toggle to make UX smoother when simply selecting wallet // Collapsing should be a rare event in normal use sidebarTree.setToggleClickCount(2); // Ensure we always have the soft wallet open TreePath walletPath = sidebarTree.getPathForRow(0); sidebarTree.getSelectionModel().setSelectionPath(walletPath); sidebarTree.expandPath(walletPath); // Ensure we use the previous selection Screen startingScreen; try { startingScreen = Screen.valueOf(Configurations.currentConfiguration.getAppearance().getCurrentScreen()); } catch (IllegalArgumentException e) { // Unknown starting screen - possibly an old configuration // Default to same as configuration (safest option given network connectivity) startingScreen = Screen.SEND_REQUEST; } for (int row = 0; row < sidebarTree.getRowCount(); row++) { TreePath screenPath = sidebarTree.getPathForRow(row); if (screenPath != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) screenPath.getLastPathComponent(); SidebarNodeInfo nodeInfo = (SidebarNodeInfo) node.getUserObject(); Screen detailScreen = nodeInfo.getDetailScreen(); if (detailScreen.equals(startingScreen)) { sidebarTree.setSelectionRow(row); } } } // Get the tree cell renderer to handle the row height sidebarTree.setRowHeight(0); sidebarTree.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); sidebarTree.setFont(sidebarTree.getFont().deriveFont(16.0f)); sidebarTree.addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent e) { TreePath path = sidebarTree.getPathForLocation(e.getX(), e.getY()); if (path != null) { handleTreeSelection((DefaultMutableTreeNode) path.getLastPathComponent()); } } }); sidebarTree.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { TreePath path = sidebarTree.getSelectionPath(); if (path != null) { handleTreeSelection((DefaultMutableTreeNode) path.getLastPathComponent()); } } }); sidebarPane.setViewportView(sidebarTree); sidebarPane.setBorder(null); return sidebarPane; } private DefaultMutableTreeNode createSidebarTreeNodes() { DefaultMutableTreeNode root = TreeNodes.newSidebarTreeNode("", Screen.SEND_REQUEST); // Add nodes root.add(TreeNodes.newSidebarTreeNode(MessageKey.BUY_OR_SELL, Screen.BUY_SELL)); root.add(TreeNodes.newSidebarTreeNode(MessageKey.SEND_OR_REQUEST, Screen.SEND_REQUEST)); root.add(TreeNodes.newSidebarTreeNode(MessageKey.PAYMENTS, Screen.TRANSACTIONS)); root.add(TreeNodes.newSidebarTreeNode(MessageKey.CONTACTS, Screen.CONTACTS)); // Add application nodes root.add(TreeNodes.newSidebarTreeNode(MessageKey.HELP, Screen.HELP)); settingsNode = TreeNodes.newSidebarTreeNode(MessageKey.SETTINGS, Screen.SETTINGS); root.add(settingsNode); root.add(TreeNodes.newSidebarTreeNode(MessageKey.MANAGE_WALLET, Screen.MANAGE_WALLET)); toolsNode = TreeNodes.newSidebarTreeNode(MessageKey.TOOLS, Screen.TOOLS); root.add(toolsNode); root.add(TreeNodes.newSidebarTreeNode(MessageKey.EXIT_OR_SWITCH, Screen.EXIT)); return root; } /** * @param node The selected node */ private void handleTreeSelection(DefaultMutableTreeNode node) { SidebarNodeInfo nodeInfo = (SidebarNodeInfo) node.getUserObject(); Screen detailScreen = nodeInfo.getDetailScreen(); // Filter out multiple events for the same screen, but allow repeats to occur (such as the exit screen) boolean ignore = detailScreen.equals(lastSelectedScreen) && Dates.nowUtc().isBefore(lastSelectionDateTime.plusSeconds(1)); if (!ignore) { switch (detailScreen) { // Add special cases case BUY_SELL: Panels.showLightBox(Wizards.newBuySellWizard().getWizardScreenHolder()); break; case EXIT: Panels.showLightBox(Wizards.newExitWizard().getWizardScreenHolder()); break; default: Configurations.currentConfiguration.getAppearance().setCurrentScreen(nodeInfo.getDetailScreen().name()); ViewEvents.fireShowDetailScreenEvent(nodeInfo.getDetailScreen()); } } else { log.debug("Ignoring selection: '{}'", detailScreen); } lastSelectedScreen = detailScreen; lastSelectionDateTime = Dates.nowUtc(); } /** * Do everything to grab the focus without triggering a selection event * This is necessary to ensure keyboard navigation of the sidebar is retained after * a cancelled Exit operation */ public void requestFocus() { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { lastSelectionDateTime = Dates.nowUtc(); sidebarTree.setFocusable(true); sidebarTree.requestFocusInWindow(); } }); } @Subscribe public void onShowDetailScreen(final ShowScreenEvent event) { Preconditions.checkNotNull(event, "'event' must be present"); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Screen screen = event.getScreen(); if (sidebarTree != null) { // Check that the settings / preferences screen has just been selected if (Screen.SETTINGS.equals(screen)) { if (settingsNode != null) { // Select the settings node sidebarTree.setSelectionPath(new TreePath(settingsNode.getPath())); } } // Check that the tools screen has just been selected if (Screen.TOOLS.equals(screen)) { if (toolsNode != null) { // Select the tools node sidebarTree.setSelectionPath(new TreePath(toolsNode.getPath())); } } } } }); } }