diff --git a/data/images/Makefile.am b/data/images/Makefile.am
index efaed99..6802557 100644
--- a/data/images/Makefile.am
+++ b/data/images/Makefile.am
@@ -2,6 +2,7 @@ imagesdir = $(datadir)/planner/images
 images_DATA = 				\
 	tasks.png			\
 	gantt.png			\
+	pert.png			\
 	resources.png			\
 	resources_usage.png		\
 	24_insert_task.png		\
diff --git a/data/images/pert.png b/data/images/pert.png
new file mode 100644
index 0000000000000000000000000000000000000000..d532faa998aad04c39eed7158531f6e6d1e86856
GIT binary patch
literal 1623
zcmV-d2B`UoP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FWS01FWTe`H^g00007bV*G`2iXM@
z4iqm$^e`m=00q}cL_t(&-t8H?ZWKH89NRPAy(=hqC>1{uKtcjh@c{__Bv7Fg6o~Sc
zx1faYfJBf6kyI2&i4+n76i5gu+1+{U*yk3EM;@=o<4MxNk*x8IXO7SN;Q{yyf8oyt
zv~M;xHZaRFoKC0M_u<#CUk7h?baXWM8+)JWbV^y4(dOnREiv%&<x9>vms)Gd7~@3L
ztW{OjAW=#!Ju`E6&3^8_W7cE71prtq7F1Q0n$PD&nx@68SFcpVz{bV~e*gY`Je$qd
z7-Q>*D2aYENkpKv1^@u9b#t$j0<CrP%sh98?|pmCes1OgTm=9;OOoVYDP{KJ#S0Pu
z00##LI2w&4=Y0MC{rfUa(}sA1%*^fQjT-T{vXUfe)@`uw&9^qO?wSv$)*7-blTV&J
zSr<b5SC(ZC0KXZSOeRP~TnHhhl<?!n4<I7AckkXHdwu)%ZEJILwHdv0=guIUTU%Sr
zd^{cl5lKYkKY#v2003bY8DnT~Xw2r*r%#O?8VT705#iCJNAUXf>lVRXbA!{?st@Pd
z+8Vrh^9C4W4Lyk13<JVOY+klisg#2GeBRioJG9m?o6UL&cZD;b&%OA3IBnWoXc!Pe
zz+$mzHZvMP#FkUpK4IQ5aQE)rXw&G^fEh`WH1xRpEos07*MURCBO<(e_pY(?t*xyk
z(*XduefxH2q;Z|!mb?$w5(91oKty=>@FA?Mth5+#Bg_8&ek($S@Bu&zr>zYeK7alk
z3<ygMR8{2-jk~+Mu)Dk4>5zs{-$wSFG1kJlx3?EpD%2+2wRcMcN~x9&Zf<S@=X{Ac
zHxk(QX3jbHGV=cYd$9ekOR_#K`b<o?#2YXK*v{-D)_3(u%r{p`^)lc~hMgL)iDis6
zO!y=mVhRz1(ls&NHI<>nFZqDYK&)i?r0m1f=a?Zs==Fh6_!=?~W#Z6UjG%foa2AR8
zZF>Fs^+C1J&dyF81ICGcs*06Tmp{4StA*@*;MP}z!{~WvA*7w5wNS_>g=yO+lS#{j
zhC~S;+`M@+supsuZc>cS#I6&^gfKS?+w<-1?bf}48#it&l_S0~#BOK|gQnPw3Zv1d
z0)djbs}M7_kXta?u)n__rG3#S?X!eYqX8H1-rinkE#x!Zs6=KQsLy0h6GrW|ot+&J
zLIgAX5EJgq1E)zgvCPK!Q<rZn^$WhpaGqh*Cs|+5;GTGd9K2Hlp(xa4i(OI3-q>cs
z?rm)!3E6s-55(A8j5bVTWcSxL@utU(B|f^GHW~1&Q7H5+5y9%}>L3Aq{P=O037sVh
z#fD+G7IO1JS1r`Zz|qkWT)K3rvl>0G-oOqPv)OFvtVwI#L_<>x^$C;>%>(BZV!IA-
zeL{?O;!r;@R13Fm-RewkU58pB;-h>Z#Azs8w$3VrfDa!&c=G^b3|3cH;lYCk-g6=M
ztZ8shY@1Ms$XG47rGmj7s)bIi0pn0ZVsVUC+-_=ndpo*!wo80p2iGw0>(?*3diAOz
zq9V&OIU0?kDnTEEp`RP4ZIBEdd7c9z7DS{jU%pHY18JI4p67X0RnMM3f4&X?vWqxh
z&C)mQ1}4<shN{|Hs|5f&tEwuOQc~+D-{Z%Rc~KOSb3Wpn^E}V-%a<?X<KyGe$;rvM
zD2j1emU6LJjFeI%rPNq!Es02Ktw&nxl!#<K5+X_gK-51A2>>cYTp(fz03~CrM8q5s
z3&vQ%7|R)B8RxtZLS#aSTuPZwCX@Nf%F67@l`AD8s;a7Tt#$h+pMj@OpW?y60j6n+
zS(ZVb=XiK{h~K__lgG!$|5Q~q(OQp*C?%p15lH|@Yhq=s(WEAvJNO9z6aZ8Ju&5a-
z0iXbYvYzLNm@~#^Ns>%2Uc5NJcI{d<9*^O|g$s0YazX&$|IHftFB$9P$7^EKdKki!
zdM@gB%n7fqiPtp)hKU6Ll-9he-=lhfb?yJrJ){U7I0p|gT?Eo;2{~`Tvk>;j!2h9V
V+>ZNDwh{mU002ovPDHLkV1j5q`RD)u

literal 0
HcmV?d00001

diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
index 1de86e8..c481d4e 100644
--- a/data/ui/Makefile.am
+++ b/data/ui/Makefile.am
@@ -4,6 +4,7 @@ ui_DATA = \
 	gantt-view.ui		\
 	task-view.ui		\
 	gantt-view.ui		\
+	pert-view.ui		\
 	html-plugin.ui		\
 	main-window.ui		\
 	resource-view.ui	\
diff --git a/data/ui/gantt-view.ui b/data/ui/gantt-view.ui
index 5892ab4..a14a863 100644
--- a/data/ui/gantt-view.ui
+++ b/data/ui/gantt-view.ui
@@ -15,6 +15,7 @@
         <menuitem    action="EditColumns"/>
         <menuitem    action="HighlightCriticalTasks"/>
         <menuitem    action="ShowGuideLines"/>
+        <menuitem    action="DisplaySlipLine"/>
         <menuitem    action="NonstandardDays"/>
       </placeholder>
     </menu>
diff --git a/data/ui/pert-view.ui b/data/ui/pert-view.ui
new file mode 100644
index 0000000..5910c97
--- /dev/null
+++ b/data/ui/pert-view.ui
@@ -0,0 +1,51 @@
+<ui>
+  <menubar         name="MenuBar">
+    <menu          action="Edit">
+      <placeholder name="Selection placeholder"/>
+      <menuitem    action="SelectAll"/>
+    </menu>
+
+    <menu          action="View">
+      <placeholder name="View specific placeholder">
+        <menuitem    action="ChangeToAutomaticCeiling"/>
+        <menuitem    action="ChangeToAutomaticTaskFlow"/>
+      </placeholder>
+      <placeholder name="View specific placeholder bottom">
+        <menuitem    action="EditColumns"/>
+        <menuitem    action="HighlightCriticalTasks"/>
+      </placeholder>
+    </menu>
+
+    <menu          action="Actions">
+      <placeholder name="View specific actions placeholder"/>
+      <menuitem    action="InsertTask"/>
+      <menuitem    action="InsertTasks"/>
+      <menuitem    action="RemoveTask"/>
+      <menuitem    action="LinkTasks"/>
+      <menuitem    action="UnlinkTask"/>
+      <separator/>
+      <menuitem    action="IndentTask"/>
+      <menuitem    action="UnindentTask"/>
+      <separator/>
+      <menuitem    action="MoveTaskUp"/>
+      <menuitem    action="MoveTaskDown"/>
+      <separator/>
+      <menuitem    action="ResetConstraint"/>
+      <separator/>
+      <menuitem    action="EditTask"/>
+    </menu>
+  </menubar>
+
+  <toolbar         name="ToolBar">
+    <toolitem      action="InsertTask"/>
+    <toolitem      action="RemoveTask"/>
+    <toolitem      action="LinkTasks"/>
+    <toolitem      action="UnlinkTask"/>
+    <toolitem      action="IndentTask"/>
+    <toolitem      action="UnindentTask"/>
+    <separator/>
+    <toolitem      action="MoveTaskUp"/>
+    <toolitem      action="MoveTaskDown"/>
+  </toolbar>
+</ui>
+
diff --git a/libplanner/mrp-project.c b/libplanner/mrp-project.c
index 775f2e0..a00a42d 100644
--- a/libplanner/mrp-project.c
+++ b/libplanner/mrp-project.c
@@ -131,6 +131,7 @@ static gboolean project_load_from_sql             (MrpProject       *project,
 						   GError          **error);
 static gboolean project_set_storage               (MrpProject       *project,
 						   const gchar      *storage_name);
+
 #if 0
 static void     project_dump_task_tree            (MrpProject       *project);
 #endif
@@ -2339,3 +2340,9 @@ mrp_project_get_block_scheduling (MrpProject *project)
 	return mrp_task_manager_get_block_scheduling (priv->task_manager);
 }
 
+mrptime mrp_project_get_finish(MrpProject *project)
+{
+	MrpProjectPriv *priv;
+	priv = project->priv;
+	return mrp_task_manager_get_project_finish(priv->task_manager);
+}
diff --git a/libplanner/mrp-project.h b/libplanner/mrp-project.h
index f8162d2..e09fbcf 100644
--- a/libplanner/mrp-project.h
+++ b/libplanner/mrp-project.h
@@ -152,5 +152,6 @@ void             mrp_project_set_block_scheduling     (MrpProject           *pro
 						       gboolean              block);
 gboolean         mrp_project_get_block_scheduling     (MrpProject           *project);
 
+mrptime mrp_project_get_finish(MrpProject *project);
 
 #endif /* __MRP_PROJECT_H__ */
diff --git a/libplanner/mrp-task-manager.c b/libplanner/mrp-task-manager.c
index 2388959..a22105a 100644
--- a/libplanner/mrp-task-manager.c
+++ b/libplanner/mrp-task-manager.c
@@ -2223,7 +2223,7 @@ task_manager_do_backward_pass (MrpTaskManager *manager)
 	GList              *tasks, *l;
 	GList              *successors, *s;
 	mrptime             project_finish;
-	mrptime             t1, t2;
+	mrptime             t1, t2, t;
 	gint                duration;
 	gboolean            critical;
 	gboolean            was_critical;
@@ -2246,6 +2246,8 @@ task_manager_do_backward_pass (MrpTaskManager *manager)
 			t1 = MIN (project_finish, mrp_task_get_latest_finish (parent));
 		}
 
+		t = mrp_task_get_finish(task);
+
 		successors = imrp_task_peek_successors (task);
 		for (s = successors; s; s = s->next) {
 			MrpRelation *relation;
@@ -2266,6 +2268,7 @@ task_manager_do_backward_pass (MrpTaskManager *manager)
 						mrp_relation_get_lag (relation);
 
 					t1 = MIN (t1, t2);
+					t = MAX (t, mrp_task_get_start (successor));
 				}
 			} else {
 				/* No children, check the real successor. */
@@ -2273,8 +2276,13 @@ task_manager_do_backward_pass (MrpTaskManager *manager)
 					mrp_relation_get_lag (relation);
 
 				t1 = MIN (t1, t2);
+				t = MAX (t, mrp_task_get_start (successor));
 			}
 		}
+		if(!successors)
+		{
+			t = project_finish;
+		}
 
 		imrp_task_set_latest_finish (task, t1);
 
@@ -2284,9 +2292,15 @@ task_manager_do_backward_pass (MrpTaskManager *manager)
 		duration = mrp_task_get_finish (task) - mrp_task_get_start (task);
 		t1 -= duration;
 		imrp_task_set_latest_start (task, t1);
-
 		t2 = mrp_task_get_start (task);
 
+		g_object_set (task,
+					"slack", mrp_task_manager_calculate_summary_duration (manager, task, t2, t1),
+					NULL);
+		g_object_set (task,
+					"freefloat", mrp_task_manager_calculate_summary_duration (manager, task, mrp_task_get_finish(task), t),
+					NULL);
+
 		was_critical = mrp_task_get_critical (task);
 		critical = (t1 == t2);
 
@@ -2935,3 +2949,9 @@ mrp_task_manager_calculate_summary_duration (MrpTaskManager *manager,
 	return duration;
 }
 
+mrptime mrp_task_manager_get_project_finish(MrpTaskManager *manager)
+{
+	MrpTaskManagerPriv *priv;
+	priv = manager->priv;
+	return mrp_task_get_finish (priv->root);
+}
diff --git a/libplanner/mrp-task-manager.h b/libplanner/mrp-task-manager.h
index 6b326fb..eb87b80 100644
--- a/libplanner/mrp-task-manager.h
+++ b/libplanner/mrp-task-manager.h
@@ -88,6 +88,6 @@ gint            mrp_task_manager_calculate_summary_duration  (MrpTaskManager
 void            mrp_task_manager_dump_task_tree       (MrpTaskManager       *manager);
 void            mrp_task_manager_dump_task_list       (MrpTaskManager       *manager);
 
-
+mrptime mrp_task_manager_get_project_finish(MrpTaskManager *manager);
 
 #endif /* __MRP_TASK_MANAGER_H__ */
diff --git a/libplanner/mrp-task.c b/libplanner/mrp-task.c
index 92a95b8..73176eb 100644
--- a/libplanner/mrp-task.c
+++ b/libplanner/mrp-task.c
@@ -1,4 +1,4 @@
- /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2004-2005 Imendio AB
  * Copyright (C) 2001-2002 CodeFactory AB
@@ -38,6 +38,8 @@ enum {
 	PROP_LATEST_START,
 	PROP_LATEST_FINISH,
 	PROP_DURATION,
+	PROP_SLACK,
+	PROP_FREEFLOAT,
 	PROP_WORK,
 	PROP_CRITICAL,
 	PROP_TYPE,
@@ -96,9 +98,12 @@ struct _MrpTaskPriv {
 	/* Calculated duration. */
 	gint              duration;
 
-	/* Slack. */
+	/* Calculated slack. */
 	gint              slack;
 
+	/* Calculated free float. */
+	gint              freefloat;
+
 	/* Calculated start and finish values. */
 	mrptime           start;
 	mrptime           finish;
@@ -341,6 +346,24 @@ task_class_init (MrpTaskClass *klass)
 
 	g_object_class_install_property (
 		object_class,
+		PROP_SLACK,
+		g_param_spec_int ("slack",
+				  "Slack",
+				  "Slack of the task",
+				  -1, G_MAXINT, 0,
+				  G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FREEFLOAT,
+		g_param_spec_int ("freefloat",
+				  "Free float",
+				  "Free float of the task",
+				  -1, G_MAXINT, 0,
+				  G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
 		PROP_WORK,
 		g_param_spec_int ("work",
 				  "Work",
@@ -497,6 +520,32 @@ task_set_property (GObject      *object,
 			priv->duration = i_val;
 
 			g_object_notify (object, "work");
+			g_object_notify (object, "slack");
+			g_object_notify (object, "freefloat");
+			changed = TRUE;
+
+			mrp_task_invalidate_cost (task);
+		}
+		break;
+
+	case PROP_SLACK:
+		i_val = g_value_get_int (value);
+
+		if (priv->slack != i_val) {
+			priv->slack = i_val;
+
+			changed = TRUE;
+
+			mrp_task_invalidate_cost (task);
+		}
+		break;
+
+	case PROP_FREEFLOAT:
+		i_val = g_value_get_int (value);
+
+		if (priv->freefloat != i_val) {
+			priv->freefloat = i_val;
+
 			changed = TRUE;
 
 			mrp_task_invalidate_cost (task);
@@ -622,6 +671,12 @@ task_get_property (GObject    *object,
 	case PROP_DURATION:
 		g_value_set_int (value, priv->duration);
 		break;
+	case PROP_SLACK:
+		g_value_set_int (value, priv->slack);
+		break;
+	case PROP_FREEFLOAT:
+		g_value_set_int (value, priv->freefloat);
+		break;
 	case PROP_WORK:
 		g_value_set_int (value, priv->work);
 		break;
@@ -916,7 +971,6 @@ const gchar *
 mrp_task_get_name (MrpTask *task)
 {
 	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
-
 	return task->priv->name;
 }
 
@@ -1544,6 +1598,38 @@ mrp_task_get_duration (MrpTask *task)
 }
 
 /**
+ * mrp_task_get_slack:
+ * @task: an #MrpTask
+ *
+ * Fetches the slack of @task.
+ *
+ * Return value: The slack of @task.
+ **/
+gint
+mrp_task_get_slack (MrpTask *task)
+{
+	g_return_val_if_fail (MRP_IS_TASK (task), 0);
+
+	return task->priv->slack;
+}
+
+/**
+ * mrp_task_get_freefloat:
+ * @task: an #MrpTask
+ *
+ * Fetches the free float of @task.
+ *
+ * Return value: The free float of @task.
+ **/
+gint
+mrp_task_get_freefloat (MrpTask *task)
+{
+	g_return_val_if_fail (MRP_IS_TASK (task), 0);
+
+	return task->priv->freefloat;
+}
+
+/**
  * mrp_task_get_work:
  * @task: an #MrpTask
  *
diff --git a/libplanner/mrp-task.h b/libplanner/mrp-task.h
index b9f6a83..9a63955 100644
--- a/libplanner/mrp-task.h
+++ b/libplanner/mrp-task.h
@@ -145,6 +145,7 @@ MrpTaskType      mrp_task_get_task_type             (MrpTask          *task);
 MrpTaskSched     mrp_task_get_sched                 (MrpTask          *task);
 gshort           mrp_task_get_percent_complete      (MrpTask          *task);
 gboolean         mrp_task_get_critical              (MrpTask          *task);
-
+gint	mrp_task_get_slack	(MrpTask	*task);
+gint	mrp_task_get_freefloat	(MrpTask	*task);
 
 #endif /* __MRP_TASK_H__ */
diff --git a/po/fr.po b/po/fr.po
index fca0760..e6a5ed2 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -1299,14 +1299,27 @@ msgid "Edit visible columns"
 msgstr "Edite les colonnes visibles"
 
 #: ../src/planner-gantt-view.c:185 ../src/planner-task-view.c:161
+#: ../src/planner-pert-view.c:172
 msgid "_Highlight Critical Tasks"
 msgstr "_Mettre en évidence les tâches critiques"
 
+#: ../src/planner-pert-view.c:169
+msgid "Change Display To Critical on _Top"
+msgstr "Afficher les tâches critiques au _dessus"
+
+#: ../src/planner-pert-view.c:171
+msgid "Change Display To Tasks _Flow"
+msgstr "Afficher les tâches en _flux de tâches"
+
 #: ../src/planner-gantt-view.c:188
 msgid "_Show Guide Lines"
 msgstr "Afficher les lignes du guide"
 
-#: ../src/planner-gantt-view.c:191 ../src/planner-task-view.c:163
+#: ../src/planner-gantt-view.c:193
+msgid "_Display Slip Line"
+msgstr "Afficher la ligne de _glissade"
+
+#: ../src/planner-gantt-view.c:196 ../src/planner-task-view.c:163
 msgid "_Nonstandard Days"
 msgstr "Jours _non standards"
 
@@ -1321,6 +1334,14 @@ msgstr "Gantt"
 msgid "_Gantt Chart"
 msgstr "Diagramme de _Gantt"
 
+#: ../src/planner-pert-view.c:293
+msgid "PERT"
+msgstr "PERT"
+
+#: ../src/planner-pert-view.c:299
+msgid "_PERT Chart"
+msgstr "Diagramme _PERT"
+
 #. i18n: WBS is short for work breakdown structure, and is a
 #. * project management term. You might want to leave it
 #. * untranslated unless there is a localized term for it.
@@ -1334,8 +1355,13 @@ msgid "WBS"
 msgstr "TPÉ"
 
 #: ../src/planner-gantt-view.c:591 ../src/planner-task-view.c:338
+#: ../src/planner-pert-chart.c:1824
 msgid "Start"
-msgstr "Démarré"
+msgstr "Début"
+
+#: ../src/planner-pert-chart.c:1882
+msgid "End"
+msgstr "Fin"
 
 #: ../src/planner-gantt-view.c:592 ../src/planner-task-view.c:339
 msgid "Finish"
@@ -1343,7 +1369,11 @@ msgstr "Terminé"
 
 #: ../src/planner-gantt-view.c:595 ../src/planner-task-view.c:342
 msgid "Slack"
-msgstr "Latitude"
+msgstr "Marge totale"
+
+#: ../src/planner-gantt-view.c:595 ../src/planner-task-view.c:342
+msgid "Free float"
+msgstr "Marge libre"
 
 #: ../src/planner-gantt-view.c:597 ../src/planner-task-view.c:344
 msgid "Assigned to"
@@ -1967,6 +1997,7 @@ msgid "Reset task constraint"
 msgstr "Réinitialiser les contraintes de la tâche"
 
 #: ../src/planner-task-tree.c:1497 ../src/planner-task-tree.c:1654
+#: ../src/planner-pert-node.c:961
 msgid "N/A"
 msgstr "N/D"
 
diff --git a/src/Makefile.am b/src/Makefile.am
index c6b0484..95e9cad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -119,6 +119,7 @@ libplannerapp_la_SOURCES = 			\
 	planner-working-time-dialog.c	\
 	planner-working-time-dialog.h	\
 	$(gantt_task_source)		\
+	$(pert_task_source)		\
 	$(task_source)			\
 	$(resource_source)		\
 	$(usage_source)
@@ -163,6 +164,17 @@ usage_source = 				\
 	planner-usage-tree.c		\
 	planner-usage-tree.h
 
+pert_task_source = 			\
+	planner-pert-chart.c		\
+	planner-pert-chart.h		\
+	planner-pert-print.c		\
+	planner-pert-print.h		\
+	planner-relation-pert-arrow.c	\
+	planner-relation-pert-arrow.h	\
+	planner-pert-node.c		\
+	planner-pert-node.h		\
+	planner-pert-view.c		\
+	planner-pert-view.h
 
 #
 # Plugins
diff --git a/src/Makefile.win32 b/src/Makefile.win32
index b2f0766..ae055a0 100644
--- a/src/Makefile.win32
+++ b/src/Makefile.win32
@@ -87,6 +87,7 @@ C_SRC = \
 	planner-window.c		\
 	planner-working-time-dialog.c	\
 	$(gantt_task_source)		\
+	$(pert_task_source)		\
 	$(task_source)			\
 	$(resource_source)		\
 	$(usage_source)
@@ -116,6 +117,13 @@ usage_source = 				\
 	planner-usage-row.c		\
 	planner-usage-tree.c
 
+pert_task_source = 			\
+	planner-pert-chart.c		\
+	planner-pert-print.c		\
+	planner-relation-pert-arrow.c	\
+	planner-pert-node.c		\
+	planner-pert-view.c
+
 #
 # Plugins
 #
diff --git a/src/planner-gantt-chart.c b/src/planner-gantt-chart.c
index a050ce8..bcd9c1d 100644
--- a/src/planner-gantt-chart.c
+++ b/src/planner-gantt-chart.c
@@ -30,6 +30,7 @@
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <libplanner/mrp-task.h>
+#include <libgnomecanvas/gnome-canvas.h>
 #include "planner-marshal.h"
 #include "planner-conf.h"
 #include "planner-gantt-chart.h"
@@ -56,6 +57,7 @@ static gdouble f = 1.0;
 #define CRITICAL_PATH_KEY  "/views/gantt_view/highlight_critical_path"
 #define NOSTDDAYS_PATH_KEY "/views/gantt_view/display_nonstandard_days"
 #define GUIDELINES_PATH_KEY "/views/gantt_view/show_guidelines"
+#define SLIPLINE_PATH_KEY "/views/gantt_view/display_slipline"
 
 
 typedef struct _TreeNode TreeNode;
@@ -112,10 +114,14 @@ struct _PlannerGanttChartPriv {
 	/* GuideLines . */
 	gboolean         guidelines;
 
+	/* SlipLine . */
+	gboolean		 display_slipline;
+
 	/* Keep a list of signal connection ids, so we can remove them
 	 * easily.
 	 */
 	GList           *signal_ids;
+	GnomeCanvasItem *slipline;
 };
 
 /* Properties */
@@ -218,6 +224,18 @@ static void        gantt_chart_tree_node_remove         (PlannerGanttChart  *cha
 static void        gantt_chart_tree_traverse            (TreeNode           *node,
 							 TreeFunc            func,
 							 gpointer            data);
+static gboolean gantt_chart_button_press_event(GtkWidget  *widget, GdkEvent *event);
+
+static gint gantt_find_end_working_time(PlannerGanttChart *chart, MrpTask *task);
+
+static GList* gantt_chart_find_tasks_do (PlannerGanttChart *chart,
+			   GtkTreeIter       *iter,
+			   GList        *list,
+			   gint		sx1,
+			   gint		sx2);
+
+void gantt_chart_draw_slipline(PlannerGanttChart* chart);
+void gantt_destroy_slipline(PlannerGanttChart* chart);
 
 static guint         signals[LAST_SIGNAL];
 static GtkVBoxClass *parent_class = NULL;
@@ -274,6 +292,7 @@ gantt_chart_class_init (PlannerGanttChartClass *class)
 	widget_class->map           = gantt_chart_map;
 	widget_class->unrealize     = gantt_chart_unrealize;
 	widget_class->size_allocate = gantt_chart_size_allocate;
+	widget_class->button_press_event = gantt_chart_button_press_event;
 
 	class->set_scroll_adjustments = gantt_chart_set_adjustments;
 
@@ -398,12 +417,21 @@ gantt_chart_init (PlannerGanttChart *chart)
 							  NULL);
 	priv->nonstandard_days   = planner_conf_get_bool (NOSTDDAYS_PATH_KEY,
 							  NULL);
+
 	priv->guidelines = planner_conf_get_bool (GUIDELINES_PATH_KEY, NULL);
 
+	priv->display_slipline = planner_conf_get_bool (SLIPLINE_PATH_KEY, NULL);
+
 	g_object_set (G_OBJECT(chart->priv->background),
 			"show_guidelines",
 			priv->guidelines,
 			NULL);
+
+	g_object_set (G_OBJECT(chart->priv->header),
+				"display_slipline",
+				priv->display_slipline,
+				NULL);
+	priv->slipline = NULL;
 }
 
 static void
@@ -1032,6 +1060,8 @@ gantt_chart_reflow_idle (PlannerGanttChart *chart)
 	priv->height_changed = FALSE;
 	priv->reflow_idle_id = 0;
 
+	gantt_chart_draw_slipline(chart);
+
 	return FALSE;
 }
 
@@ -1926,6 +1956,8 @@ planner_gantt_chart_status_updated (PlannerGanttChart *chart,
 	g_return_if_fail (PLANNER_IS_GANTT_CHART (chart));
 
 	g_signal_emit (chart, signals[STATUS_UPDATED], 0, message);
+
+	gantt_chart_draw_slipline(chart);
 }
 
 void
@@ -1984,6 +2016,34 @@ planner_gantt_chart_set_show_guidelines (PlannerGanttChart *chart,
 	planner_conf_set_bool (GUIDELINES_PATH_KEY, state, NULL);
 }
 
+void
+planner_gantt_chart_set_display_slipline (PlannerGanttChart *chart,
+				    gboolean           state)
+{
+	PlannerGanttChartPriv *priv;
+
+	g_return_if_fail (PLANNER_IS_GANTT_CHART (chart));
+
+	priv = chart->priv;
+
+	if (priv->display_slipline == state)
+	{
+		return;
+	}
+
+	priv->display_slipline = state;
+
+	g_object_set (G_OBJECT(chart->priv->header),
+		      "display_slipline",
+		      state,
+		      NULL);
+
+	gtk_widget_queue_draw (GTK_WIDGET (priv->canvas));
+	gantt_chart_draw_slipline(chart);
+
+	planner_conf_set_bool (SLIPLINE_PATH_KEY, state, NULL);
+}
+
 gboolean
 planner_gantt_chart_get_show_guidelines (PlannerGanttChart *chart)
 {
@@ -1993,6 +2053,14 @@ planner_gantt_chart_get_show_guidelines (PlannerGanttChart *chart)
 }
 
 gboolean
+planner_gantt_chart_get_display_slipline (PlannerGanttChart *chart)
+{
+	g_return_val_if_fail (PLANNER_IS_GANTT_CHART (chart), FALSE);
+
+	return chart->priv->display_slipline;
+}
+
+gboolean
 planner_gantt_chart_get_highlight_critical_tasks (PlannerGanttChart *chart)
 {
 	g_return_val_if_fail (PLANNER_IS_GANTT_CHART (chart), FALSE);
@@ -2028,3 +2096,231 @@ planner_gantt_chart_get_nonstandard_days (PlannerGanttChart *chart)
 
 	return chart->priv->nonstandard_days;
 }
+
+gint gantt_find_end_working_time(PlannerGanttChart *chart, MrpTask *task)
+{
+	MrpProject          *project;
+	MrpDay              *day;
+	MrpCalendar         *calendar;
+	mrptime		ival_start, ival_end, t, t1, t2;
+	GList               *intervals, *l;
+	gint                x1, x2, level;
+	MrpTimeUnit	minor_unit, major_unit;
+	gboolean	majunit;
+
+	PlannerGanttChartPriv *priv;
+	priv = chart->priv;
+
+	project = mrp_object_get_project (MRP_OBJECT (task));
+	calendar = mrp_project_get_calendar (project);
+
+	g_object_get (priv->header,
+				  "sliplinex1", &x1,
+				  "sliplinex2", &x2,
+				  "sliplinemajorunit", &majunit,
+				  NULL);
+
+	level = planner_scale_clamp_zoom (priv->zoom);
+	minor_unit = planner_scale_conf[level].minor_unit;
+	major_unit = planner_scale_conf[level].major_unit;
+	int end = floor (x2/ SCALE(priv->zoom));
+
+	if(majunit)
+	{
+		t2 = mrp_time_align_next (end, major_unit);
+		t1 = mrp_time_align_prev (end, major_unit);
+	}
+	else
+	{
+		t2 = mrp_time_align_next (end, minor_unit);
+		t1 = mrp_time_align_prev (end, minor_unit);
+		/*g_print ("%s (%s)\n",
+					 mrp_time_format ("%H:%M", t1),
+					 mrp_time_format ("%a %e %b", t1));*/
+	}
+
+	t = mrp_time_align_day (end);
+	day = mrp_calendar_get_day (calendar, t, TRUE);
+	intervals = mrp_calendar_day_get_intervals (calendar, day, TRUE);
+
+	while(g_list_length(intervals)==0 && t > t1)
+	{
+		t = mrp_time_align_prev (t-1, MRP_TIME_UNIT_DAY);
+		day = mrp_calendar_get_day (calendar, t, TRUE);
+		intervals = mrp_calendar_day_get_intervals (calendar, day, TRUE);
+	}
+
+	if(g_list_length(intervals) != 0)
+	{
+		ival_end = t1;
+		for (l = intervals; l && ival_end < t2; l = l->next)
+		{
+			mrp_interval_get_absolute (l->data, t, &ival_start, &ival_end);
+		}
+		return floor (ival_end * SCALE(priv->zoom) + 0.5);
+	}
+	else return -1;
+}
+
+GList*
+gantt_chart_find_tasks_do (PlannerGanttChart *chart,
+			   GtkTreeIter       *iter,
+			   GList        *list,
+			   gint			sx1,
+			   gint			sx2)
+{
+	PlannerGanttChartPriv *priv;
+	GtkTreeIter            child;
+	GtkTreePath           *path;
+	TreeNode              *node;
+	PlannerGanttRow *row;
+	gdouble x1, y1, x2, y2;
+
+	priv = chart->priv;
+
+	do {
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->model), iter);
+		node = gantt_chart_tree_node_at_path((TreeNode *)priv->tree, path);
+		row = PLANNER_GANTT_ROW (node->item);
+		planner_gantt_row_get_geometry(row, &x1, &y1, &x2, &y2);
+
+		if(sx1 < x2 && sx2 > x1)
+		{
+			list = g_list_append(list, node);
+		}
+
+		gtk_tree_path_free (path);
+
+		if (gtk_tree_model_iter_children (priv->model, &child, iter)) {
+			list = gantt_chart_find_tasks_do (chart, &child, list, sx1, sx2);
+		}
+	} while (gtk_tree_model_iter_next (priv->model, iter));
+
+	return list;
+}
+
+void gantt_destroy_slipline(PlannerGanttChart* chart)
+{
+	PlannerGanttChartPriv *priv;
+	priv = chart->priv;
+	if(priv->slipline != NULL)
+	{
+		gtk_object_destroy (GTK_OBJECT (priv->slipline));
+		priv->slipline = NULL;
+	}
+}
+
+void gantt_chart_draw_slipline(PlannerGanttChart* chart)
+{
+	PlannerGanttChartPriv *priv;
+	gint sx1, sx2, xendworking;
+	priv = chart->priv;
+
+	g_object_get (priv->header,
+							"sliplinex1", &sx1,
+							"sliplinex2", &sx2,
+							NULL);
+
+	if(sx1 != -1 && priv->display_slipline)
+	{
+		GnomeCanvasPoints *gpoints;
+		GtkTreeIter iter;
+		GList  *list, *l;
+		gint i;
+		gdouble x1, y1, x2, y2, percent;
+		gdouble hx1, hy1, hx2, hy2;
+		TreeNode *node;
+		gdouble widthunits;
+		gboolean unit;
+
+		i=2;
+		list = l = NULL;
+		gtk_tree_model_get_iter_first(GTK_TREE_MODEL(priv->model), &iter);
+		list = gantt_chart_find_tasks_do (chart, &iter, list, sx1, sx2);
+
+		//exists tasks on the line
+		if(g_list_length(list) != 0)
+		{
+			node = list->data;
+			xendworking = gantt_find_end_working_time(chart, node->task);
+			if(xendworking != -1) //working day
+			{
+				gpoints = gnome_canvas_points_new (g_list_length(list)*3+2);
+				//first point near the header
+				gpoints->coords[0] = xendworking;
+				gpoints->coords[1] = 0;
+				for (l = list; l; l = l->next)
+				{
+					node = l->data;
+					planner_gantt_row_get_geometry(PLANNER_GANTT_ROW(node->item), &x1, &y1, &x2, &y2);
+					percent = mrp_task_get_percent_complete(node->task);
+
+					gpoints->coords[i] = xendworking;
+					gpoints->coords[i+1] = y1;
+					gpoints->coords[i+2] = x1 + ((x2-x1)*(percent/100.0));
+					gpoints->coords[i+3] = y1 + ((y2-y1) /2.);
+					gpoints->coords[i+4] = xendworking;
+					gpoints->coords[i+5] = y2;
+					i+=6;
+				}
+				//last point at the bottom of the window
+				gantt_chart_get_visible_region(chart, &hx1, &hy1, &hx2, &hy2);
+				gpoints->coords[i] = xendworking;
+				gpoints->coords[i+1] = hy2 + priv->row_height;
+
+				if(priv->slipline != NULL)
+				{
+					gtk_object_destroy (GTK_OBJECT (priv->slipline));
+					priv->slipline = NULL;
+				}
+
+				g_object_get (priv->header,
+									"sliplinemajorunit",
+									&unit,
+									NULL);
+
+				if(unit)
+				{
+					widthunits = 2.0;
+				}
+				else
+				{
+					widthunits = 1.0;
+				}
+				priv->slipline = gnome_canvas_item_new(gnome_canvas_root (priv->canvas),
+															gnome_canvas_line_get_type(),
+															"points", gpoints,
+															"fill_color", "black",
+															"width_units", widthunits,
+															"smooth", TRUE,
+															NULL);
+
+				//a position above the background but all the way below the rows
+				gnome_canvas_item_lower_to_bottom(priv->slipline);
+				gnome_canvas_item_raise(priv->slipline, 1);
+				gnome_canvas_points_free(gpoints);
+			}
+			else
+			{
+				gantt_destroy_slipline(chart);
+			}
+
+		}
+		else
+		{
+			gantt_destroy_slipline(chart);
+		}
+	}
+	else
+	{
+		gantt_destroy_slipline(chart);
+	}
+}
+
+static gboolean gantt_chart_button_press_event(GtkWidget *widget, GdkEvent *event)
+{
+	PlannerGanttChart     *chart;
+	chart = PLANNER_GANTT_CHART (widget);
+	gantt_chart_draw_slipline(chart);
+	return TRUE;
+}
diff --git a/src/planner-gantt-chart.h b/src/planner-gantt-chart.h
index 93244e5..f1191dd 100644
--- a/src/planner-gantt-chart.h
+++ b/src/planner-gantt-chart.h
@@ -98,6 +98,13 @@ planner_gantt_chart_set_show_guidelines               (PlannerGanttChart  *chart
 gboolean
 planner_gantt_chart_get_show_guidelines              (PlannerGanttChart *chart);
 
+
+void planner_gantt_chart_set_display_slipline         (PlannerGanttChart *chart,
+							gboolean       state);
+
+gboolean
+planner_gantt_chart_get_display_slipline              (PlannerGanttChart *chart);
+
 void
 planner_gantt_chart_set_nonstandard_days             (PlannerGanttChart *chart,
 							   gboolean           state);
diff --git a/src/planner-gantt-header.c b/src/planner-gantt-header.c
index a88486e..3c6bb57 100644
--- a/src/planner-gantt-header.c
+++ b/src/planner-gantt-header.c
@@ -55,6 +55,10 @@ struct _PlannerGanttHeaderPriv {
 	gdouble             x2;
 
 	gchar              *date_hint;
+
+	gboolean	display_slipline;
+	gint		sliplinex1, sliplinex2;
+	gboolean	sliplinemajorunit;
 };
 
 /* Properties */
@@ -64,7 +68,11 @@ enum {
 	PROP_X1,
 	PROP_X2,
 	PROP_SCALE,
-	PROP_ZOOM
+	PROP_ZOOM,
+	PROP_DISPLAY_SLIPLINE,
+	PROP_SLIPLINEX1,
+	PROP_SLIPLINEX2,
+	PROP_SLIPLINEMAJORUNIT
 };
 
 enum {
@@ -95,14 +103,13 @@ static gboolean gantt_header_motion_notify_event (GtkWidget               *widge
 						  GdkEventMotion          *event);
 static gboolean gantt_header_leave_notify_event  (GtkWidget	          *widget,
 						  GdkEventCrossing        *event);
+static gboolean gantt_header_button_press_event(GtkWidget *widget, GdkEvent *event);
 static void     gantt_header_set_adjustments     (PlannerGanttHeader      *header,
 						  GtkAdjustment           *hadj,
 						  GtkAdjustment           *vadj);
 static void     gantt_header_adjustment_changed  (GtkAdjustment           *adjustment,
 						  PlannerGanttHeader      *header);
 
-
-
 static GtkWidgetClass *parent_class = NULL;
 static guint           signals[LAST_SIGNAL];
 
@@ -165,8 +172,8 @@ gantt_header_class_init (PlannerGanttHeaderClass *class)
 	widget_class->size_allocate = gantt_header_size_allocate;
 	widget_class->expose_event = gantt_header_expose_event;
 	widget_class->leave_notify_event = gantt_header_leave_notify_event;
-
 	widget_class->motion_notify_event = gantt_header_motion_notify_event;
+	widget_class->button_press_event = gantt_header_button_press_event;
 
 	class->set_scroll_adjustments = gantt_header_set_adjustments;
 
@@ -226,6 +233,42 @@ gantt_header_class_init (PlannerGanttHeaderClass *class)
 				     -G_MAXDOUBLE, G_MAXDOUBLE, 7,
 				     G_PARAM_WRITABLE));
 
+	g_object_class_install_property (
+			o_class,
+			PROP_SLIPLINEX1,
+			g_param_spec_int ("sliplinex1",
+					     NULL,
+					     NULL,
+					     -1, G_MAXINT, -1,
+					     G_PARAM_READABLE));
+
+	g_object_class_install_property (
+				o_class,
+				PROP_SLIPLINEX2,
+				g_param_spec_int ("sliplinex2",
+						     NULL,
+						     NULL,
+						     -1, G_MAXINT, -1,
+						     G_PARAM_READABLE));
+
+	g_object_class_install_property (
+				o_class,
+				PROP_DISPLAY_SLIPLINE,
+				g_param_spec_boolean ("display-slipline",
+						      NULL,
+						      NULL,
+						      FALSE,
+						      G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+					o_class,
+					PROP_SLIPLINEMAJORUNIT,
+					g_param_spec_boolean ("sliplinemajorunit",
+							      NULL,
+							      NULL,
+							      FALSE,
+							      G_PARAM_READWRITE));
+
 	signals[DATE_HINT_CHANGED] =
 		g_signal_new ("date-hint-changed",
 			      G_TYPE_FROM_CLASS (class),
@@ -260,6 +303,11 @@ gantt_header_init (PlannerGanttHeader *header)
 
 	priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (header),
 						       NULL);
+
+	priv->display_slipline = FALSE;
+	priv->sliplinemajorunit = FALSE;
+	priv->sliplinex1 = -1;
+	priv->sliplinex2 = -1;
 }
 
 static void
@@ -326,6 +374,9 @@ gantt_header_set_property (GObject      *object,
 	case PROP_ZOOM:
 		gantt_header_set_zoom (header, g_value_get_double (value));
 		break;
+	case PROP_DISPLAY_SLIPLINE:
+		priv->display_slipline = g_value_get_boolean (value);
+		break;
 	default:
 		break;
 	}
@@ -364,10 +415,20 @@ gantt_header_get_property (GObject    *object,
 			   GParamSpec *pspec)
 {
 	PlannerGanttHeader *header;
-
+	PlannerGanttHeaderPriv *priv;
 	header = PLANNER_GANTT_HEADER (object);
+	priv = header->priv;
 
 	switch (prop_id) {
+	case PROP_SLIPLINEX1:
+		g_value_set_int (value, priv->sliplinex1);
+		break;
+	case PROP_SLIPLINEX2:
+			g_value_set_int (value, priv->sliplinex2);
+			break;
+	case PROP_SLIPLINEMAJORUNIT:
+		g_value_set_boolean (value, priv->sliplinemajorunit);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -702,6 +763,61 @@ gantt_header_leave_notify_event (GtkWidget	  *widget,
 	return FALSE;
 }
 
+static gboolean gantt_header_button_press_event(GtkWidget *widget, GdkEvent *event)
+{
+	gint					x, lastx = 0;
+	mrptime                 t0, t;
+	gboolean				found;
+
+	x = -1;
+	found = FALSE;
+
+	PlannerGanttHeader     *header;
+	PlannerGanttHeaderPriv *priv;
+	header = PLANNER_GANTT_HEADER (widget);
+	priv = header->priv;
+
+	if(priv->display_slipline)
+	{
+
+		t0 = floor (priv->x1 / priv->hscale + 0.5);
+
+		if(event->button.y >= 0 && event->button.y < priv->height/2)
+		{
+			t = mrp_time_align_prev (t0, priv->major_unit);
+			while (!found) //start from t1 until the x is found
+			{
+				t = mrp_time_align_next (t, priv->major_unit);
+				lastx = x;
+				x = floor (t * priv->hscale) - priv->x1;
+				if(event->button.x <= x)
+				{
+					found = TRUE;
+					priv->sliplinemajorunit = TRUE;
+				}
+			}
+		}
+		else if(event->button.y >= priv->height/2 && event->button.y < priv->height)
+		{
+			t = mrp_time_align_prev (t0, priv->minor_unit);
+			while (!found) //start from t1 until the x is found
+			{
+				t = mrp_time_align_next (t, priv->minor_unit);
+				lastx = x;
+				x = floor (t * priv->hscale) - priv->x1;
+				if(event->button.x <= x)
+				{
+					found = TRUE;
+					priv->sliplinemajorunit = FALSE;
+				}
+			}
+		}
+		priv->sliplinex1 = priv->x1 + lastx;
+		priv->sliplinex2 = priv->x1 + x;
+	}
+	return FALSE;
+}
+
 /* Callbacks */
 static void
 gantt_header_set_adjustments (PlannerGanttHeader *header,
@@ -748,4 +864,3 @@ planner_gantt_header_new (void)
 {
 	return g_object_new (PLANNER_TYPE_GANTT_HEADER, NULL);
 }
-
diff --git a/src/planner-gantt-model.c b/src/planner-gantt-model.c
index 42ed928..8122f9a 100644
--- a/src/planner-gantt-model.c
+++ b/src/planner-gantt-model.c
@@ -644,6 +644,8 @@ gantt_model_get_column_type (GtkTreeModel *tree_model,
 		return G_TYPE_INT;
 	case COL_SLACK:
 		return G_TYPE_INT;
+	case COL_FREEFLOAT:
+		return G_TYPE_INT;
 	case COL_WEIGHT:
 		return G_TYPE_INT;
 	case COL_EDITABLE:
@@ -790,9 +792,6 @@ gantt_model_get_value (GtkTreeModel *tree_model,
 {
 	GNode       *node;
 	MrpTask     *task;
-	MrpProject  *project;
-	mrptime      t1, t2;
-	gint         duration;
 	MrpTaskType  type;
 	const gchar *name;
 	const gchar *cached_str;
@@ -842,22 +841,13 @@ gantt_model_get_value (GtkTreeModel *tree_model,
 		break;
 
 	case COL_SLACK:
-		t1 = mrp_task_get_finish (task);
-		t2 = mrp_task_get_latest_finish (task);
-		project = mrp_object_get_project (MRP_OBJECT (task));
-
-		/* We don't support negative slack */
-		if (t2 >= t1) {
-			/* use summary duration calc since we want to use
-			 * the project calendar and ignore resource assignments */
-			duration = mrp_project_calculate_summary_duration (
-				project, task, t1, t2);
-		} else {
-			duration = 0;
-		}
+		g_value_init (value, G_TYPE_INT);
+		g_value_set_int (value, mrp_task_get_slack(task));
+		break;
 
+	case COL_FREEFLOAT:
 		g_value_init (value, G_TYPE_INT);
-		g_value_set_int (value, duration);
+		g_value_set_int (value, mrp_task_get_freefloat(task));
 		break;
 
 	case COL_WEIGHT:
@@ -1222,6 +1212,3 @@ value_cache_clear_cache_wbs (PlannerGanttModel *model)
 {
 	model->priv->wbs_stamp++;
 }
-
-
-
diff --git a/src/planner-gantt-model.h b/src/planner-gantt-model.h
index b381959..14c0e0d 100644
--- a/src/planner-gantt-model.h
+++ b/src/planner-gantt-model.h
@@ -55,6 +55,7 @@ enum {
 	COL_DURATION,
 	COL_WORK,
 	COL_SLACK,
+	COL_FREEFLOAT,
 	COL_WEIGHT,
 	COL_EDITABLE,
 	COL_TASK,
@@ -75,7 +76,5 @@ MrpTask      *     planner_gantt_model_get_task               (PlannerGanttModel
 							       GtkTreeIter       *iter);
 MrpTask           *planner_gantt_model_get_task_from_path     (PlannerGanttModel *model,
 							       GtkTreePath       *path);
-
-
 #endif /* __PLANNER_GANTT_MODEL_H__ */
 
diff --git a/src/planner-gantt-view.c b/src/planner-gantt-view.c
index b1e08f0..6040654 100644
--- a/src/planner-gantt-view.c
+++ b/src/planner-gantt-view.c
@@ -87,6 +87,8 @@ static void          gantt_view_highlight_critical_cb     (GtkAction         *ac
 							   gpointer           data);
 static void          gantt_view_show_guidelines_cb	  (GtkAction         *action,
 							   gpointer           data);
+static void          gantt_view_display_slipline_cb	  (GtkAction         *action,
+							   gpointer           data);
 static void          gantt_view_nonstandard_days_cb       (GtkAction         *action,
 							   gpointer           data);
 static void          gantt_view_edit_columns_cb           (GtkAction         *action,
@@ -188,6 +190,9 @@ static const GtkToggleActionEntry toggle_entries[] = {
 	{ "ShowGuideLines", NULL, N_("_Show Guide Lines"),
 	  NULL, NULL,
 	  G_CALLBACK (gantt_view_show_guidelines_cb), FALSE },
+	{ "DisplaySlipLine", NULL, N_("_Display Slip Line"),
+	NULL, NULL,
+	G_CALLBACK (gantt_view_display_slipline_cb), FALSE },
 	{ "NonstandardDays", NULL, N_("_Nonstandard Days"),
 	  NULL, NULL,
 	  G_CALLBACK (gantt_view_nonstandard_days_cb), FALSE }
@@ -256,7 +261,7 @@ static void
 gantt_view_activate (PlannerView *view)
 {
 	PlannerGanttViewPriv *priv;
-	gboolean              show_critical, show_nostd_days, show_guidelines;
+	gboolean              show_critical, show_nostd_days, show_guidelines, display_slipline;
 	gchar                *filename;
 
 	priv = PLANNER_GANTT_VIEW (view)->priv;
@@ -291,6 +296,9 @@ gantt_view_activate (PlannerView *view)
 	show_guidelines = planner_gantt_chart_get_show_guidelines (
 			PLANNER_GANTT_CHART (priv->gantt));
 
+	display_slipline = planner_gantt_chart_get_display_slipline (
+				PLANNER_GANTT_CHART (priv->gantt));
+
 	planner_task_tree_set_highlight_critical (PLANNER_TASK_TREE (priv->tree),
 						  show_critical);
 
@@ -309,6 +317,10 @@ gantt_view_activate (PlannerView *view)
 		GTK_TOGGLE_ACTION (gtk_action_group_get_action (priv->actions, "ShowGuideLines")),
 		show_guidelines);
 
+	gtk_toggle_action_set_active (
+			GTK_TOGGLE_ACTION (gtk_action_group_get_action (priv->actions, "DisplaySlipLine")),
+			display_slipline);
+
 	gantt_view_selection_changed_cb (PLANNER_TASK_TREE (priv->tree),
 					 PLANNER_GANTT_VIEW (view));
 	gantt_view_update_zoom_sensitivity (PLANNER_GANTT_VIEW (view));
@@ -593,6 +605,7 @@ gantt_view_create_widget (PlannerGanttView *view)
 				      COL_WORK, _("Work"),
 				      COL_DURATION, _("Duration"),
 				      COL_SLACK, _("Slack"),
+				      COL_FREEFLOAT, _("Free float"),
 				      COL_COST, _("Cost"),
 				      COL_ASSIGNED_TO, _("Assigned to"),
 				      /* i18n: The string "% Complete" will be used in the header
@@ -1170,6 +1183,24 @@ gantt_view_show_guidelines_cb (GtkAction *action,
 }
 
 static void
+gantt_view_display_slipline_cb (GtkAction *action,
+				  gpointer   data)
+{
+	PlannerGanttView     *view;
+	PlannerGanttViewPriv *priv;
+	gboolean              state;
+
+	view = PLANNER_GANTT_VIEW (data);
+	priv = view->priv;
+
+	state = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION(action));
+
+	planner_gantt_chart_set_display_slipline (
+		PLANNER_GANTT_CHART (priv->gantt),
+		state);
+}
+
+static void
 gantt_view_update_zoom_sensitivity (PlannerGanttView *view)
 {
 	gboolean in, out;
diff --git a/src/planner-pert-chart.c b/src/planner-pert-chart.c
new file mode 100644
index 0000000..63d8bf3
--- /dev/null
+++ b/src/planner-pert-chart.c
@@ -0,0 +1,2294 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004      Imendio AB
+ * Copyright (C) 2001-2002 CodeFactory AB
+ * Copyright (C) 2001-2002 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2001-2002 Mikael Hallendal <micke@imendio.com>
+ * Copyright (C) 2004      Alvaro del Castillo <acs@barrapunto.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <libplanner/mrp-task.h>
+#include "planner-marshal.h"
+#include "planner-conf.h"
+#include "planner-pert-chart.h"
+#include "planner-pert-node.h"
+#include "planner-gantt-model.h"
+#include "planner-relation-pert-arrow.h"
+#include "planner-scale-utils.h"
+
+/* Padding to the left and right of the contents of the pert chart. */
+#define PADDING 10.0
+
+/* Font width factor. */
+static gdouble f = 1.0;
+
+#define CRITICAL_PATH_KEY  "/views/pert_view/highlight_critical_path"
+#define MODE_PATH_KEY  "/views/pert_view/display_mode_path"
+
+typedef struct _TreeNode TreeNode;
+typedef void (*TreeFunc) (TreeNode *node, gpointer data);
+
+struct _TreeNode {
+	MrpTask          *task;
+	GnomeCanvasItem  *item;
+	TreeNode         *parent;
+	TreeNode        **children;
+	guint             num_children;
+	guint             expanded : 1;
+};
+
+typedef struct {
+	gulong   id;
+	gpointer instance;
+} ConnectData;
+
+struct _PlannerPertChartPriv {
+	GnomeCanvas     *canvas;
+
+	GtkAdjustment   *hadjustment;
+	GtkAdjustment   *vadjustment;
+
+	GtkTreeModel    *model;
+	TreeNode        *tree;
+	PlannerTaskTree *view;
+
+	GHashTable      *relation_hash;
+
+	gint             row_height;
+
+	/* Cached height. */
+	gdouble          height;
+
+	mrptime          last_time;
+	guint		width;
+
+	gboolean         height_changed;
+	guint            reflow_idle_id;
+
+	/* Critical path. */
+	gboolean         highlight_critical;
+
+	/* Keep a list of signal connection ids, so we can remove them
+	 * easily.
+	 */
+	GList           *signal_ids;
+	GList			*max_heights;
+
+	gint			display_mode;
+	GList			*end_list, *start_list;
+	GList			*end_arrow_list, *start_arrow_list;
+	PlannerPertNode *start, *end;
+	gint			hscale, vscale, node_height, node_width;
+};
+
+/* Properties */
+enum {
+	PROP_0,
+	PROP_ROW_HEIGHT,
+	PROP_MODEL
+};
+
+enum {
+	STATUS_UPDATED,
+	RESOURCE_CLICKED,
+	LAST_SIGNAL
+};
+
+static void        pert_chart_class_init               (PlannerPertChartClass  *klass);
+static void        pert_chart_init                     (PlannerPertChart       *chart);
+static void        pert_chart_finalize                 (GObject            *object);
+static void        pert_chart_set_property             (GObject            *object,
+							 guint               prop_id,
+							 const GValue       *value,
+							 GParamSpec         *pspec);
+static void        pert_chart_get_property             (GObject            *object,
+							 guint               prop_id,
+							 GValue             *value,
+							 GParamSpec         *pspec);
+static void        pert_chart_destroy                  (GtkObject          *object);
+static void        pert_chart_style_set                (GtkWidget          *widget,
+							 GtkStyle           *prev_style);
+static void        pert_chart_realize                  (GtkWidget          *widget);
+static void        pert_chart_unrealize                (GtkWidget          *widget);
+static void        pert_chart_map                      (GtkWidget          *widget);
+static void        pert_chart_size_allocate            (GtkWidget          *widget,
+							 GtkAllocation      *allocation);
+static void        pert_chart_set_adjustments          (PlannerPertChart       *chart,
+							 GtkAdjustment      *hadj,
+							 GtkAdjustment      *vadj);
+static void        pert_chart_node_changed              (GtkTreeModel       *model,
+							 GtkTreePath        *path,
+							 GtkTreeIter        *iter,
+							 gpointer            data);
+static void        pert_chart_node_inserted             (GtkTreeModel       *model,
+							 GtkTreePath        *path,
+							 GtkTreeIter        *iter,
+							 gpointer            data);
+static void        pert_chart_node_deleted              (GtkTreeModel       *model,
+							 GtkTreePath        *path,
+							 gpointer            data);
+static void        pert_chart_rows_reordered           (GtkTreeModel       *model,
+							 GtkTreePath        *parent,
+							 GtkTreeIter        *iter,
+							 gint               *new_order,
+							 gpointer            data);
+static void        pert_chart_relation_added           (MrpTask            *task,
+							 MrpRelation        *relation,
+							 PlannerPertChart  *chart);
+static void        pert_chart_relation_removed         (MrpTask            *task,
+							 MrpRelation        *relation,
+							 PlannerPertChart  *chart);
+static void        pert_chart_task_removed             (MrpTask            *task,
+							 PlannerPertChart  *chart);
+static void		   pert_chart_task_position_changed    (MrpTask		*task,
+							 GParamSpec *pspec,
+							 PlannerPertChart *chart);
+static void        pert_chart_build_tree               (PlannerPertChart  *chart);
+static void        pert_chart_reflow                   (PlannerPertChart  *chart,
+							 gboolean            height_changed);
+static TreeNode *  pert_chart_insert_task              (PlannerPertChart  *chart,
+							 GtkTreePath        *path,
+							 MrpTask            *task);
+static PlannerRelationPertArrow * pert_chart_add_relation
+							(PlannerPertChart  *chart,
+							 TreeNode           *task,
+							 TreeNode           *predecessor,
+							 MrpRelationType     type);
+static void        pert_chart_get_visible_region       (PlannerPertChart *chart,
+							 gdouble            *x1,
+							 gdouble            *y1,
+							 gdouble            *x2,
+							 gdouble            *y2);
+static void        pert_chart_set_scroll_region        (PlannerPertChart  *chart,
+							 gdouble             x1,
+							 gdouble             y1,
+							 gdouble             x2,
+							 gdouble             y2);
+static PlannerPertNode *pert_chart_get_node_from_task   (PlannerPertChart  *chart,
+							 MrpTask            *task);
+static TreeNode *  pert_chart_tree_node_new            (void);
+static void        pert_chart_tree_node_insert_path    (TreeNode           *node,
+							 GtkTreePath        *path,
+							 TreeNode           *new_node);
+static TreeNode *  pert_chart_tree_node_at_path        (TreeNode           *root,
+							 GtkTreePath        *path);
+static void        pert_chart_tree_node_remove         (PlannerPertChart  *chart,
+							 TreeNode           *node);
+static void	pert_chart_reflow_selection            (PlannerPertChart  *chart);
+static gboolean    pert_chart_reflow_idle              (PlannerPertChart  *chart);
+gint	list_get_max					(GList  *list);
+static void	pert_chart_automatic_taskflow_do	(PlannerPertChart	*chart,
+							GtkTreeIter	*iter);
+static void	pert_chart_automatic_ceiling_do		(PlannerPertChart	*chart,
+							GtkTreeIter	*iter);
+gint	relation_pred_comparator				(gconstpointer	item1,
+								gconstpointer	item2);
+gint	task_slack_comparator				(gconstpointer	item1,
+							gconstpointer	item2);
+GList*	pert_chart_sort_by_ascending_slack	(GList	*relations,
+							MrpTask	*task);
+void	pert_chart_purge_informations_do	(PlannerPertChart	*chart,
+							GtkTreeIter*	iter);
+void	pert_chart_purge_informations		(PlannerPertChart	*chart);
+gint	pert_chart_find_longest_way_do		(MrpTask	*task);
+gint	pert_chart_find_longest_way		(PlannerPertChart	*chart);
+void	pert_chart_create_end_node		(PlannerPertChart	*chart);
+void	pert_chart_create_start_node		(PlannerPertChart	*chart);
+
+static guint         signals[LAST_SIGNAL];
+static GtkVBoxClass *parent_class = NULL;
+
+
+GType
+planner_pert_chart_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		static const GTypeInfo info = {
+			sizeof (PlannerPertChartClass),
+			NULL,		/* base_init */
+			NULL,		/* base_finalize */
+			(GClassInitFunc) pert_chart_class_init,
+			NULL,		/* class_finalize */
+			NULL,		/* class_data */
+			sizeof (PlannerPertChart),
+			0,              /* n_preallocs */
+			(GInstanceInitFunc) pert_chart_init
+		};
+
+		type = g_type_register_static (GTK_TYPE_VBOX, "PlannerPertChart",
+					       &info, 0);
+	}
+
+	return type;
+}
+
+static void
+pert_chart_class_init (PlannerPertChartClass *class)
+{
+	GObjectClass      *o_class;
+	GtkObjectClass    *object_class;
+	GtkWidgetClass    *widget_class;
+	GtkContainerClass *container_class;
+
+	parent_class = g_type_class_peek_parent (class);
+
+	o_class         = (GObjectClass *) class;
+	object_class    = (GtkObjectClass *) class;
+	widget_class    = (GtkWidgetClass *) class;
+	container_class = (GtkContainerClass *) class;
+
+	o_class->set_property = pert_chart_set_property;
+	o_class->get_property = pert_chart_get_property;
+	o_class->finalize     = pert_chart_finalize;
+
+	object_class->destroy = pert_chart_destroy;
+
+	widget_class->style_set     = pert_chart_style_set;
+	widget_class->realize       = pert_chart_realize;
+	widget_class->map           = pert_chart_map;
+	widget_class->unrealize     = pert_chart_unrealize;
+	widget_class->size_allocate = pert_chart_size_allocate;
+
+	class->set_scroll_adjustments = pert_chart_set_adjustments;
+
+	widget_class->set_scroll_adjustments_signal =
+		g_signal_new ("set_scroll_adjustments",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (PlannerPertChartClass, set_scroll_adjustments),
+			      NULL, NULL,
+			      planner_marshal_VOID__OBJECT_OBJECT,
+			      G_TYPE_NONE, 2,
+			      GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
+
+	signals[STATUS_UPDATED] =
+		g_signal_new ("status-updated",
+			      G_TYPE_FROM_CLASS (class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      planner_marshal_VOID__POINTER,
+			      G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+	signals[RESOURCE_CLICKED] =
+		g_signal_new ("resource-clicked",
+			      G_TYPE_FROM_CLASS (class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      planner_marshal_VOID__OBJECT,
+			      G_TYPE_NONE, 1,
+			      MRP_TYPE_RESOURCE);
+
+	/* Properties. */
+	g_object_class_install_property (o_class,
+					 PROP_MODEL,
+					 g_param_spec_object ("model",
+							      NULL,
+							      NULL,
+							      GTK_TYPE_TREE_MODEL,
+							      G_PARAM_READWRITE));
+
+	g_object_class_install_property (o_class,
+					 PROP_ROW_HEIGHT,
+					 g_param_spec_int ("row-height",
+							   NULL,
+							   NULL,
+							   0, G_MAXINT, 0,
+							   G_PARAM_READWRITE));
+}
+
+static void
+pert_chart_init (PlannerPertChart *chart)
+{
+	PlannerPertChartPriv *priv;
+
+	gtk_widget_set_redraw_on_allocate (GTK_WIDGET (chart), FALSE);
+
+	priv = g_new0 (PlannerPertChartPriv, 1);
+	chart->priv = priv;
+
+	priv->tree = pert_chart_tree_node_new ();
+
+	priv->height_changed = FALSE;
+	priv->reflow_idle_id = 0;
+
+	gtk_box_set_homogeneous (GTK_BOX (chart), FALSE);
+	gtk_box_set_spacing (GTK_BOX (chart), 0);
+
+	priv->canvas = GNOME_CANVAS (gnome_canvas_new ());
+	priv->canvas->close_enough = 5;
+	gnome_canvas_set_center_scroll_region (priv->canvas, FALSE);
+
+	/* Easiest way to get access to the chart from the canvas items. */
+	g_object_set_data (G_OBJECT (priv->canvas), "chart", chart);
+
+	gtk_box_pack_start (GTK_BOX (chart),
+			    GTK_WIDGET (priv->canvas),
+			    TRUE,
+			    TRUE,
+			    0);
+
+	priv->row_height = -1;
+	priv->height = -1;
+
+	priv->max_heights = NULL;
+
+	priv->last_time = MRP_TIME_INVALID;
+
+	priv->relation_hash = g_hash_table_new (NULL, NULL);
+
+	priv->highlight_critical = planner_conf_get_bool (CRITICAL_PATH_KEY,
+							  NULL);
+
+	priv->display_mode = planner_conf_get_int (MODE_PATH_KEY, NULL);
+	priv->end_list = NULL;
+	priv->start_list = NULL;
+	priv->end_arrow_list = NULL;
+	priv->start_arrow_list = NULL;
+
+	priv->hscale = 180;
+	priv->vscale = 120;
+	priv->node_width = 100;
+	priv->node_height = 80;
+}
+
+static void
+pert_chart_set_property (GObject      *object,
+			  guint         prop_id,
+			  const GValue *value,
+			  GParamSpec   *pspec)
+{
+	PlannerPertChart *chart;
+
+	chart = PLANNER_PERT_CHART (object);
+
+	switch (prop_id) {
+	case PROP_MODEL:
+		planner_pert_chart_set_model (chart, g_value_get_object (value));
+		break;
+	case PROP_ROW_HEIGHT:
+		chart->priv->row_height = g_value_get_int (value);
+		pert_chart_reflow (chart, TRUE);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+pert_chart_get_property (GObject    *object,
+			  guint       prop_id,
+			  GValue     *value,
+			  GParamSpec *pspec)
+{
+	PlannerPertChart *chart;
+
+	chart = PLANNER_PERT_CHART (object);
+
+	switch (prop_id) {
+	case PROP_MODEL:
+		g_value_set_object (value, G_OBJECT (chart->priv->model));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+pert_chart_finalize (GObject *object)
+{
+	PlannerPertChart *chart = PLANNER_PERT_CHART (object);
+
+	g_hash_table_destroy (chart->priv->relation_hash);
+
+	g_free (chart->priv);
+
+	if (G_OBJECT_CLASS (parent_class)->finalize) {
+		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
+	}
+}
+
+static void
+pert_chart_destroy (GtkObject *object)
+{
+	PlannerPertChart *chart = PLANNER_PERT_CHART (object);
+
+	planner_pert_chart_set_model (chart, NULL);
+
+	/* FIXME: free more stuff. */
+
+	if (chart->priv->model != NULL) {
+		g_object_unref (chart->priv->model);
+		chart->priv->model = NULL;
+	}
+
+	if (GTK_OBJECT_CLASS (parent_class)->destroy) {
+		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+	}
+}
+
+static void
+pert_chart_style_set (GtkWidget *widget,
+		       GtkStyle  *prev_style)
+{
+	PlannerPertChart     *chart;
+	PlannerPertChartPriv *priv;
+	PangoContext     *context;
+	PangoFontMetrics *metrics;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (widget));
+
+	if (GTK_WIDGET_CLASS (parent_class)->style_set) {
+		GTK_WIDGET_CLASS (parent_class)->style_set (widget,
+							    prev_style);
+	}
+
+	chart = PLANNER_PERT_CHART (widget);
+	priv = chart->priv;
+
+	context = gtk_widget_get_pango_context (widget);
+
+	metrics = pango_context_get_metrics (context,
+					     widget->style->font_desc,
+					     NULL);
+
+	f = 0.2 * pango_font_metrics_get_approximate_char_width (metrics) / PANGO_SCALE;
+}
+
+static void
+pert_chart_realize (GtkWidget *widget)
+{
+	PlannerPertChart     *chart;
+	PlannerPertChartPriv *priv;
+	GdkColormap           *colormap;
+	GtkStyle              *style;
+	GtkWidget             *canvas;
+
+	chart = PLANNER_PERT_CHART (widget);
+	priv = chart->priv;
+
+	canvas = GTK_WIDGET (priv->canvas);
+
+	if (GTK_WIDGET_CLASS (parent_class)->realize) {
+		(* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
+	}
+
+	/* Set the background to white. */
+	style = gtk_style_copy (canvas->style);
+	colormap = gtk_widget_get_colormap (canvas);
+	gdk_color_white(colormap, &style->bg[GTK_STATE_NORMAL]);
+	gtk_widget_set_style (canvas, style);
+	gtk_style_unref (style);
+}
+
+static void
+pert_chart_unrealize (GtkWidget *widget)
+{
+	PlannerPertChart *chart;
+
+	chart = PLANNER_PERT_CHART (widget);
+
+	if (GTK_WIDGET_CLASS (parent_class)->unrealize) {
+		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+	}
+}
+
+/* We reflow when we are mapped. This updates the chart when changes are made
+ * for example in the task view and this view is hidden.
+ */
+static void
+pert_chart_map (GtkWidget *widget)
+{
+	PlannerPertChart *chart;
+
+	chart = PLANNER_PERT_CHART(widget);
+
+	if (GTK_WIDGET_CLASS (parent_class)->map) {
+		(* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+	}
+
+	chart->priv->height_changed = TRUE;
+	planner_pert_chart_reflow_now (chart);
+}
+
+static void
+pert_chart_size_allocate (GtkWidget     *widget,
+			   GtkAllocation *allocation)
+{
+	PlannerPertChart *chart;
+	gboolean           height_changed;
+
+	height_changed = widget->allocation.height != allocation->height;
+
+	GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
+
+	chart = PLANNER_PERT_CHART (widget);
+
+	/* Force reflow (if we are mapped), since it looks smoother with less
+	 * jumping around.
+	 */
+	if (GTK_WIDGET_MAPPED (chart)) {
+		planner_pert_chart_reflow_now (chart);
+	}
+}
+
+static void
+pert_chart_set_adjustments (PlannerPertChart *chart,
+			     GtkAdjustment     *hadj,
+			     GtkAdjustment     *vadj)
+{
+	PlannerPertChartPriv *priv;
+	gboolean               need_adjust = FALSE;
+
+	g_return_if_fail (hadj == NULL || GTK_IS_ADJUSTMENT (hadj));
+	g_return_if_fail (vadj == NULL || GTK_IS_ADJUSTMENT (vadj));
+
+	priv = chart->priv;
+
+	if (hadj == NULL) {
+		hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+	}
+	if (vadj == NULL) {
+		vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+	}
+
+	if (priv->hadjustment && (priv->hadjustment != hadj)) {
+		g_object_unref (priv->hadjustment);
+	}
+
+	if (priv->vadjustment && (priv->vadjustment != vadj)) {
+		g_object_unref (priv->vadjustment);
+	}
+
+	if (priv->hadjustment != hadj) {
+		priv->hadjustment = hadj;
+		g_object_ref (priv->hadjustment);
+		gtk_object_sink (GTK_OBJECT (priv->hadjustment));
+
+		need_adjust = TRUE;
+	}
+
+	if (priv->vadjustment != vadj) {
+		priv->vadjustment = vadj;
+		g_object_ref (priv->vadjustment);
+		gtk_object_sink (GTK_OBJECT (priv->vadjustment));
+
+		need_adjust = TRUE;
+	}
+
+	if (need_adjust) {
+		gtk_widget_set_scroll_adjustments (GTK_WIDGET (priv->canvas),
+						   hadj,
+						   vadj);
+	}
+}
+
+static void
+pert_chart_node_changed (GtkTreeModel *model,
+			 GtkTreePath  *path,
+			 GtkTreeIter  *iter,
+			 gpointer      data)
+{
+#if 0
+	gboolean free_path = FALSE;
+
+	g_return_if_fail (path != NULL || iter != NULL);
+
+	if (path == NULL) {
+		path = gtk_tree_model_get_path (model, iter);
+		free_path = TRUE;
+	}
+	else if (iter == NULL) {
+		gtk_tree_model_get_iter (model, iter, path);
+	}
+
+	if (free_path) {
+		gtk_tree_path_free (path);
+	}
+#endif
+}
+
+static void
+pert_chart_node_inserted (GtkTreeModel *model,
+			  GtkTreePath  *path,
+			  GtkTreeIter  *iter,
+			  gpointer      data)
+{
+	PlannerPertChart     *chart;
+	PlannerPertChartPriv *priv;
+	gboolean               free_path = FALSE;
+	MrpTask               *task;
+	TreeNode              *node;
+
+	chart = data;
+	priv = chart->priv;
+
+	g_return_if_fail (path != NULL || iter != NULL);
+
+	if (path == NULL) {
+		path = gtk_tree_model_get_path (model, iter);
+		free_path = TRUE;
+	}
+	else if (iter == NULL) {
+		gtk_tree_model_get_iter (model, iter, path);
+	}
+
+	task = planner_gantt_model_get_task (PLANNER_GANTT_MODEL (model), iter);
+
+	node = pert_chart_insert_task (chart, path, task);
+
+	if (free_path) {
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+pert_chart_node_has_child_toggled (GtkTreeModel *model,
+				   GtkTreePath  *path,
+				   GtkTreeIter  *iter,
+				   gpointer      data)
+{
+	PlannerPertChart     *chart;
+	PlannerPertChartPriv *priv;
+	gboolean               free_path = FALSE;
+	TreeNode              *node;
+
+	chart = data;
+	priv = chart->priv;
+
+	g_return_if_fail (path != NULL || iter != NULL);
+
+	if (path == NULL) {
+		path = gtk_tree_model_get_path (model, iter);
+		free_path = TRUE;
+	}
+
+	node = pert_chart_tree_node_at_path (priv->tree, path);
+	gnome_canvas_item_request_update (node->item);
+
+	if (free_path) {
+		gtk_tree_path_free (path);
+	}
+}
+
+static void
+pert_chart_remove_children (PlannerPertChart *chart,
+			     TreeNode          *node)
+{
+	gint i;
+
+	for (i = 0; i < node->num_children; i++) {
+		pert_chart_remove_children (chart, node->children[i]);
+	}
+
+	gtk_object_destroy (GTK_OBJECT (node->item));
+	node->item = NULL;
+	node->task = NULL;
+
+	g_free (node->children);
+	node->children = NULL;
+	g_free (node);
+}
+
+static void
+pert_chart_node_deleted (GtkTreeModel *model,
+			 GtkTreePath  *path,
+			 gpointer      data)
+{
+	PlannerPertChart *chart = data;
+	TreeNode          *node;
+
+	g_return_if_fail (path != NULL);
+
+	node = pert_chart_tree_node_at_path (chart->priv->tree, path);
+
+	pert_chart_tree_node_remove (chart, node);
+	pert_chart_remove_children (chart, node);
+
+	pert_chart_reflow (chart, TRUE);
+}
+
+static void
+pert_chart_rows_reordered (GtkTreeModel *model,
+			    GtkTreePath  *parent,
+			    GtkTreeIter  *iter,
+			    gint         *new_order,
+			    gpointer      data)
+{
+#if 0
+	gint       len;
+
+	len = gtk_tree_model_iter_n_children (model, iter);
+
+	if (len < 2) {
+		return;
+	}
+
+	/* FIXME: impl. */
+#endif
+}
+
+static void
+pert_chart_build_tree_do (PlannerPertChart *chart,
+			   GtkTreeIter       *iter,
+			   GHashTable        *hash)
+{
+	PlannerPertChartPriv *priv;
+	GtkTreeIter            child;
+	GtkTreePath           *path;
+	MrpTask               *task;
+	TreeNode              *node;
+
+	priv = chart->priv;
+
+	do {
+		task = planner_gantt_model_get_task (PLANNER_GANTT_MODEL (priv->model), iter);
+
+		path = gtk_tree_model_get_path (priv->model, iter);
+
+		node = pert_chart_insert_task (chart, path, task);
+
+		g_hash_table_insert (hash, task, node);
+
+		gtk_tree_path_free (path);
+
+		if (gtk_tree_model_iter_children (priv->model, &child, iter)) {
+			pert_chart_build_tree_do (chart, &child, hash);
+		}
+	} while (gtk_tree_model_iter_next (priv->model, iter));
+}
+
+static void
+pert_chart_build_relations (PlannerPertChart *chart,
+			     GtkTreeIter       *iter,
+			     GHashTable        *hash)
+{
+	PlannerPertChartPriv *priv;
+	GtkTreeIter            child;
+	MrpTask               *task;
+	MrpRelation           *relation;
+	MrpTask               *predecessor;
+	TreeNode              *task_node;
+	TreeNode              *predecessor_node;
+	GList                 *relations, *l;
+	PlannerRelationPertArrow  *arrow;
+	MrpRelationType	       rel_type;
+
+	priv = chart->priv;
+
+	do {
+		task = planner_gantt_model_get_task (
+			PLANNER_GANTT_MODEL (priv->model), iter);
+
+		relations = mrp_task_get_predecessor_relations (task);
+
+		for (l = relations; l; l = l->next) {
+			relation = l->data;
+
+			predecessor = mrp_relation_get_predecessor (relation);
+
+			task_node = g_hash_table_lookup (hash, task);
+			predecessor_node = g_hash_table_lookup (hash, predecessor);
+
+			rel_type = mrp_relation_get_relation_type(relation);
+
+			arrow = pert_chart_add_relation (chart,
+							  task_node,
+							  predecessor_node,
+							  rel_type);
+
+			g_hash_table_insert (priv->relation_hash, relation, arrow);
+		}
+
+		if (gtk_tree_model_iter_children (priv->model, &child, iter)) {
+			pert_chart_build_relations (chart, &child, hash);
+		}
+
+	} while (gtk_tree_model_iter_next (priv->model, iter));
+}
+
+static void
+pert_chart_build_tree (PlannerPertChart *chart)
+{
+	GtkTreeIter  iter;
+	GtkTreePath *path;
+	GHashTable  *hash;
+
+	path = gtk_tree_path_new_root ();
+	if (!gtk_tree_model_get_iter (chart->priv->model, &iter, path)) {
+		gtk_tree_path_free (path);
+		return;
+	}
+
+	hash = g_hash_table_new (NULL, NULL);
+
+	pert_chart_build_tree_do (chart, &iter, hash);
+	gtk_tree_model_get_iter (chart->priv->model, &iter, path);
+	pert_chart_build_relations (chart, &iter, hash);
+
+	gtk_tree_path_free (path);
+	g_hash_table_destroy (hash);
+
+	/* FIXME: free paths used as keys. */
+}
+
+void
+planner_pert_chart_reflow_now (PlannerPertChart *chart)
+{
+	if (!GTK_WIDGET_MAPPED (chart)) {
+		return;
+	}
+	pert_chart_reflow_idle (chart);
+}
+
+static void
+pert_chart_reflow (PlannerPertChart *chart, gboolean height_changed)
+{
+	if (!GTK_WIDGET_MAPPED (chart)) {
+		return;
+	}
+
+	chart->priv->height_changed |= height_changed;
+
+	if (chart->priv->reflow_idle_id != 0) {
+		return;
+	}
+
+	chart->priv->reflow_idle_id = g_idle_add ((GSourceFunc) pert_chart_reflow_idle, chart);
+}
+
+static TreeNode *
+pert_chart_insert_task (PlannerPertChart *chart,
+			 GtkTreePath  *path,
+			 MrpTask      *task)
+{
+	PlannerPertChartPriv *priv;
+	GnomeCanvasItem       *item;
+	TreeNode              *tree_node;
+
+	priv = chart->priv;
+
+	item = gnome_canvas_item_new (gnome_canvas_root (priv->canvas),
+				      PLANNER_TYPE_PERT_NODE,
+				      "vscale", priv->vscale,
+					  "hscale", priv->hscale,
+					  "node_width", priv->node_width,
+					  "node_height", priv->node_height,
+				      "task", task,
+				      NULL);
+	planner_pert_node_init_menu (PLANNER_PERT_NODE (item));
+
+	tree_node = pert_chart_tree_node_new ();
+	tree_node->item = item;
+	tree_node->task = task;
+
+	pert_chart_tree_node_insert_path (priv->tree, path, tree_node);
+
+	g_signal_connect (task,
+			  "relation-added",
+			  G_CALLBACK (pert_chart_relation_added),
+			  chart);
+
+	g_signal_connect (task,
+			  "relation-removed",
+			  G_CALLBACK (pert_chart_relation_removed),
+			  chart);
+
+	g_signal_connect (task,
+			  "removed",
+			  G_CALLBACK (pert_chart_task_removed),
+			  chart);
+
+	g_signal_connect (task,
+				  "notify",
+				  G_CALLBACK (pert_chart_task_position_changed),
+				  chart);
+
+	//no successor/predecessor except end/start node
+	if(!mrp_task_get_predecessor_relations(task))
+	{
+		if(!g_list_find(priv->start_list, task))
+		{
+			priv->start_list = g_list_append(priv->start_list, task);
+		}
+	}
+	if(!mrp_task_get_successor_relations(task))
+	{
+		if(!g_list_find(priv->end_list, task))
+		{
+			priv->end_list = g_list_append(priv->end_list, task);
+		}
+	}
+	pert_chart_reflow(chart, TRUE);
+
+	return tree_node;
+}
+
+static PlannerRelationPertArrow *
+pert_chart_add_relation (PlannerPertChart *chart,
+			  TreeNode	*task,
+			  TreeNode	*predecessor,
+			  MrpRelationType    type)
+{
+	return planner_relation_pert_arrow_new (PLANNER_PERT_NODE (task->item),
+					   PLANNER_PERT_NODE (predecessor->item),
+					   type);
+}
+
+static void
+show_hide_descendants (TreeNode *node, gboolean show)
+{
+	gint i;
+
+	for (i = 0; i < node->num_children; i++) {
+		planner_pert_node_set_visible (PLANNER_PERT_NODE (node->children[i]->item),
+					  show);
+
+		if (!show || (show && node->children[i]->expanded)) {
+			show_hide_descendants (node->children[i], show);
+		}
+	}
+}
+
+static void
+collapse_descendants (TreeNode *node)
+{
+	gint i;
+
+	for (i = 0; i < node->num_children; i++) {
+		node->children[i]->expanded = FALSE;
+		collapse_descendants (node->children[i]);
+	}
+}
+
+GtkWidget *
+planner_pert_chart_new (void)
+{
+
+	return planner_pert_chart_new_with_model (NULL);
+}
+
+GtkWidget *
+planner_pert_chart_new_with_model (GtkTreeModel *model)
+{
+	PlannerPertChart *chart;
+
+	chart = PLANNER_PERT_CHART (gtk_type_new (planner_pert_chart_get_type ()));
+
+	if (model) {
+		planner_pert_chart_set_model (chart, model);
+	}
+
+	return GTK_WIDGET (chart);
+}
+
+void
+planner_pert_chart_expand_row (PlannerPertChart *chart, GtkTreePath *path)
+{
+	TreeNode *node;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	node = pert_chart_tree_node_at_path (chart->priv->tree, path);
+
+	if (node) {
+		//FIXME implement summary node
+		//node->expanded = TRUE;
+		node->expanded = FALSE;
+		//show_hide_descendants (node, TRUE);
+		show_hide_descendants (node, FALSE);
+		pert_chart_reflow (chart, TRUE);
+	}
+}
+
+void
+planner_pert_chart_collapse_row (PlannerPertChart *chart, GtkTreePath *path)
+{
+	TreeNode *node;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	node = pert_chart_tree_node_at_path (chart->priv->tree, path);
+
+	if (node) {
+		node->expanded = FALSE;
+		collapse_descendants (node);
+		show_hide_descendants (node, FALSE);
+		pert_chart_reflow (chart, TRUE);
+	}
+}
+
+static void
+pert_chart_relation_added (MrpTask           *task,
+			    MrpRelation       *relation,
+			    PlannerPertChart *chart)
+{
+	GtkTreePath          *task_path;
+	GtkTreePath          *predecessor_path;
+	TreeNode             *task_node;
+	TreeNode             *predecessor_node;
+	PlannerRelationPertArrow *arrow;
+	MrpTask              *predecessor;
+	MrpRelationType	      rel_type;
+
+	predecessor = mrp_relation_get_predecessor (relation);
+
+	if (g_getenv ("PLANNER_DEBUG_UNDO_TASK")) {
+		g_message ("Adding a new relation arrow (%p): %s (%p) -> %s (%p)",
+			   relation,
+			   mrp_task_get_name (predecessor), predecessor,
+			   mrp_task_get_name (task), task);
+	}
+
+	if (task == predecessor) {
+		/* We are only interested in the successor task. */
+		return;
+	}
+
+	task_path = planner_gantt_model_get_path_from_task (
+		PLANNER_GANTT_MODEL (chart->priv->model), task);
+	predecessor_path = planner_gantt_model_get_path_from_task (
+		PLANNER_GANTT_MODEL (chart->priv->model), predecessor);
+
+	task_node = pert_chart_tree_node_at_path (chart->priv->tree,
+						   task_path);
+	predecessor_node = pert_chart_tree_node_at_path (chart->priv->tree,
+							  predecessor_path);
+
+	rel_type = mrp_relation_get_relation_type (relation);
+
+	arrow = pert_chart_add_relation (chart,
+					  task_node,
+					  predecessor_node,
+					  rel_type);
+
+	g_hash_table_insert (chart->priv->relation_hash, relation, arrow);
+
+	if(g_list_length(mrp_task_get_successor_relations(predecessor))==1)
+	{
+		if(g_list_find(chart->priv->end_list, predecessor))
+		{
+				chart->priv->end_list = g_list_remove(chart->priv->end_list, predecessor);
+		}
+	}
+	if(g_list_length(mrp_task_get_predecessor_relations(task))==1)
+	{
+		if(g_list_find(chart->priv->start_list, task))
+		{
+			chart->priv->start_list = g_list_remove(chart->priv->start_list, task);
+		}
+	}
+	pert_chart_reflow(chart, TRUE);
+}
+
+static void
+pert_chart_relation_removed (MrpTask           *task,
+			      MrpRelation       *relation,
+			      PlannerPertChart *chart)
+{
+	GnomeCanvasItem *arrow;
+	MrpTask         *predecessor;
+
+	predecessor = mrp_relation_get_predecessor (relation);
+
+	if (task == predecessor) {
+
+		if(!g_list_length(mrp_task_get_successor_relations(task)))
+		{
+			if(!g_list_find(chart->priv->end_list, task))
+			{
+				chart->priv->end_list = g_list_append(chart->priv->end_list, task);
+			}
+		}
+		/* We are only interested in the successor task. */
+		return;
+	}
+
+	arrow = g_hash_table_lookup (chart->priv->relation_hash, relation);
+	if (arrow != NULL) {
+		g_hash_table_remove (chart->priv->relation_hash, relation);
+
+		gtk_object_destroy (GTK_OBJECT (arrow));
+		//pert_chart_reflow (chart, FALSE);
+	}
+
+	if(!g_list_length(mrp_task_get_predecessor_relations(task)))
+	{
+		if(!g_list_find(chart->priv->start_list, task))
+		{
+			chart->priv->start_list = g_list_append(chart->priv->start_list, task);
+		}
+	}
+
+	pert_chart_reflow(chart, TRUE);
+}
+
+static void
+pert_chart_task_removed (MrpTask            *task,
+			  PlannerPertChart  *chart)
+{
+	GList *l, *relations;
+
+	if (g_getenv ("PLANNER_DEBUG_UNDO_TASK")) {
+		g_message ("Task removed: %s", mrp_task_get_name (task));
+		g_message ("Cleaning signals for task: %s", mrp_task_get_name (task));
+	}
+
+	relations = mrp_task_get_predecessor_relations (task);
+	for (l = relations; l; l = l->next) {
+		pert_chart_relation_removed (task, l->data, chart);
+	}
+
+	relations = mrp_task_get_successor_relations (task);
+	for (l = relations; l; l = l->next) {
+		pert_chart_relation_removed (task, l->data, chart);
+	}
+
+	g_signal_handlers_disconnect_by_func (task, pert_chart_relation_added, chart);
+	g_signal_handlers_disconnect_by_func (task, pert_chart_relation_removed, chart);
+	g_signal_handlers_disconnect_by_func (task, pert_chart_task_removed, chart);
+
+	if(g_list_find(chart->priv->start_list, task))
+	{
+		chart->priv->start_list = g_list_remove(chart->priv->start_list, task);
+	}
+	if(g_list_find(chart->priv->end_list, task))
+	{
+		chart->priv->end_list = g_list_remove(chart->priv->end_list, task);
+	}
+	pert_chart_reflow(chart, TRUE);
+}
+
+static void
+pert_chart_task_position_changed (MrpTask *task, GParamSpec *pspec, PlannerPertChart *chart)
+{
+	pert_chart_reflow(chart, TRUE);
+}
+
+static gboolean
+pert_chart_task_moved_task_traverse_func (MrpTask *task, PlannerPertChart *chart)
+{
+	GList                 *relations;
+	GList                 *l;
+	MrpRelation           *relation;
+	PlannerPertChartPriv *priv;
+	PlannerRelationPertArrow  *arrow;
+	PlannerPertNode       *row;
+
+	priv = chart->priv;
+
+	row = pert_chart_get_node_from_task (chart, task);
+
+	relations = mrp_task_get_predecessor_relations (task);
+	for (l = relations; l; l = l->next) {
+		relation = l->data;
+
+		arrow = g_hash_table_lookup (priv->relation_hash, relation);
+		if (arrow) {
+			planner_relation_pert_arrow_set_successor (arrow, row);
+		}
+	}
+
+	relations = mrp_task_get_successor_relations (task);
+	for (l = relations; l; l = l->next) {
+		relation = l->data;
+
+		arrow = g_hash_table_lookup (priv->relation_hash, relation);
+		if (arrow) {
+			planner_relation_pert_arrow_set_predecessor (arrow, row);
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+pert_chart_task_moved (MrpProject        *project,
+			MrpTask           *task,
+			PlannerPertChart *chart)
+{
+	PlannerPertChartPriv *priv;
+
+	priv = chart->priv;
+
+	/* Note: Seems like we don't need this? */
+	/*pert_chart_reflow_now (chart);*/
+
+	/* When a task has been indented or otherwise moved, it gets re-inserted
+	 * in the new place. The relation arrow must be connected to the new
+	 * canvas pert node items. So we check if any of the relations has an
+	 * arrow item associated with it, and reconnects if so.
+	 */
+	mrp_project_task_traverse (project,
+				   task,
+				   (MrpTaskTraverseFunc) pert_chart_task_moved_task_traverse_func,
+				   chart);
+}
+
+static void
+pert_chart_add_signal (PlannerPertChart *chart, gpointer instance, gulong id)
+{
+	ConnectData *data;
+
+	data = g_new0 (ConnectData, 1);
+
+	data->instance = instance;
+	data->id = id;
+
+	chart->priv->signal_ids = g_list_prepend (chart->priv->signal_ids,
+						  data);
+}
+
+static void
+pert_chart_disconnect_signals (PlannerPertChart *chart)
+{
+	GList       *l;
+	ConnectData *data;
+
+	for (l = chart->priv->signal_ids; l; l = l->next) {
+		data = l->data;
+
+		g_signal_handler_disconnect (data->instance,
+					     data->id);
+		g_free (data);
+	}
+
+	g_list_free (chart->priv->signal_ids);
+	chart->priv->signal_ids = NULL;
+}
+
+PlannerTaskTree *
+planner_pert_chart_get_view (PlannerPertChart *chart)
+{
+	g_return_val_if_fail (PLANNER_IS_PERT_CHART (chart), NULL);
+
+	return chart->priv->view;
+}
+
+void
+planner_pert_chart_set_view (PlannerPertChart *chart, PlannerTaskTree *view)
+{
+	g_return_if_fail (PLANNER_IS_TASK_TREE (view));
+
+	chart->priv->view = view;
+}
+
+GtkTreeModel *
+planner_pert_chart_get_model (PlannerPertChart *chart)
+{
+	g_return_val_if_fail (PLANNER_IS_PERT_CHART (chart), NULL);
+
+	return chart->priv->model;
+}
+
+void
+planner_pert_chart_set_model (PlannerPertChart *chart,
+			       GtkTreeModel      *model)
+{
+	PlannerPertChartPriv *priv;
+	MrpTask               *root;
+	MrpProject            *project;
+	gulong                 id;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	priv = chart->priv;
+
+	if (model == priv->model) {
+		return;
+	}
+
+	if (priv->model) {
+		pert_chart_disconnect_signals (chart);
+		g_object_unref (priv->model);
+	}
+
+	priv->model = model;
+
+	if (model) {
+		g_object_ref (model);
+
+		pert_chart_build_tree (chart);
+
+		project = planner_gantt_model_get_project (PLANNER_GANTT_MODEL (model));
+		root = mrp_project_get_root_task (project);
+
+		/* Connect with _after so that we get our event after the model
+		 * is done.
+		 */
+		id = g_signal_connect_after (project,
+					     "task-moved",
+					     G_CALLBACK (pert_chart_task_moved),
+					     chart);
+		pert_chart_add_signal (chart, project, id);
+
+		id = g_signal_connect (model,
+				       "row-changed",
+				       G_CALLBACK (pert_chart_node_changed),
+				       chart);
+		pert_chart_add_signal (chart, model, id);
+
+		id = g_signal_connect (model,
+				       "row-inserted",
+				       G_CALLBACK (pert_chart_node_inserted),
+				       chart);
+		pert_chart_add_signal (chart, model, id);
+
+		id = g_signal_connect (model,
+				       "row-deleted",
+				       G_CALLBACK (pert_chart_node_deleted),
+				       chart);
+		pert_chart_add_signal (chart, model, id);
+
+		id = g_signal_connect (model,
+				       "rows-reordered",
+				       G_CALLBACK (pert_chart_rows_reordered),
+				       chart);
+		pert_chart_add_signal (chart, model, id);
+
+		id = g_signal_connect (model,
+				       "row-has-child-toggled",
+				       G_CALLBACK (pert_chart_node_has_child_toggled),
+				       chart);
+		pert_chart_add_signal (chart, model, id);
+
+		priv->last_time = mrp_task_get_finish (root);
+
+		/* Force a reflow initially to avoid visible reflow on
+		 * start-up .
+		 */
+		priv->height_changed = TRUE;
+		planner_pert_chart_reflow_now (chart);
+	}
+
+	g_object_notify (G_OBJECT (chart), "model");
+}
+
+/* TreeNode */
+
+static TreeNode *
+pert_chart_tree_node_new (void)
+{
+	TreeNode *node;
+
+	node = g_new0 (TreeNode, 1);
+	//node->expanded = TRUE;
+	//FIXME implement the summary nodes
+	node->expanded = FALSE;
+
+	return node;
+}
+
+static void
+pert_chart_tree_node_insert_path (TreeNode *node, GtkTreePath *path, TreeNode *new_node)
+{
+	gint *indices, i, depth;
+
+	depth = gtk_tree_path_get_depth (path);
+	indices = gtk_tree_path_get_indices (path);
+
+	for (i = 0; i < depth - 1; i++) {
+		node = node->children[indices[i]];
+	}
+
+	node->num_children++;
+	node->children = g_realloc (node->children, sizeof (gpointer) * node->num_children);
+
+	if (node->num_children - 1 == indices[i]) {
+		/* Don't need to move if the new node is at the end. */
+	} else {
+		memmove (node->children + indices[i] + 1,
+			 node->children + indices[i],
+			 sizeof (gpointer) * (node->num_children - indices[i] - 1));
+	}
+
+	node->children[indices[i]] = new_node;
+
+	new_node->parent = node;
+}
+
+static void
+pert_chart_tree_node_remove (PlannerPertChart *chart, TreeNode *node)
+{
+	TreeNode *parent;
+	gint     i, pos;
+
+	parent = node->parent;
+
+	pos = -1;
+	for (i = 0; i < parent->num_children; i++) {
+		if (parent->children[i] == node) {
+			pos = i;
+			break;
+		}
+	}
+
+	g_assert (pos != -1);
+
+	memmove (parent->children + pos,
+		 parent->children + pos + 1,
+		 sizeof (gpointer) * (parent->num_children - pos - 1));
+
+	parent->num_children--;
+	parent->children = g_realloc (parent->children, sizeof (gpointer) * parent->num_children);
+
+	if (g_getenv ("PLANNER_DEBUG_UNDO_TASK")) {
+		g_message ("Cleaning signals for: %s", mrp_task_get_name (node->task));
+	}
+
+	g_signal_handlers_disconnect_by_func (node->task, pert_chart_relation_added, chart);
+	g_signal_handlers_disconnect_by_func (node->task, pert_chart_relation_removed, chart);
+	g_signal_handlers_disconnect_by_func (node->task, pert_chart_task_removed, chart);
+
+	node->parent = NULL;
+}
+
+static TreeNode *
+pert_chart_tree_node_at_path (TreeNode *node, GtkTreePath *path)
+{
+	gint *indices, i, depth;
+
+	depth = gtk_tree_path_get_depth (path);
+	indices = gtk_tree_path_get_indices (path);
+
+	for (i = 0; i < depth; i++) {
+		/* FIXME: workaround for a bug in the pert view, the
+		 * row_expanded (and collapsed) should not be handled before we
+		 * have inserted the rows.
+		 */
+		if (node->num_children <= indices[i]) {
+			return NULL;
+		}
+
+		node = node->children[indices[i]];
+	}
+
+	return node;
+}
+
+void
+planner_pert_chart_scroll_to (PlannerPertChart *chart, time_t t)
+{
+	//gint x1, x2;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	//FIXME: add range check.
+
+	//FIXME: "port" to mrptime.
+#if 0
+	x1 = chart->priv->project_start * chart->priv->hscale;
+	x2 = t * chart->priv->hscale;
+
+	gnome_canvas_scroll_to (chart->priv->canvas, x2 - x1, 0);
+#endif
+}
+
+static void
+pert_chart_get_visible_region (PlannerPertChart *chart,
+			       gdouble            *x1,
+			       gdouble            *y1,
+			       gdouble            *x2,
+			       gdouble            *y2)
+{
+	GnomeCanvas *canvas;
+	gint cx, cy;
+
+	canvas = chart->priv->canvas;
+
+	gnome_canvas_get_scroll_offsets(canvas, &cx, &cy);
+	gnome_canvas_c2w(canvas, cx, cy, x1, y1);
+
+	*x2 = *x1 + GTK_WIDGET(canvas)->allocation.width;
+	*y2 = *y1 + GTK_WIDGET(canvas)->allocation.height;
+}
+
+static void
+pert_chart_set_scroll_region (PlannerPertChart *chart,
+			       gdouble            x1,
+			       gdouble            y1,
+			       gdouble            x2,
+			       gdouble            y2)
+{
+	GnomeCanvas *canvas;
+	gdouble      ox1, oy1, ox2, oy2;
+
+	canvas = chart->priv->canvas;
+
+	gnome_canvas_get_scroll_region (canvas,
+					&ox1,
+					&oy1,
+					&ox2,
+					&oy2);
+
+	if (ox1 == x1 && oy1 == y1 && ox2 == x2 && oy2 == y2) {
+		return;
+	}
+
+	gnome_canvas_set_scroll_region (canvas,
+					x1,
+					y1,
+					x2,
+					y2);
+}
+
+static PlannerPertNode *
+pert_chart_get_node_from_task (PlannerPertChart *chart,
+			       MrpTask           *task)
+{
+	PlannerGanttModel *model;
+	GtkTreePath  *path;
+	TreeNode     *node;
+
+	model = PLANNER_GANTT_MODEL (chart->priv->model);
+
+	path = planner_gantt_model_get_path_from_task (model, task);
+
+	node = pert_chart_tree_node_at_path (chart->priv->tree, path);
+
+	gtk_tree_path_free (path);
+
+	return PLANNER_PERT_NODE (node->item);
+}
+
+void
+planner_pert_chart_status_updated (PlannerPertChart *chart,
+				    const gchar       *message)
+{
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	g_signal_emit (chart, signals[STATUS_UPDATED], 0, message);
+}
+
+void
+planner_pert_chart_resource_clicked (PlannerPertChart *chart,
+				      MrpResource       *resource)
+{
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	g_signal_emit (chart, signals[RESOURCE_CLICKED], 0, resource);
+}
+
+void
+planner_pert_chart_set_highlight_critical_tasks (PlannerPertChart *chart,
+						  gboolean           state)
+{
+	PlannerPertChartPriv *priv;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	priv = chart->priv;
+
+	if (priv->highlight_critical == state) {
+		return;
+	}
+
+	priv->highlight_critical = state;
+
+	gtk_widget_queue_draw (GTK_WIDGET (priv->canvas));
+
+	planner_conf_set_bool (CRITICAL_PATH_KEY, state, NULL);
+}
+
+gboolean
+planner_pert_chart_get_highlight_critical_tasks (PlannerPertChart *chart)
+{
+	g_return_val_if_fail (PLANNER_IS_PERT_CHART (chart), FALSE);
+
+	return chart->priv->highlight_critical;
+}
+
+void
+planner_pert_chart_set_display_mode (PlannerPertChart *chart,
+						  gint           value)
+{
+	PlannerPertChartPriv *priv;
+
+	g_return_if_fail (PLANNER_IS_PERT_CHART (chart));
+
+	priv = chart->priv;
+
+	if (priv->display_mode == value) {
+		return;
+	}
+
+	priv->display_mode = value;
+
+	gtk_widget_queue_draw (GTK_WIDGET (priv->canvas));
+	pert_chart_reflow(chart, TRUE);
+
+	planner_conf_set_int (MODE_PATH_KEY, value, NULL);
+}
+
+gint
+planner_pert_chart_get_display_mode (PlannerPertChart *chart)
+{
+	g_return_val_if_fail (PLANNER_IS_PERT_CHART (chart), FALSE);
+
+	return chart->priv->display_mode;
+}
+
+void pert_chart_purge_informations_do(PlannerPertChart *chart, GtkTreeIter* iter)
+{
+	PlannerPertChartPriv	*priv;
+	PlannerPertNode			*node;
+	GtkTreeIter				child;
+	MrpTask					*task;
+	priv = chart->priv;
+	do
+	{
+		task = planner_gantt_model_get_task (PLANNER_GANTT_MODEL(priv->model), iter);
+		node = pert_chart_get_node_from_task (chart, task);
+		g_object_set(node,
+					"pcol", 0,
+					"pheight", -1,
+					NULL);
+		if (gtk_tree_model_iter_children (priv->model, &child, iter))
+		{
+			pert_chart_purge_informations_do (chart, &child);
+		}
+	}while (gtk_tree_model_iter_next (priv->model, iter));
+}
+
+void pert_chart_purge_informations(PlannerPertChart *chart)
+{
+	GtkTreePath				*path;
+	GtkTreeIter				iter;
+
+	path = gtk_tree_path_new_root ();
+	if (gtk_tree_model_get_iter (chart->priv->model, &iter, path))
+	{
+		pert_chart_purge_informations_do(chart, &iter);
+	}
+}
+
+gint task_slack_comparator(gconstpointer item1, gconstpointer item2)
+{
+	gint	slack1, slack2;
+	g_object_get(MRP_TASK(item1),
+					"slack", &slack1,
+					NULL);
+	g_object_get(MRP_TASK(item2),
+					"slack", &slack2,
+					NULL);
+	return slack1 - slack2;
+}
+
+gint relation_pred_comparator(gconstpointer item1, gconstpointer item2)
+{
+	MrpTask	*predecessor1, *predecessor2;
+	gint	slack1, slack2;
+	predecessor1 = mrp_relation_get_predecessor ((MrpRelation*) item1);
+	predecessor2 = mrp_relation_get_predecessor ((MrpRelation*) item2);
+	g_object_get(predecessor1,
+					"slack", &slack1,
+					NULL);
+	g_object_get(predecessor2,
+					"slack", &slack2,
+					NULL);
+
+	return slack1 - slack2;
+}
+
+GList* pert_chart_sort_by_ascending_slack(GList *relations, MrpTask* task)
+{
+	MrpTask	*predecessor, *successor;
+	GList	*list;
+
+	g_return_val_if_fail (relations != NULL, FALSE);
+
+	list = g_list_copy(relations);
+
+	predecessor = mrp_relation_get_predecessor (list->data);
+	successor = mrp_relation_get_successor (list->data);
+	if(task == successor)
+	{
+		list = g_list_sort(list, (GCompareFunc) relation_pred_comparator);
+	}
+	return list;
+}
+
+gint list_get_max(GList* list)
+{
+	GList* l;
+	gint hmax;
+
+	hmax = 0;
+	for(l = list; l; l=l->next)
+	{
+		hmax = MAX(hmax, (gint) l->data);
+	}
+	return hmax;
+}
+
+static
+void pert_chart_automatic_taskflow_do(PlannerPertChart *chart, GtkTreeIter *iter)
+{
+	PlannerPertChartPriv	*priv;
+	GtkTreePath	*path;
+	GtkTreeIter	iter_pred;//, child;
+	MrpTask		*task, *predecessor;
+	PlannerPertNode	*task_node, *predecessor_node;
+	GList		*relations, *l, *task_list, *pred_list;
+	gint		task_col, task_height, pred_col, pred_height, i;
+	gboolean	found = FALSE;
+
+	priv = chart->priv;
+	task_list = NULL;
+	task = planner_gantt_model_get_task (PLANNER_GANTT_MODEL (priv->model), iter);
+	task_node = pert_chart_get_node_from_task (chart, task);
+
+	g_object_get (task_node,
+					"pheight", &task_height,
+					"pcol", &task_col,
+					NULL);
+
+	relations = mrp_task_get_predecessor_relations (task);
+	if(!relations) //no predecessor
+	{
+		task_col = 1;
+		//fill the list of heights up to task_col levels
+		for(i=g_list_length(priv->max_heights); i<=task_col; i++)
+		{
+			priv->max_heights = g_list_append(priv->max_heights, (gpointer)-1);
+		}
+
+		//vertical
+		if(task_height == -1)
+		{
+			task_list = g_list_nth(priv->max_heights, task_col);
+			task_height = 1 + (gint)task_list->data;
+			task_list->data = (gpointer)task_height;
+		}
+	}
+	else
+	{
+		//draw the relations with less slack before the others
+		relations = pert_chart_sort_by_ascending_slack(relations, task);
+		for (l = relations; l; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+
+			g_object_get (predecessor_node,
+							"pheight", &pred_height,
+							"pcol", &pred_col,
+							NULL);
+
+			//horizontal
+			if(pred_col == 0)
+			{
+				path = planner_gantt_model_get_path_from_task(PLANNER_GANTT_MODEL (priv->model), predecessor);
+				gtk_tree_model_get_iter (priv->model, &iter_pred, path);
+				gtk_tree_path_free (path);
+				pert_chart_automatic_taskflow_do (chart, &iter_pred);
+				g_object_get (predecessor_node,
+							"pheight", &pred_height,
+							"pcol", &pred_col,
+							NULL);
+			}
+			task_col = MAX(task_col, pred_col+1);
+
+			//fill the list of heights up to task_col levels
+			for(i=g_list_length(priv->max_heights); i<=task_col; i++)
+			{
+				priv->max_heights = g_list_append(priv->max_heights, (gpointer)-1);
+			}
+
+			//vertical
+			task_list = g_list_nth(priv->max_heights, task_col);
+			task_height = (gint) task_list->data;
+			//if the link passes through few columns; you need to keep the max height along the way
+			for(i = task_col; i>pred_col; i--)
+			{
+				pred_height = (gint) g_list_nth_data(priv->max_heights, i);
+				task_height = MAX(task_height, pred_height+1);
+			}
+		}
+
+		//check all the links : we're sure all the predecessor nodes (direct & indirect) are computed
+		task_list = g_list_nth(priv->max_heights, task_col);
+		task_height = (gint) task_list->data;
+		for (l = relations; l; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+			g_object_get (predecessor_node,
+							"pcol", &pred_col,
+							NULL);
+			//if the link passes through few columns; you need to keep the max height along the way
+			for(i = task_col; i>pred_col; i--)
+			{
+				pred_height = (gint) g_list_nth_data(priv->max_heights, i);
+				task_height = MAX(task_height, pred_height+1);
+			}
+		}
+
+		//find the direct pred raising the height
+		for (l = relations; l && !found; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+			g_object_get (predecessor_node,
+							"pheight", &pred_height,
+							"pcol", &pred_col,
+							NULL);
+
+			if((pred_col+1 == task_col) && (task_height<pred_height))
+			{
+				task_height = (gint) g_list_nth_data(priv->max_heights, pred_col);
+				found = TRUE;
+			}
+		}
+		//ok this is the good height
+		task_list->data = (gpointer) task_height;
+
+		//increase heights
+		for (l = relations; l; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+			g_object_get (predecessor_node,
+							"pcol", &pred_col,
+							NULL);
+			//an arrow is there : a node cant take the gap
+			for(i = task_col; i>pred_col; i--)
+			{
+				pred_list = g_list_nth(priv->max_heights, i);
+				pred_height = (gint) pred_list->data;
+				if(pred_height < task_height)
+				{
+					pred_list->data = (gpointer) task_height;
+				}
+			}
+		}
+		g_list_free(relations);
+	}
+
+	g_object_set (task_node,
+					"pheight", task_height,
+					"pcol", task_col,
+					NULL);
+
+	//summary node?
+	/*if (gtk_tree_model_iter_children (priv->model, &child, iter))
+	{
+		pert_chart_automatic_taskflow_do (chart, &child);
+	}*/
+}
+
+static
+void pert_chart_automatic_ceiling_do(PlannerPertChart *chart, GtkTreeIter *iter)
+{
+	PlannerPertChartPriv	*priv;
+	GtkTreePath	*path;
+	GtkTreeIter	iter_pred;//, child;
+	MrpTask		*task, *predecessor;
+	PlannerPertNode	*task_node, *predecessor_node;
+	GList		*relations, *l, *task_list, *pred_list;
+	gint		task_col, task_height, pred_col, pred_height, i;
+
+	priv = chart->priv;
+	task_list = NULL;
+	task = planner_gantt_model_get_task (PLANNER_GANTT_MODEL (priv->model), iter);
+	task_node = pert_chart_get_node_from_task (chart, task);
+
+	g_object_get (task_node,
+					"pheight", &task_height,
+					"pcol", &task_col,
+					NULL);
+
+	relations = mrp_task_get_predecessor_relations (task);
+	if(!relations) //no predecessor
+	{
+		task_col = 1;
+
+		//fill the list of heights up to task_col levels
+		for(i=g_list_length(priv->max_heights); i<=task_col; i++)
+		{
+			priv->max_heights = g_list_append(priv->max_heights, (gpointer)-1);
+		}
+
+		//vertical
+		if(task_height == -1)
+		{
+			task_list = g_list_nth(priv->max_heights, task_col);
+			task_height = 1 + (gint)task_list->data;
+			task_list->data = (gpointer)task_height;
+		}
+	}
+	else
+	{
+		//draw the relations with less slack before the others
+		relations = pert_chart_sort_by_ascending_slack(relations, task);
+		for (l = relations; l; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+
+			g_object_get (predecessor_node,
+							"pheight", &pred_height,
+							"pcol", &pred_col,
+							NULL);
+
+			//horizontal
+			if(pred_col == 0 || pred_height == -1)
+			{
+				path = planner_gantt_model_get_path_from_task(PLANNER_GANTT_MODEL (priv->model), predecessor);
+				gtk_tree_model_get_iter (priv->model, &iter_pred, path);
+				gtk_tree_path_free (path);
+				pert_chart_automatic_ceiling_do (chart, &iter_pred);
+				g_object_get (predecessor_node,
+							"pheight", &pred_height,
+							"pcol", &pred_col,
+							NULL);
+			}
+			task_col = MAX(task_col, pred_col+1);
+
+			//fill the list of heights up to task_col levels
+			for(i=g_list_length(priv->max_heights); i<=task_col; i++)
+			{
+				priv->max_heights = g_list_append(priv->max_heights, (gpointer)-1);
+			}
+
+			//vertical
+			task_list = g_list_nth(priv->max_heights, task_col);
+			task_height = (gint) task_list->data;
+			//if the link passes through few columns; you need to keep the max height along the way
+			for(i = task_col; i>pred_col; i--)
+			{
+				pred_height = (gint) g_list_nth_data(priv->max_heights, i);
+				task_height = MAX(task_height, pred_height+1);
+			}
+		}
+
+		//check all the links : we're sure all the predecessor nodes (direct & indirect) are computed
+		task_list = g_list_nth(priv->max_heights, task_col);
+		task_height = (gint) task_list->data;
+		for (l = relations; l; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+			g_object_get (predecessor_node,
+							"pcol", &pred_col,
+							NULL);
+			//if the link passes through few columns; you need to keep the max height along the way
+			for(i = task_col; i>pred_col; i--)
+			{
+				pred_height = (gint) g_list_nth_data(priv->max_heights, i);
+				task_height = MAX(task_height, pred_height+1);
+			}
+		}
+		//ok this is the good height
+		task_list->data = (gpointer) task_height;
+
+		//increase heights
+		for (l = relations; l; l = l->next)
+		{
+			predecessor = mrp_relation_get_predecessor (l->data);
+			predecessor_node = pert_chart_get_node_from_task (chart, predecessor);
+			g_object_get (predecessor_node,
+							"pcol", &pred_col,
+							NULL);
+			//an arrow is there : a node cant take the gap
+			for(i = task_col; i>pred_col; i--)
+			{
+				pred_list = g_list_nth(priv->max_heights, i);
+				pred_height = (gint) pred_list->data;
+				if(pred_height < task_height)
+				{
+					pred_list->data = (gpointer) task_height;
+				}
+			}
+		}
+		g_list_free(relations);
+	}
+
+	g_object_set (task_node,
+					"pheight", task_height,
+					"pcol", task_col,
+					NULL);
+
+	//summary node?
+	/*if (gtk_tree_model_iter_children (priv->model, &child, iter))
+	{
+		pert_chart_automatic_ceiling_do (chart, &child);
+	}*/
+}
+
+void pert_chart_create_start_node(PlannerPertChart	*chart)
+{
+	PlannerPertChartPriv	*priv;
+	GList					*l;
+	PlannerPertNode			*node;
+	PlannerRelationPertArrow *arrow;
+	MrpTask					*task;
+
+	priv = chart->priv;
+	if(priv->start)
+	{
+		gtk_object_destroy(GTK_OBJECT(priv->start));
+		priv->start = NULL;
+	}
+	task = mrp_task_new();
+	mrp_task_set_name(task, _("Start"));
+	g_object_set (task,
+				"critical", TRUE,
+				NULL);
+	priv->start = gnome_canvas_item_new (gnome_canvas_root (priv->canvas),
+								  PLANNER_TYPE_PERT_NODE,
+								  "vscale", priv->vscale,
+								  "hscale", priv->hscale,
+								  "node_width", priv->node_width,
+								  "node_height", priv->node_height,
+								  "pcol", 0,
+								  "pheight", 0,
+								  "task", task,
+								  NULL);
+	planner_pert_node_init_menu (PLANNER_PERT_NODE (priv->start));
+	//clean the list by deleting remaining arrows
+	if(g_list_length(priv->start_arrow_list))
+	{
+		for(l = priv->start_arrow_list; l ; l=l->next)
+		{
+			gtk_object_destroy((gpointer)l->data);
+		}
+		g_list_free(priv->start_arrow_list);
+		priv->start_arrow_list = NULL;
+	}
+	//create new arrows from start to node
+	for(l = priv->start_list; l; l=l->next)
+	{
+		node = pert_chart_get_node_from_task (chart, MRP_TASK(l->data));
+		arrow = planner_relation_pert_arrow_new (node,
+												priv->start,
+												MRP_RELATION_FS);
+		priv->start_arrow_list = g_list_append(priv->start_arrow_list, arrow);
+	}
+}
+
+void pert_chart_create_end_node	(PlannerPertChart	*chart)
+{
+	GList	*l;
+	PlannerPertNode	*node;
+	PlannerRelationPertArrow	*arrow;
+	PlannerPertChartPriv	*priv;
+	MrpTask		*task;
+	gint		plength, pheight, task_col, task_height, i;
+	PlannerPertNode			*task_node;
+
+	priv = chart->priv;
+
+	//end node infos
+	if(!g_list_length(priv->max_heights))
+	{
+		plength = 1;
+	}
+	else
+	{
+		plength = g_list_length(priv->max_heights);
+	}
+
+	pheight = 0;
+	priv->max_heights = g_list_append(priv->max_heights, (gpointer) -1);
+	//calculate the height of the end node with his predecessors
+	for(l=priv->end_list; l; l=l->next)
+	{
+		task = MRP_TASK(l->data);
+		task_node = pert_chart_get_node_from_task (chart, task);
+
+		g_object_get (task_node,
+						"pcol", &task_col,
+						NULL);
+		for(i = plength; i>task_col; i--)
+		{
+			task_height = (gint) g_list_nth_data(priv->max_heights, i);
+			pheight = MAX(pheight, task_height+1);
+		}
+	}
+
+	for(l=priv->end_list; l; l=l->next)
+	{
+		task = MRP_TASK(l->data);
+		task_node = pert_chart_get_node_from_task (chart, task);
+		g_object_get (task_node,
+						"pcol", &task_col,
+						NULL);
+		for(i = plength; i>task_col; i--)
+		{
+			task_height = (gint) g_list_nth_data(priv->max_heights, i);
+			pheight = MAX(pheight, task_height+1);
+		}
+	}
+
+	//clean end node
+	if(priv->end)
+	{
+		gtk_object_destroy(GTK_OBJECT(priv->end));
+		priv->end = NULL;
+	}
+
+	//node create end node
+	task = mrp_task_new();
+	mrp_task_set_name(task, _("End"));
+	g_object_set (task,
+				"critical", TRUE,
+				NULL);
+	priv->end = gnome_canvas_item_new (gnome_canvas_root (priv->canvas),
+									  PLANNER_TYPE_PERT_NODE,
+									  "vscale", priv->vscale,
+									  "hscale", priv->hscale,
+									  "node_width", priv->node_width,
+									  "node_height", priv->node_height,
+									  "pcol", plength,
+									  "pheight", pheight,
+									  "task", task,
+									  NULL);
+
+	planner_pert_node_init_menu (PLANNER_PERT_NODE (priv->end));
+	//clean arrows to end
+	if(g_list_length(priv->end_arrow_list))
+	{
+		for(l = priv->end_arrow_list; l ; l=l->next)
+		{
+			gtk_object_destroy((gpointer)l->data);
+		}
+		g_list_free(priv->end_arrow_list);
+		priv->end_arrow_list = NULL;
+	}
+	//create arrows from node to end
+	if(g_list_length(priv->end_list))
+	{
+		for(l = priv->end_list; l; l=l->next)
+		{
+			node = pert_chart_get_node_from_task (chart, MRP_TASK(l->data));
+			arrow = planner_relation_pert_arrow_new (priv->end,
+													node,
+													MRP_RELATION_FS);
+			priv->end_arrow_list = g_list_append(priv->end_arrow_list, arrow);
+		}
+	}
+	else //no task except start & end nodes
+	{
+		arrow = planner_relation_pert_arrow_new (priv->end,
+												priv->start,
+												MRP_RELATION_FS);
+		priv->end_arrow_list = g_list_append(priv->end_arrow_list, arrow);
+	}
+}
+
+void pert_chart_reflow_selection(PlannerPertChart *chart)
+{
+	PlannerPertChartPriv	*priv;
+	GtkTreeIter		iter;
+	GtkTreePath		*path;
+	MrpTask			*task;
+	GList			*l;
+
+	priv = chart->priv;
+
+	//recalculate the heights
+	g_list_free(priv->max_heights);
+	priv->max_heights = NULL;
+
+	pert_chart_purge_informations(chart);
+
+	//sort by ascending slack
+	priv->end_list = g_list_sort(priv->end_list, (GCompareFunc) task_slack_comparator);
+	//begin at the end node and moves from node to predecessor to compute the positions of the nodes
+	for(l = priv->end_list; l; l=l->next)
+	{
+		task = MRP_TASK(l->data);
+		path = planner_gantt_model_get_path_from_task (PLANNER_GANTT_MODEL (priv->model), task);
+		gtk_tree_model_get_iter (priv->model, &iter, path);
+		gtk_tree_path_free (path);
+
+		if(priv->display_mode == 0)
+		{
+			pert_chart_automatic_ceiling_do(chart, &iter);
+		}
+		else if(priv->display_mode == 1)
+		{
+			pert_chart_automatic_taskflow_do(chart, &iter);
+		}
+	}
+
+	pert_chart_create_start_node(chart);
+	pert_chart_create_end_node(chart);
+}
+
+static gboolean pert_chart_reflow_idle(PlannerPertChart *chart)
+{
+	PlannerPertChartPriv	*priv;
+	gdouble			x1, y1, x2, y2;
+	gdouble			vx1, vy1, vx2, vy2;
+	gdouble			width, height;
+	gdouble			bx1, bx2;
+	GtkAllocation		allocation;
+
+	priv = chart->priv;
+
+	pert_chart_reflow_selection(chart);
+
+	allocation = GTK_WIDGET (priv->canvas)->allocation;
+
+	x1 = 0;
+	x2 = (g_list_length(priv->max_heights)-1)*priv->hscale + priv->node_width + PADDING;
+	y1 = 0;
+	y2 = (list_get_max(priv->max_heights))*priv->vscale + priv->node_height - PADDING;
+
+	width = MAX (x2 - x1, allocation.width - 1.0);
+	height = MAX (y2 - y1, allocation.height - 1.0);
+
+	gnome_canvas_item_get_bounds (priv->canvas->root,
+					  &bx1, NULL,
+					  &bx2, NULL);
+
+	// Put some padding after the right-most coordinate.
+	bx2 += PADDING;
+
+	width = MAX (width, bx2 - bx1);
+
+	x2 = x1 + width;
+
+	pert_chart_get_visible_region(chart, &vx1, &vy1, &vx2, &vy2);
+
+	x2 = MAX (x2, vx2);
+	y2 = MAX (y2, vy2);
+
+	pert_chart_set_scroll_region (chart,
+					   x1,
+					   y1,
+					   x2,
+					   y1 + height);
+
+	priv->height_changed = FALSE;
+	priv->reflow_idle_id = 0;
+	return FALSE;
+}
diff --git a/src/planner-pert-chart.h b/src/planner-pert-chart.h
new file mode 100644
index 0000000..e454687
--- /dev/null
+++ b/src/planner-pert-chart.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2002 CodeFactory AB
+ * Copyright (C) 2001-2002 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2001-2002 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PLANNER_PERT_CHART_H__
+#define __PLANNER_PERT_CHART_H__
+
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtktreemodel.h>
+#include <libgnomecanvas/gnome-canvas.h>
+#include "planner-task-tree.h"
+
+#define PLANNER_TYPE_PERT_CHART		(planner_pert_chart_get_type ())
+#define PLANNER_PERT_CHART(obj)		(GTK_CHECK_CAST ((obj), PLANNER_TYPE_PERT_CHART, PlannerPertChart))
+#define PLANNER_PERT_CHART_CLASS(klass)	(GTK_CHECK_CLASS_CAST ((klass), PLANNER_TYPE_PERT_CHART, PlannerPertChartClass))
+#define PLANNER_IS_PERT_CHART(obj)		(GTK_CHECK_TYPE ((obj), PLANNER_TYPE_PERT_CHART))
+#define PLANNER_IS_PERT_CHART_CLASS(klass)	(GTK_CHECK_CLASS_TYPE ((obj), PLANNER_TYPE_PERT_CHART))
+#define PLANNER_PERT_CHART_GET_CLASS(obj)	(GTK_CHECK_GET_CLASS ((obj), PLANNER_TYPE_PERT_CHART, PlannerPertChartClass))
+
+typedef struct _PlannerPertChart           PlannerPertChart;
+typedef struct _PlannerPertChartClass      PlannerPertChartClass;
+typedef struct _PlannerPertChartPriv       PlannerPertChartPriv;
+
+struct _PlannerPertChart
+{
+	GtkVBox                parent;
+	PlannerPertChartPriv *priv;
+};
+
+struct _PlannerPertChartClass
+{
+	GtkVBoxClass parent_class;
+
+	void  (* set_scroll_adjustments) (PlannerPertChart  *chart,
+					  GtkAdjustment *hadjustment,
+					  GtkAdjustment *vadjustment);
+};
+
+
+GType            planner_pert_chart_get_type         (void) G_GNUC_CONST;
+GtkWidget       *planner_pert_chart_new              (void);
+GtkWidget       *planner_pert_chart_new_with_model   (GtkTreeModel      *model);
+PlannerTaskTree *planner_pert_chart_get_view         (PlannerPertChart *chart);
+void             planner_pert_chart_set_view         (PlannerPertChart *chart,
+						       PlannerTaskTree   *view);
+GtkTreeModel    *planner_pert_chart_get_model        (PlannerPertChart *tree_view);
+void             planner_pert_chart_set_model        (PlannerPertChart *tree_view,
+						       GtkTreeModel      *model);
+void             planner_pert_chart_expand_row       (PlannerPertChart *chart,
+						       GtkTreePath       *path);
+void             planner_pert_chart_collapse_row     (PlannerPertChart *chart,
+						       GtkTreePath       *path);
+void             planner_pert_chart_scroll_to        (PlannerPertChart *chart,
+						       time_t             t);
+void             planner_pert_chart_status_updated   (PlannerPertChart *chart,
+						       const gchar       *message);
+void             planner_pert_chart_resource_clicked (PlannerPertChart *chart,
+						       MrpResource       *resource);
+void             planner_pert_chart_reflow_now       (PlannerPertChart *chart);
+
+void
+planner_pert_chart_set_highlight_critical_tasks      (PlannerPertChart  *chart,
+						       gboolean       state);
+
+gboolean
+planner_pert_chart_get_highlight_critical_tasks      (PlannerPertChart  *chart);
+
+void
+planner_pert_chart_set_display_mode (PlannerPertChart *chart, gint value);
+
+gint
+planner_pert_chart_get_display_mode (PlannerPertChart *chart);
+
+
+#endif /* __PLANNER_PERT_CHART_H__ */
diff --git a/src/planner-pert-node.c b/src/planner-pert-node.c
new file mode 100644
index 0000000..e5e7e25
--- /dev/null
+++ b/src/planner-pert-node.c
@@ -0,0 +1,2128 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2005 Imendio AB
+ * Copyright (C) 2001-2003 CodeFactory AB
+ * Copyright (C) 2001-2003 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2001-2003 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temgle Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <libplanner/mrp-project.h>
+#include <libplanner/mrp-resource.h>
+#include <libplanner/mrp-task.h>
+#include <libplanner/mrp-calendar.h>
+#include <glib/gi18n.h>
+#include <libgnomecanvas/gnome-canvas.h>
+#include "planner-marshal.h"
+#include "planner-format.h"
+#include "planner-pert-node.h"
+#include "planner-pert-chart.h"
+#include "planner-canvas-line.h"
+#include "eel-canvas-rect.h"
+#include "planner-scale-utils.h"
+#include "planner-task-tree.h"
+#include "planner-task-popup.h"
+#include "planner-task-cmd.h"
+
+/* The padding between the pert node and the window. */
+#define PADDING 10.0
+#define HTEXT_PADDING 3.0
+#define VTEXT_PADDING 5.0
+
+#define THICKNESS 2
+
+/* Snap to this amount of time when dragging duration. */
+#define SNAP (60.0*15.0)
+
+/* Trim the value to short int work space. */
+#define TRSH(a) ((int)((a) < SHRT_MIN ? (short int)SHRT_MIN : ((a) > SHRT_MAX ? (short int)SHRT_MAX : (a))))
+
+/* Same gdk_draw_rectangle but with trimmed x,y,w,h args. */
+#define draw_cut_rectangle(a,b,c,d,e,f,g)		\
+		gdk_draw_rectangle ((a), (b), (c), TRSH (d), TRSH (e), TRSH (f), TRSH (g))
+
+/* Same gdk_draw_line but with trimmed x1,y1,x2,y2 args. */
+#define draw_cut_line(a,b,c,d,e,f)		 \
+		gdk_draw_line ((a), (b), TRSH (c), TRSH (d), TRSH (e), TRSH (f))
+
+/* Same gdk_draw_layout but with trimmed x,y args. */
+#define draw_cut_layout(a,b,c,d,e)		         \
+		gdk_draw_layout ((a), (b), TRSH (c), TRSH (d), (e))
+
+enum
+{
+	PROP_0,
+	PROP_X,
+	PROP_Y,
+	PROP_PCOL,
+	PROP_PHEIGHT,
+	PROP_TASK,
+	PROP_HIGHLIGHT,
+	PROP_HSCALE,
+	PROP_VSCALE,
+	PROP_NODE_WIDTH,
+	PROP_NODE_HEIGHT
+};
+
+enum
+{
+	/* For relation arrows. */
+	GEOMETRY_CHANGED,
+	VISIBILITY_CHANGED,
+	LAST_SIGNAL
+};
+
+typedef enum
+{
+	STATE_NONE          = 0,
+	STATE_DRAG_LINK     = 1 << 0,
+	STATE_DRAG_ANY = STATE_DRAG_LINK
+} State;
+
+typedef enum
+{
+	DRAG_NONE_SPOT,
+	DRAG_RELATION_SPOT
+} DragSpot;
+
+struct _PlannerPertNodePriv
+{
+	/* FIXME: Don't need those per pert node. */
+	GdkGC       *complete_gc;
+	GdkGC       *break_gc;
+	GdkGC       *fill_gc;
+	GdkGC       *frame_gc;
+	GdkGC       *ttask_gc;
+
+
+	/* FIXME: Don't need those per pert node. */
+	GdkColor     color_normal;
+	GdkColor     color_normal_light;
+	GdkColor     color_normal_dark;
+
+	GdkColor     color_milestone;
+	GdkColor     color_milestone_light;
+	GdkColor     color_milestone_dark;
+
+	GdkColor	 color_summary;
+	GdkColor	 color_summary_light;
+	GdkColor	 color_summary_dark;
+
+	GdkColor     color_critical;
+	GdkColor     color_critical_light;
+	GdkColor     color_critical_dark;
+
+	MrpTask     *task;
+
+	State        state;
+
+	gint	pcol;
+	gint	pheight;
+
+	guint        visible    : 1;
+	guint        highlight  : 1;
+
+	gdouble      x;
+	gdouble      y;
+
+	guint        scroll_timeout_id;
+
+	/* FIXME: Don't need this per node. */
+	GtkItemFactory *popup_factory;
+
+	gint		hscale, vscale, node_height, node_width;
+};
+
+static void     pert_node_class_init                  (PlannerPertNodeClass  *class);
+static void     pert_node_init                        (PlannerPertNode       *node);
+static void     pert_node_destroy                     (GtkObject             *object);
+static void     pert_node_set_property                (GObject               *object,
+						       guint                  param_id,
+						       const GValue          *value,
+						       GParamSpec            *pspec);
+static void     pert_node_get_property                (GObject               *object,
+						       guint                  param_id,
+						       GValue                *value,
+						       GParamSpec            *pspec);
+static void     pert_node_update                      (GnomeCanvasItem       *item,
+						       double                *affine,
+						       ArtSVP                *clip_path,
+						       int                    flags);
+static void     pert_node_realize                     (GnomeCanvasItem       *item);
+static void     pert_node_unrealize                   (GnomeCanvasItem       *item);
+static void     pert_node_draw                        (GnomeCanvasItem       *item,
+						       GdkDrawable           *drawable,
+						       gint                   x,
+						       gint                   y,
+						       gint                   width,
+						       gint                   height);
+static double   pert_node_point                       (GnomeCanvasItem       *item,
+						       double                 x,
+						       double                 y,
+						       gint                   cx,
+						       gint                   cy,
+						       GnomeCanvasItem      **actual_item);
+static void     pert_node_bounds                      (GnomeCanvasItem       *item,
+						       double                *x1,
+						       double                *y1,
+						       double                *x2,
+						       double                *y2);
+static gboolean pert_node_event                       (GnomeCanvasItem       *item,
+						       GdkEvent              *event);
+static void     pert_node_notify_cb                   (MrpTask               *task,
+						       GParamSpec            *pspec,
+						       PlannerPertNode       *node);
+static void     pert_node_geometry_changed            (PlannerPertNode       *node);
+static gboolean pert_node_canvas_scroll               (GtkWidget             *widget,
+						       gint                   delta_x,
+						       gint                   delta_y);
+static PlannerCmd *task_cmd_edit_property             (PlannerWindow   *window,
+					               PlannerTaskTree *tree,
+						       MrpTask         *task,
+						       const gchar     *property,
+						       const GValue    *value);
+
+static GList *  pert_node_get_selected_tasks          (GtkTreeSelection      *selection);
+gchar* g_substr (const gchar* string, gint         start, gint         end);
+
+
+static GnomeCanvasItemClass *parent_class;
+static guint                 signals[LAST_SIGNAL];
+static GdkBitmap            *complete_stipple = NULL;
+static gchar                 complete_stipple_pattern[] = { 0x02, 0x01 };
+static GdkBitmap            *break_stipple = NULL;
+static gchar                 break_stipple_pattern[] = { 0x03 };
+
+/* Data related to dragging items */
+static GnomeCanvasItem   *drag_item;
+static gdouble            drag_item_wx_min;
+static gdouble            drag_item_wx_max;
+static gdouble            drag_item_wy_min;
+static gdouble            drag_item_wy_max;
+static gdouble            drag_wx1, drag_wy1;
+static GnomeCanvasPoints *drag_points = NULL;
+static GnomeCanvasItem   *target_item;
+static GnomeCanvasItem   *old_target_item;
+
+GType
+planner_pert_node_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		static const GTypeInfo info = {
+			sizeof (PlannerPertNodeClass),
+			NULL,		/* base_init */
+			NULL,		/* base_finalize */
+			(GClassInitFunc) pert_node_class_init,
+			NULL,		/* class_finalize */
+			NULL,		/* class_data */
+			sizeof (PlannerPertNode),
+			0,              /* n_preallocs */
+			(GInstanceInitFunc) pert_node_init
+		};
+
+		type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM,
+					       "PlannerPertNode",
+					       &info,
+					       0);
+	}
+
+	return type;
+}
+
+static void
+pert_node_class_init (PlannerPertNodeClass *class)
+{
+	GObjectClass         *gobject_class;
+	GtkObjectClass       *object_class;
+	GnomeCanvasItemClass *item_class;
+
+	gobject_class = (GObjectClass *) class;
+	object_class = (GtkObjectClass *) class;
+	item_class = (GnomeCanvasItemClass *) class;
+
+	parent_class = g_type_class_peek_parent (class);
+
+	gobject_class->set_property = pert_node_set_property;
+	gobject_class->get_property = pert_node_get_property;
+
+	item_class->event = pert_node_event;
+
+	signals[GEOMETRY_CHANGED] =
+		g_signal_new ("geometry-changed",
+			      G_TYPE_FROM_CLASS (class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      planner_marshal_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE,
+			      G_TYPE_NONE, 4,
+			      G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+	signals[VISIBILITY_CHANGED] =
+		g_signal_new ("visibility-changed",
+			      G_TYPE_FROM_CLASS (class),
+			      G_SIGNAL_RUN_LAST,
+			      0,
+			      NULL, NULL,
+			      planner_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_BOOLEAN);
+
+		g_object_class_install_property
+	                (gobject_class,
+	                 PROP_PCOL,
+	                 g_param_spec_int ("pcol", NULL, NULL,
+					      -1, G_MAXINT, -1,
+					      (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+        g_object_class_install_property
+                (gobject_class,
+                 PROP_PHEIGHT,
+                 g_param_spec_int ("pheight", NULL, NULL,
+				      -1, G_MAXINT, -1,
+				      (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+        g_object_class_install_property
+                (gobject_class,
+                 PROP_TASK,
+                 g_param_spec_object ("task", NULL, NULL,
+				      MRP_TYPE_TASK,
+				      G_PARAM_READWRITE));
+
+        g_object_class_install_property
+                (gobject_class,
+                 PROP_HIGHLIGHT,
+                 g_param_spec_boolean ("highlight", NULL, NULL,
+				       FALSE,
+				       G_PARAM_READWRITE));
+
+        g_object_class_install_property
+                        (gobject_class,
+                         PROP_NODE_HEIGHT,
+                         g_param_spec_int ("node_height", NULL, NULL,
+						0, G_MAXINT, 0,
+						G_PARAM_READWRITE));
+
+        g_object_class_install_property
+                        (gobject_class,
+                         PROP_NODE_WIDTH,
+                         g_param_spec_int ("node_width", NULL, NULL,
+						0, G_MAXINT, 0,
+						G_PARAM_READWRITE));
+
+        g_object_class_install_property
+                        (gobject_class,
+                         PROP_HSCALE,
+                         g_param_spec_int ("hscale", NULL, NULL,
+						0, G_MAXINT, 0,
+						G_PARAM_READWRITE));
+
+        g_object_class_install_property
+                        (gobject_class,
+                         PROP_VSCALE,
+                         g_param_spec_int ("vscale", NULL, NULL,
+						0, G_MAXINT, 0,
+						G_PARAM_READWRITE));
+
+	object_class->destroy = pert_node_destroy;
+
+	item_class->update = pert_node_update;
+	item_class->realize = pert_node_realize;
+	item_class->unrealize = pert_node_unrealize;
+	item_class->draw = pert_node_draw;
+	item_class->point = pert_node_point;
+	item_class->bounds = pert_node_bounds;
+}
+
+static void
+pert_node_init (PlannerPertNode *node)
+{
+	PlannerPertNodePriv *priv;
+
+	node->priv = g_new0 (PlannerPertNodePriv, 1);
+	priv = node->priv;
+
+	priv->x = PADDING;
+	priv->y = PADDING;
+	priv->visible = TRUE;
+	priv->highlight = FALSE;
+	priv->pheight = -1;
+	priv->pcol = -1;
+	priv->hscale = 0;
+	priv->vscale = 0;
+	priv->node_width = 0;
+	priv->node_height = 0;
+}
+
+static void
+pert_node_destroy (GtkObject *object)
+{
+	PlannerPertNode     *node;
+	PlannerPertNodePriv *priv;
+
+	g_return_if_fail (PLANNER_IS_PERT_NODE (object));
+
+	node = PLANNER_PERT_NODE (object);
+	priv = node->priv;
+
+	if (priv) {
+		if (priv->scroll_timeout_id) {
+			g_source_remove (priv->scroll_timeout_id);
+			priv->scroll_timeout_id = 0;
+		}
+
+		g_free (priv);
+		node->priv = NULL;
+	}
+
+	if (GTK_OBJECT_CLASS (parent_class)->destroy) {
+		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+	}
+}
+
+static void
+pert_node_get_bounds (PlannerPertNode *node,
+		      double     *px1,
+		      double     *py1,
+		      double     *px2,
+		      double     *py2)
+{
+	GnomeCanvasItem *item;
+	gdouble          wx1, wy1, wx2, wy2;
+	gint             cx1, cy1, cx2, cy2;
+
+	item = GNOME_CANVAS_ITEM (node);
+
+	/* Get the items bbox in canvas pixel coordinates. */
+
+	wx1 = node->priv->x - 1;
+	wy1 = node->priv->y;
+	wx2 = node->priv->x + node->priv->node_width + 1;
+	wy2 = node->priv->y + node->priv->node_height;
+
+	gnome_canvas_item_i2w (item, &wx1, &wy1);
+	gnome_canvas_item_i2w (item, &wx2, &wy2);
+	gnome_canvas_w2c (item->canvas, wx1, wy1, &cx1, &cy1);
+	gnome_canvas_w2c (item->canvas, wx2, wy2, &cx2, &cy2);
+
+	*px1 = cx1 - 1;
+	*py1 = cy1 - 1;
+	*px2 = cx2 + 1;
+	*py2 = cy2 + 1;
+}
+
+static void
+pert_node_set_property (GObject      *object,
+			guint         param_id,
+			const GValue *value,
+			GParamSpec   *pspec)
+{
+	GnomeCanvasItem      *item;
+	PlannerPertNode      *node;
+	PlannerPertNodePriv  *priv;
+	gboolean              changed = FALSE;
+	gboolean              tmp_bool;
+	gint				  tmp_int;
+
+	g_return_if_fail (PLANNER_IS_PERT_NODE (object));
+
+	item = GNOME_CANVAS_ITEM (object);
+	node  = PLANNER_PERT_NODE (object);
+	priv = node->priv;
+
+	switch (param_id) {
+	case PROP_PCOL:
+			tmp_int = g_value_get_int (value);
+			if (tmp_int != priv->pcol)
+			{
+				priv->pcol = tmp_int;
+				priv->x = (priv->pcol * priv->hscale) + PADDING;
+				changed = TRUE;
+			}
+			break;
+
+	case PROP_PHEIGHT:
+		tmp_int = g_value_get_int (value);
+		if (tmp_int != priv->pheight)
+		{
+			priv->pheight = tmp_int;
+			priv->y = (priv->pheight * priv->vscale) + PADDING;
+			changed = TRUE;
+		}
+		break;
+
+	case PROP_TASK:
+		if (priv->task != NULL)
+		{
+			g_object_unref (priv->task);
+			// FIXME: Disconnect notify handlers.
+		}
+		priv->task = g_object_ref (g_value_get_object (value));
+		g_signal_connect_object (priv->task,
+					 "notify",
+					 G_CALLBACK (pert_node_notify_cb),
+					 node,
+					 0);
+		changed = TRUE;
+		break;
+
+	case PROP_HIGHLIGHT:
+		tmp_bool = g_value_get_boolean (value);
+		if (tmp_bool != priv->highlight)
+		{
+			priv->highlight = tmp_bool;
+			changed = TRUE;
+		}
+		break;
+
+	case PROP_VSCALE:
+			tmp_int = g_value_get_int (value);
+			if (tmp_int != priv->vscale)
+			{
+				priv->vscale = tmp_int;
+			}
+			break;
+
+	case PROP_HSCALE:
+			tmp_int = g_value_get_int (value);
+			if (tmp_int != priv->hscale)
+			{
+				priv->hscale = tmp_int;
+			}
+			break;
+
+	case PROP_NODE_WIDTH:
+			tmp_int = g_value_get_int (value);
+			if (tmp_int != priv->node_width)
+			{
+				priv->node_width = tmp_int;
+			}
+			break;
+
+	case PROP_NODE_HEIGHT:
+			tmp_int = g_value_get_int (value);
+			if (tmp_int != priv->node_height)
+			{
+				priv->node_height = tmp_int;
+			}
+			break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+	if (changed)
+	{
+		pert_node_geometry_changed (node);
+		gnome_canvas_item_request_update (item);
+	}
+}
+
+static void
+pert_node_get_property (GObject    *object,
+			guint       param_id,
+			GValue     *value,
+			GParamSpec *pspec)
+{
+	PlannerPertNode     *node;
+	PlannerPertNodePriv *priv;
+
+	g_return_if_fail (PLANNER_IS_PERT_NODE (object));
+
+	node = PLANNER_PERT_NODE (object);
+	priv = node->priv;
+
+	switch (param_id) {
+	case PROP_PCOL:
+			g_value_set_int (value, priv->pcol);
+			break;
+
+	case PROP_PHEIGHT:
+		g_value_set_int (value, priv->pheight);
+		break;
+
+	case PROP_TASK:
+		g_value_set_object (value, priv->task);
+		break;
+
+	case PROP_HIGHLIGHT:
+		g_value_set_boolean (value, priv->highlight);
+		break;
+
+	case PROP_VSCALE:
+		g_value_set_int (value, priv->vscale);
+		break;
+
+	case PROP_HSCALE:
+		g_value_set_int (value, priv->hscale);
+		break;
+
+	case PROP_NODE_WIDTH:
+		g_value_set_int (value, priv->node_width);
+		break;
+
+	case PROP_NODE_HEIGHT:
+		g_value_set_int (value, priv->node_height);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+pert_node_update (GnomeCanvasItem *item,
+		  double          *affine,
+		  ArtSVP          *clip_path,
+		  gint             flags)
+{
+	PlannerPertNode *node;
+	double      x1, y1, x2, y2;
+
+	node = PLANNER_PERT_NODE (item);
+
+	GNOME_CANVAS_ITEM_CLASS (parent_class)->update (item,
+							affine,
+							clip_path,
+							flags);
+
+	pert_node_get_bounds (node, &x1, &y1, &x2, &y2);
+
+	gnome_canvas_update_bbox (item, x1, y1, x2, y2);
+}
+
+static void
+pert_node_realize (GnomeCanvasItem *item)
+{
+	PlannerPertNode     *node;
+	PlannerPertNodePriv *priv;
+
+	node = PLANNER_PERT_NODE (item);
+	priv = node->priv;
+
+	GNOME_CANVAS_ITEM_CLASS (parent_class)->realize (item);
+
+	if (complete_stipple == NULL) {
+		complete_stipple = gdk_bitmap_create_from_data (
+			NULL,
+			complete_stipple_pattern,
+			2,
+			2);
+
+		g_object_add_weak_pointer (G_OBJECT (complete_stipple),
+					   (gpointer) &complete_stipple);
+	} else {
+		g_object_ref (complete_stipple);
+	}
+
+	if (break_stipple == NULL) {
+		break_stipple = gdk_bitmap_create_from_data (
+			NULL,
+			break_stipple_pattern,
+			6,
+			1);
+
+		g_object_add_weak_pointer (G_OBJECT (break_stipple),
+					   (gpointer) &break_stipple);
+	} else {
+		g_object_ref (break_stipple);
+	}
+
+	priv->complete_gc = gdk_gc_new (item->canvas->layout.bin_window);
+	gdk_gc_set_stipple (priv->complete_gc, complete_stipple);
+	gdk_gc_set_fill (priv->complete_gc, GDK_STIPPLED);
+
+	priv->break_gc = gdk_gc_new (item->canvas->layout.bin_window);
+	gdk_gc_set_stipple (priv->break_gc, break_stipple);
+	gdk_gc_set_fill (priv->break_gc, GDK_STIPPLED);
+
+	priv->fill_gc = gdk_gc_new (item->canvas->layout.bin_window);
+
+	priv->frame_gc = gdk_gc_new (item->canvas->layout.bin_window);
+
+	priv->ttask_gc = gdk_gc_new (item->canvas->layout.bin_window);
+
+	gnome_canvas_get_color (item->canvas,
+				"LightSkyBlue3",
+				&priv->color_normal);
+	gnome_canvas_get_color (item->canvas,
+				"#9ac7e0",
+				&priv->color_normal_light);
+	gnome_canvas_get_color (item->canvas,
+				"#7da1b5",
+				&priv->color_normal_dark);
+
+	gnome_canvas_get_color (item->canvas,
+				"#d5d5d5",
+				&priv->color_milestone);
+	gnome_canvas_get_color (item->canvas,
+				"#ffffff",
+				&priv->color_milestone_light);
+	gnome_canvas_get_color (item->canvas,
+				"#9f9f9f",
+				&priv->color_milestone_dark);
+
+	gnome_canvas_get_color (item->canvas,
+				"#6db866",
+				&priv->color_summary);
+	gnome_canvas_get_color (item->canvas,
+				"#82d779",
+				&priv->color_summary_light);
+	gnome_canvas_get_color (item->canvas,
+				"#5c9a55",
+				&priv->color_summary_dark);
+
+	gnome_canvas_get_color (item->canvas,
+				"indian red",
+				&priv->color_critical);
+	gnome_canvas_get_color (item->canvas,
+				"#de6464",
+				&priv->color_critical_light);
+	gnome_canvas_get_color (item->canvas,
+				"#ba5454",
+				&priv->color_critical_dark);
+}
+
+static void
+pert_node_unrealize (GnomeCanvasItem *item)
+{
+	PlannerPertNode *node;
+
+	node = PLANNER_PERT_NODE (item);
+
+	gdk_gc_unref (node->priv->complete_gc);
+	node->priv->complete_gc = NULL;
+
+	gdk_gc_unref (node->priv->break_gc);
+	node->priv->break_gc = NULL;
+
+	gdk_gc_unref (node->priv->fill_gc);
+	node->priv->fill_gc = NULL;
+
+	gdk_gc_unref (node->priv->frame_gc);
+	node->priv->frame_gc = NULL;
+
+	gdk_gc_unref (node->priv->ttask_gc);
+	node->priv->ttask_gc = NULL;
+
+	if (break_stipple) {
+		g_object_unref (break_stipple);
+	}
+
+	if (complete_stipple) {
+		g_object_unref (complete_stipple);
+	}
+
+	GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize (item);
+}
+
+static void
+pert_node_setup_frame_gc (PlannerPertNode *node, gboolean highlight)
+{
+	gdk_gc_set_line_attributes (node->priv->frame_gc,
+				    0,
+				    highlight ? GDK_LINE_ON_OFF_DASH : GDK_LINE_SOLID,
+				    GDK_CAP_BUTT,
+				    GDK_JOIN_MITER);
+}
+
+
+gchar* g_substr (const gchar* string,
+					gint         start,
+				    gint         end)
+{
+	gsize len = (end - start + 1);
+	gchar *output = g_malloc0 (len + 1);
+	return g_utf8_strncpy (output, &string[start], len);
+}
+
+static void
+pert_node_draw (GnomeCanvasItem *item,
+		GdkDrawable     *drawable,
+		gint             x,
+		gint             y,
+		gint             width,
+		gint             height)
+{
+	PlannerPertNode     *node;
+	PlannerPertNodePriv *priv;
+	PlannerPertChart   *chart;
+	MrpTaskType          type;
+	MrpProject		*project;
+	PlannerGanttModel	*model;
+	gboolean             summary;
+	gboolean             highlight_critical;
+	gboolean             critical;
+	gchar				 *text, *str;
+	PangoLayout			 *layout;
+	PangoRectangle		 rect;
+#ifdef WITH_SIMPLE_PRIORITY_SCHEDULING
+	gboolean             is_dominant;
+#endif
+	gint                 cx1, cy1, cx2, cy2, tiery, halfx, tierx;
+	gdouble              dx1, dy1, dx2, dy2, i2w_dx, i2w_dy;
+	GdkColor             color;
+
+	node = PLANNER_PERT_NODE (item);
+	priv = node->priv;
+
+	chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
+	model = PLANNER_GANTT_MODEL (planner_pert_chart_get_model (chart));
+	project = planner_gantt_model_get_project (model);
+
+	highlight_critical = planner_pert_chart_get_highlight_critical_tasks (chart);
+
+	summary = (mrp_task_get_n_children (priv->task) > 0);
+	critical = mrp_task_get_critical (priv->task);
+	type = mrp_task_get_task_type (priv->task);
+
+	i2w_dx = 0.0;
+	i2w_dy = 0.0;
+	gnome_canvas_item_i2w (item, &i2w_dx, &i2w_dy);
+
+	dx1 = node->priv->x;
+	dx2 = node->priv->x + priv->node_width;
+	dy1 = node->priv->y;
+	dy2 = node->priv->y + priv->node_height;
+
+	gnome_canvas_w2c (item->canvas,
+			  dx1 + i2w_dx,
+			  dy1 + i2w_dy,
+			  &cx1,
+			  &cy1);
+
+	gnome_canvas_w2c (item->canvas,
+			  dx2 + i2w_dx,
+			  dy2 + i2w_dy,
+			  &cx2,
+			  &cy2);
+
+	cx1 -= x;
+	cy1 -= y;
+	cx2 -= x;
+	cy2 -= y;
+
+	if (cy1 >= cy2 || cx1 >= cx2) {
+		return;
+	}
+
+#ifdef WITH_SIMPLE_PRIORITY_SCHEDULING
+	is_dominant = mrp_task_is_dominant (priv->task);
+#endif
+	pert_node_setup_frame_gc (node, !summary && priv->highlight);
+
+	if (type == MRP_TASK_TYPE_NORMAL || type == MRP_TASK_TYPE_MILESTONE)
+	{
+		gnome_canvas_get_color (item->canvas, "black", &color);
+		gdk_gc_set_foreground (priv->frame_gc, &color);
+
+#ifdef WITH_SIMPLE_PRIORITY_SCHEDULING
+		for (i = 0 ; i < (is_dominant && !(!summary && priv->highlight) ? 2 : 1) ; ++i) {
+			if (i == 1) {
+				gnome_canvas_get_color (item->canvas, "indian red", &color);
+				gdk_gc_set_foreground (priv->frame_gc, &color);
+
+				gdk_gc_set_line_attributes (priv->frame_gc,
+								0,
+								GDK_LINE_ON_OFF_DASH,
+								GDK_CAP_BUTT,
+								GDK_JOIN_MITER);
+				draw_cut_line (drawable, priv->frame_gc, cx1, cy1, cx2, cy1);
+				draw_cut_line (drawable, priv->frame_gc, cx1, cy2, cx2, cy2);
+				pert_node_setup_frame_gc (node, !summary && priv->highlight);
+				gnome_canvas_get_color (item->canvas, "black", &color);
+				gdk_gc_set_foreground (priv->frame_gc, &color);
+			}
+			else {
+#endif
+				//top line in black
+				draw_cut_line (drawable, priv->frame_gc, cx1, cy1, cx2, cy1);
+				//bottom line in black
+				draw_cut_line (drawable, priv->frame_gc, cx1, cy2, cx2, cy2);
+				#ifdef WITH_SIMPLE_PRIORITY_SCHEDULING
+			}
+		}
+#endif
+		if (!highlight_critical || !critical)
+		{
+			if(type == MRP_TASK_TYPE_MILESTONE)
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_milestone);
+			}
+			else if(summary)
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_summary);
+			}
+			else
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_normal);
+			}
+		}
+		else
+		{
+			gdk_gc_set_foreground (priv->fill_gc, &priv->color_critical);
+		}
+
+		draw_cut_rectangle (drawable,
+						priv->fill_gc,
+						TRUE,
+						cx1 +1,
+						cy1 + 1,
+						cx2 - cx1 -1,
+						cy2 - cy1 -1);
+
+		if (!highlight_critical || !critical)
+		{
+			if(type == MRP_TASK_TYPE_MILESTONE)
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_milestone_light);
+			}
+			else if(summary)
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_summary_light);
+			}
+			else
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_normal_light);
+			}
+		}
+		else
+		{
+			gdk_gc_set_foreground (priv->fill_gc, &priv->color_critical_light);
+		}
+
+		// left side in light color
+		draw_cut_line (drawable,
+			       priv->fill_gc,
+			       cx1 + 0,
+			       cy1 + 1,
+			       cx2 - 0,
+			       cy1 + 1);
+
+		// left side in light color
+		draw_cut_line (drawable,
+				   priv->fill_gc,
+				   cx1 + 1,
+				   cy1 + 1,
+				   cx1 + 1,
+				   cy2 - 1);
+
+		if (!highlight_critical || !critical)
+		{
+			if(type == MRP_TASK_TYPE_MILESTONE)
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_milestone_dark);
+			}
+			else if(summary)
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_summary_dark);
+			}
+			else
+			{
+				gdk_gc_set_foreground (priv->fill_gc, &priv->color_normal_dark);
+			}
+		}
+		else
+		{
+			gdk_gc_set_foreground (priv->fill_gc, &priv->color_critical_dark);
+		}
+
+		draw_cut_line (drawable,
+			       priv->fill_gc,
+			       cx1 + 0,
+			       cy2 - 1,
+			       cx2 - 0,
+			       cy2 - 1);
+
+		draw_cut_line (drawable,
+				   priv->fill_gc,
+				   cx2 - 1,
+				   cy1 + 1,
+				   cx2 - 1,
+				   cy2 - 1);
+
+#ifdef WITH_SIMPLE_PRIORITY_SCHEDULING
+		for (i = 0 ; i < (is_dominant && !(!summary && priv->highlight) ? 2 : 1) ; ++i) {
+			if (i == 1) {
+				gnome_canvas_get_color (item->canvas, "red", &color);
+				gdk_gc_set_foreground (priv->frame_gc, &color);
+
+				gdk_gc_set_line_attributes (priv->frame_gc,
+								0,
+								GDK_LINE_ON_OFF_DASH,
+								GDK_CAP_BUTT,
+								GDK_JOIN_MITER);
+			}
+#endif
+			//left side line in black
+			draw_cut_line (drawable, priv->frame_gc,
+					cx1, cy1, cx1, cy2);
+			//right side line in black
+			draw_cut_line (drawable, priv->frame_gc,
+					cx2, cy1, cx2, cy2);
+
+			//black line 1/3 of the height
+			tiery = (cy2 - cy1)/3;
+			halfx = (cx2 - cx1)/2;
+			tierx = (cx2 - cx1)/3;
+			draw_cut_line (drawable, priv->frame_gc, cx1, cy1+tiery, cx2, cy1+tiery);
+			//black line 2/3 of the height
+			draw_cut_line (drawable, priv->frame_gc, cx1, cy1+2*tiery, cx2, cy1+2*tiery);
+			//black line 1/2 of the width
+			draw_cut_line (drawable, priv->frame_gc, cx1+halfx, cy1, cx1+halfx, cy1+tiery);
+			//black line 1/3 of the width
+			draw_cut_line (drawable, priv->frame_gc, cx1+tierx, cy1+2*tiery, cx1+tierx, cy2);
+			//black line 2/3 of the width
+			draw_cut_line (drawable, priv->frame_gc, cx1+2*tierx, cy1+2*tiery, cx1+2*tierx, cy2);
+
+			layout = gtk_widget_create_pango_layout (GTK_WIDGET (GNOME_CANVAS_ITEM (node)->canvas), NULL);
+			//earliest start of the task
+			str = planner_format_date (mrp_task_get_start(priv->task));
+			pango_layout_set_text (layout, str, -1);
+			draw_cut_layout (drawable,
+							 GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL],
+							 cx1 + HTEXT_PADDING,
+							 cy1 + VTEXT_PADDING,
+							 layout);
+
+			//latest start of the task
+			str = planner_format_date (mrp_task_get_latest_start(priv->task));
+			pango_layout_set_text (layout, str, -1);
+			draw_cut_layout (drawable,
+							 GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL],
+							 cx1 + halfx + HTEXT_PADDING,
+							 cy1 + VTEXT_PADDING,
+							 layout);
+
+			//Name of the task
+			text = (gchar*) mrp_task_get_name(priv->task);
+			if (!text || text[0] == 0)
+				str = _("Unnamed");
+			else
+				str = text;
+
+			if(strlen(str)>12)
+			{
+				text = g_substr(str, 0, 8);
+				sprintf(str, "%s...", text);
+			}
+
+			pango_layout_set_text (layout, str, -1);
+			draw_cut_layout (drawable,
+							 GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL],
+							 cx1 + HTEXT_PADDING,
+							 cy1 + tiery + VTEXT_PADDING,
+							 layout);
+
+			//Duration of the task
+			str = planner_format_duration (MRP_PROJECT(project), mrp_task_get_duration(priv->task));
+			//FIXME have a better display
+			if(!strlen(str))
+			{
+				str = _("N/A");
+			}
+			if(strlen(str)>4)
+			{
+				text = g_substr(str, 0, 1);
+				sprintf(str, "%s..", text);
+			}
+			pango_layout_set_text (layout, str, -1);
+			pango_layout_get_extents(layout, NULL, &rect);
+
+			draw_cut_layout (drawable,
+							 GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL],
+							 cx2 + - HTEXT_PADDING - (rect.width/PANGO_SCALE),
+							 cy1 + 2*tiery + VTEXT_PADDING,
+							 layout);
+
+			//free float of the task
+			str = planner_format_duration (project, mrp_task_get_freefloat(priv->task));
+			//FIXME have a better display
+			if(strlen(str)>4)
+			{
+				text = g_substr(str, 0, 1);
+				sprintf(str, "%s..", text);
+			}
+			pango_layout_set_text (layout, str, -1);
+			pango_layout_get_extents(layout, NULL, &rect);
+
+			draw_cut_layout (drawable,
+							 GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL],
+							 cx1 + 2*tierx - HTEXT_PADDING - (rect.width/PANGO_SCALE),
+							 cy1 + 2*tiery + VTEXT_PADDING,
+							 layout);
+
+			//slack of the task
+			str = planner_format_duration (MRP_PROJECT(project), mrp_task_get_slack(priv->task));
+			//FIXME have a better display
+			if(strlen(str)>4)
+			{
+				text = g_substr(str, 0, 1);
+				sprintf(str, "%s..", text);
+			}
+			pango_layout_set_text (layout, str, -1);
+			pango_layout_get_extents(layout, NULL, &rect);
+
+			draw_cut_layout (drawable,
+							 GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL],
+							 cx1 + tierx - HTEXT_PADDING - (rect.width/PANGO_SCALE),
+							 cy1 + 2*tiery + VTEXT_PADDING,
+							 layout);
+
+#ifdef WITH_SIMPLE_PRIORITY_SCHEDULING
+			if (i == 1) {
+				pert_node_setup_frame_gc (node, !summary && priv->highlight);
+				gnome_canvas_get_color (item->canvas, "black", &color);
+				gdk_gc_set_foreground (priv->frame_gc, &color);
+			}
+		}
+#endif
+	}
+}
+
+static double
+pert_node_point (GnomeCanvasItem  *item,
+		 double            x,
+		 double            y,
+		 gint              cx,
+		 gint              cy,
+		 GnomeCanvasItem **actual_item)
+{
+	PlannerPertNode     *node;
+	PlannerPertNodePriv *priv;
+	gdouble              x1, y1, x2, y2;
+	gdouble              dx, dy;
+
+	node = PLANNER_PERT_NODE (item);
+	priv = node->priv;
+
+	*actual_item = item;
+
+	x1 = priv->x;
+	y1 = priv->y;
+	x2 = x1 + priv->node_width;
+	y2 = y1 + priv->node_height;
+
+	if (x > x1 && x < x2 && y > y1 && y < y2) {
+		return 0.0;
+	}
+
+	/* Point is outside rectangle */
+	if (x < x1) {
+		dx = x1 - x;
+	}
+	else if (x > x2) {
+		dx = x - x2;
+	} else {
+		dx = 0.0;
+	}
+
+	if (y < y1) {
+		dy = y1 - y;
+	}
+	else if (y > y2) {
+		dy = y - y2;
+	} else {
+		dy = 0.0;
+	}
+
+	return sqrt (dx * dx + dy * dy);
+}
+
+static void
+pert_node_bounds (GnomeCanvasItem *item,
+		  double          *x1,
+		  double          *y1,
+		  double          *x2,
+		  double          *y2)
+{
+	PlannerPertNode *node;
+
+	node = PLANNER_PERT_NODE (item);
+
+	pert_node_get_bounds (node, x1, y1, x2, y2);
+
+	if (GNOME_CANVAS_ITEM_CLASS (parent_class)->bounds) {
+		GNOME_CANVAS_ITEM_CLASS (parent_class)->bounds (item, x1, y1, x2, y2);
+	}
+}
+
+static void
+pert_node_notify_cb (MrpTask *task, GParamSpec *pspec, PlannerPertNode *node)
+{
+	/* Note: This is not really nice, it's bug-prone, but we can live with
+	 * it since it's a good optimization.
+	 */
+	//FIXME the display of the node need the infos from the task to be updated
+	/*if (strcmp (pspec->name, "critical") != 0 &&
+		 strcmp (pspec->name, "sched") != 0 &&
+		 strcmp (pspec->name, "priority") != 0 &&
+		 strcmp (pspec->name, "percent-complete"))
+	{
+		return;
+	}*/
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (node));
+}
+
+/* Returns the geometry of the actual bars, not the bounding box, not including
+ * the text labels.
+ */
+void
+planner_pert_node_get_geometry (PlannerPertNode *node,
+				gdouble         *x1,
+				gdouble         *y1,
+				gdouble         *x2,
+				gdouble         *y2)
+{
+	PlannerPertNodePriv *priv;
+
+	g_return_if_fail (PLANNER_IS_PERT_NODE (node));
+
+	priv = node->priv;
+
+	if (x1)
+	{
+		*x1 = priv->x;
+	}
+	if (x2)
+	{
+		*x2 = priv->x + priv->node_width;
+	}
+	if (y1)
+	{
+		*y1 = priv->y;
+	}
+	if (y2)
+	{
+		*y2 = priv->y + priv->node_height;
+	}
+}
+
+void
+planner_pert_node_set_visible (PlannerPertNode *node,
+			       gboolean         is_visible)
+{
+	if (is_visible == node->priv->visible) {
+		return;
+	}
+
+	node->priv->visible = is_visible;
+
+	if (is_visible) {
+		gnome_canvas_item_show (GNOME_CANVAS_ITEM (node));
+	} else {
+		gnome_canvas_item_hide (GNOME_CANVAS_ITEM (node));
+	}
+	g_signal_emit (node,
+		       signals[VISIBILITY_CHANGED],
+		       0,
+		       is_visible);
+}
+
+static void
+pert_node_geometry_changed (PlannerPertNode *node)
+{
+	gdouble x1, y1, x2, y2;
+
+	x1 = node->priv->x;
+	y1 = node->priv->y;
+	x2 = x1 + node->priv->node_width;
+	y2 = y1 + node->priv->node_height;
+
+	g_signal_emit (node,
+		       signals[GEOMETRY_CHANGED],
+		       0,
+		       x1, y1,
+		       x2, y2);
+}
+
+static gboolean
+pert_node_drag_item_to_pointer (PlannerPertNode *node, gboolean scroll)
+{
+	PlannerPertChart *chart;
+	GnomeCanvas *canvas;
+	gint       width, height;
+	gint       x, y, item_cx, item_cy;
+	gint       cx, cy;
+	gdouble    wx2, wy2;
+	gint dx, dy;
+	gchar       *message;
+
+	canvas = GNOME_CANVAS_ITEM (node)->canvas;
+	chart = g_object_get_data (G_OBJECT (GNOME_CANVAS_ITEM (node)->canvas), "chart");
+
+
+	/* Get the current mouse position so that we can decide if the pointer
+	 * is inside the viewport.
+	 */
+	gdk_window_get_pointer (GTK_WIDGET(canvas)->window, &x, &y, NULL);
+
+	gnome_canvas_get_scroll_offsets (canvas, &cx, &cy);
+
+	width = GTK_WIDGET(canvas)->allocation.width;
+	height = GTK_WIDGET(canvas)->allocation.height;
+
+	if(!scroll)
+	{
+		x = CLAMP(x, 0, width - 1);
+		y = CLAMP(y, 0, height - 1);
+	}
+
+	gnome_canvas_c2w (canvas, cx + x, cy + y, &wx2, &wy2);
+
+	if(drag_item_wx_min != -1.0) wx2 = MAX(wx2, drag_item_wx_min);
+	if(drag_item_wx_max != -1.0) wx2 = MIN(wx2, drag_item_wx_max);
+	if(drag_item_wy_min != -1.0) wy2 = MAX(wy2, drag_item_wy_min);
+	if(drag_item_wy_max != -1.0) wy2 = MIN(wy2, drag_item_wy_max);
+
+	switch(node->priv->state)
+	{
+	case STATE_DRAG_LINK:
+		target_item = gnome_canvas_get_item_at (canvas, wx2, wy2);
+
+		drag_points->coords[0] = drag_wx1;
+		drag_points->coords[1] = drag_wy1;
+		drag_points->coords[2] = wx2;
+		drag_points->coords[3] = wy2;
+
+		gnome_canvas_item_set (drag_item,
+				       "points", drag_points,
+				       NULL);
+
+		if (old_target_item && old_target_item != target_item) {
+			g_object_set (old_target_item,
+				      "highlight",
+				      FALSE,
+				      NULL);
+		}
+
+		if (target_item && target_item != GNOME_CANVAS_ITEM(node)) {
+			const gchar *task_name, *target_name;
+
+			g_object_set (target_item,
+				      "highlight",
+				      TRUE,
+				      NULL);
+
+			target_name = mrp_task_get_name (PLANNER_PERT_NODE (target_item)->priv->task);
+			task_name = mrp_task_get_name (node->priv->task);
+
+			if (target_name == NULL || target_name[0] == 0) {
+				target_name = _("No name");
+			}
+			if (task_name == NULL || task_name[0] == 0) {
+				task_name = _("No name");
+			}
+
+			message = g_strdup_printf (_("Make task '%s' a predecessor of '%s'"),
+						   task_name,
+						   target_name);
+
+			planner_pert_chart_status_updated (chart, message);
+
+			g_free (message);
+		}
+
+		if (target_item == NULL) {
+			planner_pert_chart_status_updated (chart, NULL);
+		}
+
+		old_target_item = target_item;
+		break;
+	default:
+		g_assert (FALSE);
+		break;
+	}
+
+	if(!scroll) return TRUE;
+
+	gnome_canvas_w2c (canvas, wx2, wy2, &item_cx, &item_cy);
+
+	if (x < 0) {
+		dx = MAX(x, (item_cx - cx) - width / 2);
+		dx = MIN(dx, 0);
+	} else if (x >= width) {
+		dx = MIN(x - width + 1, (item_cx - cx) - width/2);
+		dx = MAX(dx, 0);
+	} else {
+		dx = 0;
+	}
+
+	if (y < 0) {
+		dy = y;
+	} else if (y >= height) {
+		dy = y - height + 1;
+	} else {
+		dy = 0;
+	}
+
+	if(dx > 0)
+	{
+		planner_pert_chart_reflow_now(chart);
+		pert_node_canvas_scroll (GTK_WIDGET(canvas), dx, dy);
+	}
+	else if(dx < 0)
+	{
+		pert_node_canvas_scroll (GTK_WIDGET(canvas), dx, dy);
+		planner_pert_chart_reflow_now(chart);
+	}
+	else
+	{
+		if(dy != 0)
+		{
+			pert_node_canvas_scroll (GTK_WIDGET(canvas), dx, dy);
+		}
+		planner_pert_chart_reflow_now(chart);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+pert_node_scroll_timeout_cb (PlannerPertNode *node)
+{
+	return pert_node_drag_item_to_pointer(node, TRUE);
+}
+
+static DragSpot
+get_drag_spot(gdouble x, gdouble y, PlannerPertNodePriv *priv)
+{
+	if( (y < priv->y + priv->node_height) &&
+	    (y > priv->y) &&
+	    (x < priv->x + priv->node_width) &&
+	    (x > priv->x))
+	{
+		return DRAG_RELATION_SPOT;
+	}
+	else
+	{ /* above, below or too far to the right */
+		return DRAG_NONE_SPOT;
+	}
+}
+
+static gboolean
+pert_node_event (GnomeCanvasItem *item, GdkEvent *event)
+{
+	PlannerPertNode          *node;
+	PlannerPertNodePriv      *priv;
+	PlannerPertChart        *chart;
+	GtkWidget                *canvas_widget;
+	MrpTask                  *task;
+	MrpTask                  *target_task;
+	gboolean                  summary;
+	MrpTaskType               type;
+	DragSpot                  drag_spot;
+
+	node = PLANNER_PERT_NODE (item);
+	priv = node->priv;
+	canvas_widget = GTK_WIDGET (item->canvas);
+
+	summary = (mrp_task_get_n_children (priv->task) > 0);
+	type = mrp_task_get_task_type (priv->task);
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+		switch (event->button.button) {
+		case 3:
+			drag_spot = get_drag_spot(event->button.x, event->button.y, priv);
+
+			if (drag_spot == DRAG_RELATION_SPOT) {
+				PlannerPertChart *chart;
+				PlannerTaskTree   *tree;
+				GtkTreePath       *path;
+				GtkTreeSelection  *selection;
+				GtkTreeView       *tree_view;
+				GtkTreeIter        iter;
+				GList             *tasks;
+
+				chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
+				tree = planner_pert_chart_get_view (chart);
+				gtk_widget_grab_focus (GTK_WIDGET (tree));
+
+				path = planner_gantt_model_get_path_from_task (
+					PLANNER_GANTT_MODEL (planner_pert_chart_get_model (chart)),
+					priv->task);
+
+				tree_view = GTK_TREE_VIEW (tree);
+
+				selection = gtk_tree_view_get_selection (tree_view);
+
+				gtk_tree_model_get_iter (gtk_tree_view_get_model (tree_view), &iter, path);
+				if (!gtk_tree_selection_iter_is_selected (selection, &iter)) {
+
+					gtk_tree_selection_unselect_all (selection);
+					gtk_tree_selection_select_path (selection, path);
+					planner_task_tree_set_anchor (tree, path);
+				}
+
+				tasks = pert_node_get_selected_tasks (selection);
+				planner_task_popup_update_sensitivity (priv->popup_factory, tasks);
+				g_list_free (tasks);
+
+				gtk_item_factory_popup (priv->popup_factory,
+							event->button.x_root,
+							event->button.y_root,
+							0,
+							gtk_get_current_event_time ());
+
+				return TRUE;
+			}
+			break;
+
+		case 1:
+			if (priv->state != STATE_NONE)
+			{
+				break;
+			}
+			drag_spot = get_drag_spot(event->button.x, event->button.y, priv);
+
+			if (drag_spot == DRAG_RELATION_SPOT)
+			{
+				priv->state = STATE_DRAG_LINK;
+				if (drag_points == NULL)
+				{
+					drag_points = gnome_canvas_points_new (2);
+				}
+
+				drag_item = gnome_canvas_item_new (gnome_canvas_root (item->canvas),
+								   PLANNER_TYPE_CANVAS_LINE,
+								   "points", drag_points,
+								   "last_arrowhead", TRUE,
+								   "arrow_shape_a", 6.0,
+								   "arrow_shape_b", 6.0,
+								   "arrow_shape_c", 3.0,
+								   "fill_color", "black",
+								   "width_pixels", (guint) 0,
+								   "join_style", GDK_JOIN_MITER,
+								   NULL);
+				gnome_canvas_item_hide (drag_item);
+
+				/* set limits */
+				gnome_canvas_get_scroll_region(item->canvas, &drag_item_wx_min,
+									     &drag_item_wy_min,
+									     &drag_item_wx_max,
+									     &drag_item_wy_max);
+
+				old_target_item = NULL;
+
+				/* Start the autoscroll timeout. */
+				priv->scroll_timeout_id = gtk_timeout_add (
+					50,
+					(GSourceFunc) pert_node_scroll_timeout_cb,
+					node);
+			}
+			gnome_canvas_item_grab (item,
+						GDK_POINTER_MOTION_MASK |
+						GDK_POINTER_MOTION_HINT_MASK |
+						GDK_BUTTON_PRESS_MASK |
+						GDK_BUTTON_RELEASE_MASK,
+						NULL,
+						event->button.time);
+
+			drag_wx1 = event->button.x;
+			drag_wy1 = event->button.y;
+
+			return TRUE;
+
+		default:
+			return FALSE;
+		}
+		break;
+
+	case GDK_LEAVE_NOTIFY:
+		/* We get a leave notify when pressing button 1 over the
+		 * item. We don't want to reset the cursor when that happens.
+		 */
+		if (!(priv->state & STATE_DRAG_ANY) &&
+		    !(event->crossing.state & GDK_BUTTON1_MASK)) {
+			gdk_window_set_cursor (canvas_widget->window, NULL);
+		}
+
+		return TRUE;
+
+	case GDK_MOTION_NOTIFY:
+		if (event->motion.is_hint) {
+			gint x, y;
+
+			gdk_window_get_pointer (event->motion.window, &x, &y, NULL);
+			gnome_canvas_window_to_world(item->canvas, x, y, &event->motion.x, &event->motion.y);
+		}
+
+		if (!(priv->state & STATE_DRAG_ANY))
+		{
+			drag_spot = get_drag_spot(event->button.x, event->button.y, priv);
+			return TRUE;
+		}
+		else
+		{
+			gnome_canvas_item_raise_to_top (drag_item);
+			gnome_canvas_item_show (drag_item);
+
+			pert_node_drag_item_to_pointer (node, FALSE);
+		}
+
+		break;
+
+	case GDK_BUTTON_RELEASE: {
+		PlannerTaskTree  *tree;
+		GtkTreePath      *path;
+		GtkTreeSelection *selection;
+
+		if (event->button.button != 1)
+		{
+			return FALSE;
+		}
+
+		if (priv->state == STATE_NONE)
+		{
+			return TRUE;
+		}
+		else if (priv->state == STATE_DRAG_LINK)
+		{
+			if (old_target_item)
+			{
+				g_object_set (old_target_item,
+					      "highlight",
+					      FALSE,
+					      NULL);
+				old_target_item = NULL;
+			}
+
+			if (priv->scroll_timeout_id)
+			{
+				g_source_remove (priv->scroll_timeout_id);
+				priv->scroll_timeout_id = 0;
+			}
+
+			gtk_object_destroy (GTK_OBJECT (drag_item));
+			drag_item = NULL;
+
+			target_item = gnome_canvas_get_item_at (item->canvas,
+								event->button.x,
+								event->button.y);
+
+			if (target_item && target_item != item)
+			{
+				GError            *error = NULL;
+				PlannerCmd        *cmd;
+				PlannerPertChart *chart;
+				PlannerTaskTree   *tree;
+				PlannerWindow     *main_window;
+
+				task = priv->task;
+				target_task = PLANNER_PERT_NODE (target_item)->priv->task;
+
+				chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
+				tree = planner_pert_chart_get_view (chart);
+				main_window = planner_task_tree_get_window (tree);
+
+				cmd = planner_task_cmd_link (main_window,
+							     task,
+							     target_task,
+							     MRP_RELATION_FS,
+							     0,
+							     &error);
+
+				if (!cmd)
+				{
+					GtkWidget *dialog;
+
+					gnome_canvas_item_ungrab (item, event->button.time);
+
+					dialog = gtk_message_dialog_new (NULL,
+									 GTK_DIALOG_DESTROY_WITH_PARENT,
+									 GTK_MESSAGE_ERROR,
+									 GTK_BUTTONS_OK,
+									 "%s", error->message);
+					gtk_dialog_run (GTK_DIALOG (dialog));
+					gtk_widget_destroy (dialog);
+					g_error_free (error);
+				}
+			}
+
+			chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
+
+			planner_pert_chart_status_updated (chart, NULL);
+		}
+
+		/* We're done, reset the cursor and state, ungrab pointer. */
+		gdk_window_set_cursor (canvas_widget->window, NULL);
+
+		gnome_canvas_item_ungrab (item, event->button.time);
+
+		/* Select the clicked on task in the treeview */
+		chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
+		tree = planner_pert_chart_get_view (chart);
+		gtk_widget_grab_focus (GTK_WIDGET (tree));
+
+		path = planner_gantt_model_get_path_from_task (
+			PLANNER_GANTT_MODEL (planner_pert_chart_get_model (chart)),
+			priv->task);
+
+		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+
+		if (!(event->button.state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
+		{
+			gtk_tree_selection_unselect_all (selection);
+			gtk_tree_selection_select_path (selection, path);
+			planner_task_tree_set_anchor (tree, path);
+		}
+		else if (event->button.state & GDK_CONTROL_MASK)
+		{
+			if (gtk_tree_selection_path_is_selected (selection, path))
+			{
+				gtk_tree_selection_unselect_path (selection, path);
+			}
+			else
+			{
+				gtk_tree_selection_select_path (selection, path);
+			}
+			planner_task_tree_set_anchor (tree, path);
+		}
+		else if (event->button.state & GDK_SHIFT_MASK)
+		{
+			GtkTreePath *anchor;
+
+			anchor = planner_task_tree_get_anchor (tree);
+
+			if (anchor)
+			{
+				gtk_tree_selection_unselect_all (selection);
+				gtk_tree_selection_select_range(selection, anchor, path);
+				gtk_tree_path_free (path);
+			}
+			else
+			{
+				gtk_tree_selection_unselect_all (selection);
+				gtk_tree_selection_select_path (selection, path);
+				planner_task_tree_set_anchor (tree, path);
+			}
+		}
+
+		priv->state = STATE_NONE;
+
+		return TRUE;
+	}
+
+	case GDK_2BUTTON_PRESS:
+		if (event->button.button == 1) {
+			drag_spot = get_drag_spot(event->button.x, event->button.y, priv);
+			if (drag_spot == DRAG_RELATION_SPOT)
+			{
+				PlannerTaskTree  *tree;
+				GtkTreePath      *path;
+				GtkTreeSelection *selection;
+
+				chart = g_object_get_data (G_OBJECT (item->canvas), "chart");
+				tree = planner_pert_chart_get_view (chart);
+				gtk_widget_grab_focus (GTK_WIDGET (tree));
+
+				path = planner_gantt_model_get_path_from_task (
+					PLANNER_GANTT_MODEL (planner_pert_chart_get_model (chart)),
+					priv->task);
+
+				selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+
+				gtk_tree_selection_unselect_all (selection);
+				gtk_tree_selection_select_path (selection, path);
+				planner_task_tree_set_anchor(tree, path);
+
+				planner_task_tree_edit_task (tree, PLANNER_TASK_DIALOG_PAGE_GENERAL);
+
+				return TRUE;
+			}
+		}
+
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+static void
+eel_gtk_adjustment_set_value (GtkAdjustment *adjustment,
+			      float          value)
+{
+	float upper_page_start, clamped_value;
+
+	g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+	upper_page_start = MAX (adjustment->upper - adjustment->page_size,
+				adjustment->lower);
+	clamped_value = CLAMP (value, adjustment->lower, upper_page_start);
+	if (clamped_value != adjustment->value) {
+		adjustment->value = clamped_value;
+		gtk_adjustment_value_changed (adjustment);
+	}
+}
+
+static gboolean
+pert_node_canvas_scroll (GtkWidget *widget,
+			 gint       delta_x,
+			 gint       delta_y)
+{
+	GtkAdjustment *hadj, *vadj;
+	int            old_h_value, old_v_value;
+
+	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (widget));
+	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
+
+	/* Store the old adjustment values so we can tell if we ended up
+	 * actually scrolling. We may not have in a case where the resulting
+	 * value got pinned to the adjustment min or max.
+	 */
+	old_h_value = hadj->value;
+	old_v_value = vadj->value;
+
+	eel_gtk_adjustment_set_value (hadj, hadj->value + delta_x);
+	eel_gtk_adjustment_set_value (vadj, vadj->value + delta_y);
+
+	/* return TRUE if we did scroll */
+	return hadj->value != old_h_value || vadj->value != old_v_value;
+}
+
+void
+planner_pert_node_init_menu (PlannerPertNode *node)
+{
+	PlannerPertChart *chart;
+	PlannerTaskTree   *tree;
+
+	chart = g_object_get_data (G_OBJECT (GNOME_CANVAS_ITEM (node)->canvas), "chart");
+	tree = planner_pert_chart_get_view (chart);
+
+	node->priv->popup_factory = planner_task_popup_new (tree);
+}
+
+/* Save this code for later. */
+#if 0
+	gdouble              wx1, wx2;
+	mrptime              t1, t2;    /* First and last exposed times */
+	gint                 tx1, tx2;
+	mrptime              ival_start, ival_end, ival_prev;
+	MrpProject          *project;
+	MrpCalendar         *calendar;
+	MrpDay              *day;
+	GList               *intervals, *l;
+	MrpInterval         *ival;
+	GdkColor             color_break;
+
+
+                /* Draw non-working intervals shaded. FIXME: Disabled for now. */
+		if (0) { g_object_get (priv->task, "project", &project, NULL);
+
+		calendar = mrp_project_get_calendar (project);
+
+		/* Get exposed world coordinates. */
+		gnome_canvas_c2w (item->canvas, x, 0, &wx1, NULL);
+		gnome_canvas_c2w (item->canvas, x + width, 0, &wx2, NULL);
+
+		/* Convert to exposed time coordinates. */
+		t1 = floor (wx1 / 0.5);
+		t2 = floor (wx2 / 0.5);
+
+		t1 = mrp_time_align_day (t1);
+		t2 = mrp_time_align_day (t2 + 24*60*60);
+
+		ival_prev = t1;
+
+		gnome_canvas_get_color (item->canvas, "#b7c3c9", &color_break);
+		gdk_gc_set_foreground (priv->fill_gc, &color_break);
+
+		/*g_print ("-------------------\n");*/
+
+		/* Loop through the days between t1 and t2. */
+		while (t1 <= t2) {
+			day = mrp_calendar_get_day (calendar, t1, TRUE);
+
+			intervals = mrp_calendar_day_get_intervals (calendar, day, TRUE);
+
+			/* Loop through the intervals for this day. */
+			for (l = intervals; l; l = l->next) {
+				ival = l->data;
+
+				mrp_interval_get_absolute (ival,
+							   t1,
+							   &ival_start,
+							   &ival_end);
+
+				/*g_print ("%s %s (%s)\n",
+					 mrp_time_format ("%H:%M", ival_prev),
+					 mrp_time_format ("%H:%M", ival_start),
+					 mrp_time_format ("%a %e %b", ival_prev));*/
+
+				/* Don't draw if the interval is shorter than what we
+				 * want at this zoom level.
+				 */
+				if (planner_scale_conf[level].nonworking_limit <= ival_start - ival_prev) {
+					/* Draw the section between the end of the last working
+					 * time interval and the start of the current one,
+					 * i.e. [ival_prev, ival_start].
+					 */
+					wx1 = ival_prev;
+					wx2 = ival_start;
+
+					gnome_canvas_w2c (item->canvas, wx1, 0, &tx1, NULL);
+					gnome_canvas_w2c (item->canvas, wx2, 0, &tx2, NULL);
+
+					tx1 -= x;
+					tx2 -= x;
+
+					/* Don't draw if we're outside the exposed parts
+					 * of the pert bar.
+					 */
+					if (tx1 >= rx2) {
+						break;
+					}
+
+					if (tx2 <= rx1) {
+						ival_prev = ival_end;
+						continue;
+					}
+
+					tx1 = MAX (tx1, rx1);
+					tx2 = MIN (tx2, rx2);
+
+					if (tx1 < tx2) {
+						draw_cut_rectangle (drawable,
+								    priv->fill_gc,
+								    TRUE,
+								    tx1,
+								    cy1 + 1,
+								    tx2 - tx1,
+								    cy2 - cy1 - 1);
+					}
+
+					/*gdk_gc_set_foreground (priv->fill_gc, &color_high);
+					draw_cut_line (drawable,
+						       priv->fill_gc,
+						       tx1,
+						       cy1 + 1,
+						       tx1,
+						       cy2 - 1);
+
+					draw_cut_line (drawable,
+						       priv->fill_gc,
+						       tx2 - 1,
+						       cy1 + 1,
+						       tx2 - 1,
+						       cy2 - 1);*/
+				}
+
+				ival_prev = ival_end;
+			}
+
+			t1 += 60*60*24;
+
+			/* Draw the remaining interval if any. */
+			if (ival_prev < t1 && planner_scale_conf[level].nonworking_limit <= t1 - ival_prev) {
+				g_print ("%s %s (%s) REMAINING\n",
+					 mrp_time_format ("%H:%M", ival_prev),
+					 mrp_time_format ("%H:%M", t1),
+					 mrp_time_format ("%a %e %b", ival_prev));
+
+				wx1 = ival_prev;
+				wx2 = t1;
+
+				gnome_canvas_w2c (item->canvas, wx1, 0, &tx1, NULL);
+				gnome_canvas_w2c (item->canvas, wx2, 0, &tx2, NULL);
+
+				tx1 -= x;
+				tx2 -= x;
+
+				/* Don't draw if we're outside the exposed parts
+				 * of the pert bar.
+				 */
+				if (tx1 >= rx2 || tx2 <= rx1) {
+					continue;
+				}
+
+				tx1 = MAX (tx1, rx1);
+				tx2 = MIN (tx2, rx2);
+
+				if (tx1 < tx2) {
+					draw_cut_rectangle (drawable,
+							    priv->fill_gc,
+							    TRUE,
+							    tx1,
+							    cy1 + 1,
+							    tx2 - tx1,
+							    cy2 - cy1 - 1);
+				}
+			}
+		}
+		}
+
+#endif
+
+
+/* This should be ina shared file ideally, but there are some linking
+ * problems.
+ */
+
+typedef struct
+{
+	PlannerCmd         base;
+
+	PlannerTaskTree   *tree;
+	MrpProject        *project;
+
+	GtkTreePath       *path;
+
+	gchar             *property;
+	GValue            *value;
+	GValue            *old_value;
+} TaskCmdEditProperty;
+
+static gboolean
+task_cmd_edit_property_do (PlannerCmd *cmd_base)
+{
+	TaskCmdEditProperty *cmd;
+	PlannerGanttModel   *model;
+	MrpTask             *task;
+
+	cmd = (TaskCmdEditProperty*) cmd_base;
+
+	model = PLANNER_GANTT_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (cmd->tree)));
+	task = planner_gantt_model_get_task_from_path (model, cmd->path);
+
+	g_object_set_property (G_OBJECT (task),
+			       cmd->property,
+			       cmd->value);
+
+	return TRUE;
+}
+
+static void
+task_cmd_edit_property_undo (PlannerCmd *cmd_base)
+{
+	TaskCmdEditProperty *cmd;
+	PlannerGanttModel   *model;
+	MrpTask             *task;
+
+	cmd = (TaskCmdEditProperty*) cmd_base;
+
+	model = PLANNER_GANTT_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (cmd->tree)));
+	task = planner_gantt_model_get_task_from_path (model, cmd->path);
+
+	g_object_set_property (G_OBJECT (task),
+			       cmd->property,
+			       cmd->old_value);
+}
+
+static void
+task_cmd_edit_property_free (PlannerCmd *cmd_base)
+{
+	TaskCmdEditProperty *cmd;
+
+	cmd = (TaskCmdEditProperty*) cmd_base;
+
+	g_object_unref (cmd->project);
+
+	g_free (cmd->property);
+	g_value_unset (cmd->value);
+	g_value_unset (cmd->old_value);
+
+	g_free (cmd->value);
+	g_free (cmd->old_value);
+}
+
+static PlannerCmd *
+task_cmd_edit_property (PlannerWindow   *window,
+			PlannerTaskTree *tree,
+			MrpTask         *task,
+			const gchar     *property,
+			const GValue    *value)
+{
+	PlannerTaskTreePriv *priv;
+	PlannerCmd          *cmd_base;
+	TaskCmdEditProperty *cmd;
+	PlannerGanttModel   *model;
+
+	priv = tree->priv;
+
+	cmd_base = planner_cmd_new (TaskCmdEditProperty,
+				    _("Edit task property"),
+				    task_cmd_edit_property_do,
+				    task_cmd_edit_property_undo,
+				    task_cmd_edit_property_free);
+
+	cmd = (TaskCmdEditProperty *) cmd_base;
+
+	cmd->tree = tree;
+	cmd->project = g_object_ref (planner_window_get_project (window));
+
+	model = PLANNER_GANTT_MODEL (gtk_tree_view_get_model (GTK_TREE_VIEW (tree)));
+
+	cmd->path = planner_gantt_model_get_path_from_task (model, task);
+
+	cmd->property = g_strdup (property);
+
+	cmd->value = g_new0 (GValue, 1);
+	g_value_init (cmd->value, G_VALUE_TYPE (value));
+	g_value_copy (value, cmd->value);
+
+	cmd->old_value = g_new0 (GValue, 1);
+	g_value_init (cmd->old_value, G_VALUE_TYPE (value));
+
+	g_object_get_property (G_OBJECT (task),
+			       cmd->property,
+			       cmd->old_value);
+
+	planner_cmd_manager_insert_and_do (planner_window_get_cmd_manager (window),
+					   cmd_base);
+
+	return cmd_base;
+}
+
+static  void
+pert_node_get_selected_func (GtkTreeModel *model,
+			     GtkTreePath  *path,
+			     GtkTreeIter  *iter,
+			     gpointer      data)
+{
+	GList   **list = data;
+	MrpTask  *task;
+
+	gtk_tree_model_get (model,
+			    iter,
+			    COL_TASK, &task,
+			    -1);
+
+	*list = g_list_prepend (*list, task);
+}
+
+static GList *
+pert_node_get_selected_tasks (GtkTreeSelection *selection)
+{
+	GList *list;
+
+	list = NULL;
+	gtk_tree_selection_selected_foreach (selection,
+					     pert_node_get_selected_func,
+					     &list);
+
+	list = g_list_reverse (list);
+
+	return list;
+}
diff --git a/src/planner-pert-node.h b/src/planner-pert-node.h
new file mode 100644
index 0000000..fc06f93
--- /dev/null
+++ b/src/planner-pert-node.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2002 CodeFactory AB
+ * Copyright (C) 2001-2002 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2001-2002 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PLANNER_PERT_NODE_H__
+#define __PLANNER_PERT_NODE_H__
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/gnome-canvas.h>
+#include <libgnomecanvas/gnome-canvas-util.h>
+
+#define PLANNER_TYPE_PERT_NODE            (planner_pert_node_get_type ())
+#define PLANNER_PERT_NODE(obj)            (GTK_CHECK_CAST ((obj), PLANNER_TYPE_PERT_NODE, PlannerPertNode))
+#define PLANNER_PERT_NODE_CLASS(klass)    (GTK_CHECK_CLASS_CAST ((klass), PLANNER_TYPE_PERT_NODE, PlannerPertNodeClass))
+#define PLANNER_IS_PERT_NODE(obj)         (GTK_CHECK_TYPE ((obj), PLANNER_TYPE_PERT_NODE))
+#define PLANNER_IS_PERT_NODE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), PLANNER_TYPE_PERT_NODE))
+#define PLANNER_PERT_NODE_GET_CLASS(obj)  (GTK_CHECK_GET_CLASS ((obj), PLANNER_TYPE_PERT_NODE, PlannerPertNodeClass))
+
+
+typedef struct _PlannerPertNode      PlannerPertNode;
+typedef struct _PlannerPertNodeClass PlannerPertNodeClass;
+typedef struct _PlannerPertNodePriv  PlannerPertNodePriv;
+
+struct _PlannerPertNode {
+	GnomeCanvasItem  parent;
+	PlannerPertNodePriv  *priv;
+};
+
+struct _PlannerPertNodeClass {
+	GnomeCanvasItemClass parent_class;
+};
+
+
+GType planner_pert_node_get_type     (void) G_GNUC_CONST;
+void  planner_pert_node_get_geometry (PlannerPertNode *row,
+				 gdouble    *x1,
+				 gdouble    *y1,
+				 gdouble    *x2,
+				 gdouble    *y2);
+void  planner_pert_node_set_visible  (PlannerPertNode *row,
+				 gboolean    is_visible);
+
+void planner_pert_node_init_menu (PlannerPertNode *row);
+
+#endif /* __PLANNER_PERT_NODE_H__ */
diff --git a/src/planner-pert-print.c b/src/planner-pert-print.c
new file mode 100644
index 0000000..f664f00
--- /dev/null
+++ b/src/planner-pert-print.c
@@ -0,0 +1,1545 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 Imendio AB
+ * Copyright (C) 2003 CodeFactory AB
+ * Copyright (C) 2003 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2003 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <string.h>
+#include <libplanner/mrp-project.h>
+#include <libplanner/mrp-task.h>
+#include <libplanner/mrp-resource.h>
+#include <glib/gi18n.h>
+#include "planner-print-job.h"
+#include "planner-format.h"
+#include "planner-gantt-model.h"
+#include "planner-pert-print.h"
+#include "planner-scale-utils.h"
+
+#define d(x)
+#define GET_PAGE(d,r,c) (&d->pages[r*d->cols_of_pages+c])
+#define INDENT_FACTOR 4
+
+typedef struct {
+	mrptime  first_time;
+	mrptime  last_time;
+
+	gdouble  pert_x0;
+	gdouble  pert_y0;
+
+	GList   *elements;
+	GList   *background_elements;
+} Page;
+
+struct _PlannerPertPrintData {
+	MrpProject        *project;
+	PlannerView       *view;
+	PlannerPrintJob   *job;
+
+	/* Used to get the visible (expanded) tasks. */
+	GtkTreeView       *tree_view;
+	
+	/* Settings. */
+	gboolean           show_critical;
+
+	gint               level;
+	
+	MrpTimeUnit        major_unit;
+	PlannerScaleFormat major_format;
+	
+	MrpTimeUnit        minor_unit;
+	PlannerScaleFormat minor_format;
+	
+	gdouble            header_height;
+	
+	gint               tasks_per_page_with_header;
+	gint               tasks_per_page_without_header;
+
+	/* The matrix of pages we will produce. */
+	gint               rows_of_pages;
+	gint               cols_of_pages;
+
+	gdouble            tree_x1;
+	gdouble            tree_x2;
+	
+	gdouble            name_x1;
+	gdouble            name_x2;
+
+	gdouble            work_x1;
+	gdouble            work_x2;
+	
+	gdouble            row_height;
+
+	GHashTable        *task_start_coords;
+	GHashTable        *task_finish_coords;
+	
+	PangoFontDescription         *font;
+	PangoFontDescription         *font_bold;
+
+	GList             *tasks;
+
+	gdouble            f;
+	
+	/* Summary drawing. */
+	gdouble            summary_height;
+	gdouble            summary_thick;
+	gdouble            summary_slope;
+
+	/* Milestone drawing. */
+	gdouble            milestone_size;
+
+	/* Relation arrow drawing. */
+	gdouble            arrow_width;
+	gdouble            arrow_height;
+	
+	/* Printed time span. */
+	mrptime            start;
+	mrptime            finish;
+
+	mrptime            second_column_start;
+
+	Page              *pages;
+};
+
+typedef struct {
+	/* Page */
+	gint    row, col;
+
+	/* Position on that page. */
+	gdouble x, y;
+} TaskCoord;
+
+typedef struct {
+	MrpTask *task;
+	gint     depth;
+} PrintTask;
+
+typedef enum {
+	TASK_WHOLE,
+	TASK_LEFT,
+	TASK_RIGHT,
+	TASK_MIDDLE,
+
+	SUMMARY_WHOLE,
+	SUMMARY_LEFT,
+	SUMMARY_RIGHT,
+	SUMMARY_MIDDLE,
+
+	MILESTONE,
+	
+	RELATION_HORIZ,
+	RELATION_VERT,
+	RELATION_ARROW_DOWN,
+	RELATION_ARROW_UP,
+
+	RESOURCES,
+
+	SHADE,
+	TIMELINE
+} ElementType;
+
+typedef struct {
+	ElementType  type;
+
+	gdouble      x1, y1;
+	gdouble      x2, y2;
+
+	gdouble      x_complete;
+
+	gboolean     is_critical;
+	gchar       *resources;
+} Element;
+
+
+static void
+print_table_header (PlannerPertPrintData *data)
+{
+	gdouble x, y;
+
+	cairo_set_line_width (data->job->cr, THIN_LINE_WIDTH);
+	planner_print_job_set_font_bold (data->job);
+
+	y = data->header_height;
+
+	planner_print_job_moveto (data->job, data->tree_x1, y);
+	planner_print_job_lineto (data->job, data->tree_x2, y);
+	cairo_stroke (data->job->cr);
+
+	x = data->name_x1 + data->job->x_pad;
+	y = data->row_height;
+
+	planner_print_job_show_clipped (data->job,
+					x, y,
+					_("Name"),
+					data->name_x1 + data->job->x_pad, y - data->row_height, 
+					data->name_x2 - data->job->x_pad, y + data->row_height);
+
+	x = data->work_x1 + data->job->x_pad;
+
+	planner_print_job_show_clipped (data->job,
+					x, y,
+					_("Work"),
+					data->work_x1 + data->job->x_pad, 0, 
+					data->work_x2 - data->job->x_pad, y + data->row_height);
+
+	planner_print_job_set_font_regular (data->job);
+}
+
+static void
+print_table_tasks (PlannerPertPrintData *data,
+		   gboolean               header,
+		   GList                 *tasks,
+		   gint                   first)
+{
+	gchar     *str;
+	gint       work;
+	gdouble    x, y;
+	GList     *l, *to;
+	PrintTask *ptask;
+	gint       last, i;
+
+	if (header) {
+		last = first + data->tasks_per_page_with_header;
+	} else {
+		last = first + data->tasks_per_page_without_header;
+	}
+	
+	l = g_list_nth (tasks, first);
+	to = g_list_nth (tasks, last);
+	i = 1;
+
+	cairo_set_line_width (data->job->cr, THIN_LINE_WIDTH);
+	
+	while (l && l != to) {
+		ptask = l->data;
+		
+		g_object_get (ptask->task,
+			      "name", &str,
+			      "work", &work,
+			      NULL);
+
+		if (mrp_task_get_n_children (ptask->task) > 0) {
+			planner_print_job_set_font_bold (data->job);
+		} else {
+			planner_print_job_set_font_regular (data->job);
+		}
+
+		x = data->name_x1 + data->job->x_pad + ptask->depth * INDENT_FACTOR * data->job->x_pad;
+		y = i * data->row_height;
+		
+		if (header) {
+			y += data->header_height;
+		}
+
+		planner_print_job_show_clipped (data->job,
+						x, y - data->row_height / 4,
+						str,
+						data->name_x1 + data->job->x_pad, y - data->row_height, 
+						data->name_x2 - data->job->x_pad, y);
+
+		g_free (str);
+		
+		x = data->work_x1 + data->job->x_pad;
+		
+		str = planner_format_duration (data->project, work);
+		planner_print_job_show_clipped (data->job,
+						x, y - data->row_height / 4,
+						str,
+						data->work_x1 + data->job->x_pad, y - data->row_height, 
+						data->work_x2 - data->job->x_pad, y);
+		g_free (str);
+
+		planner_print_job_moveto (data->job, 0, y);
+		planner_print_job_lineto (data->job, data->tree_x2, y);
+		cairo_stroke (data->job->cr);
+
+		i++;
+		l = l->next;
+	}
+
+	cairo_set_line_width (data->job->cr, 1);
+}
+
+static void
+print_time_header (PlannerPertPrintData *data,
+		   gdouble                x1,
+		   gdouble                x2,
+		   mrptime                start,
+		   mrptime                finish)
+{
+	gdouble  x, y;
+	gdouble  y1, y2, y3;
+	gdouble  width;
+	mrptime  t;
+	gchar   *str;
+
+	y1 = 0;
+	y2 = data->header_height / 2;
+	y3 = data->header_height;
+
+	cairo_set_line_width (data->job->cr, THIN_LINE_WIDTH);
+
+	planner_print_job_moveto (data->job, x1, y2);
+	planner_print_job_lineto (data->job, x2, y2);
+	cairo_stroke (data->job->cr);
+
+	planner_print_job_moveto (data->job, x1, y3);
+	planner_print_job_lineto (data->job, x2, y3);
+	cairo_stroke (data->job->cr);
+
+	/* Major scale. */
+	x = x1;
+	y = y2 - data->row_height / 4;
+
+	planner_print_job_set_font_regular (data->job);	
+
+	t = mrp_time_align_prev (start, data->major_unit);
+	width = (mrp_time_align_next (t, data->major_unit) - t) / data->f;
+
+	while (t <= finish) {
+		x = x1 + (t - start) / data->f;
+
+		if (x + width > x1) {
+			if (x > x1) {
+				planner_print_job_moveto (data->job, x, y1);
+				planner_print_job_lineto (data->job, x, y2);
+				cairo_stroke (data->job->cr);
+			}
+			
+			str = planner_scale_format_time (t, data->major_unit, data->major_format);
+			
+			planner_print_job_show_clipped (data->job,
+							x + data->job->x_pad, y,
+							str,
+							MAX (x, x1), y1, 
+							x + width, y2);
+			
+			g_free (str);
+		}
+		
+		t = mrp_time_align_next (t, data->major_unit);
+	}
+
+	/* Minor scale. */
+	x = x1;	
+	y = y3 - data->row_height / 4;
+
+	t = mrp_time_align_prev (start, data->minor_unit);
+	width = (mrp_time_align_next (t, data->minor_unit) - t) / data->f;
+
+	while (t <= finish) {
+		x = x1 + (t - start) / data->f;
+
+		if (x + width > x1) {
+			if (x > x1) {
+				planner_print_job_moveto (data->job, x, y2);
+				planner_print_job_lineto (data->job, x, y3);
+				cairo_stroke (data->job->cr);
+			}
+			
+			str = planner_scale_format_time (t, data->minor_unit, data->minor_format);
+			
+			planner_print_job_show_clipped (data->job,
+							x + data->job->x_pad, y,
+							str,
+							MAX (x, x1), y2, 
+							x + width, y3);
+			
+			g_free (str);
+		}
+		
+		t = mrp_time_align_next (t, data->minor_unit);
+	}
+}
+
+typedef struct {
+	GtkTreeView *tree_view;
+	GList       *list;
+} ForeachVisibleData;
+
+static gboolean
+foreach_visible_task (GtkTreeModel *model,
+		      GtkTreePath  *path,
+		      GtkTreeIter  *iter,
+		      gpointer      data)
+{
+	ForeachVisibleData *fvd = data;
+	MrpTask            *task;
+	GtkTreeIter         parent_iter;
+	GtkTreePath        *parent_path;
+	PrintTask          *ptask;
+
+	gtk_tree_model_iter_parent (model, &parent_iter, iter);
+	parent_path = gtk_tree_model_get_path (model, &parent_iter);
+	if (gtk_tree_path_get_depth (path) == 1 ||
+	    gtk_tree_view_row_expanded (fvd->tree_view, parent_path)) {
+		gtk_tree_model_get (model,
+				    iter, 
+				    COL_TASK, &task,
+				    -1);
+		
+		ptask = g_new0 (PrintTask, 1);
+		ptask->task = task;
+		ptask->depth = gtk_tree_path_get_depth (path);
+
+		fvd->list = g_list_prepend (fvd->list, ptask);
+	}
+	gtk_tree_path_free (parent_path);
+	
+	return FALSE;
+}
+
+static GList *
+pert_print_get_visible_tasks (PlannerPertPrintData *data)
+{
+	ForeachVisibleData  fvd;
+	GtkTreeModel       *model;
+
+	model = gtk_tree_view_get_model (data->tree_view);
+
+	fvd.list = NULL;
+	fvd.tree_view = data->tree_view;
+	
+	gtk_tree_model_foreach (model,
+				foreach_visible_task,
+				&fvd);
+
+	return g_list_reverse (fvd.list);
+}
+
+static void
+pert_print_free_print_tasks (GList *tasks)
+{
+	GList *l;
+
+	for (l = tasks; l; l = l->next) {
+		g_free (l->data);
+	}
+}	
+
+static GList *
+pert_print_get_relations (PlannerPertPrintData *data)
+{
+	GList   *tasks, *l;
+	GList   *predecessors, *p;
+	GList  *relations = NULL;
+
+	tasks = mrp_project_get_all_tasks (data->project);
+	for (l = tasks; l; l = l->next) {
+		predecessors = mrp_task_get_predecessor_relations (l->data);
+		
+		for (p = predecessors; p; p = p->next) {
+			relations = g_list_prepend (relations, p->data);
+		}
+	}
+
+	g_list_free (tasks);
+
+	return relations;
+}
+
+static void
+pert_print_task (PlannerPertPrintData *data, Element *element)
+{
+	cairo_new_path (data->job->cr);
+	planner_print_job_moveto (data->job, element->x1, element->y1);
+	planner_print_job_lineto (data->job, element->x2, element->y1);
+	planner_print_job_lineto (data->job, element->x2, element->y2);
+	planner_print_job_lineto (data->job, element->x1, element->y2);
+	cairo_close_path (data->job->cr);
+	
+	cairo_save (data->job->cr);
+
+	if (data->show_critical && element->is_critical) {
+		cairo_set_source_rgb (data->job->cr, 205/255.0, 92/255.0, 92/255.0);
+	} else {
+		cairo_set_source_rgb (data->job->cr, 235/255.0, 235/255.0, 235/255.0);
+	}
+
+	cairo_fill_preserve (data->job->cr);
+	cairo_set_line_width (data->job->cr, THIN_LINE_WIDTH);
+	cairo_set_source_rgb (data->job->cr, 0, 0, 0);
+	cairo_stroke (data->job->cr);
+
+	cairo_restore (data->job->cr);
+
+
+	/* Percent complete. */
+	if (element->x_complete > 0) {
+		gdouble pad;
+
+		pad = (element->y2 - element->y1) * 0.25;
+		
+		cairo_save (data->job->cr);
+
+		cairo_new_path (data->job->cr);
+		planner_print_job_moveto (data->job, element->x1, element->y1 + pad);
+		planner_print_job_lineto (data->job, element->x_complete, element->y1 + pad);
+		planner_print_job_lineto (data->job, element->x_complete, element->y2 - pad);
+		planner_print_job_lineto (data->job, element->x1, element->y2 - pad);
+		cairo_close_path (data->job->cr);
+		
+		cairo_set_source_rgb (data->job->cr, 135/255.0, 135/255.0, 135/255.0);
+		cairo_fill (data->job->cr);
+		
+		cairo_restore (data->job->cr);
+	}
+}
+
+static gboolean
+pert_print_get_allocated_resources_string (PlannerPertPrintData  *data,
+					    MrpTask                *task,
+					    gchar                 **str,
+					    gdouble                *width)
+{
+	GList         *l;
+	GList         *assignments;
+	MrpAssignment *assignment;
+	MrpResource   *resource;
+	gchar         *name, *tmp_str, *name_unit;
+	gchar         *text = NULL;
+	gdouble        w = 0;
+	gint           units;
+
+	assignments = mrp_task_get_assignments (task);
+	for (l = assignments; l; l = l->next) {
+		assignment = l->data;
+
+		resource = mrp_assignment_get_resource (assignment);
+		units = mrp_assignment_get_units (assignment);
+
+		/* Use the resource short_name in preference to the resource
+		 * name.
+		 */
+		g_object_get (resource, 
+			      "short_name", &name, 
+			      NULL);
+				
+		if (name && name[0] == 0) {
+			g_free (name);
+
+			g_object_get (resource, 
+				      "name", &name, 
+				      NULL);
+			
+			if (name && name[0] == 0) {
+				g_free (name);
+				
+				name = g_strdup (_("Unnamed"));
+			}
+		}
+
+		if (units != 100) {
+			name_unit = g_strdup_printf ("%s [%i]", name, units);
+			g_free (name);
+		} else {
+			name_unit = name;
+		}
+		
+		if (!text) { /* First resource */
+			text = name_unit;
+			continue;
+		}
+		
+		tmp_str = g_strdup_printf ("%s, %s", text, name_unit);
+
+		g_free (text);
+		g_free (name_unit);
+
+		text = tmp_str;
+	}
+
+	if (text && width) {
+		w = planner_print_job_get_extents(data->job, text);
+	}
+
+	if (width) {
+		*width = w;
+	}
+	
+	if (str) {
+		*str = text;
+	} else {
+		g_free (text);
+	}
+
+	return text != NULL;
+}
+
+void
+planner_pert_print_do (PlannerPertPrintData *data, gint page_nr)
+{
+	GList       *l;
+	gdouble      x1, x2;
+	mrptime      t1, t2;
+	Element     *element;
+	gint         row, col, first_task;
+	Page        *page;
+
+	/* Fourth pass, generate pages. */
+	col = page_nr % data->cols_of_pages;
+	row = page_nr / data->cols_of_pages;
+	
+	x2 = data->job->width;
+
+	planner_print_job_begin_next_page (data->job);
+
+	if (row == 0) {
+		first_task = 0;
+	} else {
+		first_task = data->tasks_per_page_with_header + 
+			     data->tasks_per_page_without_header * (row - 1);
+	}
+
+	if (col == 0) {
+		x1 = data->tree_x2;
+		t1 = data->start;
+		t2 = data->second_column_start;
+
+		cairo_set_line_width (data->job->cr, THIN_LINE_WIDTH);
+		
+		planner_print_job_moveto (data->job,
+					  data->tree_x2,
+					  0);
+		planner_print_job_lineto (data->job,
+					  data->tree_x2,
+					  data->job->height);
+		cairo_stroke (data->job->cr);
+		
+		planner_print_job_moveto (data->job,
+					  data->name_x2,
+					  0);
+		planner_print_job_lineto (data->job,
+					  data->name_x2,
+					  data->job->height);
+		cairo_stroke (data->job->cr);
+		
+		print_table_tasks (data,
+				   row == 0,
+				   data->tasks,
+				   first_task);
+	} else {
+		x1 = 0;
+		t1 = data->second_column_start + (col - 1) * data->job->width * data->f;
+		t2 = t1 + data->job->width * data->f;
+	}
+
+	page = GET_PAGE (data, row, col);
+	for (l = page->background_elements; l; l = l->next) {
+		gdouble dashes[] = { 4, 4 };
+				
+		element = l->data;
+		
+		switch (element->type) {
+		case TIMELINE:
+			cairo_new_path (data->job->cr);
+			cairo_set_source_rgb (data->job->cr, 150/255.0, 150/255.0, 249/255.0);
+			
+			cairo_set_dash (data->job->cr, dashes, 2, 0);
+			
+			cairo_set_line_width (data->job->cr, 1);
+			planner_print_job_moveto (data->job, element->x1, element->y1);
+			planner_print_job_lineto (data->job, element->x1, element->y2);
+			cairo_stroke (data->job->cr);
+			
+			cairo_set_dash (data->job->cr, NULL, 0, 0);
+			
+			break;
+		case SHADE:
+			cairo_new_path (data->job->cr);
+			planner_print_job_moveto (data->job, element->x1, element->y1);
+			planner_print_job_lineto (data->job, element->x2, element->y1);
+			planner_print_job_lineto (data->job, element->x2, element->y2);
+			planner_print_job_lineto (data->job, element->x1, element->y2);
+			cairo_close_path (data->job->cr);
+			
+			cairo_set_source_rgb (data->job->cr, 249/255.0, 249/255.0, 249/255.0);
+			cairo_fill (data->job->cr);
+			
+			cairo_set_line_width (data->job->cr, THIN_LINE_WIDTH);
+			cairo_set_source_rgb (data->job->cr, 150/255.0, 150/255.0, 150/255.0);
+			planner_print_job_moveto (data->job, element->x1, element->y1);
+			planner_print_job_lineto (data->job, element->x1, element->y2);
+			cairo_stroke (data->job->cr);
+			break;
+		default:
+			break;
+		}
+	}
+	
+	cairo_set_source_rgb (data->job->cr, 0, 0, 0);
+	
+	if (row == 0) {
+		print_time_header (data, x1, x2, t1, t2);
+		if (col == 0) {
+			print_table_header (data);
+		}
+	}
+	
+	for (l = page->elements; l; l = l->next) {
+		element = l->data;
+		
+		cairo_set_source_rgb (data->job->cr, 0, 0, 0);
+		
+		switch (element->type) {
+		case TASK_LEFT:
+		case TASK_RIGHT:
+		case TASK_WHOLE:
+		case TASK_MIDDLE:
+			pert_print_task (data, element);
+			break;
+		case SUMMARY_LEFT:
+			planner_print_job_moveto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick + data->summary_height);
+			planner_print_job_lineto (data->job,
+						  element->x1 + data->summary_slope,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			
+			planner_print_job_moveto (data->job,
+						  element->x2,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case SUMMARY_RIGHT:
+			planner_print_job_moveto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick + data->summary_height);
+			planner_print_job_lineto (data->job,
+						  element->x2 - data->summary_slope,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			
+			planner_print_job_moveto (data->job,
+						  element->x2,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case SUMMARY_WHOLE:
+			planner_print_job_moveto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick + data->summary_height);
+			planner_print_job_lineto (data->job,
+						  element->x1 + data->summary_slope,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			
+			planner_print_job_moveto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick + data->summary_height);
+			planner_print_job_lineto (data->job,
+						  element->x2 - data->summary_slope,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			
+			planner_print_job_moveto (data->job,
+						  element->x2,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case SUMMARY_MIDDLE:
+			planner_print_job_moveto (data->job,
+						  element->x2,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + 2 * data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + data->summary_thick);
+			planner_print_job_lineto (data->job,
+						  element->x2,
+						  element->y1 + data->summary_thick);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case RELATION_ARROW_DOWN:
+			planner_print_job_moveto (data->job,
+						  element->x1,
+						  element->y1);
+			planner_print_job_lineto (data->job,
+						  element->x1 - data->arrow_width,
+						  element->y1 - data->arrow_height);
+			planner_print_job_lineto (data->job,
+						  element->x1 + data->arrow_width,
+						  element->y1 - data->arrow_height);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case RELATION_ARROW_UP:
+			planner_print_job_moveto (data->job,
+						  element->x1,
+						  element->y1);
+			planner_print_job_lineto (data->job,
+						  element->x1 - data->arrow_width,
+						  element->y1 + data->arrow_height);
+			planner_print_job_lineto (data->job,
+						  element->x1 + data->arrow_width,
+						  element->y1 + data->arrow_height);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case RELATION_HORIZ:
+			planner_print_job_moveto (data->job, element->x1, element->y1);
+			planner_print_job_lineto (data->job, element->x2, element->y1);
+			cairo_stroke (data->job->cr);
+			break;
+		case RELATION_VERT:
+			planner_print_job_moveto (data->job, element->x1, element->y1);
+			planner_print_job_lineto (data->job, element->x1, element->y2);
+			cairo_stroke (data->job->cr);
+			break;
+		case MILESTONE:
+			planner_print_job_moveto (data->job,
+						  element->x1,
+						  element->y1);
+			planner_print_job_lineto (data->job,
+						  element->x1 + data->milestone_size,
+						  element->y1 + data->milestone_size);
+			planner_print_job_lineto (data->job,
+						  element->x1,
+						  element->y1 + 2 * data->milestone_size);
+			planner_print_job_lineto (data->job,
+						  element->x1 - data->milestone_size,
+						  element->y1 + data->milestone_size);
+			cairo_close_path (data->job->cr);
+			cairo_fill (data->job->cr);
+			break;
+		case RESOURCES:
+			planner_print_job_show_clipped (data->job,
+							element->x1,
+							element->y1,
+							element->resources,
+							0, 0,
+							data->job->width,
+							data->job->height);
+			break;
+		default:
+			break;
+		}
+	}
+	
+	planner_print_job_finish_page (data->job, TRUE);
+}
+
+PlannerPertPrintData *
+planner_pert_print_data_new (PlannerView     *view,
+			      PlannerPrintJob *job,
+			      GtkTreeView     *tree_view,
+			      gint             level,
+			      gboolean         show_critical)
+{
+	PlannerPertPrintData *data;
+	GList                 *tasks = NULL, *l;
+	gint                   num_tasks;
+	gdouble                max_name_width = 0.0;
+	gdouble                ext;
+	GList       *relations;
+	gdouble      x1;
+	gdouble      y1, y2;
+	mrptime      t1, t2, t0;
+	mrptime      current_time;
+	mrptime      start, finish;
+	mrptime      complete;
+	gboolean     is_summary;
+	gboolean     is_critical;
+	MrpTaskType  type;
+	PrintTask   *ptask;
+	TaskCoord   *task_coord;
+	Element     *element;
+	gdouble      x0, y0;
+	gint         i, row, col;
+	gint         tasks_on_this_page;
+	gchar       *name;
+	Page        *page;
+	gchar       *str;
+	MrpCalendar *calendar;
+	MrpDay      *day;
+	GList       *ivals;
+
+	data = g_new0 (PlannerPertPrintData, 1);
+
+	data->view = view;
+	data->job = job;
+	data->project = planner_window_get_project (view->main_window);
+
+	data->tree_view = tree_view;
+
+	data->show_critical = show_critical;
+	data->level = level;
+	
+	/* Note: This looks hackish, but it's more or less the same equation
+	 * used for the zoom level in the pert chart, which actually is
+	 * calculated to have a "good feel" :).
+	 */
+	data->f = 1.8 / pow (2, level - 19);
+
+	data->major_unit = planner_scale_conf[level].major_unit;
+	data->major_format = planner_scale_conf[level].major_format;
+
+	data->minor_unit = planner_scale_conf[level].minor_unit;
+	data->minor_format = planner_scale_conf[level].minor_format;
+
+	data->task_start_coords = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+	data->task_finish_coords = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+
+	/* Start and finish of the project. */
+	data->start = mrp_project_get_project_start (data->project);
+	
+	tasks = pert_print_get_visible_tasks (data);
+	data->tasks = tasks;
+	num_tasks = g_list_length (tasks);
+
+	data->finish = data->start;
+	
+	/* Go through the tasks and get the end time by checking the right-most
+	 * resource label we will print.
+	 */
+	for (l = tasks; l; l = l->next) {
+		PrintTask *ptask = l->data;
+		MrpTask   *task  = ptask->task;
+		gchar     *name;
+		mrptime    finish;
+		gdouble    width;
+		gdouble    name_width;
+		
+		g_object_get (task,
+			      "name", &name,
+			      "finish", &finish,
+			      NULL);
+
+		ext = planner_print_job_get_extents(data->job, name);
+		name_width = ext + ptask->depth * INDENT_FACTOR * data->job->x_pad;
+		
+		if (max_name_width < name_width) {
+			max_name_width = name_width;
+		}
+
+		pert_print_get_allocated_resources_string (data, task, NULL, &width);
+
+		data->finish = MAX (data->finish, finish);/* + width * data->f);*/
+
+		g_free (name);
+	}
+	
+	/* TODO: figure out why the width of WW and WORKWO is significant */
+	data->name_x1 = 0;
+	ext = planner_print_job_get_extents (data->job, "WW");
+	data->name_x2 = data->name_x1 + max_name_width + ext;
+	
+	data->work_x1 = data->name_x2;
+	ext = planner_print_job_get_extents (data->job, "WORKWO");
+	data->work_x2 = data->work_x1 + ext;
+
+	data->tree_x1 = 0;
+	data->tree_x2 = data->work_x2;
+
+	data->second_column_start = data->start + (data->job->width - (data->tree_x2 - data->tree_x1)) * data->f;
+
+	data->row_height = 2 * planner_print_job_get_font_height (job);
+	
+	data->header_height = 2 * data->row_height + data->row_height / 4;
+	
+	/* Calculate drawing "constants". */
+	data->summary_height = 0.24 * data->row_height;
+	data->summary_thick  = 0.07 * data->row_height;
+	data->summary_slope  = 0.22 * data->row_height;
+	data->milestone_size = 0.25 * data->row_height;
+	data->arrow_height   = 0.24 * data->row_height;
+	data->arrow_width    = 0.11 * data->row_height;
+	
+	if (num_tasks > 0) {
+		data->tasks_per_page_without_header = data->job->height / data->row_height;
+		data->tasks_per_page_with_header = (data->job->height - data->header_height) /
+			data->row_height;
+		
+		data->cols_of_pages = ceil (((data->finish - data->start) /
+					     data->f + data->tree_x2 - data->tree_x1) /
+					    data->job->width);
+		
+		data->rows_of_pages = ceil ((num_tasks * data->row_height + data->header_height) /
+					    (data->job->height - data->row_height));
+		
+		if (data->tasks_per_page_without_header * (data->rows_of_pages - 2) +
+		    data->tasks_per_page_with_header >= num_tasks) {
+			data->rows_of_pages--;
+		}
+		
+		data->cols_of_pages = MAX (1, data->cols_of_pages);
+		data->rows_of_pages = MAX (1, data->rows_of_pages);
+		
+		data->pages = g_new0 (Page, data->cols_of_pages * data->rows_of_pages);
+
+
+	}
+
+	calendar = mrp_project_get_calendar (data->project);
+	
+	num_tasks = g_list_length (data->tasks);
+	
+	current_time = mrp_time_current_time();
+
+	/* Go through all tasks in a first pass and layout them so that we know
+	 * where to draw relation arrows.
+	 */
+
+	l = data->tasks;
+	row = 0;
+	col = 0;
+	while (l) {
+		if (row == 0) {
+			/* Top-most row has the header. */
+			y0 = data->header_height;
+			tasks_on_this_page = data->tasks_per_page_with_header;
+		} else {
+			y0 = 0;
+			tasks_on_this_page = data->tasks_per_page_without_header;
+		}
+		
+		x1 = data->tree_x2;
+
+		/* Loop through the tasks on this row of pages. */
+		for (i = 0; i < tasks_on_this_page; i++) {
+			ptask = l->data;
+			
+			start = mrp_task_get_work_start (ptask->task);
+			finish = mrp_task_get_finish (ptask->task);
+			complete = start + (finish - start) *
+				mrp_task_get_percent_complete (ptask->task) / 100.0;
+
+			is_summary = mrp_task_get_n_children (ptask->task) > 0;
+
+			g_object_get (ptask->task,
+				      "name", &name,
+				      "type", &type,
+				      "critical", &is_critical,
+				      NULL);
+
+			if (!is_summary && type == MRP_TASK_TYPE_MILESTONE) {
+				finish = start;
+			}
+			
+			d(g_print ("%s: ", name));
+			g_free (name);
+
+			col = 0;
+			t1 = data->start;
+
+			y1 = y0 + data->row_height * (i + 0.25);
+			y2 = y1 + 0.5 * data->row_height;
+
+			
+			/* Loop through the columns that this task covers. */
+			while (t1 <= finish) {
+				if (col == 0) {
+					/* Left-most column has the task tree. */
+					x0 = data->tree_x2;
+					t2 = data->second_column_start;
+				} else {
+					x0 = 0;
+					t2 = t1 + data->job->width * data->f;
+				}
+
+				element = g_new0 (Element, 1);
+
+				element->y1 = y1;
+				element->y2 = y2;
+				element->is_critical = is_critical;
+				
+				/* Identify the cases: only left-most part on
+				 * page, only right-most part, the whole task,
+				 * or only the mid-section.
+				 */ 
+				if (start >= t1 && start <= t2 && finish > t2) {
+					/* Left */
+					d(g_print ("left "));
+
+					if (is_summary) {
+						element->type = SUMMARY_LEFT;
+					}
+					else if (type == MRP_TASK_TYPE_MILESTONE) {
+						element->type = MILESTONE;
+					} else {
+						element->type = TASK_LEFT;
+					}
+					
+					element->x1 = x0 + (start - t1) / data->f;
+					element->x2 = data->job->width;
+					element->x_complete = x0 + (complete - t1) / data->f;
+					element->x_complete = MIN (element->x_complete, element->x2);
+				}
+				else if (start < t1 && finish >= t1 && finish <= t2) {
+					/* Right */
+					d(g_print ("right "));
+
+					if (is_summary) {
+						element->type = SUMMARY_RIGHT;
+					}
+					else if (type == MRP_TASK_TYPE_MILESTONE) {
+						element->type = MILESTONE;
+					} else {
+						element->type = TASK_RIGHT;
+					}
+					
+					element->x1 = x0;
+					element->x2 = x0 + (finish - t1) / data->f;
+					element->x_complete = x0 + (complete - t1) / data->f;
+					element->x_complete = MIN (element->x_complete, element->x2);
+				}
+				else if (start >= t1 && finish <= t2) {
+					/* Whole */
+					d(g_print ("whole "));
+
+					if (is_summary) {
+						element->type = SUMMARY_WHOLE;
+					}
+					else if (type == MRP_TASK_TYPE_MILESTONE) {
+						element->type = MILESTONE;
+					} else {
+						element->type = TASK_WHOLE;
+					}
+					
+					element->x1 = x0 + (start - t1) / data->f;
+					element->x2 = x0 + (finish - t1) / data->f;
+					element->x_complete = x0 + (complete - t1) / data->f;
+					element->x_complete = MIN (element->x_complete, element->x2);
+				}
+				else if (start < t1 && finish > t2) {
+					/* Middle */
+					d(g_print ("middle "));
+
+					if (is_summary) {
+						element->type = SUMMARY_MIDDLE;
+					}
+					else if (type == MRP_TASK_TYPE_MILESTONE) {
+						element->type = MILESTONE;
+					} else {
+						element->type = TASK_MIDDLE;
+					}
+					
+					element->x1 = x0;
+					element->x2 = data->job->width;
+
+					if (complete > t1 && complete <= t2) {
+						element->x_complete = x0 + (complete - t1) / data->f;
+						element->x_complete = MIN (element->x_complete, element->x2);
+					}
+				} else {
+					d(g_print ("nothing"));
+					g_free (element);
+					element = NULL;
+				}
+
+				if (element) {
+					switch (element->type) {
+					case TASK_WHOLE:
+					case TASK_LEFT:
+					case SUMMARY_WHOLE:
+					case SUMMARY_LEFT:
+					case MILESTONE:
+						task_coord = g_new0 (TaskCoord, 1);
+						task_coord->row = row;
+						task_coord->col = col;
+
+						task_coord->x = element->x1;
+						task_coord->y = y1 - (y2 - y1) / 2;
+						
+						g_hash_table_insert (data->task_start_coords,
+								     ptask->task,
+								     task_coord);
+						break;
+					default:
+						break;
+					}
+					
+					switch (element->type) {
+					case TASK_WHOLE:
+					case TASK_RIGHT:
+					case SUMMARY_WHOLE:
+					case SUMMARY_RIGHT:
+					case MILESTONE:
+						task_coord = g_new0 (TaskCoord, 1);
+						task_coord->row = row;
+						task_coord->col = col;
+						
+						task_coord->x = element->x2;
+						task_coord->y = y1 + (y2 - y1) / 2;
+						
+						g_hash_table_insert (data->task_finish_coords,
+								     ptask->task,
+								     task_coord);
+						break;
+					default:
+						break;
+					}
+
+					page = GET_PAGE (data, row, col);
+					page->elements = g_list_prepend (page->elements, element);
+				}
+				
+				t1 = t2;
+				col++;
+			}
+
+			d(g_print ("\n"));
+
+			/* Handle the allocated resources. */
+			if (pert_print_get_allocated_resources_string (data, ptask->task, &str, NULL)) {
+				task_coord = g_hash_table_lookup (data->task_finish_coords,
+								  ptask->task);
+
+				/* FIXME: Remove this check when the scheduler
+				 * is fixed. We get a crash here if tasks get
+				 * really odd start/finish values. This
+				 * sometimes happens for complex projects now.
+				 */
+				if (!task_coord) {
+					goto fail;
+				}
+
+				element = g_new0 (Element, 1);
+				element->type = RESOURCES;
+
+				element->x1 = task_coord->x + data->job->x_pad;
+				element->y1 = y0 + data->row_height * (i + 0.75);
+
+				element->resources = str;
+
+				page = GET_PAGE (data, row, col - 1);
+				page->elements = g_list_prepend (page->elements, element);
+			}
+
+		fail:
+			
+			l = l->next;
+			if (!l) {
+				break;
+			}
+		}
+
+		row++;
+		col = 0;
+	}
+
+	/* Second pass, go through all relations and add elements for them. */
+	relations = pert_print_get_relations (data);
+	for (l = relations; l; l = l->next) {
+		MrpTask     *predecessor, *successor;
+		TaskCoord   *pre_coord, *suc_coord;
+		Page        *page;
+		MrpRelation *relation = l->data;
+		gint         from_row, to_row;
+		gdouble      top;
+
+		predecessor = mrp_relation_get_predecessor (relation);
+		successor = mrp_relation_get_successor (relation);
+
+		pre_coord = g_hash_table_lookup (data->task_finish_coords, predecessor);
+		suc_coord = g_hash_table_lookup (data->task_start_coords, successor);
+
+		/* One of the tasks might not be visible. */
+		if (!pre_coord || !suc_coord) {
+			continue;
+		}
+		
+		element = g_new0 (Element, 1);
+		element->x1 = suc_coord->x;
+
+		from_row = MIN (pre_coord->row, suc_coord->row);
+		to_row = MAX (pre_coord->row, suc_coord->row);
+
+		/* Get the right direction and position of the arrow depending on
+		 * the order of the predecessor and successor.
+		 */
+		if ((pre_coord->row == suc_coord->row && pre_coord->y < suc_coord->y) ||
+		    (pre_coord->row < suc_coord->row)) {
+			element->y1 = suc_coord->y + data->row_height * 0.25;
+			element->type = RELATION_ARROW_DOWN;
+		} else {
+			element->y1 = suc_coord->y + data->row_height * 0.75;
+			element->type = RELATION_ARROW_UP;
+		}
+
+		page = GET_PAGE (data, suc_coord->row, suc_coord->col);
+		page->elements = g_list_prepend (page->elements, element);
+
+		for (row = from_row; row <= to_row; row++) {
+			if (row == pre_coord->row) {
+				for (col = pre_coord->col; col <= suc_coord->col; col++) {
+					element = g_new0 (Element, 1);
+					element->type = RELATION_HORIZ;
+
+					if (col == pre_coord->col) {
+						element->x1 = pre_coord->x;
+					} else {
+						if (col == 0) {
+							element->x1 = data->tree_x2;
+						} else {
+							element->x1 = 0;
+						}						
+					}
+					
+					if (col == suc_coord->col) {
+						element->x2 = suc_coord->x;
+					} else {
+						element->x2 = data->job->width;
+					}
+
+					element->y1 = pre_coord->y;
+					
+					page = GET_PAGE (data, row, col);
+					page->elements = g_list_prepend (page->elements, element);
+				}
+			}
+
+			element = g_new0 (Element, 1);
+			element->type = RELATION_VERT;
+			element->x1 = suc_coord->x;
+
+			if (row == 0) {
+				top = data->header_height;
+			} else {
+				top = 0;
+			}
+			
+			if (row == pre_coord->row) {
+				element->y1 = pre_coord->y;
+			} else {
+				if (pre_coord->row <= suc_coord->row) { 
+					element->y1 = top;
+				} else {
+					element->y1 = data->job->height;
+				}
+			}
+
+			if (row == suc_coord->row) {
+				element->y2 = suc_coord->y + data->row_height / 2;
+			} else {
+				if (pre_coord->row <= suc_coord->row) { 
+					element->y2 = data->job->height;
+				} else {
+					element->y2 = top;
+				}
+			}
+			
+			page = GET_PAGE (data, row, suc_coord->col);
+			page->elements = g_list_prepend (page->elements, element);
+		}
+	}
+
+	g_list_free (relations);
+
+	/* Third pass, add background shading for non-work intervals. */
+	for (row = 0; row < data->rows_of_pages; row++) {
+		t0 = mrp_time_align_day (data->start);
+
+		if (row == 0) {
+			/* Top-most row has the header. */
+			y0 = data->header_height;
+		} else {
+			y0 = 0;
+		}
+
+		for (col = 0; col < data->cols_of_pages; col++) {
+			mrptime ival_start, ival_end, ival_prev;
+			
+			if (col == 0) {
+				/* Left-most col has the tree. */
+				x0 = data->tree_x2;
+			} else {
+				x0 = 0;
+			}
+
+			t2 = t0 + (data->job->width - x0) * data->f;
+						
+			/* Loop through the days between t0 and t2. */
+			t1 = mrp_time_align_day(t0);
+
+			while (t1 <= t2) {
+				gboolean done = FALSE;
+
+				day = mrp_calendar_get_day (calendar, t1, TRUE);
+				
+				ivals = mrp_calendar_day_get_intervals (calendar, day, TRUE);
+
+				ival_prev = t1;
+				
+				/* Loop through the non-work intervals for this day */
+				while (!done)
+				{
+					/* The number of non-work intervals is one for each work interval and one
+					 * more for the remaining period after the last work interval */
+					if (ivals != NULL) {
+						mrp_interval_get_absolute (ivals->data,
+									   t1,
+									   &ival_start,
+									   &ival_end);
+
+						ivals = ivals->next;
+					} else {
+						ival_start = t1 + 60*60*24;
+						done = TRUE;
+					}
+
+					/* Only consider non-work intervals that are large enough and
+					 * lie (partially) within [t0...t2] */
+					if (ival_prev < t2 && ival_start > t0 && 
+					    planner_scale_conf[data->level].nonworking_limit <= ival_start - ival_prev) {
+
+						/* Only draw the part within [t0...t2] */
+
+						element = g_new0 (Element, 1);
+						element->type = SHADE;
+						element->y1 = y0;
+						element->y2 = data->job->height;
+
+						element->x1 = x0 + (MAX(t0, ival_prev) - t0) / data->f;
+						element->x2 = x0 + (MIN(t2, ival_start) - t0) / data->f;
+						
+						page = GET_PAGE (data, row, col);
+						page->background_elements = g_list_prepend (page->background_elements, element);
+
+					}
+
+					ival_prev = ival_end;
+				}
+				/* Set t1 to the start of the next day */
+				t1 = ival_start;
+			}
+
+			/* Print the current time. */
+			if (current_time >= t0 && current_time <= t2) {
+				element = g_new0 (Element, 1);
+				element->type = TIMELINE;
+				element->y1 = data->header_height;
+				element->y2 = data->job->height;
+				element->x1 = x0 + (current_time - t0) / data->f; 
+				element->x2 = element->x1;
+				page = GET_PAGE (data, row, col);
+				page->background_elements = g_list_prepend (page->background_elements, element);
+			}
+			
+			t0 = t2;
+		}
+	}
+	
+	return data;
+}
+
+static void
+free_page (Page *page)
+{
+	GList   *l;
+	Element *element;
+
+	for (l = page->elements; l; l = l->next) {
+		element = l->data;
+		g_free (element->resources);
+	}
+
+	for (l = page->background_elements; l; l = l->next) {
+		element = l->data;
+		g_free (element->resources);
+	}
+
+	g_list_free (page->elements);
+	g_list_free (page->background_elements);
+}
+
+void
+planner_pert_print_data_free (PlannerPertPrintData *data)
+{
+	gint i, num_pages;
+	
+	g_return_if_fail (data != NULL);
+
+	g_hash_table_destroy (data->task_start_coords);
+	g_hash_table_destroy (data->task_finish_coords);
+	
+	pert_print_free_print_tasks (data->tasks);
+	data->tasks = NULL;
+
+	num_pages = data->cols_of_pages * data->rows_of_pages;
+	
+	for (i = 0; i < num_pages; i++) {
+		free_page (&data->pages[i]);
+	}
+	
+	g_free (data->pages);
+	data->pages = NULL;
+
+	g_free (data);
+}
+
+gint
+planner_pert_print_get_n_pages (PlannerPertPrintData *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	
+	return data->cols_of_pages * data->rows_of_pages;
+}
diff --git a/src/planner-pert-print.h b/src/planner-pert-print.h
new file mode 100644
index 0000000..f046480
--- /dev/null
+++ b/src/planner-pert-print.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003 CodeFactory AB
+ * Copyright (C) 2003 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2003 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PLANNER_PERT_PRINT_JOB_H__
+#define __PLANNER_PERT_PRINT_JOB_H__
+
+#include <gtk/gtktreeview.h>
+#include "planner-print-job.h"
+#include "planner-view.h"
+
+typedef struct _PlannerPertPrintData PlannerPertPrintData;
+
+
+void                planner_pert_print_do             (PlannerPertPrintData *data,
+							gint                   page_nr);
+
+gint                planner_pert_print_get_n_pages    (PlannerPertPrintData *data);
+
+PlannerPertPrintData *  planner_pert_print_data_new       (PlannerView           *view,
+						   PlannerPrintJob       *job,
+						   GtkTreeView      *tree_view,
+						   gint              level,
+						   gboolean          show_critical);
+
+void                planner_pert_print_data_free      (PlannerPertPrintData *data);
+
+
+#endif /* __PLANNER_PERT_PRINT_JOB_H__ */
+
diff --git a/src/planner-pert-view.c b/src/planner-pert-view.c
new file mode 100644
index 0000000..c5d9077
--- /dev/null
+++ b/src/planner-pert-view.c
@@ -0,0 +1,1109 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2005 Imendio AB
+ * Copyright (C) 2002 CodeFactory AB
+ * Copyright (C) 2002 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2002 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <libplanner/mrp-task.h>
+#include "libplanner/mrp-paths.h"
+#include "planner-pert-view.h"
+#include "planner-cell-renderer-date.h"
+#include "planner-task-dialog.h"
+#include "planner-resource-dialog.h"
+#include "planner-gantt-model.h"
+#include "planner-task-tree.h"
+#include "planner-pert-chart.h"
+#include "planner-pert-print.h"
+#include "planner-column-dialog.h"
+#include "planner-conf.h"
+
+struct _PlannerPertViewPriv {
+	GtkWidget             *paned;
+	GtkWidget             *tree;
+	GtkWidget             *pert;
+	PlannerPertPrintData *print_data;
+
+	GtkUIManager          *ui_manager;
+	GtkActionGroup        *actions;
+	guint                  merged_id;
+	gulong                 expose_id;
+};
+
+static GtkWidget *   pert_view_create_widget             (PlannerPertView  *view);
+static void          pert_view_insert_task_cb            (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_insert_tasks_cb           (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_remove_task_cb            (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_edit_task_cb              (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_select_all_cb             (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_unlink_task_cb            (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_link_tasks_cb             (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_indent_task_cb            (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_unindent_task_cb          (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_move_task_up_cb           (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_move_task_down_cb         (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_reset_constraint_cb       (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_highlight_critical_cb     (GtkAction         *action,
+							   gpointer           data);
+static void	pert_view_change_display_mode_cb	 (GtkAction *action, GtkAction *current,
+							   gpointer   data);
+static void          pert_view_edit_columns_cb           (GtkAction         *action,
+							   gpointer           data);
+static void          pert_view_edit_columns_cb           (GtkAction         *action,
+							   gpointer           data);
+static gboolean      pert_view_expose_cb                 (GtkWidget         *widget,
+							   GdkEventExpose    *event,
+							   gpointer           user_data);
+static void          pert_view_update_row_height         (PlannerPertView  *view);
+static void          pert_view_tree_style_set_cb         (GtkWidget         *tree,
+							   GtkStyle          *prev_style,
+							   PlannerPertView  *view);
+static void          pert_view_selection_changed_cb      (PlannerTaskTree   *tree,
+							   PlannerPertView  *view);
+static void          pert_view_pert_status_updated_cb   (PlannerPertChart *pert,
+							   const gchar       *message,
+							   PlannerPertView  *view);
+static void          pert_view_relations_changed_cb      (PlannerTaskTree   *tree,
+							   MrpTask           *task,
+							   MrpRelation       *relation,
+							   PlannerPertView  *view);
+static void          pert_view_pert_resource_clicked_cb (PlannerPertChart *chart,
+							   MrpResource       *resource,
+							   PlannerPertView  *view);
+static void          pert_view_update_ui                 (PlannerPertView  *view);
+static void          pert_view_save_columns              (PlannerPertView  *view);
+static void          pert_view_load_columns              (PlannerPertView  *view);
+static void          pert_view_activate                  (PlannerView       *view);
+static void          pert_view_deactivate                (PlannerView       *view);
+static void          pert_view_setup                     (PlannerView       *view,
+							   PlannerWindow     *main_window);
+static const gchar  *pert_view_get_label                 (PlannerView       *view);
+static const gchar  *pert_view_get_menu_label            (PlannerView       *view);
+static const gchar  *pert_view_get_icon                  (PlannerView       *view);
+static const gchar  *pert_view_get_name                  (PlannerView       *view);
+static GtkWidget    *pert_view_get_widget                (PlannerView       *view);
+static void          pert_view_print_init                (PlannerView       *view,
+							   PlannerPrintJob   *job);
+static void          pert_view_print                     (PlannerView       *view,
+							   gint               page_nr);
+static gint          pert_view_print_get_n_pages         (PlannerView       *view);
+static void          pert_view_print_cleanup             (PlannerView       *view);
+
+static const GtkActionEntry entries[] = {
+	{ "InsertTask",      "planner-stock-insert-task",    N_("_Insert Task"),
+	  "<control>i",        N_("Insert a new task"),
+	  G_CALLBACK (pert_view_insert_task_cb) },
+	{ "InsertTasks",     "planner-stock-insert-task",    N_("In_sert Tasks..."),
+	  NULL,                NULL,
+	  G_CALLBACK (pert_view_insert_tasks_cb) },
+	{ "RemoveTask",      "planner-stock-remove-task",    N_("_Remove Task"),
+	  "<control>d",        N_("Remove the selected tasks"),
+	  G_CALLBACK (pert_view_remove_task_cb) },
+	{ "EditTask",        NULL,                           N_("_Edit Task Properties..."),
+	  "<shift><control>e", NULL,
+	  G_CALLBACK (pert_view_edit_task_cb) },
+	{ "SelectAll",       NULL,                           N_("Select _All"),
+	  "<control>a",        N_("Select all tasks"),
+	  G_CALLBACK (pert_view_select_all_cb) },
+	{ "UnlinkTask",      "planner-stock-unlink-task",    N_("_Unlink Task"),
+	  NULL,                N_("Unlink the selected tasks"),
+	  G_CALLBACK (pert_view_unlink_task_cb) },
+	{ "LinkTasks",       "planner-stock-link-task",      N_("_Link Tasks"),
+	  NULL,                N_("Link the selected tasks"),
+	  G_CALLBACK (pert_view_link_tasks_cb) },
+	{ "IndentTask",      "planner-stock-indent-task",    N_("I_ndent Task"),
+	  "<shift><control>i", N_("Indent the selected tasks"),
+	  G_CALLBACK (pert_view_indent_task_cb) },
+	{ "UnindentTask",    "planner-stock-unindent-task",  N_("Unin_dent Task"),
+	  "<shift><control>u", N_("Unindent the selected tasks"),
+	  G_CALLBACK (pert_view_unindent_task_cb) },
+	{ "MoveTaskUp",      "planner-stock-move-task-up",   N_("Move Task _Up"),
+	  NULL,                N_("Move the selected tasks upwards"),
+	  G_CALLBACK (pert_view_move_task_up_cb) },
+	{ "MoveTaskDown",    "planner-stock-move-task-down", N_("Move Task Do_wn"),
+	  NULL,                N_("Move the selected tasks downwards"),
+	  G_CALLBACK (pert_view_move_task_down_cb) },
+	{ "ResetConstraint", NULL,                           N_("Reset _Constraint"),
+	  NULL,                NULL,
+	  G_CALLBACK (pert_view_reset_constraint_cb) },
+	{ "EditColumns",     NULL,                           N_("Edit _Visible Columns"),
+	  NULL,                N_("Edit visible columns"),
+	  G_CALLBACK (pert_view_edit_columns_cb) }
+};
+
+static const GtkRadioActionEntry radio_entries[]={
+		{ "ChangeToAutomaticCeiling", NULL, N_("Change Display To Critical on _Top"),
+				NULL,     NULL, 0},
+		{ "ChangeToAutomaticTaskFlow", NULL, N_("Change Display To Tasks _Flow"),
+				NULL,     NULL, 1}};
+
+static const GtkToggleActionEntry toggle_entries[] = {
+	{ "HighlightCriticalTasks", NULL, N_("_Highlight Critical Tasks"),
+	  NULL, NULL,
+	  G_CALLBACK (pert_view_highlight_critical_cb), FALSE }};
+
+
+G_DEFINE_TYPE (PlannerPertView, planner_pert_view, PLANNER_TYPE_VIEW);
+
+static void
+planner_pert_view_class_init (PlannerPertViewClass *klass)
+{
+	PlannerViewClass *view_class;
+
+	view_class = PLANNER_VIEW_CLASS (klass);
+
+	view_class->setup = pert_view_setup;
+	view_class->get_label = pert_view_get_label;
+	view_class->get_menu_label = pert_view_get_menu_label;
+	view_class->get_icon = pert_view_get_icon;
+	view_class->get_name = pert_view_get_name;
+	view_class->get_widget = pert_view_get_widget;
+	view_class->activate = pert_view_activate;
+	view_class->deactivate = pert_view_deactivate;
+	view_class->print_init = pert_view_print_init;
+	view_class->print_get_n_pages = pert_view_print_get_n_pages;
+	view_class->print = pert_view_print;
+	view_class->print_cleanup = pert_view_print_cleanup;
+}
+
+static void
+planner_pert_view_init (PlannerPertView *view)
+{
+	view->priv = g_new0 (PlannerPertViewPriv, 1);
+}
+
+static void
+pert_view_activate (PlannerView *view)
+{
+	PlannerPertViewPriv *priv;
+	gboolean              show_critical;
+	gint				display_mode;
+	gchar                *filename;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	priv->actions = gtk_action_group_new ("PertView");
+	gtk_action_group_set_translation_domain (priv->actions, GETTEXT_PACKAGE);
+
+	gtk_action_group_add_actions (priv->actions,
+				      entries,
+				      G_N_ELEMENTS (entries),
+				      view);
+	gtk_action_group_add_radio_actions (priv->actions,
+						     radio_entries,
+						     G_N_ELEMENTS (radio_entries),
+						     0,
+						     G_CALLBACK (pert_view_change_display_mode_cb),
+						     view);
+	gtk_action_group_add_toggle_actions (priv->actions,
+					     toggle_entries,
+					     G_N_ELEMENTS (toggle_entries),
+					     view);
+
+	gtk_ui_manager_insert_action_group (priv->ui_manager, priv->actions, 0);
+	filename = mrp_paths_get_ui_dir ("pert-view.ui");
+	priv->merged_id = gtk_ui_manager_add_ui_from_file (priv->ui_manager,
+							   filename,
+							   NULL);
+	g_free (filename);
+	gtk_ui_manager_ensure_update (priv->ui_manager);
+
+	/* Set the initial UI state. */
+	show_critical = planner_pert_chart_get_highlight_critical_tasks (
+		PLANNER_PERT_CHART (priv->pert));
+
+	planner_task_tree_set_highlight_critical (PLANNER_TASK_TREE (priv->tree),
+						  show_critical);
+
+	gtk_toggle_action_set_active (
+		GTK_TOGGLE_ACTION (gtk_action_group_get_action (priv->actions, "HighlightCriticalTasks")),
+		show_critical);
+
+	display_mode = planner_pert_chart_get_display_mode (PLANNER_PERT_CHART (priv->pert));
+
+	gtk_radio_action_set_current_value (
+			GTK_RADIO_ACTION (gtk_action_group_get_action (priv->actions, "ChangeToAutomaticCeiling")),
+			display_mode);
+
+	pert_view_selection_changed_cb (PLANNER_TASK_TREE (priv->tree),
+					 PLANNER_PERT_VIEW (view));
+	gtk_widget_grab_focus (priv->tree);
+}
+
+static void
+pert_view_deactivate (PlannerView *view)
+{
+	PlannerPertViewPriv *priv;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	gtk_ui_manager_remove_ui (priv->ui_manager, priv->merged_id);
+}
+
+static void
+pert_view_setup (PlannerView *view, PlannerWindow *main_window)
+{
+	PlannerPertViewPriv *priv;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	priv->ui_manager = planner_window_get_ui_manager (main_window);
+}
+
+static const gchar *
+pert_view_get_label (PlannerView *view)
+{
+	/* i18n: Label used for the sidebar. Please try to make it short and use
+	 * a linebreak if necessary/possible.
+	 */
+	return _("PERT");
+}
+
+static const gchar *
+pert_view_get_menu_label (PlannerView *view)
+{
+	return _("_PERT Chart");
+}
+
+static const gchar *
+pert_view_get_icon (PlannerView *view)
+{
+	static gchar *filename = NULL;
+
+	if (!filename) {
+		filename = mrp_paths_get_image_dir ("pert.png");
+	}
+
+	return filename;
+}
+
+static const gchar *
+pert_view_get_name (PlannerView *view)
+{
+	return "pert_view";
+}
+
+static GtkWidget *
+pert_view_get_widget (PlannerView *view)
+{
+	PlannerPertViewPriv *priv;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	if (priv->paned == NULL) {
+		priv->paned = pert_view_create_widget (PLANNER_PERT_VIEW (view));
+		gtk_widget_show_all (priv->paned);
+	}
+
+	return priv->paned;
+}
+
+static void
+pert_view_print_init (PlannerView     *view,
+		       PlannerPrintJob *job)
+{
+	PlannerPertViewPriv *priv;
+	gboolean         show_critical;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	show_critical = planner_pert_chart_get_highlight_critical_tasks (
+		PLANNER_PERT_CHART (priv->pert));
+
+	priv->print_data = planner_pert_print_data_new (view, job,
+							 GTK_TREE_VIEW (priv->tree),
+							 1., show_critical);
+}
+
+static void
+pert_view_print (PlannerView *view, gint page_nr)
+
+{
+	PlannerPertViewPriv *priv;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	planner_pert_print_do (priv->print_data, page_nr);
+}
+
+static gint
+pert_view_print_get_n_pages (PlannerView *view)
+{
+	PlannerPertViewPriv *priv;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	return planner_pert_print_get_n_pages (priv->print_data);
+}
+
+static void
+pert_view_print_cleanup (PlannerView *view)
+
+{
+	PlannerPertViewPriv *priv;
+
+	priv = PLANNER_PERT_VIEW (view)->priv;
+
+	planner_pert_print_data_free (priv->print_data);
+	priv->print_data = NULL;
+}
+
+static void
+pert_view_row_expanded (GtkTreeView *tree_view,
+			 GtkTreeIter *iter,
+			 GtkTreePath *path,
+			 gpointer     data)
+{
+	PlannerPertChart *pert = data;
+
+	planner_pert_chart_expand_row (pert, path);
+}
+
+static void
+pert_view_row_collapsed (GtkTreeView *tree_view,
+			  GtkTreeIter *iter,
+			  GtkTreePath *path,
+			  gpointer     data)
+{
+	PlannerPertChart *pert = data;
+
+	planner_pert_chart_collapse_row (pert, path);
+}
+
+static void
+pert_view_task_inserted_cb (MrpProject   *project,
+			     MrpTask      *task,
+			     PlannerPertView *view)
+{
+	if(view->priv->expose_id == 0) {
+		view->priv->expose_id = g_signal_connect_after (view->priv->tree,
+								"expose_event",
+								G_CALLBACK (pert_view_expose_cb),
+								view);
+	}
+}
+
+static void
+pert_view_project_loaded_cb (MrpProject  *project,
+			      PlannerPertView *view)
+{
+	GtkTreeModel *model;
+
+	model = GTK_TREE_MODEL (planner_gantt_model_new (project));
+
+	planner_task_tree_set_model (PLANNER_TASK_TREE (view->priv->tree),
+				     PLANNER_GANTT_MODEL (model));
+
+	planner_pert_chart_set_model (PLANNER_PERT_CHART (view->priv->pert),
+				       model);
+
+	view->priv->expose_id = g_signal_connect_after (view->priv->tree,
+							"expose_event",
+							G_CALLBACK (pert_view_expose_cb),
+							view);
+
+	g_object_unref (model);
+}
+
+static void
+pert_view_tree_view_size_request_cb (GtkWidget      *widget,
+				      GtkRequisition *req,
+				      gpointer        data)
+{
+	req->height = -1;
+}
+
+static gboolean
+pert_view_tree_view_scroll_event_cb (GtkWidget      *widget,
+				      GdkEventScroll *event,
+				      gpointer        data)
+{
+	GtkAdjustment *adj;
+	GtkTreeView   *tv = GTK_TREE_VIEW (widget);
+	gdouble        new_value;
+
+	if (event->direction != GDK_SCROLL_UP &&
+	    event->direction != GDK_SCROLL_DOWN) {
+		return FALSE;
+	}
+
+	adj = gtk_tree_view_get_vadjustment (tv);
+
+	if (event->direction == GDK_SCROLL_UP) {
+		new_value = adj->value - adj->page_increment / 2;
+	}
+	else {
+		new_value = adj->value + adj->page_increment / 2;
+	}
+
+	new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
+	gtk_adjustment_set_value (adj, new_value);
+
+	return TRUE;
+}
+
+static void
+pert_view_tree_view_columns_changed_cb (GtkTreeView *tree_view,
+					 PlannerPertView *view)
+{
+	pert_view_save_columns (view);
+}
+
+static void
+pert_view_tree_view_destroy_cb (GtkTreeView *tree_view,
+				 PlannerPertView *view)
+{
+	/* Block, we don't want to save the column configuration when they are
+	 * removed by the destruction.
+	 */
+	g_signal_handlers_block_by_func (tree_view,
+					 pert_view_tree_view_columns_changed_cb,
+					 view);
+}
+
+static GtkWidget *
+pert_view_create_widget (PlannerPertView *view)
+{
+	PlannerPertViewPriv  *priv;
+	GtkWidget        *tree;
+	GtkWidget        *sw;
+	GtkWidget        *frame;
+	GtkWidget        *vbox;
+	GtkWidget        *hpaned;
+	GtkAdjustment    *hadj, *vadj;
+	GtkTreeModel     *model;
+	MrpProject       *project;
+	GtkTreeSelection *selection;
+
+	project = planner_window_get_project (PLANNER_VIEW (view)->main_window);
+
+	priv = view->priv;
+
+	g_signal_connect (project,
+			  "loaded",
+			  G_CALLBACK (pert_view_project_loaded_cb),
+			  view);
+
+	g_signal_connect (project, "task_inserted",
+			  G_CALLBACK (pert_view_task_inserted_cb),
+			  view);
+
+	model = GTK_TREE_MODEL (planner_gantt_model_new (project));
+
+	tree = planner_task_tree_new (PLANNER_VIEW (view)->main_window,
+				      PLANNER_GANTT_MODEL (model),
+				      FALSE,
+				      TRUE,
+				      /* i18n: WBS is short for work breakdown structure, and is a
+				       * project management term. You might want to leave it
+				       * untranslated unless there is a localized term for it.
+				       */
+				      COL_WBS, _("WBS"),
+				      COL_NAME, _("Name"),
+				      COL_START, _("Start"),
+				      COL_FINISH, _("Finish"),
+				      COL_WORK, _("Work"),
+				      COL_DURATION, _("Duration"),
+				      COL_SLACK, _("Slack"),
+				      COL_FREEFLOAT, _("Free float"),
+				      COL_COST, _("Cost"),
+				      COL_ASSIGNED_TO, _("Assigned to"),
+				      /* i18n: The string "% Complete" will be used in the header
+				       * of a column containing values from 0 upto 100, indicating
+				       * what part of a task has been completed.
+				       * xgettext:no-c-format
+				       */
+				      COL_COMPLETE, _("% Complete"),
+				      -1);
+
+	priv->tree = tree;
+
+	pert_view_load_columns (view);
+
+	priv->pert = planner_pert_chart_new_with_model (model);
+	planner_pert_chart_set_view (PLANNER_PERT_CHART (priv->pert),
+				      PLANNER_TASK_TREE (tree));
+
+	gtk_widget_set_events (GTK_WIDGET (priv->pert), GDK_SCROLL_MASK);
+
+	g_object_unref (model);
+
+	g_signal_connect (tree,
+			  "columns-changed",
+			  G_CALLBACK (pert_view_tree_view_columns_changed_cb),
+			  view);
+
+	g_signal_connect (tree,
+			  "destroy",
+			  G_CALLBACK (pert_view_tree_view_destroy_cb),
+			  view);
+
+	g_signal_connect (priv->pert,
+			  "status_updated",
+			  G_CALLBACK (pert_view_pert_status_updated_cb),
+			  view);
+
+	g_signal_connect (priv->pert,
+			  "resource_clicked",
+			  G_CALLBACK (pert_view_pert_resource_clicked_cb),
+			  view);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+	g_signal_connect (tree,
+			  "style_set",
+			  G_CALLBACK (pert_view_tree_style_set_cb),
+			  view);
+
+	g_signal_connect (tree,
+			  "selection_changed",
+			  G_CALLBACK (pert_view_selection_changed_cb),
+			  view);
+
+	g_signal_connect (tree,
+			  "relation_added",
+			  G_CALLBACK (pert_view_relations_changed_cb),
+			  view);
+
+	g_signal_connect (tree,
+			  "relation_removed",
+			  G_CALLBACK (pert_view_relations_changed_cb),
+			  view);
+
+	vbox = gtk_vbox_new (FALSE, 3);
+
+	gtk_box_pack_start (GTK_BOX (vbox), tree, TRUE, TRUE, 0);
+
+	hadj = gtk_tree_view_get_hadjustment (GTK_TREE_VIEW (tree));
+	gtk_box_pack_start (GTK_BOX (vbox), gtk_hscrollbar_new (hadj), FALSE, TRUE, 0);
+
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+	gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+	hpaned = gtk_hpaned_new ();
+	gtk_paned_add1 (GTK_PANED (hpaned), frame);
+
+	g_signal_connect_after (tree,
+				"size_request",
+				G_CALLBACK (pert_view_tree_view_size_request_cb),
+				NULL);
+
+	g_signal_connect_after (tree,
+				"scroll_event",
+				G_CALLBACK (pert_view_tree_view_scroll_event_cb),
+				view);
+
+	hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 90, 250, 2000));
+	vadj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (tree));
+
+	sw = gtk_scrolled_window_new (hadj, vadj);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+					GTK_POLICY_ALWAYS,
+					GTK_POLICY_AUTOMATIC);
+
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+	gtk_container_add (GTK_CONTAINER (frame), sw);
+
+	gtk_container_add (GTK_CONTAINER (sw),
+			   GTK_WIDGET (priv->pert));
+
+	gtk_paned_add2 (GTK_PANED (hpaned), frame);
+
+	gtk_paned_set_position (GTK_PANED (hpaned), 250);
+
+	g_signal_connect (tree,
+			  "row_expanded",
+			  G_CALLBACK (pert_view_row_expanded),
+			  priv->pert);
+
+	g_signal_connect (tree,
+			  "row_collapsed",
+			  G_CALLBACK (pert_view_row_collapsed),
+			  priv->pert);
+
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (tree));
+
+	return hpaned;
+}
+
+/* Command callbacks. */
+
+static void pert_view_change_display_mode_cb(GtkAction *action, GtkAction *current,
+												   gpointer   data)
+{
+	PlannerPertView     *view;
+	PlannerPertViewPriv *priv;
+	gint		         value;
+
+	view = PLANNER_PERT_VIEW (data);
+	priv = view->priv;
+
+	value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
+
+	planner_pert_chart_set_display_mode (PLANNER_PERT_CHART (priv->pert), value);
+}
+
+static void
+pert_view_insert_task_cb (GtkAction *action,
+			   gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_insert_task (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_insert_tasks_cb (GtkAction *action,
+			    gpointer   data)
+{
+	PlannerPertView *view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_insert_tasks (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_remove_task_cb (GtkAction *action,
+			   gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_remove_task (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_select_all_cb (GtkAction *action,
+			  gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_select_all (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_edit_task_cb (GtkAction *action,
+			 gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_edit_task (PLANNER_TASK_TREE (view->priv->tree),
+				     PLANNER_TASK_DIALOG_PAGE_GENERAL);
+}
+
+static void
+pert_view_unlink_task_cb (GtkAction *action,
+			   gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_unlink_task (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_link_tasks_cb (GtkAction *action,
+			  gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_link_tasks (PLANNER_TASK_TREE (view->priv->tree),
+				      MRP_RELATION_FS);
+}
+
+static void
+pert_view_indent_task_cb (GtkAction *action,
+			   gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_indent_task (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_unindent_task_cb (GtkAction *action,
+			     gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_unindent_task (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_move_task_up_cb (GtkAction  *action,
+			    gpointer    data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_move_task_up (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_move_task_down_cb (GtkAction *action,
+			      gpointer	 data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_move_task_down (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_reset_constraint_cb (GtkAction *action,
+				gpointer   data)
+{
+	PlannerPertView *view;
+
+	view = PLANNER_PERT_VIEW (data);
+
+	planner_task_tree_reset_constraint (PLANNER_TASK_TREE (view->priv->tree));
+}
+
+static void
+pert_view_highlight_critical_cb (GtkAction *action,
+				  gpointer   data)
+{
+	PlannerPertView     *view;
+	PlannerPertViewPriv *priv;
+	gboolean         state;
+
+	view = PLANNER_PERT_VIEW (data);
+	priv = view->priv;
+
+	state = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+	planner_pert_chart_set_highlight_critical_tasks (
+		PLANNER_PERT_CHART (priv->pert),
+		state);
+
+	planner_task_tree_set_highlight_critical (
+                PLANNER_TASK_TREE (priv->tree),
+		state);
+}
+
+static void
+pert_view_edit_columns_cb (GtkAction *action,
+			    gpointer   data)
+{
+	PlannerPertView     *view;
+	PlannerPertViewPriv *priv;
+
+	view = PLANNER_PERT_VIEW (data);
+	priv = view->priv;
+
+	planner_column_dialog_show (PLANNER_VIEW (view)->main_window,
+				    _("Edit pert Columns"),
+				    GTK_TREE_VIEW (priv->tree));
+}
+
+static void
+pert_view_update_row_height (PlannerPertView *view)
+{
+	GtkTreeView       *tv = GTK_TREE_VIEW (view->priv->tree);
+	GtkWidget         *pert = view->priv->pert;
+	gint               row_height;
+	gint               height;
+	GList             *cols, *l;
+	GtkTreeViewColumn *col;
+	GtkTreePath       *path;
+	GdkRectangle       rect;
+
+	/* Get the row height. */
+	cols = gtk_tree_view_get_columns (tv);
+	row_height = 0;
+
+	path = gtk_tree_path_new_first ();
+
+	for (l = cols; l; l = l->next) {
+		col = l->data;
+
+		gtk_tree_view_column_cell_get_size (col,
+						    NULL,
+						    NULL,
+						    NULL,
+						    NULL,
+						    &height);
+		row_height = MAX (row_height, height);
+
+		gtk_tree_view_get_background_area (tv,
+						   path,
+						   col,
+						   &rect);
+
+		row_height = MAX (row_height, rect.height);
+	}
+	if (path)
+		gtk_tree_path_free (path);
+
+	/* Sync with the pert widget. */
+	g_object_set (pert,
+		      "row_height", row_height,
+		      NULL);
+}
+
+static gboolean
+pert_view_expose_cb (GtkWidget      *widget,
+		      GdkEventExpose *event,
+		      gpointer        data)
+{
+	PlannerPertView     *view = PLANNER_PERT_VIEW (data);
+
+	pert_view_update_row_height (view);
+
+	g_signal_handler_disconnect (view->priv->tree,
+				     view->priv->expose_id);
+
+	return FALSE;
+}
+
+static gboolean
+idle_update_heights (PlannerPertView *view)
+{
+	pert_view_update_row_height (view);
+	return FALSE;
+}
+
+static void
+pert_view_tree_style_set_cb (GtkWidget   *tree,
+			      GtkStyle    *prev_style,
+			      PlannerPertView *view)
+{
+	if (prev_style) {
+		g_idle_add ((GSourceFunc) idle_update_heights, view);
+	} else {
+		idle_update_heights (view);
+	}
+}
+
+static void
+pert_view_selection_changed_cb (PlannerTaskTree *tree, PlannerPertView *view)
+{
+	g_return_if_fail (PLANNER_IS_VIEW (view));
+
+	pert_view_update_ui (view);
+}
+
+static void
+pert_view_pert_status_updated_cb (PlannerPertChart *pert,
+				    const gchar       *message,
+				    PlannerPertView  *view)
+{
+	planner_window_set_status (PLANNER_VIEW (view)->main_window, message);
+}
+
+static void
+pert_view_pert_resource_clicked_cb (PlannerPertChart *chart,
+				      MrpResource       *resource,
+				      PlannerPertView       *view)
+{
+	GtkWidget *dialog;
+
+	dialog = planner_resource_dialog_new (PLANNER_VIEW (view)->main_window, resource);
+	gtk_widget_show (dialog);
+}
+
+static void
+pert_view_relations_changed_cb (PlannerTaskTree *tree,
+				 MrpTask         *task,
+				 MrpRelation     *relation,
+				 PlannerPertView     *view)
+{
+	pert_view_update_ui (view);
+}
+
+static void
+pert_view_update_ui (PlannerPertView *view)
+{
+	PlannerPertViewPriv *priv;
+	GList                *list, *l;
+	gboolean              value;
+	gboolean              rel_value = FALSE;
+	gboolean              link_value = FALSE;
+	gint                  count_value = 0;
+
+	if (!PLANNER_VIEW (view)->activated) {
+		return;
+	}
+
+	priv = view->priv;
+
+	list = planner_task_tree_get_selected_tasks (PLANNER_TASK_TREE (priv->tree));
+
+	for (l = list; l; l = l->next) {
+		if (mrp_task_has_relation (MRP_TASK (l->data))) {
+			rel_value = TRUE;
+			break;
+		}
+	}
+
+	for (l = list; l; l = l->next) {
+		count_value++;
+	}
+
+	value = (list != NULL);
+	link_value = (count_value >= 2);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "EditTask"),
+		      "sensitive", value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "RemoveTask"),
+		      "sensitive", value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "UnlinkTask"),
+		      "sensitive", rel_value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "LinkTasks"),
+		      "sensitive", link_value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "IndentTask"),
+		      "sensitive", value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "UnindentTask"),
+		      "sensitive", value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "MoveTaskUp"),
+		      "sensitive", value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "MoveTaskDown"),
+		      "sensitive", value,
+		      NULL);
+
+	g_object_set (gtk_action_group_get_action (priv->actions, "ResetConstraint"),
+		      "sensitive", value,
+		      NULL);
+
+	g_list_free (list);
+}
+
+static void
+pert_view_save_columns (PlannerPertView *view)
+{
+	PlannerPertViewPriv *priv;
+
+	priv = view->priv;
+
+	planner_view_column_save_helper (PLANNER_VIEW (view), GTK_TREE_VIEW (priv->tree));
+}
+
+static void
+pert_view_load_columns (PlannerPertView *view)
+{
+	PlannerPertViewPriv *priv;
+	GList                *columns, *l;
+	GtkTreeViewColumn    *column;
+	const gchar          *id;
+	gint                  i;
+
+	priv = view->priv;
+
+	/* Load the columns. */
+	planner_view_column_load_helper (PLANNER_VIEW (view), GTK_TREE_VIEW (priv->tree));
+
+	/* Make things a bit more robust by setting defaults if we don't get any
+	 * visible columns. Should be done through a schema instead (but we'll
+	 * keep this since a lot of people get bad installations when installing
+	 * themselves).
+	 */
+	columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (priv->tree));
+	i = 0;
+	for (l = columns; l; l = l->next) {
+		if (gtk_tree_view_column_get_visible (l->data)) {
+			i++;
+		}
+	}
+
+	if (i == 0) {
+		for (l = columns; l; l = l->next) {
+			column = l->data;
+
+			if (g_object_get_data (G_OBJECT (column), "custom")) {
+				continue;
+			}
+
+			id = g_object_get_data (G_OBJECT (column), "id");
+			if (!id) {
+				continue;
+			}
+
+			if (strcmp (id, "wbs") == 0 ||
+			    strcmp (id, "name") == 0 ||
+			    strcmp (id, "work") == 0) {
+				gtk_tree_view_column_set_visible (column, TRUE);
+			} else {
+				gtk_tree_view_column_set_visible (column, FALSE);
+			}
+		}
+	}
+
+	g_list_free (columns);
+}
+
+PlannerView *
+planner_pert_view_new (void)
+{
+	PlannerView *view;
+
+	view = g_object_new (PLANNER_TYPE_PERT_VIEW, NULL);
+
+	return view;
+}
diff --git a/src/planner-pert-view.h b/src/planner-pert-view.h
new file mode 100644
index 0000000..c85d10f
--- /dev/null
+++ b/src/planner-pert-view.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PLANNER_PERT_VIEW_H__
+#define __PLANNER_PERT_VIEW_H__
+
+#include <gtk/gtk.h>
+#include "planner-view.h"
+
+#define PLANNER_TYPE_PERT_VIEW	           (planner_pert_view_get_type ())
+#define PLANNER_PERT_VIEW(obj)	           (G_TYPE_CHECK_INSTANCE_CAST ((obj), PLANNER_TYPE_PERT_VIEW, PlannerPertView))
+#define PLANNER_PERT_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), PLANNER_TYPE_PERT_VIEW, PlannerPertViewClass))
+#define PLANNER_IS_PERT_VIEW(obj)	   (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PLANNER_TYPE_PERT_VIEW))
+#define PLANNER_IS_PERT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PLANNER_TYPE_PERT_VIEW))
+#define PLANNER_PERT_VIEW_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PLANNER_TYPE_PERT_VIEW, PlannerPertViewClass))
+
+typedef struct _PlannerPertView       PlannerPertView;
+typedef struct _PlannerPertViewClass  PlannerPertViewClass;
+typedef struct _PlannerPertViewPriv   PlannerPertViewPriv;
+
+struct _PlannerPertView {
+	PlannerView           parent;
+	PlannerPertViewPriv *priv;
+};
+
+struct _PlannerPertViewClass {
+	PlannerViewClass parent_class;
+};
+
+GType        planner_pert_view_get_type     (void) G_GNUC_CONST;
+PlannerView *planner_pert_view_new          (void);
+
+#endif /* __PLANNER_PERT_VIEW_H__ */
+
diff --git a/src/planner-relation-pert-arrow.c b/src/planner-relation-pert-arrow.c
new file mode 100644
index 0000000..fb48548
--- /dev/null
+++ b/src/planner-relation-pert-arrow.c
@@ -0,0 +1,672 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2002 CodeFactory AB
+ * Copyright (C) 2001-2002 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2001-2002 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <libgnomecanvas/gnome-canvas.h>
+#include <libgnomecanvas/gnome-canvas-util.h>
+#include <libgnomecanvas/gnome-canvas-line.h>
+#include <libplanner/mrp-relation.h>
+#include "planner-pert-node.h"
+#include "planner-relation-pert-arrow.h"
+
+#undef USE_AFFINE
+
+#define ARROW_SIZE 6
+
+typedef struct {
+	gdouble x;
+	gdouble y;
+} PlannerPoint;
+
+typedef enum {
+	PLANNER_ARROW_UP,
+	PLANNER_ARROW_DOWN,
+	PLANNER_ARROW_RIGHT,
+	PLANNER_ARROW_LEFT
+} PlannerArrowDir;
+
+struct _PlannerRelationPertArrowPriv {
+	PlannerPertNode      *successor;
+	PlannerPertNode      *predecessor;
+	MrpRelationType  type;
+
+	gboolean         successor_visible;
+	gboolean         predecessor_visible;
+
+	guint            num_points;
+	PlannerPoint          points[6];
+	PlannerArrowDir       arrow_dir;
+
+	gdouble          x1;
+	gdouble          y1;
+	gdouble          x2;
+	gdouble          y2;
+	gdouble          y2_top;
+	gdouble          y2_bottom;
+};
+
+
+static void    relation_pert_arrow_init         (PlannerRelationPertArrow       *mra);
+static void    relation_pert_arrow_class_init   (PlannerRelationPertArrowClass  *klass);
+static void    relation_pert_arrow_finalize     (GObject               *object);
+static void    relation_pert_arrow_set_property (GObject               *object,
+					    guint                  prop_id,
+					    const GValue          *value,
+					    GParamSpec            *pspec);
+static void    relation_pert_arrow_update       (GnomeCanvasItem       *item,
+					    gdouble               *affine,
+					    ArtSVP                *clip_path,
+					    gint                   flags);
+static void    relation_pert_arrow_draw         (GnomeCanvasItem       *item,
+					    GdkDrawable           *drawable,
+					    gint                   x,
+					    gint                   y,
+					    gint                   width,
+					    gint                   height);
+static gdouble relation_pert_arrow_point        (GnomeCanvasItem       *item,
+					    gdouble                x,
+					    gdouble                y,
+					    gint                   cx,
+					    gint                   cy,
+					    GnomeCanvasItem      **actual_item);
+
+/* Properties */
+enum {
+	PROP_0,
+	PROP_TYPE,
+	PROP_PREDECESSOR,
+	PROP_SUCCESSOR
+};
+
+static GnomeCanvasItemClass *parent_class = NULL;
+
+
+GType
+planner_relation_pert_arrow_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		static const GTypeInfo info = {
+			sizeof (PlannerRelationPertArrowClass),
+			NULL,		/* base_init */
+			NULL,		/* base_finalize */
+			(GClassInitFunc) relation_pert_arrow_class_init,
+			NULL,		/* class_finalize */
+			NULL,		/* class_data */
+			sizeof (PlannerRelationPertArrow),
+			0,              /* n_preallocs */
+			(GInstanceInitFunc) relation_pert_arrow_init
+		};
+
+		type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM,
+					       "PlannerRelationPertArrow",
+					       &info,
+					       0);
+	}
+
+	return type;
+}
+
+static void
+relation_pert_arrow_class_init (PlannerRelationPertArrowClass *klass)
+{
+	GnomeCanvasItemClass *item_class;
+	GObjectClass         *o_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	item_class = GNOME_CANVAS_ITEM_CLASS (klass);
+	o_class = G_OBJECT_CLASS (klass);
+
+	o_class->set_property = relation_pert_arrow_set_property;
+	o_class->finalize = relation_pert_arrow_finalize;
+
+	item_class->update = relation_pert_arrow_update;
+	item_class->draw = relation_pert_arrow_draw;
+	item_class->bounds = NULL;
+	item_class->point = relation_pert_arrow_point;
+
+	/* Properties. */
+	g_object_class_install_property (o_class,
+					 PROP_TYPE,
+					 g_param_spec_enum ("type",
+							    NULL,
+							    NULL,
+							    MRP_TYPE_RELATION_TYPE,
+							    MRP_RELATION_FS,
+							    G_PARAM_WRITABLE));
+}
+
+static void
+relation_pert_arrow_init (PlannerRelationPertArrow *item)
+{
+	PlannerRelationPertArrowPriv *priv;
+
+	priv = g_new0 (PlannerRelationPertArrowPriv, 1);
+	item->priv = priv;
+
+	priv->successor_visible = TRUE;
+	priv->predecessor_visible = TRUE;
+
+	priv->type = MRP_RELATION_FS;
+	priv->arrow_dir = PLANNER_ARROW_RIGHT;
+}
+
+static void
+relation_pert_arrow_finalize (GObject *object)
+{
+	PlannerRelationPertArrow     *arrow = PLANNER_RELATION_PERT_ARROW (object);
+	PlannerRelationPertArrowPriv *priv;
+
+	priv = arrow->priv;
+
+	if (priv->predecessor) {
+		g_object_remove_weak_pointer (G_OBJECT (priv->predecessor),
+					      (gpointer)&priv->predecessor);
+	}
+
+	if (priv->successor) {
+		g_object_remove_weak_pointer (G_OBJECT (priv->successor),
+					      (gpointer)&priv->successor);
+	}
+
+	g_free (priv);
+	arrow->priv = NULL;
+
+	if (G_OBJECT_CLASS (parent_class)->finalize) {
+		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
+	}
+}
+
+static void
+relation_pert_arrow_set_property (GObject      *object,
+			     guint         prop_id,
+			     const GValue *value,
+			     GParamSpec   *pspec)
+{
+	PlannerRelationPertArrow *arrow;
+
+	arrow = PLANNER_RELATION_PERT_ARROW (object);
+
+	switch (prop_id) {
+	case PROP_TYPE:
+		arrow->priv->type = MRP_RELATION_FS;
+		break;
+
+	default:
+		break;
+	}
+}
+
+/*
+FS:
+
+  1:               A (we have a minimum value for A)
+	   XXXXXX----+
+	             | B
+		     XXXXX
+*/
+
+static void
+relation_pert_arrow_update_line_segments (PlannerRelationPertArrow *arrow)
+{
+	PlannerRelationPertArrowPriv *priv;
+	gdouble                   px1, py1, px2, py2;
+	gdouble                   sx1, sy1, sx2, sy2;
+	MrpRelationType           type;
+	gint					succ_col, pred_col;
+	gdouble					  ncol, unit, colwidth, arwidth;
+	gint					  node_width, hscale;
+
+	priv = arrow->priv;
+	type = priv->type;
+
+	planner_pert_node_get_geometry (priv->predecessor,
+					&px1,
+					&py1,
+					&px2,
+					&py2);
+
+	planner_pert_node_get_geometry (priv->successor,
+					&sx1,
+					&sy1,
+					&sx2,
+					&sy2);
+	if (px2 <= sx1)
+	{
+		g_object_get (priv->predecessor,
+								"pcol", &pred_col,
+								"node_width", &node_width,
+								"hscale", &hscale,
+								NULL);
+		g_object_get (priv->successor,
+								"pcol", &succ_col,
+								NULL);
+		ncol = succ_col - pred_col - 1;
+		unit = (px2 - px1) / node_width;
+		colwidth = hscale * unit;
+		arwidth = (hscale - node_width) * unit;
+
+		priv->num_points = 4;
+
+		priv->points[0].x = px2;
+		priv->points[0].y = py1 + (py2 - py1) / 2;
+
+		priv->points[1].x = px2 + arwidth/4;
+		//priv->points[1].x = px2 + ncol*colwidth + arwidth/4;
+		priv->points[1].y = py1 + (py2 - py1) / 2;
+
+		priv->points[2].x = px2 + 3*(arwidth/4);
+		//priv->points[2].x = px2 + ncol*colwidth + 3*(arwidth/4);
+		priv->points[2].y = sy1 + (sy2 - sy1) / 2;
+
+		priv->points[3].x = sx1;
+		priv->points[3].y = sy1 + (sy2 - sy1) / 2;
+		priv->arrow_dir = PLANNER_ARROW_RIGHT;
+	}
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (arrow));
+}
+
+static void
+relation_pert_arrow_geometry_changed (PlannerPertNode      *row,
+				 gdouble               x1,
+				 gdouble               y1,
+				 gdouble               x2,
+				 gdouble               y2,
+				 PlannerRelationPertArrow *arrow)
+{
+	relation_pert_arrow_update_line_segments (arrow);
+}
+
+static void
+relation_pert_arrow_successor_visibility_changed (PlannerPertNode      *row,
+					     gboolean              visible,
+					     PlannerRelationPertArrow *arrow)
+{
+	arrow->priv->successor_visible = visible;
+
+	if (!visible) {
+		gnome_canvas_item_hide (GNOME_CANVAS_ITEM (arrow));
+	} else if (arrow->priv->predecessor_visible) {
+		gnome_canvas_item_show (GNOME_CANVAS_ITEM (arrow));
+	}
+}
+
+static void
+relation_pert_arrow_predecessor_visibility_changed (PlannerPertNode      *row,
+					       gboolean              visible,
+					       PlannerRelationPertArrow *arrow)
+{
+	arrow->priv->predecessor_visible = visible;
+
+	if (!visible) {
+		gnome_canvas_item_hide (GNOME_CANVAS_ITEM (arrow));
+	} else if (arrow->priv->successor_visible) {
+		gnome_canvas_item_show (GNOME_CANVAS_ITEM (arrow));
+	}
+}
+
+void
+planner_relation_pert_arrow_set_predecessor (PlannerRelationPertArrow *arrow,
+					PlannerPertNode      *predecessor)
+{
+	PlannerRelationPertArrowPriv *priv;
+
+	g_return_if_fail (PLANNER_IS_RELATION_PERT_ARROW (arrow));
+	g_return_if_fail (PLANNER_IS_PERT_NODE (predecessor));
+
+	priv = arrow->priv;
+
+	if (priv->predecessor) {
+		g_object_remove_weak_pointer (G_OBJECT (priv->predecessor),
+					      (gpointer)&priv->predecessor);
+	}
+
+	priv->predecessor = predecessor;
+
+	g_object_add_weak_pointer (G_OBJECT (predecessor),
+				   (gpointer)&priv->predecessor);
+
+	g_signal_connect_object (predecessor,
+				 "geometry-changed",
+				 G_CALLBACK (relation_pert_arrow_geometry_changed),
+				 arrow,
+				 0);
+
+	g_signal_connect_object (predecessor,
+				 "visibility-changed",
+				 G_CALLBACK (relation_pert_arrow_predecessor_visibility_changed),
+				 arrow,
+				 0);
+
+	if (priv->predecessor != NULL && priv->successor != NULL) {
+		relation_pert_arrow_update_line_segments (arrow);
+	}
+}
+
+void
+planner_relation_pert_arrow_set_successor (PlannerRelationPertArrow *arrow,
+				      PlannerPertNode      *successor)
+{
+	PlannerRelationPertArrowPriv *priv;
+
+	g_return_if_fail (PLANNER_IS_RELATION_PERT_ARROW (arrow));
+	g_return_if_fail (PLANNER_IS_PERT_NODE (successor));
+
+	priv = arrow->priv;
+
+	if (priv->successor) {
+		g_object_remove_weak_pointer (G_OBJECT (priv->successor),
+					      (gpointer)&priv->successor);
+	}
+
+	priv->successor = successor;
+
+	g_object_add_weak_pointer (G_OBJECT (successor), (gpointer)&priv->successor);
+
+	g_signal_connect_object (successor,
+				 "geometry-changed",
+				 G_CALLBACK (relation_pert_arrow_geometry_changed),
+				 arrow,
+				 0);
+
+	g_signal_connect_object (successor,
+				 "visibility-changed",
+				 G_CALLBACK (relation_pert_arrow_successor_visibility_changed),
+				 arrow,
+				 0);
+
+	if (priv->predecessor != NULL && priv->successor != NULL) {
+		relation_pert_arrow_update_line_segments (arrow);
+	}
+}
+
+PlannerRelationPertArrow *
+planner_relation_pert_arrow_new (PlannerPertNode *successor,
+			    PlannerPertNode *predecessor,
+			    MrpRelationType  type)
+{
+	PlannerRelationPertArrow *arrow;
+	GnomeCanvasGroup     *root;
+
+	root = gnome_canvas_root (GNOME_CANVAS_ITEM (successor)->canvas);
+
+	arrow = PLANNER_RELATION_PERT_ARROW (
+		gnome_canvas_item_new (root,
+				       PLANNER_TYPE_RELATION_PERT_ARROW,
+				       NULL));
+
+	arrow->priv->type = type;
+
+	planner_relation_pert_arrow_set_successor (arrow, successor);
+	planner_relation_pert_arrow_set_predecessor (arrow, predecessor);
+
+	return arrow;
+}
+
+static void
+relation_pert_arrow_get_bounds (PlannerRelationPertArrow *arrow,
+			   gdouble              *x1,
+			   gdouble              *y1,
+			   gdouble              *x2,
+			   gdouble              *y2)
+{
+	PlannerRelationPertArrowPriv *priv;
+	GnomeCanvasItem          *item;
+	gdouble                   wx1, wy1, wx2, wy2;
+	gint                      cx1, cy1, cx2, cy2;
+	gint                      i;
+
+	item = GNOME_CANVAS_ITEM (arrow);
+	priv = arrow->priv;
+
+	/* Get the items bbox in canvas pixel coordinates. */
+
+	/* Silence warning. */
+	wx1 = G_MAXDOUBLE;
+	wy1 = G_MAXDOUBLE;
+	wx2 = -G_MAXDOUBLE;
+	wy2 = -G_MAXDOUBLE;
+
+	for (i = 0; i < priv->num_points; i++) {
+		wx1 = MIN (wx1, priv->points[i].x);
+		wy1 = MIN (wy1, priv->points[i].y);
+		wx2 = MAX (wx2, priv->points[i].x);
+		wy2 = MAX (wy2, priv->points[i].y);
+	}
+
+	/* We could do better here, but this is good enough. */
+	wx1 -= ARROW_SIZE / 2;
+	wy1 -= ARROW_SIZE / 2;
+	wx2 += ARROW_SIZE / 2;
+	wy2 += ARROW_SIZE / 2;
+
+	gnome_canvas_item_i2w (item, &wx1, &wy1);
+	gnome_canvas_item_i2w (item, &wx2, &wy2);
+	gnome_canvas_w2c (item->canvas, wx1, wy1, &cx1, &cy1);
+	gnome_canvas_w2c (item->canvas, wx2, wy2, &cx2, &cy2);
+
+	*x1 = cx1 - 1;
+	*y1 = cy1 - 1;
+	*x2 = cx2 + 1;
+	*y2 = cy2 + 1;
+}
+
+static void
+relation_pert_arrow_update (GnomeCanvasItem *item,
+		       double          *affine,
+		       ArtSVP          *clip_path,
+		       gint             flags)
+{
+	PlannerRelationPertArrow *arrow;
+	gdouble               x1, y1, x2, y2;
+
+	arrow = PLANNER_RELATION_PERT_ARROW (item);
+
+	GNOME_CANVAS_ITEM_CLASS (parent_class)->update (item,
+							affine,
+							clip_path,
+							flags);
+
+	relation_pert_arrow_get_bounds (arrow, &x1, &y1, &x2, &y2);
+
+	gnome_canvas_update_bbox (item, x1, y1, x2, y2);
+}
+
+static void
+relation_pert_arrow_setup_arrow (PlannerArrowDir dir,
+			    GdkPoint        points[4],
+			    gdouble         cx1,
+			    gdouble         cy1,
+			    gdouble         cx2,
+			    gdouble         cy2)
+{
+	switch (dir) {
+	case PLANNER_ARROW_DOWN:
+		points[0].x = cx2;
+		points[0].y = cy2;
+		points[1].x = cx2 - ARROW_SIZE / 2;
+		points[1].y = cy2 - ARROW_SIZE;
+		points[2].x = cx2 + ARROW_SIZE / 2;
+		points[2].y = cy2 - (ARROW_SIZE - 1);
+		points[3].x = cx2;
+		points[3].y = cy2 + 1;
+		break;
+	case PLANNER_ARROW_UP:
+		points[0].x = cx2;
+		points[0].y = cy2 + 1;
+		points[1].x = cx2 + ARROW_SIZE / 2;
+		points[1].y = cy2 + ARROW_SIZE;
+		points[2].x = cx2 - ARROW_SIZE / 2;
+		points[2].y = cy2 + ARROW_SIZE;
+		points[3].x = cx2 + 1;
+		points[3].y = cy2;
+		break;
+	case PLANNER_ARROW_RIGHT:
+		points[0].x = cx2;
+		points[0].y = cy2;
+		points[1].x = cx2 - ARROW_SIZE;
+		points[1].y = cy2 + ARROW_SIZE / 2;
+		points[2].x = cx2 - ARROW_SIZE;
+		points[2].y = cy2 - ARROW_SIZE / 2;
+		points[3].x = cx2;
+		points[3].y = cy2;
+		break;
+	case PLANNER_ARROW_LEFT:
+		points[0].x = cx2;
+		points[0].y = cy2;
+		points[1].x = cx2 + (ARROW_SIZE - 0);
+		points[1].y = cy2 + ARROW_SIZE / 2;
+		points[2].x = cx2 + (ARROW_SIZE - 0);
+		points[2].y = cy2 - ARROW_SIZE / 2;
+		points[3].x = cx2;
+		points[3].y = cy2;
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+}
+
+static void
+relation_pert_arrow_draw (GnomeCanvasItem *item,
+		     GdkDrawable     *drawable,
+		     gint             x,
+		     gint             y,
+		     gint             width,
+		     gint             height)
+{
+	PlannerRelationPertArrow     *arrow;
+	PlannerRelationPertArrowPriv *priv;
+#ifdef USE_AFFINE
+	gdouble                   i2c[6];
+	ArtPoint                  i1, i2, c1, c2;
+#else
+	gdouble                   i2w_dx;
+	gdouble                   i2w_dy;
+	gdouble                   dx1, dy1, dx2, dy2;
+#endif
+	gint                      cx1, cy1, cx2, cy2;
+	GdkGC                    *gc;
+	GdkPoint                  points[4];
+	gint                      i;
+
+	arrow = PLANNER_RELATION_PERT_ARROW (item);
+	priv = arrow->priv;
+
+	gc = gdk_gc_new (drawable);
+	gdk_gc_set_line_attributes (gc,
+				    0,
+				    GDK_LINE_SOLID,
+				    GDK_CAP_BUTT,
+				    GDK_JOIN_MITER);
+
+	/* Silence warning. */
+	cx1 = 0;
+	cy1 = 0;
+	cx2 = 0;
+	cy2 = 0;
+
+#ifdef USE_AFFINE
+	/* Get item area in canvas coordinates. */
+	gnome_canvas_item_i2c_affine (item, i2c);
+#endif
+
+	for (i = 0; i < priv->num_points - 1; i++) {
+#ifdef USE_AFFINE
+		i1.x = priv->points[i].x;
+		i1.y = priv->points[i].y;
+		i2.x = priv->points[i+1].x;
+		i2.y = priv->points[i+1].y;
+		art_affine_point (&c1, &i1, i2c);
+		art_affine_point (&c2, &i2, i2c);
+		cx1 = floor (c1.x + 0.5) - x;
+		cy1 = floor (c1.y + 0.5) - y;
+		cx2 = floor (c2.x + 0.5) - x;
+		cy2 = floor (c2.y + 0.5) - y;
+#else
+		i2w_dx = 0.0;
+		i2w_dy = 0.0;
+		gnome_canvas_item_i2w (item, &i2w_dx, &i2w_dy);
+
+		dx1 = priv->points[i].x;
+		dy1 = priv->points[i].y;
+		dx2 = priv->points[i+1].x;
+		dy2 = priv->points[i+1].y;
+
+		gnome_canvas_w2c (item->canvas,
+				  dx1 + i2w_dx,
+				  dy1 + i2w_dy,
+				  &cx1,
+				  &cy1);
+
+		gnome_canvas_w2c (item->canvas,
+				  dx2 + i2w_dx,
+				  dy2 + i2w_dy,
+				  &cx2,
+				  &cy2);
+
+		cx1 -= x;
+		cy1 -= y;
+		cx2 -= x;
+		cy2 -= y;
+#endif
+
+		gdk_draw_line (drawable,
+			       gc,
+			       cx1,
+			       cy1,
+			       cx2,
+			       cy2);
+	}
+
+	relation_pert_arrow_setup_arrow (priv->arrow_dir,
+					points,
+					cx1,
+					cy1,
+					cx2,
+					cy2);
+
+	gdk_draw_polygon (drawable,
+			  gc,
+			  TRUE,
+			  (GdkPoint *) &points,
+			  4);
+
+	g_object_unref (gc);
+}
+
+static double
+relation_pert_arrow_point (GnomeCanvasItem  *item,
+		      gdouble           x,
+		      gdouble           y,
+		      gint              cx,
+		      gint              cy,
+		      GnomeCanvasItem **actual_item)
+{
+	*actual_item = item;
+
+	return 10.0;
+}
+
diff --git a/src/planner-relation-pert-arrow.h b/src/planner-relation-pert-arrow.h
new file mode 100644
index 0000000..d319358
--- /dev/null
+++ b/src/planner-relation-pert-arrow.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2001-2002 CodeFactory AB
+ * Copyright (C) 2001-2002 Richard Hult <richard@imendio.com>
+ * Copyright (C) 2001-2002 Mikael Hallendal <micke@imendio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PLANNER_RELATION_PERT_ARROW_H__
+#define __PLANNER_RELATION_PERT_ARROW_H__
+
+#include <glib-object.h>
+#include <libgnomecanvas/gnome-canvas.h>
+#include "planner-pert-node.h"
+
+#define PLANNER_TYPE_RELATION_PERT_ARROW            (planner_relation_pert_arrow_get_type ())
+#define PLANNER_RELATION_PERT_ARROW(obj)            (GTK_CHECK_CAST ((obj), PLANNER_TYPE_RELATION_PERT_ARROW, PlannerRelationPertArrow))
+#define PLANNER_RELATION_PERT_ARROW_CLASS(klass)    (GTK_CHECK_CLASS_CAST ((klass), PLANNER_TYPE_RELATION_PERT_ARROW, PlannerRelationPertArrowClass))
+#define PLANNER_IS_RELATION_PERT_ARROW(obj)         (GTK_CHECK_TYPE ((obj), PLANNER_TYPE_RELATION_PERT_ARROW))
+#define PLANNER_IS_RELATION_PERT_ARROW_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), PLANNER_TYPE_RELATION_PERT_ARROW))
+#define PLANNER_RELATION_PERT_ARROW_GET_CLASS(obj)  (GTK_CHECK_GET_CLASS ((obj), PLANNER_TYPE_RELATION_PERT_ARROW, PlannerRelationPertArrowClass))
+
+
+typedef struct _PlannerRelationPertArrow      PlannerRelationPertArrow;
+typedef struct _PlannerRelationPertArrowClass PlannerRelationPertArrowClass;
+typedef struct _PlannerRelationPertArrowPriv  PlannerRelationPertArrowPriv;
+
+struct _PlannerRelationPertArrow {
+	GnomeCanvasItem      parent;
+	PlannerRelationPertArrowPriv *priv;
+};
+
+struct _PlannerRelationPertArrowClass {
+	GnomeCanvasItemClass parent_class;
+};
+
+
+GType            planner_relation_pert_arrow_get_type (void) G_GNUC_CONST;
+
+PlannerRelationPertArrow *planner_relation_pert_arrow_new      (PlannerPertNode *successor,
+					     PlannerPertNode *predecessor,
+					     MrpRelationType type);
+void
+planner_relation_pert_arrow_set_successor             (PlannerRelationPertArrow *arrow,
+					     PlannerPertNode      *successor);
+void
+planner_relation_pert_arrow_set_predecessor           (PlannerRelationPertArrow *arrow,
+					     PlannerPertNode      *predecessor);
+
+
+#endif /* __PLANNER_RELATION_PERT_ARROW_H__ */
+
diff --git a/src/planner-task-tree.c b/src/planner-task-tree.c
index 828b91a..5471caa 100644
--- a/src/planner-task-tree.c
+++ b/src/planner-task-tree.c
@@ -1695,6 +1695,32 @@ task_tree_slack_data_func (GtkTreeViewColumn *tree_column,
 }
 
 static void
+task_tree_freefloat_data_func (GtkTreeViewColumn *tree_column,
+			   GtkCellRenderer   *cell,
+			   GtkTreeModel      *tree_model,
+			   GtkTreeIter       *iter,
+			   gpointer           data)
+{
+	PlannerTaskTree *tree = data;
+	gint             freefloat;
+	gchar           *str;
+	gint             weight;
+
+	gtk_tree_model_get (tree_model, iter,
+			    COL_FREEFLOAT, &freefloat,
+			    COL_WEIGHT, &weight,
+			    -1);
+
+	str = planner_format_duration (tree->priv->project, freefloat);
+
+	g_object_set (cell,
+		      "text", str,
+		      "weight", weight,
+		      NULL);
+	g_free (str);
+}
+
+static void
 task_tree_name_edited (GtkCellRendererText *cell,
 		       gchar               *path_string,
 		       gchar               *new_text,
@@ -2470,7 +2496,7 @@ task_tree_add_column (PlannerTaskTree *tree,
 								cell,
 								NULL);
 		gtk_tree_view_column_set_resizable (col, TRUE);
-		gtk_tree_view_column_set_min_width (col, 70);
+		gtk_tree_view_column_set_min_width (col, 80);
 		gtk_tree_view_column_set_cell_data_func (col,
 							 cell,
 							 task_tree_slack_data_func,
@@ -2481,6 +2507,23 @@ task_tree_add_column (PlannerTaskTree *tree,
 		g_object_set_data (G_OBJECT (col), "id", "slack");
 		break;
 
+	case COL_FREEFLOAT:
+		cell = gtk_cell_renderer_text_new ();
+		col = gtk_tree_view_column_new_with_attributes (title,
+								cell,
+								NULL);
+		gtk_tree_view_column_set_resizable (col, TRUE);
+		gtk_tree_view_column_set_min_width (col, 80);
+		gtk_tree_view_column_set_cell_data_func (col,
+							 cell,
+							 task_tree_freefloat_data_func,
+							 GTK_TREE_VIEW (tree),
+							 NULL);
+		g_object_set_data (G_OBJECT (col),
+				   "data-func", task_tree_freefloat_data_func);
+		g_object_set_data (G_OBJECT (col), "id", "freefloat");
+		break;
+
 	case COL_FINISH:
 		cell = planner_cell_renderer_date_new (FALSE);
 		g_signal_connect (cell,
diff --git a/src/planner-task-view.c b/src/planner-task-view.c
index cdcfcd4..78be8cc 100644
--- a/src/planner-task-view.c
+++ b/src/planner-task-view.c
@@ -340,6 +340,7 @@ task_view_get_widget (PlannerView *view)
 						    COL_WORK, _("Work"),
 						    COL_DURATION, _("Duration"),
 						    COL_SLACK, _("Slack"),
+						    COL_FREEFLOAT, _("Free float"),
 						    COL_COST, _("Cost"),
 						    COL_ASSIGNED_TO, _("Assigned to"),
 						    /* i18n: The string "% Complete" will be used in the header
@@ -817,7 +818,6 @@ task_view_load_columns (PlannerView *view)
 			    strcmp (id, "finish") == 0 ||
 			    strcmp (id, "work") == 0 ||
 			    strcmp (id, "duration") == 0 ||
-			    strcmp (id, "duration") == 0 ||
 			    strcmp (id, "slack") == 0 ||
 			    strcmp (id, "cost") == 0 ||
 			    strcmp (id, "complete") == 0 ||
@@ -841,3 +841,4 @@ planner_task_view_new (void)
 
 	return view;
 }
+
diff --git a/src/planner-window.c b/src/planner-window.c
index e178602..19ae24d 100644
--- a/src/planner-window.c
+++ b/src/planner-window.c
@@ -47,6 +47,7 @@
 #include "planner-print-dialog.h"
 #include "planner-view.h"
 #include "planner-gantt-view.h"
+#include "planner-pert-view.h"
 #include "planner-task-view.h"
 #include "planner-resource-view.h"
 #include "planner-usage-view.h"
@@ -642,6 +643,7 @@ window_populate (PlannerWindow *window)
 
 	priv->views = NULL;
 	priv->views = g_list_append (priv->views, planner_gantt_view_new ());
+	priv->views = g_list_append (priv->views, planner_pert_view_new ());
 	priv->views = g_list_append (priv->views, planner_task_view_new ());
 	priv->views = g_list_append (priv->views, planner_resource_view_new ());
 	priv->views = g_list_append (priv->views, planner_usage_view_new ());
