summaryrefslogtreecommitdiffstats
BranchCommit messageAuthorAge
masterReplace legacy icons on search results pages with SVG icons (#42023).Go MAEDA6 hours
6.0-stableMerged r23411 from trunk to 6.0-stable (#42005).Go MAEDA2 days
5.1-stableMerged r23408 from trunk to 5.1-stable (#42013).Go MAEDA3 days
5.0-stableUpdates for 5.0.10 release.Marius Balteanu7 weeks
4.2-stableUpdates for 4.2.11 releaseGo MAEDA15 months
4.0-stableMerged r21942 from trunk to 4.2-stable (#37875).Go MAEDA2 years
4.1-stableUpdates for 4.1.7 release.Marius Balteanu3 years
3.4-stableUpdates for 3.4.13 release.Jean-Philippe Lang5 years
3.3-stableMerged r19333 from trunk to 3.3-stable (#25742)Toshi MARUYAMA5 years
3.2-stableUpdates for 3.2.9 release.Jean-Philippe Lang7 years
[...]
 
TagDownloadAuthorAge
5.1.5redmine-5.1.5.tar.gz  redmine-5.1.5.zip  Marius Balteanu11 days
6.0.2redmine-6.0.2.tar.gz  redmine-6.0.2.zip  Marius Balteanu11 days
6.0.1redmine-6.0.1.tar.gz  redmine-6.0.1.zip  Marius Balteanu6 weeks
6.0.0redmine-6.0.0.tar.gz  redmine-6.0.0.zip  Marius Balteanu6 weeks
5.1.4redmine-5.1.4.tar.gz  redmine-5.1.4.zip  Marius Balteanu7 weeks
5.0.10redmine-5.0.10.tar.gz  redmine-5.0.10.zip  Marius Balteanu7 weeks
5.1.3redmine-5.1.3.tar.gz  redmine-5.1.3.zip  Go MAEDA6 months
5.0.9redmine-5.0.9.tar.gz  redmine-5.0.9.zip  Go MAEDA6 months
5.0.8redmine-5.0.8.tar.gz  redmine-5.0.8.zip  Marius Balteanu10 months
5.1.2redmine-5.1.2.tar.gz  redmine-5.1.2.zip  Marius Balteanu10 months
[...]
 
AgeCommit messageAuthorFilesLines
2024-11-03Tagged version 5.0.105.0.10Marius Balteanu0-0/+0
2024-11-03Updates for 5.0.10 release.5.0-stableMarius Balteanu2-1/+56
2024-11-03Reverts bad change (#37072).Marius Balteanu1-1/+1
2024-11-03Merged r23193 from 5.1-stable to 5.0-stable (#37072).Marius Balteanu5-12/+12
2024-11-03Merged r23184 from 5.1-stable to 5.0-stable (#41624).Marius Balteanu1-0/+1
2024-11-03Merged r23178 from trunk to 5.0-stable (#41465).Go MAEDA5-4/+16
2024-10-28Merged r22913-r22917 from trunk to 5.0-stable (#40946).Marius Balteanu9-11/+155
2024-10-24Update Rails to 6.1.7.10 in 5.0-stable branch (#41489).Go MAEDA1-1/+1
2024-10-16Upgrade Rails to 6.1.7.9 in 5.0-stable branch (#41489).Go MAEDA1-1/+1
2024-09-25Merged r23079 from trunk to 5.0-stable (#41313).Go MAEDA1-1/+1
[...]
8' href='#n178'>178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
/*
 * Copyright 2000-2021 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.vaadin.ui;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jsoup.nodes.Element;

import com.vaadin.server.JsonPaintTarget;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.shared.ui.customlayout.CustomLayoutState;
import com.vaadin.ui.declarative.DesignContext;

/**
 * <p>
 * A container component with freely designed layout and style. The layout
 * consists of items with textually represented locations. Each item contains
 * one sub-component, which can be any Vaadin component, such as a layout. The
 * adapter and theme are responsible for rendering the layout with a given style
 * by placing the items in the defined locations.
 * </p>
 *
 * <p>
 * The placement of the locations is not fixed - different themes can define the
 * locations in a way that is suitable for them. One typical example would be to
 * create visual design for a web site as a custom layout: the visual design
 * would define locations for "menu", "body", and "title", for example. The
 * layout would then be implemented as an HTML template for each theme.
 * </p>
 *
 * <p>
 * A location is identified with the attribute "data-location" or "location"
 * which has the location name as its value.
 * </p>
 *
 * <p>
 * The default theme handles the styles that are not defined by drawing the
 * subcomponents just as in OrderedLayout.
 * </p>
 *
 * @author Vaadin Ltd.
 * @since 3.0
 */
@SuppressWarnings("serial")
public class CustomLayout extends AbstractLayout implements LegacyComponent {

    private static final int BUFFER_SIZE = 10000;

    /**
     * Custom layout slots containing the components.
     */
    private final Map<String, Component> slots = new HashMap<>();

    /**
     * Default constructor only used by subclasses. Subclasses are responsible
     * for setting the appropriate fields. Either
     * {@link #setTemplateName(String)}, that makes layout fetch the template
     * from theme, or {@link #setTemplateContents(String)}.
     *
     * @since 7.5.0
     */
    public CustomLayout() {
        setWidth(100, Unit.PERCENTAGE);
    }

    /**
     * Constructs a custom layout with the template given in the stream.
     *
     * @param templateStream
     *            Stream containing template data. Must be using UTF-8 encoding.
     *            To use a String as a template use for instance new
     *            ByteArrayInputStream("&lt;template&gt;".getBytes()).
     * @throws IOException
     */
    public CustomLayout(InputStream templateStream) throws IOException {
        this();
        initTemplateContentsFromInputStream(templateStream);
    }

    /**
     * Constructor for custom layout with given template name. Template file is
     * fetched from "&lt;theme&gt;/layouts/&lt;templateName&gt;".
     */
    public CustomLayout(String template) {
        this();
        setTemplateName(template);
    }

    protected void initTemplateContentsFromInputStream(
            InputStream templateStream) throws IOException {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(templateStream, UTF_8));
        StringBuilder builder = new StringBuilder(BUFFER_SIZE);
        try {
            char[] cbuf = new char[BUFFER_SIZE];
            int nRead;
            while ((nRead = reader.read(cbuf, 0, BUFFER_SIZE)) > 0) {
                builder.append(cbuf, 0, nRead);
            }
        } finally {
            reader.close();
        }

        setTemplateContents(builder.toString());
    }

    @Override
    protected CustomLayoutState getState() {
        return (CustomLayoutState) super.getState();
    }

    @Override
    protected CustomLayoutState getState(boolean markAsDirty) {
        return (CustomLayoutState) super.getState(markAsDirty);
    }

    /**
     * Adds the component into this container to given location. If the location
     * is already populated, the old component is removed.
     *
     * @param c
     *            the component to be added.
     * @param location
     *            the location of the component.
     */
    public void addComponent(Component c, String location) {
        final Component old = slots.get(location);
        if (old != null) {
            removeComponent(old);
        }
        slots.put(location, c);
        getState().childLocations.put(c, location);

        super.addComponent(c);
    }

    /**
     * Adds the component into this container. The component is added without
     * specifying the location (empty string is then used as location). Only one
     * component can be added to the default "" location and adding more
     * components into that location overwrites the old components.
     *
     * @param c
     *            the component to be added.
     */
    @Override
    public void addComponent(Component c) {
        this.addComponent(c, "");
    }

    /**
     * Removes the component from this container.
     *
     * @param c
     *            the component to be removed.
     */
    @Override
    public void removeComponent(Component c) {
        if (c == null) {
            return;
        }
        slots.values().remove(c);
        getState().childLocations.remove(c);
        super.removeComponent(c);
    }

    /**
     * Removes the component from this container from given location.
     *
     * @param location
     *            the Location identifier of the component.
     */
    public void removeComponent(String location) {
        this.removeComponent(slots.get(location));
    }

    /**
     * Gets the component container iterator for going trough all the components
     * in the container.
     *
     * @return the Iterator of the components inside the container.
     */
    @Override
    public Iterator<Component> iterator() {
        return Collections.unmodifiableCollection(slots.values()).iterator();
    }

    /**
     * Gets the number of contained components. Consistent with the iterator
     * returned by {@link #getComponentIterator()}.
     *
     * @return the number of contained components
     */
    @Override
    public int getComponentCount() {
        return slots.values().size();
    }

    /**
     * Gets the child-component by its location.
     *
     * @param location
     *            the name of the location where the requested component
     *            resides.
     * @return the Component in the given location or null if not found.
     */
    public Component getComponent(String location) {
        return slots.get(location);
    }

    /* Documented in superclass */
    @Override
    public void replaceComponent(Component oldComponent,
            Component newComponent) {

        // Gets the locations
        String oldLocation = null;
        String newLocation = null;
        for (final String location : slots.keySet()) {
            final Component component = slots.get(location);
            if (component == oldComponent) {
                oldLocation = location;
            }
            if (component == newComponent) {
                newLocation = location;
            }
        }

        if (oldLocation == null) {
            addComponent(newComponent);
        } else if (newLocation == null) {
            removeComponent(oldLocation);
            addComponent(newComponent, oldLocation);
        } else {
            slots.put(newLocation, oldComponent);
            slots.put(oldLocation, newComponent);
            getState().childLocations.put(newComponent, oldLocation);
            getState().childLocations.put(oldComponent, newLocation);
        }
    }

    /** Get the name of the template. */
    public String getTemplateName() {
        return getState(false).templateName;
    }

    /** Get the contents of the template. */
    public String getTemplateContents() {
        return getState(false).templateContents;
    }

    /**
     * Set the name of the template used to draw custom layout.
     *
     * With GWT-adapter, the template with name 'templatename' is loaded from
     * VAADIN/themes/themename/layouts/templatename.html. If the theme has not
     * been set (with Application.setTheme()), themename is 'default'.
     *
     * @param templateName
     */
    public void setTemplateName(String templateName) {
        getState().templateName = templateName;
        getState().templateContents = null;
    }

    /**
     * Set the contents of the template used to draw the custom layout.
     *
     * Note: setTemplateContents can be applied only before CustomLayout
     * instance has been attached.
     *
     * @param templateContents
     */
    public void setTemplateContents(String templateContents) {
        getState().templateContents = templateContents;
        getState().templateName = null;
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables) {
        // Nothing to see here
    }

    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        // Workaround to make the CommunicationManager read the template file
        // and send it to the client
        String templateName = getState(false).templateName;
        if (templateName != null && !templateName.isEmpty()) {
            Set<Object> usedResources = ((JsonPaintTarget) target)
                    .getUsedResources();
            String resourceName = "layouts/" + templateName + ".html";
            usedResources.add(resourceName);
        }
    }

    @Override
    public void readDesign(Element design, DesignContext designContext) {
        super.readDesign(design, designContext);

        for (Element child : design.children()) {

            Component childComponent = designContext.readDesign(child);

            if (child.hasAttr(":location")) {
                addComponent(childComponent, child.attr(":location"));
            } else {
                addComponent(childComponent);
            }
        }
    }

    @Override
    public void writeDesign(Element design, DesignContext designContext) {
        super.writeDesign(design, designContext);

        for (Entry<String, Component> slot : slots.entrySet()) {
            Element child = designContext.createElement(slot.getValue());
            if (slots.size() > 1 || !"".equals(slot.getKey())) {
                child.attr(":location", slot.getKey());
            }
            design.appendChild(child);
        }
    }
}