diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/DesktopConfig.java b/modules/desktop/src/com/haulmont/cuba/desktop/DesktopConfig.java index 42d882c139..ac47c674e6 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/DesktopConfig.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/DesktopConfig.java @@ -25,6 +25,7 @@ import com.haulmont.cuba.core.config.SourceType; import com.haulmont.cuba.core.config.defaults.Default; import com.haulmont.cuba.core.config.defaults.DefaultBoolean; import com.haulmont.cuba.core.config.defaults.DefaultInt; +import com.haulmont.cuba.core.config.defaults.DefaultString; import com.haulmont.cuba.core.config.type.Factory; import com.haulmont.cuba.core.config.type.IntegerListTypeFactory; @@ -115,4 +116,9 @@ public interface DesktopConfig extends Config { @Property("cuba.desktop.loginDialogDefaultPassword") @Default("admin") String getLoginDialogDefaultPassword(); + + @Property("cuba.desktop.showTooltipShortcut") + @Source(type = SourceType.DATABASE) + @DefaultString("F1") + String getShowTooltipShortcut(); } \ No newline at end of file diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/desktop-theme.xsd b/modules/desktop/src/com/haulmont/cuba/desktop/desktop-theme.xsd index c1098ecd80..958c642026 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/desktop-theme.xsd +++ b/modules/desktop/src/com/haulmont/cuba/desktop/desktop-theme.xsd @@ -44,6 +44,7 @@ + @@ -70,6 +71,11 @@ + + + + + diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/ComponentCaption.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/ComponentCaption.java index c1353d9e65..e6c7d28546 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/ComponentCaption.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/ComponentCaption.java @@ -21,6 +21,8 @@ import com.haulmont.cuba.desktop.sys.DesktopToolTipManager; import com.haulmont.cuba.desktop.sys.layout.BoxLayoutAdapter; import com.haulmont.cuba.desktop.sys.vcl.ToolTipButton; import com.haulmont.cuba.gui.components.Component; +import com.haulmont.cuba.gui.components.Component.HasContextHelp; +import org.apache.commons.lang.StringUtils; import javax.swing.*; @@ -37,20 +39,24 @@ public class ComponentCaption extends JPanel { } private void takeOwnerProperties() { - if (label == null) { - label = new JLabel(); - add(label); + if (!(owner instanceof DesktopCheckBox)) { + if (label == null) { + label = new JLabel(); + add(label); + } + + label.setText(((Component.HasCaption) owner).getCaption()); } - label.setText(((Component.HasCaption) owner).getCaption()); - if (((Component.HasCaption) owner).getDescription() != null) { + String contextHelpText = getContextHelpText(); + if (StringUtils.isNotEmpty(contextHelpText)) { if (toolTipButton == null) { toolTipButton = new ToolTipButton(); toolTipButton.setFocusable(false); DesktopToolTipManager.getInstance().registerTooltip(toolTipButton); add(toolTipButton); } - toolTipButton.setToolTipText(((Component.HasCaption) owner).getDescription()); + toolTipButton.setToolTipText(contextHelpText); } else if (toolTipButton != null) { remove(toolTipButton); toolTipButton = null; @@ -60,6 +66,15 @@ public class ComponentCaption extends JPanel { setEnabled(owner.isEnabled()); } + protected String getContextHelpText() { + if (owner instanceof HasContextHelp) { + return DesktopComponentsHelper.getContextHelpText( + ((HasContextHelp) owner).getContextHelpText(), + ((HasContextHelp) owner).isContextHelpTextHtmlEnabled()); + } + return null; + } + public void update() { takeOwnerProperties(); } diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractBox.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractBox.java index e9c4025838..6ab71cbe54 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractBox.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractBox.java @@ -21,6 +21,7 @@ import com.google.common.collect.Iterables; import com.haulmont.bali.datastruct.Pair; import com.haulmont.cuba.desktop.gui.data.DesktopContainerHelper; import com.haulmont.cuba.desktop.sys.layout.BoxLayoutAdapter; +import com.haulmont.cuba.desktop.sys.layout.MigLayoutHelper; import com.haulmont.cuba.gui.ComponentsHelper; import com.haulmont.cuba.gui.components.Component; import com.haulmont.cuba.gui.components.Frame; @@ -74,37 +75,31 @@ public abstract class DesktopAbstractBox remove(component); } - int implIndex = getActualIndex(index); - - // add caption first - ComponentCaption caption = null; - boolean haveDescription = false; - if (DesktopContainerHelper.hasExternalCaption(component)) { - caption = new ComponentCaption(component); - captions.put(component, caption); - impl.add(caption, layoutAdapter.getCaptionConstraints(component), implIndex); // CAUTION this dramatically wrong - implIndex++; - } else if (DesktopContainerHelper.hasExternalDescription(component)) { - caption = new ComponentCaption(component); - captions.put(component, caption); - haveDescription = true; - } - JComponent composition = DesktopComponentsHelper.getComposition(component); - //if component have description without caption, we need to wrap - // component to view Description button horizontally after component - if (haveDescription) { + boolean hasExternalCaption = DesktopContainerHelper.hasExternalCaption(component); + if (hasExternalCaption + || DesktopContainerHelper.hasExternalContextHelp(component)) { + ComponentCaption caption = new ComponentCaption(component); + captions.put(component, caption); + JPanel wrapper = new LayoutSlot(); BoxLayoutAdapter adapter = BoxLayoutAdapter.create(wrapper); adapter.setExpandLayout(true); adapter.setSpacing(false); adapter.setMargin(false); wrapper.add(composition); - wrapper.add(caption,new CC().alignY("top")); - impl.add(wrapper, layoutAdapter.getConstraints(component), implIndex); + + if (hasExternalCaption) { + adapter.setFlowDirection(BoxLayoutAdapter.FlowDirection.Y); + wrapper.add(caption, 0); + } else { + wrapper.add(caption, new CC().alignY("top")); + } + + impl.add(wrapper, layoutAdapter.getConstraints(component), index); wrappers.put(component, new Pair<>(wrapper, adapter)); } else { - impl.add(composition, layoutAdapter.getConstraints(component), implIndex); + impl.add(composition, layoutAdapter.getConstraints(component), index); } if (component.getId() != null) { @@ -152,16 +147,6 @@ public abstract class DesktopAbstractBox frame.registerComponent(component); } - protected int getActualIndex(int originalIndex) { - int index = originalIndex; - Object[] components = ownComponents.toArray(); - for (int i = 0; i < originalIndex; i++) { - if (DesktopContainerHelper.hasExternalCaption((Component)components[i])) - index++; - } - return index; - } - @Override public void remove(Component component) { JComponent composition = DesktopComponentsHelper.getComposition(component); @@ -269,15 +254,16 @@ public abstract class DesktopAbstractBox protected void updateComponentInternal(Component child) { boolean componentReAdded = false; - if (DesktopContainerHelper.mayHaveExternalCaption(child)) { + if (DesktopContainerHelper.mayHaveExternalCaption(child) + || DesktopContainerHelper.mayHaveExternalContextHelp(child)) { if (captions.containsKey(child) && !DesktopContainerHelper.hasExternalCaption(child) - && !DesktopContainerHelper.hasExternalDescription(child)) { + && !DesktopContainerHelper.hasExternalContextHelp(child)) { reAddChild(child); componentReAdded = true; } else if (!captions.containsKey(child) && (DesktopContainerHelper.hasExternalCaption(child) - || DesktopContainerHelper.hasExternalDescription(child))) { + || DesktopContainerHelper.hasExternalContextHelp(child))) { reAddChild(child); componentReAdded = true; } else if (captions.containsKey(child)) { @@ -295,6 +281,19 @@ public abstract class DesktopAbstractBox JComponent composition; if (wrappers.containsKey(child)) { composition = wrappers.get(child).getFirst(); + CC constraints = MigLayoutHelper.getConstraints(child); + if (child.getHeight() == -1.0) { + MigLayoutHelper.applyHeight(constraints, -1, UNITS_PIXELS, false); + } else { + MigLayoutHelper.applyHeight(constraints, 100, UNITS_PERCENTAGE, false); + } + if (child.getWidth() == -1.0) { + MigLayoutHelper.applyWidth(constraints, -1, UNITS_PIXELS, false); + } else { + MigLayoutHelper.applyWidth(constraints, 100, UNITS_PERCENTAGE, false); + } + BoxLayoutAdapter adapter = wrappers.get(child).getSecond(); + adapter.updateConstraints(DesktopComponentsHelper.getComposition(child), constraints); } else { composition = DesktopComponentsHelper.getComposition(child); } diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractField.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractField.java index 25ca69421c..0dedf6b9cb 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractField.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopAbstractField.java @@ -34,6 +34,7 @@ import com.haulmont.cuba.gui.components.compatibility.ComponentValueListenerWrap import com.haulmont.cuba.gui.components.validators.BeanValidator; import com.haulmont.cuba.gui.data.Datasource; import com.haulmont.cuba.gui.data.ValueListener; +import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import javax.swing.*; @@ -52,6 +53,9 @@ public abstract class DesktopAbstractField extends Desktop protected boolean required; protected String requiredMessage; + protected String contextHelpText; + protected Boolean contextHelpTextHtmlEnable = false; + protected Set validators = new LinkedHashSet<>(); // todo move nimbus constants to theme @@ -290,4 +294,32 @@ public abstract class DesktopAbstractField extends Desktop } } } + + @Override + public String getContextHelpText() { + return contextHelpText; + } + + @Override + public void setContextHelpText(String contextHelpText) { + if (!ObjectUtils.equals(this.contextHelpText, contextHelpText)) { + this.contextHelpText = contextHelpText; + + requestContainerUpdate(); + } + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return contextHelpTextHtmlEnable; + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + if (!ObjectUtils.equals(this.contextHelpTextHtmlEnable, enabled)) { + contextHelpTextHtmlEnable = enabled; + + requestContainerUpdate(); + } + } } \ No newline at end of file diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopComponentsHelper.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopComponentsHelper.java index 4144ec4d5b..bc328b19cf 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopComponentsHelper.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopComponentsHelper.java @@ -28,6 +28,8 @@ import com.haulmont.cuba.desktop.sys.vcl.JTabbedPaneExt; import com.haulmont.cuba.gui.components.*; import com.haulmont.cuba.gui.components.Component; import com.haulmont.cuba.gui.components.Frame; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; import javax.swing.Action; @@ -42,6 +44,8 @@ public class DesktopComponentsHelper { public static final int BUTTON_HEIGHT = 30; public static final int FIELD_HEIGHT = 28; + public static final int TOOLTIP_WIDTH = 500; + // todo move nimbus constants to theme public static final Color defaultBgColor = (Color) UIManager.get("nimbusLightBackground"); @@ -458,4 +462,11 @@ public class DesktopComponentsHelper { } } } + + public static String getContextHelpText(String contextHelpText, boolean contextHelpTextHtmlEnabled) { + if (StringUtils.isNotEmpty(contextHelpText)) { + return contextHelpTextHtmlEnabled ? contextHelpText : StringEscapeUtils.escapeHtml(contextHelpText); + } + return null; + } } \ No newline at end of file diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopFieldGroup.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopFieldGroup.java index c8abea3288..97fb96616c 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopFieldGroup.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopFieldGroup.java @@ -371,11 +371,13 @@ public class DesktopFieldGroup extends DesktopAbstractComponent DesktopAbstractComponent fieldImpl = (DesktopAbstractComponent) fieldComponent; fci.setComposition(fieldImpl); - if (fci.getDescription() != null) { + if (fci.getContextHelpText() != null) { ToolTipButton tooltipBtn = new ToolTipButton(); tooltipBtn.setVisible(fieldComponent.isVisible()); - tooltipBtn.setToolTipText(fci.getDescription()); - fci.setToolTipButton(new ToolTipButton()); + tooltipBtn.setToolTipText(DesktopComponentsHelper.getContextHelpText( + fci.getContextHelpText(), + fci.isContextHelpTextHtmlEnabled())); + fci.setToolTipButton(tooltipBtn); } else { fci.setToolTipButton(null); } @@ -455,11 +457,13 @@ public class DesktopFieldGroup extends DesktopAbstractComponent impl.add(label, labelCc.cell(colIndex * 3, insertRowIndex, 1, 1)); } - if (Strings.isNullOrEmpty(fci.getDescription())) { + if (Strings.isNullOrEmpty(fci.getContextHelpText())) { fci.setToolTipButton(null); } else if (fci.getToolTipButton() == null) { ToolTipButton toolTipButton = new ToolTipButton(); - toolTipButton.setToolTipText(fci.getDescription()); + toolTipButton.setToolTipText(DesktopComponentsHelper.getContextHelpText( + fci.getContextHelpText(), + fci.isContextHelpTextHtmlEnabled())); toolTipButton.setVisible(fieldComponent.isVisible()); fci.setToolTipButton(toolTipButton); } @@ -504,11 +508,13 @@ public class DesktopFieldGroup extends DesktopAbstractComponent DesktopAbstractComponent fieldImpl = (DesktopAbstractComponent) fci.getComponentNN(); fci.setComposition(fieldImpl); - if (fci.getDescription() != null) { + if (fci.getContextHelpText() != null) { ToolTipButton tooltipBtn = new ToolTipButton(); tooltipBtn.setVisible(fci.getComponentNN().isVisible()); - tooltipBtn.setToolTipText(fci.getDescription()); - fci.setToolTipButton(new ToolTipButton()); + tooltipBtn.setToolTipText(DesktopComponentsHelper.getContextHelpText( + fci.getContextHelpText(), + fci.isContextHelpTextHtmlEnabled())); + fci.setToolTipButton(tooltipBtn); } else { fci.setToolTipButton(null); } @@ -546,6 +552,12 @@ public class DesktopFieldGroup extends DesktopAbstractComponent if (fci.getTargetRequiredMessage() != null) { cubaField.setRequiredMessage(fci.getTargetRequiredMessage()); } + if (fci.getTargetContextHelpText() != null) { + cubaField.setContextHelpText(fci.getTargetContextHelpText()); + } + if (fci.getTargetContextHelpTextHtmlEnabled() != null) { + cubaField.setContextHelpTextHtmlEnabled(fci.getTargetContextHelpTextHtmlEnabled()); + } if (fci.getTargetEditable() != null) { cubaField.setEditable(fci.getTargetEditable()); } @@ -1057,6 +1069,8 @@ public class DesktopFieldGroup extends DesktopAbstractComponent protected CollectionDatasource targetOptionsDatasource; protected String targetCaption; protected String targetDescription; + protected String targetContextHelpText; + protected Boolean targetContextHelpTextHtmlEnabled; protected Formatter targetFormatter; protected boolean isTargetCustom; @@ -1415,6 +1429,40 @@ public class DesktopFieldGroup extends DesktopAbstractComponent } } + @Override + public String getContextHelpText() { + if (component instanceof Field) { + return ((Field) component).getContextHelpText(); + } + return targetContextHelpText; + } + + @Override + public void setContextHelpText(String contextHelpText) { + if (component instanceof Field) { + ((Field) component).setContextHelpText(contextHelpText); + } else { + this.targetContextHelpText = contextHelpText; + } + } + + @Override + public Boolean isContextHelpTextHtmlEnabled() { + if (component instanceof Field) { + return ((Field) component).isContextHelpTextHtmlEnabled(); + } + return targetContextHelpTextHtmlEnabled; + } + + @Override + public void setContextHelpTextHtmlEnabled(Boolean enabled) { + if (component instanceof Field) { + ((Field) component).setContextHelpTextHtmlEnabled(enabled); + } else { + this.targetContextHelpTextHtmlEnabled = enabled; + } + } + @Override public String getDescription() { if (component instanceof Field) { @@ -1577,6 +1625,22 @@ public class DesktopFieldGroup extends DesktopAbstractComponent this.targetRequiredMessage = targetRequiredMessage; } + public String getTargetContextHelpText() { + return targetContextHelpText; + } + + public void setTargetContextHelpText(String targetContextHelpText) { + this.targetContextHelpText = targetContextHelpText; + } + + public Boolean getTargetContextHelpTextHtmlEnabled() { + return targetContextHelpTextHtmlEnabled; + } + + public void setTargetContextHelpTextHtmlEnabled(Boolean targetContextHelpTextHtmlEnabled) { + this.targetContextHelpTextHtmlEnabled = targetContextHelpTextHtmlEnabled; + } + public CollectionDatasource getTargetOptionsDatasource() { return targetOptionsDatasource; } diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopGridLayout.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopGridLayout.java index 4f26d89371..fe5fca4f1b 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopGridLayout.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopGridLayout.java @@ -21,6 +21,7 @@ import com.haulmont.bali.datastruct.Pair; import com.haulmont.cuba.desktop.gui.data.DesktopContainerHelper; import com.haulmont.cuba.desktop.sys.layout.BoxLayoutAdapter; import com.haulmont.cuba.desktop.sys.layout.GridLayoutAdapter; +import com.haulmont.cuba.desktop.sys.layout.MigLayoutHelper; import com.haulmont.cuba.gui.ComponentsHelper; import com.haulmont.cuba.gui.components.Component; import com.haulmont.cuba.gui.components.Frame; @@ -81,30 +82,26 @@ public class DesktopGridLayout extends DesktopAbstractComponent implemen } final JComponent composition = DesktopComponentsHelper.getComposition(component); - - // add caption first - ComponentCaption caption = null; - boolean haveDescription = false; - if (DesktopContainerHelper.hasExternalCaption(component)) { - caption = new ComponentCaption(component); + boolean hasExternalCaption = DesktopContainerHelper.hasExternalCaption(component); + if (hasExternalCaption + || DesktopContainerHelper.hasExternalContextHelp(component)) { + ComponentCaption caption = new ComponentCaption(component); captions.put(component, caption); - impl.add(caption, layoutAdapter.getCaptionConstraints(component, col, row, col2, row2)); - } else if (DesktopContainerHelper.hasExternalDescription(component)) { - caption = new ComponentCaption(component); - captions.put(component, caption); - haveDescription = true; - } - // if component have description without caption, we need to wrap - // component to view Description button horizontally after component - if (haveDescription) { JPanel wrapper = new LayoutSlot(); BoxLayoutAdapter adapter = BoxLayoutAdapter.create(wrapper); adapter.setExpandLayout(true); adapter.setSpacing(false); adapter.setMargin(false); wrapper.add(composition); - wrapper.add(caption, new CC().alignY("top")); + + if (hasExternalCaption) { + adapter.setFlowDirection(BoxLayoutAdapter.FlowDirection.Y); + wrapper.add(caption, 0); + } else { + wrapper.add(caption, new CC().alignY("top")); + } + impl.add(wrapper, layoutAdapter.getConstraints(component, col, row, col2, row2)); wrappers.put(component, new Pair<>(wrapper, adapter)); } else { @@ -327,15 +324,16 @@ public class DesktopGridLayout extends DesktopAbstractComponent implemen public void updateComponent(Component child) { boolean componentReAdded = false; - if (DesktopContainerHelper.mayHaveExternalCaption(child)) { + if (DesktopContainerHelper.mayHaveExternalCaption(child) + || DesktopContainerHelper.mayHaveExternalContextHelp(child)) { if (captions.containsKey(child) && !DesktopContainerHelper.hasExternalCaption(child) - && !DesktopContainerHelper.hasExternalDescription(child)) { + && !DesktopContainerHelper.hasExternalContextHelp(child)) { reAddChild(child); componentReAdded = true; } else if (!captions.containsKey(child) && (DesktopContainerHelper.hasExternalCaption(child) - || DesktopContainerHelper.hasExternalDescription(child))) { + || DesktopContainerHelper.hasExternalContextHelp(child))) { reAddChild(child); componentReAdded = true; } else if (captions.containsKey(child)) { @@ -353,6 +351,19 @@ public class DesktopGridLayout extends DesktopAbstractComponent implemen JComponent composition; if (wrappers.containsKey(child)) { composition = wrappers.get(child).getFirst(); + CC constraints = MigLayoutHelper.getConstraints(child); + if (child.getHeight() == -1.0) { + MigLayoutHelper.applyHeight(constraints, -1, UNITS_PIXELS, false); + } else { + MigLayoutHelper.applyHeight(constraints, 100, UNITS_PERCENTAGE, false); + } + if (child.getWidth() == -1.0) { + MigLayoutHelper.applyWidth(constraints, -1, UNITS_PIXELS, false); + } else { + MigLayoutHelper.applyWidth(constraints, 100, UNITS_PERCENTAGE, false); + } + BoxLayoutAdapter adapter = wrappers.get(child).getSecond(); + adapter.updateConstraints(DesktopComponentsHelper.getComposition(child), constraints); } else { composition = DesktopComponentsHelper.getComposition(child); } diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopListEditor.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopListEditor.java index 5e142a3e0e..81cfb1418b 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopListEditor.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopListEditor.java @@ -199,6 +199,24 @@ public class DesktopListEditor extends DesktopAbstractField implements L public void setDescription(String description) { } + @Override + public String getContextHelpText() { + return null; + } + + @Override + public void setContextHelpText(String contextHelpText) { + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return false; + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + } + @Override protected void setEditableToComponent(boolean editable) { delegate.setEditable(editable); diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopTokenList.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopTokenList.java index ad16680e7b..e7c9cc6b05 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopTokenList.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopTokenList.java @@ -46,6 +46,7 @@ import com.haulmont.cuba.gui.data.ValueListener; import com.haulmont.cuba.gui.data.impl.WeakCollectionChangeListener; import net.miginfocom.layout.CC; import net.miginfocom.swing.MigLayout; +import org.apache.commons.lang.ObjectUtils; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; @@ -601,6 +602,22 @@ public class DesktopTokenList extends DesktopAbstractField implements To DesktopToolTipManager.getInstance().registerTooltip(impl); } + @Override + public void setContextHelpText(String contextHelpText) { + if (!ObjectUtils.equals(this.contextHelpText, contextHelpText)) { + this.contextHelpText = contextHelpText; + + // for now, DesktopTokenList doesn't provide context help + } + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + if (!ObjectUtils.equals(this.contextHelpTextHtmlEnable, enabled)) { + contextHelpTextHtmlEnable = enabled; + } + } + @Override @SuppressWarnings("unchecked") public T getValue() { diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopWindow.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopWindow.java index 836c0c5868..030afc521c 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopWindow.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/components/DesktopWindow.java @@ -992,37 +992,33 @@ public class DesktopWindow implements Window, Component.Disposable, remove(component); } - int implIndex = getActualIndex(index); - - ComponentCaption caption = null; - boolean haveDescription = false; - if (DesktopContainerHelper.hasExternalCaption(component)) { - caption = new ComponentCaption(component); - captions.put(component, caption); - getContainer().add(caption, layoutAdapter.getCaptionConstraints(component), implIndex); // CAUTION this dramatically wrong - implIndex++; - } else if (DesktopContainerHelper.hasExternalDescription(component)) { - caption = new ComponentCaption(component); - captions.put(component, caption); - haveDescription = true; - } - JComponent composition = DesktopComponentsHelper.getComposition(component); - // if component have description without caption, we need to wrap - // component to view Description button horizontally after component - if (haveDescription) { + boolean hasExternalCaption = DesktopContainerHelper.hasExternalCaption(component); + if (hasExternalCaption + || DesktopContainerHelper.hasExternalContextHelp(component)) { + ComponentCaption caption = new ComponentCaption(component); + captions.put(component, caption); + JPanel wrapper = new JPanel(); BoxLayoutAdapter adapter = BoxLayoutAdapter.create(wrapper); adapter.setExpandLayout(true); adapter.setSpacing(false); adapter.setMargin(false); wrapper.add(composition); - wrapper.add(caption, new CC().alignY("top")); - getContainer().add(wrapper, layoutAdapter.getConstraints(component), implIndex); + + if (hasExternalCaption) { + adapter.setFlowDirection(BoxLayoutAdapter.FlowDirection.Y); + wrapper.add(caption, 0); + } else { + wrapper.add(caption, new CC().alignY("top")); + } + + getContainer().add(wrapper, layoutAdapter.getConstraints(component), index); wrappers.put(component, new Pair<>(wrapper, adapter)); } else { - getContainer().add(composition, layoutAdapter.getConstraints(component), implIndex); + getContainer().add(composition, layoutAdapter.getConstraints(component), index); } + if (component.getId() != null) { componentByIds.put(component.getId(), component); } @@ -1126,17 +1122,6 @@ public class DesktopWindow implements Window, Component.Disposable, requestRepaint(); } - protected int getActualIndex(int originalIndex) { - int index = originalIndex; - Object[] components = ownComponents.toArray(); - for (int i = 0; i < originalIndex; i++) { - if (DesktopContainerHelper.hasExternalCaption((Component) components[i])) { - index++; - } - } - return index; - } - @Override public Component getOwnComponent(String id) { return componentByIds.get(id); @@ -1378,15 +1363,16 @@ public class DesktopWindow implements Window, Component.Disposable, @Override public void updateComponent(Component child) { boolean componentReAdded = false; - if (DesktopContainerHelper.mayHaveExternalCaption(child)) { + if (DesktopContainerHelper.mayHaveExternalCaption(child) + || DesktopContainerHelper.mayHaveExternalContextHelp(child)) { if (captions.containsKey(child) && !DesktopContainerHelper.hasExternalCaption(child) - && !DesktopContainerHelper.hasExternalDescription(child)) { + && !DesktopContainerHelper.hasExternalContextHelp(child)) { reAddChild(child); componentReAdded = true; } else if (!captions.containsKey(child) && (DesktopContainerHelper.hasExternalCaption(child) - || DesktopContainerHelper.hasExternalDescription(child))) { + || DesktopContainerHelper.hasExternalContextHelp(child))) { reAddChild(child); componentReAdded = true; } else if (captions.containsKey(child)) { @@ -1404,6 +1390,19 @@ public class DesktopWindow implements Window, Component.Disposable, JComponent composition; if (wrappers.containsKey(child)) { composition = wrappers.get(child).getFirst(); + CC constraints = MigLayoutHelper.getConstraints(child); + if (child.getHeight() == -1.0) { + MigLayoutHelper.applyHeight(constraints, -1, UNITS_PIXELS, false); + } else { + MigLayoutHelper.applyHeight(constraints, 100, UNITS_PERCENTAGE, false); + } + if (child.getWidth() == -1.0) { + MigLayoutHelper.applyWidth(constraints, -1, UNITS_PIXELS, false); + } else { + MigLayoutHelper.applyWidth(constraints, 100, UNITS_PERCENTAGE, false); + } + BoxLayoutAdapter adapter = wrappers.get(child).getSecond(); + adapter.updateConstraints(DesktopComponentsHelper.getComposition(child), constraints); } else { composition = DesktopComponentsHelper.getComposition(child); } diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/gui/data/DesktopContainerHelper.java b/modules/desktop/src/com/haulmont/cuba/desktop/gui/data/DesktopContainerHelper.java index 00ed87a09e..6361d13b11 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/gui/data/DesktopContainerHelper.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/gui/data/DesktopContainerHelper.java @@ -21,6 +21,7 @@ import com.haulmont.cuba.desktop.gui.components.DesktopCheckBox; import com.haulmont.cuba.desktop.gui.components.DesktopComponent; import com.haulmont.cuba.desktop.gui.components.DesktopContainer; import com.haulmont.cuba.gui.components.Component; +import com.haulmont.cuba.gui.components.Component.HasContextHelp; import com.haulmont.cuba.gui.components.Field; import org.apache.commons.lang.StringUtils; @@ -56,6 +57,18 @@ public class DesktopContainerHelper { return false; } + public static boolean mayHaveExternalContextHelp(Component component) { + return component instanceof HasContextHelp; + } + + public static boolean hasExternalContextHelp(Component component) { + if (component instanceof HasContextHelp) { + final String contextHelp = ((HasContextHelp) component).getContextHelpText(); + return StringUtils.isNotEmpty(contextHelp); + } + return false; + } + public static void assignContainer(Component component, DesktopContainer container) { if (component instanceof DesktopComponent) { ((DesktopComponent) component).setContainer(container); diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/res/nimbus/nimbus.xml b/modules/desktop/src/com/haulmont/cuba/desktop/res/nimbus/nimbus.xml index 683f04a954..c980d99f76 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/res/nimbus/nimbus.xml +++ b/modules/desktop/src/com/haulmont/cuba/desktop/res/nimbus/nimbus.xml @@ -41,6 +41,8 @@ + + diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/sys/DesktopToolTipManager.java b/modules/desktop/src/com/haulmont/cuba/desktop/sys/DesktopToolTipManager.java index ebbbbf50e0..f4d43fcf3b 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/sys/DesktopToolTipManager.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/sys/DesktopToolTipManager.java @@ -17,9 +17,16 @@ package com.haulmont.cuba.desktop.sys; +import com.haulmont.cuba.core.global.AppBeans; +import com.haulmont.cuba.core.global.Configuration; +import com.haulmont.cuba.desktop.DesktopConfig; +import com.haulmont.cuba.desktop.gui.components.DesktopComponentsHelper; import com.haulmont.cuba.desktop.sys.vcl.ToolTipButton; +import com.haulmont.cuba.gui.components.KeyCombination; +import org.apache.commons.lang.StringUtils; import javax.swing.*; +import javax.swing.text.StyleContext; import java.awt.*; import java.awt.event.*; @@ -28,24 +35,23 @@ import java.awt.event.*; */ public class DesktopToolTipManager extends MouseAdapter { - public static final int F1_CODE = 112; + protected static int CLOSE_TIME = 500; + protected static int SHOW_TIME = 1000; - private static int CLOSE_TIME = 500; - private static int SHOW_TIME = 1000; + protected boolean tooltipShowing = false; - private boolean tooltipShowing = false; + protected JToolTip toolTipWindow; + protected Popup window; + protected JComponent component; - private JToolTip toolTipWindow; - private Popup window; - private JComponent component; + protected Timer showTimer = new Timer(SHOW_TIME, null); + protected Timer closeTimer; - private Timer showTimer = new Timer(SHOW_TIME, null); - private Timer closeTimer; - - private MouseListener componentMouseListener = new ComponentMouseListener(); - private KeyListener fieldKeyListener = new FieldKeyListener(); - private ActionListener btnActionListener = new ButtonClickListener(); + protected Configuration configuration = AppBeans.get(Configuration.NAME); + protected MouseListener componentMouseListener = new ComponentMouseListener(); + protected KeyListener fieldKeyListener = new FieldKeyListener(); + protected ActionListener btnActionListener = new ButtonClickListener(); private static DesktopToolTipManager instance; @@ -61,23 +67,20 @@ public class DesktopToolTipManager extends MouseAdapter { return instance; } - private DesktopToolTipManager() { + protected DesktopToolTipManager() { closeTimer = new Timer(CLOSE_TIME, null); closeTimer.setRepeats(false); - closeTimer.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - window.hide(); - window = null; - tooltipShowing = false; - toolTipWindow.removeMouseListener(DesktopToolTipManager.this); - component.removeMouseListener(DesktopToolTipManager.this); + closeTimer.addActionListener(e -> { + window.hide(); + window = null; + tooltipShowing = false; + toolTipWindow.removeMouseListener(DesktopToolTipManager.this); + component.removeMouseListener(DesktopToolTipManager.this); - } }); Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { - private MouseEvent event; + protected MouseEvent event; @Override public void eventDispatched(AWTEvent e) { @@ -96,7 +99,7 @@ public class DesktopToolTipManager extends MouseAdapter { }, AWTEvent.MOUSE_EVENT_MASK); } - private boolean isPointInComponent(Point point, JComponent component) { + protected boolean isPointInComponent(Point point, JComponent component) { if (!component.isShowing()) return false; @@ -108,7 +111,7 @@ public class DesktopToolTipManager extends MouseAdapter { /** * Register tooltip for component. - * Tooltip is displayed when user press F1 on focused component + * The tooltip is displayed when a user either presses F1 on a focused component or hovers over it. * ToolTip text is taken from {@link javax.swing.JComponent#getToolTipText()}. * * @param component component to register @@ -116,6 +119,9 @@ public class DesktopToolTipManager extends MouseAdapter { public void registerTooltip(final JComponent component) { component.removeKeyListener(fieldKeyListener); component.addKeyListener(fieldKeyListener); + + component.removeMouseListener(componentMouseListener); + component.addMouseListener(componentMouseListener); } /** @@ -154,7 +160,7 @@ public class DesktopToolTipManager extends MouseAdapter { btn.addActionListener(btnActionListener); } - private void hideTooltip() { + protected void hideTooltip() { closeTimer.stop(); if (window != null) { window.hide(); @@ -165,10 +171,14 @@ public class DesktopToolTipManager extends MouseAdapter { } } - private void showTooltip(JComponent field, String text) { + protected void showTooltip(JComponent field, String text) { if (!field.isShowing()) return; + if (StringUtils.isEmpty(text)) { + return; + } + PointerInfo pointerInfo = MouseInfo.getPointerInfo(); if (pointerInfo == null) { return; @@ -198,21 +208,43 @@ public class DesktopToolTipManager extends MouseAdapter { } component = field; - final JToolTip toolTip = new JToolTip(); - toolTip.setTipText("" + text + ""); + final JToolTip toolTip = new CubaToolTip(); + toolTip.setTipText(text); final Popup tooltipContainer = PopupFactory.getSharedInstance().getPopup(field, toolTip, x, y); tooltipContainer.show(); window = tooltipContainer; toolTipWindow = toolTip; tooltipShowing = true; - if ((!(field instanceof ToolTipButton)) && ((field instanceof AbstractButton) || (field instanceof JLabel))) { + if (!(field instanceof ToolTipButton)) { toolTip.addMouseListener(this); field.addMouseListener(this); } } + protected class CubaToolTip extends JToolTip { + @Override + public void setTipText(String tipText) { + UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); + int maxTooltipWidth = lafDefaults.getInt("Tooltip.maxWidth"); + if (maxTooltipWidth == 0) { + maxTooltipWidth = DesktopComponentsHelper.TOOLTIP_WIDTH; + } + + FontMetrics fontMetrics = StyleContext.getDefaultStyleContext().getFontMetrics(getFont()); + int actualWidth = SwingUtilities.computeStringWidth(fontMetrics, tipText); + + if (actualWidth < maxTooltipWidth) { + tipText = "" + tipText + ""; + } else { + tipText = "" + tipText + ""; + } + + super.setTipText(tipText); + } + } + @Override public void mouseExited(MouseEvent e) { closeTimer.start(); @@ -225,9 +257,9 @@ public class DesktopToolTipManager extends MouseAdapter { } } - private class ComponentMouseListener extends MouseAdapter { + protected class ComponentMouseListener extends MouseAdapter { - private JComponent cmp; + protected JComponent cmp; { showTimer.setRepeats(false); @@ -243,7 +275,7 @@ public class DesktopToolTipManager extends MouseAdapter { @Override public void mouseEntered(MouseEvent e) { if (window != null) { - if (e.getSource() != component && e.getSource() != toolTipWindow && component instanceof AbstractButton) { + if (e.getSource() != component && e.getSource() != toolTipWindow) { hideTooltip(); cmp = (JComponent) e.getSource(); showTimer.start(); @@ -272,10 +304,14 @@ public class DesktopToolTipManager extends MouseAdapter { } } - private class FieldKeyListener extends KeyAdapter { + protected class FieldKeyListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == F1_CODE) { + String showTooltipShortcut = configuration.getConfig(DesktopConfig.class).getShowTooltipShortcut(); + KeyStroke keyStroke = DesktopComponentsHelper + .convertKeyCombination(KeyCombination.create(showTooltipShortcut)); + + if (KeyStroke.getKeyStrokeForEvent(e).equals(keyStroke)) { hideTooltip(); JComponent field = (JComponent) e.getSource(); showTooltip(field, field.getToolTipText()); @@ -287,7 +323,7 @@ public class DesktopToolTipManager extends MouseAdapter { } } - private class ButtonClickListener implements ActionListener { + protected class ButtonClickListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { diff --git a/modules/desktop/src/com/haulmont/cuba/desktop/theme/impl/DesktopThemeLoaderImpl.java b/modules/desktop/src/com/haulmont/cuba/desktop/theme/impl/DesktopThemeLoaderImpl.java index c50298e90c..746014e474 100644 --- a/modules/desktop/src/com/haulmont/cuba/desktop/theme/impl/DesktopThemeLoaderImpl.java +++ b/modules/desktop/src/com/haulmont/cuba/desktop/theme/impl/DesktopThemeLoaderImpl.java @@ -393,6 +393,8 @@ public class DesktopThemeLoaderImpl implements DesktopThemeLoader { return loadInsets(element); } else if ("dimension".equals(elementName)) { return loadDimension(element); + } else if ("int".equals(elementName)) { + return Integer.parseInt(element.attributeValue("value")); } else { log.error("Uknown UI property value: " + elementName); return null; diff --git a/modules/global/src/com/haulmont/cuba/bom.properties b/modules/global/src/com/haulmont/cuba/bom.properties index 2207f1218e..5a089cca1d 100644 --- a/modules/global/src/com/haulmont/cuba/bom.properties +++ b/modules/global/src/com/haulmont/cuba/bom.properties @@ -89,7 +89,7 @@ org.javassist/javassist = 3.21.0-GA org.hibernate/hibernate-validator = 5.4.2.Final org.glassfish.web/javax.el = 2.2.6 -com.vaadin = 7.7.11.cuba.1 +com.vaadin = 7.7.11.cuba.3 com.vaadin/vaadin-shared = ${com.vaadin} com.vaadin/vaadin-server = ${com.vaadin} com.vaadin/vaadin-client = ${com.vaadin} diff --git a/modules/gui/src/com/haulmont/cuba/gui/components/Component.java b/modules/gui/src/com/haulmont/cuba/gui/components/Component.java index 05800c4569..cd849bfece 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/components/Component.java +++ b/modules/gui/src/com/haulmont/cuba/gui/components/Component.java @@ -346,6 +346,35 @@ public interface Component { void setDescription(String description); } + /** + * A sub-interface implemented by components that can provide a context help. + */ + interface HasContextHelp { + /** + * @return context help text + */ + String getContextHelpText(); + + /** + * Sets context help text. If set, then a special icon will be added for a field. + * + * @param contextHelpText context help text to be set + */ + void setContextHelpText(String contextHelpText); + + /** + * @return true if field accepts context help text in HTML format, false otherwise + */ + boolean isContextHelpTextHtmlEnabled(); + + /** + * Defines if context help text can be presented as HTML. + * + * @param enabled true if field accepts context help text in HTML format, false otherwise + */ + void setContextHelpTextHtmlEnabled(boolean enabled); + } + /** * Layout having a mouse click listener. */ diff --git a/modules/gui/src/com/haulmont/cuba/gui/components/Field.java b/modules/gui/src/com/haulmont/cuba/gui/components/Field.java index d1023dadf1..952c67270a 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/components/Field.java +++ b/modules/gui/src/com/haulmont/cuba/gui/components/Field.java @@ -22,7 +22,8 @@ import java.util.Collection; * Base interface for "fields" - components intended to display and edit value of a certain entity attribute. */ public interface Field extends DatasourceComponent, Component.HasCaption, Component.HasValue, Component.Editable, - Component.BelongToFrame, Component.Validatable, Component.HasIcon { + Component.BelongToFrame, Component.Validatable, Component.HasIcon, + Component.HasContextHelp { /** * @return whether the field must contain a non-null value */ diff --git a/modules/gui/src/com/haulmont/cuba/gui/components/FieldGroup.java b/modules/gui/src/com/haulmont/cuba/gui/components/FieldGroup.java index 56e8ceb538..83ca306bca 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/components/FieldGroup.java +++ b/modules/gui/src/com/haulmont/cuba/gui/components/FieldGroup.java @@ -441,6 +441,36 @@ public interface FieldGroup extends Component, Component.BelongToFrame, Componen * @return options datasource */ CollectionDatasource getOptionsDatasource(); + + /** + * @return context help text + */ + String getContextHelpText(); + + /** + * Set context help text for declarative field. + * + * If {@link #isBound()} is true and Component implements {@link Field} then sets context help text + * to the connected Component. + * + * @param contextHelpText context help text to be set + */ + void setContextHelpText(String contextHelpText); + + /** + * @return true if field accepts context help text in HTML format, null if not set for a declarative field + */ + Boolean isContextHelpTextHtmlEnabled(); + + /** + * Defines if context help text can be presented as HTML. + *

+ * If {@link #isBound()} is true and Component implements {@link Field} then sets this attribute + * to the connected Component. + * + * @param enabled true if field accepts context help text in HTML format + */ + void setContextHelpTextHtmlEnabled(Boolean enabled); } /** diff --git a/modules/gui/src/com/haulmont/cuba/gui/window.xsd b/modules/gui/src/com/haulmont/cuba/gui/window.xsd index 27f365ae61..8f66fa790c 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/window.xsd +++ b/modules/gui/src/com/haulmont/cuba/gui/window.xsd @@ -887,6 +887,11 @@ + + + + + @@ -1157,6 +1162,7 @@ + @@ -1205,6 +1211,7 @@ + @@ -2106,6 +2113,7 @@ + @@ -2210,6 +2218,7 @@ + diff --git a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractComponentLoader.java b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractComponentLoader.java index 3fe690dac1..d57771a93f 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractComponentLoader.java +++ b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractComponentLoader.java @@ -228,6 +228,19 @@ public abstract class AbstractComponentLoader implements Co } } + protected void loadContextHelp(Component.HasContextHelp component, Element element) { + String contextHelpText = element.attributeValue("contextHelpText"); + if (StringUtils.isNotEmpty(contextHelpText)) { + contextHelpText = loadResourceString(contextHelpText); + component.setContextHelpText(contextHelpText); + } + + String htmlEnabled = element.attributeValue("contextHelpTextHtmlEnabled"); + if (StringUtils.isNotEmpty(htmlEnabled)) { + component.setContextHelpTextHtmlEnabled(Boolean.parseBoolean(htmlEnabled)); + } + } + protected boolean loadVisible(Component component, Element element) { if (component instanceof DatasourceComponent && ((DatasourceComponent) component).getDatasource() != null) { diff --git a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractFieldLoader.java b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractFieldLoader.java index 96cd0b6003..77a7930fa5 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractFieldLoader.java +++ b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/AbstractFieldLoader.java @@ -42,6 +42,7 @@ public abstract class AbstractFieldLoader extends AbstractDatas loadCaption(resultComponent, element); loadIcon(resultComponent, element); loadDescription(resultComponent, element); + loadContextHelp(resultComponent, element); loadValidators(resultComponent, element); loadRequired(resultComponent, element); diff --git a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FieldGroupLoader.java b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FieldGroupLoader.java index 3c560672d7..b5463a2a2d 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FieldGroupLoader.java +++ b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FieldGroupLoader.java @@ -432,6 +432,7 @@ public class FieldGroupLoader extends AbstractComponentLoader { } } loadDescription(field, element); + loadContextHelp(field, element); field.setXmlDescriptor(element); @@ -490,6 +491,19 @@ public class FieldGroupLoader extends AbstractComponentLoader { return field; } + protected void loadContextHelp(FieldGroup.FieldConfig field, Element element) { + String contextHelpText = element.attributeValue("contextHelpText"); + if (StringUtils.isNotEmpty(contextHelpText)) { + contextHelpText = loadResourceString(contextHelpText); + field.setContextHelpText(contextHelpText); + } + + String htmlEnabled = element.attributeValue("contextHelpTextHtmlEnabled"); + if (StringUtils.isNotEmpty(htmlEnabled)) { + field.setContextHelpTextHtmlEnabled(Boolean.parseBoolean(htmlEnabled)); + } + } + protected String getDefaultCaption(FieldGroup.FieldConfig fieldConfig, Datasource fieldDatasource) { String caption = fieldConfig.getCaption(); if (caption == null) { diff --git a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FileUploadFieldLoader.java b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FileUploadFieldLoader.java index d27986c0a4..d748dd7b29 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FileUploadFieldLoader.java +++ b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/FileUploadFieldLoader.java @@ -45,6 +45,7 @@ public class FileUploadFieldLoader extends AbstractFieldLoader loadCaption(resultComponent, element); loadDescription(resultComponent, element); + loadContextHelp(resultComponent, element); loadTabIndex(resultComponent, element); diff --git a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/TokenListLoader.java b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/TokenListLoader.java index 64aff60846..cd6a1be2be 100644 --- a/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/TokenListLoader.java +++ b/modules/gui/src/com/haulmont/cuba/gui/xml/layout/loaders/TokenListLoader.java @@ -48,6 +48,7 @@ public class TokenListLoader extends AbstractFieldLoader { loadCaption(resultComponent, element); loadIcon(resultComponent, element); loadDescription(resultComponent, element); + loadContextHelp(resultComponent, element); loadHeight(resultComponent, element); loadWidth(resultComponent, element); diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/caption/CubaCaptionWidget.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/caption/CubaCaptionWidget.java index 4c23d0cdbb..239bd5f7ee 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/caption/CubaCaptionWidget.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/caption/CubaCaptionWidget.java @@ -31,6 +31,9 @@ import com.vaadin.shared.ui.ComponentStateUtil; public class CubaCaptionWidget extends VCaption { public static final String CUBA_CLASSNAME = "c-caption"; + public static final String CONTEXT_HELP_CLASSNAME = "c-context-help-button"; + + protected Element contextHelpIndicatorElement; protected boolean captionPlacedAfterComponentByDefault = true; @@ -141,6 +144,13 @@ public class CubaCaptionWidget extends VCaption { captionText = null; } + if (ComponentStateUtil.hasDescription(owner.getState()) + && captionText != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); + } + AriaHelper.handleInputRequired(owner.getWidget(), showRequired); if (showRequired) { @@ -162,6 +172,24 @@ public class CubaCaptionWidget extends VCaption { requiredFieldIndicator = null; } + if (owner.getState() instanceof AbstractFieldState) { + AbstractFieldState state = (AbstractFieldState) owner + .getState(); + if (state.contextHelpText != null && !state.contextHelpText.isEmpty()) { + if (contextHelpIndicatorElement == null) { + contextHelpIndicatorElement = DOM.createDiv(); + contextHelpIndicatorElement.setClassName(CONTEXT_HELP_CLASSNAME); + + DOM.insertChild(getElement(), contextHelpIndicatorElement, getContextHelpInsertPosition()); + } + } else { + if (contextHelpIndicatorElement != null) { + contextHelpIndicatorElement.removeFromParent(); + contextHelpIndicatorElement = null; + } + } + } + AriaHelper.handleInputInvalid(owner.getWidget(), showError); if (showError) { @@ -209,6 +237,9 @@ public class CubaCaptionWidget extends VCaption { if (errorIndicatorElement != null && errorIndicatorElement.getParentElement() == getElement()) { width += WidgetUtil.getRequiredWidth(errorIndicatorElement); } + if (contextHelpIndicatorElement != null && contextHelpIndicatorElement.getParentElement() == getElement()) { + width += WidgetUtil.getRequiredWidth(contextHelpIndicatorElement); + } return width; } @@ -218,6 +249,10 @@ public class CubaCaptionWidget extends VCaption { return super.getTextElement(); } + public Element getContextHelpIndicatorElement() { + return contextHelpIndicatorElement; + } + public Element getRequiredIndicatorElement() { return requiredFieldIndicator; } @@ -226,6 +261,21 @@ public class CubaCaptionWidget extends VCaption { return errorIndicatorElement; } + @Override + protected int getInsertPosition(InsertPosition element) { + int pos = super.getInsertPosition(element); + + if (contextHelpIndicatorElement != null) { + pos++; + } + + return pos; + } + + protected int getContextHelpInsertPosition() { + return super.getInsertPosition(null); + } + public void setCaptionHolder(CaptionHolder captionHolder) { this.captionHolder = captionHolder; } diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxConnector.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxConnector.java index 4efc49a8f4..2443de79af 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxConnector.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxConnector.java @@ -17,7 +17,10 @@ package com.haulmont.cuba.web.toolkit.ui.client.checkbox; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.user.client.DOM; import com.haulmont.cuba.web.toolkit.ui.CubaCheckBox; +import com.vaadin.client.VTooltip; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.checkbox.CheckBoxConnector; import com.vaadin.shared.ui.Connect; @@ -25,6 +28,8 @@ import com.vaadin.shared.ui.Connect; @Connect(value = CubaCheckBox.class, loadStyle = Connect.LoadStyle.EAGER) public class CubaCheckBoxConnector extends CheckBoxConnector { + public static final String CONTEXT_HELP_CLASSNAME = "c-context-help-button"; + @Override public boolean delegateCaptionHandling() { return getWidget().captionManagedByLayout; @@ -45,5 +50,20 @@ public class CubaCheckBoxConnector extends CheckBoxConnector { getWidget().captionManagedByLayout = getState().captionManagedByLayout; super.onStateChanged(stateChangeEvent); + + if (!getWidget().captionManagedByLayout + && getState().contextHelpText != null + && !getState().contextHelpText.isEmpty()) { + getWidget().contextHelpIcon = DOM.createSpan(); + getWidget().contextHelpIcon.setInnerHTML("?"); + getWidget().contextHelpIcon.setClassName(CONTEXT_HELP_CLASSNAME); + Roles.getTextboxRole().setAriaHiddenState(getWidget().contextHelpIcon, true); + + getWidget().getElement().appendChild(getWidget().contextHelpIcon); + DOM.sinkEvents(getWidget().contextHelpIcon, VTooltip.TOOLTIP_EVENTS); + } else if (getWidget().contextHelpIcon != null) { + getWidget().contextHelpIcon.removeFromParent(); + getWidget().contextHelpIcon = null; + } } } \ No newline at end of file diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxWidget.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxWidget.java index 2f2ab96cc0..af12f4fc48 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxWidget.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/checkbox/CubaCheckBoxWidget.java @@ -17,17 +17,20 @@ package com.haulmont.cuba.web.toolkit.ui.client.checkbox; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.i18n.client.HasDirection; import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VCheckBox; public class CubaCheckBoxWidget extends VCheckBox implements FocusHandler, BlurHandler { protected boolean captionManagedByLayout = false; + protected Element contextHelpIcon; public CubaCheckBoxWidget() { addBlurHandler(this); diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/cssactionslayout/CubaCssActionsLayoutConnector.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/cssactionslayout/CubaCssActionsLayoutConnector.java index 40cf5c4593..96744db6c4 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/cssactionslayout/CubaCssActionsLayoutConnector.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/cssactionslayout/CubaCssActionsLayoutConnector.java @@ -17,12 +17,13 @@ package com.haulmont.cuba.web.toolkit.ui.client.cssactionslayout; import com.haulmont.cuba.web.toolkit.ui.CubaCssActionsLayout; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; +import com.haulmont.cuba.web.toolkit.ui.client.caption.CubaCaptionWidget; +import com.vaadin.client.*; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.ShortcutActionHandler; import com.vaadin.client.ui.csslayout.CssLayoutConnector; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.AbstractFieldState; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.MarginInfo; @@ -60,4 +61,17 @@ public class CubaCssActionsLayoutConnector extends CssLayoutConnector implements } } } + + @Override + protected VCaption createCaption(ComponentConnector child) { + return new CubaCaptionWidget(child, getConnection()); + } + + @Override + protected boolean isCaptionNeeded(ComponentConnector child) { + AbstractComponentState state = child.getState(); + return super.isCaptionNeeded(child) || (state instanceof AbstractFieldState + && ((AbstractFieldState) state).contextHelpText != null + && !((AbstractFieldState) state).contextHelpText.isEmpty()); + } } \ No newline at end of file diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/fieldgrouplayout/CubaFieldGroupLayoutComponentSlot.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/fieldgrouplayout/CubaFieldGroupLayoutComponentSlot.java index 4ebbdc376f..73e302517d 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/fieldgrouplayout/CubaFieldGroupLayoutComponentSlot.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/fieldgrouplayout/CubaFieldGroupLayoutComponentSlot.java @@ -39,7 +39,7 @@ public class CubaFieldGroupLayoutComponentSlot extends CubaGridLayoutSlot implem protected static final String INDICATORS_CLASSNAME = "caption-indicators"; protected Element requiredElement = null; - protected Element tooltipElement = null; + protected Element contextHelpIndicatorElement = null; protected Element errorIndicatorElement = null; protected Element rightCaption = null; @@ -365,6 +365,15 @@ public class CubaFieldGroupLayoutComponentSlot extends CubaGridLayoutSlot implem requiredElement = null; } + if (captionWidget.getContextHelpIndicatorElement() != null) { + captionWidget.getContextHelpIndicatorElement().removeFromParent(); + contextHelpIndicatorElement = captionWidget.getContextHelpIndicatorElement(); + rightCaption.appendChild(contextHelpIndicatorElement); + } else if (contextHelpIndicatorElement != null) { + contextHelpIndicatorElement.removeFromParent(); + contextHelpIndicatorElement = null; + } + if (captionWidget.getErrorIndicatorElement() != null) { captionWidget.getErrorIndicatorElement().removeFromParent(); diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/gridlayout/CubaGridLayoutConnector.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/gridlayout/CubaGridLayoutConnector.java index 184ba199d6..fb9309ed35 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/gridlayout/CubaGridLayoutConnector.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/gridlayout/CubaGridLayoutConnector.java @@ -25,6 +25,8 @@ import com.vaadin.client.ui.ShortcutActionHandler; import com.vaadin.client.ui.VGridLayout; import com.vaadin.client.ui.gridlayout.GridLayoutConnector; import com.vaadin.client.ui.layout.VLayoutSlot; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.AbstractFieldState; import com.vaadin.shared.ui.Connect; @Connect(CubaGridLayout.class) @@ -43,7 +45,11 @@ public class CubaGridLayoutConnector extends GridLayoutConnector implements Pain // CAUTION copied from GridLayoutConnector.updateCaption(ComponentConnector childConnector) VGridLayout layout = getWidget(); VGridLayout.Cell cell = layout.widgetToCell.get(childConnector.getWidget()); - if (VCaption.isNeeded(childConnector.getState())) { + AbstractComponentState state = childConnector.getState(); + if (VCaption.isNeeded(state) + || (state instanceof AbstractFieldState + && ((AbstractFieldState) state).contextHelpText != null + && !((AbstractFieldState) state).contextHelpText.isEmpty())) { VLayoutSlot layoutSlot = cell.slot; VCaption caption = layoutSlot.getCaption(); if (caption == null) { diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedActionsLayoutConnector.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedActionsLayoutConnector.java index c9971226e5..573732c118 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedActionsLayoutConnector.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedActionsLayoutConnector.java @@ -18,13 +18,20 @@ package com.haulmont.cuba.web.toolkit.ui.client.orderedactionslayout; import com.haulmont.cuba.web.toolkit.ui.CubaOrderedActionsLayout; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; +import com.vaadin.client.*; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.ShortcutActionHandler; +import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.client.ui.orderedlayout.AbstractOrderedLayoutConnector; +import com.vaadin.client.ui.orderedlayout.CaptionPosition; +import com.vaadin.shared.AbstractFieldState; +import com.vaadin.shared.ComponentConstants; +import com.vaadin.shared.communication.URLReference; import com.vaadin.shared.ui.Connect; +import java.util.List; + @Connect(CubaOrderedActionsLayout.class) public class CubaOrderedActionsLayoutConnector extends AbstractOrderedLayoutConnector implements Paintable { @@ -46,4 +53,60 @@ public class CubaOrderedActionsLayoutConnector extends AbstractOrderedLayoutConn } } } + + @Override + protected void updateCaptionInternal(ComponentConnector child) { + // CAUTION copied from superclass + CubaOrderedLayoutSlot slot = (CubaOrderedLayoutSlot) getWidget().getSlot(child.getWidget()); + + String caption = child.getState().caption; + URLReference iconUrl = child.getState().resources + .get(ComponentConstants.ICON_RESOURCE); + String iconUrlString = iconUrl != null ? iconUrl.getURL() : null; + Icon icon = child.getConnection().getIcon(iconUrlString); + + List styles = child.getState().styles; + String error = child.getState().errorMessage; + boolean showError = error != null; + if (child.getState() instanceof AbstractFieldState) { + AbstractFieldState abstractFieldState = (AbstractFieldState) child + .getState(); + showError = showError && !abstractFieldState.hideErrors; + } + boolean required = false; + if (child instanceof AbstractFieldConnector) { + required = ((AbstractFieldConnector) child).isRequired(); + } + boolean enabled = child.isEnabled(); + + if (slot.hasCaption() && null == caption) { + slot.setCaptionResizeListener(null); + } + + // Haulmont API + String contextHelpText = null; + if (child.getState() instanceof AbstractFieldState) { + contextHelpText = ((AbstractFieldState) child.getState()).contextHelpText; + } + + // Haulmont API + slot.setCaption(caption, contextHelpText, icon, styles, error, showError, required, + enabled, child.getState().captionAsHtml); + + AriaHelper.handleInputRequired(child.getWidget(), required); + AriaHelper.handleInputInvalid(child.getWidget(), showError); + AriaHelper.bindCaption(child.getWidget(), slot.getCaptionElement()); + + if (slot.hasCaption()) { + CaptionPosition pos = slot.getCaptionPosition(); + slot.setCaptionResizeListener(slotCaptionResizeListener); + if (child.isRelativeHeight() + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } else if (child.isRelativeWidth() + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } + } + } } \ No newline at end of file diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedLayoutSlot.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedLayoutSlot.java index f6f9dc5387..853b68053c 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedLayoutSlot.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/orderedactionslayout/CubaOrderedLayoutSlot.java @@ -35,12 +35,16 @@ import java.util.List; public class CubaOrderedLayoutSlot extends Slot { + public static final String CONTEXT_HELP_CLASSNAME = "c-context-help-button"; + + protected Element contextHelpIcon; + protected String contextHelpText; + public CubaOrderedLayoutSlot(VAbstractOrderedLayout layout, Widget widget) { super(layout, widget); } - @Override - public void setCaption(String captionText, Icon icon, List styles, + public void setCaption(String captionText, String contextHelpText, Icon icon, List styles, String error, boolean showError, boolean required, boolean enabled, boolean captionAsHtml) { // CAUTION copied from super // Caption wrappers @@ -48,7 +52,7 @@ public class CubaOrderedLayoutSlot extends Slot { final Element focusedElement = WidgetUtil.getFocusedElement(); // By default focus will not be lost boolean focusLost = false; - if (captionText != null || icon != null || error != null || required) { + if (captionText != null || icon != null || error != null || required || contextHelpText != null) { if (caption == null) { caption = DOM.createDiv(); captionWrap = DOM.createDiv(); @@ -134,6 +138,30 @@ public class CubaOrderedLayoutSlot extends Slot { requiredIcon = null; } + // Context Help + // Haulmont API + this.contextHelpText = contextHelpText; + if (contextHelpText != null && !contextHelpText.isEmpty()) { + if (contextHelpIcon == null) { + contextHelpIcon = DOM.createSpan(); + // TODO decide something better (e.g. use CSS to insert the + // character) + contextHelpIcon.setInnerHTML("?"); + contextHelpIcon.setClassName(CONTEXT_HELP_CLASSNAME); + + // The question mark should not be read by the screen reader, as it is + // purely visual. Required state is set at the element level for + // the screen reader. + Roles.getTextboxRole().setAriaHiddenState(contextHelpIcon, true); + } + if (caption != null) { + caption.appendChild(contextHelpIcon); + } + } else if (this.contextHelpIcon != null) { + this.contextHelpIcon.removeFromParent(); + this.contextHelpIcon = null; + } + // Error if (error != null && showError) { if (errorIcon == null) { diff --git a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/tooltip/CubaTooltip.java b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/tooltip/CubaTooltip.java index 19b1da9995..8b019b9f9c 100644 --- a/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/tooltip/CubaTooltip.java +++ b/modules/web-toolkit/src/com/haulmont/cuba/web/toolkit/ui/client/tooltip/CubaTooltip.java @@ -20,20 +20,17 @@ package com.haulmont.cuba.web.toolkit.ui.client.tooltip; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.event.dom.client.*; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; -import com.haulmont.cuba.web.toolkit.ui.client.caption.CubaCaptionWidget; +import com.haulmont.cuba.web.toolkit.ui.client.checkbox.CubaCheckBoxWidget; import com.haulmont.cuba.web.toolkit.ui.client.resizabletextarea.CubaResizableTextAreaWrapperWidget; import com.vaadin.client.*; -import com.vaadin.client.ui.VGridLayout; -import com.vaadin.client.ui.gridlayout.GridLayoutConnector; -import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot; -import com.vaadin.client.ui.orderedlayout.Slot; + +import static com.haulmont.cuba.web.toolkit.ui.client.caption.CubaCaptionWidget.CONTEXT_HELP_CLASSNAME; public class CubaTooltip extends VTooltip { @@ -43,7 +40,11 @@ public class CubaTooltip extends VTooltip { // If required indicators are not visible we show tooltip on mouse hover otherwise only by mouse click protected static Boolean requiredIndicatorVisible = null; + protected Element contextHelpElement = DOM.createDiv(); + public CubaTooltip() { + contextHelpElement.setClassName("c-tooltip-context-help"); + DOM.appendChild(getWidget().getElement(), contextHelpElement); tooltipEventHandler = new CubaTooltipEventHandler(); } @@ -81,13 +82,57 @@ public class CubaTooltip extends VTooltip { Profiler.leave("VTooltip.connectHandlersToWidget"); } + @Override + protected void setTooltipText(TooltipInfo info) { + super.setTooltipText(info); + + if (info.getTitle() != null && !info.getTitle().isEmpty()) { + description.removeAttribute("aria-hidden"); + } else { + description.setAttribute("aria-hidden", "true"); + } + + String contextHelp = info.getContextHelp(); + if (contextHelp != null && !contextHelp.isEmpty()) { + if (info.isContextHelpHtmlEnabled()) { + contextHelpElement.setInnerHTML(contextHelp); + } else { + if (contextHelp.contains("\n")) { + contextHelp = WidgetUtil.escapeHTML(contextHelp).replace("\n", "
"); + contextHelpElement.setInnerHTML(contextHelp); + } else { + contextHelpElement.setInnerText(contextHelp); + } + } + contextHelpElement.getStyle().clearDisplay(); + } else { + contextHelpElement.setInnerHTML(""); + contextHelpElement.getStyle().setDisplay(Style.Display.NONE); + } + } + + @Override + public void hide() { + contextHelpElement.setInnerHTML(""); + super.hide(); + } + public class CubaTooltipEventHandler extends TooltipEventHandler { protected ComponentConnector currentConnector = null; protected boolean isTooltipElement(Element element) { - return (REQUIRED_INDICATOR.equals(element.getClassName()) - || ERROR_INDICATOR.equals(element.getClassName())); + return (isRequiredIndicator(element) + || isContextHelpElement(element)); + } + + protected boolean isRequiredIndicator(Element element) { + return REQUIRED_INDICATOR.equals(element.getClassName()) + || ERROR_INDICATOR.equals(element.getClassName()); + } + + protected boolean isContextHelpElement(Element element) { + return CONTEXT_HELP_CLASSNAME.equals(element.getClassName()); } protected void checkRequiredIndicatorVisible() { @@ -114,22 +159,21 @@ public class CubaTooltip extends VTooltip { @Override protected TooltipInfo getTooltipFor(Element element) { - checkRequiredIndicatorVisible(); + Element originalElement = element; - if (!requiredIndicatorVisible) { - if (isClassNameExcluded(element.getClassName())) { - return null; - } else { - return super.getTooltipFor(element); - } + if (isClassNameExcluded(element.getClassName())) { + return null; } if (isTooltipElement(element)) { element = element.getParentElement().cast(); - int index = DOM.getChildIndex(element.getParentElement().cast(), element); - int indexOfComponent = index == 0 ? index + 1 : index - 1; - element = DOM.getChild(element.getParentElement().cast(), indexOfComponent); + Widget widget = WidgetUtil.findWidget(element); + if (!(widget instanceof CubaCheckBoxWidget)) { + int index = DOM.getChildIndex(element.getParentElement().cast(), element); + int indexOfComponent = index == 0 ? index + 1 : index - 1; + element = DOM.getChild(element.getParentElement().cast(), indexOfComponent); + } } ApplicationConnection ac = getApplicationConnection(); @@ -159,35 +203,40 @@ public class CubaTooltip extends VTooltip { + " returned a tooltip even though hasTooltip claims there are no tooltips for the connector."; currentConnector = connector; - return info; + return updateTooltip(info, originalElement); } return null; } + protected TooltipInfo updateTooltip(TooltipInfo info, Element element) { + if (isContextHelpElement(element)) { + info.setTitle(null); + info.setErrorMessage(null); + } else { + info.setContextHelp(null); + checkRequiredIndicatorVisible(); + + if (requiredIndicatorVisible && isRequiredIndicator(element)) { + info.setTitle(null); + } + } + return info; + } + @Override public void onMouseDown(MouseDownEvent event) { - checkRequiredIndicatorVisible(); - - if (requiredIndicatorVisible) { - Element element = event.getNativeEvent().getEventTarget().cast(); - if (isTooltipElement(element)) { - closeNow(); - handleShowHide(event, false); - } else { - hideTooltip(); - } + Element element = event.getNativeEvent().getEventTarget().cast(); + if (isTooltipElement(element)) { + closeNow(); + handleShowHide(event, false); + } else { + hideTooltip(); } } @Override protected void handleShowHide(DomEvent domEvent, boolean isFocused) { - checkRequiredIndicatorVisible(); - - if (!requiredIndicatorVisible) { - super.handleShowHide(domEvent, isFocused); - } - // CAUTION copied from parent with changes Event event = Event.as(domEvent.getNativeEvent()); Element element = Element.as(event.getEventTarget()); @@ -214,28 +263,25 @@ public class CubaTooltip extends VTooltip { handleHideEvent(); currentConnector = null; } else { - boolean hasTooltipIndicator = hasIndicators(currentConnector); boolean elementIsIndicator = elementIsIndicator(element); - if ((hasTooltipIndicator && elementIsIndicator) || (!hasTooltipIndicator)) { - if (closing) { - closeTimer.cancel(); - closing = false; - } - - if (isTooltipOpen()) { - closeNow(); - } - - setTooltipText(info); - updatePosition(event, isFocused); - - if (BrowserInfo.get().isIOS()) { - element.focus(); - } - - showTooltip(domEvent instanceof MouseDownEvent && elementIsIndicator); + if (closing) { + closeTimer.cancel(); + closing = false; } + + if (isTooltipOpen()) { + closeNow(); + } + + setTooltipText(info); + updatePosition(event, isFocused); + + if (BrowserInfo.get().isIOS()) { + element.focus(); + } + + showTooltip(domEvent instanceof MouseDownEvent && elementIsIndicator); } handledByFocus = isFocused; @@ -244,59 +290,7 @@ public class CubaTooltip extends VTooltip { protected boolean elementIsIndicator(Element relativeElement) { return relativeElement != null - && (REQUIRED_INDICATOR.equals(relativeElement.getClassName()) - || ERROR_INDICATOR.equals(relativeElement.getClassName())); - } - - protected boolean hasIndicators(ComponentConnector connector) { - if (connector == null || connector.getWidget() == null) { - return false; - } - - Widget parentWidget = connector.getWidget().getParent(); - - if (parentWidget instanceof Slot) { - Slot slot = (Slot) parentWidget; - if (slot.getCaptionElement() != null) { - com.google.gwt.user.client.Element captionElement = slot.getCaptionElement(); - for (int i = 0; i < captionElement.getChildCount(); i++) { - Node child = captionElement.getChild(i); - if (child instanceof Element - && (elementIsIndicator(((Element) child)))) { - return true; - } - } - } - } else if (connector.getParent() instanceof GridLayoutConnector) { - GridLayoutConnector gridLayoutConnector = (GridLayoutConnector) connector.getParent(); - VGridLayout gridWidget = gridLayoutConnector.getWidget(); - VGridLayout.Cell cell = gridWidget.widgetToCell.get(connector.getWidget()); - - ComponentConnectorLayoutSlot slot = cell.slot; - if (slot != null) { - VCaption caption = slot.getCaption(); - if (caption != null) { - com.google.gwt.user.client.Element captionElement = caption.getElement(); - for (int i = 0; i < captionElement.getChildCount(); i++) { - Node child = captionElement.getChild(i); - if (child instanceof Element - && (elementIsIndicator(((Element) child)))) { - return true; - } - } - } - - if (caption instanceof CubaCaptionWidget) { - CubaCaptionWidget cubaCaptionWidget = (CubaCaptionWidget) caption; - if (cubaCaptionWidget.getRequiredIndicatorElement() != null - || cubaCaptionWidget.getErrorIndicatorElement() != null) { - return true; - } - } - } - } - - return false; + && isTooltipElement(relativeElement); } } } \ No newline at end of file diff --git a/modules/web/src/com/haulmont/cuba/web/gui/components/WebAbstractField.java b/modules/web/src/com/haulmont/cuba/web/gui/components/WebAbstractField.java index 16a43cb466..f1c1896eff 100644 --- a/modules/web/src/com/haulmont/cuba/web/gui/components/WebAbstractField.java +++ b/modules/web/src/com/haulmont/cuba/web/gui/components/WebAbstractField.java @@ -426,4 +426,24 @@ public abstract class WebAbstractField return getClass().getSimpleName(); } + + @Override + public String getContextHelpText() { + return component.getContextHelpText(); + } + + @Override + public void setContextHelpText(String contextHelpText) { + component.setContextHelpText(contextHelpText); + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return component.isContextHelpTextHtmlEnabled(); + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + component.setContextHelpTextHtmlEnabled(enabled); + } } \ No newline at end of file diff --git a/modules/web/src/com/haulmont/cuba/web/gui/components/WebFieldGroup.java b/modules/web/src/com/haulmont/cuba/web/gui/components/WebFieldGroup.java index 5922d73730..aecd982e1d 100644 --- a/modules/web/src/com/haulmont/cuba/web/gui/components/WebFieldGroup.java +++ b/modules/web/src/com/haulmont/cuba/web/gui/components/WebFieldGroup.java @@ -34,6 +34,7 @@ import com.haulmont.cuba.web.toolkit.ui.CubaFieldGroup; import com.haulmont.cuba.web.toolkit.ui.CubaFieldGroupLayout; import com.haulmont.cuba.web.toolkit.ui.CubaFieldWrapper; import com.vaadin.server.Sizeable; +import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.dom4j.Element; import org.slf4j.Logger; @@ -325,6 +326,12 @@ public class WebFieldGroup extends WebAbstractComponent if (fci.getTargetRequiredMessage() != null) { cubaField.setRequiredMessage(fci.getTargetRequiredMessage()); } + if (fci.getTargetContextHelpText() != null) { + cubaField.setContextHelpText(fci.getTargetContextHelpText()); + } + if (fci.getTargetContextHelpTextHtmlEnabled() != null) { + cubaField.setContextHelpTextHtmlEnabled(fci.getTargetContextHelpTextHtmlEnabled()); + } if (fci.getTargetEditable() != null) { cubaField.setEditable(fci.getTargetEditable()); } @@ -813,6 +820,8 @@ public class WebFieldGroup extends WebAbstractComponent protected CollectionDatasource targetOptionsDatasource; protected String targetCaption; protected String targetDescription; + protected String targetContextHelpText; + protected Boolean targetContextHelpTextHtmlEnabled; protected Formatter targetFormatter; protected boolean isTargetCustom; @@ -1204,6 +1213,54 @@ public class WebFieldGroup extends WebAbstractComponent } } + @Override + public String getContextHelpText() { + if (component instanceof Field) { + return ((Field) component).getContextHelpText(); + } + if (composition != null && isWrapped()) { + return composition.getContextHelpText(); + } + return targetContextHelpText; + } + + @Override + public void setContextHelpText(String contextHelpText) { + if (component instanceof Field) { + ((Field) component).setContextHelpText(contextHelpText); + } else if (composition != null && isWrapped()) { + composition.setContextHelpText(contextHelpText); + } else { + this.targetContextHelpText = contextHelpText; + } + } + + @Override + public Boolean isContextHelpTextHtmlEnabled() { + if (component instanceof Field) { + return ((Field) component).isContextHelpTextHtmlEnabled(); + } + if (composition != null && isWrapped()) { + return composition.isContextHelpTextHtmlEnabled(); + } + return BooleanUtils.isTrue(targetContextHelpTextHtmlEnabled); + } + + @Override + public void setContextHelpTextHtmlEnabled(Boolean enabled) { + if (component instanceof Field) { + checkNotNullArgument(enabled, "Unable to reset contextHelpTextHtmlEnabled " + + "flag for the bound FieldConfig"); + ((Field) component).setContextHelpTextHtmlEnabled(enabled); + } else if (composition != null && isWrapped()) { + checkNotNullArgument(enabled, "Unable to reset contextHelpTextHtmlEnabled " + + "flag for the bound FieldConfig"); + composition.setContextHelpTextHtmlEnabled(enabled); + } else { + this.targetContextHelpTextHtmlEnabled = enabled; + } + } + @Override public Formatter getFormatter() { if (component instanceof HasFormatter) { @@ -1356,6 +1413,22 @@ public class WebFieldGroup extends WebAbstractComponent this.targetRequiredMessage = targetRequiredMessage; } + public String getTargetContextHelpText() { + return targetContextHelpText; + } + + public void setTargetContextHelpText(String targetContextHelpText) { + this.targetContextHelpText = targetContextHelpText; + } + + public Boolean getTargetContextHelpTextHtmlEnabled() { + return targetContextHelpTextHtmlEnabled; + } + + public void setTargetContextHelpTextHtmlEnabled(Boolean targetContextHelpTextHtmlEnabled) { + this.targetContextHelpTextHtmlEnabled = targetContextHelpTextHtmlEnabled; + } + public CollectionDatasource getTargetOptionsDatasource() { return targetOptionsDatasource; } diff --git a/modules/web/src/com/haulmont/cuba/web/gui/components/WebLookupPickerField.java b/modules/web/src/com/haulmont/cuba/web/gui/components/WebLookupPickerField.java index 038c245ed1..9bfd484fbd 100644 --- a/modules/web/src/com/haulmont/cuba/web/gui/components/WebLookupPickerField.java +++ b/modules/web/src/com/haulmont/cuba/web/gui/components/WebLookupPickerField.java @@ -203,6 +203,26 @@ public class WebLookupPickerField extends WebLookupField implements LookupPicker return pickerField.getDescription(); } + @Override + public String getContextHelpText() { + return pickerField.getContextHelpText(); + } + + @Override + public void setContextHelpText(String contextHelpText) { + pickerField.setContextHelpText(contextHelpText); + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return pickerField.isContextHelpTextHtmlEnabled(); + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + pickerField.setContextHelpTextHtmlEnabled(enabled); + } + @Override @Nullable public Action getAction(String id) { diff --git a/modules/web/src/com/haulmont/cuba/web/gui/components/WebResizableTextArea.java b/modules/web/src/com/haulmont/cuba/web/gui/components/WebResizableTextArea.java index 1a3ac5729c..7fc2f251f1 100644 --- a/modules/web/src/com/haulmont/cuba/web/gui/components/WebResizableTextArea.java +++ b/modules/web/src/com/haulmont/cuba/web/gui/components/WebResizableTextArea.java @@ -119,6 +119,26 @@ public class WebResizableTextArea extends WebAbstractTextArea impl wrapper.setDescription(description); } + @Override + public String getContextHelpText() { + return wrapper.getContextHelpText(); + } + + @Override + public void setContextHelpText(String contextHelpText) { + wrapper.setContextHelpText(contextHelpText); + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return wrapper.isContextHelpTextHtmlEnabled(); + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + wrapper.setContextHelpTextHtmlEnabled(enabled); + } + @Override public boolean isRequired() { return wrapper.isRequired(); diff --git a/modules/web/src/com/haulmont/cuba/web/gui/components/WebSearchPickerField.java b/modules/web/src/com/haulmont/cuba/web/gui/components/WebSearchPickerField.java index ff4dc7e086..a97396a096 100644 --- a/modules/web/src/com/haulmont/cuba/web/gui/components/WebSearchPickerField.java +++ b/modules/web/src/com/haulmont/cuba/web/gui/components/WebSearchPickerField.java @@ -188,6 +188,26 @@ public class WebSearchPickerField extends WebSearchField implements SearchPicker return pickerField.getCaption(); } + @Override + public String getContextHelpText() { + return pickerField.getContextHelpText(); + } + + @Override + public void setContextHelpText(String contextHelpText) { + pickerField.setContextHelpText(contextHelpText); + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return pickerField.isContextHelpTextHtmlEnabled(); + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + pickerField.setContextHelpTextHtmlEnabled(enabled); + } + @Override @Nullable public Action getAction(String id) { diff --git a/modules/web/src/com/haulmont/cuba/web/gui/components/WebSuggestionPickerField.java b/modules/web/src/com/haulmont/cuba/web/gui/components/WebSuggestionPickerField.java index d3a3b5b3de..4bac0a2879 100644 --- a/modules/web/src/com/haulmont/cuba/web/gui/components/WebSuggestionPickerField.java +++ b/modules/web/src/com/haulmont/cuba/web/gui/components/WebSuggestionPickerField.java @@ -67,6 +67,26 @@ public class WebSuggestionPickerField extends WebSuggestionField implements Sugg return pickerField.getDescription(); } + @Override + public String getContextHelpText() { + return pickerField.getContextHelpText(); + } + + @Override + public void setContextHelpText(String contextHelpText) { + pickerField.setContextHelpText(contextHelpText); + } + + @Override + public boolean isContextHelpTextHtmlEnabled() { + return pickerField.isContextHelpTextHtmlEnabled(); + } + + @Override + public void setContextHelpTextHtmlEnabled(boolean enabled) { + pickerField.setContextHelpTextHtmlEnabled(enabled); + } + protected void initValueSync(WebPickerField.Picker picker) { component.addValueChangeListener(e -> { if (updateComponentValue) diff --git a/modules/web/themes/halo/components/common/common.scss b/modules/web/themes/halo/components/common/common.scss index 775429ddc9..fd27fd3122 100644 --- a/modules/web/themes/halo/components/common/common.scss +++ b/modules/web/themes/halo/components/common/common.scss @@ -24,6 +24,34 @@ display: none; } + .c-context-help-button { + display: inline-block; + font-size: 0; + width: $v-font-size; + } + + .c-context-help-button::before { + font-family: FontAwesome; + content: "\f059"; + font-size: $v-font-size; + padding-left: .2em; + } + + .v-required-field-indicator { + padding: 0 0 0 .2em; + } + + .v-tooltip { + .c-tooltip-context-help { + margin-top: $v-tooltip-padding-vertical * 2; + overflow: auto; + } + + .v-errormessage[aria-hidden="true"]+.v-tooltip-text[aria-hidden="true"]+.c-tooltip-context-help { + margin-top: 0; + } + } + @if $v-show-required-indicators == false { .v-required-field-indicator { display: none; diff --git a/modules/web/themes/havana/components/caption/caption.scss b/modules/web/themes/havana/components/caption/caption.scss index 39d5535638..a4cccc87a0 100644 --- a/modules/web/themes/havana/components/caption/caption.scss +++ b/modules/web/themes/havana/components/caption/caption.scss @@ -29,6 +29,20 @@ cursor: pointer; } + .c-context-help-button { + display: inline-block; + + color: transparent; + background: transparent no-repeat top right; + background-image: url(sprites/question.png); /** sprite-ref: components; */ + width: 16px; + height: 16px; + } + + .c-context-help-button:hover { + cursor: pointer; + } + .#{$primary-stylename}text { padding-bottom: 2px; } diff --git a/modules/web/themes/havana/components/tooltip/tooltip.scss b/modules/web/themes/havana/components/tooltip/tooltip.scss index 2b7f49c2e0..3b3b12b62c 100644 --- a/modules/web/themes/havana/components/tooltip/tooltip.scss +++ b/modules/web/themes/havana/components/tooltip/tooltip.scss @@ -60,5 +60,13 @@ font-weight: bold; margin: 1px 0 4px 0; } + + .c-tooltip-context-help { + padding: 2px 4px; + border: none; + border-top: 1px solid #fffef5; + border-bottom: 1px solid #fbf8d9; + overflow: hidden; + } } } \ No newline at end of file