public class Album extends GalleryItem implements ListModel, Serializable, PreferenceNames {
	/* -------------------------------------------------------------------------
	 * CONSTANTS
	 */
	public static final String MODULE = "Album";


	/* -------------------------------------------------------------------------
	 * LOCAL STORAGE
	 */
	ArrayList<Picture> pictures = new ArrayList<Picture>();


	/* -------------------------------------------------------------------------
	 * SERVER INFO
	 */
	//Gallery gallery = null;
	//ArrayList subAlbums = new ArrayList();

	//Album parent; // parent Album
	//String title = GRI18n.getString(MODULE, "title");
	String name;
	ArrayList<String> extraFields;
	String summary;

	Boolean overrideResize = null;
	Boolean overrideResizeDefault = null;
	int overrideResizeDimension = -1;
	Boolean overrideAddToBeginning = null;

	int autoResize = 0;

	boolean hasFetchedInfo = false;
	boolean hasFetchedImages = false;

	transient private Long pictureFileSize;
	transient private Integer albumDepth;
	transient private boolean suppressEvents = false;

	public static List extraFieldsNoShow = Arrays.asList("Capture date", "Upload date", "Description");
	public static List extraFieldsNoShowG2 = Arrays.asList("Capture date", "Upload date");


	public Album(Gallery gallery) {
		super(gallery);

		setAllowsChildren(true);
	}

	/**
	 * Retrieves the album properties from the server.
	 */
	public void fetchAlbumProperties(StatusUpdate su) {
		if (!hasFetchedInfo && getGallery().getComm().hasCapability(GalleryCommCapabilities.CAPA_ALBUM_INFO)) {
			if (su == null) {
				su = new StatusUpdateAdapter() {
				};
			}

			try {
				gallery.getComm(su).albumProperties(su, this, false);
			} catch (RuntimeException e) {
				Log.log(Log.LEVEL_INFO, MODULE, "Server probably doesn't support album-properties");
				Log.logException(Log.LEVEL_INFO, MODULE, e);
			}
		}
	}

	public void fetchAlbumImages(StatusUpdate su, boolean recursive, int maxPictures) {
		fetchAlbumImages(su, recursive, maxPictures, false);
	}

	public void fetchAlbumImages(StatusUpdate su, boolean recursive, int maxPictures, boolean random) {
		if (getGallery().getComm().hasCapability(GalleryCommCapabilities.CAPA_FETCH_ALBUM_IMAGES)) {
			if (su == null) {
				su = new StatusUpdateAdapter() {
				};
			}

			try {
				removeRemotePictures();

				gallery.getComm(su).fetchAlbumImages(su, this, recursive, true, maxPictures, random);
			} catch (RuntimeException e) {
				Log.log(Log.LEVEL_INFO, MODULE, "Server probably doesn't support album-fetch-images");
				Log.logException(Log.LEVEL_INFO, MODULE, e);
			}
		}
	}

	public void removeRemotePictures() {
		int l = pictures.size();
		for (Iterator it = pictures.iterator(); it.hasNext();) {
			Picture picture = (Picture) it.next();
			if (picture.isOnline()) {
				it.remove();
			}
		}

		fireContentsChanged(this, 0, l - 1);
	}

	public void moveAlbumTo(StatusUpdate su, Album newParent) {
		if (getGallery().getComm().hasCapability(GalleryCommCapabilities.CAPA_MOVE_ALBUM)) {
			if (su == null) {
				su = new StatusUpdateAdapter() {
				};
			}

			try {
				if (gallery.getComm(su).moveAlbum(su, this, newParent, false)) {
					gallery.removeNodeFromParent(this);
					gallery.insertNodeInto(this, newParent, newParent.getChildCount());
				}

				//gallery.fetchAlbums(su);
			} catch (RuntimeException e) {
				Log.log(Log.LEVEL_INFO, MODULE, "Server probably doesn't support move-album");
				Log.logException(Log.LEVEL_INFO, MODULE, e);
			}
		}
	}

	/**
	 * Sets the server auto resize dimension.
	 * 
	 * @param autoResize the server's resize dimension
	 */
	public void setServerAutoResize(int autoResize) {
		this.autoResize = autoResize;
		hasFetchedInfo = true;
	}

	/**
	 * Gets the server auto resize dimension.
	 * 
	 * @return the server's resize dimension for this album
	 */
	public int getServerAutoResize() {
		fetchAlbumProperties(null);

		return autoResize;
	}

	/**
	 * Sets the gallery attribute of the Album object
	 * 
	 * @param gallery The new gallery
	 */
	/*public void setGallery(Gallery gallery) {
		this.gallery = gallery;
	}*/

	/**
	 * Gets the gallery attribute of the Album object
	 * 
	 * @return The gallery
	 */
	public Gallery getGallery() {
		return gallery;
	}

	/**
	 * Gets the pictures inside the album
	 * 
	 * @return The pictures value
	 */
	public Iterator<Picture> getPictures() {
		return pictures.iterator();
	}

	/**
	 * Adds a picture to the album
	 * 
	 * @param p the picture to add. This will change its parent album
	 */
	public void addPicture(Picture p) {
		p.setParent(this);
		addPictureInternal(p);

		int index = pictures.indexOf(p);
		fireIntervalAdded(this, index, index);
	}

	/**
	 * Adds a picture to the album
	 * 
	 * @param file the file to create the picture from
	 */
	public Picture addPicture(File file) {
		Picture p = new Picture(gallery, file);
		p.setParent(this);
		addPictureInternal(p);

		int index = pictures.indexOf(p);
		fireIntervalAdded(this, index, index);

		return p;
	}

	/**
	 * Adds pictures to the album
	 * 
	 * @param files the files to create the pictures from
	 */
	public ArrayList<Picture> addPictures(File[] files) {
		return addPictures(files, -1);
	}

	/**
	 * Adds pictures to the album at a specified index
	 * 
	 * @param files the files to create the pictures from
	 * @param index the index in the list at which to begin adding
	 */
	public ArrayList<Picture> addPictures(File[] files, int index) {
		List<File> expandedFiles = Arrays.asList(files);

		Log.log(Log.LEVEL_TRACE, MODULE, "addPictures: " + expandedFiles);

		try {
			expandedFiles = ImageUtils.expandDirectories(Arrays.asList(files));
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
		}

		Log.log(Log.LEVEL_TRACE, MODULE, "addPictures (expanded): " + expandedFiles);

		ArrayList<Picture> pictures = new ArrayList<Picture>(expandedFiles.size());

		for (File f : expandedFiles) {
			Picture p = new Picture(gallery, f);
			p.setParent(this);
			if (index == -1) {
				addPictureInternal(p);
			} else {
				addPictureInternal(index++, p);
			}

			pictures.add(p);
		}

		fireContentsChanged(this, 0, pictures.size() - 1);

		return pictures;
	}

	/**
	 * Adds picturesA to the album
	 */
	public void addPictures(List<Picture> picturesL) {
		addPictures(picturesL, -1);
	}

	public void addPictures(List<Picture> picturesL, int index) {
		for (Picture p : picturesL) {
			p.setParent(this);
			if (index == -1) {
				pictures.add(p);
			} else {
				pictures.add(index++, p);
			}
		}

		gallery.setDirty(true);

		fireContentsChanged(this, 0, pictures.size() - 1);
	}

	private void addPictureInternal(Picture p) {
		addPictureInternal(-1, p);
	}

	private void addPictureInternal(int index, Picture p) {
		// handle EXIF
		if (GalleryRemote._().properties.getBooleanProperty(EXIF_AUTOROTATE)
				&& p.getExifData() != null) {
			ImageUtils.AngleFlip af = p.getExifData().getTargetOrientation();

			if (af != null) {
				p.setFlipped(af.flip);
				p.setAngle(af.angle);
				p.setSuppressServerAutoRotate(true);
			}
		}

		if (index == -1) {
			pictures.add(p);
		} else {
			pictures.add(index, p);
		}

		gallery.setDirty(true);
	}

	public void sortPicturesAlphabetically() {
		Collections.sort(pictures, new NaturalOrderComparator<Picture>());
		fireContentsChanged(this, 0, pictures.size() - 1);
	}

    /**
     * Sorts pictures based on the EXIF Date Created.  If no
     * date, then fall to the bottom.
     */
    public void sortPicturesCreated() {
        Collections.sort(pictures, new Comparator<Picture>() {

            public int compare(Picture p1, Picture p2) {
				Date d1 = null, d2 = null;

				if (p1.getExifData() != null) {
                	d1 = p1.getExifData().getCreationDate();
				}
				if (p2.getExifData() != null) {
                	d2 = p2.getExifData().getCreationDate();
				}

                if (d1 != null && d2 == null) {
                    return 1;
                }
                
                if (d1 == null && d2 != null) {
                    return -1;
                }
                
                if (d1 == null && d2 == null) {
                    return 0;
                }
                
                return d1.compareTo(d2);
            }
        });
        fireContentsChanged(this, 0, pictures.size() - 1);
    }

	public void sortSubAlbums() {
		if (children != null) {
			Collections.sort(children, new NaturalOrderComparator<Album>());
		}

		fireContentsChanged(this, 0, pictures.size() - 1);
	}

	/**
	 * Number of pictures in the album
	 * 
	 * @return Number of pictures in the album
	 */
	public int sizePictures() {
		return pictures.size();
	}

	/**
	 * Remove all the pictures
	 */
	public void clearPictures() {
		int l = pictures.size() - 1;

		pictures.clear();

		fireIntervalRemoved(this, 0, l);
	}

	/**
	 * Remove a picture
	 * 
	 * @param n item number of the picture to remove
	 */
	public void removePicture(int n) {
		pictures.remove(n);

		fireIntervalRemoved(this, n, n);
	}

	public void removePicture(Picture p) {
		removePicture(pictures.indexOf(p));
	}

	/**
	 * Remove pictures
	 * 
	 * @param indices list of indices of pictures to remove
	 */
	public void removePictures(int[] indices) {
		int min, max;
		min = max = indices[0];

		for (int i = indices.length - 1; i >= 0; i--) {
			pictures.remove(indices[i]);
			if (indices[i] > max) max = indices[i];
			if (indices[i] < min) min = indices[i];
		}

		fireIntervalRemoved(this, min, max);
	}

	/**
	 * Get a picture from the album
	 * 
	 * @param n index of the picture to retrieve
	 * @return The Picture
	 */
	public Picture getPicture(int n) {
		return (Picture) pictures.get(n);
	}


	/**
	 * Set a picture in the album
	 * 
	 * @param n index of the picture
	 * @param p The new picture
	 */
	public void setPicture(int n, Picture p) {
		pictures.set(n, p);

		fireContentsChanged(this, n, n);
	}

	/**
	 *  Get the list of files that contain the pictures
	 *
	 *@return    The fileList value
	 */
	/*public ArrayList getFileList() {
		ArrayList l = new ArrayList( pictures.size() );

		Enumeration e = pictures.elements();
		while ( e.hasMoreElements() ) {
			l.add( ( (Picture) e.nextElement() ).getSource() );
		}

		return l;
	}*/

	/**
	 * Sets the name attribute of the Album object
	 * 
	 * @param name The new name value
	 */
	public void setName(String name) {
		this.name = removeOffendingChars(name);
	}

	static final String offendingChars = "\\/*?\"\'&<>|.+# ()";

	static String removeOffendingChars(String in) {
		StringBuffer out = new StringBuffer();

		int l = in.length();
		for (int i = 0; i < l; i++) {
			char c = in.charAt(i);
			if (offendingChars.indexOf(c) == -1) {
				out.append(c);
			}
		}

		return out.toString();
	}

	/**
	 * Gets the name attribute of the Album object
	 * 
	 * @return The name value
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the title attribute of the Album object
	 * 
	 * @param title The new title
	 */
	public void setTitle(String title) {
		this.title = title;

		if (!suppressEvents) {
			gallery.nodeChanged(this);
		}
	}

	/**
	 * Gets the aggregated file size of all the pictures in the album
	 * 
	 * @return The file size (bytes)
	 */
	public long getPictureFileSize() {
		if (pictureFileSize == null) {
			pictureFileSize = getPictureFileSize((Picture[]) pictures.toArray(new Picture[0]));
		}

		return pictureFileSize;
	}

	/**
	 * Gets the aggregated file size of a list of pictures
	 * 
	 * @param pictures the list of Pictures
	 * @return The file size (bytes)
	 */
	public static long getPictureFileSize(Picture[] pictures) {
		return getObjectFileSize(pictures);
	}

	/**
	 * Gets the aggregated file size of a list of pictures Unsafe, the Objects
	 * will be cast to Pictures.
	 * 
	 * @param pictures the list of Pictures
	 * @return The file size (bytes)
	 */
	public static long getObjectFileSize(Object[] pictures) {
		long total = 0;

		for (Object picture : pictures) {
			total += ((Picture) picture).getFileSize();
		}

		return total;
	}

	public String toString() {
		StringBuffer ret = new StringBuffer();
		ret.append(indentHelper(""));
		ret.append(title);

		if (pictures.size() != 0) {
			ret.append(" (").append(pictures.size()).append(")");
		}
		
		// using canEdit here, since that's the only operation we perform
		// currently.  eventually, when we start changing things
		// on the server, permission support will get more ... interesting.
		if (!canEdit) {
			ret.append(" ").append(GRI18n.getString(MODULE, "ro"));
		}

		return ret.toString();
	}

	public boolean equals(Object o) {
		return (o != null
				&& o instanceof Album
				&& ((Album) o).getGallery() == getGallery()
				&& getName() != null
				&& ((Album) o).getName() != null
				&& ((Album) o).getName().equals(getName()));
	}
		

	/* -------------------------------------------------------------------------
	 *ListModel Implementation
	 */
	 
	/**
	 * Gets the size attribute of the Album object
	 * 
	 * @return The size value
	 */
	public int getSize() {
		return pictures.size();
	}

	/**
	 * Gets the elementAt attribute of the Album object
	 * 
	 * @param index Description of Parameter
	 * @return The elementAt value
	 */
	public Object getElementAt(int index) {
		return pictures.get(index);
	}

	/**
	 * Description of the Method
	 */
	/*public void setParent(Album a) {
		// take care of a Gallery bug...
		if (a == this) {
			Log.log(Log.LEVEL_ERROR, MODULE, "Gallery error: the album " + name +
					" is its own parent. You should delete it, the album database " +
					"is corrupted because of it.");

			a = (Album) getRoot();
		}

		super.setParent(a);
	}*/

	public ArrayList<String> getExtraFields() {
		return extraFields;
	}

	public void setExtraFieldsString(String extraFieldsString) {
		if (extraFieldsString != null && extraFieldsString.length() > 0) {
			extraFields = new ArrayList<String>();
			StringTokenizer st = new StringTokenizer(extraFieldsString, ",");
			List noShow = null;
			if (getGallery().getGalleryVersion() == 1) {
				noShow = extraFieldsNoShow;
			} else {
				noShow = extraFieldsNoShowG2;
			}
			while (st.hasMoreTokens()) {
				String name = st.nextToken();

				if (!noShow.contains(name) && !extraFields.contains(name)) {
					extraFields.add(name);
				}
			}
		} else {
			extraFields = null;
		}
	}

	public String getSummary() {
		return summary;
	}

	public void setSummary(String summary) {
		this.summary = summary;
	}

	public ArrayList<Picture> getPicturesList() {
		return pictures;
	}

	ArrayList<Picture> getUploadablePicturesList() {
		ArrayList<Picture> uploadable = new ArrayList<Picture>();

		for (Picture picture : pictures) {
			if (!picture.isOnline()) {
				uploadable.add(picture);
			}
		}

		return uploadable;
	}

	void setPicturesList(ArrayList<Picture> pictures) {
		this.pictures = pictures;

		for (Picture picture : pictures) {
			picture.setParent(this);
		}

		fireContentsChanged(this, 0, pictures.size() - 1);
	}

	public int getAlbumDepth() throws IllegalArgumentException {
		if (albumDepth == null) {
			albumDepth = depthHelper(0);
		}

		return albumDepth;
	}

	int depthHelper(int depth) throws IllegalArgumentException {
		if (getParentAlbum() == this || depth > 20) {
			throw new IllegalArgumentException("Circular containment hierarchy. Gallery corrupted!");
		}

		if (getParentAlbum() != null) {
			return getParentAlbum().depthHelper(depth + 1);
		} else {
			return depth;
		}
	}

	public static final String INDENT_QUANTUM = "     ";

	String indentHelper(String indent) {
		if (getParentAlbum() != null) {
			return getParentAlbum().indentHelper(indent + INDENT_QUANTUM);
		} else {
			return indent;
		}
	}

	/*void notifyListeners() {
		if (!suppressEvents) {
			fireContentsChanged(this, 0, pictures.size());
			if (gallery != null) {
				gallery.albumChanged(this);
			}
		}
	}*/

	/*public ArrayList getSubAlbums() {
		return subAlbums;
	}*/

	/*public void addSubAlbum(Album a) {
		subAlbums.add(a);

		if (!suppressEvents) {
			//gallery.fireTreeNodesInserted(this, gallery.getObjectArrayForAlbum(this),
			//		new int[] { subAlbums.indexOf(a) },
			//		new Object[] { a });
			gallery.fireTreeStructureChanged(gallery, gallery.getPathForAlbum(this));
		}
	}

	public void removeSubAlbum(Album a) {
		int index = subAlbums.indexOf(a);
		if (index != -1) {
			subAlbums.remove(a);

			if (!suppressEvents) {
				//gallery.fireTreeNodesRemoved(this, gallery.getObjectArrayForAlbum(this),
				//		new int[] { index },
				//		new Object[] { a });
				gallery.fireTreeStructureChanged(gallery, gallery.getPathForAlbum(this));
				//gallery.fireTreeStructureChanged(this, new TreePath(gallery.root));
			}
		}
	}*/

