tvbrowser.extras.reminderplugin.ReminderPlugin.java Source code

Java tutorial

Introduction

Here is the source code for tvbrowser.extras.reminderplugin.ReminderPlugin.java

Source

/*
 * TV-Browser
 * Copyright (C) 04-2003 Martin Oberhauser (martin@tvbrowser.org)
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * CVS information:
 *  $RCSfile$
 *   $Source$
 *     $Date$
 *   $Author$
 * $Revision$
 */

package tvbrowser.extras.reminderplugin;

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.SourceDataLine;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JOptionPane;

import org.apache.commons.lang.StringUtils;

import tvbrowser.core.Settings;
import tvbrowser.core.TvDataUpdateListener;
import tvbrowser.core.TvDataUpdater;
import tvbrowser.core.icontheme.IconLoader;
import tvbrowser.extras.common.ConfigurationHandler;
import tvbrowser.ui.mainframe.MainFrame;
import util.exc.ErrorHandler;
import util.io.IOUtilities;
import util.io.stream.ObjectInputStreamProcessor;
import util.io.stream.ObjectOutputStreamProcessor;
import util.io.stream.StreamUtilities;
import util.ui.Localizer;
import util.ui.TVBrowserIcons;
import util.ui.UIThreadRunner;
import util.ui.UiUtilities;
import devplugin.ActionMenu;
import devplugin.ContextMenuAction;
import devplugin.ContextMenuSeparatorAction;
import devplugin.Date;
import devplugin.Plugin;
import devplugin.PluginTreeNode;
import devplugin.Program;
import devplugin.ProgramItem;
import devplugin.ProgramReceiveIf;
import devplugin.ProgramReceiveTarget;
import devplugin.SettingsItem;

/**
 * TV-Browser
 *
 * @author Martin Oberhauser
 */
public class ReminderPlugin {

    /** The localizer for this class. */
    static Localizer mLocalizer = util.ui.Localizer.getLocalizerFor(ReminderPlugin.class);

    private static final java.util.logging.Logger mLog = Logger.getLogger(ReminderPlugin.class.getName());

    private ReminderList mReminderList;
    private Properties mSettings;

    private static ReminderPlugin mInstance;
    private static String DATAFILE_PREFIX = "reminderplugin.ReminderPlugin";
    private static String DATAFILE_NAME = "reminder.dat";

    private ConfigurationHandler mConfigurationHandler;

    private static final PluginTreeNode mRootNode = new PluginTreeNode(mLocalizer.msg("pluginName", "Reminders"));

    private boolean mHasRightToStartTimer = false;

    private boolean mHasRightToSave = true;

    /** The IDs of the plugins that should receive the favorites. */
    private ProgramReceiveTarget[] mClientPluginTargets;
    private int mMarkPriority = -2;

    private ReminderPlugin() {
        mInstance = this;
        mClientPluginTargets = new ProgramReceiveTarget[0];
        mConfigurationHandler = new ConfigurationHandler(getReminderPluginId());
        loadSettings();
        mReminderList = new ReminderList();
        mReminderList.setReminderTimerListener(new ReminderTimerListener(mSettings, mReminderList));
        loadReminderData();

        TvDataUpdater.getInstance().addTvDataUpdateListener(new TvDataUpdateListener() {
            public void tvDataUpdateStarted() {
                mHasRightToSave = false;
            }

            public void tvDataUpdateFinished() {
                if (mSettings.getProperty("showRemovedDialog", "true").compareTo("true") == 0) {
                    Program[] removedPrograms = mReminderList.updatePrograms();
                    if (removedPrograms.length > 0) {
                        Window parent = UiUtilities.getLastModalChildOf(MainFrame.getInstance());
                        RemovedProgramsDialog dlg = new RemovedProgramsDialog(parent, removedPrograms);
                        util.ui.UiUtilities.centerAndShow(dlg);
                    }
                } else {
                    mReminderList.updatePrograms();
                }

                mHasRightToSave = true;
                saveReminders();

                ReminderListDialog.updateReminderList();
            }
        });

    }

