Merge remote-tracking branch 'origin/master' into design2015

This commit is contained in:
fmittag committed 2015-04-20 18:38:52 +02:00
commit c65070c78f
226 files changed
+3247 -2074

No files matched your search

@@ -10,6 +10,7 @@ import com.google.gwt.i18n.client.Messages;
public interface StringMessages extends Messages {
String save();
String remove();
String ok();
String cancel();
String add();
String edit();
@@ -18,4 +19,7 @@ public interface StringMessages extends Messages {
String remoteProcedureCall();
String serverReplies();
String errorCommunicatingWithServer();
String configuration();
String settings();
String settingsForComponent(String localizedShortName);
}
@@ -1,5 +1,6 @@
save=Save
remove=Remove
ok=OK
cancel=Cancel
add=Add
edit=Edit
@@ -19,4 +20,7 @@ refresh=Refresh
name=Name
value=Value
description=Description
error=Error
error=Error
configuration=Configuration
settings=Settings
settingsForComponent=Settings for {0}
@@ -1,5 +1,6 @@
save=Sichern
remove=Löschen
ok=OK
cancel=Abbrechen
add=Hinzufügen
edit=Bearbeiten
@@ -19,4 +20,7 @@ refresh=Aktualisieren
name=Name
value=Wert
description=Beschreibung
error=Fehler
error=Fehler
configuration=Konfiguration
settings=Einstellungen
settingsForComponent=Einstellungen für {0}
@@ -1,5 +1,6 @@
save=Сохранить
remove=Удалить
ok=OK
cancel=Отмена
add=Добавить
edit=Изменить
@@ -19,4 +20,7 @@ refresh=Обновить
name=Имя
value=Значение
description=Описание
error=Error
error=Error
configuration=Конфигурация
settingsForComponent=Параметры {0}
settings=Параметры
@@ -10,5 +10,4 @@ import com.sap.sse.gwt.client.StringMessages;
*/
public interface TextMessages extends StringMessages {
public static final TextMessages INSTANCE = GWT.create(TextMessages.class);
}
@@ -1,5 +1,6 @@
save=Save
remove=Remove
ok=OK
cancel=Cancel
add=Add
edit=Edit
@@ -8,3 +9,4 @@ serverError=An error occurred while attempting to contact the server. Please che
remoteProcedureCall=Remote Procedure Call
serverReplies=Server replies
errorCommunicatingWithServer=Error communicating with server
configuration=Configuration
@@ -0,0 +1,19 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.LazyPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.settings.AbstractSettings;
public abstract class AbstractLazyComponent<SettingsType extends AbstractSettings> extends LazyPanel implements LazyComponent<SettingsType> {
@Override
public Widget getEntryWidget() {
ensureWidget();
return getWidget();
}
@Override
public String getDependentCssClassName() {
return null;
}
}
@@ -0,0 +1,413 @@
package com.sap.sse.gwt.client.shared.components;
import java.util.Iterator;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasAnimation;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A widget that consists of a header and a content panel that discloses the content when a user clicks on the header.
*
* <h3>CSS Style Rules</h3>
* <dl class="css">
* <dt>.collapsablePanel
* <dd>the panel's primary style
* <dt>.collapsablePanel-open
* <dd>dependent style set when panel is open
* <dt>.collapsablePanel-closed
* <dd>dependent style set when panel is closed
* </dl>
* <p>
* The header and content sections can be easily selected using css with a child selector:<br/>
* .collapsablePanel-open .header { ... }
* </p>
*/
public final class CollapsablePanel extends Composite implements HasWidgets.ForIsWidget, HasAnimation,
HasOpenHandlers<CollapsablePanel>, HasCloseHandlers<CollapsablePanel> {
private static ComponentResources resources = GWT.create(ComponentResources.class);
/**
* An {@link Animation} used to open the content.
*/
private static class ContentAnimation extends Animation {
/**
* Whether the item is being opened or closed.
*/
private boolean opening;
/**
* The {@link CollapsablePanel} being affected.
*/
private CollapsablePanel curPanel;
/**
* Open or close the content.
*
* @param panel
* the panel to open or close
* @param animate
* true to animate, false to open instantly
*/
public void setOpen(CollapsablePanel panel, boolean animate) {
// Immediately complete previous open
cancel();
// Open the new item
if (animate) {
curPanel = panel;
opening = panel.isOpen;
run(ANIMATION_DURATION);
} else {
panel.contentWrapper.setVisible(panel.isOpen);
if (panel.isOpen) {
// Special treatment on the visible case to ensure LazyPanel works
panel.getContent().setVisible(true);
}
}
}
@Override
protected void onComplete() {
if (!opening) {
curPanel.contentWrapper.setVisible(false);
}
curPanel.contentWrapper.setHeight("auto");
curPanel = null;
}
@Override
protected void onStart() {
super.onStart();
if (opening) {
curPanel.contentWrapper.setVisible(true);
// Special treatment on the visible case to ensure LazyPanel works
curPanel.getContent().setVisible(true);
}
}
@Override
protected void onUpdate(double progress) {
int scrollHeight = curPanel.contentWrapper.getElement().getPropertyInt("scrollHeight");
int height = (int) (progress * scrollHeight);
if (!opening) {
height = scrollHeight - height;
}
height = Math.max(height, 1);
curPanel.contentWrapper.getElement().getStyle().setHeight(height, Unit.PX);
curPanel.contentWrapper.setWidth("auto");
}
}
public interface Imager {
Image makeImage();
void updateImage(boolean open, Image image);
}
/**
* The duration of the animation.
*/
private static final int ANIMATION_DURATION = 350;
// style name constants.
private static final String STYLENAME_DEFAULT = "collapsablePanel";
private static final String STYLENAME_SUFFIX_OPEN = "open";
private static final String STYLENAME_SUFFIX_CLOSED = "closed";
private static final String STYLENAME_HEADER = "collapsablePanel-header";
private static final String STYLENAME_CONTENT = "collapsablePanel-content";
/**
* The {@link Animation} used to open and close the content.
*/
private static ContentAnimation contentAnimation;
/**
* top level widget. The first child will be a reference to {@link #header}. The second child will be a reference to
* {@link #contentWrapper}.
*/
private final VerticalPanel mainPanel = new VerticalPanel();
/**
* The wrapper around the content widget.
*/
private final SimplePanel contentWrapper = new SimplePanel();
/**
* The wrapper around the header widget.
*/
private final SimplePanel headerWrapper = new SimplePanel();
private CollapsablePanelHeader headerPanel;
private boolean isAnimationEnabled = false;
private boolean isOpen = false;
private boolean isCollapsingEnabled = true;
/**
* Creates an empty ThePanel that is initially closed.
*/
private CollapsablePanel() {
initWidget(mainPanel);
mainPanel.add(headerWrapper);
mainPanel.add(contentWrapper);
contentWrapper.getElement().getStyle().setPadding(0, Unit.PX);
contentWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
setStyleName(STYLENAME_DEFAULT);
setContentDisplay(false);
}
/**
* Creates a ThePanel with the specified header text, an initial open/close state and a bundle of images to be used
* in the default header widget.
*
* @param openImage
* the open state image resource
* @param closedImage
* the closed state image resource
* @param headerText
* the text to be displayed in the header
*/
public CollapsablePanel(ImageResource openImage, ImageResource closedImage, String headerText, boolean hasToolbar) {
this();
headerPanel = new CollapsablePanelHeader(this, openImage, closedImage, headerText, hasToolbar);
setHeader(headerPanel);
}
/**
* Creates a ThePanel that will be initially closed using the specified text in the header.
*
* @param headerText
* the text to be displayed in the header
*/
public CollapsablePanel(String headerText, boolean hasToolbar) {
this(resources.openIcon(), resources.closeIcon(), headerText, hasToolbar);
}
public void setHeaderToolbar(Widget toolbar) {
headerPanel.setToolbar(toolbar);
}
public void add(Widget w) {
if (this.getContent() == null) {
setContent(w);
} else {
throw new IllegalStateException("A CollapsablePanel can only contain two Widgets.");
}
}
/**
* Overloaded version for IsWidget.
*
* @see #add(Widget)
*/
public void add(IsWidget w) {
this.add(asWidgetOrNull(w));
}
public HandlerRegistration addCloseHandler(CloseHandler<CollapsablePanel> handler) {
return addHandler(handler, CloseEvent.getType());
}
public HandlerRegistration addOpenHandler(OpenHandler<CollapsablePanel> handler) {
return addHandler(handler, OpenEvent.getType());
}
public void clear() {
setContent(null);
}
/**
* Gets the widget that was previously set in {@link #setContent(Widget)}.
*
* @return the panel's current content widget
*/
public Widget getContent() {
return contentWrapper.getWidget();
}
/**
* Gets the widget that is currently being used as a header.
*
* @return the widget currently being used as a header
*/
public Widget getHeader() {
return headerWrapper.getWidget();
}
/**
* Gets a {@link HasText} instance to provide access to the headers's text, if the header widget does provide such
* access.
*
* @return a reference to the header widget if it implements {@link HasText}, <code>null</code> otherwise
*/
public HasText getHeaderTextAccessor() {
Widget widget = headerWrapper.getWidget();
return (widget instanceof HasText) ? (HasText) widget : null;
}
public boolean isAnimationEnabled() {
return isAnimationEnabled;
}
/**
* Determines whether the panel is open.
*
* @return <code>true</code> if panel is in open state
*/
public boolean isOpen() {
return isOpen;
}
public boolean isCollapsingEnabled() {
return isCollapsingEnabled;
}
public void setCollapsingEnabled(boolean enable) {
isCollapsingEnabled = enable;
this.headerPanel.setCollapsingEnabled(enable);
}
public Iterator<Widget> iterator() {
return null;
}
public boolean remove(Widget w) {
if (w == getContent()) {
setContent(null);
return true;
}
return false;
}
public boolean remove(IsWidget w) {
return this.remove(asWidgetOrNull(w));
}
public void setAnimationEnabled(boolean enable) {
isAnimationEnabled = enable;
}
public void setHeader(Widget headerContent) {
final Widget currentHeaderContent = getHeader();
// Remove existing header content widget.
if (currentHeaderContent != null) {
headerWrapper.setWidget(null);
currentHeaderContent.removeStyleName(STYLENAME_HEADER);
}
// Add new header content widget if != null.
if (headerContent != null) {
headerWrapper.setWidget(headerContent);
headerContent.addStyleName(STYLENAME_HEADER);
}
}
/**
* Sets the content widget which can be opened and closed by this panel. If there is a preexisting content widget,
* it will be detached.
*
* @param content
* the widget to be used as the content panel
*/
public void setContent(Widget content) {
final Widget currentContent = getContent();
// Remove existing content widget.
if (currentContent != null) {
contentWrapper.setWidget(null);
currentContent.removeStyleName(STYLENAME_CONTENT);
}
// Add new content widget if != null.
if (content != null) {
contentWrapper.setWidget(content);
content.addStyleName(STYLENAME_CONTENT);
setContentDisplay(false);
}
}
/**
* Changes the visible state of this <code>ThePanel</code>.
*
* @param isOpen
* <code>true</code> to open the panel, <code>false</code> to close
*/
public void setOpen(boolean isOpen) {
if (this.isOpen != isOpen) {
this.isOpen = isOpen;
setContentDisplay(true);
fireEvent();
}
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-header = the clickable header.</li>
* </ul>
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
headerWrapper.ensureDebugId(baseID + "-header");
}
private void fireEvent() {
if (isOpen) {
OpenEvent.fire(this, this);
} else {
CloseEvent.fire(this, this);
}
}
private void setContentDisplay(boolean animate) {
if (isOpen) {
removeStyleDependentName(STYLENAME_SUFFIX_CLOSED);
addStyleDependentName(STYLENAME_SUFFIX_OPEN);
} else {
removeStyleDependentName(STYLENAME_SUFFIX_OPEN);
addStyleDependentName(STYLENAME_SUFFIX_CLOSED);
}
if (getContent() != null) {
if (contentAnimation == null) {
contentAnimation = new ContentAnimation();
}
contentAnimation.setOpen(this, animate && isAnimationEnabled);
}
}
}
@@ -0,0 +1,160 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.shared.components.CollapsablePanel.Imager;
/**
* The default header widget used within a {@link CollapsablePanel}.
*/
public class CollapsablePanelHeader extends FlowPanel implements HasText, OpenHandler<CollapsablePanel>,
CloseHandler<CollapsablePanel> {
private final Label textLabel;
private final Image iconImage;
private final Imager imager;
private final CollapsablePanel collapsablePanel;
private final boolean hasToolbar;
private final SimplePanel toolbarPanel;
private final ClickableHeader clickableHeaderPanel;
private static final String STYLENAME_TOOLBAR = "collapsablePanel-toolbar";
private static final String STYLENAME_HEADER_COLLAPSE_AREA = "collapsablePanel-header-collapse";
private static final String STYLENAME_HEADER_TEXT = "collapsablePanel-header-text";
private static final String STYLENAME_HEADER_IMAGE = "collapsablePanel-header-image";
protected CollapsablePanelHeader(CollapsablePanel collapsablePanel, Imager imager, String text, boolean hasToolbar) {
this.imager = imager;
this.hasToolbar = hasToolbar;
this.collapsablePanel = collapsablePanel;
iconImage = imager.makeImage();
iconImage.addStyleName(STYLENAME_HEADER_IMAGE);
textLabel= new Label(text);
textLabel.addStyleName(STYLENAME_HEADER_TEXT);
clickableHeaderPanel = new ClickableHeader(collapsablePanel);
clickableHeaderPanel.setStyleName(STYLENAME_HEADER_COLLAPSE_AREA);
add(clickableHeaderPanel);
FlowPanel clickableInnerPanel = new FlowPanel();
clickableHeaderPanel.setWidget(clickableInnerPanel);
clickableInnerPanel.add(iconImage);
clickableInnerPanel.add(textLabel);
if(hasToolbar) {
toolbarPanel = new SimplePanel();
add(toolbarPanel);
} else {
toolbarPanel = null;
}
collapsablePanel.addOpenHandler(this);
collapsablePanel.addCloseHandler(this);
setStyle();
}
protected CollapsablePanelHeader(CollapsablePanel collapsablePanel, final ImageResource openImage,
final ImageResource closedImage, String text, boolean hasToolbar) {
this(collapsablePanel, new Imager() {
public Image makeImage() {
return new Image(closedImage);
}
public void updateImage(boolean open, Image image) {
if (open) {
image.setResource(openImage);
} else {
image.setResource(closedImage);
}
}
}, text, hasToolbar);
}
public void setToolbar(Widget toolbar) {
if (hasToolbar && toolbarPanel != null) {
toolbarPanel.setWidget(toolbar);
toolbarPanel.setStyleName(STYLENAME_TOOLBAR);
}
}
protected void setCollapsingEnabled(boolean enabled)
{
clickableHeaderPanel.setClickable(enabled);
}
public final String getText() {
return textLabel.getText();
}
public final void onClose(CloseEvent<CollapsablePanel> event) {
setStyle();
}
public final void onOpen(OpenEvent<CollapsablePanel> event) {
setStyle();
}
public final void setText(String text) {
textLabel.setText(text);
}
private void setStyle() {
imager.updateImage(collapsablePanel.isOpen(), iconImage);
}
/**
* Used to wrap widgets in the header to provide click support. Effectively wraps the widget in an
* <code>anchor</code> to get automatic keyboard access.
*/
private final class ClickableHeader extends SimplePanel {
private final CollapsablePanel panel;
private boolean isClickable = true;
private ClickableHeader(final CollapsablePanel panel) {
// Anchor is used to allow keyboard access.
super(DOM.createAnchor());
this.panel = panel;
Element elem = getElement();
elem.setPropertyString("href", "javascript:void(0);");
// Avoids layout problems from having blocks in inlines.
//DOM.setStyleAttribute(elem, "display", "block");
sinkEvents(Event.ONCLICK);
}
protected void setClickable(boolean enabled) {
this.isClickable = enabled;
}
@Override
public void onBrowserEvent(Event event) {
// no need to call super.
switch (DOM.eventGetType(event)) {
case Event.ONCLICK:
// Prevent link default action.
event.preventDefault();
if(isClickable) {
panel.setOpen(!panel.isOpen());
}
}
}
}
}
@@ -0,0 +1,48 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.settings.Settings;
public interface Component<SettingsType extends Settings> {
/**
* @return the name to display to a user for quick navigation to this component
*/
String getLocalizedShortName();
/**
* A component may or may not have a {@link Widget} used to render its contents. Typically,
* entries with sub-entries don't afford a widget of their own but let the framework choose a container
* in which to aggregate their entries' widgets.
*/
Widget getEntryWidget();
boolean isVisible();
void setVisible(boolean visibility);
/**
* @return whether this component has settings that a user may change; if so,
*/
boolean hasSettings();
/**
* If this component {@link #hasSettings has settings}, this method may return a component for the settings dialog.
* It will be used to obtain a widget shown in the settings dialog, a validator for the component-specific settings,
* as well as to produce a result from the widget's state when the settings dialog wants to validate or return the
* settings.
*/
SettingsDialogComponent<SettingsType> getSettingsDialogComponent();
/**
* Updates the settings of this component. Expected to be called when a settings dialog using this component's
* {@link #getSettingsDialogComponent()} has been confirmed.
*/
void updateSettings(SettingsType newSettings);
/**
* If the component wants to add its specific styling rules, e.g., for the expand/collapse toggle buttons or
* the dragger handle, it should return a non-<code>null</code> value from this method which is then used as
* a dependent CSS class name for those components.
*/
String getDependentCssClassName();
}
@@ -0,0 +1,12 @@
package com.sap.sse.gwt.client.shared.components;
import com.sap.sse.common.settings.AbstractSettings;
public interface ComponentGroup<SettingsType extends AbstractSettings> extends Component<SettingsType> {
boolean addComponent(Component<?> component);
boolean removeComponent(Component<?> component);
Iterable<Component<?>> getComponents();
}
@@ -0,0 +1,19 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
public interface ComponentResources extends ClientBundle {
@Source("com/sap/sse/gwt/client/images/open.png")
ImageResource openIcon();
@Source("com/sap/sse/gwt/client/images/close.png")
ImageResource closeIcon();
@Source("com/sap/sse/gwt/client/images/settings.png")
ImageResource settingsIcon();
@Source("com/sap/sse/gwt/client/images/leaderboardsettings.png")
ImageResource darkSettingsIcon();
}
@@ -0,0 +1,49 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.StringMessages;
/**
* One entry in a component toolbar. Represents one {@link Component} that can be selected and whose
* settings editing dialog can be reached from here.
*
* @author Axel Uhl (D043530)
*
* @param <SettingsType>
*/
public class ComponentToolbarEntry<SettingsType extends AbstractSettings> extends HorizontalPanel {
private static ComponentResources resources = GWT.create(ComponentResources.class);
private final Component<SettingsType> component;
private final StringMessages stringMessages;
public ComponentToolbarEntry(final Component<SettingsType> component, final StringMessages stringMessages) {
super();
this.setSpacing(10);
this.component = component;
this.stringMessages = stringMessages;
}
public void addSettingsButton() {
if (component.hasSettings()) {
ImageResource settingsImage = resources.darkSettingsIcon();
Anchor showConfigAnchor = new Anchor(AbstractImagePrototype.create(settingsImage).getSafeHtml());
showConfigAnchor.setTitle(stringMessages.configuration());
showConfigAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
new SettingsDialog<SettingsType>(component, stringMessages).show();
}
});
this.add(showConfigAnchor);
}
}
}
@@ -0,0 +1,14 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.Widget;
public interface ComponentViewer {
Widget getViewerWidget();
String getViewerName();
Component<?> getRootComponent();
void forceLayout();
}
@@ -0,0 +1,25 @@
package com.sap.sse.gwt.client.shared.components;
import com.sap.sse.common.Util;
import com.sap.sse.common.settings.AbstractSettings;
public class CompositeSettings extends AbstractSettings {
public static class ComponentAndSettingsPair<SettingsType extends AbstractSettings> extends Util.Pair<Component<SettingsType>, SettingsType> {
private static final long serialVersionUID = -569811233041583043L;
public ComponentAndSettingsPair(Component<SettingsType> a, SettingsType b) {
super(a, b);
}
}
private final Iterable<ComponentAndSettingsPair<?>> settingsPerComponent;
public CompositeSettings(Iterable<ComponentAndSettingsPair<?>> settingsPerComponent) {
this.settingsPerComponent = settingsPerComponent;
}
public Iterable<ComponentAndSettingsPair<?>> getSettingsPerComponent() {
return settingsPerComponent;
}
}
@@ -0,0 +1,49 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
public class CompositeSettingsDialogComponent implements SettingsDialogComponent<CompositeSettings> {
private final Component<?>[] components;
public CompositeSettingsDialogComponent(Component<?>... components) {
this.components = components;
}
@Override
public Widget getAdditionalWidget(DataEntryDialog<?> dialog) {
TabPanel result = new TabPanel();
for (Component<?> component : components) {
Widget w = component.getSettingsDialogComponent().getAdditionalWidget((DataEntryDialog<?>) dialog);
result.add(w, component.getLocalizedShortName());
}
return result;
}
@Override
public CompositeSettings getResult() {
return null;
}
@Override
public Validator<CompositeSettings> getValidator() {
return new CompositeValidator(components);
}
@Override
public FocusWidget getFocusWidget() {
for (Component<?> component : components) {
FocusWidget fw = component.getSettingsDialogComponent().getFocusWidget();
if (fw != null) {
return fw;
}
}
return null;
}
}
@@ -0,0 +1,92 @@
package com.sap.sse.gwt.client.shared.components;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.StringMessages;
import com.sap.sse.gwt.client.shared.components.CompositeSettings.ComponentAndSettingsPair;
/**
* A composite settings dialog that combines the settings of several {@link Component}s, providing a tab
* for each component.
*
* @author Axel Uhl (d043530)
*
*/
public class CompositeTabbedSettingsDialog extends SettingsDialog<CompositeSettings> {
private final Map<Component<?>, SettingsDialogComponent<?>> settingsDialogComponents;
public CompositeTabbedSettingsDialog(StringMessages stringConstants, final Component<?>... components) {
super(new Component<CompositeSettings>() {
@Override
public boolean hasSettings() {
for (Component<?> component : components) {
if (component.hasSettings()) {
return true;
}
}
return false;
}
@Override
public SettingsDialogComponent<CompositeSettings> getSettingsDialogComponent() {
return new CompositeSettingsDialogComponent(components);
}
@Override
public void updateSettings(CompositeSettings newSettings) {
for (CompositeSettings.ComponentAndSettingsPair<?> componentAndSettings : newSettings.getSettingsPerComponent()) {
updateSettings(componentAndSettings);
}
}
private <SettingsType extends AbstractSettings> void updateSettings(ComponentAndSettingsPair<SettingsType> componentAndSettings) {
componentAndSettings.getA().updateSettings(componentAndSettings.getB());
}
@Override
public String getLocalizedShortName() {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Component<?> component : components) {
if (first) {
first = false;
} else {
result.append(" / ");
}
result.append(component.getLocalizedShortName());
}
return result.toString();
}
@Override
public Widget getEntryWidget() {
throw new RuntimeException("Virtual composite component doesn't have a widget of its own");
}
@Override
public boolean isVisible() {
return false;
}
@Override
public void setVisible(boolean visibility) {
throw new RuntimeException("Virtual composite component doesn't know how to make itself visible");
}
@Override
public String getDependentCssClassName() {
return null;
}
}, stringConstants);
settingsDialogComponents = new HashMap<Component<?>, SettingsDialogComponent<?>>();
for (Component<?> component : components) {
if (component.hasSettings()) {
settingsDialogComponents.put(component, component.getSettingsDialogComponent());
}
}
}
}
@@ -0,0 +1,58 @@
package com.sap.sse.gwt.client.shared.components;
import java.util.ArrayList;
import com.sap.sse.common.Util;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
import com.sap.sse.gwt.client.shared.components.CompositeSettings.ComponentAndSettingsPair;
public class CompositeValidator implements Validator<CompositeSettings> {
private static class ComponentAndValidator<SettingsType extends Settings> extends Util.Pair<Component<SettingsType>, Validator<SettingsType>> {
private static final long serialVersionUID = -4190322565836849861L;
public ComponentAndValidator(Component<SettingsType> a, Validator<SettingsType> b) {
super(a, b);
}
}
private final Iterable<ComponentAndValidator<?>> validators;
public CompositeValidator(Component<?>[] components) {
ArrayList<ComponentAndValidator<?>> v = new ArrayList<ComponentAndValidator<?>>();
for (Component<?> component : components) {
v.add(getComponentAndValidator(component));
}
validators = v;
}
private <SettingsType extends Settings> ComponentAndValidator<SettingsType> getComponentAndValidator(Component<SettingsType> component) {
return new ComponentAndValidator<SettingsType>(component, component.getSettingsDialogComponent().getValidator());
}
@Override
public String getErrorMessage(CompositeSettings valueToValidate) {
StringBuilder result = new StringBuilder();
for (ComponentAndSettingsPair<?> componentAndSettings : valueToValidate.getSettingsPerComponent()) {
final String errorMessage = getErrorMessage(componentAndSettings);
if (errorMessage != null) {
result.append(errorMessage);
result.append("; ");
}
}
return result.toString();
}
private <SettingsType extends AbstractSettings> String getErrorMessage(ComponentAndSettingsPair<SettingsType> componentAndSettings) {
for (ComponentAndValidator<?> componentAndValidator : validators) {
if (componentAndValidator.getA() == componentAndSettings.getA()) {
@SuppressWarnings("unchecked")
final Validator<SettingsType> validator = (Validator<SettingsType>) componentAndValidator.getB();
return validator.getErrorMessage(componentAndSettings.getB());
}
}
return null;
}
}
@@ -0,0 +1,23 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
public interface IsEmbeddableComponent {
Widget getHeaderWidget();
Widget getContentWidget();
Widget getToolbarWidget();
Widget getLegendWidget();
BusyIndicator getBusyIndicator();
boolean isEmbedded();
boolean hasToolbar();
boolean hasBusyIndicator();
}
@@ -0,0 +1,8 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.settings.AbstractSettings;
public interface LazyComponent<SettingsType extends AbstractSettings> extends Component<SettingsType> {
Widget createWidget();
}
@@ -0,0 +1,85 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.gwt.client.StringMessages;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class SettingsDialog<SettingsType extends Settings> extends DataEntryDialog<SettingsType> {
private final SettingsDialogComponent<SettingsType> settingsDialogComponent;
public SettingsDialog(final Component<SettingsType> component, StringMessages stringMessages) {
this(component, stringMessages, /* animationEnabled */ true);
}
/**
* Creates a settings button for <code>component</code> that, when clicked, opens a settings dialog for that
* component and when confirmed, updates that component's settings. The button has no CSS style attached to
* give callers full flexibility as to how to style the button.
*/
public static <T extends AbstractSettings> Button createSettingsButton(final Component<T> component, final StringMessages stringMessages) {
Button settingsButton = new Button();
settingsButton.setTitle(stringMessages.settings());
settingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
new SettingsDialog<T>(component, stringMessages).show();
}
});
return settingsButton;
}
/**
* This auxiliary constructor is required to avoid duplicate calls to {@link Component#getSettingsDialogComponent()}
* which may choose to create a new instance each call. Such duplicate instances would cause the validator to
* operate on a different instance as the one used for displaying, hence not allowing the validator to use the UI
* elements, neither for update nor read.
*/
private SettingsDialog(final Component<SettingsType> component,
SettingsDialogComponent<SettingsType> dialogComponent, StringMessages stringMessages,
boolean animationEnabled) {
super(stringMessages.settingsForComponent(component.getLocalizedShortName()), null, stringMessages.ok(),
stringMessages.cancel(), dialogComponent.getValidator(), animationEnabled,
new DialogCallback<SettingsType>() {
@Override
public void cancel() {
}
@Override
public void ok(SettingsType newSettings) {
component.updateSettings(newSettings);
}
});
this.settingsDialogComponent = dialogComponent;
}
public SettingsDialog(final Component<SettingsType> component, StringMessages stringMessages, boolean animationEnabled) {
this(component, component.getSettingsDialogComponent(), stringMessages, animationEnabled);
}
@Override
protected Widget getAdditionalWidget() {
return settingsDialogComponent.getAdditionalWidget(this);
}
@Override
protected SettingsType getResult() {
return settingsDialogComponent.getResult();
}
@Override
public void show() {
super.show();
FocusWidget focusWidget = settingsDialogComponent.getFocusWidget();
if (focusWidget != null) {
focusWidget.setFocus(true);
}
}
}
@@ -0,0 +1,43 @@
package com.sap.sse.gwt.client.shared.components;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
public interface SettingsDialogComponent<SettingsType extends Settings> {
/**
* Called by the framework to obtain the widget to show in the settings dialog. Called each time before the
* settings dialog is shown, therefore also before {@link #getResult()} is called and before any method on
* the validator returned by {@link #getValidator()} is called.
*
* @param dialog
* can be used to {@link DataEntryDialog#createCheckbox(String) produce a checkbox}, a
* {@link DataEntryDialog#createTextBox(String)} or other data entry elements that participate in key
* handling for validation triggering and for canceling and confirming the dialog. The type argument
* is therefore not propagated. It's not needed to create a simple UI control.
* @return <code>null</code> in case this component doesn't make any special contribution to the settings dialog, or
* a valid widget that will be displayed in the settings dialog
*/
Widget getAdditionalWidget(DataEntryDialog<?> dialog);
/**
* Obtains the result from this settings dialog component; usually, the result object is contructed by reading data
* from the elements of the widget constructed by {@link #getAdditionalWidget(DataEntryDialog)}.
*/
SettingsType getResult();
/**
* Obtains an optional validator that is used to validate the {@link #getAdditionalWidget(DataEntryDialog)} contents upon
* changes. If <code>null</code> is returned, no validation will be performed.
*/
Validator<SettingsType> getValidator();
/**
* @return <code>null</code> if no initial focus on any widget is desired, or a widget from the
* {@link #getAdditionalWidget(DataEntryDialog)} result that will then receive the focus when the settings
* dialog is shown
*/
FocusWidget getFocusWidget();
}