diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 660c3526..be507659 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -41,10 +41,6 @@ public final class API { public static final String PRIVILEGES_ENDPOINT = INFO_ENDPOINT + PRIVILEGES_PATH_SEGMENT; public static final String INSTITUTION_ENDPOINT = "/institution"; -// public static final String SEB_CONFIG_EXPORT_PATH_SEGMENT = "/sebconfig"; -// public static final String SEB_CONFIG_EXPORT_ENDPOINT = INSTITUTION_ENDPOINT -// + INSTITUTION_VAR_PATH_SEGMENT -// + SEB_CONFIG_EXPORT_PATH_SEGMENT; public static final String LMS_SETUP_ENDPOINT = "/lms_setup"; public static final String LMS_SETUP_TEST_PATH_SEGMENT = "/test"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationTableValue.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationTableValue.java index 2df6b4c7..54146fd1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationTableValue.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationTableValue.java @@ -24,8 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity; @JsonIgnoreProperties(ignoreUnknown = true) public final class ConfigurationTableValue implements GrantEntity { - public static final String ATTR_COLUMNS = "columnAttributeIds"; - public static final String ATTR_VALUES = "values"; + public static final String ATTR_TABLE_VALUES = "tableValues"; @NotNull @JsonProperty(CONFIGURATION_VALUE.ATTR_INSTITUTION_ID) @@ -39,24 +38,19 @@ public final class ConfigurationTableValue implements GrantEntity { @JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID) public final Long attributeId; - @JsonProperty(ATTR_COLUMNS) - public final List columnAttributeIds; - - @JsonProperty(ATTR_VALUES) - public final List values; + @JsonProperty(ATTR_TABLE_VALUES) + public final List values; @JsonCreator public ConfigurationTableValue( @JsonProperty(CONFIGURATION_VALUE.ATTR_INSTITUTION_ID) final Long institutionId, @JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID) final Long configurationId, @JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID) final Long attributeId, - @JsonProperty(ATTR_COLUMNS) final List columns, - @JsonProperty(ATTR_VALUES) final List values) { + @JsonProperty(ATTR_TABLE_VALUES) final List values) { this.institutionId = institutionId; this.configurationId = configurationId; this.attributeId = attributeId; - this.columnAttributeIds = Collections.unmodifiableList(columns); this.values = Collections.unmodifiableList(values); } @@ -88,11 +82,7 @@ public final class ConfigurationTableValue implements GrantEntity { return this.attributeId; } - public List getColumnAttributeIds() { - return this.columnAttributeIds; - } - - public List getValues() { + public List getValues() { return this.values; } @@ -100,8 +90,31 @@ public final class ConfigurationTableValue implements GrantEntity { public String toString() { return "ConfigurationTableValue [institutionId=" + this.institutionId + ", configurationId=" + this.configurationId - + ", attributeId=" + this.attributeId + ", columnAttributeIds=" + this.columnAttributeIds + ", values=" - + this.values - + "]"; + + ", attributeId=" + this.attributeId + ", values=" + this.values + "]"; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class TableValue { + + @JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID) + public final Long attributeId; + @JsonProperty(CONFIGURATION_VALUE.ATTR_LIST_INDEX) + public final Integer listIndex; + @JsonProperty(CONFIGURATION_VALUE.ATTR_VALUE) + public final String value; + + public TableValue( + @JsonProperty(CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID) final Long attributeId, + @JsonProperty(CONFIGURATION_VALUE.ATTR_LIST_INDEX) final Integer listIndex, + @JsonProperty(CONFIGURATION_VALUE.ATTR_VALUE) final String value) { + + this.attributeId = attributeId; + this.listIndex = listIndex; + this.value = value; + } + + public static TableValue of(final ConfigurationValue value) { + return new TableValue(value.attributeId, value.listIndex, value.value); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java index 04a36779..81bf9635 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java @@ -28,7 +28,7 @@ public abstract class AbstractInputField implements InputFiel protected String initValue = ""; protected int listIndex = 0; - AbstractInputField( + protected AbstractInputField( final ConfigurationAttribute attribute, final Orientation orientation, final T control, @@ -93,11 +93,15 @@ public abstract class AbstractInputField implements InputFiel .map(v -> { this.initValue = v.value; this.listIndex = (v.listIndex != null) ? v.listIndex : 0; - setDefaultValue(); + setValueToControl(this.initValue); return this.initValue; }); } - protected abstract void setDefaultValue(); + protected void setDefaultValue() { + setValueToControl(this.attribute.defaultValue); + } + + protected abstract void setValueToControl(String value); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java index 4e85ad46..ceb2b27b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CheckBoxBuilder.java @@ -81,7 +81,7 @@ public class CheckBoxBuilder implements InputFieldBuilder { } @Override - protected void setDefaultValue() { + protected void setValueToControl(final String value) { this.control.setSelection(Boolean.valueOf(this.initValue)); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java index 102b793a..b40aaa62 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ExamConfigurationServiceImpl.java @@ -51,6 +51,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.Ge import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationValues; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetOrientations; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViewList; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigTableValue; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigValue; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -269,8 +270,9 @@ public class ExamConfigurationServiceImpl implements ExamConfigurationService { @Override public void tableChanged(final ConfigurationTableValue tableValue) { - // TODO Auto-generated method stub - + this.restService.getBuilder(SaveExamConfigTableValue.class) + .withBody(tableValue) + .call(); } private String verifyErrorMessage(final Throwable error) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/LabelBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/LabelBuilder.java index 62d26d33..f362e82f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/LabelBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/LabelBuilder.java @@ -70,7 +70,7 @@ public class LabelBuilder implements InputFieldBuilder { } @Override - protected void setDefaultValue() { + protected void setValueToControl(final String value) { // Does Nothing, Label has no default value } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java index 14eeea30..3aa17356 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/PassworFieldBuilder.java @@ -146,11 +146,11 @@ public class PassworFieldBuilder implements InputFieldBuilder { } @Override - protected void setDefaultValue() { + protected void setValueToControl(final String value) { // TODO clarify setting some "fake" input when a password is set (like in config tool) if (this.initValue != null) { - this.control.setText(this.initValue); - this.confirm.setText(this.initValue); + this.control.setText(value); + this.confirm.setText(value); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java index 824a6909..818ce68c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldBuilder.java @@ -97,10 +97,9 @@ public class TextFieldBuilder implements InputFieldBuilder { } @Override - protected void setDefaultValue() { - this.control.setText(this.initValue); + protected void setValueToControl(final String value) { + this.control.setText(value); } - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java index c39f876f..d316b40e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewContext.java @@ -10,10 +10,12 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.View; import ch.ethz.seb.sebserver.gui.service.examconfig.InputField; @@ -84,6 +86,11 @@ public final class ViewContext { return this.rows; } + public List getChildAttributes(final ConfigurationAttribute attribute) { + // TODO Auto-generated method stub + return null; + } + public ValueChangeListener getValueChangeListener() { return this.valueChangeListener; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewGridBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewGridBuilder.java index f57cc4d6..1adaef33 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewGridBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/ViewGridBuilder.java @@ -19,6 +19,8 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; @@ -31,6 +33,8 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; public class ViewGridBuilder { + private static final Logger log = LoggerFactory.getLogger(ViewGridBuilder.class); + private final ExamConfigurationService examConfigurationService; private final Composite parent; private final ViewContext viewContext; @@ -85,34 +89,29 @@ public class ViewGridBuilder { attribute, orientation); - final CellFieldBuilderAdapter fieldBuilderAdapter = fieldBuilderAdapter( + this.grid[ypos][xpos] = fieldBuilderAdapter( inputFieldBuilder, attribute); - switch (orientation.title) { - case RIGHT: { - this.grid[ypos][xpos] = fieldBuilderAdapter; - this.grid[ypos][xpos + 1] = labelBuilder(attribute, orientation); - if (attribute.type == AttributeType.PASSWORD_FIELD) { - this.grid[ypos + 1][xpos + 1] = passwordConfirmLabel(attribute, orientation); + try { + switch (orientation.title) { + case RIGHT: { + this.grid[ypos][xpos + 1] = labelBuilder(attribute, orientation); + break; } - break; - } - case LEFT: { - this.grid[ypos][xpos] = labelBuilder(attribute, orientation); - if (attribute.type == AttributeType.PASSWORD_FIELD) { - this.grid[ypos + 1][xpos] = passwordConfirmLabel(attribute, orientation); + case LEFT: { + this.grid[ypos][xpos - 1] = labelBuilder(attribute, orientation); + break; + } + case TOP: { + this.grid[ypos - 1][xpos] = labelBuilder(attribute, orientation); + break; + } + default: { } - this.grid[ypos][xpos + 1] = fieldBuilderAdapter; - break; - } - case TOP: { - this.grid[ypos][xpos] = labelBuilder(attribute, orientation); - this.grid[ypos + 1][xpos] = fieldBuilderAdapter; - } - default: { - this.grid[ypos][xpos] = fieldBuilderAdapter; } + } catch (final ArrayIndexOutOfBoundsException e) { + log.error("Failed to set title as configured in: {} for attribute: {}", orientation, attribute, e); } return this; @@ -136,6 +135,7 @@ public class ViewGridBuilder { private static interface CellFieldBuilderAdapter { void createCell(ViewGridBuilder builder); + } private CellFieldBuilderAdapter dummyBuilderAdapter() { @@ -143,6 +143,12 @@ public class ViewGridBuilder { @Override public void createCell(final ViewGridBuilder builder) { } + + @Override + public String toString() { + return "[DUMMY]"; + } + }; } @@ -161,26 +167,10 @@ public class ViewGridBuilder { ViewGridBuilder.this.viewContext.registerInputField(inputField); } - }; - } - private CellFieldBuilderAdapter passwordConfirmLabel( - final ConfigurationAttribute attribute, - final Orientation orientation) { - - return new CellFieldBuilderAdapter() { @Override - public void createCell(final ViewGridBuilder builder) { - final WidgetFactory widgetFactory = builder.examConfigurationService.getWidgetFactory(); - final Label label = widgetFactory.labelLocalized( - ViewGridBuilder.this.parent, - new LocTextKey( - ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name + ".confirm"), - "Confirm Password"); - final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); - label.setAlignment(SWT.LEFT); - gridData.verticalIndent = 10; - label.setLayoutData(gridData); + public String toString() { + return "[FIELD]"; } }; } @@ -189,6 +179,10 @@ public class ViewGridBuilder { final ConfigurationAttribute attribute, final Orientation orientation) { + if (attribute.type == AttributeType.PASSWORD_FIELD) { + return passwordConfirmLabel(attribute, orientation); + } + return new CellFieldBuilderAdapter() { @Override public void createCell(final ViewGridBuilder builder) { @@ -208,7 +202,7 @@ public class ViewGridBuilder { break; } case TOP: { - label.setAlignment(SWT.BOTTOM); + label.setAlignment(SWT.LEFT); break; } @@ -221,6 +215,27 @@ public class ViewGridBuilder { }; } + private CellFieldBuilderAdapter passwordConfirmLabel( + final ConfigurationAttribute attribute, + final Orientation orientation) { + + return new CellFieldBuilderAdapter() { + @Override + public void createCell(final ViewGridBuilder builder) { + final WidgetFactory widgetFactory = builder.examConfigurationService.getWidgetFactory(); + final Label label = widgetFactory.labelLocalized( + ViewGridBuilder.this.parent, + new LocTextKey( + ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name + ".confirm"), + "Confirm Password"); + final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); + label.setAlignment(SWT.LEFT); + gridData.verticalIndent = 10; + label.setLayoutData(gridData); + } + }; + } + private static class GroupCellFieldBuilderAdapter implements CellFieldBuilderAdapter { final ViewGridBuilder builder; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/table/TableFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/table/TableFieldBuilder.java new file mode 100644 index 00000000..57bc2efb --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/table/TableFieldBuilder.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.examconfig.impl.table; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue.TableValue; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService; +import ch.ethz.seb.sebserver.gui.service.examconfig.InputField; +import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder; +import ch.ethz.seb.sebserver.gui.service.examconfig.impl.AbstractInputField; +import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext; +import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; + +@Lazy +@Component +@GuiProfile +public class TableFieldBuilder implements InputFieldBuilder { + + private static final Logger log = LoggerFactory.getLogger(TableFieldBuilder.class); + + private static final String ROW_VALUE_KEY = "RowValues"; + + private final WidgetFactory widgetFactory; + + public TableFieldBuilder(final WidgetFactory widgetFactory) { + this.widgetFactory = widgetFactory; + } + + @Override + public boolean builderFor( + final ConfigurationAttribute attribute, + final Orientation orientation) { + + if (attribute == null) { + return false; + } + + return AttributeType.TABLE == attribute.type; + } + + @Override + public InputField createInputField( + final Composite parent, + final ConfigurationAttribute attribute, + final ViewContext viewContext) { + + final I18nSupport i18nSupport = viewContext.getI18nSupport(); + + final Orientation orientation = viewContext.attributeMapping + .getOrientation(attribute.id); + final List childAttributes = + viewContext.attributeMapping.childAttributeMapping.get(attribute.id); + final List columnAttributes = childAttributes + .stream() + .filter(attr -> viewContext.attributeMapping.getOrientation(attr.id).xPosition > 0) + .sorted((attr1, attr2) -> viewContext.attributeMapping.getOrientation(attr1.id).xPosition.compareTo( + viewContext.attributeMapping.getOrientation(attr2.id).xPosition)) + .collect(Collectors.toList()); + + final Table table = new Table(parent, SWT.NONE | SWT.H_SCROLL); + table.setLayout(new GridLayout()); + final GridData gridData = new GridData( + SWT.FILL, SWT.FILL, + true, false, + (orientation != null) ? orientation.width() : 1, + (orientation != null) ? orientation.height() : 1); + gridData.heightHint = orientation.height * 40; + table.setLayoutData(gridData); + table.setHeaderVisible(true); + table.addListener(SWT.Resize, this::adaptColumnWidth); + + for (final ConfigurationAttribute columnAttribute : columnAttributes) { + final TableColumn column = new TableColumn(table, SWT.NONE); + final String text = i18nSupport.getText( + ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + columnAttribute.name, + columnAttribute.name); + column.setText(text); + column.setWidth(100); + column.setResizable(false); + } + + final TableInputField tableField = new TableInputField( + this.widgetFactory, + attribute, + orientation, + table, + childAttributes, + columnAttributes, + viewContext); + + TableColumn column = new TableColumn(table, SWT.NONE); + column.setImage(ImageIcon.ADD_BOX.getImage(parent.getDisplay())); + + column.setWidth(20); + column.setResizable(false); + column.setMoveable(false); + + column.addListener(SWT.Selection, event -> { + tableField.addRow(); + }); + + column = new TableColumn(table, SWT.NONE); + column.setImage(ImageIcon.REMOVE_BOX.getImage(parent.getDisplay())); + + column.setWidth(20); + column.setResizable(false); + column.setMoveable(false); + + column.addListener(SWT.Selection, event -> { + final int selectionIndex = table.getSelectionIndex(); + if (selectionIndex >= 0) { + tableField.deleteRow(selectionIndex); + } + }); + + table.addListener(SWT.MouseDoubleClick, event -> { + final int selectionIndex = table.getSelectionIndex(); + if (selectionIndex >= 0) { + tableField.openForm(selectionIndex); + } + }); + + return tableField; + } + + private void adaptColumnWidth(final Event event) { + try { + final Table table = (Table) event.widget; + final int currentTableWidth = table.getClientArea().width - 50; + final TableColumn[] columns = table.getColumns(); + final int columnWidth = currentTableWidth / (columns.length - 2); + for (int i = 0; i < columns.length - 2; i++) { + columns[i].setWidth(columnWidth); + } + } catch (final Exception e) { + log.warn("Failed to adaptColumnWidth: ", e); + } + } + + static final class TableInputField extends AbstractInputField { + + private final List childAttributes; + private final List columnAttributes; + private final ViewContext viewContext; + private final WidgetFactory widgetFactory; + + private List> values; + + TableInputField( + final WidgetFactory widgetFactory, + final ConfigurationAttribute attribute, + final Orientation orientation, + final Table control, + final List childAttributes, + final List columnAttributes, + final ViewContext viewContext) { + + super(attribute, orientation, control, null); + this.childAttributes = childAttributes; + this.columnAttributes = columnAttributes; + this.viewContext = viewContext; + this.widgetFactory = widgetFactory; + } + + @Override + public void initValue(final Collection values) { + // get all child values as TableValues + final List tableValues = values.stream() + .filter(this::isChildValue) + .map(TableValue::of) + .collect(Collectors.toList()); + + final Map> _initValue = new HashMap<>(); + for (final TableValue tableValue : tableValues) { + final Map rowValues = _initValue.computeIfAbsent( + tableValue.listIndex, + key -> new HashMap<>()); + rowValues.put(tableValue.attributeId, tableValue); + } + + final List rows = new ArrayList<>(_initValue.keySet()); + rows.sort((i1, i2) -> i1.compareTo(i2)); + + this.values = new ArrayList<>(); + rows + .stream() + .forEach(i -> { + final Map rowValues = _initValue.get(i); + this.values.add(rowValues); + addTableRow(rowValues); + }); + } + + private boolean isChildValue(final ConfigurationValue value) { + return this.attribute.id.equals( + this.viewContext.attributeMapping.attributeIdMapping + .get(value.attributeId).parentId); + } + + private void deleteRow(final int selectionIndex) { + this.control.remove(selectionIndex); + this.values.remove(selectionIndex); + // send new values to web-service + this.viewContext.getValueChangeListener() + .tableChanged(extractTableValue()); + } + + private void addRow() { + final int index = this.values.size(); + // create new values form default values + final Map rowValues = this.childAttributes + .stream() + .map(attr -> new TableValue(attr.id, index, attr.defaultValue)) + .collect(Collectors.toMap( + tv -> tv.attributeId, + Function.identity())); + + this.values.add(rowValues); + addTableRow(rowValues); + this.control.layout(); + } + + private void addTableRow(final Map rowValues) { + final TableItem tableItem = new TableItem(this.control, SWT.NONE); + applyTableRowValues(this.values.size() - 1); + +// TODO delete icon is not working on row as expected +// final TableEditor editor = new TableEditor(this.control); +// editor.horizontalAlignment = SWT.CENTER; +// editor.grabHorizontal = true; +// editor.minimumWidth = 20; +// final Image image = ImageIcon.REMOVE_BOX.getImage(this.control.getDisplay()); +// final Label imageLabel = new Label(this.control, SWT.NONE); +// imageLabel.setAlignment(SWT.CENTER); +// imageLabel.setImage(image); +// imageLabel.addListener(SWT.MouseDown, event -> System.out.println("*************** removeRow")); +// editor.setEditor(imageLabel, tableItem, this.columnAttributes.size()); +// tableItem.setData("EDITOR", editor); +// +// editor.layout(); +// this.control.layout(true, true); + + } + + private void applyRowValues( + final int rowIndex, + final Map rowValues) { + + // set the new values + this.values.set(rowIndex, rowValues); + // update table row + applyTableRowValues(rowIndex); + + } + + private void applyTableRowValues(final int index) { + final TableItem item = this.control.getItem(index); + final Map rowValues = this.values.get(index); + + int cellIndex = 0; + for (final ConfigurationAttribute attr : this.columnAttributes) { + final String value = rowValues.containsKey(attr.id) + ? rowValues.get(attr.id).value + : null; + item.setText(cellIndex, value); + cellIndex++; + } + + item.setData(ROW_VALUE_KEY, item); + + // send new values to web-service + this.viewContext.getValueChangeListener() + .tableChanged(extractTableValue()); + } + + private void openForm(final int selectionIndex) { + final Map rowValues = this.values.get(selectionIndex); + final ModalInputDialog> dialog = new ModalInputDialog<>( + this.control.getShell(), + this.widgetFactory); + + final TableRowFormBuilder builder = new TableRowFormBuilder(rowValues); + dialog.open( + new LocTextKey("Title"), + v -> System.out.println("Values Applied"), + builder); + } + + private ConfigurationTableValue extractTableValue() { + final List collect = this.values + .stream() + .flatMap(map -> map.values().stream()) + .collect(Collectors.toList()); + + return new ConfigurationTableValue( + this.viewContext.getInstitutionId(), + this.viewContext.getConfigurationId(), + this.attribute.id, + collect); + + } + + @Override + protected void setDefaultValue() { + // NOTE this just empty the list for now + // TODO do we need default values for lists? + this.control.setSelection(-1); + if (this.control.getItemCount() > 0) { + for (final TableItem item : this.control.getItems()) { + item.dispose(); + } + } + + final List values = new ArrayList<>(); + this.viewContext.getValueChangeListener().tableChanged( + new ConfigurationTableValue( + this.viewContext.getInstitutionId(), + this.viewContext.getConfigurationId(), + this.attribute.id, + values)); + } + + @Override + protected void setValueToControl(final String value) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/table/TableRowFormBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/table/TableRowFormBuilder.java new file mode 100644 index 00000000..656b96c3 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/table/TableRowFormBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.examconfig.impl.table; + +import java.util.Map; +import java.util.function.Supplier; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue.TableValue; +import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; + +public class TableRowFormBuilder implements ModalInputDialogComposer> { + + private final Map rowValues; + + public TableRowFormBuilder(final Map rowValues) { + this.rowValues = rowValues; + } + + @Override + public Supplier> compose(final Composite parent) { + final Label test = new Label(parent, SWT.NONE); + test.setText("TEST"); + return () -> null; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/ModalInputDialogComposer.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/ModalInputDialogComposer.java index 41b92ab7..364d9d36 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/ModalInputDialogComposer.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/ModalInputDialogComposer.java @@ -10,9 +10,11 @@ package ch.ethz.seb.sebserver.gui.service.page; import java.util.function.Supplier; +import org.eclipse.swt.widgets.Composite; + @FunctionalInterface public interface ModalInputDialogComposer { - Supplier compose(PageContext pageContext); + Supplier compose(Composite parent); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java index c885a620..4fd88ceb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java @@ -50,7 +50,6 @@ public class ModalInputDialog extends Dialog { public void open( final LocTextKey title, - final PageContext pageContext, final Consumer callback, final ModalInputDialogComposer contentComposer) { @@ -68,8 +67,7 @@ public class ModalInputDialog extends Dialog { gridData.horizontalSpan = 2; main.setLayoutData(gridData); - final PageContext internalPageContext = pageContext.copyOf(main); - final Supplier valueSuppier = contentComposer.compose(internalPageContext); + final Supplier valueSuppier = contentComposer.compose(main); final Button ok = this.widgetFactory.buttonLocalized(shell, OK_TEXT_KEY); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/SaveExamConfigTableValue.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/SaveExamConfigTableValue.java new file mode 100644 index 00000000..f5eb0d1a --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/SaveExamConfigTableValue.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig; + +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class SaveExamConfigTableValue extends RestCall { + + protected SaveExamConfigTableValue() { + super(new TypeKey<>( + CallType.SAVE, + EntityType.CONFIGURATION_VALUE, + new TypeReference() { + }), + HttpMethod.PUT, + MediaType.APPLICATION_JSON_UTF8, + API.CONFIGURATION_VALUE_ENDPOINT + API.CONFIGURATION_TABLE_VALUE_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java index ca8d192d..7ec04439 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionService.java @@ -35,7 +35,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result; * */ public interface BulkActionService { - /** Use this to collect all EntityKey's of all dependent entities for a given BulkAction. + /** Use this to collect all EntityKey's of dependent entities for a given BulkAction. * * @param action the BulkAction defining the source entity keys and acts also as the * dependency collector */ diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionSupportDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionSupportDAO.java index 91e71139..89314de7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionSupportDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/bulkaction/BulkActionSupportDAO.java @@ -45,10 +45,10 @@ public interface BulkActionSupportDAO { /** This processed a given BulkAction for all entities of the concrete type of this BulkActionSupportDAO * that are defined by this given BulkAction. - * + * * This returns a Collection of EntityKey results of each Entity that has been processed. * If there was an error for a particular Entity, the Result will have an error reference. - * + * * @param bulkAction the BulkAction containing the source entity and all dependencies * @return a Collection of EntityKey results of each Entity that has been processed. */ @Transactional @@ -92,7 +92,15 @@ public interface BulkActionSupportDAO { .collect(Collectors.toList()); } - @Transactional(readOnly = true) + /** Get dependency keys of all source entities of a given BulkAction + * This method simply goes through all source EntityKeys of the given BulkAction + * and applies the selection functions for each, collecting the resulting dependency EntityKeys + * into one Set of all dependency keys for all source keys + * + * + * @param bulkAction The BulkAction that defines the source keys + * @param selectionFunction a selection functions that gives all dependency keys for a given source key + * @return */ default Set getDependencies( final BulkAction bulkAction, final Function>> selectionFunction) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java index 2d47f0de..34f48ee9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java @@ -13,10 +13,10 @@ import static org.mybatis.dynamic.sql.SqlBuilder.isIn; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeValueType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValue.TableValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; @@ -213,61 +214,49 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO { .build() .execute(); - final List columnAttributeIds = columnAttributes.stream() - .map(a -> a.getId()) - .collect(Collectors.toList()); + final Map attributeMapping = columnAttributes + .stream() + .collect(Collectors.toMap(attr -> attr.getId(), Function.identity())); // get all values of the table and group them by attribute and sorted by list/row index - final List valueRecords = - this.configurationValueRecordMapper.selectByExample() - .where( - ConfigurationValueRecordDynamicSqlSupport.institutionId, - isEqualTo(institutionId)) - .and( - ConfigurationValueRecordDynamicSqlSupport.configurationId, - isEqualTo(configurationId)) - .and( - ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId, - SqlBuilder.isIn(columnAttributeIds)) - .build() - .execute(); - - int rows = 0; - final List values = new ArrayList<>(); - final Map> valueMapping = new LinkedHashMap<>(); - for (final ConfigurationValueRecord valueRecord : valueRecords) { - final List list = valueMapping.putIfAbsent( - valueRecord.getId(), - new ArrayList<>()); - list.add(valueRecord); - list.sort((r1, r2) -> r1.getListIndex().compareTo(r2.getListIndex())); - rows = list.size(); - } - - for (int row = 0; row < rows; row++) { - for (final ConfigurationAttributeRecord aRecord : columnAttributes) { - final List list = valueMapping.get(aRecord.getId()); - if (list != null) { - final ConfigurationValueRecord valueRecord = list.get(row); - if (valueRecord != null) { - values.add((isBigValue(aRecord)) ? valueRecord.getText() : valueRecord.getValue()); - continue; - } - } - - values.add(null); - } - } + final List values = this.configurationValueRecordMapper.selectByExample() + .where( + ConfigurationValueRecordDynamicSqlSupport.institutionId, + isEqualTo(institutionId)) + .and( + ConfigurationValueRecordDynamicSqlSupport.configurationId, + isEqualTo(configurationId)) + .and( + ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId, + SqlBuilder.isIn(new ArrayList<>(attributeMapping.keySet()))) + .build() + .execute() + .stream() + .map(value -> getTableValue(value, attributeMapping)) + .collect(Collectors.toList()); return new ConfigurationTableValue( institutionId, configurationId, attributeId, - new ArrayList<>(valueMapping.keySet()), values); }); } + private TableValue getTableValue( + final ConfigurationValueRecord value, + final Map attributeMapping) { + + final Long configurationAttributeId = value.getConfigurationAttributeId(); + final ConfigurationAttributeRecord configurationAttributeRecord = attributeMapping + .get(configurationAttributeId); + final boolean bigValue = isBigValue(configurationAttributeRecord); + return new TableValue( + value.getConfigurationAttributeId(), + value.getListIndex(), + bigValue ? value.getText() : value.getValue()); + } + @Override @Transactional public Result saveTableValue(final ConfigurationTableValue value) { @@ -276,18 +265,20 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO { .flatMap(val -> attributeRecordById(val.attributeId)) .map(attributeRecord -> { - final List columnAttributes = - this.configurationAttributeRecordMapper.selectByExample() - .where( - ConfigurationAttributeRecordDynamicSqlSupport.parentId, - isEqualTo(attributeRecord.getId())) - .build() - .execute(); + final Map attributeMap = this.configurationAttributeRecordMapper + .selectByExample() + .where( + ConfigurationAttributeRecordDynamicSqlSupport.parentId, + isEqualTo(attributeRecord.getId())) + .build() + .execute() + .stream() + .collect(Collectors.toMap(rec -> rec.getId(), Function.identity())); - final List columnAttributeIds = columnAttributes.stream() + final List columnAttributeIds = attributeMap.values() + .stream() .map(a -> a.getId()) .collect(Collectors.toList()); - final int columns = columnAttributeIds.size(); // first delete all old values of this table this.configurationValueRecordMapper.deleteByExample() @@ -301,27 +292,19 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO { .execute(); // then add the new values - int columnIndex = 0; - int rowIndex = 0; - for (final String val : value.values) { - final ConfigurationAttributeRecord columnAttr = columnAttributes.get(columnIndex); + for (final TableValue tableValue : value.values) { + final ConfigurationAttributeRecord columnAttr = attributeMap.get(tableValue.attributeId); final boolean bigValue = isBigValue(columnAttr); final ConfigurationValueRecord valueRecord = new ConfigurationValueRecord( null, value.institutionId, value.configurationId, columnAttr.getId(), - rowIndex, - (bigValue) ? null : val, - (bigValue) ? val : null); + tableValue.listIndex, + (bigValue) ? null : tableValue.value, + (bigValue) ? tableValue.value : null); this.configurationValueRecordMapper.insert(valueRecord); - - columnIndex++; - if (columnIndex >= columns) { - columnIndex = 0; - rowIndex++; - } } return value;