blob: a291ab32e95099a5a1ea645e8d77b084d8328d4f [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.openapi.ui;
import com.intellij.CommonBundle;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.MnemonicHelper;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.help.HelpManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.StackingPopupDispatcher;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.JBColor;
import com.intellij.ui.UIBundle;
import com.intellij.ui.components.JBOptionButton;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.Alarm;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.AwtVisitor;
import com.intellij.util.ui.DialogUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.xml.util.XmlStringUtil;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* The standard base class for modal dialog boxes. The dialog wrapper could be used only on event dispatch thread.
* In case when the dialog must be created from other threads use
* {@link EventQueue#invokeLater(Runnable)} or {@link EventQueue#invokeAndWait(Runnable)}.
* <p/>
* See also http://confluence.jetbrains.net/display/IDEADEV/IntelliJ+IDEA+DialogWrapper.
*/
@SuppressWarnings({"SSBasedInspection", "MethodMayBeStatic", "UnusedDeclaration"})
public abstract class DialogWrapper {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.DialogWrapper");
public enum IdeModalityType {
IDE,
PROJECT,
MODELESS;
@NotNull
public Dialog.ModalityType toAwtModality() {
switch (this) {
case IDE:
return Dialog.ModalityType.APPLICATION_MODAL;
case PROJECT:
return Dialog.ModalityType.DOCUMENT_MODAL;
case MODELESS:
return Dialog.ModalityType.MODELESS;
}
throw new IllegalStateException(toString());
}
}
/**
* The default exit code for "OK" action.
*/
public static final int OK_EXIT_CODE = 0;
/**
* The default exit code for "Cancel" action.
*/
public static final int CANCEL_EXIT_CODE = 1;
/**
* The default exit code for "Close" action. Equal to cancel.
*/
public static final int CLOSE_EXIT_CODE = CANCEL_EXIT_CODE;
/**
* If you use your own custom exit codes you have to start them with
* this constant.
*/
public static final int NEXT_USER_EXIT_CODE = 2;
/**
* If your action returned by <code>createActions</code> method has non
* <code>null</code> value for this key, then the button that corresponds to the action will be the
* default button for the dialog. It's true if you don't change this behaviour
* of <code>createJButtonForAction(Action)</code> method.
*/
@NonNls public static final String DEFAULT_ACTION = "DefaultAction";
@NonNls public static final String FOCUSED_ACTION = "FocusedAction";
@NonNls private static final String NO_AUTORESIZE = "NoAutoResizeAndFit";
private static final KeyStroke SHOW_OPTION_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);
@NotNull
private final DialogWrapperPeer myPeer;
private int myExitCode = CANCEL_EXIT_CODE;
/**
* The shared instance of default border for dialog's content pane.
*/
public static final Border ourDefaultBorder = new EmptyBorder(UIUtil.PANEL_REGULAR_INSETS);
private float myHorizontalStretch = 1.0f;
private float myVerticalStretch = 1.0f;
/**
* Defines horizontal alignment of buttons.
*/
private int myButtonAlignment = SwingConstants.RIGHT;
private boolean myCrossClosesWindow = true;
private Insets myButtonMargins = new Insets(2, 16, 2, 16);
protected Action myOKAction;
protected Action myCancelAction;
protected Action myHelpAction;
private JButton[] myButtons;
private boolean myClosed = false;
protected boolean myPerformAction = false;
private Action myYesAction = null;
private Action myNoAction = null;
protected JCheckBox myCheckBoxDoNotShowDialog;
@Nullable
private DoNotAskOption myDoNotAsk;
protected JComponent myPreferredFocusedComponent;
private Computable<Point> myInitialLocationCallback;
protected final Disposable myDisposable = new Disposable() {
@Override
public String toString() {
return DialogWrapper.this.toString();
}
@Override
public void dispose() {
DialogWrapper.this.dispose();
}
};
private final List<JBOptionButton> myOptionsButtons = new ArrayList<JBOptionButton>();
private int myCurrentOptionsButtonIndex = -1;
private boolean myResizeInProgress = false;
private ComponentAdapter myResizeListener;
@NotNull
protected String getDoNotShowMessage() {
return CommonBundle.message("dialog.options.do.not.show");
}
public void setDoNotAskOption(@Nullable DoNotAskOption doNotAsk) {
myDoNotAsk = doNotAsk;
}
private ErrorText myErrorText;
private int myMaxErrorTextLength;
private final Alarm myErrorTextAlarm = new Alarm();
/**
* Creates modal <code>DialogWrapper</code>. The currently active window will be the dialog's parent.
*
* @param project parent window for the dialog will be calculated based on focused window for the
* specified <code>project</code>. This parameter can be <code>null</code>. In this case parent window
* will be suggested based on current focused window.
* @param canBeParent specifies whether the dialog can be parent for other windows. This parameter is used
* by <code>WindowManager</code>.
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
*/
protected DialogWrapper(@Nullable Project project, boolean canBeParent) {
this(project, canBeParent, IdeModalityType.IDE);
}
protected DialogWrapper(@Nullable Project project, boolean canBeParent, @NotNull IdeModalityType ideModalityType) {
myPeer = createPeer(project, canBeParent, ideModalityType);
final Window window = myPeer.getWindow();
if (window != null) {
myResizeListener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
if (!myResizeInProgress) {
myActualSize = myPeer.getSize();
if (myErrorText != null && myErrorText.isVisible()) {
myActualSize.height -= myErrorText.getHeight() + 10;
}
}
}
};
window.addComponentListener(myResizeListener);
}
createDefaultActions();
}
/**
* Creates modal <code>DialogWrapper</code> that can be parent for other windows.
* The currently active window will be the dialog's parent.
*
* @param project parent window for the dialog will be calculated based on focused window for the
* specified <code>project</code>. This parameter can be <code>null</code>. In this case parent window
* will be suggested based on current focused window.
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
* @see com.intellij.openapi.ui.DialogWrapper#DialogWrapper(com.intellij.openapi.project.Project, boolean)
*/
protected DialogWrapper(@Nullable Project project) {
this(project, true);
}
/**
* Creates modal <code>DialogWrapper</code>. The currently active window will be the dialog's parent.
*
* @param canBeParent specifies whether the dialog can be parent for other windows. This parameter is used
* by <code>WindowManager</code>.
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
*/
protected DialogWrapper(boolean canBeParent) {
this((Project)null, canBeParent);
}
/**
* Typically, we should set a parent explicitly. Use WindowManager#suggestParentWindow
* method to find out the best parent for your dialog. Exceptions are cases
* when we do not have a project to figure out which window
* is more suitable as an owner for the dialog.
* <p/>
* Instead, use {@link DialogWrapper#DialogWrapper(com.intellij.openapi.project.Project, boolean, boolean)}
*/
@Deprecated
protected DialogWrapper(boolean canBeParent, boolean applicationModalIfPossible) {
this(null, canBeParent, applicationModalIfPossible);
}
protected DialogWrapper(Project project, boolean canBeParent, boolean applicationModalIfPossible) {
ensureEventDispatchThread();
if (ApplicationManager.getApplication() != null) {
myPeer = createPeer(
project != null ? WindowManager.getInstance().suggestParentWindow(project) : WindowManager.getInstance().findVisibleFrame()
, canBeParent, applicationModalIfPossible);
}
else {
myPeer = createPeer(null, canBeParent, applicationModalIfPossible);
}
createDefaultActions();
}
/**
* @param parent parent component which is used to calculate heavy weight window ancestor.
* <code>parent</code> cannot be <code>null</code> and must be showing.
* @param canBeParent can be parent
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
*/
protected DialogWrapper(@NotNull Component parent, boolean canBeParent) {
ensureEventDispatchThread();
myPeer = createPeer(parent, canBeParent);
createDefaultActions();
}
//validation
private final Alarm myValidationAlarm = new Alarm(getValidationThreadToUse(), myDisposable);
@NotNull
protected Alarm.ThreadToUse getValidationThreadToUse() {
return Alarm.ThreadToUse.SWING_THREAD;
}
private int myValidationDelay = 300;
private boolean myDisposed = false;
private boolean myValidationStarted = false;
private final ErrorPainter myErrorPainter = new ErrorPainter();
private JComponent myErrorPane;
private boolean myErrorPainterInstalled = false;
/**
* Allows to postpone first start of validation
*
* @return <code>false</code> if start validation in <code>init()</code> method
*/
protected boolean postponeValidation() {
return true;
}
/**
* Validates user input and returns <code>null</code> if everything is fine
* or validation description with component where problem has been found.
*
* @return <code>null</code> if everything is OK or validation descriptor
*/
@Nullable
protected ValidationInfo doValidate() {
return null;
}
public void setValidationDelay(int delay) {
myValidationDelay = delay;
}
private void reportProblem(@NotNull final ValidationInfo info) {
installErrorPainter();
myErrorPainter.setValidationInfo(info);
if (!myErrorText.isTextSet(info.message)) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
setErrorText(info.message);
myPeer.getRootPane().getGlassPane().repaint();
getOKAction().setEnabled(false);
}
});
}
}
private void installErrorPainter() {
if (myErrorPainterInstalled) return;
myErrorPainterInstalled = true;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
IdeGlassPaneUtil.installPainter(myErrorPane, myErrorPainter, myDisposable);
}
});
}
private void clearProblems() {
myErrorPainter.setValidationInfo(null);
if (!myErrorText.isTextSet(null)) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
setErrorText(null);
myPeer.getRootPane().getGlassPane().repaint();
getOKAction().setEnabled(true);
}
});
}
}
protected void createDefaultActions() {
myOKAction = new OkAction();
myCancelAction = new CancelAction();
myHelpAction = new HelpAction();
}
public void setUndecorated(boolean undecorated) {
myPeer.setUndecorated(undecorated);
}
public final void addMouseListener(@NotNull MouseListener listener) {
myPeer.addMouseListener(listener);
}
public final void addMouseListener(@NotNull MouseMotionListener listener) {
myPeer.addMouseListener(listener);
}
public final void addKeyListener(@NotNull KeyListener listener) {
myPeer.addKeyListener(listener);
}
/**
* Closes and disposes the dialog and sets the specified exit code.
*
* @param exitCode exit code
* @param isOk is OK
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
*/
public final void close(int exitCode, boolean isOk) {
ensureEventDispatchThread();
if (myClosed) return;
myClosed = true;
myExitCode = exitCode;
Window window = getWindow();
if (window != null && myResizeListener != null) {
window.removeComponentListener(myResizeListener);
myResizeListener = null;
}
if (isOk) {
processDoNotAskOnOk(exitCode);
}
else {
processDoNotAskOnCancel();
}
Disposer.dispose(myDisposable);
}
public final void close(int exitCode) {
close(exitCode, exitCode != CANCEL_EXIT_CODE);
}
/**
* Creates border for dialog's content pane. By default content
* pane has has empty border with <code>(8,12,8,12)</code> insets. Subclasses can
* return <code>null</code> for no border.
*
* @return content pane border
*/
@Nullable
protected Border createContentPaneBorder() {
return ourDefaultBorder;
}
/**
* Creates panel located at the south of the content pane. By default that
* panel contains dialog's buttons. This default implementation uses <code>createActions()</code>
* and <code>createJButtonForAction(Action)</code> methods to construct the panel.
*
* @return south panel
*/
@Nullable
protected JComponent createSouthPanel() {
Action[] actions = filter(createActions());
Action[] leftSideActions = createLeftSideActions();
List<JButton> buttons = new ArrayList<JButton>();
boolean hasHelpToMoveToLeftSide = false;
if (UIUtil.isUnderAquaLookAndFeel() && Arrays.asList(actions).contains(getHelpAction())) {
hasHelpToMoveToLeftSide = true;
actions = ArrayUtil.remove(actions, getHelpAction());
}
if (SystemInfo.isMac) {
for (Action action : actions) {
if (action instanceof MacOtherAction) {
leftSideActions = ArrayUtil.append(leftSideActions, action);
actions = ArrayUtil.remove(actions, action);
break;
}
}
}
else if (UIUtil.isUnderGTKLookAndFeel() && Arrays.asList(actions).contains(getHelpAction())) {
leftSideActions = ArrayUtil.append(leftSideActions, getHelpAction());
actions = ArrayUtil.remove(actions, getHelpAction());
}
JPanel panel = new JPanel(new BorderLayout());
final JPanel lrButtonsPanel = new JPanel(new GridBagLayout());
final Insets insets = SystemInfo.isMacOSLeopard ? new Insets(0, 0, 0, 0) : new Insets(8, 0, 0, 0);
if (actions.length > 0 || leftSideActions.length > 0) {
int gridX = 0;
if (leftSideActions.length > 0) {
JPanel buttonsPanel = createButtons(leftSideActions, buttons);
if (actions.length > 0) {
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20)); // leave some space between button groups
}
lrButtonsPanel.add(buttonsPanel,
new GridBagConstraints(gridX++, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, insets, 0,
0));
}
lrButtonsPanel.add(Box.createHorizontalGlue(), // left strut
new GridBagConstraints(gridX++, 0, 1, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0,
0));
if (actions.length > 0) {
if (SystemInfo.isMac) {
// move ok action to the right
int okNdx = ArrayUtil.indexOf(actions, getOKAction());
if (okNdx >= 0 && okNdx != actions.length - 1) {
actions = ArrayUtil.append(ArrayUtil.remove(actions, getOKAction()), getOKAction());
}
// move cancel action to the left
int cancelNdx = ArrayUtil.indexOf(actions, getCancelAction());
if (cancelNdx > 0) {
actions = ArrayUtil.mergeArrays(new Action[]{getCancelAction()}, ArrayUtil.remove(actions, getCancelAction()));
}
/*if (!hasFocusedAction(actions)) {
int ndx = ArrayUtil.find(actions, getCancelAction());
if (ndx >= 0) {
actions[ndx].putValue(FOCUSED_ACTION, Boolean.TRUE);
}
}*/
}
JPanel buttonsPanel = createButtons(actions, buttons);
lrButtonsPanel.add(buttonsPanel,
new GridBagConstraints(gridX++, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, insets, 0,
0));
}
if (SwingConstants.CENTER == myButtonAlignment) {
lrButtonsPanel.add(Box.createHorizontalGlue(), // right strut
new GridBagConstraints(gridX, 0, 1, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, insets, 0,
0));
}
myButtons = buttons.toArray(new JButton[buttons.size()]);
}
if (hasHelpToMoveToLeftSide) {
JButton helpButton = new JButton(getHelpAction());
if (!UIUtil.isUnderDarcula()) {
helpButton.putClientProperty("JButton.buttonType", "help");
helpButton.setText("");
}
helpButton.setMargin(insets);
helpButton.setToolTipText(ActionsBundle.actionDescription("HelpTopics"));
panel.add(helpButton, BorderLayout.WEST);
}
panel.add(lrButtonsPanel, BorderLayout.CENTER);
final DoNotAskOption askOption = myDoNotAsk;
if (askOption != null) {
myCheckBoxDoNotShowDialog = new JCheckBox(askOption.getDoNotShowMessage());
JComponent southPanel = panel;
if (!askOption.canBeHidden()) {
return southPanel;
}
final JPanel withCB = addDoNotShowCheckBox(southPanel, myCheckBoxDoNotShowDialog);
myCheckBoxDoNotShowDialog.setSelected(!askOption.isToBeShown());
DialogUtil.registerMnemonic(myCheckBoxDoNotShowDialog, '&');
panel = withCB;
}
panel.setBorder(IdeBorderFactory.createEmptyBorder(new Insets(8, 0, 0, 0)));
return panel;
}
@NotNull
private Action[] filter(@NotNull Action[] actions) {
ArrayList<Action> answer = new ArrayList<Action>();
for (Action action : actions) {
if (action != null && (ApplicationInfo.contextHelpAvailable() || action != getHelpAction())) {
answer.add(action);
}
}
return answer.toArray(new Action[answer.size()]);
}
protected boolean toBeShown() {
return !myCheckBoxDoNotShowDialog.isSelected();
}
public boolean isTypeAheadEnabled() {
return false;
}
@NotNull
public static JPanel addDoNotShowCheckBox(@NotNull JComponent southPanel, @NotNull JCheckBox checkBox) {
final JPanel panel = new JPanel(new BorderLayout());
JPanel wrapper = new JPanel(new GridBagLayout());
wrapper.add(checkBox);
panel.add(wrapper, BorderLayout.WEST);
panel.add(southPanel, BorderLayout.EAST);
checkBox.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 20));
return panel;
}
private boolean hasFocusedAction(@NotNull Action[] actions) {
for (Action action : actions) {
if (action.getValue(FOCUSED_ACTION) != null && (Boolean)action.getValue(FOCUSED_ACTION)) {
return true;
}
}
return false;
}
@NotNull
private JPanel createButtons(@NotNull Action[] actions, @NotNull List<JButton> buttons) {
if (!UISettings.getShadowInstance().ALLOW_MERGE_BUTTONS) {
final List<Action> actionList = new ArrayList<Action>();
for (Action action : actions) {
actionList.add(action);
if (action instanceof OptionAction) {
final Action[] options = ((OptionAction)action).getOptions();
actionList.addAll(Arrays.asList(options));
}
}
if (actionList.size() != actions.length) {
actions = actionList.toArray(actionList.toArray(new Action[actionList.size()]));
}
}
JPanel buttonsPanel = new JPanel(new GridLayout(1, actions.length, SystemInfo.isMacOSLeopard ? 0 : 5, 0));
for (final Action action : actions) {
JButton button = createJButtonForAction(action);
final Object value = action.getValue(Action.MNEMONIC_KEY);
if (value instanceof Integer) {
final int mnemonic = ((Integer)value).intValue();
final Object name = action.getValue(Action.NAME);
if (mnemonic == 'Y' && "Yes".equals(name)) {
myYesAction = action;
}
else if (mnemonic == 'N' && "No".equals(name)) {
myNoAction = action;
}
button.setMnemonic(mnemonic);
}
if (action.getValue(FOCUSED_ACTION) != null) {
myPreferredFocusedComponent = button;
}
buttons.add(button);
buttonsPanel.add(button);
}
return buttonsPanel;
}
/**
* Creates <code>JButton</code> for the specified action. If the button has not <code>null</code>
* value for <code>DialogWrapper.DEFAULT_ACTION</code> key then the created button will be the
* default one for the dialog.
*
* @param action action for the button
* @return button with action specified
* @see com.intellij.openapi.ui.DialogWrapper#DEFAULT_ACTION
*/
protected JButton createJButtonForAction(Action action) {
JButton button;
if (action instanceof OptionAction && UISettings.getShadowInstance().ALLOW_MERGE_BUTTONS) {
final Action[] options = ((OptionAction)action).getOptions();
button = new JBOptionButton(action, options);
final JBOptionButton eachOptionsButton = (JBOptionButton)button;
eachOptionsButton.setOkToProcessDefaultMnemonics(false);
eachOptionsButton.setOptionTooltipText(
"Press " + KeymapUtil.getKeystrokeText(SHOW_OPTION_KEYSTROKE) + " to expand or use a mnemonic of a contained action");
myOptionsButtons.add(eachOptionsButton);
final Set<JBOptionButton.OptionInfo> infos = eachOptionsButton.getOptionInfos();
for (final JBOptionButton.OptionInfo eachInfo : infos) {
if (eachInfo.getMnemonic() >= 0) {
final char mnemonic = (char)eachInfo.getMnemonic();
new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
final JBOptionButton buttonToActivate = eachInfo.getButton();
buttonToActivate.showPopup(eachInfo.getAction(), true);
}
}.registerCustomShortcutSet(MnemonicHelper.createShortcut(mnemonic), getPeer().getRootPane());
}
}
}
else {
button = new JButton(action);
}
String text = button.getText();
if (SystemInfo.isMac) {
button.putClientProperty("JButton.buttonType", "text");
}
if (text != null) {
int mnemonic = 0;
StringBuilder plainText = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '_' || ch == '&') {
i++;
if (i >= text.length()) {
break;
}
ch = text.charAt(i);
if (ch != '_' && ch != '&') {
// Mnemonic is case insensitive.
int vk = ch;
if (vk >= 'a' && vk <= 'z') {
vk -= 'a' - 'A';
}
mnemonic = vk;
}
}
plainText.append(ch);
}
button.setText(plainText.toString());
final Object name = action.getValue(Action.NAME);
if (mnemonic == KeyEvent.VK_Y && "Yes".equals(name)) {
myYesAction = action;
}
else if (mnemonic == KeyEvent.VK_N && "No".equals(name)) {
myNoAction = action;
}
button.setMnemonic(mnemonic);
}
setMargin(button);
if (action.getValue(DEFAULT_ACTION) != null) {
if (!myPeer.isHeadless()) {
getRootPane().setDefaultButton(button);
}
}
return button;
}
private void setMargin(@NotNull JButton button) {
// Aqua LnF does a good job of setting proper margin between buttons. Setting them specifically causes them be 'square' style instead of
// 'rounded', which is expected by apple users.
if (!SystemInfo.isMac) {
if (myButtonMargins == null) {
return;
}
button.setMargin(myButtonMargins);
}
}
@NotNull
protected DialogWrapperPeer createPeer(@NotNull Component parent, final boolean canBeParent) {
return DialogWrapperPeerFactory.getInstance().createPeer(this, parent, canBeParent);
}
/**
* Dialogs with no parents are discouraged.
* Instead, use e.g. {@link DialogWrapper#createPeer(Window, boolean, boolean)}
*/
@Deprecated
@NotNull
protected DialogWrapperPeer createPeer(boolean canBeParent, boolean applicationModalIfPossible) {
return createPeer(null, canBeParent, applicationModalIfPossible);
}
@NotNull
protected DialogWrapperPeer createPeer(final Window owner, final boolean canBeParent, final IdeModalityType ideModalityType) {
return DialogWrapperPeerFactory.getInstance().createPeer(this, owner, canBeParent, ideModalityType);
}
@Deprecated
@NotNull
protected DialogWrapperPeer createPeer(final Window owner, final boolean canBeParent, final boolean applicationModalIfPossible) {
return DialogWrapperPeerFactory.getInstance()
.createPeer(this, owner, canBeParent, applicationModalIfPossible ? IdeModalityType.IDE : IdeModalityType.PROJECT);
}
@NotNull
protected DialogWrapperPeer createPeer(@Nullable final Project project,
final boolean canBeParent,
@NotNull IdeModalityType ideModalityType) {
return DialogWrapperPeerFactory.getInstance().createPeer(this, project, canBeParent, ideModalityType);
}
@NotNull
protected DialogWrapperPeer createPeer(@Nullable final Project project, final boolean canBeParent) {
return DialogWrapperPeerFactory.getInstance().createPeer(this, project, canBeParent);
}
@Nullable
protected JComponent createTitlePane() {
return null;
}
/**
* Factory method. It creates the panel located at the
* north of the dialog's content pane. The implementation can return <code>null</code>
* value. In this case there will be no input panel.
*
* @return north panel
*/
@Nullable
protected JComponent createNorthPanel() {
return null;
}
/**
* Factory method. It creates panel with dialog options. Options panel is located at the
* center of the dialog's content pane. The implementation can return <code>null</code>
* value. In this case there will be no options panel.
*
* @return center panel
*/
@Nullable
protected abstract JComponent createCenterPanel();
/**
* @see java.awt.Window#toFront()
*/
public void toFront() {
myPeer.toFront();
}
/**
* @see java.awt.Window#toBack()
*/
public void toBack() {
myPeer.toBack();
}
protected boolean setAutoAdjustable(boolean autoAdjustable) {
JRootPane rootPane = getRootPane();
if (rootPane == null) return false;
rootPane.putClientProperty(NO_AUTORESIZE, autoAdjustable ? null : Boolean.TRUE);
return true;
}
//true by default
public boolean isAutoAdjustable() {
JRootPane rootPane = getRootPane();
return rootPane == null || rootPane.getClientProperty(NO_AUTORESIZE) == null;
}
/**
* Dispose the wrapped and releases all resources allocated be the wrapper to help
* more efficient garbage collection. You should never invoke this method twice or
* invoke any method of the wrapper after invocation of <code>dispose</code>.
*
* @throws IllegalStateException if the dialog is disposed not on the event dispatch thread
*/
protected void dispose() {
ensureEventDispatchThread();
myErrorTextAlarm.cancelAllRequests();
myValidationAlarm.cancelAllRequests();
myDisposed = true;
if (myButtons != null) {
for (JButton button : myButtons) {
button.setAction(null); // avoid memory leak via KeyboardManager
}
}
final JRootPane rootPane = getRootPane();
// if rootPane = null, dialog has already been disposed
if (rootPane != null) {
unregisterKeyboardActions(rootPane);
if (myActualSize != null && isAutoAdjustable()) {
setSize(myActualSize.width, myActualSize.height);
}
myPeer.dispose();
}
}
public static void unregisterKeyboardActions(final JRootPane rootPane) {
new AwtVisitor(rootPane) {
@Override
public boolean visit(final Component component) {
if (component instanceof JComponent) {
final JComponent eachComp = (JComponent)component;
final ActionMap actionMap = eachComp.getActionMap();
final KeyStroke[] strokes = eachComp.getRegisteredKeyStrokes();
for (KeyStroke eachStroke : strokes) {
boolean remove = true;
if (actionMap != null) {
for (int i : new int[]{JComponent.WHEN_FOCUSED, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, JComponent.WHEN_IN_FOCUSED_WINDOW}) {
final InputMap inputMap = eachComp.getInputMap(i);
final Object key = inputMap.get(eachStroke);
if (key != null) {
final Action action = actionMap.get(key);
if (action instanceof UIResource) remove = false;
}
}
}
if (remove) eachComp.unregisterKeyboardAction(eachStroke);
}
}
return false;
}
};
}
/**
* This method is invoked by default implementation of "Cancel" action. It just closes dialog
* with <code>CANCEL_EXIT_CODE</code>. This is convenient place to override functionality of "Cancel" action.
* Note that the method does nothing if "Cancel" action isn't enabled.
*/
public void doCancelAction() {
processDoNotAskOnCancel();
if (getCancelAction().isEnabled()) {
close(CANCEL_EXIT_CODE);
}
}
private void processDoNotAskOnCancel() {
if (myDoNotAsk != null) {
if (myDoNotAsk.shouldSaveOptionsOnCancel() && myDoNotAsk.canBeHidden()) {
myDoNotAsk.setToBeShown(toBeShown(), CANCEL_EXIT_CODE);
}
}
}
/**
* You can use this method if you want to know by which event this actions got triggered. It is called only if
* the cancel action was triggered by some input event, <code>doCancelAction</code> is called otherwise.
*
* @param source AWT event
* @see #doCancelAction
*/
public void doCancelAction(AWTEvent source) {
doCancelAction();
}
/**
* Programmatically perform a "click" of default dialog's button. The method does
* nothing if the dialog has no default button.
*/
public void clickDefaultButton() {
JButton button = getRootPane().getDefaultButton();
if (button != null) {
button.doClick();
}
}
/**
* This method is invoked by default implementation of "OK" action. It just closes dialog
* with <code>OK_EXIT_CODE</code>. This is convenient place to override functionality of "OK" action.
* Note that the method does nothing if "OK" action isn't enabled.
*/
protected void doOKAction() {
processDoNotAskOnOk(OK_EXIT_CODE);
if (getOKAction().isEnabled()) {
close(OK_EXIT_CODE);
}
}
protected void processDoNotAskOnOk(int exitCode) {
if (myDoNotAsk != null) {
if (myDoNotAsk.canBeHidden()) {
myDoNotAsk.setToBeShown(toBeShown(), exitCode);
}
}
}
/**
* @return whether the native window cross button closes the window or not.
* <code>true</code> means that cross performs hide or dispose of the dialog.
*/
public boolean shouldCloseOnCross() {
return myCrossClosesWindow;
}
/**
* Creates actions for dialog.
* <p/>
* By default "OK" and "Cancel" actions are returned. The "Help" action is automatically added if
* {@link #getHelpId()} returns non-null value.
* <p/>
* Each action is represented by <code>JButton</code> created by {@link #createJButtonForAction(javax.swing.Action)}.
* These buttons are then placed into {@link #createSouthPanel() south panel} of dialog.
*
* @return dialog actions
* @see #createSouthPanel
* @see #createJButtonForAction
*/
@NotNull
protected Action[] createActions() {
if (getHelpId() == null) {
if (SystemInfo.isMac) {
return new Action[]{getCancelAction(), getOKAction()};
}
return new Action[]{getOKAction(), getCancelAction()};
}
else {
if (SystemInfo.isMac) {
return new Action[]{getHelpAction(), getCancelAction(), getOKAction()};
}
return new Action[]{getOKAction(), getCancelAction(), getHelpAction()};
}
}
@NotNull
protected Action[] createLeftSideActions() {
return new Action[0];
}
/**
* @return default implementation of "OK" action. This action just invokes
* <code>doOKAction()</code> method.
* @see #doOKAction
*/
@NotNull
protected Action getOKAction() {
return myOKAction;
}
/**
* @return default implementation of "Cancel" action. This action just invokes
* <code>doCancelAction()</code> method.
* @see #doCancelAction
*/
@NotNull
protected Action getCancelAction() {
return myCancelAction;
}
/**
* @return default implementation of "Help" action. This action just invokes
* <code>doHelpAction()</code> method.
* @see #doHelpAction
*/
@NotNull
protected Action getHelpAction() {
return myHelpAction;
}
protected boolean isProgressDialog() {
return false;
}
public final boolean isModalProgress() {
return isProgressDialog();
}
/**
* Returns content pane
*
* @return content pane
* @see javax.swing.JDialog#getContentPane
*/
public Container getContentPane() {
return myPeer.getContentPane();
}
/**
* @see javax.swing.JDialog#validate
*/
public void validate() {
myPeer.validate();
}
/**
* @see javax.swing.JDialog#repaint
*/
public void repaint() {
myPeer.repaint();
}
/**
* Returns key for persisting dialog dimensions.
* <p/>
* Default implementation returns <code>null</code> (no persisting).
*
* @return dimension service key
*/
@Nullable
@NonNls
protected String getDimensionServiceKey() {
return null;
}
@Nullable
public final String getDimensionKey() {
return getDimensionServiceKey();
}
public int getExitCode() {
return myExitCode;
}
/**
* @return component which should be focused when the dialog appears
* on the screen.
*/
@Nullable
public JComponent getPreferredFocusedComponent() {
return SystemInfo.isMac ? myPreferredFocusedComponent : null;
}
/**
* @return horizontal stretch of the dialog. It means that the dialog's horizontal size is
* the product of horizontal stretch by horizontal size of packed dialog. The default value
* is <code>1.0f</code>
*/
public final float getHorizontalStretch() {
return myHorizontalStretch;
}
/**
* @return vertical stretch of the dialog. It means that the dialog's vertical size is
* the product of vertical stretch by vertical size of packed dialog. The default value
* is <code>1.0f</code>
*/
public final float getVerticalStretch() {
return myVerticalStretch;
}
protected final void setHorizontalStretch(float hStretch) {
myHorizontalStretch = hStretch;
}
protected final void setVerticalStretch(float vStretch) {
myVerticalStretch = vStretch;
}
/**
* @return window owner
* @see java.awt.Window#getOwner
*/
public Window getOwner() {
return myPeer.getOwner();
}
public Window getWindow() {
return myPeer.getWindow();
}
public JComponent getContentPanel() {
return (JComponent)myPeer.getContentPane();
}
/**
* @return root pane
* @see javax.swing.JDialog#getRootPane
*/
public JRootPane getRootPane() {
return myPeer.getRootPane();
}
/**
* @return dialog size
* @see java.awt.Window#getSize
*/
public Dimension getSize() {
return myPeer.getSize();
}
/**
* @return dialog title
* @see java.awt.Dialog#getTitle
*/
public String getTitle() {
return myPeer.getTitle();
}
protected void init() {
if (!SwingUtilities.isEventDispatchThread()) {
LOG.error("Dialog must be init in EDT only: "+Thread.currentThread());
}
myErrorText = new ErrorText();
myErrorText.setVisible(false);
final JPanel root = new JPanel(createRootLayout());
//{
// @Override
// public void paint(Graphics g) {
// if (ApplicationManager.getApplication() != null) {
// UISettings.setupAntialiasing(g);
// }
// super.paint(g);
// }
//};
myPeer.setContentPane(root);
final CustomShortcutSet sc = new CustomShortcutSet(SHOW_OPTION_KEYSTROKE);
final AnAction toggleShowOptions = new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
expandNextOptionButton();
}
};
toggleShowOptions.registerCustomShortcutSet(sc, root);
final JPanel northSection = new JPanel(new BorderLayout());
root.add(northSection, BorderLayout.NORTH);
JComponent titlePane = createTitlePane();
if (titlePane != null) {
northSection.add(titlePane, BorderLayout.CENTER);
}
JComponent centerSection = new JPanel(new BorderLayout());
root.add(centerSection, BorderLayout.CENTER);
root.setBorder(createContentPaneBorder());
final JComponent n = createNorthPanel();
if (n != null) {
centerSection.add(n, BorderLayout.NORTH);
}
final JComponent c = createCenterPanel();
if (c != null) {
centerSection.add(c, BorderLayout.CENTER);
myErrorPane = c;
}
if (myErrorPane == null) {
myErrorPane = root;
}
final JPanel southSection = new JPanel(new BorderLayout());
root.add(southSection, BorderLayout.SOUTH);
southSection.add(myErrorText, BorderLayout.CENTER);
final JComponent south = createSouthPanel();
if (south != null) {
southSection.add(south, BorderLayout.SOUTH);
}
new MnemonicHelper().register(root);
if (!postponeValidation()) {
startTrackingValidation();
}
if (SystemInfo.isWindows) {
installEnterHook(root);
}
myErrorTextAlarm.setActivationComponent(root);
}
@NotNull
LayoutManager createRootLayout() {
return new BorderLayout();
}
private static void installEnterHook(JComponent root) {
new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (owner instanceof JButton && owner.isEnabled()) {
((JButton)owner).doClick();
}
}
@Override
public void update(AnActionEvent e) {
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
e.getPresentation().setEnabled(owner instanceof JButton && owner.isEnabled());
}
}.registerCustomShortcutSet(CustomShortcutSet.fromString("ENTER"), root);
}
private void expandNextOptionButton() {
if (myCurrentOptionsButtonIndex > 0) {
myOptionsButtons.get(myCurrentOptionsButtonIndex).closePopup();
myCurrentOptionsButtonIndex++;
}
else if (!myOptionsButtons.isEmpty()) {
myCurrentOptionsButtonIndex = 0;
}
if (myCurrentOptionsButtonIndex >= 0 && myCurrentOptionsButtonIndex < myOptionsButtons.size()) {
myOptionsButtons.get(myCurrentOptionsButtonIndex).showPopup(null, true);
}
}
void startTrackingValidation() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!myValidationStarted && !myDisposed) {
myValidationStarted = true;
initValidation();
}
}
});
}
protected final void initValidation() {
myValidationAlarm.cancelAllRequests();
final Runnable validateRequest = new Runnable() {
@Override
public void run() {
if (myDisposed) return;
final ValidationInfo result = doValidate();
if (result == null) {
clearProblems();
}
else {
reportProblem(result);
}
if (!myDisposed) {
initValidation();
}
}
};
if (getValidationThreadToUse() == Alarm.ThreadToUse.SWING_THREAD) {
myValidationAlarm.addRequest(validateRequest, myValidationDelay, ModalityState.current());
}
else {
myValidationAlarm.addRequest(validateRequest, myValidationDelay);
}
}
protected boolean isNorthStrictedToPreferredSize() {
return true;
}
protected boolean isCenterStrictedToPreferredSize() {
return false;
}
protected boolean isSouthStrictedToPreferredSize() {
return true;
}
@NotNull
protected JComponent createContentPane() {
return new JPanel();
}
/**
* @see java.awt.Window#pack
*/
public void pack() {
myPeer.pack();
}
public Dimension getPreferredSize() {
return myPeer.getPreferredSize();
}
/**
* Sets horizontal alignment of dialog's buttons.
*
* @param alignment alignment of the buttons. Acceptable values are
* <code>SwingConstants.CENTER</code> and <code>SwingConstants.RIGHT</code>.
* The <code>SwingConstants.RIGHT</code> is the default value.
* @throws java.lang.IllegalArgumentException if <code>alignment</code> isn't acceptable
*/
protected final void setButtonsAlignment(@MagicConstant(intValues = {SwingConstants.CENTER, SwingConstants.RIGHT}) int alignment) {
if (SwingConstants.CENTER != alignment && SwingConstants.RIGHT != alignment) {
throw new IllegalArgumentException("unknown alignment: " + alignment);
}
myButtonAlignment = alignment;
}
/**
* Sets margin for command buttons ("OK", "Cancel", "Help").
*
* @param insets buttons margin
*/
public final void setButtonsMargin(@Nullable Insets insets) {
myButtonMargins = insets;
}
public final void setCrossClosesWindow(boolean crossClosesWindow) {
myCrossClosesWindow = crossClosesWindow;
}
protected final void setCancelButtonIcon(Icon icon) {
// Setting icons causes buttons be 'square' style instead of
// 'rounded', which is expected by apple users.
if (!SystemInfo.isMac) {
myCancelAction.putValue(Action.SMALL_ICON, icon);
}
}
protected final void setCancelButtonText(String text) {
myCancelAction.putValue(Action.NAME, text);
}
public void setModal(boolean modal) {
myPeer.setModal(modal);
}
public boolean isModal() {
return myPeer.isModal();
}
protected void setOKActionEnabled(boolean isEnabled) {
myOKAction.setEnabled(isEnabled);
}
protected final void setOKButtonIcon(Icon icon) {
// Setting icons causes buttons be 'square' style instead of
// 'rounded', which is expected by apple users.
if (!SystemInfo.isMac) {
myOKAction.putValue(Action.SMALL_ICON, icon);
}
}
/**
* @param text action without mnemonic. If mnemonic is set, presentation would be shifted by one to the left
* {@link javax.swing.AbstractButton#setText(java.lang.String)}
* {@link javax.swing.AbstractButton#updateDisplayedMnemonicIndex(java.lang.String, int)}
*/
protected final void setOKButtonText(String text) {
myOKAction.putValue(Action.NAME, text);
}
protected final void setOKButtonMnemonic(int c) {
myOKAction.putValue(Action.MNEMONIC_KEY, c);
}
/**
* @return the help identifier or null if no help is available.
*/
@Nullable
protected String getHelpId() {
return null;
}
/**
* Invoked by default implementation of "Help" action.
* Note that the method does nothing if "Help" action isn't enabled.
* <p/>
* The default implementation shows the help page with id returned
* by {@link #getHelpId()}. If that method returns null,
* a message box with message "no help available" is shown.
*/
protected void doHelpAction() {
if (myHelpAction.isEnabled()) {
String helpId = getHelpId();
if (helpId != null) {
HelpManager.getInstance().invokeHelp(helpId);
}
else {
Messages.showMessageDialog(getContentPane(), UIBundle.message("there.is.no.help.for.this.dialog.error.message"),
UIBundle.message("no.help.available.dialog.title"), Messages.getInformationIcon());
}
}
}
public boolean isOK() {
return getExitCode() == OK_EXIT_CODE;
}
public boolean isOKActionEnabled() {
return myOKAction.isEnabled();
}
/**
* @return <code>true</code> if and only if visible
* @see java.awt.Component#isVisible
*/
public boolean isVisible() {
return myPeer.isVisible();
}
/**
* @return <code>true</code> if and only if showing
* @see java.awt.Window#isShowing
*/
public boolean isShowing() {
return myPeer.isShowing();
}
/**
* @param width width
* @param height height
* @see javax.swing.JDialog#setSize
*/
public void setSize(int width, int height) {
myPeer.setSize(width, height);
}
/**
* @param title title
* @see javax.swing.JDialog#setTitle
*/
public void setTitle(String title) {
myPeer.setTitle(title);
}
/**
* @see javax.swing.JDialog#isResizable
*/
public void isResizable() {
myPeer.isResizable();
}
/**
* @param resizable is resizable
* @see javax.swing.JDialog#setResizable
*/
public void setResizable(boolean resizable) {
myPeer.setResizable(resizable);
}
/**
* @return dialog location
* @see javax.swing.JDialog#getLocation
*/
@NotNull
public Point getLocation() {
return myPeer.getLocation();
}
/**
* @param p new dialog location
* @see javax.swing.JDialog#setLocation(Point)
*/
public void setLocation(@NotNull Point p) {
myPeer.setLocation(p);
}
/**
* @param x x
* @param y y
* @see javax.swing.JDialog#setLocation(int, int)
*/
public void setLocation(int x, int y) {
myPeer.setLocation(x, y);
}
public void centerRelativeToParent() {
myPeer.centerInParent();
}
/**
* Show the dialog
*
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
*/
public void show() {
showAndGetOk();
}
public boolean showAndGet() {
show();
return isOK();
}
/**
* You need this method ONLY for NON-MODAL dialogs. Otherwise, use {@link #show()} or {@link #showAndGet()}.
*
* @return result callback
*/
@NotNull
public AsyncResult<Boolean> showAndGetOk() {
final AsyncResult<Boolean> result = new AsyncResult<Boolean>();
ensureEventDispatchThread();
registerKeyboardShortcuts();
final Disposable uiParent = Disposer.get("ui");
if (uiParent != null) { // may be null if no app yet (license agreement)
Disposer.register(uiParent, myDisposable); // ensure everything is disposed on app quit
}
Disposer.register(myDisposable, new Disposable() {
@Override
public void dispose() {
result.setDone(isOK());
}
});
myPeer.show();
return result;
}
/**
* @return Location in absolute coordinates which is used when dialog has no dimension service key or no position was stored yet.
* Can return null. In that case dialog will be centered relative to its owner.
*/
@Nullable
public Point getInitialLocation() {
return myInitialLocationCallback == null ? null : myInitialLocationCallback.compute();
}
public void setInitialLocationCallback(@NotNull Computable<Point> callback) {
myInitialLocationCallback = callback;
}
private void registerKeyboardShortcuts() {
final JRootPane rootPane = getRootPane();
if (rootPane == null) return;
ActionListener cancelKeyboardAction = createCancelAction();
if (cancelKeyboardAction != null) {
rootPane
.registerKeyboardAction(cancelKeyboardAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
registerForEveryKeyboardShortcut(cancelKeyboardAction, CommonShortcuts.getCloseActiveWindow());
}
if (ApplicationInfo.contextHelpAvailable()) {
ActionListener helpAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doHelpAction();
}
};
registerForEveryKeyboardShortcut(helpAction, CommonShortcuts.getContextHelp());
rootPane.registerKeyboardAction(helpAction, KeyStroke.getKeyStroke(KeyEvent.VK_HELP, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
}
if (myButtons != null) {
rootPane.registerKeyboardAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
focusPreviousButton();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
rootPane.registerKeyboardAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
focusNextButton();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
}
if (myYesAction != null) {
rootPane.registerKeyboardAction(myYesAction, KeyStroke.getKeyStroke(KeyEvent.VK_Y, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
}
if (myNoAction != null) {
rootPane.registerKeyboardAction(myNoAction, KeyStroke.getKeyStroke(KeyEvent.VK_N, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
}
}
/**
*
* @return null if we should ignore <Esc> for window closing
*/
@Nullable
protected ActionListener createCancelAction() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
MenuElement[] selectedPath = menuSelectionManager.getSelectedPath();
if (selectedPath.length > 0) { // hide popup menu if any
menuSelectionManager.clearSelectedPath();
}
else {
if (ApplicationManager.getApplication() == null) {
doCancelAction(e);
return;
}
final StackingPopupDispatcher popupDispatcher = StackingPopupDispatcher.getInstance();
if (popupDispatcher != null && !popupDispatcher.isPopupFocused()) {
doCancelAction(e);
}
}
}
};
}
private void registerForEveryKeyboardShortcut(ActionListener action, @NotNull ShortcutSet shortcuts) {
for (Shortcut shortcut : shortcuts.getShortcuts()) {
if (shortcut instanceof KeyboardShortcut) {
KeyboardShortcut ks = (KeyboardShortcut)shortcut;
KeyStroke first = ks.getFirstKeyStroke();
KeyStroke second = ks.getSecondKeyStroke();
if (second == null) {
getRootPane().registerKeyboardAction(action, first, JComponent.WHEN_IN_FOCUSED_WINDOW);
}
}
}
}
private void focusPreviousButton() {
for (int i = 0; i < myButtons.length; i++) {
if (myButtons[i].hasFocus()) {
if (i == 0) {
myButtons[myButtons.length - 1].requestFocus();
return;
}
myButtons[i - 1].requestFocus();
return;
}
}
}
private void focusNextButton() {
for (int i = 0; i < myButtons.length; i++) {
if (myButtons[i].hasFocus()) {
if (i == myButtons.length - 1) {
myButtons[0].requestFocus();
return;
}
myButtons[i + 1].requestFocus();
return;
}
}
}
public long getTypeAheadTimeoutMs() {
return 0l;
}
public boolean isToDispatchTypeAhead() {
return isOK();
}
public static boolean isMultipleModalDialogs() {
final Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (c != null) {
final DialogWrapper wrapper = findInstance(c);
return wrapper != null && wrapper.getPeer().getCurrentModalEntities().length > 1;
}
return false;
}
/**
* Base class for dialog wrapper actions that need to ensure that only
* one action for the dialog is running.
*/
protected abstract class DialogWrapperAction extends AbstractAction {
/**
* The constructor
*
* @param name the action name (see {@link Action#NAME})
*/
protected DialogWrapperAction(@NotNull String name) {
putValue(NAME, name);
}
/**
* {@inheritDoc}
*/
@Override
public void actionPerformed(ActionEvent e) {
if (myClosed) return;
if (myPerformAction) return;
try {
myPerformAction = true;
doAction(e);
}
finally {
myPerformAction = false;
}
}
/**
* Do actual work for the action. This method is called only if no other action
* is performed in parallel (checked using {@link com.intellij.openapi.ui.DialogWrapper#myPerformAction}),
* and dialog is active (checked using {@link com.intellij.openapi.ui.DialogWrapper#myClosed})
*
* @param e action
*/
protected abstract void doAction(ActionEvent e);
}
protected class OkAction extends DialogWrapperAction {
protected OkAction() {
super(CommonBundle.getOkButtonText());
putValue(DEFAULT_ACTION, Boolean.TRUE);
}
@Override
protected void doAction(ActionEvent e) {
ValidationInfo info = doValidate();
if (info != null) {
if (info.component != null && info.component.isVisible()) {
IdeFocusManager.getInstance(null).requestFocus(info.component, true);
}
DialogEarthquakeShaker.shake((JDialog)getPeer().getWindow());
startTrackingValidation();
return;
}
doOKAction();
}
}
protected class CancelAction extends DialogWrapperAction {
private CancelAction() {
super(CommonBundle.getCancelButtonText());
}
@Override
protected void doAction(ActionEvent e) {
doCancelAction();
}
}
/**
* The action that just closes dialog with the specified exit code
* (like the default behavior of the actions "Ok" and "Cancel").
*/
protected class DialogWrapperExitAction extends DialogWrapperAction {
/**
* The exit code for the action
*/
protected final int myExitCode;
/**
* The constructor
*
* @param name the action name
* @param exitCode the exit code for dialog
*/
public DialogWrapperExitAction(String name, int exitCode) {
super(name);
myExitCode = exitCode;
}
@Override
protected void doAction(ActionEvent e) {
if (isEnabled()) {
close(myExitCode);
}
}
}
private class HelpAction extends AbstractAction {
private HelpAction() {
putValue(NAME, CommonBundle.getHelpButtonText());
}
@Override
public void actionPerformed(ActionEvent e) {
doHelpAction();
}
}
private Dimension myActualSize = null;
private String myLastErrorText = null;
protected final void setErrorText(@Nullable final String text) {
if (Comparing.equal(myLastErrorText, text)) {
return;
}
myLastErrorText = text;
myErrorTextAlarm.cancelAllRequests();
myErrorTextAlarm.addRequest(new Runnable() {
@Override
public void run() {
final String text = myLastErrorText;
if (myActualSize == null && !StringUtil.isEmpty(text)) {
myActualSize = getSize();
}
myErrorText.setError(text);
if (text != null && text.length() > myMaxErrorTextLength) {
// during the first update, resize only for growing. during a subsequent update,
// if error text becomes longer, the min size calculation may not calculate enough size,
// so we pack() even though it could cause the dialog to become smaller.
if (myMaxErrorTextLength == 0) {
updateHeightForErrorText();
}
myMaxErrorTextLength = text.length();
updateHeightForErrorText();
}
myErrorText.repaint();
if (StringUtil.isEmpty(text) && myActualSize != null) {
resizeWithAnimation(myActualSize);
myMaxErrorTextLength = 0;
}
}
}, 300, null);
}
@Nullable
public static DialogWrapper findInstance(Component c) {
while (c != null) {
if (c instanceof DialogWrapperDialog) {
return ((DialogWrapperDialog)c).getDialogWrapper();
}
c = c.getParent();
}
return null;
}
private void resizeWithAnimation(@NotNull final Dimension size) {
//todo[kb]: fix this PITA
myResizeInProgress = true;
if (!Registry.is("enable.animation.on.dialogs")) {
setSize(size.width, size.height);
myResizeInProgress = false;
return;
}
new Thread("DialogWrapper resizer") {
int time = 200;
int steps = 7;
@Override
public void run() {
int step = 0;
final Dimension cur = getSize();
int h = (size.height - cur.height) / steps;
int w = (size.width - cur.width) / steps;
while (step++ < steps) {
setSize(cur.width + w * step, cur.height + h * step);
try {
//noinspection BusyWait
sleep(time / steps);
}
catch (InterruptedException ignore) {
}
}
setSize(size.width, size.height);
//repaint();
if (myErrorText.shouldBeVisible()) {
myErrorText.setVisible(true);
}
myResizeInProgress = false;
}
}.start();
}
private void updateHeightForErrorText() {
Dimension errorSize = myErrorText.getPreferredSize();
resizeWithAnimation(new Dimension(Math.max(myActualSize.width, errorSize.width + 40), myActualSize.height + errorSize.height + 10));
}
private static class ErrorText extends JPanel {
private final JLabel myLabel = new JLabel();
private Dimension myPrefSize;
private String myText;
private ErrorText() {
setLayout(new BorderLayout());
JBScrollPane pane =
new JBScrollPane(myLabel, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
pane.setBorder(IdeBorderFactory.createEmptyBorder());
pane.setBackground(null);
pane.getViewport().setBackground(null);
pane.setOpaque(false);
add(pane, BorderLayout.CENTER);
}
public void setError(String text) {
final Dimension oldSize = getPreferredSize();
myText = text;
if (text == null) {
myLabel.setText("");
myLabel.setIcon(null);
setVisible(false);
setBorder(null);
}
else {
myLabel
.setText(XmlStringUtil.wrapInHtml("<font color='#" + ColorUtil.toHex(JBColor.RED) + "'><left>" + text + "</left></b></font>"));
myLabel.setIcon(AllIcons.Actions.Lightning);
myLabel.setBorder(new EmptyBorder(4, 10, 0, 2));
setVisible(true);
}
final Dimension size = getPreferredSize();
if (oldSize.height < size.height) {
revalidate();
}
}
public boolean shouldBeVisible() {
return !StringUtil.isEmpty(myText);
}
public boolean isTextSet(@Nullable String text) {
return StringUtil.equals(text, myText);
}
@Override
public Dimension getPreferredSize() {
return myPrefSize == null ? myLabel.getPreferredSize() : myPrefSize;
}
}
@NotNull
public final DialogWrapperPeer getPeer() {
return myPeer;
}
/**
* Ensure that dialog is used from even dispatch thread.
*
* @throws IllegalStateException if the dialog is invoked not on the event dispatch thread
*/
private static void ensureEventDispatchThread() {
if (!EventQueue.isDispatchThread()) {
throw new IllegalStateException("The DialogWrapper can be used only on event dispatch thread.");
}
}
@NotNull
public final Disposable getDisposable() {
return myDisposable;
}
/**
* @see PropertyDoNotAskOption
*/
public interface DoNotAskOption {
/**
* @return default selection state of checkbox (false -> checkbox selected)
*/
boolean isToBeShown();
/**
* @param toBeShown - if dialog should be shown next time (checkbox selected -> false)
* @param exitCode of corresponding DialogWrapper
*/
void setToBeShown(boolean toBeShown, int exitCode);
/**
* @return true if checkbox should be shown
*/
boolean canBeHidden();
boolean shouldSaveOptionsOnCancel();
@NotNull
String getDoNotShowMessage();
}
public static class PropertyDoNotAskOption implements DoNotAskOption {
@NotNull
private final String myProperty;
public PropertyDoNotAskOption(@NotNull String property) {
myProperty = property;
}
@Override
public boolean isToBeShown() {
return PropertiesComponent.getInstance().getBoolean(myProperty, false);
}
@Override
public void setToBeShown(boolean value, int exitCode) {
PropertiesComponent.getInstance().setValue(myProperty, Boolean.toString(value));
}
@Override
public boolean canBeHidden() {
return false;
}
@Override
public boolean shouldSaveOptionsOnCancel() {
return false;
}
@NotNull
@Override
public String getDoNotShowMessage() {
return CommonBundle.message("dialog.options.do.not.ask");
}
}
@NotNull
private ErrorPaintingType getErrorPaintingType() {
return ErrorPaintingType.SIGN;
}
private class ErrorPainter extends AbstractPainter {
private ValidationInfo myInfo;
@Override
public void executePaint(Component component, Graphics2D g) {
if (myInfo != null && myInfo.component != null) {
final JComponent comp = myInfo.component;
final int w = comp.getWidth();
final int h = comp.getHeight();
Point p;
switch (getErrorPaintingType()) {
case DOT:
p = SwingUtilities.convertPoint(comp, 2, h / 2, component);
AllIcons.Ide.ErrorPoint.paintIcon(component, g, p.x, p.y);
break;
case SIGN:
p = SwingUtilities.convertPoint(comp, w, 0, component);
AllIcons.General.Error.paintIcon(component, g, p.x - 8, p.y - 8);
break;
case LINE:
p = SwingUtilities.convertPoint(comp, 0, h, component);
final GraphicsConfig config = new GraphicsConfig(g);
g.setColor(new Color(255, 0, 0, 100));
g.fillRoundRect(p.x, p.y - 2, w, 4, 2, 2);
config.restore();
break;
}
}
}
@Override
public boolean needsRepaint() {
return true;
}
public void setValidationInfo(@Nullable ValidationInfo info) {
myInfo = info;
}
}
private static enum ErrorPaintingType {DOT, SIGN, LINE}
}