diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index 5a08c21002a..e07fdd07bbc 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -32,6 +32,7 @@ import pooler import netsvc from osv import fields +import addons import zipfile import release @@ -301,7 +302,7 @@ def load_information_from_description_file(module): :param module: The name of the module (sale, purchase, ...) """ for filename in ['__openerp__.py', '__terp__.py']: - description_file = get_module_resource(module, filename) + description_file = addons.get_module_resource(module, filename) if os.path.isfile(description_file): return eval(tools.file_open(description_file).read()) diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index ae046adc95d..a320d14b9c5 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -159,7 +159,7 @@ class act_window(osv.osv): ] def get_filters(self, cr, uid, model): - cr.execute('select id from ir_act_window a where a.id not in (select act_id from ir_act_window_user_rel) and a.res_model=\''+model+'\' and a.filter=\'1\';') + cr.execute('SELECT id FROM ir_act_window a WHERE a.id not in (SELECT act_id FROM ir_act_window_user_rel) AND a.res_model=\''+model+'\' and a.filter=\'1\';') all_ids = cr.fetchall() filter_ids = map(lambda x:x[0],all_ids) act_ids = self.search(cr,uid,[('res_model','=',model),('filter','=',1),('default_user_ids','in',(','.join(map(str,[uid,])),))]) @@ -377,7 +377,9 @@ server_object_lines() class actions_server(osv.osv): def _select_signals(self, cr, uid, context={}): - cr.execute("select distinct w.osv, t.signal from wkf w, wkf_activity a, wkf_transition t where w.id = a.wkf_id and t.act_from = a.id or t.act_to = a.id and t.signal!='' and t.signal not in (null, NULL)") + cr.execute("SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t \ + WHERE w.id = a.wkf_id AND t.act_from = a.id OR t.act_to = a.id AND t.signal!='' \ + AND t.signal not in (null, NULL)") result = cr.fetchall() or [] res = [] for rs in result: diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index b66d7eedcf6..95ce993c8b1 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # @@ -15,12 +15,13 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## from osv import fields,osv from lxml import etree +from tools import graph import tools import netsvc import os @@ -59,6 +60,7 @@ class view(osv.osv): ('mdx','mdx'), ('graph', 'Graph'), ('calendar', 'Calendar'), + ('diagram','Diagram'), ('gantt', 'Gantt'), ('search','Search')), 'View Type', required=True), 'arch': fields.text('View Architecture', required=True), @@ -129,6 +131,64 @@ class view(osv.osv): return result return super(view, self).write(cr, uid, ids, vals, context) + + def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node,signal,scale,context={}): + nodes=[] + nodes_name=[] + transitions=[] + start=[] + tres={} + sig={} + no_ancester=[] + blank_nodes = [] + + _Model_Obj=self.pool.get(model) + _Node_Obj=self.pool.get(node_obj) + _Arrow_Obj=self.pool.get(conn_obj) + + for model_key,model_value in _Model_Obj._columns.items(): + if model_value._type=='one2many': + if model_value._obj==node_obj: + _Node_Field=model_key + _Model_Field=model_value._fields_id + flag=False + for node_key,node_value in _Node_Obj._columns.items(): + if node_value._type=='one2many': + if node_value._obj==conn_obj: + if src_node in _Arrow_Obj._columns and flag: + _Source_Field=node_key + if des_node in _Arrow_Obj._columns and not flag: + _Destination_Field=node_key + flag = True + + datas = _Model_Obj.read(cr, uid, id, [],context) + for a in _Node_Obj.read(cr,uid,datas[_Node_Field],[]): + if a[_Source_Field] or a[_Destination_Field]: + nodes_name.append((a['id'],a['name'])) + nodes.append(a['id']) + else: + blank_nodes.append({'id': a['id'],'name':a['name']}) + + if a.has_key('flow_start') and a['flow_start']: + start.append(a['id']) + else: + if not a[_Source_Field]: + no_ancester.append(a['id']) + for t in _Arrow_Obj.read(cr,uid, a[_Destination_Field],[]): + transitions.append((a['id'], t[des_node][0])) + tres[str(t['id'])] = (a['id'],t[des_node][0]) + if t.has_key(str(signal)) and str(t[signal])=='False': + t[signal]=' ' + sig[str(t['id'])] = (a['id'],t.get(signal,' ')) + g = graph(nodes, transitions, no_ancester) + g.process(start) + g.scale(*scale) + result = g.result_get() + results = {} + for node in nodes_name: + results[str(node[0])] = result[node[0]] + results[str(node[0])]['name'] = node[1] + return {'nodes': results, 'transitions': tres, 'signal' : sig, 'blank_nodes': blank_nodes} view() class view_sc(osv.osv): diff --git a/bin/addons/base/ir/workflow/workflow.py b/bin/addons/base/ir/workflow/workflow.py index 89e18a08fb4..2a551e9549f 100644 --- a/bin/addons/base/ir/workflow/workflow.py +++ b/bin/addons/base/ir/workflow/workflow.py @@ -44,45 +44,64 @@ class workflow(osv.osv): wf_service.clear_cache(cr, user) return super(workflow, self).write(cr, user, ids, vals, context=context) + def get_active_workitems(self, cr, uid, res, res_id, context={}): + + cr.execute('select * from wkf where osv=%s limit 1',(res,)) + wkfinfo = cr.dictfetchone() + workitems = [] + + if wkfinfo: + cr.execute('SELECT id FROM wkf_instance \ + WHERE res_id=%s AND wkf_id=%s \ + ORDER BY state LIMIT 1', + (res_id, wkfinfo['id'])) + inst_id = cr.fetchone() + + cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (inst_id,)) + workitems = dict(cr.fetchall()) + + return {'wkf': wkfinfo, 'workitems': workitems} + + # # scale = (vertical-distance, horizontal-distance, min-node-width(optional), min-node-height(optional), margin(default=20)) # - def graph_get(self, cr, uid, id, scale, context={}): - - nodes= [] - nodes_name = [] - transitions = [] - start = [] - tres = {} - no_ancester = [] - workflow = self.browse(cr, uid, id, context) - for a in workflow.activities: - nodes_name.append((a.id,a.name)) - nodes.append(a.id) - if a.flow_start: - start.append(a.id) - else: - if not a.in_transitions: - no_ancester.append(a.id) - - for t in a.out_transitions: - transitions.append((a.id, t.act_to.id)) - tres[t.id] = (a.id, t.act_to.id) - - - g = graph(nodes, transitions, no_ancester) - g.process(start) - g.scale(*scale) - result = g.result_get() - results = {} - - for node in nodes_name: - results[str(node[0])] = result[node[0]] - results[str(node[0])]['name'] = node[1] - - return {'nodes': results, 'transitions': tres} +# def graph_get(self, cr, uid, id, scale, context={}): +# +# nodes= [] +# nodes_name = [] +# transitions = [] +# start = [] +# tres = {} +# no_ancester = [] +# workflow = self.browse(cr, uid, id, context) +# for a in workflow.activities: +# nodes_name.append((a.id,a.name)) +# nodes.append(a.id) +# if a.flow_start: +# start.append(a.id) +# else: +# if not a.in_transitions: +# no_ancester.append(a.id) +# +# for t in a.out_transitions: +# transitions.append((a.id, t.act_to.id)) +# tres[t.id] = (a.id, t.act_to.id) +# +# +# g = graph(nodes, transitions, no_ancester) +# g.process(start) +# g.scale(*scale) +# result = g.result_get() +# results = {} +# +# for node in nodes_name: +# results[str(node[0])] = result[node[0]] +# results[str(node[0])]['name'] = node[1] +# +# return {'nodes': results, 'transitions': tres} def create(self, cr, user, vals, context=None): diff --git a/bin/addons/base/ir/workflow/workflow_view.xml b/bin/addons/base/ir/workflow/workflow_view.xml index a0bfb7fdc03..04457f21338 100644 --- a/bin/addons/base/ir/workflow/workflow_view.xml +++ b/bin/addons/base/ir/workflow/workflow_view.xml @@ -2,13 +2,13 @@ - + - + workflow.form workflow @@ -23,6 +23,25 @@ + + + workflow.diagram + workflow + diagram + + + + + + + + + + + + + + workflow.tree workflow @@ -35,12 +54,13 @@ - + Workflows workflow form + tree,form,diagram @@ -109,7 +129,7 @@ - + Activities workflow.activity @@ -117,14 +137,14 @@ - - + + - + workflow.transition.form workflow.transition @@ -157,8 +177,8 @@ - - + + Transitions workflow.transition @@ -166,13 +186,13 @@ - + - + workflow.instance.form workflow.instance @@ -201,7 +221,7 @@ - + Instances workflow.instance @@ -209,13 +229,13 @@ - + - + workflow.workitem.form workflow.workitem @@ -242,7 +262,7 @@ - + Workitems workflow.workitem @@ -250,6 +270,6 @@ - + diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py index e11b4964471..01ea8cc0745 100644 --- a/bin/addons/base/module/module.py +++ b/bin/addons/base/module/module.py @@ -237,6 +237,8 @@ class module(osv.osv): def button_upgrade(self, cr, uid, ids, context=None): depobj = self.pool.get('ir.module.module.dependency') todo = self.browse(cr, uid, ids, context=context) + self.update_list(cr, uid) + i = 0 while i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -89,7 +128,7 @@ - + @@ -139,7 +178,7 @@ - + @@ -357,8 +396,10 @@ + + @@ -429,7 +470,7 @@ - + @@ -442,9 +483,9 @@ - + - + @@ -469,6 +510,7 @@ + @@ -545,6 +587,7 @@ + diff --git a/bin/osv/fields.py b/bin/osv/fields.py index ce02de20a27..19735857680 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -81,6 +81,7 @@ class _column(object): self.select = select self.selectable = True self.group_operator = args.get('group_operator', False) + self.parent_field = args.get('parent_field', False) for a in args: if args[a]: setattr(self, a, args[a]) @@ -365,7 +366,8 @@ class one2many(_column): _prefetch = False _type = 'one2many' - def __init__(self, obj, fields_id, string='unknown', limit=None, **args): + def __init__(self, obj, fields_id, string='unknown', limit=20, **args): + args.update({'parent_field':fields_id}) _column.__init__(self, string=string, **args) self._obj = obj self._fields_id = fields_id diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 0beee3be289..c580474b8b9 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -247,6 +247,15 @@ class browse_record(object): new_data[n] = browse_null() elif f._type in ('one2many', 'many2many') and len(data[n]): new_data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context) + elif f._type in ('reference'): + if data[n]: + ref_obj, ref_id = data[n].split(',') + ref_id = long(ref_id) + obj = self._table.pool.get(ref_obj) + compids = False + new_data[n] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) + else: + new_data[n] = browse_null() else: new_data[n] = data[n] self._data[data['id']].update(new_data) @@ -370,6 +379,10 @@ class orm_template(object): CONCURRENCY_CHECK_FIELD = '__last_update' + def view_init(self, cr , uid , fields_list, context=None): + """Override this method to do specific things when a view on the object is opened.""" + pass + def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None): raise _('The read_group method is not implemented on this object !') @@ -383,7 +396,7 @@ class orm_template(object): model_id = cr.fetchone()[0] if 'module' in context: name_id = 'model_'+self._name.replace('.','_') - cr.execute('select * from ir_model_data where name=%s and res_id=%s', (name_id,model_id)) + cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module'])) if not cr.rowcount: cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \ (name_id, context['module'], 'ir.model', model_id) @@ -711,6 +724,7 @@ class orm_template(object): else: module, xml_id = current_module, line[i] id = ir_model_data_obj._get_id(cr, uid, module, xml_id) + res_res_id = ir_model_data_obj.read(cr, uid, [id], ['res_id']) if res_res_id: @@ -1008,7 +1022,7 @@ class orm_template(object): continue res[f] = {'type': self._columns[f]._type} for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator', - 'change_default', 'translate', 'help', 'select', 'selectable'): + 'change_default', 'translate', 'help', 'select', 'selectable','parent_field'): if getattr(self._columns[f], arg): res[f][arg] = getattr(self._columns[f], arg) if not read_access: @@ -1188,7 +1202,21 @@ class orm_template(object): button.set('readonly', str(int(not can_click))) arch = etree.tostring(node, encoding="utf-8").replace('\t', '') - fields = self.fields_get(cr, user, fields_def.keys(), context) + + #code for diagram view. + fields={} + if node.tag=='diagram': + if node.getchildren()[0].tag=='node': + node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context) + if node.getchildren()[1].tag=='arrow': + arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context) + for key,value in node_fields.items(): + fields[key]=value + for key,value in arrow_fields.items(): + fields[key]=value + else: + fields = self.fields_get(cr, user, fields_def.keys(), context) + for field in fields_def: if field == 'id': # sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references) @@ -1204,7 +1232,6 @@ class orm_template(object): msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model" netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg) raise except_orm('View error', msg) - return arch, fields def __get_default_calendar_view(self): @@ -1463,7 +1490,7 @@ class orm_template(object): if context and context.get('active_id',False): data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action if data_menu: - act_id = int(data_menu.split(',')[1]) + act_id = data_menu.id if act_id: data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0] result['submenu'] = getattr(data_action,'menus', False) @@ -1501,6 +1528,10 @@ class orm_template(object): 'action': resaction, 'relate': resrelate } + if result['type']=='form' and result['arch'].count("default_focus")>1: + msg = "Form View contain more than one default_focus attribute" + netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg) + raise except_orm('View Error !',msg) return result _view_look_dom_arch = __view_look_dom_arch @@ -1679,6 +1710,7 @@ class orm_memory(orm_template): return id_new def default_get(self, cr, uid, fields_list, context=None): + self.view_init(cr, uid, fields_list, context) if not context: context = {} value = {} diff --git a/bin/tools/graph.py b/bin/tools/graph.py index 7b0375504f2..05a1b8302f0 100644 --- a/bin/tools/graph.py +++ b/bin/tools/graph.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # @@ -16,7 +16,7 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## @@ -26,80 +26,80 @@ import math class graph(object): def __init__(self, nodes, transitions, no_ancester=None): """Initailize graph's object - + @param nodes: list of ids of nodes in the graph @param transitions: list of edges in the graph in the form (source_node, destination_node) - @param no_ancester: list of nodes with no incoming edges + @param no_ancester: list of nodes with no incoming edges """ - + self.nodes = nodes or [] self.edges = transitions or [] self.no_ancester = no_ancester or {} trans = {} - + for t in transitions: trans.setdefault(t[0], []) trans[t[0]].append(t[1]) self.transitions = trans self.result = {} - - + + def init_rank(self): """Computes rank of the nodes of the graph by finding initial feasible tree """ self.edge_wt = {} for link in self.links: self.edge_wt[link] = self.result[link[1]]['x'] - self.result[link[0]]['x'] - + tot_node = self.partial_order.__len__() - #do until all the nodes in the component are searched + #do until all the nodes in the component are searched while self.tight_tree()self.edge_wt[edge]-1): slack = self.edge_wt[edge]-1 new_edge = edge - + if new_edge[0] not in self.reachable_nodes: delta = -(self.edge_wt[new_edge]-1) else: delta = self.edge_wt[new_edge]-1 - + for node in self.result: if node in self.reachable_nodes: self.result[node]['x'] += delta - + for edge in self.edge_wt: - self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x'] - - self.init_cutvalues() - - + self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x'] + + self.init_cutvalues() + + def tight_tree(self): self.reachable_nodes = [] self.tree_edges = [] - self.reachable_node(self.start) + self.reachable_node(self.start) return self.reachable_nodes.__len__() - - + + def reachable_node(self, node): - """Find the nodes of the graph which are only 1 rank apart from each other + """Find the nodes of the graph which are only 1 rank apart from each other """ - + if node not in self.reachable_nodes: self.reachable_nodes.append(node) for edge in self.edge_wt: @@ -109,27 +109,27 @@ class graph(object): if edge[1] not in self.reachable_nodes: self.reachable_nodes.append(edge[1]) self.reachable_node(edge[1]) - - + + def init_cutvalues(self): """Initailize cut values of edges of the feasible tree. - Edges with negative cut-values are removed from the tree to optimize rank assignment + Edges with negative cut-values are removed from the tree to optimize rank assignment """ self.cut_edges = {} self.head_nodes = [] i=0; - + for edge in self.tree_edges: self.head_nodes = [] rest_edges = [] rest_edges += self.tree_edges rest_edges.__delitem__(i) - self.head_component(self.start, rest_edges) + self.head_component(self.start, rest_edges) i+=1 positive = 0 negative = 0 for source_node in self.transitions: - if source_node in self.head_nodes: + if source_node in self.head_nodes: for dest_node in self.transitions[source_node]: if dest_node not in self.head_nodes: negative+=1 @@ -140,62 +140,62 @@ class graph(object): self.cut_edges[edge] = positive - negative - + def head_component(self, node, rest_edges): """Find nodes which are reachable from the starting node, after removing an edge """ if node not in self.head_nodes: self.head_nodes.append(node) - + for edge in rest_edges: - if edge[0]==node: + if edge[0]==node: self.head_component(edge[1],rest_edges) - + def process_ranking(self, node, level=0): """Computes initial feasible ranking after making graph acyclic with depth-first search """ - + if node not in self.result: self.result[node] = {'y': None, 'x':level, 'mark':0} else: if level > self.result[node]['x']: self.result[node]['x'] = level - + if self.result[node]['mark']==0: self.result[node]['mark'] = 1 for sec_end in self.transitions.get(node, []): self.process_ranking(sec_end, level+1) - - + + def make_acyclic(self, parent, node, level, tree): """Computes Partial-order of the nodes with depth-first search """ - + if node not in self.partial_order: self.partial_order[node] = {'level':level, 'mark':0} if parent: tree.append((parent, node)) - + if self.partial_order[node]['mark']==0: self.partial_order[node]['mark'] = 1 for sec_end in self.transitions.get(node, []): self.links.append((node, sec_end)) self.make_acyclic(node, sec_end, level+1, tree) - return tree + return tree + - def rev_edges(self, tree): - """reverse the direction of the edges whose source-node-partail_order> destination-node-partail_order - to make the graph acyclic + """reverse the direction of the edges whose source-node-partail_order> destination-node-partail_order + to make the graph acyclic """ Is_Cyclic = False - i=0 + i=0 for link in self.links: src = link[0] des = link[1] - edge_len = self.partial_order[des]['level'] - self.partial_order[src]['level'] + edge_len = self.partial_order[des]['level'] - self.partial_order[src]['level'] if edge_len < 0: self.links.__delitem__(i) self.links.insert(i, (des, src)) @@ -205,9 +205,9 @@ class graph(object): elif math.fabs(edge_len) > 1: Is_Cyclic = True i += 1 - + return Is_Cyclic - + def exchange(self, e, f): """Exchange edges to make feasible-tree optimized @param edge: edge with negative cut-value @@ -216,144 +216,144 @@ class graph(object): self.tree_edges.__delitem__(self.tree_edges.index(e)) self.tree_edges.append(f) self.init_cutvalues() - - + + def enter_edge(self, edge): - """Finds a new_edge with minimum slack value to replace an edge with negative cut-value - - @param edge: edge with negative cut-value + """Finds a new_edge with minimum slack value to replace an edge with negative cut-value + + @param edge: edge with negative cut-value """ - + self.head_nodes = [] rest_edges = [] rest_edges += self.tree_edges rest_edges.__delitem__(rest_edges.index(edge)) self.head_component(self.start, rest_edges) - + if self.head_nodes.__contains__(edge[1]): l = [] for node in self.result: if not self.head_nodes.__contains__(node): - l.append(node) + l.append(node) self.head_nodes = l - + slack = 100 new_edge = edge for source_node in self.transitions: - if source_node in self.head_nodes: + if source_node in self.head_nodes: for dest_node in self.transitions[source_node]: if dest_node not in self.head_nodes: if(slack>(self.edge_wt[edge]-1)): slack = self.edge_wt[edge]-1 new_edge = (source_node, dest_node) - - return new_edge - + + return new_edge + def leave_edge(self): - """Returns the edge with negative cut_value(if exists) + """Returns the edge with negative cut_value(if exists) """ if self.critical_edges: for edge in self.critical_edges: self.cut_edges[edge] = 0 - + for edge in self.cut_edges: if self.cut_edges[edge]<0: return edge - - return None - - + + return None + + def finalize_rank(self, node, level): self.result[node]['x'] = level for destination in self.optimal_edges.get(node, []): self.finalize_rank(destination, level+1) - - + + def normalize(self): """The ranks are normalized by setting the least rank to zero. """ - + least_rank = min(map(lambda x: x['x'], self.result.values())) - + if(least_rank!=0): for node in self.result: - self.result[node]['x']-=least_rank - - - def make_chain(self): - """Edges between nodes more than one rank apart are replaced by chains of unit + self.result[node]['x']-=least_rank + + + def make_chain(self): + """Edges between nodes more than one rank apart are replaced by chains of unit length edges between temporary nodes. """ - + for edge in self.edge_wt: if self.edge_wt[edge]>1: self.transitions[edge[0]].remove(edge[1]) start = self.result[edge[0]]['x'] end = self.result[edge[1]]['x'] - - for rank in range(start+1, end): - if not self.result.get((rank, 'temp'), False): + + for rank in range(start+1, end): + if not self.result.get((rank, 'temp'), False): self.result[(rank, 'temp')] = {'y': None, 'x': rank, 'mark': 0} - + for rank in range(start, end): - if start==rank: + if start==rank: self.transitions[edge[0]].append((rank+1, 'temp')) elif rank==end-1: self.transitions.setdefault((rank, 'temp'), []).append(edge[1]) else: - self.transitions.setdefault((rank, 'temp'), []).append((rank+1, 'temp')) - - + self.transitions.setdefault((rank, 'temp'), []).append((rank+1, 'temp')) + + def init_order(self, node, level): """Initialize orders the nodes in each rank with depth-first search - """ - if not self.result[node]['y']: + """ + if not self.result[node]['y']: self.result[node]['y'] = self.order[level] - self.order[level] = self.order[level]+1 - + self.order[level] = self.order[level]+1 + for sec_end in self.transitions.get(node, []): self.init_order(sec_end, self.result[sec_end]['x']) - - + + def order_heuristic(self): for i in range(12): self.wmedian() - - + + def wmedian(self): """Applies median heuristic to find optimzed order of the nodes with in their ranks """ - for level in self.levels: - + for level in self.levels: + node_median = [] - nodes = self.levels[level] - for node in nodes: + nodes = self.levels[level] + for node in nodes: node_median.append((node, self.median_value(node, level-1))) sort_list = sorted(node_median, key=operator.itemgetter(1)) new_list = [tuple[0] for tuple in sort_list] - + self.levels[level] = new_list order = 0 for node in nodes: self.result[node]['y'] = order - order +=1 + order +=1 def median_value(self, node, adj_rank): - """Returns median value of a vertex , defined as the median position of the adjacent vertices - - @param node: node to process - @param adj_rank: rank 1 less than the node's rank + """Returns median value of a vertex , defined as the median position of the adjacent vertices + + @param node: node to process + @param adj_rank: rank 1 less than the node's rank """ adj_nodes = self.adj_position(node, adj_rank) l = len(adj_nodes) m = l/2 - + if l==0: - return -1.0 + return -1.0 elif l%2 == 1: return adj_nodes[m]#median of the middle element elif l==2: @@ -361,106 +361,106 @@ class graph(object): else: left = adj_nodes[m-1] - adj_nodes[0] right = adj_nodes[l-1] - adj_nodes[m] - return ((adj_nodes[m-1]*right) + (adj_nodes[m]*left))/(left+right) - - + return ((adj_nodes[m-1]*right) + (adj_nodes[m]*left))/(left+right) + + def adj_position(self, node, adj_rank): """Returns list of the present positions of the nodes adjacent to node in the given adjacent rank. - - @param node: node to process - @param adj_rank: rank 1 less than the node's rank + + @param node: node to process + @param adj_rank: rank 1 less than the node's rank """ - - pre_level_nodes = self.levels.get(adj_rank, []) + + pre_level_nodes = self.levels.get(adj_rank, []) adj_nodes = [] - + if pre_level_nodes: for src in pre_level_nodes: if (self.transitions.get(src) and self.transitions[src].__contains__(node)): adj_nodes.append(self.result[src]['y']) - - return adj_nodes - - + + return adj_nodes + + def preprocess_order(self): levels = {} - + for r in self.partial_order: l = self.result[r]['x'] levels.setdefault(l,[]) levels[l].append(r) - + self.levels = levels - - - def graph_order(self): - """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component + + + def graph_order(self): + """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component """ - mid_pos = None + mid_pos = 0.0 max_level = max(map(lambda x: len(x), self.levels.values())) - + for level in self.levels: if level: no = len(self.levels[level]) - factor = (max_level - no) * 0.10 - list = self.levels[level] + factor = (max_level - no) * 0.10 + list = self.levels[level] list.reverse() - + if no%2==0: first_half = list[no/2:] - factor = -factor + factor = -factor else: first_half = list[no/2+1:] if max_level==1:#for the case when horizontal graph is there self.result[list[no/2]]['y'] = mid_pos + (self.result[list[no/2]]['x']%2 * 0.5) else: self.result[list[no/2]]['y'] = mid_pos + factor - - last_half = list[:no/2] - + + last_half = list[:no/2] + i=1 for node in first_half: self.result[node]['y'] = mid_pos - (i + factor) i += 1 - + i=1 for node in last_half: self.result[node]['y'] = mid_pos + (i + factor) i += 1 - else: + else: self.max_order += max_level+1 - mid_pos = self.result[self.start]['y'] - + mid_pos = self.result[self.start]['y'] + def tree_order(self, node, last=0): mid_pos = self.result[node]['y'] l = self.transitions.get(node, []) l.reverse() no = len(l) - + if no%2==0: - first_half = l[no/2:] - factor = 1 + first_half = l[no/2:] + factor = 1 else: first_half = l[no/2+1:] factor = 0 - - last_half = l[:no/2] - + + last_half = l[:no/2] + i=1 for child in first_half: self.result[child]['y'] = mid_pos - (i - (factor * 0.5)) i += 1 - + if self.transitions.get(child, False): if last: self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 last = self.tree_order(child, last) - + if no%2: mid_node = l[no/2] - self.result[mid_node]['y'] = mid_pos - + self.result[mid_node]['y'] = mid_pos + if self.transitions.get((mid_node), False): if last: self.result[mid_node]['y'] = last + len(self.transitions[mid_node])/2 + 1 @@ -469,51 +469,51 @@ class graph(object): if last: self.result[mid_node]['y'] = last + 1 self.result[node]['y'] = self.result[mid_node]['y'] - mid_pos = self.result[node]['y'] - - i=1 + mid_pos = self.result[node]['y'] + + i=1 last_child = None - for child in last_half: + for child in last_half: self.result[child]['y'] = mid_pos + (i - (factor * 0.5)) - last_child = child + last_child = child i += 1 if self.transitions.get(child, False): if last: - self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 + self.result[child]['y'] = last + len(self.transitions[child])/2 + 1 last = self.tree_order(child, last) - + if last_child: last = self.result[last_child]['y'] - - return last - - - def process_order(self): - """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component + + return last + + + def process_order(self): + """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component """ - + if self.Is_Cyclic: max_level = max(map(lambda x: len(x), self.levels.values())) - + if max_level%2: self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1) else: self.result[self.start]['y'] = (max_level)/2 + self.max_order + (self.max_order and 1) - + self.graph_order() - - else: - self.result[self.start]['y'] = 0 + + else: + self.result[self.start]['y'] = 0 self.tree_order(self.start, 0) min_order = math.fabs(min(map(lambda x: x['y'], self.result.values()))) - - index = self.start_nodes.index(self.start) + + index = self.start_nodes.index(self.start) same = False - + roots = [] - if index>0: + if index>0: for start in self.start_nodes[:index]: - same = True + same = True for edge in self.tree_list[start][1:]: if self.tree_list[self.start].__contains__(edge): continue @@ -522,46 +522,46 @@ class graph(object): break if same: roots.append(start) - - if roots: - min_order += self.max_order + + if roots: + min_order += self.max_order else: min_order += self.max_order + 1 - + for level in self.levels: for node in self.levels[level]: self.result[node]['y'] += min_order - + if roots: roots.append(self.start) one_level_el = self.tree_list[self.start][0][1] base = self.result[one_level_el]['y']# * 2 / (index + 2) - - + + no = len(roots) first_half = roots[:no/2] - + if no%2==0: last_half = roots[no/2:] else: last_half = roots[no/2+1:] - + factor = -math.floor(no/2) for start in first_half: - self.result[start]['y'] = base + factor + self.result[start]['y'] = base + factor factor += 1 - + if no%2: self.result[roots[no/2]]['y'] = base + factor factor +=1 - + for start in last_half: - self.result[start]['y'] = base + factor + self.result[start]['y'] = base + factor factor += 1 - + self.max_order = max(map(lambda x: x['y'], self.result.values())) - - def find_starts(self): + + def find_starts(self): """Finds other start nodes of the graph in the case when graph is disconneted """ rem_nodes = [] @@ -577,7 +577,7 @@ class graph(object): count = 0 new_start = rem_nodes[0] largest_tree = [] - + for node in rem_nodes: self.partial_order = {} tree = self.make_acyclic(None, node, 0, []) @@ -589,120 +589,120 @@ class graph(object): if not largest_tree: new_start = rem_nodes[0] rem_nodes.remove(new_start) - + self.start_nodes.append(new_start) - - + + for edge in largest_tree: if rem_nodes.__contains__(edge[0]): rem_nodes.remove(edge[0]) if rem_nodes.__contains__(edge[1]): rem_nodes.remove(edge[1]) - + if not rem_nodes: break - + def rank(self): """Finds the optimized rank of the nodes using Network-simplex algorithm - + @param start: starting node of the component """ - self.levels = {} + self.levels = {} self.critical_edges = [] self.partial_order = {} self.links = [] self.Is_Cyclic = False - + self.tree_list[self.start] = self.make_acyclic(None, self.start, 0, []) - self.Is_Cyclic = self.rev_edges(self.tree_list[self.start]) + self.Is_Cyclic = self.rev_edges(self.tree_list[self.start]) self.process_ranking(self.start) self.init_rank() - + #make cut values of all tree edges to 0 to optimize feasible tree - e = self.leave_edge() - + e = self.leave_edge() + while e : f = self.enter_edge(e) if e==f: self.critical_edges.append(e) else: - self.exchange(e,f) + self.exchange(e,f) e = self.leave_edge() - + #finalize rank using optimum feasible tree # self.optimal_edges = {} # for edge in self.tree_edges: # source = self.optimal_edges.setdefault(edge[0], []) # source.append(edge[1]) - + # self.finalize_rank(self.start, 0) - + #normalization - self.normalize() + self.normalize() for edge in self.edge_wt: self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x'] - + def order_in_rank(self): """Finds optimized order of the nodes within their ranks using median heuristic - - @param start: starting node of the component + + @param start: starting node of the component """ - + self.make_chain() self.preprocess_order() self.order = {} max_rank = max(map(lambda x: x, self.levels.keys())) - + for i in range(max_rank+1): self.order[i] = 0 - + self.init_order(self.start, self.result[self.start]['x']) - + for level in self.levels: self.levels[level].sort(lambda x, y: cmp(self.result[x]['y'], self.result[y]['y'])) - - self.order_heuristic() + + self.order_heuristic() self.process_order() - + def process(self, starting_node): """Process the graph to find ranks and order of the nodes - - @param starting_node: node from where to start the graph search + + @param starting_node: node from where to start the graph search """ - + self.start_nodes = starting_node or [] - self.partial_order = {} - self.links = [] - self.tree_list = {} - + self.partial_order = {} + self.links = [] + self.tree_list = {} + if self.nodes: if self.start_nodes: #add dummy edges to the nodes which does not have any incoming edges tree = self.make_acyclic(None, self.start_nodes[0], 0, []) - + for node in self.no_ancester: for sec_node in self.transitions.get(node, []): if sec_node in self.partial_order.keys(): self.transitions[self.start_nodes[0]].append(node) break - + self.partial_order = {} tree = self.make_acyclic(None, self.start_nodes[0], 0, []) - - - # if graph is disconnected or no start-node is given - #than to find starting_node for each component of the node + + + # if graph is disconnected or no start-node is given + #than to find starting_node for each component of the node if len(self.nodes) > len(self.partial_order): - self.find_starts() - - self.max_order = 0 + self.find_starts() + + self.max_order = 0 #for each component of the graph find ranks and order of the nodes - for s in self.start_nodes: + for s in self.start_nodes: self.start = s self.rank() # First step:Netwoek simplex algorithm - self.order_in_rank() #Second step: ordering nodes within ranks - + self.order_in_rank() #Second step: ordering nodes within ranks + def __str__(self): result = '' @@ -716,21 +716,21 @@ class graph(object): def scale(self, maxx, maxy, nwidth=0, nheight=0, margin=20): """Computes actual co-ordiantes of the nodes """ - + #for flat edges ie. source an destination nodes are on the same rank for src in self.transitions: for des in self.transitions[src]: - if (self.result[des]['x'] - self.result[src]['x'] == 0): + if (self.result[des]['x'] - self.result[src]['x'] == 0): self.result[src]['x'] += 0.08 self.result[des]['x'] -= 0.08 - + factorX = maxx + nheight - factorY = maxy + nwidth - + factorY = maxy + nwidth + for node in self.result: self.result[node]['y'] = (self.result[node]['y']) * factorX + margin self.result[node]['x'] = (self.result[node]['x']) * factorY + margin - + def result_get(self): return self.result @@ -763,14 +763,14 @@ if __name__=='__main__': import ImageDraw img = Image.new("RGB", (800, 600), "#ffffff") draw = ImageDraw.Draw(img) - + result = g.result_get() node_res = {} for node in nodes: node_res[node] = result[node] - + for name,node in node_res.items(): - + draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128)) draw.text( (int(node['y']), int(node['x'])), name, (128,128,128))