be.uza.keratoconus.systemtrayapp.SystemTrayMenu.java Source code

Java tutorial

Introduction

Here is the source code for be.uza.keratoconus.systemtrayapp.SystemTrayMenu.java

Source

/*
This file is part of Keratoconus Assistant.
    
Keratoconus Assistant 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.
    
Keratoconus Assistant 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 Keratoconus Assistant.  If not, see 
<http://www.gnu.org/licenses/>.
 */

package be.uza.keratoconus.systemtrayapp;

import java.awt.AWTException;
import java.awt.HeadlessException;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javafx.geometry.Pos;
import javafx.stage.StageStyle;

import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;

import aQute.bnd.annotation.component.Activate;
import aQute.bnd.annotation.component.Component;
import aQute.bnd.annotation.component.ConfigurationPolicy;
import aQute.bnd.annotation.component.Deactivate;
import aQute.bnd.annotation.component.Reference;
import be.uza.keratoconus.configuration.api.Classification.Category;
import be.uza.keratoconus.configuration.api.PentacamConfigurationService;
import be.uza.keratoconus.systemtrayapp.api.ShowPageEvent;
import be.uza.keratoconus.userprefs.api.PreferencesWindow;
import be.uza.keratoconus.userprefs.api.UserPreferences;
import be.uza.keratoconus.userprefs.impl.UserPreferencesMenu;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

@Component(provide = UserPreferences.class, configurationPolicy = ConfigurationPolicy.ignore, properties = EventConstants.EVENT_TOPIC
        + "=" + UserPreferencesChangedEvent.USERPREFS_CHANGED_TOPIC_PREFIX)
public class SystemTrayMenu extends PopupMenu implements ActionListener, UserPreferences, EventHandler {

    private static final String SELECTED_MODEL_NAME = "selectedModelName";

    private static final String PREFS_FILE_NAME = "preferences.json";

    private static final String MENU_TEXT_USER_PREFS = "User preferences ...";

    private static final long serialVersionUID = 1L;

    private static class UserPrefsDAO {
        private boolean popupsEnabled;
        private boolean messagesEnabled;
        private boolean animateEnabled;
        private Map<Category, Double> displayTimeSeconds;
        private StageStyle mainPopupStageStyle;
        private Pos mainPopupPosition;
        private boolean patientRecordsEnabled;
        private String patientRecordDirectory;
        private ChartType detailChartType;
        private String baseIconPath;
        public String selectedModelName;
    }

    private UserPrefsDAO prefs;
    private LogService logService;
    private EventAdmin eventAdmin;
    private PentacamConfigurationService pentacamConfigurationService;
    private UserPreferencesMenu prefsMenu;
    private Path prefsDirPath;
    private Path configFilePath;
    private ComponentContext ownComponentContext;
    private double defaultDisplayTimeSeconds = 30D;
    private StageStyle defaultWindowStageStyle;
    private Pos defaultWindowPosition;
    private ChartType defaultDetailChartType = ChartType.BAR;
    private String defaultBaseIconPath;
    private String applicationTitle;
    private Map<PreferencesWindow, Map<String, Object>> preferencesWindowMap = new HashMap<>();
    private boolean active;

    public SystemTrayMenu() throws HeadlessException {
    }

    @Reference
    protected void setPentacamConfigurationService(PentacamConfigurationService s) {
        pentacamConfigurationService = s;
    }

    @Reference
    protected void setLogService(LogService s) {
        logService = s;
    }

    @Reference
    protected void setEventAdmin(EventAdmin s) {
        eventAdmin = s;
    }

    @Reference(dynamic = true, multiple = true, optional = true)
    protected void addPreferencesWindow(PreferencesWindow pw, Map<String, Object> properties) {
        preferencesWindowMap.put(pw, properties);
        if (active) {
            prefsMenu.addPreferencesWindow(pw, extractRank(properties));
        }
    }

    protected void removePreferencesWindow(PreferencesWindow pw) {
        if (active) {
            prefsMenu.removePreferencesWindow(pw);
        }
        preferencesWindowMap.remove(pw);
    }

    @Activate
    protected void activate(ComponentContext cc) throws IOException, AWTException {
        ownComponentContext = cc;
        prefsDirPath = pentacamConfigurationService.getUserPrefsDirectoryPath();
        applicationTitle = pentacamConfigurationService.getApplicationTitle();
        defaultBaseIconPath = pentacamConfigurationService.getBaseIconPath();
        configFilePath = FileSystems.getDefault().getPath(PREFS_FILE_NAME);
        Files.createDirectories(prefsDirPath);
        prefs = readConfig();

        prefsMenu = new UserPreferencesMenu(MENU_TEXT_USER_PREFS, this);
        setUpMenu();
        for (Entry<PreferencesWindow, Map<String, Object>> entry : preferencesWindowMap.entrySet()) {
            PreferencesWindow pw = entry.getKey();
            Map<String, Object> properties = entry.getValue();
            prefsMenu.addPreferencesWindow(pw, extractRank(properties));
        }
        active = true;
    }

