/*
* Copyright 2000-2009 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 org.community.intellij.plugins.communitycase.ui;
import com.intellij.ide.ui.ListCellRendererWrapper;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vcs.AbstractVcsHelper;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import org.community.intellij.plugins.communitycase.Branch;
import org.community.intellij.plugins.communitycase.Remote;
import org.community.intellij.plugins.communitycase.Vcs;
import org.community.intellij.plugins.communitycase.config.ConfigUtil;
import org.community.intellij.plugins.communitycase.i18n.Bundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Utilities for plugin user interface
*/
public class UiUtil {
/**
* Text containing in the label when there is no current branch
*/
public static final String NO_CURRENT_BRANCH = Bundle.getString("common.no.active.branch");
/**
* A private constructor for utility class
*/
private UiUtil() { }
/**
* Displays a "success"-notification.
*/
public static void notifySuccess(Project project, String title, String description) {
Notifications.Bus.notify(new Notification(Vcs.NOTIFICATION_GROUP_ID, title, description, NotificationType.INFORMATION), project);
}
/**
* @return a list cell renderer for virtual files (it renders presentable URL)
* @param listCellRenderer
*/
public static ListCellRenderer getVirtualFileListCellRenderer(final ListCellRenderer listCellRenderer) {
return new ListCellRendererWrapper<VirtualFile>(listCellRenderer) {
@Override
public void customize(final JList list, final VirtualFile file, final int index, final boolean selected, final boolean hasFocus) {
setText(file == null || !file.isValid() ? "(invalid)" : file.getPresentableUrl());
}
};
}
/**
* Get text field from combobox
*
* @param comboBox a combobox to examine
* @return the text field reference
*/
public static JTextField getTextField(JComboBox comboBox) {
return (JTextField)comboBox.getEditor().getEditorComponent();
}
/**
* Create list cell renderer for remotes. It shows both name and url and highlights the default
* remote for the branch with bold.
*
*
* @param defaultRemote a default remote
* @param fetchUrl if true, the fetch url is shown
* @param listCellRenderer
* @return a list cell renderer for virtual files (it renders presentable URL
*/
public static ListCellRenderer getRemoteListCellRenderer(final String defaultRemote, final boolean fetchUrl,
final ListCellRenderer listCellRenderer) {
return new ListCellRendererWrapper<Remote>(listCellRenderer) {
@Override
public void customize(final JList list, final Remote remote, final int index, final boolean selected, final boolean hasFocus) {
final String text;
if (remote == null) {
text = Bundle.getString("util.remote.renderer.none");
}
else if (".".equals(remote.name())) {
text = Bundle.getString("util.remote.renderer.self");
}
else {
String key;
if (defaultRemote != null && defaultRemote.equals(remote.name())) {
key = "util.remote.renderer.default";
}
else {
key = "util.remote.renderer.normal";
}
text = Bundle.message(key, remote.name(), fetchUrl ? remote.fetchUrl() : remote.pushUrl());
}
setText(text);
}
};
}
/**
* Setup root chooser with specified elements and link selection to the current branch label.
*
* @param project a context project
* @param roots roots for the project
* @param defaultRoot a default root
* @param RootChooser root selector
* @param currentBranchLabel current branch label (might be null)
*/
public static void setupRootChooser(@NotNull final Project project,
@NotNull final List<VirtualFile> roots,
@Nullable final VirtualFile defaultRoot,
@NotNull final JComboBox RootChooser,
@Nullable final JLabel currentBranchLabel) {
for (VirtualFile root : roots) {
RootChooser.addItem(root);
}
RootChooser.setRenderer(getVirtualFileListCellRenderer(RootChooser.getRenderer()));
RootChooser.setSelectedItem(defaultRoot != null ? defaultRoot : roots.get(0));
if (currentBranchLabel != null) {
final ActionListener listener = new ActionListener() {
public void actionPerformed(final ActionEvent e) {
try {
VirtualFile root = (VirtualFile)RootChooser.getSelectedItem();
assert root != null : "The root must not be null";
Branch current = Branch.current(project, root);
if (current == null) {
currentBranchLabel.setText(NO_CURRENT_BRANCH);
}
else {
currentBranchLabel.setText(current.getName());
}
}
catch (VcsException ex) {
Vcs.getInstance(project).showErrors(Collections.singletonList(ex), Bundle.getString("merge.retrieving.branches"));
}
}
};
listener.actionPerformed(null);
RootChooser.addActionListener(listener);
}
}
/**
* Get root from the chooser
*
* @param RootChooser the chooser constructed with {@link #setupRootChooser(Project, List, VirtualFile, JComboBox, JLabel)}.
* @return the current selection
*/
public static VirtualFile getRootFromRootChooser(JComboBox RootChooser) {
return (VirtualFile)RootChooser.getSelectedItem();
}
/**
* Show error associated with the specified operation
*
* @param project the project
* @param ex the exception
* @param operation the operation name
*/
public static void showOperationError(final Project project, final VcsException ex, @NonNls @NotNull final String operation) {
showOperationError(project, operation, ex.getMessage());
}
/**
* Show errors associated with the specified operation
*
* @param project the project
* @param exs the exceptions to show
* @param operation the operation name
*/
public static void showOperationErrors(final Project project,
final Collection<VcsException> exs,
@NonNls @NotNull final String operation) {
if (exs.size() == 1) {
//noinspection ThrowableResultOfMethodCallIgnored
showOperationError(project, operation, exs.iterator().next().getMessage());
}
else if (exs.size() > 1) {
// TODO use dialog in order to show big messages
StringBuilder b = new StringBuilder();
for (VcsException ex : exs) {
b.append(Bundle.message("errors.message.item", ex.getMessage()));
}
showOperationError(project, operation, Bundle.message("errors.message", b.toString()));
}
}
/**
* Show error associated with the specified operation
*
* @param project the project
* @param message the error description
* @param operation the operation name
*/
public static void showOperationError(final Project project, final String operation, final String message) {
Messages.showErrorDialog(project, message, Bundle.message("error.occurred.during", operation));
}
/**
* Show errors on the tab
*
* @param project the context project
* @param title the operation title
* @param errors the errors to display
*/
public static void showTabErrors(Project project, String title, List<VcsException> errors) {
AbstractVcsHelper.getInstance(project).showErrors(errors, title);
}
/**
* Setup remotes combobox. The default remote for the current branch is selected by default.
* This method gets current branch for the project.
*
* @param project the project
* @param root the root
* @param remoteCombobox the combobox to update
* @param fetchUrl if true, the fetch url is shown instead of push url
*/
public static void setupRemotes(final Project project, final VirtualFile root, final JComboBox remoteCombobox, final boolean fetchUrl) {
Branch Branch = null;
try {
Branch = Branch.current(project, root);
}
catch (VcsException ex) {
// ignore error
}
final String branch = Branch != null ? Branch.getName() : null;
setupRemotes(project, root, branch, remoteCombobox, fetchUrl);
}
/**
* Setup remotes combobox. The default remote for the current branch is selected by default.
*
* @param project the project
* @param root the root
* @param currentBranch the current branch
* @param remoteCombobox the combobox to update
* @param fetchUrl if true, the fetch url is shown for remotes, push otherwise
*/
public static void setupRemotes(final Project project,
final VirtualFile root,
final String currentBranch,
final JComboBox remoteCombobox,
final boolean fetchUrl) {
try {
List<Remote> remotes = Remote.list(project, root);
String remote = null;
if (currentBranch != null) {
remote = ConfigUtil.getValue(project, root, "branch." + currentBranch + ".remote");
}
remoteCombobox.setRenderer(getRemoteListCellRenderer(remote, fetchUrl, remoteCombobox.getRenderer()));
Remote toSelect = null;
remoteCombobox.removeAllItems();
for (Remote r : remotes) {
remoteCombobox.addItem(r);
if (r.name().equals(remote)) {
toSelect = r;
}
}
if (toSelect != null) {
remoteCombobox.setSelectedItem(toSelect);
}
}
catch (VcsException e) {
Vcs.getInstance(project).showErrors(Collections.singletonList(e), Bundle.getString("pull.retrieving.remotes"));
}
}
/**
* Checks state of the {@code checked} checkbox and if state is {@code checkedState} than to disable {@code changed}
* checkbox and change its state to {@code impliedState}. When the {@code checked} checkbox changes states to other state,
* than enable {@code changed} and restore its state. Note that the each checkbox should be implied by only one other checkbox.
*
* @param checked the checkbox to monitor
* @param checkedState the state that triggers disabling changed state
* @param changed the checkbox to change
* @param impliedState the implied state of checkbox
*/
public static void imply(final JCheckBox checked, final boolean checkedState, final JCheckBox changed, final boolean impliedState) {
ActionListener l = new ActionListener() {
Boolean previousState;
public void actionPerformed(ActionEvent e) {
if (checked.isSelected() == checkedState) {
if (previousState == null) {
previousState = changed.isSelected();
}
changed.setEnabled(false);
changed.setSelected(impliedState);
}
else {
changed.setEnabled(true);
if (previousState != null) {
changed.setSelected(previousState);
previousState = null;
}
}
}
};
checked.addActionListener(l);
l.actionPerformed(null);
}
/**
* Declares states for two checkboxes to be mutually exclusive. When one of the checkboxes goes to the specified state, other is
* disabled and forced into reverse of the state (to prevent very fast users from selecting incorrect state or incorrect
* initial configuration).
*
* @param first the first checkbox
* @param firstState the state of the first checkbox
* @param second the second checkbox
* @param secondState the state of the second checkbox
*/
public static void exclusive(final JCheckBox first, final boolean firstState, final JCheckBox second, final boolean secondState) {
ActionListener l = new ActionListener() {
/**
* One way check for the condition
* @param checked the first to check
* @param checkedState the state to match
* @param changed the changed control
* @param impliedState the implied state
*/
private void check(final JCheckBox checked, final boolean checkedState, final JCheckBox changed, final boolean impliedState) {
if (checked.isSelected() == checkedState) {
changed.setSelected(impliedState);
changed.setEnabled(false);
}
else {
changed.setEnabled(true);
}
}
/**
* {@inheritDoc}
*/
public void actionPerformed(ActionEvent e) {
check(first, firstState, second, !secondState);
check(second, secondState, first, !firstState);
}
};
first.addActionListener(l);
second.addActionListener(l);
l.actionPerformed(null);
}
/**
* Checks state of the {@code checked} checkbox and if state is {@code checkedState} than to disable {@code changed}
* text field and clean it. When the {@code checked} checkbox changes states to other state,
* than enable {@code changed} and restore its state. Note that the each text field should be implied by
* only one other checkbox.
*
* @param checked the checkbox to monitor
* @param checkedState the state that triggers disabling changed state
* @param changed the checkbox to change
*/
public static void implyDisabled(final JCheckBox checked, final boolean checkedState, final JTextField changed) {
ActionListener l = new ActionListener() {
String previousState;
public void actionPerformed(ActionEvent e) {
if (checked.isSelected() == checkedState) {
if (previousState == null) {
previousState = changed.getText();
}
changed.setEnabled(false);
changed.setText("");
}
else {
changed.setEnabled(true);
if (previousState != null) {
changed.setText(previousState);
previousState = null;
}
}
}
};
checked.addActionListener(l);
l.actionPerformed(null);
}
/**
* Handles a low-level execution exception.
* Checks that executable is valid. If it is not, then shows proper notification with an option to fix the path to ClearCase executables.
* If it's valid, then we don't know what could happen and just display the general error notification.
*/
public static void checkExecutableAndShowNotification(final Project project, VcsException e) {
if (Vcs.getInstance(project).getExecutableValidator().checkExecutableAndNotifyIfNeeded()) {
Notification notification = new Notification(Vcs.NOTIFICATION_GROUP_ID, Bundle.getString("general.error"), e.getLocalizedMessage(), NotificationType.ERROR);
Notifications.Bus.notify(notification, project);
}
}
}