/* 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 Nov 19, 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.awt.Rectangle; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import org.eclipse.jdt.annotation.NonNull; import org.lobobrowser.html.HtmlRendererContext; import org.lobobrowser.html.domimpl.HTMLElementImpl; import org.lobobrowser.html.domimpl.ModelNode; import org.lobobrowser.html.renderer.TableMatrix.RTableRowGroup; import org.lobobrowser.html.style.RenderState; import org.lobobrowser.html.style.RenderThreadState; import org.lobobrowser.ua.UserAgentContext; import org.lobobrowser.util.CollectionUtilities; class RTable extends BaseBlockyRenderable { private static final int MAX_CACHE_SIZE = 10; private final Map<LayoutKey, LayoutValue> cachedLayout = new HashMap<>(5); private final TableMatrix tableMatrix; private SortedSet<@NonNull PositionedRenderable> positionedRenderables; private int otherOrdinal; private LayoutKey lastLayoutKey = null; private LayoutValue lastLayoutValue = null; public RTable(final HTMLElementImpl modelNode, final UserAgentContext pcontext, final HtmlRendererContext rcontext, final FrameContext frameContext, final RenderableContainer container) { super(container, modelNode, pcontext); this.tableMatrix = new TableMatrix(modelNode, pcontext, rcontext, frameContext, this, this); } @Override public 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; } this.prePaint(g); final Dimension size = this.getSize(); // TODO: No scrollbars final TableMatrix tm = this.tableMatrix; tm.paint(g, size); final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final Iterator<PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); pr.paint(g); /* final BoundableRenderable r = pr.renderable; r.paintTranslated(g); */ } } } @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(); // Having whiteSpace == NOWRAP and having a NOWRAP override // are not exactly the same thing. final boolean overrideNoWrap = RenderThreadState.getState().overrideNoWrap; final LayoutKey layoutKey = new LayoutKey(availWidth, availHeight, whitespace, font, overrideNoWrap); LayoutValue layoutValue; if (sizeOnly) { layoutValue = cachedLayout.get(layoutKey); } else { if (java.util.Objects.equals(layoutKey, this.lastLayoutKey)) { layoutValue = this.lastLayoutValue; } else { layoutValue = null; } } if (layoutValue == null) { final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { prs.clear(); } this.otherOrdinal = 0; this.clearGUIComponents(); this.clearDelayedPairs(); this.applyStyle(availWidth, availHeight); final TableMatrix tm = this.tableMatrix; final Insets insets = this.getInsets(false, false); tm.reset(insets, availWidth, availHeight); // TODO: No scrollbars tm.build(availWidth, availHeight, sizeOnly); tm.doLayout(insets); // Import applicable delayed pairs. // Only needs to be done if layout was forced. Otherwise, they should've been imported already. final Collection<DelayedPair> pairs = this.delayedPairs; if (pairs != null) { final Iterator<DelayedPair> i = pairs.iterator(); while (i.hasNext()) { final DelayedPair pair = i.next(); if (pair.containingBlock == this) { this.importDelayedPair(pair); } } } layoutValue = new LayoutValue(tm.getTableWidth(), tm.getTableHeight()); 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; this.sendGUIComponentsToParent(); this.sendDelayedPairsToParent(); } @Override public void invalidateLayoutLocal() { super.invalidateLayoutLocal(); this.cachedLayout.clear(); this.lastLayoutKey = null; this.lastLayoutValue = null; } /* * (non-Javadoc) * * @see org.xamjwg.html.renderer.BoundableRenderable#getRenderablePoint(int, * int) */ public RenderableSpot getLowestRenderableSpot(final int x, final int y) { final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final Iterator<PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); final BoundableRenderable r = pr.renderable; final int childX = x - r.getVisualX(); final int childY = y - r.getVisualY(); final RenderableSpot rs = r.getLowestRenderableSpot(childX, childY); if (rs != null) { return rs; } } } final RenderableSpot rs = this.tableMatrix.getLowestRenderableSpot(x, y); if (rs != null) { return rs; } return new RenderableSpot(this, x, y); } /* * (non-Javadoc) * * @see * org.xamjwg.html.renderer.BoundableRenderable#onMouseClick(java.awt.event * .MouseEvent, int, int) */ public boolean onMouseClick(final MouseEvent event, final int x, final int y) { final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final Iterator<PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); final BoundableRenderable r = pr.renderable; final Rectangle bounds = r.getVisualBounds(); if (bounds.contains(x, y)) { final int childX = x - r.getVisualX(); final int childY = y - r.getVisualY(); if (!r.onMouseClick(event, childX, childY)) { return false; } } } } return this.tableMatrix.onMouseClick(event, x, y); } public boolean onDoubleClick(final MouseEvent event, final int x, final int y) { final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final Iterator<PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); final BoundableRenderable r = pr.renderable; final Rectangle bounds = r.getVisualBounds(); if (bounds.contains(x, y)) { final int childX = x - r.getVisualX(); final int childY = y - r.getVisualY(); if (!r.onDoubleClick(event, childX, childY)) { return false; } } } } return this.tableMatrix.onDoubleClick(event, x, y); } /* * (non-Javadoc) * * @see * org.xamjwg.html.renderer.BoundableRenderable#onMouseDisarmed(java.awt.event * .MouseEvent) */ public boolean onMouseDisarmed(final MouseEvent event) { return this.tableMatrix.onMouseDisarmed(event); } /* * (non-Javadoc) * * @see * org.xamjwg.html.renderer.BoundableRenderable#onMousePressed(java.awt.event * .MouseEvent, int, int) */ public boolean onMousePressed(final MouseEvent event, final int x, final int y) { final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final Iterator<PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); final BoundableRenderable r = pr.renderable; final Rectangle bounds = r.getVisualBounds(); if (bounds.contains(x, y)) { final int childX = x - r.getVisualX(); final int childY = y - r.getVisualY(); if (!r.onMousePressed(event, childX, childY)) { return false; } } } } return this.tableMatrix.onMousePressed(event, x, y); } /* * (non-Javadoc) * * @see * org.xamjwg.html.renderer.BoundableRenderable#onMouseReleased(java.awt.event * .MouseEvent, int, int) */ public boolean onMouseReleased(final MouseEvent event, final int x, final int y) { final Collection<PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final Iterator<PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); final BoundableRenderable r = pr.renderable; final Rectangle bounds = r.getVisualBounds(); if (bounds.contains(x, y)) { final int childX = x - r.getVisualX(); final int childY = y - r.getVisualY(); if (!r.onMouseReleased(event, childX, childY)) { return false; } } } } return this.tableMatrix.onMouseReleased(event, x, y); } /* * (non-Javadoc) * * @see org.xamjwg.html.renderer.RCollection#getRenderables() */ public Iterator<@NonNull ? extends Renderable> getRenderables(final boolean topFirst) { final Collection<@NonNull PositionedRenderable> prs = this.positionedRenderables; if (prs != null) { final List<@NonNull Renderable> c = new java.util.LinkedList<>(); final Iterator<@NonNull PositionedRenderable> i = prs.iterator(); while (i.hasNext()) { final PositionedRenderable pr = i.next(); final BoundableRenderable r = pr.renderable; c.add(r); } final Iterator<@NonNull RAbstractCell> i2 = this.tableMatrix.getCells(); while (i2.hasNext()) { c.add(i2.next()); } final Iterator<@NonNull RTableRowGroup> i3 = this.tableMatrix.getRowGroups(); while (i3.hasNext()) { c.add(i3.next()); } if (topFirst) { Collections.reverse(c); } return c.iterator(); } else { final Iterator<@NonNull Renderable>[] rs = new Iterator[] {this.tableMatrix.getCells(), this.tableMatrix.getRowGroups()}; return CollectionUtilities.iteratorUnion(rs); } } public void repaint(final ModelNode modelNode) { // NOP } /* * (non-Javadoc) * * @see org.xamjwg.html.renderer.RenderableContainer#getBackground() */ public Color getPaintedBackgroundColor() { return this.container.getPaintedBackgroundColor(); } private final void addPositionedRenderable(final @NonNull BoundableRenderable renderable, final boolean verticalAlignable, final boolean isFloat, final boolean isFixed) { // Expected to be called only in GUI thread. SortedSet<@NonNull PositionedRenderable> others = this.positionedRenderables; if (others == null) { others = new TreeSet<>(new ZIndexComparator()); this.positionedRenderables = others; } others.add(new PositionedRenderable(renderable, verticalAlignable, this.otherOrdinal++, isFloat, isFixed, false)); renderable.setParent(this); if (renderable instanceof RUIControl) { this.container.addComponent(((RUIControl) renderable).widget.getComponent()); } } private void importDelayedPair(final DelayedPair pair) { BoundableRenderable r = pair.positionPairChild(); // final BoundableRenderable r = pair.child; this.addPositionedRenderable(r, false, false, pair.isFixed); } @Override public String toString() { return "RTable[this=" + System.identityHashCode(this) + ",node=" + this.modelNode + "]"; } private static class LayoutKey { public final int availWidth; public final int availHeight; public final int whitespace; public final Font font; public final boolean overrideNoWrap; public LayoutKey(final int availWidth, final int availHeight, final int whitespace, final Font font, final boolean overrideNoWrap) { super(); this.availWidth = availWidth; this.availHeight = availHeight; this.whitespace = whitespace; this.font = font; this.overrideNoWrap = overrideNoWrap; } @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) && (other.overrideNoWrap == this.overrideNoWrap) && 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()) ^ this.whitespace; } } 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; } } @Override public void layout(int availWidth, int availHeight, boolean b, boolean c, FloatingBoundsSource source, boolean sizeOnly) { this.doLayout(availWidth, availHeight, sizeOnly); } }