source: sasview/src/sas/perspectives/calculator/density_panel.py @ 3db44fb

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 3db44fb was 3db44fb, checked in by butler, 9 years ago

1) Fixed second issue that was caused by the recent cleanup of
DocumentationWindow?: loading html at anchor point for context help
(broken). In order to preserve the cleanup, the class was refactored to
take another parameter: html instruction string. This keeps it general
to accept not only the # anchor but alos queries of all sorts in the
future. Thus all modules using this class were also edited to match.

2) in process of editing the dozen or so instances did a bit of code
cleanup and pylint cleanup.

  • Property mode set to 100644
File size: 16.4 KB
Line 
1"""
2This module provide GUI for the mass density calculator
3
4"""
5import wx
6import sys
7from sas.guiframe.panel_base import PanelBase
8from wx.lib.scrolledpanel import ScrolledPanel
9from sas.guiframe.utils import check_float
10from sas.guiframe.events import StatusEvent
11from periodictable import formula as Formula
12from sas.perspectives.calculator import calculator_widgets as widget
13from sas.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/perspectives/calculator/density_calculator_help.html"
387        _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, "",
388                                          "Density/Volume Calculator Help")
389
390    def on_close(self, event):
391        """
392        close the window containing this panel
393        """
394        self.parent.Close()
395
396    def clear_outputs(self):
397        """
398        Clear the outputs textctrl
399        """
400        self.molar_mass_ctl.SetValue("")
401        self.output_ctl.SetValue("")
402
403    def _format_number(self, value=None):
404        """
405        Return a float in a standardized, human-readable formatted string
406        """
407        try:
408            value = float(value)
409        except:
410            output = ''
411            return output
412
413        output = "%-12.5f" % value
414        return output.lstrip().rstrip()
415
416class DensityWindow(widget.CHILD_FRAME):
417    """
418    """
419    def __init__(self, parent=None, title="Density/Volume Calculator",
420                  base=None, manager=None,
421                  size=(PANEL_SIZE * 1.05, PANEL_SIZE / 1.55), *args, **kwds):
422        """
423        """
424        kwds['title'] = title
425        kwds['size'] = size
426        widget.CHILD_FRAME.__init__(self, parent, *args, **kwds)
427        """
428        """
429        self.manager = manager
430        self.panel = DensityPanel(self, base=base)
431        self.Bind(wx.EVT_CLOSE, self.on_close)
432        self.SetPosition((wx.LEFT, PANEL_TOP))
433        self.Show(True)
434
435    def on_close(self, event):
436        """
437        On close event
438        """
439        if self.manager != None:
440            self.manager.cal_md_frame = None
441        self.Destroy()
442
443
444class ViewApp(wx.App):
445    """
446    """
447    def OnInit(self):
448        """
449        """
450        widget.CHILD_FRAME = wx.Frame
451        frame = DensityWindow(None, title="Density/Volume Calculator")
452        frame.Show(True)
453        self.SetTopWindow(frame)
454        return True
455
456
457if __name__ == "__main__":
458    app = ViewApp(0)
459    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.