Author: echatellier Date: 2012-03-25 23:15:46 +0200 (Sun, 25 Mar 2012) New Revision: 2840 Url: http://chorem.org/repositories/revision/jtimer/2840 Log: Add branche for new time tracking algorithm. refs #13, #282, #492 Added: branches/1.4.0-ttalgo/ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java Removed: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java Deleted: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java =================================================================== --- trunk/src/main/java/org/chorem/jtimer/JTimer.java 2012-03-23 15:11:54 UTC (rev 2836) +++ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java 2012-03-25 21:15:46 UTC (rev 2840) @@ -1,1458 +0,0 @@ -/* - * #%L - * jTimer - * - * $Id$ - * $HeadURL$ - * %% - * Copyright (C) 2007 - 2012 CodeLutin, Chatellier Eric - * %% - * 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/gpl-3.0.html>. - * #L% - */ - -package org.chorem.jtimer; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.io.File; -import java.io.IOException; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Timer; - -import javax.swing.ButtonGroup; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JRadioButtonMenuItem; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JToolBar; -import javax.swing.KeyStroke; -import javax.swing.ListSelectionModel; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.TreePath; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.chorem.jtimer.data.DataViolationException; -import org.chorem.jtimer.data.TimerCore; -import org.chorem.jtimer.entities.TimerProject; -import org.chorem.jtimer.entities.TimerTask; -import org.chorem.jtimer.ui.HelpFrame; -import org.chorem.jtimer.ui.StatusBar; -import org.chorem.jtimer.ui.TimerTaskEditor; -import org.chorem.jtimer.ui.alert.AlertEditor; -import org.chorem.jtimer.ui.report.ReportView; -import org.chorem.jtimer.ui.systray.SystrayManager; -import org.chorem.jtimer.ui.tasks.IdleDialog; -import org.chorem.jtimer.ui.tasks.RefreshTreeTask; -import org.chorem.jtimer.ui.tasks.RunTaskJob; -import org.chorem.jtimer.ui.treetable.ProjectsAndTasksTable; -import org.chorem.jtimer.ui.widget.WindowProperty2; -import org.chorem.jtimer.ui.ws.SwingConnectionInformationHandler; -import org.chorem.jtimer.ws.ConnectionDataHandler; -import org.chorem.jtimer.ws.ProjectManagement; -import org.jdesktop.application.Action; -import org.jdesktop.application.Application; -import org.jdesktop.application.ApplicationContext; -import org.jdesktop.application.ResourceMap; -import org.jdesktop.application.SingleFrameApplication; -import org.jdesktop.application.Task; -import org.jdesktop.application.TaskMonitor; - -/** - * Main jTimer application window. - * - * Respect JSR-296 - * - * @author chatellier - * @version $Revision$ - * - * Last update : $Date$ - * By : $Author$ - */ -public class JTimer extends SingleFrameApplication implements - TreeSelectionListener, MouseListener { - - /** log. */ - private static Log log = LogFactory.getLog(JTimer.class); - - /** Timer core controller. */ - protected TimerCore core; - - /** Tree structure. */ - protected ProjectsAndTasksTable projectsAndTasksTable; - - /** Systray manager. */ - protected SystrayManager systrayManager; - - /** I18n resources map. */ - protected ResourceMap resourceMap; - - /** Jtimer application config. */ - public static JTimerConfig config; - - /** Single project selection property. */ - protected boolean selectedSingleProject; - - /** Single task selection property. */ - protected boolean selectedSingleTask; - - /** Single task or project selection. */ - protected boolean selectedSingleElement; - - /** Single running task selection. */ - protected boolean selectedSingleRunningTask; - - /** Single non running task selection. */ - protected boolean selectedSingleStoppedTask; - - /** Multiples projects selection. */ - protected boolean selectedMultiplesProjects; - - /** Multiples tasks selection. */ - protected boolean selectedMultiplesTasks; - - /** Multiples elements selection. */ - protected boolean selectedMultiplesElements; - - /** - * Main. launch UI - * - * @param args args - */ - public static void main(String[] args) { - - if (log.isInfoEnabled()) { - log.info("Starting " + JTimer.class.getSimpleName() + " at " - + new Date()); - } - launch(JTimer.class, args); - } - - /** - * Initialize application. Called before UI build. - * - * @param args args - * @see Application#initialize(String[]) - */ - @Override - protected void initialize(String[] args) { - - // super, but does nothing - super.initialize(args); - - // load configuration - loadConfiguration(args); - - // init resources map - ApplicationContext ctxt = getContext(); - resourceMap = ctxt.getResourceMap(); - - // fix start in iconified mode - ctxt.getSessionStorage().putProperty(JFrame.class, new WindowProperty2()); - - // init timercore - core = new TimerCore(); - - // handler - ConnectionDataHandler handler = new SwingConnectionInformationHandler( - this); - ProjectManagement managementService = JTimerFactory - .getProjectManagementService(); - if (managementService != null) { - managementService.setConnectionDataHandler(handler); - } - - // Systray mgr - systrayManager = new SystrayManager(this); - core.getData().addDataEventListener(systrayManager); - - IdleDialog.init(this); - } - - /** - * Load configuration. - * - * @param args args to parse command line options - */ - protected void loadConfiguration(String[] args) { - - config = new JTimerConfig(); - - // add file migration for configuration file created before version 1.4 - File homeDir = new File(System.getProperty("user.home"), ".jtimer"); - File oldFile = new File(homeDir, "JTimer.properties"); - File newFile = new File(config.appConfig.getUserConfigDirectory(), config.appConfig.getConfigFileName()); - if (oldFile.isFile() && !newFile.isFile()) { - if (log.isInfoEnabled()) { - log.info("Migration configuration file location"); - } - try { - FileUtils.copyFile(oldFile, newFile); - } catch (IOException ex) { - if (log.isErrorEnabled()) { - log.error("Can't copy config file to new location", ex); - } - } - } - - // parse after file migration - config.parse(args); - } - - /** - * startup. - * - * Create frame menu bar. Create main component. - * - * @see Application#startup() - */ - @Override - protected void startup() { - - // set Menu Bar - getMainFrame().setJMenuBar(createMenuBar()); - - // show main panel components - show(createMainComponent()); - - } - - /** - * Create main component. - * - * Toolbar on top. Tree middle. Status bar on bottom. - * - * @return The component - */ - protected JComponent createMainComponent() { - - // panel = main component - JPanel panel = new JPanel(new BorderLayout()); - - // toolbar on top (north) - panel.add(createToolBar(), BorderLayout.NORTH); - - // tree middle (center-top) - ProjectsAndTasksTable projectTreeTable = createTreeTable(); - JScrollPane scrollPaneProjectTreeTable = new JScrollPane( - projectTreeTable); - panel.add(scrollPaneProjectTreeTable, BorderLayout.CENTER); - - // status bar bottom - StatusBar sb = new StatusBar(this, core.getData()); - // status bar ui will be notified from events - core.getData().addDataEventListener(sb); - panel.add(sb, BorderLayout.SOUTH); - - // taille par defaut au premier lancement de l'application - // sera ecrasee par la restauration de la session - panel.setPreferredSize(new Dimension(640, 480)); - - return panel; - } - - /** - * Create complex tree table. - * - * @return ProjectsAndTaskTable instance - */ - protected ProjectsAndTasksTable createTreeTable() { - - projectsAndTasksTable = new ProjectsAndTasksTable(this, core); - - // name used in properties files - projectsAndTasksTable.setName("projectslist"); - projectsAndTasksTable.addTreeSelectionListener(this); - projectsAndTasksTable.addMouseListener(this); - projectsAndTasksTable.setShowClosed(config.isShowClosed()); - - // since merge option, selection can be multiple - projectsAndTasksTable - .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - - // remove F2 KeyStroke from table - KeyStroke keyToRemove = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0); - InputMap imap = projectsAndTasksTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - while (imap != null) { - imap.remove(keyToRemove); - imap = imap.getParent(); - } - - return projectsAndTasksTable; - } - - /** - * Create toolbar. - * - * @return tool bar builded - */ - protected JComponent createToolBar() { - String[] toolbarActionNames = { "startTask", "stopTask", "newProject", - "newTask", "---", "addAnnotation", "editAlert" }; - JToolBar toolBar = new JToolBar(); - toolBar.setFloatable(false); - for (String actionName : toolbarActionNames) { - - if (actionName.equals("---")) { - toolBar.add(new JToolBar.Separator()); - } else { - JButton button = new JButton(); - button.setAction(getAction(actionName)); - - // hide text - // button.setVerticalTextPosition(JButton.BOTTOM); - // button.setHorizontalTextPosition(JButton.CENTER); - button.setHideActionText(true); - - button.setFocusable(false); - toolBar.add(button); - } - } - return toolBar; - } - - /** - * Create application menu bar. - * - * @return menu bar - */ - protected JMenuBar createMenuBar() { - - JMenuBar menuBar = new JMenuBar(); - String[] fileMenuActionNames = { "quit" }; - menuBar.add(createMenu("fileMenu", fileMenuActionNames)); - - String[] projectMenuActionNames = { "newProject", "editProject", - "closeProject", "deleteProject" }; - menuBar.add(createMenu("projectMenu", projectMenuActionNames)); - - String[] taskMenuActionNames = { "newTask", "editTask", "closeTask", - "deleteTask", "---", "startTask", "stopTask", "---", - "addAnnotation", "editAlert", "increment1Task", - "increment5Task", "increment30Task", "decrement1Task", - "decrement5Task", "decrement30Task", "setToZero", "mergeTasks" }; - menuBar.add(createMenu("taskMenu", taskMenuActionNames)); - - String[] reportMenuActionNames = { "makeReport" }; - menuBar.add(createMenu("reportMenu", reportMenuActionNames)); - - JMenu optionmMenu = createOptionMenu(); - menuBar.add(optionmMenu); - - String[] helpMenuActionNames = { "about" }; - menuBar.add(createMenu("helpMenu", helpMenuActionNames)); - - return menuBar; - } - - /** - * Create option dynamic menu. - * - * @return option menu - */ - protected JMenu createOptionMenu() { - JMenu menu = new JMenu(); - menu.setName("optionMenu"); - - // show closed - JMenuItem showClosedItem = new JCheckBoxMenuItem(); - showClosedItem.setAction(getAction("isShowClosed")); - showClosedItem.setSelected(config.isShowClosed()); - showClosedItem.setIcon(null); - menu.add(showClosedItem); - - // close to systray - JMenuItem closeToSysItem = new JCheckBoxMenuItem(); - closeToSysItem.setAction(getAction("isCloseToSystray")); - closeToSysItem.setSelected(config.isCloseToSystray()); - closeToSysItem.setIcon(null); - menu.add(closeToSysItem); - - // report first day of week - JMenu reportFDoW = new JMenu(); - reportFDoW.setName("optionReportFirstDayMenu"); - Calendar calendar = Calendar.getInstance(); - ButtonGroup bg = new ButtonGroup(); - // affiche la liste des jours dans l'ordre de la locale utilisateur - for (int day = calendar.getFirstDayOfWeek() ; day < calendar.getFirstDayOfWeek() + 7 ; day++) { - int realDay = (day - 1) % 7 + 1; - JRadioButtonMenuItem fdowItem = new JRadioButtonMenuItem(); - fdowItem.setAction(getAction("isReportFirstDayOfWeek" + realDay)); - fdowItem.setSelected(realDay == JTimer.config.getReportFirstDayOfWeek()); - fdowItem.setIcon(null); - reportFDoW.add(fdowItem); - bg.add(fdowItem); - } - menu.add(reportFDoW); - - return menu; - } - - /** - * Create single menu. - * - * @param menuName menu name - * @param actionNames associated actions - * @return menu - */ - protected JMenu createMenu(String menuName, String[] actionNames) { - JMenu menu = new JMenu(); - menu.setName(menuName); - addActionToMenu(menu, actionNames); - return menu; - } - - /** - * Add saf action to an existing menu. - * - * Menu have to be a JMenu or JPopupMenu. - * - * @param menu parent menu - * @param actionNames action names - */ - protected void addActionToMenu(JComponent menu, String[] actionNames) { - for (String actionName : actionNames) { - if (actionName.equals("---")) { - menu.add(new JSeparator()); - } else if (actionName.startsWith("is")) { - // if action name start by is - // display it as CheckBox - JMenuItem menuItem = new JCheckBoxMenuItem(); - // link to an @Action - menuItem.setAction(getAction(actionName)); - menuItem.setIcon(null); - menu.add(menuItem); - } else { - JMenuItem menuItem = new JMenuItem(); - // link to an @Action - menuItem.setAction(getAction(actionName)); - menuItem.setIcon(null); - menu.add(menuItem); - } - } - } - - /** - * Ready. Called when UI is ready and displayed. - * - * @see Application#ready() - */ - @Override - protected void ready() { - - // init core, load list, synchronization, etc... - boolean init = core.init(); - - if (init) { - // schedule tree refresh at midnight - scheduleTreeRefresh(); - - // install icon (do it at last action) - systrayManager.install(); - } else { - String failTitle = resourceMap.getString("startFail.title"); - String failMessage = resourceMap.getString("startFail.message"); - JOptionPane.showMessageDialog(getMainFrame(), failMessage, - failTitle, JOptionPane.ERROR_MESSAGE); - exit(); - } - } - - /** - * Called on application shutdown. - * - * Save context. - * - * @see SingleFrameApplication#shutdown() - */ - @Override - protected void shutdown() { - log.debug("Shutdown called"); - core.exit(); - - // save context - // super, sauve le context des fenetres, etc... - super.shutdown(); - } - - /** - * Refresh tree at midnight. - */ - protected void scheduleTreeRefresh() { - - // task used to refresh tree - java.util.TimerTask refreshTreeTask = new RefreshTreeTask(core); - - Timer timer = new Timer(); - - Calendar date = Calendar.getInstance(); - date.setTimeInMillis(System.currentTimeMillis()); - date.set(Calendar.HOUR_OF_DAY, 0); - date.set(Calendar.MINUTE, 0); - date.set(Calendar.SECOND, 0); - date.set(Calendar.MILLISECOND, 0); - date.add(Calendar.DAY_OF_YEAR, 1); // run only next day - - // Schedule to run every day in midnight - // task,firstTime,period - timer.schedule(refreshTreeTask, date.getTime(), // at date - 1000 * 60 * 60 * 24 // every day - ); - } - - /** - * Display a popup error message. - * - * @param errorMessageKey saf error message key - */ - protected void displayErrorMessage(String errorMessageKey) { - String title = resourceMap.getString("action.invalidActionTitle"); - String message = resourceMap.getString(errorMessageKey); - - // check untranslated string - if (StringUtils.isEmpty(message)) { - message = resourceMap.getString("action.missingErrorMessage", - errorMessageKey); - } - - JOptionPane.showMessageDialog(getMainFrame(), message, title, - JOptionPane.ERROR_MESSAGE); - } - - /** - * New project action. - * - * Ask user for project name - */ - @Action - public void newProject() { - - String projectName = JOptionPane.showInputDialog(getMainFrame(), - resourceMap.getString("input.newProjectMessage"), resourceMap - .getString("input.newProjectTitle"), - JOptionPane.QUESTION_MESSAGE); - - if (projectName != null) { - - // remove unneeded spaces - projectName = projectName.trim(); - - TimerProject p = new TimerProject(projectName); - - // add creation date - p.setCreationDate(new Date()); - - try { - core.getData().addProject(p); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - } - - /** - * Edit project - * - * Enabled when a project is selected - */ - @Action(enabledProperty = "selectedSingleProject") - public void editProject() { - TimerProject project = projectsAndTasksTable.getSelectedProjects().get( - 0); - - String newProjectName = (String) JOptionPane.showInputDialog(getMainFrame(), resourceMap - .getString("input.editProjectMessage"), resourceMap - .getString("input.editProjectTitle"), - JOptionPane.INFORMATION_MESSAGE, null, null, project.getName()); - - if (newProjectName != null) { - - // remove unneeded spaces - newProjectName = newProjectName.trim(); - - try { - core.getData().editProject(project, newProjectName); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - } - - /** - * Create new task action. - * - * Enabled when a project or a task is selected - */ - @Action(enabledProperty = "selectedSingleElement") - public void newTask() { - - // select task to add new task - TimerTask selectedTask = projectsAndTasksTable.getSelectedElements() - .get(0); - - String taskName = JOptionPane.showInputDialog(getMainFrame(), - resourceMap.getString("input.newTaskMessage", selectedTask - .getName()), resourceMap - .getString("input.newTaskTitle"), - JOptionPane.QUESTION_MESSAGE); - - if (taskName != null) { - - // remove unneeded spaces - taskName = taskName.trim(); - - TimerTask t = new TimerTask(taskName); - - // Fix creation date - t.setCreationDate(new Date()); - - try { - core.getData().addTask(selectedTask, t); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - - } - - /** - * Edit task. - * - * Enabled when a task is selected - */ - @Action(enabledProperty = "selectedSingleTask") - public void editTask() { - - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - - TimerTaskEditor editor = new TimerTaskEditor(this, task, core); - editor.setLocationByPlatform(true); - editor.setVisible(true); - } - - /** - * Start selected task in tree. - * - * If it not already been running - * - * @return TimerTask scheduled for start - * @see Task - */ - @Action(enabledProperty = "selectedSingleStoppedTask") - public Task<?, ?> startTask() { - - // search for selected task in tree - // can't be null - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - - RunTaskJob jobToRun = new RunTaskJob(this, task, core.getData()); - core.getData().startTask(task); - - return jobToRun; - } - - /** - * Called by task job manager when task as been started. - * - * @param task started task - */ - public void startedTask(TimerTask task) { - setSelectedSingleRunningTask(true); - setSelectedSingleStoppedTask(false); - } - - /** - * Return job that manage running task. - * - * @param task timer task to get job - * @return job or null - */ - protected RunTaskJob getJobForRunningTask(TimerTask task) { - RunTaskJob job = null; - - TaskMonitor tm = getContext().getTaskMonitor(); - for (Task<Void, Long> t : tm.getTasks()) { - TimerTask localtask = ((RunTaskJob) t).getTask(); - if (task.equals(localtask)) { - job = (RunTaskJob) t; - } - } - - return job; - } - - /** - * Stop selected task in tree. - * - * Verify if it has been started - */ - @Action(enabledProperty = "selectedSingleRunningTask") - public void stopTask() { - - // task can't be null - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - stopTask(task); - - } - - /** - * Stop a task. - * - * Delegate method. Called by idle detect ? - * Called by delete method too. - * - * @param task task to stop - */ - public void stopTask(TimerTask task) { - - RunTaskJob rtt = getJobForRunningTask(task); - // test if task is already running - if (rtt != null) { - rtt.wantToStop(); - core.getData().stopTask(task); - - // re-enable/disable buttons - setSelectedSingleRunningTask(false); - setSelectedSingleStoppedTask(true); - } - } - - /** - * Stop all running tasks. - */ - public void stopAllTasks() { - - TaskMonitor tm = getContext().getTaskMonitor(); - for (Task<Void, Long> t : tm.getTasks()) { - // task - TimerTask ttask = ((RunTaskJob) t).getTask(); - stopTask(ttask); - } - } - - /** - * Close project. - */ - @Action(enabledProperty = "selectedSingleProject") - public void closeProject() { - TimerProject project = projectsAndTasksTable.getSelectedProjects().get(0); - - core.getData().changeProjectCloseState(project); - } - - /** - * Close task. - */ - @Action(enabledProperty = "selectedSingleTask") - public void closeTask() { - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - - core.getData().changeTaskCloseState(task); - } - - /** - * Delete project. - */ - @Action(enabledProperty = "selectedSingleProject") - public void deleteProject() { - - TimerProject project = projectsAndTasksTable.getSelectedProjects().get( - 0); - - if (project != null) { - int confirm = JOptionPane.showConfirmDialog(getMainFrame(), - resourceMap.getString("input.deleteProjectMessage", project - .getName()), resourceMap - .getString("input.deleteProjectTitle"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - - if (confirm == JOptionPane.YES_OPTION) { // approved - try { - core.getData().deleteProject(project); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - - } - - } - - /** - * Delete task. - */ - @Action(enabledProperty = "selectedSingleTask") - public void deleteTask() { - - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - - if (task != null) { - int confirm = JOptionPane.showConfirmDialog(getMainFrame(), - resourceMap.getString("input.deleteTaskMessage", task - .getName()), resourceMap - .getString("input.deleteTaskTitle"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - - if (confirm == JOptionPane.YES_OPTION) { // approved - try { - stopTask(task); - core.getData().deleteTask(task); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - } - } - - /** - * Change show closed option. - * - * @param event action event - */ - @Action - public void isShowClosed(ActionEvent event) { - JCheckBoxMenuItem source = (JCheckBoxMenuItem) event.getSource(); - boolean showClosed = source.isSelected(); - projectsAndTasksTable.setShowClosed(showClosed); - config.setShowClosed(showClosed); - } - - /** - * Change close to systray option. - * - * @param event action event - */ - @Action - public void isCloseToSystray(ActionEvent event) { - JCheckBoxMenuItem source = (JCheckBoxMenuItem) event.getSource(); - boolean closeToSystray = source.isSelected(); - config.setCloseToSystray(closeToSystray); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek1() { - config.setReportFirstDayOfWeek(1); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek2() { - config.setReportFirstDayOfWeek(2); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek3() { - config.setReportFirstDayOfWeek(3); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek4() { - config.setReportFirstDayOfWeek(4); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek5() { - config.setReportFirstDayOfWeek(5); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek6() { - config.setReportFirstDayOfWeek(6); - } - - /** - * Change report first day of week. - */ - @Action - public void isReportFirstDayOfWeek7() { - config.setReportFirstDayOfWeek(7); - } - - /** - * Increment task time. - */ - @Action(enabledProperty = "selectedSingleTask") - public void increment5Task() { - incrementTaskTime(5 * 60); - } - - /** - * Increment task time. - */ - @Action(enabledProperty = "selectedSingleTask") - public void increment30Task() { - incrementTaskTime(30 * 60); - } - - /** - * Decrement task time. - */ - @Action(enabledProperty = "selectedSingleTask") - public void decrement1Task() { - incrementTaskTime(-60); - } - - /** - * Decrement task time. - */ - @Action(enabledProperty = "selectedSingleTask") - public void decrement5Task() { - incrementTaskTime(-5 * 60); - } - - /** - * Decrement task time. - */ - @Action(enabledProperty = "selectedSingleTask") - public void decrement30Task() { - incrementTaskTime(-30 * 60); - } - - /** - * Increment task time. - */ - @Action(enabledProperty = "selectedSingleTask") - public void increment1Task() { - incrementTaskTime(60); - } - - /** - * Increment task time. - * - * To decrement, set negative increment:) - * - * @param increment increment in seconds - */ - protected void incrementTaskTime(long increment) { - TimerTask selectedTask = projectsAndTasksTable.getSelectedTasks() - .get(0); - - // check if task is running - RunTaskJob job = getJobForRunningTask(selectedTask); - if (job != null) { - // task is running - // increment is in seconds !!! - job.incrementTaskTime(increment * 1000); - } else { - // task is not running - Date now = new Date(); - long todayTime = selectedTask.getTime(now); - - long newTodayTime = todayTime + increment; - - try { - // check if + negative increment still positive - if (newTodayTime > 0) { - core.getData().changeTaskTime(selectedTask, now, - newTodayTime); - } else { - // force to 0 - core.getData().changeTaskTime(selectedTask, now, 0L); - } - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - } - - /** - * Reset task time to zero. - */ - @Action(enabledProperty = "selectedSingleTask") - public void setToZero() { - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - incrementTaskTime(-task.getTime(new Date())); - } - - /** - * Merge multiples tasks action. - */ - @Action(enabledProperty = "selectedMultiplesTasks") - public void mergeTasks() { - List<TimerTask> tasks = projectsAndTasksTable.getSelectedTasks(); - - TimerTask destinationTask = tasks.get(0); - List<TimerTask> otherTasks = tasks.subList(1, tasks.size()); - int confirm = JOptionPane.showConfirmDialog(getMainFrame(), - resourceMap.getString("input.mergeTaskMessage", tasks.size(), - destinationTask.getName()), resourceMap - .getString("input.mergeTaskTitle"), - JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (confirm == JOptionPane.YES_OPTION) { - - try { - core.getData().mergeTasks(destinationTask, otherTasks); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - } - - /** - * Add annotation action. - */ - @Action(enabledProperty = "selectedSingleTask") - public void addAnnotation() { - // select task to add new annotation - TimerTask selectedTask = projectsAndTasksTable.getSelectedTasks() - .get(0); - - String annotation = JOptionPane.showInputDialog(getMainFrame(), - resourceMap.getString("input.addAnnotationMessage", - selectedTask.getName()), resourceMap - .getString("input.addAnnotationTitle"), - JOptionPane.QUESTION_MESSAGE); - - if (annotation != null) { - // remove useless spaces - annotation = annotation.trim(); - - try { - core.getData().addAnnotation(selectedTask, new Date(), - annotation); - } catch (DataViolationException e) { - displayErrorMessage(e.getExceptionKey()); - } - } - } - - /** - * Edit alert action. - */ - @Action(enabledProperty = "selectedSingleTask") - public void editAlert() { - // select task to edit alert - TimerTask selectedTask = projectsAndTasksTable.getSelectedTasks() - .get(0); - - AlertEditor alertEditor = new AlertEditor(this, core.getData(), - selectedTask); - show(alertEditor); - } - - /** - * Create report. - */ - @Action - public void makeReport() { - ReportView view = new ReportView(this, core); - show(view); - } - - /** - * Show about frame. - */ - @Action - public void about() { - HelpFrame aboutFrame = new HelpFrame(this); - show(aboutFrame); - } - - /** - * Is selected project. - * - * @return selected project property - */ - public boolean isSelectedSingleProject() { - return selectedSingleProject; - } - - /** - * Change selected project property. - * - * @param selectedSingleProject selected project property - */ - public void setSelectedSingleProject(boolean selectedSingleProject) { - boolean oldValue = this.selectedSingleProject; - this.selectedSingleProject = selectedSingleProject; - firePropertyChange("selectedSingleProject", oldValue, - selectedSingleProject); - } - - /** - * Is selected task. - * - * @return selected task property - */ - public boolean isSelectedSingleTask() { - return selectedSingleTask; - } - - /** - * Change selected task property. - * - * @param selectedSingleTask selected task property - */ - public void setSelectedSingleTask(boolean selectedSingleTask) { - boolean oldValue = this.selectedSingleTask; - this.selectedSingleTask = selectedSingleTask; - firePropertyChange("selectedSingleTask", oldValue, selectedSingleTask); - } - - /** - * Is selected task or project. - * - * @return selected task or project property - */ - public boolean isSelectedSingleElement() { - return selectedSingleElement; - } - - /** - * Change selected task or project property. - * - * @param selectedSingleElement selected task or project property - */ - public void setSelectedSingleElement(boolean selectedSingleElement) { - boolean oldValue = this.selectedSingleElement; - this.selectedSingleElement = selectedSingleElement; - firePropertyChange("selectedSingleElement", oldValue, - selectedSingleElement); - } - - /** - * Is selected running task. - * - * @return the selectedSingleRunningTask - */ - public boolean isSelectedSingleRunningTask() { - return selectedSingleRunningTask; - } - - /** - * Change selected running task property. - * - * @param selectedSingleRunningTask selected running task property - */ - public void setSelectedSingleRunningTask(boolean selectedSingleRunningTask) { - boolean oldValue = this.selectedSingleRunningTask; - this.selectedSingleRunningTask = selectedSingleRunningTask; - firePropertyChange("selectedSingleRunningTask", oldValue, - selectedSingleRunningTask); - } - - /** - * Is selected non running task. - * - * @return selected non running task - */ - public boolean isSelectedSingleStoppedTask() { - return selectedSingleStoppedTask; - } - - /** - * Change selected non running task property. - * - * @param selectedSingleStoppedTask selected non running task - */ - public void setSelectedSingleStoppedTask(boolean selectedSingleStoppedTask) { - boolean oldValue = this.selectedSingleStoppedTask; - this.selectedSingleStoppedTask = selectedSingleStoppedTask; - firePropertyChange("selectedSingleStoppedTask", oldValue, - selectedSingleStoppedTask); - } - - /** - * Is selected multiples projects. - * - * @return selected multiples projects - */ - public boolean isSelectedMultiplesProjects() { - return selectedMultiplesProjects; - } - - /** - * Change selected multiples projects property. - * - * @param selectedMultiplesProjects selected multiples projects - */ - public void setSelectedMultiplesProjects(boolean selectedMultiplesProjects) { - boolean oldValue = this.selectedMultiplesProjects; - this.selectedMultiplesProjects = selectedMultiplesProjects; - firePropertyChange("selectedMultiplesProjects", oldValue, - selectedMultiplesProjects); - } - - /** - * Is selected multiples tasks. - * - * @return selected multiples tasks - */ - public boolean isSelectedMultiplesTasks() { - return selectedMultiplesTasks; - } - - /** - * Change selected multiples tasks property. - * - * @param selectedMultiplesTasks selected multiples tasks - */ - public void setSelectedMultiplesTasks(boolean selectedMultiplesTasks) { - boolean oldValue = this.selectedMultiplesTasks; - this.selectedMultiplesTasks = selectedMultiplesTasks; - firePropertyChange("selectedMultiplesTasks", oldValue, - selectedMultiplesTasks); - } - - /** - * Is selected multiples elements. - * - * @return selected multiples elements - */ - public boolean isSelectedMultiplesElements() { - return selectedMultiplesElements; - } - - /** - * Change selected multiples elements property. - * - * @param selectedMultiplesElements selected multiples elements - */ - public void setSelectedMultiplesElements(boolean selectedMultiplesElements) { - boolean oldValue = this.selectedMultiplesElements; - this.selectedMultiplesElements = selectedMultiplesElements; - firePropertyChange("selectedMultiplesElements", oldValue, - selectedMultiplesElements); - } - - /* - * @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent) - */ - @Override - public void valueChanged(TreeSelectionEvent e) { - // get selected elements - List<TimerTask> elements = projectsAndTasksTable.getSelectedElements(); - List<TimerProject> projects = projectsAndTasksTable - .getSelectedProjects(); - List<TimerTask> tasks = projectsAndTasksTable.getSelectedTasks(); - - // notify application that tree selection has changed - if (tasks.size() == 1 && projects.size() == 0) { - TimerTask task = tasks.get(0); - setSelectedSingleTask(true); - - // ask for SAF job manager if task is running - RunTaskJob job = getJobForRunningTask(task); - if (job == null || job.isStopping()) { - setSelectedSingleStoppedTask(true); - setSelectedSingleRunningTask(false); - } else { - setSelectedSingleStoppedTask(false); - setSelectedSingleRunningTask(true); - } - setSelectedMultiplesTasks(false); - setSelectedSingleProject(false); - setSelectedMultiplesProjects(false); - } else { - setSelectedSingleTask(false); - setSelectedSingleStoppedTask(false); - setSelectedSingleRunningTask(false); - - if (tasks.size() > 1) { - setSelectedMultiplesTasks(projects.size() == 0); - } else { - setSelectedSingleProject(tasks.size() == 0 - && projects.size() == 1); - setSelectedMultiplesProjects(tasks.size() == 0 - && projects.size() > 1); - } - } - setSelectedSingleElement(elements.size() == 1); - setSelectedMultiplesElements(elements.size() > 1); - } - - /** - * Get action for named component. - * - * Util method. - * - * @param actionName action name - * @return swing action - */ - protected javax.swing.Action getAction(String actionName) { - return getContext().getActionMap().get(actionName); - } - - /** - * Show window - */ - @Action - public void show() { - // TODO better code ? that use SAF methods ? - getMainFrame().setVisible(true); - getMainFrame().toFront(); - } - - /** - * Show window - */ - @Action - public void hide() { - // TODO better code ? that use SAF methods ? - getMainFrame().setVisible(false); - } - - /** - * Notified by job on pre idle detect. - */ - public void preIdleDetect() { - systrayManager.preIdleDetect(); - } - - /** - * Notified by job on post idle detect. - */ - public void postIdleDetect() { - systrayManager.postIdleDetect(); - } - - /* - * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) - */ - public void mouseClicked(MouseEvent e) { - - if (log.isDebugEnabled()) { - log.debug("Mouse clicked (" + e.getClickCount() - + " clics), source = " - + e.getSource().getClass().getName()); - } - - switch (e.getButton()) { - - // clic gauche - case MouseEvent.BUTTON1: - // clic sur l'arbre des projets - if (e.getSource() == projectsAndTasksTable) { - // demarre la tache lors d'un double clic dessus - if (e.getClickCount() == 2) { - - if (isSelectedSingleStoppedTask()) { // can only launch non running tasks - Task<?, ?> appTask = startTask(); - if (appTask != null) { - - // first, on dlb click stop all running tasks - stopAllTasks(); - - getContext().getTaskService().execute(appTask); - } - /* TODO EC-20100416 temp disable, called twice, can display - -1 task running :( - } else if (isSelectedSingleRunningTask()) { - TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); - stopTask(task); */ - } else { - if (log.isDebugEnabled()) { - log.debug("Non selected non running task to launch"); - } - } - } else { - if (log.isDebugEnabled()) { - log.debug("Single clic on tree, do nothing"); - } - } - } - break; - - // gestion du clic droit - case MouseEvent.BUTTON3: - - // force task selection on rigth clic - TreePath path = projectsAndTasksTable.getPathForLocation(e.getX(), - e.getY()); - int selectedRow = projectsAndTasksTable.getRowForPath(path); - projectsAndTasksTable.getSelectionModel().setSelectionInterval( - selectedRow, selectedRow); - - // fix item selection - JPopupMenu menu = new JPopupMenu(); - - String[] actionNames = null; - - // construct menu in function of selection - if (isSelectedSingleProject()) { - actionNames = new String[] { "newTask", "---", "newProject", - "editProject", "closeProject", "deleteProject" }; - } - - if (isSelectedSingleTask()) { - actionNames = new String[] { "startTask", "stopTask", "---", - "newTask", "editTask", "closeTask", "deleteTask", - "---", "addAnnotation", "editAlert", "increment1Task", - "increment5Task", "increment30Task", "decrement1Task", - "decrement5Task", "decrement30Task", "setToZero" }; - } - - if (isSelectedMultiplesTasks()) { - actionNames = new String[] { "mergeTasks" }; - } - - // case, right clic, but nothing selected - if (actionNames != null) { - addActionToMenu(menu, actionNames); - - menu.show(e.getComponent(), e.getX(), e.getY()); - } - } - } - - public void mouseEntered(MouseEvent e) { - } - - public void mouseExited(MouseEvent e) { - } - - public void mousePressed(MouseEvent e) { - } - - public void mouseReleased(MouseEvent e) { - } -} Copied: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java (from rev 2837, trunk/src/main/java/org/chorem/jtimer/JTimer.java) =================================================================== --- branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java (rev 0) +++ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/JTimer.java 2012-03-25 21:15:46 UTC (rev 2840) @@ -0,0 +1,1473 @@ +/* + * #%L + * jTimer + * + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2007 - 2012 CodeLutin, Chatellier Eric + * %% + * 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/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.File; +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Timer; + +import javax.swing.ButtonGroup; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreePath; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.data.DataViolationException; +import org.chorem.jtimer.data.TimerCore; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerTask; +import org.chorem.jtimer.ui.HelpFrame; +import org.chorem.jtimer.ui.StatusBar; +import org.chorem.jtimer.ui.TimerTaskEditor; +import org.chorem.jtimer.ui.alert.AlertEditor; +import org.chorem.jtimer.ui.report.ReportView; +import org.chorem.jtimer.ui.systray.SystrayManager; +import org.chorem.jtimer.ui.tasks.IdleDialog; +import org.chorem.jtimer.ui.tasks.RefreshTreeTask; +import org.chorem.jtimer.ui.tasks.RunTaskJob; +import org.chorem.jtimer.ui.treetable.ProjectsAndTasksTable; +import org.chorem.jtimer.ui.widget.WindowProperty2; +import org.chorem.jtimer.ui.ws.SwingConnectionInformationHandler; +import org.chorem.jtimer.ws.ConnectionDataHandler; +import org.chorem.jtimer.ws.ProjectManagement; +import org.jdesktop.application.Action; +import org.jdesktop.application.Application; +import org.jdesktop.application.ApplicationContext; +import org.jdesktop.application.ResourceMap; +import org.jdesktop.application.SingleFrameApplication; +import org.jdesktop.application.Task; +import org.jdesktop.application.TaskMonitor; + +/** + * Main jTimer application window. + * + * Respect JSR-296 + * + * @author chatellier + * @version $Revision$ + * + * Last update : $Date$ + * By : $Author$ + */ +public class JTimer extends SingleFrameApplication implements + TreeSelectionListener, MouseListener { + + /** log. */ + private static Log log = LogFactory.getLog(JTimer.class); + + /** Timer core controller. */ + protected TimerCore core; + + /** Tree structure. */ + protected ProjectsAndTasksTable projectsAndTasksTable; + + /** Systray manager. */ + protected SystrayManager systrayManager; + + /** I18n resources map. */ + protected ResourceMap resourceMap; + + /** Jtimer application config. */ + public static JTimerConfig config; + + /** Single project selection property. */ + protected boolean selectedSingleProject; + + /** Single task selection property. */ + protected boolean selectedSingleTask; + + /** Single task or project selection. */ + protected boolean selectedSingleElement; + + /** Single running task selection. */ + protected boolean selectedSingleRunningTask; + + /** Single non running task selection. */ + protected boolean selectedSingleStoppedTask; + + /** Multiples projects selection. */ + protected boolean selectedMultiplesProjects; + + /** Multiples tasks selection. */ + protected boolean selectedMultiplesTasks; + + /** Multiples elements selection. */ + protected boolean selectedMultiplesElements; + + /** + * Main. launch UI + * + * @param args args + */ + public static void main(String[] args) { + + if (log.isInfoEnabled()) { + log.info("Starting " + JTimer.class.getSimpleName() + " at " + + new Date()); + } + launch(JTimer.class, args); + } + + /** + * Initialize application. Called before UI build. + * + * @param args args + * @see Application#initialize(String[]) + */ + @Override + protected void initialize(String[] args) { + + // super, but does nothing + super.initialize(args); + + // load configuration + loadConfiguration(args); + + // init resources map + ApplicationContext ctxt = getContext(); + resourceMap = ctxt.getResourceMap(); + + // fixme awt application name. Can be seen only with gnome-shell + // tray icon is displayed with name "org-chorem-jtimer-JTimer" instead of + // only "jTimer" with following fix : + try { + Toolkit xToolkit = Toolkit.getDefaultToolkit(); + java.lang.reflect.Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName"); + awtAppClassNameField.setAccessible(true); + awtAppClassNameField.set(xToolkit, resourceMap.getString("Application.title")); + } catch (Exception ex) { + if (log.isWarnEnabled()) { + log.warn("Can change awt application name", ex); + } + } + + // fix start in iconified mode + ctxt.getSessionStorage().putProperty(JFrame.class, new WindowProperty2()); + + // init timercore + core = new TimerCore(); + + // handler + ConnectionDataHandler handler = new SwingConnectionInformationHandler( + this); + ProjectManagement managementService = JTimerFactory + .getProjectManagementService(); + if (managementService != null) { + managementService.setConnectionDataHandler(handler); + } + + // Systray mgr + systrayManager = new SystrayManager(this); + core.getData().addDataEventListener(systrayManager); + + IdleDialog.init(this); + } + + /** + * Load configuration. + * + * @param args args to parse command line options + */ + protected void loadConfiguration(String[] args) { + + config = new JTimerConfig(); + + // add file migration for configuration file created before version 1.4 + File homeDir = new File(System.getProperty("user.home"), ".jtimer"); + File oldFile = new File(homeDir, "JTimer.properties"); + File newFile = new File(config.appConfig.getUserConfigDirectory(), config.appConfig.getConfigFileName()); + if (oldFile.isFile() && !newFile.isFile()) { + if (log.isInfoEnabled()) { + log.info("Migration configuration file location"); + } + try { + FileUtils.copyFile(oldFile, newFile); + } catch (IOException ex) { + if (log.isErrorEnabled()) { + log.error("Can't copy config file to new location", ex); + } + } + } + + // parse after file migration + config.parse(args); + } + + /** + * startup. + * + * Create frame menu bar. Create main component. + * + * @see Application#startup() + */ + @Override + protected void startup() { + + // set Menu Bar + getMainFrame().setJMenuBar(createMenuBar()); + + // show main panel components + show(createMainComponent()); + + } + + /** + * Create main component. + * + * Toolbar on top. Tree middle. Status bar on bottom. + * + * @return The component + */ + protected JComponent createMainComponent() { + + // panel = main component + JPanel panel = new JPanel(new BorderLayout()); + + // toolbar on top (north) + panel.add(createToolBar(), BorderLayout.NORTH); + + // tree middle (center-top) + ProjectsAndTasksTable projectTreeTable = createTreeTable(); + JScrollPane scrollPaneProjectTreeTable = new JScrollPane( + projectTreeTable); + panel.add(scrollPaneProjectTreeTable, BorderLayout.CENTER); + + // status bar bottom + StatusBar sb = new StatusBar(this, core.getData()); + // status bar ui will be notified from events + core.getData().addDataEventListener(sb); + panel.add(sb, BorderLayout.SOUTH); + + // taille par defaut au premier lancement de l'application + // sera ecrasee par la restauration de la session + panel.setPreferredSize(new Dimension(640, 480)); + + return panel; + } + + /** + * Create complex tree table. + * + * @return ProjectsAndTaskTable instance + */ + protected ProjectsAndTasksTable createTreeTable() { + + projectsAndTasksTable = new ProjectsAndTasksTable(this, core); + + // name used in properties files + projectsAndTasksTable.setName("projectslist"); + projectsAndTasksTable.addTreeSelectionListener(this); + projectsAndTasksTable.addMouseListener(this); + projectsAndTasksTable.setShowClosed(config.isShowClosed()); + + // since merge option, selection can be multiple + projectsAndTasksTable + .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + // remove F2 KeyStroke from table + KeyStroke keyToRemove = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0); + InputMap imap = projectsAndTasksTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + while (imap != null) { + imap.remove(keyToRemove); + imap = imap.getParent(); + } + + return projectsAndTasksTable; + } + + /** + * Create toolbar. + * + * @return tool bar builded + */ + protected JComponent createToolBar() { + String[] toolbarActionNames = { "startTask", "stopTask", "newProject", + "newTask", "---", "addAnnotation", "editAlert" }; + JToolBar toolBar = new JToolBar(); + toolBar.setFloatable(false); + for (String actionName : toolbarActionNames) { + + if (actionName.equals("---")) { + toolBar.add(new JToolBar.Separator()); + } else { + JButton button = new JButton(); + button.setAction(getAction(actionName)); + + // hide text + // button.setVerticalTextPosition(JButton.BOTTOM); + // button.setHorizontalTextPosition(JButton.CENTER); + button.setHideActionText(true); + + button.setFocusable(false); + toolBar.add(button); + } + } + return toolBar; + } + + /** + * Create application menu bar. + * + * @return menu bar + */ + protected JMenuBar createMenuBar() { + + JMenuBar menuBar = new JMenuBar(); + String[] fileMenuActionNames = { "quit" }; + menuBar.add(createMenu("fileMenu", fileMenuActionNames)); + + String[] projectMenuActionNames = { "newProject", "editProject", + "closeProject", "deleteProject" }; + menuBar.add(createMenu("projectMenu", projectMenuActionNames)); + + String[] taskMenuActionNames = { "newTask", "editTask", "closeTask", + "deleteTask", "---", "startTask", "stopTask", "---", + "addAnnotation", "editAlert", "increment1Task", + "increment5Task", "increment30Task", "decrement1Task", + "decrement5Task", "decrement30Task", "setToZero", "mergeTasks" }; + menuBar.add(createMenu("taskMenu", taskMenuActionNames)); + + String[] reportMenuActionNames = { "makeReport" }; + menuBar.add(createMenu("reportMenu", reportMenuActionNames)); + + JMenu optionmMenu = createOptionMenu(); + menuBar.add(optionmMenu); + + String[] helpMenuActionNames = { "about" }; + menuBar.add(createMenu("helpMenu", helpMenuActionNames)); + + return menuBar; + } + + /** + * Create option dynamic menu. + * + * @return option menu + */ + protected JMenu createOptionMenu() { + JMenu menu = new JMenu(); + menu.setName("optionMenu"); + + // show closed + JMenuItem showClosedItem = new JCheckBoxMenuItem(); + showClosedItem.setAction(getAction("isShowClosed")); + showClosedItem.setSelected(config.isShowClosed()); + showClosedItem.setIcon(null); + menu.add(showClosedItem); + + // close to systray + JMenuItem closeToSysItem = new JCheckBoxMenuItem(); + closeToSysItem.setAction(getAction("isCloseToSystray")); + closeToSysItem.setSelected(config.isCloseToSystray()); + closeToSysItem.setIcon(null); + menu.add(closeToSysItem); + + // report first day of week + JMenu reportFDoW = new JMenu(); + reportFDoW.setName("optionReportFirstDayMenu"); + Calendar calendar = Calendar.getInstance(); + ButtonGroup bg = new ButtonGroup(); + // affiche la liste des jours dans l'ordre de la locale utilisateur + for (int day = calendar.getFirstDayOfWeek() ; day < calendar.getFirstDayOfWeek() + 7 ; day++) { + int realDay = (day - 1) % 7 + 1; + JRadioButtonMenuItem fdowItem = new JRadioButtonMenuItem(); + fdowItem.setAction(getAction("isReportFirstDayOfWeek" + realDay)); + fdowItem.setSelected(realDay == JTimer.config.getReportFirstDayOfWeek()); + fdowItem.setIcon(null); + reportFDoW.add(fdowItem); + bg.add(fdowItem); + } + menu.add(reportFDoW); + + return menu; + } + + /** + * Create single menu. + * + * @param menuName menu name + * @param actionNames associated actions + * @return menu + */ + protected JMenu createMenu(String menuName, String[] actionNames) { + JMenu menu = new JMenu(); + menu.setName(menuName); + addActionToMenu(menu, actionNames); + return menu; + } + + /** + * Add saf action to an existing menu. + * + * Menu have to be a JMenu or JPopupMenu. + * + * @param menu parent menu + * @param actionNames action names + */ + protected void addActionToMenu(JComponent menu, String[] actionNames) { + for (String actionName : actionNames) { + if (actionName.equals("---")) { + menu.add(new JSeparator()); + } else if (actionName.startsWith("is")) { + // if action name start by is + // display it as CheckBox + JMenuItem menuItem = new JCheckBoxMenuItem(); + // link to an @Action + menuItem.setAction(getAction(actionName)); + menuItem.setIcon(null); + menu.add(menuItem); + } else { + JMenuItem menuItem = new JMenuItem(); + // link to an @Action + menuItem.setAction(getAction(actionName)); + menuItem.setIcon(null); + menu.add(menuItem); + } + } + } + + /** + * Ready. Called when UI is ready and displayed. + * + * @see Application#ready() + */ + @Override + protected void ready() { + + // init core, load list, synchronization, etc... + boolean init = core.init(); + + if (init) { + // schedule tree refresh at midnight + scheduleTreeRefresh(); + + // install icon (do it at last action) + systrayManager.install(); + } else { + String failTitle = resourceMap.getString("startFail.title"); + String failMessage = resourceMap.getString("startFail.message"); + JOptionPane.showMessageDialog(getMainFrame(), failMessage, + failTitle, JOptionPane.ERROR_MESSAGE); + exit(); + } + } + + /** + * Called on application shutdown. + * + * Save context. + * + * @see SingleFrameApplication#shutdown() + */ + @Override + protected void shutdown() { + log.debug("Shutdown called"); + core.exit(); + + // save context + // super, sauve le context des fenetres, etc... + super.shutdown(); + } + + /** + * Refresh tree at midnight. + */ + protected void scheduleTreeRefresh() { + + // task used to refresh tree + java.util.TimerTask refreshTreeTask = new RefreshTreeTask(core); + + Timer timer = new Timer(); + + Calendar date = Calendar.getInstance(); + date.setTimeInMillis(System.currentTimeMillis()); + date.set(Calendar.HOUR_OF_DAY, 0); + date.set(Calendar.MINUTE, 0); + date.set(Calendar.SECOND, 0); + date.set(Calendar.MILLISECOND, 0); + date.add(Calendar.DAY_OF_YEAR, 1); // run only next day + + // Schedule to run every day in midnight + // task,firstTime,period + timer.schedule(refreshTreeTask, date.getTime(), // at date + 1000 * 60 * 60 * 24 // every day + ); + } + + /** + * Display a popup error message. + * + * @param errorMessageKey saf error message key + */ + protected void displayErrorMessage(String errorMessageKey) { + String title = resourceMap.getString("action.invalidActionTitle"); + String message = resourceMap.getString(errorMessageKey); + + // check untranslated string + if (StringUtils.isEmpty(message)) { + message = resourceMap.getString("action.missingErrorMessage", + errorMessageKey); + } + + JOptionPane.showMessageDialog(getMainFrame(), message, title, + JOptionPane.ERROR_MESSAGE); + } + + /** + * New project action. + * + * Ask user for project name + */ + @Action + public void newProject() { + + String projectName = JOptionPane.showInputDialog(getMainFrame(), + resourceMap.getString("input.newProjectMessage"), resourceMap + .getString("input.newProjectTitle"), + JOptionPane.QUESTION_MESSAGE); + + if (projectName != null) { + + // remove unneeded spaces + projectName = projectName.trim(); + + TimerProject p = new TimerProject(projectName); + + // add creation date + p.setCreationDate(new Date()); + + try { + core.getData().addProject(p); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + } + + /** + * Edit project + * + * Enabled when a project is selected + */ + @Action(enabledProperty = "selectedSingleProject") + public void editProject() { + TimerProject project = projectsAndTasksTable.getSelectedProjects().get( + 0); + + String newProjectName = (String) JOptionPane.showInputDialog(getMainFrame(), resourceMap + .getString("input.editProjectMessage"), resourceMap + .getString("input.editProjectTitle"), + JOptionPane.INFORMATION_MESSAGE, null, null, project.getName()); + + if (newProjectName != null) { + + // remove unneeded spaces + newProjectName = newProjectName.trim(); + + try { + core.getData().editProject(project, newProjectName); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + } + + /** + * Create new task action. + * + * Enabled when a project or a task is selected + */ + @Action(enabledProperty = "selectedSingleElement") + public void newTask() { + + // select task to add new task + TimerTask selectedTask = projectsAndTasksTable.getSelectedElements() + .get(0); + + String taskName = JOptionPane.showInputDialog(getMainFrame(), + resourceMap.getString("input.newTaskMessage", selectedTask + .getName()), resourceMap + .getString("input.newTaskTitle"), + JOptionPane.QUESTION_MESSAGE); + + if (taskName != null) { + + // remove unneeded spaces + taskName = taskName.trim(); + + TimerTask t = new TimerTask(taskName); + + // Fix creation date + t.setCreationDate(new Date()); + + try { + core.getData().addTask(selectedTask, t); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + + } + + /** + * Edit task. + * + * Enabled when a task is selected + */ + @Action(enabledProperty = "selectedSingleTask") + public void editTask() { + + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + + TimerTaskEditor editor = new TimerTaskEditor(this, task, core); + editor.setLocationByPlatform(true); + editor.setVisible(true); + } + + /** + * Start selected task in tree. + * + * If it not already been running + * + * @return TimerTask scheduled for start + * @see Task + */ + @Action(enabledProperty = "selectedSingleStoppedTask") + public Task<?, ?> startTask() { + + // search for selected task in tree + // can't be null + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + + RunTaskJob jobToRun = new RunTaskJob(this, task, core.getData()); + core.getData().startTask(task); + + return jobToRun; + } + + /** + * Called by task job manager when task as been started. + * + * @param task started task + */ + public void startedTask(TimerTask task) { + setSelectedSingleRunningTask(true); + setSelectedSingleStoppedTask(false); + } + + /** + * Return job that manage running task. + * + * @param task timer task to get job + * @return job or null + */ + protected RunTaskJob getJobForRunningTask(TimerTask task) { + RunTaskJob job = null; + + TaskMonitor tm = getContext().getTaskMonitor(); + for (Task t : tm.getTasks()) { + TimerTask localtask = ((RunTaskJob) t).getTask(); + if (task.equals(localtask)) { + job = (RunTaskJob) t; + } + } + + return job; + } + + /** + * Stop selected task in tree. + * + * Verify if it has been started + */ + @Action(enabledProperty = "selectedSingleRunningTask") + public void stopTask() { + + // task can't be null + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + stopTask(task); + + } + + /** + * Stop a task. + * + * Delegate method. Called by idle detect ? + * Called by delete method too. + * + * @param task task to stop + */ + public void stopTask(TimerTask task) { + + RunTaskJob rtt = getJobForRunningTask(task); + // test if task is already running + if (rtt != null) { + rtt.wantToStop(); + core.getData().stopTask(task); + + // re-enable/disable buttons + setSelectedSingleRunningTask(false); + setSelectedSingleStoppedTask(true); + } + } + + /** + * Stop all running tasks. + */ + public void stopAllTasks() { + + TaskMonitor tm = getContext().getTaskMonitor(); + for (Task t : tm.getTasks()) { + // task + TimerTask ttask = ((RunTaskJob) t).getTask(); + stopTask(ttask); + } + } + + /** + * Close project. + */ + @Action(enabledProperty = "selectedSingleProject") + public void closeProject() { + TimerProject project = projectsAndTasksTable.getSelectedProjects().get(0); + + core.getData().changeProjectCloseState(project); + } + + /** + * Close task. + */ + @Action(enabledProperty = "selectedSingleTask") + public void closeTask() { + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + + core.getData().changeTaskCloseState(task); + } + + /** + * Delete project. + */ + @Action(enabledProperty = "selectedSingleProject") + public void deleteProject() { + + TimerProject project = projectsAndTasksTable.getSelectedProjects().get( + 0); + + if (project != null) { + int confirm = JOptionPane.showConfirmDialog(getMainFrame(), + resourceMap.getString("input.deleteProjectMessage", project + .getName()), resourceMap + .getString("input.deleteProjectTitle"), + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (confirm == JOptionPane.YES_OPTION) { // approved + try { + core.getData().deleteProject(project); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + + } + + } + + /** + * Delete task. + */ + @Action(enabledProperty = "selectedSingleTask") + public void deleteTask() { + + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + + if (task != null) { + int confirm = JOptionPane.showConfirmDialog(getMainFrame(), + resourceMap.getString("input.deleteTaskMessage", task + .getName()), resourceMap + .getString("input.deleteTaskTitle"), + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (confirm == JOptionPane.YES_OPTION) { // approved + try { + stopTask(task); + core.getData().deleteTask(task); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + } + } + + /** + * Change show closed option. + * + * @param event action event + */ + @Action + public void isShowClosed(ActionEvent event) { + JCheckBoxMenuItem source = (JCheckBoxMenuItem) event.getSource(); + boolean showClosed = source.isSelected(); + projectsAndTasksTable.setShowClosed(showClosed); + config.setShowClosed(showClosed); + } + + /** + * Change close to systray option. + * + * @param event action event + */ + @Action + public void isCloseToSystray(ActionEvent event) { + JCheckBoxMenuItem source = (JCheckBoxMenuItem) event.getSource(); + boolean closeToSystray = source.isSelected(); + config.setCloseToSystray(closeToSystray); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek1() { + config.setReportFirstDayOfWeek(1); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek2() { + config.setReportFirstDayOfWeek(2); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek3() { + config.setReportFirstDayOfWeek(3); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek4() { + config.setReportFirstDayOfWeek(4); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek5() { + config.setReportFirstDayOfWeek(5); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek6() { + config.setReportFirstDayOfWeek(6); + } + + /** + * Change report first day of week. + */ + @Action + public void isReportFirstDayOfWeek7() { + config.setReportFirstDayOfWeek(7); + } + + /** + * Increment task time. + */ + @Action(enabledProperty = "selectedSingleTask") + public void increment5Task() { + incrementTaskTime(5 * 60); + } + + /** + * Increment task time. + */ + @Action(enabledProperty = "selectedSingleTask") + public void increment30Task() { + incrementTaskTime(30 * 60); + } + + /** + * Decrement task time. + */ + @Action(enabledProperty = "selectedSingleTask") + public void decrement1Task() { + incrementTaskTime(-60); + } + + /** + * Decrement task time. + */ + @Action(enabledProperty = "selectedSingleTask") + public void decrement5Task() { + incrementTaskTime(-5 * 60); + } + + /** + * Decrement task time. + */ + @Action(enabledProperty = "selectedSingleTask") + public void decrement30Task() { + incrementTaskTime(-30 * 60); + } + + /** + * Increment task time. + */ + @Action(enabledProperty = "selectedSingleTask") + public void increment1Task() { + incrementTaskTime(60); + } + + /** + * Increment task time. + * + * To decrement, set negative increment:) + * + * @param increment increment in seconds + */ + protected void incrementTaskTime(long increment) { + TimerTask selectedTask = projectsAndTasksTable.getSelectedTasks() + .get(0); + + // check if task is running + RunTaskJob job = getJobForRunningTask(selectedTask); + if (job != null) { + // task is running + // increment is in seconds !!! + job.incrementTaskTime(increment * 1000); + } else { + // task is not running + Date now = new Date(); + long todayTime = selectedTask.getTime(now); + + long newTodayTime = todayTime + increment; + + try { + // check if + negative increment still positive + if (newTodayTime > 0) { + core.getData().changeTaskTime(selectedTask, now, + newTodayTime); + } else { + // force to 0 + core.getData().changeTaskTime(selectedTask, now, 0L); + } + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + } + + /** + * Reset task time to zero. + */ + @Action(enabledProperty = "selectedSingleTask") + public void setToZero() { + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + incrementTaskTime(-task.getTime(new Date())); + } + + /** + * Merge multiples tasks action. + */ + @Action(enabledProperty = "selectedMultiplesTasks") + public void mergeTasks() { + List<TimerTask> tasks = projectsAndTasksTable.getSelectedTasks(); + + TimerTask destinationTask = tasks.get(0); + List<TimerTask> otherTasks = tasks.subList(1, tasks.size()); + int confirm = JOptionPane.showConfirmDialog(getMainFrame(), + resourceMap.getString("input.mergeTaskMessage", tasks.size(), + destinationTask.getName()), resourceMap + .getString("input.mergeTaskTitle"), + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (confirm == JOptionPane.YES_OPTION) { + + try { + core.getData().mergeTasks(destinationTask, otherTasks); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + } + + /** + * Add annotation action. + */ + @Action(enabledProperty = "selectedSingleTask") + public void addAnnotation() { + // select task to add new annotation + TimerTask selectedTask = projectsAndTasksTable.getSelectedTasks() + .get(0); + + String annotation = JOptionPane.showInputDialog(getMainFrame(), + resourceMap.getString("input.addAnnotationMessage", + selectedTask.getName()), resourceMap + .getString("input.addAnnotationTitle"), + JOptionPane.QUESTION_MESSAGE); + + if (annotation != null) { + // remove useless spaces + annotation = annotation.trim(); + + try { + core.getData().addAnnotation(selectedTask, new Date(), + annotation); + } catch (DataViolationException e) { + displayErrorMessage(e.getExceptionKey()); + } + } + } + + /** + * Edit alert action. + */ + @Action(enabledProperty = "selectedSingleTask") + public void editAlert() { + // select task to edit alert + TimerTask selectedTask = projectsAndTasksTable.getSelectedTasks() + .get(0); + + AlertEditor alertEditor = new AlertEditor(this, core.getData(), + selectedTask); + show(alertEditor); + } + + /** + * Create report. + */ + @Action + public void makeReport() { + ReportView view = new ReportView(this, core); + show(view); + } + + /** + * Show about frame. + */ + @Action + public void about() { + HelpFrame aboutFrame = new HelpFrame(this); + show(aboutFrame); + } + + /** + * Is selected project. + * + * @return selected project property + */ + public boolean isSelectedSingleProject() { + return selectedSingleProject; + } + + /** + * Change selected project property. + * + * @param selectedSingleProject selected project property + */ + public void setSelectedSingleProject(boolean selectedSingleProject) { + boolean oldValue = this.selectedSingleProject; + this.selectedSingleProject = selectedSingleProject; + firePropertyChange("selectedSingleProject", oldValue, + selectedSingleProject); + } + + /** + * Is selected task. + * + * @return selected task property + */ + public boolean isSelectedSingleTask() { + return selectedSingleTask; + } + + /** + * Change selected task property. + * + * @param selectedSingleTask selected task property + */ + public void setSelectedSingleTask(boolean selectedSingleTask) { + boolean oldValue = this.selectedSingleTask; + this.selectedSingleTask = selectedSingleTask; + firePropertyChange("selectedSingleTask", oldValue, selectedSingleTask); + } + + /** + * Is selected task or project. + * + * @return selected task or project property + */ + public boolean isSelectedSingleElement() { + return selectedSingleElement; + } + + /** + * Change selected task or project property. + * + * @param selectedSingleElement selected task or project property + */ + public void setSelectedSingleElement(boolean selectedSingleElement) { + boolean oldValue = this.selectedSingleElement; + this.selectedSingleElement = selectedSingleElement; + firePropertyChange("selectedSingleElement", oldValue, + selectedSingleElement); + } + + /** + * Is selected running task. + * + * @return the selectedSingleRunningTask + */ + public boolean isSelectedSingleRunningTask() { + return selectedSingleRunningTask; + } + + /** + * Change selected running task property. + * + * @param selectedSingleRunningTask selected running task property + */ + public void setSelectedSingleRunningTask(boolean selectedSingleRunningTask) { + boolean oldValue = this.selectedSingleRunningTask; + this.selectedSingleRunningTask = selectedSingleRunningTask; + firePropertyChange("selectedSingleRunningTask", oldValue, + selectedSingleRunningTask); + } + + /** + * Is selected non running task. + * + * @return selected non running task + */ + public boolean isSelectedSingleStoppedTask() { + return selectedSingleStoppedTask; + } + + /** + * Change selected non running task property. + * + * @param selectedSingleStoppedTask selected non running task + */ + public void setSelectedSingleStoppedTask(boolean selectedSingleStoppedTask) { + boolean oldValue = this.selectedSingleStoppedTask; + this.selectedSingleStoppedTask = selectedSingleStoppedTask; + firePropertyChange("selectedSingleStoppedTask", oldValue, + selectedSingleStoppedTask); + } + + /** + * Is selected multiples projects. + * + * @return selected multiples projects + */ + public boolean isSelectedMultiplesProjects() { + return selectedMultiplesProjects; + } + + /** + * Change selected multiples projects property. + * + * @param selectedMultiplesProjects selected multiples projects + */ + public void setSelectedMultiplesProjects(boolean selectedMultiplesProjects) { + boolean oldValue = this.selectedMultiplesProjects; + this.selectedMultiplesProjects = selectedMultiplesProjects; + firePropertyChange("selectedMultiplesProjects", oldValue, + selectedMultiplesProjects); + } + + /** + * Is selected multiples tasks. + * + * @return selected multiples tasks + */ + public boolean isSelectedMultiplesTasks() { + return selectedMultiplesTasks; + } + + /** + * Change selected multiples tasks property. + * + * @param selectedMultiplesTasks selected multiples tasks + */ + public void setSelectedMultiplesTasks(boolean selectedMultiplesTasks) { + boolean oldValue = this.selectedMultiplesTasks; + this.selectedMultiplesTasks = selectedMultiplesTasks; + firePropertyChange("selectedMultiplesTasks", oldValue, + selectedMultiplesTasks); + } + + /** + * Is selected multiples elements. + * + * @return selected multiples elements + */ + public boolean isSelectedMultiplesElements() { + return selectedMultiplesElements; + } + + /** + * Change selected multiples elements property. + * + * @param selectedMultiplesElements selected multiples elements + */ + public void setSelectedMultiplesElements(boolean selectedMultiplesElements) { + boolean oldValue = this.selectedMultiplesElements; + this.selectedMultiplesElements = selectedMultiplesElements; + firePropertyChange("selectedMultiplesElements", oldValue, + selectedMultiplesElements); + } + + /* + * @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent) + */ + @Override + public void valueChanged(TreeSelectionEvent e) { + // get selected elements + List<TimerTask> elements = projectsAndTasksTable.getSelectedElements(); + List<TimerProject> projects = projectsAndTasksTable + .getSelectedProjects(); + List<TimerTask> tasks = projectsAndTasksTable.getSelectedTasks(); + + // notify application that tree selection has changed + if (tasks.size() == 1 && projects.size() == 0) { + TimerTask task = tasks.get(0); + setSelectedSingleTask(true); + + // ask for SAF job manager if task is running + RunTaskJob job = getJobForRunningTask(task); + if (job == null || job.isStopping()) { + setSelectedSingleStoppedTask(true); + setSelectedSingleRunningTask(false); + } else { + setSelectedSingleStoppedTask(false); + setSelectedSingleRunningTask(true); + } + setSelectedMultiplesTasks(false); + setSelectedSingleProject(false); + setSelectedMultiplesProjects(false); + } else { + setSelectedSingleTask(false); + setSelectedSingleStoppedTask(false); + setSelectedSingleRunningTask(false); + + if (tasks.size() > 1) { + setSelectedMultiplesTasks(projects.size() == 0); + } else { + setSelectedSingleProject(tasks.size() == 0 + && projects.size() == 1); + setSelectedMultiplesProjects(tasks.size() == 0 + && projects.size() > 1); + } + } + setSelectedSingleElement(elements.size() == 1); + setSelectedMultiplesElements(elements.size() > 1); + } + + /** + * Get action for named component. + * + * Util method. + * + * @param actionName action name + * @return swing action + */ + protected javax.swing.Action getAction(String actionName) { + return getContext().getActionMap().get(actionName); + } + + /** + * Show window + */ + @Action + public void show() { + // TODO better code ? that use SAF methods ? + getMainFrame().setVisible(true); + getMainFrame().toFront(); + } + + /** + * Show window + */ + @Action + public void hide() { + // TODO better code ? that use SAF methods ? + getMainFrame().setVisible(false); + } + + /** + * Notified by job on pre idle detect. + */ + public void preIdleDetect() { + systrayManager.preIdleDetect(); + } + + /** + * Notified by job on post idle detect. + */ + public void postIdleDetect() { + systrayManager.postIdleDetect(); + } + + /* + * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent) + */ + public void mouseClicked(MouseEvent e) { + + if (log.isDebugEnabled()) { + log.debug("Mouse clicked (" + e.getClickCount() + + " clics), source = " + + e.getSource().getClass().getName()); + } + + switch (e.getButton()) { + + // clic gauche + case MouseEvent.BUTTON1: + // clic sur l'arbre des projets + if (e.getSource() == projectsAndTasksTable) { + // demarre la tache lors d'un double clic dessus + if (e.getClickCount() == 2) { + + if (isSelectedSingleStoppedTask()) { // can only launch non running tasks + Task<?, ?> appTask = startTask(); + if (appTask != null) { + + // first, on dlb click stop all running tasks + stopAllTasks(); + + getContext().getTaskService().execute(appTask); + } + /* TODO EC-20100416 temp disable, called twice, can display + -1 task running :( + } else if (isSelectedSingleRunningTask()) { + TimerTask task = projectsAndTasksTable.getSelectedTasks().get(0); + stopTask(task); */ + } else { + if (log.isDebugEnabled()) { + log.debug("Non selected non running task to launch"); + } + } + } else { + if (log.isDebugEnabled()) { + log.debug("Single clic on tree, do nothing"); + } + } + } + break; + + // gestion du clic droit + case MouseEvent.BUTTON3: + + // force task selection on rigth clic + TreePath path = projectsAndTasksTable.getPathForLocation(e.getX(), + e.getY()); + int selectedRow = projectsAndTasksTable.getRowForPath(path); + projectsAndTasksTable.getSelectionModel().setSelectionInterval( + selectedRow, selectedRow); + + // fix item selection + JPopupMenu menu = new JPopupMenu(); + + String[] actionNames = null; + + // construct menu in function of selection + if (isSelectedSingleProject()) { + actionNames = new String[] { "newTask", "---", "newProject", + "editProject", "closeProject", "deleteProject" }; + } + + if (isSelectedSingleTask()) { + actionNames = new String[] { "startTask", "stopTask", "---", + "newTask", "editTask", "closeTask", "deleteTask", + "---", "addAnnotation", "editAlert", "increment1Task", + "increment5Task", "increment30Task", "decrement1Task", + "decrement5Task", "decrement30Task", "setToZero" }; + } + + if (isSelectedMultiplesTasks()) { + actionNames = new String[] { "mergeTasks" }; + } + + // case, right clic, but nothing selected + if (actionNames != null) { + addActionToMenu(menu, actionNames); + + menu.show(e.getComponent(), e.getX(), e.getY()); + } + } + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } +} Deleted: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java =================================================================== --- trunk/src/main/java/org/chorem/jtimer/ui/report/ReportView.java 2012-03-23 15:11:54 UTC (rev 2836) +++ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java 2012-03-25 21:15:46 UTC (rev 2840) @@ -1,594 +0,0 @@ -/* - * #%L - * jTimer - * - * $Id$ - * $HeadURL$ - * %% - * Copyright (C) 2008 - 2012 CodeLutin, Chatellier Eric - * %% - * 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/gpl-3.0.html>. - * #L% - */ - -package org.chorem.jtimer.ui.report; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Desktop; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTextArea; -import javax.swing.JTree; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.chorem.jtimer.JTimer; -import org.chorem.jtimer.data.TimerCore; -import org.chorem.jtimer.entities.TimerProject; -import org.chorem.jtimer.entities.TimerTask; -import org.chorem.jtimer.ui.report.ReportGenerator.Type; -import org.chorem.jtimer.ui.report.tree.CheckBoxTreeCellEditor; -import org.chorem.jtimer.ui.report.tree.CheckBoxTreeCellRenderer; -import org.chorem.jtimer.ui.report.tree.CheckBoxTreeModel; -import org.jdesktop.application.Action; -import org.jdesktop.application.Application; -import org.jdesktop.application.FrameView; -import org.jdesktop.swingx.JXDatePicker; - -/** - * Reports UI. - * - * @author chatellier - * @version $Revision$ - * - * Last update : $Date$ - * By : $Author$ - */ -public class ReportView extends FrameView implements DocumentListener { - - /** Class logger */ - protected static Log log = LogFactory.getLog(ReportView.class); - - /** Timer core. */ - protected TimerCore core; - - /** Unselected task set. */ - protected Set<TimerTask> uncheckedTaskSet; - - /** Report sorting : day */ - protected JRadioButton radioByDay; - /** Report sorting : month */ - protected JRadioButton radioByMonth; - /** Report sorting : week */ - protected JRadioButton radioByWeek; - /** Report sorting : year */ - protected JRadioButton radioByYear; - /** Report sorting : project */ - protected JRadioButton radioByProject; - - /** Include annotations on reports */ - protected JCheckBox checkIncludesAnnotations; - /** Include annotations time on reports */ - protected JCheckBox checkIncludesAnnotationsTime; - - /** Date pickers, from... to */ - protected JXDatePicker datePickerFrom, datePickerTo; - - /** Project tree (with checkbox) */ - protected JTree projectsTree; - - /** Show hidden project box. */ - protected JCheckBox showHiddenProjectBox; - - /** Report process generator */ - protected ReportGenerator reportGenerator; - - /** Report output */ - protected JTextArea reportArea; - - /** Can send mail property. */ - protected boolean canSendMail; - - /** - * Reposts constructor. - * - * @param application parent reference - * @param core core reference - */ - public ReportView(Application application, TimerCore core) { - - super(application); - - // modify frame name - // otherwise, get parent frame dimention - getFrame().setName("reportFrame"); - - this.core = core; - this.uncheckedTaskSet = new HashSet<TimerTask>(); - - // set title - //setTitle(resourceMap.getString("reportTitle")); - - setComponent(getMainComponent()); - - reportGenerator = new ReportGenerator(); - - // reset selection - //uncheckedTaskSet.clear(); - } - - /** - * Get main view component. - * - * TODO use less complicated UI (no gbl) - * @return main component - */ - protected JComponent getMainComponent() { - - JPanel configComponent = new JPanel(new GridBagLayout()); - - // panel for options - JPanel panelOption = new JPanel(new GridBagLayout()); - panelOption.setBorder(BorderFactory.createTitledBorder(getResourceMap() - .getString("reportOptions"))); - - // first date picker - JLabel labelFrom = new JLabel(getResourceMap().getString("reportFrom")); - panelOption.add(labelFrom, new GridBagConstraints(0, 0, 1, 1, 0, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - datePickerFrom = new JXDatePicker(); - datePickerFrom.getMonthView().setDayForeground(Calendar.SUNDAY, Color.RED); - datePickerFrom.setFormats(DateFormat.getDateInstance(DateFormat.FULL)); - panelOption.add(datePickerFrom, new GridBagConstraints(1, 0, 2, 1, 1, - 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - // second date picker - JLabel labelTo = new JLabel(getResourceMap().getString("reportTo")); - panelOption.add(labelTo, new GridBagConstraints(0, 1, 1, 1, 0, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - datePickerTo = new JXDatePicker(); - datePickerTo.getMonthView().setDayForeground(Calendar.SUNDAY, Color.RED); - datePickerTo.setFormats(DateFormat.getDateInstance(DateFormat.FULL)); - panelOption.add(datePickerTo, new GridBagConstraints(1, 1, 2, 1, 0, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - // set current week by default - currentWeek(); - - // action button to show current or previous week - JButton currentWeekButton = new JButton(); - currentWeekButton.setAction(getContext().getActionMap(this).get( - "currentWeek")); - panelOption.add(currentWeekButton, new GridBagConstraints(3, 0, 1, 1, 0, - 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - JButton previousWeekButton = new JButton(); - previousWeekButton.setAction(getContext().getActionMap(this).get( - "previousWeek")); - panelOption.add(previousWeekButton, new GridBagConstraints(3, 1, 1, 1, 0, - 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - // Option for period grouping - radioByDay = new JRadioButton( - getResourceMap().getString("reportDaily"), true); - radioByMonth = new JRadioButton(getResourceMap().getString( - "reportMonthly")); - radioByWeek = new JRadioButton(getResourceMap().getString( - "reportWeekly")); - radioByYear = new JRadioButton(getResourceMap().getString( - "reportYearly")); - radioByProject = new JRadioButton(getResourceMap().getString( - "reportByProject")); - - ButtonGroup group = new ButtonGroup(); - group.add(radioByDay); - group.add(radioByMonth); - group.add(radioByWeek); - group.add(radioByYear); - group.add(radioByProject); - - panelOption.add(radioByDay, new GridBagConstraints(0, 2, 2, 1, 1, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - panelOption.add(radioByMonth, new GridBagConstraints(2, 2, 2, 1, 1, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - panelOption.add(radioByWeek, new GridBagConstraints(0, 3, 2, 1, 0, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - panelOption.add(radioByYear, new GridBagConstraints(2, 3, 2, 1, 0, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - panelOption.add(radioByProject, new GridBagConstraints(0, 4, 4, 1, 0, - 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - // miscellaneous - checkIncludesAnnotations = new JCheckBox(); - checkIncludesAnnotations.setAction(getContext().getActionMap(this).get( - "reportAnnotations")); - panelOption.add(checkIncludesAnnotations, new GridBagConstraints(0, 5, - 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - checkIncludesAnnotationsTime = new JCheckBox(getResourceMap().getString( - "reportAnnotationsTime")); - checkIncludesAnnotationsTime.setEnabled(false); - panelOption.add(checkIncludesAnnotationsTime, new GridBagConstraints(2, 5, - 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - // panel form projects - JPanel panelProjects = new JPanel(new BorderLayout()); - panelProjects.setBorder(BorderFactory - .createTitledBorder(getResourceMap() - .getString("reportProjects"))); - - // show hidden box - showHiddenProjectBox = new JCheckBox(getContext().getActionMap(this) - .get("showHiddenProjects")); - panelProjects.add(showHiddenProjectBox, BorderLayout.NORTH); - - projectsTree = new JTree(); - projectsTree.setRootVisible(true); - projectsTree.setEditable(true); - projectsTree.setModel(new CheckBoxTreeModel(core, getResourceMap().getString("reportProjectsList") + " :")); - projectsTree.setCellEditor(new CheckBoxTreeCellEditor(core, projectsTree, - uncheckedTaskSet)); - projectsTree.setCellRenderer(new CheckBoxTreeCellRenderer(core, projectsTree, - uncheckedTaskSet)); - - JScrollPane jspTable = new JScrollPane(projectsTree); - panelProjects.add(jspTable, BorderLayout.CENTER); - - configComponent.add(panelOption, new GridBagConstraints(0, 0, 1, 1, 0, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - configComponent.add(panelProjects, new GridBagConstraints(0, 1, 1, 1, 1, - 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(1, 1, 1, 1), 0, 0)); - - // panel for report text - JPanel panelReports = new JPanel(new GridBagLayout()); - panelReports.setBorder(BorderFactory.createTitledBorder(getResourceMap() - .getString("reportContent"))); - - reportArea = new JTextArea(); - reportArea.setFont(new Font("Courier", Font.PLAIN, 12)); - reportArea.getDocument().addDocumentListener(this); - JScrollPane jspReport = new JScrollPane(reportArea); - panelReports.add(jspReport, new GridBagConstraints(0, 0, 3, 1, 1, 1, - GridBagConstraints.CENTER, GridBagConstraints.BOTH, - new Insets(1, 1, 1, 1), 0, 0)); - - // buttons - JButton generateButton = new JButton(); - generateButton.setAction(getContext().getActionMap(this).get( - "generateReport")); - panelReports.add(generateButton, new GridBagConstraints(0, 1, 1, 1, 1, - 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - JButton sendMailButton = new JButton(); - sendMailButton.setAction(getContext().getActionMap(this) - .get("sendMail")); - panelReports.add(sendMailButton, new GridBagConstraints(1, 1, 1, 1, 1, - 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - JButton closeButton = new JButton(); - closeButton.setAction(getContext().getActionMap(this).get("closeView")); - panelReports.add(closeButton, new GridBagConstraints(2, 1, 1, 1, 1, 0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(1, 1, 1, 1), 0, 0)); - - JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, configComponent, panelReports); - splitPane.setOneTouchExpandable(true); - return splitPane; - } - - /** - * Select current week in date pickers. - */ - @Action - public void currentWeek() { - fillPickerDates(0); - } - - /** - * Display previous week in date pickers. - */ - @Action - public void previousWeek() { - fillPickerDates(-1); - } - - /** - * Fill picker date with predefined week selection (from current) - * and apply a delay (-1 = previous week). - * - * @param delay delay to add to current week - */ - protected void fillPickerDates(int delay) { - // init dates - Calendar calendarBegin = Calendar.getInstance(); - int firstDayOfWeek = JTimer.config.getReportFirstDayOfWeek(); - if (firstDayOfWeek <= 0 || firstDayOfWeek > 7) { - firstDayOfWeek = calendarBegin.getFirstDayOfWeek(); - } - calendarBegin.set(Calendar.DAY_OF_WEEK, firstDayOfWeek); - calendarBegin.set(Calendar.HOUR, 0); - calendarBegin.set(Calendar.MINUTE, 0); - calendarBegin.set(Calendar.SECOND, 0); - calendarBegin.set(Calendar.MILLISECOND, 0); - - // calendar must be in current week by default - if (calendarBegin.getTime().after(new Date())) { - calendarBegin.add(Calendar.WEEK_OF_YEAR, -1); - } - - // get end of week - // take calendarBegin and add a week time - Calendar calendarEnd = (Calendar) calendarBegin.clone(); - calendarEnd.add(Calendar.WEEK_OF_YEAR, 1); - calendarEnd.add(Calendar.DAY_OF_YEAR, -1); // take the day before - - // apply delai - calendarBegin.add(Calendar.WEEK_OF_YEAR, delay); - calendarEnd.add(Calendar.WEEK_OF_YEAR, delay); - - datePickerFrom.setDate(calendarBegin.getTime()); - datePickerTo.setDate(calendarEnd.getTime()); - } - - /** - * Close action. - */ - @Action - public void closeView() { - getApplication().hide(this); - } - - /** - * Show annotation checkbox checked. - */ - @Action - public void reportAnnotations() { - checkIncludesAnnotationsTime.setEnabled(checkIncludesAnnotations.isSelected()); - } - - /** - * Make report. - * - * Set content in {@link #reportArea} text area. - */ - @Action - public void generateReport() { - - // Choose selected project type - Type reportType = null; - if (radioByDay.isSelected()) { - reportType = Type.BY_DAY_REPORT; - } else if (radioByMonth.isSelected()) { - reportType = Type.BY_MONTH_REPORT; - } else if (radioByWeek.isSelected()) { - reportType = Type.BY_WEEK_REPORT; - } else if (radioByYear.isSelected()) { - reportType = Type.BY_YEAR_REPORT; - } else { - reportType = Type.BY_PROJECT_REPORT; - } - - // get filtred project list - // without non selected projet and tasks - List<TimerProject> selectedProjects = getSelectedProjects(core - .getData().getProjectsList(), uncheckedTaskSet); - - // make report - String report = reportGenerator.getReportText(reportType, - selectedProjects, datePickerFrom.getDate(), datePickerTo - .getDate(), checkIncludesAnnotations.isSelected(), - checkIncludesAnnotationsTime.isSelected()); - - if (report != null && !report.isEmpty()) { - reportArea.setText(report); - } else { - reportArea.setText(""); - } - } - - /** - * Get project list without project and task unselected in tree. - * - * @param projects original project list - * @param uncheckedTaskList unselected task list - * @return selected project list - */ - protected List<TimerProject> getSelectedProjects( - List<TimerProject> projects, Collection<TimerTask> uncheckedTaskList) { - - List<TimerProject> currentProjects = new ArrayList<TimerProject>(); - for (TimerProject project : projects) { - if (!uncheckedTaskList.contains(project)) { - TimerProject clonedProject = project.clone(); - clonedProject.getSubTasks().clear(); - clonedProject.getSubTasks().addAll( - getSelectedTasks(project.getSubTasks(), - uncheckedTaskList)); - currentProjects.add(clonedProject); - } - } - - return currentProjects; - } - - /** - * Get tasks list without tasks unselected in tree. - * - * @param tasks original project list - * @param uncheckedTaskList unselected task list - * @return selected project list - */ - protected List<TimerTask> getSelectedTasks(List<? extends TimerTask> tasks, - Collection<TimerTask> uncheckedTaskList) { - - List<TimerTask> currentTask = new ArrayList<TimerTask>(); - for (TimerTask task : tasks) { - if (!uncheckedTaskList.contains(task)) { - TimerTask clonedTask = task.clone(); - clonedTask.getSubTasks().clear(); - clonedTask.getSubTasks() - .addAll( - getSelectedTasks(task.getSubTasks(), - uncheckedTaskList)); - currentTask.add(clonedTask); - } - } - - return currentTask; - } - - /** - * Update tree, showing hidden projects or not. - */ - @Action - public void showHiddenProjects() { - CheckBoxTreeModel model = (CheckBoxTreeModel)projectsTree.getModel(); - model.setShowClosed(showHiddenProjectBox.isSelected()); - } - - /** - * Send report by mail. - */ - @Action(enabledProperty = "sendMailEnabled") - public void sendMail() { - - String report = reportArea.getText(); - - try { - String encodedReport = URLEncoder.encode(report, "UTF-8"); - - // bugfix for spaces - encodedReport = encodedReport.replaceAll("\\+", "%20"); - - URI mailtoURI = new URI("mailto:?body=" + encodedReport); - Desktop.getDesktop().mail(mailtoURI); - } catch (IOException e) { - if (log.isWarnEnabled()) { - log.warn("Cannot open link (maybe defaut browser in not configured ?)"); - } - if (log.isDebugEnabled()) { - log.debug("Error while opening link", e); - } - } catch (URISyntaxException e) { - if (log.isErrorEnabled()) { - log.error("Error while opening link", e); - } - } catch (UnsupportedOperationException e) { - if (log.isWarnEnabled()) { - log.warn("Cannot open link (maybe defaut browser in not configured ?)"); - } - if (log.isDebugEnabled()) { - log.debug("Error while opening link", e); - } - } - } - - /** - * Can send mail ? - * - * @return true if can send mail - */ - public boolean isSendMailEnabled() { - return canSendMail; - } - - /** - * Change can send mail property. - * - * @param enabled can send mail - */ - public void setSendMailEnabled(boolean enabled) { - boolean oldValue = canSendMail; - canSendMail = enabled; - firePropertyChange("sendMailEnabled", oldValue, canSendMail); - } - - /* - * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent) - */ - @Override - public void changedUpdate(DocumentEvent e) { - documentChanged(); - } - - /* - * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent) - */ - @Override - public void insertUpdate(DocumentEvent e) { - documentChanged(); - } - - /* - * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent) - */ - @Override - public void removeUpdate(DocumentEvent e) { - documentChanged(); - } - - /** - * Document content changed. - * - * Update can send mail property. - */ - protected void documentChanged() { - setSendMailEnabled(reportArea.getText().trim().length() > 0); - } -} Copied: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java (from rev 2838, trunk/src/main/java/org/chorem/jtimer/ui/report/ReportView.java) =================================================================== --- branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java (rev 0) +++ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/report/ReportView.java 2012-03-25 21:15:46 UTC (rev 2840) @@ -0,0 +1,613 @@ +/* + * #%L + * jTimer + * + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2008 - 2012 CodeLutin, Chatellier Eric + * %% + * 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/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.ui.report; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextArea; +import javax.swing.JTree; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.JTimer; +import org.chorem.jtimer.data.TimerCore; +import org.chorem.jtimer.entities.TimerProject; +import org.chorem.jtimer.entities.TimerTask; +import org.chorem.jtimer.ui.report.ReportGenerator.Type; +import org.chorem.jtimer.ui.report.tree.CheckBoxTreeCellEditor; +import org.chorem.jtimer.ui.report.tree.CheckBoxTreeCellRenderer; +import org.chorem.jtimer.ui.report.tree.CheckBoxTreeModel; +import org.jdesktop.application.Action; +import org.jdesktop.application.Application; +import org.jdesktop.application.FrameView; +import org.jdesktop.swingx.JXDatePicker; + +/** + * Reports UI. + * + * @author chatellier + * @version $Revision$ + * + * Last update : $Date$ + * By : $Author$ + */ +public class ReportView extends FrameView implements DocumentListener { + + /** Class logger */ + protected static Log log = LogFactory.getLog(ReportView.class); + + /** Timer core. */ + protected TimerCore core; + + /** Unselected task set. */ + protected Set<TimerTask> uncheckedTaskSet; + + /** Report sorting : day */ + protected JRadioButton radioByDay; + /** Report sorting : month */ + protected JRadioButton radioByMonth; + /** Report sorting : week */ + protected JRadioButton radioByWeek; + /** Report sorting : year */ + protected JRadioButton radioByYear; + /** Report sorting : project */ + protected JRadioButton radioByProject; + + /** Include annotations on reports */ + protected JCheckBox checkIncludesAnnotations; + /** Include annotations time on reports */ + protected JCheckBox checkIncludesAnnotationsTime; + + /** Date pickers, from... to */ + protected JXDatePicker datePickerFrom, datePickerTo; + + /** Project tree (with checkbox) */ + protected JTree projectsTree; + + /** Show hidden project box. */ + protected JCheckBox showHiddenProjectBox; + + /** Report process generator */ + protected ReportGenerator reportGenerator; + + /** Report output */ + protected JTextArea reportArea; + + /** Can send mail property. */ + protected boolean canSendMail; + + /** + * Reposts constructor. + * + * @param application parent reference + * @param core core reference + */ + public ReportView(Application application, TimerCore core) { + + super(application); + + // modify frame name + // otherwise, get parent frame dimention + getFrame().setName("reportFrame"); + + this.core = core; + this.uncheckedTaskSet = new HashSet<TimerTask>(); + + // set title + //setTitle(resourceMap.getString("reportTitle")); + + setComponent(getMainComponent()); + + reportGenerator = new ReportGenerator(); + + // reset selection + //uncheckedTaskSet.clear(); + } + + /** + * Get main view component. + * + * TODO use less complicated UI (no gbl) + * @return main component + */ + protected JComponent getMainComponent() { + + JPanel configComponent = new JPanel(new GridBagLayout()); + + // panel for options + JPanel panelOption = new JPanel(new GridBagLayout()); + panelOption.setBorder(BorderFactory.createTitledBorder(getResourceMap() + .getString("reportOptions"))); + + // first date picker + JLabel labelFrom = new JLabel(getResourceMap().getString("reportFrom")); + panelOption.add(labelFrom, new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + datePickerFrom = new JXDatePicker(); + datePickerFrom.getMonthView().setDayForeground(Calendar.SUNDAY, Color.RED); + datePickerFrom.setFormats(DateFormat.getDateInstance(DateFormat.FULL)); + panelOption.add(datePickerFrom, new GridBagConstraints(1, 0, 2, 1, 1, + 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + // second date picker + JLabel labelTo = new JLabel(getResourceMap().getString("reportTo")); + panelOption.add(labelTo, new GridBagConstraints(0, 1, 1, 1, 0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + datePickerTo = new JXDatePicker(); + datePickerTo.getMonthView().setDayForeground(Calendar.SUNDAY, Color.RED); + datePickerTo.setFormats(DateFormat.getDateInstance(DateFormat.FULL)); + panelOption.add(datePickerTo, new GridBagConstraints(1, 1, 2, 1, 0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + // set current week by default + currentWeek(); + + // action button to show current or previous week + JButton currentWeekButton = new JButton(); + currentWeekButton.setAction(getContext().getActionMap(this).get( + "currentWeek")); + panelOption.add(currentWeekButton, new GridBagConstraints(3, 0, 1, 1, 0, + 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + JButton previousWeekButton = new JButton(); + previousWeekButton.setAction(getContext().getActionMap(this).get( + "previousWeek")); + panelOption.add(previousWeekButton, new GridBagConstraints(3, 1, 1, 1, 0, + 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + // Option for period grouping + radioByDay = new JRadioButton( + getResourceMap().getString("reportDaily"), true); + radioByMonth = new JRadioButton(getResourceMap().getString( + "reportMonthly")); + radioByWeek = new JRadioButton(getResourceMap().getString( + "reportWeekly")); + radioByYear = new JRadioButton(getResourceMap().getString( + "reportYearly")); + radioByProject = new JRadioButton(getResourceMap().getString( + "reportByProject")); + + ButtonGroup group = new ButtonGroup(); + group.add(radioByDay); + group.add(radioByMonth); + group.add(radioByWeek); + group.add(radioByYear); + group.add(radioByProject); + + panelOption.add(radioByDay, new GridBagConstraints(0, 2, 2, 1, 1, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + panelOption.add(radioByMonth, new GridBagConstraints(2, 2, 2, 1, 1, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + panelOption.add(radioByWeek, new GridBagConstraints(0, 3, 2, 1, 0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + panelOption.add(radioByYear, new GridBagConstraints(2, 3, 2, 1, 0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + panelOption.add(radioByProject, new GridBagConstraints(0, 4, 4, 1, 0, + 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + // miscellaneous + checkIncludesAnnotations = new JCheckBox(); + checkIncludesAnnotations.setAction(getContext().getActionMap(this).get( + "reportAnnotations")); + panelOption.add(checkIncludesAnnotations, new GridBagConstraints(0, 5, + 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + checkIncludesAnnotationsTime = new JCheckBox(getResourceMap().getString( + "reportAnnotationsTime")); + checkIncludesAnnotationsTime.setEnabled(false); + panelOption.add(checkIncludesAnnotationsTime, new GridBagConstraints(2, 5, + 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + // panel form projects + JPanel panelProjects = new JPanel(new BorderLayout()); + panelProjects.setBorder(BorderFactory + .createTitledBorder(getResourceMap() + .getString("reportProjects"))); + + // show hidden box + showHiddenProjectBox = new JCheckBox(getContext().getActionMap(this) + .get("showHiddenProjects")); + panelProjects.add(showHiddenProjectBox, BorderLayout.NORTH); + + projectsTree = new JTree(); + projectsTree.setRootVisible(true); + projectsTree.setEditable(true); + projectsTree.setModel(new CheckBoxTreeModel(core, getResourceMap().getString("reportProjectsList") + " :")); + projectsTree.setCellEditor(new CheckBoxTreeCellEditor(core, projectsTree, + uncheckedTaskSet)); + projectsTree.setCellRenderer(new CheckBoxTreeCellRenderer(core, projectsTree, + uncheckedTaskSet)); + + JScrollPane jspTable = new JScrollPane(projectsTree); + panelProjects.add(jspTable, BorderLayout.CENTER); + + configComponent.add(panelOption, new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + configComponent.add(panelProjects, new GridBagConstraints(0, 1, 1, 1, 1, + 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(1, 1, 1, 1), 0, 0)); + + // panel for report text + JPanel panelReports = new JPanel(new GridBagLayout()); + panelReports.setBorder(BorderFactory.createTitledBorder(getResourceMap() + .getString("reportContent"))); + + reportArea = new JTextArea(); + reportArea.setFont(new Font("Courier", Font.PLAIN, 12)); + reportArea.getDocument().addDocumentListener(this); + JScrollPane jspReport = new JScrollPane(reportArea); + panelReports.add(jspReport, new GridBagConstraints(0, 0, 3, 1, 1, 1, + GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(1, 1, 1, 1), 0, 0)); + + // buttons + JButton generateButton = new JButton(); + generateButton.setAction(getContext().getActionMap(this).get( + "generateReport")); + panelReports.add(generateButton, new GridBagConstraints(0, 1, 1, 1, 1, + 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + JButton sendMailButton = new JButton(); + sendMailButton.setAction(getContext().getActionMap(this) + .get("sendMail")); + panelReports.add(sendMailButton, new GridBagConstraints(1, 1, 1, 1, 1, + 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + JButton closeButton = new JButton(); + closeButton.setAction(getContext().getActionMap(this).get("closeView")); + panelReports.add(closeButton, new GridBagConstraints(2, 1, 1, 1, 1, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0)); + + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, configComponent, panelReports); + splitPane.setOneTouchExpandable(true); + return splitPane; + } + + /** + * Select current week in date pickers. + */ + @Action + public void currentWeek() { + fillPickerDates(0); + } + + /** + * Display previous week in date pickers. + */ + @Action + public void previousWeek() { + fillPickerDates(-1); + } + + /** + * Fill picker date with predefined week selection (from current) + * and apply a delay (-1 = previous week). + * + * @param delay delay to add to current week + */ + protected void fillPickerDates(int delay) { + // init dates + Calendar calendarBegin = Calendar.getInstance(); + int firstDayOfWeek = JTimer.config.getReportFirstDayOfWeek(); + if (firstDayOfWeek <= 0 || firstDayOfWeek > 7) { + firstDayOfWeek = calendarBegin.getFirstDayOfWeek(); + } + calendarBegin.set(Calendar.DAY_OF_WEEK, firstDayOfWeek); + calendarBegin.set(Calendar.HOUR, 0); + calendarBegin.set(Calendar.MINUTE, 0); + calendarBegin.set(Calendar.SECOND, 0); + calendarBegin.set(Calendar.MILLISECOND, 0); + + // calendar must be in current week by default + if (calendarBegin.getTime().after(new Date())) { + calendarBegin.add(Calendar.WEEK_OF_YEAR, -1); + } + + // get end of week + // take calendarBegin and add a week time + Calendar calendarEnd = (Calendar) calendarBegin.clone(); + calendarEnd.add(Calendar.WEEK_OF_YEAR, 1); + calendarEnd.add(Calendar.DAY_OF_YEAR, -1); // take the day before + + // apply delai + calendarBegin.add(Calendar.WEEK_OF_YEAR, delay); + calendarEnd.add(Calendar.WEEK_OF_YEAR, delay); + + datePickerFrom.setDate(calendarBegin.getTime()); + datePickerTo.setDate(calendarEnd.getTime()); + } + + /** + * Close action. + */ + @Action + public void closeView() { + getApplication().hide(this); + } + + /** + * Show annotation checkbox checked. + */ + @Action + public void reportAnnotations() { + checkIncludesAnnotationsTime.setEnabled(checkIncludesAnnotations.isSelected()); + } + + /** + * Make report. + * + * Set content in {@link #reportArea} text area. + */ + @Action + public void generateReport() { + + // Choose selected project type + Type reportType = null; + if (radioByDay.isSelected()) { + reportType = Type.BY_DAY_REPORT; + } else if (radioByMonth.isSelected()) { + reportType = Type.BY_MONTH_REPORT; + } else if (radioByWeek.isSelected()) { + reportType = Type.BY_WEEK_REPORT; + } else if (radioByYear.isSelected()) { + reportType = Type.BY_YEAR_REPORT; + } else { + reportType = Type.BY_PROJECT_REPORT; + } + + // get filtered project list + // without non selected project and tasks + List<TimerProject> selectedProjects = getSelectedProjects(core + .getData().getProjectsList(), uncheckedTaskSet); + + // make report + String report = reportGenerator.getReportText(reportType, + selectedProjects, datePickerFrom.getDate(), datePickerTo + .getDate(), checkIncludesAnnotations.isSelected(), + checkIncludesAnnotationsTime.isSelected()); + + if (report != null && !report.isEmpty()) { + reportArea.setText(report); + } else { + reportArea.setText(""); + } + } + + /** + * Get project list without project and task unselected in tree. + * + * @param projects original project list + * @param uncheckedTaskList unselected task list + * @return selected project list + */ + protected List<TimerProject> getSelectedProjects( + List<TimerProject> projects, Collection<TimerTask> uncheckedTaskList) { + + List<TimerProject> currentProjects = new ArrayList<TimerProject>(); + for (TimerProject project : projects) { + + // take care of show closed option + if (!project.isClosed() || showHiddenProjectBox.isSelected()) { + List<TimerTask> subTasks = getSelectedTasks(project.getSubTasks(), + uncheckedTaskList); + + // add current project only of at least one subtask is selected + // or current project is selected + if (!uncheckedTaskList.contains(project) || !subTasks.isEmpty()) { + TimerProject clonedProject = project.clone(); + clonedProject.getSubTasks().clear(); + clonedProject.getSubTasks().addAll(subTasks); + currentProjects.add(clonedProject); + } + } + } + + return currentProjects; + } + + /** + * Get tasks list without tasks unselected in tree. + * + * @param tasks original project list + * @param uncheckedTaskList unselected task list + * @return selected project list + */ + protected List<TimerTask> getSelectedTasks(List<? extends TimerTask> tasks, + Collection<TimerTask> uncheckedTaskList) { + + List<TimerTask> currentTask = new ArrayList<TimerTask>(); + for (TimerTask task : tasks) { + + // take care of show closed option + if (!task.isClosed() || showHiddenProjectBox.isSelected()) { + List<TimerTask> subTasks = getSelectedTasks(task.getSubTasks(), + uncheckedTaskList); + + // add current task only of at least one subtask is selected + // or current task is selected + if (!uncheckedTaskList.contains(task) || !subTasks.isEmpty()) { + TimerTask clonedTask; + if (uncheckedTaskList.contains(task)) { + // to not show time of not selected task in report + clonedTask = new TimerTask(task.getName()); + } else { + clonedTask = task.clone(); + } + clonedTask.getSubTasks().clear(); + clonedTask.getSubTasks().addAll(subTasks); + currentTask.add(clonedTask); + } + } + } + + return currentTask; + } + + /** + * Update tree, showing hidden projects or not. + */ + @Action + public void showHiddenProjects() { + CheckBoxTreeModel model = (CheckBoxTreeModel)projectsTree.getModel(); + model.setShowClosed(showHiddenProjectBox.isSelected()); + } + + /** + * Send report by mail. + */ + @Action(enabledProperty = "sendMailEnabled") + public void sendMail() { + + String report = reportArea.getText(); + + try { + String encodedReport = URLEncoder.encode(report, "UTF-8"); + + // bugfix for spaces + encodedReport = encodedReport.replaceAll("\\+", "%20"); + + URI mailtoURI = new URI("mailto:?body=" + encodedReport); + Desktop.getDesktop().mail(mailtoURI); + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Cannot open link (maybe defaut browser in not configured ?)"); + } + if (log.isDebugEnabled()) { + log.debug("Error while opening link", e); + } + } catch (URISyntaxException e) { + if (log.isErrorEnabled()) { + log.error("Error while opening link", e); + } + } catch (UnsupportedOperationException e) { + if (log.isWarnEnabled()) { + log.warn("Cannot open link (maybe defaut browser in not configured ?)"); + } + if (log.isDebugEnabled()) { + log.debug("Error while opening link", e); + } + } + } + + /** + * Can send mail ? + * + * @return true if can send mail + */ + public boolean isSendMailEnabled() { + return canSendMail; + } + + /** + * Change can send mail property. + * + * @param enabled can send mail + */ + public void setSendMailEnabled(boolean enabled) { + boolean oldValue = canSendMail; + canSendMail = enabled; + firePropertyChange("sendMailEnabled", oldValue, canSendMail); + } + + /* + * @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent) + */ + @Override + public void changedUpdate(DocumentEvent e) { + documentChanged(); + } + + /* + * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent) + */ + @Override + public void insertUpdate(DocumentEvent e) { + documentChanged(); + } + + /* + * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent) + */ + @Override + public void removeUpdate(DocumentEvent e) { + documentChanged(); + } + + /** + * Document content changed. + * + * Update can send mail property. + */ + protected void documentChanged() { + setSendMailEnabled(reportArea.getText().trim().length() > 0); + } +} Deleted: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java =================================================================== --- trunk/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java 2012-03-23 15:11:54 UTC (rev 2836) +++ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java 2012-03-25 21:15:46 UTC (rev 2840) @@ -1,449 +0,0 @@ -/* - * #%L - * jTimer - * - * $Id$ - * $HeadURL$ - * %% - * Copyright (C) 2007 - 2012 CodeLutin, Chatellier Eric - * %% - * 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/gpl-3.0.html>. - * #L% - */ - -package org.chorem.jtimer.ui.tasks; - -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.List; - -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; - -import org.apache.commons.lang3.time.DurationFormatUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.chorem.jtimer.JTimer; -import org.chorem.jtimer.data.TimerDataManager; -import org.chorem.jtimer.entities.TimerAlert; -import org.chorem.jtimer.entities.TimerTask; -import org.chorem.jtimer.entities.TimerTaskHelper; -import org.chorem.jtimer.entities.TimerAlert.Type; -import org.chorem.jtimer.ui.system.SystemInfo; -import org.chorem.jtimer.ui.system.SystemInfoFactory; -import org.chorem.jtimer.ui.system.UnsupportedSystemInfoException; -import org.jdesktop.application.Task; - -/** - * RunTaskJob. - * - * Notify every second the core controler. - * - * @author chatellier - * @version $Revision$ - * - * Last update : $Date$ - * By : $Author$ - */ -public class RunTaskJob extends Task<Void, Long> { - - /** Class logger. */ - private static Log log = LogFactory.getLog(RunTaskJob.class); - - /** Parent app reference. */ - protected JTimer parentApp; - - /** The task to manage. */ - protected TimerTask managedTask; - - /** Data manager. */ - protected TimerDataManager dataManager; - - /** System information (idle...). */ - protected SystemInfo systemInfo; - - /** Already thrown alert. */ - protected Collection<TimerAlert> alreadyTrownAlerts; - - /** The today total task time at start time. */ - protected long taskTimingBeforeStartInMs; - - /** Calendar instance when timing start. */ - protected Calendar taskStartCalendar; - - /** - * Offset to adjust time for current day. - * Include : - * - Task idle time (task was running but time not counted) - * - Manual user incremented time - */ - protected long offsetTimeInMs; - - /** Last publish time to detect hibernate. */ - protected long lastPublishTimestamp; - - /** Want to stop flag. */ - protected Boolean bWantToStop; - - /** - * Constructor. - * - * Take the task to manage - * - * @param parentApp parent application - * @param managedTask task - * @param dataManager data - */ - public RunTaskJob(JTimer parentApp, TimerTask managedTask, - TimerDataManager dataManager) { - super(parentApp); - this.parentApp = parentApp; - - // init with False - bWantToStop = Boolean.FALSE; - - // save vars - this.managedTask = managedTask; - this.dataManager = dataManager; - - // must be init here, checkAlreadyThrownAlerts is called multiples times - alreadyTrownAlerts = new HashSet<TimerAlert>(); - checkAlreadyThrownAlerts(managedTask); - - // init system info to get idleTime - try { - systemInfo = SystemInfoFactory.getSystemInfo(); - } catch (UnsupportedSystemInfoException e) { - if (log.isErrorEnabled()) { - log.error("Can't get system info", e); - } - systemInfo = null; - } - } - - /** - * Check for already thrown alerts. - * - * Used to not fired again already fired alerts during previous - * timing. - * - * @param task current task - */ - protected void checkAlreadyThrownAlerts(TimerTask task) { - - Date now = new Date(); - // check alert to be fired - for (TimerAlert alert : task.getAlerts()) { - if (alert.getType().equals(Type.REACH_DAILY_TIME) - && TimerTaskHelper.getTotalTime(task, now) > alert.getDuration()) { - alreadyTrownAlerts.add(alert.clone()); - } else if (alert.getType().equals(Type.REACH_TOTAL_TIME) - && TimerTaskHelper.getAllTotalTime(task) > alert.getDuration()) { - alreadyTrownAlerts.add(alert.clone()); - } - } - - // se souvient aussi de alerte deja lancées - // pour les taches parentes - if (task.getParent() != null) { - checkAlreadyThrownAlerts(task.getParent()); - } - - } - - /** - * Task getter. - * - * @return managed task - */ - public TimerTask getTask() { - return managedTask; - } - - /** - * Method to increment time while task is running (in ms). - * - * @param increment increment, can be negative - */ - public void incrementTaskTime(long increment) { - - long nowTimestamp = Calendar.getInstance().getTimeInMillis(); - - // new var, to not modify parameter - long localIncrement = increment; - - // do go over 0 - if (taskTimingBeforeStartInMs + nowTimestamp - - taskStartCalendar.getTimeInMillis() + offsetTimeInMs - + increment < 0) { - localIncrement = -(taskTimingBeforeStartInMs + nowTimestamp - - taskStartCalendar.getTimeInMillis() + offsetTimeInMs); - } - - offsetTimeInMs += localIncrement; - } - - /** - * Reset timing. - * - * For example if task goes over 0h00, need to reset... - */ - public void resetTiming() { - - taskStartCalendar = Calendar.getInstance(); - lastPublishTimestamp = taskStartCalendar.getTimeInMillis(); - // need to be this for hibernate in same day - taskTimingBeforeStartInMs = managedTask.getTime(new Date()) * 1000; - offsetTimeInMs = 0; - } - - /** - * Check if task need to be reset. - * - * Test if today number if different of taskStartCalendar day number - */ - protected void checkReset() { - - Calendar nowCalendar = Calendar.getInstance(); - - // si les jours actuel et jours de debut de start ne sont - // pas identiques : reset - if (nowCalendar.get(Calendar.DAY_OF_YEAR) != taskStartCalendar - .get(Calendar.DAY_OF_YEAR)) { - if (log.isInfoEnabled()) { - log.info("Day change detected, reset timing"); - } - resetTiming(); - } - - } - - /* - * @see org.jdesktop.swingworker.SwingWorker#doInBackground() - */ - @Override - protected Void doInBackground() throws Exception { - - // task effective start - // notify ui - parentApp.startedTask(managedTask); - - // reset on start just for init - resetTiming(); - - // get idle time - long configIdleTime = JTimer.config.getIdleTime() * 1000; - - boolean dontWantToStop = true; - while (dontWantToStop) { - long currentTime = Calendar.getInstance().getTimeInMillis(); - - // try to detect hibernate idle time - if (currentTime - lastPublishTimestamp >= configIdleTime) { - // hibernate detected - // update time without idle time for specified day - if (log.isInfoEnabled()) { - log.info("Hibernate detected, reseting timing"); - } - resetTiming(); - } - - lastPublishTimestamp = currentTime; - - // check user idle time - long idleTime = 0; - if (systemInfo != null) { // idle time available - idleTime = systemInfo.getIdleTime(); - - if (log.isDebugEnabled()) { - log.debug("User is idle since " + (idleTime / 1000) + " s"); - } - } - - // check reset before publish (day change) - checkReset(); - - // if long idleTime is unavailable, if is always false - if (idleTime >= configIdleTime) { - - // to not display negative time near midnight - long idleTimeOffset = Math.min(idleTime, currentTime - taskStartCalendar.getTimeInMillis()); - - // idle detected - // update time without idle time - // math.min > for idle detected at 0:00 - offsetTimeInMs -= idleTimeOffset; - publish(taskTimingBeforeStartInMs + currentTime - taskStartCalendar.getTimeInMillis() + offsetTimeInMs); - - // send idle detect event - JTimer parentApplication = (JTimer)getApplication(); - parentApplication.preIdleDetect(); - // ask user what to do (long blocking call) - int option = IdleDialog.showIdleDialog(currentTime - idleTime); - // send idle detect event - parentApplication.postIdleDetect(); - - // day can have changed during idle - checkReset(); - - // get time after user idle - long afterIdleTime = Calendar.getInstance().getTimeInMillis(); - lastPublishTimestamp = afterIdleTime; - - switch (option) { - - case IdleDialog.REVERT: - // just stop the task - ((JTimer) getApplication()).stopTask(managedTask); - break; - - case IdleDialog.CONTINUE: - // refresh time - // remove idle time previously added - offsetTimeInMs += idleTimeOffset; - publish(taskTimingBeforeStartInMs + afterIdleTime - - taskStartCalendar.getTimeInMillis() - + offsetTimeInMs); - break; - - default: // RESUME - - // resume = increment idle time - offsetTimeInMs -= afterIdleTime - currentTime; - // update time - publish(taskTimingBeforeStartInMs + afterIdleTime - - taskStartCalendar.getTimeInMillis() - + offsetTimeInMs); - break; - - } - } else { - // pas de idle, met a jour le temps - publish(taskTimingBeforeStartInMs + currentTime - - taskStartCalendar.getTimeInMillis() + offsetTimeInMs); - } - - Thread.sleep(1000); // 1s - - // test if task want to stop - synchronized (bWantToStop) { - dontWantToStop = !bWantToStop.booleanValue(); - } - } - - return null; - } - - /* - * @see application.Task#process(java.util.List) - */ - @Override - protected void process(List<Long> durations) { - - // take last notification - // can be notified of many result - // for example, if UI lag... - long currentDuration = durations.get(durations.size() - 1); - - dataManager.changeTaskTime(managedTask, new Date(), - currentDuration / 1000); - - checkTaskAlerts(managedTask); - } - - /** - * Check for alert to be fired. - * - * @param task - */ - protected void checkTaskAlerts(TimerTask task) { - Date now = new Date(); - - // check alert to be fired - for (TimerAlert alert : task.getAlerts()) { - if (!alreadyTrownAlerts.contains(alert)) { - if (alert.getType().equals(Type.REACH_DAILY_TIME) - && TimerTaskHelper.getTotalTime(task, now) >= alert.getDuration()) { - displayAlert(task, Type.REACH_DAILY_TIME, alert.getDuration()); - alreadyTrownAlerts.add(alert.clone()); - } else if (alert.getType().equals(Type.REACH_TOTAL_TIME) - && TimerTaskHelper.getAllTotalTime(task) >= alert.getDuration()) { - displayAlert(task, Type.REACH_TOTAL_TIME, alert.getDuration()); - alreadyTrownAlerts.add(alert.clone()); - } - } - } - - // lance aussi les alertes sur les taches parentes - // par exemple, si une tache passe en temps journaliers a 1h - // sont parent y passe aussi, donc les alertes doivent être levée. - if (task.getParent() != null) { - checkTaskAlerts(task.getParent()); - } - } - - /** - * Display alert message without breaking UI. - * - * @param task task - * @param alertType alert type - * @param alertDuration alert duration - */ - protected void displayAlert(final TimerTask task, final Type alertType, - final long alertDuration) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - String alertMessage = null; - String formattedTime = DurationFormatUtils.formatDuration(alertDuration * 1000, "HH:mm:ss"); - if (Type.REACH_DAILY_TIME.equals(alertType)) { - alertMessage = getResourceMap().getString( - "alert.dailyAlertMessage", task.getName(), formattedTime); - } else if (Type.REACH_TOTAL_TIME.equals(alertType)) { - alertMessage = getResourceMap().getString( - "alert.totalAlertMessage", task.getName(), formattedTime); - } - - JOptionPane.showMessageDialog(null, alertMessage, - getResourceMap().getString("alert.title"), - JOptionPane.INFORMATION_MESSAGE, getResourceMap() - .getIcon("alert.alertIcon")); - } - }); - } - - /** - * Notify that task want to stop - */ - public void wantToStop() { - synchronized (bWantToStop) { - bWantToStop = Boolean.TRUE; - } - } - - /** - * Tell if task is trying to stop. - * - * @return stopping flag - */ - public boolean isStopping() { - boolean stopping; - synchronized (bWantToStop) { - stopping = bWantToStop.booleanValue(); - } - return stopping; - } -} Copied: branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java (from rev 2839, trunk/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java) =================================================================== --- branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java (rev 0) +++ branches/1.4.0-ttalgo/src/main/java/org/chorem/jtimer/ui/tasks/RunTaskJob.java 2012-03-25 21:15:46 UTC (rev 2840) @@ -0,0 +1,435 @@ +/* + * #%L + * jTimer + * + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2007 - 2012 CodeLutin, Chatellier Eric + * %% + * 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/gpl-3.0.html>. + * #L% + */ + +package org.chorem.jtimer.ui.tasks; + +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.lang3.time.DurationFormatUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.chorem.jtimer.JTimer; +import org.chorem.jtimer.data.TimerDataManager; +import org.chorem.jtimer.entities.TimerAlert; +import org.chorem.jtimer.entities.TimerTask; +import org.chorem.jtimer.entities.TimerTaskHelper; +import org.chorem.jtimer.entities.TimerAlert.Type; +import org.chorem.jtimer.ui.system.SystemInfo; +import org.chorem.jtimer.ui.system.SystemInfoFactory; +import org.chorem.jtimer.ui.system.UnsupportedSystemInfoException; +import org.jdesktop.application.Task; + +/** + * RunTaskJob. + * + * Notify every second the core controler. + * + * @author chatellier + * @version $Revision$ + * + * Last update : $Date$ + * By : $Author$ + */ +public class RunTaskJob extends Task<Void, Void> { + + /** Class logger. */ + private static Log log = LogFactory.getLog(RunTaskJob.class); + + /** Parent app reference. */ + protected JTimer parentApp; + + /** The task to manage. */ + protected TimerTask managedTask; + + /** Data manager. */ + protected TimerDataManager dataManager; + + /** System information (idle...). */ + protected SystemInfo systemInfo; + + /** Already thrown alert. */ + protected Collection<TimerAlert> alreadyTrownAlerts; + + /** Timestamp when timing start. */ + protected long taskStartTimestamp; + + /** Last publish time to detect hibernate. */ + protected long lastUserActivity; + + /** + * Offset to adjust time for current day (last activity day). + * Include : + * - Task initial time + * - Task idle time (task was running but time not counted) + * - Manual user incremented time + */ + protected long offsetTimeInMs; + + /** Want to stop flag. */ + protected Boolean bWantToStop; + + /** + * Constructor. + * + * Take the task to manage + * + * @param parentApp parent application + * @param managedTask task + * @param dataManager data + */ + public RunTaskJob(JTimer parentApp, TimerTask managedTask, + TimerDataManager dataManager) { + super(parentApp); + this.parentApp = parentApp; + + // init with False + bWantToStop = Boolean.FALSE; + + // save vars + this.managedTask = managedTask; + this.dataManager = dataManager; + + // must be init here, checkAlreadyThrownAlerts is called multiples times + alreadyTrownAlerts = new HashSet<TimerAlert>(); + checkAlreadyThrownAlerts(managedTask); + + // init system info to get idleTime + try { + systemInfo = SystemInfoFactory.getSystemInfo(); + } catch (UnsupportedSystemInfoException e) { + if (log.isErrorEnabled()) { + log.error("Can't get system info", e); + } + systemInfo = null; + } + } + + /** + * Check for already thrown alerts. + * + * Used to not fired again already fired alerts during previous + * timing. + * + * @param task current task + */ + protected void checkAlreadyThrownAlerts(TimerTask task) { + + Date now = new Date(); + // check alert to be fired + for (TimerAlert alert : task.getAlerts()) { + if (alert.getType().equals(Type.REACH_DAILY_TIME) + && TimerTaskHelper.getTotalTime(task, now) > alert + .getDuration()) { + alreadyTrownAlerts.add(alert.clone()); + } else if (alert.getType().equals(Type.REACH_TOTAL_TIME) + && TimerTaskHelper.getAllTotalTime(task) > alert + .getDuration()) { + alreadyTrownAlerts.add(alert.clone()); + } + } + + // se souvient aussi de alerte deja lancées + // pour les taches parentes + if (task.getParent() != null) { + checkAlreadyThrownAlerts(task.getParent()); + } + + } + + /** + * Task getter. + * + * @return managed task + */ + public TimerTask getTask() { + return managedTask; + } + + /** + * Method to increment time while task is running (in ms). + * + * @param increment increment, can be negative + */ + public void incrementTaskTime(long increment) { + + long nowTimestamp = Calendar.getInstance().getTimeInMillis(); + + // new var, to not modify parameter + long localIncrement = increment; + + // do go over 0 + if (nowTimestamp - taskStartTimestamp + offsetTimeInMs + increment < 0) { + localIncrement = -(nowTimestamp - taskStartTimestamp + offsetTimeInMs); + } + + offsetTimeInMs += localIncrement; + } + + /** + * Reset timing. + * + * For example if task goes over 0h00, need to reset... + */ + public void resetTiming() { + taskStartTimestamp = System.currentTimeMillis(); + lastUserActivity = taskStartTimestamp; + offsetTimeInMs = managedTask.getTime(new Date()) * 1000; + } + + /* + * @see org.jdesktop.swingworker.SwingWorker#doInBackground() + */ + @Override + protected Void doInBackground() throws Exception { + + // task effective start + // notify ui + parentApp.startedTask(managedTask); + + // init timing + resetTiming(); + + // get idle time + long configIdleTime = JTimer.config.getIdleTime() * 1000; + long idleTime = 0; + + boolean dontWantToStop = true; + while (dontWantToStop) { + + long currentTime = System.currentTimeMillis(); + + // check user idle time + if (systemInfo != null) { // idle time available + idleTime = systemInfo.getIdleTime(); + + if (log.isDebugEnabled()) { + log.debug("User is idle since " + (idleTime / 1000) + " s"); + } + } + + // if long idleTime is unavailable, if is always false + if (idleTime >= configIdleTime) { + lastUserActivity -= idleTime; + } + + // check for idleness with last user activity (real idle and hibernate) + if (currentTime - lastUserActivity >= configIdleTime) { + + // update idle to publish time without counting idle + offsetTimeInMs -= currentTime - lastUserActivity; + publish(); + + // display idle detected (blocking call) + JTimer parentApplication = (JTimer) getApplication(); + parentApplication.preIdleDetect(); + int option = IdleDialog.showIdleDialog(currentTime - idleTime); + parentApplication.postIdleDetect(); + + // manage tracking resume after idle + currentTime = System.currentTimeMillis(); + lastUserActivity = currentTime; + + switch (option) { + case IdleDialog.REVERT: + // just stop the task + ((JTimer) getApplication()).stopTask(managedTask); + break; + + case IdleDialog.CONTINUE: + // refresh time + // remove idle time previously added + //offsetTimeInMs += idleTimeOffset; + //publish(taskTimingBeforeStartInMs + afterIdleTime + // - taskStartCalendar.getTimeInMillis() + // + offsetTimeInMs); + + break; + + case IdleDialog.RESUME: + // resume = increment idle time + offsetTimeInMs += currentTime - lastUserActivity; + + // check day change (after offset) + Calendar nowCalendar = Calendar.getInstance(); + Calendar startCalendar = Calendar.getInstance(); + startCalendar.setTimeInMillis(taskStartTimestamp); + if (!DateUtils.isSameDay(startCalendar, nowCalendar)) { + resetTiming(); + } + + // update time + publish(); + break; + } + + } else { + + // no idle detected + lastUserActivity = currentTime; + + // check day change + Calendar nowCalendar = Calendar.getInstance(); + Calendar startCalendar = Calendar.getInstance(); + startCalendar.setTimeInMillis(taskStartTimestamp); + if (!DateUtils.isSameDay(startCalendar, nowCalendar)) { + resetTiming(); + } + + // update new time + publish(); + } + + Thread.sleep(1000); // 1s + + // test if task want to stop + synchronized (bWantToStop) { + dontWantToStop = !bWantToStop.booleanValue(); + } + } + + return null; + } + + /* + * @see application.Task#process(java.util.List) + */ + @Override + protected void process(List<Void> nothing) { + + // take last notification + // can be notified of many result + // for example, if UI lag... + long currentTime = System.currentTimeMillis(); + long currentDuration = currentTime - taskStartTimestamp + offsetTimeInMs; + + // time can be negative due to adjust caused by idle or hibernate + // just publish 0 in this case + if (currentDuration < 0) { + currentDuration = 0; + } + + dataManager.changeTaskTime(managedTask, new Date(), + currentDuration / 1000); + + checkTaskAlerts(managedTask); + } + + /** + * Check for alert to be fired. + * + * @param task + */ + protected void checkTaskAlerts(TimerTask task) { + Date now = new Date(); + + // check alert to be fired + for (TimerAlert alert : task.getAlerts()) { + if (!alreadyTrownAlerts.contains(alert)) { + if (alert.getType().equals(Type.REACH_DAILY_TIME) + && TimerTaskHelper.getTotalTime(task, now) >= alert + .getDuration()) { + displayAlert(task, Type.REACH_DAILY_TIME, + alert.getDuration()); + alreadyTrownAlerts.add(alert.clone()); + } else if (alert.getType().equals(Type.REACH_TOTAL_TIME) + && TimerTaskHelper.getAllTotalTime(task) >= alert + .getDuration()) { + displayAlert(task, Type.REACH_TOTAL_TIME, + alert.getDuration()); + alreadyTrownAlerts.add(alert.clone()); + } + } + } + + // lance aussi les alertes sur les taches parentes + // par exemple, si une tache passe en temps journaliers a 1h + // sont parent y passe aussi, donc les alertes doivent être levée. + if (task.getParent() != null) { + checkTaskAlerts(task.getParent()); + } + } + + /** + * Display alert message without breaking UI. + * + * @param task task + * @param alertType alert type + * @param alertDuration alert duration + */ + protected void displayAlert(final TimerTask task, final Type alertType, + final long alertDuration) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + String alertMessage = null; + String formattedTime = DurationFormatUtils.formatDuration( + alertDuration * 1000, "HH:mm:ss"); + if (Type.REACH_DAILY_TIME.equals(alertType)) { + alertMessage = getResourceMap().getString( + "alert.dailyAlertMessage", task.getName(), + formattedTime); + } else if (Type.REACH_TOTAL_TIME.equals(alertType)) { + alertMessage = getResourceMap().getString( + "alert.totalAlertMessage", task.getName(), + formattedTime); + } + + JOptionPane.showMessageDialog(null, alertMessage, + getResourceMap().getString("alert.title"), + JOptionPane.INFORMATION_MESSAGE, getResourceMap() + .getIcon("alert.alertIcon")); + } + }); + } + + /** + * Notify that task want to stop + */ + public void wantToStop() { + synchronized (bWantToStop) { + bWantToStop = Boolean.TRUE; + } + } + + /** + * Tell if task is trying to stop. + * + * @return stopping flag + */ + public boolean isStopping() { + boolean stopping; + synchronized (bWantToStop) { + stopping = bWantToStop.booleanValue(); + } + return stopping; + } +}