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 index ce8da4bc..4355428b 100644 --- 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 @@ -171,15 +171,16 @@ interface CellFieldBuilderAdapter { public void createCell(final ViewGridBuilder builder) { final WidgetFactory widgetFactory = builder.examConfigurationService.getWidgetFactory(); final Orientation o = this.orientationsOfGroup.stream().findFirst().orElse(null); + final String groupKey = ViewGridBuilder.getGroupKey(o.groupId); final LocTextKey groupLabelKey = new LocTextKey( ExamConfigurationService.GROUP_LABEL_LOC_TEXT_PREFIX + - o.groupId, - o.groupId); + groupKey, + groupKey); final LocTextKey groupTooltipKey = new LocTextKey( ExamConfigurationService.GROUP_LABEL_LOC_TEXT_PREFIX + - o.groupId + + groupKey + ExamConfigurationService.TOOL_TIP_SUFFIX, - o.groupId); + groupKey); final Group group = widgetFactory.groupLocalized( builder.parent, @@ -233,7 +234,7 @@ interface CellFieldBuilderAdapter { } this.width = this.width - this.x; - this.height = this.height - this.y + 1; + this.height = this.height - this.y + 2; } @Override @@ -252,7 +253,8 @@ interface CellFieldBuilderAdapter { final ExpandBar expandBar = widgetFactory.expandBarLocalized( builder.parent, - expandTooltipText); + expandTooltipText, + true); expandBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, this.width, this.height + 2)); for (final Map.Entry> entry : this.orientationsOfExpandBar.entrySet()) { @@ -268,7 +270,7 @@ interface CellFieldBuilderAdapter { this.width, labelKey); - expandItem.setHeight(this.height * 25); + expandItem.setHeight(this.height * 23); final Composite body = (Composite) expandItem.getControl(); final ViewGridBuilder expandBuilder = new ViewGridBuilder( body, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/ProctoringViewRules.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/ProctoringViewRules.java index 4031f26c..f6f3792f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/ProctoringViewRules.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/ProctoringViewRules.java @@ -24,18 +24,24 @@ import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext; public class ProctoringViewRules implements ValueChangeRule { public static final String KEY_ENABLE_AI = "proctoringAIEnable"; - public static final String KEY_ENABLE_JITSI = "jitsiMeetEnable"; + public static final String AI_GROUP_FACE_NUMBER = "proctoringDetectFaceCount"; + public static final String AI_GROUP_FACE_ANGLE = "proctoringDetectFaceYaw"; + public static final String KEY_ENABLE_JITSI = "jitsiMeetEnable"; public static final String JITSI_GROUP_AUDIO_VIDEO = "jitsiMeetReceiveAudio"; public static final String JITSI_GROUP_FEATURES = "jitsiMeetFeatureFlagChat"; public static final String JITSI_GROUP_CONTROLS = "jitsiMeetAudioMuted"; - public static final String AI_GROUP_FACE_NUMBER = "proctoringDetectFaceCount"; - public static final String AI_GROUP_FACE_ANGLE = "proctoringDetectFaceYaw"; + public static final String KEY_ENABLE_ZOOM = "zoomEnable"; + public static final String ZOOM_GROUP_AUDIO_VIDEO = "zoomReceiveAudio"; + public static final String ZOOM_GROUP_FEATURES = "zoomFeatureFlagChat"; + public static final String ZOOM_GROUP_CONTROLS = "zoomAudioMuted"; @Override public boolean observesAttribute(final ConfigurationAttribute attribute) { - return KEY_ENABLE_AI.equals(attribute.name) || KEY_ENABLE_JITSI.equals(attribute.name); + return KEY_ENABLE_AI.equals(attribute.name) || + KEY_ENABLE_JITSI.equals(attribute.name) || + KEY_ENABLE_ZOOM.equals(attribute.name); } @Override @@ -54,6 +60,16 @@ public class ProctoringViewRules implements ValueChangeRule { context.disableGroup(JITSI_GROUP_FEATURES); context.disableGroup(JITSI_GROUP_CONTROLS); } + } else if (KEY_ENABLE_ZOOM.equals(attribute.name)) { + if (BooleanUtils.toBoolean(value.value)) { + context.enableGroup(ZOOM_GROUP_AUDIO_VIDEO); + context.enableGroup(ZOOM_GROUP_FEATURES); + context.enableGroup(ZOOM_GROUP_CONTROLS); + } else { + context.disableGroup(ZOOM_GROUP_AUDIO_VIDEO); + context.disableGroup(ZOOM_GROUP_FEATURES); + context.disableGroup(ZOOM_GROUP_CONTROLS); + } } else if (KEY_ENABLE_AI.equals(attribute.name)) { if (BooleanUtils.toBoolean(value.value)) { context.enableGroup(AI_GROUP_FACE_NUMBER); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index 8e68d7f8..e0e6a4f1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.widget; import static ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY; import java.io.InputStream; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -526,10 +527,42 @@ public class WidgetFactory { public ExpandBar expandBarLocalized( final Composite parent, - final LocTextKey locTooltipKey) { + final LocTextKey locTooltipKey, + final boolean exclusive) { final ExpandBar expandBar = new ExpandBar(parent, SWT.NONE); this.polyglotPageService.injectI18n(expandBar, locTooltipKey); + + if (exclusive) { + expandBar.addListener(SWT.Expand, event -> { + try { + final Widget expandItem = event.item; + Arrays.asList(expandBar.getItems()) + .stream() + .filter(item -> !item.equals(expandItem)) + .forEach(item -> item.setExpanded(false)); + } catch (final Exception e) { + if (log.isDebugEnabled()) { + log.warn("Failed to automate ExpandBar", e); + } + } + }); + expandBar.addListener(SWT.Collapse, event -> { + try { + final Widget expandItem = event.item; + int itemIndex = Arrays.asList(expandBar.getItems()).indexOf(expandItem) + 1; + if (itemIndex >= expandBar.getItemCount()) { + itemIndex = 0; + } + expandBar.getItem(itemIndex).setExpanded(true); + } catch (final Exception e) { + if (log.isDebugEnabled()) { + log.warn("Failed to automate ExpandBar", e); + } + } + }); + } + return expandBar; } @@ -538,6 +571,7 @@ public class WidgetFactory { final int columns, final LocTextKey locTextKey) { + final int itemCount = parent.getItemCount(); final ExpandItem expandItem = new ExpandItem(parent, SWT.NONE); final Composite body = new Composite(expandItem.getParent(), SWT.NONE); final GridLayout gridLayout = new GridLayout(columns, true); @@ -546,8 +580,9 @@ public class WidgetFactory { gridLayout.marginHeight = 0; body.setLayout(gridLayout); expandItem.setControl(body); - + expandItem.setExpanded(itemCount == 0); this.polyglotPageService.injectI18n(expandItem, locTextKey); + return expandItem; } diff --git a/src/main/resources/config/sql/base/V7__insert_data_v1_2.sql b/src/main/resources/config/sql/base/V7__insert_data_v1_2.sql deleted file mode 100644 index c27366ba..00000000 --- a/src/main/resources/config/sql/base/V7__insert_data_v1_2.sql +++ /dev/null @@ -1,24 +0,0 @@ -INSERT IGNORE INTO configuration_attribute VALUES - (1500, 'zoomEnable', 'CHECKBOX', null, null, null, null, 'false'), - (1501, 'zoomAudioOnly', 'CHECKBOX', null, null, null, null, 'false'), - (1502, 'zoomAudioMuted', 'CHECKBOX', null, null, null, null, 'true'), - (1503, 'zoomFeatureFlagChat', 'CHECKBOX', null, null, null, null, 'false'), - (1504, 'zoomFeatureFlagCloseCaptions', 'CHECKBOX', null, null, null, null, 'false'), - (1505, 'zoomFeatureFlagDisplayMeetingName', 'CHECKBOX', null, null, null, null, 'false'), - (1506, 'zoomFeatureFlagRaiseHand', 'CHECKBOX', null, null, null, null, 'false'), - (1507, 'zoomFeatureFlagRecording', 'CHECKBOX', null, null, null, null, 'false'), - (1508, 'zoomFeatureFlagTileView', 'CHECKBOX', null, null, null, null, 'false'), - (1509, 'zoomRoom', 'TEXT_FIELD', null, null, null, null, ''), - (1510, 'zoomServerURL', 'TEXT_FIELD', null, null, null, null, ''), - (1511, 'zoomSubject', 'TEXT_FIELD', null, null, null, null, ''), - (1512, 'zoomToken', 'TEXT_FIELD', null, null, null, null, ''), - (1513, 'zoomUserInfoAvatarURL', 'TEXT_FIELD', null, null, null, null, ''), - (1514, 'zoomUserInfoDisplayName', 'TEXT_FIELD', null, null, null, null, ''), - (1515, 'zoomUserInfoEMail', 'TEXT_FIELD', null, null, null, null, ''), - (1516, 'zoomVideoMuted', 'CHECKBOX', null, null, null, null, 'false'), - - (1530, 'zoomReceiveAudio', 'CHECKBOX', null, null, null, null, 'false'), - (1531, 'zoomReceiveVideo', 'CHECKBOX', null, null, null, null, 'false'), - (1532, 'zoomSendAudio', 'CHECKBOX', null, null, null, null, 'true'), - (1533, 'zoomSendVideo', 'CHECKBOX', null, null, null, null, 'true') - ; \ No newline at end of file diff --git a/src/main/resources/config/sql/base/V7__proctoring_migration_v1_2.sql b/src/main/resources/config/sql/base/V7__proctoring_migration_v1_2.sql new file mode 100644 index 00000000..d9054880 --- /dev/null +++ b/src/main/resources/config/sql/base/V7__proctoring_migration_v1_2.sql @@ -0,0 +1,59 @@ +UPDATE orientation SET group_id='[proctoring|jitsi]' WHERE config_attribute_id='1102'; +UPDATE orientation SET group_id='jitsi_features[proctoring|jitsi]' WHERE config_attribute_id='1103'; +UPDATE orientation SET group_id='jitsi_features[proctoring|jitsi]' WHERE config_attribute_id='1106'; +UPDATE orientation SET group_id='jitsi_features[proctoring|jitsi]' WHERE config_attribute_id='1108'; +UPDATE orientation SET group_id='jitsi_features[proctoring|jitsi]' WHERE config_attribute_id='1104'; +UPDATE orientation SET group_id='jitsi_features[proctoring|jitsi]' WHERE config_attribute_id='1105'; +UPDATE orientation SET group_id='jitsi_controls[proctoring|jitsi]' WHERE config_attribute_id='1100'; +UPDATE orientation SET group_id='jitsi_controls[proctoring|jitsi]' WHERE config_attribute_id='1116'; +UPDATE orientation SET group_id='jitsi_controls[proctoring|jitsi]' WHERE config_attribute_id='1101'; +UPDATE orientation SET group_id='jitsi_audio_video[proctoring|jitsi]' WHERE config_attribute_id='1130'; +UPDATE orientation SET group_id='jitsi_audio_video[proctoring|jitsi]' WHERE config_attribute_id='1131'; +UPDATE orientation SET group_id='jitsi_audio_video[proctoring|jitsi]' WHERE config_attribute_id='1132'; +UPDATE orientation SET group_id='jitsi_audio_video[proctoring|jitsi]' WHERE config_attribute_id='1133'; + +INSERT IGNORE INTO configuration_attribute VALUES + (1500, 'zoomEnable', 'CHECKBOX', null, null, null, null, 'false'), + (1501, 'zoomAudioOnly', 'CHECKBOX', null, null, null, null, 'false'), + (1502, 'zoomAudioMuted', 'CHECKBOX', null, null, null, null, 'true'), + (1503, 'zoomFeatureFlagChat', 'CHECKBOX', null, null, null, null, 'false'), + (1504, 'zoomFeatureFlagCloseCaptions', 'CHECKBOX', null, null, null, null, 'false'), + (1505, 'zoomFeatureFlagDisplayMeetingName', 'CHECKBOX', null, null, null, null, 'false'), + (1506, 'zoomFeatureFlagRaiseHand', 'CHECKBOX', null, null, null, null, 'false'), + (1507, 'zoomFeatureFlagRecording', 'CHECKBOX', null, null, null, null, 'false'), + (1508, 'zoomFeatureFlagTileView', 'CHECKBOX', null, null, null, null, 'false'), + (1509, 'zoomRoom', 'TEXT_FIELD', null, null, null, null, ''), + (1510, 'zoomServerURL', 'TEXT_FIELD', null, null, null, null, ''), + (1511, 'zoomSubject', 'TEXT_FIELD', null, null, null, null, ''), + (1512, 'zoomToken', 'TEXT_FIELD', null, null, null, null, ''), + (1513, 'zoomUserInfoAvatarURL', 'TEXT_FIELD', null, null, null, null, ''), + (1514, 'zoomUserInfoDisplayName', 'TEXT_FIELD', null, null, null, null, ''), + (1515, 'zoomUserInfoEMail', 'TEXT_FIELD', null, null, null, null, ''), + (1516, 'zoomVideoMuted', 'CHECKBOX', null, null, null, null, 'false'), + + (1530, 'zoomReceiveAudio', 'CHECKBOX', null, null, null, null, 'false'), + (1531, 'zoomReceiveVideo', 'CHECKBOX', null, null, null, null, 'false'), + (1532, 'zoomSendAudio', 'CHECKBOX', null, null, null, null, 'true'), + (1533, 'zoomSendVideo', 'CHECKBOX', null, null, null, null, 'true') + ; + +SET @proct_view_id = (SELECT id FROM view WHERE name='proctoring'); + +INSERT IGNORE INTO orientation (config_attribute_id, template_id, view_id, group_id, x_position, y_position, width, height, title) VALUES + (1500, 0, @proct_view_id, '[proctoring|Zoom]', 6, 1, 6, 1, 'NONE'), + + (1503, 0, @proct_view_id, 'zoom_features[proctoring|Zoom]', 6, 7, 6, 1, 'NONE'), + (1506, 0, @proct_view_id, 'zoom_features[proctoring|Zoom]', 6, 8, 6, 1, 'NONE'), + (1508, 0, @proct_view_id, 'zoom_features[proctoring|Zoom]', 6, 9, 6, 1, 'NONE'), + (1504, 0, @proct_view_id, 'zoom_features[proctoring|Zoom]', 6, 10, 6, 1, 'NONE'), + (1505, 0, @proct_view_id, 'zoom_features[proctoring|Zoom]', 6, 11, 6, 1, 'NONE'), + + (1502, 0, @proct_view_id, 'zoom_controls[proctoring|Zoom]', 6, 13, 6, 1, 'NONE'), + (1516, 0, @proct_view_id, 'zoom_controls[proctoring|Zoom]', 6, 14, 6, 1, 'NONE'), + (1501, 0, @proct_view_id, 'zoom_controls[proctoring|Zoom]', 6, 15, 6, 1, 'NONE'), + + (1530, 0, @proct_view_id, 'zoom_audio_video[proctoring|Zoom]', 6, 2, 6, 1, 'NONE'), + (1531, 0, @proct_view_id, 'zoom_audio_video[proctoring|Zoom]', 6, 3, 6, 1, 'NONE'), + (1532, 0, @proct_view_id, 'zoom_audio_video[proctoring|Zoom]', 6, 4, 6, 1, 'NONE'), + (1533, 0, @proct_view_id, 'zoom_audio_video[proctoring|Zoom]', 6, 5, 6, 1, 'NONE') + ; \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 247f8a8b..b5afb240 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1300,6 +1300,8 @@ sebserver.examconfig.props.label.enableF10=Enable F10 sebserver.examconfig.props.label.enableF11=Enable F11 sebserver.examconfig.props.label.enableF12=Enable F12 +sebserver.examconfig.props.group.jitsi=Jitsi Meet Service Settings +sebserver.examconfig.props.group.jitsi.tooltip=Settings used with a Jitsi Meet service for proctoring sebserver.examconfig.props.group.jitsi_audio_video=Jitsi Meet Audio Video Settings sebserver.examconfig.props.group.jitsi_audio_video.tooltip= sebserver.examconfig.props.group.jitsi_features=Jitsi Meet Features @@ -1383,6 +1385,62 @@ sebserver.examconfig.props.label.remoteProctoringViewShow.2.tooltip= sebserver.examconfig.props.label.remoteProctoringViewShow.3=Always sebserver.examconfig.props.label.remoteProctoringViewShow.3.tooltip= +sebserver.examconfig.props.group.Zoom=Zoom Service Settings +sebserver.examconfig.props.group.Zoom.tooltip=Settings used with a Zoom Meeting service for proctoring +sebserver.examconfig.props.group.zoom_audio_video=Zoom Audio Video Settings +sebserver.examconfig.props.group.zoom_audio_video.tooltip= +sebserver.examconfig.props.group.zoom_features=Zoom Features +sebserver.examconfig.props.group.zoom_features.tooltip= +sebserver.examconfig.props.group.zoom_controls=Zoom User Controls +sebserver.examconfig.props.group.zoom_controls.tooltip=When the proctoring view is disabled (see its display policy on left), users can mute and unmute audio and video manually.
Audio and video streams can be disabled globally with the settings below. +sebserver.examconfig.props.label.zoomAudioMuted=Audio Initially Muted +sebserver.examconfig.props.label.zoomAudioMuted.tooltip= +sebserver.examconfig.props.label.zoomAudioOnly=Audio Only +sebserver.examconfig.props.label.zoomAudioOnly.tooltip= +sebserver.examconfig.props.label.zoomEnable=Enable Zoom +sebserver.examconfig.props.label.zoomEnable.tooltip= +sebserver.examconfig.props.label.zoomFeatureFlagChat=Enable Chat +sebserver.examconfig.props.label.zoomFeatureFlagChat.tooltip= +sebserver.examconfig.props.label.zoomFeatureFlagCloseCaptions=Enable Close Captions +sebserver.examconfig.props.label.zoomFeatureFlagCloseCaptions.tooltip= +sebserver.examconfig.props.label.zoomFeatureFlagDisplayingName=Display Meeting Name +sebserver.examconfig.props.label.zoomFeatureFlagDisplayMeetingName.tooltip +sebserver.examconfig.props.label.zoomFeatureFlagRaiseHand=Enable Raise Hand +sebserver.examconfig.props.label.zoomFeatureFlagRaiseHand.tooltip= +sebserver.examconfig.props.label.zoomFeatureFlagRecording=Allow Recording +sebserver.examconfig.props.label.zoomFeatureFlagRecording.tooltip= +sebserver.examconfig.props.label.zoomFeatureFlagTileView=Allow Tile View +sebserver.examconfig.props.label.zoomFeatureFlagTileView.tooltip=Note: Disabling Allow Tile View is not yet functional in this version. +sebserver.examconfig.props.label.zoomRoom=Room +sebserver.examconfig.props.label.zoomRoom.tooltip= +sebserver.examconfig.props.label.zoomServerURL=Server URL +sebserver.examconfig.props.label.zoomServerURL.tooltip= +sebserver.examconfig.props.label.zoomSubject=Subject +sebserver.examconfig.props.label.zoomSubject.tooltip= +sebserver.examconfig.props.label.zoomToken=Token +sebserver.examconfig.props.label.zoomToken.tooltip= +sebserver.examconfig.props.label.zoomUserInfoAvatarURL=Avatar URL +sebserver.examconfig.props.label.zoomUserInfoAvatarURL.tooltip= +sebserver.examconfig.props.label.zoomUserInfoDisplayName=Display Name +sebserver.examconfig.props.label.zoomUserInfoDisplayName.tooltip= +sebserver.examconfig.props.label.zoomUserInfoEMail=Info Mail +sebserver.examconfig.props.label.zoomUserInfoEMail.tooltip= +sebserver.examconfig.props.label.zoomVideoMuted=Video Initially Muted +sebserver.examconfig.props.label.zoomVideoMuted.tooltip= + +sebserver.examconfig.props.label.zoomReceiveAudio=Receive Audio +sebserver.examconfig.props.label.zoomReceiveAudio.tooltip=Global settings for receiving and sending audio/video streams.
Streams can also be enabled/disabled by SEB Server during the exam session. +sebserver.examconfig.props.label.zoomReceiveVideo=Receive Video +sebserver.examconfig.props.label.zoomReceiveVideo.tooltip=Global settings for receiving and sending audio/video streams.
Streams can also be enabled/disabled by SEB Server during the exam session. +sebserver.examconfig.props.label.zoomSendAudio=Send Audio +sebserver.examconfig.props.label.zoomSendAudio.tooltip=Global settings for receiving and sending audio/video streams.
Streams can also be enabled/disabled by SEB Server during the exam session. +sebserver.examconfig.props.label.zoomSendVideo=Send Video +sebserver.examconfig.props.label.zoomSendVideo.tooltip=Global settings for receiving and sending audio/video streams.
Streams can also be enabled/disabled by SEB Server during the exam session. + + + + + sebserver.examconfig.props.label.showProctoringViewButton=Show Proctoring Button sebserver.examconfig.props.label.showProctoringViewButton diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index 10240965..e2d0cf42 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -944,7 +944,50 @@ Table-GridLine:vertical, Table-GridLine:header, Table-GridLine:horizontal:rowtem } +/* ExpandBar default theme */ +ExpandBar { + color: #FFFFFF; + background-color: white; + border: none; +} +ExpandBar[BORDER] { + border: none; +} +ExpandItem { + color: #FFFFFF; + border: none; +} +ExpandItem-Header { + border: none; + cursor: pointer; + background-color: #595959; + background-gradient-color: #595959; + background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) ); + text-shadow: none; +} +ExpandItem-Header:disabled { + cursor: default; +} + +ExpandItem-Button { + background-image: url( static/images/expand.png ); +} + +ExpandItem-Button:hover { + background-image: + url( static/images/expand.png ); +} + +ExpandItem-Button:expanded { + background-image: + url( static/images/collapse.png ); +} + +ExpandItem-Button:expanded:hover { + background-image: + url( static/images/collapse.png ); +} diff --git a/src/main/resources/static/css/sms.css b/src/main/resources/static/css/sms.css index 92461f87..6d21b3c8 100644 --- a/src/main/resources/static/css/sms.css +++ b/src/main/resources/static/css/sms.css @@ -803,6 +803,53 @@ Table-GridLine:vertical, Table-GridLine:header, Table-GridLine:horizontal:rowtem color: transparent; } +/* ExpandBar default theme */ +ExpandBar { + color: #FFFFFF; + background-color: white; + border: none; +} + +ExpandBar[BORDER] { + border: none; +} + +ExpandItem { + color: #FFFFFF; + border: none; +} + +ExpandItem-Header { + border: none; + cursor: pointer; + background-color: #595959; + background-gradient-color: #595959; + background-image: gradient( linear, left top, left bottom, from( #595959 ), to( #595959 ) ); + text-shadow: none; +} + +ExpandItem-Header:disabled { + cursor: default; +} + +ExpandItem-Button { + background-image: url( static/images/expand.png ); +} + +ExpandItem-Button:hover { + background-image: + url( static/images/expand.png ); +} + +ExpandItem-Button:expanded { + background-image: + url( static/images/collapse.png ); +} + +ExpandItem-Button:expanded:hover { + background-image: + url( static/images/collapse.png ); +} diff --git a/src/main/resources/static/images/collapse.png b/src/main/resources/static/images/collapse.png new file mode 100644 index 00000000..59366b36 Binary files /dev/null and b/src/main/resources/static/images/collapse.png differ diff --git a/src/main/resources/static/images/expand.png b/src/main/resources/static/images/expand.png new file mode 100644 index 00000000..ff81970a Binary files /dev/null and b/src/main/resources/static/images/expand.png differ