    /**
     * Gets the current instance of this class, or if
     * there is no instance creates a new one.
     *
     * @return The current instance of this class.
     */
    public static synchronized ReminderPlugin getInstance() {
        if (mInstance == null) {
            new ReminderPlugin();
        }
        return mInstance;
    }

    @Override
    public String toString() {
        return getName();
    }

    static String getName() {
        return mLocalizer.msg("pluginName", "Reminder");
    }

    /**
     * Is been called by TVBrowser when the TV-Browser start is finished.
     */
    public void handleTvBrowserStartFinished() {
        updateRootNode(false);
        mHasRightToStartTimer = true;
        mReminderList.removeExpiredItems();
        mReminderList.startTimer();
    }

    /**
     * Is used by the ReminderList to track if
     * the TV-Browser start was finished.
     * (When it's finished then the Timer is allowed to start.)
     *
     * @return If the Timer is allowed to start.
     */
    protected boolean isAllowedToStartTimer() {
        return mHasRightToStartTimer;
    }

    private void loadSettings() {

        try {
            Properties prop = mConfigurationHandler.loadSettings();
            loadSettings(prop);
        } catch (IOException e) {
            ErrorHandler.handle("Could not load reminder data.", e);
        }

    }

    private ObjectInputStream getObjectInputStream(File f) throws IOException {
        return new ObjectInputStream(new BufferedInputStream(new FileInputStream(f), 0x4000));
    }

    private void loadReminderData() {
        try {

            File newFile = new File(Settings.getUserSettingsDirName(), DATAFILE_NAME);

            if (newFile.exists()) {
                StreamUtilities.objectInputStream(newFile, 0x4000, new ObjectInputStreamProcessor() {

                    @Override
                    public void process(ObjectInputStream inputStream) throws IOException {
                        try {
                            readData(inputStream);
                        } catch (ClassNotFoundException e) {
                            ErrorHandler.handle("Could not load reminder data", e);
                        }
                    }
                });
            } else {
                tryToReadDataFromPreviousVersions();
            }
            mReminderList.removeExpiredItems();
            mReminderList.setReminderTimerListener(new ReminderTimerListener(mSettings, mReminderList));

        } catch (IOException e) {
            ErrorHandler.handle("Could not load reminder data", e);
        }
    }

    private void tryToReadDataFromPreviousVersions() {
        boolean oldDataRead = false;
        try {
            File nodeFile = new File(Settings.getUserSettingsDirName(), "java.reminderplugin.ReminderPlugin.node");
            if (nodeFile.exists()) {
                StreamUtilities.objectInputStream(nodeFile, 0x4000, new ObjectInputStreamProcessor() {

                    @Override
                    public void process(ObjectInputStream inputStream) throws IOException {
                        try {
                            readReminderFromTVBrowser21and20(inputStream);
                        } catch (ClassNotFoundException e) {
                            mLog.log(Level.WARNING, "Could not read data from previous version", e);
                        }
                    }
                });
                oldDataRead = true;
                nodeFile.delete();
            }
            File datFile = new File(Settings.getUserSettingsDirName(), "java.reminderplugin.ReminderPlugin.dat");
            if (datFile.exists()) {
                if (!oldDataRead) {
                    StreamUtilities.objectInputStream(datFile, 0x4000, new ObjectInputStreamProcessor() {

                        @Override
                        public void process(ObjectInputStream inputStream) throws IOException {
                            try {
                                readReminderFromBeforeTVBrowser20(inputStream);
                            } catch (ClassNotFoundException e) {
                                mLog.log(Level.WARNING, "Could not read data from previous version", e);
                            }
                        }
                    });
                }
                datFile.delete();
            }
        } catch (IOException e) {
            mLog.log(Level.WARNING, "Could not read data from previous version", e);
        }
    }

    /**
     * Gets the settings for the reminder.
     *
     * @return The settings of the reminder.
     */
    public Properties getSettings() {
        return mSettings;
    }

    /**
     * Halt the reminder listener.
     */
    public void pauseReminder() {
        mReminderList.stopTimer();
    }

