Initial commit
54
README.txt
Normal file
|
|
@ -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=<<spring-framework-2.5.6>>/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 <profiles> 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").
|
||||
38
navalplanner-business/pom.xml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.navalplanner</groupId>
|
||||
<artifactId>navalplanner</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>navalplanner-business</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Naval Planner Business Module</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${jdbcDriver.groupId}</groupId>
|
||||
<artifactId>${jdbcDriver.artifactId}</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -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 () {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package org.navalplanner.business.common.daos;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.navalplanner.business.common.exceptions.InstanceNotFoundException;
|
||||
|
||||
public interface IGenericDao <E, PK extends Serializable>{
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
|
@ -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<E, PK extends Serializable>
|
||||
implements IGenericDao<E, PK> {
|
||||
|
||||
private Class<E> entityClass;
|
||||
|
||||
@Autowired
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public GenericDaoHibernate() {
|
||||
this.entityClass = (Class<E>) ((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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<E, PK extends Serializable>
|
||||
implements IGenericDao<E, PK> {
|
||||
|
||||
private Class<E> entityClass;
|
||||
|
||||
private HibernateTemplate hibernateTemplate;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public GenericDaoHibernateTemplate() {
|
||||
this.entityClass = (Class<E>) ((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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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<Resource, Long> {}
|
||||
|
|
@ -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<ResourceGroup, Long> {}
|
||||
|
|
@ -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<Worker, Long> {}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Resource, Long>
|
||||
implements IResourceDao {}
|
||||
|
|
@ -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<ResourceGroup, Long>
|
||||
implements IResourceGroupDao {}
|
||||
|
|
@ -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<Worker, Long>
|
||||
implements IWorkerDao {}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Resource> resources = new HashSet<Resource>();
|
||||
|
||||
public Set<Resource> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public void setResources(Set<Resource> 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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE hibernate-configuration
|
||||
PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<property name="hibernate.dialect">${hibernate.dialect}</property>
|
||||
<property name="hibernate.show_sql">${hibernate.show_sql}</property>
|
||||
<property name="hibernate.format_sql">${hibernate.format_sql}</property>
|
||||
<property name="hibernate.use_sql_comments">${hibernate.use_sql_comments}</property>
|
||||
<property name="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</property>
|
||||
</session-factory>
|
||||
</hibernate-configuration>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
|
||||
|
||||
<!-- Data source. -->
|
||||
<bean id="dataSource"
|
||||
class="org.springframework.jndi.JndiObjectFactoryBean"
|
||||
p:jndiName="${dataSource.jndiName}"
|
||||
p:resourceRef="true" />
|
||||
|
||||
<!-- Hibernate Session Factory. -->
|
||||
<bean id="sessionFactory"
|
||||
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
|
||||
p:dataSource-ref="dataSource"
|
||||
p:configLocation="classpath:/navalplanner-business-hibernate.cfg.xml">
|
||||
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
<value>org/navalplanner/business/resources/entities/Resources.hbm.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
|
||||
</bean>
|
||||
|
||||
<!-- Spring Transaction manager -->
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
|
||||
p:sessionFactory-ref="sessionFactory"/>
|
||||
|
||||
<!-- Enable configuration of transactional behavior based on
|
||||
annotations -->
|
||||
<tx:annotation-driven transaction-manager="transactionManager"/>
|
||||
|
||||
<!-- For enabling annotation-based configuration (in particular, required
|
||||
for "@Autowired") -->
|
||||
<context:annotation-config/>
|
||||
|
||||
<!-- ======================== Business Objects ======================== -->
|
||||
|
||||
<!-- Resource DAOs and registry -->
|
||||
<bean id="resourceDao"
|
||||
class="org.navalplanner.business.resources.daos.impl.ResourceDaoHibernate"/>
|
||||
|
||||
<bean id="workerDao"
|
||||
class="org.navalplanner.business.resources.daos.impl.WorkerDaoHibernate"/>
|
||||
|
||||
<bean id="resourceGroupDao"
|
||||
class="org.navalplanner.business.resources.daos.impl.ResourceGroupDaoHibernate"/>
|
||||
|
||||
<bean id="resourcesDaoRegistry"
|
||||
class="org.navalplanner.business.resources.daos.ResourcesDaoRegistry"
|
||||
factory-method="getInstance"/>
|
||||
|
||||
<!-- Service layer -->
|
||||
<bean id="resourceService"
|
||||
class="org.navalplanner.business.resources.services.impl.ResourceServiceImpl"/>
|
||||
|
||||
</beans>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding='UTF-8'?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
|
||||
|
||||
<hibernate-mapping>
|
||||
|
||||
<class name="org.navalplanner.business.resources.entities.Resource">
|
||||
|
||||
<id name="id" access="field">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
|
||||
<!-- IMPORTANT: type="long" must be specified (otherwise, Hibernate
|
||||
infers type="integer". -->
|
||||
<version name="version" type="long" access="field"/>
|
||||
|
||||
<many-to-one name="resourceGroup"
|
||||
column="RESOURCE_GROUP_ID"
|
||||
class="org.navalplanner.business.resources.entities.ResourceGroup"/>
|
||||
|
||||
<joined-subclass
|
||||
name="org.navalplanner.business.resources.entities.Worker">
|
||||
|
||||
<key column="WORKER_ID"/>
|
||||
|
||||
<property name="firstName"/>
|
||||
<property name="surname"/>
|
||||
<property name="nif"/>
|
||||
<property name="dailyHours"/>
|
||||
|
||||
</joined-subclass>
|
||||
|
||||
<joined-subclass
|
||||
name="org.navalplanner.business.resources.entities.ResourceGroup">
|
||||
|
||||
<key column="RESOURCE_GROUP_ID"/>
|
||||
|
||||
<set name="resources" inverse="true" cascade="save-update">
|
||||
<key column="RESOURCE_GROUP_ID"/>
|
||||
<one-to-many
|
||||
class="org.navalplanner.business.resources.entities.Resource"/>
|
||||
</set>
|
||||
|
||||
</joined-subclass>
|
||||
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>
|
||||
|
|
@ -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 () {}
|
||||
|
||||
}
|
||||
|
|
@ -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()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE hibernate-configuration
|
||||
PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<property name="hibernate.dialect">${hibernate.dialect}</property>
|
||||
<property name="hibernate.show_sql">true</property>
|
||||
<property name="hibernate.format_sql">true</property>
|
||||
<property name="hibernate.use_sql_comments">true</property>
|
||||
<property name="hibernate.hbm2ddl.auto">update</property>
|
||||
</session-factory>
|
||||
</hibernate-configuration>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
|
||||
|
||||
<!-- Data source -->
|
||||
<bean id="dataSource"
|
||||
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
|
||||
p:driverClassName="${jdbcDriver.className}"
|
||||
p:url="${testDataSource.url}" p:username="${testDataSource.user}"
|
||||
p:password="${testDataSource.password}"/>
|
||||
|
||||
|
||||
<!-- Hibernate Session Factory. -->
|
||||
<bean id="sessionFactory"
|
||||
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
|
||||
p:dataSource-ref="dataSource"
|
||||
p:configLocation="classpath:/navalplanner-business-hibernate-test.cfg.xml">
|
||||
|
||||
<!-- FIXME: ideally this chunk of XML should be only in
|
||||
src/main/resources/navalplanner-business-spring-config.xml. -->
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
<value>org/navalplanner/business/resources/entities/Resources.hbm.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
36
navalplanner-gantt-zk/pom.xml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.navalplanner</groupId>
|
||||
<artifactId>navalplanner</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>navalplanner-gantt-zk</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Naval Planner ZK Components Module</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- ZK -->
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zul</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zkplus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zk</artifactId>
|
||||
</dependency>
|
||||
<!-- Commons Logging -->
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.zkoss.ganttz;
|
||||
|
||||
public interface DependencyAddedListener {
|
||||
|
||||
public void dependenceAdded(Dependency dependency);
|
||||
}
|
||||
|
|
@ -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<Dependency> getDependencies() {
|
||||
List<Object> 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<Dependency>() {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Object> children = getChildren();
|
||||
return Planner.findComponentsOfType(TimeTracker.class, children).get(0);
|
||||
}
|
||||
|
||||
public TaskList getTaskList() {
|
||||
List<Object> children = getChildren();
|
||||
return Planner.findComponentsOfType(TaskList.class, children).get(0);
|
||||
}
|
||||
|
||||
public DependencyList getDependencyList() {
|
||||
List<Object> children = getChildren();
|
||||
return Planner.findComponentsOfType(DependencyList.class, children)
|
||||
.get(0);
|
||||
}
|
||||
|
||||
public Planner getPlanner() {
|
||||
return (Planner) getParent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String, TaskBean> tasksById = new HashMap<String, TaskBean>();
|
||||
|
||||
public Planner() {
|
||||
}
|
||||
|
||||
private TaskList getTaskList() {
|
||||
List<Object> children = findOneComponentOfType(GanttPanel.class)
|
||||
.getChildren();
|
||||
return Planner.findComponentsOfType(TaskList.class, children).get(0);
|
||||
}
|
||||
|
||||
private <T> T findOneComponentOfType(Class<T> type) {
|
||||
List<T> 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 <T> List<T> findComponentsOfType(Class<T> type,
|
||||
List<? extends Object> children) {
|
||||
ArrayList<T> result = new ArrayList<T>();
|
||||
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<Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
298
navalplanner-gantt-zk/src/main/java/org/zkoss/ganttz/Task.java
Executable file
|
|
@ -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<WeakReference<DependencyAddedListener>> dependencyListeners = new LinkedList<WeakReference<DependencyAddedListener>>();
|
||||
|
||||
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<DependencyAddedListener>(
|
||||
listener));
|
||||
}
|
||||
|
||||
private void fireDependenceAdded(Dependency dependency) {
|
||||
ArrayList<DependencyAddedListener> active = new ArrayList<DependencyAddedListener>();
|
||||
synchronized (this) {
|
||||
ListIterator<WeakReference<DependencyAddedListener>> iterator = dependencyListeners
|
||||
.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
WeakReference<DependencyAddedListener> 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<String> set;
|
||||
|
||||
private LengthType(int seconds, String... sufixes) {
|
||||
milliseconds = seconds * 1000;
|
||||
set = new HashSet<String>(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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<WeakReference<DependencyAddedListener>> listeners = new LinkedList<WeakReference<DependencyAddedListener>>();
|
||||
|
||||
private ZoomLevelChangedListener zoomLevelChangedListener;
|
||||
|
||||
private final WeakReferencedListeners<TaskRemovedListener> 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<WeakReference<DependencyAddedListener>> 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<TaskRemovedListener>() {
|
||||
@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<Task> getTasks() {
|
||||
ArrayList<Task> result = new ArrayList<Task>();
|
||||
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<DependencyAddedListener>(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<Task>() {
|
||||
|
||||
@Override
|
||||
public void onEvent(Task choosen, Event event) {
|
||||
choosen.addDependency();
|
||||
}
|
||||
}).item("Erase", new ItemAction<Task>() {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.zkoss.ganttz;
|
||||
|
||||
public interface TaskRemovedListener {
|
||||
|
||||
public void taskRemoved(Task taskRemoved);
|
||||
|
||||
}
|
||||
|
|
@ -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<WeakReference<ZoomLevelChangedListener>> zoomListeners = new LinkedList<WeakReference<ZoomLevelChangedListener>>();
|
||||
|
||||
private DatesMapper datesMapper = null;
|
||||
|
||||
private Collection<DetailItem> detailsFirstLevelCached = null;
|
||||
|
||||
private Collection<DetailItem> detailsSecondLevelCached = null;
|
||||
|
||||
public void addZoomListener(ZoomLevelChangedListener listener) {
|
||||
zoomListeners
|
||||
.add(new WeakReference<ZoomLevelChangedListener>(listener));
|
||||
}
|
||||
|
||||
private void fireZoomChanged(ZoomLevel detailLevel) {
|
||||
ListIterator<WeakReference<ZoomLevelChangedListener>> listIterator = zoomListeners
|
||||
.listIterator();
|
||||
while (listIterator.hasNext()) {
|
||||
ZoomLevelChangedListener listener = listIterator.next().get();
|
||||
if (listener == null) {
|
||||
listIterator.remove();
|
||||
} else {
|
||||
listener.zoomLevelChanged(detailLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<TimeTrackerState.DetailItem> getDetailsFirstLevel() {
|
||||
if (detailsFirstLevelCached == null) {
|
||||
detailsFirstLevelCached = getTimeTrackerState()
|
||||
.getFirstLevelDetails(getTestInterval());
|
||||
}
|
||||
return detailsFirstLevelCached;
|
||||
}
|
||||
|
||||
public Interval getRealInterval() {
|
||||
return getTimeTrackerState().getRealIntervalFor(getTestInterval());
|
||||
}
|
||||
|
||||
public Collection<TimeTrackerState.DetailItem> 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<DetailItem> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T extends XulElement> {
|
||||
|
||||
public static <T extends XulElement> MenuBuilder<T> on(Page page,
|
||||
Collection<T> elements) {
|
||||
return new MenuBuilder<T>(page, elements);
|
||||
}
|
||||
|
||||
public static <T extends XulElement> MenuBuilder<T> on(Page page,
|
||||
T... elements) {
|
||||
return on(page, Arrays.asList(elements));
|
||||
}
|
||||
|
||||
public static interface ItemAction<T> {
|
||||
|
||||
void onEvent(T choosen, Event event);
|
||||
}
|
||||
|
||||
private class Item {
|
||||
final String name;
|
||||
|
||||
final ItemAction<T> action;
|
||||
|
||||
Item(String name, ItemAction<T> action) {
|
||||
this.name = name;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
Menuitem createMenuItem() {
|
||||
Menuitem result = new Menuitem();
|
||||
result.setLabel(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final List<T> elements;
|
||||
|
||||
private final List<Item> items = new ArrayList<Item>();
|
||||
|
||||
private Component root;
|
||||
|
||||
private MenuBuilder(Page page, Collection<? extends T> elements) {
|
||||
this.elements = new ArrayList<T>(elements);
|
||||
this.root = page.getLastRoot();
|
||||
}
|
||||
|
||||
public MenuBuilder<T> item(String name, ItemAction<T> 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<T> action = item.action;
|
||||
action.onEvent(referenced, event);
|
||||
}
|
||||
});
|
||||
result.appendChild(menuItem);
|
||||
}
|
||||
root.appendChild(result);
|
||||
if (setContext) {
|
||||
for (T element : elements) {
|
||||
element.setContext(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<T> {
|
||||
|
||||
public interface ListenerNotification<T> {
|
||||
|
||||
void doNotify(T listener);
|
||||
|
||||
}
|
||||
|
||||
public static <T> WeakReferencedListeners<T> create() {
|
||||
return new WeakReferencedListeners<T>();
|
||||
}
|
||||
|
||||
private LinkedList<WeakReference<T>> listeners = new LinkedList<WeakReference<T>>();
|
||||
|
||||
private WeakReferencedListeners() {
|
||||
|
||||
}
|
||||
|
||||
public synchronized void addListener(T listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be null");
|
||||
listeners.add(new WeakReference<T>(listener));
|
||||
}
|
||||
|
||||
public synchronized void fireEvent(
|
||||
ListenerNotification<? super T> notification) {
|
||||
ListIterator<WeakReference<T>> listIterator = listeners.listIterator();
|
||||
ArrayList<T> active = new ArrayList<T>();
|
||||
while (listIterator.hasNext()) {
|
||||
T listener = listIterator.next().get();
|
||||
if (listener == null)
|
||||
listIterator.remove();
|
||||
else {
|
||||
active.add(listener);
|
||||
}
|
||||
}
|
||||
for (T listener : active) {
|
||||
notification.doNotify(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DetailItem> buildCollectionDetailsFirstLevel(
|
||||
int initialYear, int endYear) {
|
||||
|
||||
Collection<DetailItem> detailsVector = new Vector<DetailItem>();
|
||||
|
||||
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<DetailItem> buildCollectionDetailsSecondLevel(
|
||||
int initialYear, int endYear) {
|
||||
|
||||
Collection<DetailItem> detailsVector = new Vector<DetailItem>();
|
||||
|
||||
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<DetailItem> createDetailsForFirstLevel(
|
||||
Interval interval) {
|
||||
int[] pairYears = calculateInitialEndYear(interval.getStart(), interval
|
||||
.getFinish());
|
||||
return buildCollectionDetailsFirstLevel(pairYears[0], pairYears[1]);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<DetailItem> 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<DetailItem> buildCollectionDetailsFirstLevel(
|
||||
Date initialDate, Date endDate, int initialYear, int endYear) {
|
||||
|
||||
Collection<DetailItem> detailsVector = new Vector<DetailItem>();
|
||||
|
||||
// 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<DetailItem> buildCollectionDetailsSecondLevel(
|
||||
Date initialDate, Date endDate, int initialYear, int endYear) {
|
||||
ArrayList<DetailItem> result = new ArrayList<DetailItem>();
|
||||
int startDateQuarter = calculateInQuarterPeriodDateInYear(initialDate,
|
||||
initialYear);
|
||||
ArrayList<DetailItem> quarters = new ArrayList<DetailItem>();
|
||||
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<DetailItem> createDetailsForFirstLevel(
|
||||
Interval interval) {
|
||||
int[] pairYears = calculateInitialEndYear(interval.getStart(), interval
|
||||
.getFinish());
|
||||
return buildCollectionDetailsFirstLevel(interval.getStart(), interval
|
||||
.getFinish(), pairYears[0], pairYears[1]);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<DetailItem> createDetailsForSecondLevel(
|
||||
Interval interval) {
|
||||
int[] pairYears = calculateInitialEndYear(interval.getStart(), interval
|
||||
.getFinish());
|
||||
return buildCollectionDetailsSecondLevel(interval.getStart(), interval
|
||||
.getFinish(), pairYears[0], pairYears[1]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<DetailItem> getFirstLevelDetails(Interval interval) {
|
||||
return markEvens(createDetailsForFirstLevel(interval));
|
||||
}
|
||||
|
||||
private static List<DetailItem> markEvens(
|
||||
Collection<? extends DetailItem> items) {
|
||||
boolean even = false;
|
||||
ArrayList<DetailItem> result = new ArrayList<DetailItem>();
|
||||
for (DetailItem detailItem : items) {
|
||||
result.add(detailItem.markEven(even));
|
||||
even = !even;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
protected abstract Collection<DetailItem> createDetailsForFirstLevel(
|
||||
Interval interval);
|
||||
|
||||
protected abstract Collection<DetailItem> createDetailsForSecondLevel(
|
||||
Interval interval);
|
||||
|
||||
public Collection<DetailItem> 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);
|
||||
|
||||
}
|
||||
|
|
@ -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 <code>this</code>. 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 <code>this</code>. Otherwise
|
||||
* returns the previous one.
|
||||
*/
|
||||
public ZoomLevel previous() {
|
||||
if (ordinal() == 0) {
|
||||
return this;
|
||||
}
|
||||
return ZoomLevel.values()[ordinal() - 1];
|
||||
}
|
||||
|
||||
public TimeTrackerState getTimeTrackerState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.zkoss.ganttz.util.zoom;
|
||||
|
||||
|
||||
public interface ZoomLevelChangedListener {
|
||||
|
||||
public void zoomLevelChanged(ZoomLevel detailLevel);
|
||||
|
||||
}
|
||||
90
navalplanner-gantt-zk/src/main/resources/metainfo/zk/lang-addon.xml
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<language-addon>
|
||||
<addon-name>ganttz</addon-name>
|
||||
<language-name>xul/html</language-name>
|
||||
|
||||
<extension>zul</extension>
|
||||
<extension>xul</extension>
|
||||
|
||||
<zscript>
|
||||
import org.zkoss.ganttz.*;
|
||||
</zscript>
|
||||
|
||||
<component>
|
||||
<component-name>planner</component-name>
|
||||
<component-class>org.zkoss.ganttz.Planner</component-class>
|
||||
<mold>
|
||||
<mold-name>default</mold-name>
|
||||
<mold-uri>~./ganttz/planner.dsp</mold-uri>
|
||||
</mold>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>listdetails</component-name>
|
||||
<component-class>org.zkoss.ganttz.ListDetails</component-class>
|
||||
<macro-uri>~./ganttz/zul/listdetails.zul</macro-uri>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>taskdetail</component-name>
|
||||
<component-class>org.zkoss.ganttz.TaskDetail</component-class>
|
||||
<macro-uri>~./ganttz/zul/taskdetail.zul</macro-uri>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>ganttpanel</component-name>
|
||||
<component-class>org.zkoss.ganttz.GanttPanel</component-class>
|
||||
<mold>
|
||||
<mold-name>default</mold-name>
|
||||
<mold-uri>~./ganttz/ganttpanel.dsp</mold-uri>
|
||||
</mold>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>task</component-name>
|
||||
<component-class>org.zkoss.ganttz.Task</component-class>
|
||||
<mold>
|
||||
<mold-name>default</mold-name>
|
||||
<mold-uri>~./ganttz/task.dsp</mold-uri>
|
||||
</mold>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>tasklist</component-name>
|
||||
<component-class>org.zkoss.ganttz.TaskList</component-class>
|
||||
<mold>
|
||||
<mold-name>default</mold-name>
|
||||
<mold-uri>~./ganttz/tasklist.dsp</mold-uri>
|
||||
</mold>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>dependencylist</component-name>
|
||||
<component-class>org.zkoss.ganttz.DependencyList</component-class>
|
||||
<mold>
|
||||
<mold-name>default</mold-name>
|
||||
<mold-uri>~./ganttz/dependencylist.dsp</mold-uri>
|
||||
</mold>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>dependency</component-name>
|
||||
<component-class>org.zkoss.ganttz.Dependency</component-class>
|
||||
<mold>
|
||||
<mold-name>default</mold-name>
|
||||
<mold-uri>~./ganttz/dependency.dsp</mold-uri>
|
||||
</mold>
|
||||
</component>
|
||||
|
||||
<component>
|
||||
<component-name>timetracker</component-name>
|
||||
<component-class>org.zkoss.ganttz.TimeTracker</component-class>
|
||||
<macro-uri>~./ganttz/zul/timetracker.zul</macro-uri>
|
||||
<property>
|
||||
<property-name>detailLevel</property-name>
|
||||
<property-value>1</property-value>
|
||||
</property>
|
||||
</component>
|
||||
|
||||
</language-addon>
|
||||
7
navalplanner-gantt-zk/src/main/resources/web/ganttz/css/task.css.dsp
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
<style>
|
||||
.z-taskassigment-normal {
|
||||
cursos: move;
|
||||
border: 2px solid #538BA2;
|
||||
background-color: #BBBBBB;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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" %>
|
||||
|
||||
<c:set var="self" value="${requestScope.arg.self}"/>
|
||||
|
||||
<div class="dependency" id="${self.uuid}" z.type="ganttz.dependency.Dependency" idTaskOrig="${self.idTaskOrig}" idTaskEnd="${self.idTaskEnd}" ${self.outerAttrs}">
|
||||
</div>
|
||||
|
|
@ -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" %>
|
||||
|
||||
<c:set var="self" value="${requestScope.arg.self}"/>
|
||||
|
||||
<div id="${self.uuid}" z.type="ganttz.dependencylist.Dependencylist" z.autoz="true"${self.outerAttrs}">
|
||||
|
||||
<div id="listdependencies">
|
||||
<c:forEach var="child" items="${self.children}">
|
||||
${z:redraw(child, null)}
|
||||
</c:forEach>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -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" %>
|
||||
|
||||
|
||||
<c:set var="self" value="${requestScope.arg.self}"/>
|
||||
|
||||
<div id="ganttpanel">
|
||||
|
||||
|
||||
<div id="${self.uuid}" z.type="ganttz.ganttpanel.GanttPanel" ${self.outerAttrs}>
|
||||
<c:forEach var="child" items="${self.children}">
|
||||
${z:redraw(child, null)}
|
||||
</c:forEach>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 216 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 147 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 186 B |
|
|
@ -0,0 +1,10 @@
|
|||
<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %>
|
||||
<%@ taglib uri="http://www.zkoss.org/dsp/zk/core" prefix="z" %>
|
||||
|
||||
<c:set var="self" value="${requestScope.arg.self}"/>
|
||||
|
||||
<div id="${self.uuid}" z.type="ganttz.planner.Planner" ${self.outerAttrs}">
|
||||
<c:forEach var="child" items="${self.children}">
|
||||
${z:redraw(child, null)}
|
||||
</c:forEach>
|
||||
</div>
|
||||
11
navalplanner-gantt-zk/src/main/resources/web/ganttz/task.dsp
Executable file
|
|
@ -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" %>
|
||||
|
||||
<c:set var="self" value="${requestScope.arg.self}"/>
|
||||
|
||||
<div id="row${self.uuid}" class="row" z.valor="boxid="${self.uuid}">
|
||||
<div id="${self.uuid}" z.type="ganttz.task.Task" idTask="${self.id}" z.autoz="true"${self.outerAttrs}">
|
||||
<span id="${self.uuid}!real">${self.taskName}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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" %>
|
||||
|
||||
<script type="text/javascript" src="http://yui.yahooapis.com/2.7.0/build/yahoo-dom-event/yahoo-dom-event.js" ></script>
|
||||
|
||||
<script type="text/javascript" src="http://yui.yahooapis.com/2.7.0/build/selector/selector-min.js"></script>
|
||||
|
||||
<!-- Drag and Drop source file -->
|
||||
<script type="text/javascript" src="http://yui.yahooapis.com/2.7.0/build/dragdrop/dragdrop-min.js" ></script>
|
||||
|
||||
<!-- Combo-handled YUI JS files: -->
|
||||
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&2.7.0/build/dragdrop/dragdrop-min.js"></script>
|
||||
|
||||
<!-- Combo-handled YUI CSS files: -->
|
||||
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.7.0/build/resize/assets/skins/sam/resize.css">
|
||||
|
||||
<c:include page="~./ganttz/css/task.css.dsp"/>
|
||||
|
||||
<!-- Combo-handled YUI JS files: -->
|
||||
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&2.7.0/build/element/element-min.js&2.7.0/build/dragdrop/dragdrop-min.js&2.7.0/build/resize/resize-min.js"></script>
|
||||
<!-- Source file -->
|
||||
|
||||
<script type="text/javascript" src="http://yui.yahooapis.com/2.7.0/build/logger/logger-min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.body.class = "yui-skin-sam";
|
||||
var myContainer = document.body.appendChild(document.createElement("div"));
|
||||
var myLogReader = new YAHOO.widget.LogReader(myContainer);
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<c:set var="self" value="${requestScope.arg.self}"/>
|
||||
|
||||
<div id="${self.uuid}" z.type="ganttz.tasklist.Tasklist" z.autoz="true"${self.outerAttrs}" sameHeightElementId="${self.sameHeightElementId}">
|
||||
|
||||
<div id="listtasks">
|
||||
<c:forEach var="child" items="${self.children}">
|
||||
${z:redraw(child, null)}
|
||||
</c:forEach>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
|
||||
<zk>
|
||||
<vbox class="listdetails" id="insertionPoint">
|
||||
<button label="${c:l('listdetails.add_task')}"
|
||||
onClick="insertionPoint.getParent().addTask();" />
|
||||
<taskdetail taskId="task1" start="01/06/2009" length="365 days"
|
||||
taskName="Tarefa1"
|
||||
color="blue"/>
|
||||
<taskdetail taskId="task2" start="01/01/2011" length="365 days"
|
||||
taskName="Tarefa2"
|
||||
color="red"/>
|
||||
<taskdetail taskId="task3" start="01/01/2013" length="365 days"
|
||||
taskName="Tarefa3"
|
||||
color="yellow"/>
|
||||
|
||||
<taskdetail taskId="task4" start="01/01/2013" length="365 days"
|
||||
taskName="Tarefa4"
|
||||
color="yellow"/>
|
||||
|
||||
<!--taskdetail taskId="task5" start="01/01/2010" length="100 days"
|
||||
taskName="Tarefa5"
|
||||
color="orange"/>
|
||||
|
||||
<taskdetail taskId="task6" start="01/01/2010" length="100 days"
|
||||
taskName="Tarefa6"
|
||||
color="white"/-->
|
||||
|
||||
</vbox>
|
||||
</zk>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<zk>
|
||||
<zscript><![CDATA[
|
||||
top = self;
|
||||
]]>
|
||||
</zscript>
|
||||
<hbox>
|
||||
<textbox id="nameBox" value="" onChange="top.updateBean();" />
|
||||
<datebox id="startDateBox" compact="true" onChange="top.updateBean();"/>
|
||||
<datebox id="endDateBox" compact="true" onChange="top.updateBean();" />
|
||||
</hbox>
|
||||
|
||||
<zscript><![CDATA[
|
||||
top.nameBox = nameBox;
|
||||
top.startDateBox = startDateBox;
|
||||
top.endDateBox = endDateBox;
|
||||
]]>
|
||||
</zscript>
|
||||
</zk>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
|
||||
<zk>
|
||||
|
||||
<zscript><![CDATA[
|
||||
top = self;
|
||||
]]>
|
||||
</zscript>
|
||||
|
||||
<style dynamic="true">
|
||||
tr.z-vbox-sep {
|
||||
height: 0px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<vbox>
|
||||
<hbox>
|
||||
<button image="zkau/web/ganttz/img/zoom_in.gif" forward="onIncrease"/>
|
||||
<button image="zkau/web/ganttz/img/zoom_out.gif" forward="onDecrease"/>
|
||||
<label value="Detail level:"/>
|
||||
<label id="mcdetaillevel" value="${c:l(arg.detailLevel)}"/>
|
||||
</hbox>
|
||||
|
||||
<div>
|
||||
<grid>
|
||||
<columns>
|
||||
<column label="${each.name}" width="${each.size}px" forEach="${top.detailsFirstLevel}"></column>
|
||||
</columns>
|
||||
|
||||
</grid>
|
||||
<grid style="width: ${top.horizontalSize+2}px;">
|
||||
<columns>
|
||||
<column label="${each.name}" class="timetracker_column${(each.even?'_even':'')}" width="${each.size}px" forEach="${top.detailsSecondLevel}"></column>
|
||||
</columns>
|
||||
|
||||
<rows>
|
||||
<row id="fake_row" style="height: 100px;">
|
||||
<div class="fake_column timetracker_column${(each.even?'_even':'')}" style="height: 100px;" forEach="${top.detailsSecondLevel}" />
|
||||
</row>
|
||||
</rows>
|
||||
|
||||
</grid>
|
||||
</div>
|
||||
|
||||
<zscript><![CDATA[
|
||||
top.fakeRow = fake_row;
|
||||
]]>
|
||||
</zscript>
|
||||
|
||||
</vbox>
|
||||
</zk>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
zkDependency = {};
|
||||
|
||||
zkDependency.origin = function(dependency) {
|
||||
return document.getElementById(dependency.getAttribute("idTaskOrig"));
|
||||
}
|
||||
|
||||
zkDependency.destination = function(dependency) {
|
||||
return document.getElementById(dependency.getAttribute("idTaskEnd"));
|
||||
}
|
||||
|
||||
zkDependency.draw = function(dependency) {
|
||||
var orig = zkPlanner.findPos(this.origin(dependency));
|
||||
var dest = zkPlanner.findPos(this.destination(dependency));
|
||||
|
||||
// This corner case may depend on dependence type
|
||||
orig[0] = orig[0] + Math.max( 0,
|
||||
this.origin(dependency).offsetWidth - zkTask.CORNER_WIDTH);
|
||||
orig[1] = orig[1] + zkTask.HEIGHT;
|
||||
dest[1] = dest[1] + zkTask.HALF_HEIGHT;
|
||||
zkPlanner.drawArrow(dependency, orig, dest);
|
||||
|
||||
}
|
||||
|
||||
zkDependency.init = function(dependency) {
|
||||
zkPlanner.setupArrow(dependency);
|
||||
YAHOO.util.Event.onDOMReady(function() {
|
||||
zkDependency.draw(dependency);
|
||||
});
|
||||
|
||||
YAHOO.util.Event.onDOMReady(function() {
|
||||
boxes = [ zkTask.getDD(zkDependency.origin(dependency)), zkTask.getDD(zkDependency.destination(dependency))];
|
||||
for(var i = 0; i < boxes.length; i++){
|
||||
boxes[i].on('dragEvent',function(ev){
|
||||
zkDependency.draw(dependency);
|
||||
}, null, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
zkDependencylist = {};
|
||||
|
||||
zkDependencylist.init = function (cmp) {
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
zkGanttPanel = {};
|
||||
|
||||
zkGanttPanel.init = function(cmp){
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
zkPlanner = {};
|
||||
|
||||
zkPlanner.init = function(planner){
|
||||
|
||||
}
|
||||
|
||||
zkPlanner.findImageElement = function(arrow, name) {
|
||||
var children = arrow.getElementsByTagName("img");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
if (child.getAttribute("class").indexOf(name) != -1) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function get_origin() {
|
||||
return YAHOO.util.Dom.getXY('listdependencies');
|
||||
}
|
||||
|
||||
zkPlanner.findPos = function(obj) {
|
||||
var pos1 = get_origin();
|
||||
var pos2 = YAHOO.util.Dom.getXY(obj.id);
|
||||
return [ pos2[0] - pos1[0], pos2[1] - pos1[1] ];
|
||||
}
|
||||
zkPlanner.findPosForMouseCoordinates = function(x, y){
|
||||
/* var pos1 = get_origin() */
|
||||
var pos1 = YAHOO.util.Dom.getXY('listtasks');
|
||||
return [x - pos1[0], y - pos1[1]];
|
||||
}
|
||||
|
||||
|
||||
zkPlanner.setupArrow = function(arrowDiv){
|
||||
var base_url_images = "zkau/web/ganttz/img/";
|
||||
var image_data = [ [ "start", "pixel.gif" ], [ "mid", "pixel.gif" ],
|
||||
[ "end", "pixel.gif" ], [ "arrow", "arrow.png" ] ];
|
||||
for ( var i = 0; i < image_data.length; i++) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute("class", image_data[i][0]+" extra_padding");
|
||||
img.src = base_url_images + image_data[i][1];
|
||||
arrowDiv.appendChild(img);
|
||||
}
|
||||
}
|
||||
|
||||
zkPlanner.drawArrow = function(arrow, orig, dest){
|
||||
var xorig = orig[0] - zkTask.DEPENDENCY_PADDING;
|
||||
var yorig = orig[1] - zkTask.HALF_DEPENDENCY_PADDING;
|
||||
var xend = dest[0] - zkTask.DEPENDENCY_PADDING;
|
||||
var yend = dest[1] - zkTask.HALF_DEPENDENCY_PADDING;
|
||||
|
||||
var width = (xend - xorig);
|
||||
var xmid = xorig + width;
|
||||
|
||||
var depstart = this.findImageElement(arrow, 'start');
|
||||
/* depstart.style.top = yorig + "px";
|
||||
depstart.style.left = xorig + "px";
|
||||
depstart.style.width = width + "px"; */
|
||||
depstart.style.display = "none";
|
||||
|
||||
var depmid = this.findImageElement(arrow, 'mid');
|
||||
if (yend > yorig) {
|
||||
depmid.style.top = yorig + "px";
|
||||
depmid.style.height = yend - yorig + "px";
|
||||
} else {
|
||||
depmid.style.top = yend + "px";
|
||||
depmid.style.height = yorig - yend + "px";
|
||||
}
|
||||
depmid.style.left = xorig + "px";
|
||||
|
||||
var depend = this.findImageElement(arrow, 'end');
|
||||
depend.style.top = yend + "px";
|
||||
depend.style.left = xorig + "px";
|
||||
depend.style.width = width + "px";
|
||||
|
||||
if (width < 0) {
|
||||
depend.style.left = xend + "px";
|
||||
depend.style.width = Math.abs(width) + "px";
|
||||
}
|
||||
var deparrow = this.findImageElement(arrow, 'arrow');
|
||||
var baseURL = 'zkau/web/ganttz/img/';
|
||||
if ( width == 0 ) {
|
||||
deparrow.src = baseURL+"arrow2.png";
|
||||
deparrow.style.top = yend - 10 + "px";
|
||||
deparrow.style.left = xend - 5 + "px";
|
||||
if ( yorig > yend ) {
|
||||
deparrow.src = baseURL+"arrow4.png";
|
||||
deparrow.style.top = yend + "px";
|
||||
}
|
||||
} else {
|
||||
deparrow.style.top = yend - 5 + "px";
|
||||
deparrow.style.left = xend - 10 + "px";
|
||||
deparrow.src = baseURL+"arrow.png";
|
||||
|
||||
if (width < 0) {
|
||||
deparrow.src = baseURL+"arrow3.png";
|
||||
deparrow.style.left = xend + "px";
|
||||
deparrow.style.top = yend - 5 + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
250
navalplanner-gantt-zk/src/main/resources/web/js/ganttz/task.js
Executable file
|
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
*
|
||||
* ganttz.js
|
||||
*
|
||||
*/
|
||||
|
||||
zkTask = {};
|
||||
|
||||
zkTask.CORNER_WIDTH = 20;
|
||||
zkTask.HEIGHT = 20;
|
||||
zkTask.HALF_HEIGHT = 10;
|
||||
zkTask.DEPENDENCY_PADDING = 4;
|
||||
zkTask.HALF_DEPENDENCY_PADDING = 2;
|
||||
|
||||
zkTask.getDD = function(cmp) {
|
||||
if (typeof (cmp.created_dd) == 'undefined') {
|
||||
|
||||
// Create the laned drag&drop component
|
||||
cmp.created_dd = new YAHOO.example.DDRegion(cmp.id, '', {
|
||||
cont : 'row' + cmp.id
|
||||
});
|
||||
}
|
||||
return cmp.created_dd;
|
||||
}
|
||||
|
||||
zkTask.init = function(cmp) {
|
||||
// Configure the drag&drop over the component
|
||||
var dd = zkTask.getDD(cmp);
|
||||
|
||||
// Register the event endDragEvent
|
||||
dd.on('endDragEvent', function(ev) {
|
||||
zkau.send( {
|
||||
uuid : cmp.id,
|
||||
cmd : "updatePosition",
|
||||
data : [ cmp.style.left, cmp.style.top ]
|
||||
});
|
||||
|
||||
}, null, false);
|
||||
|
||||
// Configure the task element to be resizable
|
||||
var resize = new YAHOO.util.Resize(cmp.id, {
|
||||
handles : [ 'r' ],
|
||||
proxy : true
|
||||
});
|
||||
resize.on('resize', function(ev) {
|
||||
|
||||
zkau.send( {
|
||||
uuid : cmp.id,
|
||||
cmd : "updateSize",
|
||||
data : [ cmp.style.width ]
|
||||
});
|
||||
|
||||
}, zkTask, true);
|
||||
|
||||
// Listen to mousemove events
|
||||
YAHOO.util.Event.on(document.body, 'mousemove', function(e) {
|
||||
var arrPos = YAHOO.util.Event.getXY(e);
|
||||
zkTask.xMouse = arrPos[0];
|
||||
zkTask.yMouse = arrPos[1];
|
||||
});
|
||||
};
|
||||
|
||||
zkTask.xMouse;
|
||||
zkTask.yMouse;
|
||||
|
||||
zkTask.setAttr = function(cmp, nm, val) {
|
||||
|
||||
switch (nm) {
|
||||
|
||||
case "style.top":
|
||||
case "style.width":
|
||||
case "style.left": {
|
||||
zkau.setAttr(cmp, nm, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
zkTask.addDependency = function(cmp) {
|
||||
zkTask.createArrow(cmp);
|
||||
};
|
||||
|
||||
zkTask.setAttr = function(cmp, name, value) {
|
||||
if ("name" == name) {
|
||||
var span = YAHOO.util.Selector.query("span", cmp, true);
|
||||
span.innerHTML = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
zkTask.createArrow = function(cmp) {
|
||||
var arrow = document.createElement('div');
|
||||
var listtasksNode = document.getElementById("listtasks");
|
||||
var listdependenciesNode = document.getElementById("listdependencies");
|
||||
var cmpNode = document.getElementById(cmp.id);
|
||||
|
||||
cmp.parentNode.appendChild(arrow);
|
||||
zkPlanner.setupArrow(arrow);
|
||||
var xMouse = zkTask.xMouse;
|
||||
var yMouse = zkTask.yMouse;
|
||||
function updateArrow() {
|
||||
var origin = zkPlanner.findPos(cmp);
|
||||
origin[0] = origin[0]
|
||||
+ Math.max(0, cmpNode.offsetWidth - zkTask.CORNER_WIDTH);
|
||||
origin[1] = origin[1] - listtasksNode.offsetTop
|
||||
+ listdependenciesNode.offsetTop + zkTask.HEIGHT;
|
||||
var destination = zkPlanner.findPosForMouseCoordinates(xMouse, yMouse);
|
||||
zkPlanner.drawArrow(arrow, origin, destination);
|
||||
}
|
||||
updateArrow();
|
||||
mousemoveListener = function(e) {
|
||||
var arrPos = YAHOO.util.Event.getXY(e);
|
||||
xMouse = arrPos[0];
|
||||
yMouse = arrPos[1];
|
||||
updateArrow();
|
||||
};
|
||||
mouseClickListener = function(e) {
|
||||
var parentNode = arrow.parentNode;
|
||||
var task;
|
||||
if (task = zkTask.isOverTask(cmp, arrow)) {
|
||||
zkau.send( {
|
||||
uuid : cmp.id,
|
||||
cmd : "addDependency",
|
||||
data : [ task.getAttribute('idtask') ]
|
||||
});
|
||||
}
|
||||
parentNode.removeChild(arrow);
|
||||
YAHOO.util.Event.removeListener(document.body, 'click',
|
||||
mouseClickListener);
|
||||
YAHOO.util.Event.removeListener(document.body, 'mousemove',
|
||||
mousemoveListener);
|
||||
};
|
||||
YAHOO.util.Event.on(document.body, 'mousemove', mousemoveListener);
|
||||
YAHOO.util.Event.on(document.body, 'click', mouseClickListener);
|
||||
};
|
||||
|
||||
zkTask.isOverTask = function(cmp, arrow) {
|
||||
|
||||
var listtasksNode = document.getElementById("listtasks");
|
||||
var ganttPanelNode = document.getElementById("ganttpanel");
|
||||
|
||||
arrayTasks = zkTask.getElementsByAttribute(listtasksNode, "div", "z.type",
|
||||
"ganttz.task.Task");
|
||||
|
||||
/* Cursor relative positions to #listtasks (439,160) and ganttPanel scroll */
|
||||
var xpos = zkTask.xMouse - listtasksNode.offsetLeft
|
||||
+ ganttPanelNode.scrollLeft;
|
||||
var ypos = zkTask.yMouse - listtasksNode.offsetTop;
|
||||
/*
|
||||
* This way of getting cursor coordinates, is unable to calculate scrollbar
|
||||
* offset.
|
||||
*/
|
||||
|
||||
for ( var i = 0; i < arrayTasks.length; i++) {
|
||||
|
||||
var task = arrayTasks[i];
|
||||
|
||||
YAHOO.log(" zkTask.xMouse (" + zkTask.xMouse + "," + zkTask.yMouse
|
||||
+ ") " + "task.pos (" + xpos + "," + ypos + ") "
|
||||
+ "task.offsetLeft (" + task.offsetLeft + "," + task.offsetTop
|
||||
+ ") " + "task.idTask-" + task.toString());
|
||||
|
||||
if (((xpos) > (task.offsetLeft))
|
||||
&& ((xpos) < (task.offsetLeft + task.offsetWidth))
|
||||
&& (ypos > (task.offsetTop))
|
||||
&& (ypos < (task.offsetTop + task.offsetHeight))) {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
zkTask.getElementsByAttribute = function(oElm, strTagName, strAttributeName,
|
||||
strAttributeValue) {
|
||||
|
||||
var arrElements = (strTagName == "*" && oElm.all) ? oElm.all : oElm
|
||||
.getElementsByTagName(strTagName);
|
||||
var arrReturnElements = new Array();
|
||||
var oAttributeValue = (typeof strAttributeValue != "undefined") ? new RegExp(
|
||||
"(^|\\s)" + strAttributeValue + "(\\s|$)")
|
||||
: null;
|
||||
var oCurrent;
|
||||
var oAttribute;
|
||||
|
||||
for ( var i = 0; i < arrElements.length; i++) {
|
||||
oCurrent = arrElements[i];
|
||||
oAttribute = oCurrent.getAttribute
|
||||
&& oCurrent.getAttribute(strAttributeName);
|
||||
if (typeof oAttribute == "string" && oAttribute.length > 0) {
|
||||
if (typeof strAttributeValue == "undefined"
|
||||
|| (oAttributeValue && oAttributeValue.test(oAttribute))) {
|
||||
arrReturnElements.push(oCurrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return arrReturnElements;
|
||||
}
|
||||
|
||||
YAHOO.example.DDRegion = function(id, sGroup, config) {
|
||||
this.cont = config.cont;
|
||||
YAHOO.example.DDRegion.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
var myDom = YAHOO.util.Dom, myEvent = YAHOO.util.Event
|
||||
|
||||
YAHOO.extend(YAHOO.example.DDRegion, YAHOO.util.DD, {
|
||||
cont : null,
|
||||
init : function() {
|
||||
//Call the parent's init method
|
||||
YAHOO.example.DDRegion.superclass.init.apply(this, arguments);
|
||||
this.initConstraints();
|
||||
|
||||
myEvent.on(window, 'resize', function() {
|
||||
this.initConstraints();
|
||||
}, this, true);
|
||||
},
|
||||
initConstraints : function() {
|
||||
|
||||
//Get the top, right, bottom and left positions
|
||||
var region = myDom.getRegion(this.cont);
|
||||
|
||||
// Get the element we are working on
|
||||
var el = this.getEl();
|
||||
|
||||
// Get the xy position of it
|
||||
var xy = myDom.getXY(el);
|
||||
|
||||
// Get the width and height
|
||||
var width = parseInt(myDom.getStyle(el, 'width'), 10);
|
||||
var height = parseInt(myDom.getStyle(el, 'height'), 10);
|
||||
|
||||
// Set left to x minus left
|
||||
var left = xy[0] - region.left;
|
||||
|
||||
// Set right to right minus x minus width
|
||||
var right = region.right - xy[0] - width;
|
||||
|
||||
// Set top to y minus top
|
||||
var top = xy[1] - region.top;
|
||||
|
||||
// Set bottom to bottom minus y minus height
|
||||
var bottom = region.bottom - xy[1] - height;
|
||||
|
||||
// Set the constraints based on the above calculations
|
||||
this.setXConstraint(left, right);
|
||||
this.setYConstraint(top, bottom);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
zkTaskDetails = {};
|
||||
|
||||
zkTaskDetails.init = function(cmp) {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
zkTasklist = {};
|
||||
|
||||
zkTasklist.init = function(cmp) {
|
||||
zkTasklist.adjust_height(cmp);
|
||||
}
|
||||
|
||||
zkTasklist.adjust_height = function(cmp) {
|
||||
var height = cmp.style.height;
|
||||
var component_to_adjust = document.getElementById(cmp
|
||||
.getAttribute('sameHeightElementId'));
|
||||
|
||||
function setHeight(element, offset) {
|
||||
if (!offset) {
|
||||
offset = 0;
|
||||
}
|
||||
var newheigth = parseInt(height) + offset;
|
||||
element.style["height"] = newheigth + 'px';
|
||||
}
|
||||
|
||||
setHeight(document.getElementById('ganttpanel'), 120);/*
|
||||
* timetracker
|
||||
* height
|
||||
*/
|
||||
if (component_to_adjust) {
|
||||
setHeight(component_to_adjust);
|
||||
}
|
||||
var found = YAHOO.util.Selector.query(".fake_column", component_to_adjust,
|
||||
false);
|
||||
found.each( function(element) {
|
||||
setHeight(element);
|
||||
});
|
||||
}
|
||||
55
navalplanner-webapp/pom.xml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.navalplanner</groupId>
|
||||
<artifactId>navalplanner</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>navalplanner-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>Naval Planner Web Client Module</name>
|
||||
|
||||
<build>
|
||||
<finalName>navalplanner-webapp</finalName>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- ZK -->
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zul</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zkplus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zk</artifactId>
|
||||
</dependency>
|
||||
<!-- Naval Planner ZK Components -->
|
||||
<dependency>
|
||||
<groupId>org.navalplanner</groupId>
|
||||
<artifactId>navalplanner-gantt-zk</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring</artifactId>
|
||||
</dependency>
|
||||
<!-- BeanShell (required by ZK)-->
|
||||
<dependency>
|
||||
<groupId>bsh</groupId>
|
||||
<artifactId>bsh</artifactId>
|
||||
</dependency>
|
||||
<!-- Apache Commons Fileupload (required by ZK) -->
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package org.zk.myhello.pages;
|
||||
|
||||
import org.zkoss.ganttz.util.zoom.ZoomLevel;
|
||||
import org.zkoss.zk.ui.Page;
|
||||
import org.zkoss.zk.ui.util.Initiator;
|
||||
import org.zkoss.zul.Div;
|
||||
import org.zkoss.zul.Label;
|
||||
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Francisco Javier Moran Rúa
|
||||
*
|
||||
*/
|
||||
public class MyHelloPageListener extends Div implements Initiator {
|
||||
|
||||
private ZoomLevel currentDetailLevel;
|
||||
|
||||
public MyHelloPageListener() {
|
||||
setCurrentDetailLevel(ZoomLevel.DETAIL_ONE);
|
||||
}
|
||||
|
||||
public void doInit(Page arg0, Object[] arg1) throws Exception {
|
||||
setCurrentDetailLevel(ZoomLevel.DETAIL_ONE);
|
||||
}
|
||||
|
||||
public void doAfterCompose(Page arg0) throws Exception {
|
||||
setCurrentDetailLevel(ZoomLevel.DETAIL_ONE);
|
||||
}
|
||||
|
||||
public boolean doCatch(Throwable arg0) throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void doFinally() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the currentDetailLevel
|
||||
*/
|
||||
public ZoomLevel getCurrentDetailLevel() {
|
||||
return this.currentDetailLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param currentDetailLevel
|
||||
* the currentDetailLevel to set
|
||||
*/
|
||||
public void setCurrentDetailLevel(ZoomLevel currentDetailLevel) {
|
||||
this.currentDetailLevel = currentDetailLevel;
|
||||
}
|
||||
|
||||
public void repaint() {
|
||||
Label l = (Label) getFellow("valor");
|
||||
l.invalidate();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
task.name=Nome
|
||||
task.start=Comezo
|
||||
task.end=Final
|
||||
task.notes=Notas
|
||||
task.ok=Aceptar
|
||||
listdetails.add_task=Engadir
|
||||
task.new_task_name=Nova Tarefa
|
||||
DETAIL_ONE=Detalle 1
|
||||
DETAIL_TWO=Detalle 2
|
||||
DETAIL_THREE=Detalle 3
|
||||
DETAIL_FOUR=Detalle 4
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
task.name=Name
|
||||
task.start=Start
|
||||
task.end=End
|
||||
task.notes=Notes
|
||||
task.ok=Ok
|
||||
listdetails.add_task=Add
|
||||
task.new_task_name=New Task
|
||||
DETAIL_ONE=Detail 1
|
||||
DETAIL_TWO=Detail 2
|
||||
DETAIL_THREE=Detail 3
|
||||
DETAIL_FOUR=Detail 4
|
||||
67
navalplanner-webapp/src/main/webapp/WEB-INF/web.xml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app id="WebApp_ID" version="2.4"
|
||||
xmlns="http://java.sun.com/xml/ns/j2ee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
|
||||
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
|
||||
<display-name>
|
||||
helloworld</display-name>
|
||||
|
||||
<!-- /// -->
|
||||
<!-- DSP -->
|
||||
<servlet>
|
||||
<description><![CDATA[The servlet loads the DSP pages.]]></description>
|
||||
<servlet-name>dspLoader</servlet-name>
|
||||
<servlet-class>org.zkoss.web.servlet.dsp.InterpreterServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>dspLoader</servlet-name>
|
||||
<url-pattern>*.dsp</url-pattern>
|
||||
</servlet-mapping>
|
||||
<!-- /// -->
|
||||
|
||||
<!-- //// -->
|
||||
<!-- ZK -->
|
||||
<listener>
|
||||
<description>Used to cleanup when a session is destroyed</description>
|
||||
<display-name>ZK Session Cleaner</display-name>
|
||||
<listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
|
||||
</listener>
|
||||
<servlet>
|
||||
<description>ZK loader for ZUML pages</description>
|
||||
<servlet-name>zkLoader</servlet-name>
|
||||
<servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
|
||||
<!-- Must. Specifies URI of the update engine (DHtmlUpdateServlet).
|
||||
It must be the same as <url-pattern> for the update engine.
|
||||
-->
|
||||
<init-param>
|
||||
<param-name>update-uri</param-name>
|
||||
<param-value>/zkau</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>zkLoader</servlet-name>
|
||||
<url-pattern>*.zul</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>zkLoader</servlet-name>
|
||||
<url-pattern>*.zhtml</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet>
|
||||
<description>The asynchronous update engine for ZK</description>
|
||||
<servlet-name>auEngine</servlet-name>
|
||||
<servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>auEngine</servlet-name>
|
||||
<url-pattern>/zkau/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
<!-- //// -->
|
||||
|
||||
<welcome-file-list>
|
||||
|
||||
<welcome-file>myhello.zul</welcome-file>
|
||||
|
||||
</welcome-file-list>
|
||||
</web-app>
|
||||
5
navalplanner-webapp/src/main/webapp/WEB-INF/zk.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<zk>
|
||||
<log>
|
||||
<log-base></log-base>
|
||||
</log>
|
||||
</zk>
|
||||
137
navalplanner-webapp/src/main/webapp/css/productionmanagement.css
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/* ProductionManagement Global Styles */
|
||||
|
||||
/*** Tasks ***/
|
||||
#listtasks {
|
||||
position:relative;
|
||||
width:400px; /** Constants */
|
||||
top:100px;
|
||||
}
|
||||
|
||||
.listdetails {
|
||||
width:200px;
|
||||
float:left;
|
||||
margin-top:80px;
|
||||
}
|
||||
|
||||
.listdetails table {
|
||||
height:30px;
|
||||
}
|
||||
|
||||
.listdetails td {
|
||||
padding-left:3px;
|
||||
}
|
||||
|
||||
.listdetails input {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
|
||||
.z-datebox-inp {
|
||||
width:100px;
|
||||
}
|
||||
|
||||
|
||||
/* Task box properties */
|
||||
.yui-resize {
|
||||
border: 1px solid;
|
||||
text-align:center;
|
||||
vertical-align: middle;
|
||||
font-size:0.8em;
|
||||
z-index:10;
|
||||
}
|
||||
|
||||
/* Task lane properties */
|
||||
.row {
|
||||
height: 19px;
|
||||
border-bottom: dotted 1px #CCCCCC;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
width: 1800px;
|
||||
}
|
||||
|
||||
|
||||
/*** Dependencies ***/
|
||||
#listdependencies {
|
||||
position:relative;
|
||||
width:400px;
|
||||
float:left;
|
||||
top:0px; /* Should be -60, recalc. redrawing deps. */
|
||||
}
|
||||
|
||||
.dependence {
|
||||
z-index:1;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.end, .start, .mid, .arrow {
|
||||
position:absolute;
|
||||
padding:4px;
|
||||
}
|
||||
|
||||
.end, .start {
|
||||
height:1px;
|
||||
}
|
||||
|
||||
.mid {
|
||||
width:1px;
|
||||
}
|
||||
|
||||
/*.center {
|
||||
float:left;
|
||||
overflow-x:scroll;
|
||||
width:600px;
|
||||
}
|
||||
.footer {
|
||||
clear:both;
|
||||
} */
|
||||
|
||||
|
||||
/* Width: ganttpanel constant,
|
||||
Height: is recalculated on number of tasks */
|
||||
#ganttpanel {
|
||||
/* border: 1px solid; */
|
||||
height:400px;
|
||||
width: 900px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
#ganttpanel table {
|
||||
float:left;
|
||||
padding:0;
|
||||
margin:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
#ganttpanel table td {
|
||||
padding:0;
|
||||
}
|
||||
|
||||
.fake_column.timetracker_column_even {
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
|
||||
.timetracker_fake_row {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
/*
|
||||
.extra_padding {
|
||||
padding: 6px;
|
||||
} */
|
||||
|
||||
.logo {
|
||||
background-image: url("../img/v3/blue_ga.jpg");
|
||||
height:50px;
|
||||
width:300px;
|
||||
height:100px;
|
||||
float:left;
|
||||
clear:both;
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
.vision {
|
||||
}
|
||||
|
||||
.headings {
|
||||
height:20px;
|
||||
}
|
||||
BIN
navalplanner-webapp/src/main/webapp/img/v1/blue_ga.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
navalplanner-webapp/src/main/webapp/img/v2/blue_ga.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
navalplanner-webapp/src/main/webapp/img/v3/blue_ga.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
63
navalplanner-webapp/src/main/webapp/myhello.zul
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?init class="org.zk.myhello.pages.MyHelloPageListener"?>
|
||||
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
|
||||
<?link rel="stylesheet" type="text/css" href="/css/productionmanagement.css"?>
|
||||
<?page id="myhello"?>
|
||||
<zk>
|
||||
|
||||
|
||||
<div class="headings">
|
||||
<div class="logo"></div>
|
||||
<div class="vision"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<planner>
|
||||
<div id="idContextMenuTaskAssigment" use="org.zk.myhello.pages.MyHelloPageListener">
|
||||
</div>
|
||||
|
||||
<listdetails>
|
||||
</listdetails>
|
||||
<ganttpanel>
|
||||
<timetracker detailLevel="${idContextMenuTaskAssigment.currentDetailLevel}" />
|
||||
<tasklist id="taskList">
|
||||
<task id="task1" color="blue"/>
|
||||
|
||||
<task id="task2" color="red"/>
|
||||
|
||||
<task id="task3" color="yellow"/>
|
||||
|
||||
<task id="task4" color="green"/>
|
||||
|
||||
<!--task id="task5" color="orange"/>
|
||||
|
||||
<task id="task6" color="white"/-->
|
||||
|
||||
</tasklist>
|
||||
|
||||
|
||||
<window border="normal" width="300px" apply="${taskList.modalFormComposer}">
|
||||
<grid>
|
||||
<rows>
|
||||
<row>${c:l('task.name')} <textbox id="name"/></row>
|
||||
<row>${c:l('task.start')} <datebox id="startDateBox" compact="true"/>
|
||||
</row>
|
||||
<row>
|
||||
${c:l('task.end')} <datebox id="endDateBox" compact="true" />
|
||||
</row>
|
||||
<row>
|
||||
${c:l('task.notes')} <textbox id="notes" />
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
<button id="ok" label=" ${c:l('task.ok')}" />
|
||||
</window>
|
||||
|
||||
<dependencylist>
|
||||
<dependency idTaskOrig="task1" idTaskEnd="task2"/>
|
||||
<dependency idTaskOrig="task2" idTaskEnd="task3"/>
|
||||
<dependency idTaskOrig="task1" idTaskEnd="task4"/>
|
||||
</dependencylist>
|
||||
</ganttpanel>
|
||||
</planner>
|
||||
|
||||
</zk>
|
||||
338
pom.xml
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.navalplanner</groupId>
|
||||
<artifactId>navalplanner</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.0.0</version>
|
||||
<name>Naval Planner</name>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- Modules -->
|
||||
<modules>
|
||||
<module>navalplanner-business</module>
|
||||
<module>navalplanner-gantt-zk</module>
|
||||
<module>navalplanner-webapp</module>
|
||||
</modules>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- Default values for properties. These default values are expected to be
|
||||
valid for most profiles. In any case, profiles can overwrite values
|
||||
when necessary.
|
||||
-->
|
||||
<properties>
|
||||
<!-- Data source properties -->
|
||||
<dataSource.user>naval</dataSource.user>
|
||||
<dataSource.password>naval</dataSource.password>
|
||||
<dataSource.jndiName>jdbc/navalplanner-ds</dataSource.jndiName>
|
||||
<testDataSource.user>${dataSource.user}</testDataSource.user>
|
||||
<testDataSource.password>${dataSource.password}</testDataSource.password>
|
||||
</properties>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- Profiles.
|
||||
|
||||
* The build is always executed by selecting at least two non-exclusive
|
||||
profiles. By default, such profiles are "dev" and "mysql" (meaning
|
||||
"use MySQL assuming a development environment").
|
||||
|
||||
* General profiles. There are two general (database-independent)
|
||||
profiles: "dev" and "prod". The former is used for development
|
||||
(including testing) and the latter is used for production (including
|
||||
testing). As shown below, two dataSources (databases schemas) are
|
||||
used in both profiles: one for running (dataSource) and another one
|
||||
for the Maven test fase (testDataSource). Note the Maven test fase
|
||||
is executed both with development and production profiles.
|
||||
|
||||
* Database-specific profiles. There is a profile for each supported
|
||||
database.
|
||||
|
||||
* Specific profiles can be defined to better adapt to a particular
|
||||
environment by overwriting/adding properties and/or including other
|
||||
chunks of valid XML.
|
||||
|
||||
* Usage:
|
||||
|
||||
+ mvn <<goal>> => Execute <<goal>> with default profiles.
|
||||
+ mvn -Pdev,<<database>> <<goal> => Execute <<goal>> with
|
||||
development and <<database>> profiles.
|
||||
+ mvn -Pprod,<<database>> <<goal>> => Execute <<goal>> with
|
||||
production and <<database>> profiles.
|
||||
|
||||
+ Note that when using -P option all desired profiles must be
|
||||
specified (e.g. "-Pprod" with the intention to select "prod"
|
||||
and the default database profiles is not correct;
|
||||
"-Pprod,<<database>>" must be used instead).
|
||||
|
||||
* Examples:
|
||||
|
||||
+ mvn <<goal>>
|
||||
+ mvn -Pdev,mysql <<goal>>
|
||||
+ mvn -Pprod,mysql <<goal>>
|
||||
|
||||
-->
|
||||
<profiles>
|
||||
|
||||
<!-- Development profile -->
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<!-- Data source properties -->
|
||||
<dataSource.url>jdbc:mysql://localhost/navaldev</dataSource.url>
|
||||
<testDataSource.url>${dataSource.url}test</testDataSource.url>
|
||||
<!-- Hibernate properties -->
|
||||
<hibernate.show_sql>true</hibernate.show_sql>
|
||||
<hibernate.format_sql>true</hibernate.format_sql>
|
||||
<hibernate.use_sql_comments>true</hibernate.use_sql_comments>
|
||||
<hibernate.hbm2ddl.auto>update</hibernate.hbm2ddl.auto>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<!-- Production profile -->
|
||||
<profile>
|
||||
<id>prod</id>
|
||||
<properties>
|
||||
<!-- Data source properties -->
|
||||
<!-- NOTE: particular production profiles will probably wish to
|
||||
overwrite "dataSource.url" property and also maybe
|
||||
"testDataSource.url".-->
|
||||
<dataSource.url>jdbc:mysql://localhost/navalprod</dataSource.url>
|
||||
<testDataSource.url>${dataSource.url}test</testDataSource.url>
|
||||
<!-- Hibernate properties -->
|
||||
<hibernate.show_sql>false</hibernate.show_sql>
|
||||
<hibernate.format_sql>false</hibernate.format_sql>
|
||||
<hibernate.use_sql_comments>false</hibernate.use_sql_comments>
|
||||
<hibernate.hbm2ddl.auto>update</hibernate.hbm2ddl.auto>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<!-- MySQL profile -->
|
||||
<profile>
|
||||
<id>mysql</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<!-- JDBC driver properties -->
|
||||
<jdbcDriver.groupId>mysql</jdbcDriver.groupId>
|
||||
<jdbcDriver.artifactId>mysql-connector-java</jdbcDriver.artifactId>
|
||||
<jdbcDriver.version>5.0.5</jdbcDriver.version>
|
||||
<jdbcDriver.className>com.mysql.jdbc.Driver</jdbcDriver.className>
|
||||
<!-- Hibernate properties -->
|
||||
<hibernate.dialect>org.hibernate.dialect.MySQLDialect</hibernate.dialect>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>postgresql</id>
|
||||
<properties>
|
||||
<!-- JDBC driver properties -->
|
||||
<jdbcDriver.groupId>postgresql</jdbcDriver.groupId>
|
||||
<jdbcDriver.artifactId>postgresql</jdbcDriver.artifactId>
|
||||
<jdbcDriver.version>8.3-603.jdbc4</jdbcDriver.version>
|
||||
<jdbcDriver.className>org.postgresql.Driver</jdbcDriver.className>
|
||||
<!-- Data source properties -->
|
||||
<!-- FIXME: Workaround for current bug in profile-based
|
||||
approach. -->
|
||||
<dataSource.url>jdbc:postgresql://localhost/navaldev</dataSource.url>
|
||||
<!-- Hibernate properties -->
|
||||
<hibernate.dialect>org.hibernate.dialect.PostgreSQLDialect</hibernate.dialect>
|
||||
</properties>
|
||||
|
||||
</profile>
|
||||
|
||||
<!-- Add here other profiles. -->
|
||||
|
||||
</profiles>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- Dependency management -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- JDBC driver -->
|
||||
<dependency>
|
||||
<groupId>${jdbcDriver.groupId}</groupId>
|
||||
<artifactId>${jdbcDriver.artifactId}</artifactId>
|
||||
<version>${jdbcDriver.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Hibernate -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate</artifactId>
|
||||
<version>3.2.6.ga</version>
|
||||
</dependency>
|
||||
<!-- JUnit -->
|
||||
<!-- IMPORTANT: Spring TestContext 2.5.x is not compatible with
|
||||
JUnit 4.5. -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring</artifactId>
|
||||
<version>2.5.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>2.5.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- ZK -->
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zul</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zkplus</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zkoss.zk</groupId>
|
||||
<artifactId>zk</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
<!-- Commons Logging (required by many frameworks)-->
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.0.4</version>
|
||||
</dependency>
|
||||
<!-- Naval Planner ZK Components -->
|
||||
<dependency>
|
||||
<groupId>org.navalplanner</groupId>
|
||||
<artifactId>navalplanner-gantt-zk</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<!-- BeanShell (required by ZK)-->
|
||||
<dependency>
|
||||
<groupId>bsh</groupId>
|
||||
<artifactId>bsh</artifactId>
|
||||
<version>2.0b4</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- Apache Commons Fileupload (required by ZK) -->
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Filtering -->
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>../src/main/jetty</directory>
|
||||
<includes>
|
||||
<include>jetty-env.xml</include>
|
||||
</includes>
|
||||
<targetPath>../jetty</targetPath>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</testResource>
|
||||
</testResources>
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Compiler configuration -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<verbose>true</verbose>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Assembly configuration -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/src.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Jetty configuration -->
|
||||
<plugin>
|
||||
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
<artifactId>maven-jetty-plugin</artifactId>
|
||||
<!-- FIXME: try to not to specify version. -->
|
||||
<version>6.1.12.rc2</version>
|
||||
<configuration>
|
||||
<jettyEnvXml>target/jetty/jetty-env.xml</jettyEnvXml>
|
||||
<scanIntervalSeconds>5</scanIntervalSeconds>
|
||||
<scanTargetPatterns>
|
||||
<scanTargetPattern>
|
||||
<directory>src/main/webapp/WEB-INF</directory>
|
||||
<includes>
|
||||
<include>*</include>
|
||||
</includes>
|
||||
</scanTargetPattern>
|
||||
</scanTargetPatterns>
|
||||
|
||||
<!-- Log to the console. -->
|
||||
<requestLog implementation="org.mortbay.jetty.NCSARequestLog">
|
||||
<!-- This do anything for Jetty, but is a workaround
|
||||
for a Maven bug that prevents the requestLog from
|
||||
being set. -->
|
||||
<append>true</append>
|
||||
</requestLog>
|
||||
<!--
|
||||
<connectors>
|
||||
<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
|
||||
<port>9090</port>
|
||||
</connector>
|
||||
</connectors>
|
||||
-->
|
||||
</configuration>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${jdbcDriver.groupId}</groupId>
|
||||
<artifactId>${jdbcDriver.artifactId}</artifactId>
|
||||
<version>${jdbcDriver.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
21
src/main/assembly/src.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<assembly>
|
||||
<id>src</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
<format>tar.gz</format>
|
||||
<format>tar.bz2</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>**/.project</exclude>
|
||||
<exclude>**/.classpath</exclude>
|
||||
<exclude>**/.settings/**</exclude>
|
||||
<exclude>**/target/**</exclude>
|
||||
</excludes>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
18
src/main/jetty/jetty-env.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN"
|
||||
"http://jetty.mortbay.org/configure.dtd">
|
||||
|
||||
<Configure class="org.mortbay.jetty.webapp.WebAppContext">
|
||||
<New id="navalplanner-ds" class="org.mortbay.jetty.plus.naming.Resource">
|
||||
<Arg>${dataSource.jndiName}</Arg>
|
||||
<Arg>
|
||||
<New class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<Set name="driverClassName">${jdbcDriver.className}</Set>
|
||||
<Set name="url">${dataSource.url}</Set>
|
||||
<Set name="username">${dataSource.user}</Set>
|
||||
<Set name="password">${dataSource.password}</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</New>
|
||||
</Configure>
|
||||
|
||||