edit cloud project

This commit is contained in:
Branislav Stojkovic
2016-10-31 22:00:02 +01:00
parent ad5bdbe22d
commit 5981f96916
20 changed files with 479 additions and 99 deletions
@@ -1,8 +1,10 @@
package org.diylc.common; package org.diylc.common;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.diylc.core.IPropertyValidator; import org.diylc.core.IPropertyValidator;
import org.diylc.core.annotations.EditableProperty;
/** /**
* Entity class for editable properties extracted from component objects. Represents a single * Entity class for editable properties extracted from component objects. Represents a single
@@ -21,9 +23,12 @@ public class PropertyWrapper implements Cloneable {
private IPropertyValidator validator; private IPropertyValidator validator;
private boolean unique = true; private boolean unique = true;
private boolean changed = false; private boolean changed = false;
private String additionalOptions;
private int sortOrder;
private String[] listItems;
public PropertyWrapper(String name, Class<?> type, String getter, String setter, boolean defaultable, public PropertyWrapper(String name, Class<?> type, String getter, String setter, boolean defaultable,
IPropertyValidator validator) { IPropertyValidator validator, String additionalOptions, int sortOrder) {
super(); super();
this.name = name; this.name = name;
this.type = type; this.type = type;
@@ -31,11 +36,18 @@ public class PropertyWrapper implements Cloneable {
this.setter = setter; this.setter = setter;
this.defaultable = defaultable; this.defaultable = defaultable;
this.validator = validator; this.validator = validator;
this.additionalOptions = additionalOptions;
this.sortOrder = sortOrder;
} }
public void readFrom(Object object) throws IllegalArgumentException, IllegalAccessException, public void readFrom(Object object) throws IllegalArgumentException, IllegalAccessException,
InvocationTargetException, SecurityException, NoSuchMethodException { InvocationTargetException, SecurityException, NoSuchMethodException {
this.value = object.getClass().getMethod(getter).invoke(object); this.value = object.getClass().getMethod(getter).invoke(object);
if (additionalOptions.startsWith(EditableProperty.DYNAMIC_LIST)) {
String functionName = additionalOptions.substring(EditableProperty.DYNAMIC_LIST.length());
Method listMethod = object.getClass().getMethod(functionName);
listItems = (String[]) listMethod.invoke(object);
}
} }
// public void readUniqueFrom(IDIYComponent component) // public void readUniqueFrom(IDIYComponent component)
@@ -92,6 +104,22 @@ public class PropertyWrapper implements Cloneable {
this.changed = changed; this.changed = changed;
} }
public String getAdditionalOptions() {
return additionalOptions;
}
public int getSortOrder() {
return sortOrder;
}
public String[] getListItems() {
return listItems;
}
public void setOptions(String[] listItems) {
this.listItems = listItems;
}
// @Override // @Override
// public Object clone() throws CloneNotSupportedException { // public Object clone() throws CloneNotSupportedException {
// // Try to invoke clone method on value if possible. // // Try to invoke clone method on value if possible.
@@ -105,7 +133,8 @@ public class PropertyWrapper implements Cloneable {
@Override @Override
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
PropertyWrapper clone = PropertyWrapper clone =
new PropertyWrapper(this.name, this.type, this.getter, this.setter, this.defaultable, this.validator); new PropertyWrapper(this.name, this.type, this.getter, this.setter, this.defaultable, this.validator,
this.additionalOptions, this.sortOrder);
clone.value = this.value; clone.value = this.value;
clone.changed = this.changed; clone.changed = this.changed;
clone.unique = this.unique; clone.unique = this.unique;
@@ -116,11 +145,11 @@ public class PropertyWrapper implements Cloneable {
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + ((additionalOptions == null) ? 0 : additionalOptions.hashCode());
result = prime * result + (defaultable ? 1231 : 1237); result = prime * result + (defaultable ? 1231 : 1237);
result = prime * result + ((getter == null) ? 0 : getter.hashCode()); result = prime * result + ((getter == null) ? 0 : getter.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((setter == null) ? 0 : setter.hashCode()); result = prime * result + ((setter == null) ? 0 : setter.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result; return result;
} }
@@ -133,6 +162,11 @@ public class PropertyWrapper implements Cloneable {
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
PropertyWrapper other = (PropertyWrapper) obj; PropertyWrapper other = (PropertyWrapper) obj;
if (additionalOptions == null) {
if (other.additionalOptions != null)
return false;
} else if (!additionalOptions.equals(other.additionalOptions))
return false;
if (defaultable != other.defaultable) if (defaultable != other.defaultable)
return false; return false;
if (getter == null) { if (getter == null) {
@@ -54,7 +54,7 @@ public class Project implements Serializable {
gridSpacing = DEFAULT_GRID_SPACING; gridSpacing = DEFAULT_GRID_SPACING;
} }
@EditableProperty(defaultable = false) @EditableProperty(defaultable = false, sortOrder = 1)
public String getTitle() { public String getTitle() {
return title; return title;
} }
@@ -63,7 +63,7 @@ public class Project implements Serializable {
this.title = title; this.title = title;
} }
@EditableProperty @EditableProperty(sortOrder = 3)
public String getAuthor() { public String getAuthor() {
return author; return author;
} }
@@ -72,7 +72,7 @@ public class Project implements Serializable {
this.author = author; this.author = author;
} }
@EditableProperty(defaultable = false) @EditableProperty(defaultable = false, additionalOptions = EditableProperty.MULTI_LINE, sortOrder = 2)
public String getDescription() { public String getDescription() {
return description; return description;
} }
@@ -81,7 +81,7 @@ public class Project implements Serializable {
this.description = description; this.description = description;
} }
@EditableProperty @EditableProperty(sortOrder = 4)
public Size getWidth() { public Size getWidth() {
return width; return width;
} }
@@ -90,7 +90,7 @@ public class Project implements Serializable {
this.width = width; this.width = width;
} }
@EditableProperty @EditableProperty(sortOrder = 5)
public Size getHeight() { public Size getHeight() {
return height; return height;
} }
@@ -99,7 +99,7 @@ public class Project implements Serializable {
this.height = height; this.height = height;
} }
@EditableProperty(name = "Grid spacing", validatorClass = SpacingValidator.class) @EditableProperty(name = "Grid spacing", validatorClass = SpacingValidator.class, sortOrder = 6)
public Size getGridSpacing() { public Size getGridSpacing() {
return gridSpacing; return gridSpacing;
} }
@@ -16,6 +16,18 @@ import org.diylc.core.IPropertyValidator;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface EditableProperty { public @interface EditableProperty {
/**
* Used to designate that a {@link String} property can have multi-lined text.
*/
public static final String MULTI_LINE = "MULTI-LINE";
/**
* Used when property can have one of the values from a dynamic list. In such cases,
* {@link EditableProperty#additionalOptions()} should begin with
* {@link EditableProperty#DYNAMIC_LIST} and contain a method name from the parent object that
* should be called to obtain the {@link String} array with possible options.
*/
public static final String DYNAMIC_LIST = "DYNAMIC_LIST:";
/** /**
* Returns property name. If left blank property name will be extracted from the getter name, e.g. * Returns property name. If left blank property name will be extracted from the getter name, e.g.
* getter <code>getXYZ</code> will induce property name <code>XYZ</code>. <br> * getter <code>getXYZ</code> will induce property name <code>XYZ</code>. <br>
@@ -39,4 +51,19 @@ public @interface EditableProperty {
* @return * @return
*/ */
Class<? extends IPropertyValidator> validatorClass() default AllowAllValidator.class; Class<? extends IPropertyValidator> validatorClass() default AllowAllValidator.class;
/**
* Optional parameter in case we want to force an order of properties.
*
* @return
*/
int sortOrder() default Integer.MAX_VALUE;
/**
* Optional parameter for properties that require additional configuration. This will be passed
* down to the editor factory.
*
* @return
*/
String additionalOptions() default "";
} }
@@ -2,6 +2,8 @@ package org.diylc.plugins.cloud.model;
import java.io.Serializable; import java.io.Serializable;
import org.diylc.core.annotations.EditableProperty;
public class ProjectEntity implements Serializable { public class ProjectEntity implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@@ -11,12 +13,15 @@ public class ProjectEntity implements Serializable {
private String description; private String description;
private String owner; private String owner;
private String category; private String category;
private String categoryForDisplay;
private String updated; private String updated;
private String thumbnailUrl; private String thumbnailUrl;
private String downloadUrl; private String downloadUrl;
private String keywords;
private int viewCount; private int viewCount;
private int downloadCount; private int downloadCount;
private int commentCount; private int commentCount;
private String[] categories;
public ProjectEntity() { public ProjectEntity() {
super(); super();
@@ -30,6 +35,7 @@ public class ProjectEntity implements Serializable {
this.id = id; this.id = id;
} }
@EditableProperty(defaultable = false, sortOrder = 1)
public String getName() { public String getName() {
return name; return name;
} }
@@ -38,6 +44,7 @@ public class ProjectEntity implements Serializable {
this.name = name; this.name = name;
} }
@EditableProperty(defaultable = false, additionalOptions = EditableProperty.MULTI_LINE, sortOrder = 3)
public String getDescription() { public String getDescription() {
return description; return description;
} }
@@ -53,7 +60,7 @@ public class ProjectEntity implements Serializable {
public void setOwner(String owner) { public void setOwner(String owner) {
this.owner = owner; this.owner = owner;
} }
public String getCategory() { public String getCategory() {
return category; return category;
} }
@@ -61,6 +68,23 @@ public class ProjectEntity implements Serializable {
public void setCategory(String category) { public void setCategory(String category) {
this.category = category; this.category = category;
} }
@EditableProperty(defaultable = false, sortOrder = 2, additionalOptions = EditableProperty.DYNAMIC_LIST + "getCategories")
public String getCategoryForDisplay() {
return categoryForDisplay;
}
public void setCategoryForDisplay(String categoryForDisplay) {
this.categoryForDisplay = categoryForDisplay;
}
public String[] getCategories() {
return categories;
}
public void setCategories(String[] categories) {
this.categories = categories;
}
public String getUpdated() { public String getUpdated() {
return updated; return updated;
@@ -110,6 +134,15 @@ public class ProjectEntity implements Serializable {
this.commentCount = commentCount; this.commentCount = commentCount;
} }
@EditableProperty(defaultable = false, sortOrder = 4)
public String getKeywords() {
return keywords;
}
public void setKeywords(String keywords) {
this.keywords = keywords;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
@@ -120,6 +153,7 @@ public class ProjectEntity implements Serializable {
result = prime * result + downloadCount; result = prime * result + downloadCount;
result = prime * result + ((downloadUrl == null) ? 0 : downloadUrl.hashCode()); result = prime * result + ((downloadUrl == null) ? 0 : downloadUrl.hashCode());
result = prime * result + id; result = prime * result + id;
result = prime * result + ((keywords == null) ? 0 : keywords.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((owner == null) ? 0 : owner.hashCode()); result = prime * result + ((owner == null) ? 0 : owner.hashCode());
result = prime * result + ((thumbnailUrl == null) ? 0 : thumbnailUrl.hashCode()); result = prime * result + ((thumbnailUrl == null) ? 0 : thumbnailUrl.hashCode());
@@ -158,6 +192,11 @@ public class ProjectEntity implements Serializable {
return false; return false;
if (id != other.id) if (id != other.id)
return false; return false;
if (keywords == null) {
if (other.keywords != null)
return false;
} else if (!keywords.equals(other.keywords))
return false;
if (name == null) { if (name == null) {
if (other.name != null) if (other.name != null)
return false; return false;
@@ -8,14 +8,18 @@ import java.net.NetworkInterface;
import java.net.URL; import java.net.URL;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.diylc.appframework.miscutils.ConfigurationManager; import org.diylc.appframework.miscutils.ConfigurationManager;
import org.diylc.common.PropertyWrapper;
import org.diylc.plugins.cloud.model.CommentEntity; import org.diylc.plugins.cloud.model.CommentEntity;
import org.diylc.plugins.cloud.model.IServiceAPI; import org.diylc.plugins.cloud.model.IServiceAPI;
import org.diylc.plugins.cloud.model.ProjectEntity; import org.diylc.plugins.cloud.model.ProjectEntity;
import org.diylc.plugins.cloud.model.UserEntity; import org.diylc.plugins.cloud.model.UserEntity;
import org.diylc.presenter.ComparatorFactory;
import org.diylc.presenter.ComponentProcessor;
import com.diyfever.httpproxy.PhpFlatProxy; import com.diyfever.httpproxy.PhpFlatProxy;
import com.diyfever.httpproxy.ProxyFactory; import com.diyfever.httpproxy.ProxyFactory;
@@ -153,6 +157,24 @@ public class CloudPresenter {
} }
} }
public void updateProjectDetails(ProjectEntity project, String diylcVersion) throws IOException, CloudException {
String username = ConfigurationManager.getInstance().readString(USERNAME_KEY, null);
String token = ConfigurationManager.getInstance().readString(TOKEN_KEY, null);
if (username == null || token == null)
throw new CloudException("Login failed. Please try to login again.");
try {
String res =
getService().uploadProject(username, token, getMachineId(), project.getName(), project.getCategoryForDisplay(),
project.getDescription(), diylcVersion, project.getKeywords(), null, null, project.getId());
if (!res.equals(SUCCESS))
throw new CloudException(res);
} catch (Exception e) {
throw new CloudException(e);
}
}
public void deleteProject(int projectId) throws CloudException { public void deleteProject(int projectId) throws CloudException {
String username = ConfigurationManager.getInstance().readString(USERNAME_KEY, null); String username = ConfigurationManager.getInstance().readString(USERNAME_KEY, null);
String token = ConfigurationManager.getInstance().readString(TOKEN_KEY, null); String token = ConfigurationManager.getInstance().readString(TOKEN_KEY, null);
@@ -282,14 +304,15 @@ public class CloudPresenter {
LOG.info("Received " + projects.size() + " results. Downloading thumbnails..."); LOG.info("Received " + projects.size() + " results. Downloading thumbnails...");
// Download thumbnails and replace urls with local paths to speed up loading in the main // Download thumbnails and replace urls with local paths to speed up loading in the main
// thread // thread
for (int i = 0; i < projects.size(); i++) { for (ProjectEntity project : projects) {
String url = projects.get(i).getThumbnailUrl(); String url = project.getThumbnailUrl();
URL website = new URL(url); URL website = new URL(url);
ReadableByteChannel rbc = Channels.newChannel(website.openStream()); ReadableByteChannel rbc = Channels.newChannel(website.openStream());
File temp = File.createTempFile("thumbnail", ".png"); File temp = File.createTempFile("thumbnail", ".png");
FileOutputStream fos = new FileOutputStream(temp); FileOutputStream fos = new FileOutputStream(temp);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
projects.get(i).setThumbnailUrl(temp.getAbsolutePath()); project.setThumbnailUrl(temp.getAbsolutePath());
project.setCategories(getCategories());
fos.close(); fos.close();
} }
LOG.info("Finished downloading thumbnails."); LOG.info("Finished downloading thumbnails.");
@@ -324,6 +347,20 @@ public class CloudPresenter {
} }
} }
public List<PropertyWrapper> getProjectProperties(ProjectEntity project) {
List<PropertyWrapper> properties = ComponentProcessor.getInstance().extractProperties(ProjectEntity.class);
try {
for (PropertyWrapper property : properties) {
property.readFrom(project);
}
} catch (Exception e) {
LOG.error("Could not get project entity properties", e);
return null;
}
Collections.sort(properties, ComparatorFactory.getInstance().getDefaultPropertyComparator());
return properties;
}
private String getMachineId() { private String getMachineId() {
if (machineId == null) { if (machineId == null) {
try { try {
@@ -67,6 +67,27 @@ public class ComparatorFactory {
return propertyNameComparator; return propertyNameComparator;
} }
/**
* Creates a comparator that compares properties by sort order and then by name
* @return
*/
public Comparator<PropertyWrapper> getDefaultPropertyComparator() {
if (propertyNameComparator == null) {
propertyNameComparator = new Comparator<PropertyWrapper>() {
@Override
public int compare(PropertyWrapper o1, PropertyWrapper o2) {
int comp = new Integer(o1.getSortOrder()).compareTo(o2.getSortOrder());
if (comp != 0)
return comp;
return o1.getName().compareToIgnoreCase(o2.getName());
}
};
}
return propertyNameComparator;
}
public Comparator<IDIYComponent<?>> getComponentZOrderComparator() { public Comparator<IDIYComponent<?>> getComponentZOrderComparator() {
if (componentZOrderComparator == null) { if (componentZOrderComparator == null) {
componentZOrderComparator = new Comparator<IDIYComponent<?>>() { componentZOrderComparator = new Comparator<IDIYComponent<?>>() {
@@ -154,7 +154,7 @@ public class ComponentProcessor {
Method setter = clazz.getMethod("set" + method.getName().substring(3), method.getReturnType()); Method setter = clazz.getMethod("set" + method.getName().substring(3), method.getReturnType());
PropertyWrapper property = PropertyWrapper property =
new PropertyWrapper(name, method.getReturnType(), method.getName(), setter.getName(), new PropertyWrapper(name, method.getReturnType(), method.getName(), setter.getName(),
annotation.defaultable(), validator); annotation.defaultable(), validator, annotation.additionalOptions(), annotation.sortOrder());
properties.add(property); properties.add(property);
} }
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
@@ -228,7 +228,7 @@ public class ComponentProcessor {
// } // }
// } // }
} }
Collections.sort(properties, ComparatorFactory.getInstance().getPropertyNameComparator()); Collections.sort(properties, ComparatorFactory.getInstance().getDefaultPropertyComparator());
return properties; return properties;
} }
@@ -1691,7 +1691,7 @@ public class Presenter implements IPlugInPort {
LOG.error("Could not get project properties", e); LOG.error("Could not get project properties", e);
return null; return null;
} }
Collections.sort(properties, ComparatorFactory.getInstance().getPropertyNameComparator()); Collections.sort(properties, ComparatorFactory.getInstance().getDefaultPropertyComparator());
return properties; return properties;
} }
@@ -58,7 +58,11 @@ public class DialogFactory {
} }
public PropertyEditorDialog createPropertyEditorDialog(List<PropertyWrapper> properties, String title) { public PropertyEditorDialog createPropertyEditorDialog(List<PropertyWrapper> properties, String title) {
PropertyEditorDialog editor = new PropertyEditorDialog(mainFrame, properties, title); return createPropertyEditorDialog(mainFrame, properties, title);
}
public PropertyEditorDialog createPropertyEditorDialog(JFrame owner, List<PropertyWrapper> properties, String title) {
PropertyEditorDialog editor = new PropertyEditorDialog(owner, properties, title);
return editor; return editor;
} }
@@ -147,7 +151,8 @@ public class DialogFactory {
return dialog; return dialog;
} }
public UploadDialog createUploadDialog(JFrame ownerFrame, IPlugInPort plugInPort, String[] categories, boolean isUpdate) { public UploadDialog createUploadDialog(JFrame ownerFrame, IPlugInPort plugInPort, String[] categories,
boolean isUpdate) {
UploadDialog dialog = new UploadDialog(ownerFrame, plugInPort, categories, isUpdate); UploadDialog dialog = new UploadDialog(ownerFrame, plugInPort, categories, isUpdate);
return dialog; return dialog;
} }
@@ -0,0 +1,31 @@
package org.diylc.swing.gui.components;
import javax.swing.JTextArea;
/**
* {@link JTextArea} that replaces new line characters with html &lt;br&gt; tags.
*
* @author Branislav Stojkovic
*/
public class HTMLTextArea extends JTextArea {
private static final long serialVersionUID = 1L;
public HTMLTextArea(String text) {
super(text.replace("<br>", "\n"));
}
public HTMLTextArea() {
super();
}
@Override
public String getText() {
return super.getText().replace("\n", "<br>");
}
@Override
public void setText(String t) {
super.setText(t.replace("<br>", "\n"));
}
}
@@ -9,6 +9,7 @@ import javax.swing.JLabel;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.diylc.common.PropertyWrapper; import org.diylc.common.PropertyWrapper;
import org.diylc.core.annotations.EditableProperty;
import org.diylc.core.measures.AbstractMeasure; import org.diylc.core.measures.AbstractMeasure;
public class FieldEditorFactory { public class FieldEditorFactory {
@@ -16,6 +17,14 @@ public class FieldEditorFactory {
private static final Logger LOG = Logger.getLogger(FieldEditorFactory.class); private static final Logger LOG = Logger.getLogger(FieldEditorFactory.class);
public static Component createFieldEditor(PropertyWrapper property) { public static Component createFieldEditor(PropertyWrapper property) {
if (property.getType().equals(String.class) && property.getListItems() != null) {
StringListEditor editor = new StringListEditor(property);
return editor;
}
if (property.getType().equals(String.class) && EditableProperty.MULTI_LINE.equals(property.getAdditionalOptions())) {
MultiLineStringEditor editor = new MultiLineStringEditor(property);
return editor;
}
if (property.getType().equals(String.class)) { if (property.getType().equals(String.class)) {
StringEditor editor = new StringEditor(property); StringEditor editor = new StringEditor(property);
return editor; return editor;
@@ -0,0 +1,66 @@
package org.diylc.swing.gui.editor;
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.diylc.common.PropertyWrapper;
import org.diylc.swing.gui.components.HTMLTextArea;
import org.diylc.utils.Constants;
public class MultiLineStringEditor extends JScrollPane {
private static final long serialVersionUID = 1L;
private Color oldBg;
private JTextArea textArea;
private final PropertyWrapper property;
public MultiLineStringEditor(PropertyWrapper property) {
super();
this.property = property;
setViewportView(getTextArea());
setPreferredSize(new Dimension(192, 64));
}
public JTextArea getTextArea() {
if (textArea == null) {
textArea = new HTMLTextArea(property.getValue() == null ? "" : (String) property.getValue());
textArea.setFont(getFont());
oldBg = textArea.getBackground();
textArea.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
textChanged();
}
@Override
public void insertUpdate(DocumentEvent e) {
textChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
textChanged();
}
});
if (!property.isUnique()) {
textArea.setBackground(Constants.MULTI_VALUE_COLOR);
}
}
return textArea;
}
private void textChanged() {
property.setChanged(true);
getTextArea().setBackground(oldBg);
property.setValue(getTextArea().getText());
}
}
@@ -92,19 +92,20 @@ public class PropertyEditorDialog extends ButtonDialog {
GridBagConstraints gbc = new GridBagConstraints(); GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0; gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.LINE_START; gbc.anchor = GridBagConstraints.FIRST_LINE_START;
for (PropertyWrapper property : properties) { for (PropertyWrapper property : properties) {
gbc.gridx = 0; gbc.gridx = 0;
gbc.fill = GridBagConstraints.NONE; gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 0; gbc.weightx = 0;
gbc.insets = new Insets(4, 2, 2, 2);
editorPanel.add(new JLabel(property.getName() + ": "), gbc); editorPanel.add(new JLabel(property.getName() + ": "), gbc);
gbc.gridx = 1; gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL; gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1; gbc.weightx = 1;
gbc.insets = new Insets(2, 2, 2, 2);
Component editor = FieldEditorFactory.createFieldEditor(property); Component editor = FieldEditorFactory.createFieldEditor(property);
editor.addKeyListener(new KeyAdapter() { editor.addKeyListener(new KeyAdapter() {
@@ -20,6 +20,7 @@ public class StringEditor extends JTextField {
public StringEditor(PropertyWrapper property) { public StringEditor(PropertyWrapper property) {
super(property.getValue() == null ? "" : (String) property.getValue()); super(property.getValue() == null ? "" : (String) property.getValue());
this.property = property; this.property = property;
this.setColumns(32);
getDocument().addDocumentListener(new DocumentListener() { getDocument().addDocumentListener(new DocumentListener() {
@Override @Override
@@ -0,0 +1,41 @@
package org.diylc.swing.gui.editor;
import java.awt.Color;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import org.diylc.common.PropertyWrapper;
import org.diylc.utils.Constants;
public class StringListEditor extends JComboBox {
private static final long serialVersionUID = 1L;
private Color oldBg = getBackground();
private final PropertyWrapper property;
public StringListEditor(final PropertyWrapper property) {
this.property = property;
Object[] values = property.getListItems();
setModel(new DefaultComboBoxModel(values));
setSelectedItem(property.getValue());
addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
property.setChanged(true);
setBackground(oldBg);
StringListEditor.this.property.setValue(e.getItem());
}
}
});
if (!property.isUnique()) {
setBackground(Constants.MULTI_VALUE_COLOR);
}
}
}
@@ -24,6 +24,7 @@ import org.diylc.plugins.cloud.model.CommentEntity;
import org.diylc.plugins.cloud.model.ProjectEntity; import org.diylc.plugins.cloud.model.ProjectEntity;
import org.diylc.plugins.cloud.presenter.CloudPresenter; import org.diylc.plugins.cloud.presenter.CloudPresenter;
import org.diylc.swing.ISimpleView; import org.diylc.swing.ISimpleView;
import org.diylc.swing.gui.components.HTMLTextArea;
public class CommentDialog extends JDialog { public class CommentDialog extends JDialog {
@@ -119,7 +120,7 @@ public class CommentDialog extends JDialog {
private JTextArea getReplyArea() { private JTextArea getReplyArea() {
if (replyArea == null) { if (replyArea == null) {
replyArea = new JTextArea(); replyArea = new HTMLTextArea();
replyArea.setFont(getSendButton().getFont()); replyArea.setFont(getSendButton().getFont());
} }
return replyArea; return replyArea;
@@ -132,7 +133,7 @@ public class CommentDialog extends JDialog {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
final String comment = getReplyArea().getText().replace("\n", "<br>"); final String comment = getReplyArea().getText();
if (comment.trim().length() == 0) if (comment.trim().length() == 0)
owner.showMessage("Cannot post an empty comment.", "Cloud Error", IView.ERROR_MESSAGE); owner.showMessage("Cannot post an empty comment.", "Cloud Error", IView.ERROR_MESSAGE);
@@ -179,7 +180,7 @@ public class CommentDialog extends JDialog {
+ c.getPostedAt() + " </html>"), gbc); + c.getPostedAt() + " </html>"), gbc);
gbc.gridy = lastPosition++; gbc.gridy = lastPosition++;
JTextArea commentArea = new JTextArea(c.getComment().replace("<br>", "\n")); JTextArea commentArea = new HTMLTextArea(c.getComment());
commentArea.setEditable(false); commentArea.setEditable(false);
commentArea.setFont(getSendButton().getFont()); commentArea.setFont(getSendButton().getFont());
commentArea.setLineWrap(true); commentArea.setLineWrap(true);
@@ -26,6 +26,7 @@ import javax.swing.event.ChangeListener;
import org.diylc.common.DrawOption; import org.diylc.common.DrawOption;
import org.diylc.common.IPlugInPort; import org.diylc.common.IPlugInPort;
import org.diylc.swing.gui.components.HTMLTextArea;
import org.diylc.swingframework.ButtonDialog; import org.diylc.swingframework.ButtonDialog;
import org.diylc.utils.KeywordExtractor; import org.diylc.utils.KeywordExtractor;
@@ -213,7 +214,7 @@ public class UploadDialog extends ButtonDialog {
private JTextArea getDescriptionArea() { private JTextArea getDescriptionArea() {
if (descriptionArea == null) { if (descriptionArea == null) {
descriptionArea = new JTextArea(); descriptionArea = new HTMLTextArea();
// set default from the project // set default from the project
for (int i = 0; i < plugInPort.getProjectProperties().size(); i++) for (int i = 0; i < plugInPort.getProjectProperties().size(); i++)
@@ -242,7 +243,7 @@ public class UploadDialog extends ButtonDialog {
} }
public String getDescription() { public String getDescription() {
return getDescriptionArea().getText().replace("\n", "<br>"); return getDescriptionArea().getText();
} }
public String getCategory() { public String getCategory() {
@@ -18,6 +18,7 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import org.diylc.plugins.cloud.model.UserEntity; import org.diylc.plugins.cloud.model.UserEntity;
import org.diylc.swing.gui.components.HTMLTextArea;
import org.diylc.swingframework.ButtonDialog; import org.diylc.swingframework.ButtonDialog;
public class UserEditDialog extends ButtonDialog { public class UserEditDialog extends ButtonDialog {
@@ -68,7 +69,7 @@ public class UserEditDialog extends ButtonDialog {
} }
public String getBio() { public String getBio() {
return bio.replace("\n", "<br>"); return bio;
} }
@Override @Override
@@ -279,9 +280,9 @@ public class UserEditDialog extends ButtonDialog {
public JTextArea getBioArea() { public JTextArea getBioArea() {
if (bioArea == null) { if (bioArea == null) {
bioArea = new JTextArea(); bioArea = new HTMLTextArea();
if (existingEntity != null) if (existingEntity != null)
bioArea.setText(existingEntity.getBio().replace("<br>", "\n")); bioArea.setText(existingEntity.getBio());
bioArea.setFont(getUserNameField().getFont()); bioArea.setFont(getUserNameField().getFont());
bioArea.setBorder(getUserNameField().getBorder()); bioArea.setBorder(getUserNameField().getBorder());
bioArea.setPreferredSize(new Dimension(192, 69)); bioArea.setPreferredSize(new Dimension(192, 69));
@@ -36,6 +36,7 @@ import javax.swing.text.DefaultCaret;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.diylc.common.IPlugInPort; import org.diylc.common.IPlugInPort;
import org.diylc.common.ITask; import org.diylc.common.ITask;
import org.diylc.common.PropertyWrapper;
import org.diylc.core.IView; import org.diylc.core.IView;
import org.diylc.images.IconLoader; import org.diylc.images.IconLoader;
import org.diylc.plugins.cloud.model.CommentEntity; import org.diylc.plugins.cloud.model.CommentEntity;
@@ -47,6 +48,8 @@ import org.diylc.swing.ISimpleView;
import org.diylc.swing.ISwingUI; import org.diylc.swing.ISwingUI;
import org.diylc.swing.gui.DialogFactory; import org.diylc.swing.gui.DialogFactory;
import org.diylc.swing.gui.DummyView; import org.diylc.swing.gui.DummyView;
import org.diylc.swing.gui.components.HTMLTextArea;
import org.diylc.swing.gui.editor.PropertyEditorDialog;
import org.diylc.swing.plugins.cloud.view.CommentDialog; import org.diylc.swing.plugins.cloud.view.CommentDialog;
import org.diylc.swing.plugins.cloud.view.UploadDialog; import org.diylc.swing.plugins.cloud.view.UploadDialog;
import org.diylc.swing.plugins.file.FileFilterEnum; import org.diylc.swing.plugins.file.FileFilterEnum;
@@ -178,7 +181,7 @@ public class ResultsScrollPanel extends JScrollPane {
final JLabel nameLabel = new JLabel("<html><b>" + project.getName() + "</b></html>"); final JLabel nameLabel = new JLabel("<html><b>" + project.getName() + "</b></html>");
nameLabel.setFont(nameLabel.getFont().deriveFont(12f)); nameLabel.setFont(nameLabel.getFont().deriveFont(12f));
final JTextArea descriptionArea = new JTextArea(project.getDescription().replace("<br>", "\n")); final JTextArea descriptionArea = new HTMLTextArea(project.getDescription());
descriptionArea.setEditable(false); descriptionArea.setEditable(false);
descriptionArea.setFont(thumbnailLabel.getFont()); descriptionArea.setFont(thumbnailLabel.getFont());
descriptionArea.setLineWrap(true); descriptionArea.setLineWrap(true);
@@ -301,91 +304,145 @@ public class ResultsScrollPanel extends JScrollPane {
final JSeparator separator = new JSeparator(); final JSeparator separator = new JSeparator();
final JPanel buttonPanel = new JPanel(new FlowLayout()); final JPanel buttonPanel = new JPanel(new FlowLayout());
final JLabel editButton = new JLabel(IconLoader.CloudEdit.getIcon()); final JLabel editButton = new JLabel(IconLoader.CloudEdit.getIcon());
editButton.setFocusable(true); editButton.setFocusable(true);
editButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); editButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
editButton.setToolTipText("Edit details without changing the project file"); editButton.setToolTipText("Edit details without changing the project file");
editButton.addMouseListener(new MouseAdapter() {
final JLabel reuploadButton = new JLabel(IconLoader.CloudUpload.getIcon());
reuploadButton.setFocusable(true);
reuploadButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
reuploadButton.setToolTipText("Re-upload to the cloud");
reuploadButton.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
final Presenter thumbnailPresenter = new Presenter(new DummyView()); List<PropertyWrapper> projectProperties = cloudPresenter.getProjectProperties(project);
final File file = PropertyEditorDialog editor =
DialogFactory.getInstance().showOpenDialog(FileFilterEnum.DIY.getFilter(), null, DialogFactory.getInstance().createPropertyEditorDialog(cloudUI.getOwnerFrame(), projectProperties,
FileFilterEnum.DIY.getExtensions()[0], null, cloudUI.getOwnerFrame()); "Edit Published Project");
if (file != null) { editor.setVisible(true);
LOG.info("Preparing re-upload of project " + project.getName() + "(" + project.getId() + ")"); if (ButtonDialog.OK.equals(editor.getSelectedButtonCaption())) {
cloudUI.executeBackgroundTask(new ITask<String[]>() { // Update the project
for (PropertyWrapper property : projectProperties) {
@Override try {
public String[] doInBackground() throws Exception { property.writeTo(project);
LOG.debug("Uploading from " + file.getAbsolutePath()); } catch (Exception ex) {
thumbnailPresenter.loadProjectFromFile(file.getAbsolutePath()); LOG.error("Could not update the project", ex);
return cloudPresenter.getCategories(); cloudUI.showMessage("Could not update the project. Detailed message is in the logs.", "Error",
ISwingUI.ERROR_MESSAGE);
return;
} }
}
cloudUI.executeBackgroundTask(new ITask<ProjectEntity>() {
@Override @Override
public void complete(final String[] result) { public ProjectEntity doInBackground() throws Exception {
final UploadDialog dialog = cloudPresenter.updateProjectDetails(project, plugInPort.getCurrentVersionNumber().toString());
DialogFactory.getInstance().createUploadDialog(cloudUI.getOwnerFrame(), thumbnailPresenter, result, true); return cloudPresenter.fetchUserUploads(project.getId()).get(0);
dialog.setVisible(true);
if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) {
try {
final File thumbnailFile = File.createTempFile("upload-thumbnail", ".png");
if (ImageIO.write(dialog.getThumbnail(), "png", thumbnailFile)) {
cloudUI.executeBackgroundTask(new ITask<ProjectEntity>() {
@Override
public ProjectEntity doInBackground() throws Exception {
cloudPresenter.uploadProject(dialog.getName(), dialog.getCategory(), dialog.getDescription(),
dialog.getKeywords(), plugInPort.getCurrentVersionNumber().toString(), thumbnailFile, file,
project.getId());
return cloudPresenter.fetchUserUploads(project.getId()).get(0);
}
@Override
public void failed(Exception e) {
cloudUI.showMessage(e.getMessage(), "Upload Error", IView.ERROR_MESSAGE);
}
@Override
public void complete(ProjectEntity result) {
nameLabel.setText("<html><b>" + project.getName() + "</b></html>");
categoryLabel.setText(result.getCategory());
descriptionArea.setText(result.getDescription().replace("<br>", "\n"));
categoryLabel.setText("<html>Category: <b>" + result.getCategory() + "</b></html>");
updatedLabel.setText("<html>Last updated: <b>" + result.getUpdated() + "</b></html>");
thumbnailLabel.setIcon(new ImageIcon(dialog.getThumbnail()));
cloudUI
.showMessage(
"The project has been uploaded to the cloud successfully. Thank you for your contribution!",
"Upload Success", IView.INFORMATION_MESSAGE);
}
});
} else {
cloudUI.showMessage("Could not prepare temporary files to be uploaded to the cloud.",
"Upload Error", IView.ERROR_MESSAGE);
}
} catch (Exception e) {
cloudUI.showMessage(e.getMessage(), "Upload Error", IView.ERROR_MESSAGE);
}
}
} }
@Override @Override
public void failed(Exception e) { public void failed(Exception e) {
cloudUI.showMessage("Could not open file. " + e.getMessage(), "Error", ISwingUI.ERROR_MESSAGE); cloudUI.showMessage("Could not update the project. Detailed message is in the logs.", "Error",
ISwingUI.ERROR_MESSAGE);
}
@Override
public void complete(ProjectEntity result) {
nameLabel.setText("<html><b>" + project.getName() + "</b></html>");
descriptionArea.setText(result.getDescription());
categoryLabel.setText("<html>Category: <b>" + result.getCategory() + "</b></html>");
updatedLabel.setText("<html>Last updated: <b>" + result.getUpdated() + "</b></html>");
cloudUI.showMessage(
"The project has been uploaded to the cloud successfully. Thank you for your contribution!",
"Upload Success", IView.INFORMATION_MESSAGE);
} }
}); });
} }
} }
}); });
final JLabel replaceButton = new JLabel(IconLoader.CloudUpload.getIcon());
replaceButton.setFocusable(true);
replaceButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
replaceButton.setToolTipText("Replace project with a new version");
replaceButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (cloudUI.showConfirmDialog(
"Are you sure you want to replace the project \""
+ project.getName()
+ "\" with a new file?\nThis opperation is irreversible. Once replaced, the old version of the project cannot be restored.",
"Replace Project", IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) == IView.YES_OPTION) {
final Presenter thumbnailPresenter = new Presenter(new DummyView());
final File file =
DialogFactory.getInstance().showOpenDialog(FileFilterEnum.DIY.getFilter(), null,
FileFilterEnum.DIY.getExtensions()[0], null, cloudUI.getOwnerFrame());
if (file != null) {
LOG.info("Preparing re-upload of project " + project.getName() + "(" + project.getId() + ")");
cloudUI.executeBackgroundTask(new ITask<String[]>() {
@Override
public String[] doInBackground() throws Exception {
LOG.debug("Uploading from " + file.getAbsolutePath());
thumbnailPresenter.loadProjectFromFile(file.getAbsolutePath());
return cloudPresenter.getCategories();
}
@Override
public void complete(final String[] result) {
final UploadDialog dialog =
DialogFactory.getInstance().createUploadDialog(cloudUI.getOwnerFrame(), thumbnailPresenter, result,
true);
dialog.setVisible(true);
if (ButtonDialog.OK.equals(dialog.getSelectedButtonCaption())) {
try {
final File thumbnailFile = File.createTempFile("upload-thumbnail", ".png");
if (ImageIO.write(dialog.getThumbnail(), "png", thumbnailFile)) {
cloudUI.executeBackgroundTask(new ITask<ProjectEntity>() {
@Override
public ProjectEntity doInBackground() throws Exception {
cloudPresenter.uploadProject(dialog.getName(), dialog.getCategory(), dialog.getDescription(),
dialog.getKeywords(), plugInPort.getCurrentVersionNumber().toString(), thumbnailFile,
file, project.getId());
return cloudPresenter.fetchUserUploads(project.getId()).get(0);
}
@Override
public void failed(Exception e) {
cloudUI.showMessage(e.getMessage(), "Upload Error", IView.ERROR_MESSAGE);
}
@Override
public void complete(ProjectEntity result) {
nameLabel.setText("<html><b>" + project.getName() + "</b></html>");
descriptionArea.setText(result.getDescription());
categoryLabel.setText("<html>Category: <b>" + result.getCategory() + "</b></html>");
updatedLabel.setText("<html>Last updated: <b>" + result.getUpdated() + "</b></html>");
thumbnailLabel.setIcon(new ImageIcon(dialog.getThumbnail()));
cloudUI
.showMessage(
"The project has been uploaded to the cloud successfully. Thank you for your contribution!",
"Upload Success", IView.INFORMATION_MESSAGE);
}
});
} else {
cloudUI.showMessage("Could not prepare temporary files to be uploaded to the cloud.",
"Upload Error", IView.ERROR_MESSAGE);
}
} catch (Exception e) {
cloudUI.showMessage(e.getMessage(), "Upload Error", IView.ERROR_MESSAGE);
}
}
}
@Override
public void failed(Exception e) {
cloudUI.showMessage("Could not open file. " + e.getMessage(), "Error", ISwingUI.ERROR_MESSAGE);
}
});
}
}
}
});
final JLabel deleteButton = new JLabel(IconLoader.CloudDelete.getIcon()); final JLabel deleteButton = new JLabel(IconLoader.CloudDelete.getIcon());
deleteButton.setFocusable(true); deleteButton.setFocusable(true);
deleteButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); deleteButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
@@ -394,8 +451,8 @@ public class ResultsScrollPanel extends JScrollPane {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (cloudUI.showConfirmDialog("Are you sure you want to permanently delete project " + project.getName() if (cloudUI.showConfirmDialog("Are you sure you want to permanently delete project \"" + project.getName()
+ " from the cloud?", "Delete Project", IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) == IView.YES_OPTION) { + "\" from the cloud?", "Delete Project", IView.YES_NO_OPTION, IView.QUESTION_MESSAGE) == IView.YES_OPTION) {
LOG.info("Deleting project " + project.getName() + "(" + project.getId() + ")"); LOG.info("Deleting project " + project.getName() + "(" + project.getId() + ")");
cloudUI.executeBackgroundTask(new ITask<Void>() { cloudUI.executeBackgroundTask(new ITask<Void>() {
@@ -435,7 +492,7 @@ public class ResultsScrollPanel extends JScrollPane {
buttonPanel.add(downloadButton); buttonPanel.add(downloadButton);
if (showEditControls) { if (showEditControls) {
buttonPanel.add(editButton); buttonPanel.add(editButton);
buttonPanel.add(reuploadButton); buttonPanel.add(replaceButton);
buttonPanel.add(deleteButton); buttonPanel.add(deleteButton);
} }
+8
View File
@@ -1358,6 +1358,14 @@
<changeType>IMPROVEMENT</changeType> <changeType>IMPROVEMENT</changeType>
<description>Popup dialogs work better with keyboard - Enter confirms, Escape dismisses dialogs</description> <description>Popup dialogs work better with keyboard - Enter confirms, Escape dismisses dialogs</description>
</org.diylc.appframework.update.Change> </org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Make order of items in the 'Edit Project' dialog more logical</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Allow multi-line project description</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change> <org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType> <changeType>BUG_FIX</changeType>
<description>Changing Display to one trimmer affects all trimmers and does not get saved to the file</description> <description>Changing Display to one trimmer affects all trimmers and does not get saved to the file</description>