From 4f4fe94a8cbebf6078fbe63de099b8658743681c Mon Sep 17 00:00:00 2001 From: Alberto Garcia Date: Tue, 14 Apr 2009 17:51:03 +0200 Subject: [PATCH] Initial commit --- README.txt | 54 +++ navalplanner-business/pom.xml | 38 ++ .../business/BusinessGlobalNames.java | 10 + .../business/common/daos/IGenericDao.java | 20 ++ .../common/daos/impl/GenericDaoHibernate.java | 109 ++++++ .../impl/GenericDaoHibernateTemplate.java | 84 +++++ .../DuplicateInstanceException.java | 11 + .../common/exceptions/InstanceException.java | 27 ++ .../exceptions/InstanceNotFoundException.java | 11 + .../business/resources/daos/IResourceDao.java | 6 + .../resources/daos/IResourceGroupDao.java | 6 + .../business/resources/daos/IWorkerDao.java | 6 + .../resources/daos/ResourcesDaoRegistry.java | 46 +++ .../daos/impl/ResourceDaoHibernate.java | 8 + .../daos/impl/ResourceGroupDaoHibernate.java | 9 + .../daos/impl/WorkerDaoHibernate.java | 8 + .../business/resources/entities/Resource.java | 59 +++ .../resources/entities/ResourceGroup.java | 80 +++++ .../business/resources/entities/Worker.java | 58 +++ .../resources/services/ResourceService.java | 48 +++ .../services/impl/ResourceServiceImpl.java | 56 +++ .../navalplanner-business-hibernate.cfg.xml | 13 + .../navalplanner-business-spring-config.xml | 66 ++++ .../resources/entities/Resources.hbm.xml | 49 +++ .../business/test/BusinessGlobalNames.java | 10 + .../services/ResourceServiceTest.java | 173 +++++++++ ...valplanner-business-hibernate-test.cfg.xml | 13 + ...valplanner-business-spring-config-test.xml | 36 ++ navalplanner-gantt-zk/pom.xml | 36 ++ .../java/org/zkoss/ganttz/DatesMapper.java | 15 + .../zkoss/ganttz/DatesMapperOnInterval.java | 44 +++ .../java/org/zkoss/ganttz/Dependency.java | 99 +++++ .../zkoss/ganttz/DependencyAddedListener.java | 9 + .../java/org/zkoss/ganttz/DependencyList.java | 122 +++++++ .../java/org/zkoss/ganttz/GanttPanel.java | 29 ++ .../java/org/zkoss/ganttz/ListDetails.java | 41 +++ .../main/java/org/zkoss/ganttz/Planner.java | 87 +++++ .../src/main/java/org/zkoss/ganttz/Task.java | 298 +++++++++++++++ .../ganttz/TaskAssigmentMoveCommand.java | 29 ++ .../main/java/org/zkoss/ganttz/TaskBean.java | 97 +++++ .../java/org/zkoss/ganttz/TaskDetail.java | 174 +++++++++ .../zkoss/ganttz/TaskEditFormComposer.java | 92 +++++ .../main/java/org/zkoss/ganttz/TaskList.java | 212 +++++++++++ .../org/zkoss/ganttz/TaskRemovedListener.java | 7 + .../java/org/zkoss/ganttz/TimeTracker.java | 139 +++++++ .../org/zkoss/ganttz/util/GanttUtils.java | 26 ++ .../java/org/zkoss/ganttz/util/Interval.java | 62 ++++ .../org/zkoss/ganttz/util/MenuBuilder.java | 112 ++++++ .../ganttz/util/WeakReferencedListeners.java | 48 +++ .../util/zoom/DetailOneTimeTrackerState.java | 82 +++++ .../util/zoom/DetailTwoTimeTrackerState.java | 146 ++++++++ .../ganttz/util/zoom/TimeTrackerState.java | 146 ++++++++ .../org/zkoss/ganttz/util/zoom/ZoomLevel.java | 58 +++ .../util/zoom/ZoomLevelChangedListener.java | 8 + .../main/resources/metainfo/zk/lang-addon.xml | 90 +++++ .../resources/web/ganttz/css/task.css.dsp | 7 + .../main/resources/web/ganttz/dependency.dsp | 7 + .../resources/web/ganttz/dependencylist.dsp | 14 + .../main/resources/web/ganttz/ganttpanel.dsp | 16 + .../main/resources/web/ganttz/img/arrow.png | Bin 0 -> 218 bytes .../main/resources/web/ganttz/img/arrow2.png | Bin 0 -> 216 bytes .../main/resources/web/ganttz/img/arrow3.png | Bin 0 -> 211 bytes .../main/resources/web/ganttz/img/arrow4.png | Bin 0 -> 208 bytes .../main/resources/web/ganttz/img/pixel.gif | Bin 0 -> 147 bytes .../main/resources/web/ganttz/img/zoom_in.gif | Bin 0 -> 186 bytes .../resources/web/ganttz/img/zoom_out.gif | Bin 0 -> 186 bytes .../src/main/resources/web/ganttz/planner.dsp | 10 + .../src/main/resources/web/ganttz/task.dsp | 11 + .../main/resources/web/ganttz/tasklist.dsp | 43 +++ .../resources/web/ganttz/zul/listdetails.zul | 29 ++ .../resources/web/ganttz/zul/taskdetail.zul | 18 + .../resources/web/ganttz/zul/timetracker.zul | 53 +++ .../resources/web/js/ganttz/dependency.js | 39 ++ .../resources/web/js/ganttz/dependencylist.js | 4 + .../resources/web/js/ganttz/ganttpanel.js | 5 + .../main/resources/web/js/ganttz/planner.js | 101 ++++++ .../src/main/resources/web/js/ganttz/task.js | 250 +++++++++++++ .../resources/web/js/ganttz/taskdetails.js | 5 + .../main/resources/web/js/ganttz/tasklist.js | 32 ++ navalplanner-webapp/pom.xml | 55 +++ .../zk/myhello/pages/MyHelloPageListener.java | 62 ++++ .../main/webapp/WEB-INF/i3-label.properties | 11 + .../webapp/WEB-INF/i3-label_en_US.properties | 11 + .../src/main/webapp/WEB-INF/web.xml | 67 ++++ .../src/main/webapp/WEB-INF/zk.xml | 5 + .../main/webapp/css/productionmanagement.css | 137 +++++++ .../src/main/webapp/img/v1/blue_ga.jpg | Bin 0 -> 6089 bytes .../src/main/webapp/img/v2/blue_ga.jpg | Bin 0 -> 21552 bytes .../src/main/webapp/img/v3/blue_ga.jpg | Bin 0 -> 5534 bytes .../src/main/webapp/myhello.zul | 63 ++++ pom.xml | 338 ++++++++++++++++++ src/main/assembly/src.xml | 21 ++ src/main/jetty/jetty-env.xml | 18 + 93 files changed, 4854 insertions(+) create mode 100644 README.txt create mode 100644 navalplanner-business/pom.xml create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/BusinessGlobalNames.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/DuplicateInstanceException.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceException.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceNotFoundException.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceDao.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceGroupDao.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IWorkerDao.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ResourcesDaoRegistry.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceDaoHibernate.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceGroupDaoHibernate.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/WorkerDaoHibernate.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/ResourceGroup.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Worker.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java create mode 100644 navalplanner-business/src/main/resources/navalplanner-business-hibernate.cfg.xml create mode 100644 navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml create mode 100644 navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/Resources.hbm.xml create mode 100644 navalplanner-business/src/test/java/org/navalplanner/business/test/BusinessGlobalNames.java create mode 100644 navalplanner-business/src/test/java/org/navalplanner/business/test/resources/services/ResourceServiceTest.java create mode 100644 navalplanner-business/src/test/resources/navalplanner-business-hibernate-test.cfg.xml create mode 100644 navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml create mode 100644 navalplanner-gantt-zk/pom.xml create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapper.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapperOnInterval.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyAddedListener.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/GanttPanel.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java create mode 100755 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskAssigmentMoveCommand.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskList.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskRemovedListener.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TimeTracker.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/GanttUtils.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/Interval.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/MenuBuilder.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/WeakReferencedListeners.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailOneTimeTrackerState.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailTwoTimeTrackerState.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/TimeTrackerState.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevel.java create mode 100644 navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevelChangedListener.java create mode 100755 navalplanner-gantt-zk/src/main/resources/metainfo/zk/lang-addon.xml create mode 100755 navalplanner-gantt-zk/src/main/resources/web/ganttz/css/task.css.dsp create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/dependency.dsp create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/dependencylist.dsp create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/ganttpanel.dsp create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow.png create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow2.png create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow3.png create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow4.png create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/pixel.gif create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/zoom_in.gif create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/img/zoom_out.gif create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/planner.dsp create mode 100755 navalplanner-gantt-zk/src/main/resources/web/ganttz/task.dsp create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/tasklist.dsp create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/zul/listdetails.zul create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/zul/taskdetail.zul create mode 100644 navalplanner-gantt-zk/src/main/resources/web/ganttz/zul/timetracker.zul create mode 100644 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/dependency.js create mode 100644 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/dependencylist.js create mode 100644 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/ganttpanel.js create mode 100644 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/planner.js create mode 100755 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/task.js create mode 100644 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/taskdetails.js create mode 100644 navalplanner-gantt-zk/src/main/resources/web/js/ganttz/tasklist.js create mode 100644 navalplanner-webapp/pom.xml create mode 100644 navalplanner-webapp/src/main/java/org/zk/myhello/pages/MyHelloPageListener.java create mode 100644 navalplanner-webapp/src/main/webapp/WEB-INF/i3-label.properties create mode 100644 navalplanner-webapp/src/main/webapp/WEB-INF/i3-label_en_US.properties create mode 100644 navalplanner-webapp/src/main/webapp/WEB-INF/web.xml create mode 100644 navalplanner-webapp/src/main/webapp/WEB-INF/zk.xml create mode 100644 navalplanner-webapp/src/main/webapp/css/productionmanagement.css create mode 100644 navalplanner-webapp/src/main/webapp/img/v1/blue_ga.jpg create mode 100644 navalplanner-webapp/src/main/webapp/img/v2/blue_ga.jpg create mode 100644 navalplanner-webapp/src/main/webapp/img/v3/blue_ga.jpg create mode 100644 navalplanner-webapp/src/main/webapp/myhello.zul create mode 100644 pom.xml create mode 100644 src/main/assembly/src.xml create mode 100644 src/main/jetty/jetty-env.xml diff --git a/README.txt b/README.txt new file mode 100644 index 000000000..2fb946c4c --- /dev/null +++ b/README.txt @@ -0,0 +1,54 @@ +* DB creation + ----------- + + + Start MySQL: + - Unix: mysqld --default-table-type=InnoDB + - Windows: mysqld-nt --default-table-type=InnoDB(Windows). + + + Create DB "navaldev" (for development): + - mysqladmin -u root create navaldev + + + Create user "naval" with password "naval": + - mysql -u root + GRANT ALL PRIVILEGES ON navaldev.* to naval@localhost IDENTIFIED BY 'naval'; + + + Create another DB with name "navaldevtest" (for testing). The user created + above will need to access this new DB. + + - mysqladmin -u root create navaldevtest + - mysql -u root + GRANT ALL PRIVILEGES ON navaldevtest.* to naval@localhost IDENTIFIED BY 'naval'; + + + PostgreSQL -> DB name=navaldev, user=naval, password=naval. + +* Compilation + ----------- + + + Download Spring Framework 2.5.6. + + + mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta \ + -Dversion=1.0.1B -Dpackaging=jar \ + -Dfile=<>/lib/j2ee/jta.jar + + + cd navalplanner + + + mvn install + + + cd navalplanner-webapp + + + mvn jetty:run + + + Access to http://localhost:8080/navalplanner-webapp. + + + To install the web application in a web container, use the WAR file: + navalplanner-webapp/target/navalplanner-webapp.war + + + NOTE: For PostgreSQL: mvn -Pdev,postgresql install + +* Profiles + -------- + + Check section in the root pom.xml to see the profile-based approach + used in the project. The default profiles (the one assumed by the above + instructions) are "dev" and "mysql" (meaning "use MySQL assuming a development + environment"). diff --git a/navalplanner-business/pom.xml b/navalplanner-business/pom.xml new file mode 100644 index 000000000..36adf8373 --- /dev/null +++ b/navalplanner-business/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.navalplanner + navalplanner + 1.0.0 + + navalplanner-business + jar + Naval Planner Business Module + + + + ${jdbcDriver.groupId} + ${jdbcDriver.artifactId} + + + org.hibernate + hibernate + + + junit + junit + + + org.springframework + spring + + + org.springframework + spring-test + + + + diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/BusinessGlobalNames.java b/navalplanner-business/src/main/java/org/navalplanner/business/BusinessGlobalNames.java new file mode 100644 index 000000000..23a51fe51 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/BusinessGlobalNames.java @@ -0,0 +1,10 @@ +package org.navalplanner.business; + +public class BusinessGlobalNames { + + public final static String BUSINESS_SPRING_CONFIG_FILE = + "classpath:/navalplanner-business-spring-config.xml"; + + private BusinessGlobalNames () {} + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java new file mode 100644 index 000000000..0358356f6 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/IGenericDao.java @@ -0,0 +1,20 @@ +package org.navalplanner.business.common.daos; + +import java.io.Serializable; + +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; + +public interface IGenericDao { + + /** + * It updates and inserts the object passed as a parameter. + */ + public void save(E entity); + + public E find(PK id) throws InstanceNotFoundException; + + public boolean exists(PK id); + + public void remove(PK id) throws InstanceNotFoundException; + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java new file mode 100644 index 000000000..ae739f70a --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernate.java @@ -0,0 +1,109 @@ +package org.navalplanner.business.common.daos.impl; + +import java.io.Serializable; +import java.lang.reflect.ParameterizedType; + +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Restrictions; + +import org.navalplanner.business.common.daos.IGenericDao; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.orm.hibernate3.SessionFactoryUtils; + +/** + * All Hibernate DAOs must extend directly from this class. This constraint is + * imposed by the constructor of this class that must infer the type of the + * entity from the concrete DAO declaration. + * + * The class autowires a SessionFactory bean and allows to implement DAOs with + * the Hibernate's native API. Subclasses access Hibernate's Session by calling + * on getSession() method. Operations must be implemented by catching + * HibernateException and rethrowing it by using + * convertHibernateAccessException() method. See source code of this class + * for an example. + */ +public class GenericDaoHibernate + implements IGenericDao { + + private Class entityClass; + + @Autowired + private SessionFactory sessionFactory; + + @SuppressWarnings("unchecked") + public GenericDaoHibernate() { + this.entityClass = (Class) ((ParameterizedType) getClass(). + getGenericSuperclass()).getActualTypeArguments()[0]; + } + + protected Session getSession() { + return sessionFactory.getCurrentSession(); + } + + protected DataAccessException convertHibernateAccessException( + HibernateException e) { + + return SessionFactoryUtils.convertHibernateAccessException(e); + + } + + public void save(E entity) { + + try { + getSession().saveOrUpdate(entity); + } catch (HibernateException e) { + throw convertHibernateAccessException(e); + } + + } + + @SuppressWarnings("unchecked") + public E find(PK id) throws InstanceNotFoundException { + + try { + + E entity = (E) getSession().get(entityClass, id); + + if (entity == null) { + throw new InstanceNotFoundException(id, entityClass.getName()); + } + + return entity; + + } catch (HibernateException e) { + throw convertHibernateAccessException(e); + } + + } + + public boolean exists(final PK id) { + + try { + + return getSession().createCriteria(entityClass). + add(Restrictions.idEq(id)). + setProjection(Projections.id()). + uniqueResult() != null; + + } catch (HibernateException e) { + throw convertHibernateAccessException(e); + } + + } + + public void remove(PK id) throws InstanceNotFoundException { + + try { + getSession().delete(find(id)); + } catch (HibernateException e) { + throw convertHibernateAccessException(e); + } + + } + +} \ No newline at end of file diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java new file mode 100644 index 000000000..325ef5114 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/daos/impl/GenericDaoHibernateTemplate.java @@ -0,0 +1,84 @@ +package org.navalplanner.business.common.daos.impl; + +import java.io.Serializable; +import java.lang.reflect.ParameterizedType; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Restrictions; + +import org.navalplanner.business.common.daos.IGenericDao; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateCallback; +import org.springframework.orm.hibernate3.HibernateTemplate; + +// FIXME: This class is not currently used. I prefer GenericDaoHibernate. + +/** + * All Hibernate DAOs must extend directly from this class. This constraint is + * imposed by the constructor of this class that must infer the type of the + * entity from the concrete DAO declaration. + * + * The class autowires a SessionFactory bean and allows to implement Spring's + * HibernateTemplate-based DAOs. Subclasses access HibernateTemplate by calling + * on getHibernateTemplate() method. + */ +public class GenericDaoHibernateTemplate + implements IGenericDao { + + private Class entityClass; + + private HibernateTemplate hibernateTemplate; + + @SuppressWarnings("unchecked") + public GenericDaoHibernateTemplate() { + this.entityClass = (Class) ((ParameterizedType) getClass(). + getGenericSuperclass()).getActualTypeArguments()[0]; + } + + protected HibernateTemplate getHibernateTemplate() { + return hibernateTemplate; + } + + @Autowired + public void setSessionFactory(SessionFactory sessionFactory) { + hibernateTemplate = new HibernateTemplate(sessionFactory); + } + + public void save(E entity) { + hibernateTemplate.saveOrUpdate(entity); + } + + @SuppressWarnings("unchecked") + public E find(PK id) throws InstanceNotFoundException { + + E entity = (E) hibernateTemplate.get(entityClass, id); + + if (entity == null) { + throw new InstanceNotFoundException(id, entityClass.getName()); + } + + return entity; + + } + + public boolean exists(final PK id) { + + return (Boolean) hibernateTemplate.execute(new HibernateCallback() { + public Object doInHibernate(Session session) { + return session.createCriteria(entityClass). + add(Restrictions.idEq(id)). + setProjection(Projections.id()). + uniqueResult() != null; + } + }); + + } + + public void remove(PK id) throws InstanceNotFoundException { + hibernateTemplate.delete(find(id)); + } + +} \ No newline at end of file diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/DuplicateInstanceException.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/DuplicateInstanceException.java new file mode 100644 index 000000000..1b17cf0c3 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/DuplicateInstanceException.java @@ -0,0 +1,11 @@ +package org.navalplanner.business.common.exceptions; + +@SuppressWarnings("serial") +public class DuplicateInstanceException extends InstanceException { + + public DuplicateInstanceException(Object key, String className) { + super("Duplicate instance", key, className); + } + +} + diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceException.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceException.java new file mode 100644 index 000000000..f6ac58d27 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceException.java @@ -0,0 +1,27 @@ +package org.navalplanner.business.common.exceptions; + +@SuppressWarnings("serial") +public abstract class InstanceException extends Exception { + + private Object key; + private String className; + + protected InstanceException(String specificMessage, Object key, + String className) { + + super(specificMessage + " (key = '" + key + "' - className = '" + + className + "')"); + this.key = key; + this.className = className; + + } + + public Object getKey() { + return key; + } + + public String getClassName() { + return className; + } + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceNotFoundException.java b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceNotFoundException.java new file mode 100644 index 000000000..3b4dfb01e --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/common/exceptions/InstanceNotFoundException.java @@ -0,0 +1,11 @@ +package org.navalplanner.business.common.exceptions; + +@SuppressWarnings("serial") +public class InstanceNotFoundException extends InstanceException { + + public InstanceNotFoundException(Object key, String className) { + super("Instance not found", key, className); + } + +} + diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceDao.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceDao.java new file mode 100644 index 000000000..685ed5291 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceDao.java @@ -0,0 +1,6 @@ +package org.navalplanner.business.resources.daos; + +import org.navalplanner.business.common.daos.IGenericDao; +import org.navalplanner.business.resources.entities.Resource; + +public interface IResourceDao extends IGenericDao {} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceGroupDao.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceGroupDao.java new file mode 100644 index 000000000..a8cd13472 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IResourceGroupDao.java @@ -0,0 +1,6 @@ +package org.navalplanner.business.resources.daos; + +import org.navalplanner.business.common.daos.IGenericDao; +import org.navalplanner.business.resources.entities.ResourceGroup; + +public interface IResourceGroupDao extends IGenericDao {} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IWorkerDao.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IWorkerDao.java new file mode 100644 index 000000000..56327f8a5 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/IWorkerDao.java @@ -0,0 +1,6 @@ +package org.navalplanner.business.resources.daos; + +import org.navalplanner.business.common.daos.IGenericDao; +import org.navalplanner.business.resources.entities.Worker; + +public interface IWorkerDao extends IGenericDao {} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ResourcesDaoRegistry.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ResourcesDaoRegistry.java new file mode 100644 index 000000000..34a66e0d8 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/ResourcesDaoRegistry.java @@ -0,0 +1,46 @@ +package org.navalplanner.business.resources.daos; + +import org.springframework.beans.factory.annotation.Autowired; + +// FIXME: Improve with +// http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom??? +// (I think it is not necessary). + +/** + * Classes in which dependency injection (DI) is not directly supported by + * Spring (e.g. entities) must use ResourcesDaoRegistry to access + * resource DAOs. For the rest of classes (e.g. services, tests, etc.) Spring + * DI will be a more convenient option. + */ +public final class ResourcesDaoRegistry { + + private static ResourcesDaoRegistry instance = new ResourcesDaoRegistry(); + + @Autowired + private IResourceDao resourceDao; + + @Autowired + private IWorkerDao workerDao; + + @Autowired + private IResourceGroupDao resourceGroupDao; + + private ResourcesDaoRegistry() {} + + public static ResourcesDaoRegistry getInstance() { + return instance; + } + + public static IResourceDao getResourceDao() { + return getInstance().resourceDao; + } + + public static IWorkerDao getWorkerDao() { + return getInstance().workerDao; + } + + public static IResourceGroupDao getResourceGroupDao() { + return getInstance().resourceGroupDao; + } + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceDaoHibernate.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceDaoHibernate.java new file mode 100644 index 000000000..d49b1c4fb --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceDaoHibernate.java @@ -0,0 +1,8 @@ +package org.navalplanner.business.resources.daos.impl; + +import org.navalplanner.business.common.daos.impl.GenericDaoHibernate; +import org.navalplanner.business.resources.daos.IResourceDao; +import org.navalplanner.business.resources.entities.Resource; + +public class ResourceDaoHibernate extends GenericDaoHibernate + implements IResourceDao {} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceGroupDaoHibernate.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceGroupDaoHibernate.java new file mode 100644 index 000000000..c978da021 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/ResourceGroupDaoHibernate.java @@ -0,0 +1,9 @@ +package org.navalplanner.business.resources.daos.impl; + +import org.navalplanner.business.common.daos.impl.GenericDaoHibernate; +import org.navalplanner.business.resources.daos.IResourceGroupDao; +import org.navalplanner.business.resources.entities.ResourceGroup; + +public class ResourceGroupDaoHibernate + extends GenericDaoHibernate + implements IResourceGroupDao {} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/WorkerDaoHibernate.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/WorkerDaoHibernate.java new file mode 100644 index 000000000..789c783ef --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/daos/impl/WorkerDaoHibernate.java @@ -0,0 +1,8 @@ +package org.navalplanner.business.resources.daos.impl; + +import org.navalplanner.business.common.daos.impl.GenericDaoHibernate; +import org.navalplanner.business.resources.daos.IWorkerDao; +import org.navalplanner.business.resources.entities.Worker; + +public class WorkerDaoHibernate extends GenericDaoHibernate + implements IWorkerDao {} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java new file mode 100644 index 000000000..a799159d0 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Resource.java @@ -0,0 +1,59 @@ +package org.navalplanner.business.resources.entities; + +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.daos.ResourcesDaoRegistry; + +// FIXME: Alternatively, Resource can be modeled with the style: +// Resource.getParent() & Resource.getChilds(). This way, Resource does not +// depend on ResourceGroup. However, such an option allows combinations not +// semantically correct (e.g. a simple resource, such as Worker, could be the +// child another simple resource, general methods like getChilds() do not make +// sense for simple entities, etc.). In consequence, I prefer the modeling +// option shown below. +public abstract class Resource { + + private Long id; + private ResourceGroup resourceGroup; + + @SuppressWarnings("unused") + private long version; + + public Long getId() { + return id; + } + + public ResourceGroup getResourceGroup() { + return resourceGroup; + } + + public void setResourceGroup(ResourceGroup resourceGroup) { + this.resourceGroup = resourceGroup; + } + + public abstract int getDailyCapacity(); + + /** + * It removes the resource from the database and updates references. + * The default implementation removes the resource from the resource group + * it belongs to (if it belongs to someone) and from the database. This + * implementation should be valid for simple resources. + */ + public void remove() { + + /* Remove from the resource group it belongs to. */ + ResourceGroup resourceGroup = getResourceGroup(); + + if (resourceGroup != null) { + resourceGroup.removeResource(this); + } + + /* Remove from the database. */ + try { + ResourcesDaoRegistry.getResourceDao().remove(getId()); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + + } + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/ResourceGroup.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/ResourceGroup.java new file mode 100644 index 000000000..11d32ca87 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/ResourceGroup.java @@ -0,0 +1,80 @@ +package org.navalplanner.business.resources.entities; + +import java.util.HashSet; +import java.util.Set; + +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.daos.ResourcesDaoRegistry; + +public class ResourceGroup extends Resource { + + private Set resources = new HashSet(); + + public Set getResources() { + return resources; + } + + public void setResources(Set resources) { + this.resources = resources; + } + + public void addResource(Resource resource) { + + /* + * Remove resource from its current resource group (if it belongs to + * one). + */ + if (resource.getResourceGroup() != null) { + resource.getResourceGroup().removeResource(resource); + } + + /* Add resource to this resource group. */ + resource.setResourceGroup(this); + resources.add(resource); + + } + + public void addResource(Long resourceId) throws InstanceNotFoundException { + + Resource resource = + ResourcesDaoRegistry.getResourceDao().find(resourceId); + addResource(resource); + + } + + public void removeResource(Resource resource) { + + if (resources.contains(resource)) { + resources.remove(resource); + resource.setResourceGroup(null); + } + + } + + @Override + public int getDailyCapacity() { + + int dailyCapacity = 0; + + for (Resource r : resources) { + dailyCapacity += r.getDailyCapacity(); + } + + return dailyCapacity; + + } + + @Override + public void remove() { + + for (Resource r : resources) { + r.setResourceGroup(null); + } + resources.clear(); + + super.remove(); + + } + + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Worker.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Worker.java new file mode 100644 index 000000000..7d945c75b --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/Worker.java @@ -0,0 +1,58 @@ +package org.navalplanner.business.resources.entities; + +public class Worker extends Resource { + + private String firstName; + private String surname; + private String nif; + private int dailyHours; + + public Worker() {} + + public Worker(String firstName, String surname, String nif, + int dailyHours) { + + this.firstName = firstName; + this.surname = surname; + this.nif = nif; + this.dailyHours = dailyHours; + + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public String getNif() { + return nif; + } + + public void setNif(String nif) { + this.nif = nif; + } + + public int getDailyHours() { + return dailyHours; + } + + public void setDailyHours(int dailyHours) { + this.dailyHours = dailyHours; + } + + public int getDailyCapacity() { + return dailyHours; + } + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java new file mode 100644 index 000000000..3efdb51d5 --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/ResourceService.java @@ -0,0 +1,48 @@ +package org.navalplanner.business.resources.services; + +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.entities.Resource; + +// FIXME: Define validation approach for creating and updating entities. + +// FIXME: Originally we though the interface in a less generic way (e.g. +// createWorker, createResourceGroup, etc.). Now it is completely generic. + +// FIXME: The interface must be oriented to detached objects (e.g. +// removeResource(resource) instead of removeResource(resourceId))??? - It +// depends on final requirements. +public interface ResourceService { + + /** + * It updates or inserts the resource passed as a parameter. If the + * resource is a composite resource, updating or inserting is cascaded to + * the resources contained in it. + */ + public void saveResource(Resource resource); + + public Resource findResource(Long resourceId) + throws InstanceNotFoundException; + + /** + * It adds a resource to a resource group. It the resource already belongs + * to a resource group, the resource is moved to the new group. + */ + public void addResourceToResourceGroup(Long resourceId, + Long resourceGroupId) throws InstanceNotFoundException; + +// FIXME: Is the following signature better than the previous one??? - I prefer +// the previous one. +// public void addResourceToResourceGroup(Long resourceId, Long resourceGroupId) +// throws ResourceNotFoundException, ResourceGroupNotFoundException; + + public int getResourceDailyCapacity(Long resourceId) + throws InstanceNotFoundException; + + /** + * It removes a resource. If the resource is a composite resource, the + * resources contained in it are not removed. + */ + public void removeResource(Long resourceId) + throws InstanceNotFoundException; + +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java new file mode 100644 index 000000000..48aeefc4f --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/services/impl/ResourceServiceImpl.java @@ -0,0 +1,56 @@ +package org.navalplanner.business.resources.services.impl; + +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.daos.IResourceDao; +import org.navalplanner.business.resources.daos.IResourceGroupDao; +import org.navalplanner.business.resources.entities.Resource; +import org.navalplanner.business.resources.entities.ResourceGroup; +import org.navalplanner.business.resources.services.ResourceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +public class ResourceServiceImpl implements ResourceService { + + @Autowired + private IResourceDao resourceDao; + + @Autowired + private IResourceGroupDao resourceGroupDao; + + public void saveResource(Resource resource) { + resourceDao.save(resource); + } + + @Transactional(readOnly = true) + public Resource findResource(Long resourceId) + throws InstanceNotFoundException { + + return resourceDao.find(resourceId); + + } + + public void addResourceToResourceGroup(Long resourceId, + Long resourceGroupId) throws InstanceNotFoundException { + + ResourceGroup resourceGroup = resourceGroupDao.find(resourceGroupId); + + resourceGroup.addResource(resourceId); + + } + + @Transactional(readOnly = true) + public int getResourceDailyCapacity(Long resourceId) + throws InstanceNotFoundException { + + return resourceDao.find(resourceId).getDailyCapacity(); + + } + + public void removeResource(Long resourceId) + throws InstanceNotFoundException { + + resourceDao.find(resourceId).remove(); + } + +} diff --git a/navalplanner-business/src/main/resources/navalplanner-business-hibernate.cfg.xml b/navalplanner-business/src/main/resources/navalplanner-business-hibernate.cfg.xml new file mode 100644 index 000000000..17f56b3f1 --- /dev/null +++ b/navalplanner-business/src/main/resources/navalplanner-business-hibernate.cfg.xml @@ -0,0 +1,13 @@ + + + + + ${hibernate.dialect} + ${hibernate.show_sql} + ${hibernate.format_sql} + ${hibernate.use_sql_comments} + ${hibernate.hbm2ddl.auto} + + diff --git a/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml b/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml new file mode 100644 index 000000000..b882228a7 --- /dev/null +++ b/navalplanner-business/src/main/resources/navalplanner-business-spring-config.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + org/navalplanner/business/resources/entities/Resources.hbm.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/Resources.hbm.xml b/navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/Resources.hbm.xml new file mode 100644 index 000000000..05fc6c425 --- /dev/null +++ b/navalplanner-business/src/main/resources/org/navalplanner/business/resources/entities/Resources.hbm.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/BusinessGlobalNames.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/BusinessGlobalNames.java new file mode 100644 index 000000000..61dda759e --- /dev/null +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/BusinessGlobalNames.java @@ -0,0 +1,10 @@ +package org.navalplanner.business.test; + +public class BusinessGlobalNames { + + public final static String BUSINESS_SPRING_CONFIG_TEST_FILE = + "classpath:/navalplanner-business-spring-config-test.xml"; + + private BusinessGlobalNames () {} + +} diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/services/ResourceServiceTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/services/ResourceServiceTest.java new file mode 100644 index 000000000..29f99711a --- /dev/null +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/resources/services/ResourceServiceTest.java @@ -0,0 +1,173 @@ +package org.navalplanner.business.test.resources.services; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.navalplanner.business.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_FILE; +import static org.navalplanner.business.test.BusinessGlobalNames.BUSINESS_SPRING_CONFIG_TEST_FILE; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.navalplanner.business.common.exceptions.InstanceNotFoundException; +import org.navalplanner.business.resources.daos.IResourceDao; +import org.navalplanner.business.resources.entities.ResourceGroup; +import org.navalplanner.business.resources.entities.Worker; +import org.navalplanner.business.resources.services.ResourceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={BUSINESS_SPRING_CONFIG_FILE, + BUSINESS_SPRING_CONFIG_TEST_FILE}) +@Transactional +public class ResourceServiceTest { + + @Autowired + private ResourceService resourceService; + + @Autowired + private IResourceDao resourceDao; + + @Test + public void testAddResourceToResourceGroup() + throws InstanceNotFoundException { + + /* Two workers. One of them belongs to a resource group. */ + Worker worker1 = new Worker("worker-1", "worker-1-surname", + "11111111A", 8); + Worker worker2 = new Worker("worker-2", "worker-2-surname", + "22222222B", 7); + ResourceGroup resourceGroup1 = new ResourceGroup(); + resourceGroup1.addResource(worker1); + resourceService.saveResource(resourceGroup1); // worker1 is also saved. + resourceService.saveResource(worker2); + + /* A resource group. */ + ResourceGroup resourceGroup2 = new ResourceGroup(); + resourceService.saveResource(resourceGroup2); + + /* Add workers to resource group. */ + resourceService.addResourceToResourceGroup(worker1.getId(), + resourceGroup2.getId()); + resourceService.addResourceToResourceGroup(worker2.getId(), + resourceGroup2.getId()); + + /* Check resource group. */ + ResourceGroup resourceGroup = (ResourceGroup) + resourceService.findResource(resourceGroup2.getId()); + + assertEquals(2, resourceGroup.getResources().size()); + assertTrue(resourceGroup.getResources().contains(worker1)); + assertTrue(resourceGroup.getResources().contains(worker2)); + + /* Check worker1 is no longer in group 1. */ + assertFalse(resourceGroup1.getResources().contains(worker1)); + + } + + @Test + public void testGetResourceDailyCapacity() + throws InstanceNotFoundException { + + /* Three workers. */ + Worker worker1 = new Worker("worker-1", "worker-1-surname", + "11111111A", 8); + Worker worker2 = new Worker("worker-2", "worker-2-surname", + "22222222B", 7); + Worker worker3 = new Worker("worker-3", "worker-3-surname", + "33333333C", 6); + + /* A group of two workers. */ + ResourceGroup resourceGroup1 = new ResourceGroup(); + Worker worker4 = new Worker("worker-4", "worker-4-surname", + "44444444D", 5); + Worker worker5 = new Worker("worker-5", "worker-5-surname", + "55555555E", 4); + resourceGroup1.addResource(worker4); + resourceGroup1.addResource(worker5); + + /* + * A complex group containing the first three workers and a group with + * the last two workers. + */ + ResourceGroup resourceGroup2 = new ResourceGroup(); + resourceGroup2.addResource(worker1); + resourceGroup2.addResource(worker2); + resourceGroup2.addResource(worker3); + resourceGroup2.addResource(resourceGroup1); + + /* Calculate total daily capacity. */ + int totalDailyCapacity = + worker1.getDailyCapacity() + worker2.getDailyCapacity() + + worker3.getDailyCapacity() + worker4.getDailyCapacity() + + worker5.getDailyCapacity(); + + /* Save the second group (and in consequence all resources). */ + resourceService.saveResource(resourceGroup2); + + /* Test ResourceService's getResourceDailyCapacity. */ + int resourceGroupDailyCapacity = + resourceService.getResourceDailyCapacity(resourceGroup2.getId()); + + assertEquals(totalDailyCapacity, resourceGroupDailyCapacity); + + } + + @Test + public void testRemoveResource() throws InstanceNotFoundException { + + /* A group of three workers. */ + ResourceGroup resourceGroup = new ResourceGroup(); + Worker worker1 = new Worker("worker-1", "worker-2-surname", + "11111111A", 8); + Worker worker2 = new Worker("worker-2", "worker-3-surname", + "22222222B", 6); + Worker worker3 = new Worker("worker-3", "worker-3-surname", + "33333333C", 4); + resourceGroup.addResource(worker1); + resourceGroup.addResource(worker2); + resourceGroup.addResource(worker3); + resourceService.saveResource(resourceGroup); + + /* Remove worker 3. */ + resourceService.removeResource(worker3.getId()); + + /* Check worker 3 does not exist. */ + assertFalse(resourceDao.exists(worker3.getId())); + + /* + * Check worker 3 is not in resource group and the other workers + * are still in the group. + */ + assertFalse(resourceGroup.getResources().contains(worker3)); + assertTrue(resourceGroup.getResources().contains(worker1)); + assertTrue(resourceGroup.getResources().contains(worker2)); + + /* Remove the group. */ + resourceService.removeResource(resourceGroup.getId()); + + /* Check the resource group does not exist. */ + assertFalse(resourceDao.exists(resourceGroup.getId())); + + /* Check workers still exist. */ + assertTrue(resourceDao.exists(worker1.getId())); + assertTrue(resourceDao.exists(worker2.getId())); + + /* Check workers do not belong to any resource group. */ + assertNull(worker1.getResourceGroup()); + assertNull(worker2.getResourceGroup()); + + /* Remove workers. */ + resourceService.removeResource(worker1.getId()); + resourceService.removeResource(worker2.getId()); + + /* Check workers do not exist. */ + assertFalse(resourceDao.exists(worker1.getId())); + assertFalse(resourceDao.exists(worker2.getId())); + + } + +} diff --git a/navalplanner-business/src/test/resources/navalplanner-business-hibernate-test.cfg.xml b/navalplanner-business/src/test/resources/navalplanner-business-hibernate-test.cfg.xml new file mode 100644 index 000000000..b16a83287 --- /dev/null +++ b/navalplanner-business/src/test/resources/navalplanner-business-hibernate-test.cfg.xml @@ -0,0 +1,13 @@ + + + + + ${hibernate.dialect} + true + true + true + update + + diff --git a/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml b/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml new file mode 100644 index 000000000..05d325e3e --- /dev/null +++ b/navalplanner-business/src/test/resources/navalplanner-business-spring-config-test.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + org/navalplanner/business/resources/entities/Resources.hbm.xml + + + + + + diff --git a/navalplanner-gantt-zk/pom.xml b/navalplanner-gantt-zk/pom.xml new file mode 100644 index 000000000..dd6820a9a --- /dev/null +++ b/navalplanner-gantt-zk/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.navalplanner + navalplanner + 1.0.0 + + navalplanner-gantt-zk + jar + Naval Planner ZK Components Module + + + + + org.zkoss.zk + zul + + + org.zkoss.zk + zkplus + + + org.zkoss.zk + zk + + + + commons-logging + commons-logging + + + + diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapper.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapper.java new file mode 100644 index 000000000..f64697061 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapper.java @@ -0,0 +1,15 @@ +package org.zkoss.ganttz; + +import java.util.Date; + +public interface DatesMapper { + + int toPixels(Date date); + + Date toDate(int pixel); + + int toPixels(long milliseconds); + + long toMilliseconds(int pixels); + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapperOnInterval.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapperOnInterval.java new file mode 100644 index 000000000..8cf2e673c --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DatesMapperOnInterval.java @@ -0,0 +1,44 @@ +/** + * + */ +package org.zkoss.ganttz; + +import java.util.Date; + +import org.zkoss.ganttz.util.Interval; + +public class DatesMapperOnInterval implements DatesMapper { + private final int horizontalSize; + private final Interval stubInterval; + private long millisecondsPerPixel; + + public DatesMapperOnInterval(int horizontalSize, Interval stubInterval) { + this.horizontalSize = horizontalSize; + this.stubInterval = stubInterval; + this.millisecondsPerPixel = stubInterval.getLengthBetween() + / horizontalSize; + } + + @Override + public Date toDate(int pixels) { + return new Date(stubInterval.getStart().getTime() + + millisecondsPerPixel * pixels); + } + + @Override + public int toPixels(Date date) { + double proportion = stubInterval.getProportion(date); + return (int) (horizontalSize * proportion); + } + + @Override + public int toPixels(long milliseconds) { + Date date = new Date(stubInterval.getStart().getTime() + milliseconds); + return this.toPixels(date); + } + + @Override + public long toMilliseconds(int pixels) { + return millisecondsPerPixel * pixels; + } +} \ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java new file mode 100644 index 000000000..881123873 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Dependency.java @@ -0,0 +1,99 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import org.zkoss.zk.au.out.AuInvoke; +import org.zkoss.zk.ui.ext.AfterCompose; +import org.zkoss.zul.impl.XulElement; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class Dependency extends XulElement implements AfterCompose { + + private Task source; + + public Task getSource() { + return source; + } + + public Task getDestination() { + return destination; + } + + private Task destination; + + public Dependency() { + + } + + public Dependency(Task source, Task destination) { + this(); + if (source == null) + throw new IllegalArgumentException("source cannot be null"); + if (destination == null) + throw new IllegalArgumentException("destination cannot be null"); + this.source = source; + this.destination = destination; + } + + @Override + public void afterCompose() { + PropertyChangeListener listener = new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + redrawDependency(); + } + }; + this.source.getTaskBean().addPropertyChangeListener(listener); + this.destination.getTaskBean().addPropertyChangeListener(listener); + } + + /** + * @return the idTaskOrig + */ + public String getIdTaskOrig() { + return source.getUuid(); + } + + public void setIdTaskOrig(String idTaskOrig) { + this.source = findTask(idTaskOrig); + + } + + private Task findTask(String idTaskOrig) { + return (Task) getFellow(idTaskOrig); + } + + /** + * @return the idTaskEnd + */ + public String getIdTaskEnd() { + return destination.getUuid(); + } + + public void setIdTaskEnd(String idTaskEnd) { + this.destination = findTask(idTaskEnd); + } + + public void zoomChanged() { + redrawDependency(); + } + + public void redrawDependency() { + response("zoomChanged", new AuInvoke(this, "draw")); + } + + public boolean contains(Task task) { + return getSource().equals(task) || getDestination().equals(task); + } +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyAddedListener.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyAddedListener.java new file mode 100644 index 000000000..e5713f12b --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyAddedListener.java @@ -0,0 +1,9 @@ +/** + * + */ +package org.zkoss.ganttz; + +public interface DependencyAddedListener { + + public void dependenceAdded(Dependency dependency); +} \ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java new file mode 100644 index 000000000..6e76c366f --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/DependencyList.java @@ -0,0 +1,122 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.zkoss.ganttz.util.MenuBuilder; +import org.zkoss.ganttz.util.MenuBuilder.ItemAction; +import org.zkoss.ganttz.util.zoom.ZoomLevel; +import org.zkoss.ganttz.util.zoom.ZoomLevelChangedListener; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.ext.AfterCompose; +import org.zkoss.zul.Menupopup; +import org.zkoss.zul.impl.XulElement; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class DependencyList extends XulElement implements AfterCompose { + + private static final Log LOG = LogFactory.getLog(DependencyList.class); + + private TaskRemovedListener taskRemovedListener; + + private ZoomLevelChangedListener listener; + + public DependencyList() { + } + + private List getDependencies() { + List children = getChildren(); + return Planner.findComponentsOfType(Dependency.class, children); + } + + void addDependency(Dependency dependency) { + appendChild(dependency); + addContextMenu(dependency); + } + + private void addContextMenu(Dependency dependency) { + dependency.setContext(getContextMenu()); + } + + private GanttPanel getGanttPanel() { + return (GanttPanel) getParent(); + } + + @Override + public void afterCompose() { + if (listener == null) { + listener = new ZoomLevelChangedListener() { + @Override + public void zoomLevelChanged(ZoomLevel detailLevel) { + for (Dependency dependency : getDependencies()) { + dependency.zoomChanged(); + } + } + }; + getTimeTracker().addZoomListener(listener); + } + if (taskRemovedListener == null) { + taskRemovedListener = new TaskRemovedListener() { + + @Override + public void taskRemoved(Task taskRemoved) { + for (Dependency dependency : DependencyList.this + .getDependencies()) { + if (dependency.contains(taskRemoved)) { + dependency.detach(); + } + } + } + + }; + getGanttPanel().getTaskList().addTaskRemovedListener( + taskRemovedListener); + } + addContextMenu(); + + } + + private void addContextMenu() { + for (Dependency dependency : getDependencies()) { + addContextMenu(dependency); + } + } + + private Menupopup contextMenu; + + private Dependency dependencyForContextMenu = null; + + private Menupopup getContextMenu() { + if (contextMenu == null) { + contextMenu = MenuBuilder.on(getPage(), getDependencies()).item( + "Erase", new ItemAction() { + @Override + public void onEvent(Dependency choosen, Event event) { + removeChild(choosen); + } + }).create(); + } + return contextMenu; + } + + private TimeTracker getTimeTracker() { + return getGanttPanel().getTimeTracker(); + } + + public void redrawDependencies() { + for (Dependency dependency : getDependencies()) { + dependency.redrawDependency(); + } + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/GanttPanel.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/GanttPanel.java new file mode 100644 index 000000000..1808e68ad --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/GanttPanel.java @@ -0,0 +1,29 @@ +package org.zkoss.ganttz; + +import java.util.List; + +import org.zkoss.zul.impl.XulElement; + +public class GanttPanel extends XulElement { + + public TimeTracker getTimeTracker() { + List children = getChildren(); + return Planner.findComponentsOfType(TimeTracker.class, children).get(0); + } + + public TaskList getTaskList() { + List children = getChildren(); + return Planner.findComponentsOfType(TaskList.class, children).get(0); + } + + public DependencyList getDependencyList() { + List children = getChildren(); + return Planner.findComponentsOfType(DependencyList.class, children) + .get(0); + } + + public Planner getPlanner() { + return (Planner) getParent(); + } + +} \ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java new file mode 100644 index 000000000..3e1d05d44 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/ListDetails.java @@ -0,0 +1,41 @@ +package org.zkoss.ganttz; + +import java.util.Date; +import java.util.UUID; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.zkoss.util.resource.Labels; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.HtmlMacroComponent; + +public class ListDetails extends HtmlMacroComponent { + + private static Log LOG = LogFactory.getLog(ListDetails.class); + + public ListDetails() { + LOG.info("constructing list details"); + } + + Planner getPlanner() { + return (Planner) getParent(); + } + + public void addTask() { + TaskDetail taskDetail = new TaskDetail(); + String newId = UUID.randomUUID().toString(); + taskDetail.setTaskId(newId); + taskDetail.setDynamicProperty("start", TaskDetail.format(new Date())); + taskDetail.setDynamicProperty("length", "30 days"); + taskDetail.setDynamicProperty("taskName", Labels + .getLabel("task.new_task_name")); + Component insertionPoint = getFellow("insertionPoint"); + taskDetail.setParent(insertionPoint); + taskDetail.afterCompose(); + Task task = new Task(); + getPlanner().addTask(task); + task.setColor("yellow"); + task.setId(newId); + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java new file mode 100644 index 000000000..4eaa4dc68 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Planner.java @@ -0,0 +1,87 @@ +package org.zkoss.ganttz; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.zkoss.zk.ui.ext.AfterCompose; +import org.zkoss.zul.impl.XulElement; + +public class Planner extends XulElement implements AfterCompose { + + private DependencyAddedListener dependencyAddedListener; + + private Map tasksById = new HashMap(); + + public Planner() { + } + + private TaskList getTaskList() { + List children = findOneComponentOfType(GanttPanel.class) + .getChildren(); + return Planner.findComponentsOfType(TaskList.class, children).get(0); + } + + private T findOneComponentOfType(Class type) { + List result = findComponentsOfType(type, getChildren()); + if (result.isEmpty()) { + throw new RuntimeException("it should have found a " + + type.getSimpleName() + " in " + + Planner.class.getSimpleName()); + } + return result.get(0); + } + + void publish(String taskId, TaskBean task) { + if (taskId == null) + throw new IllegalArgumentException("taskId cannot be null"); + if (task == null) + throw new IllegalArgumentException("task cannot be null"); + if (tasksById.containsKey(taskId)) + throw new IllegalArgumentException("task with id " + taskId + + " is already in " + tasksById); + tasksById.put(taskId, task); + } + + TaskBean retrieve(String taskId) { + return tasksById.get(taskId); + } + + public static List findComponentsOfType(Class type, + List children) { + ArrayList result = new ArrayList(); + for (Object child : children) { + if (type.isInstance(child)) { + result.add(type.cast(child)); + } + } + return result; + } + + private GanttPanel getGanntPanel() { + return findOneComponentOfType(GanttPanel.class); + } + + private DependencyList getDependencyList() { + List children = getGanntPanel().getChildren(); + return findComponentsOfType(DependencyList.class, children).get(0); + } + + @Override + public void afterCompose() { + TaskList taskList = getTaskList(); + dependencyAddedListener = new DependencyAddedListener() { + @Override + public void dependenceAdded(Dependency dependency) { + getDependencyList().addDependency(dependency); + } + }; + taskList.addDependencyListener(dependencyAddedListener); + } + + public void addTask(Task task) { + getTaskList().addTask(task); + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java new file mode 100755 index 000000000..87f0c4525 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java @@ -0,0 +1,298 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.zkoss.lang.Objects; +import org.zkoss.xml.HTMLs; +import org.zkoss.zk.au.AuRequest; +import org.zkoss.zk.au.Command; +import org.zkoss.zk.au.ComponentCommand; +import org.zkoss.zk.au.out.AuInvoke; +import org.zkoss.zk.mesg.MZk; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.UiException; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zul.Div; + +/** + * + * @author javi + */ +public class Task extends Div { + + private static Pattern pixelsSpecificationPattern = Pattern + .compile("\\s*(\\d+)px\\s*;?\\s*"); + + private static int stripPx(String pixels) { + Matcher matcher = pixelsSpecificationPattern.matcher(pixels); + if (!matcher.matches()) + throw new IllegalArgumentException("pixels " + pixels + + " is not valid. It must be " + + pixelsSpecificationPattern.pattern()); + return Integer.valueOf(matcher.group(1)); + } + + private static Command _updatecmd = new ComponentCommand( + "onUpdatePosition", 0) { + + protected void process(AuRequest request) { + + final Task ta = (Task) request.getComponent(); + + if (ta == null) { + throw new UiException(MZk.ILLEGAL_REQUEST_COMPONENT_REQUIRED, + this); + } + + String[] requestData = request.getData(); + + if ((requestData != null) && (requestData.length != 2)) { + throw new UiException(MZk.ILLEGAL_REQUEST_WRONG_DATA, + new Object[] { Objects.toString(requestData), this }); + } else { + + ta.doUpdatePosition(requestData[0], requestData[1]); + Events.postEvent(new Event(getId(), ta, request.getData())); + } + } + + }; + + private static Command _updatewidthcmd = new ComponentCommand( + "onUpdateWidth", 0) { + + protected void process(AuRequest request) { + + final Task ta = (Task) request.getComponent(); + + if (ta == null) { + throw new UiException(MZk.ILLEGAL_REQUEST_COMPONENT_REQUIRED, + this); + } + + String[] requestData = request.getData(); + + if ((requestData != null) && (requestData.length != 1)) { + throw new UiException(MZk.ILLEGAL_REQUEST_WRONG_DATA, + new Object[] { Objects.toString(requestData), this }); + } else { + + ta.doUpdateSize(requestData[0]); + Events.postEvent(new Event(getId(), ta, request.getData())); + } + } + }; + + private static Command _adddependencycmd = new ComponentCommand( + "onAddDependency", 0) { + + protected void process(AuRequest request) { + + final Task task = (Task) request.getComponent(); + + if (task == null) { + throw new UiException(MZk.ILLEGAL_REQUEST_COMPONENT_REQUIRED, + this); + } + + String[] requestData = request.getData(); + + if ((requestData != null) && (requestData.length != 1)) { + throw new UiException(MZk.ILLEGAL_REQUEST_WRONG_DATA, + new Object[] { Objects.toString(requestData), this }); + } else { + task.doAddDependency(requestData[0]); + Events.postEvent(new Event(getId(), task, request.getData())); + } + } + }; + + public Task() { + setHeight("20px"); /* Initial constant for standard task height */ + setContext("idContextMenuTaskAssigment"); + } + + private String _color; + + private List> dependencyListeners = new LinkedList>(); + + private TaskBean taskBean; + + public TaskBean getTaskBean() { + return taskBean; + } + + public String getTaskName() { + return taskBean.getName(); + } + + @Override + public void setId(String id) { + super.setId(id); + if (taskBean != null) + throw new IllegalStateException("taskBean already set"); + taskBean = getPlanner().retrieve(id); + updateProperties(); + taskBean.addPropertyChangeListener(new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + updateProperties(); + } + }); + } + + public String getLength() { + return null; + } + + public void addDependencyListener(DependencyAddedListener listener) { + dependencyListeners.add(new WeakReference( + listener)); + } + + private void fireDependenceAdded(Dependency dependency) { + ArrayList active = new ArrayList(); + synchronized (this) { + ListIterator> iterator = dependencyListeners + .listIterator(); + while (iterator.hasNext()) { + WeakReference next = iterator.next(); + DependencyAddedListener listener = next.get(); + if (listener == null) { + iterator.remove(); + } else { + active.add(listener); + } + } + } + for (DependencyAddedListener listener : active) { + listener.dependenceAdded(dependency); + } + } + + public Command getCommand(String cmdId) { + + Command c = null; + + if ("updatePosition".equals(cmdId)) + c = _updatecmd; + else if ("updateSize".equals(cmdId)) + c = _updatewidthcmd; + else if ("addDependency".equals(cmdId)) + c = _adddependencycmd; + + return c; + } + + // Command action to do + void doUpdatePosition(String leftX, String topY) { + System.out.println("leftX:" + getLeft() + "newLeft:" + leftX); + setTop(topY); + this.taskBean.setBeginDate(getMapper().toDate(stripPx(leftX))); + } + + void doUpdateSize(String size) { + System.out.println("size:" + getWidth() + "newWidth:" + size); + int pixels = stripPx(size); + this.taskBean.setLengthMilliseconds(getMapper().toMilliseconds(pixels)); + } + + void doAddDependency(String dependencyId) { + Dependency dependency = new Dependency(this, + ((Task) getFellow(dependencyId))); + fireDependenceAdded(dependency); + } + + public String getColor() { + return _color; + } + + public void setColor(String color) { + + if ((color != null) && (color.length() == 0)) { + color = null; + } + + if (!Objects.equals(_color, color)) { + _color = color; + } + } + + /* + * We override the method of getRealStyle to put the color property as part + * of the style + */ + + protected String getRealStyle() { + + final StringBuffer sb = new StringBuffer(super.getRealStyle()); + + if (getColor() != null) { + HTMLs.appendStyle(sb, "background-color", getColor()); + } + HTMLs.appendStyle(sb, "position", "absolute"); + + return sb.toString(); + } + + /* + * We send a response to the client to create the arrow we are going to use + * to create the dependency + */ + + public void addDependency() { + response("depkey", new AuInvoke(this, "addDependency")); + } + + private DatesMapper getMapper() { + return getTaskList().getMapper(); + } + + public TaskList getTaskList() { + return (TaskList) getParent(); + } + + public Planner getPlanner() { + return getTaskList().getPlanner(); + } + + @Override + public void setParent(Component parent) { + if (parent != null && !(parent instanceof TaskList)) + throw new UiException("Unsupported parent for rows: " + parent); + super.setParent(parent); + } + + public void zoomChanged() { + updateProperties(); + } + + private void updateProperties() { + setLeft(getMapper().toPixels(this.taskBean.getBeginDate()) + "px"); + setWidth(getMapper().toPixels(this.taskBean.getLengthMilliseconds()) + + "px"); + smartUpdate("name", this.taskBean.getName()); + + } + + public void remove() { + getTaskList().removeTask(this); + + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskAssigmentMoveCommand.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskAssigmentMoveCommand.java new file mode 100644 index 000000000..fead36d90 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskAssigmentMoveCommand.java @@ -0,0 +1,29 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz; + +import org.zkoss.zk.au.AuRequest; +import org.zkoss.zk.au.Command; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class TaskAssigmentMoveCommand extends Command { + + public TaskAssigmentMoveCommand(String event,int flags) { + super(event,flags); + } + + protected void process(AuRequest request) { + + System.out.println("Processing command"); + + + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java new file mode 100644 index 000000000..6b42056ce --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskBean.java @@ -0,0 +1,97 @@ +package org.zkoss.ganttz; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Date; + +public class TaskBean { + + private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport( + this); + + private String name; + + private Date beginDate = null; + + private long lengthMilliseconds = 0; + + private String notes; + + public TaskBean() { + } + + public TaskBean(String name, Date beginDate, long lengthMilliseconds) { + if (name == null) + throw new IllegalArgumentException("name cannot be null"); + if (beginDate == null) + throw new IllegalArgumentException("beginDate cannot be null"); + if (lengthMilliseconds < 0) + throw new IllegalArgumentException( + "length in milliseconds must be positive. Instead it is " + + lengthMilliseconds); + this.name = name; + this.beginDate = beginDate; + this.lengthMilliseconds = lengthMilliseconds; + } + + public String getName() { + return name; + } + + public void setName(String name) { + String previousValue = this.name; + this.name = name; + propertyChangeSupport.firePropertyChange("name", previousValue, + this.name); + } + + public void setBeginDate(Date beginDate) { + Date previousValue = this.beginDate; + this.beginDate = beginDate; + propertyChangeSupport.firePropertyChange("beginDate", previousValue, + this.beginDate); + } + + public Date getBeginDate() { + return new Date(beginDate.getTime()); + } + + public void setLengthMilliseconds(long lengthMilliseconds) { + long previousValue = this.lengthMilliseconds; + this.lengthMilliseconds = lengthMilliseconds; + propertyChangeSupport.firePropertyChange("lengthMilliseconds", + previousValue, this.lengthMilliseconds); + } + + public long getLengthMilliseconds() { + return lengthMilliseconds; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + this.propertyChangeSupport.removePropertyChangeListener(listener); + } + + public Date getEndDate() { + return new Date(beginDate.getTime() + lengthMilliseconds); + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + String previousValue = this.notes; + this.notes = notes; + propertyChangeSupport.firePropertyChange("notes", previousValue, + this.notes); + } + + public void setEndDate(Date value) { + setLengthMilliseconds(value.getTime() - beginDate.getTime()); + } + +} \ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java new file mode 100644 index 000000000..413801f14 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskDetail.java @@ -0,0 +1,174 @@ +package org.zkoss.ganttz; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.zkoss.zk.ui.AbstractComponent; +import org.zkoss.zk.ui.HtmlMacroComponent; +import org.zkoss.zk.ui.ext.AfterCompose; +import org.zkoss.zul.Datebox; +import org.zkoss.zul.Textbox; + +public class TaskDetail extends HtmlMacroComponent implements AfterCompose { + + private static long parseLength(String length) { + return LengthType.getTimeInMilliseconds(length); + } + + private static Date parseStartDate(String start) { + try { + return dateFormat.parse(start); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + static String format(Date date) { + return dateFormat.format(date); + } + + private static DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + + private static final Log LOG = LogFactory.getLog(TaskDetail.class); + + private static Pattern lengthPattern = Pattern + .compile("\\s*(\\d+)\\s*(\\w+)\\s*"); + + private enum LengthType { + HOUR(3600, "h", "hour", "hora", "horas"), DAYS(3600 * 24, "day", "dia", + "dias", "días", "día", "days"); + + private final long milliseconds; + + private Set set; + + private LengthType(int seconds, String... sufixes) { + milliseconds = seconds * 1000; + set = new HashSet(Arrays.asList(sufixes)); + } + + public static long getTimeInMilliseconds(String spec) { + Matcher matcher = lengthPattern.matcher(spec); + if (!matcher.matches()) + throw new IllegalArgumentException("spec " + spec + + " is not matched by " + lengthPattern.pattern()); + long number = Integer.parseInt(matcher.group(1)); + String specifier = matcher.group(2).toLowerCase(); + for (LengthType type : LengthType.values()) { + if (type.set.contains(specifier)) { + return number * type.milliseconds; + } + } + throw new IllegalArgumentException(specifier + " not found"); + } + } + + private String taskId; + + private TaskBean taskBean; + + public TaskBean getTaskBean() { + return taskBean; + } + + private Textbox nameBox; + + public Textbox getNameBox() { + return nameBox; + } + + public void setNameBox(Textbox nameBox) { + this.nameBox = nameBox; + } + + public Datebox getStartDateBox() { + return startDateBox; + } + + public void setStartDateBox(Datebox startDateBox) { + this.startDateBox = startDateBox; + this.startDateBox.setCompact(true); + this.startDateBox.setFormat("dd/MM/yyyy"); + } + + public Datebox getEndDateBox() { + return endDateBox; + } + + public void setEndDateBox(Datebox endDateBox) { + this.endDateBox = endDateBox; + this.endDateBox.setFormat("dd/MM/yyyy"); + } + + private Datebox startDateBox; + + private Datebox endDateBox; + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public TaskDetail() { + LOG.info("Detail component constructor"); + } + + public TaskBean getData() { + return taskBean; + } + + private Planner getPlanner() { + AbstractComponent parent = (AbstractComponent) getParent(); + while (!(parent instanceof ListDetails)) { + parent = (AbstractComponent) parent.getParent(); + } + return ((ListDetails) parent).getPlanner(); + } + + @Override + public void afterCompose() { + super.afterCompose(); + taskBean = new TaskBean((String) getDynamicProperty("taskName"), + parseStartDate((String) getDynamicProperty("start")), + parseLength((String) getDynamicProperty("length"))); + getPlanner().publish(taskId, taskBean); + updateComponents(); + taskBean.addPropertyChangeListener(new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + updateComponents(); + } + }); + } + + public void updateBean() { + if (getEndDateBox().getValue().before(getStartDateBox().getValue())) { + updateComponents(); + return; + } + taskBean.setName(getNameBox().getValue()); + taskBean.setBeginDate(getStartDateBox().getValue()); + taskBean.setEndDate(getEndDateBox().getValue()); + } + + private void updateComponents() { + getNameBox().setValue(taskBean.getName()); + getStartDateBox().setValue(taskBean.getBeginDate()); + getEndDateBox().setValue(taskBean.getEndDate()); + } +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java new file mode 100644 index 000000000..2c6c93f21 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskEditFormComposer.java @@ -0,0 +1,92 @@ +package org.zkoss.ganttz; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.util.GenericForwardComposer; +import org.zkoss.zul.Datebox; +import org.zkoss.zul.Textbox; +import org.zkoss.zul.Window; + +public class TaskEditFormComposer extends GenericForwardComposer { + + public TaskEditFormComposer() { + + } + + private Window window; + private TaskBean currentTask; + + private Textbox name; + + private Datebox startDateBox; + + private Datebox endDateBox; + + private Textbox notes; + + private PropertyChangeListener propertyChangeListener; + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + window = (Window) comp; + window.setVisible(false); + } + + public void showEditFormFor(Task task) { + cleanListener(); + this.currentTask = task.getTaskBean(); + window.doPopup(); + propertyChangeListener = new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (window.isVisible()) { + updateComponentValuesForTask(currentTask); + } + } + }; + currentTask.addPropertyChangeListener(propertyChangeListener); + updateComponentValuesForTask(currentTask); + } + + private void cleanListener() { + if (this.currentTask != null) { + this.currentTask + .removePropertyChangeListener(propertyChangeListener); + } + } + + private void updateComponentValuesForTask(TaskBean currentTask) { + window.setTitle(currentTask.getName()); + name.setValue(currentTask.getName()); + startDateBox.setValue(currentTask.getBeginDate()); + endDateBox.setValue(currentTask.getEndDate()); + notes.setValue(currentTask.getNotes()); + } + + public void onChange$name(Event event) { + currentTask.setName(name.getValue()); + } + + public void onChange$startDateBox(Event event) { + currentTask.setBeginDate(startDateBox.getValue()); + } + + public void onChange$endDateBox(Event event) { + currentTask.setEndDate(endDateBox.getValue()); + } + + public void onChange$notes(Event event) { + currentTask.setNotes(notes.getValue()); + } + + public void onClick$ok(Event event) { + window.setVisible(false); + cleanListener(); + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskList.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskList.java new file mode 100644 index 000000000..a0f247557 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskList.java @@ -0,0 +1,212 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import org.zkoss.ganttz.util.MenuBuilder; +import org.zkoss.ganttz.util.WeakReferencedListeners; +import org.zkoss.ganttz.util.MenuBuilder.ItemAction; +import org.zkoss.ganttz.util.WeakReferencedListeners.ListenerNotification; +import org.zkoss.ganttz.util.zoom.ZoomLevel; +import org.zkoss.ganttz.util.zoom.ZoomLevelChangedListener; +import org.zkoss.zk.au.out.AuInvoke; +import org.zkoss.zk.ui.AbstractComponent; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.ext.AfterCompose; +import org.zkoss.zul.Menupopup; +import org.zkoss.zul.impl.XulElement; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class TaskList extends XulElement implements AfterCompose { + + private static final int HEIGHT_PER_ROW = 40; + + private List> listeners = new LinkedList>(); + + private ZoomLevelChangedListener zoomLevelChangedListener; + + private final WeakReferencedListeners taskRemovedListeners = WeakReferencedListeners + .create(); + + private Menupopup contextMenu; + + private TaskEditFormComposer taskEditFormComposer = new TaskEditFormComposer(); + + public synchronized void addTask(Task task) { + task.setParent(this); + invalidate(); + getDependencyList().invalidate(); + addContextMenu(task); + addListenerForTaskEditForm(task); + ListIterator> iterator = listeners + .listIterator(); + while (iterator.hasNext()) { + DependencyAddedListener listener = iterator.next().get(); + if (listener != null) { + task.addDependencyListener(listener); + } else { + iterator.remove(); + } + } + } + + private DependencyList getDependencyList() { + return getGanttPanel().getDependencyList(); + } + + private void addListenersForTaskEditForm() { + for (Task task : getTasks()) { + addListenerForTaskEditForm(task); + } + } + + private void addListenerForTaskEditForm(final Task task) { + task.addEventListener("onDoubleClick", new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + taskEditFormComposer.showEditFormFor(task); + } + }); + } + + private void addContextMenu(final Task task) { + task.addEventListener("onRightClick", new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + getContextMenuForTasks().open(task); + } + }); + } + + private void addContextMenu() { + for (Task task : getTasks()) { + addContextMenu(task); + } + } + + public void addRemoveListener(TaskRemovedListener listener) { + taskRemovedListeners.addListener(listener); + } + + public void removeTask(final Task task) { + removeChild(task); + task.detach(); + taskRemovedListeners + .fireEvent(new ListenerNotification() { + @Override + public void doNotify(TaskRemovedListener listener) { + listener.taskRemoved(task); + + } + }); + } + + @Override + public String getHeight() { + return getTasksNumber() * HEIGHT_PER_ROW + "px"; + } + + public String getSameHeightElementId() { + TimeTracker timeTracker = getTimeTracker(); + AbstractComponent fakeRow = timeTracker.getFakeRow(); + return fakeRow.getUuid(); + } + + private TimeTracker getTimeTracker() { + return (getGanttPanel()).getTimeTracker(); + } + + DatesMapper getMapper() { + return getTimeTracker().getMapper(); + } + + private List getTasks() { + ArrayList result = new ArrayList(); + for (Object child : getChildren()) { + if (child instanceof Task) { + result.add((Task) child); + } + } + return result; + } + + private int getTasksNumber() { + return getTasks().size(); + } + + public void addTaskRemovedListener(TaskRemovedListener taskRemovedListener) { + taskRemovedListeners.addListener(taskRemovedListener); + } + + public void addDependencyListener(DependencyAddedListener listener) { + listeners.add(new WeakReference(listener)); + for (Task task : getTasks()) { + task.addDependencyListener(listener); + } + } + + @Override + public void afterCompose() { + if (zoomLevelChangedListener == null) { + zoomLevelChangedListener = new ZoomLevelChangedListener() { + @Override + public void zoomLevelChanged(ZoomLevel detailLevel) { + for (Task task : getTasks()) { + task.zoomChanged(); + } + response("adjust_height", new AuInvoke(TaskList.this, + "adjust_height")); + } + }; + getTimeTracker().addZoomListener(zoomLevelChangedListener); + } + addListenersForTaskEditForm(); + addContextMenu(); + } + + private Menupopup getContextMenuForTasks() { + if (contextMenu == null) { + contextMenu = MenuBuilder.on(getPage(), getTasks()).item( + "Add Dependency", new ItemAction() { + + @Override + public void onEvent(Task choosen, Event event) { + choosen.addDependency(); + } + }).item("Erase", new ItemAction() { + @Override + public void onEvent(Task choosen, Event event) { + choosen.remove(); + } + }).createWithoutSettingContext(); + } + return contextMenu; + } + + public Planner getPlanner() { + return getGanttPanel().getPlanner(); + } + + private GanttPanel getGanttPanel() { + return (GanttPanel) getParent(); + } + + public TaskEditFormComposer getModalFormComposer() { + return taskEditFormComposer; + } +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskRemovedListener.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskRemovedListener.java new file mode 100644 index 000000000..1bcc56b23 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TaskRemovedListener.java @@ -0,0 +1,7 @@ +package org.zkoss.ganttz; + +public interface TaskRemovedListener { + + public void taskRemoved(Task taskRemoved); + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TimeTracker.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TimeTracker.java new file mode 100644 index 000000000..e61549b8c --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/TimeTracker.java @@ -0,0 +1,139 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import org.zkoss.ganttz.util.Interval; +import org.zkoss.ganttz.util.zoom.TimeTrackerState; +import org.zkoss.ganttz.util.zoom.ZoomLevel; +import org.zkoss.ganttz.util.zoom.ZoomLevelChangedListener; +import org.zkoss.ganttz.util.zoom.TimeTrackerState.DetailItem; +import org.zkoss.zk.ui.AbstractComponent; +import org.zkoss.zk.ui.HtmlMacroComponent; +import org.zkoss.zul.Label; + +/** + * + * + * @author Francisco Javier Moran Rúa + * + */ + +public class TimeTracker extends HtmlMacroComponent { + + private static Interval getTestInterval() { + return new Interval(TimeTrackerState.year(2009), TimeTrackerState + .year(2019)); + } + + private AbstractComponent fakeRow; + + private List> zoomListeners = new LinkedList>(); + + private DatesMapper datesMapper = null; + + private Collection detailsFirstLevelCached = null; + + private Collection detailsSecondLevelCached = null; + + public void addZoomListener(ZoomLevelChangedListener listener) { + zoomListeners + .add(new WeakReference(listener)); + } + + private void fireZoomChanged(ZoomLevel detailLevel) { + ListIterator> listIterator = zoomListeners + .listIterator(); + while (listIterator.hasNext()) { + ZoomLevelChangedListener listener = listIterator.next().get(); + if (listener == null) { + listIterator.remove(); + } else { + listener.zoomLevelChanged(detailLevel); + } + } + } + + public Collection getDetailsFirstLevel() { + if (detailsFirstLevelCached == null) { + detailsFirstLevelCached = getTimeTrackerState() + .getFirstLevelDetails(getTestInterval()); + } + return detailsFirstLevelCached; + } + + public Interval getRealInterval() { + return getTimeTrackerState().getRealIntervalFor(getTestInterval()); + } + + public Collection getDetailsSecondLevel() + throws Exception { + if (detailsSecondLevelCached == null) { + detailsSecondLevelCached = getTimeTrackerState() + .getSecondLevelDetails(getTestInterval()); + } + return detailsSecondLevelCached; + } + + private TimeTrackerState getTimeTrackerState() { + return getDetailLevel().getTimeTrackerState(); + } + + public int getHorizontalSize() { + // Code to improve. Not optimus. We have to calculate the details twice + int result = 0; + Collection detailsFirstLevel = getDetailsFirstLevel(); + for (TimeTrackerState.DetailItem item : detailsFirstLevel) { + result += item.getSize(); + } + return result; + } + + public void onIncrease() { + changeDetailLevel(getDetailLevel().next()); + } + + private void changeDetailLevel(ZoomLevel d) { + setDynamicProperty("detailLevel", d); + datesMapper = null; + detailsFirstLevelCached = null; + detailsSecondLevelCached = null; + final Label lb = (Label) getFellow("mcdetaillevel"); + lb.setValue(getDetailLevel().toString()); + recreate(); + fireZoomChanged(d); + } + + public void onDecrease() { + changeDetailLevel(getDetailLevel().previous()); + } + + private ZoomLevel getDetailLevel() { + return (ZoomLevel) getDynamicProperty("detailLevel"); + } + + public AbstractComponent getFakeRow() { + return fakeRow; + } + + public void setFakeRow(AbstractComponent fakeRow) { + this.fakeRow = fakeRow; + } + + public DatesMapper getMapper() { + if (datesMapper == null) { + datesMapper = new DatesMapperOnInterval(getHorizontalSize(), + getRealInterval()); + } + return datesMapper; + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/GanttUtils.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/GanttUtils.java new file mode 100644 index 000000000..b18852469 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/GanttUtils.java @@ -0,0 +1,26 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz.util; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class GanttUtils { + + public static int getIntFromStylePosition(String position) throws Exception { + + String[] tokens = position.split("px"); + + if (tokens.length != 1) { + throw new Exception("Bad formatting for input parameter"); + } + + return Integer.parseInt(tokens[0]); + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/Interval.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/Interval.java new file mode 100644 index 000000000..8d97e5f63 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/Interval.java @@ -0,0 +1,62 @@ +/** + * + */ +package org.zkoss.ganttz.util; + +import java.util.Date; + +public class Interval { + private final Date start; + + private final Date finish; + + private final long lengthBetween; + + public Interval(Date start, Date finish) { + if (start == null) + throw new IllegalArgumentException("begin cannot be null"); + if (finish == null) + throw new IllegalArgumentException("end cannot be null"); + if (start.compareTo(finish) > 0) + throw new IllegalArgumentException("start must be prior to end"); + this.start = start; + this.finish = finish; + lengthBetween = this.finish.getTime() - this.start.getTime(); + } + + public Date getStart() { + return new Date(start.getTime()); + } + + public Date getFinish() { + return new Date(finish.getTime()); + } + + public long getLengthBetween() { + return lengthBetween; + } + + public Date atProportion(double proportion) { + // comparisons with doubles are dangerous, change it + if (proportion > 1.0d) { + throw new IllegalArgumentException( + "the proportion must be less or equal than one"); + } + if (proportion < 0d) { + throw new IllegalArgumentException( + "the proportion must be bigger than cero"); + } + return new Date(start.getTime() + (int) (lengthBetween * proportion)); + } + + public double getProportion(Date date) { + if (!isIncluded(date)) + throw new IllegalArgumentException("date " + date + + " must be between [" + start + "," + finish + "]"); + return ((double) date.getTime() - start.getTime()) / lengthBetween; + } + + private boolean isIncluded(Date date) { + return start.compareTo(date) <= 0 && finish.compareTo(date) >= 0; + } +} \ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/MenuBuilder.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/MenuBuilder.java new file mode 100644 index 000000000..462dfa3c5 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/MenuBuilder.java @@ -0,0 +1,112 @@ +package org.zkoss.ganttz.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.Page; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.OpenEvent; +import org.zkoss.zul.Menuitem; +import org.zkoss.zul.Menupopup; +import org.zkoss.zul.impl.api.XulElement; + +public class MenuBuilder { + + public static MenuBuilder on(Page page, + Collection elements) { + return new MenuBuilder(page, elements); + } + + public static MenuBuilder on(Page page, + T... elements) { + return on(page, Arrays.asList(elements)); + } + + public static interface ItemAction { + + void onEvent(T choosen, Event event); + } + + private class Item { + final String name; + + final ItemAction action; + + Item(String name, ItemAction action) { + this.name = name; + this.action = action; + } + + Menuitem createMenuItem() { + Menuitem result = new Menuitem(); + result.setLabel(name); + return result; + } + + } + + private final List elements; + + private final List items = new ArrayList(); + + private Component root; + + private MenuBuilder(Page page, Collection elements) { + this.elements = new ArrayList(elements); + this.root = page.getLastRoot(); + } + + public MenuBuilder item(String name, ItemAction itemAction) { + if (name == null) + throw new IllegalArgumentException("name cannot be null"); + if (itemAction == null) + throw new IllegalArgumentException("itemAction cannot be null"); + items.add(new Item(name, itemAction)); + return this; + } + + private T referenced; + + public Menupopup createWithoutSettingContext() { + return create(false); + } + + public Menupopup create() { + return create(true); + } + + private Menupopup create(boolean setContext) { + Menupopup result = new Menupopup(); + result.addEventListener("onOpen", new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + OpenEvent openEvent = (OpenEvent) event; + referenced = (T) openEvent.getReference(); + } + }); + for (final Item item : items) { + Menuitem menuItem = item.createMenuItem(); + menuItem.addEventListener("onClick", new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + ItemAction action = item.action; + action.onEvent(referenced, event); + } + }); + result.appendChild(menuItem); + } + root.appendChild(result); + if (setContext) { + for (T element : elements) { + element.setContext(result); + } + } + return result; + } +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/WeakReferencedListeners.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/WeakReferencedListeners.java new file mode 100644 index 000000000..01a2a98d2 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/WeakReferencedListeners.java @@ -0,0 +1,48 @@ +package org.zkoss.ganttz.util; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.ListIterator; + +public class WeakReferencedListeners { + + public interface ListenerNotification { + + void doNotify(T listener); + + } + + public static WeakReferencedListeners create() { + return new WeakReferencedListeners(); + } + + private LinkedList> listeners = new LinkedList>(); + + private WeakReferencedListeners() { + + } + + public synchronized void addListener(T listener) { + if (listener == null) + throw new IllegalArgumentException("listener cannot be null"); + listeners.add(new WeakReference(listener)); + } + + public synchronized void fireEvent( + ListenerNotification notification) { + ListIterator> listIterator = listeners.listIterator(); + ArrayList active = new ArrayList(); + while (listIterator.hasNext()) { + T listener = listIterator.next().get(); + if (listener == null) + listIterator.remove(); + else { + active.add(listener); + } + } + for (T listener : active) { + notification.doNotify(listener); + } + } +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailOneTimeTrackerState.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailOneTimeTrackerState.java new file mode 100644 index 000000000..bc749e2b2 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailOneTimeTrackerState.java @@ -0,0 +1,82 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz.util.zoom; + +import java.util.Collection; +import java.util.Vector; + +import org.zkoss.ganttz.util.Interval; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class DetailOneTimeTrackerState extends TimeTrackerState { + + public static final DetailOneTimeTrackerState INSTANCE = new DetailOneTimeTrackerState(); + private static final int FIRST_LEVEL_ITEM_SIZE = 200; + private static final int SECOND_LEVEL_ITEM_SIZE = 100; + + private DetailOneTimeTrackerState() { + }; + + private Collection buildCollectionDetailsFirstLevel( + int initialYear, int endYear) { + + Collection detailsVector = new Vector(); + + for (int i = initialYear; i <= endYear; i++) { + DetailItem d = new DetailItem(FIRST_LEVEL_ITEM_SIZE, String + .valueOf(i)); + detailsVector.add(d); + } + + return detailsVector; + } + + private Collection buildCollectionDetailsSecondLevel( + int initialYear, int endYear) { + + Collection detailsVector = new Vector(); + + for (int i = initialYear; i <= endYear; i++) { + + DetailItem d1 = new DetailItem(SECOND_LEVEL_ITEM_SIZE, "H1"); + detailsVector.add(d1); + + DetailItem d2 = new DetailItem(SECOND_LEVEL_ITEM_SIZE, "H2"); + detailsVector.add(d2); + + } + + return detailsVector; + } + + @Override + protected Collection createDetailsForFirstLevel( + Interval interval) { + int[] pairYears = calculateInitialEndYear(interval.getStart(), interval + .getFinish()); + return buildCollectionDetailsFirstLevel(pairYears[0], pairYears[1]); + + } + + @Override + protected Collection createDetailsForSecondLevel( + Interval interval) { + int[] pairYears = calculateInitialEndYear(interval.getStart(), interval + .getFinish()); + return buildCollectionDetailsSecondLevel(pairYears[0], pairYears[1]); + } + + public Interval getRealIntervalFor(Interval interval) { + int[] pairYears = calculateInitialEndYear(interval.getStart(), interval + .getFinish()); + return new Interval(year(pairYears[0]), year(pairYears[1] + 1)); + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailTwoTimeTrackerState.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailTwoTimeTrackerState.java new file mode 100644 index 000000000..b3b63b0f0 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/DetailTwoTimeTrackerState.java @@ -0,0 +1,146 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz.util.zoom; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.Vector; + +import org.zkoss.ganttz.util.Interval; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public class DetailTwoTimeTrackerState extends TimeTrackerState { + + public static final DetailTwoTimeTrackerState INSTANCE = new DetailTwoTimeTrackerState(); + private static final int FIRST_LEVEL_ITEM_SIZE = 400; + private static final int SECOND_LEVEL_ITEM_SIZE = 100; + + public Interval getRealIntervalFor(Interval interval) { + int[] pairYears = calculateInitialEndYear(interval.getStart(), interval + .getFinish()); + int startQuarter = calculateInQuarterPeriodDateInYear(interval + .getStart(), pairYears[0]); + int endQuarter = calculateInQuarterPeriodDateInYear(interval + .getFinish(), pairYears[1]); + return new Interval(quarterAt(startQuarter - 1, year(pairYears[0])), + quarterAt(endQuarter, year(pairYears[1]))); + } + + private static Date quarterAt(int quarter, Date date) { + int year = from(date).get(Calendar.YEAR); + Calendar calendar = from(year(year)); + calendar.add(Calendar.MONTH, 3 * quarter); + return calendar.getTime(); + } + + static Calendar from(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + } + + private Collection buildCollectionDetailsFirstLevel( + Date initialDate, Date endDate, int initialYear, int endYear) { + + Collection detailsVector = new Vector(); + + // Calculate the size of the first detail of the first level + int quarter = calculateInQuarterPeriodDateInYear(initialDate, + initialYear); + detailsVector.add(new DetailItem((4 - (quarter - 1)) + * FIRST_LEVEL_ITEM_SIZE / 4, String.valueOf(initialYear))); + + for (int i = (initialYear + 1); i < endYear; i++) { + DetailItem d = new DetailItem(FIRST_LEVEL_ITEM_SIZE, String + .valueOf(i)); + detailsVector.add(d); + } + + // Calculate the size of the last detail of the first level + int endQuarter = calculateInQuarterPeriodDateInYear(endDate, endYear); + detailsVector + .add(new DetailItem(endQuarter * FIRST_LEVEL_ITEM_SIZE / 4, + String.valueOf(endYear))); + + return detailsVector; + + } + + private Collection buildCollectionDetailsSecondLevel( + Date initialDate, Date endDate, int initialYear, int endYear) { + ArrayList result = new ArrayList(); + int startDateQuarter = calculateInQuarterPeriodDateInYear(initialDate, + initialYear); + ArrayList quarters = new ArrayList(); + for (int i = 0; i < 4; i++) { + quarters.add(new DetailItem(SECOND_LEVEL_ITEM_SIZE, "Q" + (i + 1))); + } + // DetailItem is an inmutable class so it can be safely shared + result.addAll(quarters.subList(startDateQuarter - 1, 4)); + for (int i = (initialYear + 1); i < endYear; i++) { + result.addAll(quarters); + } + int quarterEndDate = calculateInQuarterPeriodDateInYear(endDate, + endYear); + result.addAll(quarters.subList(0, quarterEndDate)); + return result; + } + + /** + * + * @param date + * @param year + * @return a number from 1(quarter until to 1st April) to 4(quarter until + * 1st January of the next year) showing the quarter in which the + * date is for the year + */ + private int calculateInQuarterPeriodDateInYear(Date date, int year) { + Date[] quarters = createQuartersForYear(year); + for (int i = 0; i < quarters.length; i++) { + if (date.before(quarters[i])) { + return i + 1; + } + } + throw new IllegalArgumentException("date " + date + " is not in year " + + year); + } + + private static Date[] createQuartersForYear(int year) { + Date yearDate = year(year); + Date[] result = new Date[4]; + for (int i = 0; i < result.length; i++) { + result[i] = quarterAt(i + 1, yearDate); + } + return result; + } + + @Override + protected Collection createDetailsForFirstLevel( + Interval interval) { + int[] pairYears = calculateInitialEndYear(interval.getStart(), interval + .getFinish()); + return buildCollectionDetailsFirstLevel(interval.getStart(), interval + .getFinish(), pairYears[0], pairYears[1]); + + } + + @Override + protected Collection createDetailsForSecondLevel( + Interval interval) { + int[] pairYears = calculateInitialEndYear(interval.getStart(), interval + .getFinish()); + return buildCollectionDetailsSecondLevel(interval.getStart(), interval + .getFinish(), pairYears[0], pairYears[1]); + + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/TimeTrackerState.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/TimeTrackerState.java new file mode 100644 index 000000000..fcea43002 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/TimeTrackerState.java @@ -0,0 +1,146 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz.util.zoom; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; + +import org.zkoss.ganttz.util.Interval; + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public abstract class TimeTrackerState { + + protected static final long MILLSECONDS_IN_DAY = 1000 * 60 * 60 * 24; + protected static final int NUMBER_OF_ITEMS_MINIMUM = 10; + + /** + * This class is conceived as an immutable class. + * + * @author Francisco Javier Moran Rúa + * + */ + public final static class DetailItem { + + private int size; + private String name; + + private final boolean even; + + public DetailItem(int size, String name) { + this(size, name, false); + } + + public DetailItem(int size, String name, boolean even) { + this.size = size; + this.name = name; + this.even = even; + } + + /** + * @return the size + */ + public int getSize() { + return size; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + public DetailItem markEven(boolean even) { + return new DetailItem(size, name, even); + } + + public boolean isEven() { + return even; + } + + } + + public Collection getFirstLevelDetails(Interval interval) { + return markEvens(createDetailsForFirstLevel(interval)); + } + + private static List markEvens( + Collection items) { + boolean even = false; + ArrayList result = new ArrayList(); + for (DetailItem detailItem : items) { + result.add(detailItem.markEven(even)); + even = !even; + } + return result; + + } + + protected abstract Collection createDetailsForFirstLevel( + Interval interval); + + protected abstract Collection createDetailsForSecondLevel( + Interval interval); + + public Collection getSecondLevelDetails(Interval interval) { + return markEvens(createDetailsForSecondLevel(interval)); + } + + protected static int[] calculateInitialEndYear(Date initialDate, + Date endDate) { + + int[] pairYears = new int[2]; + + long yearsInBetween = calculateYearsBetween(initialDate, endDate); + Calendar cal = new GregorianCalendar(); + cal.setTime(initialDate); + int initialYear = cal.get(Calendar.YEAR); + int endYear; + + if (yearsInBetween >= NUMBER_OF_ITEMS_MINIMUM) { + cal.setTime(endDate); + endYear = cal.get(Calendar.YEAR); + } else { + endYear = initialYear + NUMBER_OF_ITEMS_MINIMUM; + } + + pairYears[0] = initialYear; + pairYears[1] = endYear; + + return pairYears; + } + + protected static long calculateYearsBetween(Date initialDate, Date endDate) { + + System.out.println("Initial date:" + initialDate); + System.out.println("End date:" + endDate); + long milsecondsDiff = endDate.getTime() - initialDate.getTime(); + + // To chech later: If you put MILLSECONDS_IN_YEAR the + // division is made wrongly. + + long days = milsecondsDiff / MILLSECONDS_IN_DAY; + return (days / 365); + } + + public static Date year(int year) { + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(Calendar.YEAR, year); + return calendar.getTime(); + } + + public abstract Interval getRealIntervalFor(Interval testInterval); + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevel.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevel.java new file mode 100644 index 000000000..b3f9202b8 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevel.java @@ -0,0 +1,58 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.zkoss.ganttz.util.zoom; + + +/** + * + * @author Francisco Javier Moran Rúa + * + */ +public enum ZoomLevel { + + DETAIL_ONE(DetailOneTimeTrackerState.INSTANCE), DETAIL_TWO( + DetailTwoTimeTrackerState.INSTANCE), DETAIL_THREE( + DetailTwoTimeTrackerState.INSTANCE), DETAIL_FOUR( + DetailTwoTimeTrackerState.INSTANCE); + + private final TimeTrackerState state; + + private ZoomLevel(TimeTrackerState state) { + if (state == null) + throw new IllegalArgumentException("state cannot be null"); + this.state = state; + } + + /** + * + * @return if there is no next, returns this. Otherwise returns + * the next one. + */ + public ZoomLevel next() { + final int next = ordinal() + 1; + if (next == ZoomLevel.values().length) { + return this; + } + return ZoomLevel.values()[next]; + } + + /** + * + * @return if there is no previous, returns this. Otherwise + * returns the previous one. + */ + public ZoomLevel previous() { + if (ordinal() == 0) { + return this; + } + return ZoomLevel.values()[ordinal() - 1]; + } + + public TimeTrackerState getTimeTrackerState() { + return state; + } + +} diff --git a/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevelChangedListener.java b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevelChangedListener.java new file mode 100644 index 000000000..dbf9efdf9 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/util/zoom/ZoomLevelChangedListener.java @@ -0,0 +1,8 @@ +package org.zkoss.ganttz.util.zoom; + + +public interface ZoomLevelChangedListener { + + public void zoomLevelChanged(ZoomLevel detailLevel); + +} diff --git a/navalplanner-gantt-zk/src/main/resources/metainfo/zk/lang-addon.xml b/navalplanner-gantt-zk/src/main/resources/metainfo/zk/lang-addon.xml new file mode 100755 index 000000000..221fc24a9 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/metainfo/zk/lang-addon.xml @@ -0,0 +1,90 @@ + + + + ganttz + xul/html + + zul + xul + + + import org.zkoss.ganttz.*; + + + + planner + org.zkoss.ganttz.Planner + + default + ~./ganttz/planner.dsp + + + + + listdetails + org.zkoss.ganttz.ListDetails + ~./ganttz/zul/listdetails.zul + + + + taskdetail + org.zkoss.ganttz.TaskDetail + ~./ganttz/zul/taskdetail.zul + + + + ganttpanel + org.zkoss.ganttz.GanttPanel + + default + ~./ganttz/ganttpanel.dsp + + + + + task + org.zkoss.ganttz.Task + + default + ~./ganttz/task.dsp + + + + + tasklist + org.zkoss.ganttz.TaskList + + default + ~./ganttz/tasklist.dsp + + + + + dependencylist + org.zkoss.ganttz.DependencyList + + default + ~./ganttz/dependencylist.dsp + + + + + dependency + org.zkoss.ganttz.Dependency + + default + ~./ganttz/dependency.dsp + + + + + timetracker + org.zkoss.ganttz.TimeTracker + ~./ganttz/zul/timetracker.zul + + detailLevel + 1 + + + + diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/css/task.css.dsp b/navalplanner-gantt-zk/src/main/resources/web/ganttz/css/task.css.dsp new file mode 100755 index 000000000..cfe4eafa7 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/css/task.css.dsp @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/dependency.dsp b/navalplanner-gantt-zk/src/main/resources/web/ganttz/dependency.dsp new file mode 100644 index 000000000..2bf6b78c5 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/dependency.dsp @@ -0,0 +1,7 @@ +<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> +<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %> + + + +
+
diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/dependencylist.dsp b/navalplanner-gantt-zk/src/main/resources/web/ganttz/dependencylist.dsp new file mode 100644 index 000000000..93adbb27c --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/dependencylist.dsp @@ -0,0 +1,14 @@ +<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> +<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %> + + + +
+ +
+ + ${z:redraw(child, null)} + +
+ +
\ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/ganttpanel.dsp b/navalplanner-gantt-zk/src/main/resources/web/ganttz/ganttpanel.dsp new file mode 100644 index 000000000..e265ce403 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/ganttpanel.dsp @@ -0,0 +1,16 @@ +<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> +<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %> + + + + +
+ + +
+ + ${z:redraw(child, null)} + +
+ +
\ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow.png b/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..b86b8af58f2c5547c1991789f25c3d908cfb1c7c GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VN3FMcVYMs zf(!O8p9~b?EbxddW?hT|;+&tG zo0?a`;9QiNSdyBeP@Y+mq2TW68xY>eCk|9(>gnPbB5^r6AtB+%c?XsRm*5i~YnD!1*(7cT>p1`oVDNPHb6Mw<&;$U- Cd_IQ& literal 0 HcmV?d00001 diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow2.png b/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow2.png new file mode 100644 index 0000000000000000000000000000000000000000..1d7952aacb9feef4000f7968e292b6cacb8e06ed GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VN3FMcVYMs zf(!O8p9~b?EbxddW?DglKgOI#yLobz*Y zQ}ap~oQqNuOHxx5$}>wc6x=<11Hv2m#DR*8JzX3_BrYeXBqaPe@4%AKa*0LM*ie97 zV&(;(WhY$RN?LoS%sA2Et*C6NW+WsmBk_=nLGd}W=rNV(??CMgp00i_>zopr05Tjp ArvLx| literal 0 HcmV?d00001 diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow3.png b/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/arrow3.png new file mode 100644 index 0000000000000000000000000000000000000000..d4687eb09a171bafab4fe272cf0fe0d7e59dc619 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VN3FMcVYMs zf(!O8p9~b?EbxddW? to-`nXZAQlzo|j>h-$=40f4F%}28J29*~C-V}>VN3FMcVYMs zf(!O8p9~b?EbxddW?|NsC0A^s6Va%Ew3Wn>_CX>@2HM@dak04x9i000310RR9610WCr06TV4 BO9KD^ literal 0 HcmV?d00001 diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/zoom_in.gif b/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/zoom_in.gif new file mode 100644 index 0000000000000000000000000000000000000000..6ee0b3507d078aa532de139b78b83d43ae311b36 GIT binary patch literal 186 zcmV;r07d^tNk%w1VGjTg0OJn;^!4?#w6?vzz}wv2$H>b0`ugDE;^pS%(bCiJ@bJvd z&g$#y!o$VZ*VwMFvj6}8A^s6Va%Ew3Wn>_CX>@2HM@dak04x9i000jF4*&oJTJXt9 z>vPzN##we!W>iOjh0gE*3PU8%abCewm^APR;Ke+H6Grhs^c;vsqw;GUe+FUdp*T8p oRi6!aaS$Yn#z9iGIv6?%&x{c;h>ykW^BEP{bV=c7=}RI2I~>eh@Bjb+ literal 0 HcmV?d00001 diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/zoom_out.gif b/navalplanner-gantt-zk/src/main/resources/web/ganttz/img/zoom_out.gif new file mode 100644 index 0000000000000000000000000000000000000000..1d11d109be6f15e6be39a2697cc2b770db1b7664 GIT binary patch literal 186 zcmV;r07d^tNk%w1VGjTg0OJn;^!4?#w6?vzz}wv2$H>b0`ugDE;^pS%(bCiJ@bJvd z&g$#y!o$VZ*VwMFvj6}8A^s6Va%Ew3Wn>_CX>@2HM@dak04x9i000jF4*&oJTJXt9 z>vPzN##we!W>iOjh0gE*3PU8%abCewm{ +<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %> + + + +
+ + ${z:redraw(child, null)} + +
\ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/task.dsp b/navalplanner-gantt-zk/src/main/resources/web/ganttz/task.dsp new file mode 100755 index 000000000..563d983a6 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/task.dsp @@ -0,0 +1,11 @@ + +<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> +<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %> + + + +
+
+ ${self.taskName} +
+
\ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/tasklist.dsp b/navalplanner-gantt-zk/src/main/resources/web/ganttz/tasklist.dsp new file mode 100644 index 000000000..3ce404d62 --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/tasklist.dsp @@ -0,0 +1,43 @@ +<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> +<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + ${z:redraw(child, null)} + +
+ +
\ No newline at end of file diff --git a/navalplanner-gantt-zk/src/main/resources/web/ganttz/zul/listdetails.zul b/navalplanner-gantt-zk/src/main/resources/web/ganttz/zul/listdetails.zul new file mode 100644 index 000000000..2dd7272aa --- /dev/null +++ b/navalplanner-gantt-zk/src/main/resources/web/ganttz/zul/listdetails.zul @@ -0,0 +1,29 @@ + + + +