/* * Copyright © 2010 Martin Riedel * * This file is part of TransFile. * * TransFile is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * TransFile is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with TransFile. If not, see <http://www.gnu.org/licenses/>. */ package net.sourceforge.transfile.ui.swing; import static net.sourceforge.jenerics.i18n.Translator.getDefaultTranslator; import static net.sourceforge.transfile.ui.swing.StatusService.StatusMessage; import java.util.LinkedList; import java.util.List; import java.util.Locale; import javax.swing.AbstractListModel; import javax.swing.DefaultListSelectionModel; import javax.swing.JList; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import net.sourceforge.transfile.exceptions.LogicError; import net.sourceforge.jenerics.i18n.Translator; /** * <p>Custom JList displaying {@link StatusService.StatusMessage}s, from most recent (first position) to oldest (last position).</p> * * <p>New messages added to the list will be considered the most recent ones, pushing older ones further down the list.</p> * * <p>Automatically reflects locale changes.</p> * * @author Martin Riedel * */ public class StatusList extends JList { private static final long serialVersionUID = 7671184530745247412L; /* * Reference to the custom model StatusList uses */ private final StatusListModel model; /** * Constructs a new StatusListModel instance * * @param minRows * <br />The minimum number of rows in the list * <br />Should not be < 1 */ public StatusList(final int minRows) { if (minRows <1) throw new LogicError("can't initialise with a minimum of less than 1 rows"); this.model = new StatusListModel(minRows); setModel(this.model); // make list items unselectable setSelectionModel(new StatusListSelectionModel()); } /** * Adds a new message to the list. The message will be considered the most recent one, and thus be * inserted at the beginning of the list * * @param message * <br />The message to be added. * <br />Should not be null. */ public void addMessage(final StatusMessage message) { this.model.addMessage(message); } /** * <p>ListModel for {@link StatusList}. Allows for insertion of new items (messages) at the * first position in the list, pushing back older messages.</p> * * <p>Automatically reflects locale changes / autotranslation and fires the appropriate events * indicating that the list contents have changed.</p> * * @author Martin Riedel * */ static class StatusListModel extends AbstractListModel { private static final long serialVersionUID = -4679723768372813345L; /* * The messages currently being displayed / the [maxRetainedItems] most recent messages added to the list */ private final List<StatusMessage> messages = new LinkedList<StatusMessage>(); /* * The minimum number of rows in the list */ private final int minRows; /** * Creates a new StatusListModel * * * @param minRows * <br />The minimum number of rows in the list * <br />Should not be < 1 */ public StatusListModel(final int minRows) { this.minRows = minRows; // fireContentsChanged when StatusMessages are autotranslated getDefaultTranslator().addTranslatorListener(new TranslatorListener()); for (int i = 0; i < minRows; i++) this.messages.add(new StatusDummy()); } /** * Stores the provided message as the newest message in the model. * * @param message * <br />The message to be added to the list. * <br />Should not be null. */ public void addMessage(final StatusMessage message) { // add new message to the beginning of the list this.messages.add(0, message); // if the list is holding more than the specified minimum number of messages/rows, // remove the oldest (rightmost) object if it is a StatusDummy if (this.messages.size() > this.minRows) if (this.messages.get(this.messages.size() - 1) instanceof StatusDummy) this.messages.remove(this.messages.size() - 1); // list has changed, fire event fireContentsChanged(this, 0, this.messages.size() - 1); //TODO fireIntervalAdded? } /** * {@inheritDoc} */ @Override public Object getElementAt(int pos) { return this.messages.get(pos); } /** * {@inheritDoc} */ @Override public int getSize() { return this.messages.size(); } /** * Called whenever the application's locale changes. All {@link StatusMessage}s have been * dynamically translated already, so this method merely refreshes the list. * */ final void onLocaleChanged() { fireContentsChanged(this, 0, StatusListModel.this.messages.size() - 1); } /** * Listens for locale changes and tells the list that its contents have changed (they have been autotranslated) * when one happens. The messages themselves are dynamically translated, so there's no need to do anything * with them here. * * @author Martin Riedel * */ private class TranslatorListener implements Translator.Listener { /** * Constructs a new instance */ public TranslatorListener() { // do nothing, just allow instantiation } /** * {@inheritDoc} */ @Override public void localeChanged(Locale oldLocale, Locale newLocale) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { StatusListModel.this.onLocaleChanged(); } }); } } } /** * A {@link ListSelectionModel} that doesn't allow selections of any kind. * * @author Martin Riedel * */ private static class StatusListSelectionModel extends DefaultListSelectionModel { private static final long serialVersionUID = 2726424284954401907L; /** * Constructs a new instance * */ StatusListSelectionModel() { // do nothing, just allow instantiation } /** * {@inheritDoc} */ @Override public boolean isSelectionEmpty() { return true; } /** * {@inheritDoc} */ @Override public boolean isSelectedIndex(final int index) { return false; } } /** * A dummy {@link StatusService.StatusMessage} used to pad the list so that it always * displays a specified minimum of rows/messages. * * @author Martin Riedel * */ static final class StatusDummy extends StatusMessage { public static final String DUMMY_TEXT = ""; private final String dummyField = "42"; /** * Constructs a new StatusDummy * */ public StatusDummy() { super(DUMMY_TEXT); } /** * {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((this.dummyField == null) ? 0 : this.dummyField.hashCode()); return result; } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; StatusDummy other = (StatusDummy) obj; if (this.dummyField == null) { if (other.dummyField != null) return false; } else if (!this.dummyField.equals(other.dummyField)) return false; return true; } } }