    /**
     * Save the reminder data.
     */
    public synchronized void store() {
        try {
            String userDirectoryName = Settings.getUserSettingsDirName();
            File userDirectory = new File(userDirectoryName);
            File tmpDatFile = new File(userDirectory, DATAFILE_NAME + ".temp");
            File datFile = new File(userDirectory, DATAFILE_NAME);
            StreamUtilities.objectOutputStream(tmpDatFile, new ObjectOutputStreamProcessor() {
                public void process(ObjectOutputStream out) throws IOException {
                    writeData(out);
                    out.flush();
                    out.close();
                }
            });

            datFile.delete();
            tmpDatFile.renameTo(datFile);
        } catch (IOException e) {
            ErrorHandler.handle("Could not store reminder data.", e);
        }
        try {
            mConfigurationHandler.storeSettings(mSettings);
        } catch (IOException e) {
            ErrorHandler.handle("Could not store reminder settings.", e);
        }
    }

    private void readData(ObjectInputStream in) throws IOException, ClassNotFoundException {

        int version = in.readInt(); // version

        mReminderList.setReminderTimerListener(null);
        mReminderList.read(in);

        if (version == 3) {
            mClientPluginTargets = new ProgramReceiveTarget[in.readInt()];

            for (int i = 0; i < mClientPluginTargets.length; i++) {
                mClientPluginTargets[i] = new ProgramReceiveTarget(in);
            }
        }
    }

    private void readReminderFromTVBrowser21and20(ObjectInputStream in) throws IOException, ClassNotFoundException {

        int cnt = in.readInt();
        for (int i = 0; i < cnt; i++) {
            int type = in.readInt();
            if (type == 2) { // Node.PROGRAM
                ProgramItem item = new ProgramItem();
                item.read(in);
                String m = item.getProperty("minutes");
                int minutes;
                try {
                    minutes = Integer.parseInt(m);
                } catch (NumberFormatException e) {
                    minutes = 10;
                }
                Program program = item.getProgram();

                if (program != null) {
                    mReminderList.add(program, new ReminderContent(minutes));
                }

                in.readInt(); // cnt (should be 0)

            }
        }
        in.close();
    }

    /**
     * read the object from an input stream.
     *
     * @param in the stream to read from
     * @throws IOException if something went wrong reading the stream
     * @throws ClassNotFoundException if the object could not be deserialized
     */
    private void readReminderFromBeforeTVBrowser20(final ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        int version = in.readInt();
        if (version == 1) {
            int size = in.readInt();
            for (int i = 0; i < size; i++) {
                in.readInt(); // read version
                int reminderMinutes = in.readInt();
                Date programDate = Date.readData(in);
                String programId = (String) in.readObject();
                Program program = Plugin.getPluginManager().getProgram(programDate, programId);

                // Only add items that were able to load their program
                if (program != null) {
                    mReminderList.add(program, new ReminderContent(reminderMinutes));
                }
            }
        } else if (version == 2) {
            mReminderList.setReminderTimerListener(null);
            mReminderList.read(in);
        }
    }

    /**
     * Save the data of this plugin in the given stream.
     * <p>
     * @param out The stream to write the data in.
     * @throws IOException
     */
    public void writeData(ObjectOutputStream out) throws IOException {
        out.writeInt(3);
        mReminderList.writeData(out);
        out.writeInt(mClientPluginTargets.length);

        for (ProgramReceiveTarget target : mClientPluginTargets) {
            target.writeData(out);
        }
    }