    private int extractRank(Map<String, Object> properties) {
        String rankString = (String) properties.get("rank");
        try {
            return Integer.parseInt(rankString);
        } catch (Exception e) {
            logService.log(ownComponentContext.getServiceReference(), LogService.LOG_WARNING,
                    "Configuration problem: could not parse rank of " + PreferencesWindow.class.getSimpleName()
                            + " component " + properties.get(ComponentConstants.COMPONENT_NAME));
            return 0;
        }
    }

    @Deactivate
    protected void deactivate() {
        active = false;
        prefsMenu = null;
    }

    private void setUpMenu() {
        addActionListener(this);
        final MenuItem menuItemAbout = new MenuItem("About " + applicationTitle);
        add(menuItemAbout);
        menuItemAbout.addActionListener(
                event -> eventAdmin.postEvent(new ShowPageEvent("/html/about.html", "About " + applicationTitle)));
        final MenuItem menuItemManual = new MenuItem("User Manual");
        add(menuItemManual);
        menuItemManual.addActionListener(event -> eventAdmin
                .postEvent(new ShowPageEvent("/html/manual1.html", applicationTitle + " - User Manual")));
        addSeparator();

        add(prefsMenu);
        addSeparator();

        final MenuItem menuItemRestart = new MenuItem("Restart " + applicationTitle);
        add(menuItemRestart);
        final MenuItem menuItemExit = new MenuItem("Exit " + applicationTitle);
        add(menuItemExit);
    }

    @Override
    public void configurePopupsOnOff(boolean b) {
        logInfo("Setting popus enabled to " + b);
        prefs.popupsEnabled = b;
        writeConfig(prefs);
    }

    @Override
    public void configureMessagesOnOff(boolean b) {
        logInfo("Setting messages enabled to " + b);
        prefs.messagesEnabled = b;
        writeConfig(prefs);
    }

    @Override
    public void configureAnimateOnOff(boolean b) {
        logInfo("Setting animations enabled to " + b);
        prefs.animateEnabled = b;
        writeConfig(prefs);
    }

    @Override
    public void configureMainPopupPosition(Pos pos) {
        logInfo("Setting main popup position to " + pos);
        prefs.mainPopupPosition = pos;
        writeConfig(prefs);
    }

    @Override
    public void configureMainPopupStageStyle(StageStyle ss) {
        logInfo("Setting main popup stage style to " + ss);
        prefs.mainPopupStageStyle = ss;
        writeConfig(prefs);
    }

    @Override
    public void configureGraphicalPatientRecordOnOff(Boolean b) {
        logInfo("Setting graphical patient records enabled to " + b);
        prefs.patientRecordsEnabled = b;
        writeConfig(prefs);
    }

    @Override
    public void configurePatientRecordDirectory(String path) {
        logInfo("Setting patient record directory to " + path);
        prefs.patientRecordDirectory = path;
        writeConfig(prefs);
    }

    @Override
    public void configureDetailChartType(ChartType ct) {
        logInfo("Setting detail chart type to " + ct);
        prefs.detailChartType = ct;
        writeConfig(prefs);
    }

    @Override
    public void configureSelectedModelName(String modelName) {
        logInfo("Setting selected model name to " + modelName);
        prefs.selectedModelName = modelName;
        writeConfig(prefs);
        Map<String, String> changed = new HashMap<>();
        changed.put(SELECTED_MODEL_NAME, modelName);
        eventAdmin.postEvent(new UserPreferencesChangedEvent(SELECTED_MODEL_NAME, changed));
    }

    private UserPrefsDAO readConfig() {
        GsonBuilder builder = new GsonBuilder();
        builder.enableComplexMapKeySerialization();
        Gson gson = builder.create();
        Path path = prefsDirPath.resolve(configFilePath);
        try {
            if (Files.exists(path) && Files.size(path) > 0) {
                try (InputStream fileInputStream = Files.newInputStream(path)) {
                    final UserPrefsDAO config = gson.fromJson(new InputStreamReader(fileInputStream),
                            UserPrefsDAO.class);
                    return config;
                }
            }
        } catch (IOException ioe) {
            logService.log(LogService.LOG_WARNING, "Exception thrown when reading user configuration", ioe);
        }

        UserPrefsDAO defaultConfig = createDefaultUserConfig();
        defaultConfig.animateEnabled = true;
        defaultConfig.popupsEnabled = true;
        defaultConfig.displayTimeSeconds = new HashMap<>();
        writeConfig(defaultConfig);
        return defaultConfig;
    }

    private UserPrefsDAO createDefaultUserConfig() {
        return new UserPrefsDAO();
    }

    private void writeConfig(UserPrefsDAO userConfig) {
        Gson gson = new Gson();
        Path path = prefsDirPath.resolve(FileSystems.getDefault().getPath(PREFS_FILE_NAME));
        logInfo("Writing JSON to " + path);
        try (final OutputStreamWriter writer = new OutputStreamWriter(Files.newOutputStream(path))) {
            gson.toJson(userConfig, writer);
        } catch (IOException e) {
            logException(e, "Problem encountered when saving user configuration to " + path);
        }
    }