	public Boolean getOverrideResize() {
		return overrideResize;
	}

	public void setOverrideResize(Boolean overrideResize) {
		this.overrideResize = overrideResize;
	}

	public Boolean getOverrideResizeDefault() {
		return overrideResizeDefault;
	}

	public void setOverrideResizeDefault(Boolean overrideResizeDefault) {
		this.overrideResizeDefault = overrideResizeDefault;
	}

	public int getOverrideResizeDimension() {
		return overrideResizeDimension;
	}

	public void setOverrideResizeDimension(int overrideResizeDimension) {
		this.overrideResizeDimension = overrideResizeDimension;
	}

	public Boolean getOverrideAddToBeginning() {
		return overrideAddToBeginning;
	}

	public void setOverrideAddToBeginning(Boolean overrideAddToBeginning) {
		this.overrideAddToBeginning = overrideAddToBeginning;
	}

	public boolean getResize() {
		if (overrideResize != null) {
			return overrideResize;
		} else {
			return GalleryRemote._().properties.getBooleanProperty(RESIZE_BEFORE_UPLOAD);
		}
	}

	public boolean getResizeDefault() {
		if (overrideResizeDefault != null) {
			return overrideResizeDefault;
		} else {
			return GalleryRemote._().properties.getIntDimensionProperty(RESIZE_TO) == 0;
		}
	}

	public int getResizeDimension() {
		if (overrideResizeDimension != -1) {
			return overrideResizeDimension;
		} else {
			return GalleryRemote._().properties.getIntDimensionProperty(RESIZE_TO);
		}
	}

	public boolean getAddToBeginning() {
		if (overrideAddToBeginning != null) {
			return overrideAddToBeginning;
		} else {
			// todo
			return false;
		}
	}

	public boolean isHasFetchedImages() {
		return hasFetchedImages;
	}

	public void setHasFetchedImages(boolean hasFetchedImages) {
		this.hasFetchedImages = hasFetchedImages;
	}

	public void setSuppressEvents(boolean suppressEvents) {
		this.suppressEvents = suppressEvents;
	}

	/*
	 *******************  LIST HANDLING (FOR PICTURES)  ***************
	 */

	public void addListDataListener(ListDataListener l) {
		if (listenerList == null) listenerList = new EventListenerList();
		listenerList.add(ListDataListener.class, l);
	}


	public void removeListDataListener(ListDataListener l) {
		if (listenerList == null) listenerList = new EventListenerList();
		listenerList.remove(ListDataListener.class, l);
	}

