From 6f9bacf1eb5071e682cbc95cac1b59e5ff3fafa3 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 15 May 2019 17:01:12 +0200 Subject: [PATCH] SEBSERV-45 created exam config pages general and user interface --- .../ch/ethz/seb/sebserver/gbl/Constants.java | 2 + .../seb/sebserver/gbl/api/EntityType.java | 2 +- .../seb/sebserver/gbl/async/AsyncService.java | 4 - .../ethz/seb/sebserver/gbl/model/Domain.java | 3 +- .../gbl/model/sebconfig/AttributeType.java | 12 +- .../sebconfig/ConfigurationAttribute.java | 2 + .../gbl/model/sebconfig/TitleOrientation.java | 4 +- .../sebserver/gbl/model/sebconfig/View.java | 22 +- .../ch/ethz/seb/sebserver/gbl/util/Utils.java | 19 ++ .../content/SebExamConfigPropertiesForm.java | 93 +++--- .../examconfig/ExamConfigurationService.java | 33 ++- .../gui/service/examconfig/InputField.java | 8 +- .../service/examconfig/InputFieldBuilder.java | 2 + .../examconfig/ValueChangeListener.java | 6 + .../examconfig/impl/AbstractInputField.java | 44 ++- .../examconfig/impl/AttributeMapping.java | 4 + .../impl/CellFieldBuilderAdapter.java | 198 +++++++++++++ .../examconfig/impl/CheckBoxBuilder.java | 45 ++- .../impl/ExamConfigurationServiceImpl.java | 23 +- .../impl/MultiCheckboxSelection.java | 107 +++++++ .../examconfig/impl/PassworFieldBuilder.java | 2 +- .../impl/RadioSelectionFieldBuilder.java | 112 ++++++++ .../impl/SelectionFieldBuilder.java | 75 +++++ .../impl/SingleSelectionFieldBuilder.java | 48 +--- .../examconfig/impl/SliderFieldBuilder.java | 115 ++++++++ .../examconfig/impl/TableFieldBuilder.java | 6 +- .../examconfig/impl/TableRowFormBuilder.java | 6 +- .../examconfig/impl/TextFieldBuilder.java | 2 +- .../service/examconfig/impl/ViewContext.java | 93 +++++- .../examconfig/impl/ViewGridBuilder.java | 267 +++++------------- .../impl/rules/BrowserViewModeRule.java | 74 +++++ .../impl/rules/ExitKeySequenceChangeRule.java | 46 +++ .../impl/rules/HideToolbarDefaultRule.java | 47 +++ .../gui/service/i18n/I18nSupport.java | 11 +- .../service/page/impl/PageContextImpl.java | 1 + .../sebserver/gui/widget/ColorSelection.java | 2 +- .../seb/sebserver/gui/widget/ImageUpload.java | 2 +- .../seb/sebserver/gui/widget/Message.java | 2 +- .../sebserver/gui/widget/MultiSelection.java | 4 +- .../gui/widget/MultiSelectionCheckbox.java | 133 +++++++++ .../gui/widget/MultiSelectionCombo.java | 9 +- .../sebserver/gui/widget/RadioSelection.java | 121 ++++++++ .../seb/sebserver/gui/widget/Selection.java | 11 + .../sebserver/gui/widget/SingleSelection.java | 16 +- .../sebserver/gui/widget/ThresholdList.java | 2 +- .../sebserver/gui/widget/WidgetFactory.java | 119 ++++++-- ...AdditionalAttributesDynamicSqlSupport.java | 14 +- .../mapper/AdditionalAttributesMapper.java | 36 +-- ...ientConnectionRecordDynamicSqlSupport.java | 20 +- .../mapper/ClientConnectionRecordMapper.java | 36 +-- .../ClientEventRecordDynamicSqlSupport.java | 18 +- .../batis/mapper/ClientEventRecordMapper.java | 36 +-- ...ationAttributeRecordDynamicSqlSupport.java | 20 +- .../ConfigurationAttributeRecordMapper.java | 36 +-- ...figurationNodeRecordDynamicSqlSupport.java | 20 +- .../mapper/ConfigurationNodeRecordMapper.java | 36 +-- .../ConfigurationRecordDynamicSqlSupport.java | 16 +- .../mapper/ConfigurationRecordMapper.java | 36 +-- ...igurationValueRecordDynamicSqlSupport.java | 18 +- .../ConfigurationValueRecordMapper.java | 36 +-- ...nfigurationMapRecordDynamicSqlSupport.java | 14 +- .../ExamConfigurationMapRecordMapper.java | 36 +-- .../mapper/ExamRecordDynamicSqlSupport.java | 22 +- .../batis/mapper/ExamRecordMapper.java | 36 +-- .../IndicatorRecordDynamicSqlSupport.java | 14 +- .../batis/mapper/IndicatorRecordMapper.java | 36 +-- .../InstitutionRecordDynamicSqlSupport.java | 16 +- .../batis/mapper/InstitutionRecordMapper.java | 36 +-- .../LmsSetupRecordDynamicSqlSupport.java | 22 +- .../batis/mapper/LmsSetupRecordMapper.java | 36 +-- .../OrientationRecordDynamicSqlSupport.java | 24 +- .../batis/mapper/OrientationRecordMapper.java | 36 +-- .../mapper/RoleRecordDynamicSqlSupport.java | 10 +- .../batis/mapper/RoleRecordMapper.java | 36 +-- ...ebClientConfigRecordDynamicSqlSupport.java | 20 +- .../mapper/SebClientConfigRecordMapper.java | 36 +-- .../ThresholdRecordDynamicSqlSupport.java | 12 +- .../batis/mapper/ThresholdRecordMapper.java | 36 +-- ...serActivityLogRecordDynamicSqlSupport.java | 18 +- .../mapper/UserActivityLogRecordMapper.java | 36 +-- .../mapper/UserRecordDynamicSqlSupport.java | 24 +- .../batis/mapper/UserRecordMapper.java | 36 +-- .../mapper/ViewRecordDynamicSqlSupport.java | 15 +- .../batis/mapper/ViewRecordMapper.java | 50 ++-- .../batis/model/AdditionalAttributes.java | 28 +- .../batis/model/ClientConnectionRecord.java | 40 +-- .../batis/model/ClientEventRecord.java | 36 +-- .../model/ConfigurationAttributeRecord.java | 40 +-- .../batis/model/ConfigurationNodeRecord.java | 40 +-- .../batis/model/ConfigurationRecord.java | 32 +-- .../batis/model/ConfigurationValueRecord.java | 36 +-- .../model/ExamConfigurationMapRecord.java | 28 +- .../datalayer/batis/model/ExamRecord.java | 44 +-- .../batis/model/IndicatorRecord.java | 28 +- .../batis/model/InstitutionRecord.java | 32 +-- .../datalayer/batis/model/LmsSetupRecord.java | 44 +-- .../batis/model/OrientationRecord.java | 48 ++-- .../datalayer/batis/model/RoleRecord.java | 20 +- .../batis/model/SebClientConfigRecord.java | 40 +-- .../batis/model/ThresholdRecord.java | 24 +- .../batis/model/UserActivityLogRecord.java | 36 +-- .../datalayer/batis/model/UserRecord.java | 48 ++-- .../datalayer/batis/model/ViewRecord.java | 34 ++- .../servicelayer/dao/impl/ViewDAOImpl.java | 3 + .../sebconfig/SebConfigCryptor.java | 12 + .../sebconfig/SebConfigEncryptionService.java | 10 - .../servicelayer/sebconfig/ZipService.java | 6 + .../sebconfig/impl/DecimalTypeValidator.java | 10 +- .../impl/ExitKeySequenceValidator.java | 85 ++++++ .../sebconfig/impl/IntegerTypeValidator.java | 10 +- .../sebconfig/impl/NoneEncryptor.java | 4 - .../sebconfig/impl/PasswordEncryptor.java | 6 - .../impl/SebConfigEncryptionServiceImpl.java | 11 - .../impl/SebExamConfigServiceImpl.java | 3 +- .../sebconfig/impl/WindowsSizeValidator.java | 55 ++++ .../sebconfig/impl/ZipServiceImpl.java | 4 - .../api/SebClientConfigController.java | 4 - src/main/resources/logback-spring.xml | 16 +- src/main/resources/messages.properties | 93 +++++- src/main/resources/schema-demo.sql | 1 + src/main/resources/schema-dev.sql | 1 + src/main/resources/static/css/sebserver.css | 63 +++-- .../SebConfigEncryptionServiceImplTest.java | 2 + src/test/resources/schema-test.sql | 1 + 124 files changed, 2772 insertions(+), 1300 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/MultiCheckboxSelection.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/RadioSelectionFieldBuilder.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SelectionFieldBuilder.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/SliderFieldBuilder.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/BrowserViewModeRule.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/ExitKeySequenceChangeRule.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/HideToolbarDefaultRule.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCheckbox.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/widget/RadioSelection.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExitKeySequenceValidator.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/WindowsSizeValidator.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 6559e536..279c8319 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -31,6 +31,8 @@ public final class Constants { public static final String FORM_URL_ENCODED_SEPARATOR = "&"; public static final String FORM_URL_ENCODED_NAME_VALUE_SEPARATOR = "="; + public static final String PERCENTAGE = "%"; + public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; public static final String DEFAULT_DISPLAY_DATE_FORMAT = "MM-dd-yyy HH:mm"; public static final String TIME_ZONE_OFFSET_TAIL_FORMAT = "|ZZ"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/EntityType.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/EntityType.java index c35d8470..557919e7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/EntityType.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/EntityType.java @@ -2,7 +2,7 @@ package ch.ethz.seb.sebserver.gbl.api; import javax.annotation.Generated; -@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2019-04-30T14:19:48.997+02:00") +@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2019-05-13T14:59:57.056+02:00") public enum EntityType { CONFIGURATION_ATTRIBUTE, CONFIGURATION_VALUE, diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncService.java b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncService.java index 1d2b2b24..87632603 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/async/AsyncService.java @@ -10,8 +10,6 @@ package ch.ethz.seb.sebserver.gbl.async; import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -19,8 +17,6 @@ import org.springframework.stereotype.Service; @Service public class AsyncService { - private static final Logger log = LoggerFactory.getLogger(AsyncService.class); - private final AsyncRunner asyncRunner; protected AsyncService(final AsyncRunner asyncRunner) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/Domain.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/Domain.java index 99b97513..e4c24736 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/Domain.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/Domain.java @@ -5,7 +5,7 @@ import javax.annotation.Generated; /** Defines the global names of the domain model and domain model fields. * This shall be used as a static overall domain model names reference within SEB Server Web-Service as well as within the integrated GUI * This file is generated by the org.eth.demo.sebserver.gen.DomainModelNameReferencePlugin and must not be edited manually.**/ -@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2019-04-30T14:19:48.912+02:00") +@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2019-05-13T14:59:57.013+02:00") public interface Domain { interface CONFIGURATION_ATTRIBUTE { @@ -38,6 +38,7 @@ public interface Domain { String REFERENCE_NAME = "views"; String ATTR_ID = "id"; String ATTR_NAME = "name"; + String ATTR_COLUMNS = "columns"; String ATTR_POSITION = "position"; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/AttributeType.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/AttributeType.java index c8032071..3f687af1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/AttributeType.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/AttributeType.java @@ -23,8 +23,8 @@ public enum AttributeType { TEXT_AREA(TEXT), /** Check Box or boolean type */ CHECKBOX(TEXT), - /** Check Box or boolean type without label (e.g.: used in a table) */ - CHECK_FIELD(TEXT), + + SLIDER(TEXT), /** Integer number type */ INTEGER(TEXT), @@ -32,10 +32,14 @@ public enum AttributeType { DECIMAL(TEXT), /** Single selection type (Drop-down) */ SINGLE_SELECTION(TEXT), - /** Multiple selection type */ - MULTI_SELECTION(LIST), + + COMBO_SELECTION(TEXT), /** Radio selection type (like single selection but with check-boxes) */ RADIO_SELECTION(TEXT), + /** Multiple selection type */ + MULTI_SELECTION(LIST), + /** Multiple selection with checkbox type */ + MULTI_CHECKBOX_SELECTION(LIST), FILE_UPLOAD(BASE64_BINARY), diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java index 71997f45..4ccbb267 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/ConfigurationAttribute.java @@ -24,6 +24,8 @@ import ch.ethz.seb.sebserver.gbl.model.Entity; @JsonIgnoreProperties(ignoreUnknown = true) public final class ConfigurationAttribute implements Entity { + public static final String DEPENDENCY_RESOURCE_LOC_TEXT_KEY = "resourceLocTextKey"; + public static final String FILTER_ATTR_PARENT_ID = "parentId"; public static final String FILTER_ATTR_TYPE = "type"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/TitleOrientation.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/TitleOrientation.java index e4b33088..e1d77b98 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/TitleOrientation.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/TitleOrientation.java @@ -1,6 +1,6 @@ /* * 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/. @@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gbl.model.sebconfig; public enum TitleOrientation { NONE, LEFT, + LEFT_SPAN, RIGHT, + RIGHT_SPAN, TOP } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/View.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/View.java index 322f0014..e2d25742 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/View.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/sebconfig/View.java @@ -28,6 +28,10 @@ public class View implements Entity { @JsonProperty(VIEW.ATTR_NAME) public final String name; + @NotNull + @JsonProperty(VIEW.ATTR_COLUMNS) + public final Integer columns; + @NotNull @JsonProperty(VIEW.ATTR_POSITION) public final Integer position; @@ -35,16 +39,19 @@ public class View implements Entity { public View( @JsonProperty(VIEW.ATTR_ID) final Long id, @JsonProperty(VIEW.ATTR_NAME) final String name, + @JsonProperty(VIEW.ATTR_COLUMNS) final Integer columns, @JsonProperty(VIEW.ATTR_POSITION) final Integer position) { this.id = id; this.name = name; + this.columns = columns; this.position = position; } public View(final POSTMapper postParams) { this.id = null; this.name = postParams.getString(Domain.VIEW.ATTR_NAME); + this.columns = postParams.getInteger(Domain.VIEW.ATTR_COLUMNS); this.position = postParams.getInteger(Domain.VIEW.ATTR_POSITION); } @@ -52,6 +59,10 @@ public class View implements Entity { return this.position; } + public Integer getColumns() { + return this.columns; + } + public Long getId() { return this.id; } @@ -75,15 +86,8 @@ public class View implements Entity { @Override public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("View [id="); - builder.append(this.id); - builder.append(", name="); - builder.append(this.name); - builder.append(", position="); - builder.append(this.position); - builder.append("]"); - return builder.toString(); + return "View [id=" + this.id + ", name=" + this.name + ", columns=" + this.columns + ", position=" + + this.position + "]"; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java index efe9b465..4e3f0ae2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/util/Utils.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; public final class Utils { @@ -293,4 +294,22 @@ public final class Utils { return toCharArray(CharBuffer.wrap(chars)); } + public static Map getAttributeDependencyMap(final ConfigurationAttribute attribute) { + if (StringUtils.isBlank(attribute.dependencies)) { + return Collections.emptyMap(); + } + + try { + return Arrays.asList(StringUtils.split(attribute.dependencies, Constants.LIST_SEPARATOR)) + .stream() + .map(s -> StringUtils.split(s, Constants.EMBEDDED_LIST_SEPARATOR)) + .collect(Collectors.toMap(pair -> pair[0], pair -> pair[1])); + } catch (final Exception e) { + log.error("Unexpected error while trying to parse dependency map of ConfigurationAttribute: {}", + attribute, + e); + return Collections.emptyMap(); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropertiesForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropertiesForm.java index 8d141037..f4350aab 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropertiesForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigPropertiesForm.java @@ -16,6 +16,8 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -45,8 +47,10 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @GuiProfile public class SebExamConfigPropertiesForm implements TemplateComposer { + private static final Logger log = LoggerFactory.getLogger(SebExamConfigPropertiesForm.class); + private static final String VIEW_TEXT_KEY_PREFIX = "sebserver.examconfig.props.form.views."; - private static final String VIEW_TOOLTIP_TEXT_KEY_PREFIX = "tooltip"; + private static final String VIEW_TOOLTIP_TEXT_KEY_SUFFIX = ".tooltip"; private static final LocTextKey TITLE_TEXT_KEY = new LocTextKey("sebserver.examconfig.props.from.title"); @@ -75,58 +79,63 @@ public class SebExamConfigPropertiesForm implements TemplateComposer { final EntityKey entityKey = pageContext.getEntityKey(); final EntityKey parentEntityKey = pageContext.getParentEntityKey(); - final ConfigurationNode configNode = this.restService.getBuilder(GetExamConfigNode.class) - .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) - .call() - .onError(pageContext::notifyError) - .getOrThrow(); - - final Configuration configuration = this.restService.getBuilder(GetConfigurations.class) - .withQueryParam(Configuration.FILTER_ATTR_CONFIGURATION_NODE_ID, configNode.getModelId()) - .withQueryParam(Configuration.FILTER_ATTR_FOLLOWUP, Constants.TRUE_STRING) - .call() - .map(Utils::toSingleton) - .onError(pageContext::notifyError) - .getOrThrow(); - final Composite content = widgetFactory.defaultPageLayout( pageContext.getParent(), TITLE_TEXT_KEY); - final AttributeMapping attributes = this.examConfigurationService - .getAttributes(configNode.templateId) - .onError(pageContext::notifyError) - .getOrThrow(); + try { - final List views = this.examConfigurationService.getViews(attributes); + final ConfigurationNode configNode = this.restService.getBuilder(GetExamConfigNode.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .onError(pageContext::notifyError) + .getOrThrow(); - final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content); - tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + final Configuration configuration = this.restService.getBuilder(GetConfigurations.class) + .withQueryParam(Configuration.FILTER_ATTR_CONFIGURATION_NODE_ID, configNode.getModelId()) + .withQueryParam(Configuration.FILTER_ATTR_FOLLOWUP, Constants.TRUE_STRING) + .call() + .map(Utils::toSingleton) + .onError(pageContext::notifyError) + .getOrThrow(); - final List viewContexts = new ArrayList<>(); - for (final View view : views) { - final ViewContext viewContext = this.examConfigurationService.createViewContext( - pageContext, - configuration, - view, - attributes, - 4, - 20); - viewContexts.add(viewContext); + final AttributeMapping attributes = this.examConfigurationService + .getAttributes(configNode.templateId) + .onError(pageContext::notifyError) + .getOrThrow(); - final Composite viewGrid = this.examConfigurationService.createViewGrid( - tabFolder, - viewContext); + final List views = this.examConfigurationService.getViews(attributes); - final TabItem tabItem = widgetFactory.tabItemLocalized( - tabFolder, - new LocTextKey(VIEW_TEXT_KEY_PREFIX + view.name), - new LocTextKey(VIEW_TEXT_KEY_PREFIX + view.name + VIEW_TOOLTIP_TEXT_KEY_PREFIX)); - tabItem.setControl(viewGrid); + final TabFolder tabFolder = widgetFactory.tabFolderLocalized(content); + tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + final List viewContexts = new ArrayList<>(); + for (final View view : views) { + final ViewContext viewContext = this.examConfigurationService.createViewContext( + pageContext, + configuration, + view, + attributes, + 20); + viewContexts.add(viewContext); + + final Composite viewGrid = this.examConfigurationService.createViewGrid( + tabFolder, + viewContext); + + final TabItem tabItem = widgetFactory.tabItemLocalized( + tabFolder, + new LocTextKey(VIEW_TEXT_KEY_PREFIX + view.name)); + tabItem.setControl(viewGrid); + } + + this.examConfigurationService.initInputFieldValues(configuration.id, viewContexts); + + } catch (final Exception e) { + log.error("Unexpected error while trying to fetch exam configuration data and create views", e); + pageContext.notifyError(e); } - this.examConfigurationService.initInputFieldValues(configuration.id, viewContexts); - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java index 1f662239..4ffdf43e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ExamConfigurationService.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.examconfig; import java.util.Collection; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.widgets.Composite; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; @@ -20,6 +21,8 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.View; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gui.service.examconfig.impl.AttributeMapping; 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.PageContext; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -27,6 +30,7 @@ public interface ExamConfigurationService { public static final String ATTRIBUTE_LABEL_LOC_TEXT_PREFIX = "sebserver.examconfig.props.label."; public static final String GROUP_LABEL_LOC_TEXT_PREFIX = "sebserver.examconfig.props.group."; + public static final String TOOL_TIP_SUFFIX = ".tooltip"; WidgetFactory getWidgetFactory(); @@ -43,7 +47,6 @@ public interface ExamConfigurationService { Configuration configuration, View view, AttributeMapping attributeMapping, - int columns, int rows); Composite createViewGrid( @@ -54,4 +57,32 @@ public interface ExamConfigurationService { Long configurationId, Collection viewContexts); + static String attributeNameKey(final ConfigurationAttribute attribute) { + if (attribute == null) { + return null; + } + + return ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name; + } + + static LocTextKey attributeNameLocKey(final ConfigurationAttribute attribute) { + if (attribute == null) { + return null; + } + + return new LocTextKey(attributeNameKey(attribute)); + } + + static LocTextKey getToolTipKey( + final ConfigurationAttribute attribute, + final I18nSupport i18nSupport) { + + final String attributeNameKey = ExamConfigurationService.attributeNameKey(attribute) + TOOL_TIP_SUFFIX; + if (StringUtils.isBlank(i18nSupport.getText(attributeNameKey, ""))) { + return null; + } else { + return new LocTextKey(attributeNameKey); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java index dac89599..994adae9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputField.java @@ -21,7 +21,7 @@ public interface InputField { Orientation getOrientation(); - void initValue(Collection values); + ConfigurationValue initValue(Collection values); void initValue(final String value, final Integer listIndex); @@ -31,8 +31,10 @@ public interface InputField { void clearError(); - void disable(); + void disable(boolean group); - void enable(); + void enable(boolean group); + + void setDefaultValue(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputFieldBuilder.java index f672847a..e9a5421f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/InputFieldBuilder.java @@ -62,6 +62,8 @@ public interface InputFieldBuilder { final GridLayout gridLayout = new GridLayout(numColumns, true); gridLayout.verticalSpacing = 0; gridLayout.marginHeight = 1; + gridLayout.marginWidth = 0; + gridLayout.marginRight = 5; comp.setLayout(gridLayout); final GridData gridData = new GridData( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ValueChangeListener.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ValueChangeListener.java index 155806f2..eda7afee 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ValueChangeListener.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/ValueChangeListener.java @@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.service.examconfig; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext; public interface ValueChangeListener { @@ -22,4 +23,9 @@ public interface ValueChangeListener { void tableChanged(ConfigurationTableValues tableValue); + void notifyGUI( + ViewContext viewContext, + ConfigurationAttribute attribute, + ConfigurationValue initValue); + } 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 c2d51700..d4adaee1 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 @@ -10,8 +10,12 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl; import java.util.Collection; +import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; @@ -20,6 +24,8 @@ import ch.ethz.seb.sebserver.gui.service.examconfig.InputField; public abstract class AbstractInputField implements InputField { + private static final Logger log = LoggerFactory.getLogger(AbstractInputField.class); + protected final ConfigurationAttribute attribute; protected final Orientation orientation; protected final T control; @@ -46,17 +52,25 @@ public abstract class AbstractInputField implements InputFiel } @Override - public void disable() { + public void disable(final boolean group) { if (this.control.isEnabled()) { - setDefaultValue(); - this.control.setEnabled(false); + if (group) { + this.control.getParent().getParent().setEnabled(false); + } else { + setDefaultValue(); + this.control.setEnabled(false); + } } } @Override - public void enable() { + public void enable(final boolean group) { if (!this.control.isEnabled()) { - this.control.setEnabled(true); + if (group) { + this.control.getParent().getParent().setEnabled(true); + } else { + this.control.setEnabled(true); + } } } @@ -86,11 +100,15 @@ public abstract class AbstractInputField implements InputFiel } @Override - public void initValue(final Collection values) { - values.stream() + public ConfigurationValue initValue(final Collection values) { + return values.stream() .filter(a -> this.attribute.id.equals(a.attributeId)) .findFirst() - .ifPresent(v -> initValue(v.value, v.listIndex)); + .map(v -> { + initValue(v.value, v.listIndex); + return v; + }) + .orElse(null); } @Override @@ -100,8 +118,16 @@ public abstract class AbstractInputField implements InputFiel setValueToControl(this.initValue); } - protected void setDefaultValue() { + @Override + public void setDefaultValue() { setValueToControl(this.attribute.defaultValue); + final Event event = new Event(); + try { + this.control.notifyListeners(SWT.Selection, event); + this.control.notifyListeners(SWT.FocusOut, event); + } catch (final Exception e) { + log.warn("Failed to send value update to server: {}", this.attribute, e); + } } protected abstract void setValueToControl(String value); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AttributeMapping.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AttributeMapping.java index 65985da9..e4b5ef05 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AttributeMapping.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AttributeMapping.java @@ -77,6 +77,7 @@ public class AttributeMapping { this.attributeGroupMapping = Utils.immutableMapOf(orientations .stream() + .filter(o -> o.groupId != null) .map(o -> o.groupId) .collect(Collectors.toSet()) .stream() @@ -176,6 +177,9 @@ public class AttributeMapping { } private List getAttributesOfGroup(final String groupName) { + if (groupName == null) { + return Collections.emptyList(); + } return this.orientationAttributeMapping .values() .stream() diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java new file mode 100644 index 00000000..cb3718e9 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/CellFieldBuilderAdapter.java @@ -0,0 +1,198 @@ +/* + * 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; + +import java.util.Collection; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.TitleOrientation; +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.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +interface CellFieldBuilderAdapter { + + void createCell(ViewGridBuilder builder); + + static CellFieldBuilderAdapter dummyBuilderAdapter() { + return new CellFieldBuilderAdapter() { + @Override + public void createCell(final ViewGridBuilder builder) { + } + + @Override + public String toString() { + return "[DUMMY]"; + } + }; + } + + static CellFieldBuilderAdapter fieldBuilderAdapter( + final InputFieldBuilder inputFieldBuilder, + final ConfigurationAttribute attribute) { + + return new CellFieldBuilderAdapter() { + @Override + public void createCell(final ViewGridBuilder builder) { + + final InputField inputField = inputFieldBuilder.createInputField( + builder.parent, + attribute, + builder.viewContext); + + if (inputField != null) { + builder.viewContext.registerInputField(inputField); + } + } + + @Override + public String toString() { + return "[FIELD]"; + } + }; + } + + static CellFieldBuilderAdapter labelBuilder( + 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( + builder.parent, + new LocTextKey(ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name, + attribute.name)); + + final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); + switch (orientation.title) { + case LEFT: + case RIGHT: { + label.setAlignment(SWT.LEFT); + gridData.verticalIndent = 5; + break; + } + case LEFT_SPAN: + case RIGHT_SPAN: { + label.setAlignment(SWT.LEFT); + gridData.verticalIndent = 5; + gridData.horizontalSpan = orientation.width; + break; + } + case TOP: { + gridData.horizontalSpan = orientation.width; + label.setAlignment(SWT.LEFT); + break; + } + + default: { + label.setAlignment(SWT.LEFT); + } + } + label.setLayoutData(gridData); + label.pack(); + } + + @Override + public String toString() { + return "[LABEL]"; + } + }; + } + + static 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( + builder.parent, + new LocTextKey( + ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name + + ".confirm")); + final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); + label.setAlignment(SWT.LEFT); + gridData.verticalIndent = 20; + label.setLayoutData(gridData); + //label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key); + } + + @Override + public String toString() { + return "[PASSWORD CONFIRM LABEL]"; + } + }; + } + + static class GroupCellFieldBuilderAdapter implements CellFieldBuilderAdapter { + + final Collection orientationsOfGroup; + + int x = 100; + int y = 100; + int width = 1; + int height = 1; + + GroupCellFieldBuilderAdapter(final Collection orientationsOfGroup) { + this.orientationsOfGroup = orientationsOfGroup; + + for (final Orientation o : this.orientationsOfGroup) { + final int xpos = o.xPosition - ((o.title == TitleOrientation.LEFT) ? 1 : 0); + this.x = (xpos < this.x) ? xpos : this.x; + final int ypos = o.yPosition - ((o.title == TitleOrientation.TOP) ? 1 : 0); + this.y = (ypos < this.y) ? ypos : this.y; + this.width = (this.width < o.xpos() + o.width()) ? o.xpos() + o.width() : this.width; + this.height = (this.height < o.ypos() + o.height()) ? o.ypos() + o.height() : this.height; + } + + this.width = this.width - this.x; + this.height = this.height - this.y + 1; + } + + @Override + public void createCell(final ViewGridBuilder builder) { + final WidgetFactory widgetFactory = builder.examConfigurationService.getWidgetFactory(); + final Orientation o = this.orientationsOfGroup.stream().findFirst().get(); + final LocTextKey groupLabelKey = new LocTextKey( + ExamConfigurationService.GROUP_LABEL_LOC_TEXT_PREFIX + o.groupId, + o.groupId); + + final Group group = widgetFactory.groupLocalized( + builder.parent, + this.width, + groupLabelKey); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, this.width, this.height)); + + final ViewGridBuilder groupBuilder = new ViewGridBuilder( + group, + builder.viewContext, + this, + builder.examConfigurationService); + + for (final Orientation orientation : this.orientationsOfGroup) { + final ConfigurationAttribute attribute = builder.viewContext.getAttribute(orientation.attributeId); + groupBuilder.add(attribute); + } + groupBuilder.compose(); + } + } +} \ No newline at end of file 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 2ef55fcd..8da9807a 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 @@ -21,14 +21,23 @@ 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.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.i18n.I18nSupport; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @Lazy @Component @GuiProfile public class CheckBoxBuilder implements InputFieldBuilder { + private final WidgetFactory widgetFactory; + + protected CheckBoxBuilder(final WidgetFactory widgetFactory) { + this.widgetFactory = widgetFactory; + } + @Override public boolean builderFor( final ConfigurationAttribute attribute, @@ -38,8 +47,7 @@ public class CheckBoxBuilder implements InputFieldBuilder { return false; } - return attribute.type == AttributeType.CHECK_FIELD || - attribute.type == AttributeType.CHECKBOX; + return attribute.type == AttributeType.CHECKBOX; } @Override @@ -52,23 +60,32 @@ public class CheckBoxBuilder implements InputFieldBuilder { Objects.requireNonNull(attribute); Objects.requireNonNull(viewContext); - final Button checkbox = new Button(parent, SWT.CHECK); - if (attribute.type == AttributeType.CHECKBOX) { - checkbox.setText(attribute.name); - } + final I18nSupport i18nSupport = this.widgetFactory.getI18nSupport(); + final Orientation orientation = viewContext + .getOrientation(attribute.id); + final Composite innerGrid = InputFieldBuilder + .createInnerGrid(parent, orientation); + + final Button checkbox = this.widgetFactory.buttonLocalized( + innerGrid, + SWT.CHECK, + ExamConfigurationService.attributeNameLocKey(attribute), + ExamConfigurationService.getToolTipKey(attribute, i18nSupport)); + + final CheckboxField checkboxField = new CheckboxField( + attribute, + viewContext.getOrientation(attribute.id), + checkbox); checkbox.addListener( SWT.Selection, event -> viewContext.getValueChangeListener().valueChanged( viewContext, attribute, - String.valueOf(checkbox.getSelection()), - 0)); + checkboxField.getValue(), + checkboxField.listIndex)); - return new CheckboxField( - attribute, - viewContext.getOrientation(attribute.id), - checkbox); + return checkboxField; } static final class CheckboxField extends AbstractInputField