    private void loadSettings(Properties settings) {
        if (settings == null) {
            settings = new Properties();
        }
        if (settings.getProperty("usemsgbox") == null) {
            settings.setProperty("usemsgbox", "true");
        }
        mSettings = settings;

        if (settings.containsKey("usethisplugin") || settings.containsKey("usesendplugin")) {
            String plugins = settings.getProperty("usethisplugin", "").trim();
            boolean sendEnabled = settings.getProperty("usesendplugin", "").compareToIgnoreCase("true") == 0;

            settings.remove("usethisplugin");
            settings.remove("usesendplugin");

            if (plugins.length() > 0 && sendEnabled) {
                if (plugins.indexOf(';') == -1) {
                    mClientPluginTargets = new ProgramReceiveTarget[1];
                    mClientPluginTargets[0] = ProgramReceiveTarget
                            .createDefaultTargetForProgramReceiveIfId(plugins);
                } else {
                    String[] ids = plugins.split(";");

                    mClientPluginTargets = new ProgramReceiveTarget[ids.length];

                    for (int i = 0; i < ids.length; i++) {
                        mClientPluginTargets[i] = ProgramReceiveTarget
                                .createDefaultTargetForProgramReceiveIfId(ids[i]);
                    }
                }
            }
        }

        if (settings.containsKey("autoCloseReminderAtProgramEnd")) {
            if (settings.getProperty("autoCloseReminderAtProgramEnd", "true").equalsIgnoreCase("true")) {
                settings.setProperty("autoCloseBehaviour", "onEnd");
            }

            settings.remove("autoCloseReminderAtProgramEnd");
        }
    }

