On 09/04/2013 01:29 PM, Makarius wrote:
> Yes, the layout of this (and some other dockables) is still not quite right:
> it is just basic FlowLayout.
>
> I was waiting for the hidden student project supervised by Fabian Immler,
> to see if alternatively layout ideas show up.
>
> In the meantime it is also possible for Swing layout experts to say
> what is best here.  (I do not even master GridBagLayout so far.)
After creating a similar layout in the Eclipse WindowBuilder
in a matter of minutes (a great tool, by the way!),
I was intrigued to see the odd behaviour already for very
small examples.

The reason for the odd behaviour is, indeed, somewhat intricate:

FlowLayouts invariably compute their preferred size
in one line. However, when later they are asked to
layout the children of their container, they break
them into lines and thus use up more vertical space
than advertised originally. As a result, components
in the later lines may be partly obscured when
re-sizing windows.

This behavior is, unfortunately, inherent in the
Swing/AWT layout mechanism, which proceeds once bottom-up
to compute the preferred sizes, and then once top-down to
decide on the sizes of the components. There is no way
of computing the preferred size using some form of
"size hint" propagated in the top-down phase
(unlike in the SWT framework).

The attached class RetriggeringFlowLayout fixes
this problem by caching the previous size decision
from the top-down phase and re-triggering the layout
if that decision has changed.

In my example layout, this gives the desired effect.
(It just uses the new class instead of plain FlowLayout.)
If it does not solve the problem in jEdit, I'm willing
to dig some deeper.

Cheers,

Holger
package layout;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.Rectangle;

import javax.swing.JComponent;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;

/**
 * FlowLayouts invariably compute their preferred size
 * in one line. However, when later they are asked to 
 * layout the children of their container, they break
 * them into lines and thus use up more vertical space
 * than originally advertised. As a result, components
 * in the later lines may disappear when re-sizing windows.
 * 
 * This behavior is inherent in the Swing/AWT layout
 * mechanism, which proceeds once bottom-up to compute
 * the preferred sizes, and once top-down to decide on the
 * sizes of the components. There is no way of computing
 * the preferred size using some form of "size hint"
 * propagated in the top-down phase.
 * 
 * This class provides a workaround: when the target container
 * gets assigned a new size, it is invalidated and re-layouted
 * in the next UI event dispatch cycle. The previous size
 * is stored locally and is used as the required size hint. 
 * 
 * @author Holger Gast <g...@informatik.uni-tuebingen.de>
 *
 * This code is covered by the FreeBSD license and accordingly
 * is made available without any warranties whatsoever.
 *
 */
public class RetriggeringFlowLayout extends FlowLayout {
	private Rectangle previousLayout = null;

	public RetriggeringFlowLayout(int align, int hgap, int vgap) {
		super(align, hgap, vgap);
	}

	public RetriggeringFlowLayout(int align) {
		super(align);
	}

	@Override
	public Dimension preferredLayoutSize(Container target) {
		int hgap = getHgap();
		int vgap = getVgap();
		synchronized (target.getTreeLock()) {
			Insets insets = target.getInsets();

			// the overall size, if we are working in multi-line mode
			Dimension global = previousLayout != null ? new Dimension(0, 0)
					: null;
			// compute available width analogously to layoutContainer()
			int availableWidth = previousLayout == null ? 0
					: previousLayout.width - insets.left - insets.right - 2
							* hgap;
			// the current line (keep as 'dim' to maintain
			// correlation with previous code)
			Dimension dim = new Dimension(0, 0);
			int nmembers = target.getComponentCount();
			boolean firstVisibleComponent = true;
			boolean useBaseline = getAlignOnBaseline();
			int maxAscent = 0;
			int maxDescent = 0;

			for (int i = 0; i < nmembers; i++) {
				Component m = target.getComponent(i);
				if (m.isVisible()) {
					Dimension d = m.getPreferredSize();

					// New: start a new line if this component does not
					// fit into the current line
					if (global != null) {
						if (dim.width + d.width > availableWidth) {
							if (useBaseline) {
								dim.height = Math.max(maxAscent + maxDescent,
										dim.height);
							}
							addCurrentLineToGlobalSize(dim, global);
							global.height += vgap;
							// clear the curent line by re-setting the
							// corresponding state
							dim.setSize(0, 0);
							maxAscent = 0;
							maxDescent = 0;
							firstVisibleComponent = true;
						}
					}
					// then continue as before, by inserting the component
					// into the current line
					dim.height = Math.max(dim.height, d.height);
					if (firstVisibleComponent) {
						firstVisibleComponent = false;
					} else {
						dim.width += hgap;
					}
					dim.width += d.width;
					if (useBaseline) {
						int baseline = m.getBaseline(d.width, d.height);
						if (baseline >= 0) {
							maxAscent = Math.max(maxAscent, baseline);
							maxDescent = Math.max(maxDescent, d.height
									- baseline);
						}
					}
				}
			}
			// finish the current line
			if (useBaseline) {
				dim.height = Math.max(maxAscent + maxDescent, dim.height);
			}
			// add the last, current line to the global size,
			if (global != null) {
				addCurrentLineToGlobalSize(dim, global);
				dim = global;
			}
			// proceed as before to add the insets
			dim.width += insets.left + insets.right + hgap * 2;
			dim.height += insets.top + insets.bottom + vgap * 2;
			return dim;
		}
	}

	private void addCurrentLineToGlobalSize(Dimension line, Dimension global) {
		global.width = Math.max(global.width, line.width);
		global.height += line.height;
	}

	@Override
	public void layoutContainer(final Container target) {
		super.layoutContainer(target);
		if (!target.getBounds().equals(previousLayout)) {
			previousLayout = target.getBounds();
			// avoid interaction between current layout and 
			// the next one by queuing the re-layout for the
			// next dispatch cycle.
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					target.invalidate();
					JComponent parent = (JComponent) target;
					RepaintManager.currentManager(parent).addInvalidComponent(
							parent);
				}
			});

		}
	}
}
package layout;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;

import javax.swing.AbstractListModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
 * 
 * @author Holger Gast <g...@informatik.uni-tuebingen.de>
 *
 * This code is covered by the FreeBSD license and accordingly
 * is made available without any warranties whatsoever.
 *
 */
public class LayoutExample {

	private JFrame frame;
	private JTextField txtCriteria;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					LayoutExample window = new LayoutExample();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public LayoutExample() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100, 100, 450, 300);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(new BorderLayout(0, 0));
		
		JPanel parameterPane = new JPanel();
		frame.getContentPane().add(parameterPane, BorderLayout.NORTH);
		FlowLayout layout = new RetriggeringFlowLayout(FlowLayout.CENTER, 5, 5);
		layout.setAlignment(FlowLayout.LEFT);
		layout.setAlignOnBaseline(true);
		parameterPane.setLayout(layout);
		
		JLabel lblSearchCriteria = new JLabel("Search Criteria");
		parameterPane.add(lblSearchCriteria);
		
		txtCriteria = new JTextField();
		txtCriteria.setText("criteria");
		parameterPane.add(txtCriteria);
		txtCriteria.setColumns(10);
		
		JComboBox comboBox = new JComboBox();
		comboBox.setModel(new DefaultComboBoxModel(new String[] {"Aaaaa", "Bbbbb", "Ccccc", "Ddddd"}));
		parameterPane.add(comboBox);
		
		JList resultList = new JList();
		resultList.setModel(new AbstractListModel() {
			String[] values = new String[] {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
			public int getSize() {
				return values.length;
			}
			public Object getElementAt(int index) {
				return values[index];
			}
		});
		frame.getContentPane().add(resultList);
	}

}
_______________________________________________
isabelle-dev mailing list
isabelle-...@in.tum.de
https://mailmanbroy.informatik.tu-muenchen.de/mailman/listinfo/isabelle-dev

Reply via email to