gt: 		List<File> watchedFolders = new ArrayList<File>();
context: /*
   * Syncany, www.syncany.org
   * Copyright (C) 2011-2013 Philipp C. Heckel <philipp.heckel@gmail.com>
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package org.syncany.gui.tray;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.List;
  import java.util.Map;
  import java.util.Timer;
  import java.util.TimerTask;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  import org.eclipse.swt.widgets.Display;
  import org.eclipse.swt.widgets.Shell;
  import org.syncany.config.GuiConfigHelper;
  import org.syncany.config.GuiEventBus;
  import org.syncany.config.to.GuiConfigTO;
  import org.syncany.gui.history.HistoryDialog;
  import org.syncany.gui.preferences.PreferencesDialog;
  import org.syncany.gui.util.DesktopUtil;
  import org.syncany.gui.util.I18n;
  import org.syncany.gui.wizard.WizardDialog;
  import org.syncany.operations.ChangeSet;
  import org.syncany.operations.daemon.ControlServer.ControlCommand;
  import org.syncany.operations.daemon.Watch;
  import org.syncany.operations.daemon.Watch.SyncStatus;
  import org.syncany.operations.daemon.messages.CleanupEndSyncExternalEvent;
  import org.syncany.operations.daemon.messages.CleanupStartCleaningSyncExternalEvent;
  import org.syncany.operations.daemon.messages.ControlManagementRequest;
  import org.syncany.operations.daemon.messages.DaemonReloadedExternalEvent;
  import org.syncany.operations.daemon.messages.DownChangesDetectedSyncExternalEvent;
  import org.syncany.operations.daemon.messages.DownDownloadFileSyncExternalEvent;
  import org.syncany.operations.daemon.messages.DownEndSyncExternalEvent;
  import org.syncany.operations.daemon.messages.ExitGuiInternalEvent;
  import org.syncany.operations.daemon.messages.GenlinkFolderRequest;
  import org.syncany.operations.daemon.messages.GenlinkFolderResponse;
  import org.syncany.operations.daemon.messages.GuiConfigChangedGuiInternalEvent;
  import org.syncany.operations.daemon.messages.ListWatchesManagementRequest;
  import org.syncany.operations.daemon.messages.ListWatchesManagementResponse;
  import org.syncany.operations.daemon.messages.LogFolderRequest;
  import org.syncany.operations.daemon.messages.LogFolderResponse;
  import org.syncany.operations.daemon.messages.PluginManagementResponse;
  import org.syncany.operations.daemon.messages.RemoveWatchManagementRequest;
  import org.syncany.operations.daemon.messages.RemoveWatchManagementResponse;
  import org.syncany.operations.daemon.messages.UpEndSyncExternalEvent;
  import org.syncany.operations.daemon.messages.UpIndexChangesDetectedSyncExternalEvent;
  import org.syncany.operations.daemon.messages.UpIndexStartSyncExternalEvent;
  import org.syncany.operations.daemon.messages.UpUploadFileInTransactionSyncExternalEvent;
  import org.syncany.operations.daemon.messages.UpUploadFileSyncExternalEvent;
  import org.syncany.operations.daemon.messages.UpdateManagementResponse;
  import org.syncany.operations.daemon.messages.WatchEndSyncExternalEvent;
  import org.syncany.operations.gui.UpdateChecker;
  import org.syncany.operations.gui.UpdateChecker.UpdateCheckListener;
  import org.syncany.operations.init.GenlinkOperationOptions;
  import org.syncany.operations.log.LogOperationOptions;
  import org.syncany.util.FileUtil;
  import org.syncany.util.StringUtil;
  
  import com.google.common.base.Predicate;
  import com.google.common.collect.Maps;
  import com.google.common.eventbus.Subscribe;
  
  /**
   * Represents the tray icon, showing the status of the application,
   * a menu to control the application and the ability to display
   * notifications. The tray icon is the central entry point for
   * the application.
   *
   * @author Philipp C. Heckel <philipp.heckel@gmail.com>
   * @author Vincent Wiencek <vwiencek@gmail.com>
   */
  public abstract class TrayIcon {
  	protected static final Logger logger = Logger.getLogger(TrayIcon.class.getSimpleName());
  
  	protected static int ANIMATION_REFRESH_TIME = 800;	
  	protected static String URL_REPORT_ISSUE = "https://www.syncany.org/r/issue";
  	protected static String URL_DONATE = "https://www.syncany.org/r/donate";
  	protected static String URL_HOMEPAGE = "https://www.syncany.org";
  
  	protected Shell trayShell;
  	protected TrayIconTheme theme;
  	protected WizardDialog wizard;
  	protected HistoryDialog history;
  	protected PreferencesDialog preferences;
  
  	protected GuiConfigTO guiConfig;
  	protected GuiEventBus eventBus;
  	
  	protected UpdateChecker updateChecker;
  
  	protected Thread animationThread;
  	protected AtomicBoolean syncing;
  	protected Map<String, Boolean> clientSyncStatus;
  	protected Map<String, Long> clientUploadFileSize;
  	protected RecentFileChanges recentFileChanges;
  
  	public TrayIcon(Shell shell, TrayIconTheme theme) {
  		this.trayShell = shell;
  		this.theme = theme;
  
  		this.guiConfig = GuiConfigHelper.loadOrCreateGuiConfig();
  
  		this.eventBus = GuiEventBus.getInstance();
  		this.eventBus.register(this);
  
  		this.syncing = new AtomicBoolean(false);
  		this.clientSyncStatus = Maps.newConcurrentMap();
  		this.clientUploadFileSize = Maps.newConcurrentMap();
  		this.recentFileChanges = new RecentFileChanges(this);
  		
  		initUpdateChecker();
  		initAnimationThread();
  		initTrayImage();
  	}
  
  	protected void showNew() {
  		Display.getDefault().asyncExec(new Runnable() {
  			@Override
  			public void run() {
  				if (wizard == null) {
  					wizard = new WizardDialog(trayShell);
  					wizard.open();
  
  					wizard = null;
  				}
  				else {
  					DesktopUtil.bringToFront(wizard.getWindowShell());
  				}
  			}
  		});
  	}
  	
  	protected void showBrowseHistory() {
  		Display.getDefault().asyncExec(new Runnable() {
  			@Override
  			public void run() {
  				if (history == null) {
  					history = new HistoryDialog();
  					history.open();
  
  					history = null;
  				}
  				else {
  					DesktopUtil.bringToFront(history.getWindowShell());
  				}
  			}
  		});
  	}
  
  	protected void showPreferences() {
  		Display.getDefault().asyncExec(new Runnable() {
  			@Override
  			public void run() {
  				if (preferences == null) {
  					preferences = new PreferencesDialog(trayShell);
  					preferences.open();
  
  					preferences = null;
  				}
  				else {
  					DesktopUtil.bringToFront(preferences.getWindowShell());
  				}
  			}
  		});
  	}
  
  	protected void showFolder(File folder) {
  		DesktopUtil.launch(folder.getAbsolutePath());
  	}
  
  	protected void showRecentFile(File file) {
  		DesktopUtil.launch(file.getAbsolutePath());
  	}
  
  	protected void showReportIssue() {
  		DesktopUtil.launch(URL_REPORT_ISSUE);
  	}
  
  	protected void showDonate() {
  		DesktopUtil.launch(URL_DONATE);
  	}
  
  	protected void showWebsite() {
  		DesktopUtil.launch(URL_HOMEPAGE);
  	}
  
  	protected void exitApplication() {
  		dispose();
  		eventBus.post(new ExitGuiInternalEvent());
  	}
  
  	public TrayIconTheme getTheme() {
  		return theme;
  	}
  
  	protected void removeFolder(File folder) {
  		eventBus.post(new RemoveWatchManagementRequest(folder));
  	}
  
  	protected void copyLink(File folder) {
  		GenlinkOperationOptions genlinkOptions = new GenlinkOperationOptions();
  		genlinkOptions.setShortUrl(guiConfig.isShortLinks());
  
  		GenlinkFolderRequest genlinkRequest = new GenlinkFolderRequest();
  		genlinkRequest.setRoot(folder.getAbsolutePath());
  		genlinkRequest.setOptions(genlinkOptions);
  
  		eventBus.post(genlinkRequest);
  	}
  
  	@Subscribe
  	public void onGenlinkResponseReceived(GenlinkFolderResponse genlinkResponse) {
  		DesktopUtil.copyToClipboard(genlinkResponse.getResult().getShareLink());
  
  		if (guiConfig.isNotifications()) {
  			String subject = I18n.getText("org.syncany.gui.tray.TrayIcon.notify.copied.subject");
  			String message = I18n.getText("org.syncany.gui.tray.TrayIcon.notify.copied.message");
  
  			displayNotification(subject, message);
  		}
  	}
  
  	@Subscribe
  	public void onRemoveWatchResponseReceived(RemoveWatchManagementResponse removeWatchResponse) {
  		if (removeWatchResponse.getCode() == RemoveWatchManagementResponse.OKAY) {
  			logger.log(Level.INFO, "Watch removed successfully from daemon config. Now reloading daemon.");
  			eventBus.post(new ControlManagementRequest(ControlCommand.RELOAD));
  		}
  		else {
  			logger.log(Level.WARNING, "Watch NOT removed from daemon config. Doing nothing.");
  		}
  	}
  
  	@Subscribe
  	public void onDaemonReloadedEventReceived(DaemonReloadedExternalEvent daemonReloadedEvent) {
  		eventBus.post(new ListWatchesManagementRequest());
  	}
  
  	@Subscribe
  	public void onListWatchesResponseReceived(ListWatchesManagementResponse listWatchesResponse) {
  		logger.log(Level.FINE, "List watches response recevied: " + listWatchesResponse.getWatches().size() + " watch(es)");
  
  		cleanSyncStatus();
  