package charactermanaj.model;

import java.awt.Color;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import charactermanaj.util.ApplicationLogHandler;
import charactermanaj.util.BeanPropertiesUtilities;
import charactermanaj.util.BeanPropertiesUtilities.PropertyAccessor;
import charactermanaj.util.BeanPropertiesUtilities.PropertyAccessorMap;
import charactermanaj.util.ConfigurationDirUtilities;

/**
 * アプリケーションの全域にわたる設定.<br>
 * アプリケーション設定は、クラスパス上のリソース、コートベース直下のappConfig.xml、ユーザーごとのappConfig.xmlの順に読み込まれます.<br>
 * 設定値は{@link BeanPropertiesUtilities}によってXMLプロパティファイルとして永続化されます。<br>
 *
 * @author seraphy
 * @see BeanPropertiesUtilities
 */
public final class AppConfig {

	/**
	 * アプリケーション設定ファイルの名前
	 */
	private static final String CONFIG_NAME = "appConfig.xml";

	/**
	 * 全ユーザー用キャラクターディレクトリのシステムプロパティのキー名.<br>
	 */
	public static final String COMMON_CHARACTER_DIR_PROPERTY_NAME = "character.dir";

	/**
	 * 開発用仕様バージョン番号
	 */
	private static final String DEFAULT_SPECIFICATION_VERSION = "1.0";


	/**
	 * ロガー
	 */
	private static final Logger logger = Logger.getLogger(AppConfig.class.getName());


	/**
	 * シングルトンインスタンス
	 */
	private static final AppConfig singleton = new AppConfig();

