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