Java tutorial
/******************************************************************************* * Copyright (C) 2005, 2013 Wolfgang Schramm and Contributors * * This program 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 version 2 of the License. * * This program 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 * this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA *******************************************************************************/ package net.tourbook.photo; import java.io.File; import java.io.FileFilter; import java.lang.reflect.InvocationTargetException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import net.tourbook.common.UI; import net.tourbook.common.time.TimeTools; import net.tourbook.common.util.ColumnDefinition; import net.tourbook.common.util.ColumnManager; import net.tourbook.common.util.ITourViewer; import net.tourbook.common.util.LRUMap; import net.tourbook.common.util.StatusUtil; import net.tourbook.common.util.Util; import net.tourbook.common.weather.IWeather; import net.tourbook.photo.internal.Activator; import net.tourbook.photo.internal.GalleryActionBar; import net.tourbook.photo.internal.GalleryType; import net.tourbook.photo.internal.Messages; import net.tourbook.photo.internal.PhotoDateInfo; import net.tourbook.photo.internal.PhotoFilterGPS; import net.tourbook.photo.internal.PhotoFilterTour; import net.tourbook.photo.internal.PhotoGalleryToolTip; import net.tourbook.photo.internal.PhotoRenderer; import net.tourbook.photo.internal.RatingStarBehaviour; import net.tourbook.photo.internal.TableColumnFactory; import net.tourbook.photo.internal.gallery.MT20.FullScreenImageViewer; import net.tourbook.photo.internal.gallery.MT20.GalleryMT20; import net.tourbook.photo.internal.gallery.MT20.GalleryMT20Item; import net.tourbook.photo.internal.gallery.MT20.IGalleryContextMenuProvider; import net.tourbook.photo.internal.gallery.MT20.IItemListener; import net.tourbook.photo.internal.manager.ExifCache; import net.tourbook.photo.internal.manager.GallerySorting; import net.tourbook.photo.internal.manager.ThumbnailStore; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.layout.PixelConverter; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.ui.part.PageBook; import org.eclipse.ui.progress.UIJob; /** * This class is a compilation from different source codes: * * <pre> * org.eclipse.swt.examples.fileviewer * org.sharemedia.gui.libraryviews.GalleryLibraryView * org.dawb.common * org.apache.commons.sanselan * </pre> */ public abstract class ImageGallery implements IItemListener, IGalleryContextMenuProvider, IPhotoProvider, ITourViewer, IPhotoEvictionListener { /** * Number of gallery positions which are cached */ private static final int MAX_GALLERY_POSITIONS = 100; private static final String MENU_ID_PHOTO_GALLERY = "menu.net.tourbook.photo.PhotoGallery"; //$NON-NLS-1$ private static final int DELAY_JOB_SUBSEQUENT_FILTER = 500; // ms private static final long DELAY_JOB_UI_FILTER = 200; // ms private static final long DELAY_JOB_UI_LOADING = 200; // ms public static final int MIN_GALLERY_ITEM_WIDTH = 10; // pixel public static final int MAX_GALLERY_ITEM_WIDTH = 2000; // pixel public static final String STATE_THUMB_IMAGE_SIZE = "STATE_THUMB_IMAGE_SIZE"; //$NON-NLS-1$ private static final String STATE_GALLERY_POSITION_KEY = "STATE_GALLERY_POSITION_KEY"; //$NON-NLS-1$ private static final String STATE_GALLERY_POSITION_VALUE = "STATE_GALLERY_POSITION_VALUE"; //$NON-NLS-1$ private static final String STATE_IMAGE_SORTING = "STATE_IMAGE_SORTING"; //$NON-NLS-1$ private static final String STATE_SELECTED_ITEMS = "STATE_SELECTED_ITEMS"; //$NON-NLS-1$ private static final String DEFAULT_GALLERY_FONT = "arial,sans-serif"; //$NON-NLS-1$ private IDialogSettings _state; private final IPreferenceStore _prefStore = Activator.getDefault().getPreferenceStore(); /* * worker thread management */ /** * Worker start time */ private long _workerStart; /** * Lock for all worker control data and state */ private final Object _workerLock = new Object(); /** * The worker's thread */ private volatile Thread _workerThread = null; /** * True if the worker must exit on completion of the current cycle */ private volatile boolean _workerStopped = false; /** * True if the worker must cancel its operations prematurely perhaps due to a state update */ private volatile boolean _workerCancelled = false; /** * Worker state information -- this is what gets synchronized by an update */ private volatile File _workerStateDir = null; /** * State information to use for the next cycle */ private volatile File _workerNextFolder = null; /** * Manages the worker's thread */ private final Runnable _workerRunnable; /* * image loading/filtering */ private PhotoFilterGPS _imageFilterGPS; private PhotoFilterTour _imageFilterTour; private boolean _filterJob1stRun; private boolean _filterJobIsCanceled; private ReentrantLock JOB_LOCK = new ReentrantLock(); private Job _jobFilter; private AtomicBoolean _jobFilterIsSubsequentScheduled = new AtomicBoolean(); private int _jobFilterDirtyCounter; private UIJob _jobUIFilter; private AtomicBoolean _jobUIFilterJobIsScheduled = new AtomicBoolean(); private int _jobUIFilterDirtyCounter; private Photo[] _jobUIFilterPhoto; private Job _jobUILoading; private AtomicBoolean _jobUILoadingIsScheduled = new AtomicBoolean(); private int _jobUILoadingDirtyCounter; private int _currentExifRunId; /** * */ public Comparator<Photo> SORT_BY_IMAGE_DATE; public Comparator<Photo> SORT_BY_FILE_NAME; /** * Contains current gallery sorting id: {@link PicDirView#GALLERY_SORTING_BY_DATE} or * {@link PicDirView#GALLERY_SORTING_BY_NAME} */ private Comparator<Photo> _currentComparator; private GallerySorting _currentSorting; private PhotoRenderer _photoRenderer; private FullScreenImageViewer _fullScreenImageViewer; private PhotoGalleryToolTip _photoGalleryTooltip; /** * Folder which images are currently be displayed */ private File _photoFolder; /** * Folder which images should be displayed in the gallery */ private File _photoFolderWhichShouldBeDisplayed; protected IPhotoGalleryProvider _photoGalleryProvider; private int _galleryStyle; private boolean _isShowCustomActionBar; private boolean _isShowThumbsize; private boolean _isHandleRatingStars; private boolean _isAttributesPainted; /** * Contains photos for <b>ALL</b> gallery items including <b>HIDDEN</b> items */ private Photo[] _allPhotos; /** * Contains filtered gallery items. * <p> * Only these items are displayed in the gallery, {@link #_allPhotos} items contains also hidden * gallery items. */ private Photo[] _sortedAndFilteredPhotos; FileFilter _fileFilter; /** * Photo image size without border */ private int _photoImageSize; private int _photoBorderSize; private boolean _isShowTooltip; private boolean _isShowAnnotations; /** * keep gallery position for each used folder */ private LRUMap<String, Double> _galleryPositions; private String _newGalleryPositionKey; private String _currentGalleryPositionKey; private String _defaultStatusMessage = UI.EMPTY_STRING; private int[] _restoredSelection; private GalleryType _galleryType; private TableViewer _photoViewer; private ColumnManager _columnManager; private PixelConverter _pc; private GalleryMT20Item _hoveredItem; private int[] _delayCounter = { 0 }; private Double _contentGalleryPosition; private boolean _isLinkPhotoDisplayed; private final NumberFormat _nf1 = NumberFormat.getNumberInstance(); { _nf1.setMinimumFractionDigits(1); _nf1.setMaximumFractionDigits(1); } /* * UI resources */ private Font _galleryFont; /* * UI controls */ private Display _display; private Composite _uiContainer; private GalleryMT20 _galleryMT20; private GalleryActionBar _galleryActionBar; private PageBook _pageBook; private Composite _pageDefault; private Composite _pageGalleryInfo; private Composite _pageDetails; private Label _lblDefaultPage; private Label _lblGalleryInfo; private GalleryMT20Item[] _sortedGalleryItems; { _galleryPositions = new LRUMap<String, Double>(MAX_GALLERY_POSITIONS); _workerRunnable = new Runnable() { @Override public void run() { while (!_workerStopped) { synchronized (_workerLock) { _workerCancelled = false; _workerStateDir = _workerNextFolder; } workerExecute(); synchronized (_workerLock) { try { if ((!_workerCancelled) && (_workerStateDir == _workerNextFolder)) { /* * wait until the next images should be displayed */ _workerLock.wait(); } } catch (final InterruptedException e) { } } } _workerThread = null; /* * wake up UI thread in case it is in a modal loop awaiting thread termination (see * workerStop()) */ _display.wake(); } }; SORT_BY_IMAGE_DATE = new Comparator<Photo>() { @Override public int compare(final Photo photo1, final Photo photo2) { if (_workerCancelled) { // couldn't find another way how to stop sorting return 0; } final long diff = photo1.imageExifTime - photo2.imageExifTime; return diff < 0 ? -1 : diff > 0 ? 1 : 0; } }; SORT_BY_FILE_NAME = new Comparator<Photo>() { @Override public int compare(final Photo photo1, final Photo photo2) { if (_workerCancelled) { // couldn't find another way how to stop sorting return 0; } return photo1.imageFilePathName.compareToIgnoreCase(photo2.imageFilePathName); } }; } private class ContentProvider implements IStructuredContentProvider { @Override public void dispose() { } @Override public Object[] getElements(final Object inputElement) { // return _sortedGalleryItems if (_sortedAndFilteredPhotos == null) { return new Photo[0]; } return _sortedAndFilteredPhotos; } @Override public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { } } private class GalleryImplementation extends GalleryMT20 { public GalleryImplementation(final Composite parent, final int style, final IDialogSettings state) { super(parent, style, state); } @Override public Photo getPhoto(final int filterIndex) { if (filterIndex >= _sortedAndFilteredPhotos.length) { return null; } final Photo photo = _sortedAndFilteredPhotos[filterIndex]; return photo; } @Override protected void onBeforeModifySelection() { onBeforeModifyGallerySelection(); } } private class LoadCallbackExif implements ILoadCallBack { private int __runId; private Photo __photo; public LoadCallbackExif(final Photo photo, final int runId) { __photo = photo; __runId = runId; } @Override public void callBackImageIsLoaded(final boolean isUpdateUI) { // keep exif metadata final PhotoImageMetadata metadata = __photo.getImageMetaDataRaw(); if (metadata != null) { ExifCache.put(__photo.imageFilePathName, metadata); } updateSqlState(); if (__runId != _currentExifRunId) { // this callback is from an older run ID, ignore it return; } jobFilter_22_ScheduleSubsequent(DELAY_JOB_SUBSEQUENT_FILTER); jobUILoading_20_Schedule(); } private void updateSqlState() { final AtomicReference<PhotoSqlLoadingState> sqlLoadingState = __photo.getSqlLoadingState(); final boolean isInLoadingQueue = sqlLoadingState.get() == PhotoSqlLoadingState.IS_IN_LOADING_QUEUE; final boolean isSqlLoaded = sqlLoadingState.compareAndSet(PhotoSqlLoadingState.IS_LOADED, PhotoSqlLoadingState.IS_IN_LOADING_QUEUE); if (isInLoadingQueue == false && isSqlLoaded == false) { final IPhotoServiceProvider photoServiceProvider = Photo.getPhotoServiceProvider(); PhotoLoadManager.putPhotoInLoadingQueueSql(__photo, this, photoServiceProvider, false); } } } public ImageGallery(final IDialogSettings state) { _state = state; } public void closePhotoTooltip() { _photoGalleryTooltip.close(); } /** * create the views context menu * * @param isRecreate */ private void createContextMenu() { final MenuManager menuMgr = new MenuManager(); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(final IMenuManager menuMgr2) { fillContextMenu(menuMgr2); } }); final Table table = _photoViewer.getTable(); // if (isRecreate) { // // /* // * when a tooltip is reparented, the context menu must be recreated otherwise an // * exception is thown that the menu shell has the wrong parent // */ // // menuMgr.dispose(); // } final Menu tableContextMenu = menuMgr.createContextMenu(table); table.setMenu(tableContextMenu); _columnManager.createHeaderContextMenu(table, tableContextMenu); } private void createGalleryFont() { if (_galleryFont != null) { _galleryFont.dispose(); } final String prefGalleryFont = _prefStore.getString(IPhotoPreferences.PHOTO_VIEWER_FONT); if (prefGalleryFont.length() > 0) { try { // System.out.println(UI.timeStamp() + "setting gallery font: " + prefGalleryFont); //$NON-NLS-1$ _galleryFont = new Font(_display, new FontData(prefGalleryFont)); } catch (final Exception e) { // ignore } } if (_galleryFont == null) { StatusUtil.log("This font cannot be created: \"" + prefGalleryFont + "\"");//$NON-NLS-1$ //$NON-NLS-2$ _galleryFont = new Font(_display, DEFAULT_GALLERY_FONT, 7, SWT.NORMAL); } } /** * @param parent * @param style * @param photoGalleryProvider * @param state */ public void createImageGallery(final Composite parent, final int style, final IPhotoGalleryProvider photoGalleryProvider) { PhotoUI.init(); _galleryStyle = style; _photoGalleryProvider = photoGalleryProvider; _pc = new PixelConverter(parent); _columnManager = new ColumnManager(this, _state); defineAllColumns(); jobFilter_10_Create(); jobUIFilter_10_Create(); jobUILoading_10_Create(); PhotoCache.addEvictionListener(this); createUI(parent); parent.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(final DisposeEvent e) { onDispose(); } }); } /** * @return Returns a selection with all selected photos. */ private PhotoSelection createPhotoSelection() { final Collection<GalleryMT20Item> allItems = _galleryMT20.getSelection(); final ArrayList<Photo> photos = new ArrayList<Photo>(); for (final GalleryMT20Item item : allItems) { final Photo photo = item.photo; if (photo != null) { photos.add(photo); } } return new PhotoSelection(photos, allItems, _galleryMT20.getSelectionIndex(), _isLinkPhotoDisplayed); } /** * @param isAllImages * @return */ private PhotosWithExifSelection createPhotoSelectionWithExif(final boolean isAllImages) { final ArrayList<Photo> loadedExifData = getLoadedExifImageData(_photoFolderWhichShouldBeDisplayed, isAllImages); if (loadedExifData == null) { MessageDialog.openInformation(_display.getActiveShell(), Messages.Pic_Dir_Dialog_Photos_Title, Messages.Pic_Dir_Dialog_Photos_DialogInterrupted_Message); return null; } /* * check if a photo is selected */ if (loadedExifData.size() == 0) { if (isAllImages) { MessageDialog.openInformation(_display.getActiveShell(), Messages.Pic_Dir_Dialog_Photos_Title, NLS.bind(Messages.Pic_Dir_Dialog_Photos_NoSelectedImagesInFolder_Message, // _photoFolderWhichShouldBeDisplayed.getAbsolutePath())); } else { MessageDialog.openInformation(_display.getActiveShell(), Messages.Pic_Dir_Dialog_Photos_Title, Messages.Pic_Dir_Dialog_Photos_NoSelectedImage_Message); } return null; } return new PhotosWithExifSelection(loadedExifData); } private void createUI(final Composite parent) { _uiContainer = parent; _display = parent.getDisplay(); _fileFilter = ImageUtils.createImageFileFilter(); createUI_05(parent); } private void createUI_05(final Composite parent) { final Composite container = new Composite(parent, SWT.NONE); GridDataFactory.fillDefaults().grab(true, true).applyTo(container); GridLayoutFactory.fillDefaults().numColumns(1).spacing(0, 0).applyTo(container); // container.setBackground(_display.getSystemColor(SWT.COLOR_DARK_RED)); { if (_isShowThumbsize || _isShowCustomActionBar) { _galleryActionBar = new GalleryActionBar(container, this, _isShowThumbsize, _isShowCustomActionBar); } _pageBook = new PageBook(container, SWT.NONE); GridDataFactory.fillDefaults().grab(true, true).applyTo(_pageBook); { createUI_20_PageGallery(_pageBook); createUI_30_PageDetails(_pageBook); createUI_99_PageDefault(_pageBook); createUI_40_PageGalleryInfo(_pageBook); } } _photoGalleryTooltip = new PhotoGalleryToolTip(_galleryMT20); _photoGalleryTooltip.setReceiveMouseMoveEvent(true); _galleryMT20.addItemListener(this); } /** * Create gallery */ private void createUI_20_PageGallery(final Composite parent) { _galleryMT20 = new GalleryImplementation(parent, _galleryStyle, _state); _galleryMT20.setHigherQualityDelay(200); // _gallery.setAntialias(SWT.OFF); // _gallery.setInterpolation(SWT.LOW); _galleryMT20.setAntialias(SWT.ON); _galleryMT20.setInterpolation(SWT.HIGH); _galleryMT20.setItemMinMaxSize(MIN_GALLERY_ITEM_WIDTH, MAX_GALLERY_ITEM_WIDTH); _galleryMT20.addListener(SWT.Modify, new Listener() { // a modify event is fired when gallery is zoomed in/out @Override public void handleEvent(final Event event) { PhotoLoadManager.stopImageLoading(false); updateUI_AfterZoomInOut(event.width); } }); _galleryMT20.addListener(SWT.Selection, new Listener() { // a gallery item is selected/deselected @Override public void handleEvent(final Event event) { onSelectPhoto(); } }); _galleryMT20.setContextMenuProvider(this); _photoGalleryProvider.registerContextMenu(MENU_ID_PHOTO_GALLERY, _galleryMT20.getContextMenuManager()); _fullScreenImageViewer = _galleryMT20.getFullScreenImageViewer(); // allow the gallery to get access to the photo _galleryMT20.setPhotoProvider(this); // set photo renderer which paints the image but also starts the image loading _photoRenderer = new PhotoRenderer(_galleryMT20, this); _galleryMT20.setItemRenderer(_photoRenderer); } private void createUI_30_PageDetails(final PageBook parent) { _pageDetails = new Composite(parent, SWT.NONE); GridDataFactory.fillDefaults().grab(true, true).applyTo(_pageDetails); GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(_pageDetails); { createUI_32_PhotoViewer(_pageDetails); } } private void createUI_32_PhotoViewer(final Composite parent) { final Table table = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI); GridDataFactory.fillDefaults().grab(true, true).applyTo(table); table.setHeaderVisible(true); // table.setLinesVisible(_prefStore.getBoolean(ITourbookPreferences.VIEW_LAYOUT_DISPLAY_LINES)); /* * create table viewer */ _photoViewer = new TableViewer(table); _columnManager.createColumns(_photoViewer); _photoViewer.setUseHashlookup(true); _photoViewer.setContentProvider(new ContentProvider()); // _photoViewer.setComparator(new ContentComparator()); _photoViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(final SelectionChangedEvent event) { final ISelection eventSelection = event.getSelection(); if (eventSelection instanceof StructuredSelection) { // onSelectTour(((StructuredSelection) eventSelection).toArray()); } } }); createContextMenu(); } private void createUI_40_PageGalleryInfo(final PageBook parent) { _pageGalleryInfo = new Composite(parent, SWT.NONE); // GridDataFactory.fillDefaults().grab(true, false).applyTo(container); GridLayoutFactory.fillDefaults()// .numColumns(1).margins(5, 5).applyTo(_pageGalleryInfo); { _lblGalleryInfo = new Label(_pageGalleryInfo, SWT.WRAP); GridDataFactory.fillDefaults()// .grab(true, false).applyTo(_lblGalleryInfo); } } private void createUI_99_PageDefault(final PageBook parent) { _pageDefault = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults()// .numColumns(1).margins(5, 5).applyTo(_pageDefault); { _lblDefaultPage = new Label(_pageDefault, SWT.WRAP); GridDataFactory.fillDefaults()// .grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(_lblDefaultPage); } } private void defineAllColumns() { defineColumn_ImageFileName(); defineColumn_AdjustedDate(); defineColumn_AdjustedTime(); defineColumn_ExifTime(); defineColumn_Dimension(); defineColumn_Orientation(); defineColumn_ImageDirectionText(); defineColumn_ImageDirectionDegree(); // defineColumnAltitude(); // defineColumnLatitude(); // defineColumnLongitude(); defineColumn_Location(); } /** * column: date */ private void defineColumn_AdjustedDate() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_DATE.createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { // final Photo photo = (Photo) cell.getElement(); // // cell.setText(_dateFormatter.print(photo.adjustedTime)); } }); } /** * column: time */ private void defineColumn_AdjustedTime() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_TIME.createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { // final Photo photo = (Photo) cell.getElement(); // // cell.setText(_timeFormatter.print(photo.adjustedTime)); } }); } /** * column: width x height */ private void defineColumn_Dimension() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_DIMENSION.createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); cell.setText(photo.getDimensionText()); } }); } /** * column: tour start time */ private void defineColumn_ExifTime() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_TIME.createColumn(_columnManager, _pc); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); cell.setText(TimeTools.getZonedDateTime(photo.imageExifTime).format(TimeTools.Formatter_Time_M)); } }); } /** * column: image direction degree */ private void defineColumn_ImageDirectionDegree() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_IMAGE_DIRECTION_DEGREE// .createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); final double imageDirection = photo.getImageDirection(); if (imageDirection == Double.MIN_VALUE) { cell.setText(UI.EMPTY_STRING); } else { cell.setText(Integer.toString((int) imageDirection)); } } }); } // /** // * column: altitude // */ // private void defineColumnAltitude() { // // final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_ALTITUDE.createColumn(_columnManager, _pc); // colDef.setIsDefaultColumn(); // colDef.setLabelProvider(new CellLabelProvider() { // @Override // public void update(final ViewerCell cell) { // // final Photo photo = (Photo) cell.getElement(); // final double altitude = photo.getAltitude(); // // if (altitude == Double.MIN_VALUE) { // cell.setText(UI.EMPTY_STRING); // } else { // cell.setText(Integer.toString((int) (altitude / UI.UNIT_VALUE_ALTITUDE))); // } // } // }); // } /** * column: image direction degree */ private void defineColumn_ImageDirectionText() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_IMAGE_DIRECTION_TEXT// .createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); final double imageDirection = photo.getImageDirection(); if (imageDirection == Double.MIN_VALUE) { cell.setText(UI.EMPTY_STRING); } else { final int imageDirectionInt = (int) imageDirection; cell.setText(getDirectionText(imageDirectionInt)); } } }); } /** * column: name */ private void defineColumn_ImageFileName() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_NAME.createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); cell.setText(photo.imageFileName); } }); } // /** // * column: latitude // */ // private void defineColumnLatitude() { // // final ColumnDefinition colDef = TableColumnFactory.LATITUDE.createColumn(_columnManager, _pc); // // colDef.setIsDefaultColumn(); // colDef.setLabelProvider(new CellLabelProvider() { // @Override // public void update(final ViewerCell cell) { // // final double latitude = ((Photo) cell.getElement()).getLatitude(); // // if (latitude == Double.MIN_VALUE) { // cell.setText(UI.EMPTY_STRING); // } else { // cell.setText(_nf8.format(latitude)); // } // } // }); // } /** * column: location */ private void defineColumn_Location() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_LOCATION.createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); cell.setText(photo.getGpsAreaInfo()); } }); } // /** // * column: longitude // */ // private void defineColumnLongitude() { // // final ColumnDefinition colDef = net.tourbook.ui.TableColumnFactory.LONGITUDE.createColumn(_columnManager, _pc); // // colDef.setIsDefaultColumn(); // colDef.setLabelProvider(new CellLabelProvider() { // @Override // public void update(final ViewerCell cell) { // // final double longitude = ((Photo) cell.getElement()).getLongitude(); // // if (longitude == Double.MIN_VALUE) { // cell.setText(UI.EMPTY_STRING); // } else { // cell.setText(_nf8.format(longitude)); // } // } // }); // } /** * column: orientation */ private void defineColumn_Orientation() { final ColumnDefinition colDef = TableColumnFactory.PHOTO_FILE_ORIENTATION.createColumn(_columnManager, _pc); colDef.setIsDefaultColumn(); colDef.setLabelProvider(new CellLabelProvider() { @Override public void update(final ViewerCell cell) { final Photo photo = (Photo) cell.getElement(); cell.setText(Integer.toString(photo.getOrientation())); } }); } private void deselectAll() { _galleryMT20.deselectAll(); // update UI onSelectPhoto(); } /** * Disposes and deletes all thumb images. */ private void disposeAndDeleteAllImages() { PhotoLoadManager.stopImageLoading(true); ThumbnailStore.cleanupStoreFiles(true, true); PhotoImageCache.disposeAll(); ExifCache.clear(); } protected abstract void enableActions(final boolean isItemAvailable); protected abstract void enableAttributeActions(boolean isAttributesPainted); @Override public void evictedPhoto(final Photo evictedPhoto) { final String evictedImageFilePathName = evictedPhoto.imageFilePathName; // check if it is still evicted if (PhotoCache.getPhoto(evictedImageFilePathName) != null) { // photo is already recreated return; } for (final Photo photo : _allPhotos) { if (photo.imageFilePathName.equals(evictedImageFilePathName)) { // evicted photo is still displayed, cache is again PhotoCache.setPhoto(photo); break; } } } @Override public void fillContextMenu(final IMenuManager menuMgr) { menuMgr.add(new Separator(UI.MENU_SEPARATOR_ADDITIONS)); // menuMgr.add(_actionMergePhotosWithTours); } /** * This is called when a filter button is pressed. * * @param photoFilterGPS * @param photoFilterTour * @param isUpdateGallery */ public void filterGallery(final PhotoFilterGPS photoFilterGPS, final PhotoFilterTour photoFilterTour) { _imageFilterGPS = photoFilterGPS; _imageFilterTour = photoFilterTour; /* * deselect all, this could be better implemented to keep selection, but is not yet done */ deselectAll(); jobFilter_22_ScheduleSubsequent(0); } /** * Preserves gallery positions for different gallery contents. * * @return */ private double getCachedGalleryPosition() { double galleryPosition = 0; // keep current gallery position if (_currentGalleryPositionKey != null) { _galleryPositions.put(_currentGalleryPositionKey, _galleryMT20.getGalleryPosition()); } // get old position if (_newGalleryPositionKey != null) { _currentGalleryPositionKey = _newGalleryPositionKey; // get old gallery position final Double oldPosition = _galleryPositions.get(_newGalleryPositionKey); galleryPosition = oldPosition == null ? 0 : oldPosition; // System.out.println(UI.timeStampNano() + " get old position " + oldPosition); // // TODO remove SYSTEM.OUT.PRINTLN } // System.out.println(UI.timeStampNano() + " getCachedGalleryPosition() " + galleryPosition); // // TODO remove SYSTEM.OUT.PRINTLN return galleryPosition; } @Override public ColumnManager getColumnManager() { return _columnManager; } private Comparator<Photo> getCurrentComparator() { return _currentSorting == GallerySorting.FILE_NAME ? SORT_BY_FILE_NAME : SORT_BY_IMAGE_DATE; } /** * @return Returns gallery action bar container. {@link #setShowActionBar(boolean)} must be * called with the parameter <code>true</code> that the action bar is created. */ public Composite getCustomActionBarContainer() { return _galleryActionBar.getCustomContainer(); } private String getDirectionText(final int degreeDirection) { final float degree = (degreeDirection + 22.5f) / 45.0f; final int directionIndex = ((int) degree) % 8; return IWeather.windDirectionText[directionIndex]; } public FullScreenImageViewer getFullScreenImageViewer() { return _fullScreenImageViewer; } public Control getGallery() { return _galleryMT20; } public Control getGalleryContainer() { return _pageBook; } public Collection<GalleryMT20Item> getGallerySelection() { return _galleryMT20.getSelection(); } /** * @param selectedFolder * @param isGetAllImages * @return Returns photo data for the images in the requested folder, sorted by date/time or * <code>null</code> when loading was canceled by the user. */ private ArrayList<Photo> getLoadedExifImageData(final File selectedFolder, final boolean isGetAllImages) { final boolean isFolderFilesLoaded = _photoFolder.getAbsolutePath() .equals(_photoFolderWhichShouldBeDisplayed.getAbsolutePath()); if (PhotoLoadManager.getExifQueueSize() > 0 || isFolderFilesLoaded == false) { /* * wait until all image exif data are loaded */ if (isEXIFDataLoaded() == false) { return null; } } Photo[] sortedPhotosArray; if (isGetAllImages) { // get all filtered photos sortedPhotosArray = _sortedAndFilteredPhotos.clone(); } else { // get all selected photos final Collection<GalleryMT20Item> galleryItems = _galleryMT20.getSelection(); sortedPhotosArray = new Photo[galleryItems.size()]; int itemIndex = 0; for (final GalleryMT20Item item : galleryItems) { final Photo photo = item.photo; if (photo != null) { sortedPhotosArray[itemIndex++] = photo; } } } // sort photos by date/time Arrays.sort(sortedPhotosArray, SORT_BY_IMAGE_DATE); final ArrayList<Photo> sortedPhotos = new ArrayList<Photo>(sortedPhotosArray.length); for (final Photo photo : sortedPhotosArray) { sortedPhotos.add(photo); } return sortedPhotos; } /** * @return Returns folder which is currently displayed. */ public File getPhotoFolder() { return _photoFolder; } /** * Creates a {@link PhotosWithExifSelection} * * @param isAllImages * @return Returns a {@link ISelection} for selected or all images or <code>null</code> null * when loading EXIF data was canceled by the user. */ public PhotosWithExifSelection getSelectedPhotosWithExif(final boolean isAllImages) { return createPhotoSelectionWithExif(isAllImages); } @Override public Photo[] getSortedAndFilteredPhotos() { return _sortedAndFilteredPhotos; } /** * This message is displayed when no other message is displayed. * * @return */ private String getStatusDefaultMessage() { final Collection<GalleryMT20Item> allSelectedPhoto = _galleryMT20.getSelection(); final int allPhotoSize = allSelectedPhoto.size(); return allPhotoSize == 0 // // hide status message when nothing is selected ? UI.EMPTY_STRING : NLS.bind(Messages.Pic_Dir_StatusLabel_SelectedImages, allPhotoSize); } @Override public ColumnViewer getViewer() { return _photoViewer; } public void handlePrefStoreModifications(final PropertyChangeEvent event) { final String property = event.getProperty(); if (property.equals(IPhotoPreferences.PHOTO_VIEWER_PREF_EVENT_IMAGE_QUALITY_IS_MODIFIED)) { _display.asyncExec(new Runnable() { @Override public void run() { final MessageDialog messageDialog = new MessageDialog(_display.getActiveShell(), Messages.Pic_Dir_Dialog_CleanupStoreImages_Title, null, Messages.Pic_Dir_Dialog_CleanupStoreImages_Message, MessageDialog.QUESTION, new String[] { Messages.Pic_Dir_Dialog_CleanupStoreImages_KeepImages, Messages.Pic_Dir_Dialog_CleanupStoreImages_DiscardImages }, 0); if (messageDialog.open() == 1) { // discard all imaged disposeAndDeleteAllImages(); } _galleryMT20.updateGallery(false, _galleryMT20.getGalleryPosition()); if (_galleryActionBar != null) { _galleryActionBar.updateUI_ImageIndicatorTooltip(); } } }); } else if (property.equals(IPhotoPreferences.PHOTO_VIEWER_PREF_EVENT_IMAGE_VIEWER_UI_IS_MODIFIED) || property.equals(IPhotoPreferences.PHOTO_VIEWER_PREF_EVENT_FULLSIZE_VIEWER_IS_MODIFIED)) { updateUI_FromPrefStore(true); updateUI_AfterZoomInOut(_galleryMT20.getItemWidth()); } else if (property.equals(IPhotoPreferences.PHOTO_VIEWER_FONT)) { onModifyFont(); } } public boolean isDisposed() { return _galleryMT20.isDisposed(); } /** * @return Returns <code>true</code> when EXIF data for all (not filtered) photos are loaded. */ private boolean isEXIFDataLoaded() { final boolean isLoaded[] = new boolean[] { true }; try { final IRunnableWithProgress runnable = new IRunnableWithProgress() { @Override public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { final boolean isFolderFilesLoadedInitialValue = _photoFolder.getAbsolutePath() .equals(_photoFolderWhichShouldBeDisplayed.getAbsolutePath()); /* * ensure files for the requested folder are read from the filesystem */ if (isFolderFilesLoadedInitialValue == false) { monitor.beginTask(NLS.bind(Messages.Pic_Dir_Dialog_LoadingFolderFiles, _photoFolderWhichShouldBeDisplayed), IProgressMonitor.UNKNOWN); boolean isFolderFilesLoaded = isFolderFilesLoadedInitialValue; while (isFolderFilesLoaded == false) { /* * wait until files are loaded from the file system */ Thread.sleep(10); isFolderFilesLoaded = _photoFolder.getAbsolutePath() .equals(_photoFolderWhichShouldBeDisplayed.getAbsolutePath()); } /* * wait until the loading job has started otherwise it is possible that the * exif queue is empty and is checked before it is filled (this happened) */ Thread.sleep(100); } /* * ensure all image EXIF data are loaded */ final int allPhotoSize = _allPhotos.length; monitor.beginTask(Messages.Pic_Dir_Dialog_LoadingEXIFData, IProgressMonitor.UNKNOWN); int exifLoadingQueueSize = PhotoLoadManager.getExifQueueSize(); while (exifLoadingQueueSize > 0) { Thread.sleep(100); if (monitor.isCanceled()) { isLoaded[0] = false; return; } // show loading progress final int newExifLoadingQueueSize = PhotoLoadManager.getExifQueueSize(); final double _percent = (double) (allPhotoSize - exifLoadingQueueSize) / allPhotoSize * 100.0; monitor.subTask(NLS.bind(Messages.Pic_Dir_Dialog_LoadingEXIFData_Subtask, new Object[] { exifLoadingQueueSize, allPhotoSize, _nf1.format(_percent) })); exifLoadingQueueSize = newExifLoadingQueueSize; } } }; new ProgressMonitorDialog(Display.getCurrent().getActiveShell()).run(true, true, runnable); } catch (final InvocationTargetException e) { StatusUtil.log(e); } catch (final InterruptedException e) { StatusUtil.log(e); } return isLoaded[0]; } public boolean isLinkPhotoDisplayed() { return _isLinkPhotoDisplayed; } private void jobFilter_10_Create() { _jobFilter = new Job("PicDirImages: Filtering images") {//$NON-NLS-1$ @Override protected IStatus run(final IProgressMonitor monitor) { _filterJobIsCanceled = false; _jobFilterIsSubsequentScheduled.set(false); try { if (_filterJob1stRun) { _filterJob1stRun = false; jobFilter_30_Run1st(); } else { jobFilter_32_RunSubsequent(); } } catch (final Exception e) { StatusUtil.log(e); } return Status.OK_STATUS; } }; _jobFilter.setSystem(true); } private void jobFilter_12_Stop() { _filterJobIsCanceled = true; _jobUIFilterPhoto = null; // wait until the filter job has ended try { _jobFilter.cancel(); _jobFilter.join(); } catch (final InterruptedException e) { StatusUtil.log(e); } } private void jobFilter_20_Schedule1st() { // filter must be stopped before new photos are set jobFilter_12_Stop(); JOB_LOCK.lock(); { try { // this is the initial run of the filter job _filterJob1stRun = true; _sortedAndFilteredPhotos = new Photo[0]; _currentExifRunId++; _jobFilter.schedule(); } finally { JOB_LOCK.unlock(); } } /* * hide status message for the first delay, because when nothing is filtered, the UI is * flickering with an initial message */ updateUI_StatusMessageInUIThread(UI.EMPTY_STRING); } private void jobFilter_22_ScheduleSubsequent(final long delay) { JOB_LOCK.lock(); { try { _jobFilterDirtyCounter++; if ((_jobFilter.getState() == Job.RUNNING) == false) { // filter job is NOT running, schedule it final boolean isScheduled = _jobFilterIsSubsequentScheduled.getAndSet(true); if (isScheduled == false) { _jobFilter.schedule(delay); } } } finally { JOB_LOCK.unlock(); } } } private void jobFilter_23_ScheduleSubsequentWithoutRunCheck() { _jobFilterDirtyCounter++; // filter job is NOT running, schedule it final boolean isScheduled = _jobFilterIsSubsequentScheduled.getAndSet(true); if (isScheduled == false) { _jobFilter.schedule(DELAY_JOB_SUBSEQUENT_FILTER); } } /** * Filter and sort photos and load EXIF data (which is required for sorting). */ private void jobFilter_30_Run1st() { // final long start = System.nanoTime(); if (_allPhotos.length == 0) { // there are no images in the current folder, /* * gallery MUST be updated even when no images are displayed because images from the * previous folder are still displayed */ updateUI_GalleryInfo(); return; } final boolean isGPSFilter = _imageFilterGPS == PhotoFilterGPS.WITH_GPS; final boolean isNoGPSFilter = _imageFilterGPS == PhotoFilterGPS.NO_GPS; final boolean isTourFilter = _imageFilterTour == PhotoFilterTour.WITH_TOURS; final boolean isNoTourFilter = _imageFilterTour == PhotoFilterTour.NO_TOURS; final boolean isFilterSet = isGPSFilter || isNoGPSFilter || isTourFilter || isNoTourFilter; // get current dirty counter final int currentDirtyCounter = _jobFilterDirtyCounter; Photo[] newFilteredPhotos = null; if (isFilterSet) { final int numberOfPhotos = _allPhotos.length; final Photo[] tempFilteredPhotos = new Photo[numberOfPhotos]; // filterindex is incremented when the filter contains a gallery item int filterIndex = 0; int photoIndex = 0; // loop: all photos for (final Photo photo : _allPhotos) { if (_filterJobIsCanceled) { return; } boolean isPhotoInFilter = false; if (photo.isExifLoaded == false) { // image is not yet loaded, it must be loaded to get the gps state putInExifLoadingQueue(photo); } // check again, the gps state could have been cached and set if (photo.isExifLoaded) { final boolean isPhotoWithGps = _isLinkPhotoDisplayed ? photo.isLinkPhotoWithGps : photo.isTourPhotoWithGps; if (isGPSFilter) { if (isPhotoWithGps) { isPhotoInFilter = true; } } else if (isNoGPSFilter) { if (!isPhotoWithGps) { isPhotoInFilter = true; } } } final boolean isSavedInTour = photo.isSavedInTour; if (isTourFilter) { if (isSavedInTour) { isPhotoInFilter = true; } } else if (isNoTourFilter) { if (!isSavedInTour) { isPhotoInFilter = true; } } if (isPhotoInFilter) { tempFilteredPhotos[filterIndex] = _allPhotos[photoIndex]; filterIndex++; } photoIndex++; } // remove trailing array items which are not set newFilteredPhotos = Arrays.copyOf(tempFilteredPhotos, filterIndex); } else { // a filter is not set, display all images but load exif data which is necessary when sorting by date newFilteredPhotos = Arrays.copyOf(_allPhotos, _allPhotos.length); // loop: all photos for (final Photo photo : _allPhotos) { if (_filterJobIsCanceled) { return; } if (photo.isExifLoaded == false) { // image is not yet loaded, it must be loaded to get the gps state putInExifLoadingQueue(photo); } } } // check sorting // if (_initialSorting != _currentSorting) { // // /* // * photo must be sorted because sorting is different than the initial sorting, this // * will sort only the filtered photo // */ // Arrays.sort(newFilteredPhotos, getCurrentComparator()); // } /** * Update UI * <p> * gallery MUST be updated even when no images are displayed because images from the * previous folder are still displayed */ updateUI_GalleryItems(newFilteredPhotos, null); if (_jobFilterDirtyCounter > currentDirtyCounter) { // filter is dirty again jobFilter_23_ScheduleSubsequentWithoutRunCheck(); } else { // clear progress bar jobUILoading_20_Schedule(); } // final float timeDiff = (float) (System.nanoTime() - start) / 1000000; //// if (timeDiff > 10) {} // System.out.println("filterJob_20_RunInitial:\t" + timeDiff + " ms\t"); //$NON-NLS-1$ //$NON-NLS-2$ // // TODO remove SYSTEM.OUT.PRINTLN } /** * Run filter and sorting again with newly loaded EXIF data until all EXIF data are loaded. */ private void jobFilter_32_RunSubsequent() { // final long start = System.nanoTime(); final boolean isGPSFilter = _imageFilterGPS == PhotoFilterGPS.WITH_GPS; final boolean isNoGPSFilter = _imageFilterGPS == PhotoFilterGPS.NO_GPS; final boolean isTourFilter = _imageFilterTour == PhotoFilterTour.WITH_TOURS; final boolean isNoTourFilter = _imageFilterTour == PhotoFilterTour.NO_TOURS; final boolean isFilterSet = isGPSFilter || isNoGPSFilter || isTourFilter || isNoTourFilter; // get current dirty counter final int currentDirtyCounter = _jobFilterDirtyCounter; Photo[] newFilteredPhotos = null; if (isFilterSet) { final int numberOfPhotos = _allPhotos.length; final Photo[] tempFilteredPhotos = new Photo[numberOfPhotos]; // filterindex is incremented when the filter contains a gallery item int filterIndex = 0; int photoIndex = 0; // loop: all photos for (final Photo photo : _allPhotos) { if (_filterJobIsCanceled) { return; } boolean isPhotoInFilterGps = false; boolean isPhotoInFilterTour = false; if (photo.isExifLoaded) { final boolean isPhotoWithGps = _isLinkPhotoDisplayed ? photo.isLinkPhotoWithGps : photo.isTourPhotoWithGps; if (isGPSFilter) { if (isPhotoWithGps) { isPhotoInFilterGps = true; } } else if (isNoGPSFilter) { if (!isPhotoWithGps) { isPhotoInFilterGps = true; } } else { // no gps filter isPhotoInFilterGps = true; } } else { // no gps filter isPhotoInFilterGps = true; } final boolean isSavedInTour = photo.isSavedInTour; if (isTourFilter) { if (isSavedInTour) { isPhotoInFilterTour = true; } } else if (isNoTourFilter) { if (!isSavedInTour) { isPhotoInFilterTour = true; } } else { // no tour filter isPhotoInFilterTour = true; } if (isPhotoInFilterGps && isPhotoInFilterTour) { tempFilteredPhotos[filterIndex] = _allPhotos[photoIndex]; filterIndex++; } photoIndex++; } // remove trailing array items which are not set newFilteredPhotos = Arrays.copyOf(tempFilteredPhotos, filterIndex); } else { // a filter is not set, display all images but load exif data which is necessary when filtering by date newFilteredPhotos = Arrays.copyOf(_allPhotos, _allPhotos.length); } // check sorting // if (_initialSorting != _currentSorting) { // // /* // * photo must be sorted because sorting is different than the initial sorting, this // * will sort only the filtered photo // */ // Arrays.sort(newFilteredPhotos, getCurrentComparator()); // } /** * UI update must be run in a UI job because the update can be very long when many * (thousands) small images are displayed */ _jobUIFilterPhoto = newFilteredPhotos; jobUIFilter_20_Schedule(0); if (_jobFilterDirtyCounter > currentDirtyCounter) { // filter is dirty again jobFilter_23_ScheduleSubsequentWithoutRunCheck(); } else { // clear progress bar jobUILoading_20_Schedule(); } // final float timeDiff = (float) (System.nanoTime() - start) / 1000000; // System.out.println("filterJob_30_RunSubsequent:\t" + timeDiff + " ms\t"); //$NON-NLS-1$ //$NON-NLS-2$ // // TODO remove SYSTEM.OUT.PRINTLN } private void jobUIFilter_10_Create() { _jobUIFilter = new UIJob("PicDirImages: Update UI with filtered gallery images") { //$NON-NLS-1$ @Override public IStatus runInUIThread(final IProgressMonitor monitor) { _jobUIFilterJobIsScheduled.set(false); try { jobUIFilter_30_Run(); } catch (final Exception e) { StatusUtil.log(e); } return Status.OK_STATUS; } }; _jobUIFilter.setSystem(true); } private void jobUIFilter_20_Schedule(final long delay) { JOB_LOCK.lock(); { try { _jobUIFilterDirtyCounter++; if ((_jobUIFilter.getState() == Job.RUNNING) == false) { // filter job is NOT running, schedule it final boolean isScheduled = _jobUIFilterJobIsScheduled.getAndSet(true); if (isScheduled == false) { _jobUIFilter.schedule(delay); } } } finally { JOB_LOCK.unlock(); } } } private void jobUIFilter_30_Run() { final Photo[] uiUpdatePhoto = _jobUIFilterPhoto; _jobUIFilterPhoto = null; if (uiUpdatePhoto == null) { return; } final int currentDirtyCounter = _jobUIFilterDirtyCounter; updateUI_GalleryItems(uiUpdatePhoto, null); if (_jobUIFilterDirtyCounter > currentDirtyCounter) { // UI is dirty again jobUIFilter_20_Schedule(DELAY_JOB_UI_FILTER); } } private void jobUILoading_10_Create() { _jobUILoading = new UIJob("PicDirImages: Update UI when loading images") {//$NON-NLS-1$ @Override public IStatus runInUIThread(final IProgressMonitor monitor) { _jobUILoadingIsScheduled.set(false); try { jobUILoading_30_Run(); } catch (final Exception e) { StatusUtil.log(e); } return Status.OK_STATUS; } }; _jobUILoading.setSystem(true); } public void jobUILoading_20_Schedule() { JOB_LOCK.lock(); { try { _jobUILoadingDirtyCounter++; if ((_jobUILoading.getState() == Job.RUNNING) == false) { // UI job is NOT running, schedule it final boolean isScheduled = _jobUILoadingIsScheduled.getAndSet(true); if (isScheduled == false) { _jobUILoading.schedule(DELAY_JOB_UI_LOADING); } } } finally { JOB_LOCK.unlock(); } } } private void jobUILoading_21_ScheduleWithoutRunCheck() { _jobUILoadingDirtyCounter++; final boolean isScheduled = _jobUILoadingIsScheduled.getAndSet(true); if (isScheduled == false) { _jobUILoading.schedule(DELAY_JOB_UI_LOADING); } } private void jobUILoading_30_Run() { final int currentDirtyCounter = _jobUILoadingDirtyCounter; final int exifQueueSize = PhotoLoadManager.getExifQueueSize(); final int imageQueueSize = PhotoLoadManager.getImageQueueSize(); final int imageHQQueueSize = PhotoLoadManager.getImageHQQueueSize(); if (exifQueueSize > 0) { updateUI_StatusMessageInUIThread(NLS.bind(Messages.Pic_Dir_StatusLabel_LoadingImagesFilter, new Object[] { imageQueueSize, imageHQQueueSize, exifQueueSize })); } else if (imageQueueSize > 0 || imageHQQueueSize > 0) { updateUI_StatusMessageInUIThread(NLS.bind(// Messages.Pic_Dir_StatusLabel_LoadingImages, new Object[] { imageQueueSize, imageHQQueueSize, exifQueueSize })); } else { // hide last message updateUI_StatusMessageInUIThread(UI.EMPTY_STRING); } if (_jobUILoadingDirtyCounter > currentDirtyCounter) { // UI is dirty again, schedule it again jobUILoading_21_ScheduleWithoutRunCheck(); } } private void onBeforeModifyGallerySelection() { if (_hoveredItem == null) { return; } _hoveredItem.isHovered = false; _hoveredItem.isNeedExitUIUpdate = false; _hoveredItem.hoveredStars = 0; _hoveredItem.isHovered_AnnotationTour = false; _hoveredItem.isHovered_InvalidImage = false; if (_hoveredItem.allSelectedGalleryItems != null) { for (final GalleryMT20Item selectedItems : _hoveredItem.allSelectedGalleryItems) { selectedItems.isInHoveredGroup = false; _galleryMT20.redrawItem(selectedItems); } /** * This collection cannot be cleared because it's the original list with selected items * in the gallery, so only the reference is set to <code>null</code>. */ _hoveredItem.allSelectedGalleryItems = null; } else { _galleryMT20.redrawItem(_hoveredItem); } _hoveredItem = null; } private void onDispose() { PhotoCache.removeEvictionListener(this); if (_galleryFont != null) { _galleryFont.dispose(); } stopLoadingImages(); } @Override public boolean onItemMouseDown(final GalleryMT20Item mouseDownItem, final int itemMouseX, final int itemMouseY) { if (mouseDownItem == null) { return false; } if (_isHandleRatingStars || _isShowAnnotations || mouseDownItem.photo.isLoadingError()) { if (_photoRenderer.isMouseDownHandled(mouseDownItem, itemMouseX, itemMouseY)) { _galleryMT20.redrawItem(mouseDownItem); return true; } } return false; } @Override public void onItemMouseExit(final GalleryMT20Item exitHoveredItem, final int itemMouseX, final int itemMouseY) { if (_isHandleRatingStars == false && _isShowAnnotations == false && exitHoveredItem.photo.isLoadingError() == false) { return; } /* * reset hovering */ final boolean isUpdateUI = exitHoveredItem.isNeedExitUIUpdate; exitHoveredItem.isHovered = false; exitHoveredItem.isNeedExitUIUpdate = false; exitHoveredItem.hoveredStars = 0; exitHoveredItem.isHovered_AnnotationTour = false; exitHoveredItem.isHovered_InvalidImage = false; if (exitHoveredItem.allSelectedGalleryItems != null) { for (final GalleryMT20Item selectedItems : exitHoveredItem.allSelectedGalleryItems) { selectedItems.isInHoveredGroup = false; if (isUpdateUI) { _galleryMT20.redrawItem(selectedItems); } } /** * This collection cannot be cleared because it's the original list with selected items * in the gallery, so only the reference is set to <code>null</code>. */ exitHoveredItem.allSelectedGalleryItems = null; _hoveredItem = null; } else { if (isUpdateUI) { _galleryMT20.redrawItem(exitHoveredItem); } } } @Override public void onItemMouseHovered(final GalleryMT20Item hoveredItem, final int itemMouseX, final int itemMouseY) { if (_isShowTooltip) { _photoGalleryTooltip.show(hoveredItem); } if (hoveredItem == null) { return; } final boolean isRatingStarsHandledAndPainted = _isHandleRatingStars && _isAttributesPainted; if (isRatingStarsHandledAndPainted || _isShowAnnotations || hoveredItem.photo.isLoadingError()) { _hoveredItem = hoveredItem; hoveredItem.isHovered = true; // this will set allSelectedGalleryItems in the hovered item final boolean isModified = _photoRenderer.isItemHovered(hoveredItem, itemMouseX, itemMouseY); // don't reset here, this is done in the item exit event hoveredItem.isNeedExitUIUpdate |= isModified; if (isModified) { if (hoveredItem.allSelectedGalleryItems != null) { for (final GalleryMT20Item selectedItems : hoveredItem.allSelectedGalleryItems) { selectedItems.isInHoveredGroup = true; _galleryMT20.redrawItem(selectedItems); } } else { _galleryMT20.redrawItem(hoveredItem); } } } } private void onModifyFont() { createGalleryFont(); _photoRenderer.setFont(_galleryFont); } public void onReparentShell(final Shell reparentedShell) { _photoGalleryTooltip.onReparentShell(reparentedShell); /* * when a tooltip is reparented, the context menu must be recreated otherwise an exception * is thown that the menu shell has the wrong parent */ createContextMenu(); } private void onSelectPhoto() { // show default message updateUI_StatusMessage(UI.EMPTY_STRING); // fire selection _photoGalleryProvider.setSelection(createPhotoSelection()); } /** * Get gps state and exif data * * @return Returns <code>true</code> when exif data is already available from the cache and must * not be loaded. */ private boolean putInExifLoadingQueue(final Photo photo) { final PhotoImageMetadata photoImageMetadata = ExifCache.get(photo.imageFilePathName); if (photoImageMetadata != null) { photo.updateImageMetadata(photoImageMetadata); return true; } PhotoLoadManager.putImageInLoadingQueueExif(photo, new LoadCallbackExif(photo, _currentExifRunId)); return false; } @Override public ColumnViewer recreateViewer(final ColumnViewer columnViewer) { _pageDetails.setRedraw(false); { _photoViewer.getTable().dispose(); createUI_32_PhotoViewer(_pageDetails); _pageDetails.layout(); // update the viewer reloadViewer(); } _pageDetails.setRedraw(true); return _photoViewer; } public void redrawItem(final GalleryMT20Item galleryItem) { _galleryMT20.redrawItem(galleryItem); } public void refreshUI() { _galleryMT20.redraw(); } @Override public void reloadViewer() { _photoViewer.setInput(new Object[0]); } void restoreState() { _galleryMT20.restoreState(); // set font onModifyFont(); /* * gallery sorting */ final GallerySorting defaultSorting = GallerySorting.FILE_NAME; final String stateValue = Util.getStateString(_state, STATE_IMAGE_SORTING, defaultSorting.name()); try { _currentSorting = GallerySorting.valueOf(stateValue); } catch (final Exception e) { _currentSorting = defaultSorting; } // pref store settings updateUI_FromPrefStore(false); /** * !!! very important !!! * <p> * show gallery to initialize client area, otherwise the width is 0 until the page is * displayed in a later step */ showPageBookPage(_galleryMT20, false); // show default page _lblDefaultPage.setText(_defaultStatusMessage); showPageBookPage(_pageDefault, false); /* * set thumbnail size after gallery client area is set */ final int stateThumbSize = Util.getStateInt(_state, STATE_THUMB_IMAGE_SIZE, PhotoLoadManager.IMAGE_SIZE_THUMBNAIL); // restore gallery action bar if (_galleryActionBar != null) { _galleryActionBar.restoreState(_state, stateThumbSize); _galleryActionBar.setThumbnailSizeVisibility(_galleryMT20.isVertical()); } // restore thumbnail image size setThumbnailSizeRestore(stateThumbSize); /* * gallery folder/tour image positions */ final String[] positionKeys = _state.getArray(STATE_GALLERY_POSITION_KEY); final String[] positionValues = _state.getArray(STATE_GALLERY_POSITION_VALUE); if (positionKeys != null && positionValues != null) { // ensure same size if (positionKeys.length == positionValues.length) { for (int positionIndex = 0; positionIndex < positionKeys.length; positionIndex++) { final String positionValueString = positionValues[positionIndex]; try { final Double positionValue = Double.parseDouble(positionValueString); _galleryPositions.put(positionKeys[positionIndex], positionValue); } catch (final Exception e) { // ignore } } } } _restoredSelection = Util.getStateIntArray(_state, STATE_SELECTED_ITEMS, null); } public void saveState() { if (_galleryMT20.isVertical()) { /* * image size is only used in a vertical gallery, horizontal gallery is using the * clientarea height to get the image size */ _state.put(STATE_THUMB_IMAGE_SIZE, _photoImageSize); } _state.put(STATE_IMAGE_SORTING, _currentSorting.name()); /* * keep gallery positions */ // preserve current gallery position final double galleryPosition = _galleryMT20.getGalleryPosition(); if (_currentGalleryPositionKey != null) { _galleryPositions.put(_currentGalleryPositionKey, galleryPosition); } else if (_newGalleryPositionKey != null) { _galleryPositions.put(_newGalleryPositionKey, galleryPosition); } final Set<String> positionKeys = _galleryPositions.keySet(); final int positionSize = positionKeys.size(); if (positionSize > 0) { final String[] positionKeyArray = positionKeys.toArray(new String[positionSize]); final String[] positionValues = new String[positionSize]; for (int positionIndex = 0; positionIndex < positionKeyArray.length; positionIndex++) { final String positionKey = positionKeyArray[positionIndex]; positionValues[positionIndex] = _galleryPositions.get(positionKey).toString(); } _state.put(STATE_GALLERY_POSITION_KEY, positionKeyArray); _state.put(STATE_GALLERY_POSITION_VALUE, positionValues); } Util.setState(_state, STATE_SELECTED_ITEMS, _galleryMT20.getSelectionIndex()); _columnManager.saveState(_state); _galleryMT20.saveState(); } void selectGalleryType(final GalleryType galleryType) { _galleryType = galleryType; showPageGalleryContent(); } public void selectItem(final int itemIndex, final String galleryPositionKey) { _galleryMT20.selectItem(itemIndex); // // ensure to keep position, this has not worked in the full screen gallery when image was resized //// _currentGalleryPositionKey = galleryPositionKey; //// _newGalleryPositionKey = galleryPositionKey; // // final double currentGalleryPosition = _galleryMT20.getGalleryPosition(); //// _galleryPositions.put(galleryPositionKey, currentGalleryPosition); // // System.out.println(UI.timeStampNano() + " selectItem()\t" + galleryPositionKey + "\t" + currentGalleryPosition); // // TODO remove SYSTEM.OUT.PRINTLN } public void setDefaultStatusMessage(final String message) { _defaultStatusMessage = message; } public void setExternalMouseListener(final IExternalGalleryListener externalGalleryMouseListener) { _galleryMT20.setExternalMouseListener(externalGalleryMouseListener); } /** * @param photoFilterGPS * @param photoFilterTour */ public void setFilter(final PhotoFilterGPS photoFilterGPS, final PhotoFilterTour photoFilterTour) { _imageFilterGPS = photoFilterGPS; _imageFilterTour = photoFilterTour; } public void setFocus() { _galleryMT20.setFocus(); } public void setFullScreenImageViewer(final FullScreenImageViewer fullScreenImageViewer) { _fullScreenImageViewer = fullScreenImageViewer; _galleryMT20.setFullScreenImageViewer(fullScreenImageViewer); } private void setIsLinkPhotoDisplayed(final boolean isLinkPhotoDisplayed) { _isLinkPhotoDisplayed = isLinkPhotoDisplayed; _photoRenderer.setIsLinkPhotoDisplayed(isLinkPhotoDisplayed); } public void setPhotoInfo(final boolean isShowPhotoName, final PhotoDateInfo photoDateInfo, final boolean isShowAnnotations, final boolean isShowTooltip) { _isShowTooltip = isShowTooltip; _isShowAnnotations = isShowAnnotations; _photoRenderer.setPhotoInfo(isShowPhotoName, photoDateInfo, isShowAnnotations); } /** * A custom actionbar can be displayed, by default it is hidden. The actionbar can be retrieved * with {@link #getCustomActionBarContainer()}. * <p> * This method <b>must</b> be called before * {@link #createImageGallery(Composite, int, IPhotoGalleryProvider)} is called. */ public void setShowCustomActionBar() { _isShowCustomActionBar = true; } /** * Prevent to open pref dialog, when it's opened it would close this tooltip and the pref dialog * is hidden -->> APP IS FREEZING !!! */ public void setShowOtherShellActions(final boolean isShowOtherShellActions) { _galleryMT20.setIsShowOtherShellActions(isShowOtherShellActions); } void setShowPhotoRatingStars(final RatingStarBehaviour ratingStarBehaviour) { _isHandleRatingStars = ratingStarBehaviour == RatingStarBehaviour.HOVERED_STARS; _photoRenderer.setShowRatingStars(ratingStarBehaviour); // redraw gallery with new settings _galleryMT20.redraw(); } /** * Thumbnail size combobox can be displayed, by default it is hidden, This method <b>must</b> be * called before {@link #createImageGallery(Composite, int, IPhotoGalleryProvider)} is called. */ public void setShowThumbnailSize() { _isShowThumbsize = true; } public void setSorting(final GallerySorting gallerySorting) { // set new sorting algorithm _currentSorting = gallerySorting; } private void setStatusMessage(final String message) { final IStatusLineManager statusLineManager = _photoGalleryProvider.getStatusLineManager(); if (statusLineManager != null) { statusLineManager.setMessage(message); } } /** * Setting thumbnail size is done in the gallery action bar, this can be done only for a * vertical gallery. * * @param newImageSize */ public void setThumbnailSize(final int newImageSize) { int newGalleryItemSize = newImageSize + _photoBorderSize; if (newGalleryItemSize == _galleryMT20.getItemWidth()) { // nothing has changed return; } final int prevGalleryItemSize = _galleryMT20.getItemWidth(); // update gallery final int adjustedItemSize = _galleryMT20.zoomGallery(newGalleryItemSize, false); if (adjustedItemSize == -1 || adjustedItemSize == prevGalleryItemSize) { // nothing has changed return; } PhotoLoadManager.stopImageLoading(false); if (adjustedItemSize != newGalleryItemSize) { /* * size has been modified, this case can occure when the gallery is switching the * scrollbars on/off depending on the content */ newGalleryItemSize = adjustedItemSize; } updateUI_AfterZoomInOut(newGalleryItemSize); } private void setThumbnailSizeRestore(final int thumbSize) { PhotoLoadManager.stopImageLoading(false); int requestedItemSize = thumbSize + _photoBorderSize; // update gallery final int adjustedItemSize = _galleryMT20.zoomGallery(requestedItemSize, true); // check if size has been modified when zoomed in/out if (adjustedItemSize != requestedItemSize) { /* * size has been modified, this case can occure when the gallery is switching the * scrollbars on/off depending on the content */ requestedItemSize = adjustedItemSize; } updateUI_AfterZoomInOut(requestedItemSize); } public void setVertical(final boolean isVerticalGallery) { if (isVerticalGallery) { final int verticalWidth = _galleryMT20.getVerticalItemWidth(); updateUI_AfterZoomInOut(verticalWidth); } _galleryActionBar.setThumbnailSizeVisibility(isVerticalGallery); _galleryMT20.setVertical(isVerticalGallery); } public void showImages(final ArrayList<Photo> allPhotos, final String galleryPositionKey, final boolean isLinkPhotoDisplayed, final boolean isFilteredAndSorted) { final Photo[] photos = allPhotos.toArray(new Photo[allPhotos.size()]); if (isFilteredAndSorted == false) { // sort photos with current sorting algorithm Arrays.sort(photos, getCurrentComparator()); } showImages(photos, galleryPositionKey, isLinkPhotoDisplayed); } /** * Display images for a selected folder. * * @param imageFolder * @param isReloadFolder * @param isLinkPhotoDisplayed */ public void showImages(final File imageFolder, final boolean isReloadFolder, final boolean isLinkPhotoDisplayed) { jobFilter_12_Stop(); PhotoLoadManager.stopImageLoading(true); ////////////////////////////////////////// // // MUST BE REMOVED, IS ONLY FOR TESTING // // disposeAndDeleteAllImages(); // PhotoLoadManager.removeInvalidImageFiles(); // // MUST BE REMOVED, IS ONLY FOR TESTING // ////////////////////////////////////////// setIsLinkPhotoDisplayed(isLinkPhotoDisplayed); if (imageFolder == null) { _lblDefaultPage.setText(Messages.Pic_Dir_Label_ReadingFolders); } else { _lblDefaultPage.setText(NLS.bind(Messages.Pic_Dir_Label_Loading, imageFolder.getAbsolutePath())); } showPageBookPage(_pageDefault, true); _photoFolderWhichShouldBeDisplayed = imageFolder; workerUpdate(imageFolder, isReloadFolder); } public void showImages(final Photo[] allFilteredAnsSortedPhotos, final String galleryPositionKey, final boolean isLinkPhotoDisplayed) { // System.out.println(UI.timeStampNano() + " showImages() " + galleryPositionKey); // // TODO remove SYSTEM.OUT.PRINTLN jobFilter_12_Stop(); PhotoLoadManager.stopImageLoading(true); ////////////////////////////////////////// // // MUST BE REMOVED, IS ONLY FOR TESTING // // disposeAndDeleteAllImages(); // PhotoLoadManager.removeInvalidImageFiles(); // // MUST BE REMOVED, IS ONLY FOR TESTING // ////////////////////////////////////////// setIsLinkPhotoDisplayed(isLinkPhotoDisplayed); // images are not loaded from a folder, photos are already available _photoFolder = null; _newGalleryPositionKey = galleryPositionKey; // initialize tooltip for new images _photoGalleryTooltip.reset(true); _galleryMT20.deselectAll(); _allPhotos = allFilteredAnsSortedPhotos; final double galleryPosition = getCachedGalleryPosition(); updateUI_GalleryItems(_allPhotos, galleryPosition); } /** * @param isShowPhotoName * @param photoDateInfo * @param isShowAnnotations * @param isShowTooltip */ void showInfo(final boolean isShowPhotoName, final PhotoDateInfo photoDateInfo, final boolean isShowAnnotations, final boolean isShowTooltip) { setPhotoInfo(isShowPhotoName, photoDateInfo, isShowAnnotations, isShowTooltip); // reset tooltip, otherwise it could be displayed if it should not _photoGalleryTooltip.reset(true); _galleryMT20.redraw(); } public void showItem(final int itemIndex) { _galleryMT20.showItem(itemIndex); } private void showPageBookPage(final Composite page, final boolean isDelay) { if (isDelay) { /* * delay showing the default page because it is flickering when an image is displayed * again within a few milliseconds */ _delayCounter[0]++; if (page == _pageDefault || page == _pageGalleryInfo) { _pageBook.getDisplay().timerExec(100, new Runnable() { private int __delayCounter = _delayCounter[0]; private Composite __page = page; @Override public void run() { // check if this still the same page which should be displayed if (__delayCounter == _delayCounter[0]) { _pageBook.showPage(__page); } } }); } else { _pageBook.showPage(page); } } else { _pageBook.showPage(page); } } private void showPageGalleryContent() { Composite galleryContent; if (_galleryType == GalleryType.DETAILS) { galleryContent = _pageDetails; _photoViewer.setInput(new Object()); } else { // default is thumnail gallery galleryContent = _galleryMT20; // update gallery _galleryMT20.setVirtualItems(_sortedGalleryItems, _contentGalleryPosition); } showPageBookPage(galleryContent, true); } public void showRestoreFolder(final String restoreFolderName) { if (restoreFolderName == null) { _lblDefaultPage.setText(Messages.Pic_Dir_StatusLabel_NoFolder); } else { _lblDefaultPage.setText(NLS.bind(Messages.Pic_Dir_StatusLabel_RestoringFolder, restoreFolderName)); } showPageBookPage(_pageDefault, true); } public void sortGallery(final GallerySorting gallerySorting) { // check if resorting is needed if (_currentSorting == gallerySorting) { return; } /* * deselect all, this could be better implemented to keep selection, but is not yet done */ deselectAll(); // set new sorting algorithm _currentSorting = gallerySorting; BusyIndicator.showWhile(_display, new Runnable() { @Override public void run() { sortGallery_10_Runnable(); } }); } /** * This will sort all already created gallery items */ private void sortGallery_10_Runnable() { if (_allPhotos == null || _allPhotos.length == 0) { // there are no files return; } final GalleryMT20Item[] virtualGalleryItems = _galleryMT20.getAllVirtualItems(); final int virtualSize = virtualGalleryItems.length; if (virtualSize == 0) { // there are no items return; } // sort photos with new sorting algorithm Arrays.sort(_sortedAndFilteredPhotos, getCurrentComparator()); updateUI_GalleryItems(_sortedAndFilteredPhotos, null); } public void stopLoadingImages() { // stop jobs jobFilter_12_Stop(); PhotoLoadManager.stopImageLoading(true); workerStop(); } /** * @param fgColor * @param bgColor * @param selectionFgColor * @param noFocusSelectionFgColor * @param initUI * Is <code>true</code> after a restore to update the UI that not a default UI color * is displayed. */ public void updateColors(final Color fgColor, final Color bgColor, final Color selectionFgColor, final Color noFocusSelectionFgColor, final boolean initUI) { /* * set color in action bar only for Linux & Windows, setting color in OSX looks not very * good */ if (UI.IS_OSX == false && _galleryActionBar != null) { // looks ugli with a custom toolbar manager // _galleryActionBar.updateColors(fgColor, bgColor); } /* * gallery */ _galleryMT20.setColors(fgColor, bgColor); _photoRenderer.setColors(fgColor, bgColor, selectionFgColor, noFocusSelectionFgColor); _pageDefault.setBackground(bgColor); /* * loading page */ _lblDefaultPage.setForeground(fgColor); _lblDefaultPage.setBackground(bgColor); /* * page: folder info */ _pageGalleryInfo.setForeground(fgColor); _pageGalleryInfo.setBackground(bgColor); _lblGalleryInfo.setForeground(fgColor); _lblGalleryInfo.setBackground(bgColor); } /** * The UI of the photos are updated. * * @param arrayList * Contains photos which are modified. Items in this list should be of type * {@link Photo}. */ public void updatePhotos(final ArrayList<?> arrayList) { if (_allPhotos == null) { return; } for (int photoIndex = 0; photoIndex < _allPhotos.length; photoIndex++) { final Photo galleryPhoto = _allPhotos[photoIndex]; final String galleryImageFilePathName = galleryPhoto.imageFilePathName; for (final Object object : arrayList) { if (object instanceof Photo) { final Photo updatedPhoto = (Photo) object; if (galleryImageFilePathName.equals(updatedPhoto.imageFilePathName)) { _allPhotos[photoIndex] = updatedPhoto; break; } } } } /* * this is the easy way to update the UI for all visible photos */ _galleryMT20.redraw(); } /** * @param galleryItemSizeWithBorder * Image size with border */ private void updateUI_AfterZoomInOut(final int galleryItemSizeWithBorder) { final int imageSizeWithoutBorder = galleryItemSizeWithBorder - _photoBorderSize; if (imageSizeWithoutBorder != _photoImageSize) { // image size has changed _photoImageSize = imageSizeWithoutBorder; if (_galleryActionBar != null) { _galleryActionBar.updateUI_AfterZoomInOut(imageSizeWithoutBorder); } _photoGalleryTooltip.setGalleryImageSize(_photoImageSize); _photoGalleryTooltip.reset(true); _isAttributesPainted = _photoRenderer.isAttributesPainted(galleryItemSizeWithBorder); enableAttributeActions(_isAttributesPainted); } } private void updateUI_FromPrefStore(final boolean isUpdateUI) { /* * image quality */ final boolean isShowHighQuality = _prefStore.getBoolean(// IPhotoPreferences.PHOTO_VIEWER_IS_SHOW_IMAGE_WITH_HIGH_QUALITY); final int hqMinSize = _prefStore.getInt(// IPhotoPreferences.PHOTO_VIEWER_HIGH_QUALITY_IMAGE_MIN_SIZE); _galleryMT20.setImageQuality(isShowHighQuality, hqMinSize); /* * text minimum thumb size */ final int minThumbSize = _prefStore.getInt(IPhotoPreferences.PHOTO_VIEWER_TEXT_MIN_THUMB_SIZE); final int borderSize = _prefStore.getInt(IPhotoPreferences.PHOTO_VIEWER_IMAGE_BORDER_SIZE); _photoRenderer.setSizeImageBorder(borderSize); _photoRenderer.setSizeTextMinThumb(minThumbSize); // get update border size _photoBorderSize = _photoRenderer.getBorderSize(); _fullScreenImageViewer.setPrefSettings(isUpdateUI); } private void updateUI_GalleryInfo() { _display.syncExec(new Runnable() { @Override public void run() { if (_galleryMT20.isDisposed()) { return; } showPageBookPage(_pageGalleryInfo, true); final int imageCount = _allPhotos.length; if (imageCount == 0) { if (_defaultStatusMessage.length() > 0) { _lblGalleryInfo.setText(_defaultStatusMessage); } else { _lblGalleryInfo.setText(Messages.Pic_Dir_StatusLabel_NoImages); } } else { final int exifLoadingQueueSize = PhotoLoadManager.getExifQueueSize(); if (exifLoadingQueueSize > 0) { // show filter message only when image files are being loaded _lblGalleryInfo.setText( NLS.bind(Messages.Pic_Dir_StatusLabel_FilteringImages, exifLoadingQueueSize)); } else { if (_sortedAndFilteredPhotos.length == 0) { if (imageCount == 1) { _lblGalleryInfo.setText(Messages.Pic_Dir_StatusLabel_1ImageIsHiddenByFilter); } else { _lblGalleryInfo.setText(NLS .bind(Messages.Pic_Dir_StatusLabel_nImagesAreHiddenByFilter, imageCount)); } } else { // this case should not happen, the gallery is displayed } } } if (imageCount == 0) { _isAttributesPainted = false; } enableActions(imageCount > 0); enableAttributeActions(_isAttributesPainted); } }); } /** * Set gallery items into a list according to the new sorting/filtering * * @param filteredAndSortedPhotos * @param galleryPosition * When <code>null</code> the old position is preserved, otherwise images at a new * position are displayed. */ private void updateUI_GalleryItems(final Photo[] filteredAndSortedPhotos, final Double galleryPosition) { final HashMap<String, GalleryMT20Item> existingGalleryItems = _galleryMT20.getCreatedGalleryItems(); final int photoSize = filteredAndSortedPhotos.length; final GalleryMT20Item[] sortedGalleryItems = new GalleryMT20Item[photoSize]; // convert sorted photos into sorted gallery items for (int itemIndex = 0; itemIndex < photoSize; itemIndex++) { final Photo sortedPhotos = filteredAndSortedPhotos[itemIndex]; // get gallery item for the current photo final GalleryMT20Item galleryItem = existingGalleryItems.get(sortedPhotos.imageFilePathName); if (galleryItem != null) { sortedGalleryItems[itemIndex] = galleryItem; } } _sortedAndFilteredPhotos = filteredAndSortedPhotos; _display.syncExec(new Runnable() { @Override public void run() { if (_galleryMT20.isDisposed()) { return; } final boolean isItemAvailable = sortedGalleryItems.length > 0; enableActions(isItemAvailable); if (isItemAvailable) { // gallery items are available _sortedGalleryItems = sortedGalleryItems; _contentGalleryPosition = galleryPosition; showPageGalleryContent(); } else { // there is no gallery item updateUI_GalleryInfo(); } } }); } public void updateUI_StatusMessage(final String message) { if (message.length() == 0) { setStatusMessage(getStatusDefaultMessage()); } else { setStatusMessage(message); } } public void updateUI_StatusMessageInUIThread(final String message) { _display.asyncExec(new Runnable() { @Override public void run() { if (_galleryMT20.isDisposed()) { return; } if (message.length() == 0) { setStatusMessage(getStatusDefaultMessage()); } else { setStatusMessage(message); } } }); } /** * Get image files from current directory */ private void workerExecute() { _workerStart = System.currentTimeMillis(); Photo[] newPhotos = null; if (_workerStateDir != null) { _display.syncExec(new Runnable() { @Override public void run() { // guard against the ui being closed before this runs if (_uiContainer.isDisposed()) { return; } setStatusMessage(UI.EMPTY_STRING); } }); // get all image files, sorting is not yet done final File[] files = _workerStateDir.listFiles(_fileFilter); // check if interruption occred if (_workerCancelled) { return; } int numberOfImages = 0; if (files == null) { // prevent NPE newPhotos = new Photo[0]; } else { // image files are available numberOfImages = files.length; newPhotos = new Photo[numberOfImages]; // create a photo for each image file for (int fileIndex = 0; fileIndex < numberOfImages; fileIndex++) { final File photoFile = files[fileIndex]; Photo galleryPhoto = PhotoCache.getPhoto(photoFile.getAbsolutePath()); if (galleryPhoto == null) { /* * photo is not found in the photo cache, create a new photo */ galleryPhoto = new Photo(photoFile); PhotoCache.setPhoto(galleryPhoto); } newPhotos[fileIndex] = galleryPhoto; } /* * sort photos with currently selected comparator */ _currentComparator = getCurrentComparator(); Arrays.sort(newPhotos, _currentComparator); } // check if the previous files retrival has been interrupted if (_workerCancelled) { return; } _photoFolder = _workerStateDir; if (_photoFolder != null) { _newGalleryPositionKey = _photoFolder.getAbsolutePath(); } workerExecute_DisplayImages(newPhotos); } } private void workerExecute_DisplayImages(final Photo[] photos) { _allPhotos = photos; _display.syncExec(new Runnable() { @Override public void run() { // guard against the ui being closed before this runs if (_uiContainer.isDisposed()) { return; } // initialize tooltip for a new folder _photoGalleryTooltip.reset(true); final double galleryPosition = getCachedGalleryPosition(); _galleryMT20.setupItems(0, galleryPosition, _restoredSelection); _restoredSelection = null; /* * update status info */ final long timeDiff = System.currentTimeMillis() - _workerStart; final String timeDiffText = NLS.bind(Messages.Pic_Dir_Status_Loaded, new Object[] { Long.toString(timeDiff), Integer.toString(_allPhotos.length) }); setStatusMessage(timeDiffText); } }); /* * start filter always, even when no filter is set because it is loading exif data which is * used to sort images correctly by exif date (when available) and not by file date */ jobFilter_20_Schedule1st(); } /** * Stops the worker and waits for it to terminate. */ private void workerStop() { if (_workerThread == null) { return; } synchronized (_workerLock) { _workerCancelled = true; _workerStopped = true; _workerLock.notifyAll(); } while (_workerThread != null) { if (!_display.readAndDispatch()) { _display.sleep(); } } } /** * Notifies the worker that it should update itself with new data. Cancels any previous * operation and begins a new one. * * @param newFolder * the new base directory for the table, null is ignored * @param isReloadFolder */ private void workerUpdate(final File newFolder, final boolean isReloadFolder) { if (newFolder == null) { return; } if (isReloadFolder == false && _workerNextFolder != null && _workerNextFolder.equals(newFolder)) { return; } synchronized (_workerLock) { _workerNextFolder = newFolder; _workerStopped = false; _workerCancelled = true; _workerLock.notifyAll(); } if (_workerThread == null) { _workerThread = new Thread(_workerRunnable, "PicDirImages: Retrieving folder image files");//$NON-NLS-1$ _workerThread.start(); } } }