/* GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Contact info: lobochief@users.sourceforge.net */ /* * Created on Apr 17, 2005 */ package org.lobobrowser.html.renderer; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Insets; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.jdt.annotation.NonNull; import org.lobobrowser.html.domimpl.ModelNode; import org.lobobrowser.html.domimpl.NodeImpl; import org.lobobrowser.html.domimpl.UINode; import org.lobobrowser.html.style.RenderState; import org.lobobrowser.ua.UserAgentContext; import cz.vutbr.web.css.CSSProperty.VerticalAlign; /** * @author J. H. S. */ class RUIControl extends BaseElementRenderable { private static final int MAX_CACHE_SIZE = 10; public final UIControl widget; private final FrameContext frameContext; public RUIControl(final ModelNode me, final UIControl widget, final RenderableContainer container, final FrameContext frameContext, final UserAgentContext ucontext) { super(container, me, ucontext); this.widget = widget; this.frameContext = frameContext; widget.setRUIControl(this); } @Override public void focus() { super.focus(); final java.awt.Component c = this.widget.getComponent(); c.requestFocus(); } @Override public final void invalidateLayoutLocal() { // Invalidate widget (some redundancy) super.invalidateLayoutLocal(); this.widget.invalidate(); // Invalidate cached values this.cachedLayout.clear(); this.lastLayoutKey = null; this.lastLayoutValue = null; } public VerticalAlign getVAlign() { return this.widget.getVAlign(); } public boolean hasBackground() { return (this.backgroundColor != null) || (this.backgroundImage != null) || (this.lastBackgroundImageUri != null); } @Override public final void paintShifted(final Graphics g) { final RenderState rs = this.modelNode.getRenderState(); if ((rs != null) && (rs.getVisibility() != RenderState.VISIBILITY_VISIBLE)) { // Just don't paint it. return; } // Prepaint borders, background images, etc. this.prePaint(g); // We need to paint the GUI component. // For various reasons, we need to do that // instead of letting AWT do it. final Insets insets = this.getBorderInsets(); g.translate(insets.left, insets.top); try { this.widget.paint(g); } finally { g.translate(-insets.left, -insets.top); } } public boolean onMouseClick(final java.awt.event.MouseEvent event, final int x, final int y) { final ModelNode me = this.modelNode; if (me != null) { return HtmlController.getInstance().onMouseClick(me, event, x, y); } else { return true; } } public boolean onDoubleClick(final java.awt.event.MouseEvent event, final int x, final int y) { final ModelNode me = this.modelNode; if (me != null) { return HtmlController.getInstance().onDoubleClick(me, event, x, y); } else { return true; } } public boolean onMousePressed(final java.awt.event.MouseEvent event, final int x, final int y) { final ModelNode me = this.modelNode; if (me != null) { return HtmlController.getInstance().onMouseDown(me, event, x, y); } else { return true; } } public boolean onMouseReleased(final java.awt.event.MouseEvent event, final int x, final int y) { final ModelNode me = this.modelNode; if (me != null) { return HtmlController.getInstance().onMouseUp(me, event, x, y); } else { return true; } } public boolean onMouseDisarmed(final java.awt.event.MouseEvent event) { final ModelNode me = this.modelNode; if (me != null) { return HtmlController.getInstance().onMouseDisarmed(me, event); } else { return true; } } /* * (non-Javadoc) * * @see * org.xamjwg.html.renderer.BoundableRenderable#invalidateState(org.xamjwg * .html.renderer.RenderableContext) */ public void invalidateRenderStyle() { // NOP - No RenderStyle below this node. } /* * (non-Javadoc) * * @see * org.xamjwg.html.domimpl.ContainingBlockContext#repaint(org.xamjwg.html. * renderer.RenderableContext) */ public void repaint(final ModelNode modelNode) { final Object widget = this.widget; if (widget instanceof UINode) { ((UINode) widget).repaint(modelNode); } else { this.repaint(); } } @Override public void updateWidgetBounds(final int guiX, final int guiY) { // Overrides super.updateWidgetBounds(guiX, guiY); final Insets insets = this.getBorderInsets(); this.widget.setBounds(guiX + insets.left, guiY + insets.top, this.width - insets.left - insets.right, this.height - insets.top - insets.bottom); } @Override public Color getBlockBackgroundColor() { return this.widget.getBackgroundColor(); } /* * (non-Javadoc) * * @see * org.xamjwg.html.renderer.BoundableRenderable#paintSelection(java.awt.Graphics * , boolean, org.xamjwg.html.renderer.RenderablePoint, * org.xamjwg.html.renderer.RenderablePoint) */ @Override public boolean paintSelection(final Graphics g, boolean inSelection, final RenderableSpot startPoint, final RenderableSpot endPoint) { inSelection = super.paintSelection(g, inSelection, startPoint, endPoint); if (inSelection) { final Color over = new Color(0, 0, 255, 50); final Color oldColor = g.getColor(); try { g.setColor(over); g.fillRect(0, 0, this.width, this.height); } finally { g.setColor(oldColor); } } return inSelection; } @Override public boolean extractSelectionText(final StringBuffer buffer, final boolean inSelection, final RenderableSpot startPoint, final RenderableSpot endPoint) { // No text here return inSelection; } public RenderableSpot getLowestRenderableSpot(final int x, final int y) { // Nothing draggable - return self return new RenderableSpot(this, x, y); } private int declaredWidth = -1; private int declaredHeight = -1; private LayoutKey lastLayoutKey = null; private LayoutValue lastLayoutValue = null; private final Map<LayoutKey, LayoutValue> cachedLayout = new HashMap<>(5); @Override public void doLayout(final int availWidth, final int availHeight, final boolean sizeOnly) { final Map<LayoutKey, LayoutValue> cachedLayout = this.cachedLayout; final RenderState rs = this.modelNode.getRenderState(); final int whitespace = rs == null ? RenderState.WS_NORMAL : rs.getWhiteSpace(); final Font font = rs == null ? null : rs.getFont(); final LayoutKey layoutKey = new LayoutKey(availWidth, availHeight, whitespace, font); LayoutValue layoutValue; if (sizeOnly) { layoutValue = cachedLayout.get(layoutKey); } else { if (java.util.Objects.equals(this.lastLayoutKey, layoutKey)) { layoutValue = this.lastLayoutValue; } else { layoutValue = null; } } if (layoutValue == null) { this.applyStyle(availWidth, availHeight); final UIControl widget = this.widget; widget.reset(availWidth, availHeight); final RenderState renderState = this.modelNode.getRenderState(); Insets paddingInsets = this.paddingInsets; if (paddingInsets == null) { paddingInsets = RBlockViewport.ZERO_INSETS; } Insets borderInsets = this.borderInsets; if (borderInsets == null) { borderInsets = RBlockViewport.ZERO_INSETS; } Insets marginInsets = this.marginInsets; if (marginInsets == null) { marginInsets = RBlockViewport.ZERO_INSETS; } final int actualAvailWidth = availWidth - paddingInsets.left - paddingInsets.right - borderInsets.left - borderInsets.right - marginInsets.left - marginInsets.right; final int actualAvailHeight = availHeight - paddingInsets.top - paddingInsets.bottom - borderInsets.top - borderInsets.bottom - marginInsets.top - marginInsets.bottom; final Integer dw = this.getDeclaredWidth(renderState, actualAvailWidth); final Integer dh = this.getDeclaredHeight(renderState, actualAvailHeight); final int declaredWidth = dw == null ? -1 : dw.intValue(); final int declaredHeight = dh == null ? -1 : dh.intValue(); this.declaredWidth = declaredWidth; this.declaredHeight = declaredHeight; this.widthConstrained = declaredWidth != -1; this.heightConstrained = declaredHeight != -1; final Insets insets = this.getInsets(false, false); int finalWidth = declaredWidth == -1 ? -1 : declaredWidth + insets.left + insets.right; int finalHeight = declaredHeight == -1 ? -1 : declaredHeight + insets.top + insets.bottom; if ((finalWidth == -1) || (finalHeight == -1)) { final Dimension size = widget.getPreferredSize(); if (finalWidth == -1) { finalWidth = size.width + insets.left + insets.right; } if (finalHeight == -1) { finalHeight = size.height + insets.top + insets.bottom; } } { final Integer maxWidth = getDeclaredMaxWidth(renderState, actualAvailWidth); if (maxWidth != null) { if (finalWidth > maxWidth) { finalWidth = maxWidth; widthConstrained = true; } } } { final Integer minWidth = getDeclaredMinWidth(renderState, actualAvailWidth); if (minWidth != null) { if (finalWidth < minWidth) { finalWidth = minWidth; widthConstrained = true; } } } { final Integer maxHeight = getDeclaredMaxHeight(renderState, actualAvailHeight); if (maxHeight != null) { if (finalHeight > maxHeight) { finalHeight = maxHeight; heightConstrained = true; } } } { final Integer minHeight = getDeclaredMinHeight(renderState, actualAvailHeight); if (minHeight != null) { if (finalHeight < minHeight) { finalHeight = minHeight; heightConstrained = true; } } } layoutValue = new LayoutValue(finalWidth, finalHeight); if (sizeOnly) { if (cachedLayout.size() > MAX_CACHE_SIZE) { // Unlikely, but we should ensure it's bounded. cachedLayout.clear(); } cachedLayout.put(layoutKey, layoutValue); this.lastLayoutKey = null; this.lastLayoutValue = null; } else { this.lastLayoutKey = layoutKey; this.lastLayoutValue = layoutValue; } } this.width = layoutValue.width; this.height = layoutValue.height; } /** * May be called by controls when they wish to modifiy their preferred size * (e.g. an image after it's loaded). This method must be called in the GUI * thread. */ public final void preferredSizeInvalidated() { final int dw = RUIControl.this.declaredWidth; final int dh = RUIControl.this.declaredHeight; if ((dw == -1) || (dh == -1)) { this.frameContext.delayedRelayout((NodeImpl) this.modelNode); } else { RUIControl.this.repaint(); } } public Iterator<@NonNull Renderable> getRenderables(final boolean topFirst) { // No children for GUI controls return null; } public Color getPaintedBackgroundColor() { return this.container.getPaintedBackgroundColor(); } public Color getForegroundColor() { final RenderState rs = this.modelNode.getRenderState(); return rs == null ? null : rs.getColor(); } private static class LayoutKey { public final int availWidth; public final int availHeight; public final int whitespace; public final Font font; public LayoutKey(final int availWidth, final int availHeight, final int whitespace, final Font font) { this.availWidth = availWidth; this.availHeight = availHeight; this.whitespace = whitespace; this.font = font; } @Override public boolean equals(final Object obj) { if (obj == this) { return true; } if (!(obj instanceof LayoutKey)) { return false; } final LayoutKey other = (LayoutKey) obj; return (other.availWidth == this.availWidth) && (other.availHeight == this.availHeight) && (other.whitespace == this.whitespace) && java.util.Objects.equals(other.font, this.font); } @Override public int hashCode() { final Font font = this.font; return ((this.availWidth * 1000) + this.availHeight) ^ (font == null ? 0 : font.hashCode()); } } private static class LayoutValue { public final int width; public final int height; public LayoutValue(final int width, final int height) { this.width = width; this.height = height; } } private boolean widthConstrained = false; private boolean heightConstrained = false; protected boolean isWidthConstrained() { return widthConstrained; } protected boolean isHeightConstrained() { return heightConstrained; } @Override public void setInnerWidth(Integer newWidth) { super.setInnerWidth(newWidth); widthConstrained = true; } @Override public void setInnerHeight(Integer newHeight) { super.setInnerHeight(newHeight); heightConstrained = true; } }