    @Override
    public boolean isPopupsEnabled() {
        return prefs.popupsEnabled;
    }

    @Override
    public boolean isMessagesEnabled() {
        return prefs.messagesEnabled;
    }

    @Override
    public boolean isAnimateEnabled() {
        return prefs.animateEnabled;
    }

    @Override
    public boolean isPatientRecordEnabled() {
        return prefs.patientRecordsEnabled;
    }

    @Override
    public double getDisplayTimeSeconds(Category cat) {
        final Double value = prefs.displayTimeSeconds.get(cat);
        return value == null ? defaultDisplayTimeSeconds : value;
    }

    @Override
    public StageStyle getMainPopupStageStyle() {
        StageStyle value = prefs.mainPopupStageStyle == null ? defaultWindowStageStyle : prefs.mainPopupStageStyle;
        return value == null ? StageStyle.UNDECORATED : value;
    }

    @Override
    public Pos getMainPopupPosition() {
        Pos value = prefs.mainPopupPosition == null ? defaultWindowPosition : prefs.mainPopupPosition;
        return value == null ? Pos.BOTTOM_RIGHT : value;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        logInfo("Menu item '" + command + "' selected");
        if (command != null) {
            if (command.startsWith("Restart ")) {
                restartApplication();
            }
            if (command.startsWith("Exit ")) {
                exitApplication();
            }
        }
    }

    private void exitApplication() {
        logInfo("Terminating application");
        try {
            ownComponentContext.getBundleContext().getBundle(0).stop();
        } catch (BundleException e1) {
            logException(e1, "Exception thrown when stopping framework");
        }
    }

    private void restartApplication() {
        logInfo("Restarting application");
        try {
            FrameworkUtil.getBundle(pentacamConfigurationService.getClass()).update();
            //               ownComponentContext.getBundleContext().getBundle(0).update();
        } catch (BundleException e1) {
            logException(e1, "Exception thrown when restarting framework");
        }
    }

    @Override
    public String getPatientRecordDirectory() {
        return prefs.patientRecordDirectory;
    }

    @Override
    public void setDefaultWindowStageStyle(String wss) {
        try {
            Field field = StageStyle.class.getField(wss.toUpperCase());
            defaultWindowStageStyle = (StageStyle) field.get(null);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            logService.log(LogService.LOG_WARNING, "Exception thrown when setting default window stage style", e);
        }
    }

    @Override
    public void setDefaultWindowPosition(String wpos) {
        try {
            Field field = Pos.class.getField(wpos.replace(' ', '_').toUpperCase());
            defaultWindowPosition = (Pos) field.get(null);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            logService.log(LogService.LOG_WARNING, "Exception thrown when setting default window position", e);
        }
    }

    @Override
    public void setDefaultDisplayTimeSeconds(double seconds) {
        defaultDisplayTimeSeconds = seconds;
    }

    @Override
    public void configureDisplayTimeSeconds(Category cat, double seconds) {
        prefs.displayTimeSeconds.put(cat, seconds);
    }

    @Override
    public ChartType getDetailChartType() {
        ChartType value = prefs.detailChartType == null ? defaultDetailChartType : prefs.detailChartType;
        return value == null ? ChartType.BAR : value;
    }

    @Override
    public String getBaseIconPath() {
        String path = prefs.baseIconPath == null ? defaultBaseIconPath : prefs.baseIconPath;
        return path;
    }

    @Override
    public String getSelectedModelName() {
        return prefs.selectedModelName;
    }

    @Override
    public String getIconPath(String variant) {
        String path = prefs.baseIconPath == null ? defaultBaseIconPath : prefs.baseIconPath;
        String basePath = path;
        int lastDot = basePath.lastIndexOf('.');
        return basePath.substring(0, lastDot) + "-" + variant + basePath.substring(lastDot);
    }

    @Override
    public void setDefaultDetailChartType(String detail_chart_type) {
        ChartType value = ChartType.valueOf(detail_chart_type.toUpperCase());
        if (value != null) {
            defaultDetailChartType = value;
        }
    }

    private void logInfo(String message) {
        logService.log(ownComponentContext.getServiceReference(), LogService.LOG_INFO, message);
    }

    private void logException(Exception e, String message) {
        logService.log(ownComponentContext.getServiceReference(), LogService.LOG_WARNING, message, e);
    }

    @Override
    public void handleEvent(Event event) {
        if (event instanceof UserPreferencesChangedEvent) {
            UserPreferencesChangedEvent userPreferencesChangedEvent = (UserPreferencesChangedEvent) event;
            if (userPreferencesChangedEvent.getChanges().keySet().contains(SELECTED_MODEL_NAME)) {
                restartApplication();
            }
        }

    }

}