/* This file is part of RouteConverter. RouteConverter 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. RouteConverter 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 RouteConverter; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Copyright (C) 2007 Christian Pesch. All Rights Reserved. */ package slash.navigation.converter.gui.panels; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; import com.intellij.uiDesigner.core.Spacer; import slash.common.helpers.TimeZoneAndId; import slash.common.helpers.TimeZoneAndIds; import slash.navigation.base.Wgs84Position; import slash.navigation.base.Wgs84Route; import slash.navigation.common.NavigationPosition; import slash.navigation.common.SimpleNavigationPosition; import slash.navigation.converter.gui.RouteConverter; import slash.navigation.converter.gui.actions.AddPhotosAction; import slash.navigation.converter.gui.actions.DeletePositionAction; import slash.navigation.converter.gui.actions.TagPhotosAction; import slash.navigation.converter.gui.dnd.PanelDropHandler; import slash.navigation.converter.gui.helpers.PhotosTableHeaderMenu; import slash.navigation.converter.gui.helpers.PhotosTablePopupMenu; import slash.navigation.converter.gui.helpers.TagStrategy; import slash.navigation.converter.gui.models.FilteringPositionsModel; import slash.navigation.converter.gui.models.OverlayPositionsModel; import slash.navigation.converter.gui.models.PhotoTagStateToJLabelAdapter; import slash.navigation.converter.gui.models.PhotosTableColumnModel; import slash.navigation.converter.gui.models.PositionTableColumn; import slash.navigation.converter.gui.models.PositionsModel; import slash.navigation.converter.gui.models.PositionsModelImpl; import slash.navigation.converter.gui.predicates.FilterPredicate; import slash.navigation.converter.gui.predicates.TagStatePhotoPredicate; import slash.navigation.converter.gui.predicates.TautologyPredicate; import slash.navigation.converter.gui.renderer.DescriptionColumnTableCellEditor; import slash.navigation.converter.gui.renderer.FilterPredicateListCellRenderer; import slash.navigation.converter.gui.renderer.TagStrategyListCellRenderer; import slash.navigation.converter.gui.renderer.TimeZoneAndIdListCellRenderer; import slash.navigation.gui.actions.ActionManager; import slash.navigation.gui.actions.FrameAction; import slash.navigation.photo.PhotoFormat; import slash.navigation.photo.PhotoPosition; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import java.util.prefs.Preferences; import static java.awt.event.ItemEvent.SELECTED; import static java.awt.event.KeyEvent.VK_DELETE; import static java.lang.Integer.MAX_VALUE; import static javax.help.CSH.setHelpIDString; import static javax.swing.DropMode.ON; import static javax.swing.JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT; import static javax.swing.KeyStroke.getKeyStroke; import static javax.swing.event.TableModelEvent.ALL_COLUMNS; import static slash.navigation.base.RouteCharacteristics.Waypoints; import static slash.navigation.converter.gui.helpers.TagStrategy.Create_Backup_In_Subdirectory; import static slash.navigation.converter.gui.helpers.TagStrategy.Create_Tagged_Photo_In_Subdirectory; import static slash.navigation.converter.gui.models.LocalActionConstants.PHOTOS; import static slash.navigation.converter.gui.models.PositionColumns.EXIF_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.GPS_COLUMN_INDEX; import static slash.navigation.converter.gui.models.PositionColumns.PHOTO_COLUMN_INDEX; import static slash.navigation.gui.helpers.JMenuHelper.registerAction; import static slash.navigation.gui.helpers.JTableHelper.calculateRowHeight; import static slash.navigation.photo.TagState.NotTaggable; import static slash.navigation.photo.TagState.Taggable; import static slash.navigation.photo.TagState.Tagged; /** * The Photos panel of the route converter user interface. * * @author Christian Pesch */ public class PhotoPanel implements PanelInTab { private static final Preferences preferences = Preferences.userNodeForPackage(ConvertPanel.class); private static final String FILTER_PHOTO_PREDICATE_PREFERENCE = "filterPhotoPredicate"; private static final int ROW_HEIGHT_FOR_PHOTO_COLUMN = 200; private JPanel photosPanel; private JTable tablePhotos; private JLabel labelPhotos; private JComboBox<FilterPredicate> comboBoxFilterPhotoPredicate; private JButton buttonAddPhotos; private JComboBox<TimeZoneAndId> comboBoxPhotoTimeZone; private JComboBox<TagStrategy> comboBoxTagStrategy; private JButton buttonTagPhotos; private static final ComboBoxModel<FilterPredicate> FILTER_PREDICATE_MODEL = new DefaultComboBoxModel<>(new FilterPredicate[]{ new TautologyPredicate("All"), new TagStatePhotoPredicate(Tagged), new TagStatePhotoPredicate(Taggable), new TagStatePhotoPredicate(NotTaggable), }); private PositionsModel photosModel = new OverlayPositionsModel(new PositionsModelImpl()); private FilteringPositionsModel filteredPhotosModel; public PhotoPanel() { $$$setupUI$$$(); initialize(); } private void initialize() { final RouteConverter r = RouteConverter.getInstance(); photosModel.setRoute(new Wgs84Route(new PhotoFormat(), Waypoints, new ArrayList<Wgs84Position>())); filteredPhotosModel = new FilteringPositionsModel(photosModel, getFilterPredicatePreference()); tablePhotos.setModel(filteredPhotosModel); PhotosTableColumnModel tableColumnModel = new PhotosTableColumnModel(); tablePhotos.setColumnModel(tableColumnModel); tablePhotos.setDropMode(ON); r.getUnitSystemModel().addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { getFilteredPhotosModel().fireTableRowsUpdated(0, MAX_VALUE, ALL_COLUMNS); } }); r.getTimeZone().addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { getFilteredPhotosModel().fireTableRowsUpdated(0, MAX_VALUE, ALL_COLUMNS); } }); tablePhotos.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; if (getFilteredPhotosModel().isContinousRange()) return; handlePositionsUpdate(); } }); tableColumnModel.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { handleColumnVisibilityUpdate((PositionTableColumn) e.getSource()); } }); tablePhotos.registerKeyboardAction(new FrameAction() { public void run() { r.getContext().getActionManager().run("delete-photos"); } }, getKeyStroke(VK_DELETE, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); tablePhotos.setDropMode(ON); new PhotoTagStateToJLabelAdapter(photosModel, labelPhotos); ActionManager actionManager = r.getContext().getActionManager(); new PhotosTableHeaderMenu(tablePhotos.getTableHeader(), tableColumnModel, actionManager); new PhotosTablePopupMenu(tablePhotos).createPopupMenu(); actionManager.register("add-photos", new AddPhotosAction()); actionManager.register("delete-photos", new DeletePositionAction(tablePhotos, getFilteredPhotosModel())); actionManager.registerLocal("delete", PHOTOS, "delete-photos"); actionManager.register("tag-photos", new TagPhotosAction()); registerAction(buttonAddPhotos, "add-photos"); registerAction(buttonTagPhotos, "tag-photos"); setHelpIDString(tablePhotos, "photo-list"); comboBoxFilterPhotoPredicate.setModel(FILTER_PREDICATE_MODEL); comboBoxFilterPhotoPredicate.setSelectedItem(getFilterPredicatePreference()); comboBoxFilterPhotoPredicate.setRenderer(new FilterPredicateListCellRenderer()); comboBoxFilterPhotoPredicate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() != SELECTED) { return; } FilterPredicate filterPredicate = FilterPredicate.class.cast(e.getItem()); setFilterPredicatePreference(filterPredicate); filteredPhotosModel.setFilterPredicate(filterPredicate); } }); TimeZoneAndIds timeZoneAndIds = TimeZoneAndIds.getInstance(); ComboBoxModel<TimeZoneAndId> timeZoneModel = new DefaultComboBoxModel<>(timeZoneAndIds.getTimeZones()); timeZoneModel.setSelectedItem(timeZoneAndIds.getTimeZoneAndIdFor(r.getPhotoTimeZone().getTimeZone())); comboBoxPhotoTimeZone.setModel(timeZoneModel); comboBoxPhotoTimeZone.setRenderer(new TimeZoneAndIdListCellRenderer()); comboBoxPhotoTimeZone.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() != SELECTED) return; TimeZoneAndId timeZoneAndId = TimeZoneAndId.class.cast(e.getItem()); r.getPhotoTimeZone().setTimeZone(timeZoneAndId.getTimeZone()); } }); r.getPhotoTimeZone().addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { r.getGeoTagger().updateClosestPositionsForTagging(); } }); ComboBoxModel<TagStrategy> tagStrategyModel = new DefaultComboBoxModel<>(new TagStrategy[]{ Create_Backup_In_Subdirectory, Create_Tagged_Photo_In_Subdirectory }); tagStrategyModel.setSelectedItem(r.getTagStrategyPreference()); comboBoxTagStrategy.setModel(tagStrategyModel); comboBoxTagStrategy.setRenderer(new TagStrategyListCellRenderer()); comboBoxTagStrategy.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() != SELECTED) { return; } TagStrategy tagStrategy = TagStrategy.class.cast(e.getItem()); r.setTagStrategyPreference(tagStrategy); } }); photosPanel.setTransferHandler(new PanelDropHandler()); handlePositionsUpdate(); for (PositionTableColumn column : tableColumnModel.getPreparedColumns()) handleColumnVisibilityUpdate(column); } private int getDefaultRowHeight() { return calculateRowHeight(this, new DescriptionColumnTableCellEditor(), new SimpleNavigationPosition(null, null)); } public Component getRootComponent() { return photosPanel; } public String getLocalName() { return PHOTOS; } public JComponent getFocusComponent() { return tablePhotos; } public JButton getDefaultButton() { return buttonAddPhotos; } public JTable getPhotosView() { return tablePhotos; } public PositionsModel getPhotosModel() { return photosModel; } private PositionsModel getFilteredPhotosModel() { return filteredPhotosModel; } public void initializeSelection() { handlePositionsUpdate(); } private void handlePositionsUpdate() { int[] selectedRows = tablePhotos.getSelectedRows(); boolean existsASelectedPosition = selectedRows.length > 0; RouteConverter r = RouteConverter.getInstance(); ActionManager actionManager = r.getContext().getActionManager(); actionManager.enableLocal("delete", PHOTOS, existsASelectedPosition); actionManager.enable("tag-photos", existsASelectedPosition); if (r.isPhotosPanelSelected()) { List<NavigationPosition> selectedPositions = new ArrayList<>(); for (int selectedRow : selectedRows) { PhotoPosition photoPosition = (PhotoPosition) getFilteredPhotosModel().getPosition(selectedRow); NavigationPosition closestPositionForTagging = photoPosition.getClosestPositionForTagging(); NavigationPosition position = closestPositionForTagging != null ? closestPositionForTagging : photoPosition; if (position.hasCoordinates()) selectedPositions.add(position); } r.selectPositionsInMap(selectedPositions); } } private void handleColumnVisibilityUpdate(PositionTableColumn column) { if (column.getModelIndex() == PHOTO_COLUMN_INDEX || column.getModelIndex() == EXIF_COLUMN_INDEX || column.getModelIndex() == GPS_COLUMN_INDEX) tablePhotos.setRowHeight(column.isVisible() ? ROW_HEIGHT_FOR_PHOTO_COLUMN : getDefaultRowHeight()); } private FilterPredicate getFilterPredicatePreference() { FilterPredicate result = FILTER_PREDICATE_MODEL.getElementAt(0); String name = preferences.get(FILTER_PHOTO_PREDICATE_PREFERENCE, result.getName()); for (int i = 0, c = FILTER_PREDICATE_MODEL.getSize(); i < c; i++) { FilterPredicate filterPredicate = FILTER_PREDICATE_MODEL.getElementAt(i); if (filterPredicate.getName().equals(name)) { result = filterPredicate; break; } } return result; } private void setFilterPredicatePreference(FilterPredicate filterPredicate) { preferences.put(FILTER_PHOTO_PREDICATE_PREFERENCE, filterPredicate.getName()); } public void addPhotos(List<File> files) { RouteConverter.getInstance().getGeoTagger().addPhotos(files); } /** * Method generated by IntelliJ IDEA GUI Designer * >>> IMPORTANT!! <<< * DO NOT edit this method OR call it in your code! * * @noinspection ALL */ private void $$$setupUI$$$() { photosPanel = new JPanel(); photosPanel.setLayout(new GridLayoutManager(3, 1, new Insets(3, 3, 3, 3), -1, -1)); photosPanel.setMinimumSize(new Dimension(-1, -1)); photosPanel.setPreferredSize(new Dimension(560, 560)); final JScrollPane scrollPane1 = new JScrollPane(); photosPanel.add(scrollPane1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); tablePhotos = new JTable(); tablePhotos.setAutoCreateColumnsFromModel(false); scrollPane1.setViewportView(tablePhotos); final JPanel panel1 = new JPanel(); panel1.setLayout(new GridLayoutManager(2, 4, new Insets(0, 0, 0, 0), -1, -1)); photosPanel.add(panel1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); final JLabel label1 = new JLabel(); this.$$$loadLabelText$$$(label1, ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("photos-colon")); panel1.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final JLabel label2 = new JLabel(); this.$$$loadLabelText$$$(label2, ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("show-photos")); panel1.add(label2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); labelPhotos = new JLabel(); labelPhotos.setHorizontalAlignment(2); labelPhotos.setHorizontalTextPosition(2); labelPhotos.setText("-"); panel1.add(labelPhotos, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); buttonAddPhotos = new JButton(); this.$$$loadButtonText$$$(buttonAddPhotos, ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("add-photos-action")); buttonAddPhotos.setToolTipText(ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("add-photos-action-tooltip")); panel1.add(buttonAddPhotos, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); comboBoxFilterPhotoPredicate = new JComboBox(); panel1.add(comboBoxFilterPhotoPredicate, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); final Spacer spacer1 = new Spacer(); panel1.add(spacer1, new GridConstraints(0, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); final JPanel panel2 = new JPanel(); panel2.setLayout(new GridLayoutManager(2, 3, new Insets(0, 0, 0, 0), -1, -1)); photosPanel.add(panel2, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); final JLabel label3 = new JLabel(); this.$$$loadLabelText$$$(label3, ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("camera-timezone")); panel2.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); comboBoxPhotoTimeZone = new JComboBox(); panel2.add(comboBoxPhotoTimeZone, new GridConstraints(0, 1, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, new Dimension(300, -1), 0, false)); final JLabel label4 = new JLabel(); this.$$$loadLabelText$$$(label4, ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("tag-strategy")); panel2.add(label4, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); comboBoxTagStrategy = new JComboBox(); panel2.add(comboBoxTagStrategy, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); buttonTagPhotos = new JButton(); this.$$$loadButtonText$$$(buttonTagPhotos, ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("tag-photos-action")); buttonTagPhotos.setToolTipText(ResourceBundle.getBundle("slash/navigation/converter/gui/RouteConverter").getString("tag-photos-action-tooltip")); panel2.add(buttonTagPhotos, new GridConstraints(1, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); } /** * @noinspection ALL */ private void $$$loadLabelText$$$(JLabel component, String text) { StringBuffer result = new StringBuffer(); boolean haveMnemonic = false; char mnemonic = '\0'; int mnemonicIndex = -1; for (int i = 0; i < text.length(); i++) { if (text.charAt(i) == '&') { i++; if (i == text.length()) break; if (!haveMnemonic && text.charAt(i) != '&') { haveMnemonic = true; mnemonic = text.charAt(i); mnemonicIndex = result.length(); } } result.append(text.charAt(i)); } component.setText(result.toString()); if (haveMnemonic) { component.setDisplayedMnemonic(mnemonic); component.setDisplayedMnemonicIndex(mnemonicIndex); } } /** * @noinspection ALL */ private void $$$loadButtonText$$$(AbstractButton component, String text) { StringBuffer result = new StringBuffer(); boolean haveMnemonic = false; char mnemonic = '\0'; int mnemonicIndex = -1; for (int i = 0; i < text.length(); i++) { if (text.charAt(i) == '&') { i++; if (i == text.length()) break; if (!haveMnemonic && text.charAt(i) != '&') { haveMnemonic = true; mnemonic = text.charAt(i); mnemonicIndex = result.length(); } } result.append(text.charAt(i)); } component.setText(result.toString()); if (haveMnemonic) { component.setMnemonic(mnemonic); component.setDisplayedMnemonicIndex(mnemonicIndex); } } /** * @noinspection ALL */ public JComponent $$$getRootComponent$$$() { return photosPanel; } }