/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id: TrayIconManager.java 15105 2011-05-11 09:26:16Z harry $ */ package de.dal33t.powerfolder.ui; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NOT_CONNECTED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NOT_LOGGED_IN; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NOT_STARTED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NO_FOLDERS; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.PAUSED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.SYNCHRONIZED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.SYNCING; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.SYNC_INCOMPLETE; import java.awt.EventQueue; import java.awt.Image; import java.awt.TrayIcon; import java.io.IOException; import java.util.TimerTask; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.PFComponent; import de.dal33t.powerfolder.PreferencesEntry; import de.dal33t.powerfolder.clientserver.ServerClient; import de.dal33t.powerfolder.ui.event.SyncStatusEvent; import de.dal33t.powerfolder.ui.event.SyncStatusListener; import de.dal33t.powerfolder.ui.notices.SimpleNotificationNotice; import de.dal33t.powerfolder.ui.util.DelayedUpdater; import de.dal33t.powerfolder.ui.util.Icons; import de.dal33t.powerfolder.util.Format; import de.dal33t.powerfolder.util.Translation; import de.dal33t.powerfolder.util.os.OSUtil; /** * Encapsultes tray icon functionality. Anything to do with the tray icon should * be done *HERE*. This keeps all tray functionality encapsulated. * <p/> * Blink has the highest priority. If blink is true, the 'P' icon will blink and * the blinkText will be the tool tip, explaining why it is blinking. * <p/> * Sync has second highest priority. If syncing, the icon will rotate and tool * tip will say 'syncing'. * <p/> * Otherwise, normal or warning will display. */ public class TrayIconManager extends PFComponent { private static final long ROTATION_STEP_DELAY = 200L; private final UIController uiController; private TrayIcon trayIcon; private final AtomicInteger atomicAngle = new AtomicInteger(); private final AtomicBoolean atomicConnectedAndLoggedIn = new AtomicBoolean(); private final AtomicBoolean atomicSyncing = new AtomicBoolean(); private final DelayedUpdater iconUpdater; public TrayIconManager(UIController uiController) { super(uiController.getController()); this.uiController = uiController; if (OSUtil.isLinux()) { // PFC-2331 whitelistSystray(getController()); } iconUpdater = new DelayedUpdater(getController()); Image image = Icons.getImageById(Icons.SYSTRAY_SYNC_COMPLETE); if (image == null) { logSevere("Unable to retrieve default system tray icon. " + "System tray disabled"); OSUtil.disableSystray(); return; } trayIcon = new TrayIcon(image); trayIcon.setImageAutoSize(true); updateConnectionStatus(); updateIcon(NOT_STARTED); getController().getUIController().getApplicationModel() .addSyncStatusListener(new SyncStatusListener() { public void syncStatusChanged(final SyncStatusEvent event) { iconUpdater.schedule(new Runnable() { public void run() { updateIcon(event); } }); } public boolean fireInEventDispatchThread() { return true; } }); getController().scheduleAndRepeat(new SpinnerTask(), ROTATION_STEP_DELAY); } public static void whitelistSystray(Controller controller) { ScheduledFuture<?> fut = controller.schedule(new Runnable() { public void run() { try { Runtime .getRuntime() .exec( "gsettings set com.canonical.Unity.Panel systray-whitelist \"['all']\""); } catch (IOException e) { Logger.getLogger(TrayIconManager.class.getName()).warning( "Unable to whitelist application for system tray icon. " + e); } } }, 0); try { fut.get(5, TimeUnit.SECONDS); } catch (Exception e) { Logger.getLogger(TrayIconManager.class.getName()).warning( "Unable to whitelist application for system tray icon. " + e); } } /** * Only use this to actually display the TrayIcon. Any modifiers should be * done through this. * * @return */ public TrayIcon getTrayIcon() { return trayIcon; } private void updateConnectionStatus() { ServerClient client = getController().getOSClient(); boolean connected = client.isConnected(); boolean loggedIn = client.isLoggedIn(); // Do a notification if moved between connected and not connected. if (!PreferencesEntry.SHOW_SYSTEM_NOTIFICATIONS .getValueBoolean(getController())) { return; } if (atomicConnectedAndLoggedIn.getAndSet(connected && loggedIn) != connected && loggedIn) { // State changed, notify ui. String notificationText; String title = Translation .getTranslation("tray_icon_manager.status_change.title"); if (connected && loggedIn) { notificationText = Translation .getTranslation("tray_icon_manager.status_change.connected"); } else if (!getController().getNodeManager().isStarted()) { notificationText = Translation .getTranslation("tray_icon_manager.status_change.disabled"); } else { notificationText = Translation .getTranslation("tray_icon_manager.status_change.connecting"); } uiController .getApplicationModel() .getNoticesModel() .handleNotice( new SimpleNotificationNotice(title, notificationText)); } } private void updateIcon(SyncStatusEvent event) { if (trayIcon == null) { // Tray icon not supported? return; } StringBuilder tooltip = new StringBuilder(); tooltip.append(Translation.getTranslation("general.application.name") + ' ' + Controller.PROGRAM_VERSION); tooltip.append(" \n"); Image image; boolean syncing = false; if (event.equals(PAUSED)) { image = Icons.getImageById(Icons.SYSTRAY_PAUSE); tooltip .append(Translation.getTranslation("systray.tooltip.paused")); } else if (event.equals(NOT_STARTED)) { image = Icons.getImageById(Icons.SYSTRAY_WARNING); tooltip.append(Translation .getTranslation("systray.tooltip.not_started")); } else if (event.equals(NO_FOLDERS)) { image = Icons.getImageById(Icons.SYSTRAY_WARNING); tooltip.append(Translation .getTranslation("systray.tooltip.no_folders")); } else if (event.equals(SYNCING)) { syncing = true; image = Icons.getImageById(Icons.SYSTRAY_SYNC_ANIMATION[atomicAngle .get()]); if (trayIcon != null) { trayIcon.setImage(image); } tooltip.append(Translation .getTranslation("systray.tooltip.syncing")); double overallSyncPercentage = getController().getUIController() .getApplicationModel().getFolderRepositoryModel() .getOverallSyncPercentage(); if (overallSyncPercentage >= 0) { tooltip.append(' '); tooltip .append(Format.formatDecimal(overallSyncPercentage) + '%'); } } else if (event.equals(SYNCHRONIZED)) { image = Icons.getImageById(Icons.SYSTRAY_SYNC_COMPLETE); tooltip.append(Translation .getTranslation("systray.tooltip.in_sync")); } else if (event.equals(SYNC_INCOMPLETE)) { image = Icons.getImageById(Icons.SYSTRAY_SYNC_INCOMPLETE); tooltip.append(Translation .getTranslation("systray.tooltip.sync_incomplete")); } else if (event.equals(NOT_CONNECTED)) { image = Icons.getImageById(Icons.SYSTRAY_SYNC_INCOMPLETE); tooltip.append(Translation .getTranslation("systray.tooltip.not_connected")); } else if (event.equals(NOT_LOGGED_IN)) { image = Icons.getImageById(Icons.SYSTRAY_WARNING); tooltip.append(Translation .getTranslation("systray.tooltip.not_logged_in")); } else { logSevere("Not handling all sync states: " + event); image = Icons.getImageById(Icons.QUESTION); } atomicSyncing.set(syncing); trayIcon.setImage(image); trayIcon.setToolTip(tooltip.toString()); } private void spinIcon() { if (atomicSyncing.get()) { int i = atomicAngle.incrementAndGet(); if (i >= Icons.SYSTRAY_SYNC_ANIMATION.length) { atomicAngle.set(0); i = 0; // Update tool tip every time we pass zero. StringBuilder tooltip = new StringBuilder(); tooltip.append(Translation .getTranslation("systray.tooltip.syncing")); double overallSyncPercentage = getController() .getUIController().getApplicationModel() .getFolderRepositoryModel().getOverallSyncPercentage(); if (overallSyncPercentage >= 0) { tooltip.append(' '); tooltip .append(Format.formatDecimal(overallSyncPercentage) + '%'); } trayIcon.setToolTip(tooltip.toString()); } Image image = Icons.getImageById(Icons.SYSTRAY_SYNC_ANIMATION[i]); if (trayIcon != null) { trayIcon.setImage(image); } } } /** * Display tray sync icon in hi resolution? Linux needs to be low * resolution, otherwise it looks rubbish. * * @return */ public static boolean isHiRes() { return OSUtil.isLinux() || OSUtil.isMacOS(); } /** * Timer to rotate the icon. */ private class SpinnerTask extends TimerTask { public void run() { EventQueue.invokeLater(new Runnable() { public void run() { spinIcon(); } }); } } }