All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.vergne.translation.editor.MapListPanel Maven / Gradle / Ivy

package fr.vergne.translation.editor;

import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;
import java.util.logging.Logger;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import fr.vergne.translation.TranslationEntry;
import fr.vergne.translation.TranslationMap;
import fr.vergne.translation.TranslationProject;
import fr.vergne.translation.editor.ListModel.MapsChangedListener;
import fr.vergne.translation.editor.content.MapCellRenderer;
import fr.vergne.translation.editor.content.MapTreeNode;
import fr.vergne.translation.editor.tool.ToolProvider;
import fr.vergne.translation.impl.EmptyProject;
import fr.vergne.translation.impl.NoTranslationFilter;
import fr.vergne.translation.util.EntryFilter;
import fr.vergne.translation.util.MapInformer;
import fr.vergne.translation.util.MapInformer.MapSummaryListener;
import fr.vergne.translation.util.MapInformer.NoDataException;

@SuppressWarnings("serial")
public class MapListPanel, TMap extends TranslationMap, MapID, TProject extends TranslationProject>
		extends JPanel {

	public static final Logger logger = Logger.getLogger(MapListPanel.class
			.getName());
	private final JTree tree;
	private final Map mapSummaries = Collections
			.synchronizedMap(new HashMap());
	private final Collection> mapSummaryListeners = new HashSet>();
	private final TreeSet currentIDs = new TreeSet(
			new Comparator() {

				@Override
				public int compare(MapID id1, MapID id2) {
					return id1.toString().compareToIgnoreCase(id2.toString());
				}
			});
	private TranslationProject currentProject = new EmptyProject<>();
	private final Collection> listeners = new HashSet<>();

	public MapListPanel(final ToolProvider toolProvider) {
		final MapInformer mapInformer = new MapInformer() {

			@Override
			public String getName(MapID mapId) throws NoDataException {
				return toolProvider.getMapNamer().getNameFor(mapId);
			}

			@Override
			public int getEntriesCount(MapID id) throws NoDataException {
				MapSummary mapSummary = mapSummaries.get(id);
				if (mapSummary == null) {
					throw new NoDataException();
				} else {
					return mapSummary.total;
				}
			}

			@Override
			public int getEntriesRemaining(MapID id) throws NoDataException {
				MapSummary mapSummary = mapSummaries.get(id);
				if (mapSummary == null) {
					throw new NoDataException();
				} else {
					Integer remaining = mapSummary.remaining
							.get(remainingFilter);
					if (remaining == null) {
						throw new NoDataException();
					} else {
						return remaining;
					}
				}
			}

			@Override
			public boolean isModified(MapID id) throws NoDataException {
				MapSummary mapSummary = mapSummaries.get(id);
				if (mapSummary == null) {
					throw new NoDataException();
				} else {
					return mapSummary.isModified;
				}
			}

			@Override
			public int getAllEntriesCount() throws NoDataException {
				int total = 0;
				for (MapID id : currentIDs) {
					MapSummary summary = mapSummaries.get(id);
					if (summary == null) {
						throw new NoDataException();
					} else {
						total += summary.total;
					}
				}
				return total;
			}

			@Override
			public int getAllEntriesRemaining() throws NoDataException {
				int count = 0;
				for (MapID id : currentIDs) {
					MapSummary summary = mapSummaries.get(id);
					if (summary == null) {
						throw new NoDataException();
					} else {
						Integer remaining = summary.remaining
								.get(remainingFilter);
						if (remaining == null) {
							throw new NoDataException();
						} else {
							count += remaining;
						}
					}
				}
				return count;
			}

			@Override
			public void addMapSummaryListener(MapSummaryListener listener) {
				mapSummaryListeners.add(listener);
			}

			@Override
			public void removeMapSummaryListener(
					MapSummaryListener listener) {
				mapSummaryListeners.remove(listener);
			}
		};

		setLayout(new GridLayout(1, 1));
		tree = buildTreeComponent(mapInformer);
		add(new JScrollPane(tree));

		configureBackgroundSummarizing();
	}

	private JTree buildTreeComponent(MapInformer mapInformer) {
		final ListModel listModel = new ListModel(mapInformer);
		final JTree tree = new JTree(listModel);

		MapCellRenderer cellRenderer = new MapCellRenderer(
				tree.getCellRenderer(), mapInformer);
		tree.setCellRenderer(cellRenderer);

		tree.setRootVisible(true);
		tree.getSelectionModel().setSelectionMode(
				TreeSelectionModel.SINGLE_TREE_SELECTION);
		final TreePath[] selection = new TreePath[1];
		tree.addTreeSelectionListener(new TreeSelectionListener() {

			@Override
			public void valueChanged(TreeSelectionEvent event) {
				TreePath[] paths = event.getPaths();
				for (TreePath path : paths) {
					if (event.isAddedPath(path)) {
						selection[0] = path;
					} else {
						// do not change
					}
				}
			}
		});
		listModel.addTreeModelListener(new TreeModelListener() {

			private void recoverSelection() {
				/*
				 * Invoke the recovery later in order to ensure it is done after
				 * any other action on the tree, otherwise the selection could
				 * be lost due to another action made done the recovery.
				 */
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						if (selection[0] != null) {
							TreePath path = selection[0];
							Object selectedNode = path.getLastPathComponent();
							if (selectedNode instanceof MapTreeNode) {
								@SuppressWarnings("unchecked")
								MapID id = ((MapTreeNode) selectedNode)
										.getMapID();
								Collection ids = listModel
										.getCurrentMapIDs();
								if (ids.contains(id)) {
									tree.clearSelection();
									tree.setSelectionPath(selection[0]);
								} else {
									// still present
								}
							} else {
								// not a map, ignore its selection
							}
						} else {
							// no selection to recover
						}
					}
				});
			}

			@Override
			public void treeStructureChanged(TreeModelEvent e) {
				recoverSelection();
			}

			@Override
			public void treeNodesRemoved(TreeModelEvent e) {
				recoverSelection();
			}

			@Override
			public void treeNodesInserted(TreeModelEvent e) {
				recoverSelection();
			}

			@Override
			public void treeNodesChanged(TreeModelEvent e) {
				recoverSelection();
			}
		});
		tree.addMouseListener(new MouseListener() {

			@Override
			public void mouseReleased(MouseEvent event) {
				// nothing to do
			}

			@Override
			public void mousePressed(MouseEvent event) {
				// nothing to do
			}

			@Override
			public void mouseExited(MouseEvent event) {
				// nothing to do
			}

			@Override
			public void mouseEntered(MouseEvent event) {
				// nothing to do
			}

			@Override
			public void mouseClicked(MouseEvent event) {
				synchronized (mapSummaries) {
					if (event.getButton() == MouseEvent.BUTTON1
							&& event.getClickCount() == 2) {
						MapID id = getSelectedID(tree);
						if (id != null) {
							updateMapSummary(id, false);
							for (MapSelectedListener listener : listeners) {
								listener.mapSelected(id);
							}
						} else {
							// nothing to do
						}
					} else {
						// nothing to do for single click
					}
				}
			}

		});
		tree.addKeyListener(new KeyListener() {

			@Override
			public void keyTyped(KeyEvent arg0) {
				// nothing to do
			}

			@Override
			public void keyReleased(KeyEvent arg0) {
				int keyCode = arg0.getKeyCode();
				if (keyCode == KeyEvent.VK_F5) {
					MapID id = getSelectedID(tree);
					if (id != null) {
						updateMapSummary(id, true);
					} else {
						// nothing to do
					}
				} else {
					// no action for other keys
				}
			}

			@Override
			public void keyPressed(KeyEvent arg0) {
				// nothing to do
			}
		});
		return tree;
	}

	private MapID getSelectedID(final JTree tree) {
		Object source = tree.getSelectionPath().getLastPathComponent();
		if (source instanceof MapTreeNode) {
			@SuppressWarnings("unchecked")
			MapTreeNode node = (MapTreeNode) source;
			MapID id = node.getMapID();
			return id;
		} else {
			return null;
		}
	}

	private void configureBackgroundSummarizing() {
		// sense the app closing
		final boolean[] isClosed = { false };
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				JFrame frame = (JFrame) SwingUtilities
						.getWindowAncestor(MapListPanel.this);
				if (frame == null) {
					SwingUtilities.invokeLater(this);
				} else {
					frame.addWindowListener(new WindowListener() {

						@Override
						public void windowOpened(WindowEvent arg0) {
							// do nothing
						}

						@Override
						public void windowIconified(WindowEvent arg0) {
							// do nothing
						}

						@Override
						public void windowDeiconified(WindowEvent arg0) {
							// do nothing
						}

						@Override
						public void windowDeactivated(WindowEvent arg0) {
							// do nothing
						}

						@Override
						public void windowClosing(WindowEvent arg0) {
							// do nothing
						}

						@Override
						public void windowClosed(WindowEvent arg0) {
							isClosed[0] = true;
						}

						@Override
						public void windowActivated(WindowEvent arg0) {
							// do nothing
						}

					});
				}
			}
		});

		// create the background task
		final boolean[] isRunning = { false };
		final Runnable backgroundSummary = new Runnable() {

			@Override
			public void run() {
				try {
					isRunning[0] = true;
					MapID id;
					if (isClosed[0] || (id = getWaitingMap()) == null) {
						isRunning[0] = false;
					} else {
						updateMapSummary(id, false);
						SwingUtilities.invokeLater(this);
					}
				} catch (Exception e) {
					isRunning[0] = false;
					throw new RuntimeException(e);
				}
			}

			private MapID getWaitingMap() {
				@SuppressWarnings("unchecked")
				ListModel model = (ListModel) tree.getModel();
				MapInformer mapInformer = model.getMapInformer();

				Iterator iterator = model.getCurrentMapIDs().iterator();
				while (iterator.hasNext()) {
					MapID id = iterator.next();
					try {
						mapInformer.getEntriesCount(id);
					} catch (NoDataException e) {
						return id;
					}
				}

				Iterator iterator2 = model.getAllMapsIDs().iterator();
				while (iterator2.hasNext()) {
					MapID id = iterator2.next();
					try {
						mapInformer.getEntriesCount(id);
					} catch (NoDataException e) {
						return id;
					}
				}

				return null;
			}
		};

		// sense the necessity to launch the task
		@SuppressWarnings("unchecked")
		ListModel model = (ListModel) tree.getModel();
		model.addMapsChangedListener(new MapsChangedListener() {

			@Override
			public void mapsChanged() {
				if (isRunning[0]) {
					// already running
				} else {
					SwingUtilities.invokeLater(backgroundSummary);
				}
			}
		});
	}

	@SuppressWarnings("unchecked")
	public void setProject(
			TranslationProject currentProject) {
		this.currentProject = currentProject;

		Collection newIDs = new LinkedList<>();
		for (MapID id : currentProject) {
			newIDs.add(id);
		}

		if (currentIDs.containsAll(newIDs) && newIDs.containsAll(currentIDs)) {
			// same IDs, don't change
		} else {
			Collection removed = new LinkedList(currentIDs);
			removed.removeAll(newIDs);
			mapSummaries.keySet().removeAll(removed);
			currentIDs.clear();
			currentIDs.addAll(newIDs);

			((ListModel) tree.getModel()).setMaps(currentIDs);
		}
	}

	public TranslationProject getProject() {
		return currentProject;
	}

	public void updateMapSummary(final MapID id, boolean force) {
		synchronized (mapSummaries) {
			if (!force && mapSummaries.get(id) != null) {
				// nothing to load
			} else {
				logger.finest("Summarizing " + id + "...");
				MapSummary summary = new MapSummary();
				TMap map = currentProject.getMap(id);
				summary.total = map.size();
				Collection> filters = new LinkedHashSet<>(
						currentProject.getEntryFilters());
				EntryFilter nullFilter = new NoTranslationFilter<>();
				filters.add(nullFilter);
				for (EntryFilter filter : filters) {
					summary.remaining.put(filter, 0);
				}

				Iterator iterator = map.iterator();
				while (iterator.hasNext()) {
					TEntry entry = iterator.next();
					for (EntryFilter filter : filters) {
						if (filter.isRelevant(entry)) {
							int remaining = summary.remaining.get(filter);
							remaining++;
							summary.remaining.put(filter, remaining);
						} else {
							// do not count it
						}
					}
				}
				summary.remaining.put(null, summary.remaining.get(nullFilter));
				mapSummaries.put(id, summary);
				logger.finest("Map summarized: " + summary);

				for (MapSummaryListener listener : mapSummaryListeners) {
					listener.mapSummarized(id);
				}
			}
		}
	}

	private static class MapSummary {
		int total;
		final Map, Integer> remaining = new HashMap<>();
		boolean isModified;

		@Override
		public String toString() {
			String status = isModified ? "modified" : "saved";
			return remaining + "/" + total + " (" + status + ")";
		}
	}

	public void addMapSelectedListener(MapSelectedListener listener) {
		listeners.add(listener);
	}

	public void removeMapSelectedListener(MapSelectedListener listener) {
		listeners.remove(listener);
	}

	public static interface MapSelectedListener {
		public void mapSelected(MapID id);
	}

	public void setModifiedStatus(MapID id, boolean isModified) {
		mapSummaries.get(id).isModified = isModified;
		for (MapSummaryListener listener : mapSummaryListeners) {
			listener.mapSummarized(id);
		}
	}

	@SuppressWarnings("unchecked")
	public void requestUpdate() {
		((ListModel) tree.getModel()).requestUpdate();
	}

	@SuppressWarnings("unchecked")
	public void setClearedDisplayed(boolean isDisplayed) {
		((ListModel) tree.getModel()).setClearedDisplayed(isDisplayed);
	}

	private EntryFilter remainingFilter = null;

	public void setRemainingFilter(EntryFilter remainingFilter) {
		this.remainingFilter = remainingFilter;
		requestUpdate();
	}

	public EntryFilter getRemainingFilter() {
		return remainingFilter;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy