1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
2# Copyright (C) 2016-2019 German Aerospace Center (DLR) and others.
3# SUMOPy module
4# Copyright (C) 2012-2017 University of Bologna - DICAM
5# This program and the accompanying materials
6# are made available under the terms of the Eclipse Public License v2.0
7# which accompanies this distribution, and is available at
8# http://www.eclipse.org/legal/epl-v20.html
9# SPDX-License-Identifier: EPL-2.0
10
11# @file    origin_to_destination_wxgui.py
12# @author  Joerg Schweizer
13# @date
14# @version $Id$
15
16import wx
17
18
19import agilepy.lib_base.classman as cm
20import agilepy.lib_base.arrayman as am
21from agilepy.lib_base.misc import get_inversemap
22from agilepy.lib_wx.ogleditor import *
23from agilepy.lib_wx.objpanel import ObjPanel
24from coremodules.network.network import SumoIdsConf, MODES
25
26
27class OdCommonMixin:
28    def add_odoptions_common(self, modes=None, activitytypes=None):
29        # print 'add_odoptions_common',modes
30        self.add(am.AttrConf('t_start', 0,
31                             groupnames=['options'],
32                             perm='rw',
33                             name='Start time',
34                             unit='s',
35                             info='Start time of interval',
36                             ))
37
38        self.add(am.AttrConf('t_end', 3600,
39                             groupnames=['options'],
40                             perm='rw',
41                             name='End time',
42                             unit='s',
43                             info='End time of interval',
44                             ))
45
46        # here we ged classes not vehicle type
47        # specific vehicle type within a class will be generated later
48        if modes is None:
49            modechoices = {'': -1}
50        else:
51            modechoices = modes.names.get_indexmap()
52        # print '  modechoices',modechoices
53        self.add(am.AttrConf('id_mode',   -1,
54                             groupnames=['options'],
55                             choices=modechoices,
56                             name='Mode',
57                             info='Transport mode.',
58                             ))
59
60        self.add(cm.AttrConf('scale', 1.0,
61                             groupnames=['options'],
62                             perm='rw',
63                             name='Scale',
64                             info='Scale demand by this factor before adding. Value od 1.0 means no scaling.'
65                             ))
66
67        if activitytypes is None:
68            activitytypechoices = {'': -1}
69            id_act_orig = -1
70            id_act_dest = -1
71        else:
72            activitytypechoices = activitytypes.names.get_indexmap()
73            id_act_orig = activitytypes.names.get_id_from_index('home')
74            id_act_dest = activitytypes.names.get_id_from_index('work')
75
76        self.add(cm.AttrConf('id_activitytype_orig', id_act_orig,
77                             groupnames=['options'],
78                             choices=activitytypechoices,
79                             perm='rw',
80                             name='Activity at orig.',
81                             info='Activity type at origin.',
82                             ))
83
84        self.add(cm.AttrConf('id_activitytype_dest', id_act_dest,
85                             groupnames=['options'],
86                             choices=activitytypechoices,
87                             perm='rw',
88                             name='Activity at dest.',
89                             info='Activity type at destination.',
90                             ))
91
92
93class OdFlowsWxGuiMixin:
94    """Contains OdFlow spacific functions that communicate between the widgets of the main wx gui
95    and the functions of the plugin.
96    """
97
98    def refresh_odflow(self, is_refresh):
99        if is_refresh:
100            neteditor = self.get_neteditor()
101            #neteditor.add_tool(AddODflowTool(self, odintervals))
102            neteditor.add_toolclass(AddODflowTool)
103
104    def add_menu_odflows(self, menubar):
105        menubar.append_menu('demand/Zone-to-zone demand',
106                            bitmap=self.get_icon("fig_od_24px.png"),
107                            )
108        menubar.append_item('demand/Zone-to-zone demand/add zone-to-zone flows...',
109                            self.on_add_odtrips,
110                            info='Add or import trips between origin and destination zones, with a certain mode during a certain time interval.',
111                            bitmap=self.get_agileicon("Document_Import_24px.png"),
112                            )
113
114        menubar.append_item('demand/Zone-to-zone demand/generate trips from flows',
115                            self.on_generate_odtrips,
116                            # info=self.on_generate_odtrips.__doc__.strip(),
117                            #bitmap = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE_AS,wx.ART_MENU),
118                            )
119
120        menubar.append_item('demand/Zone-to-zone demand/generate routes from flows, if connected',
121                            self.on_generate_odroutes,
122                            # info=self.on_generate_odtrips.__doc__.strip(),
123                            #bitmap = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE_AS,wx.ART_MENU),
124                            )
125
126        menubar.append_item('demand/Zone-to-zone demand/clear zone-to-zone flows',
127                            self.on_clear_odtrips,
128                            info='Clear all zone to zone trips.',
129                            bitmap=wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_MENU),
130                            )
131
132    def on_add_odtrips(self, event=None):
133        """
134        Opend odm wizzard
135        """
136        dlg = AddOdDialog(self._mainframe, self._demand.odintervals)
137        dlg.Show()
138        dlg.MakeModal(True)
139        self._mainframe.browse_obj(self._demand.odintervals)
140        # self.scenariopanel.refresh_panel(self.scenario)
141
142    def on_generate_odtrips(self, event=None):
143        """
144        Generates trips from origin to destination zone from current OD matrices.
145        """
146        self._demand.odintervals.generate_trips(n_trials_connect=-1,
147                                                is_make_route=False,)
148        self._mainframe.browse_obj(self._demand.trips)
149
150    def on_generate_odroutes(self, event=None):
151        """
152        Generates routes from origin to destination zone from current OD matrices, if connected.
153        """
154        self._demand.odintervals.generate_trips(n_trials_connect=5,
155                                                is_make_route=True,)
156        self._mainframe.browse_obj(self._demand.trips)
157
158    def on_clear_odtrips(self, event=None):
159        """
160        Generates trips from origin to destination zone from current OD matrices.
161        """
162        self._demand.odintervals.clear_od_trips()
163        self._mainframe.browse_obj(self._demand.odintervals)
164
165
166class AddODflowTool(OdCommonMixin, SelectTool):
167    """
168    OD flow toolfor OGL canvas.
169    """
170
171    def __init__(self, parent, mainframe=None):
172        """
173        To be overridden by specific tool.
174        """
175        self.init_common('odflow', parent, 'Add OD flow',
176                         info="""Tool to add a flow between a zone of origin and a zone of destination:
177                            <LEFT MOUSE> on the respective borderlines of zone of origin, and zone of destination.
178                            Enter time interval and transport mode.
179                            Enter number of trips and press "add flows".
180                            <LEFT MOUSE> on border of a new zone of destination to add flows to other zones of destination.
181                            Or press button "Reset zones" to start with a new assignment.
182                            """,
183                         is_textbutton=False,
184                         )
185
186        self._init_select(is_show_selected=False)
187
188        #self.drawobj_zone_orig = None
189        #self.drawobj_zone_dest = None
190        self.add_odoptions_common()
191        # make options
192        self.add(cm.AttrConf('id_orig', -1,
193                             groupnames=['options'],
194                             choices={'': -1},
195                             perm='r',
196                             name='Orig zone',
197                             info='Name of traffic assignment zone of origin of trip. Click on zone border to specify.',
198                             ))
199
200        self.add(cm.AttrConf('id_dest', -1,
201                             dtype='object',
202                             groupnames=['options'],
203                             choices={'': -1},
204                             perm='r',
205                             name='Dest zone',
206                             info='Name of traffic assignment zone of destination of trip.Click on zone border to specify.',
207                             ))
208
209        self.add(cm.AttrConf('tripnumber', 0,
210                             groupnames=['options'],
211                             perm='rw',
212                             name='Trips',
213                             info='Number of trips from zone of origin to zone of destination.',
214                             ))
215
216    def set_button_info(self, bsize=(32, 32)):
217        # print 'set_button_info select tool'  self.get_icon("icon_sumo_24px.png")
218        iconpath = os.path.join(os.path.dirname(__file__), 'images')
219        self._bitmap = wx.Bitmap(os.path.join(iconpath, 'fig_od_32px.png'), wx.BITMAP_TYPE_PNG)
220        self._bitmap_sel = self._bitmap
221
222    def set_cursor(self):
223        # http://www.wxpython.org/docs/api/wx.Cursor-class.html
224        if self._canvas is not None:
225            self._canvas.SetCursor(wx.StockCursor(wx.CURSOR_RIGHT_ARROW))
226
227    def activate(self, canvas=None):
228        """
229        This call by metacanvas??TooldsPallet signals that the tool has been
230        activated and can now interact with metacanvas.
231        """
232        # print 'activate'
233        SelectTool.activate(self, canvas)
234
235        zones = self.get_zones()
236        zonenames = zones.ids_sumo.get_indexmap()
237        zonenames[''] = -1
238        self.id_orig.choices = zonenames
239        self.id_dest.choices = zonenames
240
241        modechoices = self.get_scenario().demand.vtypes.get_modechoices()
242        self.id_mode.set_value(modechoices['passenger'])
243        self.id_mode.choices = modechoices
244
245        activitytypes = self.get_scenario().demand.activitytypes
246        self.id_activitytype_orig.choices = activitytypes.names.get_indexmap()
247        self.id_activitytype_dest.choices = activitytypes.names.get_indexmap()
248        self.id_activitytype_orig.set_value(activitytypes.names.get_id_from_index('home'))
249        self.id_activitytype_dest.set_value(activitytypes.names.get_id_from_index('work'))
250
251        self.highlight_zones()
252        canvas.draw()
253
254    def deactivate(self):
255        """
256        This call by metacanvas signals that the tool has been
257        deactivated and can now interact with metacanvas.
258        """
259
260        self._is_active = False
261        self.unhighlight_zones()
262        self._canvas.draw()
263        self.deactivate_select()
264
265    def on_execute_selection(self, event):
266        """
267        Definively execute operation on currently selected drawobjects.
268        """
269        # print 'AddODflowTool.on_execute_selection',self.get_netelement_current()
270        # self.set_objbrowser()
271        # self.highlight_current()
272        self.unhighlight_current()
273        self.unhighlight_zones()
274        netelement_current = self.get_netelement_current()
275        if netelement_current is not None:
276            (zones, id_zone) = netelement_current
277            if zones.get_ident() == 'zones':
278                # print '  check',self.name_orig.get_value(),'*',self.name_orig.get_value() is ''
279                if self.id_orig.get_value() < 0:
280                    # print '    set name_orig',zones.ids_sumo[id_zone]
281                    self.id_orig.set_value(id_zone)
282
283                else:
284                    # print '    set name_dest',zones.ids_sumo[id_zone]
285                    self.id_dest.set_value(id_zone)
286
287                self.unselect_all()  # includes unhighlight
288                self.highlight_zones()
289                self.parent.refresh_optionspanel(self)
290        return True
291
292    def highlight_zones(self):
293       # print 'highlight_zones',self.id_orig.value,self.id_dest.value
294        zones = self.get_zones()
295        drawing = self.get_drawing()
296        if self.id_orig.value >= 0:
297            drawing.highlight_element(zones, [self.id_orig.value], is_update=True)
298        if self.id_dest.value >= 0:
299            drawing.highlight_element(zones, [self.id_dest.value], is_update=True)
300
301    def unhighlight_zones(self):
302        zones = self.get_zones()
303        drawing = self.get_drawing()
304        if self.id_orig.value >= 0:
305            drawing.unhighlight_element(zones, self.id_orig.value, is_update=True)
306        if self.id_dest.value >= 0:
307            drawing.unhighlight_element(zones, self.id_dest.value, is_update=True)
308
309    def on_change_selection(self, event):
310        """
311        Called after selection has been changed with SHIFT-click
312        Do operation on currently selected drawobjects.
313        """
314        # self.set_objbrowser()
315        return False
316
317    def get_netelement_current(self):
318        mainframe = self.parent.get_mainframe()
319        if mainframe is not None:
320            drawobj, _id = self.get_current_selection()
321            if drawobj is not None:
322                obj = drawobj.get_netelement()
323                return obj, _id
324            else:
325                return None
326        else:
327            return None
328
329    def get_scenario(self):
330        # get net and scenario via netdrawing
331        return self.get_drawing().get_net().parent
332
333    def get_odintervals(self):
334        return self.get_scenario().demand.odintervals
335
336    def get_zones(self):
337        return self.get_scenario().landuse.zones
338
339    def on_add_new(self, event=None):
340        self._optionspanel.apply()
341        zonenames = self.get_zones().ids_sumo
342        # print 'on_add_new',
343        # print '  odintervals',self.get_odintervals(),dir(self.get_odintervals())
344        odtrips = self.get_odintervals().add_od_flow(self.t_start.value, self.t_end.value,
345                                                     self.id_mode.value,
346                                                     self.id_activitytype_orig.value,
347                                                     self.id_activitytype_dest.value,
348                                                     self.scale.value,
349                                                     zonenames[self.id_orig.value],
350                                                     zonenames[self.id_dest.value],
351                                                     self.tripnumber.value,
352                                                     )
353        self.unselect_all()
354        self.unhighlight_zones()
355        mainframe = self.parent.get_mainframe()
356        if mainframe is not None:
357            mainframe.browse_obj(odtrips)
358
359        self.id_dest.set_value(-1)  # set empty zone
360        self.highlight_zones()
361        self.parent.refresh_optionspanel(self)
362        self._canvas.draw()
363
364    def on_clear_zones(self, event=None):
365        self.unhighlight_zones()
366        self.id_orig.set_value(-1)  # set empty zone
367        self.id_dest.set_value(-1)  # set empty zone
368        self.unselect_all()
369
370        self.parent.refresh_optionspanel(self)
371        self._canvas.draw()
372
373    def get_optionspanel(self, parent, size=wx.DefaultSize):
374        """
375        Return tool option widgets on given parent
376        """
377        size = (200, -1)
378        buttons = [('Add flow', self.on_add_new, 'Add a new OD flow to demand.'),
379                   ('Rest Zones', self.on_clear_zones, 'Clear the fields with zones of origin and destination.'),
380                   #('Save flows', self.on_add, 'Save OD flows to current demand.'),
381                   #('Cancel', self.on_close, 'Close wizzard without adding flows.'),
382                   ]
383        defaultbuttontext = 'Add flow'
384        self._optionspanel = ObjPanel(parent, obj=self,
385                                      attrconfigs=None,
386                                      groupnames=['options'],
387                                      func_change_obj=None,
388                                      show_groupnames=False, show_title=True, is_modal=False,
389                                      mainframe=self.parent.get_mainframe(),
390                                      pos=wx.DefaultPosition, size=size, style=wx.MAXIMIZE_BOX | wx.RESIZE_BORDER,
391                                      immediate_apply=True, panelstyle='default',  # 'instrumental'
392                                      buttons=buttons, defaultbutton=defaultbuttontext,
393                                      standartbuttons=[],  # standartbuttons=['restore']
394                                      )
395
396        return self._optionspanel
397
398
399class AddOdWizzard(OdCommonMixin, am.ArrayObjman):
400    """Contains information and methods to add an od matrix for
401    a certain mode and for a certain time interval to the scenario.
402    """
403
404    def __init__(self, odintervals):
405        # print 'AddOdWizzard',odintervals#,odintervals.times_start
406        # print ' dir(odintervals)',dir(odintervals)
407        zones = odintervals.get_zones()
408
409        self._init_objman('odm_adder', parent=odintervals,
410                          name='ODM Wizzard',
411                          info='Wizzard to add origin zone to destination zone demand informations.',
412                          )
413
414        self.add_odoptions_common(odintervals.parent.get_scenario().net.modes, odintervals.parent.activitytypes)
415
416        self.add_col(am.ArrayConf('names_orig', '',
417                                  dtype='object',
418                                  groupnames=['state'],
419                                  choices=list(zones.ids_sumo.get_value()),
420                                  name='Orig zone',
421                                  info='Name of traffic assignment zone of origin of trip.',
422                                  ))
423
424        self.add_col(am.ArrayConf('names_dest', '',
425                                  dtype='object',
426                                  groupnames=['state'],
427                                  choices=list(zones.ids_sumo.get_value()),
428                                  name='Dest zone',
429                                  info='Name of traffic assignment zone of destination of trip.',
430                                  ))
431
432        self.add_col(am.ArrayConf('tripnumbers', 0,
433                                  groupnames=['state'],
434                                  perm='rw',
435                                  name='Trips',
436                                  info='Number of trips from zone of origin to zone of destination.',
437                                  xmltag='tripnumber',
438                                  ))
439
440        self.add(cm.FuncConf('func_make_row', 'on_add_row', None,
441                             groupnames=['rowfunctions', '_private'],
442                             name='New OD flow.',
443                             info='Add a new OD flow.',
444                             ))
445
446        self.add(cm.FuncConf('func_delete_row', 'on_del_row', None,
447                             groupnames=['rowfunctions', '_private'],
448                             name='Del OD flow',
449                             info='Delete OD flow.',
450                             ))
451
452        # self.attrs.print_attrs()
453
454    def on_del_row(self, id_row):
455        # print 'on_del_row', id_row
456        if id_row is not None:
457            self.del_row(id_row)
458
459    def on_add_row(self, id=None):
460        if len(self) > 0:
461            # copy previous
462            od_last = self.get_row(self.get_ids()[-1])
463            #id_orig = self.odtab.ids_orig.get(id_last)
464            #id_dest = self.odtab.ids_dest.get(id_last)
465            #id = self.suggest_id()
466            self.add_row(**od_last)
467        else:
468            # create empty
469            self.add_row()
470
471    def add_demand(self):
472        """
473        Add demand to scenario.
474        """
475        # print 'AddOdm.add_demand'
476        odintervals = self.parent
477        #demand = self._scenario.demand
478        # odm={} # create a temporary dict with (o,d) as key and trips as value
479        ids = self.get_ids()
480        odintervals.add_od_flows(self.t_start.value, self.t_end.value,
481                                 self.id_mode.value,
482                                 self.id_activitytype_orig.value, self.id_activitytype_dest.value,
483                                 self.scale.value,
484                                 self.names_orig[ids], self.names_dest[ids],
485                                 self.tripnumbers[ids]
486                                 )
487
488        #t_start, t_end, id_mode,
489        #                   id_activitytype_orig, id_activitytype_dest,
490        #                  scale, names_orig,names_dest,tripnumbers):
491
492    def import_csv(self, filepath, sep=",", n_firstline=1):
493        names_zone = self.parent.get_zones().ids_sumo
494        # make shure that index table is updated
495        names_zone.rebuild_indices()
496        f = open(filepath, 'r')
497        # print '  open',filepath
498        i_line = n_firstline
499        for line in f.readlines():
500            # print '    ',line,
501            cols = line.split(sep)
502            if len(cols) == 3:
503                name_orig, name_dest, tripnumbers_str = cols
504
505                name_orig = name_orig.strip()
506                name_dest = name_dest.strip()
507                if names_zone.has_index(name_orig) & names_zone.has_index(name_dest):
508                    id_new = self.suggest_id()
509                    self.add_row(id_new,
510                                 names_orig=name_orig,
511                                 names_dest=name_dest,
512                                 tripnumbers=int(tripnumbers_str)
513                                 )
514                else:
515                    print 'WARNING: unknown zonename in line %d of file %s' % (i_line, filepath)
516
517            else:
518                if len(cols) != 0:
519                    print 'WARNING: inconsistent o,d,trips info in line %d of file %s' % (i_line, filepath)
520            i_line += 1
521        # self.odtab.print_rows()
522        f.close()
523
524
525class AddOdDialog(wx.Frame):
526
527    """
528    A frame used for the ObjBrowser Demo
529
530    """
531
532    def __init__(self, parent, odintervals):
533        wx.Frame.__init__(self, parent, -1, title='Add OD flow Wizzard', pos=wx.DefaultPosition, size=wx.DefaultSize)
534        self.wizzard = AddOdWizzard(odintervals)
535        self.parent = parent
536        # Set up the MenuBar
537        MenuBar = wx.MenuBar()
538
539        file_menu = wx.Menu()
540        item = file_menu.Append(-1, "&Import CSV...",
541                                "Import OD data from a CSV text file with format <zonename orig>, <zonename dest>,<number of trips>")
542        self.Bind(wx.EVT_MENU, self.on_import_csv, item)
543        #item = file_menu.Append(-1, "&Import Exel...","Import OD data from an Exel file.")
544        #self.Bind(wx.EVT_MENU, self.on_import_exel, item)
545
546        item = file_menu.Append(-1, "&Save flows and close", "Add OD flows to scenario and close wizzard")
547        self.Bind(wx.EVT_MENU, self.on_add, item)
548
549        item = file_menu.Append(-1, "&Close", "Close wizzard withot saving")
550        self.Bind(wx.EVT_MENU, self.on_close, item)
551
552        MenuBar.Append(file_menu, "&File")
553
554        edit_menu = wx.Menu()
555        item = edit_menu.Append(-1, "&Add OD flow to table",
556                                "Add a new flow by defining zones of origin, destination and number of trips in table")
557        self.Bind(wx.EVT_MENU, self.on_add_new, item)
558        MenuBar.Append(edit_menu, "&Edit")
559
560        if odintervals.get_net().parent is not None:
561            self.dirpath = odintervals.get_net().parent.get_workdirpath()
562        else:
563            self.dirpath = os.getcwd()
564
565        #help_menu = wx.Menu()
566        # item = help_menu.Append(-1, "&About",
567        #                        "More information About this program")
568        #self.Bind(wx.EVT_MENU, self.on_menu, item)
569        #MenuBar.Append(help_menu, "&Help")
570
571        self.SetMenuBar(MenuBar)
572
573        self.CreateStatusBar()
574
575        self.browser = self.make_browser()
576
577        # Create a sizer to manage the Canvas and message window
578        MainSizer = wx.BoxSizer(wx.VERTICAL)
579        MainSizer.Add(self.browser, 4, wx.EXPAND)
580
581        self.SetSizer(MainSizer)
582        self.Bind(wx.EVT_CLOSE, self.on_close)
583
584        self.EventsAreBound = False
585
586        # getting all the colors for random objects
587        # wxlib.colourdb.updateColourDB()
588        #self.colors = wxlib.colourdb.getColourList()
589
590        return None
591
592    def make_browser(self):
593        # Create Browser widget here
594        buttons = [  # ('Add new OD flow', self.on_add_new, 'Add a new flow by defining zones of origin, destination and number of trips in table.'),
595            ('Save flows', self.on_add, 'Save OD flows to current demand.'),
596            ('Cancel', self.on_close, 'Close wizzard without adding flows.'),
597        ]
598        defaultbuttontext = 'Save flows'
599        # standartbuttons=['cancel','apply','ok']# apply does not show
600        standartbuttons = ['apply']
601
602        browser = ObjPanel(self, self.wizzard,
603                           attrconfigs=None,
604                           id=None, ids=None,
605                           groupnames=None,
606                           func_change_obj=None,
607                           show_groupnames=False, show_title=False,
608                           is_modal=True,
609                           mainframe=None,
610                           pos=wx.DefaultPosition,
611                           size=wx.DefaultSize,
612                           style=wx.MAXIMIZE_BOX | wx.RESIZE_BORDER,
613                           immediate_apply=False,  # True,
614                           panelstyle='default',
615                           buttons=buttons,
616                           standartbuttons=standartbuttons,
617                           defaultbutton=defaultbuttontext,
618                           )
619        return browser
620
621    def on_import_csv(self, event=None):
622        # print 'on_import_csv'
623        self.browser.apply()
624        wizzard = self.browser.obj
625
626        wildcards_all = "CSV files (*.csv)|*.csv|CSV files (*.txt)|*.txt|All files (*.*)|*.*"  # +"|"+otherwildcards
627        dlg = wx.FileDialog(self.parent, message="Import CSV file",
628                            defaultDir=self.dirpath,
629                            defaultFile="",
630                            wildcard=wildcards_all,
631                            style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR
632                            )
633
634        # Show the dialog and retrieve the user response. If it is the OK response,
635        # process the data.
636        if dlg.ShowModal() == wx.ID_OK:
637            # This returns a Python list of files that were selected.
638            paths = dlg.GetPaths()
639            # print 'You selected %d files:' % len(paths)
640            if len(paths) > 0:
641                filepath = paths[0]
642            else:
643                filepath = ''
644            dlg.Destroy()
645        else:
646            return
647        ###
648        wizzard.import_csv(filepath)
649        ##
650
651        self.refresh_browser()
652        #
653        self.Raise()
654        # self.SetFocus()
655        # self.MakeModal(False)
656        # self.MakeModal(True)
657        # self.browser.restore()
658
659    def on_add_new(self, event=None):
660        # print 'on_add,AddOdm',self.browser.obj
661        self.browser.apply()  # important to transfer widget values to obj!
662        wizzard = self.browser.obj
663        wizzard.on_add_row()
664        self.refresh_browser()
665
666    def on_add(self, event=None):
667        # print 'on_add,AddOdm',self.browser.obj
668        self.browser.apply()  # important to transfer widget values to obj!
669        wizzard = self.browser.obj
670        wizzard.add_demand()
671
672        self.on_close(event)
673
674    def on_close(self, event=None):
675        self.MakeModal(False)
676        self.Destroy()
677        pass
678
679    def on_add_close(self, event=None):
680        self.on_add()
681        self.on_close()
682
683    def refresh_browser(self):
684        """
685        Deletes previous conents
686        Builds panel for obj
687        Updates path window and history
688        """
689        # print 'Wizzard.refresh_panel with',obj.ident
690        # remove previous obj panel
691        sizer = self.GetSizer()
692        sizer.Remove(0)
693        self.browser.Destroy()
694        #del self.browser
695        self.browser = self.make_browser()
696
697        sizer.Add(self.browser, 1, wx.GROW)
698
699        self.Refresh()
700        # sizer.Fit(self)
701        sizer.Layout()
702        # add to history
703