package org.jabref.gui.push;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import org.jabref.Globals;
import org.jabref.gui.IconTheme;
import org.jabref.gui.JabRefFrame;
import org.jabref.gui.actions.MnemonicAwareAction;
import org.jabref.gui.keyboard.KeyBinding;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.OS;
import org.jabref.preferences.JabRefPreferences;
import com.jgoodies.forms.builder.ButtonBarBuilder;
/**
* Customized UI component for pushing to external applications. Has a selection popup menu to change the selected
* external application. This class implements the ActionListener interface. When actionPerformed() is invoked, the
* currently selected PushToApplication is activated. The actionPerformed() method can be called with a null argument.
*/
public class PushToApplicationButton implements ActionListener {
private static final Icon ARROW_ICON = IconTheme.JabRefIcon.DOWN.getSmallIcon();
private final JabRefFrame frame;
private final List<PushToApplication> pushActions;
private JPanel comp;
private JButton pushButton;
private PushToApplication toApp;
private JPopupMenu popup;
private final Map<PushToApplication, PushToApplicationAction> actions = new HashMap<>();
private final Dimension buttonDim = new Dimension(23, 23);
private final MenuAction mAction = new MenuAction();
private final JPopupMenu optPopup = new JPopupMenu();
private final JMenuItem settings = new JMenuItem(Localization.lang("Settings"));
public PushToApplicationButton(JabRefFrame frame, List<PushToApplication> pushActions) {
this.frame = frame;
this.pushActions = pushActions;
init();
}
private void init() {
comp = new JPanel();
comp.setLayout(new BorderLayout());
JButton menuButton = new JButton(PushToApplicationButton.ARROW_ICON);
menuButton.setMargin(new Insets(0, 0, 0, 0));
menuButton.setPreferredSize(
new Dimension(menuButton.getIcon().getIconWidth(), menuButton.getIcon().getIconHeight()));
menuButton.addActionListener(e -> {
if (popup == null) {
buildPopupMenu();
}
popup.show(comp, 0, menuButton.getHeight());
});
menuButton.setToolTipText(Localization.lang("Select external application"));
pushButton = new JButton();
if (OS.OS_X) {
menuButton.putClientProperty("JButton.buttonType", "toolbar");
pushButton.putClientProperty("JButton.buttonType", "toolbar");
}
// Set the last used external application
String appSelected = Globals.prefs.get(JabRefPreferences.PUSH_TO_APPLICATION);
for (PushToApplication application : pushActions) {
if (application.getApplicationName().equals(appSelected)) {
toApp = application;
break;
}
}
if (toApp == null) {
// Nothing found, pick first
toApp = pushActions.get(0);
}
setSelected();
pushButton.addActionListener(this);
pushButton.addMouseListener(new PushButtonMouseListener());
pushButton.setOpaque(false);
menuButton.setOpaque(false);
comp.setOpaque(false);
comp.add(pushButton, BorderLayout.CENTER);
comp.add(menuButton, BorderLayout.EAST);
comp.setMaximumSize(comp.getPreferredSize());
optPopup.add(settings);
settings.addActionListener(event -> {
JPanel options = toApp.getSettingsPanel();
if (options != null) {
PushToApplicationButton.showSettingsDialog(frame, toApp, options);
}
});
buildPopupMenu();
}
/**
* Create a selection menu for the available "Push" options.
*/
private void buildPopupMenu() {
popup = new JPopupMenu();
for (PushToApplication application : pushActions) {
JMenuItem item = new JMenuItem(application.getApplicationName(), application.getIcon());
item.setToolTipText(application.getTooltip());
item.addActionListener(new PopupItemActionListener(application));
popup.add(item);
}
}
/**
* Update the PushButton to default to the given application.
*
* @param i The List index of the application to default to.
*/
private void setSelected(PushToApplication newApplication) {
toApp = newApplication;
setSelected();
}
private void setSelected() {
pushButton.setIcon(toApp.getIcon());
pushButton.setToolTipText(toApp.getTooltip());
pushButton.setPreferredSize(buttonDim);
// Store the last used application
Globals.prefs.put(JabRefPreferences.PUSH_TO_APPLICATION, toApp.getApplicationName());
mAction.setTitle(toApp.getApplicationName());
mAction.setIcon(toApp.getIcon());
}
/**
* Get the toolbar component for the push button.
*
* @return The component.
*/
public Component getComponent() {
return comp;
}
public Action getMenuAction() {
return mAction;
}
@Override
public void actionPerformed(ActionEvent e) {
// Lazy initialization of the push action:
PushToApplicationAction action = actions.get(toApp);
if (action == null) {
action = new PushToApplicationAction(frame, toApp);
actions.put(toApp, action);
}
action.actionPerformed(new ActionEvent(toApp, 0, "push"));
}
private static class BooleanHolder {
public boolean value;
public BooleanHolder(boolean value) {
this.value = value;
}
}
public static void showSettingsDialog(JFrame parent, PushToApplication toApp, JPanel options) {
final BooleanHolder okPressed = new BooleanHolder(false);
final JDialog diag = new JDialog(parent, Localization.lang("Settings"), true);
options.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
diag.getContentPane().add(options, BorderLayout.CENTER);
ButtonBarBuilder bb = new ButtonBarBuilder();
JButton ok = new JButton(Localization.lang("OK"));
JButton cancel = new JButton(Localization.lang("Cancel"));
bb.addGlue();
bb.addButton(ok);
bb.addButton(cancel);
bb.addGlue();
bb.getPanel().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
diag.getContentPane().add(bb.getPanel(), BorderLayout.SOUTH);
ok.addActionListener(e -> {
okPressed.value = true;
diag.dispose();
});
cancel.addActionListener(e -> diag.dispose());
// Key bindings:
ActionMap am = bb.getPanel().getActionMap();
InputMap im = bb.getPanel().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close");
am.put("close", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
diag.dispose();
}
});
diag.pack();
diag.setLocationRelativeTo(parent);
// Show the dialog:
diag.setVisible(true);
// If the user pressed Ok, ask the PushToApplication implementation
// to store its settings:
if (okPressed.value) {
toApp.storeSettings();
}
}
class PopupItemActionListener implements ActionListener {
private final PushToApplication application;
public PopupItemActionListener(PushToApplication application) {
this.application = application;
}
@Override
public void actionPerformed(ActionEvent e) {
// Change the selection:
setSelected(application);
// Invoke the selected operation (is that expected behaviour?):
//PushToApplicationButton.this.actionPerformed(null);
// It makes sense to transfer focus to the push button after the
// menu closes:
pushButton.requestFocus();
}
}
class MenuAction extends MnemonicAwareAction {
public MenuAction() {
putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.PUSH_TO_APPLICATION));
}
public void setTitle(String appName) {
putValue(Action.NAME, Localization.menuTitle("Push entries to external application (%0)", appName));
}
@Override
public void actionPerformed(ActionEvent e) {
PushToApplicationButton.this.actionPerformed(null);
}
public void setIcon(Icon icon) {
putValue(Action.SMALL_ICON, icon);
}
}
class PushButtonMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent event) {
if (event.isPopupTrigger()) {
processPopupTrigger(event);
}
}
@Override
public void mouseClicked(MouseEvent event) {
if (event.isPopupTrigger()) {
processPopupTrigger(event);
}
}
@Override
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger()) {
processPopupTrigger(event);
}
}
private void processPopupTrigger(MouseEvent e) {
// We only want to show the popup if a settings panel exists for the selected
// item:
if (toApp.getSettingsPanel() != null) {
optPopup.show(pushButton, e.getX(), e.getY());
}
}
}
}