org.zephyrsoft.sdb2.MainController.java Source code

Java tutorial

Introduction

Here is the source code for org.zephyrsoft.sdb2.MainController.java

Source

/*
 * This file is part of the Song Database (SDB).
 * 
 * SDB 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 2 of the License, or
 * (at your option) any later version.
 * 
 * SDB 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 SDB. If not, see <http://www.gnu.org/licenses/>.
 */
package org.zephyrsoft.sdb2;

import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsDevice;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;

import org.apache.commons.lang3.Validate;
import org.jdesktop.core.animation.timing.Animator;
import org.jdesktop.core.animation.timing.TimingSource;
import org.jdesktop.swing.animation.timing.sources.SwingTimerTimingSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zephyrsoft.sdb2.gui.MainWindow;
import org.zephyrsoft.sdb2.model.AddressablePart;
import org.zephyrsoft.sdb2.model.FilterTypeEnum;
import org.zephyrsoft.sdb2.model.ScreenContentsEnum;
import org.zephyrsoft.sdb2.model.Song;
import org.zephyrsoft.sdb2.model.SongsModel;
import org.zephyrsoft.sdb2.model.XMLConverter;
import org.zephyrsoft.sdb2.model.settings.SettingKey;
import org.zephyrsoft.sdb2.model.settings.SettingsModel;
import org.zephyrsoft.sdb2.presenter.Presentable;
import org.zephyrsoft.sdb2.presenter.Presenter;
import org.zephyrsoft.sdb2.presenter.PresenterBundle;
import org.zephyrsoft.sdb2.presenter.PresenterWindow;
import org.zephyrsoft.sdb2.presenter.ScreenHelper;
import org.zephyrsoft.sdb2.presenter.Scroller;
import org.zephyrsoft.util.StringTools;
import org.zephyrsoft.util.gui.ErrorDialog;

/**
 * Controller for {@link MainWindow}.
 * 
 * @author Mathis Dirksen-Thedens
 */
public class MainController implements Scroller {

    private static Logger LOG = LoggerFactory.getLogger(MainController.class);

    private StatisticsController statisticsController;

    private String songsFileName = null;
    private SongsModel songs = null;
    private SettingsModel settings = null;

    private List<GraphicsDevice> screens;
    private PresenterBundle presentationControl;
    private Song currentlyPresentedSong = null;

    private ExecutorService executor = Executors.newSingleThreadExecutor();
    private Future<?> countDownFuture;

    public MainController(StatisticsController statisticsController) {
        this.statisticsController = statisticsController;
    }

    public boolean present(Presentable presentable) {
        // end old presentation (if any)
        if (presentationControl != null) {
            presentationControl.hidePresenter();
        }

        presentationControl = new PresenterBundle();

        Presenter presenter1 = createPresenter(
                ScreenHelper.getScreen(screens, settings.getString(SettingKey.SCREEN_1_DISPLAY)), presentable,
                (ScreenContentsEnum) settings.get(SettingKey.SCREEN_1_CONTENTS));
        if (presenter1 != null) {
            presentationControl.addPresenter(presenter1);
        }

        Presenter presenter2 = createPresenter(
                ScreenHelper.getScreen(screens, settings.getString(SettingKey.SCREEN_2_DISPLAY)), presentable,
                (ScreenContentsEnum) settings.get(SettingKey.SCREEN_2_CONTENTS));
        if (presenter2 != null) {
            presentationControl.addPresenter(presenter2);
        }

        if (presentationControl.size() == 0) {
            ErrorDialog.openDialog(null,
                    "Could not start presentation!\n\nPlease specify at least one existing presentation display:\nCheck your system configuration\nand/or adjust this program's configuration\n(see tab \"Global Settings\")!");
            return false;
        } else {
            currentlyPresentedSong = presentable.getSong();

            if (currentlyPresentedSong != null) {
                startCountDown(settings.getInteger(SettingKey.SECONDS_UNTIL_COUNTED), currentlyPresentedSong);
            } else {
                stopCountDown();
            }

            // start presentation
            presentationControl.showPresenter();

            return true;
        }
    }

