/*
* Copyright 2013 Hewlett-Packard Development Company, L.P
*
* 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.hp.alm.ali.idea.checkin;
import com.hp.alm.ali.idea.entity.EntityAdapter;
import com.hp.alm.ali.idea.filter.FilterChooser;
import com.hp.alm.ali.idea.entity.edit.LockingStrategy;
import com.hp.alm.ali.idea.rest.RestService;
import com.hp.alm.ali.idea.rest.ServerType;
import com.hp.alm.ali.idea.rest.ServerTypeListener;
import com.hp.alm.ali.idea.services.AbstractCachingService;
import com.hp.alm.ali.idea.services.EntityLabelService;
import com.hp.alm.ali.idea.services.MetadataService;
import com.hp.alm.ali.idea.services.ProjectListService;
import com.hp.alm.ali.idea.services.ProjectUserService;
import com.hp.alm.ali.idea.ui.ChooseEntityTypePopup;
import com.hp.alm.ali.idea.model.Entity;
import com.hp.alm.ali.idea.entity.EntityRef;
import com.hp.alm.ali.idea.services.EntityService;
import com.hp.alm.ali.idea.model.Field;
import com.hp.alm.ali.idea.services.ActiveItemService;
import com.hp.alm.ali.idea.entity.EntityListener;
import com.hp.alm.ali.idea.cfg.AliProjectConfiguration;
import com.hp.alm.ali.idea.ui.editor.field.CommentField;
import com.hp.alm.ali.idea.model.Metadata;
import com.hp.alm.ali.idea.util.ApplicationUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.editor.actions.ContentChooser;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vcs.CheckinProjectPanel;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.checkin.CheckinHandler;
import com.intellij.openapi.vcs.ui.RefreshableOnComponent;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.UIUtil;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AliCheckinHandler extends CheckinHandler implements ActionListener, DocumentListener, ActiveItemService.Listener, ServerTypeListener {
private JPanel header;
private JCheckBox markFixed;
private JComboBox markFixedSelection;
private ActionToolbar toolbar;
private JCheckBox addComment;
private JPanel panelComment;
private JScrollPane commentPane;
private JTextArea comment;
private JPanel panel;
private CheckinProjectPanel checkinProjectPanel;
private Project project;
private RestService restService;
private EntityService entityService;
private EntityLabelService entityLabelService;
private ActiveItemService activeItemService;
private EntityRef ref;
private AliProjectConfiguration projConf;
private String lastAddedComment = null;
private String originalMessage;
public AliCheckinHandler(CheckinProjectPanel checkinProjectPanel) {
this.checkinProjectPanel = checkinProjectPanel;
this.project = checkinProjectPanel.getProject();
this.restService = project.getComponent(RestService.class);
this.entityService = project.getComponent(EntityService.class);
this.entityLabelService = project.getComponent(EntityLabelService.class);
this.activeItemService = project.getComponent(ActiveItemService.class);
this.ref = activeItemService.getActiveItem();
this.originalMessage = checkinProjectPanel.getCommitMessage();
this.projConf = project.getComponent(AliProjectConfiguration.class);
panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createTitledBorder("HP ALI"));
activeItemService.addListener(this);
restService.addServerTypeListener(this);
doConnectedTo(restService.getServerTypeIfAvailable());
}
private void setupPanel(final CheckinProjectPanel checkinProjectPanel, final EntityRef ref) {
if (!RepositoryCheckinHelper.isMergingCommit(checkinProjectPanel)) {
final String prefix = restService.getServerStrategy().getCheckinPrefix(ref);
if (!originalMessage.startsWith(prefix)) {
// only pre-fill message if current message doesn't refer to the correct item
entityService.requestCachedEntity(ref, Arrays.asList("name"), new EntityAdapter() {
@Override
public void entityLoaded(final Entity entity, Event event) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (checkinProjectPanel.getCommitMessage().equals(originalMessage)) {
// only if message hasn't changed yet
checkinProjectPanel.setCommitMessage(prefix + " " + entity.getPropertyValue("name"));
// store original message in order to allow revert back
VcsConfiguration.getInstance(project).saveCommitMessage(originalMessage);
}
}
});
}
});
}
}
markFixed = new JCheckBox("Mark " + ref.toString() + " as ");
if(ref.type.equals("defect")) {
markFixedSelection = new JComboBox();
markFixed.setEnabled(false);
markFixedSelection.setEnabled(false);
JPanel jPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
jPanel.add(markFixed);
jPanel.add(markFixedSelection);
panel.add(jPanel, BorderLayout.NORTH);
EntityListener callback = new EntityAdapter() {
@Override
public void entityLoaded(Entity entity, Event event) {
final List<String> allowedTransitions = projConf.getStatusTransitions().getAllowedTransitions(entity.getPropertyValue("status"));
Metadata metadata = project.getComponent(MetadataService.class).getEntityMetadata(ref.type);
final Field field = metadata.getAllFields().get("status");
final List<String> list = project.getComponent(ProjectListService.class).getProjectList(ref.type, field);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
public void run() {
for(String state: list) {
if(allowedTransitions.contains(state)) {
markFixedSelection.addItem(state);
}
}
if(markFixedSelection.getItemCount() > 0) {
markFixedSelection.setSelectedIndex(0);
markFixed.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
markFixedSelection.setEnabled(markFixed.isSelected());
}
});
markFixed.setEnabled(true);
markFixedSelection.setEnabled(true);
}
}
});
}
};
entityService.requestCachedEntity(ref, Arrays.asList("status"), callback);
}
comment = new JTextArea();
comment.setLineWrap(true);
comment.setWrapStyleWord(true);
comment.setBorder(BorderFactory.createEtchedBorder());
comment.getDocument().addDocumentListener(this);
final List<String> comments = projConf.getComments();
if(!comments.isEmpty()) {
Collections.reverse(comments);
comment.setText(comments.get(0));
}
header = new JPanel(new BorderLayout());
addComment = new JCheckBox("Add comment to "+ref.toString());
addComment.addActionListener(this);
header.add(addComment, BorderLayout.WEST);
// make sure checkboxes refer to correct entity label (requirement vs. User Story)
entityLabelService.loadEntityLabelAsync(ref.type, new AbstractCachingService.DispatchCallback<String>() {
@Override
public void loaded(String entityLabel) {
markFixed.setText("Mark " + entityLabel + " #" + ref.id + " as ");
addComment.setText("Add comment to " + entityLabel + " #" + ref.id);
}
});
DefaultActionGroup group = new DefaultActionGroup();
group.add(new AnAction("Choose Message", "Choose Recent Message", IconLoader.getIcon("/actions/consoleHistory.png")) {
public void update(AnActionEvent e) {
super.update(e);
e.getPresentation().setEnabled(!comments.isEmpty());
}
public void actionPerformed(AnActionEvent e) {
ContentChooser<String> chooser = new ContentChooser<String>(project, "Choose Message", false) {
protected void removeContentAt(final String content) {
projConf.getComments().remove(content);
}
protected String getStringRepresentationFor(String content) {
return content;
}
protected List<String> getContents() {
return comments;
}
};
chooser.show();
if (chooser.isOK()) {
if(!addComment.isSelected()) {
addComment.setSelected(true);
showCommentPane();
}
int selectedIndex = chooser.getSelectedIndex();
if (selectedIndex >= 0) {
comment.setText(chooser.getAllContents().get(selectedIndex));
}
}
}
});
toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
header.add(toolbar.getComponent(), BorderLayout.EAST);
panelComment = new JPanel(new BorderLayout());
panelComment.add(header, BorderLayout.NORTH);
commentPane = new JBScrollPane(comment);
panel.add(panelComment, BorderLayout.CENTER);
// requirement doesn't show up in Idea12 unless explicitly revalidated
panel.revalidate();
}
private void removeListeners() {
activeItemService.removeListener(this);
restService.removeServerTypeListener(this);
}
public void checkinFailed(List<VcsException> exception) {
removeListeners();
}
public void checkinSuccessful() {
removeListeners();
if(markFixed == null) {
// no associated work item
return;
}
if(markFixed.isSelected() || (addComment.isSelected() && !comment.getText().isEmpty())) {
EntityService entityService = project.getComponent(EntityService.class);
LockingStrategy lockingStrategy = restService.getServerStrategy().getLockingStrategy();
Entity entity = lockingStrategy.lock(ref.toEntity());
if(entity != null) {
Set<String> modified = new HashSet<String>();
if(markFixed.isSelected()) {
String value = markFixedSelection.getSelectedItem().toString();
if(!value.equals(entity.getProperty("status"))) {
entity.setProperty("status", value);
modified.add("status");
}
}
if(addComment.isSelected() && !comment.getText().isEmpty()) {
String userName = project.getComponent(AliProjectConfiguration.class).getUsername();
String fullName = project.getComponent(ProjectUserService.class).getUserFullName(userName);
String commentProperty = entity.isInitialized("dev-comments")? "dev-comments": "comments";
String mergedComment = CommentField.mergeComment(entity.getPropertyValue(commentProperty), comment.getText(), userName, fullName);
entity.setProperty(commentProperty, mergedComment);
modified.add(commentProperty);
}
entityService.updateEntity(entity, modified, false);
lockingStrategy.unlock(entity);
}
}
}
public RefreshableOnComponent getAfterCheckinConfigurationPanel(Disposable disposable) {
return new RefreshableOnComponent() {
public JComponent getComponent() {
return panel;
}
public void refresh() {
}
public void saveState() {
}
public void restoreState() {
}
};
}
private void showCommentPane() {
panelComment.add(commentPane, BorderLayout.CENTER);
panelComment.setSize(new Dimension(panel.getWidth() - 20, 120));
panelComment.setPreferredSize(new Dimension(panel.getWidth() - 20, 120));
panelComment.setMaximumSize(new Dimension(panel.getWidth() - 20, 120));
panelComment.revalidate();
panelComment.repaint();
}
public void actionPerformed(ActionEvent actionEvent) {
if(addComment.isSelected()) {
showCommentPane();
} else {
panelComment.remove(commentPane);
panelComment.setPreferredSize(null);
panelComment.revalidate();
panelComment.repaint();
}
}
public void insertUpdate(DocumentEvent documentEvent) {
storeComment();
}
public void removeUpdate(DocumentEvent documentEvent) {
storeComment();
}
public void changedUpdate(DocumentEvent documentEvent) {
storeComment();
}
private void storeComment() {
if(lastAddedComment != null) {
projConf.removeComment(lastAddedComment);
}
if(projConf.addComment(comment.getText())) {
lastAddedComment = comment.getText();
}
}
public void onActivated(EntityRef ref) {
if(ref != null) {
this.ref = ref;
connectedTo(restService.getServerTypeIfAvailable());
}
}
@Override
public void connectedTo(final ServerType serverType) {
ApplicationUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (!panel.isShowing()) {
// there is no cancel event available to the checkin handler, instead we try to detect obsolete listener
// invocation and cleanup
removeListeners();
} else {
doConnectedTo(serverType);
}
}
});
}
private void doConnectedTo(ServerType serverType) {
panel.removeAll();
if(!serverType.isConnected()) {
if (serverType == ServerType.CONNECTING) {
panel.add(new JLabel("Connecting..."));
} else {
panel.add(new JLabel("Not connected to server"));
}
} else if(ref == null) {
DefaultActionGroup group = new DefaultActionGroup();
ChooseEntityTypeAction choose = new ChooseEntityTypeAction(project, panel, Arrays.asList("defect", "requirement"), new ChooseEntityTypePopup.Listener() {
@Override
public void selected(final String entityType) {
FilterChooser popup = restService.getServerStrategy().getFilterChooser(entityType, false, true, false, null);
popup.show();
String selectedId = popup.getSelectedValue();
if (selectedId != null && !selectedId.isEmpty()) {
activeItemService.activate(new Entity(entityType, Integer.valueOf(selectedId)), true, false);
}
}
});
group.add(choose);
panel.add(new JLabel("Not associated with any work item"), BorderLayout.WEST);
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
panel.add(toolbar.getComponent(), BorderLayout.EAST);
choose.setComponent(toolbar.getComponent());
} else {
setupPanel(checkinProjectPanel, ref);
}
}
public static class ChooseEntityTypeAction extends AnAction {
private static Icon icon = IconLoader.getIcon("/actions/quickList.png");
private Project project;
private JComponent component;
private List<String> entityTypes;
private ChooseEntityTypePopup.Listener listener;
public ChooseEntityTypeAction(Project project, JComponent component, List<String> entityTypes, ChooseEntityTypePopup.Listener listener) {
this.project = project;
this.component = component;
this.entityTypes = entityTypes;
this.listener = listener;
getTemplatePresentation().setIcon(icon);
}
public void setComponent(JComponent component) {
this.component = component;
}
@Override
public void actionPerformed(AnActionEvent anActionEvent) {
ChooseEntityTypePopup popup = new ChooseEntityTypePopup(project, entityTypes, listener);
popup.showOrInvokeDirectly(component, 0, 0);
}
}
}