load2 = () -> {
+ final BufferedImage img = ImageIO.read(in);
+ if (img != null) {
+ ImageData data = convertToSWT(img);
+ if (createThumb) {
+ float w = 0;
+ float h = 0;
+ boolean doscale = false;
+
+ if (data.height > dim.x - space * 2) {
+ doscale = true;
+ h = (float) (data.height - (dim.x - space * 2)) / data.height;
+ }
+ if (data.width > dim.x - space * 2) {
+ doscale = true;
+ w = (float) (data.width - (dim.x - space * 2)) / data.width;
+ }
+
+ if (doscale) {
+ final float scale = Math.max(w, h);
+ w = data.width - data.width * scale;
+ h = data.height - data.height * scale;
+ if (w < 1) {
+ w = 1;
+ }
+ if (h < 1) {
+ h = 1;
+ }
+ data = data.scaledTo((int) w, (int) h);
+ }
+ }
+ return new Image(display, data);
+ } else {
+ throw new IOException("Unable to load");
+ }
+ };
+ return BusyIndicator.compute(load2).get();
+ }
+
+ /**
+ * Returns if the fullsize image has been initialize or not.. ie null or not.
+ */
+ public boolean hasFullsize() {
+ if (fullsize != null && !fullsize.isDisposed()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void initThumb() throws FileNotFoundException, InterruptedException, ExecutionException, IOException {
+ if (thumb != null) {
+ thumb.dispose();
+ }
+
+ thumb = getImage(new FileImageInputStream(new File(file.getAbsolutePath())), true);
+ }
+
+ public void dispose() {
+ if (fullsize != null && !fullsize.isDisposed()) {
+ fullsize.dispose();
+ }
+
+ if (thumb != null && !thumb.isDisposed()) {
+ thumb.dispose();
+ }
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public void setSelected(final boolean selected) {
+ this.selected = selected;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(final String displayName) {
+ this.displayName = displayName;
+ }
+
+ private void replaceWithCannotDisplayImage() {
+ try {
+ dispose();
+ fullsize = null;
+ thumb = null;
+ final String tmpName = getDisplayName();
+ setFile(unavailable);
+ setDisplayName(tmpName + " (image could not be displayed)");
+ } catch (final Exception e) {
+ manager.getImageOrganizer().removeHolder(this);
+ }
+ }
+
+ /**
+ * From: https://stackoverflow.com/questions/6498467/conversion-from-bufferedimage-to-swt-image
+ *
+ * snippet 156: convert between SWT Image and AWT BufferedImage.
+ *
+ * For a list of all SWT example snippets see https://www.eclipse.org/swt/snippets/
+ */
+ private static ImageData convertToSWT(final BufferedImage bufferedImage) {
+ if (bufferedImage.getColorModel() instanceof DirectColorModel) {
+ /*
+ * DirectColorModel colorModel = (DirectColorModel)bufferedImage.getColorModel(); PaletteData
+ * palette = new PaletteData( colorModel.getRedMask(), colorModel.getGreenMask(),
+ * colorModel.getBlueMask()); ImageData data = new ImageData(bufferedImage.getWidth(),
+ * bufferedImage.getHeight(), colorModel.getPixelSize(), palette); WritableRaster raster =
+ * bufferedImage.getRaster(); int[] pixelArray = new int[3]; for (int y = 0; y < data.height; y++) {
+ * for (int x = 0; x < data.width; x++) { raster.getPixel(x, y, pixelArray); int pixel =
+ * palette.getPixel(new RGB(pixelArray[0], pixelArray[1], pixelArray[2])); data.setPixel(x, y,
+ * pixel); } }
+ */
+ final DirectColorModel colorModel = (DirectColorModel) bufferedImage.getColorModel();
+ final PaletteData palette = new PaletteData(colorModel.getRedMask(), colorModel.getGreenMask(), colorModel.getBlueMask());
+ final ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ final int rgb = bufferedImage.getRGB(x, y);
+ final int pixel = palette.getPixel(new RGB(rgb >> 16 & 0xFF, rgb >> 8 & 0xFF, rgb & 0xFF));
+ data.setPixel(x, y, pixel);
+ if (colorModel.hasAlpha()) {
+ data.setAlpha(x, y, rgb >> 24 & 0xFF);
+ }
+ }
+ }
+ return data;
+ } else if (bufferedImage.getColorModel() instanceof IndexColorModel) {
+ final IndexColorModel colorModel = (IndexColorModel) bufferedImage.getColorModel();
+ final int size = colorModel.getMapSize();
+ final byte[] reds = new byte[size];
+ final byte[] greens = new byte[size];
+ final byte[] blues = new byte[size];
+ colorModel.getReds(reds);
+ colorModel.getGreens(greens);
+ colorModel.getBlues(blues);
+ final RGB[] rgbs = new RGB[size];
+ for (int i = 0; i < rgbs.length; i++) {
+ rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF);
+ }
+ final PaletteData palette = new PaletteData(rgbs);
+ final ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
+ data.transparentPixel = colorModel.getTransparentPixel();
+ final WritableRaster raster = bufferedImage.getRaster();
+ final int[] pixelArray = new int[1];
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ raster.getPixel(x, y, pixelArray);
+ data.setPixel(x, y, pixelArray[0]);
+ }
+ }
+ return data;
+ } else if (bufferedImage.getColorModel() instanceof ComponentColorModel) {
+ final ComponentColorModel colorModel = (ComponentColorModel) bufferedImage.getColorModel();
+ // ASSUMES: 3 BYTE BGR IMAGE TYPE
+ final PaletteData palette = new PaletteData(0x0000FF, 0x00FF00, 0xFF0000);
+ final ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette);
+ // This is valid because we are using a 3-byte Data model with no transparent
+ // pixels
+ data.transparentPixel = -1;
+ final WritableRaster raster = bufferedImage.getRaster();
+ final int[] pixelArray = new int[4];
+ for (int y = 0; y < data.height; y++) {
+ for (int x = 0; x < data.width; x++) {
+ raster.getPixel(x, y, pixelArray);
+ final int pixel = palette.getPixel(new RGB(pixelArray[0], pixelArray[1], pixelArray[2]));
+ data.setPixel(x, y, pixel);
+ }
+ }
+ return data;
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "ImageHolder [displayName=" + displayName + "]";
+ }
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/core/ImageOrganizer.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/core/ImageOrganizer.java
new file mode 100644
index 0000000..b1ab7fe
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/core/ImageOrganizer.java
@@ -0,0 +1,231 @@
+/*
+ * Software released under Common Public License (CPL) v1.0
+ */
+package me.glindholm.plugin.quickimage2.core;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * @author Per Salomonsson
+ *
+ */
+public class ImageOrganizer {
+ public static final int VIEW_THUMB = 0;
+ public static final int VIEW_FULLSIZE = 1;
+
+ public String path;
+ public String filename;
+ private int index = 0;
+
+ private final List holders = new ArrayList<>();
+ // private ImageHolder[] holders;
+ private final Display display;
+ private int activeView = VIEW_FULLSIZE;
+ private final QManager manager;
+
+ public ImageOrganizer(final QManager manager, final Display display) {
+ this.manager = manager;
+ this.display = display;
+ }
+
+ /**
+ * Can be used instead of setPath
+ *
+ */
+ public void setStorage(final IStorage storage) {
+ path = null;
+ filename = null;
+ holders.clear();
+ final ImageHolder holder = new ImageHolder(manager, display);
+ holder.setStorage(storage);
+ holders.add(holder);
+ }
+
+ public void setPath(final String path, final String filename) {
+ this.path = path;
+ this.filename = filename;
+ initList();
+ }
+
+ private ImageHolder holder(final int index) {
+ return holders.get(index);
+ }
+
+ public void removeHolder(final ImageHolder holder) {
+ holders.remove(holder);
+ while (index >= holders.size() && index > 0) {
+ index--;
+ }
+
+ manager.getImageEditor().setPartName(getCurrent().getDisplayName());
+ }
+
+ public void removeHolder(int index) {
+ holders.remove(index);
+
+ while (index >= holders.size() && index > 0) {
+ index--;
+ }
+ }
+
+ public ImageHolder getNext() {
+ if (holder(index).getFullsize() != null && !holder(index).getFullsize().isDisposed()) {
+ holder(index).getFullsize().dispose();
+ }
+
+ index = (index + 1) % holders.size();
+
+ return holder(index);
+ }
+
+ public int getCount() {
+ return holders.size();
+ }
+
+ public boolean isSingle() {
+ return getHolders().size() == 1;
+ }
+
+ public List getHolders() {
+ return holders;
+ }
+
+ public ImageHolder getPrevious() {
+ if (holder(index).getFullsize() != null && !holder(index).getFullsize().isDisposed()) {
+ holder(index).getFullsize().dispose();
+ }
+
+ if (index > 0) {
+ index--;
+ }
+
+ return holder(index);
+ }
+
+ public boolean hasNext() {
+ return holders.size() - 1 != index;
+ }
+
+ public boolean hasPrevious() {
+ return index != 0;
+ }
+
+ public ImageHolder getCurrent() {
+ return holder(index);
+ }
+
+ public int getTotalWidth() {
+ return 0;
+ }
+
+ private void initList() {
+ final File f = new File(path);
+ final File[] files = f.listFiles((FileFilter) name -> !name.getName().startsWith("."));
+
+ if (files != null) {
+ final String[] sfiles = new String[files.length];
+
+ for (int i = 0; i < files.length; i++) {
+ sfiles[i] = files[i].getAbsolutePath();
+ }
+
+ Arrays.sort(sfiles, String.CASE_INSENSITIVE_ORDER);
+
+ for (int i = 0; i < sfiles.length; i++) {
+ if (sfiles[i].endsWith(filename)) {
+ index = i;
+ }
+
+ final ImageHolder h = new ImageHolder(manager, display);
+ h.setFile(new File(sfiles[i]));
+ holders.add(h);
+ }
+ }
+ }
+
+ public Point getThumbWidth() {
+ if (holders == null) {
+ return new Point(1, 1);
+ }
+
+ return holder(0).getThumbDimension();
+ }
+
+ public int getActiveView() {
+ return activeView;
+ }
+
+ public void setActiveView(final int activeView) {
+ this.activeView = activeView;
+ }
+
+ public boolean selectHolder(final int x, final int y) {
+ boolean tmp = false;
+ boolean found = false;
+ for (int i = 0; i < holders.size(); i++) {
+ tmp = holder(i).mouseClickedOver(x, y);
+ if (tmp && !found) {
+ if (holder(index).hasFullsize() && !holder(index).getFullsize().isDisposed()) {
+ holder(index).getFullsize().dispose();
+ }
+ index = i;
+ found = true;
+ holder(i).setSelected(true);
+ } else {
+ holder(i).setSelected(false);
+ }
+ }
+
+ return found;
+ }
+
+ public void setCurrentToSelected() {
+ for (int i = 0; i < holders.size(); i++) {
+ if (holder(i).isSelected()) {
+ holder(index).getFullsize().dispose();
+ holder(i).setSelected(false);
+ }
+ }
+ getCurrent().setSelected(true);
+ }
+
+ public void setSelectedToCurrent() {
+ for (int i = 0; i < holders.size(); i++) {
+ if (holder(i).isSelected()) {
+ holder(index).getFullsize().dispose();
+ index = i;
+ break;
+ }
+ }
+ }
+
+ public void dispose() {
+ for (int i = 0; i < holders.size(); i++) {
+ holder(i).dispose();
+ }
+ }
+}
+
+class ImageFileFilter implements FileFilter {
+ String[] patterns = { ".bmp", ".cur", ".gif", ".ico", ".incs", ".jpeg", ".jpg", ".pbm", ".pcx", ".pgm", ".pict", ".png", ".pnm", ".ppm", ".psd", ".sgi",
+ ".svg", ".tga", ".tif", ".tiff", ".webp", ".xpm", ".xwd" };
+
+ @Override
+ public boolean accept(final File f) {
+ for (final String pattern : patterns) {
+ if (f.getName().toLowerCase().endsWith(pattern)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/core/QManager.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/core/QManager.java
new file mode 100644
index 0000000..1e646fb
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/core/QManager.java
@@ -0,0 +1,51 @@
+/*
+ * Software released under Common Public License (CPL) v1.0
+ */
+package me.glindholm.plugin.quickimage2.core;
+
+import me.glindholm.plugin.quickimage2.editors.QuickImageEditor;
+import me.glindholm.plugin.quickimage2.widgets.QStatusCanvas;
+import me.glindholm.plugin.quickimage2.widgets.QuickImageCanvas;
+
+/**
+ * @author Per Salomonsson
+ *
+ */
+public class QManager {
+ private QuickImageCanvas imageCanvas;
+ private QStatusCanvas statusCanvas;
+ private ImageOrganizer imageOrganizer;
+ private QuickImageEditor imageEditor;
+
+ public QuickImageCanvas getImageCanvas() {
+ return imageCanvas;
+ }
+
+ public void setImageCanvas(final QuickImageCanvas imageCanvas) {
+ this.imageCanvas = imageCanvas;
+ }
+
+ public QuickImageEditor getImageEditor() {
+ return imageEditor;
+ }
+
+ public void setImageEditor(final QuickImageEditor imageEditor) {
+ this.imageEditor = imageEditor;
+ }
+
+ public ImageOrganizer getImageOrganizer() {
+ return imageOrganizer;
+ }
+
+ public void setImageOrganizer(final ImageOrganizer imageOrganizer) {
+ this.imageOrganizer = imageOrganizer;
+ }
+
+ public QStatusCanvas getStatusCanvas() {
+ return statusCanvas;
+ }
+
+ public void setStatusCanvas(final QStatusCanvas statusCanvas) {
+ this.statusCanvas = statusCanvas;
+ }
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/editors/QuickImageEditor.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/editors/QuickImageEditor.java
new file mode 100644
index 0000000..5e48a71
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/editors/QuickImageEditor.java
@@ -0,0 +1,441 @@
+/*
+ * Software released under Common Public License (CPL) v1.0
+ */
+package me.glindholm.plugin.quickimage2.editors;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IStorageEditorInput;
+import org.eclipse.ui.editors.text.ILocationProvider;
+import org.eclipse.ui.part.EditorPart;
+import org.eclipse.ui.part.FileEditorInput;
+
+import me.glindholm.plugin.quickimage2.QuickImagePlugin;
+import me.glindholm.plugin.quickimage2.core.ImageOrganizer;
+import me.glindholm.plugin.quickimage2.core.QManager;
+import me.glindholm.plugin.quickimage2.util.LogUtil;
+import me.glindholm.plugin.quickimage2.widgets.QStatusCanvas;
+import me.glindholm.plugin.quickimage2.widgets.QuickImageCanvas;
+
+/**
+ * @author Per Salomonsson
+ *
+ */
+public class QuickImageEditor extends EditorPart {
+ private ToolItem previous, next, rotate, zoomIn, zoomOut, zoomOrg, zoomFit, view;
+ private Composite parent;
+ private String iconsdir;
+ private QManager manager;
+
+ private Image previousImage;
+ private Image nextImage;
+ private Image rotateImage;
+ private Image zoomInImage;
+ private Image zoomOutImage;
+ private Image zoom100Image;
+ private Image zoomFitImage;
+ private Image viewThumb;
+ private Image viewFullsize;
+
+ @Override
+ public void createPartControl(final Composite parent) {
+ this.parent = parent;
+ // fileorg = new FileOrganizer();
+ manager = new QManager();
+ manager.setImageOrganizer(new ImageOrganizer(manager, parent.getDisplay()));
+ manager.setImageEditor(this);
+
+ try {
+ // iconsdir =
+ // FileLocator.resolve(QuickImagePlugin.getDefault().getBundle().getEntry("/")).getFile() + "icons"
+ // + File.separator;
+ URL dir = FileLocator.find(QuickImagePlugin.getDefault().getBundle(), new Path("icons"), null);
+ dir = FileLocator.toFileURL(dir);
+ iconsdir = dir.getPath() + File.separator;
+ } catch (final IOException e1) {
+ LogUtil.error(e1);
+ }
+
+ // find out what kind the resource(s) to load is
+ final IEditorInput editorInput = getEditorInput();
+ if (editorInput instanceof FileEditorInput) {
+ // opened from file system so lets se what is in the current
+ // directory as well
+ final FileEditorInput fileEditorInput = (FileEditorInput) getEditorInput();
+ final IEditorInput file = getEditorInput();
+ setPartName(file.getName());
+
+ manager.getImageOrganizer().setPath(fileEditorInput.getPath().removeLastSegments(1).toOSString(), fileEditorInput.getName());
+ } else if (editorInput.getAdapter(ILocationProvider.class) != null) {
+ final ILocationProvider location = editorInput.getAdapter(ILocationProvider.class);
+ final IPath path = location.getPath(editorInput);
+ setPartName(editorInput.getName());
+ manager.getImageOrganizer().setPath(path.removeLastSegments(1).toOSString(), editorInput.getName());
+ } else if (editorInput instanceof final IStorageEditorInput storageEditorInput) {
+ IStorage storage;
+ try {
+ storage = storageEditorInput.getStorage();
+ setPartName(storage.getName());
+ manager.getImageOrganizer().setStorage(storage);
+ } catch (final CoreException e) {
+ e.printStackTrace();
+ }
+ } else if (editorInput.getAdapter(IFile.class) != null) {
+ final IFile file = editorInput.getAdapter(IFile.class);
+ setPartName(file.getName());
+ manager.getImageOrganizer().setPath(file.getLocation().removeLastSegments(1).toOSString(), file.getName());
+ } else {
+ // could not display image, show err message instead
+ LogUtil.error("could not display image for " + editorInput);
+ }
+
+ initElements();
+
+ manager.getImageOrganizer().setActiveView(ImageOrganizer.VIEW_FULLSIZE);
+ manager.getImageCanvas().setIconsPath(iconsdir);
+ manager.getImageCanvas().updateFullsizeData();
+ manager.getStatusCanvas().updateWithCurrent();
+ }
+
+ private void initElements() {
+ final FormLayout layout = new FormLayout();
+ final Composite compos = new Composite(parent, SWT.NONE);
+ compos.setLayout(layout);
+ compos.setLayoutData(new FormData());
+ final FormData toolbarData = new FormData();
+
+ manager.setImageCanvas(new QuickImageCanvas(manager, compos, SWT.NONE));
+ manager.setStatusCanvas(new QStatusCanvas(manager, compos, SWT.NONE));
+
+ final ToolBar toolBar = new ToolBar(compos, SWT.FLAT);
+ toolBar.setLayoutData(toolbarData);
+
+ previous = new ToolItem(toolBar, SWT.FLAT);
+ previous.setToolTipText("Previous Image");
+ previousImage = new Image(parent.getDisplay(), iconsdir + "previous.gif");
+ previous.setImage(previousImage);
+ previous.setSelection(true);
+
+ next = new ToolItem(toolBar, SWT.FLAT);
+ next.setToolTipText("Next Image");
+ nextImage = new Image(parent.getDisplay(), iconsdir + "next.gif");
+ next.setImage(nextImage);
+
+ rotate = new ToolItem(toolBar, SWT.FLAT);
+ rotate.setToolTipText("Rotate");
+ rotateImage = new Image(parent.getDisplay(), iconsdir + "rotate.gif");
+ rotate.setImage(rotateImage);
+
+ viewThumb = new Image(parent.getDisplay(), iconsdir + "thumb.gif");
+ viewFullsize = new Image(parent.getDisplay(), iconsdir + "fullsize.gif");
+ view = new ToolItem(toolBar, SWT.FLAT);
+ view.setToolTipText("view Thumbnails");
+ view.setImage(viewThumb);
+
+ new ToolItem(toolBar, SWT.SEPARATOR);
+
+ zoomIn = new ToolItem(toolBar, SWT.FLAT);
+ zoomIn.setToolTipText("zoom in");
+ zoomInImage = new Image(parent.getDisplay(), iconsdir + "zoom_in.gif");
+ zoomIn.setImage(zoomInImage);
+
+ zoomOut = new ToolItem(toolBar, SWT.FLAT);
+ zoomOut.setToolTipText("zoom out");
+ zoomOutImage = new Image(parent.getDisplay(), iconsdir + "zoom_out.gif");
+ zoomOut.setImage(zoomOutImage);
+
+ zoomOrg = new ToolItem(toolBar, SWT.FLAT);
+ zoomOrg.setToolTipText("zoom original size");
+ zoom100Image = new Image(parent.getDisplay(), iconsdir + "zoom_100.gif");
+ zoomOrg.setImage(zoom100Image);
+
+ zoomFit = new ToolItem(toolBar, SWT.CHECK);
+ zoomFit.setToolTipText("fit image in window");
+ zoomFitImage = new Image(parent.getDisplay(), iconsdir + "zoom_fit.gif");
+ zoomFit.setImage(zoomFitImage);
+
+ final FormData canvasData = new FormData();
+
+ // imgCanvas = new QuickImageCanvas(compos, SWT.NONE);
+ manager.getImageCanvas().setLayoutData(canvasData);
+
+ final FormData statusData = new FormData();
+
+ // statusCanvas = new QStatusCanvas(imgCanvas, compos, SWT.NONE);
+ manager.getStatusCanvas().setLayoutData(statusData);
+
+ canvasData.top = new FormAttachment(toolBar, 0);
+ canvasData.bottom = new FormAttachment(100, -30); // FIXME How to calculate the -30?
+ canvasData.right = new FormAttachment(100, 0);
+ canvasData.left = new FormAttachment(0, 0);
+
+ toolbarData.top = new FormAttachment(0, 0);
+ toolbarData.left = new FormAttachment(0, 0);
+ toolbarData.right = new FormAttachment(100, 0);
+
+ statusData.top = new FormAttachment(manager.getImageCanvas(), 0);
+ statusData.bottom = new FormAttachment(100, 0);
+ statusData.right = new FormAttachment(100, 0);
+ statusData.left = new FormAttachment(0, 0);
+
+ rotate.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ zoomFit.setSelection(false);
+ manager.getImageCanvas().rotate();
+ }
+ });
+
+ previous.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ clickedPrevious(e);
+ }
+ });
+
+ next.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ clickedNext(e);
+ }
+ });
+
+ view.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ toggleView();
+ }
+ });
+
+ // parent.addDisposeListener(new DisposeListener() {
+ //
+ // public void widgetDisposed(DisposeEvent e)
+ // {
+ // disposeAll();
+ // }
+ // });
+
+ zoomIn.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ zoomFit.setSelection(false);
+ manager.getImageCanvas().zoomIn();
+ }
+ });
+
+ zoomOut.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ zoomFit.setSelection(false);
+ manager.getImageCanvas().zoomOut();
+ }
+ });
+
+ zoomFit.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ manager.getImageCanvas().zoomFit();
+ }
+ });
+
+ zoomOrg.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent e) {
+ zoomFit.setSelection(false);
+ manager.getImageCanvas().zoomOriginal();
+ }
+ });
+
+ manager.getImageCanvas().addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(final ControlEvent e) {
+ if (zoomFit.getSelection()) {
+ manager.getImageCanvas().zoomFit();
+ }
+ }
+ });
+
+ previous.setEnabled(manager.getImageOrganizer().hasPrevious());
+ next.setEnabled(manager.getImageOrganizer().hasNext());
+
+ if (manager.getImageOrganizer().isSingle()) {
+ next.setEnabled(false);
+ previous.setEnabled(false);
+ view.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ manager.getImageOrganizer().dispose();
+ manager.getImageCanvas().dispose();
+ manager.getStatusCanvas().dispose();
+
+ if (previousImage != null && !previousImage.isDisposed()) {
+ previousImage.dispose();
+ }
+ if (nextImage != null && !nextImage.isDisposed()) {
+ nextImage.dispose();
+ }
+ if (rotateImage != null && !rotateImage.isDisposed()) {
+ rotateImage.dispose();
+ }
+ if (zoomInImage != null && !zoomInImage.isDisposed()) {
+ zoomInImage.dispose();
+ }
+ if (zoomOutImage != null && !zoomOutImage.isDisposed()) {
+ zoomOutImage.dispose();
+ }
+ if (zoom100Image != null && !zoom100Image.isDisposed()) {
+ zoom100Image.dispose();
+ }
+ if (zoomFitImage != null && !zoomFitImage.isDisposed()) {
+ zoomFitImage.dispose();
+ }
+
+ if (viewThumb != null && !viewThumb.isDisposed()) {
+ viewThumb.dispose();
+ }
+
+ if (viewFullsize != null && !viewFullsize.isDisposed()) {
+ viewFullsize.dispose();
+ }
+
+ // Runtime.getRuntime().gc();
+ super.dispose();
+ }
+
+ // private void disposeAll()
+ // {
+ // System.out.println("QuickImageEditor.disposeAll()");
+ // // previous.getImage().dispose();
+ // // next.getImage().dispose();
+ // // rotate.getImage().dispose();
+ // // zoomIn.getImage().dispose();
+ // // zoomOut.getImage().dispose();
+ // // view.getImage().dispose();
+ // }
+
+ public void toggleView() {
+ if (manager.getImageOrganizer().getActiveView() == ImageOrganizer.VIEW_FULLSIZE) {
+ previous.setEnabled(false);
+ next.setEnabled(false);
+ rotate.setEnabled(false);
+ manager.getImageOrganizer().setActiveView(ImageOrganizer.VIEW_THUMB);
+ manager.getImageOrganizer().setCurrentToSelected();
+ view.setImage(viewFullsize);
+ view.setToolTipText("View Fullsize");
+ view.setEnabled(false);
+ view.setEnabled(true);
+ zoomIn.setEnabled(false);
+ zoomOut.setEnabled(false);
+ zoomFit.setEnabled(false);
+ zoomOrg.setEnabled(false);
+ } else {
+ previous.setEnabled(manager.getImageOrganizer().hasPrevious());
+ next.setEnabled(manager.getImageOrganizer().hasNext());
+ rotate.setEnabled(true);
+ manager.getImageOrganizer().setActiveView(ImageOrganizer.VIEW_FULLSIZE);
+ // manager.getImageOrganizer().setSelectedToCurrent();
+ view.setImage(viewThumb);
+ view.setToolTipText("View Thumbnails");
+ manager.getImageCanvas().updateFullsizeData();
+ view.setEnabled(false);
+ view.setEnabled(true);
+ zoomIn.setEnabled(true);
+ zoomOut.setEnabled(true);
+ zoomFit.setSelection(false);
+ zoomFit.setEnabled(true);
+ zoomOrg.setEnabled(true);
+ }
+
+ manager.getImageCanvas().updateThumbData();
+
+ }
+
+ private void clickedPrevious(final SelectionEvent e) {
+ manager.getImageOrganizer().getPrevious();
+ manager.getImageCanvas().updateFullsizeData();
+ manager.getStatusCanvas().updateWithCurrent();
+ setPartName(manager.getImageOrganizer().getCurrent().getDisplayName());
+ previous.setEnabled(manager.getImageOrganizer().hasPrevious());
+ next.setEnabled(manager.getImageOrganizer().hasNext());
+ }
+
+ private void clickedNext(final SelectionEvent e) {
+ manager.getImageOrganizer().getNext();
+ manager.getImageCanvas().updateFullsizeData();
+ manager.getStatusCanvas().updateWithCurrent();
+ setPartName(manager.getImageOrganizer().getCurrent().getDisplayName());
+ previous.setEnabled(manager.getImageOrganizer().hasPrevious());
+ next.setEnabled(manager.getImageOrganizer().hasNext());
+
+ }
+
+ @Override
+ public void setPartName(final String s) {
+ super.setPartName(s);
+ }
+
+ @Override
+ public void init(final IEditorSite site, final IEditorInput input) {
+ setSite(site);
+ setInput(input);
+ }
+
+ @Override
+ public void setFocus() {
+ if (manager.getImageCanvas() != null) {
+ manager.getImageCanvas().setFocus();
+ }
+ }
+
+ @Override
+ public void doSave(final IProgressMonitor monitor) {
+ }
+
+ @Override
+ public void doSaveAs() {
+ }
+
+ @Override
+ public boolean isDirty() {
+ return false;
+ }
+
+ @Override
+ public boolean isSaveAsAllowed() {
+ return false;
+ }
+
+ public String getIconsdir() {
+ return iconsdir;
+ }
+
+ public void setIconsdir(final String iconsdir) {
+ this.iconsdir = iconsdir;
+ }
+}
\ No newline at end of file
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/swt/ImageInputStream.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/swt/ImageInputStream.java
new file mode 100644
index 0000000..b7ce927
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/swt/ImageInputStream.java
@@ -0,0 +1,21 @@
+package me.glindholm.plugin.quickimage2.swt;
+
+import java.io.IOException;
+
+import javax.imageio.stream.ImageInputStreamImpl;
+
+public class ImageInputStream extends ImageInputStreamImpl {
+
+ @Override
+ public int read() throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/swt/QuickImageImageLoader.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/swt/QuickImageImageLoader.java
new file mode 100644
index 0000000..f724839
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/swt/QuickImageImageLoader.java
@@ -0,0 +1,7 @@
+package me.glindholm.plugin.quickimage2.swt;
+
+import org.eclipse.swt.graphics.ImageLoader;
+
+public class QuickImageImageLoader extends ImageLoader {
+
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/util/LogUtil.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/util/LogUtil.java
new file mode 100644
index 0000000..2de0547
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/util/LogUtil.java
@@ -0,0 +1,44 @@
+package me.glindholm.plugin.quickimage2.util;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+import me.glindholm.plugin.quickimage2.QuickImagePlugin;
+
+/**
+ * Nodeclipse Log Util
+ *
+ * @author Lamb Gao, Paul Verest
+ */
+public class LogUtil {
+
+ public static void info(final String message) {
+ log(IStatus.INFO, IStatus.OK, message, null);
+ }
+
+ public static void error(final Throwable exception) {
+ error("Unexpected Exception", exception);
+ }
+
+ public static void error(final String message) {
+ error(message, null);
+ }
+
+ public static void error(final String message, final Throwable exception) {
+ log(IStatus.ERROR, IStatus.ERROR, message, exception);
+ }
+
+ public static void log(final int severity, final int code, final String message, final Throwable exception) {
+ log(createStatus(severity, code, message, exception));
+ }
+
+ public static IStatus createStatus(final int severity, final int code, final String message, final Throwable exception) {
+ return new Status(severity, QuickImagePlugin.PLUGIN_ID, code, message, exception);
+ }
+
+ public static void log(final IStatus status) {
+ final ILog log = QuickImagePlugin.getDefault().getLog();
+ log.log(status);
+ }
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/widgets/QStatusCanvas.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/widgets/QStatusCanvas.java
new file mode 100644
index 0000000..44b4aec
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/widgets/QStatusCanvas.java
@@ -0,0 +1,112 @@
+/*
+ * Software released under Common Public License (CPL) v1.0
+ */
+package me.glindholm.plugin.quickimage2.widgets;
+
+import java.text.DecimalFormat;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Text;
+
+import me.glindholm.plugin.quickimage2.core.ImageHolder;
+import me.glindholm.plugin.quickimage2.core.QManager;
+
+/**
+ * @author Per Salomonsson
+ *
+ */
+public class QStatusCanvas extends Canvas {
+ private String filesize = "";
+ private int height = 0;
+ private int width = 0;
+ private String filename = "";
+ private int depth = 0;
+ private Image image;
+ private static final DecimalFormat df = new DecimalFormat("0.000");
+ private final Color colorDarkGray;
+ private final QManager manager;
+ private final Composite parent;
+
+ public QStatusCanvas(final QManager manager, final Composite parent, final int style) {
+ // super(parent, style | SWT.BORDER);
+ super(parent, style | SWT.FLAT);
+ this.parent = parent;
+ this.manager = manager;
+
+ addPaintListener(event -> paint(event.gc));
+ colorDarkGray = parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
+ // separator = new Color(getDisplay(), 140,140,140);
+ // setBackground(new Color(parent.getDisplay(), 255,0,0));
+ }
+
+ public void updateWithCurrent() {
+ final ImageHolder current = manager.getImageOrganizer().getCurrent();
+
+ image = current.getFullsize();
+ if (image != null) {
+ depth = image.getImageData().depth;
+ width = image.getBounds().width;
+ height = image.getBounds().height;
+ }
+
+ filename = current.getDisplayName();
+ filesize = format(current.getImageSize());
+ if (current.getImageSize() == 0) {
+ filesize = "unknown";
+ }
+ redraw();
+ }
+
+ private static String format(final long size) {
+ if (size < 0) {
+ return "-";
+ }
+
+ final double formattedValue;
+ if (size <= 1048575) {
+ formattedValue = size / 1024.0;
+ df.applyPattern("0.00 KB");
+ } else if (size >= 1048576 && size <= 1073741823) {
+ formattedValue = size / 1048576.0;
+ df.applyPattern("0.00 MB");
+ } else {
+ formattedValue = size / 1073741824.0;
+ df.applyPattern("0.00 GB");
+ }
+ return df.format(formattedValue);
+
+ }
+
+ void paint(final GC gc) {
+ int x = 0;
+ final int canvasHeight = getSize().y;
+
+ x += Math.max(addText(filesize, x, gc), 110);
+ gc.drawLine(x, 0, x, canvasHeight);
+
+ x += Math.max(addText("Depth: " + depth, x, gc), 100);
+ gc.drawLine(x, 0, x, canvasHeight);
+
+ x += Math.max(addText(width + "x" + height, x, gc), 105);
+ gc.drawLine(x, 0, x, canvasHeight);
+
+ addText("Name: " + filename, x, gc);
+
+ gc.setForeground(colorDarkGray);
+ }
+
+ private int addText(final String text, final int startPos, final GC gc) {
+ final Text size = new Text(parent, SWT.BORDER);
+ size.setText(text);
+ final Point point = size.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ gc.drawString(text, startPos + 5, 1);
+
+ return point.x + 5;
+ }
+}
diff --git a/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/widgets/QuickImageCanvas.java b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/widgets/QuickImageCanvas.java
new file mode 100644
index 0000000..2ed830f
--- /dev/null
+++ b/quickimage2-plugin/src/me/glindholm/plugin/quickimage2/widgets/QuickImageCanvas.java
@@ -0,0 +1,468 @@
+/*
+ * Software released under Common Public License (CPL) v1.0
+ */
+package me.glindholm.plugin.quickimage2.widgets;
+
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ScrollBar;
+
+import me.glindholm.plugin.quickimage2.core.ImageHolder;
+import me.glindholm.plugin.quickimage2.core.ImageOrganizer;
+import me.glindholm.plugin.quickimage2.core.QManager;
+
+/**
+ * @author Per Salomonsson
+ *
+ */
+public class QuickImageCanvas extends Canvas {
+ private Image originalImage;
+ private Image workingImage;
+ private Image backImage;
+ private final Composite parent;
+ private int clientw, clienth, imgx, imgy, imgw, imgh, scrolly, scrollx, mousex, mousey = 1;
+ private final Color COLOR_WIDGET_BACKGROUND;
+ private boolean listenForMouseMovement = false;
+ private Cursor handOpen, handClosed;
+ private double zoomScale = 1;
+ private final QManager manager;
+
+ public QuickImageCanvas(final QManager manager, final Composite parent, final int style) {
+ super(parent, style | SWT.BORDER | SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
+
+ this.manager = manager;
+ COLOR_WIDGET_BACKGROUND = parent.getBackground();
+
+ addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(final ControlEvent e) {
+ updateScrollbarPosition();
+ }
+ });
+
+ addMouseListener(new MouseListener() {
+ @Override
+ public void mouseDoubleClick(final MouseEvent e) {
+ eventMouseDoubleClick(e);
+ }
+
+ @Override
+ public void mouseDown(final MouseEvent e) {
+ eventMouseDown(e);
+ }
+
+ @Override
+ public void mouseUp(final MouseEvent e) {
+ listenForMouseMovement = false;
+ setCursor(handOpen);
+ }
+ });
+
+ addMouseMoveListener(e -> {
+ if (listenForMouseMovement) {
+ followMouse(e);
+ }
+ });
+
+ addPaintListener(event -> paint(event.gc));
+
+ getHorizontalBar().setEnabled(true);
+ getHorizontalBar().addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent event) {
+ updateHorizontalScroll((ScrollBar) event.widget);
+ }
+ });
+
+ getVerticalBar().setEnabled(true);
+ getVerticalBar().addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(final SelectionEvent event) {
+ updateVerticalScroll((ScrollBar) event.widget);
+ }
+ });
+
+ this.parent = parent;
+ // updateFullsizeData();
+ // setCurrentFullsizeImage(manager.getImageOrganizer().getCurrent().getFullsize());
+ // workingImage =
+ // manager.getImageOrganizer().getCurrent().getFullsize();
+ }
+
+ private void eventMouseDown(final MouseEvent e) {
+ listenForMouseMovement = true;
+ mousex = e.x;
+ mousey = e.y;
+ setCursor(handClosed);
+
+ if (manager.getImageOrganizer().getActiveView() == ImageOrganizer.VIEW_THUMB) {
+ manager.getImageOrganizer().selectHolder(e.x, e.y);
+ manager.getStatusCanvas().updateWithCurrent();
+ manager.getImageEditor().setPartName(manager.getImageOrganizer().getCurrent().getDisplayName());
+ redraw();
+ }
+ }
+
+ private void eventMouseDoubleClick(final MouseEvent e) {
+ if (!manager.getImageOrganizer().isSingle()) {
+ if (manager.getImageOrganizer().getActiveView() == ImageOrganizer.VIEW_FULLSIZE) {
+ manager.getImageOrganizer().getCurrent().getFullsize().dispose();
+ manager.getImageEditor().toggleView();
+ } else if (manager.getImageOrganizer().selectHolder(e.x, e.y)) {
+ manager.getImageEditor().toggleView();
+ }
+ }
+
+ manager.getStatusCanvas().updateWithCurrent();
+
+ // redraw();
+ }
+
+ public void setIconsPath(final String path) {
+ handOpen = new Cursor(getDisplay(), new ImageData(path + "cursor_hand_open.gif"), 10, 0);
+ handClosed = new Cursor(getDisplay(), new ImageData(path + "cursor_hand_closed.gif"), 10, 0);
+ setCursor(handOpen);
+ }
+
+ private void followMouse(final MouseEvent e) {
+
+ if (clientw < imgw) {
+ final int mouseDiffX = mousex - e.x;
+ mousex = e.x;
+ imgx -= mouseDiffX;
+ getHorizontalBar().setSelection(getHorizontalBar().getSelection() + mouseDiffX);
+ final int minx = clientw - imgw;
+ final int maxx = 0;
+ if (imgx < minx) {
+ imgx = minx;
+ }
+ if (imgx > maxx) {
+ imgx = maxx;
+ }
+ scrollx = getHorizontalBar().getSelection();
+ }
+ if (clienth < imgh) {
+ final int mouseDiffY = mousey - e.y;
+ mousey = e.y;
+ imgy -= mouseDiffY;
+ getVerticalBar().setSelection(getVerticalBar().getSelection() + mouseDiffY);
+ final int miny = clienth - imgh;
+ final int maxy = 0;
+ if (imgy < miny) {
+ imgy = miny;
+ }
+ if (imgy > maxy) {
+ imgy = maxy;
+ }
+
+ scrolly = getVerticalBar().getSelection();
+ }
+
+ redraw();
+ }
+
+ //
+ // public void setSourceImage(File file)
+ // {
+ // if (workingImage != null)
+ // workingImage.dispose();
+ //
+ // workingImage = new Image(getDisplay(), new
+ // ImageData(file.getAbsolutePath()));
+ // updateScrollbarPosition();
+ // }
+
+ void paint(final GC gc) {
+ if (backImage != null) {
+ backImage.dispose();
+ }
+
+ backImage = new Image(getDisplay(), clientw, clienth);
+
+ final GC backGC = new GC(backImage);
+ backGC.setBackground(COLOR_WIDGET_BACKGROUND);
+ backGC.setClipping(getClientArea());
+ backGC.fillRectangle(getClientArea());
+ // backGC.drawImage(workingImage, imgx, imgy);
+
+ final ImageOrganizer organizer = manager.getImageOrganizer();
+
+ if (manager.getImageOrganizer().getActiveView() == ImageOrganizer.VIEW_FULLSIZE) {
+ backGC.drawImage(workingImage, imgx, imgy);
+ } else
+ // draw thumbnails
+ {
+ final Point thumbDim = organizer.getThumbWidth();
+
+ int cols = clientw / organizer.getThumbWidth().x;
+ if (cols < 1) {
+ cols = 1;
+ }
+ int rows = organizer.getCount() / cols;
+ if (organizer.getCount() > rows * cols) {
+ rows++;
+ }
+
+ final List holders = organizer.getHolders();
+ int index = 0;
+ for (int i = 0; i < rows; i++) {
+ if (imgy + i * thumbDim.x > clienth) { // make sure that only the
+ // neccessary thumbs are
+ // initiated (and not all ==
+ // very SLOW)
+ break;
+ }
+ for (int k = 0; k < cols; k++) {
+ if (index == organizer.getCount()) {
+ break;
+ }
+
+ holders.get(index).drawThumb(backGC, k * thumbDim.x, imgy + i * thumbDim.y);
+ index++;
+ }
+ }
+ }
+
+ gc.drawImage(backImage, 0, 0);
+ backGC.dispose();
+ }
+
+ private void updateScrollVisibility() {
+ // only show when neccessary
+ getHorizontalBar().setVisible(clientw < imgw);
+ getVerticalBar().setVisible(clienth < imgh);
+ }
+
+ private void updateVerticalScroll(final ScrollBar bar) {
+ imgy -= bar.getSelection() - scrolly;
+ scrolly = bar.getSelection();
+ redraw();
+ }
+
+ private void updateHorizontalScroll(final ScrollBar bar) {
+ imgx -= bar.getSelection() - scrollx;
+ scrollx = bar.getSelection();
+ redraw();
+ }
+
+ public void zoomFit() {
+
+ zoomScale = 1;
+ double scaleWidth, scaleHeight = 0;
+
+ scaleWidth = originalImage.getBounds().width - getClientArea().width;
+ scaleHeight = originalImage.getBounds().height - getClientArea().height;
+
+ if (scaleWidth > 0) {
+ scaleWidth = scaleWidth / originalImage.getBounds().width;
+ }
+ if (scaleHeight > 0) {
+ scaleHeight = scaleHeight / originalImage.getBounds().height;
+ }
+
+ if (scaleWidth > scaleHeight && scaleWidth > 0) {
+ zoomScale = 1 - scaleWidth;
+ } else if (scaleHeight > scaleWidth && scaleHeight > 0) {
+ zoomScale = 1 - scaleHeight;
+ }
+
+ if (zoomScale < 0.001) {
+ zoomScale = 0.001;
+ }
+
+ onZoom();
+ }
+
+ public void zoomIn() {
+ if (zoomScale < 1) {
+ zoomScale *= 2;
+ } else {
+ zoomScale += 0.5;
+ if (zoomScale > 4) {
+ zoomScale = 4;
+ }
+ }
+
+ onZoom();
+ }
+
+ public void zoomOut() {
+ if (zoomScale <= 1) {
+ if (zoomScale > 0.001) {
+ zoomScale /= 2;
+ }
+ } else {
+ zoomScale -= 0.5;
+ }
+
+ onZoom();
+ }
+
+ public void zoomOriginal() {
+ zoomScale = 1;
+ onZoom();
+ }
+
+ private void onZoom() {
+ int w = (int) (originalImage.getBounds().width * zoomScale);
+ int h = (int) (originalImage.getBounds().height * zoomScale);
+ if (w < 1) {
+ w = 1;
+ }
+ if (h < 1) {
+ h = 1;
+ }
+
+ final ImageData imageData = originalImage.getImageData().scaledTo(w, h);
+ if (workingImage != null && !workingImage.isDisposed()) {
+ workingImage.dispose();
+ }
+
+ workingImage = new Image(getDisplay(), imageData);
+ updateScrollbarPosition();
+ }
+
+ public void updateThumbData() {
+ updateScrollbarPosition();
+ }
+
+ public void updateFullsizeData() {
+ disposeImages();
+ zoomScale = 1;
+ originalImage = manager.getImageOrganizer().getCurrent().getFullsize();
+ workingImage = new Image(getDisplay(), originalImage.getImageData());
+
+ updateScrollbarPosition();
+ }
+
+ public void rotate() {
+ final ImageData originalData = workingImage.getImageData();
+ final PaletteData originalPalette = originalData.palette;
+ ImageData tmpData;
+ PaletteData tmpPalette;
+
+ if (originalPalette.isDirect) {
+ tmpPalette = new PaletteData(originalPalette.redMask, originalPalette.greenMask, originalPalette.blueMask);
+ } else {
+ tmpPalette = new PaletteData(originalPalette.getRGBs());
+ }
+
+ tmpData = new ImageData(originalData.height, originalData.width, originalData.depth, tmpPalette);
+
+ tmpData.transparentPixel = originalData.transparentPixel;
+
+ for (int i = 0; i < originalData.width; i++) {
+ for (int k = 0; k < originalData.height; k++) {
+ tmpData.setPixel(k, originalData.width - 1 - i, originalData.getPixel(i, k));
+ }
+ }
+
+ if (workingImage != null) {
+ workingImage.dispose();
+ }
+ workingImage = new Image(getDisplay(), tmpData);
+ updateScrollbarPosition();
+ }
+
+ private void updateScrollbarPosition() {
+ clientw = getClientArea().width;
+ clienth = getClientArea().height;
+ if (clientw < 1) {
+ clientw = 1;
+ }
+ if (clienth < 1) {
+ clienth = 1;
+ }
+
+ if (manager.getImageOrganizer().getActiveView() == ImageOrganizer.VIEW_FULLSIZE) {
+ imgh = workingImage.getBounds().height;
+ imgw = workingImage.getBounds().width;
+ } else {
+ final ImageOrganizer organizer = manager.getImageOrganizer();
+ int cols = clientw / organizer.getThumbWidth().x;
+ if (cols < 1) {
+ cols = 1;
+ }
+ int rows = organizer.getCount() / cols;
+ if (organizer.getCount() > rows * cols) {
+ rows++;
+ }
+
+ imgh = organizer.getThumbWidth().y * rows;
+ imgw = clientw / organizer.getThumbWidth().x;
+ }
+
+ updateScrollVisibility();
+ getVerticalBar().setSelection(0);
+ getHorizontalBar().setSelection(0);
+ imgx = clientw / 2 - imgw / 2;
+ imgy = clienth / 2 - imgh / 2;
+
+ if (imgx < 0) {
+ imgx = 0;
+ }
+ if (imgy < 0) {
+ imgy = 0;
+ }
+
+ scrollx = getHorizontalBar().getSelection();
+ scrolly = getVerticalBar().getSelection();
+
+ final ScrollBar vertical = getVerticalBar();
+ vertical.setMaximum(imgh);
+ vertical.setThumb(Math.min(clienth, imgh));
+ vertical.setIncrement(40);
+ vertical.setPageIncrement(clienth);
+
+ final ScrollBar horizontal = getHorizontalBar();
+ horizontal.setMaximum(imgw);
+ horizontal.setThumb(Math.min(clientw, imgw));
+ horizontal.setIncrement(40);
+ horizontal.setPageIncrement(clientw);
+
+ redraw();
+ }
+
+ private void disposeImages() {
+ if (originalImage != null && !originalImage.isDisposed()) {
+ originalImage.dispose();
+ }
+ if (workingImage != null && !workingImage.isDisposed()) {
+ workingImage.dispose();
+ }
+ if (backImage != null && !backImage.isDisposed()) {
+ backImage.dispose();
+ }
+
+ }
+
+ @Override
+ public void dispose() {
+ disposeImages();
+
+ if (handOpen != null && !handOpen.isDisposed()) {
+ handOpen.dispose();
+ }
+ if (handClosed != null && !handClosed.isDisposed()) {
+ handClosed.dispose();
+ }
+
+ super.dispose();
+ }
+}
\ No newline at end of file
diff --git a/quickimage2-test/META-INF/MANIFEST.MF b/quickimage2-test/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..03b58b2
--- /dev/null
+++ b/quickimage2-test/META-INF/MANIFEST.MF
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: QuickImage2
+Bundle-SymbolicName: me.glindholm.plugin.quickimage2.test
+Bundle-Version: 2.1.0.qualifier
+Bundle-Vendor: George Lindholm
diff --git a/quickimage2-test/pom.xml b/quickimage2-test/pom.xml
new file mode 100644
index 0000000..ae7556a
--- /dev/null
+++ b/quickimage2-test/pom.xml
@@ -0,0 +1,12 @@
+
+ 4.0.0
+
+ me.glindholm.plugin.quickimage2
+ quickimage2
+ 2.1.0-SNAPSHOT
+
+ me.glindholm.plugin.quickimage2.test
+ eclipse-test-plugin
+
\ No newline at end of file
diff --git a/quickimage2-test/src/test/resources/Images/Janosch500-Tropical-Waters-Folders-Documents.ico b/quickimage2-test/src/test/resources/Images/Janosch500-Tropical-Waters-Folders-Documents.ico
new file mode 100644
index 0000000..a124eee
Binary files /dev/null and b/quickimage2-test/src/test/resources/Images/Janosch500-Tropical-Waters-Folders-Documents.ico differ
diff --git a/quickimage2-test/src/test/resources/Images/Logo_of_Twitter.svg b/quickimage2-test/src/test/resources/Images/Logo_of_Twitter.svg
new file mode 100644
index 0000000..d60af2b
--- /dev/null
+++ b/quickimage2-test/src/test/resources/Images/Logo_of_Twitter.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/quickimage2-test/src/test/resources/Images/Scene1A_C12B07_wwh_full.ico b/quickimage2-test/src/test/resources/Images/Scene1A_C12B07_wwh_full.ico
new file mode 100644
index 0000000..52312a1
Binary files /dev/null and b/quickimage2-test/src/test/resources/Images/Scene1A_C12B07_wwh_full.ico differ
diff --git a/quickimage2-test/src/test/resources/Images/sample.dcx b/quickimage2-test/src/test/resources/Images/sample.dcx
new file mode 100644
index 0000000..cc01cf0
Binary files /dev/null and b/quickimage2-test/src/test/resources/Images/sample.dcx differ
diff --git a/quickimage2-test/src/test/resources/Images/sample1.webp b/quickimage2-test/src/test/resources/Images/sample1.webp
new file mode 100644
index 0000000..cc735b9
Binary files /dev/null and b/quickimage2-test/src/test/resources/Images/sample1.webp differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.bmp" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.bmp"
new file mode 100644
index 0000000..ea88c92
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.bmp" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.cur" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.cur"
new file mode 100644
index 0000000..d444183
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.cur" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.dds" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.dds"
new file mode 100644
index 0000000..44a7540
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.dds" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.gif" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.gif"
new file mode 100644
index 0000000..5686b1b
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.gif" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.ico" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.ico"
new file mode 100644
index 0000000..147bdcb
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.ico" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.jpeg" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.jpeg"
new file mode 100644
index 0000000..ff2d4eb
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.jpeg" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.jpg" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.jpg"
new file mode 100644
index 0000000..ff2d4eb
Binary files /dev/null and "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.jpg" differ
diff --git "a/quickimage2-test/src/test/resources/Images/sample_640\303\227426.pam" "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.pam"
new file mode 100644
index 0000000..c38e16c
--- /dev/null
+++ "b/quickimage2-test/src/test/resources/Images/sample_640\303\227426.pam"
@@ -0,0 +1,5150 @@
+P7
+WIDTH 640
+HEIGHT 426
+DEPTH 3
+MAXVAL 255
+TUPLTYPE RGB
+ENDHDR
+²‚}‹ªzŠªzŠ«{‹§w‡¢q„ŸnŸp‚Ÿp‚žoƒ›l€˜i}–g{”dz”dz‘c}’d~ˆ\u‚Xp‹ay[r‹e|à ¶…c{|ZryWo}[s~[sxUmzTm€Zs{WqyUoxTnxTnxVowUnuSlrPivVntTlqQiqQiqSkrTlrTlqSksRmpOjnNfpPhqSkqSkmPfiLb®‘¥uXlnSdtYj_DSmRamRagL[iPchObhOdiPehOefMcfMcfMceMcfNdfOcfOcfOaeN`eN^cN]_J]`M`_J]cNaiTgZEXU@SjUhaJ^SSZDYV@UXBWXBWWAVWAVWAVWAVWAVWAVZDYU?TT>SYCXYCXT>SRSS=RWAV]G\YCXP:QY?ZQ7TTSZG[M:M^L\O=MYDUQQS>QP;NORG4HI6JI6JL9MM:NJ9LG6IK:MRATu`s9$7K5JG1FK8LL9MG6IK:MG8M>/DG9PXJa=/F@2IL@VD6MI:OM;QL:PH6LG5KH6LG5KD2HG5KG5KF4JF4JF4JG5KH6LH6LI7MG5KE3IE3IF4JF4JE3IC1GI7MA/EL:PK9O;)?I7MVDZC1GG4JJ7ML9OC0FI6LK8NI6LjWmG4JI6L;(>P=SD1GD1GXE[J7MlZpUCY@.DJ8NA/EJ8NN1EE6KG8ME6KG8MC4IB3HH9NJ;PA/EL:PD2HE3IH6NO=UR@XL:RH6NJ8PJ8PF4LG5MH6NI7MI7MH6LG5KH6LI7M@.DTBXQ?UJ8NE3IG5KF4LH6NL:RI7OF4LF4LH6NI7OI7QH6PJ8PI7OI7OH6NH6NH6NI7OI7OH6NI7OI7OI7OK9QNQE4GeTgP>TA/EO=UK9QI7QF4NOXM;UL:TQ?YTB\P>XH6PO^Q>\OZOZTA]UB`Q>\P=[R?]S@^R?]TA_S@^UB`TA_OSS=RV@UV@US=RS=RXBWYCXU?VT:U{a~L4NK3MbLcQ;PN;OWDWTBRhVfO:KS>OM6HjSeQ8MW@TXAUR=PR=PVATR?RK8KJ7KORG4HK8LI6JI6JJ7KL9MI8KF5HH7JMSA3J>0Gi[rF8O;/EG9PA2GF4JL:PO=SL:PG5KE3IH6LH6LG5KG5KF4JG5KG5KH6LH6LH6LG5KF4JF4JH6LI7MI7MI7ML:PF4JH6LK9OE3IE3IL:PP>TF3IL9OP=SG4JH5KD1G?,BZG]yf|03S@VA.DD1GL9OG4JYF\F4Jo…B0F=+AXF\C1G?-CQ?UI7MC1GP>TNVI7OD2JM;SL:RE3K\JbXF^D2JE3K\Jb<*BK9QH6NJ8PF7NN?T5(<[Nb}p„8)>J;PD5JG8MPAVE6K=.CG8MJ8N>,BK9OI7MF4LL:RN-@Q@S?.Am[qVDZ>,DM;SM;USA[L9UA.JOVJ8PNZD1OK8V_LlM:ZJ7WG4RL7V\HdT@\E1MN:VT@\M:VM:VI9TYId?/JB2MŸ‘«QC]UGaWGbI7QP>XVD^UC]O=WM;UP>XTB\O\S@`Q=`P=]R?_R?]P=[P=YR?[R?[P=YQ>ZTA]TA_Q>\Q>\S@^Q>\S@^P=[S@^K8VL9W]JhQ>\S@`L9YF3S|i‰ÖÃãn[{<)I\IiP@]RB_UB`UB`UB`UB`UB`TA_YFdVCaVA`XCbYDcYDc[Fe]HgVCcUBbU@aXCd[Fg\GhZEfZEf[Ce[Ce]Cf_Eh_Eh^Dg_EhbHk_Gg_Gg_Gg_Gg_Gg_Gg`Hj`Hj]Eg`HjaIk_Gi_GkaImaIm_Gk[CedLnaIkaIkhPt_GkX@deMq\DhoW{\CjL3ZmT{†m”w^…cJqVSYCXZD[A'B„j‡½¥¿9!;_I`S=RYFZTAT^L\F4Du`qI4EU>RYBVgNcU>RR;OT?RU@SS>QQRP=QOQ>-@XGZF5HH7JN=PC2EI8KL;NL;NVEXH7JH7JK:MP?R5$7fSgVCW?,@L9MM:NL9ML9MM:NL;NI8KI8KJ9LA,?ydwnXm:$9OQ@SG8MC4IK,BG5KYG]kYo=+AK9OG5KG5KJ8NG5KO=SJ8N{‘?-CH6LF4JD2HG5KQ?UD2HK9OK9OE3IK9OJ8NG5K?-EB0HM;S@.FJ8PA/GC1ISAYUC[?-EO=UWE]A/GI7OC1IF7NKJ9LJ9LI7ME3KI7OJ8PH6NH6NK9QK9QG5MG5MH6NI7OI7OI7MH6LI7MJ8NM;QH6LUCYJ8NC1GP>TF4LM;SF4LG5MI7OJ8PJ8PJ8PJ8RK9SI7OI7OI7OI7OI7OJ8PJ8PK9QJ8PK9QJ8PI7OJ8PL:RK9QH6LL:PWFYP>TE3I8&<‚p†WE]D2JP>XL:T?,HUB^VC_D1MQ>ZM:VJ8RNVJ8PNZQ>ZQ>ZQ>\S@`R?_R?_S@`S@^Q>\P=YQ>ZS@\Q>ZQ>ZS@\S@^R?]R?_TAaUB`UB`VCaK8V`MkkXvJ7UF3QWDdP=]]JjI6VZGgñÞþzg‡L9YP=]VCcXEeTAaTAaYFfWDdQ>^VAbWBcXCdXCdVAbVAb\GhcNo]HiWBcS>_VAbZEf[Fg]Eg^Fh^Fh[CeZBd]Eg`Fi_Eh^Dg_Eh^Ff^Ff^Ff^Ff^Fh^Fh^Fh^Fh`HjbJlbJn`Hl`HlaIm`Hl^Fj_GieMogOseMqaIm[Cg`HlnVzkSwX@dbIppW~^ElW>esZŽuœqW~Y?f`FmeKrnT{nT{VSXBWXBWT>SQ;PT>SXBWZD[eMg>$A…Ÿ…Ÿ?)@ZDYN;NOQT?RR?SR?SN;OOQN=PC2EP?RN=PM0GG9Pm_v@2IE3INTE3II7MI7MI7MH6LH6LH6LH6LH6LG5KH6LH6LF4JD2HE3IH6LJ8N?-CL:PL:PE3II7MM;QH6LD2HYG]Q?UF4JE3IO=SO=SE3IG5K;)?eSi]KaA/EM;QH6LG5K>,BK9OD2HdRh…s‰2 6J8NM;QF4JH6LE3IL:PI7MB0FL:PO=SD2HQ?WG5MM;SP>VA/GK9QM;SB0HYG_VD\9'?[IaI7OI7OD2JK9OO@U@3EVI[8+=gXk‚s†4%8G8KM>Q;,?K:MD3FRATJ9LJ9LD3FF4LH6NH6NG5MI7OL:RK9QH6NK9QL:RL:RJ8PH6LG5KG5KH6LR@VM;QC1Gn\rP>T9'=O=UL:RI7OJ8PJ8PI7OH6NI7OL:TO=WI7OI7OI7OI7OJ8PK9QK9QL:RJ8PK9QK9QJ8PL:RO=UO=UL:RF4JXGZH6LJ8NSAW9'=m…UC[=+EQ?YOVJ8PI7ONXQ>ZQ>ZQ>ZQ>ZQ>ZR?[R?[R?]R?_R?_S@`TA_TA_S@^R?[R?[TA]S@\R?[S@\S@^S@^TAaUBbN;YUB`R?]ZGeK8VN;Yr_}eRpH5UZGgZGgYFfL9YI6V_LlVCcVCcWDdVCcS@`S@`WDdXEeUBbXCdXCdYDeZEfWBcU@aYDe_JkbMnaLm\GhWBcXCd\Gh_Gi\Df_Gi\Df[Ce]Eg`Fi^Dg^Dg_Eh\Dd\Dd]Ee^Ff^Fh_Gi`Hj`HjaIkbJlbJnaImaImaIm`Hl_Gk]EgcKmmUy^Fj[Cg‡o“‡o“C+OV>biQufMt\CjgNuiPw]DkW>ehNurXbHoeKrcIpdJqƒi‡m”dJq]CjfLsqW~hNubHoiOvmSzkOwkOukOwkOukOwkOukOwlPvmQylPvkOwkOulPxmQwmQymQwnRxnRxoSyoSyoSyoSyrTzrTxoQupRvpRvpRvpRvpRtqRtpQsmOqpRtrTvpRtmOqlNpoQssUwuVxqRtmMrnNsrRwtTysSxpQsvWwsTsuStwUvyUywSwwQvwQv{Rz}UzvLryOu}Sy}Sw…Y}}TvwRs|Wx|Su{OrƒTx‡X|ˆY{‹^„[y}VsxTp{Yt~\u~ZtXsXs¶†–°€¬|Œ¬|Œ¬|Œ©y‰§v‰¦uˆ¤u‰¢s‡Ÿp„žoƒmƒ›k˜h~–f|˜h‚Ž^x”f€”hc|Žd|‡\wƒ[u…]w…_x†^x„\vƒ[u„\v„Yu}Up}VqXs€YtXs{WqyUozVpzXqwUnyWpzZrzZrwWouUmtTltTltTltTlsSkrRjqQipPhnQgoRh}`trUi\?Sž•€’V9KnSdv[liNalQdjQfhOdiPfkRhjPihNgiOhhOefNdeNbdMaeNbfOagPbgRefQdbM`cNafOc\EY`I]u^rcL`aJ^_F[cJ_Y@UgNcjQf\CX^F\]E[]E[_G]^F\\DZ\DZ_G][CY]E[]E[[CYZBXZBXZBX[CYXBWL6KpZo`J_O9N[EZ^H]Q;PXBWXBWXBWXBWXBWWAVWAVWAVU?TU?TWAVYCXWAVRSYCXV@UWAVWAVU?TU?TV@UWAVWAXQ9SpWt.0¯—±‹uŒ@*?dQdN;ND2B^L\I4EZEVr[oD-AU>RYBVaL_WBUS>QXCVXCVP;NL9MQ>RN;ON;OOQK:MVEXD3FG6IL;NUDWF5HC2EK:MH7JJ9LN=PS@TP=QE2FYFZ?.AG6IMeSiUCYA/EI7MG5KK9OB0FL:PC1G]KamƒA/EJ8NSAWA/EF4JO=SSAWH6LD2HI7ME3IVD\yg8&>SAYC1IXF^>,DM;SG5MeSkM;S?-EVD\K9QH6NI7M?0EL=PD5HTEX;,?o`s‚s†5&9F5HL;NYH[I8K>-@L;NK:MJ9LI7OI7OI7OI7OJ8PK9QJ8PH6NL:RL:RL:RK9QI7OG5MG5MH6NA/EL:PM;Q<*@~l‚]Ka<*BJ8PK9QM;SM;SL:RI7OG5MH6PI7QJ8PJ8PJ8PJ8PJ8PK9QL:RL:RK9QL:RL:RK9QL:RO=UO=UL:RNXE2NYFbJ7SN;WSA[M;UL:TP>XQ?YL:TK9SO=WQ?YL:TK9SNXQ>ZWD`I6TR?]WDb=*H¡Ž¬‹x–7$@RB]XHcA1LK8T[HdN;WM:VL9UH5QWD`OX`NhaOiO=WH6PSA[VC_N;WR?[R?[R?[R?[R?[R?[S@^S@^P=]R?_S@^TA_UB`UB^UB^TA]TA]TA]TA]S@\S@^TA_UBbUBbQ>\TA_\IgJ7UN;YS@^M:Xub€mZzB/OH5U_LlYFf_Ll_LlWDdZFiVBeT@cVBeWCfVBeVBeXDgYCgXBfYCg[EiZDhXBfZBf\Dh^IjfQrePqZEfVAb]Hi`HjYAc^Fh]Eg^Fh_Gi^Fh\Df^Dg`Fi_Gg_Gg`Hj`HjaIkbJlbJlbJl`Hj`Hj`HlaImaImaIm`HlaImlRwT:_eKprX}[AfeKp’x¦Œ±tZQ7^X>enT{fLscIpmSzkQxlRyW=dlRyqW~eKr_EllRy§´ª·‚h\Bi^DkoU|hNubHooU|kOwkOwkOxkOwkOxlPxmQzmQyoS|mQylPylPxlPymQynR{nRzlPxlPxlPxnRzqR{rS|rS|rTzoQwpRxpRxqSyrRyqQvqQvpPutVzsUyqSwpRvqSwsUyvX|xZ~tTytTytTytTyuUztTytRwrPstUurSsrSuuVxxV{wUzwSywSyyS|}W~xOw{SxW|€X{…[}SwzUw€X{€TwQu…Vz‹Y~[~Ž_‡[|}VuyUq~Zv]yZwYtƒ[vµ…•¯«{‹«{‹}ªzЧv‰¥t‡¦w‹£tˆ q…žoƒžn„œl‚™i–f|™iƒŽ^x“e–h‚‘c}Œ`y‡[vŠ_zˆ]x…]w†[v„\v†[v„Yt‚Ws€UqWrXsXsXs|Xr{Wq{Wq{WqxVoyWp{YrzZrxXpwWovVnuUmtTlsSksSktTlvVnuUmqTjoRhkNb]@T‚eyU8L£†˜p‚cHYtYjlQdoTgmTiiPehOejQgiOheKdiOhhNgfNdfNdeMcfOcfOcgPbcNalWjdOb_J]iRfdMa[DXeNbpYm]FZdK`_F[cJ_W>SqXmdK`]E[[CY]E[`H^`H^]E[^F\cKa[CY]E[\DZYAWX@VZBX[CY[CYaK`S=RV@UlVkQ;P^H]WAVU?TYCXYCXYCXXBWXBWXBWXBWXBWWAVS=RU?TZDYXBWRUZB\Q9ShPjD.EÜÆÛs]r8%8dQdVDTZHXR=NN9JeNbbK_P9MU>RU@S_J]\GZO:MO:M[FYXEYK8LOQB1DWFYF5HK:MMQP?RG6IL;NN;OR?SD1EQ>RSBUH7JN=PI8KJ9LJ9LJ9LJ9LM:NOUC[G5M>,DM;SG5MF4L]KcUC[F4LN,DD2JG5MK9QM;SM;SK9QH6PG5OK9QJ8PJ8PJ8PK9QK9QL:RL:RM;SM;SL:RK9QK9QM;SL:RI7OI7MP>TNXQ?YM;UNXL9UM:VO^S@`TA_TA_TA]VC_VC_UB^S@\UB^UB^TA]TA_VCaVCcTAa\IgQ>\J7U`MkZGeN;Y\IgJ7UlYyjWwaNnH5UP=]ZGgH5UVCcZFiT@cT@c[Gj\HkWCfT@cVBeXBfV@dV@dZDh]Gk[Ei[CgZBfWAecNofQr[FgWBc^IjaIk[Ce^Fh^Fh_Gi`Hj_Gi\Df_EhbHkbJlaIiaIkaIk`Hj`Hj_Gi_Gi`Hj_Gi_GkbJnbJnaImaImbJn`FkeKpdJoaGl`Fk\BgZ@e_Ej•{¢uœoU|Z@gdJqfLs]CjbHo]CjpV}eKreKrhNuiOvoU|VRkPcoTgoSikOejNenRikQjfLehNghNghNghOefNdfOcfOcfOccKahPffNdaI_dLbeMcaI_\DZhOenUkZAWkRh\CYaH^bI_hOecKa]E[\DZ_G]`H^]E[_G]cKa\DZ_G]_G]\DZZBX\DZ\DZ[CY^H]aK`P:OT>SpZoM7L[EZXBWXBWXBWXBWXBWXBWXBWXBWWAVV@UU?TV@UWAVWAVV@UU?TWAVXBWV@UU?TWAVXBWWAVV@UU?VR:TX@ZW@Z\F]RTI4G]H[_J]L7JK6I^I\bLaS=RQ>RQ>RP=QOTR?USBU>-@}lK:MG6IK:MJ9LSBUD3FMTJ;PKSPAVM>SO@U@1F@1FL=RKTK9OI7MQ?U8&T5#9Q?UiWmD2HL:PH6LK9OPATL>OD6GC5FQCTG9J;*<Šy‹~m
MVD^O=WQ>ZK8TK9SP>XP>XL:TM;USA[R@ZK9SK9SR@ZR@ZM;UK9SO=WP>XL:TM;SM;SM;UM;UM;UN\VCaOSU?TYCXYCXS=RXBWWAVV@UWAVV@UU?TV@UXBWW?Y[C]N8O`Ja_L`K8LÕÂÕ_L_L9LdQdP=P]J]^I\N9LqYoYAWU@SQRP=QP=SQ>TS@VTAWUDWH7JD3Fq`sE4GE4GXGZC2EN=PRATG6IL;NMQK:MM:NI6JXEYJ7K?.AP?RN=PH7JJ9LL;NL;NH7JI6JM:NN;OL9MJ7KP=QG4HQ>RI6JN;OmZnM:NO>QMTNOF8IRDUA3DXJ[B1C9(:€on€5$6UDV7&8]L^VCVE2FG5KI7MK9OK9OI7MG5KJ8PNVO=UM;SG5KSAWJ8NM;QP>VM;SNXO=WL:TN\WDbA1LhXs‡w’=-HSC^RB]Q>ZUB^S?[O;WYEaUA]\EbV?\R;XScT@e[Gl\FlWAg[EkYCiXBhYCi[BiY@gZAh]Dk`JnYCg\DhcKocKo\Dh\DhaImcKo^Fj[Cg_GkcKocKoaIm_GkaIkaIkaIkaIkbJlbJlbJlbJlcKoaImaImdLpeMqbJnaImdLphNsaGlcIneKpaGlgMrjPw^DkiOvhNuaGnpV}ˆm–y^‡aFoeJseLsgNueLsiPwjQxiPwlSzkRykRykRylSzdKrgNu‡n•–}¤ƒh‘sV‚tUrSqR~pQ}qR~qR~rSpQ}sT€uV‚tUpQ}mNznO{oP|oS|nR{pQ{rS}uV€uV€tUrS}rS|sT}uU~vVvVuU~tT{sSztV|sU{sUytVzvX|wY}vV{uUzxX}tTyqQvsSxzX{}[~zX{uVx|]y[}xZ~y[zZwW~xUxUzU€‚]ˆY‚~X€W‚Yˆ`…Y~W|…[†Z~†V|ŽZ\ƒ[^ƒŠ[Tu€Tuˆ_ŽcŠ]|‰ZzŒ]}¯°€°’®}«z©x‹¨wŒ©x¤u‰¢s‡¢rˆ¡q‡žn†›kƒ›kƒžn†šj„˜g„•d“b’a~_|^{\yŒ[yba~‡Yv†Xu_|Œ^{…Xu‚Ws‚Zuƒ[vYtYtZu€Yt~WryUozVp{WqyWpvTmuSlvTmwUnxVowUnuSltRkrRjrRjrRjrRjrRitTkrUkoRhsVlcF\oRh½ ¶kPciNavZpgKarVmfJaoSlmQjiOhhNggMfgMfhNghOegNdgNdaH^iPfgNdeLbjQgeLb^E[cJ`cJ`cJ`cJ`cJ`cJ`bI_aH^aH^aI_cKaaI_]E[]E[_G]_G][CY[CYkSiaI_U=SZBXeMcW?UV>T\F[XBWYCX]G\ZDYS=RlVkN8MWAV[EZ[EZV@US=RU?TWAVWAVS=RU?TWAVXBWWAVV@UV@UV@UYCX[EZYCXS=RT>SZDYZDYU?TYA[O7Q]G^D.EiVjQ>R_N`ÒÁÓK:LSBTVCVdQdQ;PT>S]E[oWmXAUO8LT=QbK_[FYI4GK5J_I^nXmC-BYFZR?SK8NORL9MT>UL6MS@VJ7MR@VK9OD5JJ;PPAVO@UK9OJ8NM:PK8NYCZF0GH5IM:NK8LL9ML9MF3GTAUtauN;OR?SI8KN=PTCUC2DMTN;QD1GQ>T?,BTAWQ>TUBXQ>TP=SA.DUBXUBXG4JG4HN;OJ7KG4HM:NK8LH5IP=QE2FP=QTAUN;OI6JK8LL9MJ9LMNTF4JkYocQg7%;ZH^YG]@.DJ8NNT>,BŒz‚p†A/EWE[E3IK9OD2HL:PK9OeSiL:PE3ISDWKœ‹žgTh./[H\A.BXEYQ>RC0FM:PM:PL9OP=SJ7MF2KO;TL8QL8QM9RM9RM9TM9TL8SL8QJ7ML9OK8NJ7MM:POYQ=XPYQ=XN:UK7RPYVB^T@\M9US?[T@\Q=YUA]\HdXD`L9U`Pk;+F^NišH5QN;W[HdI6R\HdO;WS[YEaO;WS?[VB^O;WVB^_KgXD`U@_VA`VA`T?^S>]T?^VA`VA`WBaVA`VB^VB^WC_XD`XD`XD`ZFbWC_U@_VA`WBcWBcZDh]HiYDeR=\[FeVA`WBaYDcO:Y^IhT?``Kl[FgWBckVw`KlJ5VbMnWChXDijTzeOuWAgWAgV@fZDjU?eWAg\Cj\CjZAhZAh[Bi]Dk\Dh^Fj_Gk`Hl_Gk_Gk_Gk_GkbJn`Hl^Fj]Ei_GkaIm_Im`HldLndJmbHkaGjaGjcIleKpfLqdJodJodJodJoeKreKreKreKreKpfLqgMrgMrfLsfLsgMthNuaGnoU|kPy[@idIrd„i’sXiNwjOxjOxjOxiNwjOxkPylQznS|lQzpU~pU~fKt_DmpU~‹p™}`ŒtUlMypQ}tUsT€pQ}pQ}uV‚uV‚tUrSqR~rSsT€uV‚nO{qR~sT~rS}rS}sT~tUrS}wVsR}uU~vVsS|tT}wT~qQzvX~wYyY€xXwW~vV}wW~xXvV{wW|yW|yW|zX{zX{zX{yZ|tTywY}|^‚xZ~sSzxX}Z„vS}uP{Z…[„}W€‚Y„[ƒ€W~V{~V{€V|Š\ƒ‡WŠV}’[ƒŽZ‘_„Œ]‚TxƒWx‹_€`Ža‚’c…“a„±‘²‚’²”¯~‘¬{ŽªyŒªyŽªyާxŒ¥vФtŠ¢rˆžn†šj‚šj‚œl„œkˆši†˜g„–e‚”c€“b‘`}_|\z‘`~Ž`}‰[xˆZw‹]z‹]z†YvƒXtƒ[vƒ[v‚ZuYt‚Zu€Yt}Vq|Up{Wq|Xr{WqwUnvTmwUnxVoxVowUnvTmuSltTlsSksSksSkxXoqQhqTjnQgtWmtWmjMcuXn½¢µT9LmQghLbsWnpTklPioSliOhiOhhNghNghNghNghOehOefMchOeeLbdKagNdeLbbI_cJ`cJ`bI_bI_bI_aH^aH^aH^`G]`H^cKadLb`H^]E[^F\_G]^F\`H^W?U^F\jRh^F\^F\X@VbJ`V@U\F[YCXT>S_I^XBWZDYkUjYCXV@UV@UZDYZDYWAVWAVYCXWAVV@UU?TU?TU?TXBWZDY\F[RSU?TXBWW?WTTQ>TS@ViVlB/EUBXD1GUBXUBXK8NG4JQ>TE2HVCYQ>TK8LM:NK8LK8LOQMTP>TQ?UB0FvdzcQgA/EO=SQ?UNQA2EH7JH7JRATP?RRAT*,©–ªcPdD1EN;OJ7KWDXWDZD1GL9OWDZK8NI6LQ=VI5NL8QM9RM9RM9RM9TM9TM9TL8QM9RN;QM:PK8NM:PP=SPYH4OL:RL:RK9QL:RNVL:RI7OJ8PNVO=UM;SPY[GbJ6QPYQ=XQ=XQ=XR>YR>YQ=XQ=XPZPZVB^VB^R>ZL9U]Mh?/JeUp›ˆ¤I6RN;W_LhG3OUA]]FcL5RXA^XA^U>[W@]S?[UA]VB^XD`XD`R>ZN:VQ=YXCbXCbXCbWBaWBaWBaT?^R=\XCbWBaVB^VB^WC_XD`XD`XD`]IeZFbYDcYDcXCdWBcXBf[Eixc„aLkK6UWBaU@_P;Z^IhT?^bMnH3TWBcZEfQ<]mXyq\}I4UXDiVBgZDju_…cMsM7]bLrS=c`JpZDjW>eX?f]DkaHo^ElZAh^Fj^Fj_Gk_Gk^Fj_GkaImcKoaIm_Gk^Fj^Fj`HlbJnaKoaKocIlcIlcIlcIldJmdJmdJodJoeKpeKpeKpfLqfLsfLsfLsfLseKpfLqgMrgMrfLsfLsgMthNulRydJqdIrlQzjOxaFofKtuZƒjOxkPykPykPyjOxkPylQznS|pU~iNwgLunS|tY‚pU~iNweIrmNzoP|sT€tUqR~mNzoP|tUrSrSrSrSqR~qR~sT€uV‚qR~tUvWsT~rS}sT~uV€vWuTqP{sS|xXyY‚~^‡ƒ`Š~[…tT{tV|wW~wW~vV}vV}xXyY€yY~yY~{Y~{Y~|Z}}[~}[~}[~_„pRvsUy€b†}]„tT{wT~}Z„zU€€[†}W€wQz~U}ƒZ‚„[ƒ…\„W|€V|Š\ƒ‡WŒX•^†\ƒ’`†‘_„…W{†X|‹_€‹^Œ]^Ž\²”²”²”°’|«zªyŽªyލxަvŒ¤tŠ£s‰ pˆ›kƒ›kƒm…œnŠšlˆ—i…•gƒ–e‚•d“b€’a^|^|Ž]{\zŠ\y‰[xˆ[zˆ[z„Yu†[w†[w„YuYt‚ZuYt~Wr~Wr~Wr|Xr{WqzVpyUoxVoyWpwUnwUnwUnwUnvTmvTmtTltTlyYqoOgwWosSklLdwWorTlbE[vYmË®ÂU:MrVliMdiMdlPifJckNjkNjjPkiOjhNghNghNgiOhmQhhLcgKbgKbeI`gKbgNdcJ`dKacJ`bI_aH^`H^`H^`H^aI_^F\cKafNdcKa^F\]E[^F\_G]`H^YAWcKaiQgO7MV>T[CYV>TT>SaK`cMbS=RYCXaK`L6K_I^aK`V@UT>SZDY]G\WAVV@U[EZYCXWAVU?TU?TV@UXBWZDY[EZXBWT>SU?TZDYYCXRQL9LQ>TC0FQ>TL9Oxe{ˆu‹:'=UBXJ7MG4Jp]sUBXG4JN;QK8NN;QORS@TM:NF3GP=QR?SVCWM:N>+?VCWucyA/EF4JH6LM;QK9OG5KUCY?-C…s‰Q?UF4J_McD2HQ?UO=SK9OM;QG5KSAWR@V-1‹¡n\r>,BWE[O=SA/EK9OK9OK9O_McO@U@1FVDZP>TG5KO=SL:PI7MTAWK8NC0FVCYL9OE2HH5KP=SUBVQ>RJ7KK8LQ>TP=SL9OL9OM9RM9RN:UN:UN:UN:UM9TM9RN:SOWQ=VO;TA-FjVoUAZK7PK7PN:UJ6QK9QL:RNWQ=VN:SN:SPYPZS?[R>ZQ=YQ=YS?[UA]OZVC_Q=YŸ‹§\Hd<(DaMi^JfM9UaMiR;XW@]^GdSZ[GcS?[M8W\GfU@_T?^T?^U@_XCbXCbWBcU@_XCbXD`XD`WC_WC^WC^WC^XD_[GcYEaXCbYDcXCdV@dV@fXBfS>_r]|aLkU@_\Gf\Gf]HgR=\[Fg\GhXCdVAbaLmYDeU@ayd…_IoL6\S=cS=ccMsgQwO9_bLr]Dk]Dk]Dk]Dk\Cj]Dk_Fm`GnaGlaGl_Gk^Fj^Fj_GkbJneMqbJnaIm^Hl^Hl`JnaKobLpaKobHkcIleKpfLqfLqeKpdJocInfLqfLqfLsgMtgMtgMtgMtgMtfLsgMthNuhNugMtgMthNuiOvgLueJshMvmR{jOxdIrfKtoT}lPymQzmQzmQzmQzmQznR{pT}oS|nR{mQzmQzqU~sW€oS|iMvoP|qR~qR~rStUvWƒtUqR~pQ}qR~rSsT€rSrStUuV‚sT~vWwX‚tUrS}sT~wVyXƒxXsS|uU~xX{X‚\†^†|YwW€xXyY‚yY‚yY‚yY‚zZ{[‚~\|Z{Y~|Z~\€^]€~\b„mNpxX}¤„©¿œÄ´‘¹™tŸ„_Š›t ›t ‘h’…\†„[ƒ„[ƒW}€V|€W€V~ˆ[‚‡WŽYƒ–a‹’^…“a‡“a‡‡Y}ˆZ~Œ`ƒŒ^‚‹\€]€[²”²”±€“¯~‘¬{ŽªyŒ©x©x§w¥u‹£s‰£s‰¡q‰žn†žn†p‡™mˆ˜l‡–h„”f‚•d”c€“b€‘`~‘`~\zŒ[y^|Œ^{ˆZw‡ZyŠ]|…Zv†[w†[w„Yu‚Zuƒ[vƒ[vYt~Wr~Wr|Xr|Xr{WqzVpyWpyWpvTmwUnwUnxVowUnwUntTltTlwWomMewWoyYqlLdnNfvXpwZpgJ^s‡®“¦cH[y]tbF]gKduYrkNjlOkkQljPkiOhhNgiOhiOhmQjfJaiMdjNecG^fJajQgdKafMceLbdKabI_aI_aI_bJ`cKa_G]bJ`dLbcKa`H^_G]^F\^F\[CYfNdX@VW?UqYo{cyL4JiQgYCXRSdNc[EZT>SU?TXBWWAVU?TT>SU?VYCZU?TRUWAVTR[EZbLaM7LWAVeOd]G\G1HU?VQ>TTAWP=QQ>RTAUP=QS@TC0DdQeQ>RK8LP=QORR?SK8LL4LTT[I_%+ ´uh|3&:F9MM>SR@VL9OO9PF.FT>UO9NP=QO+A‚o…vcy@-CM:PTAWD1GcPfUBXE2HQ>TN;QQ>RK8LP=QR?SL9MN;OR?SL9ML9MORYFZM:NH5IR?SG4HL9M[H\5"6O=S…s‰2 6UCYM;QI7MP>TNWS?XR>WK7PS?XF2Kye~nZs8$=[G`K7PI5PT@[O;VM9T\HcYE`N:US?ZQ=XR>YS?ZR>YPZO;WPZVB^XD`T@\T@\VB^UA]Q=YPZE1MR>Z¤¬kWsC/KO;WZFbZFbT=Z[DaW@]T@\UA][GcUA]P_ZEfXCd]HifQr\GhT?`oY}`JpZDj_IoU?e\FljTzS=cV=d\CjaHoaHo]Dk[Bi^ElbIpaGlaGl`Hl_Gk_Gk_GkbJncKofNreMqbLpaKo`Jn`Jn`Jn`JnbHkdJmeKpgMrgMrfLqeKpdJogMrgMrgMtgMtgMthNuhNuhNugMthNuiOvhNuhNuhNuhNuiOvcHqmR{mR{dIrhMvuZƒtY‚fKtmQzmQznR{nR{mQzmQzoS|pT}oS|sW€tXoS|jNwkOxsW€z^‡xY…wX„sT€oP|rSxY…wX„qR~rSsT€uV‚uV‚uV‚tUuV‚vWƒtUvWvWtUtUvWxW‚vVzZƒvVxXyY‚xUyV€{X€wT|{[„{[„{[„{[„zZƒzZƒzZzZ~\|ZzX}{Y~~\€^€^~\z[}`‚„d‰{[€iFnfCkuP{€[†tMywQzwNxwNv€W†]……[†\‚‚Y€Wˆ[‚ˆX€Ž[„—bŒ’^…’`†_…†X}‡[~a„_ƒŽ_ƒ“a„“_ƒ´ƒ–³‚•±€“¯~‘|‘«zªyލwŒ¨xŽ¥u‹£s‹£s‹¢rŠŸo‡žn†žqˆ™mˆ–k†•i„“g‚“e’d€“b€‘`~”a€\{\z_}^~ˆYy‡ZyŒ_~…Zv‡\x†[w…Zv„Yu†[w„\wƒ[vXs~Wr~Wr}Vq{Wq{WqzVpyUovTmvTmwUnwUnwUnvTmuSluSlsSkqQioOgwWo{[sqQinNftTky\pbEWŒoƒtWkhLcnRikOhnRkkNjlOkkQlkQljPkiOjiOjiOhjNggIclPimQjeIbfJckOhgKdgMffLedJccIbaIabJbcKcdLdbI_`G]`G]bI_cJ`aH^_F\\CYbI_ZAWaH^`G]Q8N¢‰ŸfMcTSRRSRW?U^F\bLaQ;PJ4IfPe\F]S=TWAXU?VQ>RN;OTAUWDXG4HWDXC0D\I]UBVP=QN;ON;OP=QQ>RN;ON;OTQ?,?}k{[IYD/@VARO:KQRL9OVCYE2HP=S;(>“€–xe{?,BTAWG4JVCYYF\S@VL9OP=SR?SM:NQ>RR?SJ7KL9MS@TORP=QK8LK8LQ>RTAUQ>RO9NO9NQ;PRTK:MH7JK:MQ@SG6IQ@SUDWB1D‚q„WFYG6IN=PI8KTCVG6IG5KH6LH6NQ?WK9QJ8PG5MO=UP>VH4MN:SUAZVB[M9RJ6OWC\E2HM:NG4HR?SVCWH5KJ7MTAWL9OL8QM9RM9TM9TM9UM9UM9UL8SM9RN;QOVL:RNWT@YK7PM9RF2K^Jco[tC/HPZPZR>ZS?[VB^N:VS?[PZQ=XWC^YE`T@[Q=XR>YO;W^JfQ:WQ:WbKhS^JfM9UT@\T@\UA]WC_S?[UA]ZFbXD`T@\[Gc\GfK6U[FeZEdZEd[FeXCdU@aVAbZEdYDcZFbZFbYEaWC^WC^XD_YE`ZFbXD`YDc\Gh\Fj[EiZDj\Fj[FgWBaYDcVA`bMl]HgK6UhSrp[|P;\ZEf\GhO:[fQrp[|T?`Q;_kUy_ImXBf`JnXBfZBfcKo`Hl^Fj\Dh]EibHmcInaGl^DiaGlbHmcIncInaIm`Hl`HlaImfNrfNrcMqcMqbLpaKoaKoaKoeKpeKpfLqfLqfLqfLqfLqfLqgMtgMtgMtgMtgMthNuhNuhNuiMujNvjNvjNvjNviMujNwkOxnR{nR{lPyjNwkOxnR{mP|iLxoPzoPzpQ{pQ{oPzoPzqR|rS}uV€qR|qR|uV€uV€pQ{mNxpQ{sR}zY„|[†uToNyqP{uTwVuTvU€xW‚wVvU€vU€vU€vU€vWvWvW€vW€zZƒ|\…yY‚uU~xXwW~~[ƒ\„zW{X€\„\„zZƒyXƒyXƒyXƒyY‚yY‚zWzW~\|Z{Y~{Y~[]]€^~_{\~}[~]€ˆdŠ–r˜•o˜„^‡‹bŒdމ^‰†\„Š`ˆˆ^„‚U|~Tz„[ƒX€‰\…‡X‚Zƒ–a‹[…]†Œ\‚…W|‡[~a„_ƒŽ_ƒ”b‡–bˆ¶…˜´ƒ–²”°’¯~“|‘«z©x«{‘§w¤tŒ¤tŒ¢rŠŸo‡m…›n…šn‰—l‡–j…”hƒ”f‚”f‚”c“b€”a€]|Ž]{‘`~Ž_‰\{‰\{Œ_~†[wˆ]y‡\x…Zv…Zv‡\x…]xƒ[v€YtXs~Wr}Vq|Xr|XrzVpyUowUnwUnwUnwUnwUnvTmuSluSloOg}]uoOgmMezZrsSkmMeqQhnQesVhpSgbEYpTkmQhjNgfJclOkkNjiOjjPkkQlkQliOjgMhiKeiKelPimQjiMfhLeiMfgKdeKdeKddJccIbaIaaIabJbbJbcJ`_F\]DZ`G]cJ`bI__F\\CY`G]_F\fMc[BX[BXG.D´›±V>TV>TdNc_I^[EZYCX[EZV@UV@US=R[EZ`J_^H][EZ[EZYCXV@UYCXYCXXBWWAVWAVXBW\F[^H]Q;P]G\bLa[EZU?TV@UYCXWAVRTO9PO7OT>UV@UI6JK8LTAUS@TO[FWO:MQ>RI6LJ7M\I_TAW[H^9&<¦“©mZp=*@eRhM:PI6L`McP=SORORQ>RM:NN;OR?SR?SQ>RM:NM:NP=QN;OK8LOSRYG]bPfI7ML:PA/EUCYE3INQK:M’SBUE4GRATF5HQ@SL:PD2JUC[=+C\Jb}kƒ9'?`NfD2JL8QR>WD0IL8QD0IWC\F2KS?XR?SH5IOWI5NO;TM9RG3Lt`yZFaK7RK9QM;SO=UP>VP>VP>VP>VP>VO=UM;SL:RNVSAYSAYR@XQ=XS?ZS?ZQ=XPYR>WPZS?[R>ZR>ZS?[T@\YEaJ6RWC_aMiO;WK7SVB^R>ZT@\O;WN:UT@[XD_VB]S?ZS?ZV?\P9V`IfV?\SZ\IeVC_WC_S?[ZFbYEaPRXAUW@TU>RU>RW?UYAWM7LRRL9MR?SN6NN6NYCZQ>TH6L;,AWJ^C6J?2FÑÄØM>S>,B\I_L6MS;SL6MT>SJ7KM:NR?SP=QR?SR?RH5HQ>QE2EZEVE0A‰t…U@QJ5FVATORR?SORP=QQ>RN;OORM:NOTD2HL:PYG]B0FTBXK9OJ8N{‘C1GC1GVEXRATJ9LC2ESBUK:MSBUP?RF5H?.Aœ‹žP?RI8KQ@SQ@SM;QQ?WF4NWE_B0Jm‡}k…/6XD_Q=X@,G_KfUA\T@[G3NZC_SRE2HUBXO;TO;TO;VPVR@XR@XQ?WQ?WM;SJ8PK9QO=USAYTBZSAYQ=XS?ZS?ZQ=XPYQ=VO;TF2KVB[UAZPZS?[UA]M9UUA]gSoaMiJ6RJ6R^JfS?[Q=YQ=XS?ZUA\UA\UA\T@[S_YDe]HiZEfXCdZEdXCbZFb[GbZFaXD_WC^ZFa\HcZFbXD`XCdZEfZDhXBfYCi[Ei\GhfQpS>]VA`[FeR=\`KjiTsO:[^IjhSt[FgXCdaLmYDeU@aq\}wbƒQ<]XCdkSuZBd]Eg^Fh_GieMojRtfNp^Dg[AdaGjhNscIndJqeKreKrdJqdJqeLsfMt`JpbLrcMsdNtcMscMsdPufPvfNrgMrgMrfLqfLqgMrhNshNshNuhNuiOviOviOviOviOviOvjNvkOwlPxlPxkOwkOwlPymQzfJspT}tXoS|kNznQ}sV‚uX„pQ{rQ|sR}rQ|rQ|rQ|sR}uTsR}tS~vU€uTtS~tS~yXƒ}\‡{[„tT}rR{yY‚€`‰€`‰zZƒtT}xXxXxXwW€wW€wW€xXzZƒxY‚z[„|\…zZƒzZƒ}]†_†€`‡^†|Y~[ƒ\„}[€~\]‚{X€~^‡}\‡|[†}\‡‚_‰ƒ`Š‚_‡^†\„€]…_„€^ƒ€\€‚^‚…a…‰e‰‚`€^‡c‡‰e‰€\‚|X~‚\ƒ†`‡V~‚YWW‰\ƒ‰\ƒ‰]Œbˆ‚\ƒ‚\ƒ‹a‰Š[…Œ\†”aŒ]†“`‰Ž^„‡Y~‰]c‡Ž`„Ž_ƒ”b‡–d‰·†™´ƒ–±€“°’¯~“|‘ªyލwŒ©y¥u‹¢rˆ£s‰£s‹ p†žn†œo†™m†–k†•i„”hƒ”fƒ”fƒ”c“b€”a€”a€’a_^~``Œ_~`}`}‹^{†[w…Zv…Zv…ZvYt„\w‚Zu€XsXs€Yt€YtXs{Wq}Ys{YryWpxVowUnwUnwUnwUnzZr{[soOgvVnxWroNisRmmMenQgpSgsVjeH^oQitVnkOhkOhmQjjMihKgjMilRmmSliOjdJclPilPidF`cG`pTmpTmgKdgKdfLefLefLefLeeMedLdcKccKcdKaaH^`G]bI_bI__F\`G]cJ`^E[cJ`aH^`G][BXhOeH/EhPf©‘§J4I]G\aK`WAVYCXWAV\F[cMb[EZXBW_I^`J_ZDYXBW\F[S=RXBW]G\\F[XBWV@UXBW[EZ`J_WAVS=RYCX^H][EZWAVV@US=R^H]N;OVCWWDWQ@RQ@RVEWTCUTCUO>QbQdTAWɶÌQ>TM7N[EZT?RV?S\GZ]FZU@SS;QV@U^H]H2GQ;Pq[p\F]G1HmWnN8OS@TM:NR?S_L`E2FVCWH5IWDXI6JP=Q_L`R?SL9MS@TL9MOU@S‹v‡D/BZEXM:NXE[I6LL9OUBXS@VR?UL9OI6L¦“©^KaH5KYF\P=SWDZR?SP=QS@TM:NP=Q^K_\I]P=QN;OL9MS@TUBVP=QP=QUBVUBVRTT