/*
*
* Copyright 2014 http://Bither.net
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package net.bither.viewsystem.base;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import net.bither.Bither;
import net.bither.BitherUI;
import net.bither.core.CoreMessageKey;
import net.bither.fonts.AwesomeDecorator;
import net.bither.fonts.AwesomeIcon;
import net.bither.languages.Languages;
import net.bither.languages.MessageKey;
import net.bither.viewsystem.components.ImageDecorator;
import net.bither.viewsystem.panels.BackgroundPanel;
import net.bither.viewsystem.panels.LightBoxPanel;
import net.bither.viewsystem.panels.RoundedPanel;
import net.bither.viewsystem.themes.Themes;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionListener;
/**
* <p>Factory to provide the following to views:</p>
* <ul>
* <li>Creation of panels</li>
* </ul>
*
* @since 0.0.1
*/
public class Panels {
private static final Logger log = LoggerFactory.getLogger(Panels.class);
/**
* A global reference to the application frame
*/
// public static JFrame applicationFrame;
private static Optional<LightBoxPanel> lightBoxPanel = Optional.absent();
private static Optional<LightBoxPanel> lightBoxPopoverPanel = Optional.absent();
/**
* <p>A default MiG layout constraint with:</p>
* <ul>
* <li>Zero insets</li>
* <li>Fills all available space (X and Y)</li>
* <li>Handles left-to-right and right-to-left presentation automatically</li>
* </ul>
*
* @return A default MiG layout constraint that fills all X and Y with RTL appended
*/
public static String migXYLayout() {
return migLayout("fill,insets 0");
}
/**
* <p>A default MiG layout constraint with:</p>
* <ul>
* <li>Zero insets</li>
* <li>Fills all available space (X only)</li>
* <li>Handles left-to-right and right-to-left presentation automatically</li>
* </ul>
*
* @return A default MiG layout constraint that fills all X with RTL appended suitable for screens
*/
public static String migXLayout() {
return migLayout("fillx,insets 0");
}
/**
* <p>A non-standard MiG layout constraint with:</p>
* <ul>
* <li>Optional "fill", "insets", "hidemode" etc</li>
* <li>Handles left-to-right and right-to-left presentation automatically</li>
* </ul>
*
* @param layout Any of the usual MiG layout constraints except RTL (e.g. "fillx,insets 1 2 3 4")
* @return The MiG layout constraint with RTL handling appended
*/
public static String migLayout(String layout) {
return layout + (Languages.isLeftToRight() ? "" : ",rtl");
}
/**
* <p>A default MiG layout constraint with:</p>
* <ul>
* <li>Detail screen insets</li>
* <li>Fills all available space (X and Y)</li>
* <li>Handles left-to-right and right-to-left presentation automatically</li>
* </ul>
*
* @return A MiG layout constraint that fills all X and Y with RTL appended suitable for detail views
*/
public static String migXYDetailLayout() {
return migLayout("fill,insets 10 5 5 5");
}
/**
* <p>A default MiG layout constraint with:</p>
* <ul>
* <li>Popover screen insets</li>
* <li>Fills all available space (X only)</li>
* <li>Handles left-to-right and right-to-left presentation automatically</li>
* </ul>
*
* @return A MiG layout constraint that fills all X with RTL appended suitable for popovers
*/
public static String migXPopoverLayout() {
return migLayout("fill,insets 10 10 10 10");
}
/**
* @return A simple theme-aware panel with a single cell MigLayout that fills all X and Y
*/
public static JPanel newPanel() {
return Panels.newPanel(
new MigLayout(
migXYLayout(), // Layout
"[]", // Columns
"[]" // Rows
));
}
/**
* @param layout The layout manager for the panel (typically MigLayout)
* @return A simple theme-aware detail panel with the given layout
*/
public static JPanel newPanel(LayoutManager2 layout) {
JPanel panel = new JPanel(layout);
// Theme
panel.setBackground(Themes.currentTheme.detailPanelBackground());
// Force transparency
panel.setOpaque(false);
// Ensure LTR and RTL is detected by the layout
panel.applyComponentOrientation(Languages.currentComponentOrientation());
return panel;
}
/**
* @return A simple panel with rounded corners and a single column MigLayout
*/
public static JPanel newRoundedPanel() {
return newRoundedPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
}
/**
* @param layout The MiGLayout to use
* @return A simple panel with rounded corners
*/
public static JPanel newRoundedPanel(LayoutManager2 layout) {
JPanel panel = new RoundedPanel(layout);
// Theme
panel.setBackground(Themes.currentTheme.detailPanelBackground());
panel.setForeground(Themes.currentTheme.fadedText());
return panel;
}
/**
* @param icon The Awesome icon to use as the basis of the image for consistent LaF
* @return A theme-aware panel with rounded corners and a single cell MigLayout
*/
public static BackgroundPanel newDetailBackgroundPanel(AwesomeIcon icon) {
// Create an image from the AwesomeIcon
Image image = ImageDecorator.toImageIcon(
AwesomeDecorator.createIcon(
icon,
Themes.currentTheme.fadedText(),
BitherUI.HUGE_ICON_SIZE
)).getImage();
BackgroundPanel panel = new BackgroundPanel(image, BackgroundPanel.ACTUAL);
panel.setLayout(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
panel.setAlpha(BitherUI.DETAIL_PANEL_BACKGROUND_ALPHA);
panel.setPaint(Themes.currentTheme.detailPanelBackground());
panel.setBackground(Themes.currentTheme.detailPanelBackground());
return panel;
}
/**
* <p>Show a light box</p>
*
* @param panel The panel to act as the focus of the light box
*/
public synchronized static void showLightBox(JPanel panel) {
Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "LightBox requires the EDT");
Preconditions.checkState(!lightBoxPanel.isPresent(), "Light box should never be called twice");
// Prevent focus
allowFocus(Bither.getMainFrame(), false);
// Add the light box panel
lightBoxPanel = Optional.of(new LightBoxPanel(panel, JLayeredPane.MODAL_LAYER));
}
/**
* <p>Hides the currently showing light box panel (and any popover)</p>
*/
public synchronized static void hideLightBoxIfPresent() {
Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "LightBoxPopover requires the EDT");
hideLightBoxPopoverIfPresent();
if (lightBoxPanel.isPresent()) {
lightBoxPanel.get().close();
}
lightBoxPanel = Optional.absent();
// Finally allow focus
allowFocus(Bither.getMainFrame(), true);
}
public synchronized static boolean lightBoxPanelIsShow() {
return lightBoxPanel.isPresent();
}
public synchronized static boolean lightBoxPopoverPanelIsShow() {
return lightBoxPopoverPanel.isPresent();
}
/**
* <p>Show a light box pop over</p>
*
* @param panel The panel to act as the focus of the popover
*/
public synchronized static void showLightBoxPopover(JPanel panel) {
Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "LightBoxPopover requires the EDT");
Preconditions.checkState(lightBoxPanel.isPresent(), "LightBoxPopover should not be called unless a light box is showing");
Preconditions.checkState(!lightBoxPopoverPanel.isPresent(), "LightBoxPopover should never be called twice");
lightBoxPopoverPanel = Optional.of(new LightBoxPanel(panel, JLayeredPane.DRAG_LAYER));
}
/**
* <p>Hides the currently showing light box popover panel</p>
*/
public synchronized static void hideLightBoxPopoverIfPresent() {
Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "LightBoxPopover requires the EDT");
if (lightBoxPopoverPanel.isPresent()) {
// A popover is not part of a handover so gets closed completely
lightBoxPopoverPanel.get().close();
}
lightBoxPopoverPanel = Optional.absent();
}
/**
* <p>An "exit selector" panel confirms an exit or switch operation</p>
*
* @param listener The action listener
* @param exitCommand The exit command name
* @param switchCommand The switch command name
* @return A new "exit selector" panel
*/
public static JPanel newExitSelector(
ActionListener listener,
String exitCommand,
String switchCommand
) {
JPanel panel = Panels.newPanel();
JRadioButton radio1 = RadioButtons.newRadioButton(listener, MessageKey.EXIT_WALLET);
radio1.setSelected(true);
radio1.setActionCommand(exitCommand);
JRadioButton radio2 = RadioButtons.newRadioButton(listener, MessageKey.SWITCH_WALLET);
radio2.setActionCommand(switchCommand);
// Wallet selection is mutually exclusive
ButtonGroup group = new ButtonGroup();
group.add(radio1);
group.add(radio2);
// Add to the panel
panel.add(radio1, "wrap");
panel.add(radio2, "wrap");
return panel;
}
/**
* <p>A "licence selector" panel provides a means of ensuring the user agrees with the licence</p>
*
* @param listener The action listener
* @param agreeCommand The agree command name
* @param disagreeCommand The disagree command name
* @return A new "licence selector" panel
*/
public static JPanel newLicenceSelector(
ActionListener listener,
String agreeCommand,
String disagreeCommand
) {
JPanel panel = Panels.newPanel();
JRadioButton radio1 = RadioButtons.newRadioButton(listener, MessageKey.ACCEPT_LICENCE);
radio1.setActionCommand(agreeCommand);
JRadioButton radio2 = RadioButtons.newRadioButton(listener, MessageKey.REJECT_LICENCE);
radio2.setSelected(true);
radio2.setActionCommand(disagreeCommand);
// Wallet selection is mutually exclusive
ButtonGroup group = new ButtonGroup();
group.add(radio1);
group.add(radio2);
// Add to the panel
panel.add(radio1, "wrap");
panel.add(radio2, "wrap");
return panel;
}
/**
* <p>A "wallet selector" panel provides a means of choosing how a wallet is to be created/accessed</p>
*
* @param listener The action listener
* @param createCommand The create command name
* @param existingWalletCommand The existing wallet command name
* @param restorePasswordCommand The restore credentials command name
* @param restoreWalletCommand The restore wallet command name
* @return A new "wallet selector" panel
*/
public static JPanel newWalletSelector(
ActionListener listener,
String createCommand,
String existingWalletCommand,
String restorePasswordCommand,
String restoreWalletCommand
) {
JPanel panel = Panels.newPanel();
JRadioButton radio1 = RadioButtons.newRadioButton(listener, MessageKey.CREATE_WALLET);
radio1.setSelected(true);
radio1.setActionCommand(createCommand);
JRadioButton radio2 = RadioButtons.newRadioButton(listener, MessageKey.USE_EXISTING_WALLET);
radio2.setActionCommand(existingWalletCommand);
JRadioButton radio3 = RadioButtons.newRadioButton(listener, MessageKey.RESTORE_PASSWORD);
radio3.setActionCommand(restorePasswordCommand);
JRadioButton radio4 = RadioButtons.newRadioButton(listener, MessageKey.RESTORE_WALLET);
radio4.setActionCommand(restoreWalletCommand);
// Check for existing wallets
// if (WalletManager.getWalletSummaries(false).isEmpty()) {
// radio2.setEnabled(false);
// radio2.setForeground(UIManager.getColor("RadioButton.disabledText"));
// }
// Wallet selection is mutually exclusive
ButtonGroup group = new ButtonGroup();
group.add(radio1);
group.add(radio2);
group.add(radio3);
group.add(radio4);
// Add to the panel
panel.add(radio1, "wrap");
panel.add(radio2, "wrap");
panel.add(radio3, "wrap");
panel.add(radio4, "wrap");
return panel;
}
/**
* <p>A "Trezor select PIN" panel provides a means of choosing how a device PIN is to be changed/removed</p>
*
* @param listener The action listener
* @param changeCommand The change PIN command name
* @param removeCommand The remove PIN command name
* @return A new "wallet selector" panel
*/
public static JPanel newChangePinSelector(
ActionListener listener,
String changeCommand,
String removeCommand
) {
JPanel panel = Panels.newPanel();
JRadioButton radio1 = RadioButtons.newRadioButton(listener, MessageKey.CHANGE_PIN_OPTION);
radio1.setSelected(true);
radio1.setActionCommand(changeCommand);
JRadioButton radio2 = RadioButtons.newRadioButton(listener, MessageKey.REMOVE_PIN_OPTION);
radio2.setActionCommand(removeCommand);
// Wallet selection is mutually exclusive
ButtonGroup group = new ButtonGroup();
group.add(radio1);
group.add(radio2);
// Add to the panel
panel.add(radio1, "wrap");
panel.add(radio2, "wrap");
return panel;
}
/**
* <p>A "confirm seed phrase" panel displays the instructions to enter the seed phrase from a piece of paper</p>
*
* @return A new "confirm seed phrase" panel
*/
public static JPanel newConfirmSeedPhrase() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXYLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newConfirmSeedPhraseNote(), "grow,push");
return panel;
}
/**
* <p>A "seed phrase warning" panel displays the instructions to write down the seed phrase on a piece of paper</p>
*
* @return A new "seed phrase warning" panel
*/
public static JPanel newSeedPhraseWarning() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newCreateWalletPreparationNote(), "grow,push");
return panel;
}
/**
* <p>A "debugger warning" panel displays instructions to the user about a debugger being attached</p>
*
* @return A new "debugger warning" panel
*/
public static JPanel newDebuggerWarning() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Ensure it is accessible
AccessibilityDecorator.apply(panel, CoreMessageKey.DEBUGGER_ATTACHED);
//PanelDecorator.applyDangerFadedTheme(panel);
// Add to the panel
panel.add(Labels.newDebuggerWarningNote(), "grow,push");
return panel;
}
/**
* <p>A "language change" panel displays instructions to the user about a language change</p>
*
* @return A new "language change" panel
*/
public static JPanel newLanguageChange() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// PanelDecorator.applySuccessFadedTheme(panel);
// Add to the panel
panel.add(Labels.newLanguageChangeNote(), "grow,push");
return panel;
}
/**
* <p>A "restore from backup" panel displays the instructions to restore from a backup folder</p>
*
* @return A new "restore from backup" panel
*/
public static JPanel newRestoreFromBackup() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newRestoreFromBackupNote(), "grow,push");
return panel;
}
/**
* <p>A "restore from seed phrase" panel displays the instructions to restore from a seed phrase</p>
*
* @return A new "restore from seed phrase" panel
*/
public static JPanel newRestoreFromSeedPhrase() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newRestoreFromSeedPhraseNote(), "grow,push");
return panel;
}
/**
* <p>A "restore from timestamp" panel displays the instructions to restore from a seed phrase and timestamp</p>
*
* @return A new "restore from timestamp" panel
*/
public static JPanel newRestoreFromTimestamp() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newRestoreFromTimestampNote(), "grow,push");
return panel;
}
/**
* <p>A "select backup directory" panel displays the instructions to choose an appropriate backup directory</p>
*
* @return A new "select backup directory" panel
*/
public static JPanel newSelectBackupDirectory() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newSelectBackupLocationNote(), "grow,push");
return panel;
}
/**
* <p>A "select export payments directory" panel displays the instructions to choose an appropriate export payments directory</p>
*
* @return A new "select export payments directory" panel
*/
public static JPanel newSelectExportPaymentsDirectory() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(),
"[]", // Columns
"[]" // Rows
));
// Add to the panel
panel.add(Labels.newSelectExportPaymentsLocationNote(), "grow,push");
return panel;
}
/**
* New vertical dashed separator
*/
public static JPanel newVerticalDashedSeparator() {
JPanel panel = new JPanel();
panel.setMaximumSize(new Dimension(1, 10000));
panel.setBorder(BorderFactory.createDashedBorder(Themes.currentTheme.headerPanelBackground(), 5, 5));
return panel;
}
/**
* <p>Invalidate a panel so that Swing will later redraw it properly with layout changes (normally as a result of a locale change)</p>
*
* @param panel The panel to invalidate
*/
public static void invalidate(JPanel panel) {
// Added new content so validate/repaint
panel.validate();
panel.repaint();
}
/**
* <p>Recursive method to enable or disable the focus on all components in the given container</p>
* <p>Filters components that cannot have focus by design (e.g. JLabel)</p>
*
* @param component The component
* @param allowFocus True if the components should be able to gain focus
*/
private static void allowFocus(final Component component, final boolean allowFocus) {
// Limit the focus change to those components that could grab it
if (component instanceof AbstractButton) {
component.setFocusable(allowFocus);
}
if (component instanceof JComboBox) {
component.setFocusable(allowFocus);
}
if (component instanceof JTree) {
component.setFocusable(allowFocus);
}
if (component instanceof JTextComponent) {
component.setFocusable(allowFocus);
}
if (component instanceof JTable) {
component.setFocusable(allowFocus);
}
// Recursive search
if (component instanceof Container) {
for (Component child : ((Container) component).getComponents()) {
allowFocus(child, allowFocus);
}
}
}
}