From 5981f9691625c4f96beddd009ac35c6fdf7f1ee8 Mon Sep 17 00:00:00 2001 From: Branislav Stojkovic Date: Mon, 31 Oct 2016 22:00:02 +0100 Subject: [PATCH] edit cloud project --- .../src/org/diylc/common/PropertyWrapper.java | 40 +++- .../src/org/diylc/core/Project.java | 12 +- .../core/annotations/EditableProperty.java | 27 +++ .../plugins/cloud/model/ProjectEntity.java | 41 +++- .../cloud/presenter/CloudPresenter.java | 43 +++- .../diylc/presenter/ComparatorFactory.java | 21 ++ .../diylc/presenter/ComponentProcessor.java | 4 +- .../src/org/diylc/presenter/Presenter.java | 2 +- .../org/diylc/swing/gui/DialogFactory.java | 9 +- .../swing/gui/components/HTMLTextArea.java | 31 +++ .../swing/gui/editor/FieldEditorFactory.java | 9 + .../gui/editor/MultiLineStringEditor.java | 66 ++++++ .../gui/editor/PropertyEditorDialog.java | 7 +- .../diylc/swing/gui/editor/StringEditor.java | 1 + .../swing/gui/editor/StringListEditor.java | 41 ++++ .../plugins/cloud/view/CommentDialog.java | 7 +- .../plugins/cloud/view/UploadDialog.java | 5 +- .../plugins/cloud/view/UserEditDialog.java | 7 +- .../view/browser/ResultsScrollPanel.java | 197 +++++++++++------- diylc/diylc-swing/update.xml | 8 + 20 files changed, 479 insertions(+), 99 deletions(-) create mode 100644 diylc/diylc-swing/src/org/diylc/swing/gui/components/HTMLTextArea.java create mode 100644 diylc/diylc-swing/src/org/diylc/swing/gui/editor/MultiLineStringEditor.java create mode 100644 diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringListEditor.java diff --git a/diylc/diylc-core/src/org/diylc/common/PropertyWrapper.java b/diylc/diylc-core/src/org/diylc/common/PropertyWrapper.java index 633b479e..07b2ad39 100644 --- a/diylc/diylc-core/src/org/diylc/common/PropertyWrapper.java +++ b/diylc/diylc-core/src/org/diylc/common/PropertyWrapper.java @@ -1,8 +1,10 @@ package org.diylc.common; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import org.diylc.core.IPropertyValidator; +import org.diylc.core.annotations.EditableProperty; /** * 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 boolean unique = true; 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, - IPropertyValidator validator) { + IPropertyValidator validator, String additionalOptions, int sortOrder) { super(); this.name = name; this.type = type; @@ -31,11 +36,18 @@ public class PropertyWrapper implements Cloneable { this.setter = setter; this.defaultable = defaultable; this.validator = validator; + this.additionalOptions = additionalOptions; + this.sortOrder = sortOrder; } public void readFrom(Object object) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException { 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) @@ -92,6 +104,22 @@ public class PropertyWrapper implements Cloneable { 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 // public Object clone() throws CloneNotSupportedException { // // Try to invoke clone method on value if possible. @@ -105,7 +133,8 @@ public class PropertyWrapper implements Cloneable { @Override public Object clone() throws CloneNotSupportedException { 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.changed = this.changed; clone.unique = this.unique; @@ -116,11 +145,11 @@ public class PropertyWrapper implements Cloneable { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + ((additionalOptions == null) ? 0 : additionalOptions.hashCode()); result = prime * result + (defaultable ? 1231 : 1237); result = prime * result + ((getter == null) ? 0 : getter.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((setter == null) ? 0 : setter.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @@ -133,6 +162,11 @@ public class PropertyWrapper implements Cloneable { if (getClass() != obj.getClass()) return false; 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) return false; if (getter == null) { diff --git a/diylc/diylc-core/src/org/diylc/core/Project.java b/diylc/diylc-core/src/org/diylc/core/Project.java index 68aab3c4..cfcdb30a 100644 --- a/diylc/diylc-core/src/org/diylc/core/Project.java +++ b/diylc/diylc-core/src/org/diylc/core/Project.java @@ -54,7 +54,7 @@ public class Project implements Serializable { gridSpacing = DEFAULT_GRID_SPACING; } - @EditableProperty(defaultable = false) + @EditableProperty(defaultable = false, sortOrder = 1) public String getTitle() { return title; } @@ -63,7 +63,7 @@ public class Project implements Serializable { this.title = title; } - @EditableProperty + @EditableProperty(sortOrder = 3) public String getAuthor() { return author; } @@ -72,7 +72,7 @@ public class Project implements Serializable { this.author = author; } - @EditableProperty(defaultable = false) + @EditableProperty(defaultable = false, additionalOptions = EditableProperty.MULTI_LINE, sortOrder = 2) public String getDescription() { return description; } @@ -81,7 +81,7 @@ public class Project implements Serializable { this.description = description; } - @EditableProperty + @EditableProperty(sortOrder = 4) public Size getWidth() { return width; } @@ -90,7 +90,7 @@ public class Project implements Serializable { this.width = width; } - @EditableProperty + @EditableProperty(sortOrder = 5) public Size getHeight() { return height; } @@ -99,7 +99,7 @@ public class Project implements Serializable { this.height = height; } - @EditableProperty(name = "Grid spacing", validatorClass = SpacingValidator.class) + @EditableProperty(name = "Grid spacing", validatorClass = SpacingValidator.class, sortOrder = 6) public Size getGridSpacing() { return gridSpacing; } diff --git a/diylc/diylc-core/src/org/diylc/core/annotations/EditableProperty.java b/diylc/diylc-core/src/org/diylc/core/annotations/EditableProperty.java index cd87d4d0..9bc60eeb 100644 --- a/diylc/diylc-core/src/org/diylc/core/annotations/EditableProperty.java +++ b/diylc/diylc-core/src/org/diylc/core/annotations/EditableProperty.java @@ -16,6 +16,18 @@ import org.diylc.core.IPropertyValidator; @Retention(RetentionPolicy.RUNTIME) 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. * getter getXYZ will induce property name XYZ.
@@ -39,4 +51,19 @@ public @interface EditableProperty { * @return */ Class 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 ""; } diff --git a/diylc/diylc-core/src/org/diylc/plugins/cloud/model/ProjectEntity.java b/diylc/diylc-core/src/org/diylc/plugins/cloud/model/ProjectEntity.java index 5188f55f..291a63ca 100644 --- a/diylc/diylc-core/src/org/diylc/plugins/cloud/model/ProjectEntity.java +++ b/diylc/diylc-core/src/org/diylc/plugins/cloud/model/ProjectEntity.java @@ -2,6 +2,8 @@ package org.diylc.plugins.cloud.model; import java.io.Serializable; +import org.diylc.core.annotations.EditableProperty; + public class ProjectEntity implements Serializable { private static final long serialVersionUID = 1L; @@ -11,12 +13,15 @@ public class ProjectEntity implements Serializable { private String description; private String owner; private String category; + private String categoryForDisplay; private String updated; private String thumbnailUrl; private String downloadUrl; + private String keywords; private int viewCount; private int downloadCount; private int commentCount; + private String[] categories; public ProjectEntity() { super(); @@ -30,6 +35,7 @@ public class ProjectEntity implements Serializable { this.id = id; } + @EditableProperty(defaultable = false, sortOrder = 1) public String getName() { return name; } @@ -38,6 +44,7 @@ public class ProjectEntity implements Serializable { this.name = name; } + @EditableProperty(defaultable = false, additionalOptions = EditableProperty.MULTI_LINE, sortOrder = 3) public String getDescription() { return description; } @@ -53,7 +60,7 @@ public class ProjectEntity implements Serializable { public void setOwner(String owner) { this.owner = owner; } - + public String getCategory() { return category; } @@ -61,6 +68,23 @@ public class ProjectEntity implements Serializable { public void setCategory(String 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() { return updated; @@ -110,6 +134,15 @@ public class ProjectEntity implements Serializable { this.commentCount = commentCount; } + @EditableProperty(defaultable = false, sortOrder = 4) + public String getKeywords() { + return keywords; + } + + public void setKeywords(String keywords) { + this.keywords = keywords; + } + @Override public int hashCode() { final int prime = 31; @@ -120,6 +153,7 @@ public class ProjectEntity implements Serializable { result = prime * result + downloadCount; result = prime * result + ((downloadUrl == null) ? 0 : downloadUrl.hashCode()); result = prime * result + id; + result = prime * result + ((keywords == null) ? 0 : keywords.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((owner == null) ? 0 : owner.hashCode()); result = prime * result + ((thumbnailUrl == null) ? 0 : thumbnailUrl.hashCode()); @@ -158,6 +192,11 @@ public class ProjectEntity implements Serializable { return false; if (id != other.id) return false; + if (keywords == null) { + if (other.keywords != null) + return false; + } else if (!keywords.equals(other.keywords)) + return false; if (name == null) { if (other.name != null) return false; diff --git a/diylc/diylc-core/src/org/diylc/plugins/cloud/presenter/CloudPresenter.java b/diylc/diylc-core/src/org/diylc/plugins/cloud/presenter/CloudPresenter.java index ce9019a2..2dd7c8b2 100644 --- a/diylc/diylc-core/src/org/diylc/plugins/cloud/presenter/CloudPresenter.java +++ b/diylc/diylc-core/src/org/diylc/plugins/cloud/presenter/CloudPresenter.java @@ -8,14 +8,18 @@ import java.net.NetworkInterface; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.util.Collections; import java.util.List; import org.apache.log4j.Logger; import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.common.PropertyWrapper; import org.diylc.plugins.cloud.model.CommentEntity; import org.diylc.plugins.cloud.model.IServiceAPI; import org.diylc.plugins.cloud.model.ProjectEntity; 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.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 { String username = ConfigurationManager.getInstance().readString(USERNAME_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..."); // Download thumbnails and replace urls with local paths to speed up loading in the main // thread - for (int i = 0; i < projects.size(); i++) { - String url = projects.get(i).getThumbnailUrl(); + for (ProjectEntity project : projects) { + String url = project.getThumbnailUrl(); URL website = new URL(url); ReadableByteChannel rbc = Channels.newChannel(website.openStream()); File temp = File.createTempFile("thumbnail", ".png"); FileOutputStream fos = new FileOutputStream(temp); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - projects.get(i).setThumbnailUrl(temp.getAbsolutePath()); + project.setThumbnailUrl(temp.getAbsolutePath()); + project.setCategories(getCategories()); fos.close(); } LOG.info("Finished downloading thumbnails."); @@ -324,6 +347,20 @@ public class CloudPresenter { } } + public List getProjectProperties(ProjectEntity project) { + List 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() { if (machineId == null) { try { diff --git a/diylc/diylc-core/src/org/diylc/presenter/ComparatorFactory.java b/diylc/diylc-core/src/org/diylc/presenter/ComparatorFactory.java index ef9d689a..1c405df3 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/ComparatorFactory.java +++ b/diylc/diylc-core/src/org/diylc/presenter/ComparatorFactory.java @@ -67,6 +67,27 @@ public class ComparatorFactory { return propertyNameComparator; } + /** + * Creates a comparator that compares properties by sort order and then by name + * @return + */ + public Comparator getDefaultPropertyComparator() { + if (propertyNameComparator == null) { + propertyNameComparator = new Comparator() { + + @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> getComponentZOrderComparator() { if (componentZOrderComparator == null) { componentZOrderComparator = new Comparator>() { diff --git a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java index 68529469..768b88e2 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java +++ b/diylc/diylc-core/src/org/diylc/presenter/ComponentProcessor.java @@ -154,7 +154,7 @@ public class ComponentProcessor { Method setter = clazz.getMethod("set" + method.getName().substring(3), method.getReturnType()); PropertyWrapper property = new PropertyWrapper(name, method.getReturnType(), method.getName(), setter.getName(), - annotation.defaultable(), validator); + annotation.defaultable(), validator, annotation.additionalOptions(), annotation.sortOrder()); properties.add(property); } } catch (NoSuchMethodException e) { @@ -228,7 +228,7 @@ public class ComponentProcessor { // } // } } - Collections.sort(properties, ComparatorFactory.getInstance().getPropertyNameComparator()); + Collections.sort(properties, ComparatorFactory.getInstance().getDefaultPropertyComparator()); return properties; } diff --git a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java index 303b4ce8..179307a0 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/Presenter.java +++ b/diylc/diylc-core/src/org/diylc/presenter/Presenter.java @@ -1691,7 +1691,7 @@ public class Presenter implements IPlugInPort { LOG.error("Could not get project properties", e); return null; } - Collections.sort(properties, ComparatorFactory.getInstance().getPropertyNameComparator()); + Collections.sort(properties, ComparatorFactory.getInstance().getDefaultPropertyComparator()); return properties; } diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java b/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java index a8414f80..a6ca7798 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/DialogFactory.java @@ -58,7 +58,11 @@ public class DialogFactory { } public PropertyEditorDialog createPropertyEditorDialog(List properties, String title) { - PropertyEditorDialog editor = new PropertyEditorDialog(mainFrame, properties, title); + return createPropertyEditorDialog(mainFrame, properties, title); + } + + public PropertyEditorDialog createPropertyEditorDialog(JFrame owner, List properties, String title) { + PropertyEditorDialog editor = new PropertyEditorDialog(owner, properties, title); return editor; } @@ -147,7 +151,8 @@ public class DialogFactory { 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); return dialog; } diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/components/HTMLTextArea.java b/diylc/diylc-swing/src/org/diylc/swing/gui/components/HTMLTextArea.java new file mode 100644 index 00000000..c326c2db --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/components/HTMLTextArea.java @@ -0,0 +1,31 @@ +package org.diylc.swing.gui.components; + +import javax.swing.JTextArea; + +/** + * {@link JTextArea} that replaces new line characters with html <br> tags. + * + * @author Branislav Stojkovic + */ +public class HTMLTextArea extends JTextArea { + + private static final long serialVersionUID = 1L; + + public HTMLTextArea(String text) { + super(text.replace("
", "\n")); + } + + public HTMLTextArea() { + super(); + } + + @Override + public String getText() { + return super.getText().replace("\n", "
"); + } + + @Override + public void setText(String t) { + super.setText(t.replace("
", "\n")); + } +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/FieldEditorFactory.java b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/FieldEditorFactory.java index a1464e93..cdebbe1c 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/FieldEditorFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/FieldEditorFactory.java @@ -9,6 +9,7 @@ import javax.swing.JLabel; import org.apache.log4j.Logger; import org.diylc.common.PropertyWrapper; +import org.diylc.core.annotations.EditableProperty; import org.diylc.core.measures.AbstractMeasure; public class FieldEditorFactory { @@ -16,6 +17,14 @@ public class FieldEditorFactory { private static final Logger LOG = Logger.getLogger(FieldEditorFactory.class); 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)) { StringEditor editor = new StringEditor(property); return editor; diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/MultiLineStringEditor.java b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/MultiLineStringEditor.java new file mode 100644 index 00000000..b879d004 --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/MultiLineStringEditor.java @@ -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()); + } +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java index 5b556288..046c0049 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/PropertyEditorDialog.java @@ -92,19 +92,20 @@ public class PropertyEditorDialog extends ButtonDialog { GridBagConstraints gbc = new GridBagConstraints(); 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) { gbc.gridx = 0; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0; - + gbc.insets = new Insets(4, 2, 2, 2); editorPanel.add(new JLabel(property.getName() + ": "), gbc); gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1; + gbc.insets = new Insets(2, 2, 2, 2); Component editor = FieldEditorFactory.createFieldEditor(property); editor.addKeyListener(new KeyAdapter() { diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringEditor.java b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringEditor.java index e43cbfa6..ad03c7ed 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringEditor.java +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringEditor.java @@ -20,6 +20,7 @@ public class StringEditor extends JTextField { public StringEditor(PropertyWrapper property) { super(property.getValue() == null ? "" : (String) property.getValue()); this.property = property; + this.setColumns(32); getDocument().addDocumentListener(new DocumentListener() { @Override diff --git a/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringListEditor.java b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringListEditor.java new file mode 100644 index 00000000..bf618895 --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/gui/editor/StringListEditor.java @@ -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); + } + } +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/CommentDialog.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/CommentDialog.java index f158fe2d..844c1728 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/CommentDialog.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/CommentDialog.java @@ -24,6 +24,7 @@ import org.diylc.plugins.cloud.model.CommentEntity; import org.diylc.plugins.cloud.model.ProjectEntity; import org.diylc.plugins.cloud.presenter.CloudPresenter; import org.diylc.swing.ISimpleView; +import org.diylc.swing.gui.components.HTMLTextArea; public class CommentDialog extends JDialog { @@ -119,7 +120,7 @@ public class CommentDialog extends JDialog { private JTextArea getReplyArea() { if (replyArea == null) { - replyArea = new JTextArea(); + replyArea = new HTMLTextArea(); replyArea.setFont(getSendButton().getFont()); } return replyArea; @@ -132,7 +133,7 @@ public class CommentDialog extends JDialog { @Override public void actionPerformed(ActionEvent e) { - final String comment = getReplyArea().getText().replace("\n", "
"); + final String comment = getReplyArea().getText(); if (comment.trim().length() == 0) owner.showMessage("Cannot post an empty comment.", "Cloud Error", IView.ERROR_MESSAGE); @@ -179,7 +180,7 @@ public class CommentDialog extends JDialog { + c.getPostedAt() + " "), gbc); gbc.gridy = lastPosition++; - JTextArea commentArea = new JTextArea(c.getComment().replace("
", "\n")); + JTextArea commentArea = new HTMLTextArea(c.getComment()); commentArea.setEditable(false); commentArea.setFont(getSendButton().getFont()); commentArea.setLineWrap(true); diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UploadDialog.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UploadDialog.java index be0056db..8046b75f 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UploadDialog.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UploadDialog.java @@ -26,6 +26,7 @@ import javax.swing.event.ChangeListener; import org.diylc.common.DrawOption; import org.diylc.common.IPlugInPort; +import org.diylc.swing.gui.components.HTMLTextArea; import org.diylc.swingframework.ButtonDialog; import org.diylc.utils.KeywordExtractor; @@ -213,7 +214,7 @@ public class UploadDialog extends ButtonDialog { private JTextArea getDescriptionArea() { if (descriptionArea == null) { - descriptionArea = new JTextArea(); + descriptionArea = new HTMLTextArea(); // set default from the project for (int i = 0; i < plugInPort.getProjectProperties().size(); i++) @@ -242,7 +243,7 @@ public class UploadDialog extends ButtonDialog { } public String getDescription() { - return getDescriptionArea().getText().replace("\n", "
"); + return getDescriptionArea().getText(); } public String getCategory() { diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UserEditDialog.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UserEditDialog.java index eaa6e242..bc6ffd93 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UserEditDialog.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/UserEditDialog.java @@ -18,6 +18,7 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.diylc.plugins.cloud.model.UserEntity; +import org.diylc.swing.gui.components.HTMLTextArea; import org.diylc.swingframework.ButtonDialog; public class UserEditDialog extends ButtonDialog { @@ -68,7 +69,7 @@ public class UserEditDialog extends ButtonDialog { } public String getBio() { - return bio.replace("\n", "
"); + return bio; } @Override @@ -279,9 +280,9 @@ public class UserEditDialog extends ButtonDialog { public JTextArea getBioArea() { if (bioArea == null) { - bioArea = new JTextArea(); + bioArea = new HTMLTextArea(); if (existingEntity != null) - bioArea.setText(existingEntity.getBio().replace("
", "\n")); + bioArea.setText(existingEntity.getBio()); bioArea.setFont(getUserNameField().getFont()); bioArea.setBorder(getUserNameField().getBorder()); bioArea.setPreferredSize(new Dimension(192, 69)); diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/browser/ResultsScrollPanel.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/browser/ResultsScrollPanel.java index 8d1341ff..9a7a2c19 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/browser/ResultsScrollPanel.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/cloud/view/browser/ResultsScrollPanel.java @@ -36,6 +36,7 @@ import javax.swing.text.DefaultCaret; import org.apache.log4j.Logger; import org.diylc.common.IPlugInPort; import org.diylc.common.ITask; +import org.diylc.common.PropertyWrapper; import org.diylc.core.IView; import org.diylc.images.IconLoader; 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.gui.DialogFactory; 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.UploadDialog; import org.diylc.swing.plugins.file.FileFilterEnum; @@ -178,7 +181,7 @@ public class ResultsScrollPanel extends JScrollPane { final JLabel nameLabel = new JLabel("" + project.getName() + ""); nameLabel.setFont(nameLabel.getFont().deriveFont(12f)); - final JTextArea descriptionArea = new JTextArea(project.getDescription().replace("
", "\n")); + final JTextArea descriptionArea = new HTMLTextArea(project.getDescription()); descriptionArea.setEditable(false); descriptionArea.setFont(thumbnailLabel.getFont()); descriptionArea.setLineWrap(true); @@ -301,91 +304,145 @@ public class ResultsScrollPanel extends JScrollPane { final JSeparator separator = new JSeparator(); final JPanel buttonPanel = new JPanel(new FlowLayout()); - + final JLabel editButton = new JLabel(IconLoader.CloudEdit.getIcon()); editButton.setFocusable(true); editButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); editButton.setToolTipText("Edit details without changing the project file"); - - 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() { + editButton.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - 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() { - - @Override - public String[] doInBackground() throws Exception { - LOG.debug("Uploading from " + file.getAbsolutePath()); - thumbnailPresenter.loadProjectFromFile(file.getAbsolutePath()); - return cloudPresenter.getCategories(); + List projectProperties = cloudPresenter.getProjectProperties(project); + PropertyEditorDialog editor = + DialogFactory.getInstance().createPropertyEditorDialog(cloudUI.getOwnerFrame(), projectProperties, + "Edit Published Project"); + editor.setVisible(true); + if (ButtonDialog.OK.equals(editor.getSelectedButtonCaption())) { + // Update the project + for (PropertyWrapper property : projectProperties) { + try { + property.writeTo(project); + } catch (Exception ex) { + LOG.error("Could not update the project", ex); + cloudUI.showMessage("Could not update the project. Detailed message is in the logs.", "Error", + ISwingUI.ERROR_MESSAGE); + return; } + } + cloudUI.executeBackgroundTask(new ITask() { @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() { - - @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("" + project.getName() + ""); - categoryLabel.setText(result.getCategory()); - descriptionArea.setText(result.getDescription().replace("
", "\n")); - categoryLabel.setText("Category: " + result.getCategory() + ""); - updatedLabel.setText("Last updated: " + result.getUpdated() + ""); - 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); - } - } + public ProjectEntity doInBackground() throws Exception { + cloudPresenter.updateProjectDetails(project, plugInPort.getCurrentVersionNumber().toString()); + return cloudPresenter.fetchUserUploads(project.getId()).get(0); } @Override 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("" + project.getName() + ""); + descriptionArea.setText(result.getDescription()); + categoryLabel.setText("Category: " + result.getCategory() + ""); + updatedLabel.setText("Last updated: " + result.getUpdated() + ""); + 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() { + + @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() { + + @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("" + project.getName() + ""); + descriptionArea.setText(result.getDescription()); + categoryLabel.setText("Category: " + result.getCategory() + ""); + updatedLabel.setText("Last updated: " + result.getUpdated() + ""); + 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()); deleteButton.setFocusable(true); deleteButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); @@ -394,8 +451,8 @@ public class ResultsScrollPanel extends JScrollPane { @Override public void mouseClicked(MouseEvent e) { - 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) { + 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) { LOG.info("Deleting project " + project.getName() + "(" + project.getId() + ")"); cloudUI.executeBackgroundTask(new ITask() { @@ -435,7 +492,7 @@ public class ResultsScrollPanel extends JScrollPane { buttonPanel.add(downloadButton); if (showEditControls) { buttonPanel.add(editButton); - buttonPanel.add(reuploadButton); + buttonPanel.add(replaceButton); buttonPanel.add(deleteButton); } diff --git a/diylc/diylc-swing/update.xml b/diylc/diylc-swing/update.xml index fffaeba0..79ade78e 100644 --- a/diylc/diylc-swing/update.xml +++ b/diylc/diylc-swing/update.xml @@ -1358,6 +1358,14 @@ IMPROVEMENT Popup dialogs work better with keyboard - Enter confirms, Escape dismisses dialogs + + IMPROVEMENT + Make order of items in the 'Edit Project' dialog more logical + + + IMPROVEMENT + Allow multi-line project description + BUG_FIX Changing Display to one trimmer affects all trimmers and does not get saved to the file