    public void startCountDown(final int seconds, final Song song) {
        Runnable countDownRunnable = new Runnable() {
            @Override
            public void run() {
                LOG.debug("start sleeping for {} seconds", seconds);
                try {
                    Thread.sleep(seconds * 1000);
                    statisticsController.countSongAsPresentedToday(song);
                } catch (InterruptedException e) {
                    // if interrupted, do nothing (the countdown was stopped)
                    LOG.debug("interrupted");
                }
            }
        };
        stopCountDown();
        if (executor.isShutdown()) {
            throw new IllegalStateException("background executor is already stopped");
        } else {
            countDownFuture = executor.submit(countDownRunnable);
        }
    }

    public void stopCountDown() {
        if (countDownFuture != null) {
            LOG.debug("stopping countdown");
            countDownFuture.cancel(true);
            countDownFuture = null;
        } else {
            LOG.debug("wanted to stop countdown, but nothing to do");
        }
    }

    @Override
    public List<AddressablePart> getParts() {
        Validate.notNull(presentationControl, "there is no active presentation");
        return presentationControl.getParts();
    }

    @Override
    public void moveToPart(Integer part) {
        presentationControl.moveToPart(part);
    }

    @Override
    public void moveToLine(Integer part, Integer line) {
        presentationControl.moveToLine(part, line);
    }

    private PresenterWindow createPresenter(GraphicsDevice screen, Presentable presentable,
            ScreenContentsEnum contents) {
        if (screen == null) {
            // nothing to be done
            return null;
        }
        return new PresenterWindow(screen, presentable, contents, settings);
    }

    public List<GraphicsDevice> getScreens() {
        return Collections.unmodifiableList(screens);
    }

    public void detectScreens() {
        if (screens == null) {
            screens = new ArrayList<>();
        } else {
            screens.clear();
        }

        // for the setting "don't show at all"
        screens.add(null);

        List<GraphicsDevice> screensInternal = ScreenHelper.getScreens();
        for (GraphicsDevice screen : screensInternal) {
            screens.add(screen);
        }
    }

    public boolean prepareClose() {
        LOG.debug("preparing to close application");
        return saveAll();
    }

    public boolean saveAll() {
        boolean successfullySavedSongs = saveSongs();
        boolean successfullySavedSettings = saveSettings();
        boolean successfullySavedStatistics = statisticsController.saveStatistics();
        return successfullySavedSongs && successfullySavedSettings && successfullySavedStatistics;
    }

    public void shutdown() {
        shutdown(0);
    }

    public void shutdown(int exitCode) {
        LOG.debug("closing application, exit code " + exitCode);
        executor.shutdownNow();
        System.exit(exitCode);
    }

    public void loadSongs(String fileName) {
        if (!StringTools.isBlank(fileName)) {
            songsFileName = fileName;
        }
        songs = populateSongsModel();
        if (songs == null) {
            // there was a problem while reading
            songs = new SongsModel();
        }
    }

    public void exportStatisticsAll(File targetExcelFile) {
        statisticsController.exportStatisticsAll(songs, targetExcelFile);
    }

    public void loadSettings() {
        LOG.debug("loading settings from file");
        File file = new File(FileAndDirectoryLocations.getSettingsFileName());
        try {
            InputStream xmlInputStream = new FileInputStream(file);
            settings = XMLConverter.fromXMLToSettingsModel(xmlInputStream);
            xmlInputStream.close();
        } catch (IOException e) {
            LOG.error("could not read settings from \"" + file.getAbsolutePath() + "\"");
        }
        if (settings == null) {
            // there was a problem while reading
            settings = new SettingsModel();
        }
        loadDefaultSettingsForUnsetSettings();
    }

