1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# elements_gui.py 4 5# Copyright (c) 2005-2014, Christoph Gohlke 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions are met: 10# 11# * Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# * Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# * Neither the name of the copyright holders nor the names of any 17# contributors may be used to endorse or promote products derived 18# from this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30# POSSIBILITY OF SUCH DAMAGE. 31 32"""Periodic Table of Elements - A user interface for elements.py. 33 34:Author: `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`_ 35 36:Version: 2013.03.18 37 38Requirements 39------------ 40 41* `CPython 2.7 <http://www.python.org>`_ 42* `wxPython 2.8 <http://www.wxpython.org>`_ 43* `Elements.py 2013.03.18 <http://www.lfd.uci.edu/~gohlke/>`_ 44 45""" 46from __future__ import division, print_function 47 48import sys 49import math 50import io 51import webbrowser 52 53import wxversion 54#wxversion.ensureMinimal('2.8') 55import wx 56from wx.lib import fancytext, buttons, rcsizer 57 58from elements import ELEMENTS, SERIES 59 60from abipy.gui.awx.panels import ListCtrlFromTable 61try: 62 from pseudo_dojo.refdata.nist import database as nist 63except ImportError: 64 pass 65 66 67class MainApp(wx.App): 68 """Main application.""" 69 70 name = "Periodic Table of Elements" 71 version = "2012.04.05" 72 website = "http://www.lfd.uci.edu/~gohlke/" 73 copyright = ("Christoph Gohlke\n" 74 "Laboratory for Fluorescence Dynamics\n" 75 "University of California, Irvine") 76 icon = "elements" 77 78 def OnInit(self): 79 _ = wx.LogNull() 80 wx.InitAllImageHandlers() 81 mainframe = WxPeriodicTable(None, -1) 82 self.SetTopWindow(mainframe) 83 if wx.Platform != "__WXMAC__": 84 mainframe.Centre() 85 mainframe.ApplyLayout(False) 86 mainframe.Show() 87 return 1 88 89 90class ElementButton(buttons.GenToggleButton): 91 """Button representing chemical element.""" 92 93 def __init__(self, *args, **kwds): 94 buttons.GenToggleButton.__init__(self, *args, **kwds) 95 self.color = wx.Colour(255, 255, 255) 96 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) 97 self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown) 98 99 @property 100 def Z(self): 101 """Atomic number corresponding to this button.""" 102 return self.GetId() - 100 103 104 def OnEraseBackground(self, event): 105 pass 106 107 def SetButtonColour(self, color): 108 self.color = color 109 110 def OnPaint(self, event): 111 width, height = self.GetClientSizeTuple() 112 dc = wx.BufferedPaintDC(self) 113 brush = wx.Brush(self.GetBackgroundColour(), wx.SOLID) 114 if wx.Platform == "__WXMAC__": 115 brush.MacSetTheme(1) # kThemeBrushDialogBackgroundActive 116 dc.SetBackground(brush) 117 dc.SetPen(wx.TRANSPARENT_PEN) 118 dc.SetBrush(wx.Brush(self.color if self.up else 'WHITE', wx.SOLID)) 119 dc.Clear() 120 dc.DrawRectanglePointSize((1, 1), (width - 2, height - 2)) 121 if self.up: 122 self.DrawBezel(dc, 0, 0, width - 1, height - 1) 123 self.DrawLabel(dc, width, height) 124 if (self.hasFocus and self.useFocusInd): 125 self.DrawFocusIndicator(dc, width, height) 126 127 def DrawLabel(self, dc, width, height): 128 font = self.GetFont() 129 font.SetWeight(wx.FONTWEIGHT_BOLD) 130 dc.SetFont(font) 131 132 if self.IsEnabled(): 133 dc.SetTextForeground(self.GetForegroundColour()) 134 else: 135 dc.SetTextForeground(wx.SystemSettings.GetColour( 136 wx.SYS_COLOUR_GRAYTEXT)) 137 138 label = self.GetLabel() 139 txtwidth, txtheight = dc.GetTextExtent(label) 140 xpos = (width - txtwidth) // 2 141 ypos = (height*0.75 - txtheight) // 2 - 1 142 dc.DrawText(label, xpos, ypos) 143 144 font.SetWeight(wx.FONTWEIGHT_LIGHT) 145 font.SetPointSize((font.GetPointSize()*6) // 8) 146 dc.SetFont(font) 147 label = "%i" % (self.GetId() - 100) 148 txtwidth, txtheight = dc.GetTextExtent(label) 149 dc.DrawText(label, (width-txtwidth)//2, 4+ypos+(height-txtheight)//2) 150 151 def makePopupMenu(self): 152 """Build and return the popup menu.""" 153 menu = wx.Menu() 154 155 self.ID_POPUP_NIST_LDA = wx.NewId() 156 menu.Append(self.ID_POPUP_NIST_LDA, "NIST SCF data (LDA)") 157 158 # Associate menu/toolbar items with their handlers. 159 menu_handlers = [ 160 (self.ID_POPUP_NIST_LDA, self.onNistLda), 161 ] 162 163 for combo in menu_handlers: 164 mid, handler = combo[:2] 165 self.Bind(wx.EVT_MENU, handler, id=mid) 166 167 return menu 168 169 def onRightDown(self, event): 170 """Called when right button is pressed.""" 171 popup_menu = self.makePopupMenu() 172 self.PopupMenu(popup_menu, event.GetPosition()) 173 174 def onNistLda(self, event): 175 """ 176 Show the LDA levels of the neutral atom. 177 (useful to decide if semicore states should be included in the valence). 178 """ 179 try: 180 entry = nist.get_neutral_entry(self.Z) 181 except KeyError: 182 return 183 184 table = entry.to_table() 185 frame = wx.Frame(self, title="LDA levels for atom %s (NIST database) " % entry.symbol) 186 ListCtrlFromTable(frame, table) 187 frame.Show() 188 189 190class PeriodicPanel(wx.Panel): 191 """Periodic table of elements panel.""" 192 193 layout = """ 194 . 1 . . . . . . . . . . . . . . . . 18 . 195 1 H 2 . . . . . . . . . . 13 14 15 16 17 He . 196 2 Li Be . . . . . . . . . . B C N O F Ne . 197 3 Na Mg 3 4 5 6 7 8 9 10 11 12 Al Si P S Cl Ar . 198 4 K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr . 199 5 Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Xe . 200 6 Cs Ba * Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn . 201 7 Fr Ra ** Rf Db Sg Bh Hs Mt . . . . . . . . . . 202 . . . . . . . . . . . . . . . . . . . . 203 . . . * La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu . 204 . . . ** Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr . 205 """ 206 # Class used to instanciates the buttons in the panel. 207 element_button_class = ElementButton 208 209 def __init__(self, *args, **kwds): 210 kwds["style"] = wx.TAB_TRAVERSAL 211 wx.Panel.__init__(self, *args, **kwds) 212 rows = len(self.layout.splitlines()) - 2 213 cols = len(self.layout.splitlines()[1].split()) 214 self.sizer = wx.FlexGridSizer(rows, cols, 0, 0) 215 self.buttons = list(range(0, len(ELEMENTS))) 216 self.selected = -1 217 218 self.info = ElementPanel(self, -1, pos=(0, 0)) 219 220 # create element buttons 221 buttonsize = math.ceil(float(self.info.GetSize()[0] + 4) / 9.0) 222 if buttonsize < 30: 223 buttonsize = 30 224 for row in self.layout.splitlines()[1:-1]: 225 for col in row.split(): 226 if col == '.': 227 self.sizer.Add((SPACER, SPACER)) 228 elif col[0] in '123456789*': 229 static = wx.StaticText(self, -1, col, 230 style=wx.ALIGN_CENTER|wx.ALIGN_BOTTOM) 231 self.sizer.Add(static, 0, 232 (wx.ALL|wx.ALIGN_CENTER_HORIZONTAL| 233 wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE), SPACER//2) 234 else: 235 ele = ELEMENTS[col] 236 button = self.element_button_class(self, ele.number+100, ele.symbol, 237 size=(buttonsize, buttonsize)) 238 self.buttons[ele.number - 1] = button 239 button.SetBezelWidth(1) 240 button.SetToolTipString(ele.name) 241 col = COLORS[ele.series] 242 button.SetButtonColour(wx.Colour(col[0], col[1], col[2])) 243 self.sizer.Add(button, 0, (wx.LEFT|wx.BOTTOM| 244 wx.FIXED_MINSIZE|wx.ALIGN_CENTER_HORIZONTAL| 245 wx.ALIGN_CENTER_VERTICAL), 0) 246 self.Bind(wx.EVT_BUTTON, self.OnSelect, button) 247 248 self.SetAutoLayout(True) 249 self.SetSizer(self.sizer) 250 self.sizer.SetSizeHints(self) 251 self.sizer.Fit(self) 252 253 # position element info panel 254 cw = self.sizer.GetColWidths() 255 rh = self.sizer.GetRowHeights() 256 self.info.Move((sum(cw[:3])+cw[3]//2, (rh[0]-SPACER)//2-1)) 257 258 # legend of chemical series 259 self.legendpos = (sum(cw[:13]), (rh[0]-SPACER)//2-1) 260 self.legendsize = (sum(cw[13:17]) + cw[17]//2, -1) 261 self.legend = wx.StaticText(self, -1, " Alkaline earth metals ", 262 style=wx.ALIGN_CENTER, pos=self.legendpos, size=self.legendsize) 263 self.legend.SetToolTipString("Chemical series") 264 265 # blinking element button 266 self.highlight = False 267 self.timer = wx.Timer(self) 268 self.timer.Start(750) 269 self.Bind(wx.EVT_TIMER, self.OnTimer) 270 271 def AddCtrl(self, ctrl, pos=200): 272 self.sizer.Remove(pos) 273 self.sizer.Insert(pos, ctrl, 0, (wx.ALL|wx.ALIGN_CENTER_HORIZONTAL| 274 wx.ALIGN_BOTTOM|wx.FIXED_MINSIZE), 0) 275 self.Layout() 276 277 def OnTimer(self, evt): 278 button = self.buttons[self.selected] 279 button.SetToggle(button.up) 280 281 def OnSelect(self, evt): 282 self.GetParent().SetSelection(evt.GetId() - 101) 283 284 def SetSelection(self, select): 285 """Set active element.""" 286 if self.selected == select: 287 return 288 # reset old selection 289 self.buttons[self.selected].SetToggle(False) 290 # highlight new selection 291 self.selected = select 292 self.buttons[select].SetToggle(True) 293 ele = ELEMENTS[select + 1] 294 col = COLORS[ele.series] 295 self.legend.SetBackgroundColour(wx.Colour(col[0], col[1], col[2])) 296 self.legend.SetLabel(SERIES[ele.series]) 297 self.legend.Move(self.legendpos) 298 self.legend.SetSize(self.legendsize) 299 self.info.SetSelection(select) 300 301 302class ElementPanel(wx.Panel): 303 """Element information panel.""" 304 305 def __init__(self, *args, **kwds): 306 kwds["style"] = wx.NO_BORDER | wx.TAB_TRAVERSAL 307 wx.Panel.__init__(self, *args, **kwds) 308 self.selected = -1 309 310 # create controls 311 self.number = wx.StaticText(self, -1, "808", 312 style=wx.ALIGN_RIGHT) 313 self.position = wx.StaticText(self, -1, "6, 88, 9", 314 style=wx.ALIGN_LEFT) 315 self.symbol = wx.StaticText(self, -1, "Mm", 316 style=wx.ALIGN_CENTER_HORIZONTAL) 317 self.name = wx.StaticText(self, -1, "Praseodymium ", 318 style=wx.ALIGN_CENTER_HORIZONTAL) 319 self.mass = wx.StaticText(self, -1, "123.4567890 ", 320 style=wx.ALIGN_CENTER_HORIZONTAL) 321 self.massnumber = wx.StaticText(self, -1, "123 A ", 322 style=wx.ALIGN_RIGHT) 323 self.protons = wx.StaticText(self, -1, "123 P ", 324 style=wx.ALIGN_RIGHT) 325 self.neutrons = wx.StaticText(self, -1, "123 N ", 326 style=wx.ALIGN_RIGHT) 327 self.electrons = wx.StaticText(self, -1, "123 e ", 328 style=wx.ALIGN_RIGHT) 329 self.eleshell = wx.StaticText(self, -1, "2, 8, 18, 32, 32, 15, 2", 330 style=wx.ALIGN_LEFT) 331 self.eleconfig = StaticFancyText(self, -1, 332 "[Xe] 4f<sup>14</sup> 5d<sup>10</sup>" 333 " 6s<sup>2</sup> 6p<sup>6</sup> ", 334 style=wx.ALIGN_LEFT) 335 self.oxistates = wx.StaticText(self, -1, "1*, 2, 3, 4, 5, 6, -7 ") 336 self.atmrad = wx.StaticText(self, -1, "1.234 A ", 337 style=wx.ALIGN_RIGHT) 338 self.ionpot = wx.StaticText(self, -1, "123.4567890 eV ") 339 self.eleneg = wx.StaticText(self, -1, "123.45678 ") 340 341 # set control properties 342 font = self.GetFont() 343 font.SetWeight(wx.FONTWEIGHT_BOLD) 344 self.number.SetFont(font) 345 self.name.SetFont(font) 346 font.SetPointSize(font.GetPointSize() * 1.9) 347 self.symbol.SetFont(font) 348 349 self.number.SetToolTipString("Atomic number") 350 self.position.SetToolTipString("Group, Period, Block") 351 self.symbol.SetToolTipString("Symbol") 352 self.name.SetToolTipString("Name") 353 self.mass.SetToolTipString("Relative atomic mass") 354 self.eleshell.SetToolTipString("Electrons per shell") 355 self.massnumber.SetToolTipString("Mass number (most abundant isotope)") 356 self.protons.SetToolTipString("Protons") 357 self.neutrons.SetToolTipString("Neutrons (most abundant isotope)") 358 self.electrons.SetToolTipString("Electrons") 359 self.eleconfig.SetToolTipString("Electron configuration") 360 self.oxistates.SetToolTipString("Oxidation states") 361 self.atmrad.SetToolTipString("Atomic radius") 362 self.ionpot.SetToolTipString("Ionization potentials") 363 self.eleneg.SetToolTipString("Electronegativity") 364 365 # layout 366 sizer = rcsizer.RowColSizer() 367 sizer.col_w = SPACER 368 sizer.row_h = SPACER 369 sizer.Add(self.number, row=0, col=0, 370 flag=wx.ALIGN_CENTER_HORIZONTAL|wx.FIXED_MINSIZE) 371 sizer.Add(self.position, row=0, col=1, 372 flag=wx.ALIGN_LEFT|wx.FIXED_MINSIZE) 373 sizer.Add(self.symbol, row=1, col=0, rowspan=2, colspan=2, 374 flag=(wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL| 375 wx.FIXED_MINSIZE)) 376 sizer.Add(self.name, row=3, col=0, colspan=2, 377 flag=wx.ALIGN_CENTER_HORIZONTAL|wx.FIXED_MINSIZE) 378 sizer.Add(self.mass, row=4, col=0, colspan=2, 379 flag=wx.ALIGN_CENTER_HORIZONTAL|wx.FIXED_MINSIZE) 380 sizer.Add(self.massnumber, row=0, col=2, 381 flag=wx.ALIGN_RIGHT|wx.FIXED_MINSIZE) 382 sizer.Add(self.protons, row=1, col=2, 383 flag=wx.ALIGN_RIGHT|wx.FIXED_MINSIZE) 384 sizer.Add(self.neutrons, row=2, col=2, 385 flag=wx.ALIGN_RIGHT|wx.FIXED_MINSIZE) 386 sizer.Add(self.electrons, row=3, col=2, 387 flag=wx.ALIGN_RIGHT|wx.FIXED_MINSIZE) 388 sizer.Add(self.atmrad, row=4, col=2, 389 flag=wx.ALIGN_RIGHT|wx.FIXED_MINSIZE) 390 sizer.Add(self.eleconfig, row=0, col=4, flag=wx.ADJUST_MINSIZE) 391 sizer.Add(self.eleshell, row=1, col=4, flag=wx.ADJUST_MINSIZE) 392 sizer.Add(self.oxistates, row=2, col=4, flag=wx.ADJUST_MINSIZE) 393 sizer.Add(self.ionpot, row=3, col=4, flag=wx.ADJUST_MINSIZE) 394 sizer.Add(self.eleneg, row=4, col=4, flag=wx.ADJUST_MINSIZE) 395 396 self.SetAutoLayout(True) 397 self.SetSizer(sizer) 398 sizer.Fit(self) 399 sizer.SetSizeHints(self) 400 self.Layout() 401 402 def SetSelection(self, select): 403 """Set active element.""" 404 if self.selected == select: 405 return 406 self.Freeze() 407 self.selected = select 408 ele = ELEMENTS[select + 1] 409 410 self.number.SetLabel("%i" % ele.number) 411 self.position.SetLabel("%i, %i, %s" % ( 412 ele.group, ele.period, ele.block)) 413 self.mass.SetLabel("%.10g" % ele.mass) 414 self.eleshell.SetLabel(', '.join("%i" % i for i in ele.eleshells)) 415 self.massnumber.SetLabel('%i A ' % ele.nominalmass) 416 self.protons.SetLabel('%i P ' % ele.protons) 417 self.neutrons.SetLabel('%i N ' % ele.neutrons) 418 self.electrons.SetLabel('%i e ' % ele.electrons) 419 self.oxistates.SetLabel(ele.oxistates) 420 self.atmrad.SetLabel(_u("%.10g \xc5 ") % ele.atmrad if ele.atmrad else 421 "") 422 self.eleneg.SetLabel("%.10g" % ele.eleneg if ele.eleneg else "") 423 self.ionpot.SetLabel( 424 "%.10g eV" % ele.ionenergy[0] if ele.ionenergy else "") 425 self.symbol.SetLabel(ele.symbol) 426 self.name.SetLabel(ele.name) 427 428 label = [] 429 for orb in ele.eleconfig.split(): 430 if not orb.startswith('[') and len(orb) > 2: 431 orb = orb[:2] + '<sup>' + orb[2:] + '</sup>' 432 label.append(orb) 433 label.append("<sup> </sup>") # fix ADJUST_MINSIZE 434 self.eleconfig.SetLabel(' '.join(label)) 435 436 self.Thaw() 437 self.Layout() 438 439 440class DetailsPanel(wx.Panel): 441 """Element details panel.""" 442 443 def __init__(self, *args, **kwds): 444 kwds["style"] = wx.NO_BORDER|wx.TAB_TRAVERSAL 445 wx.Panel.__init__(self, *args, **kwds) 446 self.selected = -1 447 448 # create controls 449 cb_style = wx.CB_READONLY|wx.CB_SORT 450 if wx.Platform == "__WXMAC__": 451 cb_style = wx.CB_READONLY 452 453 454 self.names = LabeledCtrl(self, wx.ComboBox, "Element Name", 455 choices=[p.name for p in ELEMENTS], 456 style=cb_style, size=(1, -1)) 457 self.symbols = LabeledCtrl(self, wx.ComboBox, "Symbol", '', 458 choices=[p.symbol for p in ELEMENTS], 459 style=cb_style, size=(1, -1)) 460 self.numbers = LabeledCtrl(self, wx.ComboBox, "Number", 461 choices=["%s" % p.number for p in ELEMENTS], 462 style=wx.CB_READONLY, size=(1, -1)) 463 self.mass = LabeledCtrl(self, wx.ComboBox, "Relative Atomic Mass", 464 choices=["%-.10g" % p.mass for p in ELEMENTS], 465 style=wx.CB_READONLY, size=(1, -1)) 466 self.atmrad = LabeledCtrl(self, wx.ComboBox, 467 _u("Atomic Radius (\xc5)"), 468 choices=["%-.10g" % p.atmrad for p in ELEMENTS], 469 style=wx.CB_READONLY, size=(1, -1)) 470 self.covrad = LabeledCtrl(self, wx.ComboBox, 471 _u("Covalent Radius (\xc5)"), 472 choices=["%-.10g" % p.covrad for p in ELEMENTS], 473 style=wx.CB_READONLY, size=(1, -1)) 474 self.vdwrad = LabeledCtrl(self, wx.ComboBox, 475 _u("V.d.Waals Radius (\xc5)"), 476 choices=["%-.10g" % p.vdwrad for p in ELEMENTS], 477 style=wx.CB_READONLY, size=(1, -1)) 478 self.eleneg = LabeledCtrl(self, wx.ComboBox, "Electronegativity", 479 choices=["%-.10g" % p.eleneg for p in ELEMENTS], 480 style=wx.CB_READONLY, size=(1, -1)) 481 self.eleconfig = LabeledCtrl(self, wx.ComboBox, "e- Config", 482 choices=[p.eleconfig for p in ELEMENTS], 483 style=wx.CB_READONLY, size=(1, -1)) 484 self.eleshells = LabeledCtrl(self, wx.ComboBox, "Electrons per Shell", 485 choices=[', '.join("%i" % i for i in p.eleshells) 486 for p in ELEMENTS], 487 style=wx.CB_READONLY, size=(1, -1)) 488 self.oxistates = LabeledCtrl(self, wx.ComboBox, "Oxidation States", 489 choices=[p.oxistates for p in ELEMENTS], 490 style=wx.CB_READONLY, size=(1, -1)) 491 self.ionpot = LabeledCtrl(self, wx.Choice, 492 "Ionization Potentials (eV)", choices=[], size=(1, -1)) 493 self.isotopes = LabeledCtrl(self, wx.Choice, "Isotopes", 494 choices=[], size=(1, -1)) 495 496 # layout 497 sizer = wx.BoxSizer(wx.VERTICAL) 498 sizer_top = wx.BoxSizer(wx.HORIZONTAL) 499 sizer_left = wx.BoxSizer(wx.VERTICAL) 500 sizer_right = wx.BoxSizer(wx.VERTICAL) 501 sizer_num = wx.BoxSizer(wx.HORIZONTAL) 502 style = wx.RIGHT|wx.BOTTOM|wx.EXPAND|wx.ADJUST_MINSIZE 503 sizer_left.Add(self.names, 0, style, SPACER) 504 sizer_left.Add(self.mass, 0, style, SPACER) 505 sizer_left.Add(self.atmrad, 0, style, SPACER) 506 sizer_left.Add(self.covrad, 0, style, SPACER) 507 sizer_left.Add(self.vdwrad, 0, style, SPACER) 508 sizer_left.Add(self.eleneg, 0, style, SPACER) 509 sizer_top.Add(sizer_left, 1, wx.LEFT|wx.RIGHT, 0) 510 style = wx.BOTTOM|wx.EXPAND|wx.ADJUST_MINSIZE 511 sizer_num.Add(self.symbols, 1, style, 0) 512 sizer_num.Add((SPACER, 5), 0, 0, 0) 513 sizer_num.Add(self.numbers, 1, style, 0) 514 sizer_right.Add(sizer_num, 0, style, SPACER) 515 sizer_right.Add(self.eleconfig, 0, style, SPACER) 516 sizer_right.Add(self.eleshells, 0, style, SPACER) 517 sizer_right.Add(self.oxistates, 0, style, SPACER) 518 sizer_right.Add(self.ionpot, 0, style, SPACER) 519 sizer_right.Add(self.isotopes, 0, style, SPACER) 520 sizer_top.Add(sizer_right, 1, wx.TOP|wx.RIGHT, 0) 521 sizer.Add(sizer_top, 1, 522 wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND|wx.ADJUST_MINSIZE, SPACER) 523 self.SetAutoLayout(True) 524 self.SetSizerAndFit(sizer, True) 525 sizer.SetSizeHints(self) 526 self.Layout() 527 528 # bind events 529 self.Bind(wx.EVT_COMBOBOX, self.OnSelectName, self.names.ctrl) 530 self.Bind(wx.EVT_COMBOBOX, self.OnSelectSymbol, self.symbols.ctrl) 531 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.numbers.ctrl) 532 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.mass.ctrl) 533 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.atmrad.ctrl) 534 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.covrad.ctrl) 535 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.vdwrad.ctrl) 536 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.eleshells.ctrl) 537 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.eleneg.ctrl) 538 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.eleconfig.ctrl) 539 self.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.oxistates.ctrl) 540 541 def SetSelection(self, select): 542 """Set active element.""" 543 if self.selected == select: 544 return 545 self.selected = select 546 ele = ELEMENTS[select+1] 547 548 self.names.ctrl.SetStringSelection(ele.name) 549 self.symbols.ctrl.SetStringSelection(ele.symbol) 550 self.numbers.ctrl.SetSelection(select) 551 self.mass.ctrl.SetSelection(select) 552 self.eleconfig.ctrl.SetSelection(select) 553 self.atmrad.ctrl.SetSelection(select) 554 self.covrad.ctrl.SetSelection(select) 555 self.vdwrad.ctrl.SetSelection(select) 556 self.eleneg.ctrl.SetSelection(select) 557 self.eleshells.ctrl.SetSelection(select) 558 self.oxistates.ctrl.SetSelection(select) 559 560 self.isotopes.ctrl.Clear() 561 for index, massnum in enumerate(sorted(ele.isotopes)): 562 iso = ele.isotopes[massnum] 563 self.isotopes.ctrl.Append("%3i: %8.4f , %8.4f%%" % ( 564 massnum, iso.mass, iso.abundance*100.0)) 565 if massnum == ele.nominalmass: 566 self.isotopes.ctrl.SetSelection(index) 567 568 self.ionpot.ctrl.Clear() 569 for ion in ele.ionenergy: 570 self.ionpot.ctrl.Append("%8.4f" % ion) 571 self.ionpot.ctrl.SetSelection(0) 572 573 def OnSelect(self, evt): 574 self.SetSelection(evt.GetSelection()) 575 event = SelectionEvent(pteEVT_ELE_CHANGED, self.GetId(), self.selected) 576 self.GetEventHandler().ProcessEvent(event) 577 evt.Skip() 578 579 def OnSelectName(self, evt): 580 name = self.names.ctrl.GetValue() 581 self.SetSelection(ELEMENTS[name].number - 1) 582 event = SelectionEvent(pteEVT_ELE_CHANGED, self.GetId(), self.selected) 583 self.GetEventHandler().ProcessEvent(event) 584 evt.Skip() 585 586 def OnSelectSymbol(self, evt): 587 name = self.symbols.ctrl.GetValue() 588 self.SetSelection(ELEMENTS[name].number - 1) 589 event = SelectionEvent(pteEVT_ELE_CHANGED, self.GetId(), self.selected) 590 self.GetEventHandler().ProcessEvent(event) 591 evt.Skip() 592 593 594class DecriptionPanel(wx.Panel): 595 """Element description panel.""" 596 597 def __init__(self, *args, **kwds): 598 kwds["style"] = wx.NO_BORDER|wx.TAB_TRAVERSAL 599 wx.Panel.__init__(self, *args, **kwds) 600 self.selected = -1 601 602 self.description = wx.TextCtrl(self, -1, " \n \n", 603 style=wx.TE_MULTILINE|wx.TE_READONLY) 604 font = self.description.GetFont() 605 font.SetPointSize((font.GetPointSize() + 1)) 606 self.description.SetFont(font) 607 sizer = wx.BoxSizer(wx.VERTICAL) 608 sizer.Add(self.description, 1, 609 wx.TOP|wx.LEFT|wx.RIGHT|wx.BOTTOM|wx.EXPAND|wx.FIXED_MINSIZE, 610 SPACER) 611 612 self.SetAutoLayout(True) 613 self.SetSizerAndFit(sizer, True) 614 sizer.SetSizeHints(self) 615 self.Layout() 616 617 def SetSelection(self, select): 618 """Set active element.""" 619 if self.selected == select: 620 return 621 self.selected = select 622 623 ele = ELEMENTS[select + 1] 624 self.description.SetValue(ele.description) 625 626 627class LabeledCtrl(wx.BoxSizer): 628 """BoxSizer containing label, control, and unit.""" 629 630 def __init__(self, parent, control, label, unit=None, space=" ", 631 *args, **kwds): 632 wx.BoxSizer.__init__(self, wx.HORIZONTAL) 633 self.label = wx.StaticText(parent, -1, label + space) 634 self.ctrl = control(parent, -1, *args, **kwds) 635 self.Add(self.label, 0, wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE) 636 self.Add(self.ctrl, 1, (wx.LEFT|wx.EXPAND|wx.ALIGN_CENTER_VERTICAL| 637 wx.ALIGN_RIGHT|wx.ADJUST_MINSIZE), 0) 638 if unit: 639 self.unit = wx.StaticText(parent, -1, unit) 640 self.Add(self.unit, 0, 641 wx.RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.FIXED_MINSIZE, 0) 642 else: 643 self.unit = None 644 645 646class StaticFancyText(fancytext.StaticFancyText): 647 """StaticFancyText with SetLabel function.""" 648 649 def SetLabel(self, label): 650 bmp = fancytext.RenderToBitmap( 651 label, wx.Brush(self.GetBackgroundColour(), wx.SOLID)) 652 self.SetBitmap(bmp) 653 654 655class DisclosureCtrl(buttons.GenBitmapTextToggleButton): 656 """Disclosure triangle button.""" 657 658 bmp0 = (b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\x00' 659 b'\x00\r\x08\x06\x00\x00\x00r\xeb\xe4|\x00\x00\x00\x04sBIT' 660 b'\x08\x08\x08\x08|\x08d\x88\x00\x00\x00\xd5IDAT(\x91\x9d' 661 b'\x921n\x83@\x10E\xdf.!\x96\x9c\x90P"\xd9\x86\x92\x82\x03p' 662 b'\x02h\xa9i9A\xe2\xb38)9\x02w\xc0\t\x94I\xa0H\xd2\xc7' 663 b'\x96(\x918\xc0\xa6Z\x17\x91\x05&_\x1a\xe9k4O\xfa3\x1a!' 664 b'\xa4\xc1\\Im>\xde\xdf\xd4l\xa8,K\x9e\x9fv\xeax\xf8\x99\x84' 665 b'\x85\x8e\xb7}|P\x00\xb6m\x13\x04\x01q\x1cc\xdd\xdd\x8bs\xd0' 666 b'\xd5\xdfF\xdf\xf7TUE\xdb\xb6\xbc\xbe\xecU\x18\x86\x98\xd7' 667 b'\x0b1\ni\r\xc3@Q\x14\xd4u\xcd\xf7\xd7\xa7r]\x97\xe5\xcd' 668 b'\xad\x18\x85\xb4\xba\xae#\xcfs|\xdf?\xf5\xe4\xc8<\x00\x8e' 669 b'\xe3\x90e\x19i\x9aN\xc7\xb3,\x8b(\x8a\xb8h\xa7Y\xd7' 670 b'\xf3<\x0f\xd34I\x92\x84\xd5zsv\xf8$!\r\x844h\x9aFi?Y\xff' 671 b'\xf9\xbd_\xd7\x8c7Z\xc0k\x8d8\x00\x00\x00\x00IEND\xaeB`\x82') 672 673 bmp1 = (b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\x00' 674 b'\x00\r\x08\x06\x00\x00\x00r\xeb\xe4|\x00\x00\x00\x04sBIT' 675 b'\x08\x08\x08\x08|\x08d\x88\x00\x00\x00\xc1IDAT(\x91\x9d\xd2;' 676 b'\x8e\xc20\x10\x80\xe1\x7fB\x0c\x95\xc1\xa5%\xc0)S\xb8I\xd2' 677 b'\xb8Le\x97\xf6\x99s\x81\xdd\xbdBN\xb2\xdb!Y\t\xac`\xbay|' 678 b'\xa3)F\xa49\xf0n4o\x8bOQ\x0b\xf0\xf3\xfd\xf5\xbb,\x0b\xeb' 679 b'\xba>\x1d\xec\xba\x8ey\x9e\x19\xc6I\x1a\x80a\x9cD)\xf5r' 680 b'\xbbR\x8aa\x9c\xa4:\xaf\x94\x821f\x17\x18c(\xa5<\xf2\x07' 681 b'\xba\xde\xee\xe2\xbd\xdfE\xde{\xae\xb7\xbbl\x10@J\t\xadu\x05' 682 b'\xb4\xd6\xa4\x94\xaaZ\x85\xf4\xf9"1\xc6j \xc6\x88>_\xe4)\x02' 683 b'\x08!`\xad\x05\xc0ZK\x08as\xee\x06\xa9\xe3Ir\xce\xb4mK\xce' 684 b'\x19u<\xc9\xbf\x08\xc09G\xdf\xf78\xe7\xf6\xda\xc8\'\xbf\xf7' 685 b'\x07\x13\x12\x18B\x17\x9fx\xa0\x00\x00\x00\x00IEND\xaeB`\x82') 686 687 def __init__(self, parent, winid, label, *args, **kwds): 688 kwds["style"] = wx.BORDER_NONE|wx.BU_EXACTFIT 689 buttons.GenBitmapTextToggleButton.__init__(self, parent, winid, None, 690 label, *args, **kwds) 691 if isinstance(self.bmp0, type(b'')): 692 self.__class__.bmp0 = wx.BitmapFromImage(wx.ImageFromStream( 693 io.BytesIO(self.bmp0))) 694 self.__class__.bmp1 = wx.BitmapFromImage(wx.ImageFromStream( 695 io.BytesIO(self.bmp1))) 696 697 self.SetBitmapLabel(self.bmp0) 698 self.SetBitmapSelected(self.bmp1) 699 if not label: 700 self.SetSize(self.bmp0.GetSize()) 701 else: 702 self.SetBestSize() 703 self.labelDelta = 0 704 self.useFocusInd = False 705 self.SetToolTipString('Show') 706 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) 707 708 def OnEraseBackground(self, event): 709 pass 710 711 def Notify(self): 712 wx.lib.buttons.GenBitmapTextToggleButton.Notify(self) 713 self.SetToolTipString("%s" % ('Show' if self.up else 'Hide')) 714 715 def DoGetBestSize(self): 716 width, height, usemin = self._GetLabelSize() 717 return width + 5, height + 4 718 719 def OnPaint(self, event): 720 width, height = self.GetClientSizeTuple() 721 dc = wx.BufferedPaintDC(self) 722 brush = None 723 bgcol = self.GetBackgroundColour() 724 brush = wx.Brush(bgcol, wx.SOLID) 725 defattr = self.GetDefaultAttributes() 726 if self.style & wx.BORDER_NONE and bgcol == defattr.colBg: 727 defattr = self.GetParent().GetDefaultAttributes() 728 if self.GetParent().GetBackgroundColour() == defattr.colBg: 729 if wx.Platform == "__WXMSW__": 730 if self.DoEraseBackground(dc): 731 brush = None 732 elif wx.Platform == "__WXMAC__": 733 brush.MacSetTheme(1) 734 else: 735 bgcol = self.GetParent().GetBackgroundColour() 736 brush = wx.Brush(bgcol, wx.SOLID) 737 if brush is not None: 738 dc.SetBackground(brush) 739 dc.Clear() 740 self.DrawLabel(dc, width, height) 741 742 def DrawLabel(self, dc, width, height, center=False): 743 bmp = self.bmpLabel 744 if bmp is not None: 745 if self.bmpDisabled and not self.IsEnabled(): 746 bmp = self.bmpDisabled 747 if self.bmpFocus and self.hasFocus: 748 bmp = self.bmpFocus 749 if self.bmpSelected and not self.up: 750 bmp = self.bmpSelected 751 bmpwidth, bmpheight = bmp.GetWidth(), bmp.GetHeight() 752 hasmask = bmp.GetMask() is not None 753 else: 754 bmpwidth = bmpheight = 0 755 756 dc.SetFont(self.GetFont()) 757 color = (self.GetForegroundColour() if self.IsEnabled() else 758 wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) 759 dc.SetTextForeground(color) 760 761 label = self.GetLabel() 762 txtwidth, txtheight = dc.GetTextExtent(label) 763 # center bitmap and text 764 xpos = (width - bmpwidth - txtwidth) // 2 if center else 0 765 if bmp is not None: 766 dc.DrawBitmap(bmp, xpos, (height - bmpheight) // 2, hasmask) 767 xpos += 5 768 dc.DrawText(label, xpos + bmpwidth, (height - txtheight) // 2) 769 770 771class SelectionEvent(wx.PyCommandEvent): 772 """Notification of changed element.""" 773 774 def __init__(self, evtType, winid, sel): 775 wx.PyCommandEvent.__init__(self, evtType, winid) 776 self.selection = sel 777 778 def SetSelection(self, select): 779 self.selection = select 780 781 def GetSelection(self): 782 return self.selection 783 784 785class WxPeriodicTable(wx.Frame): 786 """Main application window.""" 787 788 # Class used to instanciate the panel with the elements. 789 periodic_panel_class = PeriodicPanel 790 791 def __init__(self, *args, **kwds): 792 kwds["style"] = (wx.DEFAULT_DIALOG_STYLE | wx.MINIMIZE_BOX | wx.TAB_TRAVERSAL) 793 wx.Frame.__init__(self, *args, **kwds) 794 self.selected = -1 795 796 self.SetTitle(MainApp.name) 797 icon = wx.EmptyIcon() 798 #icon.CopyFromBitmap(wx.Bitmap(MainApp.icon + '.png', 799 # wx.BITMAP_TYPE_ANY)) 800 self.SetIcon(icon) 801 self.SetBackgroundColour(wx.SystemSettings_GetColour( 802 wx.SYS_COLOUR_3DFACE)) 803 804 # create menu 805 self.menu = wx.MenuBar() 806 self.SetMenuBar(self.menu) 807 menu = wx.Menu() 808 menu.Append(wx.ID_EXIT, "Exit", "Exit the application", wx.ITEM_NORMAL) 809 self.menu.Append(menu, "File") 810 menu = wx.Menu() 811 menu.Append(wx.ID_COPY, 812 "Copy\tCtrl+C", "Copy selected element to the clipboard", 813 wx.ITEM_NORMAL) 814 self.menu.Append(menu, "Edit") 815 menu = wx.Menu() 816 menu.Append(wx.ID_VIEW_DETAILS, "Details", "Show or hide details", 817 wx.ITEM_CHECK) 818 self.menu.Append(menu, "View") 819 menu = wx.Menu() 820 menu.Append(wx.ID_ABOUT, "About...", 821 "Display information about the program", wx.ITEM_NORMAL) 822 self.menu.Append(menu, "Help") 823 824 # create panels and controls 825 self.notebook = wx.Notebook(self, -1, style=0) 826 self.description = DecriptionPanel(self.notebook, -1) 827 self.details = DetailsPanel(self.notebook, -1) 828 829 self.table = self.periodic_panel_class(self, id=-1) 830 831 self.disclose = DisclosureCtrl(self.table, -1, '') 832 self.table.AddCtrl(self.disclose) 833 self.notebook.AddPage(self.description, "Description") 834 self.notebook.AddPage(self.details, "Properties") 835 836 # event bindings 837 self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT) 838 self.Bind(wx.EVT_MENU, self.OnCopy, id=wx.ID_COPY) 839 self.Bind(wx.EVT_MENU, self.OnDetails, id=wx.ID_VIEW_DETAILS) 840 self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT) 841 self.Bind(EVT_ELE_CHANGED, self.OnSelect, self.table) 842 self.Bind(EVT_ELE_CHANGED, self.OnSelect, self.details) 843 self.Bind(wx.EVT_BUTTON, self.OnDetails, self.disclose) 844 845 # create sizers 846 self.sizer = wx.BoxSizer(wx.VERTICAL) 847 self.sizer.Add(self.table, 1, (wx.LEFT|wx.TOP|wx.RIGHT|wx.EXPAND| 848 wx.ALIGN_CENTER_HORIZONTAL| wx.EXPAND|wx.ADJUST_MINSIZE), BORDER-5) 849 self.sizer.Add((BORDER, BORDER)) 850 self.sizer.Add(self.notebook, 0, (wx.LEFT|wx.RIGHT|wx.BOTTOM| 851 wx.EXPAND|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL| 852 wx.ADJUST_MINSIZE), BORDER) 853 854 self.notebook.SetSelection(1) 855 self.SetAutoLayout(True) 856 self.SetSizerAndFit(self.sizer, True) 857 self.sizer.SetSizeHints(self) 858 self.ApplyLayout(True) 859 self.SetSelection(0) 860 861 def ApplyLayout(self, show=False): 862 self.show_info = show 863 self.sizer.Show(2, show, True) 864 self.SetAutoLayout(True) 865 self.SetSizerAndFit(self.sizer, True) 866 self.sizer.SetSizeHints(self) 867 self.Layout() 868 self.menu.Check(wx.ID_VIEW_DETAILS, show) 869 self.disclose.SetToggle(self.show_info) 870 871 def OnDetails(self, evt): 872 self.ApplyLayout(not self.show_info) 873 874 def OnEraseBackground(self, event): 875 pass 876 877 def OnCopy(self, evt): 878 dobj = wx.TextDataObject() 879 dobj.SetText(repr(ELEMENTS[self.selected + 1])) 880 if wx.TheClipboard.Open(): 881 wx.TheClipboard.SetData(dobj) 882 wx.TheClipboard.Close() 883 884 def OnAbout(self, evt): 885 info = wx.AboutDialogInfo() 886 info.Name = MainApp.name 887 info.Version = MainApp.version 888 info.Copyright = MainApp.copyright 889 info.WebSite = MainApp.website 890 wx.AboutBox(info) 891 892 def OnWebsite(self, evt): 893 webbrowser.open(MainApp.website) 894 895 def OnWikipedia(self, evt): 896 webbrowser.open("http://en.wikipedia.org/wiki/%s" % ( 897 ELEMENTS[self.selected].name), 1) 898 899 def OnWebElements(self, evt): 900 webbrowser.open("http://www.webelements.com/%s/" % ( 901 ELEMENTS[self.selected].name.lower())) 902 903 def OnSelect(self, evt): 904 self.SetSelection(evt.GetSelection()) 905 906 def SetSelection(self, select): 907 """Set active element.""" 908 if self.selected != select: 909 self.selected = select 910 self.description.SetSelection(select) 911 self.table.SetSelection(select) 912 self.details.SetSelection(select) 913 914 def OnExit(self, evt): 915 self.Close() 916 if __name__ == "__main__": 917 sys.exit(0) 918 else: 919 return self.selected 920 921 922pteEVT_ELE_CHANGED = wx.NewEventType() 923EVT_ELE_CHANGED = wx.PyEventBinder(pteEVT_ELE_CHANGED, 1) 924 925SPACER = 12 if wx.Platform == "__WXMAC__" else 10 926BORDER = 22 if wx.Platform == "__WXMAC__" else 10 927 928COLORS = { 929 1: (0x99, 0xff, 0x99), # Nonmetals 930 2: (0xc0, 0xff, 0xff), # Noble gases 931 3: (0xff, 0x99, 0x99), # Alkali metals 932 4: (0xff, 0xde, 0xad), # Alkaline earth metals 933 5: (0xcc, 0xcc, 0x99), # Metalloids 934 6: (0xff, 0xff, 0x99), # Halogens 935 7: (0xcc, 0xcc, 0xcc), # Poor metals 936 8: (0xff, 0xc0, 0xc0), # Transition metals 937 9: (0xff, 0xbf, 0xff), # Lanthanides 938 10: (0xff, 0x99, 0xcc), # Actinides 939} 940 941_u = (lambda x: x.decode('latin-1')) if sys.version[0] == '2' else str 942 943if __name__ == "__main__": 944 MainApp(0).MainLoop() 945