diff --git a/README.md b/README.md
new file mode 100644
index 0000000..041977a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+AdvancedCameraSettings_r3
+=========================
+
+This program is for webcams to persistent the config
+
+Usage
+-----
+
+1. java -jar AdvancedCameraSettings.jar
+2. Config all the webcams
+3. Save the config, so that config.ini is created
+4. during windows startup, run the loadconfig.bat (you can do a file shortcut at C:\Trakomatic\Cron\@reboot)
+
+
+Credits
+-------
+
+(Codes from https://obsproject.com/forum/threads/using-2-or-more-logitech-c920s-or-c930es-together-successfully.26707/ ,
+edited by Cxrus)
\ No newline at end of file
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..8d770e1
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+ Builds, tests, and runs the project AdvancedCameraSettings.
+
+
+
diff --git a/lib/dsj.dll b/lib/dsj.dll
new file mode 100644
index 0000000..c0beae7
Binary files /dev/null and b/lib/dsj.dll differ
diff --git a/lib/dsj.jar b/lib/dsj.jar
new file mode 100644
index 0000000..e344114
Binary files /dev/null and b/lib/dsj.jar differ
diff --git a/src/AdvancedCameraSettingsMain.java b/src/AdvancedCameraSettingsMain.java
new file mode 100644
index 0000000..25e7ef9
--- /dev/null
+++ b/src/AdvancedCameraSettingsMain.java
@@ -0,0 +1,124 @@
+import com.digitalmodular.utilities.ConfigManager;
+import de.humatic.dsj.CaptureDeviceControls;
+import java.util.ArrayList;
+
+import util.CameraFormatSelectionDialog;
+import util.CameraSelectionDialog;
+import util.DirectShowCamera;
+import de.humatic.dsj.DSFilterInfo;
+import de.humatic.dsj.DSMediaType;
+import java.util.Map;
+
+/**
+ * @author Mark Jeronimus
+ */
+// date 2014/08/05
+public class AdvancedCameraSettingsMain {
+ public static void main(String[] args) throws Exception {
+ if(args.length > 0 && args[0].equals("--load") ) {
+ loadCamerasSettings();
+ } else {
+ DirectShowCamera[] cameras = queryCameras();
+
+ if (cameras.length == 0)
+ System.exit(0);
+
+ new MultiCameraTestMain(cameras);
+ }
+ }
+
+ private static DirectShowCamera[] queryCameras() {
+ DSFilterInfo[] cameraInfos = DirectShowCamera.getCameras();
+ ArrayList camerasToExclude = new ArrayList();
+ ArrayList cameras = new ArrayList();
+ DSFilterInfo cameraInfo = null;
+ int i = 1;
+
+ do {
+ CameraSelectionDialog cd = new CameraSelectionDialog("Select camera " + i, cameraInfos, camerasToExclude);
+ cameraInfo = cd.getSelectedCamera();
+ if (cameraInfo == null)
+ break;
+
+ CameraFormatSelectionDialog cfd = new CameraFormatSelectionDialog("Select format for camera " + i,
+ DirectShowCamera.getFormats(cameraInfo));
+ DSMediaType format = cfd.getSelectedFormat();
+ if (format == null)
+ break;
+
+ camerasToExclude.add(cameraInfo);
+
+ DirectShowCamera camera = new DirectShowCamera(cameraInfo);
+ camera.selectFormat(format);
+ cameras.add(camera);
+ i++;
+ } while (cameraInfo != null);
+
+ return cameras.toArray(new DirectShowCamera[cameras.size()]);
+ }
+
+ public AdvancedCameraSettingsMain() {
+
+ }
+
+
+ private static final int[][] propertyKeys = {
+ {CaptureDeviceControls.EXPOSURE, CaptureDeviceControls.GAIN, CaptureDeviceControls.BRIGHTNESS,
+ CaptureDeviceControls.CONTRAST, CaptureDeviceControls.SATURATION, CaptureDeviceControls.HUE,
+ CaptureDeviceControls.COLORENABLE, CaptureDeviceControls.WHITEBALANCE, CaptureDeviceControls.GAMMA},
+ {CaptureDeviceControls.BACKLIGHTCOMPENSATION, CaptureDeviceControls.SHARPNESS, CaptureDeviceControls.INPUT_LEVEL,
+ CaptureDeviceControls.INPUT_SELECT},
+ {CaptureDeviceControls.FOCUS, CaptureDeviceControls.IRIS, CaptureDeviceControls.ZOOM, CaptureDeviceControls.PAN,
+ CaptureDeviceControls.TILT, CaptureDeviceControls.ROLL},
+ {CaptureDeviceControls.MASTER_VOL, CaptureDeviceControls.MASTER_PAN, CaptureDeviceControls.TREBLE,
+ CaptureDeviceControls.BASS, CaptureDeviceControls.BALANCE},
+ {CaptureDeviceControls.CAMCONTROL_ABSOLUTE, CaptureDeviceControls.CAMCONTROL_AUTO,
+ CaptureDeviceControls.CAMCONTROL_MANUAL, CaptureDeviceControls.CAMCONTROL_RELATIVE},
+ {CaptureDeviceControls.VC_FLIP_HOR, CaptureDeviceControls.VC_FLIP_VER, CaptureDeviceControls.VC_TRIGGER,
+ CaptureDeviceControls.VC_TRIGGER_ENABLE},
+ {CaptureDeviceControls.LT_DIGITAL_PAN, CaptureDeviceControls.LT_DIGITAL_PANTILTZOOM,
+ CaptureDeviceControls.LT_DIGITAL_TILT, CaptureDeviceControls.LT_DIGITAL_ZOOM,
+ CaptureDeviceControls.LT_EXPOSURE_TIME, CaptureDeviceControls.LT_FACE_TRACKING, CaptureDeviceControls.LT_FINDFACE,
+ CaptureDeviceControls.LT_LED} };
+
+ private static void loadCamerasSettings() {
+ DSFilterInfo[] cameraInfos = DirectShowCamera.getCameras();
+ ConfigManager.revert();
+ Map data = ConfigManager.getAllData();
+
+ for(DSFilterInfo cameraInfo : cameraInfos) {
+ DirectShowCamera camera = new DirectShowCamera(cameraInfo);
+
+ try {
+ camera.start();
+ for (int j = 0; j < propertyKeys.length; j++) {
+ for (int i = 0; i < propertyKeys[j].length; i++) {
+
+ try {
+ String value = data.get(camera.getPath() + "?" + propertyKeys[j][i]);
+ if(value != null) {
+ System.out.println("Set "+camera.getName()+" value "+propertyKeys[j][i]+" to "+value);
+ camera.setParameterValue(propertyKeys[j][i], Integer.parseInt(value));
+ }
+ }
+ catch(Exception e) {}
+
+ try {
+ String value = data.get(camera.getPath() + "&" + propertyKeys[j][i]);
+ if(value != null) {
+ System.out.println("Set "+camera.getName()+" auto "+propertyKeys[j][i]+" to "+value);
+ camera.setAuto(propertyKeys[j][i], Boolean.parseBoolean(value));
+ }
+ }
+ catch(Exception e) {}
+ }
+ }
+ camera.stop();
+ }
+ catch(Exception e) {
+ System.out.println(e);
+ }
+ }
+
+ }
+}
diff --git a/src/MultiCameraTestMain.java b/src/MultiCameraTestMain.java
new file mode 100644
index 0000000..d3e80fc
--- /dev/null
+++ b/src/MultiCameraTestMain.java
@@ -0,0 +1,97 @@
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+
+import javax.swing.JFrame;
+import javax.swing.Timer;
+
+import util.CameraSettingsPanel;
+import util.DirectShowCamera;
+import util.FrameListener;
+
+import com.digitalmodular.utilities.swing.ImagePanel;
+
+/**
+ * @author Mark Jeronimus
+ * @version 1.0
+ * @since 1.0
+ * @date 2012/04/03
+ */
+public class MultiCameraTestMain extends JFrame implements FrameListener {
+ private final DirectShowCamera[] cameras;
+ private ImagePanel[] images;
+
+ public MultiCameraTestMain(final DirectShowCamera[] cameras) throws Exception {
+ super("Dual camera test");
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ this.cameras = cameras;
+ images = new ImagePanel[cameras.length];
+
+ for (int i = 0; i < cameras.length; i++) {
+ DirectShowCamera camera = cameras[i];
+
+ images[i] = new ImagePanel(true);
+
+ camera.addFrameListener(this);
+ camera.start();
+ }
+
+ {
+ Timer t = new Timer(1000, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ System.out.println("initializeComponents");
+ initializeComponents();
+ setSize(400 * cameras.length, 600);
+ setLocationRelativeTo(null);
+ setVisible(true);
+ }
+ });
+ t.setRepeats(false);
+ t.start();
+ }
+ }
+
+ void initializeComponents() {
+ setLayout(new GridLayout(2, cameras.length));
+
+ for (DirectShowCamera camera : cameras)
+ add(new CameraSettingsPanel(camera));
+
+ for (int i = 0; i < cameras.length; i++) {
+ add(images[i]);
+ }
+ }
+
+ @Override
+ public synchronized void newFrame(Object source, byte[] data) {
+ // Catch exceptions because somewhere up the call tree it is silently
+ // caught and not reported.
+ try {
+ int index = -1;
+ for (int i = 0; i < cameras.length; i++) {
+ if (cameras[i] == source) {
+ index = i;
+ break;
+ }
+ }
+
+ int width = cameras[index].getFormat().getWidth();
+ int height = cameras[index].getFormat().getHeight();
+
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
+ byte[] array = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
+ System.arraycopy(data, 0, array, 0, array.length);
+
+ images[index].setImage(image);
+
+ repaint();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/com/digitalmodular/utilities/ConfigManager.java b/src/com/digitalmodular/utilities/ConfigManager.java
new file mode 100644
index 0000000..bee622a
--- /dev/null
+++ b/src/com/digitalmodular/utilities/ConfigManager.java
@@ -0,0 +1,163 @@
+// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
+// Jad home page: http://www.kpdus.com/jad.html
+// Decompiler options: packimports(3)
+// Source File Name: ConfigManager.java
+
+package com.digitalmodular.utilities;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class ConfigManager {
+ private static final String filename = "config.ini";
+ private static TreeMap data = new TreeMap();
+
+ static {
+ revert();
+ }
+
+ public static void revert() {
+ data.clear();
+
+ try {
+ BufferedReader in = new BufferedReader(new FileReader(ConfigManager.filename));
+ String s;
+ while ((s = in.readLine()) != null) {
+ int split = s.indexOf(' ');
+ String key = s.substring(0, split++);
+ String value = s.substring(split);
+ ConfigManager.data.put(key, value);
+ }
+ in.close();
+ }
+ catch (FileNotFoundException filenotfoundexception) {}
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void save() {
+ try {
+ BufferedWriter out = new BufferedWriter(new FileWriter(ConfigManager.filename));
+ Set keys = ConfigManager.data.keySet();
+ for (String key : keys) {
+ out.write(new StringBuilder(key).append(" ").append(ConfigManager.data.get(key)).append("\n").toString());
+ }
+ out.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void setValue(String key, String value) {
+ ConfigManager.data.put(key, value);
+ }
+
+ public static void setIntValue(String key, int value) {
+ ConfigManager.data.put(key, Integer.toString(value));
+ }
+
+ public static void setLongValue(String key, long value) {
+ ConfigManager.data.put(key, Long.toString(value));
+ }
+
+ public static void setFloatValue(String key, float value) {
+ ConfigManager.data.put(key, Float.toString(value));
+ }
+
+ public static void setDoubleValue(String key, double value) {
+ ConfigManager.data.put(key, Double.toString(value));
+ }
+
+ public static void setBoolValue(String key, boolean value) {
+ ConfigManager.data.put(key, Boolean.toString(value));
+ }
+
+ public static String getValue(String key, String defaultValue) {
+ String value = ConfigManager.data.get(key);
+ if (value == null) {
+ ConfigManager.setValue(key, defaultValue);
+ return defaultValue;
+ }
+ return value;
+ }
+
+ public static int getIntValue(String key, int defaultValue) {
+ String value = ConfigManager.data.get(key);
+ if (value == null) {
+ ConfigManager.setIntValue(key, defaultValue);
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public static long getLongValue(String key, long defaultValue) {
+ String value = ConfigManager.data.get(key);
+ if (value == null) {
+ ConfigManager.setLongValue(key, defaultValue);
+ return defaultValue;
+ }
+ try {
+ return Long.parseLong(value);
+ }
+ catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public static float getFloatValue(String key, float defaultValue) {
+ String value = ConfigManager.data.get(key);
+ if (value == null) {
+ ConfigManager.setFloatValue(key, defaultValue);
+ return defaultValue;
+ }
+ try {
+ return Float.parseFloat(value);
+ }
+ catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public static double getDoubleValue(String key, double defaultValue) {
+ String value = ConfigManager.data.get(key);
+ if (value == null) {
+ ConfigManager.setDoubleValue(key, defaultValue);
+ return defaultValue;
+ }
+ try {
+ return Double.parseDouble(value);
+ }
+ catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ public static boolean getBoolValue(String key, boolean defaultValue) {
+ String value = ConfigManager.data.get(key);
+ if (value == null) {
+ ConfigManager.setBoolValue(key, defaultValue);
+ return defaultValue;
+ }
+ return Boolean.parseBoolean(value);
+ }
+
+ public static Map getAllData() {
+ Map allData = new TreeMap();
+ allData.putAll(data);
+ return allData;
+ }
+}
diff --git a/src/com/digitalmodular/utilities/swing/ImagePanel.java b/src/com/digitalmodular/utilities/swing/ImagePanel.java
new file mode 100644
index 0000000..b3ff17e
--- /dev/null
+++ b/src/com/digitalmodular/utilities/swing/ImagePanel.java
@@ -0,0 +1,113 @@
+package com.digitalmodular.utilities.swing;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JComponent;
+import javax.swing.border.Border;
+
+/**
+ * @author Mark Jeronimus
+ */
+// date 2011-06-20
+public class ImagePanel extends JComponent
+{
+ public Image image;
+ public boolean stretch;
+
+ public ImagePanel()
+ {
+ this(false);
+ }
+
+ public ImagePanel(boolean stretch)
+ {
+ this.stretch = stretch;
+ }
+
+ public ImagePanel(BufferedImage image)
+ {
+ setImage(image);
+ }
+
+ public void setImage(Image im)
+ {
+ image = im;
+ sizeChanged();
+ }
+
+ public Image getImage()
+ {
+ return image;
+ }
+
+ private void sizeChanged()
+ {
+ if (stretch)
+ return;
+
+ Dimension size = new Dimension();
+
+ if (image != null)
+ {
+ size.width = image.getWidth(null);
+ size.height = image.getHeight(null);
+ }
+
+ Border border = getBorder();
+ if (border != null)
+ {
+ Insets insets = border.getBorderInsets(this);
+
+ size.width += insets.left + insets.right;
+ size.height += insets.top + insets.bottom;
+ }
+
+ setPreferredSize(size);
+ setSize(size);
+ }
+
+ @Override
+ public void setBorder(Border border)
+ {
+ super.setBorder(border);
+ sizeChanged();
+ }
+
+ @Override
+ public void paintComponent(Graphics g)
+ {
+ // super.paintComponent(g);
+
+ if (image != null)
+ {
+ int x = 0;
+ int y = 0;
+ int width = getWidth();
+ int height = getHeight();
+
+ Border border = getBorder();
+ if (border != null)
+ {
+ Insets insets = border.getBorderInsets(this);
+
+ x = insets.left;
+ y = insets.top;
+ width -= insets.right + x;
+ height -= insets.right + x;
+ }
+
+ if (stretch)
+ {
+ g.drawImage(image, x, y, width, height, this);
+ }
+ else
+ {
+ g.drawImage(image, x, y, null);
+ }
+ }
+ }
+}
diff --git a/src/com/digitalmodular/utilities/swing/TableLayout.java b/src/com/digitalmodular/utilities/swing/TableLayout.java
new file mode 100644
index 0000000..0bba4b5
--- /dev/null
+++ b/src/com/digitalmodular/utilities/swing/TableLayout.java
@@ -0,0 +1,324 @@
+package com.digitalmodular.utilities.swing;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * A Layout Manager which creates a basic table of components. The widths of the cells are specified as a ratio array of int
. The
+ * relative widths of the rendered columns will always follow these ratios.
+ *
+ * The preferred widths of the columns will be either the sum of the columns with configured widths as pixels, or the configured ratios
+ * stretched equally in such a way that no single component is narrower than it's own preferred width, whichever is larger, added to the sum of
+ * the gap widths. When the width of the parent container is larger, the cells are simply stretched linearly according to the configured ratios.
+ *
+ * A special case is when a ratio is specified as zero. In these cases, each column specified as zero will get such a preferred width that no
+ * single component inside that column is narrower than it's own preferred width. These columns do stretch, however, when the width of the
+ * parent container is larger than the preferred width, everything is stretched linearly.
+ *
+ * For example, a TableLayout
with column widths {5, 2}
filled with equal components with a width of 100 pixels will
+ * generate a layout with preferred column widths of 250 and 100 pixels (ratio 5:2). Setting this layout to a container with an absolute width
+ * of 700 pixels will force the table columns to 500 and 200 pixels wide.
+ *
+ * Another example. A TableLayout
with column widths {0, 200, 0, 120}
filled with different components for each column
+ * with relative widths of 30, 100, 50 and 100 pixels will generate a layout with preferred column widths of 30, 200, 50 and 120 pixels (ratio
+ * x:200:x:120). Setting this layout to a container with an absolute width of 800 pixels will force the table columns to 60, 400, 100 and 240
+ * pixels wide.
+ *
+ * @author Mark Jeronimus
+ * @see LayoutManager
+ * @see Component#getPreferredSize() date 2006/11/24
+ */
+public class TableLayout implements LayoutManager, Serializable
+{
+ private int hgap;
+ private int vgap;
+ private int[] columns;
+ private int[] colWidths;
+
+ private int numColumns;
+ private int[] rowHeights;
+ private int widestColumn;
+
+ /**
+ * Create a TableLayout with specified column width ratios and gap sizes.
+ *
+ * @param hgap
+ * the horizontal space between all columns, in pixels
+ * @param vgap
+ * the vertical space between all rows, in pixels
+ * @param columns
+ * the column-width ratios
+ * @throws IllegalArgumentException
+ * when the array is null, the array length is zero or one of the array elements is negative
+ */
+ public TableLayout(int hgap, int vgap, int... columns)
+ {
+ setColumns(columns);
+ setHgap(hgap);
+ setVgap(vgap);
+ }
+
+ /**
+ * Set or change the column width ratios.
+ *
+ * @param columns
+ * the column width ratios
+ * @throws IllegalArgumentException
+ * when the array is null, the array length is zero or one of the array elements is negative
+ */
+ public void setColumns(int... columns)
+ {
+ if (columns == null || columns.length == 0)
+ throw new IllegalArgumentException("No columns specified");
+ this.columns = columns;
+ numColumns = this.columns.length;
+
+ int widestColumnSize = -1;
+ widestColumn = 0;
+ for (int i = 0; i < numColumns; i++)
+ {
+ int w = this.columns[i];
+ if (w < 0)
+ throw new IllegalArgumentException("Negative column width specified at index " + i);
+
+ if (w > 0 && widestColumnSize < w)
+ {
+ widestColumnSize = w;
+ widestColumn = i;
+ }
+ }
+
+ colWidths = new int[this.columns.length];
+ }
+
+ /**
+ * Returns the column width ratios. The returned array is a direct reference to the contained column array, so care must be taken not to
+ * alter any elements to illegal values (negative values).
+ *
+ * @return the column width ratios
+ */
+ public int[] getColumnns()
+ {
+ return columns;
+ }
+
+ /**
+ * Set or change the horizontal space between all columns, in pixels
+ *
+ * @param hgap
+ * the horizontal space between all columns, in pixels
+ */
+ public void setHgap(int hgap)
+ {
+ if (hgap < 0)
+ throw new IllegalArgumentException("Negative hgap specified");
+ this.hgap = hgap;
+ }
+
+ /**
+ * Get the horizontal space between all columns, in pixels
+ *
+ * @return the horizontal space between all columns, in pixels
+ */
+ public int getHgap()
+ {
+ return hgap;
+ }
+
+ /**
+ * Set or change the vertical space between all rows, in pixels
+ *
+ * @param vgap
+ * the vertical space between all rows, in pixels
+ */
+ public void setVgap(int vgap)
+ {
+ if (vgap < 0)
+ throw new IllegalArgumentException("Negative vgap specified");
+ this.vgap = vgap;
+ }
+
+ /**
+ * Get the vertical space between all rows, in pixels
+ *
+ * @return the vertical space between all rows, in pixels
+ */
+ public int getVgap()
+ {
+ return vgap;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addLayoutComponent(String name, Component comp)
+ {}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void removeLayoutComponent(Component comp)
+ {}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container parent)
+ {
+ synchronized (parent.getTreeLock())
+ {
+ Insets insets = parent.getInsets();
+ int components = parent.getComponentCount();
+ int numRows = (components + numColumns - 1) / numColumns;
+
+ rowHeights = new int[numRows];
+ Arrays.fill(rowHeights, 0);
+
+ int narrowestColumn = widestColumn;
+ int narrowestSize = columns[widestColumn];
+
+ int component = 0;
+ for (int y = 0; y < numRows; y++)
+ {
+ for (int x = 0; x < numColumns; x++)
+ {
+ Component comp = parent.getComponent(component++);
+ Dimension d = comp.getPreferredSize();
+
+ if (columns[x] == 0)
+ {
+ if (colWidths[x] < d.width)
+ colWidths[x] = d.width;
+ }
+ else if (d.width > columns[x] * narrowestSize / columns[narrowestColumn])
+ {
+ if (columns[x] > 0)
+ narrowestColumn = x;
+ narrowestSize = d.width;
+ }
+
+ if (rowHeights[y] < d.height)
+ rowHeights[y] = d.height;
+
+ if (component == components)
+ break;
+ }
+ }
+ int totalWidth = insets.left + insets.right + (numColumns - 1) * hgap;
+ for (int x = 0; x < numColumns; x++)
+ {
+ if (columns[x] != 0)
+ colWidths[x] = columns[x] * narrowestSize / columns[narrowestColumn];
+
+ totalWidth += colWidths[x];
+ }
+ int totalHeight = insets.top + insets.bottom + (numRows - 1) * vgap;
+ for (int y = 0; y < numRows; y++)
+ totalHeight += rowHeights[y];
+ return new Dimension(totalWidth, totalHeight);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container parent)
+ {
+ synchronized (parent.getTreeLock())
+ {
+ Insets insets = parent.getInsets();
+ int components = parent.getComponentCount();
+ int numRows = (components + numColumns - 1) / numColumns;
+
+ int[] colWidths = new int[numColumns];
+ System.arraycopy(columns, 0, colWidths, 0, numColumns);
+ int[] rowHeights = new int[numRows];
+ Arrays.fill(rowHeights, 0);
+
+ int component = 0;
+ for (int y = 0; y < numRows; y++)
+ {
+ for (int x = 0; x < numColumns; x++)
+ {
+ Component comp = parent.getComponent(component++);
+ Dimension d = comp.getMinimumSize();
+ if (colWidths[x] < d.width)
+ colWidths[x] = d.width;
+ if (rowHeights[y] < d.height)
+ rowHeights[y] = d.height;
+ if (component == components)
+ break;
+ }
+ }
+ int totalWidth = insets.left + insets.right + (numColumns - 1) * hgap;
+ for (int x = 0; x < numColumns; x++)
+ totalWidth += colWidths[x];
+ int totalHeight = insets.top + insets.bottom + (numRows - 1) * vgap;
+ for (int y = 0; y < numRows; y++)
+ totalHeight += rowHeights[y];
+ return new Dimension(totalWidth, totalHeight);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void layoutContainer(Container parent)
+ {
+ synchronized (parent.getTreeLock())
+ {
+ Insets insets = parent.getInsets();
+ int components = parent.getComponentCount();
+
+ int numRows = (components + numColumns - 1) / numColumns;
+
+ Dimension preferredSize = preferredLayoutSize(parent);
+ int width = parent.getWidth() - (insets.left + insets.right + (numColumns - 1) * hgap);
+ int height = parent.getHeight() - (insets.top + insets.bottom + (numRows - 1) * vgap);
+ int preferredWidth = preferredSize.width - (insets.left + insets.right + (numColumns - 1) * hgap);
+ int preferredHeight = preferredSize.height - (insets.top + insets.bottom + (numRows - 1) * vgap);
+
+ int[] currentColWidths = new int[numColumns];
+ for (int x = 0; x < numColumns; x++)
+ currentColWidths[x] = colWidths[x] * width / preferredWidth;
+ int[] currentRowHeights = new int[numRows];
+ for (int y = 0; y < numRows; y++)
+ currentRowHeights[y] = rowHeights[y] * height / preferredHeight;
+
+ int component = 0;
+ int yPos = insets.top;
+ for (int y = 0; y < numRows; y++)
+ {
+ int xPos = insets.left;
+ for (int x = 0; x < numColumns; x++)
+ {
+ parent.getComponent(component++).setBounds(xPos, yPos, currentColWidths[x], currentRowHeights[y]);
+ if (component == components)
+ break;
+
+ xPos += currentColWidths[x] + hgap;
+ }
+ yPos += currentRowHeights[y] + vgap;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return super.getClass().getSimpleName() + "[columns=" + columns + ", hgap=" + hgap + ", vgap=" + vgap + "]";
+ }
+}
diff --git a/src/com/digitalmodular/utilities/swing/table/MutableTableModel.java b/src/com/digitalmodular/utilities/swing/table/MutableTableModel.java
new file mode 100644
index 0000000..84900a4
--- /dev/null
+++ b/src/com/digitalmodular/utilities/swing/table/MutableTableModel.java
@@ -0,0 +1,251 @@
+package com.digitalmodular.utilities.swing.table;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * @author Mark Jeronimus
+ */
+// date 2005-08-07
+public class MutableTableModel extends AbstractTableModel implements List