Skip to content

Instantly share code, notes, and snippets.

@sipadan2003
Last active July 16, 2016 17:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sipadan2003/f07408d4e3e63d00d284d8b7a1d78c5b to your computer and use it in GitHub Desktop.
Save sipadan2003/f07408d4e3e63d00d284d8b7a1d78c5b to your computer and use it in GitHub Desktop.
Enhanced GridBagLayout Components
package tv.minato.common.gui;
/**
*{@link tv.minato.gui.layout.TMGridBagLayout TMGridBagLayout}でレイアウトを指定するためのクラスです。
*@since 1.8
*@Version 1.00 2016/07/16 for Java 1.8
*@author 1.00 Takayuki MINATO
*/
public class TMGridBagConstraints extends Object implements Cloneable {
/**
*コンポーネントのx位置です。
*/
public int x;
/**
*コンポーネントのy位置です。
*/
public int y;
/**
*コンポーネントのグリッド幅です。
*/
public int width;
/**
*コンポーネントのグリッド高です。
*/
public int height;
/**
*本クラスを構築します。<br/>
*コンストラクタTMGridConstraint(0, 0, 1, 1)で構築するのと同じです。
*@see #TMGridBagConstraints(int, int, int, int)
*/
public TMGridBagConstraints(){
this(0, 0, 1, 1);
}
/**
*本クラスを構築します。
*@param x コンポーネントのx位置
*@param y コンポーネントのy位置
*@param w コンポーネントのグリッド幅
*@param h コンポーネントのグリッド高
*@throws IllegalArgumentException 引数に不適切な値が指定された場合
*/
public TMGridBagConstraints(int x, int y, int w, int h) throws IllegalArgumentException {
if(x<0 || y<0 || w<=0 || h<=0){
throw new IllegalArgumentException("The argument value is invalid: x="+x+", y="+y+", w="+w+", h"+h);
}
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
/**
*本クラスのコピーを作成します。
*@return 本クラスのコピー
*/
@Override public Object clone () {
try {
return super.clone();
}catch(final CloneNotSupportedException e){
throw new RuntimeException("Failed to create clone");
}
}
}
package tv.minato.common.gui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
/**
*{@link java.awt.GridLayout}を改良したレイアウトマネージャです。
*@since 1.8
*@Version 1.00 2016/07/16 for Java 1.8
*@Version 1.01 2016/07/17 for Java 1.8 Change two of List to one of Map.
*@author 0.50-1.01 Takayuki MINATO
*/
public final class TMGridBagLayout extends Object implements LayoutManager2, Serializable {
private static final long serialVersionUID = 6450251806839731638L;
private final Map<Component, TMGridBagConstraints> constraintsMap = new HashMap<>();
private boolean calculated = false;
private int[] gridWidths;
private int[] gridHeights;
private int resizableGridX = -1;
private int resizableGridY = -1;
private int hGap = 0;
private int vGap = 0;
/**
*本クラスを構築します。
*@param width x軸方向のグリッド数(0以上の数値)
*@param height y軸方向のグリッド数(0以上の数値)
*@throws IllegalArgumentException 引数の値に誤りがあるとき
*/
public TMGridBagLayout(int width, int height){
this(width, height, -1, -1);
}
/**
*本クラスを構築します。
*@param width x軸方向のグリッド数(0以上の数値)
*@param height y軸方向のグリッド数(0以上の数値)
*@param resizableX 親コンテナの幅に応じて幅を伸縮するx軸方向のグリッド位置(0未満の場合伸縮しない)
*@param resizableY 親コンテナの高さに応じて高さを伸縮するy軸方向のグリッド位置(0未満の場合伸縮しない)
*@throws IllegalArgumentException 引数の値に誤りがあるとき
*/
public TMGridBagLayout(int width, int height, int resizableX, int resizableY) throws IllegalArgumentException {
if(width <= 0 || height <= 0){
throw new IllegalArgumentException("The argument value is invalid: width="+width+", height="+height);
}
this.gridWidths = new int[width];
this.gridHeights = new int[height];
this.setResizableX(resizableX);
this.setResizableY(resizableY);
}
/**
*コンテナのサイズに応じて、サイズ変更を行うx軸方向のグリッドを取得します。
*@return x軸方向のグリッド(デフォルトは、どのコンポーネントもサイズ変更しないことを示す-1)
*/
public int getResizableX(){
return this.resizableGridX;
}
/**
*コンテナのサイズに応じて、サイズ変更を行うx軸方向のグリッドを指定します。
*@param resizableX x軸方向のグリッド(0未満の場合、どのコンポーネントもサイズ変更を行わない)
*@throws IllegalArgumentException 引数の値に誤りがあるとき
*/
public void setResizableX(int resizableX) throws IllegalArgumentException {
if(resizableX >= this.gridWidths.length){
throw new IllegalArgumentException("The argument value is invalid: resizableX="+resizableX);
}
this.resizableGridX = resizableX;
}
/**
*コンテナのサイズに応じて、サイズ変更を行うy軸方向のグリッドを取得します。
*@return y軸方向のグリッド(デフォルトは、どのコンポーネントもサイズ変更しないことを示す-1)
*/
public int getResizableY(){
return this.resizableGridY;
}
/**
*コンテナのサイズに応じて、サイズ変更を行うy軸方向のグリッドを指定します。
*@param resizableY y軸方向のグリッド(0未満の場合、どのコンポーネントもサイズ変更を行わない)
*@throws IllegalArgumentException 引数の値に誤りがあるとき
*/
public void setResizableY(int resizableY) throws IllegalArgumentException {
if(resizableY >= this.gridHeights.length){
throw new IllegalArgumentException("The argument value is invalid: resizableY="+resizableY);
}
this.resizableGridY = resizableY;
}
/**
*コンポーネント間のx軸方向のスペースを取得します。
*@return コンポーネント間のスペース
*/
public int getHgap(){
return this.hGap;
}
/**
*コンポーネント間のx軸方向のスペースを設定します。
*@param gap コンポーネント間のスペース
*/
public void setHgap(int gap){
this.hGap = gap;
}
/**
*コンポーネント間のx軸方向のスペースを取得します。
*@return コンポーネント間のスペース
*/
public int getVgap(){
return this.vGap;
}
/**
*コンポーネント間のy軸方向のスペースを設定します。
*@param gap コンポーネント間のスペース
*/
public void setVgap(int gap){
this.vGap = gap;
}
/**
*コンポーネントを追加するときに呼ばれます。
*@param name コンポーネントに関連づけられた文字列
*@param comp 追加されるコンポーネント
*/
@Override
public void addLayoutComponent(String name, Component comp){
//NOP
}
/**
*コンポーネントを追加するときに呼ばれます。
*@param comp 追加されるコンポーネント
*@param constraints コンポーネントに関連づけられたコンストレイント({@link TMGridBagConstraints}を利用)
*@throws IllegalArgumentException 引数constraintsの値に誤りがあるとき
*/
@Override
public void addLayoutComponent(Component comp, Object constraints){
TMGridBagConstraints gc = (TMGridBagConstraints)constraints;
if(gc.x+gc.width>this.gridWidths.length || gc.y+gc.height>this.gridHeights.length){
throw new IllegalArgumentException("The Value of constraints is invalid: constraints="+gc);
}
gc = (TMGridBagConstraints)gc.clone();
constraintsMap.put(comp, gc);
}
/**
*コンポーネントを削除するときに呼ばれます。
*@param comp 削除されるコンポーネント
*/
@Override
public void removeLayoutComponent(Component comp){
constraintsMap.remove(comp);
}
/**
*x軸方向の配置方法を取得します。
*@param target コンテナ
*@return x軸の配置方法(0~1の間の数値)
*/
@Override
public float getLayoutAlignmentX(Container target){
return 0L;
}
/**
*y軸方向の配置方法を取得します。
*@param target コンテナ
*@return y軸の配置方法(0~1の間の数値)
*/
@Override
public float getLayoutAlignmentY(Container target){
return 0L;
}
/**
*コンテナの最小サイズを取得します。
*@param target コンテナ
*@return コンテナの最小サイズ
*/
@Override
public Dimension minimumLayoutSize(Container target){
return this.preferredLayoutSize(target);
}
/**
*コンテナの最大サイズを計算します。
*@param target コンテナ
*@return コンテナの最大サイズ
*/
@Override
public Dimension maximumLayoutSize(Container target){
return this.preferredLayoutSize(target);
}
/**
*コンテナの推奨サイズを取得します。
*@param target コンテナ
*@return コンテナの推奨サイズ
*/
@Override
public Dimension preferredLayoutSize(Container target){
//コンポーネントのサイズを求める
calculateSize();
//幅と高さ等からサイズを算出
final int[] widths = this.gridWidths;
final int[] heights = this.gridHeights;
final int w = totalInteger(widths, 0, widths.length);
final int h = totalInteger(heights, 0, heights.length);
final int hg= this.hGap * (widths.length-1);
final int vg= this.vGap * (heights.length-1);
final Insets in = target.getInsets();
return new Dimension(in.left+in.right+w+hg, in.top+in.bottom+h+vg);
}
/**
*コンポーネントをレイアウトするときに呼ばれます。
*@param target コンテナ
*/
@Override
public void layoutContainer(Container target){
final Map<Component, TMGridBagConstraints> map = this.constraintsMap;
//コンポーネントがなければ、レイアウトしない
if(map.isEmpty()){
return;
}
//コンポーネントのサイズを求める
calculateSize();
//x,y,width,heightを算出
final int[] widths = this.gridWidths;
final int[] heights = this.gridHeights;
final int hg = this.hGap;
final int vg = this.vGap;
final Insets in = target.getInsets();
final int insetsWidth = in.left + in.right;
final int insetsHeight = in.top + in.bottom;
final Dimension size = target.getSize();
final int hs= hg * (widths.length-1);
final int vs= vg * (heights.length-1);
final int totalWidth = totalInteger(widths, 0, widths.length)+insetsWidth+hs;
final int totalHeight = totalInteger(heights, 0, heights.length)+insetsHeight+vs;
final int dw = (size.width - totalWidth);
final int dh = (size.height - totalHeight);
//コンテナの幅がpreferredSizeより大きいか小さい場合、特定の行列のサイズを調整する
final int resizableX = this.resizableGridX;
final int resizableY = this.resizableGridY;
if(resizableX >= 0){
widths[resizableX] += dw;
if(widths[resizableX] < 0){
widths[resizableX] = 0;
}
}
if(resizableY >= 0){
heights[resizableY] += dh;
if(heights[resizableY] < 0){
heights[resizableY] = 0;
}
}
map.forEach((comp, gc)->{
int x = totalInteger(widths, 0, gc.x) + hg*gc.x;
int y = totalInteger(heights, 0, gc.y) + vg*gc.y;
int w = totalInteger(widths, gc.x, gc.width);
int h = totalInteger(heights, gc.y, gc.height);
w += hg * (gc.width-1);
h += vg * (gc.height-1);
comp.setBounds(in.left+x, in.top+y, w, h);
});
}
/**
*レイアウトを無効にします。
*@param target コンテナ
*/
@Override
public void invalidateLayout(Container target){
this.calculated = false;
}
/**
*サイズを計算する
*/
private void calculateSize(){
if(this.calculated == true){
return;
}
this.calculated = true;
//gc.width、gc.heightの値が1であれば、コンポーネントの最大のpreferredSizeを元に決定
final int[] widths = this.gridWidths;
final int[] heights = this.gridHeights;
final Map<Component, TMGridBagConstraints> resizeComponents = new HashMap<>();
constraintsMap.forEach((comp, gc)->{
if(gc.width == 1){
widths[gc.x] = Math.max(widths[gc.x], comp.getPreferredSize().width);
}
if(gc.height == 1){
heights[gc.y] = Math.max(heights[gc.y], comp.getPreferredSize().height);
}
if(gc.width > 1 || gc.height > 1){ //width、heightが2以上は後で計算する
resizeComponents.put(comp, gc);
}
});
//gc.width、gc.heightの値が2以上のコンポーネントに合わせてサイズを調整
resizeComponents.forEach((comp, gc)->{
if(gc.width > 1){
final int p = comp.getPreferredSize().width;
final int a = totalInteger(widths, gc.x, gc.width);
if(a < p){
widths[gc.x+gc.width-1] += (p-a);
}
}
if(gc.height > 1){
final int p = comp.getPreferredSize().height;
final int a = totalInteger(heights, gc.y, gc.height);
if(a < p){
heights[gc.y+gc.height-1] += (p-a);
}
}
});
}
/**
*int配列の合計を計算
*/
private static int totalInteger(int[] v, int from, int length){
return IntStream.range(from, from+length).map(i->v[i]).sum();
}
}
package tv.minato.sample.gui;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import tv.minato.common.gui.TMGridBagConstraints;
import tv.minato.common.gui.TMGridBagLayout;
/**
*Swing Sample using TMGridBagLayout and TMGridBagConstraints.
*@author Takayuki MINATO
*/
public class TMGridBagLayoutSample extends JPanel {
private static final Insets INSETS = new Insets(5, 5, 5, 5);
public static void main(String[] args){
final JFrame f = new JFrame("TMGridBagLayoutSample");
f.getContentPane().add(new TMGridBagLayoutSample());
f.pack();
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
SwingUtilities.invokeLater(()->f.setVisible(true));
}
TMGridBagLayoutSample(){
final TMGridBagLayout layout = new TMGridBagLayout(3, 3, 1, -1);
layout.setHgap(5);
layout.setVgap(5);
setLayout(layout);
add(new JLabel("Item1: "), new TMGridBagConstraints(0, 0, 1, 1));
add(new JTextField() , new TMGridBagConstraints(1, 0, 2, 1));
add(new JLabel("Item2: "), new TMGridBagConstraints(0, 1, 1, 1));
add(new JTextField() , new TMGridBagConstraints(1, 1, 2, 1));
add(new JButton("Button"), new TMGridBagConstraints(2, 2, 1, 1));
}
@Override public Insets getInsets(){
return INSETS;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment