001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.util; 003 004import java.awt.Component; 005import java.awt.Container; 006import java.awt.Dimension; 007import java.awt.FlowLayout; 008import java.awt.Insets; 009import java.util.function.Function; 010 011/** 012 * This is an extension of the flow layout that prefers wrapping the text instead of increasing the component width 013 * when there is not enough space. 014 * <p> 015 * This allows for a better preferred size computation. 016 * It should be used in all places where a flow layout fills the full width of the parent container. 017 * <p> 018 * This does not support baseline alignment. 019 * @author Michael Zangl 020 * @since 10622 021 */ 022public class MultiLineFlowLayout extends FlowLayout { 023 /** 024 * Same as {@link FlowLayout#FlowLayout()} 025 */ 026 public MultiLineFlowLayout() { 027 super(); 028 } 029 030 /** 031 * Same as {@link FlowLayout#FlowLayout(int, int, int)} 032 * @param align Alignment 033 * @param hgap horizontal gap 034 * @param vgap vertical gap 035 */ 036 public MultiLineFlowLayout(int align, int hgap, int vgap) { 037 super(align, hgap, vgap); 038 } 039 040 /** 041 * Same as {@link FlowLayout#FlowLayout(int)} 042 * @param align Alignment 043 */ 044 public MultiLineFlowLayout(int align) { 045 super(align); 046 } 047 048 @Override 049 public Dimension preferredLayoutSize(Container target) { 050 return getLayoutSize(target, Component::getPreferredSize); 051 } 052 053 @Override 054 public Dimension minimumLayoutSize(Container target) { 055 return getLayoutSize(target, Component::getMinimumSize); 056 } 057 058 private Dimension getLayoutSize(Container target, Function<Component, Dimension> baseSize) { 059 synchronized (target.getTreeLock()) { 060 int outerWidth = getWidthOf(target); 061 062 Insets insets = target.getInsets(); 063 int containerWidth = outerWidth - insets.left - insets.right - getHgap() * 2; 064 065 int x = 0; 066 int totalHeight = insets.top + insets.bottom + getVgap() * 2; 067 int rowHeight = 0; 068 for (int i = 0; i < target.getComponentCount(); i++) { 069 Component child = target.getComponent(i); 070 if (!child.isVisible()) { 071 continue; 072 } 073 Dimension size = baseSize.apply(child); 074 if (x != 0) { 075 x += getHgap(); 076 } 077 x += size.width; 078 if (x > containerWidth) { 079 totalHeight += rowHeight + getVgap(); 080 rowHeight = 0; 081 x = 0; 082 } 083 084 rowHeight = Math.max(rowHeight, size.height); 085 } 086 totalHeight += rowHeight; 087 088 return new Dimension(outerWidth, totalHeight); 089 } 090 } 091 092 private static int getWidthOf(Container target) { 093 Container current = target; 094 while (current.getWidth() == 0 && current.getParent() != null) { 095 current = current.getParent(); 096 } 097 int width = current.getWidth(); 098 if (width == 0) { 099 return Integer.MAX_VALUE; 100 } else { 101 return width; 102 } 103 } 104 105 @Override 106 public String toString() { 107 return "MultiLineFlowLayout [align=" + getAlignment() + ']'; 108 } 109}