source: sasview/src/sas/sasgui/perspectives/calculator/density_panel.py @ d619341

Last change on this file since d619341 was 02b4d10, checked in by butler, 9 years ago

Links new help to mass density calculation tool (density_panel.py)

  • Property mode set to 100644
File size: 16.5 KB
Line 
1"""
2This module provide GUI for the mass density calculator
3
4"""
5import wx
6import sys
7from sas.sasgui.guiframe.panel_base import PanelBase
8from wx.lib.scrolledpanel import ScrolledPanel
9from sas.sasgui.guiframe.utils import check_float
10from sas.sasgui.guiframe.events import StatusEvent
11from periodictable import formula as Formula
12from sas.sasgui.perspectives.calculator import calculator_widgets as widget
13from sas.sasgui.guiframe.documentation_window import DocumentationWindow
14
15AVOGADRO = 6.02214129e23
16_INPUTS = ['Mass Density', 'Molar Volume']
17_UNITS = ['g/cm^(3)     ', 'cm^(3)/mol ']
18#Density panel size
19if sys.platform.count("win32") > 0:
20    PANEL_TOP = 0
21    _STATICBOX_WIDTH = 410
22    _BOX_WIDTH = 200
23    PANEL_SIZE = 440
24    FONT_VARIANT = 0
25else:
26    PANEL_TOP = 60
27    _STATICBOX_WIDTH = 430
28    _BOX_WIDTH = 200
29    PANEL_SIZE = 460
30    FONT_VARIANT = 1
31
32class DensityPanel(ScrolledPanel, PanelBase):
33    """
34    Provides the mass density calculator GUI.
35    """
36    ## Internal nickname for the window, used by the AUI manager
37    window_name = "Mass Density Calculator"
38    ## Name to appear on the window title bar
39    window_caption = "Mass Density Calculator"
40    ## Flag to tell the AUI manager to put this panel in the center pane
41    CENTER_PANE = True
42
43    def __init__(self, parent, base=None, *args, **kwds):
44        """
45        """
46        ScrolledPanel.__init__(self, parent, *args, **kwds)
47        PanelBase.__init__(self)
48        self.SetupScrolling()
49        #Font size
50        self.SetWindowVariant(variant=FONT_VARIANT)
51        # Object that receive status event
52        self.base = base
53        self.parent = parent
54        # chemeical formula, string
55        self.compound = ''
56        # value of the density/volume, float
57        self.input = None
58        # text controls
59        self.compound_ctl = None
60        self.input_ctl = None
61        self.molar_mass_ctl = None
62        self.output_ctl = None
63        self.ctr_color = self.GetBackgroundColour()
64        # button
65        self.button_calculate = None
66        # list
67        self._input_list = _INPUTS
68        self._input = self._input_list[1]
69        self._output = self._input_list[0]
70        self._unit_list = _UNITS
71        #Draw the panel
72        self._do_layout()
73        self.SetAutoLayout(True)
74        self.Layout()
75
76    def _do_layout(self):
77        """
78        Draw window content
79        """
80        # units
81        unit_density = self._unit_list[0]
82        unit_volume = self._unit_list[1]
83
84        # sizers
85        sizer_input = wx.GridBagSizer(5, 5)
86        sizer_output = wx.GridBagSizer(5, 5)
87        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
88        self.sizer1 = wx.BoxSizer(wx.HORIZONTAL)
89        self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
90        sizer3 = wx.BoxSizer(wx.HORIZONTAL)
91        vbox = wx.BoxSizer(wx.VERTICAL)
92
93        # inputs
94        inputbox = wx.StaticBox(self, -1, "Inputs")
95        boxsizer1 = wx.StaticBoxSizer(inputbox, wx.VERTICAL)
96        boxsizer1.SetMinSize((_STATICBOX_WIDTH, -1))
97        compound_txt = wx.StaticText(self, -1, 'Molecular Formula ')
98        self.compound_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
99        self.compound_eg1 = wx.StaticText(self, -1, '     e.g., H2O')
100        self.compound_eg2 = wx.StaticText(self, -1, 'e.g., D2O')
101        self.input_cb = wx.ComboBox(self, -1, style=wx.CB_READONLY)
102        wx.EVT_COMBOBOX(self.input_cb, -1, self.on_select_input)
103        hint_input_name_txt = 'Mass or volume.'
104        self.input_cb.SetToolTipString(hint_input_name_txt)
105        unit_density1 = "     " + unit_density
106        self.input_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
107        self.unit_input_density = wx.StaticText(self, -1, unit_density1)
108        self.unit_input_volume = wx.StaticText(self, -1, unit_volume)
109        iy = 0
110        ix = 0
111        sizer_input.Add(compound_txt, (iy, ix), (1, 1),
112                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
113        ix += 1
114        sizer_input.Add(self.compound_ctl, (iy, ix), (1, 1),
115                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
116        ix += 1
117        sizer_input.Add(self.compound_eg1, (iy, ix), (1, 1),
118                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
119
120        ix += 1
121        sizer_input.Add(self.compound_eg2, (iy, ix), (1, 1),
122                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
123        self.compound_eg1.Show(False)
124        iy += 1
125        ix = 0
126        sizer_input.Add(self.input_cb, (iy, ix), (1, 1),
127                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
128        ix += 1
129        sizer_input.Add(self.input_ctl, (iy, ix), (1, 1),
130                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
131        ix += 1
132        sizer_input.Add(self.unit_input_density, (iy, ix), (1, 1),
133                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
134        ix += 1
135        self.unit_input_density.Show(False)
136        sizer_input.Add(self.unit_input_volume, (iy, ix), (1, 1),
137                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
138        boxsizer1.Add(sizer_input)
139        self.sizer1.Add(boxsizer1, 0, wx.EXPAND | wx.ALL, 10)
140
141        # outputs
142        outputbox = wx.StaticBox(self, -1, "Outputs")
143        boxsizer2 = wx.StaticBoxSizer(outputbox, wx.VERTICAL)
144        boxsizer2.SetMinSize((_STATICBOX_WIDTH, -1))
145
146        molar_mass_txt = wx.StaticText(self, -1, 'Molar Mass ')
147        self.molar_mass_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
148        self.molar_mass_ctl.SetEditable(False)
149        self.molar_mass_ctl.SetBackgroundColour(self.ctr_color)
150        self.molar_mass_unit1 = wx.StaticText(self, -1, '     g/mol')
151        self.molar_mass_unit2 = wx.StaticText(self, -1, 'g/mol')
152
153        self.output_cb = wx.ComboBox(self, -1, style=wx.CB_READONLY)
154        wx.EVT_COMBOBOX(self.output_cb, -1, self.on_select_output)
155        hint_output_name_txt = 'Mass or volume.'
156        self.output_cb.SetToolTipString(hint_output_name_txt)
157        list = []
158        for item in self._input_list:
159            name = str(item)
160            list.append(name)
161        list.sort()
162        for idx in range(len(list)):
163            self.input_cb.Append(list[idx], idx)
164            self.output_cb.Append(list[idx], idx)
165        self.input_cb.SetStringSelection("Molar Volume")
166        self.output_cb.SetStringSelection("Mass Density")
167        unit_volume = "     " + unit_volume
168        self.output_ctl = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, -1))
169        self.output_ctl.SetEditable(False)
170        self.output_ctl.SetBackgroundColour(self.ctr_color)
171        self.unit_output_density = wx.StaticText(self, -1, unit_density)
172        self.unit_output_volume = wx.StaticText(self, -1, unit_volume)
173        iy = 0
174        ix = 0
175        sizer_output.Add(molar_mass_txt, (iy, ix), (1, 1),
176                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
177        ix += 1
178        sizer_output.Add(self.molar_mass_ctl, (iy, ix), (1, 1),
179                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
180        ix += 1
181        sizer_output.Add(self.molar_mass_unit1, (iy, ix), (1, 1),
182                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
183        ix += 1
184        sizer_output.Add(self.molar_mass_unit2, (iy, ix), (1, 1),
185                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
186        self.molar_mass_unit1.Show(False)
187        iy += 1
188        ix = 0
189        sizer_output.Add(self.output_cb, (iy, ix), (1, 1),
190                             wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
191        ix += 1
192        sizer_output.Add(self.output_ctl, (iy, ix), (1, 1),
193                            wx.EXPAND | wx.ADJUST_MINSIZE, 0)
194        ix += 1
195        sizer_output.Add(self.unit_output_volume,
196                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
197        ix += 1
198        sizer_output.Add(self.unit_output_density,
199                         (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
200
201        self.unit_output_volume.Show(False)
202        boxsizer2.Add(sizer_output)
203        self.sizer2.Add(boxsizer2, 0, wx.EXPAND | wx.ALL, 10)
204
205        # buttons
206        id = wx.NewId()
207        self.button_calculate = wx.Button(self, id, "Calculate")
208        self.button_calculate.SetToolTipString("Calculate.")
209        self.Bind(wx.EVT_BUTTON, self.calculate, id=id)
210
211        id = wx.NewId()
212        self.button_help = wx.Button(self, id, "HELP")
213        self.button_help.SetToolTipString("Help for density calculator.")
214        self.Bind(wx.EVT_BUTTON, self.on_help, id=id)
215
216        self.button_close = wx.Button(self, wx.ID_CANCEL, 'Close')
217        self.button_close.Bind(wx.EVT_BUTTON, self.on_close)
218        self.button_close.SetToolTipString("Close this window.")
219
220        sizer_button.Add((100, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
221        sizer_button.Add(self.button_calculate, 0,
222                                        wx.RIGHT | wx.ADJUST_MINSIZE, 20)
223        sizer_button.Add(self.button_help, 0,
224                                        wx.RIGHT | wx.ADJUST_MINSIZE, 20)
225        sizer_button.Add(self.button_close, 0,
226                                        wx.RIGHT | wx.ADJUST_MINSIZE, 20)
227        sizer3.Add(sizer_button)
228
229        # layout
230        vbox.Add(self.sizer1)
231        vbox.Add(self.sizer2)
232        vbox.Add(sizer3)
233        vbox.Fit(self)
234        self.SetSizer(vbox)
235
236    def on_select_input(self, event):
237        """
238        On selection of input combobox,
239        update units and output combobox
240        """
241        if event == None:
242            return
243        event.Skip()
244
245        combo = event.GetEventObject()
246        self._input = combo.GetValue()
247        for name in self._input_list:
248            if self._input != name:
249                self._output = name
250                break
251
252        self.set_values()
253
254    def on_select_output(self, event):
255        """
256        On selection of output combobox,
257        update units and input combobox
258        """
259        if event == None:
260            return
261        event.Skip()
262
263        combo = event.GetEventObject()
264        self._output = combo.GetValue()
265        for name in self._input_list:
266            if self._output != name:
267                self._input = name
268                break
269
270        self.set_values()
271
272    def set_values(self):
273        """
274        Sets units and combobox values
275        """
276        input, output = self.get_input()
277        if input is None:
278            return
279        # input
280        self.input_cb.SetValue(str(input))
281        # output
282        self.output_cb.SetValue(str(output))
283        # unit
284        if self._input_list.index(input) == 0:
285            self.molar_mass_unit1.Show(True)
286            self.molar_mass_unit2.Show(False)
287            self.compound_eg1.Show(True)
288            self.compound_eg2.Show(False)
289            self.unit_input_density.Show(True)
290            self.unit_output_volume.Show(True)
291            self.unit_input_volume.Show(False)
292            self.unit_output_density.Show(False)
293        else:
294            self.molar_mass_unit1.Show(False)
295            self.molar_mass_unit2.Show(True)
296            self.compound_eg1.Show(False)
297            self.compound_eg2.Show(True)
298            self.unit_input_volume.Show(True)
299            self.unit_output_density.Show(True)
300            self.unit_input_density.Show(False)
301            self.unit_output_volume.Show(False)
302        # layout   
303        self.clear_outputs()
304        self.sizer1.Layout()
305        self.sizer2.Layout()
306
307    def get_input(self):
308        """
309        Return the current input and output combobox values
310        """
311        return self._input, self._output
312
313    def check_inputs(self):
314        """
315        Check validity user inputs
316        """
317        flag = True
318        msg = ""
319        if check_float(self.input_ctl):
320            self.input = float(self.input_ctl.GetValue())
321        else:
322            flag = False
323            input_type = str(self.input_cb.GetValue())
324            msg += "Error for %s value :expect float" % input_type
325
326        self.compound = self.compound_ctl.GetValue().lstrip().rstrip()
327        if self.compound != "":
328            try :
329                Formula(self.compound)
330                self.compound_ctl.SetBackgroundColour(wx.WHITE)
331                self.compound_ctl.Refresh()
332            except:
333                self.compound_ctl.SetBackgroundColour("pink")
334                self.compound_ctl.Refresh()
335                flag = False
336                msg += "Enter correct formula"
337        else:
338            self.compound_ctl.SetBackgroundColour("pink")
339            self.compound_ctl.Refresh()
340            flag = False
341            msg += "Enter Formula"
342        return flag, msg
343
344
345    def calculate(self, event):
346        """
347        Calculate the mass Density/molar Volume of the molecules
348        """
349        self.clear_outputs()
350        try:
351            #Check validity user inputs
352            flag, msg = self.check_inputs()
353            if self.base is not None and msg.lstrip().rstrip() != "":
354                msg = "Density/Volume Calculator: %s" % str(msg)
355                wx.PostEvent(self.base, StatusEvent(status=msg))
356            if not flag:
357               return
358            #get ready to compute
359            mol_formula = Formula(self.compound)
360            molar_mass = float(mol_formula.molecular_mass) * AVOGADRO
361            output = self._format_number(molar_mass / self.input)
362            self.molar_mass_ctl.SetValue(str(self._format_number(molar_mass)))
363            self.output_ctl.SetValue(str(output))
364        except:
365            if self.base is not None:
366                msg = "Density/Volume Calculator: %s" % (sys.exc_value)
367                wx.PostEvent(self.base, StatusEvent(status=msg))
368        if event is not None:
369            event.Skip()
370
371    def on_help(self, event):
372        """
373        Bring up the density/volume calculator Documentation whenever
374        the HELP button is clicked.
375
376        Calls DocumentationWindow with the path of the location within the
377        documentation tree (after /doc/ ....".  Note that when using old
378        versions of Wx (before 2.9) and thus not the release version of
379        installers, the help comes up at the top level of the file as
380        webbrowser does not pass anything past the # to the browser when it is
381        running "file:///...."
382
383    :param evt: Triggers on clicking the help button
384    """
385
386        _TreeLocation = "user/sasgui/perspectives/calculator/"
387        _TreeLocation += "density_calculator_help.html"
388        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
389                                          "Density/Volume Calculator Help")
390
391    def on_close(self, event):
392        """
393        close the window containing this panel
394        """
395        self.parent.Close()
396
397    def clear_outputs(self):
398        """
399        Clear the outputs textctrl
400        """
401        self.molar_mass_ctl.SetValue("")
402        self.output_ctl.SetValue("")
403
404    def _format_number(self, value=None):
405        """
406        Return a float in a standardized, human-readable formatted string
407        """
408        try:
409            value = float(value)
410        except:
411            output = ''
412            return output
413
414        output = "%-12.5f" % value
415        return output.lstrip().rstrip()
416
417class DensityWindow(widget.CHILD_FRAME):
418    """
419    """
420    def __init__(self, parent=None, title="Density/Volume Calculator",
421                  base=None, manager=None,
422                  size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.55), *args, **kwds):
423        """
424        """
425        kwds['title'] = title
426        kwds['size'] = size
427        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
428        """
429        """
430        self.manager = manager
431        self.panel = DensityPanel(self, base=base)
432        self.Bind(wx.EVT_CLOSE, self.on_close)
433        self.SetPosition((wx.LEFT, PANEL_TOP))
434        self.Show(True)
435
436    def on_close(self, event):
437        """
438        On close event
439        """
440        if self.manager != None:
441            self.manager.cal_md_frame = None
442        self.Destroy()
443
444
445class ViewApp(wx.App):
446    """
447    """
448    def OnInit(self):
449        """
450        """
451        widget.CHILD_FRAME = wx.Frame
452        frame = DensityWindow(None, title="Density/Volume Calculator")
453        frame.Show(True)
454        self.SetTopWindow(frame)
455        return True
456
457
458if __name__ == "__main__":
459    app = ViewApp(0)
460    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.