    protected ActionMenu getContextMenuActions(final Frame parentFrame, final Program program) {
        if (mReminderList.contains(program)) {
            int remainingMinutes = getTimeToProgramStart(program);
            if (remainingMinutes < 0) {
                remainingMinutes = 1;
            }
            int maxIndex = 1;
            for (int i = 1; i < ReminderFrame.REMIND_VALUE_ARR.length; i++) {
                if (ReminderFrame.REMIND_VALUE_ARR[i] < remainingMinutes) {
                    maxIndex = i;
                }
            }
            final ReminderListItem item = mReminderList.getReminderItem(program);
            String[] entries = ReminderFrame.REMIND_MSG_ARR;

            ArrayList<ActionMenu> actions = new ArrayList<ActionMenu>(maxIndex + 2);

            actions.add(new ActionMenu(new AbstractAction(entries[0]) {
                public void actionPerformed(ActionEvent e) {
                    mReminderList.removeWithoutChecking(program);
                    updateRootNode(true);
                }
            }));
            actions.add(new ActionMenu(ContextMenuSeparatorAction.getInstance()));

            for (int i = 1; i <= maxIndex; i++) {
                final int minutes = ReminderFrame.REMIND_VALUE_ARR[i];
                actions.add(new ActionMenu(new AbstractAction(entries[i]) {
                    public void actionPerformed(ActionEvent e) {
                        item.setMinutes(minutes);
                    }
                }, minutes == item.getMinutes()));
            }

            actions.add(new ActionMenu(ContextMenuSeparatorAction.getInstance()));
            actions.add(new ActionMenu(new AbstractAction(mLocalizer.msg("comment", "Change comment"),
                    TVBrowserIcons.edit(TVBrowserIcons.SIZE_SMALL)) {
                @Override
                public void actionPerformed(ActionEvent e) {
                    item.changeComment(parentFrame);
                }
            }));

            return new ActionMenu(getName(), IconLoader.getInstance().getIconFromTheme("apps", "appointment", 16),
                    actions.toArray(new ActionMenu[actions.size()]));
        } else if ((program.isExpired() || program.isOnAir())
                && (!program.equals(Plugin.getPluginManager().getExampleProgram()))) {
            return null;
        } else {
            ContextMenuAction action = new ContextMenuAction();
            action.setText(mLocalizer.msg("contextMenuText", "Remind me"));
            action.setSmallIcon(IconLoader.getInstance().getIconFromTheme("actions", "appointment-new", 16));
            action.setActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent event) {
                    final Window w = UiUtilities.getLastModalChildOf(MainFrame.getInstance());
                    try {
                        UIThreadRunner.invokeAndWait(new Runnable() {

                            @Override
                            public void run() {
                                ReminderDialog dlg = new ReminderDialog(w, program, mSettings);
                                Settings.layoutWindow("extras.remiderContext", dlg);

                                if (mSettings.getProperty("showTimeSelectionDialog", "true")
                                        .compareTo("true") == 0) {
                                    UiUtilities.centerAndShow(dlg);

                                    if (dlg.getOkPressed()) {
                                        mReminderList.add(program, dlg.getReminderContent());
                                        mReminderList.unblockProgram(program);
                                        updateRootNode(true);
                                    }
                                    dlg.dispose();
                                } else {
                                    mReminderList.add(program, dlg.getReminderContent());
                                    mReminderList.unblockProgram(program);
                                    updateRootNode(true);
                                }
                            }
                        });
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
            return new ActionMenu(action);
        }
    }

    /**
     * Gets the default reminder time.
     * <p>
     * @return The default reminder time in minutes.
     */
    public int getDefaultReminderTime() {
        String defaultReminderEntryStr = (String) mSettings.get("defaultReminderEntry");
        int minutes = 10;
        if (defaultReminderEntryStr != null) {
            try {
                int inx = Integer.parseInt(defaultReminderEntryStr);
                if (inx < ReminderDialog.SMALL_REMIND_VALUE_ARR.length) {
                    minutes = ReminderDialog.SMALL_REMIND_VALUE_ARR[inx];
                }
            } catch (NumberFormatException e) {
                // ignore
            }
        }
        return minutes;
    }

    /**
     * Add the given programs to the reminder list.
     * <p>
     * @param programArr The programs to add.
     */
    public void addPrograms(Program[] programArr) {
        mReminderList.addAndCheckBlocked(programArr, getDefaultReminderTime());
        updateRootNode(true);
    }

    /**
     * Removes the given program from the reminder list.
     * <p>
     * @param prog The program to remove.
     * @return The reminder minutes of the program
     * or -1 if the program was not in the list.
     */
    public int removeProgram(Program prog) {
        ReminderListItem item = null;

        synchronized (mReminderList) {
            item = mReminderList.getReminderItem(prog);

            mReminderList.remove(prog);
        }

        if (item != null) {
            return item.getMinutes();
        }

        return -2;
    }

    /**
     * Remove the given programs from the reminder list.
     * <p>
     * @param progArr The programs to remove.
     */
    public void removePrograms(Program[] progArr) {
        synchronized (mReminderList) {
            mReminderList.remove(progArr);
        }
    }

    /**
     * Adds a program to the reminder list.
     * <p>
     * @param prog The program to add.
     * @param reminderMinutes The reminder minutes for the program.
     */
    public void addProgram(Program prog, int reminderMinutes) {
        mReminderList.add(prog, new ReminderContent(reminderMinutes));
    }

    /**
     * Gets the reminder minutes for the given program.
     * <p>
     * @param prog The program to get the reminder minutes for.
     * @return The reminder minutes of the program or -1 if the
     * program is not in the reminder list
     * @since 2.7
     */
    public int getReminderMinutesForProgram(Program prog) {
        ReminderListItem item = null;

        synchronized (mReminderList) {
            item = mReminderList.getReminderItem(prog);
        }

        if (item != null) {
            return item.getMinutes();
        }

        return -1;
    }

    /**
     * Gets the root node for the plugin tree.
     * <p>
     * @return The root node for the plugin tree.
     */
    public static PluginTreeNode getRootNode() {
        return mRootNode;
    }

    /**
     * Updates the plugin tree entry for this plugin.
     * <p>
     *
     * @param save
     *          <code>True</code> if the reminder entries should be saved.
     */
    public void updateRootNode(boolean save) {
        mRootNode.removeAllActions();
        mRootNode.getMutableTreeNode()
                .setIcon(IconLoader.getInstance().getIconFromTheme("apps", "appointment", 16));

        Action editReminders = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                ReminderListDialog dlg = new ReminderListDialog(MainFrame.getInstance(), mReminderList);
                UiUtilities.centerAndShow(dlg);
            }
        };
        editReminders.putValue(Action.SMALL_ICON,
                IconLoader.getInstance().getIconFromTheme("apps", "appointment", 16));
        editReminders.putValue(Action.NAME, mLocalizer.ellipsisMsg("buttonText", "Edit reminder list"));