	public void fireContentsChanged(Object source, int index0, int index1) {
		if (listenerList == null) listenerList = new EventListenerList();
		Object[] listeners = listenerList.getListenerList();
		ListDataEvent e = null;

		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ListDataListener.class) {
				if (e == null) {
					e = new ListDataEvent(source, ListDataEvent.CONTENTS_CHANGED, index0, index1);
				}
				((ListDataListener) listeners[i + 1]).contentsChanged(e);
			}
		}
	}

	public void fireIntervalAdded(Object source, int index0, int index1) {
		if (listenerList == null) listenerList = new EventListenerList();
		Object[] listeners = listenerList.getListenerList();
		ListDataEvent e = null;

		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ListDataListener.class) {
				if (e == null) {
					e = new ListDataEvent(source, ListDataEvent.INTERVAL_ADDED, index0, index1);
				}
				((ListDataListener) listeners[i + 1]).intervalAdded(e);
			}
		}
	}


	public void fireIntervalRemoved(Object source, int index0, int index1) {
		if (listenerList == null) listenerList = new EventListenerList();
		Object[] listeners = listenerList.getListenerList();
		ListDataEvent e = null;

		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ListDataListener.class) {
				if (e == null) {
					e = new ListDataEvent(source, ListDataEvent.INTERVAL_REMOVED, index0, index1);
				}
				((ListDataListener) listeners[i + 1]).intervalRemoved(e);
			}
		}
	}

	transient protected EventListenerList listenerList = new EventListenerList();
} public class Gallery extends DefaultTreeModel implements Serializable, PreferenceNames {
	public static final String MODULE = "Gallery";

	String urlString = null;
	String username;
	String password;
	String alias;
	String key;
	String userAgent = "Gallery Remote " + GalleryRemote._().properties.getProperty("version");
	boolean autoLoadOnStartup = false;

	transient GalleryComm comm = null;

	transient StatusUpdate su;
	transient private int prefsIndex;
	transient private Boolean ambiguousUrl;
	transient private boolean blockWrites = false;
	transient public boolean cookieLogin = false;
	transient public int galleryVersion = 1;
	transient public int forceGalleryVersion = 0;
	transient public String forceProtocolEncoding = null;
	transient public int resizeJpegQuality = -1;
	transient public boolean dirty = false;
	transient public int type = TYPE_STANDALONE;

	public static final int TYPE_STANDALONE = 99;
	public static final int TYPE_APPLET = 99;

	public static final int TOSTRING_MAXLEN = 40;
	public String authToken;

	public Gallery(StatusUpdate su) {
		super(null);
		this.su = su;

		// make sure to update flat album list when tree is changed
		addTreeModelListener(new TreeModelListener() {
			public void treeNodesChanged(TreeModelEvent e) {
				flatAlbumList = null;
			}

			public void treeNodesInserted(TreeModelEvent e) {
				treeNodesChanged(e);
			}

			public void treeNodesRemoved(TreeModelEvent e) {
				treeNodesChanged(e);
			}

			public void treeStructureChanged(TreeModelEvent e) {
				treeNodesChanged(e);
			}
		});

		// when loading from prefs, galleries not yet created. No matter: in that case, the
		// prefsIndex is forced.
		if (GalleryRemote._().getMainFrame() != null) {
			prefsIndex = GalleryRemote._().getCore().getGalleries().getSize();
		}
	}

	/*
	* **** Gallery online management ****
	*/

	public void doUploadFiles(StatusUpdate su) {
		GalleryComm comm = getComm(su);

		if (comm != null) {
			comm.uploadFiles(su, true);
		} else {
			// don't worry about it, an error message is displayed somewhere else.
		}
	}

	public void doFetchAlbums(StatusUpdate su) {
		doFetchAlbums(su, true);
	}

	public void doFetchAlbums(StatusUpdate su, boolean async) {
		GalleryComm comm = getComm(su);

		if (comm != null) {
			comm.fetchAlbums(su, async);
		} else {
			// don't worry about it, an error message is displayed somewhere else.
		}
	}

	public void doNewAlbum(Album a, StatusUpdate su) {
		Log.log(Log.LEVEL_INFO, MODULE, "Creating new album " + a.toString());

		// create album synchronously
		getComm(su).newAlbum(su, a, false);

		// refresh album list asynchronously
        //fetchAlbums(su);

//		if (!newAlbumName.equals(a.getName())) {
//			//Log.log(Log.LEVEL_INFO, MODULE, "Album name probably conflicted on the server, need to reload album list");
//			//getComm(su).fetchAlbums(su, false);
//			a.setName(newAlbumName);
//		}

		//addAlbum(a);

		//return newAlbumName;
	}

	public void incrementViewCount(Picture p, StatusUpdate su) {
		if (getComm().hasCapability(GalleryCommCapabilities.CAPA_INCREMENT_VIEW_COUNT)) {
			Log.log(Log.LEVEL_INFO, MODULE, "Incrementing viewCount on " + p.toString());

			getComm(su).incrementViewCount(su, p);
		}
	}

	public void logOut() {
		//if (comm != null) {
		//	comm.logOut();
		//}
		comm = null;

		setRoot(null);

		dirty = false;
	}

	/*
	* **** Gallery contents handling ****
	*/

	public File getGalleryDefaultFile() {
		StringBuffer defaultFilePath = new StringBuffer();

		defaultFilePath.append(System.getProperty("user.home"));
		defaultFilePath.append(File.separator);
		defaultFilePath.append(".GalleryRemote");
		defaultFilePath.append(File.separator);
		defaultFilePath.append("backup.");
		defaultFilePath.append(getPrefsIndex());
		defaultFilePath.append(".grg");

		// Define which file is used to store the current state if
		// the user does not provide a specific file.
		return (new File(defaultFilePath.toString()));
	}

	public ArrayList<Picture> getAllPictures() {
		return getAllPictures(false);
	}

	public ArrayList<Picture> getAllUploadablePictures() {
		return getAllPictures(true);
	}

	public ArrayList<Picture> getAllPictures(boolean onlyUploadable) {
		ArrayList<Picture> pictures = new ArrayList<Picture>();
		ArrayList<Album> albumList = getFlatAlbumList();

		if (albumList != null) {
			for (Album a : albumList) {
				if (onlyUploadable) {
					pictures.addAll(a.getUploadablePicturesList());
				} else {
					pictures.addAll(a.getPicturesList());
				}
			}
		}

		return pictures;
	}

	/**
	 * Delete all of the pictures from the current gallery without
	 * affecting the list of albums that are loaded.  This is used
	 * by the "New" function in the UI.
	 */
	public void deleteAllPictures() {
		ArrayList albumList = getFlatAlbumList();

		if (albumList != null) {
			Iterator i = albumList.iterator();
			while (i.hasNext()) {
				Album a = (Album) i.next();
				a.clearPictures();
			}
		}
	}

	public int countAllPictures() {
		int c = 0;
		ArrayList albumList = getFlatAlbumList();

		if (albumList != null) {
			Iterator i = albumList.iterator();
			while (i.hasNext()) {
				Album a = (Album) i.next();

				c += a.getSize();
			}
		}

		return c;
	}

	public boolean hasPictures() {
		ArrayList albumList = getFlatAlbumList();

		if (albumList != null) {
			Iterator i = albumList.iterator();
			while (i.hasNext()) {
				Album a = (Album) i.next();
				if (a.getSize() > 0) {
					return true;
				}
			}
		}

		return false;
	}

	/*
	* **** Gallery URL management ****
	*/

	public static String reformatUrlString(String urlString, boolean trailingSlash) {
		if (urlString == null) {
			throw new IllegalArgumentException("urlString must not be null");
		}

		if (trailingSlash && !urlString.endsWith("/")) {
			urlString += "/";
		}

		if (!urlString.startsWith("http://") && !urlString.startsWith("https://")) {
			urlString = "http://" + urlString;
		}

		return urlString;
	}

	/* Standalone URL */

	public void setUrlString(String urlString) {
		if (urlString == null) {
			this.urlString = null;
			return;
		}

		this.urlString = reformatUrlString(urlString, true);

		if (!blockWrites && this.urlString != null) {
			GalleryRemote._().properties.setProperty(GURL + prefsIndex, this.urlString);
		}
	}

	public String getUrlString() {
		if (urlString != null) {
			return urlString;
		} else {
			return "http://example.com/gallery";
		}
	}

	/* Applet URL */

	public void setApUrlString(String urlString) {
		this.urlString = urlString;
	}

	/* Desired User-Agent */

	public void setUserAgent(String userAgent) {
	        this.userAgent = userAgent;
	}

	public String getUserAgent() {
	        return userAgent;
	}

	/* Generic */

	public URL getUrl() {
		try {
			return new URL(urlString);
		} catch (MalformedURLException e) {
			Log.log(Log.LEVEL_ERROR, MODULE, "Malformed URL.");
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			JOptionPane.showMessageDialog((Component) su, "Malformed URL (" + e.getMessage() + ")",
					"Error", JOptionPane.ERROR_MESSAGE);
			return null;
		}
	}

	/*
	* **** Gallery properties management ****
	*/

	public void setUsername(String username) {
		if (/*username != null && username.length() > 0
		&&*/ !username.equals(this.username)) {

			this.username = username;

			logOut();

			if (!blockWrites) {
				GalleryRemote._().properties.setProperty(USERNAME + prefsIndex, username);
			}
		}
	}

	public void setPassword(String password) {
		//Log.log(Log.TRACE, MODULE, "setpassword: " + password);
		if (/*password != null && password.length() > 0
		&&*/ !password.equals(this.password)) {

			this.password = password;

			logOut();

			if (!blockWrites) {
				if (GalleryRemote._().properties.getBooleanProperty(SAVE_PASSWORDS)) {
					GalleryRemote._().properties.setBase64Property(PASSWORD + prefsIndex, password);
				} else {
					GalleryRemote._().properties.setProperty(PASSWORD + prefsIndex, null);
				}
			}
		}
	}

	public void setAlias(String alias) {
		this.alias = alias;

		if (!blockWrites) {
			GalleryRemote._().properties.setProperty(ALIAS + prefsIndex, alias);
		}
	}

	public void setKey(String key) {
		if (key == null) {
			this.key = null;
		} else if (!key.equals(this.key)) {
			this.key = key;

			logOut();

			if (!blockWrites) {
				GalleryRemote._().properties.setProperty(KEY + prefsIndex, key);
			}
		}
	}

	public String getUsername() {
		return username;
	}

	public String getPassword() {
		return password;
	}

	public String getAlias() {
		return alias;
	}

	public String getKey() {
		return key;
	}

	public static Gallery readFromProperties(GalleryProperties p, int prefsIndex, StatusUpdate su) {
		return readFromProperties(p, prefsIndex, su, true);
	}

	public static Gallery readFromProperties(GalleryProperties p, int prefsIndex, StatusUpdate su, boolean mustHaveUsername) {
		String url = p.getProperty(GURL + prefsIndex);
		String username = p.getProperty(USERNAME + prefsIndex, true);

		if (mustHaveUsername && username == null) {
			return null;
		}

		String password = null;
		try {
			password = p.getBase64Property(PASSWORD + prefsIndex);
		} catch (NumberFormatException e) {
		}

		Log.log(Log.LEVEL_INFO, MODULE, "Loaded saved URL " + prefsIndex + ": " + url + " (" + username + "/******)");

		Gallery g = new Gallery(su);
		if (GalleryRemote._().getCore() instanceof TreeModelListener) {
			g.addTreeModelListener((TreeModelListener) GalleryRemote._().getCore());
		}

		g.setBlockWrites(true);
		g.username = username;
		g.password = password;
		g.setUrlString(url);

		g.setAlias(p.getProperty(ALIAS + prefsIndex));
		g.setKey(p.getProperty(KEY + prefsIndex));

		g.forceGalleryVersion = p.getIntProperty(FORCE_GALLERY_VERSION + prefsIndex, 0);
		g.forceProtocolEncoding = p.getProperty(FORCE_PROTOCOL_ENCODING + prefsIndex);
		g.resizeJpegQuality = p.getIntProperty(RESIZE_JPEG_QUALITY + prefsIndex, -1);
		g.autoLoadOnStartup = p.getBooleanProperty(AUTO_LOAD_ON_STARTUP + prefsIndex, false);

		g.setPrefsIndex(prefsIndex);

		g.setBlockWrites(false);
		return g;
	}

	public void writeToProperties(PropertiesFile p) {
		Log.log(Log.LEVEL_TRACE, MODULE, "Writing to properties: " + toString());

		p.setProperty(GURL + prefsIndex, urlString);
		p.setProperty(USERNAME + prefsIndex, username);
		if (getPassword() != null && p.getBooleanProperty(SAVE_PASSWORDS)) {
			p.setBase64Property(PASSWORD + prefsIndex, password);
		} else {
			p.setProperty(PASSWORD + prefsIndex, null);
		}
		if (getAlias() != null && getAlias().length() > 0) {
			p.setProperty(ALIAS + prefsIndex, getAlias());
		} else {
			p.setProperty(ALIAS + prefsIndex, null);
		}
		if (getKey() != null && getKey().length() > 0) {
			p.setProperty(KEY + prefsIndex, getKey());
		} else {
			p.setProperty(KEY + prefsIndex, null);
		}

		p.setBooleanProperty(AUTO_LOAD_ON_STARTUP + prefsIndex, autoLoadOnStartup);
	}

	public static void removeFromProperties(PropertiesFile p, int n) {
		Log.log(Log.LEVEL_TRACE, MODULE, "Removed from properties: " + n);

		p.setProperty(GURL + n, null);
		p.setProperty(USERNAME + n, null);
		p.setProperty(PASSWORD + n, null);
		p.setProperty(ALIAS + n, null);
		p.setProperty(KEY + n, null);
		p.setProperty(FORCE_GALLERY_VERSION + n, null);
		p.setProperty(FORCE_PROTOCOL_ENCODING + n, null);
		p.setProperty(RESIZE_JPEG_QUALITY + n, null);
		p.setProperty(AUTO_LOAD_ON_STARTUP + n, null);
	}

	public void setPrefsIndex(int prefsIndex) {
		this.prefsIndex = prefsIndex;
	}

	protected int getPrefsIndex() {
		return (this.prefsIndex);
	}

	public String toString() {
		return toString(true);
	}

	public String toString(boolean disambiguate) {
		if (alias != null) {
			return alias;
		}

		String tmp = null;

		tmp = urlString;

		if (tmp == null) {
			tmp = "http://";
		}

		if (disambiguate && isAmbiguousUrl()) {
			if (username == null || username.length() == 0) {
				tmp += "[username not set]";
			} else {
				tmp += " [" + username + "]";
			}
		}

		if (tmp.length() > TOSTRING_MAXLEN) {
			tmp = tmp.substring(0, TOSTRING_MAXLEN) + "...";
		}

		return tmp;
	}

	public boolean isAmbiguousUrl() {
		if (ambiguousUrl == null) {
			ListModel galleries = GalleryRemote._().getCore().getGalleries();
			String myUrl = toString(false);

			for (int i = 0; i < galleries.getSize(); i++) {
				Gallery g = (Gallery) galleries.getElementAt(i);

				if (g != this && myUrl.equals(g.toString(false))) {
					ambiguousUrl = new Boolean(true);
					break;
				}
			}

			if (ambiguousUrl == null) {
				ambiguousUrl = new Boolean(false);
			}
		}

		return ambiguousUrl.booleanValue();
	}

	public static void uncacheAmbiguousUrl() {
		ListModel galleries = GalleryRemote._().getCore().getGalleries();

		for (int i = 0; i < galleries.getSize(); i++) {
			Gallery g = (Gallery) galleries.getElementAt(i);

			g.ambiguousUrl = null;
		}
	}

	/*
	* Miscellaneous
	*/

	/**
	 * Lazy instantiation for the GalleryComm instance.
	 */
	public GalleryComm getComm(StatusUpdate su) {
		if (comm == null && urlString != null) {
			URL url = getUrl();
			if (url != null) {
				comm = GalleryComm.getCommInstance(su, this);

				if (comm == null) {
					Log.log(Log.LEVEL_ERROR, MODULE, "No protocol implementation found");
					su.error(GRI18n.getString(MODULE, "galleryNotFound", new Object[] {urlString}));
				}
			}
		}

		return comm;
	}

	public GalleryComm getComm() {
		return comm;
	}

	public boolean hasComm() {
		return comm != null;
	}

	public Album getAlbumByName(String name) {
		ArrayList<Album> albumList = getFlatAlbumList();
		if (albumList == null || name == null) {
			return null;
		}

		for (Album a : albumList) {
			if (name.equals(a.getName())) {
				return a;
			}
		}

		return null;
	}

	public void setBlockWrites(boolean blockWrites) {
		this.blockWrites = blockWrites;
	}

	ArrayList<Album> flatAlbumList = null;
	public ArrayList<Album> getFlatAlbumList() {
		if (flatAlbumList == null) {
			if (getRoot() != null) {
				flatAlbumList = Collections.list(new TreeEnumeration((TreeNode) getRoot()));
			}
		}

		return flatAlbumList;
	}

	public Album createRootAlbum() {
		if (getRoot() != null) {
			throw new IllegalStateException("Root album already exists");
		}

		Album album = new Album(this);
		album.setTitle(GRI18n.getString("Common", "rootAlbmTitle"));
		album.setName("root.album");
		setRoot(album);

		return (Album) getRoot();
	}

	public Album getRootAlbum() {
		return (Album) getRoot();
	}

	public int getGalleryVersion() {
		if (forceGalleryVersion != 0) {
			// override
			return forceGalleryVersion;
		}

		return galleryVersion;
	}

	public void setGalleryVersion(int galleryVersion) {
		this.galleryVersion = galleryVersion;
	}

	public int getResizeJpegQuality() {
		return resizeJpegQuality;
	}

	public boolean isDirty() {
		return dirty;
	}

	public void setDirty(boolean dirty) {
		this.dirty = dirty;
	}

	public boolean isAutoLoadOnStartup() {
		return autoLoadOnStartup;
	}

	public void setAutoLoadOnStartup(boolean autoLoadOnStartup) {
		this.autoLoadOnStartup = autoLoadOnStartup;
		
		if (!blockWrites) {
			GalleryRemote._().properties.setBooleanProperty(AUTO_LOAD_ON_STARTUP + prefsIndex, autoLoadOnStartup);
		}
	}

	public int getType() {
		return type;
	}

	public void setType(int type) {
		this.type = type;
	}

	class TreeEnumeration implements Enumeration {
		protected TreeNode root;
		protected Enumeration children;
		protected Enumeration subtree;
		boolean rootSent = false;

		public TreeEnumeration(TreeNode rootNode) {
			super();
			root = rootNode;
			children = root.children();
			subtree = DefaultMutableTreeNode.EMPTY_ENUMERATION;
		}

		public boolean hasMoreElements() {
			return !rootSent || subtree.hasMoreElements() || children.hasMoreElements();
		}

		public Object nextElement() {
			Object retval = null;

			if (!rootSent) {
				retval = root;
				rootSent = true;
			} else if (subtree.hasMoreElements()) {
				retval = subtree.nextElement();
			} else if (children.hasMoreElements()) {
				subtree = new TreeEnumeration(
						(TreeNode)children.nextElement());
				retval = subtree.nextElement();
			}

			return retval;
		}
	}

	public Album newAlbum() {
		return new Album(this);
	}
} public class Picture extends GalleryItem implements Serializable, PreferenceNames, Cloneable {
	public static final String MODULE = "Picture";

	File source = null;

	HashMap<String,String> extraFields;
	ArrayList<ResizedDerivative> resizedDerivatives;

	boolean hidden;

	int angle = 0;
	boolean flipped = false;
	boolean suppressServerAutoRotate = false;

	boolean online = false;
	URL urlFull = null;
	Dimension sizeFull = null;
	URL urlResized = null;
	Dimension sizeResized = null;
	URL urlThumbnail = null;
	Dimension sizeThumbnail = null;
	Rectangle cropTo = null;

	Album albumOnServer = null;
	int indexOnServer = -1;

	transient double fileSize = 0;
	transient int indexCache = -1;
	transient Dimension dimension = null;
	transient ExifData exif = null;
	transient String forceExtension = null;
	transient String uniqueId = null;
	transient String itemId = null;
	transient String name = null;

	/**
	 * Constructor for the Picture object
	 */
	public Picture(Gallery gallery) {
		super(gallery);

		setAllowsChildren(false);
	}


	/**
	 * Constructor for the Picture object
	 * 
	 * @param source File the Picture is based on
	 */
	public Picture(Gallery gallery, File source) {
		this(gallery);

		setSource(source);
	}

	public Object clone() {
		Picture newPicture = (Picture) super.clone();

		newPicture.source = source;

		newPicture.extraFields = extraFields;
		newPicture.resizedDerivatives = resizedDerivatives;

		newPicture.hidden = hidden;

		newPicture.angle = angle;
		newPicture.flipped = flipped;
		newPicture.suppressServerAutoRotate = suppressServerAutoRotate;

		newPicture.online = online;
		newPicture.urlFull = urlFull;
		newPicture.sizeFull = sizeFull;
		newPicture.urlResized = urlResized;
		newPicture.sizeResized = sizeResized;
		newPicture.urlThumbnail = urlThumbnail;
		newPicture.sizeThumbnail = sizeThumbnail;
		newPicture.cropTo = cropTo;
		
		newPicture.albumOnServer = albumOnServer;
		newPicture.indexOnServer = indexOnServer;

		newPicture.fileSize = fileSize;
		newPicture.escapedDescription = escapedDescription;
		newPicture.indexCache = indexCache;

		return newPicture;
	}


	/**
	 * Sets the source file the Picture is based on
	 * 
	 * @param source The new file
	 */
	public void setSource(File source) {
		this.source = source;

		if (GalleryRemote._().properties.getAutoCaptions() == AUTO_CAPTIONS_FILENAME) {
			String filename = source.getName();

			if (GalleryRemote._().properties.getBooleanProperty(CAPTION_STRIP_EXTENSION)) {
				int i = filename.lastIndexOf(".");

				if (i != -1) {
					filename = filename.substring(0, i);
				}
			}

			setDescription(filename);
		} else if (GalleryRemote._().properties.getAutoCaptions() == AUTO_CAPTIONS_COMMENT
				&& getExifData() != null && getExifData().getCaption() != null) {
			setDescription(getExifData().getCaption());
		} else if (GalleryRemote._().properties.getAutoCaptions() == AUTO_CAPTIONS_DATE
				&& getExifData() != null && getExifData().getCreationDate() != null) {
			setDescription(getExifData().getCreationDate().toString());
		}

		fileSize = 0;
	}


	/**
	 * Gets the source file the Picture is based on
	 * 
	 * @return The source value
	 */
	public File getSource() {
		if (online) {
			throw new RuntimeException("Can't get source for an online file!");
		}

		return source;
	}

	/**
	 * Gets the fource file of the picture, prepared for upload.
	 * Called by GalleryComm to upload the picture.
	 * 
	 * @return The source value
	 */
	public File getUploadSource() {
		boolean useLossyCrop = false;
		File picture = getSource();
		Album album = getParentAlbum();

		// crop
		if (cropTo != null) {
			try {
				picture = ImageUtils.losslessCrop(picture.getPath(), cropTo);
			} catch (UnsupportedOperationException e) {
				Log.log(Log.LEVEL_ERROR, MODULE, "Couldn't use ImageUtils to losslessly crop the image, will try lossy");
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
				useLossyCrop = true;
			}
		}

		int resizeJpegQuality = album.getGallery().getResizeJpegQuality();

		// resize
		if (album.getResize()) {
			int i = album.getResizeDimension();

			if (i <= 0) {
				int l = album.getServerAutoResize();

				if (l != 0) {
					i = l;
				} else {
					// server can't tell us how to resize, try default
					i = GalleryRemote._().properties.getIntDimensionProperty(RESIZE_TO_DEFAULT);
				}
			}

			if (i != -1 || useLossyCrop) {
				try {
					picture = ImageUtils.resize(picture.getPath(), new Dimension(i, i), useLossyCrop?cropTo:null, resizeJpegQuality);
				} catch (UnsupportedOperationException e) {
					Log.log(Log.LEVEL_ERROR, MODULE, "Couldn't use ImageUtils to resize the image, it will be uploaded at the original size");
					Log.logException(Log.LEVEL_ERROR, MODULE, e);
				}
			}
		} else if (useLossyCrop) {
			picture = ImageUtils.resize(picture.getPath(), null, useLossyCrop?cropTo:null, resizeJpegQuality);
		}

		// rotate
		if (angle != 0 || flipped) {
			try {
				picture = ImageUtils.rotate(picture.getPath(), angle, flipped, true);
			} catch (UnsupportedOperationException e) {
				Log.log(Log.LEVEL_ERROR, MODULE, "Couldn't use jpegtran to rotate the image, it will be uploaded unrotated");
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
			}
		}

		return picture;
	}

	/**
	 * Gets the size of the file
	 * 
	 * @return The size value
	 */
	public double getFileSize() {
		if (fileSize == 0 && source != null && source.exists()) {
			fileSize = source.length();
		}

		return fileSize;
	}

	public void setFileSize(double fileSize) {
		if (!online) {
			throw new RuntimeException("Can't set the size of a local image");
		}

		this.fileSize = fileSize;
	}


	/**
	 * Gets the album this Picture is inside of
	 * 
	 * @return The album
	 */
	/*public Album getAlbum() {
		return album;
	}*/

	public String toString() {
		if (online) {
			return getName();
		} else {
			return source.getName();
		}
	}

	public int hashCode() {
		String path;

		if (online) {
			path = safeGetUrlFull().toString();
		} else {
			path = source.getName();
		}

		return path.hashCode();
	}

	// Hacks to allow Album to inherit from Picture and AbstractListModel
	public int getSize() {
		return 0;
	}

	public Object getElementAt(int index) {
		return null;
	}

	public void rotateRight() {
		angle = (angle + 1) % 4;
	}

	public void rotateLeft() {
		angle = (angle + 3) % 4;
	}

	public void flip() {
		flipped = !flipped;
	}

	public int getAngle() {
		return angle;
	}

	public void setAngle(int angle) {
		this.angle = angle;
	}

	public boolean isFlipped() {
		return flipped;
	}

	public void setFlipped(boolean flipped) {
		this.flipped = flipped;
	}

	public String getExtraField(String name) {
		if (extraFields == null) {
			return null;
		}

		return extraFields.get(name);
	}

	public String getExtraFieldsString(boolean includeKey) {
		if (extraFields == null) {
			return "";
		}

		StringBuffer sb = new StringBuffer();
		String sep = System.getProperty("line.separator");

		for (String name : getParentAlbum().getExtraFields()) {
			String value = extraFields.get(name);

			if (value != null) {
				if (includeKey) {
					sb.append(name).append(": ");
				}
				sb.append(value).append(sep);
			}
		}

		return sb.toString();
	}

	public void setExtraField(String name, String value) {
		if (extraFields == null) {
			extraFields = new HashMap<String,String>();
		}

		extraFields.put(name, value);
	}

	public void removeExtraField(String name) {
		if (extraFields == null) {
			extraFields = new HashMap<String,String>();
		}

		extraFields.remove(name);
	}

	public HashMap<String,String> getExtraFieldsMap() {
		return extraFields;
	}

	public void setSuppressServerAutoRotate(boolean suppressServerAutoRotate) {
		this.suppressServerAutoRotate = suppressServerAutoRotate;
	}

	public boolean isOnline() {
		return online;
	}

	public void setOnline(boolean online) {
		this.online = online;
	}

	public URL getUrlFull() {
		if (!online) {
			throw new RuntimeException("Can't get URL for a local file!");
		}

		return urlFull;
	}

	public URL safeGetUrlFull() {
		if (!online) {
			throw new RuntimeException("Can't get URL for a local file!");
		}

		if (urlFull != null) {
			return urlFull;
		} else if (urlResized != null) {
			return urlResized;
		} else {
			throw new RuntimeException("Neither full nor resized URL!");
		}
	}

	public void setUrlFull(URL urlFull) {
		this.urlFull = urlFull;
	}

	public Dimension getSizeFull() {
		if (!online) {
			throw new RuntimeException("Can't get dimension for a local file!");
		}

		return sizeFull;
	}

	public Dimension safeGetSizeFull() {
		if (!online) {
			throw new RuntimeException("Can't get dimension for a local file!");
		}

		if (sizeFull != null) {
			return sizeFull;
		} else if (sizeResized != null) {
			return sizeResized;
		} else {
			throw new RuntimeException("Neither full nor resized size!");
		}
	}

	public void setSizeFull(Dimension sizeFull) {
		this.sizeFull = sizeFull;
	}

	public URL getUrlResized() {
		if (!online) {
			throw new RuntimeException("Can't get URL for a local file!");
		}

		return urlResized;
	}

	public void setUrlResized(URL urlResized) {
		this.urlResized = urlResized;
	}

	public Dimension getSizeResized() {
		if (!online) {
			throw new RuntimeException("Can't get dimension for a local file!");
		}

		return sizeResized;
	}

	public void setSizeResized(Dimension sizeResized) {
		this.sizeResized = sizeResized;

		// also add the new-style derivative info
		addResizedDerivative(getUrlResized(), sizeResized);
	}

	public URL getUrlThumbnail() {
		if (!online) {
			throw new RuntimeException("Can't get URL for a local file!");
		}

		return urlThumbnail;
	}

	public void setUrlThumbnail(URL urlThumbnail) {
		this.urlThumbnail = urlThumbnail;
	}

	public Dimension getSizeThumbnail() {
		if (!online) {
			throw new RuntimeException("Can't get dimension for a local file!");
		}

		return sizeThumbnail;
	}

	public void setSizeThumbnail(Dimension sizeThumbnail) {
		this.sizeThumbnail = sizeThumbnail;
	}

	public String getName() {
		if (name == null) {
			if (isOnline()) {
				String path = safeGetUrlFull().getPath();

				int i = path.lastIndexOf('/');

				if (i != -1) {
					path = path.substring(i + 1);
				}

				i = path.lastIndexOf('.');
				if (i != -1) {
					path = path.substring(0, i);
				}

				name = path;
			} else {
				name = getSource().getName();
			}
		}

		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Album getAlbumOnServer() {
		if (!online) {
			throw new RuntimeException("Can't get Album on server for a local file!");
		}

		return albumOnServer;
	}

	public void setAlbumOnServer(Album albumOnServer) {
		this.albumOnServer = albumOnServer;
	}

	public int getIndexOnServer() {
		if (!online) {
			throw new RuntimeException("Can't get Index on server for a local file!");
		}

		return indexOnServer;
	}

	public int getIndex() {
		Album album = getParentAlbum();
		if (indexCache == -1
				|| indexCache >= album.pictures.size()
				|| album.pictures.get(indexCache) != this) {
			return album.pictures.indexOf(this);
		} else {
			return indexCache;
		}
	}

	public void setIndexOnServer(int indexOnServer) {
		this.indexOnServer = indexOnServer;
	}

	public boolean isHidden() {
		return hidden;
	}

	public void setHidden(boolean hidden) {
		this.hidden = hidden;
	}

	public Rectangle getCropTo() {
		return cropTo;
	}

	public void setCropTo(Rectangle cropTo) {
		Log.log(Log.LEVEL_TRACE, MODULE, "setCropTo " + cropTo);

		if (cropTo != null) {
			// make very sure the crop dimensions make sense
			Dimension d = getDimension();
			if (cropTo.x < 0) {
				cropTo.x = 0;
			}
			if (cropTo.y < 0) {
				cropTo.y = 0;
			}
			if (cropTo.width + cropTo.x > d.width) {
				cropTo.width = d.width - cropTo.x;
			}
			if (cropTo.height + cropTo.y > d.height) {
				cropTo.height = d.height - cropTo.y;
			}
		}

		this.cropTo = cropTo;
	}

	public Dimension getDimension() {
		if (dimension == null) {
			dimension = ImageUtils.getPictureDimension(this);
		}

		return dimension;
	}

	public ExifData getExifData() {
		if (exif == null && ImageUtils.isExifAvailable()) {
			exif = ImageUtils.getExifData(source.getPath());
		}

		return exif;
	}

	public String getForceExtension() {
		return forceExtension;
	}

	public void setForceExtension(String forceExtension) {
		this.forceExtension = forceExtension;
	}

	public String getUniqueId() {
		return uniqueId;
	}

	public void setUniqueId(String uniqueId) {
		this.uniqueId = uniqueId;
	}

	public String getItemId() {
		return itemId;
	}

	public void setItemId(String itemId) {
		this.itemId = itemId;
	}

	public void addResizedDerivative(URL url, Dimension d) {
		if (resizedDerivatives == null) {
			resizedDerivatives = new ArrayList();
		}

		resizedDerivatives.add(new ResizedDerivative(url, d));
	}

	public class ResizedDerivative {
		public URL url;
		public Dimension d;

		public ResizedDerivative(URL url, Dimension d) {
			this.url = url;
			this.d = d;
		}
	}
} public class PreferencesDialog extends JDialog implements ListSelectionListener, ActionListener {
	public static final String MODULE = "PrefsDlog";


	DefaultListModel panels = new DefaultListModel();
	HashMap panelNames = new HashMap();
	private boolean isOK = false;

	JPanel jPanel1 = new JPanel();
	JScrollPane jScrollPane1 = new JScrollPane();
	JList jIcons = new JList();
	JPanel jPanels = new JPanel();
	GridBagLayout gridBagLayout1 = new GridBagLayout();
	CardLayout jPanelsLayout = new CardLayout();
	JPanel jPanel2 = new JPanel();
	JButton jOK = new JButton();
	GridLayout gridLayout1 = new GridLayout();
	JButton jRevert = new JButton();
	JButton jCancel = new JButton();

	public PreferencesDialog(Frame owner) {
		super(owner, true);

		try {
			jbInit();
		} catch (Exception e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
		}

		loadPanes();

		jIcons.setSelectedIndex(0); // this must be done to call valueChanged

		pack();
		DialogUtil.center(this, owner);
	}

	private void loadPanes() {
		Properties panes = new Properties();
		try {
			panes.load(getClass().getResourceAsStream("panes.properties"));

			int i = 1;
			String className;
			while ((className = panes.getProperty("pane." + i++)) != null) {
				try {
					PreferencePanel pp = (PreferencePanel) GalleryRemote.secureClassForName(className).newInstance();

					pp.setDialog(this);
					pp.buildUI();

					panels.addElement(pp);

					jPanels.add(className, pp);

					panelNames.put(className, pp);
				} catch (Exception e) {
					Log.log(Log.LEVEL_ERROR, MODULE, "Bad panel: " + className);
					Log.logException(Log.LEVEL_ERROR, MODULE, e);
				}
			}

			jIcons.setModel(panels);
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
		}
	}

	public void setPanel(String className) {
		PreferencePanel pp = (PreferencePanel) panelNames.get(className);

		jIcons.setSelectedValue(pp, true);
	}

	private void jbInit() throws Exception {
		jPanel1.setLayout(gridBagLayout1);
		jScrollPane1.setAlignmentY((float) 0.5);
		jPanels.setLayout(jPanelsLayout);
		this.setTitle(GRI18n.getString(MODULE, "title"));
		jOK.setMnemonic('0');
		jOK.setText(GRI18n.getString("Common", "OK"));
		jOK.setActionCommand("OK");
		jPanel2.setLayout(gridLayout1);
		jRevert.setToolTipText(GRI18n.getString(MODULE, "revertTip"));
		jRevert.setText(GRI18n.getString(MODULE, "revert"));
		jRevert.setActionCommand("revert");
		jCancel.setText(GRI18n.getString("Common", "Cancel"));
		jCancel.setActionCommand("cancel");
		gridLayout1.setHgap(5);
		this.getContentPane().add(jPanel1, BorderLayout.CENTER);
		jPanel1.add(jScrollPane1, new GridBagConstraints(0, 0, 1, 1, 0.0, 1.0
				, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL, new Insets(10, 10, 0, 0), 0, 0));
		jPanel1.add(jPanels, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0
				, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(10, 10, 0, 10), 0, 0));
		jPanel1.add(jPanel2, new GridBagConstraints(0, 1, 2, 1, 0.0, 0.0
				, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(10, 10, 10, 10), 0, 0));
		jPanel2.add(jOK, null);
		jScrollPane1.getViewport().add(jIcons, null);
		jPanel2.add(jCancel, null);
		jPanel2.add(jRevert, null);

		jIcons.setCellRenderer(new IconsCellRenderer());
		jIcons.addListSelectionListener(this);
		jOK.addActionListener(this);
		jCancel.addActionListener(this);
		jRevert.addActionListener(this);

		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

		getRootPane().setDefaultButton(jOK);
	}

	public void valueChanged(ListSelectionEvent e) {
		PreferencePanel pp = (PreferencePanel) jIcons.getSelectedValue();

		String className = pp.getClass().getName();
		Log.log(Log.LEVEL_TRACE, MODULE, "Showing panel: " + className);

		pp.readPropertiesFirst(GalleryRemote._().properties);
		jPanelsLayout.show(jPanels, className);

		jRevert.setEnabled(pp.isReversible());

		pack();
	}

	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();
		Log.log(Log.LEVEL_INFO, MODULE, "Command selected " + cmd);

		if (cmd.equals("OK")) {
			GalleryRemote._().properties.uncache();

			Enumeration enumeration = panels.elements();
			while (enumeration.hasMoreElements()) {
				PreferencePanel pp = (PreferencePanel) enumeration.nextElement();

				if (pp.hasBeenRead()) {
					Log.log(Log.LEVEL_TRACE, MODULE, "Writing properties for panel " + pp.getClass());

					pp.writeProperties(GalleryRemote._().properties);
				}
			}

			isOK = true;

			setVisible(false);

			//Log.log(Log.TRACE, MODULE, "Updating preferences");
			//((MainFrame) getOwner()).readPreferences(oldProperties);
		} else if (cmd.equals("cancel")) {
			setVisible(false);
		} else if (cmd.equals("revert")) {
			PreferencePanel pp = (PreferencePanel) jIcons.getSelectedValue();
			Log.log(Log.LEVEL_TRACE, MODULE, "Reverting panel " + pp.getClass());
			pp.readProperties(GalleryRemote._().properties);
		}
	}

	public boolean isOK() {
		return isOK;
	}

	/**
	 * Cell renderer
	 * 
	 * @author paour
	 * @created 11 août 2002
	 */
	public class IconsCellRenderer extends DefaultListCellRenderer {
		public IconsCellRenderer() {
			super();
			setHorizontalTextPosition(JLabel.CENTER);
			setVerticalTextPosition(JLabel.BOTTOM);
			setHorizontalAlignment(JLabel.CENTER);
		}

		/**
		 * Gets the listCellRendererComponent attribute of the FileCellRenderer
		 * object
		 * 
		 * @param list     Description of Parameter
		 * @param value    Description of Parameter
		 * @param index    Description of Parameter
		 * @param selected Description of Parameter
		 * @param hasFocus Description of Parameter
		 * @return The listCellRendererComponent value
		 */
		public Component getListCellRendererComponent(
				JList list, Object value, int index,
				boolean selected, boolean hasFocus) {
			super.getListCellRendererComponent(list, value, index, selected, hasFocus);

			if (value != null && index != -1) {
				PreferencePanel pp = (PreferencePanel) value;
				setText(pp.getIcon().getText());
				setIcon(pp.getIcon().getIcon());
			} else {
				setText("dummy");
			}

			return this;
		}
	}
} public class PropertiesFile extends GalleryProperties {
	public static final String MODULE = "PropsFile";

	protected boolean read = false;
	protected boolean written = false;
	protected String mFilename;
	protected boolean readOnly = false;
	protected boolean alreadyWarned = false;
	protected String nick = null;

	public PropertiesFile(String filename) {
		super();

		setFilename(filename);
	}

	public PropertiesFile(String filename, String nick) {
		super();

		setFilename(filename);
		this.nick = nick;
	}

	public PropertiesFile(PropertiesFile p, String filename) {
		super(p);

		setFilename(filename);
	}

	public PropertiesFile(PropertiesFile p, String filename, String nick) {
		super(p);

		setFilename(filename);
		this.nick = nick;
	}


	/**
	 * Overrides default method to track dirty state
	 */
	public Object setProperty(String name, String value) {
		// if we're read-only, see if our defaults can write
		if (readOnly) {
			if (defaults != null) {
				defaults.setProperty(name, value);
			} else {
				Log.log(Log.LEVEL_ERROR, MODULE, "Read-only PropertyFile without a default: setProperty was attempted");
				Log.logStack(Log.LEVEL_ERROR, MODULE);
			}

			return getProperty(name);
		}

		written = false;

		if (value == null) {
			return remove(name);
		} else {
			return super.setProperty(name, value);
		}
	}


	/**
	 * Change the filename of the file (why would you want to do that?)
	 * 
	 * @param name The new filename value
	 */
	public synchronized void setFilename(String name) {
		if (name == null) {
			mFilename = name;
		} else {
			if (! name.endsWith(".lax") && ! name.endsWith(".properties")) {
				mFilename = name + ".properties";
			} else {
				mFilename = name;
			}
		}
	}

	public void setReadOnly() {
		readOnly = true;
	}


	/**
	 * Read a property as a string, read the file in first
	 * so you don't have to explicitly read the file in beforehand.
	 * 
	 * @param name Name of the property
	 * @return The property value
	 */
	public String getProperty(String name) {
		checkRead();

		return super.getProperty(name);
	}

	public Enumeration propertyNames() {
		checkRead();

		return super.propertyNames();
	}

	public boolean isOverridden(String name) {
		return readOnly && get(name) != null && ((String) get(name)).length() != 0;
	}

	protected void checkRead() {
		if (defaults != null && defaults instanceof PropertiesFile) {
			// also load defaults
			((PropertiesFile) defaults).checkRead();
		}

		if (!read) {
			try {
				read();
			} catch (FileNotFoundException e) {
				if (!alreadyWarned) {
					Log.logException(Log.LEVEL_ERROR, MODULE, e);
					alreadyWarned = true;
				}
			}
		}
	}


	/**
	 * Read the property file from disk
	 * 
	 * @throws java.io.FileNotFoundException Description of Exception
	 */
	public synchronized void read()
			throws FileNotFoundException {
		if (mFilename != null) {
			InputStream fileIn = null;
			try {
				// try to get from JAR
				if (mFilename.indexOf("/") == -1) {
					// only if the resource is local
					try {
						if (!alreadyWarned) {
							Log.log(Log.LEVEL_TRACE, MODULE, "Trying to find " + mFilename + " in Classpath");
						}
						fileIn = PropertiesFile.class.getResourceAsStream("/" + mFilename);
					} catch (IllegalArgumentException iae) {
						// Opera throws an exception for invalid filenames...
						fileIn = null;
					}
				}

				if (fileIn == null) {
					// no dice? OK, from cwd then...
					if (!alreadyWarned) {
						Log.log(Log.LEVEL_TRACE, MODULE, "Trying to find " + mFilename + " in Current Working Dir");
					}
					
					fileIn = new FileInputStream(mFilename);
				}

				load(fileIn);
			} catch (FileNotFoundException fnf) {
				throw fnf;
			} catch (IOException e) {
				// Todo: what should happen here?
			} finally {
				try {
					fileIn.close();
				} catch (IOException e2) {
				} catch (NullPointerException e3) {
				}
			}
		}

		read = true;
		written = true;
	}


	/**
	 * Write the property file to disk
	 */
	public synchronized void write() {
		if (readOnly && defaults != null && defaults instanceof PropertiesFile) {
			((PropertiesFile) defaults).write();
			written = true;
		}

		if (!written && !readOnly) {
			FileOutputStream fileOut = null;
			try {
				fileOut = new FileOutputStream(mFilename);
				store(fileOut, null);
			} catch (IOException e) {
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
			} finally {
				try {
					fileOut.close();
				} catch (IOException e2) {
				} catch (NullPointerException e3) {
				}
			}

			written = true;
		}
	}

	public String logPropertiesHelper(String name) {
		Object value = get(name);

		if (value == null) {
			if (defaults != null) {
				if (defaults instanceof GalleryProperties) {
					return ((GalleryProperties) defaults).logPropertiesHelper(name);
				} else {
					return name + "= |" + getProperty(name) + "|";
				}
			} else {
				return name + "= null (this shouldn't happen)";
			}
		} else {
			return name + "= |" + value.toString() + "|" + " [" + nick + "]";
		}
	}

	/**
	 * Writes this property list (key and element pairs) in this
	 * <code>Properties</code> table to the output stream in a format suitable
	 * for loading into a <code>Properties</code> table using the
	 * <code>load</code> method.
	 * The stream is written using the ISO 8859-1 character encoding.
	 * <p/>
	 * Properties from the defaults table of this <code>Properties</code>
	 * table (if any) are <i>not</i> written out by this method.
	 * <p/>
	 * If the header argument is not null, then an ASCII <code>#</code>
	 * character, the header string, and a line separator are first written
	 * to the output stream. Thus, the <code>header</code> can serve as an
	 * identifying comment.
	 * <p/>
	 * Next, a comment line is always written, consisting of an ASCII
	 * <code>#</code> character, the current date and time (as if produced
	 * by the <code>toString</code> method of <code>Date</code> for the
	 * current time), and a line separator as generated by the Writer.
	 * <p/>
	 * Then every entry in this <code>Properties</code> table is written out,
	 * one per line. For each entry the key string is written, then an ASCII
	 * <code>=</code>, then the associated element string. Each character of
	 * the element string is examined to see whether it should be rendered as
	 * an escape sequence. The ASCII characters <code>\</code>, tab, newline,
	 * and carriage return are written as <code>\\</code>, <code>\t</code>,
	 * <code>\n</code>, and <code>\r</code>, respectively. Characters less
	 * than <code>&#92;u0020</code> and characters greater than
	 * <code>&#92;u007E</code> are written as <code>&#92;u</code><i>xxxx</i> for
	 * the appropriate hexadecimal value <i>xxxx</i>. Leading space characters,
	 * but not embedded or trailing space characters, are written with a
	 * preceding <code>\</code>. The key and value characters <code>#</code>,
	 * <code>!</code>, <code>=</code>, and <code>:</code> are written with a
	 * preceding slash to ensure that they are properly loaded.
	 * <p/>
	 * After the entries have been written, the output stream is flushed.  The
	 * output stream remains open after this method returns.
	 * 
	 * @param out    an output stream.
	 * @param header a description of the property list.
	 * @throws java.io.IOException            if writing this property list to the specified
	 *                                        output stream throws an <tt>IOException</tt>.
	 * @throws java.lang.ClassCastException   if this <code>Properties</code> object
	 *                                        contains any keys or values that are not <code>Strings</code>.
	 * @throws java.lang.NullPointerException if <code>out</code> is null.
	 * @since 1.2
	 */
	public synchronized void store(OutputStream out, String header)
			throws IOException {
		BufferedWriter awriter;
		awriter = new BufferedWriter(new OutputStreamWriter(out, "8859_1"));
		if (header != null)
			writeln(awriter, "#" + header);
		writeln(awriter, "#" + new Date().toString());
		ArrayList v = new ArrayList(keySet());
		Collections.sort(v);
		for (Iterator e = v.iterator(); e.hasNext();) {
			String key = (String) e.next();
			String val = (String) get(key);
			key = saveConvert(key, true);

			/* No need to escape embedded and trailing spaces for value, hence
			* pass false to flag.
			*/
			val = saveConvert(val, false);
			writeln(awriter, key + "=" + val);
		}
		awriter.flush();
	}

	/*
	* Converts unicodes to encoded &#92;uxxxx
	* and writes out any of the characters in specialSaveChars
	* with a preceding slash
	*/
	private String saveConvert(String theString, boolean escapeSpace) {
		int len = theString.length();
		StringBuffer outBuffer = new StringBuffer(len * 2);

		for (int x = 0; x < len; x++) {
			char aChar = theString.charAt(x);
			switch (aChar) {
				case ' ':
					if (x == 0 || escapeSpace)
						outBuffer.append('\\');

					outBuffer.append(' ');
					break;
				case '\\':
					outBuffer.append('\\');
					outBuffer.append('\\');
					break;
				case '\t':
					outBuffer.append('\\');
					outBuffer.append('t');
					break;
				case '\n':
					outBuffer.append('\\');
					outBuffer.append('n');
					break;
				case '\r':
					outBuffer.append('\\');
					outBuffer.append('r');
					break;
				case '\f':
					outBuffer.append('\\');
					outBuffer.append('f');
					break;
				default:
					if ((aChar < 0x0020) || (aChar > 0x007e)) {
						outBuffer.append('\\');
						outBuffer.append('u');
						outBuffer.append(toHex((aChar >> 12) & 0xF));
						outBuffer.append(toHex((aChar >> 8) & 0xF));
						outBuffer.append(toHex((aChar >> 4) & 0xF));
						outBuffer.append(toHex(aChar & 0xF));
					} else {
						if (specialSaveChars.indexOf(aChar) != -1)
							outBuffer.append('\\');
						outBuffer.append(aChar);
					}
			}
		}
		return outBuffer.toString();
	}

	private static void writeln(BufferedWriter bw, String s) throws IOException {
		bw.write(s);
		bw.newLine();
	}

	/**
	 * Convert a nibble to a hex character
	 * 
	 * @param	nibble	the nibble to convert.
	 */
	private static char toHex(int nibble) {
		return hexDigit[(nibble & 0xF)];
	}

	private static final String specialSaveChars = "=: \t\r\n\f#!";

	/** A table of hex digits */
	private static final char[] hexDigit = {
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
	};
} public class URLPanel extends PreferencePanel implements ListSelectionListener, ActionListener {
	public static final String MODULE = "URLPa";


	JLabel icon = new JLabel(GRI18n.getString(MODULE, "icon"));

	GridBagLayout gridBagLayout1 = new GridBagLayout();
	JScrollPane jScrollPane1 = new JScrollPane();
	JList jGalleries = new JList();
	JButton jModify = new JButton();
	JButton jNew = new JButton();
	JButton jDelete = new JButton();
	JPanel jPanel1 = new JPanel();
	JLabel jDetails = new JLabel();
	GridLayout gridLayout1 = new GridLayout();

	public JLabel getIcon() {
		return icon;
	}

	public boolean isReversible() {
		return false;
	}

	public void readProperties(PropertiesFile props) {
	}

	public void writeProperties(PropertiesFile props) {
	}

	public void buildUI() {
		jbInit();
	}

	private void jbInit() {
		this.setLayout(gridBagLayout1);
		jModify.setActionCommand("Modify");
		jModify.setText(GRI18n.getString(MODULE, "modify"));
		jNew.setActionCommand("New");
		jNew.setText(GRI18n.getString(MODULE, "new"));
		jDelete.setActionCommand("Delete");
		jDelete.setText(GRI18n.getString(MODULE, "delete"));
		jPanel1.setBorder(new TitledBorder(BorderFactory.createEtchedBorder(Color.white, new Color(148, 145, 140)), GRI18n.getString(MODULE, "details")));
		jPanel1.setLayout(gridLayout1);
		gridLayout1.setColumns(1);
		jDetails.setMinimumSize(new Dimension(0, 50));
		jDetails.setPreferredSize(new Dimension(0, 50));
		jDetails.setHorizontalAlignment(SwingConstants.LEFT);
		jDetails.setVerticalAlignment(SwingConstants.TOP);
		this.add(jScrollPane1, new GridBagConstraints(0, 0, 1, 3, 1.0, 1.0
				, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
		this.add(jModify, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0
				, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 5), 0, 0));
		this.add(jNew, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0
				, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 5), 0, 0));
		this.add(jDelete, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0
				, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 5), 0, 0));
		this.add(jPanel1, new GridBagConstraints(0, 3, 2, 1, 1.0, 1.0
				, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
		jPanel1.add(jDetails, null);
		jScrollPane1.getViewport().add(jGalleries, null);

		jGalleries.setModel(GalleryRemote._().getCore().getGalleries());
		jGalleries.setCellRenderer(new GalleryCellRenderer());
		jGalleries.addListSelectionListener(this);

		jGalleries.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					int index = jGalleries.locationToIndex(e.getPoint());
					modifyGallery((Gallery) jGalleries.getModel().getElementAt(index));
				}
			}
		});

		if (GalleryRemote._().getCore().getGalleries().getSize() > 0) {
			jGalleries.setSelectedIndex(0);
		} else {
			resetUIState();
		}

		jModify.addActionListener(this);
		jNew.addActionListener(this);
		jDelete.addActionListener(this);
	}

	public void valueChanged(ListSelectionEvent e) {
		resetUIState();
	}

	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();
		Gallery g = (Gallery) jGalleries.getSelectedValue();

		Log.log(Log.LEVEL_INFO, MODULE, "Command selected " + cmd + " Gallery: " + g);

		if (cmd.equals("Modify")) {
			modifyGallery(g);
		} else if (cmd.equals("New")) {
			Gallery newG = new Gallery(GalleryRemote._().getCore().getMainStatusUpdate());
			if (GalleryRemote._().getCore() instanceof TreeModelListener) {
				newG.addTreeModelListener((TreeModelListener) GalleryRemote._().getCore());
			}

			GalleryEditorDialog ged = new GalleryEditorDialog(dialog, newG);

			if (ged.isOK()) {
				GalleryRemote._().getCore().getGalleries().addElement(newG);
				jGalleries.setSelectedValue(newG, true);

				Gallery.uncacheAmbiguousUrl();

				resetUIState();
			}
		} else if (cmd.equals("Delete")) {
			Object[] params = {g.getUrl()};
			int n = JOptionPane.showConfirmDialog(this, GRI18n.getString(MODULE, "delConfirm", params),
					GRI18n.getString(MODULE, "delete"),
					JOptionPane.WARNING_MESSAGE,
					JOptionPane.YES_NO_OPTION);

			if (n == JOptionPane.YES_OPTION) {
				((MainFrame) GalleryRemote._().getCore()).removeGallery(g);

				Gallery.uncacheAmbiguousUrl();
			}
		} else if (cmd.equals("GalleryEditorDialog")) {
			Gallery newG = (Gallery) e.getSource();

			if (GalleryRemote._().getCore().getGalleries().getIndexOf(newG) == -1) {
				GalleryRemote._().getCore().getGalleries().addElement(newG);
				jGalleries.setSelectedValue(newG, true);
			}

			Gallery.uncacheAmbiguousUrl();
			resetUIState();
		} else {
			Log.log(Log.LEVEL_ERROR, MODULE, "Unknown command: " + cmd);
		}
	}

	private void modifyGallery(Gallery g) {
		GalleryEditorDialog ged = new GalleryEditorDialog(dialog, g);

		if (ged.isOK()) {
			//jGalleries.repaint();
			int i = GalleryRemote._().getCore().getGalleries().getIndexOf(g);
			GalleryRemote._().getCore().getGalleries().removeElementAt(i);
			GalleryRemote._().getCore().getGalleries().insertElementAt(g, i);

			Gallery.uncacheAmbiguousUrl();
		}
	}

	public void resetUIState() {
		Gallery selectedGallery = (Gallery) jGalleries.getSelectedValue();

		StringBuffer sb = new StringBuffer();

		if (selectedGallery != null) {
			sb.append("<HTML>");

			sb.append(GRI18n.getString(MODULE, "gllryURL")).append(selectedGallery.getUrlString()).append("<br>");

			String username = selectedGallery.getUsername();
			if (username == null || username.length() == 0) {
				username = "&lt;Not set&gt;";
			}
			sb.append(GRI18n.getString(MODULE, "username")).append(username).append("<br>");
			
			if (selectedGallery.isAutoLoadOnStartup()) {
				sb.append(GRI18n.getString(MODULE, "autoLogin")).append("<br>");
			}

			sb.append("</HTML>");

			jModify.setEnabled(true);
			jDelete.setEnabled(true);
		} else {
			jModify.setEnabled(false);
			jDelete.setEnabled(false);
		}

		jDetails.setText(sb.toString());
	}

	public class GalleryCellRenderer extends DefaultListCellRenderer {
		/**
		 * Gets the listCellRendererComponent attribute of the FileCellRenderer
		 * object
		 * 
		 * @param list     Description of Parameter
		 * @param value    Description of Parameter
		 * @param index    Description of Parameter
		 * @param selected Description of Parameter
		 * @param hasFocus Description of Parameter
		 * @return The listCellRendererComponent value
		 */
		public Component getListCellRendererComponent(
				JList list, Object value, int index,
				boolean selected, boolean hasFocus) {
			super.getListCellRendererComponent(list, value, index, selected, hasFocus);

			Gallery g = (Gallery) value;

			setText(g.toString());
			
			if (g.isAutoLoadOnStartup()) {
				setFont(getFont().deriveFont(Font.BOLD));
			} else {
				setFont(getFont().deriveFont(Font.PLAIN));
			}

			return this;
		}
	}
} public interface PreferenceNames {
	// General panel
	public static final String SHOW_THUMBNAILS = "showThumbnails";
	public static final String THUMBNAIL_SIZE = "thumbnailSize";
	public static final String SAVE_PASSWORDS = "savePasswords";
	public static final String LOG_LEVEL = "logLevel";
	public static final String UPDATE_CHECK = "updateCheck";
	public static final String UPDATE_CHECK_BETA = "updateCheckBeta";
	public static final String UPDATE_URL = "updateUrl";
	public static final String UPDATE_URL_BETA = "updateUrlBeta";
	public static final String UI_LOCALE = "uiLocale";
	public static final String UI_LOCALE_DEV = "uiLocaleDev";
	public static final String MRU_COUNT = "mruCount";
	public static final String MRU_BASE = "mruItem.";
	public static final String LOAD_LAST_FILE = "loadLastMRU";

	// Upload panel
	public static final String RESIZE_BEFORE_UPLOAD = "resizeBeforeUpload";
	public static final String RESIZE_TO = "resizeTo";
	public static final String RESIZE_TO_DEFAULT = "resizeToDefault";
	public static final String AUTO_CAPTIONS = "autoCaptions";
	public static final int AUTO_CAPTIONS_NONE = 0;
	public static final int AUTO_CAPTIONS_FILENAME = 1;
	public static final int AUTO_CAPTIONS_COMMENT = 2;
	public static final int AUTO_CAPTIONS_DATE = 3;
	//public static final String SET_CAPTIONS_NONE = "setCaptionsNone";
	//public static final String SET_CAPTIONS_WITH_FILENAMES = "setCaptionsWithFilenames";
	//public static final String SET_CAPTIONS_WITH_METADATA_COMMENT = "setCaptionsWithMetadataComment";
	public static final String CAPTION_STRIP_EXTENSION = "captionStripExtension";
	public static final String HTML_ESCAPE_CAPTIONS = "htmlEscapeCaptions";
	public static final String EXIF_AUTOROTATE = "exifAutorotate";

	// URL panel
	public static final String USERNAME = "username.";
	public static final String PASSWORD = "password.";
	public static final String GURL = "url.";
	public static final String APPLET = "Applet";
	public static final String ALIAS = "alias.";
	public static final String KEY = "key.";
	public static final String FORCE_GALLERY_VERSION = "forceGalleryVersion.";
	public static final String FORCE_PROTOCOL_ENCODING = "forceProtocolEncoding.";
	public static final String RESIZE_JPEG_QUALITY = "resizeJpegQuality.";
	public static final String AUTO_LOAD_ON_STARTUP = "autoLoadOnStartup.";

	// Proxy panel
	public static final String USE_PROXY = "useProxy";
	public static final String PROXY_HOST = "proxyHost";
	public static final String PROXY_PORT = "proxyPort";
	public static final String PROXY_USERNAME = "proxyUsername";
	public static final String PROXY_PASSWORD = "proxyPassword";

	// Slideshow
	public static final String SLIDESHOW_PROGRESS = "slideshowProgressLocation";
	public static final String SLIDESHOW_CAPTION = "slideshowCaptionLocation";
	public static final String SLIDESHOW_EXTRA = "slideshowExtraLocation";
	public static final String SLIDESHOW_URL = "slideshowUrlLocation";
	public static final String SLIDESHOW_ALBUM = "slideshowAlbumLocation";
	public static final String SLIDESHOW_SUMMARY = "slideshowSummaryLocation";
	public static final String SLIDESHOW_DESCRIPTION = "slideshowDescriptionLocation";
	public static final String SLIDESHOW_DELAY = "slideshowDelay";
	public static final String SLIDESHOW_LOWREZ = "slideshowLowRez";
	public static final String SLIDESHOW_RANDOM = "slideshowRandom";
	public static final String SLIDESHOW_MAX_PICTURES = "slideshowMaxPictures";
	public static final String SLIDESHOW_RECURSIVE = "slideshowRecursive";
	public static final String SLIDESHOW_NOSTRETCH = "slideshowNoStretch";
	public static final String SLIDESHOW_COLOR = "slideshowColor";
	public static final String SLIDESHOW_PRELOADALL = "slideshowPreloadAll";
	public static final String SLIDESHOW_LOOP = "slideshowLoop";
	public static final String SLIDESHOW_FONTNAME = "slideshowFontName";
	public static final String SLIDESHOW_FONTSIZE = "slideshowFontSize";
	public static final String SLIDESHOW_FONTTHICKNESS = "slideshowFontThickness";
	public static final String SLIDESHOW_TRANSITION_DURATION = "slideshowTransitionDuration";

	// Other
	public static final String SUPPRESS_WARNING_IM = "suppressWarningIM";
	public static final String SUPPRESS_WARNING_JPEGTRAN = "suppressWarningJpegtran";
	public static final String SUPPRESS_WARNING_JPEGTRAN_CROP = "suppressWarningJpegtranCrop";
	public static final String SUPPRESS_WARNING_CORRUPTED = "suppressWarningCorrupted";
	public static final String SUPPRESS_WARNING_JAVA = "suppressWarningJava";
	public static final String SUPPRESS_WARNING_OUT_OF_MEMORY = "suppressWarningOutOfMemory";
	public static final String USE_JAVA_RESIZE = "useJavaResize";
	public static final String FONT_OVERRIDE_NAME = "fontOverrideName";
	public static final String FONT_OVERRIDE_STYLE = "fontOverrideStyle";
	public static final String FONT_OVERRIDE_SIZE = "fontOverrideSize";
	public static final String PREVIEW_TRANSITION_DURATION = "previewTransitionDuration";
	public static final String ALLOW_UNACCELERATED_TRANSITION = "allowUnacceleratedTransition";
	public static final String PREVIEW_DRAW_THIRDS = "previewDrawThirds";

	// Applet
	public static final String APPLET_SHOW_RESIZE = "appletShowResize";
	public static final String APPLET_DIVIDER_LOCATION = "appletDividerLocation";
	public static final String APPLET_FONTSIZE = "appletFontSize";

	// Sort
	public static final String SORT_TYPE = "sortType";
	public static final int SORT_TYPES = 2;
	public static final int SORT_TYPE_FILENAME = 1;
	public static final int SORT_TYPE_EXIF_CREATION = 2;
} public class GRI18n implements PreferenceNames {
	private static final String RESNAME =
			"com.gallery.GalleryRemote.resources.GRResources";
	private static final String RESNAME_DEV =
			"GRResources";
	private static final String RESPATH =
			"com/gallery/GalleryRemote/resources/GRResources";
	private static final String MODULE = "GRI18n";

	private static Locale grLocale;
	private static ResourceBundle grResBundle;
	private static HashMap formats = new HashMap();

	private static List lAvailLoc = null;

	private static boolean devMode = false;
	private static Properties devResProperties = null;

	static {
		String myLocale = GalleryRemote._().properties.getProperty(UI_LOCALE);
		devMode = GalleryRemote._().properties.getBooleanProperty(UI_LOCALE_DEV);

		grLocale = parseLocaleString(myLocale);

		Log.log(Log.LEVEL_INFO, MODULE, grLocale.toString());

		setResBundle();
	}

	public static Locale parseLocaleString(String localeString) {
		if (localeString == null) {
			return Locale.getDefault();
		} else {
			int i = localeString.indexOf("_");

			if (i != -1) {
				return new Locale(localeString.substring(0, i), localeString.substring(i + 1));
			} else {
				return new Locale(localeString, "");
			}
		}
	}


	public static void setLocale(String language, String country) {
		grLocale = new Locale(language, country);
		setResBundle();
	}


	public static String getString(String className, String key) {
		String msg;
		String extKey = className + "." + key;
		try {
			msg = grResBundle.getString(extKey);

			if (devResProperties != null && devResProperties.getProperty(extKey) == null) {
				if (msg.startsWith("<html>")) {
					msg = "<html>***" + msg.substring(6);
				} else {
					msg = "***" + msg;
				}
			}
		} catch (NullPointerException e) {
			Log.log(Log.LEVEL_ERROR, MODULE, "Key null error");
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			msg = "[NULLKEY]";
		} catch (MissingResourceException e) {
			Log.log(Log.LEVEL_INFO, MODULE, "Key [" + extKey + "] not defined");
			Log.logException(Log.LEVEL_INFO, MODULE, e);
			msg = "[" + extKey + "]";
		}

		return msg;
	}


	public static String getString(String className, String key, Object[] params) {
		String msg;
		String extKey = className + "." + key;
		try {
			MessageFormat format = (MessageFormat) formats.get(extKey);
			if (format == null) {
				format = new MessageFormat(fixQuotes(grResBundle.getString(extKey)), grLocale);
				formats.put(extKey, format);
			}
			msg = format.format(params);

			if (devResProperties != null && devResProperties.getProperty(extKey) == null) {
				if (msg.startsWith("<html>")) {
					msg = "<html>***" + msg.substring(6);
				} else {
					msg = "***" + msg;
				}
			}
		} catch (NullPointerException e) {
			Log.log(Log.LEVEL_ERROR, MODULE, "Key null error");
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			msg = "[NULLKEY]";
		} catch (MissingResourceException e) {
			Log.log(Log.LEVEL_INFO, MODULE, "Key [" + extKey + "] not defined");
			Log.logException(Log.LEVEL_INFO, MODULE, e);
			msg = "[" + extKey + "]";
		}

		return msg;
	}

	public static String fixQuotes(String s) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			if (c == '\'') {
				sb.append("''");
			} else {
				sb.append(c);
			}
		}

		return sb.toString();
	}

	public static Locale getCurrentLocale() {
		return grLocale;
	}


	private static void setResBundle() {
		try {
			grResBundle = ResourceBundle.getBundle(devMode?RESNAME_DEV:RESNAME, grLocale);

			if (devMode) {
				devResProperties = getLocaleProperties(grLocale);
			}
		} catch (MissingResourceException e) {
			Log.log(Log.LEVEL_ERROR, MODULE, "Resource bundle error");
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
		}
	}

	public static Properties getLocaleProperties(Locale locale) {
		Properties p = new Properties();
		String filename;
		if (locale == null) {
			filename = (devMode?RESNAME_DEV:RESPATH) + ".properties";
		} else {
			filename = (devMode?RESNAME_DEV:RESPATH) + "_" + locale.toString() + ".properties";
		}
		InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(filename);
		if (is != null) {
			try {
				p.load(is);
			} catch (IOException e) {
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
			}
		} else {
			Log.log(Log.LEVEL_ERROR, MODULE, "No file for " + filename);
		}

		return p;
	}


	public static List getAvailableLocales() {
		if (lAvailLoc == null)
			lAvailLoc = initAvailableLocales();
		return lAvailLoc;
	}

	private static List initAvailableLocales() {
		String locPath;
		String loc;
		List aList = new LinkedList();
		long start = System.currentTimeMillis();

		// todo: it seems that the dialog can't be displayed because all this
		// is running in the Swing event thread. Need to find a better way...
//		JDialog dialog = new JDialog(GalleryRemote.getInstance().mainFrame, "Please wait...");
//		dialog.getContentPane().add("Center", new JLabel("<HTML>Parsing list of locales for this platform." +
//				"<br>This can take between 1 and 10 seconds..."));
//		dialog.pack();
//		DialogUtil.center(dialog);
//		dialog.setVisible(true);
//		Thread.yield();

		Log.log(Log.LEVEL_TRACE, MODULE, "Getting the list of locales");

		// this call is apparently very slow...
		Locale[] list = Locale.getAvailableLocales();

		Log.log(Log.LEVEL_TRACE, MODULE, "The platform supports " + list.length + " locales. Pruning...");

		String prefix = "##DUMMY";
		for (int i = 0; i < list.length; i++) {
			loc = list[i].toString();

			// perf optimization: don't go through all the regions if the main language was not found
			if (!loc.startsWith(prefix)) {
				prefix = loc;
				if (devMode) {
					Log.log(Log.LEVEL_TRACE, MODULE, "Trying locale: " + loc);
				}

				locPath = (devMode?RESNAME_DEV:RESPATH) + "_" + loc + ".properties";
				if (ClassLoader.getSystemClassLoader().getResource(locPath) != null) {
					Log.log(Log.LEVEL_INFO, MODULE, "Found locale: " + loc);
					aList.add(list[i]);
					prefix = "##DUMMY";
				}
			}
		}

		Log.log(Log.LEVEL_TRACE, MODULE, "Pruned locales in " + (System.currentTimeMillis() - start) + "ms");
		//dialog.setVisible(false);

		return aList;
	}

	/*class PatienceDialog extends JDialog implements Runnable {
		public boolean done = false;

		public void run() {
			try {
				Thread.sleep(1000);


			} catch (InterruptedException e) {}
		}
	}*/
} public class ImageUtils {
	public static final String MODULE = "ImageUtils";

	static ArrayList toDelete = new ArrayList();
	static long totalTime = 0;
	static int totalIter = 0;

	public static boolean useIM = false;
	static String imPath = null;
	static int jpegQuality = 75;
	static boolean imIgnoreErrorCode = false;

	public static boolean useJpegtran = false;
	public static boolean useJpegtranCrop = false;
	static String jpegtranPath = null;
	static boolean jpegtranIgnoreErrorCode = false;

	static File tmpDir = null;

	public static final int THUMB = 0;
	public static final int PREVIEW = 1;
	public static final int UPLOAD = 2;

	static String[] filterName = new String[3];
	static String[] format = new String[3];

	public final static String DEFAULT_RESOURCE = "/default.gif";
	public final static String UNRECOGNIZED_RESOURCE = "/default.gif";

	public static Image defaultThumbnail = null;
	public static Image unrecognizedThumbnail = null;

	public static boolean deferredStopUsingIM = false;
	public static boolean deferredStopUsingJpegtran = false;
	public static boolean deferredStopUsingJpegtranCrop = false;
	public static boolean deferredOutOfMemory = false;

	public static ImageObserver observer = new ImageObserver() {
		public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
			return false;
		}
	};
	public static final String APERTURE_TO_GALLERY_SCPT = "ApertureToGallery.applescript";

	public static Image load(String filename, Dimension d, int usage) {
		return load(filename, d, usage, false);
	}

	public static Image load(String filename, Dimension d, int usage, boolean ignoreFailure) {
		if (!new File(filename).exists()) {
			return null;
		}

		Image r = null;
		long start = System.currentTimeMillis();

		if (!GalleryFileFilter.canManipulate(filename)) {
			return unrecognizedThumbnail;
		}

		if (useIM) {
			ArrayList cmd = new ArrayList();
			cmd.add(imPath);

			if (filterName[usage] != null && filterName[usage].length() > 0) {
				//cmdline.append(" -filter ").append(filterName[usage]);
				cmd.add("-filter");
				cmd.add(filterName[usage]);
			}

			cmd.add(filename);

			cmd.add("-resize");
			if (GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SLIDESHOW_NOSTRETCH)) {
				cmd.add(d.width + "x" + d.height + ">");
			} else {
				cmd.add(d.width + "x" + d.height);
			}

			cmd.add("+profile");
			cmd.add("*");

			File temp = deterministicTempFile("thumb", "." + format[usage], tmpDir, filename + d);

			if (!temp.exists()) {
				toDelete.add(temp);

				cmd.add(temp.getPath());

				int exitValue = exec((String[]) cmd.toArray(new String[0]));

				if ((exitValue != 0 && !imIgnoreErrorCode && !ignoreFailure) || ! temp.exists()) {
					if (exitValue != -1 && ! temp.exists()) {
						// don't kill IM if it's just an InterruptedException
						Log.log(Log.LEVEL_CRITICAL, MODULE, "ImageMagick doesn't seem to be working. Disabling");
						stopUsingIM();
					}
				} else {
					try {
						r = ImageIO.read(temp);
					} catch (IOException e) {
						Log.logException(Log.LEVEL_ERROR, MODULE, e);
					} catch (Throwable e) {
						Log.logException(Log.LEVEL_ERROR, MODULE, e);
						Log.log(Log.LEVEL_ERROR, MODULE, "Out of memory while loading image " + temp);
						outOfMemoryError();
					}
				}
			} else {
				try {
					r = ImageIO.read(temp);
				} catch (IOException e) {
					Log.logException(Log.LEVEL_ERROR, MODULE, e);
				} catch (Throwable e) {
					Log.logException(Log.LEVEL_ERROR, MODULE, e);
					Log.log(Log.LEVEL_ERROR, MODULE, "Out of memory while loading image " + temp);
					outOfMemoryError();
				}
			}
		}

		if (!useIM && r == null) {
			r = loadJava(filename, d, GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SLIDESHOW_NOSTRETCH));
		}

		long time = System.currentTimeMillis() - start;
		totalTime += time;
		totalIter++;
		Log.log(Log.LEVEL_TRACE, MODULE, "Time: " + time + " - Avg: " + (totalTime / totalIter));

		return r;
	}

	public static Image loadJava(Object reference, Dimension d, boolean noStretch) {
		try {
			Image r, scaled;

			//System.gc();

			if (reference instanceof String) {
				r = ImageIO.read(new File((String) reference));
			} else if (reference instanceof URL) {
				r = ImageIO.read((URL) reference);
			} else {
				throw new IllegalArgumentException("loadJava can only be called with a URL or a filename");
			}

			Dimension newD = getSizeKeepRatio(
					new Dimension(r.getWidth(observer), r.getHeight(observer)),
					d, noStretch);
			if (newD == null) {
				return r;
			}

			scaled = createResizedCopy(r, newD.width, newD.height, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			r.flush();
			r = null;

			//System.runFinalization();
			//System.gc();

			return scaled;
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return null;
		} catch (Throwable e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			Log.log(Log.LEVEL_ERROR, MODULE, "Out of memory while loading image " + reference);
			outOfMemoryError();
			return null;
		}
	}

	public static File resize(String filename, Dimension d) {
		return resize(filename, d, null, -1);
	}

	public static File resize(String filename, Dimension d, Rectangle cropTo, int resizeJpegQuality) {
		File r = null;
		long start = System.currentTimeMillis();

		if (!GalleryFileFilter.canManipulate(filename)) {
			return new File(filename);
		}

		if (useIM) {
			ArrayList cmd = new ArrayList();
			cmd.add(imPath);

			if (filterName[UPLOAD] != null && filterName[UPLOAD].length() > 0) {
				cmd.add("-filter");
				cmd.add(filterName[UPLOAD]);
			}

			cmd.add(filename);

			if (cropTo != null) {
				cmd.add("-crop");
				cmd.add(cropTo.width + "x" + cropTo.height + "+" + cropTo.x + "+" + cropTo.y);
			}

			if (d != null) {
				cmd.add("-resize");
				cmd.add(d.width + "x" + d.height + ">");
			}

			cmd.add("-quality");
			cmd.add("" + ((resizeJpegQuality == -1)?jpegQuality:resizeJpegQuality));

			r = deterministicTempFile("res"
					, "." + GalleryFileFilter.getExtension(filename), tmpDir, filename + d + cropTo);
			toDelete.add(r);

			cmd.add(r.getPath());

			int exitValue = exec((String[]) cmd.toArray(new String[0]));

			if ((exitValue != 0 && !imIgnoreErrorCode) || ! r.exists()) {
				if (exitValue != -1 && ! r.exists()) {
					// don't kill IM if it's just an InterruptedException
					Log.log(Log.LEVEL_CRITICAL, MODULE, "ImageMagick doesn't seem to be working. Disabling");
					stopUsingIM();
				}
				r = null;
			}
		}

		if (!useIM && r == null) {
			r = resizeJava(filename, d);

			if (r == null) {
				Log.log(Log.LEVEL_TRACE, MODULE, "All methods of resize failed: sending original file");
				r = new File(filename);
			}
		}

		long time = System.currentTimeMillis() - start;
		totalTime += time;
		totalIter++;
		Log.log(Log.LEVEL_TRACE, MODULE, "Time: " + time + " - Avg: " + (totalTime / totalIter));

		return r;
	}

	public static File resizeJava(String filename, Dimension d) {
		File r;

		if (!GalleryRemote._().properties.getBooleanProperty(PreferenceNames.USE_JAVA_RESIZE)) {
			return null;
		}

		if (!GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SUPPRESS_WARNING_JAVA)) {
			if (stopUsingJavaResize()) {
				return null;
			}
		}

		try {
			// read the image
			ImageInputStream iis = ImageIO.createImageInputStream(new File(filename));

			Iterator iter = ImageIO.getImageReaders(iis);
			if (!iter.hasNext()) {
				return null;
			}

			ImageReader reader = (ImageReader)iter.next();
			ImageReadParam param = reader.getDefaultReadParam();
			reader.setInput(iis, true, false);

			// Java bug with thumbnails
			//IIOImage image = reader.readAll(0, param);
			BufferedImage rim = (BufferedImage) reader.readAsRenderedImage(0, param);

			iis.close();
			reader.dispose();

			// resize the image
			//BufferedImage rim = (BufferedImage) image.getRenderedImage();

			Dimension newD = getSizeKeepRatio(
					new Dimension(rim.getWidth(), rim.getHeight()),
					d, true);

			if (newD != null) {
				BufferedImage scaled = createResizedCopy(rim, newD.width, newD.height,
						RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				//ImageObserver imageObserver = GalleryRemote._().getMainFrame();
				//ImageObserver imageObserver = null;
				//BufferedImage scaledB = new BufferedImage(scaled.getWidth(imageObserver), scaled.getHeight(imageObserver), rim.getType());
				//scaledB.getGraphics().drawImage(scaled, 0, 0, imageObserver);

				/*System.out.println("*** Original");
				IIOMetadata metadata = image.getMetadata();
				String names[] = metadata.getMetadataFormatNames();
				for (int i = 0; i < names.length; i++) {
				displayMetadata(metadata.getAsTree(names[i]));
				}

				Node root = metadata.getAsTree(metadata.getNativeMetadataFormatName());
				Node markerSequence = root.getFirstChild().getNextSibling();
				markerSequence.removeChild(markerSequence.getFirstChild().getNextSibling().getNextSibling().getNextSibling());
				markerSequence.removeChild(markerSequence.getLastChild());
				markerSequence.removeChild(markerSequence.getLastChild());
				markerSequence.removeChild(markerSequence.getLastChild());
				markerSequence.removeChild(markerSequence.getLastChild());
				markerSequence.removeChild(markerSequence.getLastChild());
				markerSequence.removeChild(markerSequence.getLastChild());
				markerSequence.removeChild(markerSequence.getLastChild());

				System.out.println("*** Modified root");
				displayMetadata(root);*/

				//metadata.setFromTree(metadata.getNativeMetadataFormatName(), root);

				//System.out.println("*** Modified metadata");
				//displayMetadata(metadata.getAsTree(metadata.getNativeMetadataFormatName()));

				// todo: despite my best efforts, I can't get the ImageIO library to keep the metadata.
				IIOImage image = new IIOImage(scaled, null, null);

				//image.getMetadata().mergeTree(metadata.getNativeMetadataFormatName(), root);

				//image.setRaster(scaledB.getRaster());

				//write the image
				r = deterministicTempFile("jres"
						, "." + GalleryFileFilter.getExtension(filename), tmpDir, filename + d);
				toDelete.add(r);

				ImageWriter writer = ImageIO.getImageWriter(reader);

				if (writer == null) {
					Log.log(Log.LEVEL_ERROR, MODULE, "No writer to write out " + filename +
							" ImageIO probably doesn't support it. Resize aborted.");
					return new File(filename);
				}

				ImageOutputStream ios;
				try {
					r.delete();
					ios = ImageIO.createImageOutputStream(r);
				} catch (IOException e) {
					throw new IIOException("Can't create output stream!", e);
				}

				writer.setOutput(ios);
				ImageWriteParam iwp = writer.getDefaultWriteParam();
				iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
				iwp.setCompressionQuality(jpegQuality / 100.0F);
				//metadata = writer.getDefaultStreamMetadata(null);
				//System.out.println("*** Default metadata");
				//displayMetadata(metadata.getAsTree(metadata.getNativeMetadataFormatName()));
				writer.write(null, image, iwp);

				//image.getMetadata().mergeTree(metadata.getNativeMetadataFormatName(), root);

				ios.flush();
				ios.close();
				writer.dispose();

				Log.log(Log.LEVEL_TRACE, MODULE, "Java resized " + filename + " to " + r.getPath());
			} else {
				return new File(filename);
			}
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return new File(filename);
		}

		return r;
	}

	public static File rotate(String filename, int angle, boolean flip, boolean resetExifOrientation) {
		File r = null;

		if (!GalleryFileFilter.canManipulateJpeg(filename)) {
			Log.log(Log.LEVEL_TRACE, MODULE, "jpegtran doesn't support rotating anything but jpeg");
			return new File(filename);
		}

		if (useJpegtran) {
			File orig = null;
			File dest = null;
			try {
				if (GalleryRemote.IS_MAC_OS_X) {
					orig = new File(filename);
					dest = deterministicTempFile("tmp"
							, "." + GalleryFileFilter.getExtension(filename), tmpDir, filename + angle + flip);

					orig.renameTo(dest);
					filename = dest.getPath();
				}

				if (flip) {
					r = jpegtranExec(filename, "-flip", "horizontal", false);
					filename = r.getPath();
				}

				if (angle != 0) {
					r = jpegtranExec(filename, "-rotate", "" + (angle * 90), false);
				}

				/*if (resetExifOrientation) {
				resetExifOrientation(filename);
				}*/
			} finally {
				if (orig != null && dest != null) {
					dest.renameTo(orig);
				}
			}
		}

		if (!useJpegtran && r == null) {
			throw new UnsupportedOperationException("jpegtran must be installed for this operation");
		}

		return r;
	}

	public static File losslessCrop(String filename, Rectangle cropTo) {
		File r = null;

		if (!GalleryFileFilter.canManipulateJpeg(filename)) {
			throw new UnsupportedOperationException("jpegtran doesn't support cropping anything but jpeg");
		}

		if (useJpegtran) {
			File orig = null;
			File dest = null;
			try {
				if (GalleryRemote.IS_MAC_OS_X) {
					orig = new File(filename);
					dest = deterministicTempFile("tmp"
							, "." + GalleryFileFilter.getExtension(filename), tmpDir, filename + cropTo);

					orig.renameTo(dest);
					filename = dest.getPath();
				}

				r = jpegtranExec(filename, "-crop", cropTo.width + "x" + cropTo.height + "+" +
						cropTo.x + "+" + cropTo.y, true);
			} finally {
				if (orig != null && dest != null) {
					dest.renameTo(orig);
				}
			}
		}

		if (!useJpegtran && r == null) {
			throw new UnsupportedOperationException("jpegtran with CROP PATCH must be installed for this operation");
		}

		return r;
	}

	private static File jpegtranExec(String filename, String arg1, String arg2, boolean crop) {
		File r;
		ArrayList cmd = new ArrayList();
		cmd.add(jpegtranPath);

		cmd.add("-copy");
		cmd.add("all");
		//cmd.add("-debug");

		cmd.add(arg1);
		cmd.add(arg2);

		r = deterministicTempFile(crop?"crop":"rot"
				, "." + GalleryFileFilter.getExtension(filename), tmpDir, filename + arg1 + arg2);
		toDelete.add(r);

		cmd.add("-outfile");
		cmd.add(r.getPath());

		cmd.add(filename);

		int exitValue = exec((String[]) cmd.toArray(new String[0]));

		if ((exitValue != 0 && !jpegtranIgnoreErrorCode) || ! r.exists()) {
			if (exitValue != -1 && ! r.exists()) {
				// don't kill jpegtran if it's just an InterruptedException
				if (crop) {
					Log.log(Log.LEVEL_CRITICAL, MODULE, "jpegtran doesn't seem to be working for cropping. Disabling");
					stopUsingJpegtranCrop();
				} else {
					Log.log(Log.LEVEL_CRITICAL, MODULE, "jpegtran doesn't seem to be working. Disabling");
					stopUsingJpegtran();
				}
			}

			r = null;
		}

		return r;
	}

	public static Image rotateImage(Image thumb, int angle, boolean flipped, Component c) {
		if (angle != 0 || flipped) {
			int width;
			int height;
			int width1;
			int height1;

			width = thumb.getWidth(c);
			height = thumb.getHeight(c);

			if (angle % 2 == 0) {
				width1 = width;
				height1 = height;
			} else {
				width1 = height;
				height1 = width;
			}

			BufferedImage vImg = (BufferedImage) c.createImage(width1, height1);

			Graphics2D g = (Graphics2D) vImg.getGraphics();

			AffineTransform transform = getRotationTransform(width, height, angle, flipped);

			g.drawImage(thumb, transform, c);

			thumb = vImg;
		}

		return thumb;
	}

	public static AffineTransform getRotationTransform(int width, int height, int angle, boolean flipped) {
		if (angle != 0 || flipped) {
			int width1;
			int height1;

			if (angle % 2 == 0) {
				width1 = width;
				height1 = height;
			} else {
				width1 = height;
				height1 = width;
			}

			AffineTransform transform = AffineTransform.getTranslateInstance(width / 2, height / 2);
			if (angle != 0) {
				transform.rotate(angle * Math.PI / 2);
			}
			if (flipped) {
				transform.scale(-1, 1);
			}
			transform.translate(-width1 / 2 - (angle == 3 ? width - width1 : 0) + (flipped ? width - width1 : 0) * (angle == 1 ? -1 : 1),
					-height1 / 2 - (angle == 1 ? height - height1 : 0));

			return transform;
		}

		return null;
	}

	public static AffineTransform createTransform(Rectangle container,
												  Rectangle contentResized,
												  Dimension content,
												  int angle, boolean flipped) {
		double scale = Math.sqrt(1.0F * content.width * content.height / contentResized.width / contentResized.height);

		AffineTransform transform = new AffineTransform();
		transform.translate(content.width / 2, content.height / 2);

		if (flipped) {
			transform.scale(-scale, scale);
		} else {
			transform.scale(scale, scale);
		}

		if (angle != 0) {
			transform.rotate(-angle * Math.PI / 2);
		}

		transform.translate(-container.width / 2, -container.height / 2);

		return transform;
	}

	public static LocalInfo getLocalFilenameForPicture(Picture p, boolean full) {
		URL u;
		Dimension d;

		if (!full && p.getSizeResized() == null) {
			// no resized version
			return null;
		}

		if (full) {
			u = p.safeGetUrlFull();
			d = p.safeGetSizeFull();
		} else {
			u = p.getUrlResized();
			d = p.getSizeResized();
		}

		String uid = p.getUniqueId();
		String ext = p.getForceExtension();

		if (uid == null || ext == null) {
			uid = u.getPath();

			int i = uid.lastIndexOf('/');
			uid = uid.substring(i + 1);

			i = uid.lastIndexOf('.');
			ext = uid.substring(i + 1);
			uid = uid.substring(0, i);
		}

		String filename = uid + "." + ext;

		return new LocalInfo(ext, filename,
				deterministicTempFile("server", "." + ext, tmpDir, uid + d), u, d);
	}

	static class LocalInfo {
		String ext;
		String filename;
		File file;
		URL url;
		Dimension size;

		public LocalInfo(String ext, String filename, File file, URL url, Dimension size) {
			this.ext = ext;
			this.filename = filename;
			this.file = file;
			this.url = url;
			this.size = size;
		}
	}

	public static File download(Picture p, Dimension d, StatusUpdate su, CancellableTransferListener tl) {
		if (!p.isOnline()) {
			return p.getSource();
		}

		URL pictureUrl;
		File f;
		String filename;
		LocalInfo fullInfo = getLocalFilenameForPicture(p, true);
		boolean stop = false;

		if (p.getSizeResized() != null) {
			LocalInfo resizedInfo = getLocalFilenameForPicture(p, false);

			if ((d.width > p.getSizeResized().width || d.height > p.getSizeResized().height
					|| fullInfo.file.exists()) && ! GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SLIDESHOW_LOWREZ)) {
				pictureUrl = fullInfo.url;
				//pictureDimension = p.getSizeFull();
				f = fullInfo.file;
				filename = fullInfo.filename;
			} else {
				pictureUrl = resizedInfo.url;
				//pictureDimension = p.getSizeResized();
				f = resizedInfo.file;
				filename = resizedInfo.filename;
			}
		} else {
			pictureUrl = fullInfo.url;
			//pictureDimension = p.getSizeFull();
			f = fullInfo.file;
			filename = fullInfo.filename;
		}

		Log.log(Log.LEVEL_TRACE, MODULE, "Going to download " + pictureUrl);

		try {
			synchronized(p) {
				// don't download the same picture twice
				if (f.exists()) {
					Log.log(Log.LEVEL_TRACE, MODULE, filename + " already existed: no need to download it again");
					return f;
				}

				long start = System.currentTimeMillis();

				URLConnection conn = openUrlConnection(pictureUrl, p);

				int size = conn.getContentLength();

				su.startProgress(StatusUpdate.LEVEL_BACKGROUND, 0, size,
						GRI18n.getString(MODULE, "down.start", new Object[]{filename}), false);

				Log.log(Log.LEVEL_TRACE, MODULE, "Saving " + p + " to " + f.getPath());

				BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
				BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f));

				byte[] buffer = new byte[2048];
				int l;
				int dl = 0;
				long t = -1;
				while (!stop && (l = in.read(buffer)) != -1) {
					out.write(buffer, 0, l);
					dl += l;

					long now = System.currentTimeMillis();
					if (t != -1 && now - t > 1000) {
						su.updateProgressValue(StatusUpdate.LEVEL_BACKGROUND, dl);
						int speed = (int) (dl / (now - start) * 1000 / 1024);
						su.updateProgressStatus(StatusUpdate.LEVEL_BACKGROUND,
								GRI18n.getString(MODULE, "down.progress",
										new Object[]{filename, new Integer(dl / 1024), new Integer(size / 1024), new Integer(speed)}));

						t = now;
					}

					if (t == -1) {
						t = now;
					}

					if (tl != null) {
						stop = !tl.dataTransferred(dl, size, 0, p);
					}
				}

				in.close();
				out.flush();
				out.close();

				if (stop) {
					Log.log(Log.LEVEL_TRACE, MODULE, "Stopped downloading " + p);
					f.delete();
				} else {
					Log.log(Log.LEVEL_TRACE, MODULE, "Downloaded " + p + " (" + dl + ") in " + ((System.currentTimeMillis() - start) / 1000) + "s");
					toDelete.add(f);
				}
			}

			su.stopProgress(StatusUpdate.LEVEL_BACKGROUND,
					GRI18n.getString(MODULE, "down.end", new Object[]{filename}));
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			f = null;

			su.stopProgress(StatusUpdate.LEVEL_BACKGROUND, "Downloading " + p + " failed");
		}

		if (stop) {
			return null;
		}

		return f;
	}

	public static URLConnection openUrlConnection(URL pictureUrl, Picture p) throws IOException {
		URLConnection conn = pictureUrl.openConnection();
		conn.setDefaultUseCaches(false);
		//conn.addRequestProperty("Connection", "Dont-keep-alive");
		String userAgent = p.getAlbumOnServer().getGallery().getUserAgent();
		if (userAgent != null) {
			conn.setRequestProperty("User-Agent", userAgent);
		}
		conn.addRequestProperty("Referer", p.getAlbumOnServer().getGallery().getUrl().toString());
//		Cookie[] cookies = CookieModule.listAllCookies();
//		for (int i = 0; i < cookies.length; i++) {
//			conn.addRequestProperty("Cookie", cookies[i].toString());
//		}

		conn.connect();
		return conn;
	}

	public static Dimension getPictureDimension(Picture p) {
		if (p.isOnline()) {
			// can't find out size without downloading
			return null;
		}

		ImageInputStream iis;
		try {
			iis = ImageIO.createImageInputStream(p.getSource());

			Iterator iter = ImageIO.getImageReaders(iis);
			if (!iter.hasNext()) {
				return null;
			}

			ImageReader reader = (ImageReader)iter.next();
			reader.setInput(iis, true, false);
			Dimension d = new Dimension(reader.getWidth(0), reader.getHeight(0));

			iis.close();
			reader.dispose();
			return d;
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return null;
		}
	}

	public static ArrayList importApertureSelection() {
		File script = new File(System.getProperty("java.io.tmpdir"), APERTURE_TO_GALLERY_SCPT);

		if (!script.exists()) {
			// copy the script file from our bundle to /tmp
			InputStream scriptResource = GalleryRemote.class.getResourceAsStream("/" + APERTURE_TO_GALLERY_SCPT);
			if (scriptResource == null) {
				try {
					scriptResource = new FileInputStream(APERTURE_TO_GALLERY_SCPT);
				} catch (FileNotFoundException e) {
					Log.logException(Log.LEVEL_ERROR, MODULE, e);
				}

				if (scriptResource == null) {
					Log.log(Log.LEVEL_ERROR, MODULE, "Can't find " + APERTURE_TO_GALLERY_SCPT);
					return null;
				}
			}
			
			BufferedReader scriptInStream = new BufferedReader(new InputStreamReader(
					scriptResource));
			BufferedWriter scriptOutStream = null;

			try {
				String line;
				scriptOutStream = new BufferedWriter(new FileWriter(script));
				while ((line = scriptInStream.readLine()) != null) {
					scriptOutStream.write(line);
					scriptOutStream.write("\n");
				}
			} catch (IOException e) {
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
			} finally {
				try {
					scriptInStream.close();
					if (scriptOutStream != null) {
						scriptOutStream.close();
					}
				} catch (IOException f) {}
			}

			toDelete.add(script);
		}

		// run script
		if (exec("osascript " + script.getAbsolutePath()) != 0) {
			return null;
		}

		// load results
		File resultFile = new File(System.getProperty("java.io.tmpdir"), "ApertureToGallery.txt");

		if (!resultFile.exists()) {
			resultFile = new File("/tmp/ApertureToGallery.txt");
		}
		
		ArrayList resultList = new ArrayList();

		if (!resultFile.exists()) {
			return null;
		} else {
			BufferedReader resultReader = null;
			try {
				resultReader = new BufferedReader(new FileReader(resultFile));
				String line = null;

				while ((line = resultReader.readLine()) != null) {
					resultList.add(line);
				}
			} catch (IOException e) {
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
			} finally {
				if (resultReader != null) {
					try {
						resultReader.close();
						resultFile.delete();
					} catch (IOException e) {
					}
				}
			}
		}

		return resultList;
	}

	public static void addToDelete(File f) {
		toDelete.add(f);
	}

	static {
		tmpDir = new File(System.getProperty("java.io.tmpdir"), "thumbs-" + System.getProperty("user.name"));

		if (!tmpDir.exists()) {
			tmpDir.mkdirs();
		}

		Log.log(Log.LEVEL_INFO, MODULE, "tmpDir: " + tmpDir.getPath());

		// Making sure ImageMagick works
		try {
			PropertiesFile pt = null;

			if (new File("imagemagick/im.properties").exists()) {
				pt = new PropertiesFile("imagemagick/im");
			} else if (new File("im.properties").exists()) {
				pt = new PropertiesFile("im");
			}

			GalleryProperties p = null;

			if (pt != null) {
				pt.read();

				// allow overriding from main property file
				p = new GalleryProperties(pt);
				GalleryRemote._().defaults.copyProperties(p);
			} else {
				p = GalleryRemote._().defaults;
			}

			p = GalleryRemote._().properties;

			useIM = p.getBooleanProperty("im.enabled");
			Log.log(Log.LEVEL_INFO, MODULE, "im.enabled: " + useIM);
			if (useIM) {
				imPath = p.getProperty("im.convertPath");
				Log.log(Log.LEVEL_INFO, MODULE, "im.convertPath: " + imPath);

				imIgnoreErrorCode = p.getBooleanProperty("im.ignoreErrorCode", imIgnoreErrorCode);
				Log.log(Log.LEVEL_INFO, MODULE, "im.ignoreErrorCode: " + imIgnoreErrorCode);

				if (imPath.indexOf('/') == -1 && imPath.indexOf('\\') == -1) {
					// unqualified path, let's investigate

					if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
					// we're on Windows with an abbreviated path: look up IM in the registry

						StringBuffer output = new StringBuffer();
						int retval = exec("reg query HKLM\\Software\\ImageMagick\\Current /v BinPath", output);

						if (retval == 0) {
							Pattern pat = Pattern.compile("^\\s*BinPath\\s*REG_SZ\\s*(.*)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
							//Pattern pat = Pattern.compile("BinPath", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
							Matcher m = pat.matcher(output.toString());
							if (m.find()) {
								imPath = m.group(1) + "\\" + imPath;

								if (!imPath.endsWith(".exe")) {
									imPath += ".exe";
								}

								Log.log(Log.LEVEL_INFO, MODULE, "Found ImageMagick in registry. imPath is now " + imPath);
							}
						} else {
							// most likely, we don't have reg.exe, try regedit.exe

							File tempFile = File.createTempFile("gr_regdump", null);

							retval = exec("regedit /E \"" + tempFile.getPath() + "\" \"HKEY_LOCAL_MACHINE\\Software\\ImageMagick\\Current\"", output);

							if (retval == 0) {
								BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(tempFile), "UTF-16"));
								String line;
								Pattern pat = Pattern.compile("^\\\"BinPath\\\"=\\\"(.*)\\\"", Pattern.CASE_INSENSITIVE);
								while ((line = br.readLine()) != null) {
									Matcher m = pat.matcher(line);
									if (m.find()) {
										imPath = m.group(1) + "\\" + imPath;

										if (!imPath.endsWith(".exe")) {
											imPath += ".exe";
										}

										Log.log(Log.LEVEL_INFO, MODULE, "Found ImageMagick in registry. imPath is now " + imPath);

										break;
									}
								}

								br.close();
							}

							tempFile.delete();
						}
					}

					// try to validate that IM works
					int exitValue = exec(new String[] {imPath, "-version"});

					if (exitValue == -2) {
						Log.log(Log.LEVEL_CRITICAL, MODULE, "Can't find ImageMagick in the system Path. Disabling");
						stopUsingIM();
					}
				} else if (!new File(imPath).exists()) {
					Log.log(Log.LEVEL_CRITICAL, MODULE, "Can't find ImageMagick Convert at the above path. Disabling");
					stopUsingIM();
				}
			}

			if (useIM) {
				filterName[THUMB] = p.getProperty("im.thumbnailResizeFilter");
				filterName[PREVIEW] = p.getProperty("im.previewResizeFilter");
				filterName[UPLOAD] = p.getProperty("im.uploadResizeFilter");
				if (filterName[UPLOAD] == null) {
					filterName[UPLOAD] = filterName[PREVIEW];
				}

				format[THUMB] = p.getProperty("im.thumbnailResizeFormat", "gif");
				format[PREVIEW] = p.getProperty("im.previewResizeFormat", "jpg");
				format[UPLOAD] = null;
			}

			// read quality even if useIM is false for java-based resize
			jpegQuality = p.getIntProperty("im.jpegQuality", jpegQuality);
		} catch (Exception e) {
			Log.logException(Log.LEVEL_CRITICAL, MODULE, e);
			stopUsingIM();
		}

		defaultThumbnail = loadJava(ImageUtils.class.getResource(DEFAULT_RESOURCE),
				GalleryRemote._().properties.getThumbnailSize(), true);

		unrecognizedThumbnail = loadJava(ImageUtils.class.getResource(UNRECOGNIZED_RESOURCE),
				GalleryRemote._().properties.getThumbnailSize(), true);

		// Making sure jpegtran works
		try {
			PropertiesFile pt = null;

			if (new File("jpegtran/jpegtran.properties").exists()) {
				pt = new PropertiesFile("jpegtran/jpegtran");
			} else if (new File("jpegtran.properties").exists()) {
				pt = new PropertiesFile("jpegtran");
			}

			GalleryProperties p = null;

			if (pt != null) {
				pt.read();

				// allow overriding from main property file
				p = new GalleryProperties(pt);
				GalleryRemote._().defaults.copyProperties(p);
			} else {
				p = GalleryRemote._().defaults;
			}
			
			p = GalleryRemote._().properties;

			useJpegtran = p.getBooleanProperty("jp.enabled");
			useJpegtranCrop = p.getBooleanProperty("jp.crop.enabled");
			Log.log(Log.LEVEL_INFO, MODULE, "jp.enabled: " + useJpegtran);
			if (useJpegtran) {
				jpegtranPath = p.getProperty("jp.path");
				Log.log(Log.LEVEL_INFO, MODULE, "jp.path: " + jpegtranPath);

				if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1
						&& !jpegtranPath.endsWith(".exe")) {
					jpegtranPath += ".exe";
				}

				jpegtranIgnoreErrorCode = p.getBooleanProperty("jp.ignoreErrorCode", jpegtranIgnoreErrorCode);
				Log.log(Log.LEVEL_INFO, MODULE, "jp.ignoreErrorCode: " + jpegtranIgnoreErrorCode);

				if (jpegtranPath.indexOf('/') == -1 && jpegtranPath.indexOf('\\') == -1) {
					Log.log(Log.LEVEL_CRITICAL, MODULE, "jpegtran path is not fully qualified, " +
							"presence won't be tested until later");
				} else if (!new File(jpegtranPath).exists()) {
					Log.log(Log.LEVEL_CRITICAL, MODULE, "Can't find jpegtran at the above path");
					stopUsingJpegtran();
				}
			}
		} catch (Exception e) {
			Log.logException(Log.LEVEL_CRITICAL, MODULE, e);
			stopUsingJpegtran();
		}
	}


	public static void purgeTemp() {
		for (Iterator it = toDelete.iterator(); it.hasNext();) {
			File file = (File) it.next();
			file.delete();
		}
	}

	public static Dimension getSizeKeepRatio(Dimension source, Dimension target, boolean noStretch) {
		if (noStretch && target.width > source.width && target.height > source.height) {
			return null;
		}

		Dimension result = new Dimension();

		float sourceRatio = Math.abs((float) source.width / source.height);
		float targetRatio = Math.abs((float) target.width / target.height);

		if (Math.abs(targetRatio) > Math.abs(sourceRatio)) {
			result.height = target.height;
			result.width = (int) (target.height * sourceRatio *
					(target.height * target.width > 0?1:-1));
		} else {
			result.width = target.width;
			result.height = (int) (target.width / sourceRatio *
					(target.height * target.width > 0?1:-1));
		}

		return result;
	}

	public static float getRatio(Dimension source, Dimension target) {
		float widthRatio = (float) target.width / source.width;
		float heightRatio = (float) target.height / source.height;

		if (heightRatio > widthRatio) {
			return widthRatio;
		} else {
			return heightRatio;
		}
	}

	public static class AngleFlip {
		public int angle = 0;
		public boolean flip = false;

		public AngleFlip(int angle, boolean flip) {
			this.angle = angle;
			this.flip = flip;
		}
	}

	public static File deterministicTempFile(String prefix, String suffix, File directory, String hash) {
		if (directory == null) {
			directory = new File(System.getProperty("java.io.tmpdir"));
		}

		return new File(directory, prefix + hash.hashCode() + suffix);
	}


	public static ExifData getExifData(String filename) {
		try {
			Class c = GalleryRemote.secureClassForName("com.gallery.GalleryRemote.util.ExifImageUtils");
			Method m = c.getMethod("getExifData", new Class[]{String.class});
			return (ExifData) m.invoke(null, new Object[]{filename});
		} catch (Throwable e) {
			Log.log(Log.LEVEL_ERROR, MODULE, "Exif library is not installed.");
			//Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return null;
		}
	}

	/*public static ImageUtils.AngleFlip getExifTargetOrientation(String filename) {
	try {
	Class c = Class.forName("com.gallery.GalleryRemote.util.ExifImageUtils");
	Method m = c.getMethod("getExifTargetOrientation", new Class[]{String.class});
	return (AngleFlip) m.invoke(null, new Object[]{filename});
	} catch (Throwable e) {
	Log.log(Log.LEVEL_TRACE, MODULE, "Exif library is not installed.");
	return null;
	}
	}

	public static Date getExifDateCreated(String filename) {
	try {
	Class c = Class.forName("com.gallery.GalleryRemote.util.ExifImageUtils");
	Method m = c.getMethod("getExifDateCreated", new Class[]{String.class});
	return (Date) m.invoke(null, new Object[]{filename});
	} catch (Throwable e) {
	Log.log(Log.LEVEL_TRACE, MODULE, "Exif library is not installed.");
	return null;
	}
	}*/

	static Boolean exifAvailable = null;
	public static boolean isExifAvailable() {
		if (exifAvailable == null) {
			try {
				Class c = GalleryRemote.secureClassForName("com.gallery.GalleryRemote.util.ExifImageUtils");
				c.getMethod("getExifData", new Class[]{String.class});
				exifAvailable = Boolean.TRUE;
			} catch (Throwable e) {
				Log.log(Log.LEVEL_ERROR, MODULE, "Exif library is not installed.");
				//Log.logException(Log.LEVEL_ERROR, MODULE, e);
				exifAvailable = Boolean.FALSE;
			}
		}

		return exifAvailable;
	}

	/* ********* Utilities ********** */
	public static List<File> expandDirectories(List<File> filesAndFolders)
			throws IOException {
		ArrayList<File> allFilesList = new ArrayList<File>();

		for (File f : filesAndFolders) {
			if (f.isDirectory()) {
				allFilesList.addAll(listFilesRecursive(f));
			} else {
				allFilesList.add(f);
			}
		}

		return allFilesList;
	}

	public static List<File> listFilesRecursive(File dir)
			throws IOException {
		ArrayList<File> ret = new ArrayList<File>();

		/* File.listFiles: stupid call returns null if there's an
				i/o exception *or* if the file is not a directory, making a mess.
				http://java.sun.com/j2se/1.4/docs/api/java/io/File.html#listFiles() */
		File[] fileArray = dir.listFiles();
		if (fileArray == null) {
			if (dir.isDirectory()) {
				/* convert to exception */
				throw new IOException("i/o exception listing directory: " + dir.getPath());
			} else {
				/* this method should only be called on a directory */
				Log.log(Log.LEVEL_CRITICAL, MODULE, "assertion failed: listFilesRecursive called on a non-dir file");
				return ret;
			}
		}

		List<File> files = Arrays.asList(fileArray);

		for (File f : files) {
			if (f.isDirectory()) {
				ret.addAll(listFilesRecursive(f));
			} else {
				ret.add(f);
			}
		}

		return ret;
	}

	public static int exec(String cmdline) {
		return exec(cmdline, null);
	}

	public static int exec(String cmdline, StringBuffer output) {
		Log.log(Log.LEVEL_TRACE, MODULE, "Executing " + cmdline);

		try {
			Process p = Runtime.getRuntime().exec(cmdline);

			return pumpExec(p, output);
		} catch (InterruptedException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return -1;
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
		}

		return 1;
	}

	public static int exec(String[] cmd) {
		return exec(cmd, null);
	}

	public static int exec(String[] cmd, StringBuffer output) {
		Log.log(Log.LEVEL_TRACE, MODULE, "Executing " + Arrays.asList(cmd));

		try {
			Process p = Runtime.getRuntime().exec(cmd);

			return pumpExec(p, output);
		} catch (InterruptedException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return -1;
		} catch (IOException e) {
			Log.logException(Log.LEVEL_ERROR, MODULE, e);
			return -2;
		}
	}

	private static int pumpExec(Process p, StringBuffer output) throws InterruptedException, IOException {
		DataInputStream out = new DataInputStream(new BufferedInputStream(p.getInputStream()));
		DataInputStream err = new DataInputStream(new BufferedInputStream(p.getErrorStream()));

		int exitValue = p.waitFor();

		String line;
		while ((line = out.readLine()) != null) {
			Log.log(Log.LEVEL_TRACE, MODULE, "Out: " + line);
			if (output != null) {
				output.append(line).append("\n");
			}
		}

		while ((line = err.readLine()) != null) {
			Log.log(Log.LEVEL_TRACE, MODULE, "Err: " + line);
		}

		Log.log(Log.LEVEL_TRACE, MODULE, "Returned with value " + exitValue);

		return exitValue;
	}

	public static void deferredTasks() {
		if (deferredStopUsingIM) {
			deferredStopUsingIM = false;

			stopUsingIM();
		}

		if (deferredStopUsingJpegtran) {
			deferredStopUsingJpegtran = false;

			stopUsingJpegtran();
		}

		if (deferredStopUsingJpegtranCrop) {
			deferredStopUsingJpegtranCrop = false;

			stopUsingJpegtranCrop();
		}

		if (deferredOutOfMemory) {
			deferredOutOfMemory = false;

			outOfMemoryError();
		}
	}

	static void stopUsingIM() {
		useIM = false;

		if (!GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SUPPRESS_WARNING_IM, false)) {
			if (GalleryRemote._().getMainFrame() != null
					&& GalleryRemote._().getMainFrame().isVisible()) {
				UrlMessageDialog md = new UrlMessageDialog(
						GRI18n.getString(MODULE, "warningTextIM"),
						GRI18n.getString(MODULE, "warningUrlIM"),
						GRI18n.getString(MODULE, "warningUrlTextIM")
				);

				if (md.dontShow()) {
					GalleryRemote._().properties.setBooleanProperty(PreferenceNames.SUPPRESS_WARNING_IM, true);
				}
			} else {
				deferredStopUsingIM = true;
			}
		}
	}

	static boolean stopUsingJavaResize() {
		UrlMessageDialog md = new UrlMessageDialog(
				GRI18n.getString(MODULE, "warningTextJava"),
				null,
				null,
				GRI18n.getString(MODULE, "useJava"),
				GRI18n.getString(MODULE, "dontUseJava")
		);

		boolean useJavaResize = (md.getButtonChosen() == 1);

		if (md.dontShow()) {
			GalleryRemote._().properties.setBooleanProperty(PreferenceNames.SUPPRESS_WARNING_JAVA, true);
			GalleryRemote._().properties.setBooleanProperty(PreferenceNames.USE_JAVA_RESIZE, useJavaResize);
		}

		return !useJavaResize;
	}

	static void stopUsingJpegtran() {
		useJpegtran = false;

		if (!GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SUPPRESS_WARNING_JPEGTRAN, false)) {
			if (GalleryRemote._().getMainFrame() != null
					&& GalleryRemote._().getMainFrame().isVisible()) {
				UrlMessageDialog md = new UrlMessageDialog(
						GRI18n.getString(MODULE, "warningTextJpegtran"),
						GRI18n.getString(MODULE, "warningUrlJpegtran"),
						GRI18n.getString(MODULE, "warningUrlTextJpegtran")
				);

				if (md.dontShow()) {
					GalleryRemote._().properties.setBooleanProperty(PreferenceNames.SUPPRESS_WARNING_JPEGTRAN, true);
				}
			} else {
				deferredStopUsingJpegtran = true;
			}
		}
	}

	static void stopUsingJpegtranCrop() {
		useJpegtranCrop = false;

		if (!GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SUPPRESS_WARNING_JPEGTRAN_CROP, false)) {
			if (GalleryRemote._().getMainFrame() != null
					&& GalleryRemote._().getMainFrame().isVisible()) {
				UrlMessageDialog md = new UrlMessageDialog(
						GRI18n.getString(MODULE, "warningTextJpegtranCrop"),
						GRI18n.getString(MODULE, "warningUrlJpegtranCrop"),
						GRI18n.getString(MODULE, "warningUrlTextJpegtranCrop")
				);

				if (md.dontShow()) {
					GalleryRemote._().properties.setBooleanProperty(PreferenceNames.SUPPRESS_WARNING_JPEGTRAN_CROP, true);
				}
			} else {
				deferredStopUsingJpegtranCrop = true;
			}
		}
	}

	static void outOfMemoryError() {
		if (!GalleryRemote._().properties.getBooleanProperty(PreferenceNames.SUPPRESS_WARNING_OUT_OF_MEMORY, false)) {
			if (GalleryRemote._().getMainFrame() != null
					&& GalleryRemote._().getMainFrame().isVisible()) {
				UrlMessageDialog md = new UrlMessageDialog(
						GRI18n.getString(MODULE, "warningTextOutOfMemory"),
						GRI18n.getString(MODULE, "warningUrlOutOfMemory"),
						GRI18n.getString(MODULE, "warningUrlTextOutOfMemory")
				);

				if (md.dontShow()) {
					GalleryRemote._().properties.setBooleanProperty(PreferenceNames.SUPPRESS_WARNING_OUT_OF_MEMORY, true);
				}
			} else {
				deferredOutOfMemory = true;
			}
		}
	}

	public static void displayMetadata(Node root) {
		displayMetadata(root, 0);
	}

	static void indent(int level) {
		for (int i = 0; i < level; i++) {
			System.out.print("  ");
		}
	}

	static void displayMetadata(Node node, int level) {
		indent(level); // emit open tag
		System.out.print("<" + node.getNodeName());
		NamedNodeMap map = node.getAttributes();
		if (map != null) { // print attribute values
			int length = map.getLength();
			for (int i = 0; i < length; i++) {
				Node attr = map.item(i);
				System.out.print(" " + attr.getNodeName() +
						"=\"" + attr.getNodeValue() + "\"");
			}
		}

		Node child = node.getFirstChild();
		if (child != null) {
			System.out.println(">"); // close current tag
			while (child != null) { // emit child tags recursively
				displayMetadata(child, level + 1);
				child = child.getNextSibling();
			}
			indent(level); // emit close tag
			System.out.println("</" + node.getNodeName() + ">");
		} else {
			System.out.println("/>");
		}
	}

	public static BufferedImage createResizedCopy(Image originalImage,
	                                int scaledWidth, int scaledHeight, Object hint)
	{
		BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_ARGB);

		Graphics2D g = scaledBI.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
		g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
		g.dispose();

		return scaledBI;
	}
} public class OsShutdown {
	public static final String MODULE = "Shutdown";
	public static final String osname = System.getProperty("os.name").toLowerCase();

	public static void shutdown() {
		Method m = getPrivateShutdownMethod();
		
		if (m != null) {
			try {
				m.invoke(null);
			} catch (Throwable e) {
				Log.logException(Log.LEVEL_ERROR, MODULE, e);
			}
		}
	}
	
	private static Method getPrivateShutdownMethod() {
		try {
			Class c = GalleryRemote.secureClassForName("com.gallery.GalleryRemote.util.PrivateShutdown");
			return c.getMethod("shutdown");
		} catch (Throwable e) {
			Log.log(Log.LEVEL_TRACE, MODULE, "Could not load PrivateShutdown, this is expected for the applet");
			return null;
		}
	}

	public static boolean isWindows() {
		return osname.indexOf("windows") != -1;
	}

	public static boolean isWin9x() {
		return isWindows() && (osname.indexOf("9") != -1 || osname.indexOf("me") != -1);
	}

	public static boolean isWinNT() {
		return isWindows() && !isWin9x();
	}

	public static boolean isMacOS() {
		return osname.indexOf("mac") != -1 && osname.indexOf("os") != -1;
	}

	public static boolean isMacOSX() {
		return osname.indexOf("mac os x") != -1;
	}

	public static boolean isLinux() {
		return osname.indexOf("linux") != -1;
	}

	public static boolean isSolaris() {
		return osname.indexOf("solaris") != -1;
	}

	public static boolean isUnix() {
		return isMacOSX() || isLinux() || isSolaris();
	}

	public static boolean canShutdown() {
		return (isWin9x() || isWinNT() || (isUnix() && !isMacOSX())) && getPrivateShutdownMethod() != null;
	}
}
import com.gallery.GalleryRemote.model.Album;
import com.gallery.GalleryRemote.model.Gallery;
import com.gallery.GalleryRemote.model.Picture;
import com.gallery.GalleryRemote.prefs.PreferencesDialog;
import com.gallery.GalleryRemote.prefs.PropertiesFile;
import com.gallery.GalleryRemote.prefs.URLPanel;
import com.gallery.GalleryRemote.prefs.PreferenceNames;
import com.gallery.GalleryRemote.util.GRI18n;
import com.gallery.GalleryRemote.util.ImageUtils;
import com.gallery.GalleryRemote.util.OsShutdown;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.applet.Applet;
        }

        if (JOptionPane.NO_OPTION == response) {
            // No action requested, tell the caller to go ahead
            return (JOptionPane.OK_OPTION);
        }

        // User reneges, stop the action
        return (JOptionPane.CANCEL_OPTION);
    }*/

	// Event handling
	/**
	 * Menu, button and field handling
	 *
	 * @param e Action event
	 */
	public void actionPerformed(ActionEvent e) {
		String command = e.getActionCommand();
		Log.log(Log.LEVEL_INFO, MODULE, "Command selected " + command);
		//Log.log(Log.TRACE, MODULE, "        event " + e );

        // todo: save
		/*String MRUFileName = null;
		if (command.startsWith("File.MRU.")) {
			MRUFileName = command.substring("File.MRU.".length());
			command = "File.Open";
		}*/

		if (command.equals("File.Quit")) {
			thisWindowClosing(null);
		/*} else if (command.equals("File.New")) {
			int response = saveOnPermission("OK_toSaveBeforeClose");

			if (JOptionPane.CANCEL_OPTION == response) {
				return;
			}

			resetState();*/
			// todo: save
		/*} else if (command.equals("File.Open")) {
			openState(MRUFileName);
		} else if (command.equals("File.Save")) {
			// Do Save As if the file is the default
			if (lastOpenedFile == null) {
				saveAsState();
			} else {
				saveState();
			}
		} else if (command.equals("File.SaveAs")) {
			saveAsState();*/
		/*} else if (command.equals("File.Close")) {
			int response = saveOnPermission("OK_toSaveBeforeClose");

			if (JOptionPane.CANCEL_OPTION == response) {
				return;
			}

			resetState();*/
		} else if (command.equals("Edit.Cut")) {
			doCut();
		} else if (command.equals("Edit.Copy")) {
			doCopy();
		} else if (command.equals("Edit.Paste")) {
			doPaste();
		} else if (command.equals("Options.Prefs")) {
			showPreferencesDialog();
		} else if (command.equals("Options.ClearCache")) {
			ImageUtils.purgeTemp();
			flushMemory();
		} else if (command.equals("Help.About")) {
			showAboutBox();
		} else if (command.equals("Fetch")) {
			if (getCurrentGallery().hasComm()
					&& getCurrentGallery().getRoot() != null) {
				// todo: save
				// We're currently logged in, but we might be dirty
                // so ask the user if it's OK to log out.
				if (getCurrentGallery().isDirty()) {
					int result = JOptionPane.showConfirmDialog(
							(JFrame) this,
							GRI18n.getString(MODULE, "logoutQuestion", new Object[]{getCurrentGallery()}),
							"Warning",
							JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
					if (result == JOptionPane.NO_OPTION || result == JOptionPane.CLOSED_OPTION) {
						return;
					}
				}

				getCurrentGallery().logOut();

				lastOpenedFile = null;

				resetUIState();

				// We've been logged out, we are now clean.
				//setDirtyFlag(false);
			} else {
				// login may have failed and caused getComm to be null.
				GalleryComm comm = getCurrentGallery().getComm(jStatusBar);

				// may have tried to connect and failed
				if (comm != null && comm.checkAuth()) {
					fetchAlbums();
				}
			}
		} else if (command.equals("NewAlbum")) {
			newAlbum();
		} else if (command.equals("Browse")) {
			browseAddPictures();
		} else if (command.equals("ApertureImport")) {
			importApertureSelection();
		} else if (command.equals("Upload")) {
			uploadPictures();
        } else if (command.equals("SortAlternative")) {
			int sortType = ((SortType) jSortCombo.getSelectedItem()).type;
			setSortType(sortType);
		} else if (command.equals("Sort")) {
			sortPictures();
		} else if (command.equals("NewGallery")) {
			showPreferencesDialog(URLPanel.class.getName());