    private void loadDefaultSettingsForUnsetSettings() {
        putDefaultIfKeyIsUnset(SettingKey.BACKGROUND_COLOR, Color.BLACK);
        putDefaultIfKeyIsUnset(SettingKey.TEXT_COLOR, Color.WHITE);

        putDefaultIfKeyIsUnset(SettingKey.TOP_MARGIN, Integer.valueOf(10));
        putDefaultIfKeyIsUnset(SettingKey.LEFT_MARGIN, Integer.valueOf(0));
        putDefaultIfKeyIsUnset(SettingKey.RIGHT_MARGIN, Integer.valueOf(0));
        putDefaultIfKeyIsUnset(SettingKey.BOTTOM_MARGIN, Integer.valueOf(20));
        putDefaultIfKeyIsUnset(SettingKey.DISTANCE_TITLE_TEXT, Integer.valueOf(20));
        putDefaultIfKeyIsUnset(SettingKey.DISTANCE_TEXT_COPYRIGHT, Integer.valueOf(20));

        putDefaultIfKeyIsUnset(SettingKey.SONG_LIST_FILTER, FilterTypeEnum.TITLE_AND_LYRICS);
        putDefaultIfKeyIsUnset(SettingKey.SCREEN_1_CONTENTS, ScreenContentsEnum.ONLY_LYRICS);
        putDefaultIfKeyIsUnset(SettingKey.SCREEN_1_DISPLAY, "");
        putDefaultIfKeyIsUnset(SettingKey.SCREEN_2_CONTENTS, ScreenContentsEnum.LYRICS_AND_CHORDS);
        putDefaultIfKeyIsUnset(SettingKey.SCREEN_2_DISPLAY, "");

        putDefaultIfKeyIsUnset(SettingKey.SHOW_TITLE, Boolean.TRUE);
        putDefaultIfKeyIsUnset(SettingKey.TITLE_FONT, new Font(Font.SERIF, Font.BOLD, 10));
        putDefaultIfKeyIsUnset(SettingKey.LYRICS_FONT, new Font(Font.SERIF, Font.PLAIN, 10));
        putDefaultIfKeyIsUnset(SettingKey.TRANSLATION_FONT, new Font(Font.SERIF, Font.PLAIN, 10));
        putDefaultIfKeyIsUnset(SettingKey.COPYRIGHT_FONT, new Font(Font.SERIF, Font.ITALIC, 10));
        putDefaultIfKeyIsUnset(SettingKey.LOGO_FILE, "");
        putDefaultIfKeyIsUnset(SettingKey.SECONDS_UNTIL_COUNTED, Integer.valueOf(60));

        // check that really all settings are set
        for (SettingKey key : SettingKey.values()) {
            if (!settings.isSet(key)) {
                throw new IllegalStateException("unset value for setting key: " + key);
            }
        }
    }

    private void putDefaultIfKeyIsUnset(SettingKey key, Object defaultValue) {
        if (!settings.isSet(key)) {
            settings.put(key, defaultValue);
        }
    }

    public synchronized boolean saveSettings() {
        File file = new File(FileAndDirectoryLocations.getSettingsFileName());
        try {
            OutputStream xmlOutputStream = new FileOutputStream(file);
            XMLConverter.fromSettingsModelToXML(settings, xmlOutputStream);
            xmlOutputStream.close();
            return true;
        } catch (IOException e) {
            LOG.error("could not write settings to \"" + file.getAbsolutePath() + "\"");
            return false;
        }
    }

    private SongsModel populateSongsModel() {
        File file = new File(getSongsFileName());
        LOG.debug("loading songs from file {}", file.getAbsolutePath());

        try {
            InputStream xmlInputStream = new FileInputStream(file);
            SongsModel modelToReturn = XMLConverter.fromXMLToSongsModel(xmlInputStream);
            xmlInputStream.close();
            return modelToReturn;
        } catch (IOException e) {
            LOG.error("could not read songs from " + file.getAbsolutePath());
            return null;
        }
    }

    public synchronized boolean saveSongs() {
        File file = new File(getSongsFileName());
        try {
            OutputStream xmlOutputStream = new FileOutputStream(file);
            XMLConverter.fromSongsModelToXML(songs, xmlOutputStream);
            xmlOutputStream.close();
            return true;
        } catch (IOException e) {
            LOG.error("could not write songs to \"" + file.getAbsolutePath() + "\"");
            return false;
        }
    }

    public SongsModel getSongs() {
        return songs;
    }

    public SettingsModel getSettings() {
        return settings;
    }

    private String getSongsFileName() {
        if (songsFileName == null) {
            return FileAndDirectoryLocations.getDefaultSongsFileName();
        } else {
            return songsFileName;
        }
    }

    /**
     * Use a nice LaF.
     * 
     * @return {@code true} if the LaF could be applied, {@code false} otherwise
     */
    public boolean setupLookAndFeel() {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
            return true;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                | UnsupportedLookAndFeelException e) {
            LOG.warn("could not apply the look-and-feel");
            return false;
        }
    }

    public Song getCurrentlyPresentedSong() {
        return currentlyPresentedSong;
    }

    public static void initAnimationTimer() {
        final TimingSource animationTimer = new SwingTimerTimingSource(5, TimeUnit.MILLISECONDS);
        Animator.setDefaultTimingSource(animationTimer);
        animationTimer.init();
    }

}