	/**
	 * プロパティ変更リスナのサポート
	 */
	private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);


	public void addPropertyChangeListener(PropertyChangeListener listener) {
		propChangeSupport.addPropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(PropertyChangeListener listener) {
		propChangeSupport.removePropertyChangeListener(listener);
	}

	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propChangeSupport.addPropertyChangeListener(propertyName, listener);
	}

	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propChangeSupport.removePropertyChangeListener(propertyName, listener);
	}

	/**
	 * インスタンスを取得する.
	 *
	 * @return インスタンス
	 */
	public static AppConfig getInstance() {
		return singleton;
	}

	/**
	 * プライベートコンストラクタ
	 */
	private AppConfig() {
		loadAppVersions();
	}

	private String implementationVersion;

	private String specificationVersion;

	/**
	 * 実装バージョンを取得する.<br>
	 * ビルドされたjarパッケージからバージョン情報を取得する.<br>
	 * クラスパスの実行からのバージョンは常に「develop」となる.<br>
	 *
	 * @return 実装バージョン
	 */
	public String getImplementationVersion() {
		return implementationVersion;
	}

	/**
	 * 仕様バージョンを取得する.<br>
	 * ビルドされたjarパッケージからバージョン情報を取得する.<br>
	 * クラスパスの実行からのバージョンは常に「develop」となる.<br>
	 *
	 * @return 仕様バージョン
	 */
	public String getSpecificationVersion() {
		return specificationVersion;
	}

	/**
	 * ビルドされたjarパッケージからバージョン情報を取得する.<br>
	 * クラスパスの実行からのバージョンは常に「develop」となる.<br>
	 */
	private void loadAppVersions() {
		Package pak = this.getClass().getPackage();
		String implementationVersion = "develop";
		String specificationVersion = DEFAULT_SPECIFICATION_VERSION;
		if (pak != null) {
			String vInfo = pak.getImplementationVersion();
			if (vInfo != null && implementationVersion.trim().length() > 0) {
				implementationVersion = vInfo.trim();
			}
			String specVInfo = pak.getSpecificationVersion();
			if (specVInfo != null && specVInfo.trim().length() > 0) {
				specificationVersion = specVInfo.trim();
			}
		}

		this.implementationVersion = implementationVersion;
		this.specificationVersion = specificationVersion;
	}

	/**
	 * 設定ファイルのロケール固有版へのファイル末尾の修飾文字列を読み込み順に取得する.
	 * @param locale ロケール、nullの場合はデフォルト
	 * @return ロケールを表すファイル末尾の修飾文字列の読み込み順のリスト
	 */
	private String[] getLocalizedSuffix(Locale locale) {
		if (locale == null) {
			locale = Locale.getDefault();
		}

		String language = locale.getLanguage();
		String country = locale.getCountry();
		String variant = locale.getVariant();

		return new String[] {
			"",
			"_" + language,
			"_" + language + "_" + country,
			"_" + language + "_" + country + "_" + variant,
		};
	}

	/**
	 * 指定されたファイル名の拡張子の前にロケール固有の修飾文字列を付与したリスト作成して返す.
	 * @param base ファイル名
	 * @param locale ロケール、nullの場合はデフォルト
	 * @return ロケールの検索順序でのロケール固有の修飾文字列が付与されたファイルのリスト
	 */
	private List<File> expandLocalizedSuffix(File base, Locale locale) {
		String path = base.getPath();

		int pt = path.lastIndexOf(".");
		String left, right;
		if (pt >= 0) {
			left = path.substring(0, pt);
			right = path.substring(pt);

		} else {
			left = path;
			right = "";
		}

		ArrayList<File> files = new ArrayList<File>();
		for (String suffix : getLocalizedSuffix(locale)) {
			String newPath = left + suffix + right;
			//System.out.println("newpath=" + newPath);
			files.add(new File(newPath));
		}
		return files;
	}

	/**
	 * 設定ファイルの読み込み順序で、読み込むべきURIのリストを返す.<br>
	 * <ul>
	 * <li>(1) リソース上の/appConfig.xml</li>
	 * <li>(2) appConfigFileシステムプロパティで指定されたファイル</li>
	 * <li>(3) コードベース下のappConfig.xml</li>
	 * <li>(4) アプリケーションデータ保存先のappConfig.xml</li>
	 * </ul>
	 * appConfigFileシステムプロパティがある場合は、(1)(2)の順。 <br>
	 * 指定がない場合は、(1)(3)(4)の順に読み取る.<br>
	 *
	 * @return 優先順位での設定ファイルの読み込み先URIのリスト
	 * @throws IOException
	 */
	public List<URI> getCandidateURIs() throws IOException {
		List<URI> uris = new ArrayList<URI>();
		// リソース中の既定 (ロケール識別あり)
		getAppConfigResourceURI(uris);

		// システムプロパティでappConfig.xmlを明示している場合は、それを読み込む。
		// (appConfigFileシステムプロパティが空の場合は、リソース埋め込みの既定の設定だけをよみこむ)
		String specifiedAppConfig = System.getProperty("appConfigFile");
		if (specifiedAppConfig != null) {
			if (specifiedAppConfig.trim().length() > 0) {
				File specifiedAppConfigFile = new File(specifiedAppConfig);
				uris.add(specifiedAppConfigFile.toURI());
			}

		} else {
			// システムプロパティて明示していない場合は、まずコードベースを使用する.(ロケール識別あり)
			File codeBase = ConfigurationDirUtilities.getApplicationBaseDir();
			for (File localizedFile : expandLocalizedSuffix(new File(codeBase,
					CONFIG_NAME).getCanonicalFile(), null)) {
				uris.add(localizedFile.toURI());
			}

			// システムプロパティて明示していない場合は、次にユーザディレクトリを使用する.
			File userDataDir = ConfigurationDirUtilities.getUserDataDir();
			uris.add(new File(userDataDir, CONFIG_NAME).toURI());
		}
		return uris;
	}

	/**
	 * リソース上のAppConfigの読み込みURIを追記して返す
	 * @param uris
	 */
	protected void getAppConfigResourceURI(List<URI> uris) {
		for (File localizedFile : expandLocalizedSuffix(new File(getClass()
				.getResource("/" + CONFIG_NAME).getPath()), null)) {
			uris.add(localizedFile.toURI());
		}
	}

	/**
	 * 保存先の試行順序ごとのファイルのリスト。
	 *
	 * @return 保存先(優先順)
	 */
	public List<File> getPrioritySaveFileList() {
		ArrayList<File> saveFiles = new ArrayList<File>();

		String specifiedAppConfig = System.getProperty("appConfigFile");
		if (specifiedAppConfig != null) {
			// システムプロパティでappConfig.xmlを明示している場合
			if (specifiedAppConfig.trim().length() > 0) {
				File specifiedAppConfigFile = new File(specifiedAppConfig);
				if (!specifiedAppConfigFile.exists()
						|| specifiedAppConfigFile.canWrite()) {
					// まだ存在しないか、書き込み可能である場合のみ候補とする.
					saveFiles.add(specifiedAppConfigFile);
				}
			}
		} else {
			// システムプロパティappConfigFileがなければユーザディレクトリへ書き込む
			// ユーザディレクトリは常に候補とする.
			File userDataDir = ConfigurationDirUtilities.getUserDataDir();
			saveFiles.add(new File(userDataDir, CONFIG_NAME));
		}

		return saveFiles;
	}

	/**
	 * プロパティをロードする.<br>
	 * 存在しないか、読み取りに失敗した場合は、該当ファイルはスキップされる.<br>
	 */
	public void loadConfig() {
		try {
			loadConfig(getCandidateURIs());
		} catch (IOException ex) {
			throw new RuntimeException("appConfig.xml loading failed.", ex);
		}
	}

	/**
	 * プロパティをロードする.<br>
	 * 存在しないか、読み取りに失敗した場合は、該当ファイルはスキップされる.<br>
	 * @param uris パラメーターの読み込み順
	 */
	public void loadConfig(List<URI> uris) {
		if (uris == null) {
			uris = Collections.emptyList();
		}
		Properties config = new Properties();
		try {
			for (URI uri : uris) {
				if (uri == null) {
					continue; // リソースがない場合はnullになる
				}
				// ファイルの実在チェック (チェックできる場合のみ)
				if ("file".equals(uri.getScheme())) {
					File file = new File(uri);
					if (!file.exists()) {
						logger.log(Level.CONFIG, "appConfig.xml is not found.:" + file);
						continue;
					}
				}
				// appConfig.xmlの読み込みを行う.
				// Properties#loadFromXML() はXMLからキーを読み取り、既存のキーに対して上書きする.
				// XMLに存在しないキーは読み込み前のままなので、繰り返し呼び出すことで「重ね合わせ」することができる.
				try {
					URL resourceURL = uri.toURL();
					InputStream is = resourceURL.openStream();
					try {
						config.loadFromXML(is);
						logger.log(Level.CONFIG, "appConfig.xml is loaded.:" + uri);
					} finally {
						is.close();
					}

				} catch (FileNotFoundException ex) {
					logger.log(Level.CONFIG, "appConfig.xml is not found.: " + uri, ex);
					// 無視する (無い場合は十分にありえるので「情報」レベルでログ。)
				} catch (Exception ex) {
					logger.log(Level.WARNING, "appConfig.xml loading failed.: " + uri, ex);
					// 無視する
				}
			}

		} catch (RuntimeException ex) {
			throw new RuntimeException("appConfig.xml loading failed.", ex);
		}
		BeanPropertiesUtilities.loadFromProperties(this, config);
	}

	/**
	 * プロパティをアプリケーションデータの指定した保存先に保存する.
	 *
	 * @throws IOException
	 *             保存に失敗した場合
	 */
	public void saveConfig(List<File> prioritySaveFiles) throws IOException {
		Properties config = getProperties();
		IOException oex = null;
		for (File configStore : prioritySaveFiles) {
			try {
				OutputStream os = new BufferedOutputStream(
						new FileOutputStream(configStore));
				try {
					config.storeToXML(os, CONFIG_NAME, "UTF-8");
					return; // 成功した時点で終了

				} finally {
					os.close();
				}

			} catch (IOException ex) {
				logger.log(Level.WARNING, "アプリケーション設定の保存に失敗しました" + ex, ex);
				oex = ex;
			}
		}

		// 例外が発生していれば、最後の例外を返す.
		if (oex != null) {
			throw oex;
		}
	}

	/**
	 * プロパティをアプリケーションデータの保存先に保存する.
	 *
	 * @throws IOException
	 *             保存に失敗した場合
	 */
	public void saveConfig() throws IOException {
		saveConfig(getPrioritySaveFileList());
	}

	/**
	 * アプリケーション設定値のデフォルト値(リソース上のAppConfigのみ)を取得する
	 * @return
	 */
	public static Map<String, Object> getDefaultProperties() {
		PropertyAccessorMap<AppConfig> accessorMap = BeanPropertiesUtilities.getPropertyAccessorMap(AppConfig.class);

		AppConfig dummy = new AppConfig(); // アプリケーションから参照されないダミーのインスタンスを作成する.

		// リソース上のAppConfigのみ読み込み
		List<URI> uris = new ArrayList<URI>();
		dummy.getAppConfigResourceURI(uris);
		dummy.loadConfig(uris);

		accessorMap.setBean(dummy);

		// 読み込んだプロパティの書き出し
		Map<String, Object> defMap = new HashMap<String, Object>();
		for (Map.Entry<String, PropertyAccessor> propEntry : accessorMap.entrySet()) {
			String name = propEntry.getKey();
			PropertyAccessor accessor = propEntry.getValue();
			Object value = accessor.getValue();
			defMap.put(name,  value);
		}
		return defMap;
	}

	/**
	 * Propertiesの値を設定した場合に設定できない項目があるかチェックする.<br>
	 * このメソッドを呼び出しても、アプリケーション設定自身は何も影響されない.<br>
	 *
	 * @param props
	 *            適用するプロパティ
	 * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される.
	 */
	public static Set<String> checkProperties(Map<String, Object> props) {
		if (props == null) {
			throw new IllegalArgumentException();
		}
		AppConfig dummy = new AppConfig(); // アプリケーションから参照されないダミーのインスタンスを作成する.
		return update(props, dummy);
	}


	/**
	 * Propertiesの値で設定を更新する.<br>
	 *
	 * @param props
	 *            適用するプロパティ
	 * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される.
	 */
	public Set<String> update(Map<String, Object> props) {
		return update(props, this);
	}

	/**
	 * Propertiesの値で設定を更新する.<br>
	 *
	 * @param props
	 *            適用するプロパティ
	 * @param bean
	 *            適用するAppConfigのインスタンス(ドライランと本番の切り替え用)
	 * @return 設定できなかったプロパティキーのコレクション、問題なければ空が返される.
	 */
	private static Set<String> update(Map<String, Object>  props, AppConfig bean) {
		if (props == null) {
			throw new IllegalArgumentException();
		}
		HashSet<String> rejectedNames = new HashSet<String>();

		PropertyAccessorMap<AppConfig> accessorMap = BeanPropertiesUtilities.getPropertyAccessorMap(AppConfig.class);
		accessorMap.setBean(bean);

		for (Map.Entry<String, Object> propEntry : props.entrySet()) {
			String name = propEntry.getKey();
			Object value = propEntry.getValue();

			PropertyAccessor accessor = accessorMap.get(name);
			if (accessor == null) {
				// プロパティがない
				rejectedNames.add(name);
				continue;
			}

			Class<?> propertyType = accessor.getPropertyType();
			if (propertyType.isPrimitive() && value == null) {
				// プリミティブ型なのにnullは入れられない
				rejectedNames.add(name);
				continue;
			}

			try {
				accessor.setValue(value);

			} catch (Exception ex) {
				// 何らかの理由でプロパティの設定に失敗している場合
				rejectedNames.add(name);
				logger.log(Level.WARNING, "invalid propery: " + name + " /val=" + value, ex);
				continue;
			}
		}
		return rejectedNames;
	}

	/**
	 * このアプリケーション設定をプロパティに書き出して返します.<br>
	 *
	 * @return プロパティ
	 */
	public Properties getProperties() {
		Properties config = new Properties();
		BeanPropertiesUtilities.saveToProperties(this, config);
		return config;
	}


	/**
	 * プロファイル選択ダイアログのプロファイルのサンプルイメージの背景色
	 *
	 * @return サンプルイメージの背景色
	 */
	public Color getSampleImageBgColor() {
		return sampleImageBgColor;
	}

	public static final String SAMPLE_IMAGE_BG_COLOR = "sampleImageBgColor";

	public void setSampleImageBgColor(Color sampleImageBgColor) {
		if (sampleImageBgColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.sampleImageBgColor;
		if (old == null ? sampleImageBgColor != null : !old.equals(sampleImageBgColor)) {
			this.sampleImageBgColor = sampleImageBgColor;
			propChangeSupport.firePropertyChange(SAMPLE_IMAGE_BG_COLOR, old, sampleImageBgColor);
		}
	}

	private Color sampleImageBgColor = Color.white;


	/**
	 * デフォルトのイメージ背景色を取得する.
	 *
	 * @return デフォルトのイメージ背景色
	 */
	public Color getDefaultImageBgColor() {
		return defaultImageBgColor;
	}

	public static final String DEFAULT_IMAGE_BG_COLOR = "defaultImageBgColor";

	public void setDefaultImageBgColor(Color defaultImageBgColor) {
		if (defaultImageBgColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.defaultImageBgColor;
		if (old == null ? defaultImageBgColor != null : !old.equals(defaultImageBgColor)) {
			this.defaultImageBgColor = defaultImageBgColor;
			propChangeSupport.firePropertyChange(DEFAULT_IMAGE_BG_COLOR, old, defaultImageBgColor);
		}
	}

	private Color defaultImageBgColor = Color.white;

	/**
	 * 使用中アイテムの背景色を取得する.
	 *
	 * @return 使用中アイテムの背景色
	 */
	public Color getCheckedItemBgColor() {
		return checkedItemBgColor;
	}

	public static final String CHECKED_ITEM_BG_COLOR = "checkedItemBgColor";

	public void setCheckedItemBgColor(Color checkedItemBgColor) {
		if (checkedItemBgColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.checkedItemBgColor;
		if (old == null ? checkedItemBgColor != null : !old.equals(checkedItemBgColor)) {
			this.checkedItemBgColor = checkedItemBgColor;
			propChangeSupport.firePropertyChange(CHECKED_ITEM_BG_COLOR, old, checkedItemBgColor);
		}
	}

	private Color checkedItemBgColor = Color.cyan.brighter();


	/**
	 * 　選択アイテムの背景色を取得する
	 *
	 * @return 選択アイテムの背景色
	 */
	public Color getSelectedItemBgColor() {
		return selectedItemBgColor;
	}

	public static final String SELECTED_ITEM_BG_COLOR = "selectedItemBgColor";

	public void setSelectedItemBgColor(Color selectedItemBgColor) {
		if (selectedItemBgColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.selectedItemBgColor;
		if (old == null ? selectedItemBgColor != null : !old.equals(selectedItemBgColor)) {
			this.selectedItemBgColor = selectedItemBgColor;
			propChangeSupport.firePropertyChange(SELECTED_ITEM_BG_COLOR, old, selectedItemBgColor);
		}
	}

	private Color selectedItemBgColor = Color.orange;

	/**
	 * 不備のあるデータ行の背景色を取得する.
	 *
	 * @return 不備のあるデータ行の背景色
	 */
	public Color getInvalidBgColor() {
		return invalidBgColor;
	}

	public static final String INVALID_BG_COLOR = "invalidBgColor";

	public void setInvalidBgColor(Color invalidBgColor) {
		if (invalidBgColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.invalidBgColor;
		if (old == null ? invalidBgColor != null : !old.equals(invalidBgColor)) {
			this.invalidBgColor = invalidBgColor;
			propChangeSupport.firePropertyChange(INVALID_BG_COLOR, old, invalidBgColor);
		}
	}

	private Color invalidBgColor = Color.red.brighter().brighter();

	/**
	 * JPEG画像変換時の圧縮率を取得する.
	 *
	 * @return 圧縮率
	 */
	public float getCompressionQuality() {
		return compressionQuality;
	}

	public static final String COMPRESSION_QUALITY = "compressionQuality";

	public void setCompressionQuality(float compressionQuality) {
		if (compressionQuality < .1f || compressionQuality > 1f) {
			throw new IllegalArgumentException();
		}
		float old = this.compressionQuality;
		if (old != compressionQuality) {
			this.compressionQuality = compressionQuality;
			propChangeSupport.firePropertyChange(COMPRESSION_QUALITY, old, compressionQuality);
		}
	}

	private float compressionQuality = .8f;

	/**
	 * エクスポートウィザードのプリセットにパーツ不足時の警告色(前景色)を取得する.
	 *
	 * @return エクスポートウィザードのプリセットにパーツ不足時の警告色(前景色)
	 */
	public Color getExportPresetWarningsForegroundColor() {
		return exportPresetWarningsForegroundColor;
	}

	public static final String EXPORT_PRESET_WARNINGS_FOREGROUND_COLOR = "exportPresetWarningsForegroundColor";

	public void setExportPresetWarningsForegroundColor(Color exportPresetWarningsForegroundColor) {
		if (exportPresetWarningsForegroundColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.exportPresetWarningsForegroundColor;
		if (old == null ? exportPresetWarningsForegroundColor != null
				: !old.equals(exportPresetWarningsForegroundColor)) {
			this.exportPresetWarningsForegroundColor = exportPresetWarningsForegroundColor;
			propChangeSupport.firePropertyChange(EXPORT_PRESET_WARNINGS_FOREGROUND_COLOR, old,
					exportPresetWarningsForegroundColor);
		}
	}

	private Color exportPresetWarningsForegroundColor = Color.red;

	/**
	 * JARファイル転送用バッファサイズ.<br>
	 *
	 * @return JARファイル転送用バッファサイズ.
	 */
	public int getJarTransferBufferSize() {
		return jarTransferBufferSize;
	}

	public static final String JAR_TRANSFER_BUFFER_SIZE = "jarTransferBufferSize";

	public void setJarTransferBufferSize(int jarTransferBufferSize) {
		if (jarTransferBufferSize <= 0) {
			throw new IllegalArgumentException();
		}
		int old = this.jarTransferBufferSize;
		if (old != jarTransferBufferSize) {
			this.jarTransferBufferSize = jarTransferBufferSize;
			propChangeSupport.firePropertyChange(JAR_TRANSFER_BUFFER_SIZE, old, jarTransferBufferSize);
		}
	}

	private int jarTransferBufferSize = 4096;

	/**
	 * ZIPファイル名のエンコーディング.<br>
	 *
	 * @return ZIPファイル名のエンコーディング.<br>
	 */
	public String getZipNameEncoding() {
		return zipNameEncoding;
	}

	public static final String ZIP_NAME_ENCODING = "zipNameEncoding";

	public void setZipNameEncoding(String zipNameEncoding) {
		if (zipNameEncoding == null) {
			throw new IllegalArgumentException();
		}
		try {
			Charset.forName(zipNameEncoding);
		} catch (Exception ex) {
			throw new RuntimeException("unsupported charset: " + zipNameEncoding);
		}
		String old = this.zipNameEncoding;
		if (old == null ? zipNameEncoding != null : !old.equals(zipNameEncoding)) {
			this.zipNameEncoding = zipNameEncoding;
			propChangeSupport.firePropertyChange(ZIP_NAME_ENCODING, old, zipNameEncoding);
		}
	}

	private String zipNameEncoding = "csWindows31J";

	/**
	 * ディセーブルなテーブルのセルのフォアグラウンドカラーを取得する.
	 *
	 * @return ディセーブルなテーブルのセルのフォアグラウンドカラー
	 */
	public Color getDisabledCellForgroundColor() {
		return disabledCellForegroundColor;
	}

	public static final String DISABLED_CELL_FOREGROUND_COLOR = "disabledCellForegroundColor";

	public void setDisabledCellForegroundColor(Color disabledCellForegroundColor) {
		if (disabledCellForegroundColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.disabledCellForegroundColor;
		if (old == null ? disabledCellForegroundColor != null : !old.equals(disabledCellForegroundColor)) {
			this.disabledCellForegroundColor = disabledCellForegroundColor;
			propChangeSupport.firePropertyChange(DISABLED_CELL_FOREGROUND_COLOR, old, disabledCellForegroundColor);
		}
	}

	private Color disabledCellForegroundColor = Color.gray;


	/**
	 * ディレクトリを監視する間隔(mSec)を取得する.
	 *
	 * @return ディレクトリを監視する間隔(mSec)
	 */
	public int getDirWatchInterval() {
		return dirWatchInterval;
	}

	public static final String DIR_WATCH_INTERVAL = "dirWatchInterval";

	public void setDirWatchInterval(int dirWatchInterval) {
		if (dirWatchInterval <= 0) {
			throw new IllegalArgumentException();
		}
		int old = this.dirWatchInterval;
		if (old != dirWatchInterval) {
			this.dirWatchInterval = dirWatchInterval;
			propChangeSupport.firePropertyChange(DIR_WATCH_INTERVAL, old, dirWatchInterval);
		}
	}

	private int dirWatchInterval = 7 * 1000;

	/**
	 * ディレクトリの監視を有効にするか?
	 *
	 * @return ディレクトリの監視を有効にする場合はtrue
	 */
	public boolean isEnableDirWatch() {
		return enableDirWatch;
	}

	public static final String ENABLE_DIR_WATCH = "enableDirWatch";

	public void setEnableDirWatch(boolean enableDirWatch) {
		boolean old = this.enableDirWatch;
		if (old != enableDirWatch) {
			this.enableDirWatch = enableDirWatch;
			propChangeSupport.firePropertyChange(ENABLE_DIR_WATCH, old, enableDirWatch);
		}
	}

	private boolean enableDirWatch = true;

	/**
	 * ファイル転送に使うバッファサイズ.<br>
	 *
	 * @return バッファサイズ
	 */
	public int getFileTransferBufferSize() {
		return fileTransferBufferSize;
	}

	public static final String FILE_TRANSFER_BUFFER_SIZE = "fileTransferBufferSize";

	public void setFileTransferBufferSize(int fileTransferBufferSize) {
		if (fileTransferBufferSize <= 0) {
			throw new IllegalArgumentException();
		}
		int old = this.fileTransferBufferSize;
		if (old != fileTransferBufferSize) {
			this.fileTransferBufferSize = fileTransferBufferSize;
			propChangeSupport.firePropertyChange(FILE_TRANSFER_BUFFER_SIZE, old, fileTransferBufferSize);
		}
	}

	private int fileTransferBufferSize = 4096;

	/**
	 * プレビューのインジケータを表示するまでのディレイ(mSec)を取得する.
	 *
	 * @return プレビューのインジケータを表示するまでのディレイ(mSec)
	 */
	public long getPreviewIndicatorDelay() {
		return previewIndeicatorDelay;
	}

	public static final String PREVIEW_INDEICATOR_DELAY = "previewIndeicatorDelay";

	public void setPreviewIndeicatorDelay(long previewIndeicatorDelay) {
		if (previewIndeicatorDelay < 0) {
			throw new IllegalArgumentException();
		}
		long old = this.previewIndeicatorDelay;
		if (old != previewIndeicatorDelay) {
			this.previewIndeicatorDelay = previewIndeicatorDelay;
			propChangeSupport.firePropertyChange(PREVIEW_INDEICATOR_DELAY, old, previewIndeicatorDelay);
		}
	}

	private long previewIndeicatorDelay = 300;

	/**
	 * 情報ダイアログの編集ボタンを「開く」アクションにする場合はtrue、「編集」アクションにする場合はfalse
	 *
	 * @return trueならばOpen、falseならばEdit
	 */
	public boolean isInformationDialogOpenMethod() {
		return informationDialogOpenMethod;
	}

	public static final String INFORMATION_DIALOG_OPEN_METHOD = "informationDialogOpenMethod";

	public void setInformationDialogOpenMethod(
			boolean informationDialogOpenMethod) {
		boolean old = this.informationDialogOpenMethod;
		if (old != informationDialogOpenMethod) {
			this.informationDialogOpenMethod = informationDialogOpenMethod;
			propChangeSupport.firePropertyChange("informationDialogOpenMethod", old, informationDialogOpenMethod);
		}
	}

	private boolean informationDialogOpenMethod = true;

	/**
	 * ログを常に残すか?<br>
	 * falseの場合は{@link ApplicationLogHandler}の実装に従って終了時に 必要なければログは削除される.<br>
	 *
	 * @return 常に残す場合はtrue、そうでなければfalse
	 */
	public boolean isNoRemoveLog() {
		return noRemoveLog;
	}

	public static final String NO_REMOVE_LOG = "noRemoveLog";

	public void setNoRemoveLog(boolean noRemoveLog) {
		boolean old = this.noRemoveLog;
		if (old != noRemoveLog) {
			this.noRemoveLog = noRemoveLog;
			propChangeSupport.firePropertyChange(NO_REMOVE_LOG, old, noRemoveLog);
		}
	}

	private boolean noRemoveLog = false;


	/**
	 * テーブルのグリッド色.<br>
	 *
	 * @return テーブルのグリッド色
	 */
	public Color getGridColor() {
		return gridColor;
	}

	public static final String GRID_COLOR = "gridColor";

	public void setGridColor(Color gridColor) {
		if (gridColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.gridColor;
		if (old == null ? gridColor != null : !old.equals(gridColor)) {
			this.gridColor = gridColor;
			propChangeSupport.firePropertyChange(GRID_COLOR, old, gridColor);
		}
	}

	private Color gridColor = Color.gray;

	/**
	 * カラーダイアログの値が変更されたら、自動的にプレビューを更新するか?
	 *
	 * @return カラーダイアログの値が変更されたら、自動的にプレビューを更新する場合はtrue (デフォルトはtrue)
	 */
	public boolean isEnableAutoColorChange() {
		return enableAutoColorChange;
	}

	public static final String ENABLE_AUTO_COLOR_CHANGE = "enableAutoColorChange";

	public void setEnableAutoColorChange(boolean enableAutoColorChange) {
		boolean old = this.enableAutoColorChange;
		if (old != enableAutoColorChange) {
			this.enableAutoColorChange = enableAutoColorChange;
			propChangeSupport.firePropertyChange(ENABLE_AUTO_COLOR_CHANGE, old, enableAutoColorChange);
		}
	}

	private boolean enableAutoColorChange = true;

	public static final String AUTHOR_EDIT_CONFLICT_BG_COLOR = "authorEditConflictBgColor";

	public void setAuthorEditConflictBgColor(Color authorEditConflictBgColor) {
		if (authorEditConflictBgColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.authorEditConflictBgColor;
		if (old == null ? authorEditConflictBgColor != null : !old.equals(authorEditConflictBgColor)) {
			this.authorEditConflictBgColor = authorEditConflictBgColor;
			propChangeSupport.firePropertyChange(AUTHOR_EDIT_CONFLICT_BG_COLOR, old, authorEditConflictBgColor);
		}
	}

	/**
	 * パーツの作者編集時に複数作者を選択した場合のに入力ボックスの背景色
	 *
	 * @return 背景色
	 */
	public Color getAuthorEditConflictBgColor() {
		return authorEditConflictBgColor;
	}

	Color authorEditConflictBgColor = Color.yellow;


	public static final String MAIN_FRAME_MAX_WIDTH = "mainFrameMaxWidth";

	public void setMainFrameMaxWidth(int mainFrameMaxWidth) {
		int old = mainFrameMaxWidth;
		if (old != mainFrameMaxWidth) {
			this.mainFrameMaxWidth = mainFrameMaxWidth;
			propChangeSupport.firePropertyChange(MAIN_FRAME_MAX_WIDTH, old, mainFrameMaxWidth);
		}
	}

	/**
	 * メインフレームの初期表示時の最大幅
	 *
	 * @return メインフレームの初期表示時の最大幅
	 */
	public int getMainFrameMaxWidth() {
		return mainFrameMaxWidth;
	}

	private int mainFrameMaxWidth = 800;

	public static final String MAIN_FRAME_MAX_HEIGHT = "mainFrameMaxHeight";

	public void setMainFrameMaxHeight(int mainFrameMaxHeight) {
		int old = this.mainFrameMaxHeight;
		if (old != mainFrameMaxHeight) {
			this.mainFrameMaxHeight = mainFrameMaxHeight;
			propChangeSupport.firePropertyChange(MAIN_FRAME_MAX_HEIGHT, old, mainFrameMaxHeight);
		}
	}

	/**
	 * メインフレームの初期表示時の最大高さ
	 *
	 * @return メインフレームの初期表示時の最大高さ
	 */
	public int getMainFrameMaxHeight() {
		return mainFrameMaxHeight;
	}

	private int mainFrameMaxHeight = 600;


	/**
	 * カラーダイアログで存在しないレイヤーをディセーブルにしない.
	 *
	 * @return ディセーブルにしない場合はtrue
	 */
	public boolean isNotDisableLayerTab() {
		return notDisableLayerTab;
	}

	public static final String NOT_DISABLE_LAYER_TAB = "notDisableLayerTab";

	public void setNotDisableLayerTab(boolean notDisableLayerTab) {
		boolean old = this.notDisableLayerTab;
		if (old != notDisableLayerTab) {
			this.notDisableLayerTab = notDisableLayerTab;
			propChangeSupport.firePropertyChange(NOT_DISABLE_LAYER_TAB, old, notDisableLayerTab);
		}
	}

	private boolean notDisableLayerTab;


	/**
	 * ログを消去する日数.<br>
	 * この指定日を経過した古いログは削除される.<br>
	 * 0の場合は削除されない.
	 *
	 * @return
	 */
	public long getPurgeLogDays() {
		return purgeLogDays;
	}

	public static final String PURGE_LOG_DAYS = "purgeLogDays";

	public void setPurgeLogDays(long purgeLogDays) {
		long old = this.purgeLogDays;
		if (old != purgeLogDays) {
			this.purgeLogDays = purgeLogDays;
			propChangeSupport.firePropertyChange(PURGE_LOG_DAYS, old, purgeLogDays);
		}
	}

	private long purgeLogDays = 10;

	public String getPartsColorGroupPattern() {
		return partsColorGroupPattern;
	}

	public static final String PARTS_COLOR_GROUP_PATTERN = "partsColorGroupPattern";

	public void setPartsColorGroupPattern(String pattern) {
		if (pattern != null && pattern.trim().length() > 0) {
			Pattern.compile(pattern);
		}
		String old = this.partsColorGroupPattern;
		if (old == null ? pattern != null : !old.equals(pattern)) {
			this.partsColorGroupPattern = pattern;
			propChangeSupport.firePropertyChange(PARTS_COLOR_GROUP_PATTERN, old, pattern);
		}
	}

	private String partsColorGroupPattern = "^.*\\(@\\).*$";

	private Color selectPanelTitleColor = Color.BLUE;

	public Color getSelectPanelTitleColor() {
		return selectPanelTitleColor;
	}

	public static final String SELECT_PANEL_TITLE_COLOR = "selectPanelTitleColor";

	public void setSelectPanelTitleColor(Color selectPanelTitleColor) {
		if (selectPanelTitleColor == null) {
			throw new IllegalArgumentException();
		}
		Color old = this.selectPanelTitleColor;
		if (old == null ? selectPanelTitleColor != null : !old.equals(selectPanelTitleColor)) {
			this.selectPanelTitleColor = selectPanelTitleColor;
			propChangeSupport.firePropertyChange(SELECT_PANEL_TITLE_COLOR, old, selectPanelTitleColor);
		}
	}

	private boolean enableAutoShrinkPanel;

	public boolean isEnableAutoShrinkPanel() {
		return enableAutoShrinkPanel;
	}

	public static final String ENABLE_AUTO_SHRINK_PANEL = "enableAutoShrinkPanel";

	public void setEnableAutoShrinkPanel(boolean enableAutoShrinkPanel) {
		boolean old = this.enableAutoShrinkPanel;
		if (old != enableAutoShrinkPanel) {
			this.enableAutoShrinkPanel = enableAutoShrinkPanel;
			propChangeSupport.firePropertyChange(ENABLE_AUTO_SHRINK_PANEL, old, enableAutoShrinkPanel);
		}
	}

	public boolean isDisableWatchDirIfNotWritable() {
		return disableWatchDirIfNotWritable;
	}

	public static final String DISABLE_WATCH_DIR_IF_NOT_WRITABLE = "disableWatchDirIfNotWritable";

	public void setDisableWatchDirIfNotWritable(boolean disableWatchDirIfNotWritable) {
		boolean old = this.disableWatchDirIfNotWritable;
		if (old != disableWatchDirIfNotWritable) {
			this.disableWatchDirIfNotWritable = disableWatchDirIfNotWritable;
			propChangeSupport.firePropertyChange(DISABLE_WATCH_DIR_IF_NOT_WRITABLE, old, disableWatchDirIfNotWritable);
		}
	}

	private boolean disableWatchDirIfNotWritable = true;

	public static final String ENABLE_PNG_SUPPORT_FOR_WINDOWS = "enablePNGSupportForWindows";

	public void setEnablePNGSupportForWindows(boolean enablePNGSupportForWindows) {
		boolean old = this.enablePNGSupportForWindows;
		if (old != enablePNGSupportForWindows) {
			this.enablePNGSupportForWindows = enablePNGSupportForWindows;
			propChangeSupport.firePropertyChange(ENABLE_PNG_SUPPORT_FOR_WINDOWS, old, enablePNGSupportForWindows);
		}
	}

	public boolean isEnablePNGSupportForWindows() {
		return enablePNGSupportForWindows;
	}

	private boolean enablePNGSupportForWindows = true;

	/**
	 * 画像表示(通常モード)でオプティマイズを有効にする最大倍率.
	 */
	private double renderingOptimizeThresholdForNormal = 2.;

	public static final String RENDERING_OPTIMIZE_THRESHOLD_FOR_NORMAL = "renderingOptimizeThresholdForNormal";

	public void setRenderingOptimizeThresholdForNormal(
			double renderingOptimizeThresholdForNormal) {
		double old = this.renderingOptimizeThresholdForNormal;
		if (old != renderingOptimizeThresholdForNormal) {
			this.renderingOptimizeThresholdForNormal = renderingOptimizeThresholdForNormal;
			propChangeSupport.firePropertyChange(RENDERING_OPTIMIZE_THRESHOLD_FOR_NORMAL, old,
					renderingOptimizeThresholdForNormal);
		}
	}

	public double getRenderingOptimizeThresholdForNormal() {
		return renderingOptimizeThresholdForNormal;
	}
	/**
	 * 画像表示(チェックモード)でオプティマイズを有効にする最大倍率.
	 */
	private double renderingOptimizeThresholdForCheck = 0.;

	public static final String RENDERING_OPTIMIZE_THRESHOLD_FOR_CHECK = "renderingOptimizeThresholdForCheck";

	public void setRenderingOptimizeThresholdForCheck(
			double renderingOptimizeThresholdForCheck) {
		double old = this.renderingOptimizeThresholdForCheck;
		if (old != renderingOptimizeThresholdForCheck) {
			this.renderingOptimizeThresholdForCheck = renderingOptimizeThresholdForCheck;
			propChangeSupport.firePropertyChange(RENDERING_OPTIMIZE_THRESHOLD_FOR_CHECK, old,
					renderingOptimizeThresholdForCheck);
		}
	}

	public double getRenderingOptimizeThresholdForCheck() {
		return renderingOptimizeThresholdForCheck;
	}

	/**
	 * バイキュービックをサポートする場合
	 */
	private boolean enableInterpolationBicubic = true;

	public static final String ENABLE_INTERPOLATION_BICUBIC = "enableInterpolationBicubic";

	public void setEnableInterpolationBicubic(boolean enableInterpolationBicubic) {
		boolean old = this.enableInterpolationBicubic;
		if (old != enableInterpolationBicubic) {
			this.enableInterpolationBicubic = enableInterpolationBicubic;
			propChangeSupport.firePropertyChange(ENABLE_INTERPOLATION_BICUBIC, old, enableInterpolationBicubic);
		}
	}

	public boolean isEnableInterpolationBicubic() {
		return enableInterpolationBicubic;
	}

	/**
	 * 事前定義済みの倍率候補.<br>
	 */
	private String predefinedZoomRanges = "20, 50, 80, 100, 120, 150, 200, 300, 400, 800";

	public String getPredefinedZoomRanges() {
		return predefinedZoomRanges;
	}

	public static final String PREDEFINED_ZOOM_RANGES = "predefinedZoomRanges";

	public void setPredefinedZoomRanges(String predefinedZoomRanges) {
		if (predefinedZoomRanges == null) {
			throw new IllegalArgumentException();
		}
		String old = this.predefinedZoomRanges;
		if (old == null ? predefinedZoomRanges != null : !old.equals(predefinedZoomRanges)) {
			this.predefinedZoomRanges = predefinedZoomRanges;
			propChangeSupport.firePropertyChange(PREDEFINED_ZOOM_RANGES, old, predefinedZoomRanges);
		}
	}

	/**
	 * ズームパネルを初期状態で表示するか?
	 */
	private boolean enableZoomPanel = true;

	public boolean isEnableZoomPanel() {
		return enableZoomPanel;
	}

	public static final String ENABLE_ZOOM_PANEL = "enableZoomPanel";

	public void setEnableZoomPanel(boolean enableZoomPanel) {
		boolean old = this.enableZoomPanel;
		if (old != enableZoomPanel) {
			this.enableZoomPanel = enableZoomPanel;
			propChangeSupport.firePropertyChange(ENABLE_ZOOM_PANEL, old, enableZoomPanel);
		}
	}

	/**
	 * ズームパネルをアクティブにする下部範囲
	 */
	private int zoomPanelActivationArea = 30;

	public int getZoomPanelActivationArea() {
		return zoomPanelActivationArea;
	}

	public static final String ZOOM_PANEL_ACTIVATION_AREA = "zoomPanelActivationArea";

	public void setZoomPanelActivationArea(int zoomPanelActivationArea) {
		int old = this.zoomPanelActivationArea;
		if (old != zoomPanelActivationArea) {
			this.zoomPanelActivationArea = zoomPanelActivationArea;
			propChangeSupport.firePropertyChange(ZOOM_PANEL_ACTIVATION_AREA, old, zoomPanelActivationArea);
		}
	}

	/**
	 * レンダリングヒントを使用するか?
	 */
	private boolean enableRenderingHints = true;

	public static final String ENABLE_RENDERING_HINTS = "enableRenderingHints";

	public void setEnableRenderingHints(boolean enableRenderingHints) {
		boolean old = this.enableRenderingHints;
		if (old != enableRenderingHints) {
			this.enableRenderingHints = enableRenderingHints;
			propChangeSupport.firePropertyChange(ENABLE_RENDERING_HINTS, old, enableRenderingHints);
		}
	}

	public boolean isEnableRenderingHints() {
		return enableRenderingHints;
	}

	/**
	 * グリッド描画とマスク
	 */
	private int drawGridMask = 2;

	public int getDrawGridMask() {
		return drawGridMask;
	}

	public static final String DRAW_GRID_MASK = "drawGridMask";

	public void setDrawGridMask(int drawGridMask) {
		drawGridMask &= 0x03;
		int old = this.drawGridMask;
		if (old != drawGridMask) {
			this.drawGridMask = drawGridMask;
			propChangeSupport.firePropertyChange(DRAW_GRID_MASK, old, drawGridMask);
		}
	}

	private Color previewGridColor = new Color(0x7f7f0000, true);

	public Color getPreviewGridColor() {
		return previewGridColor;
	}

	public static final String PREVIEW_GRID_COLOR = "previewGridColor";

	public void setPreviewGridColor(Color previewGridColor) {
		Color old = this.previewGridColor;
		if (old == null ? previewGridColor != null : !old.equals(previewGridColor)) {
			this.previewGridColor = previewGridColor;
			propChangeSupport.firePropertyChange(PREVIEW_GRID_COLOR, old, previewGridColor);
		}
	}

	private int previewGridSize = 20;

	public int getPreviewGridSize() {
		return previewGridSize;
	}

	public static final String PREVIEW_GRID_SIZE = "previewGridSize";

	public void setPreviewGridSize(int previewGridSize) {
		int old = this.previewGridSize;
		if (old != previewGridSize) {
			this.previewGridSize = previewGridSize;
			propChangeSupport.firePropertyChange(PREVIEW_GRID_SIZE, old, previewGridSize);
		}
	}

	/**
	 * チェックモード時の余白サイズ(片側)
	 */
	private int previewUnfilledSpaceForCheckMode = 0;

	public int getPreviewUnfilledSpaceForCheckMode() {
		return previewUnfilledSpaceForCheckMode;
	}

	public static final String PREVIEW_UNFILLED_SPACE_FOR_CHECK_MODE = "previewUnfilledSpaceForCheckMode";

	public void setPreviewUnfilledSpaceForCheckMode(int previewUnfilledSpaceForCheckMode) {
		int old = this.previewUnfilledSpaceForCheckMode;
		if (old != previewUnfilledSpaceForCheckMode) {
			this.previewUnfilledSpaceForCheckMode = previewUnfilledSpaceForCheckMode;
			propChangeSupport.firePropertyChange(PREVIEW_UNFILLED_SPACE_FOR_CHECK_MODE, old,
					previewUnfilledSpaceForCheckMode);
		}
	}

	/**
	 * チェックモードでツールチップを表示するか?
	 */
	private boolean enableCheckInfoTooltip = true;

	public boolean isEnableCheckInfoTooltip() {
		return enableCheckInfoTooltip;
	}

	public static final String ENABLE_CHECK_INFO_TOOLTIP = "enableCheckInfoTooltip";

	public void setEnableCheckInfoTooltip(boolean enableCheckInfoTooltip) {
		boolean old = this.enableCheckInfoTooltip;
		if (old != enableCheckInfoTooltip) {
			this.enableCheckInfoTooltip = enableCheckInfoTooltip;
			propChangeSupport.firePropertyChange(ENABLE_CHECK_INFO_TOOLTIP, old, enableCheckInfoTooltip);
		}
	}

	/**
	 * ホイールによるスクロールの単位.<br>
	 */
	private int wheelScrollUnit = 10;

	public int getWheelScrollUnit() {
		return wheelScrollUnit;
	}

	public static final String WHEEL_SCROLL_UNIT = "wheelScrollUnit";

	public void setWheelScrollUnit(int wheelScrollUnit) {
		int old = this.wheelScrollUnit;
		if (old != wheelScrollUnit) {
			this.wheelScrollUnit = wheelScrollUnit;
			propChangeSupport.firePropertyChange(WHEEL_SCROLL_UNIT, old, wheelScrollUnit);
		}
	}

	/**
	 * 壁紙にオフスクリーン描画を使用するか?.<br>
	 * (あまり劇的なパフォーマンス効果はない.)
	 */
	private boolean enableOffscreenWallpaper = false;

	public boolean isEnableOffscreenWallpaper() {
		return enableOffscreenWallpaper;
	}

	public static final String ENABLE_OFFSCREEN_WALLPAPER = "enableOffscreenWallpaper";

	public void setEnableOffscreenWallpaper(boolean enableOffscreenWallpaper) {
		boolean old = this.enableOffscreenWallpaper;
		if (old != enableOffscreenWallpaper) {
			this.enableOffscreenWallpaper = enableOffscreenWallpaper;
			propChangeSupport.firePropertyChange(ENABLE_OFFSCREEN_WALLPAPER, old, enableOffscreenWallpaper);
		}
	}

	/**
	 * 壁紙のオフスクリーンの既定サイズ.
	 */
	private int offscreenWallpaperSize = 300;

	public int getOffscreenWallpaperSize() {
		return offscreenWallpaperSize;
	}

	private static final String OFFSCREEN_WALLPAPER_SIZE = "offscreenWallpaperSize";

	public void setOffscreenWallpaperSize(int offscreenWallpaperSize) {
		int old = this.offscreenWallpaperSize;
		if (old != offscreenWallpaperSize) {
			this.offscreenWallpaperSize = offscreenWallpaperSize;
			propChangeSupport.firePropertyChange(OFFSCREEN_WALLPAPER_SIZE, old, offscreenWallpaperSize);
		}
	}


	/**
	 * ランダム選択パーツの履歴数
	 */
	private int randomChooserMaxHistory = 10;

	public int getRandomChooserMaxHistory() {
		return randomChooserMaxHistory;
	}

	public static final String RANDOM_CHOOSER_MAX_HISTORY = "randomChooserMaxHistory";

	public void setRandomChooserMaxHistory(int randomChooserMaxHistory) {
		int old = this.randomChooserMaxHistory;
		if (old != randomChooserMaxHistory) {
			this.randomChooserMaxHistory = randomChooserMaxHistory;
			propChangeSupport.firePropertyChange(RANDOM_CHOOSER_MAX_HISTORY, old, randomChooserMaxHistory);
		}
	}

	/**
	 * デフォルトのフォントサイズ、0以下の場合はシステム既定のまま
	 */
	private int defaultFontSize = 12;

	public int getDefaultFontSize() {
		return defaultFontSize;
	}

	public static final String DEFAULT_FONT_SIZE = "defaultFontSize";

	public void setDefaultFontSize(int defaultFontSize) {
		int old = this.defaultFontSize;
		if (old != defaultFontSize) {
			this.defaultFontSize = defaultFontSize;
			propChangeSupport.firePropertyChange(DEFAULT_FONT_SIZE, old, defaultFontSize);
		}
	}

	/**
	 * デフォルトのフォントファミリー、カンマ区切り
	 */
	private String fontPriority = "Lucida Grande";

	public String getFontPriority() {
		return fontPriority;
	}

	public static final String FONT_PRIORITY = "fontPriority";

	public void setFontPriority(String fontPriority) {
		if (fontPriority == null) {
			throw new IllegalArgumentException();
		}
		String old = this.fontPriority;
		if (old == null ? fontPriority != null : !old.equals(fontPriority)) {
			this.fontPriority = fontPriority;
			propChangeSupport.firePropertyChange(FONT_PRIORITY, old, fontPriority);
		}
	}

	/**
	 * ウィンドウの位置、サイズ、スクロールバーの位置、ズームの状態を復元するか？
	 */
	private boolean enableRestoreWindow = false;

	public boolean isEnableRestoreWindow() {
		return enableRestoreWindow;
	}

	public static final String ENABLE_RESTORE_WINDOW = "enableRestoreWindow";

	public void setEnableRestoreWindow(boolean enableRestoreWindow) {
		boolean old = this.enableRestoreWindow;
		if (old != enableRestoreWindow) {
			this.enableRestoreWindow = enableRestoreWindow;
			propChangeSupport.firePropertyChange(ENABLE_RESTORE_WINDOW, old, enableRestoreWindow);
		}
	}

	private boolean enableColorAdvancedSettings = true;

	public boolean isEnableColorAdvancedSettings() {
		return enableColorAdvancedSettings;
	}

	public static final String ENABLE_COLOR_ADVANCED_SETTINGS = "enableColorAdvancedSettings";

	public void setEnableColorAdvancedSettings(boolean enableColorAdvancedSettings) {
		boolean old = this.enableColorAdvancedSettings;
		if (old != enableColorAdvancedSettings) {
			this.enableColorAdvancedSettings = enableColorAdvancedSettings;
			propChangeSupport.firePropertyChange(ENABLE_COLOR_ADVANCED_SETTINGS, old, enableRestoreWindow);
		}
	}

	private boolean useRecycleBinIfSupported = true;

	public boolean isUseRecycleBinIfSupported() {
		return useRecycleBinIfSupported;
	}

	public static final String USE_RECYCLE_BIN_IF_SUPPORTED = "useRecycleBinIfSupported";

	public void setUseRecycleBinIfSupported(boolean useRecycleBinIfSupported) {
		boolean old = this.useRecycleBinIfSupported;
		if (old != useRecycleBinIfSupported) {
			this.useRecycleBinIfSupported = useRecycleBinIfSupported;
			propChangeSupport.firePropertyChange(USE_RECYCLE_BIN_IF_SUPPORTED, old, useRecycleBinIfSupported);
		}
	}

	public boolean useRLECompressionForPSD = true;

	private static final String USE_RLE_COMPRESSION_FOR_PSD = "useRLECompressionForPSD";

	public boolean isUseRLECompressionForPSD() {
		return useRLECompressionForPSD;
	}

	public void setUseRLECompressionForPSD(boolean useRLECompressionForPSD) {
		boolean old = this.useRLECompressionForPSD;
		if (old != useRLECompressionForPSD) {
			this.useRLECompressionForPSD = useRLECompressionForPSD;
			propChangeSupport.firePropertyChange(USE_RLE_COMPRESSION_FOR_PSD, old, useRLECompressionForPSD);
		}
	}

	private String impersonateUserAgent = "curl/7.58.0";

	private static final String IMPERSONATE_USER_AGENT = "impersonateUserAgent";

	public String getImpersonateUserAgent() {
		return impersonateUserAgent;
	}

	public void setImpersonateUserAgent(String impersonateUserAgent) {
		String old = this.impersonateUserAgent;
		this.impersonateUserAgent = impersonateUserAgent;
		if (old == null ? impersonateUserAgent != null : !old.equals(impersonateUserAgent)) {
			this.impersonateUserAgent = impersonateUserAgent;
			propChangeSupport.firePropertyChange(IMPERSONATE_USER_AGENT, old, impersonateUserAgent);
		}
	}

	private boolean deleteDownloadFileOnExit = true;

	private static final String DELETE_DOWNLOAD_FILE_ON_EXIT = "deleteDownloadFileOnExit";

	public boolean isDeleteDownloadFileOnExit() {
		return deleteDownloadFileOnExit;
	}

	public void setDeleteDownloadFileOnExit(boolean deleteDownloadFileOnExit) {
		boolean old = this.deleteDownloadFileOnExit;
		if (old != deleteDownloadFileOnExit) {
			this.deleteDownloadFileOnExit = deleteDownloadFileOnExit;
			propChangeSupport.firePropertyChange(DELETE_DOWNLOAD_FILE_ON_EXIT, old, deleteDownloadFileOnExit);
		}
	}
}
