/* * Copyright 2012 Vincent Lhote * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package plugin.overland.model; import java.text.MessageFormat; import java.util.ArrayList; import java.util.EventObject; import java.util.List; import java.util.Map; import javax.swing.AbstractListModel; import javax.swing.ComboBoxModel; import javax.swing.event.EventListenerList; import pcgen.system.LanguageBundle; import pcgen.util.Logging; import plugin.overland.util.Localized; /** * Stores travel methods and provides model for use in a GUI. Implementation. Visible only in same package. * * @author Vincent Lhote */ class TravelMethodImplementation implements TravelMethod { // ### Constants ### private static final String UNKNOWN_WAY_0_PLEASE_FIX_1_XML = "in_plugin_overland_error_noWay"; //$NON-NLS-1$ // ### Fields ### /** Name of this method */ private Localized name; private Map<String, Map<String, Combo>> multByRoadByTerrains; private List<Method> methods; private Map<String, Map<Localized, String>> terrainsId; private Map<String, Map<Localized, String>> routesId; private MethodModel methodModel = new MethodModel(); private ListByWayModel routesModel; private ListByWayModel terrainsModel; private PaceModel paceModel = new PaceModel(); private ChoiceModel choiceModel = new ChoiceModel(); private Method selectedMethod; // ### Constructors ### public TravelMethodImplementation(Localized name, Map<String, Map<String, Combo>> multByRoadByTerrains, Map<String, List<Localized>> terrains, Map<String, Map<Localized, String>> terrainsById, Map<String, List<Localized>> routes, Map<String, Map<Localized, String>> routesById, List<Method> methods) { this.name = name; this.multByRoadByTerrains = multByRoadByTerrains; this.terrainsId = terrainsById; this.routesId = routesById; this.methods = methods; this.routesModel = new ListByWayModel(routes); this.terrainsModel = new ListByWayModel(terrains); } @Override public ComboBoxModel getRoutesModel() { return routesModel; } @Override public ComboBoxModel getTerrainsModel() { return terrainsModel; } @Override public ComboBoxModel getMethodsModel() { return methodModel; } @Override public ComboBoxModel getPaceModel() { return paceModel; } @Override public ComboBoxModel getChoiceModel() { return choiceModel; } @Override public String toString() { return name.toString(); } /** * Returns the mult associated with current selection. Returns null if no mult value is available, * no terrains/routes selected or no mult with the terrains/routes selection. * @return usually a double */ private Number getMult() { Combo c = getSelectedCombo(); if (c == null) { return null; } return c.getMult(); } private Combo getSelectedCombo() { if (terrainsModel.getSelectedItem() == null || routesModel.getSelectedItem() == null) { return null; } String way = selectedMethod.getWay(); Map<Localized, String> map = terrainsId.get(way); if (map == null) { Logging.errorPrintLocalised(UNKNOWN_WAY_0_PLEASE_FIX_1_XML, way, name); return null; } String tId = map.get(terrainsModel.getSelectedItem()); if (!multByRoadByTerrains.containsKey(tId)) return null; String rId = routesId.get(selectedMethod.getWay()).get(routesModel.getSelectedItem()); if (!multByRoadByTerrains.get(tId).containsKey(rId)) return null; return multByRoadByTerrains.get(tId).get(rId); } private String getMultString() { Combo c = getSelectedCombo(); Number n2 = c.getMult(); if (n2 == null) return null; StringBuilder n = new StringBuilder(); n.append(LanguageBundle.getPrettyMultiplier(n2.doubleValue())); if (c.getAddMph().doubleValue() != 0) { n.append("\n").append(MessageFormat.format(LanguageBundle.getString("in_plusMph"), c.getAddMph())); //$NON-NLS-1$ //$NON-NLS-2$ } if (c.getAddMph().doubleValue() != 0) { n.append("\n").append(MessageFormat.format(LanguageBundle.getString("in_plusKmh"), c.getAddKmh())); //$NON-NLS-1$ //$NON-NLS-2$ } return n.toString(); } @Override public String getUnmodifiedImperialSpeedString() { return formatImperialSpeed(getUnmodifiedImperialSpeed()); } @Override public String getImperialSpeedString() { return formatImperialSpeed(getImperialSpeed()); } private String formatImperialSpeed(Number imperialSpeed) { if (imperialSpeed != null) { Pace selectedPace = paceModel.getSelected(); if (selectedPace != null) { String unit = selectedPace.isUseDays() ? LanguageBundle.getString("in_mpd") : LanguageBundle.getString("in_mph"); //$NON-NLS-1$ //$NON-NLS-2$ return MessageFormat.format(unit, imperialSpeed); } return null; } return null; } @Override public String getUnmodifiedMetricSpeedString() { return formatMetricSpeed(getUnmodifiedMetricSpeed()); } @Override public String getMetricSpeedString() { return formatMetricSpeed(getMetricSpeed()); } private String formatMetricSpeed(Number metricSpeed) { if (metricSpeed != null) { Pace selectedPace = paceModel.getSelected(); if (selectedPace != null) { String unit = selectedPace.isUseDays() ? LanguageBundle.getString("in_kmd") : LanguageBundle.getString("in_kmh"); //$NON-NLS-1$ //$NON-NLS-2$ return MessageFormat.format(unit, metricSpeed); } } return null; } private Double getUnmodifiedMetricSpeed() { Pace selectedPace = paceModel.getSelected(); Choice selectedChoice = choiceModel.getSelected(); if (selectedPace != null && selectedChoice != null) { double speed = selectedPace.getMult().doubleValue() * selectedChoice.getKmh().doubleValue(); if (selectedPace.isUseDays()) speed *= selectedChoice.getHoursInDay().doubleValue(); return speed; } return null; } private Double getUnmodifiedImperialSpeed() { Pace selectedPace = paceModel.getSelected(); Choice selectedChoice = choiceModel.getSelected(); if (selectedPace != null && selectedChoice != null) { double speed = selectedPace.getMult().doubleValue() * selectedChoice.getMph().doubleValue(); if (selectedPace.isUseDays()) speed *= selectedChoice.getHoursInDay().doubleValue(); return speed; } return null; } private Double getMetricSpeed() { Double d = getUnmodifiedMetricSpeed(); Combo c = getSelectedCombo(); if (d != null && c != null) { return d.doubleValue() * c.getMult().doubleValue() + c.getAddKmh().doubleValue() * getHoursInDays(); } return null; } private Double getImperialSpeed() { Double d = getUnmodifiedImperialSpeed(); Combo c = getSelectedCombo(); if (d != null && c != null) { return d.doubleValue() * c.getMult().doubleValue() + c.getAddMph().doubleValue() * getHoursInDays(); } return null; } // return 1.0 not null private double getHoursInDays() { Pace selectedPace = paceModel.getSelected(); Choice selectedChoice = choiceModel.getSelected(); if (selectedChoice != null && selectedPace != null) { if (selectedPace.isUseDays()) return selectedChoice.getHoursInDay().doubleValue(); } return 1.0; } private String selectedUseDays() { Pace selectedPace = paceModel.getSelected(); if (selectedPace == null) return LanguageBundle.getString("in_unitUnknown"); //$NON-NLS-1$ else if (selectedPace.isUseDays()) return LanguageBundle.getString("in_unitDays"); //$NON-NLS-1$ else return LanguageBundle.getString("in_unitHours"); //$NON-NLS-1$ } private String getSelectedComment() { Pace selectedPace = paceModel.getSelected(); if (selectedPace == null) return ""; //$NON-NLS-1$ return selectedPace.comment.toString(); } // ### Conversion methods ### // (based on selected elements that combine to create a speed) @Override public Number convertToMiles(double time) { Double d = getImperialSpeed(); if (d != null) { return d.doubleValue() * time; } return null; } @Override public Number convertToKm(double time) { Double d = getMetricSpeed(); if (d != null) { return d.doubleValue() * time; } return null; } @Override public Number convertToTimeFromImperial(double distance) { Double d = getImperialSpeed(); if (d != null) { return distance / d.doubleValue(); } return null; } @Override public Number convertToTimeFromMetric(double distance) { Double d = getMetricSpeed(); if (d != null) { return distance / d.doubleValue(); } return null; } // ### Event related methods ### protected EventListenerList listenerList = new EventListenerList(); @Override public void addTravelMethodListener(TravelMethodListener l) { listenerList.add(TravelMethodListener.class, l); } @Override public void removeTravelMethodListener(TravelMethodListener l) { listenerList.remove(TravelMethodListener.class, l); } public TravelMethodListener[] getMultListeners() { return listenerList.getListeners(TravelMethodListener.class); } protected void fireMultChanged(Object source) { if (terrainsModel.getSelectedItem() == null || routesModel.getSelectedItem() == null) { return; } String n2 = getMultString(); if (n2 == null) return; Object[] listeners = listenerList.getListenerList(); TravelSpeedEvent e = null; boolean hasUnmod = getUnmodifiedMetricSpeedString() != null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TravelMethodListener.class) { if (e == null) { e = new TravelSpeedEvent(source, n2); } ((TravelMethodListener) listeners[i + 1]).multUpdated(e); // if there is an unmod speed, the speed has change if (hasUnmod) { ((TravelMethodListener) listeners[i + 1]).speedUpdated(e); } } } } protected void fireUnmodifiableSpeedChanged(Object source) { if (paceModel.getSelectedItem() == null || choiceModel.getSelectedItem() == null) { return; } Object[] listeners = listenerList.getListenerList(); EventObject e = null; boolean hasMult = getMult() != null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TravelMethodListener.class) { if (e == null) { e = new EventObject(source); } ((TravelMethodListener) listeners[i + 1]).unmodifiedSpeedUpdated(e); // the modified speed also has changed if the mult has a value if (hasMult) ((TravelMethodListener) listeners[i + 1]).speedUpdated(e); } } } protected void fireCommentDaysChanged(Object source) { Object[] listeners = listenerList.getListenerList(); TravelSpeedEvent e = null; TravelSpeedEvent e2 = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TravelMethodListener.class) { if (e == null || e2 == null) { e = new TravelSpeedEvent(source, getSelectedComment()); e2 = new TravelSpeedEvent(source, selectedUseDays()); } ((TravelMethodListener) listeners[i + 1]).commentChanged(e); ((TravelMethodListener) listeners[i + 1]).useDaysChanged(e2); } } } protected void fireAllChanged(Object source) { Object[] listeners = listenerList.getListenerList(); TravelSpeedEvent eComment = null; TravelSpeedEvent eDays = null; TravelSpeedEvent eMult = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TravelMethodListener.class) { if (eComment == null || eDays == null || eMult == null) { eComment = new TravelSpeedEvent(source, getSelectedComment()); eDays = new TravelSpeedEvent(source, selectedUseDays()); eMult = new TravelSpeedEvent(source, getMultString()); } ((TravelMethodListener) listeners[i + 1]).commentChanged(eComment); ((TravelMethodListener) listeners[i + 1]).useDaysChanged(eDays); ((TravelMethodListener) listeners[i + 1]).unmodifiedSpeedUpdated(eComment); ((TravelMethodListener) listeners[i + 1]).speedUpdated(eComment); ((TravelMethodListener) listeners[i + 1]).multUpdated(eMult); } } } // ### Inner classes ### static class Method extends Named { private List<Pace> paces; private List<Choice> choices; private String way; public Method(Localized name, String way) { super(name); this.way = way; paces = new ArrayList<Pace>(); choices = new ArrayList<Choice>(); } /** * @param c */ public void add(Choice c) { choices.add(c); } /** * @param newPace */ public void add(Pace newPace) { paces.add(newPace); } /** * @return the way */ public String getWay() { return way; } } public static class Pace extends Named { /** * @param name2 * @param comment2 * @param useDays2 * @param mult2 */ public Pace(Localized name2, Localized comment2, boolean useDays2, Number mult2) { super(name2); comment = comment2; useDays = useDays2; mult = mult2; } private boolean useDays = false; private Number mult = 1; private Localized comment; /** * @return the useDays */ public boolean isUseDays() { return useDays; } /** * @return the mult */ public Number getMult() { return mult; } /** * @return the comment */ public Localized getComment() { return comment; } } static class Choice extends Named { private Number hoursInDay; private Number kmh; private Number mph; /** * @param name * @param mph * @param kmh * @param hoursInDay */ public Choice(Localized name, Number hoursInDay, double kmh, double mph) { super(name); this.hoursInDay = hoursInDay; this.kmh = kmh; this.mph = mph; } /** * @return the hoursInDay */ public Number getHoursInDay() { return hoursInDay; } /** * @return the kmh */ public Number getKmh() { return kmh; } /** * @return the mph */ public Number getMph() { return mph; } } /** * Basic super class with a localized name. */ static class Named { private Localized name; public Named(Localized name) { this.name = name; } @Override public String toString() { return name.toString(); } } static class Combo { private final Number mult; private final Number addMph; private final Number addKmh; public Combo(Number mult, Number addMph, Number addKmh) { this.mult = mult; this.addMph = addMph; this.addKmh = addKmh; } /** * @return the mult */ public Number getMult() { return mult; } /** * @return the addMph */ public Number getAddMph() { return addMph; } /** * @return the addKmh */ public Number getAddKmh() { return addKmh; } } class MethodModel extends AbstractListModel implements ComboBoxModel { private static final long serialVersionUID = 2804199879316856684L; @Override public int getSize() { return methods.size(); } @Override public Object getElementAt(int index) { return methods.get(index); } @Override public void setSelectedItem(Object anItem) { Method previousMethod = selectedMethod; int indexOf = methods.indexOf(anItem); if (indexOf >= 0) { selectedMethod = methods.get(indexOf); // do as DefaultComboModel fireContentsChanged(this, -1, -1); paceModel.fireMethodChanged(this, previousMethod); choiceModel.fireMethodChanged(this, previousMethod); terrainsModel.fireMethodChanged(this, previousMethod); routesModel.fireMethodChanged(this, previousMethod); fireAllChanged(this); } } @Override public Object getSelectedItem() { return selectedMethod; } } /** * Used for terrain and routes. */ class ListByWayModel extends AbstractListModel implements ComboBoxModel { private static final long serialVersionUID = -5596276376727073581L; private Map<String, List<Localized>> listByWay; private Localized selected; public ListByWayModel(Map<String, List<Localized>> list) { this.listByWay = list; } private int getSize(Method m) { if (m == null || !listByWay.containsKey(m.getWay())) return 0; return listByWay.get(m.getWay()).size(); } @Override public int getSize() { return getSize(selectedMethod); } @Override public Object getElementAt(int index) { if (selectedMethod == null || !listByWay.containsKey(selectedMethod.getWay())) return null; return listByWay.get(selectedMethod.getWay()).get(index); } @Override public void setSelectedItem(Object anItem) { int indexOf = listByWay.get(selectedMethod.getWay()).indexOf(anItem); if (indexOf >= 0) { selected = listByWay.get(selectedMethod.getWay()).get(indexOf); fireMultChanged(this); fireContentsChanged(this, -1, -1); } } @Override public Object getSelectedItem() { return selected; } // does not fire mult changed private void fireMethodChanged(MethodModel source, Method previousMethod) { int start = 0; int end = getSize(); if (previousMethod != null) { String previousWay = previousMethod.getWay(); String selectedWay = selectedMethod.getWay(); if (previousWay.equals(selectedWay)) return; // handle selection. keep same index if not too big, else selection becomes 0 List<Localized> previousList = listByWay.get(previousWay); int previousIndex = previousList.indexOf(selected); List<Localized> selectedList = listByWay.get(selectedWay); if (selectedList == null) { Logging.errorPrintLocalised(UNKNOWN_WAY_0_PLEASE_FIX_1_XML, selectedWay, name); // XXX do something else? return; } if (selectedMethod != null) { if (previousIndex < end && previousIndex >= 0) selected = selectedList.get(previousIndex); else selected = selectedList.get(0); } // handle firing change event int previousSize = getSize(previousMethod); if (end > previousSize) { fireIntervalAdded(source, previousSize, end - 1); } if (end < previousSize) { fireIntervalRemoved(source, end, previousSize - 1); } end = Math.min(end, previousSize); // increment start for each identical element at the start of the paces lists for (int i = 0; i < end && previousList.get(i).equals(selectedList.get(i)); i++, start++) ; // decrement end for each identical element at the end of the paces lists for (int i = end - 1; i > start && previousList.get(i).equals(selectedList.get(i)); i--, end--) ; if (start != end) { fireContentsChanged(source, start, end - 1); } else if (previousIndex >= getSize()) { // indicates that the selection has changed as in #setSelectedItem fireContentsChanged(source, -1, -1); } } else { fireIntervalAdded(source, 0, end - 1); } } } /** * Used for Pace and Choice. * * @param <T> class * @see PaceModel * @see ChoiceModel */ @SuppressWarnings("serial") protected abstract class TModel<T> extends AbstractListModel implements ComboBoxModel { private T selected; @Override public int getSize() { if (selectedMethod == null) return 0; return getList(selectedMethod).size(); } abstract List<T> getList(Method m); @Override public Object getElementAt(int index) { if (selectedMethod == null || index < 0 || getList(selectedMethod).size() <= index) return null; return getList(selectedMethod).get(index); } @Override public void setSelectedItem(Object anItem) { int indexOf = getList(selectedMethod).indexOf(anItem); if (indexOf >= 0) { selected = getList(selectedMethod).get(indexOf); fireUnmodifiableSpeedChanged(this); fireContentsChanged(this, -1, -1); } } @Override public Object getSelectedItem() { return selected; } /** * Method called when the selected method changes. * @param methodModel */ protected void fireMethodChanged(MethodModel source, Method previousMethod) { int start = 0; int end = getSize(); if (previousMethod != null) { // handle selection. keep same index if not too big, else selection becomes 0 List<T> previousList = getList(previousMethod); List<T> selectedList = getList(selectedMethod); int previousIndex = previousList.indexOf(selected); if (selectedMethod != null) { if (previousIndex < end && previousIndex >= 0) selected = selectedList.get(previousIndex); else selected = selectedList.get(0); } // handle firing change event int previousSize = previousList.size(); if (end > previousSize) { fireIntervalAdded(source, previousSize, end); } if (end < previousSize) { fireIntervalRemoved(source, end, previousSize); } end = Math.min(end, previousSize); // increment start for each identical element at the start of the paces lists for (int i = 0; i < end && previousList.get(i).equals(selectedList.get(i)); i++, start++) ; // decrement end for each identical element at the end of the paces lists for (int i = end - 1; i > start && previousList.get(i).equals(selectedList.get(i)); i--, end--) ; if (start != end) { fireContentsChanged(source, start, end - 1); } else if (previousIndex >= getSize()) { // indicates that the selection has changed as in #setSelectedItem fireContentsChanged(source, -1, -1); } } else { fireIntervalAdded(source, 0, end); } } T getSelected() { return selected; } } class PaceModel extends TModel<Pace> { private static final long serialVersionUID = 8980884569594225313L; @Override List<Pace> getList(Method m) { return m.paces; } @Override public void setSelectedItem(Object anItem) { super.setSelectedItem(anItem); fireCommentDaysChanged(this); } } class ChoiceModel extends TModel<Choice> { private static final long serialVersionUID = 3502580215371087556L; @Override List<Choice> getList(Method m) { return m.choices; } } }