        Action openSettings = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                MainFrame.getInstance().showSettingsDialog(SettingsItem.REMINDER);
            }
        };
        openSettings.putValue(Action.SMALL_ICON, TVBrowserIcons.preferences(TVBrowserIcons.SIZE_SMALL));
        openSettings.putValue(Action.NAME, Localizer.getLocalization(Localizer.I18N_SETTINGS));

        mRootNode.addAction(editReminders);
        mRootNode.addAction(null);
        mRootNode.addAction(openSettings);

        mRootNode.removeAllChildren();

        ReminderListItem[] items = mReminderList.getReminderItems();
        ArrayList<Program> listNewPrograms = new ArrayList<Program>(items.length);
        for (ReminderListItem reminderItem : items) {
            listNewPrograms.add(reminderItem.getProgram());
        }
        mRootNode.addPrograms(listNewPrograms);

        mRootNode.update();

        if (save && mHasRightToSave) {
            saveReminders();
        }
    }

    private void saveReminders() {
        Thread thread = new Thread("Save reminders") {
            @Override
            public void run() {
                store();
            }
        };
        thread.setPriority(Thread.MIN_PRIORITY);
        thread.start();
    }

    protected static ActionMenu getButtonAction() {
        AbstractAction action = new AbstractAction() {
            public void actionPerformed(ActionEvent evt) {
                getInstance().showManageRemindersDialog();
            }
        };

        action.putValue(Action.NAME, getName());
        action.putValue(Action.SMALL_ICON, IconLoader.getInstance().getIconFromTheme("apps", "appointment", 16));
        action.putValue(Plugin.BIG_ICON, IconLoader.getInstance().getIconFromTheme("apps", "appointment", 22));
        action.putValue(Action.SHORT_DESCRIPTION,
                mLocalizer.msg("description", "Reminds you of programs to not miss them."));

        return new ActionMenu(action);
    }

    /**
     * Plays a sound.
     *
     * @param fileName
     *          The file name of the sound to play.
     * @return The sound Object.
     */
    public static Object playSound(final String fileName) {
        try {
            if (StringUtils.endsWithIgnoreCase(fileName, ".mid")) {
                final Sequencer sequencer = MidiSystem.getSequencer();
                sequencer.open();

                final InputStream midiFile = new FileInputStream(fileName);
                sequencer.setSequence(MidiSystem.getSequence(midiFile));

                sequencer.start();

                new Thread("Reminder MIDI sequencer") {
                    @Override
                    public void run() {
                        setPriority(Thread.MIN_PRIORITY);
                        while (sequencer.isRunning()) {
                            try {
                                Thread.sleep(100);
                            } catch (Exception ee) {
                                // ignore
                            }
                        }

                        try {
                            sequencer.close();
                            midiFile.close();
                        } catch (Exception ee) {
                            // ignore
                        }
                    }
                }.start();

                return sequencer;
            } else {
                final AudioInputStream ais = AudioSystem.getAudioInputStream(new File(fileName));

                final AudioFormat format = ais.getFormat();
                final DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

                if (AudioSystem.isLineSupported(info)) {
                    final SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);

                    line.open(format);
                    line.start();

                    new Thread("Reminder audio playing") {
                        private boolean stopped;

                        @Override
                        public void run() {
                            byte[] myData = new byte[1024 * format.getFrameSize()];
                            int numBytesToRead = myData.length;
                            int numBytesRead = 0;
                            int total = 0;
                            int totalToRead = (int) (format.getFrameSize() * ais.getFrameLength());
                            stopped = false;

                            line.addLineListener(new LineListener() {
                                public void update(LineEvent event) {
                                    if (line != null && !line.isRunning()) {
                                        stopped = true;
                                        line.close();
                                        try {
                                            ais.close();
                                        } catch (Exception ee) {
                                            // ignore
                                        }
                                    }
                                }
                            });

                            try {
                                while (total < totalToRead && !stopped) {
                                    numBytesRead = ais.read(myData, 0, numBytesToRead);

                                    if (numBytesRead == -1) {
                                        break;
                                    }

                                    total += numBytesRead;
                                    line.write(myData, 0, numBytesRead);
                                }
                            } catch (Exception e) {
                            }

                            line.drain();
                            line.stop();
                        }
                    }.start();

                    return line;
                } else {
                    URL url = new File(fileName).toURI().toURL();
                    AudioClip clip = Applet.newAudioClip(url);
                    clip.play();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            if ((new File(fileName)).isFile()) {
                URL url;
                try {
                    url = new File(fileName).toURI().toURL();
                    AudioClip clip = Applet.newAudioClip(url);
                    clip.play();
                } catch (MalformedURLException e1) {
                }
            } else {
                String msg = mLocalizer.msg("error.1", "Error loading reminder sound file!\n({0})", fileName);
                JOptionPane.showMessageDialog(UiUtilities.getBestDialogParent(MainFrame.getInstance()), msg,
                        Localizer.getLocalization(Localizer.I18N_ERROR), JOptionPane.ERROR_MESSAGE);
            }
        }
        return null;
    }

    protected ActionMenu getContextMenuActions(Program program) {
        return getContextMenuActions(null, program);
    }

    /**
     * get the ID of the plugin (without the need to load the plugin)
     *
     * @return the id
     * @since 3.0
     */
    public static String getReminderPluginId() {
        return DATAFILE_PREFIX;
    }

    protected ProgramReceiveTarget[] getClientPluginsTargets() {
        ArrayList<ProgramReceiveTarget> list = new ArrayList<ProgramReceiveTarget>();
        for (ProgramReceiveTarget target : mClientPluginTargets) {
            ProgramReceiveIf plugin = target.getReceifeIfForIdOfTarget();
            if (plugin != null && plugin.canReceiveProgramsWithTarget()) {
                list.add(target);
            }
        }
        return list.toArray(new ProgramReceiveTarget[list.size()]);
    }

    protected void setClientPluginsTargets(ProgramReceiveTarget[] targets) {
        if (targets != null) {
            mClientPluginTargets = targets;
        } else {
            mClientPluginTargets = new ProgramReceiveTarget[0];
        }
    }

    protected int getMarkPriority() {
        if (mMarkPriority == -2 && mSettings != null) {
            mMarkPriority = Integer
                    .parseInt(mSettings.getProperty("markPriority", String.valueOf(Program.MIN_MARK_PRIORITY)));
            return mMarkPriority;
        } else {
            return mMarkPriority;
        }
    }

    protected void setMarkPriority(int priority) {
        mMarkPriority = priority;

        ReminderListItem[] items = mReminderList.getReminderItems();

        for (ReminderListItem item : items) {
            item.getProgram().validateMarking();
        }

        mSettings.setProperty("markPriority", String.valueOf(priority));
        saveReminders();
    }

    protected static int getTimeToProgramStart(Program program) {
        int progMinutesAfterMidnight = program.getHours() * 60 + program.getMinutes();
        int remainingMinutes = progMinutesAfterMidnight - IOUtilities.getMinutesAfterMidnight()
                + 1440 * (program.getDate().getNumberOfDaysSince(Date.getCurrentDate()));
        if (remainingMinutes < 0) {
            remainingMinutes += 24 * 60;
        }
        return remainingMinutes;
    }

    protected void handleTvDataUpdateFinished() {
        mReminderList.removeExpiredItems();
        updateRootNode(false);
    }

    private void showManageRemindersDialog() {
        Window w = UiUtilities.getLastModalChildOf(MainFrame.getInstance());
        ReminderListDialog dlg = new ReminderListDialog(w, mReminderList);

        int x = Integer.parseInt(mSettings.getProperty("dlgXPos", "-1"));
        int y = Integer.parseInt(mSettings.getProperty("dlgYPos", "-1"));

        if (x == -1 || y == -1) {
            UiUtilities.centerAndShow(dlg);
        } else {
            dlg.setLocation(x, y);
            dlg.setVisible(true);
        }

        mSettings.setProperty("dlgXPos", String.valueOf(dlg.getX()));
        mSettings.setProperty("dlgYPos", String.valueOf(dlg.getY()));
        mSettings.setProperty("dlgWidth", String.valueOf(dlg.getWidth()));
        mSettings.setProperty("dlgHeight", String.valueOf(dlg.getHeight()));
    }

    public static void resetLocalizer() {
        mLocalizer = Localizer.getLocalizerFor(ReminderPlugin.class);
    }

}