/* * 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.content.detail; import com.hp.alm.ali.idea.model.Field; import com.hp.alm.ali.idea.cfg.EntityFields; import com.hp.alm.ali.idea.translate.TranslateService; import com.hp.alm.ali.idea.content.AliContentFactory; import com.hp.alm.ali.idea.model.Metadata; import com.hp.alm.ali.idea.rest.ServerType; import com.hp.alm.ali.idea.rest.ServerTypeListener; import com.hp.alm.ali.idea.services.MetadataService; import com.hp.alm.ali.idea.ui.viewer.NavigableViewer; import com.hp.alm.ali.idea.ui.viewer.TranslatedViewer; import com.hp.alm.ali.idea.ui.viewer.Viewer; import com.hp.alm.ali.idea.model.Entity; import com.hp.alm.ali.idea.cfg.EntityDetails; import com.hp.alm.ali.idea.entity.CachingEntityListener; import com.hp.alm.ali.idea.entity.EntityRef; import com.hp.alm.ali.idea.services.EntityService; import com.hp.alm.ali.idea.ui.FieldNameLabel; import com.hp.alm.ali.idea.ui.ScrollablePanel; import com.hp.alm.ali.idea.cfg.AliProjectConfiguration; import com.hp.alm.ali.idea.ui.editor.field.HTMLAreaField; import com.hp.alm.ali.idea.rest.RestService; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.wm.ToolWindow; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.ui.components.labels.LinkListener; import com.intellij.util.ui.UIUtil; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; public class PropertyGrid extends ScrollablePanel implements EntityFields.ColumnsChangeListener, CachingEntityListener, ServerTypeListener, HasEntity { private Entity entity; private Map<String, LabelAndValue> map; private List<String> columns; private Project project; private EntityService entityService; private MetadataService metadataService; private TranslateService translateService; private RestService restService; private EntityDetails details; private EntityFields fields; private JComponent notFoundComponent; private GridBagLayout layout; private LinkListener moveUp = new MoveUpListener(); private LinkListener moveDown = new MoveDownListener(); private LinkListener remove = new RemoveListener(); public PropertyGrid(Project project, Entity entity) { super(new GridBagLayout()); this.project = project; this.entity = entity; layout = (GridBagLayout)getLayout(); entityService = project.getComponent(EntityService.class); metadataService = project.getComponent(MetadataService.class); translateService = project.getComponent(TranslateService.class); restService = project.getComponent(RestService.class); AliProjectConfiguration conf = project.getComponent(AliProjectConfiguration.class); fields = conf.getFields(entity.getType()); details = conf.getDetails(); entityService.addEntityListener(this); restService.addServerTypeListener(this); setBackground(new JTextPane().getBackground()); setBorder(new EmptyBorder(2, 2, 0, 0)); map = new HashMap<String, LabelAndValue>(); fields.addColumnsChangeListener(this); columns = fields.getColumns(); updateColumns(); } private boolean isFirst(String column) { return columns.indexOf(column) == 0; } private boolean isLast(String column) { return columns.indexOf(column) == columns.size() - 1; } public void columnsChanged(String columnToFocus) { ApplicationManager.getApplication().assertIsDispatchThread(); this.columns = fields.getColumns(); if(!isNotFoundShowing()) { updateColumns(); } } private GridBagConstraints getKeyConstraints(int idx, boolean longField) { GridBagConstraints clone = new GridBagConstraints(); if(longField) { clone.gridwidth = 2; clone.weightx = 1; } else { clone.gridwidth = 1; clone.weightx = 0.0; } clone.gridx = 0; clone.gridy = idx; clone.fill = GridBagConstraints.HORIZONTAL; clone.anchor = GridBagConstraints.NORTH; return clone; } private GridBagConstraints getValueConstraints(int idx, boolean longField) { GridBagConstraints clone = new GridBagConstraints(); if(longField) { clone.gridx = 0; clone.gridy = idx + 1; clone.gridwidth = 2; clone.weightx = 1; } else { clone.gridx = 1; clone.gridy = idx; clone.gridwidth = 1; clone.weightx = 1; } clone.anchor = GridBagConstraints.CENTER; clone.fill = GridBagConstraints.BOTH; return clone; } private void updateColumns() { metadataService.loadEntityMetadataAsync(entity.getType(), new MetadataService.MetadataCallback() { @Override public void metadataLoaded(final Metadata metadata) { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { updateColumns(metadata); } }); } @Override public void metadataFailed() { } }); } private void updateColumns(Metadata metadata) { boolean needsRefresh = false; if(columns.isEmpty() && restService.getServerTypeIfAvailable().isConnected()) { List<String> defaultColumns = restService.getServerStrategy().getDefaultFields(entity.getType()); if(!defaultColumns.isEmpty()) { fields.setColumns(defaultColumns); return; // will process the column changed event } } for(int i = 0; i < columns.size(); i++) { final String property = columns.get(i); String value = entity.getPropertyValue(property); if (!entity.isInitialized(property)) { if(!entity.isComplete() && metadata.getField(property) != null) { value = "<html><body><i>Retrieving information...</i></body></html>"; needsRefresh = true; } else { value = "<html><body><i>Couldn't retrieve value...</i></body></html>"; } } String newValue = HTMLAreaField.removeSmallFont(value); final LabelAndValue labelAndValue = map.get(property); if(labelAndValue != null) { // update existing component labelAndValue.label.update(); labelAndValue.value.setValue(newValue); shrinkOrExpand(i, labelAndValue.label, labelAndValue.value.getComponent()); } else { // adding new column final ColumnLabelPanel panelWithLabel = new ColumnLabelPanel(property); final Viewer viewer; Field field = metadata.getField(property); if(field != null && translateService.isTranslated(field)) { viewer = new TranslatedViewer(project, this, field, value); } else { viewer = new NavigableViewer(project, value); } viewer.getComponent().addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { int i = columns.indexOf(property); if (i >= 0) { shrinkOrExpand(i, panelWithLabel, viewer.getComponent()); } } }); panelWithLabel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if(e.getClickCount() > 1 && layout.getConstraints(panelWithLabel).gridwidth > 1) { viewer.getComponent().setVisible(!viewer.getComponent().isVisible()); } } }); add(panelWithLabel, getKeyConstraints(i * 2, false)); add(viewer.getComponent(), getValueConstraints(i * 2, false)); shrinkOrExpand(i, panelWithLabel, viewer.getComponent()); map.put(property, new LabelAndValue(panelWithLabel, viewer)); } } HashSet<String> all = new HashSet<String>(map.keySet()); all.removeAll(columns); for(String column: all) { removeNamed(column); } // make sure DelegatingScrollPane size is adjusted as needed revalidate(); repaint(); if(needsRefresh) { entityService.getEntityAsync(new EntityRef(entity), null); } } private void shrinkOrExpand(int i, Component label, Component value) { Dimension size = value.getPreferredSize(); int[][] dim = layout.getLayoutDimensions(); if(dim.length == 0 || dim[0].length == 0) { return; } boolean idxChanged = layout.getConstraints(label).gridy != i * 2; if (size.height <= 20 && size.width + dim[0][0] <= getWidth() && (layout.getConstraints(value).gridwidth > 1 || idxChanged)) { // shrink back layout.setConstraints(label, getKeyConstraints(i * 2, false)); layout.setConstraints(value, getValueConstraints(i * 2, false)); } else if(size.height > 23 && (layout.getConstraints(value).gridwidth == 1 || idxChanged)) { // 23 is combo height // expand layout.setConstraints(label, getKeyConstraints(i * 2, true)); layout.setConstraints(value, getValueConstraints(i * 2, true)); } } private boolean isNotFoundShowing() { return notFoundComponent != null && notFoundComponent.getParent() != null; } private void removeNamed(String col) { LabelAndValue labelAndValue = map.remove(col); remove(labelAndValue.label); remove(labelAndValue.value.getComponent()); } @Override public void entityLoaded(Entity entity, Event event) { if(entity.equals(this.entity)) { this.entity = entity; } else if("release-backlog-item".equals(entity.getType()) && this.entity.getType().equals(entity.getPropertyValue("entity-type")) && this.entity.getId() == Integer.valueOf(entity.getPropertyValue("entity-id"))) { this.entity.mergeRelatedEntity(entity); } else { return; } UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { if(isNotFoundShowing()) { remove(notFoundComponent); } updateColumns(); } }); } @Override public void entityNotFound(EntityRef ref, boolean removed) { noEntity(ref, false, removed); } private void noEntity(EntityRef ref, final boolean loading, final boolean removed) { if(ref.equals(new EntityRef(entity))) { UIUtil.invokeLaterIfNeeded(new Runnable() { public void run() { if(removed) { ToolWindow toolWindow = AliContentFactory.getDetailToolWindow(project); toolWindow.getContentManager().removeContent(AliContentFactory.findContent(toolWindow, entity), true); } else { if(!isNotFoundShowing()) { if(notFoundComponent == null) { notFoundComponent = HTMLAreaField.createTextPane("<html><body><i>"+(loading?"Loading entity...":"Failed to load entity")+"</i></body></html>"); } removeAll(); map.clear(); add(notFoundComponent); } } } }); } } public Entity lookup(EntityRef ref) { if(ref.equals(new EntityRef(entity))) { return entity; } else { return null; } } @Override public void connectedTo(ServerType serverType) { if(serverType.isConnected()) { entityService.getEntityAsync(new EntityRef(entity), null); } else { noEntity(new EntityRef(entity), serverType == ServerType.CONNECTING, false); } } public Entity getEntity() { return entity; } public void remove() { entityService.removeEntityListener(this); restService.removeServerTypeListener(this); fields.removeColumnsChangeListener(this); details.removeRef(new EntityRef(entity)); } private class ColumnLabelPanel extends JPanel { private ColumnControls controls; private FieldNameLabel myLabel; public ColumnLabelPanel(String col) { super(new BorderLayout()); setBorder(BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder(0, 0, 2, 0, PropertyGrid.this.getBackground()), // align with single line values (that are higher by default) BorderFactory.createEtchedBorder())); myLabel = new FieldNameLabel(entity.getType(), col, metadataService); myLabel.setHorizontalAlignment(SwingConstants.CENTER); add(myLabel, BorderLayout.WEST); controls = new ColumnControls(col); add(controls, BorderLayout.EAST); update(); } public void update() { controls.update(); } } private class ColumnControls extends JPanel { private LinkLabel moveDownLabel; private LinkLabel moveUpLabel; private LinkLabel deleteLabel; private String column; public ColumnControls(String col) { super(new GridLayout(1, 0, 5, 0)); this.column = col; setBorder(new EmptyBorder(0, 0, 0, 5)); moveDownLabel = new LinkLabel("", IconLoader.getIcon("/actions/sortDesc.png"), moveDown, col); add(moveDownLabel); moveUpLabel = new LinkLabel("", IconLoader.getIcon("/actions/sortAsc.png"), moveUp, col); add(moveUpLabel); deleteLabel = new LinkLabel("", IconLoader.getIcon("/modules/deleteContentFolder.png"), remove, col); add(deleteLabel); update(); } public void update() { boolean show = details.isColumnControls(); if(show) { boolean first = isFirst(column); boolean last = isLast(column); moveDownLabel.setVisible(!last); moveUpLabel.setVisible(!first); deleteLabel.setVisible(!(first && last)); setVisible(true); } else { setVisible(false); } } } private class RemoveListener implements LinkListener { public void linkSelected(LinkLabel linkLabel, Object o) { fields.removeColumn((String)o); } } private class MoveDownListener implements LinkListener { public void linkSelected(LinkLabel linkLabel, Object o) { fields.moveColumn((String)o, 1); } } private class MoveUpListener implements LinkListener { public void linkSelected(LinkLabel linkLabel, Object o) { fields.moveColumn((String)o, -1); } } private static class LabelAndValue { PropertyGrid.ColumnLabelPanel label; Viewer value; public LabelAndValue(ColumnLabelPanel label, Viewer value) { this.label = label; this.value = value; } } }