/** * Copyright (C) 2002-2012 The FreeCol Team * * This file is part of FreeCol. * * FreeCol 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 2 of the License, or * (at your option) any later version. * * FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.freecol.client.gui.panel; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.freecolandroid.repackaged.java.awt.Component; import org.freecolandroid.repackaged.java.awt.GridLayout; import org.freecolandroid.repackaged.java.awt.Image; import org.freecolandroid.repackaged.java.awt.datatransfer.DataFlavor; import org.freecolandroid.repackaged.java.awt.datatransfer.Transferable; import org.freecolandroid.repackaged.java.awt.datatransfer.UnsupportedFlavorException; import org.freecolandroid.repackaged.java.awt.event.ActionEvent; import org.freecolandroid.repackaged.java.awt.event.ActionListener; import org.freecolandroid.repackaged.java.awt.event.MouseListener; import org.freecolandroid.repackaged.javax.swing.BorderFactory; import org.freecolandroid.repackaged.javax.swing.DefaultListModel; import org.freecolandroid.repackaged.javax.swing.ImageIcon; import org.freecolandroid.repackaged.javax.swing.JButton; import org.freecolandroid.repackaged.javax.swing.JComboBox; import org.freecolandroid.repackaged.javax.swing.JComponent; import org.freecolandroid.repackaged.javax.swing.JLabel; import org.freecolandroid.repackaged.javax.swing.JList; import org.freecolandroid.repackaged.javax.swing.JPanel; import org.freecolandroid.repackaged.javax.swing.JScrollPane; import org.freecolandroid.repackaged.javax.swing.JTextField; import org.freecolandroid.repackaged.javax.swing.ListCellRenderer; import org.freecolandroid.repackaged.javax.swing.TransferHandler; import org.freecolandroid.repackaged.javax.swing.event.ListSelectionEvent; import org.freecolandroid.repackaged.javax.swing.event.ListSelectionListener; import org.freecolandroid.repackaged.javax.swing.plaf.PanelUI; import net.miginfocom.swing.MigLayout; import net.sf.freecol.client.FreeColClient; import net.sf.freecol.client.gui.GUI; import net.sf.freecol.client.gui.i18n.Messages; import net.sf.freecol.client.gui.plaf.FreeColSelectedPanelUI; import net.sf.freecol.common.model.Colony; import net.sf.freecol.common.model.Europe; import net.sf.freecol.common.model.GoodsType; import net.sf.freecol.common.model.Location; import net.sf.freecol.common.model.Player; import net.sf.freecol.common.model.Settlement; import net.sf.freecol.common.model.StringTemplate; import net.sf.freecol.common.model.TradeRoute; import net.sf.freecol.common.model.TradeRoute.Stop; /** * Allows the user to edit trade routes. */ public final class TradeRouteInputDialog extends FreeColDialog<Boolean> implements ActionListener { private static final Logger logger = Logger.getLogger(TradeRouteInputDialog.class.getName()); public static final DataFlavor STOP_FLAVOR = new DataFlavor(Stop.class, "Stop"); private TradeRoute originalRoute; private final JButton addStopButton = new JButton(Messages.message("traderouteDialog.addStop")); private final JButton removeStopButton = new JButton(Messages.message("traderouteDialog.removeStop")); private final CargoHandler cargoHandler = new CargoHandler(); private final MouseListener dragListener = new DragListener(getFreeColClient(), getGUI(), this); private final MouseListener dropListener = new DropListener(); private final GoodsPanel goodsPanel; private final CargoPanel cargoPanel; private final JComboBox destinationSelector = new JComboBox(); private final JTextField tradeRouteName = new JTextField(Messages.message("traderouteDialog.newRoute")); private final DefaultListModel listModel = new DefaultListModel(); private final JList stopList = new JList(listModel); private final JScrollPane tradeRouteView = new JScrollPane(stopList); private final JLabel nameLabel = new JLabel(Messages.message("traderouteDialog.nameLabel")); private final JLabel destinationLabel = new JLabel(Messages.message("traderouteDialog.destinationLabel")); /** * The constructor that will add the items to this panel. * * @param parent The parent of this panel. */ public TradeRouteInputDialog(FreeColClient freeColClient, GUI gui, TradeRoute newRoute) { super(freeColClient, gui); originalRoute = newRoute; goodsPanel = new GoodsPanel(); goodsPanel.setTransferHandler(cargoHandler); cargoPanel = new CargoPanel(); cargoPanel.setTransferHandler(cargoHandler); stopList.setCellRenderer(new StopRenderer()); stopList.setFixedCellHeight(48); stopList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { updateButtons(); } }); // button for adding new Stop addStopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int startIndex = -1; int endIndex = -1; if (destinationSelector.getSelectedIndex() == 0 ) { // All colonies + Europe startIndex = 1; endIndex = destinationSelector.getItemCount() - 1; } else { // just 1 colony startIndex = destinationSelector.getSelectedIndex(); endIndex = startIndex; } List<GoodsType> cargo = new ArrayList<GoodsType>(); for (Component comp : cargoPanel.getComponents()) { CargoLabel label = (CargoLabel) comp; cargo.add(label.getType()); } int maxIndex = stopList.getMaxSelectionIndex(); for (int i = startIndex; i <= endIndex; i++) { Stop stop = originalRoute.new Stop((Location) destinationSelector.getItemAt(i)); stop.setCargo(cargo); if (maxIndex < 0) { listModel.addElement(stop); } else { maxIndex++; listModel.add(maxIndex, stop); } } } }); // button for deleting Stop removeStopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int count = 0; for (int index : stopList.getSelectedIndices()) { listModel.remove(index - count); count++; } } }); stopList.setDragEnabled(true); stopList.setTransferHandler(new StopHandler()); stopList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { int[] indices = stopList.getSelectedIndices(); if (indices.length > 0) { cargoPanel.initialize((Stop) listModel.get(indices[0])); } } } }); setLayout(new MigLayout("wrap 4, fill", "[]20[fill]rel")); add(getDefaultHeader(Messages.message("traderouteDialog.editRoute")), "span, align center"); add(tradeRouteView, "span 1 5, grow"); add(nameLabel); add(tradeRouteName, "span"); add(destinationLabel); add(destinationSelector, "span"); add(addStopButton, "skip 2"); add(removeStopButton); add(goodsPanel, "span"); add(cargoPanel, "span, height 80:, growy"); add(okButton, "newline 20, span, split 2, tag ok"); add(cancelButton, "tag cancel"); TradeRoute tradeRoute = newRoute.clone(); Player player = getMyPlayer(); // combo box for selecting destination destinationSelector.addItem(Messages.message(StringTemplate.template("report.allColonies") .addName("%number%", ""))); if (player.getEurope() != null) { destinationSelector.addItem(player.getEurope()); } for (Colony colony : getSortedColonies()) { destinationSelector.addItem((Settlement) colony); } // add stops if any for (Stop stop : tradeRoute.getStops()) { listModel.addElement(stop); } // update cargo panel if stop is selected if (listModel.getSize() > 0) { stopList.setSelectedIndex(0); Stop selectedStop = (Stop) listModel.firstElement(); cargoPanel.initialize(selectedStop); } // update buttons according to selection updateButtons(); // set name of trade route tradeRouteName.setText(tradeRoute.getName()); restoreSavedSize(getPreferredSize()); } /** * Enables the remove stop button if a stop is selected and disables it * otherwise. */ public void updateButtons() { if (stopList.getSelectedIndices().length == 0) { removeStopButton.setEnabled(false); } else { removeStopButton.setEnabled(true); } } /** * Check that the trade route is valid. * * @return True if the trade route is valid. */ private boolean verifyNewTradeRoute() { Player player = getFreeColClient().getMyPlayer(); // Check that the name is unique for (TradeRoute route : player.getTradeRoutes()) { if (route.getId().equals(originalRoute.getId())) continue; if (route.getName().equals(tradeRouteName.getText())) { getGUI().errorMessage("traderouteDialog.duplicateName"); return false; } } // Verify that it has at least two stops if (listModel.getSize() < 2) { getGUI().errorMessage("traderouteDialog.notEnoughStops"); return false; } // Check that all stops are valid for (int index = 0; index < listModel.getSize(); index++) { Stop stop = (Stop) listModel.get(index); if (!TradeRoute.isStopValid(player, stop)) { return false; } } return true; } /** * This function analyses an event and calls the right methods to take care * of the user's requests. * * @param event The incoming ActionEvent. */ public void actionPerformed(ActionEvent event) { String command = event.getActionCommand(); if (OK.equals(command)) { if (verifyNewTradeRoute()) { getGUI().removeFromCanvas(this); originalRoute.setName(tradeRouteName.getText()); ArrayList<Stop> stops = new ArrayList<Stop>(); for (int index = 0; index < listModel.getSize(); index++) { stops.add((Stop) listModel.get(index)); } originalRoute.setStops(stops); // TODO: update trade routes only if they have been modified getController().updateTradeRoute(originalRoute); setResponse(Boolean.TRUE); } } else if (CANCEL.equals(command)) { getGUI().removeFromCanvas(this); setResponse(Boolean.FALSE); } else { super.actionPerformed(event); } } /** * Special label for goods type. * * TODO: clean this up for 0.7.0 -- The GoodsLabel needs to be modified so * that it can act as a GoodsTypeLabel (like this label), an * AbstractGoodsLabel and a CargoLabel (its current function). */ public class CargoLabel extends JLabel { private final GoodsType goodsType; public CargoLabel(GoodsType type) { super(getLibrary().getGoodsImageIcon(type)); setTransferHandler(cargoHandler); addMouseListener(dragListener); this.goodsType = type; } public GoodsType getType() { return this.goodsType; } } /** * Panel for all types of goods that can be loaded onto a carrier. */ public class GoodsPanel extends JPanel { public GoodsPanel() { super(new GridLayout(0, 4, margin, margin)); for (GoodsType goodsType : getSpecification().getGoodsTypeList()) { if (goodsType.isStorable()) { CargoLabel label = new CargoLabel(goodsType); add(label); } } setOpaque(false); setBorder(BorderFactory.createTitledBorder(Messages.message("goods"))); addMouseListener(dropListener); } } /** * Panel for the cargo the carrier is supposed to take on board at a certain * stop. * * TODO: create a single cargo panel for this purpose and the use in the * ColonyPanel, the EuropePanel and the CaptureGoodsDialog. */ public class CargoPanel extends JPanel { public CargoPanel() { super(); setOpaque(false); setBorder(BorderFactory.createTitledBorder(Messages.message("cargoOnCarrier"))); addMouseListener(dropListener); } public void initialize(Stop newStop) { removeAll(); if (newStop != null) { // stop = newStop; for (GoodsType goodsType : newStop.getCargo()) { add(new CargoLabel(goodsType)); } } revalidate(); repaint(); } } /** * TransferHandler for CargoLabels. * * TODO: check whether this could/should be folded into the * DefaultTransferHandler. */ public class CargoHandler extends TransferHandler { protected Transferable createTransferable(JComponent c) { return new ImageSelection((CargoLabel) c); } public int getSourceActions(JComponent c) { return COPY_OR_MOVE; } public boolean importData(JComponent target, Transferable data) { if (canImport(target, data.getTransferDataFlavors())) { try { CargoLabel label = (CargoLabel) data.getTransferData(DefaultTransferHandler.flavor); if (target instanceof CargoPanel) { CargoLabel newLabel = new CargoLabel(label.getType()); cargoPanel.add(newLabel); cargoPanel.revalidate(); int[] indices = stopList.getSelectedIndices(); for (int index : indices) { Stop stop = (Stop) listModel.get(index); stop.addCargo(label.getType()); stop.setModified(true); } stopList.revalidate(); stopList.repaint(); } return true; } catch (UnsupportedFlavorException ufe) { logger.warning(ufe.toString()); } catch (Exception ioe) { logger.warning(ioe.toString()); } } return false; } protected void exportDone(JComponent source, Transferable data, int action) { try { CargoLabel label = (CargoLabel) data.getTransferData(DefaultTransferHandler.flavor); if (source.getParent() instanceof CargoPanel) { cargoPanel.remove(label); int[] indices = stopList.getSelectedIndices(); for (int stopIndex : indices) { Stop stop = (Stop) listModel.get(stopIndex); ArrayList<GoodsType> cargo = new ArrayList<GoodsType>(stop.getCargo()); for (int index = 0; index < cargo.size(); index++) { if (cargo.get(index) == label.getType()) { cargo.remove(index); stop.setModified(true); break; } } stop.setCargo(cargo); } stopList.revalidate(); stopList.repaint(); cargoPanel.revalidate(); cargoPanel.repaint(); } } catch (UnsupportedFlavorException ufe) { logger.warning(ufe.toString()); } catch (Exception ioe) { logger.warning(ioe.toString()); } } public boolean canImport(JComponent c, DataFlavor[] flavors) { for (int i = 0; i < flavors.length; i++) { if (flavors[i].equals(DefaultTransferHandler.flavor)) { return true; } } return false; } } public class StopTransferable implements Transferable { private List<Stop> stops; public StopTransferable(List<Stop> stops) { this.stops = stops; } public Object getTransferData(DataFlavor flavor) { return stops; } public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[] { STOP_FLAVOR }; } public boolean isDataFlavorSupported(DataFlavor flavor) { return flavor == STOP_FLAVOR; } } /* * TransferHandler for Stops. */ public class StopHandler extends TransferHandler { protected Transferable createTransferable(JComponent c) { JList list = (JList) c; DefaultListModel model = (DefaultListModel) list.getModel(); List<Stop> stops = new ArrayList<Stop>(); for (int index : list.getSelectedIndices()) { stops.add((Stop) model.get(index)); } return new StopTransferable(stops); } public int getSourceActions(JComponent c) { return MOVE; } public boolean importData(JComponent target, Transferable data) { if (canImport(target, data.getTransferDataFlavors())) { try { List stops = (List) data.getTransferData(STOP_FLAVOR); if (target instanceof JList) { JList list = (JList) target; DefaultListModel model = (DefaultListModel) list.getModel(); int index = list.getMaxSelectionIndex(); for (Object o : stops) { Stop stop = originalRoute.new Stop((Stop) o); if (index < 0) { model.addElement(stop); } else { index++; model.add(index, stop); } } } return true; } catch (UnsupportedFlavorException ufe) { logger.warning(ufe.toString()); } catch (Exception ioe) { logger.warning(ioe.toString()); } } return false; } protected void exportDone(JComponent source, Transferable data, int action) { try { if (source instanceof JList && action == MOVE) { JList stopList = (JList) source; DefaultListModel listModel = (DefaultListModel) stopList.getModel(); for (Object o : (List) data.getTransferData(STOP_FLAVOR)) { listModel.removeElement(o); } } } catch (Exception e) { logger.warning(e.toString()); } } public boolean canImport(JComponent c, DataFlavor[] flavors) { for (int i = 0; i < flavors.length; i++) { if (flavors[i].equals(STOP_FLAVOR)) { return true; } } return false; } } private class StopRenderer implements ListCellRenderer { private final JPanel SELECTED_COMPONENT = new JPanel(); private final JPanel NORMAL_COMPONENT = new JPanel(); public StopRenderer() { NORMAL_COMPONENT.setLayout(new MigLayout("", "[80, center][]")); NORMAL_COMPONENT.setOpaque(false); SELECTED_COMPONENT.setLayout(new MigLayout("", "[80, center][]")); SELECTED_COMPONENT.setOpaque(false); SELECTED_COMPONENT.setUI((PanelUI) FreeColSelectedPanelUI.createUI(SELECTED_COMPONENT)); } /** * Returns a <code>ListCellRenderer</code> for the given <code>JList</code>. * * @param list The <code>JList</code>. * @param value The list cell. * @param index The index in the list. * @param isSelected <code>true</code> if the given list cell is selected. * @param hasFocus <code>false</code> if the given list cell has the focus. * @return The <code>ListCellRenderer</code> */ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) { JPanel panel = (isSelected ? SELECTED_COMPONENT : NORMAL_COMPONENT); panel.removeAll(); panel.setForeground(list.getForeground()); panel.setFont(list.getFont()); Stop stop = (Stop) value; Location location = stop.getLocation(); JLabel icon, name; if (location instanceof Europe) { Europe europe = (Europe) location; Image image = getLibrary().getCoatOfArmsImage(europe.getOwner().getNation(), 0.5); icon = new JLabel(new ImageIcon(image)); name = localizedLabel(europe.getNameKey()); } else if (location instanceof Colony) { Colony colony = (Colony) location; icon = new JLabel(new ImageIcon(getLibrary().getSettlementImage(colony, 0.5))); name = new JLabel(colony.getName()); } else { throw new IllegalStateException("Bogus location: " + location); } panel.add(icon, "spany"); panel.add(name, "span, wrap"); for (GoodsType cargo : stop.getCargo()) { panel.add(new JLabel(new ImageIcon(getLibrary().getGoodsImage(cargo, 0.5)))); } return panel; } } }