/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package de.cismet.commons.gui.equalizer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/**
* Special {@link EqualizerModel} that guarantees that the sum of all categories is <code>100</code>. So if a new value
* is set all the other categories' values are adapted so that the sum is <code>100</code> again. Thus this model does
* not inform listeners about a specific change at a specific index but only fires a general change event.<br/>
* <br/>
* The adaption of other values follows these rules:
*
* <ul>
* <li>the delta of the changed value is distributed equally over the other categories</li>
* <li>if a specific category value is zero it will not be considered for distribution</li>
* <li>if a specific category value reaches zero during delta distribution it will also not be considered for
* distribution anymore</li>
* <li>if the (remaining) delta is smaller than the number of remaining categories a change of <code>1</code> is
* applied to the remaining categories, ascending by index</li>
* </ul>
*
* @author martin.scholl@cismet.de
* @version 1.0
*/
// TODO: allow custom ranges
public final class RubberBandEqualizerModel extends DefaultEqualizerModel {
//~ Constructors -----------------------------------------------------------
/**
* Creates a new RubberBandEqualizerModel object using the given list of <code>EqualizerCategory</code>s. The
* {@link Range} of this model is <code>[0, 100]</code>. In addition to the policies of <code>
* DefaultEqualizerModel</code> this model implementation ensures that the sum of all values of the categories is
* always <code>100</code>. For convenience this constructor permits a category collection that contains categories
* with <code>0</code> values only. In this case the constructor distributes values among the categories equally so
* that a sum of <code>100</code> is guaranteed.
*
* @param equalizerCategories the <code>EqualizerCategory</code>s of this model
*
* @throws IllegalArgumentException if any of the policies of <code>DefaultEqualizerModel</code> are not met or if
* the sum of the categories' values is not <code>0</code> or <code>100</code>
*
* @see DefaultEqualizerModel
*/
public RubberBandEqualizerModel(final Collection<EqualizerCategory> equalizerCategories) {
super(equalizerCategories, new Range(0, 100));
int sum = 0;
for (final EqualizerCategory cat : this.equalizerCategories) {
sum += cat.getValue();
}
if (sum == 0) {
// integer div
final int initial = 100 / this.equalizerCategories.size();
final int delta = 100 % this.equalizerCategories.size();
final Iterator<EqualizerCategory> it = this.equalizerCategories.iterator();
while (it.hasNext()) {
final EqualizerCategory cat = it.next();
if ((delta == 0) || it.hasNext()) {
cat.setValue(initial);
} else {
cat.setValue(initial + delta);
}
}
} else if (sum != 100) {
throw new IllegalArgumentException("equalizer categories do not sum up to 100: " + sum); // NOI18N
}
}
//~ Methods ----------------------------------------------------------------
@Override
public void setValueAt(final int index, final int value) {
checkValueWithinRange(value);
if (getValueAt(index) == value) {
// no change, do nothing
return;
}
final int[] newVals = calculateValues(index, value);
for (int i = 0; i < getEqualizerCategoryCount(); ++i) {
equalizerCategories.get(i).setValue(newVals[i]);
}
fireEqualizerModelEvent(new EqualizerModelEvent(this));
}
/**
* DOCUMENT ME!
*
* @param index DOCUMENT ME!
* @param value DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private int[] calculateValues(final int index, final int value) {
// special case, no calculation has to be done
if (value == 100) {
final int[] values = new int[getEqualizerCategoryCount()];
for (int i = 0; i < values.length; ++i) {
values[i] = 0;
}
values[index] = 100;
return values;
}
// reverse delta
final int delta = getValueAt(index) - value;
final int[] values = new int[getEqualizerCategoryCount()];
final ArrayList<Integer> goodIndexes = new ArrayList<Integer>(getEqualizerCategoryCount());
for (int i = 0; i < getEqualizerCategoryCount(); ++i) {
values[i] = getValueAt(i);
goodIndexes.add(i);
}
values[index] = value;
removeIndex(goodIndexes, index);
// special behaviour so that we can actually manage to have values fixed to zero
if ((delta > 0) && ((values[index] + delta) != 100)) {
final ListIterator<Integer> li = goodIndexes.listIterator();
while (li.hasNext()) {
if (values[li.next()] == 0) {
li.remove();
}
}
}
int valueDelta = delta / goodIndexes.size();
int carryOver = delta % goodIndexes.size();
while (valueDelta != 0) {
final ListIterator<Integer> li = goodIndexes.listIterator();
while (li.hasNext()) {
final int i = li.next();
values[i] += valueDelta;
if (values[i] <= 0) {
li.remove();
carryOver += values[i];
values[i] = 0;
}
}
valueDelta = carryOver / goodIndexes.size();
carryOver = carryOver % goodIndexes.size();
}
final ListIterator<Integer> li = goodIndexes.listIterator();
while (carryOver != 0) {
final int i = li.next();
if (carryOver > 0) {
values[i]++;
carryOver--;
} else if ((carryOver < 0) && (values[i] != 0)) {
values[i]--;
carryOver++;
}
}
return values;
}
/**
* DOCUMENT ME!
*
* @param list DOCUMENT ME!
* @param toRemove DOCUMENT ME!
*/
private void removeIndex(final List<Integer> list, final int toRemove) {
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) == toRemove) {
list.remove(i);
return;
}
}
}
}