[MERGE] Merged with main addons.
[odoo/odoo.git] / addons / web_diagram / static / src / js / diagram.js
1 /*---------------------------------------------------------
2  * OpenERP diagram library
3  *---------------------------------------------------------*/
4
5 openerp.web_diagram = function (instance) {
6 var QWeb = instance.web.qweb,
7       _t = instance.web._t,
8      _lt = instance.web._lt;
9 instance.web.views.add('diagram', 'instance.web.DiagramView');
10 instance.web.DiagramView = instance.web.View.extend({
11     display_name: _lt('Diagram'),
12     searchable: false,
13     init: function(parent, dataset, view_id, options) {
14         this._super(parent);
15         this.set_default_options(options);
16         this.view_manager = parent;
17         this.dataset = dataset;
18         this.model = this.dataset.model;
19         this.view_id = view_id;
20         this.domain = this.dataset._domain || [];
21         this.context = {};
22         this.ids = this.dataset.ids;
23     },
24     start: function() {
25         return this.rpc("/web_diagram/diagram/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
26     },
27
28     toTitleCase: function(str) {
29         return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
30     },
31
32     on_loaded: function(result) {
33
34         var self = this;
35         if(this.ids && this.ids.length) {
36             this.id = this.ids[self.dataset.index || 0];
37         }
38
39         this.fields_view = result.fields_view,
40         this.view_id = this.fields_view.view_id,
41         this.fields = this.fields_view.fields,
42         this.nodes = this.fields_view.arch.children[0],
43         this.connectors = this.fields_view.arch.children[1],
44         this.node = this.nodes.attrs.object,
45         this.connector = this.connectors.attrs.object;
46
47         this.$element.html(QWeb.render("DiagramView", this));
48
49         this.$element.find('div.oe_diagram_pager button[data-pager-action]').click(function() {
50             var action = $(this).data('pager-action');
51             self.on_pager_action(action);
52         });
53
54         this.do_update_pager();
55
56         // New Node,Edge
57         this.$element.find('#new_node.oe_diagram_button_new').click(function(){self.add_node();});
58
59         if(this.id) {
60             self.get_diagram_info();
61         }
62
63     },
64
65     get_diagram_info: function() {
66         var self = this;
67         var params = {
68             'id': this.id,
69             'model': this.model,
70             'node': this.node,
71             'connector': this.connector,
72             'bgcolor': this.nodes.attrs.bgcolor,
73             'shape': this.nodes.attrs.shape,
74             'src_node': this.connectors.attrs.source,
75             'des_node': this.connectors.attrs.destination,
76             'label': this.connectors.attrs.label || false,
77             'visible_nodes': [],
78             'invisible_nodes': [],
79             'node_fields': [],
80             'connectors': [],
81             'connectors_fields': []
82         };
83
84         _.each(this.nodes.children, function(child) {
85             if(child.attrs.invisible == '1')
86                 params['invisible_nodes'].push(child.attrs.name);
87             else {
88                 params['visible_nodes'].push(child.attrs.name);
89                 params['node_fields'].push(self.fields[child.attrs.name]['string']|| this.toTitleCase(child.attrs.name));
90             }
91         });
92
93         _.each(this.connectors.children, function(conn) {
94             params['connectors_fields'].push(self.fields[conn.attrs.name]['string']|| this.toTitleCase(conn.attrs.name));
95             params['connectors'].push(conn.attrs.name);
96         });
97
98         this.rpc(
99             '/web_diagram/diagram/get_diagram_info',params,
100             function(result) {
101                 self.draw_diagram(result);
102             }
103         );
104     },
105
106     on_diagram_loaded: function(record) {
107         var id_record = record['id'];
108         if(id_record) {
109             this.id = id_record;
110             this.get_diagram_info();
111         }
112     },
113
114     // Set-up the drawing elements of the diagram
115     draw_diagram: function(result) {
116         var self = this;
117         var res_nodes  = result['nodes'];
118         var res_edges  = result['conn'];
119         this.parent_field = result.parent_field;
120         this.$element.find('h3.oe_diagram_title').text(result.name);
121
122         var id_to_node = {};
123
124
125         var style = {
126             edge_color: "#A0A0A0",
127             edge_label_color: "#555",
128             edge_label_font_size: 10,
129             edge_width: 2,
130             edge_spacing: 100,
131             edge_loop_radius: 100,
132
133             node_label_color: "#333",
134             node_label_font_size: 12,
135             node_outline_color: "#333",
136             node_outline_width: 1,
137             node_selected_color: "#0097BE",
138             node_selected_width: 2,
139             node_size_x: 110,
140             node_size_y: 80,
141             connector_active_color: "#FFF",
142             connector_radius: 4,
143             
144             close_button_radius: 8,
145             close_button_color: "#333",
146             close_button_x_color: "#FFF",
147
148             gray: "#DCDCDC",
149             white: "#FFF",
150             
151             viewport_margin: 50
152         };
153
154         // remove previous diagram
155         var canvas = self.$element.find('div.oe_diagram_diagram')
156                              .empty().get(0);
157
158         var r  = new Raphael(canvas, '100%','100%');
159
160         var graph  = new CuteGraph(r,style,canvas.parentNode);
161         
162         var confirm_dialog = $('#dialog').dialog({ 
163             autoOpen: false,
164             title: _t("Are you sure?") });
165         
166
167         _.each(res_nodes, function(node) {
168             var n = new CuteNode(
169                 graph,
170                 node.x + 50,  //FIXME the +50 should be in the layout algorithm
171                 node.y + 50,
172                 CuteGraph.wordwrap(node.name, 14),
173                 node.shape === 'rectangle' ? 'rect' : 'circle',
174                 node.color === 'white' ? style.white : style.gray);
175
176             n.id = node.id;
177             id_to_node[node.id] = n;
178         });
179
180         _.each(res_edges, function(edge) {
181             var e =  new CuteEdge(
182                 graph,
183                 CuteGraph.wordwrap(edge.signal, 32),
184                 id_to_node[edge.s_id],
185                 id_to_node[edge.d_id] || id_to_node[edge.s_id]  );  //WORKAROUND
186             e.id = edge.id;
187         });
188
189         CuteNode.double_click_callback = function(cutenode){
190             self.edit_node(cutenode.id);
191         };
192         var i = 0;
193         CuteNode.destruction_callback = function(cutenode){
194             if(!confirm(_t("Deleting this node cannot be undone.\nIt will also delete all connected transitions.\n\nAre you sure ?"))){
195                 return $.Deferred().reject().promise();
196             }
197             return new instance.web.DataSet(self,self.node).unlink([cutenode.id]);
198         };
199         CuteEdge.double_click_callback = function(cuteedge){
200             self.edit_connector(cuteedge.id);
201         };
202
203         CuteEdge.creation_callback = function(node_start, node_end){
204             return {label:_t("")};
205         };
206         CuteEdge.new_edge_callback = function(cuteedge){
207             self.add_connector(cuteedge.get_start().id,
208                                cuteedge.get_end().id,
209                                cuteedge);
210         };
211         CuteEdge.destruction_callback = function(cuteedge){
212             if(!confirm(_t("Deleting this transition cannot be undone.\n\nAre you sure ?"))){
213                 return $.Deferred().reject().promise();
214             }
215             return new instance.web.DataSet(self,self.connector).unlink([cuteedge.id]);
216         };
217
218     },
219
220     // Creates a popup to edit the content of the node with id node_id
221     edit_node: function(node_id){
222         var self = this;
223         var title = _t('Activity');
224         var pop = new instance.web.form.FormOpenPopup(self);
225
226         pop.show_element(
227                 self.node,
228                 node_id,
229                 self.context || self.dataset.context,
230                 {
231                     title: _t("Open: ") + title
232                 }
233             );
234
235         pop.on_write.add(function() {
236             self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
237             });
238
239         var form_fields = [self.parent_field];
240         var form_controller = pop.view_form;
241
242         form_controller.on_record_loaded.add_first(function() {
243             _.each(form_fields, function(fld) {
244                 if (!(fld in form_controller.fields)) { return; }
245                 var field = form_controller.fields[fld];
246                 field.$input.prop('disabled', true);
247                 field.$drop_down.unbind();
248                 field.$menu_btn.unbind();
249             });
250         });
251     },
252
253     // Creates a popup to add a node to the diagram
254     add_node: function(){
255         var self = this;
256         var title = _t('Activity');
257         var pop = new instance.web.form.SelectCreatePopup(self);
258         pop.select_element(
259             self.node,
260             {
261                 title: _t("Create:") + title,
262                 initial_view: 'form',
263                 disable_multiple_selection: true
264             },
265             self.dataset.domain,
266             self.context || self.dataset.context
267         );
268         pop.on_select_elements.add_last(function(element_ids) {
269             self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
270         });
271
272         var form_controller = pop.view_form;
273         var form_fields = [this.parent_field];
274
275         form_controller.on_record_loaded.add_last(function() {
276             _.each(form_fields, function(fld) {
277                 if (!(fld in form_controller.fields)) { return; }
278                 var field = form_controller.fields[fld];
279                 field.set_value(self.id);
280                 field.dirty = true;
281             });
282         });
283     },
284
285     // Creates a popup to edit the connector of id connector_id
286     edit_connector: function(connector_id){
287         var self = this;
288         var title = _t('Transition');
289         var pop = new instance.web.form.FormOpenPopup(self);
290         pop.show_element(
291             self.connector,
292             parseInt(connector_id,10),      //FIXME Isn't connector_id supposed to be an int ?
293             self.context || self.dataset.context,
294             {
295                 title: _t("Open: ") + title
296             }
297         );
298         pop.on_write.add(function() {
299             self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
300         });
301     },
302
303     // Creates a popup to add a connector from node_source_id to node_dest_id.
304     // dummy_cuteedge if not null, will be removed form the graph after the popup is closed.
305     add_connector: function(node_source_id, node_dest_id, dummy_cuteedge){
306         var self = this;
307         var title = _t('Transition');
308         var pop = new instance.web.form.SelectCreatePopup(self);
309
310         pop.select_element(
311             self.connector,
312             {
313                 title: _t("Create:") + title,
314                 initial_view: 'form',
315                 disable_multiple_selection: true
316             },
317             this.dataset.domain,
318             this.context || this.dataset.context
319         );
320
321         pop.on_select_elements.add_last(function(element_ids) {
322             self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_diagram_loaded);
323         });
324         // We want to destroy the dummy edge after a creation cancel. This destroys it even if we save the changes.
325         // This is not a problem since the diagram is completely redrawn on saved changes.
326         pop.$element.bind("dialogbeforeclose",function(){
327             if(dummy_cuteedge){
328                 dummy_cuteedge.remove();
329             }
330         });
331
332         var form_controller = pop.view_form;
333
334         form_controller.on_record_loaded.add_last(function () {
335             form_controller.fields[self.connectors.attrs.source].set_value(node_source_id);
336             form_controller.fields[self.connectors.attrs.source].dirty = true;
337             form_controller.fields[self.connectors.attrs.destination].set_value(node_dest_id);
338             form_controller.fields[self.connectors.attrs.destination].dirty = true;
339         });
340     },
341
342     on_pager_action: function(action) {
343         switch (action) {
344             case 'first':
345                 this.dataset.index = 0;
346                 break;
347             case 'previous':
348                 this.dataset.previous();
349                 break;
350             case 'next':
351                 this.dataset.next();
352                 break;
353             case 'last':
354                 this.dataset.index = this.dataset.ids.length - 1;
355                 break;
356         }
357         var loaded = this.dataset.read_index(_.keys(this.fields_view.fields))
358                 .pipe(this.on_diagram_loaded);
359         this.do_update_pager();
360         return loaded;
361     },
362
363     do_update_pager: function(hide_index) {
364         var $pager = this.$element.find('div.oe_diagram_pager');
365         var index = hide_index ? '-' : this.dataset.index + 1;
366         if(!this.dataset.count) {
367             this.dataset.count = this.dataset.ids.length;
368         }
369         $pager.find('span.oe_pager_index').html(index);
370         $pager.find('span.oe_pager_count').html(this.dataset.count);
371     },
372
373     do_show: function() {
374         this.do_push_state({});
375         return $.when(this._super(), this.on_pager_action('reload'));
376     }
377 });
378 };
379
380 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: