# -*- coding: utf-8 -*-
__author__ = "Konstantin Klementiev, Roman Chernikov"
__date__ = "30 Nov 2023"

import sys
import os
import re
from functools import partial
from collections import OrderedDict
import webbrowser
import numpy as np
import matplotlib as mpl
from matplotlib.figure import Figure
from pyparsing import ParseBaseException
sys.path.append(os.path.join('..'))  # analysis:ignore
from XAFSmass import XAFSmassCalc as xc
from XAFSmass.__init__ import (__version__, __author__, __license__)
from XAFSmass.materials_simple import elemental, compounds

# ==this may help to resolve conflict betwen Qt4 and Qt5=======================
# mpl.use("Qt4Agg")
# =============================================================================

try:
    from matplotlib.backends import qt_compat
except ImportError:
    from matplotlib.backends import qt4_compat
    qt_compat = qt4_compat
use_pyside = qt_compat.QT_API.startswith("PySide")
if use_pyside:
    QtName = "PySide"
    import PySide
    from PySide import QtGui, QtCore
    import PySide.QtGui as myQtGUI
    import matplotlib.backends.backend_qt4agg as mpl_qt
else:
    try:
        QtName = "PyQt4"
        from PyQt4 import QtGui, QtCore
        import PyQt4.QtGui as myQtGUI
        import matplotlib.backends.backend_qt4agg as mpl_qt
    except ImportError:
        QtName = "PyQt5"
        from PyQt5 import QtGui, QtCore
        import PyQt5.QtWidgets as myQtGUI
        import matplotlib.backends.backend_qt5agg as mpl_qt

QDialog, QApplication, QLabel, QComboBox, QLineEdit, QPushButton,\
    QSizePolicy, QHBoxLayout, QVBoxLayout, QFrame, QMessageBox, QWidget,\
    QSlider, QPixmap, QColor = \
    myQtGUI.QDialog, myQtGUI.QApplication, myQtGUI.QLabel,\
    myQtGUI.QComboBox, myQtGUI.QLineEdit, myQtGUI.QPushButton,\
    myQtGUI.QSizePolicy, myQtGUI.QHBoxLayout, myQtGUI.QVBoxLayout,\
    myQtGUI.QFrame, myQtGUI.QMessageBox, myQtGUI.QWidget, myQtGUI.QSlider,\
    QtGui.QPixmap, QtGui.QColor
Canvas = mpl_qt.FigureCanvasQTAgg
ToolBar = mpl_qt.NavigationToolbar2QT

# ==this can make the formulas look nicer:=====================================
mpl.rcParams['mathtext.fontset'] = 'custom'

#mpl.rcParams['mathtext.rm'] = 'serif'
#mpl.rcParams['mathtext.it'] = 'serif:italic'
#mpl.rcParams['mathtext.bf'] = 'serif:bold'
#mpl.rcParams['mathtext.cal'] = 'cursive'
#mpl.rcParams['mathtext.rm'] = 'serif'
#mpl.rcParams['mathtext.tt'] = 'monospace'

mpl.rcParams['mathtext.rm'] = 'cmr10'
mpl.rcParams['mathtext.it'] = 'cmmi10'
mpl.rcParams['mathtext.bf'] = 'cmb10'
mpl.rcParams['mathtext.cal'] = 'cmsy10'
mpl.rcParams['mathtext.bf'] = 'cmtt10'
mpl.rcParams['mathtext.bf'] = 'cmss10'
# =============================================================================

# Set the default color cycle:
# mpl.rcParams['axes.color_cycle'] = ['r', 'g', 'b', 'm', 'c', 'k']

MAC = "qt_mac_set_native_menubar" in dir()

POWDER, FOIL, GAS, XCONTENT = range(4)
whats = ('powder', 'foil, film, glass etc.', 'gas',
         'has unknown concentration')
formulas = (
    r"$\nu = (\mu_T d)\cdot S\cdot\left(N_A 2r_0 \lambda \sum_i{N_i f_i''}"
    r"\right)^{-1}; \quad m = M\cdot\nu$",
    r"$d = (\mu_T d)\cdot M\cdot\left(\rho N_A 2r_0 \lambda \sum_i{N_i f_i''}"
    r"\right)^{-1}$",
    r"$p = -\ln(1-{\rm att})\cdot kT \cdot"
    r"\left(d 2r_0 \lambda \sum_i{N_i f_i''}\right)^{-1}$",
    r"$\Delta\mu/\mu_T = N_x\Delta f_x'' \cdot"
    r"\left(\sum_{i\ne x}{N_if_i''} + N_xf_x''\right)^{-1}$")
examples = (
    "Cu(NO3)2  <i>or</i>  Cu%1Zn%1((Al2O3)%10SiO2)", "Cu%25Zn",
    "Ar <i>or</i> N2 <i>or</i> (Ar)(N2) to explore mixtures",
    "FexSiO2")
tables = ("Henke", "Brennan&Cowan", "Chantler (NIST)", "Chantler total (NIST)")
tablesF = ("Henke", "BrCo", "Chantler", "Chantler total")
edges = ("K", "L1", "L2", "L3", "M1", "M2", "M3", "M4", "M5", "N1", "N2", "N3")

gasMixerN = 5
gasMixerPressureMin = 0
gasMixerPressureMax = 3000
gasMixerPressurePageStep = 50
gasMixerPressureDefault = 1000


class ComboBoxWithPlaceholder(QComboBox):
    def paintEvent(self, event):
        painter = myQtGUI.QStylePainter(self)

        opt = myQtGUI.QStyleOptionComboBox()
        self.initStyleOption(opt)
        painter.drawComplexControl(myQtGUI.QStyle.CC_ComboBox, opt)

        if self.currentIndex() < 0:
            painter.setPen(QColor('#888888'))
            try:
                if self.placeholderText():
                    opt.currentText = self.placeholderText()
            except AttributeError:
                pass
        painter.drawControl(myQtGUI.QStyle.CE_ComboBoxLabel, opt)


class MyFormulaMplCanvas(Canvas):
    def __init__(self, parent=None, width=5, height=0.4):
        fig = Figure(figsize=(width, height), dpi=96)
        self.fig = fig
        Canvas.__init__(self, fig)
        fig.patch.set_visible(False)
        self.setParent(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.updateGeometry()
        fm = QtGui.QFontMetrics(self.font())
        self.fontsize = int(fm.height()) / 1.33
        self.setStyleSheet("background-color:transparent;")

    def update_formula(self, formula=None):
        self.fig.clf()
        self.fig.suptitle(formula, x=0.5, y=0.48, ha='center', va='center',
                          fontsize=self.fontsize)
        self.draw()


class MyMplCanvas(Canvas):
    def __init__(self, parent=None, width=6, height=5):
        fig = Figure(figsize=(width, height), dpi=96)
        self.fig = fig
        self.axes = fig.add_subplot(111)
        self.fig.subplots_adjust(left=0.15, right=0.97, bottom=0.15, top=0.97)
#        self.axes.hold(False)  # clear axes every time plot() is called
        Canvas.__init__(self, fig)
        self.setParent(parent)
        self.updateGeometry()
        fm = QtGui.QFontMetrics(self.font())
        self.fontsize = int(fm.height()) / 1.33

    def plot(self, compound, E, table):
        self.axes.cla()
        self.axes.set_xlabel('energy (keV)', fontsize=self.fontsize)
        self.axes.set_ylabel(r"$f''$", fontsize=self.fontsize)
        if compound is None:
            ll = self.axes.legend([], title='no given elements',
                                  loc='upper right', fontsize=self.fontsize)
        else:
            ced = xc.calculate_element_dict(compound.asList(), E, table)
            if isinstance(ced, str):
                QMessageBox.critical(self, "Error", ced)
                return
            elementsDict = ced[0]
            for elName, el in elementsDict.items():
                label = elName if el[5] == 0 else\
                    r"{0}, $f''$={1}, $\Delta f''$={2}".format(
                        elName, xc.round_to_n(el[4]), xc.round_to_n(el[5]))
                self.axes.plot(el[0].E*1e-3, el[0].f2, '-', marker='.',
                               label=label)
            self.axes.set_xlim(el[0].E[0]*1e-3, el[0].E[-1]*1e-3)
            ll = self.axes.legend(loc='upper right', fontsize=self.fontsize)
            ylim = self.axes.get_ylim()
            self.axes.plot([E*1e-3, E*1e-3], ylim, '--', color='gray',
                           label=None)
        mpl.artist.setp(ll.get_title(), fontsize=self.fontsize)
        self.draw()


class PlotDlg(QDialog):
    def __init__(self, parent, compound, E, table):
        super(PlotDlg, self).__init__(parent)
        bl = QVBoxLayout(self)
        self.plotCanvas = MyMplCanvas(self)
        self.plotCanvas.setSizePolicy(
            QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.toolbar = ToolBar(self.plotCanvas, self)
        bl.addWidget(self.toolbar)
        bl.addWidget(self.plotCanvas)
        pg = parent.frameGeometry()
        self.move(parent.x()+pg.width(), parent.y())
        pg = parent.geometry()
        self.resize(int(pg.width()*1.5), int(pg.height()))
        self.setWindowTitle("plots of f''")
        self.setWindowFlags(QtCore.Qt.Window)
        self.show()
        self.plotCanvas.plot(compound, E, table)

#    def closeEvent(self, event):  # is not invoked by esc. press
    def done(self, event):
        self.parent().plotDlg = None
        super(PlotDlg, self).done(event)


class MainDlg(QDialog):
    def __init__(self, parent=None):
        super(MainDlg, self).__init__(parent)

        self.whatLabel = QLabel(r"sample:")
        self.whatCB = QComboBox()
        self.whatCB.addItems(whats)
        self.whatCB.currentIndexChanged.connect(self.updateUi)
        self.what = 0

        self.formula = MyFormulaMplCanvas(self, width=3.2, height=0.5)

        self.compoundLabel = QLabel("compound:")
        self.compoundExLabel = QLabel("")
        self.compoundExLabel.setTextInteractionFlags(
            QtCore.Qt.TextInteractionFlags(QtCore.Qt.TextSelectableByMouse))
        self.compoundEdit = QLineEdit()
        self.compoundEdit.setPlaceholderText(
            'type here or select from the lists above')

        # self.compoundListsLabel = QLabel("can also be defined from")
        self.compoundList1 = ComboBoxWithPlaceholder()
        self.compoundList1.addItems(
            [k for k in OrderedDict(
                sorted(compounds.items(), key=lambda it: it[1]['rho']))]),
        try:
            self.compoundList1.setPlaceholderText(
                    "compounds (sorted by density)")
        except AttributeError:
            pass
        self.compoundList1.setToolTip("sorted by density")
        self.compoundList1.setMaxVisibleItems(18)
        self.compoundList1.setCurrentIndex(-1)
        self.compoundList1.activated.connect(self.compoundActivated)
        self.compoundList2 = ComboBoxWithPlaceholder()
        self.compoundList2.addItems(
            [k for k in OrderedDict(  # have to sort it in Py2
                sorted(elemental.items(), key=lambda it: it[1]['Z']))]),
        try:
            self.compoundList2.setPlaceholderText("elements")
        except AttributeError:
            pass
        self.compoundList2.setMaxVisibleItems(18)
        self.compoundList2.setCurrentIndex(-1)
        self.compoundList2.activated.connect(self.elementActivated)

        self.compoundMassLabel = QLabel("M (g/mol) = ")
        self.compoundMass = QLabel("")

        self.muTdLabel = QLabel("")
        self.muTdEdit = QLineEdit()
        self.muTdEdit.setPlaceholderText('typ. 2.6')
#        font = QtGui.QFont(self.font())
#        font.setPointSize(font.pointSize()+2)
#        fm = QtGui.QFontMetrics(font)
#        self.muTdEdit.setMinimumSize(fm.width("8.88"), fm.height())
        self.areaLabel = QLabel("")
        self.areaEdit = QLineEdit()
        self.areaEdit.setPlaceholderText('1.33 for \u230013mm ')
#        self.areaEdit.setMinimumSize(fm.width("8.88"), fm.height())
        self.dmudLabel = QLabel(u"δµd = ")
        self.dmudEdit = QLineEdit()
#        self.dmudEdit.setMinimumSize(fm.width("8.888"), fm.height())

        self.energyLabel = QLabel("E (eV) =")
        self.energyCB = QComboBox()
        self.read_energies()
        self.energyCB.addItems(self.energies)
        self.energyCB.setEditable(True)
        self.energyCB.currentIndexChanged.connect(self.energySelected)
        self.energy = 9029
        self.energyCB.lineEdit().setText(str(self.energy))

        self.tableLabel = QLabel("f data table:")
        self.tableCB = QComboBox()
        self.tableCB.addItems(tables)
        self.tableCB.setCurrentIndex(3)  # Chantler total
        self.tableCB.currentIndexChanged.connect(self.calculate)
        self.tablePlotButton = QPushButton('Plot f"')
        if not MAC:
            self.tablePlotButton.setFocusPolicy(QtCore.Qt.NoFocus)

        self.tablePlotButton.clicked.connect(self.plotf)

        self.resNuLabel = QLabel("")
        self.resNu = QLabel("")
        self.resMassLabel = QLabel("")
        self.resMass = QLabel("")

        self.stepLabel = QLabel("absorptance step = ")
        self.stepCB = QComboBox()
        self.stepCB.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
#        self.stepCB.setMinimumWidth(200)

        self.extraRhoLabel = QLabel(u"ρ (g/cm<sup>3</sup>) = ")
        self.extraRhoEdit = QLineEdit()
#        self.extraRhoEdit.setMinimumSize(fm.width("8.88"), fm.height())
        self.extraRhoEdit.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.extraDLabel = QLabel(u"d (µm) = ")
        self.extraD = QLabel(u"")

        self.buttonCalculate = QPushButton("Calculate")
        self.buttonCalculate.clicked.connect(self.calculate)
        self.buttonAbout = QPushButton("About...")
        self.buttonAbout.clicked.connect(self.about)
        self.buttonHelp = QPushButton("Help...")
        self.buttonHelp.clicked.connect(self.myhelp)

        whatLayout = QHBoxLayout()
        whatLayout.addWidget(self.whatLabel)
        whatLayout.addWidget(self.whatCB)
        whatLayout.addStretch()

        compoundLayout = QHBoxLayout()
        compoundLayout.addWidget(self.compoundLabel)
        compoundLayout.addWidget(self.compoundExLabel)
        compoundLayout.addStretch()

        listsLayout = QHBoxLayout()
        # listsLayout.addWidget(self.compoundListsLabel)
        listsLayout.addWidget(self.compoundList1)
        listsLayout.addWidget(self.compoundList2)

        compoundMassLayout = QHBoxLayout()
        compoundMassLayout.addWidget(self.compoundMassLabel)
        compoundMassLayout.addWidget(self.compoundMass)
        compoundMassLayout.addStretch()

        muTdLayout = QHBoxLayout()
        muTdLayout.addWidget(self.muTdLabel)
        muTdLayout.addWidget(self.muTdEdit)
        muTdLayout.addStretch()
        muTdLayout.addWidget(self.areaLabel)
        muTdLayout.addWidget(self.areaEdit)
        muTdLayout.addStretch()
        muTdLayout.addWidget(self.dmudLabel)
        muTdLayout.addWidget(self.dmudEdit)
        muTdLayout.addStretch()

        energyLayout = QHBoxLayout()
        energyLayout.addWidget(self.energyLabel)
        energyLayout.addWidget(self.energyCB)
        energyLayout.addStretch()

        tableLayout = QHBoxLayout()
        tableLayout.addWidget(self.tableLabel)
        tableLayout.addWidget(self.tableCB)
        tableLayout.addStretch()
        tableLayout.addWidget(self.tablePlotButton)
        tableLayout.addStretch()

        resLayout = QHBoxLayout()
        resLayout.addWidget(self.resNuLabel)
        resLayout.addWidget(self.resNu)
        resLayout.addStretch()
        resLayout.addWidget(self.resMassLabel)
        resLayout.addWidget(self.resMass)
        resLayout.addStretch()

        stepLayout = QHBoxLayout()
        stepLayout.addWidget(self.stepLabel)
        stepLayout.addWidget(self.stepCB)
#        stepLayout.addStretch()

        extraLayout = QHBoxLayout()
        extraLayout.addWidget(self.extraRhoLabel)
        extraLayout.addWidget(self.extraRhoEdit)
        extraLayout.addStretch()
        extraLayout.addWidget(self.extraDLabel)
        extraLayout.addWidget(self.extraD)
        extraLayout.addStretch()

        self.gasesInMixture = []
        self.gasesInMixtureN = 0
        if gasMixerN > 0:
            mixerLayout = QVBoxLayout()
            mixerLayout.setContentsMargins(0, 0, 0, 0)
            for iGas in range(gasMixerN):
                gasInMixture = QWidget()
                self.gasesInMixture.append(gasInMixture)
                gasLayout = QHBoxLayout(gasInMixture)
                gasLayout.setContentsMargins(0, 0, 0, 0)

                gasLabel = QLabel()

                gasSlider = QSlider(QtCore.Qt.Horizontal)
                gasSlider.setMinimum(gasMixerPressureMin)
                gasSlider.setMaximum(gasMixerPressureMax)
                gasSlider.setTickPosition(QSlider.TicksAbove)
                gasSlider.setTickInterval(gasMixerPressurePageStep)
                gasSlider.setPageStep(gasMixerPressurePageStep)
                gasSlider.setValue(gasMixerPressureDefault)
                gasSlider.valueChanged.connect(
                    partial(self.gasSliderChanged, iGas))

                gasValue = QLabel(
                    "{0:.0f} mbar".format(gasMixerPressureDefault))
                gasInMixture.pressure = gasMixerPressureDefault

                gasLayout.addWidget(gasLabel)
                gasLayout.addWidget(gasSlider)
                gasLayout.addWidget(gasValue)
                mixerLayout.addWidget(gasInMixture)
            self.calculateGasMixture()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.buttonCalculate)  # 1st button gets Enter
        buttonLayout.addWidget(self.buttonAbout)
        buttonLayout.addWidget(self.buttonHelp)
        buttonLayout.addStretch()

        layout = QVBoxLayout()
        layout.addLayout(whatLayout)
        layout.addWidget(self.formula)
        hline1 = QFrame(self)
        hline1.setFrameStyle(QFrame.HLine)
        layout.addWidget(hline1)
        layout.addLayout(compoundLayout)
        layout.addLayout(listsLayout)
        layout.addWidget(self.compoundEdit)
        layout.addLayout(compoundMassLayout)
        hline2 = QFrame(self)
        hline2.setFrameStyle(QFrame.HLine)
        layout.addWidget(hline2)
        layout.addLayout(muTdLayout)
        layout.addLayout(energyLayout)
        hline3 = QFrame(self)
        hline3.setFrameStyle(QFrame.HLine)
        layout.addWidget(hline3)
        layout.addLayout(tableLayout)
        hline4 = QFrame(self)
        hline4.setFrameStyle(QFrame.HLine)
        layout.addWidget(hline4)
        layout.addLayout(resLayout)
        layout.addLayout(stepLayout)
        layout.addLayout(extraLayout)
        if gasMixerN > 0:
            layout.addLayout(mixerLayout)
        layout.addStretch()
        layout.addLayout(buttonLayout)
        self.setLayout(layout)

        self.setWindowTitle("XAFSmassQt")
        selfDir = os.path.dirname(__file__)
        self.setWindowIcon(QtGui.QIcon(
            os.path.join(selfDir, 'help', '_static', 'XAFSmassQt.ico')))
        self.setWindowFlags(QtCore.Qt.Window)
        self.move(QApplication.desktop().screen().rect().center() -
                  self.rect().center())
        self.updateUi()
        self.plotDlg = None
#        pg = self.geometry()
#        self.formula.setFixedHeight(pg.width()*0.5/4)

    def sizeHint(self):
        return QtCore.QSize(0, 0)  # set to minimum possible

    def updateUi(self):
        self.what = self.whatCB.currentIndex()
        self.formula.update_formula(formulas[self.what])
        self.compoundExLabel.setText("<i>e.g.</i> " + examples[self.what])

        if self.what in [POWDER, FOIL, XCONTENT]:
            self.muTdLabel.setText(u"µ<sub>T</sub>d = ")
        elif self.what == GAS:
            self.muTdLabel.setText(u"attenuation = ")

        if self.what in [POWDER, FOIL, GAS]:
            self.dmudLabel.hide()
            self.dmudEdit.hide()
        elif self.what == XCONTENT:
            self.dmudLabel.show()
            self.dmudEdit.show()

        if self.what == POWDER:
            self.areaLabel.setText(u"S (cm<sup>2</sup>) = ")
            self.resNuLabel.setText(u"ν (mmol) = ")
            self.resMassLabel.setText("m (mg) = ")
            self.resNuLabel.show()
            self.resNu.show()
        elif self.what == FOIL:
            self.areaLabel.setText(u"ρ (g/cm<sup>3</sup>) = ")
            self.resMassLabel.setText(u"d (µm) = ")
            self.resNuLabel.hide()
            self.resNu.hide()
        elif self.what == GAS:
            self.areaLabel.setText(u"d (cm) = ")
            self.resMassLabel.setText(u"p (mbar) = ")
            self.resNuLabel.hide()
            self.resNu.hide()
        elif self.what == XCONTENT:
            self.areaLabel.setText(u"Δµd = ")
            self.resNuLabel.setText("N<sub>x</sub> = ")
            self.resMassLabel.setText(r"wt%<sub>x</sub> = ")
            self.resNuLabel.show()
            self.resNu.show()

        if self.what in [POWDER, FOIL]:
            self.stepLabel.show()
            self.stepCB.show()
        else:
            self.stepLabel.hide()
            self.stepCB.hide()

        if self.what == POWDER:
            self.extraRhoLabel.show()
            self.extraRhoEdit.show()
            self.extraDLabel.show()
            self.extraD.show()
        else:
            self.extraRhoLabel.hide()
            self.extraRhoEdit.hide()
            self.extraDLabel.hide()
            self.extraD.hide()

        for gas in self.gasesInMixture:
            gas.hide()

        self.resize(0, 0)
        self.calculate()

    def setupGasMixtures(self, action='parse'):
        if action == 'clear':
            for gas in self.gasesInMixture:
                gas.hide()
            return
        if len(self.gasesInMixture) == 0:
            return
        compound = str(self.compoundEdit.text())
        groups = re.findall(r'\((.*?)\)', compound)
        self.gasesInMixtureN = len(groups)
        if self.gasesInMixtureN == 0:
            self.setupGasMixtures('clear')
            return
        for g, gas in zip(groups, self.gasesInMixture):
            gasLabel = gas.layout().itemAt(0).widget()
            gasLabel.setText(g)
            gas.show()
        for gas in self.gasesInMixture[len(groups):]:
            gas.hide()

    def gasSliderChanged(self, gasNo, value):
        gasValue = self.gasesInMixture[gasNo].layout().itemAt(2).widget()
        self.gasesInMixture[gasNo].pressure = value
        gasValue.setText("{0:.0f} mbar".format(value))
        self.calculateGasMixture()

    def calculateGasMixture(self):
        if self.gasesInMixtureN == 0:
            return
        try:
            E = float(self.energyCB.lineEdit().text())
            length = float(self.areaEdit.text())
        except ValueError:
            return
        table = tablesF[self.tableCB.currentIndex()]
        nus2 = 0.
        sumPressure = 0.
        for gasNo in range(self.gasesInMixtureN):
            gasLabel = self.gasesInMixture[gasNo].layout().itemAt(0).widget()
            compound = str(gasLabel.text())
            parsedCompound = xc.formula.parseString(compound, parseAll=True)
            formulaList = parsedCompound.asList()
            if isinstance(formulaList[0], dict):
                sumSigma2 = formulaList[1]
            else:
                res = xc.calculate_element_dict(formulaList, E, table=table)
                if isinstance(res, str):
                    return res
                sumSigma2 = res[1]
            nu = self.gasesInMixture[gasNo].pressure / (xc.R*xc.T)*length*1e-4
            nus2 += nu * sumSigma2
            sumPressure += self.gasesInMixture[gasNo].pressure
        attenuation = 1 - np.exp(-nus2)
        self.muTdEdit.setText("{0:.3f}".format(attenuation))
        self.resMass.setText("<b>{0:.0f}</b>".format(sumPressure))
        compound = ""
        for gasNo in range(self.gasesInMixtureN):
            gasLabel = self.gasesInMixture[gasNo].layout().itemAt(0).widget()
            normPressure = self.gasesInMixture[gasNo].pressure/sumPressure
            if abs(normPressure - 1) < 1e-3:
                textNormPressure = ''
            else:
                textNormPressure = xc.round_to_n(normPressure, 3)
            compound += "({0}){1}".format(gasLabel.text(), textNormPressure)
        self.compoundEdit.setText(compound)

    def read_energies(self):
        selfDir = os.path.dirname(__file__)
        efname = os.path.join(selfDir, 'data', 'Energies.txt')
        with open(efname, 'r') as f:
            f.readline()
            f.readline()
            self.energies = []
            for line in f.readlines():
                cs = line.strip().split()
                if len(cs[0]) == 1:
                    cs[0] = '0' + cs[0]
                pre = cs[0] + ' ' + cs[1] + ' '
                for ic, c in enumerate(cs[2:]):
                    self.energies.append(pre + edges[ic] + ' ' + c + ' + 50')

    def compoundActivated(self):
        txt = str(self.compoundList1.currentText())  # otherwise u'' in Py2
        self.compoundEdit.setText(compounds[txt]['formula'])
        if self.what in [POWDER, FOIL]:
            rho = compounds[txt]['rho']
            if self.what == POWDER:
                self.extraRhoEdit.setText(str(rho))
            elif self.what == FOIL:
                self.areaEdit.setText(str(rho))
        self.calculate()

    def elementActivated(self):
        txt = str(self.compoundList2.currentText())  # otherwise u'' in Py2
        self.compoundEdit.setText(txt)
        if self.what in [POWDER, FOIL]:
            rho = elemental[txt]['rho']
            if self.what == POWDER:
                self.extraRhoEdit.setText(str(rho))
            elif self.what == FOIL:
                self.areaEdit.setText(str(rho))
        self.calculate()

    def energySelected(self):
        txt = self.energyCB.lineEdit().text()
        try:
            self.energy = float(txt)
        except ValueError:
            st = str(txt).strip().split()
            if len(st) == 6:
                e = float(st[3]) + float(st[5])
                e = int(np.floor(e)) if e == np.floor(e) else e
                self.energy = e
                self.energyCB.lineEdit().setText(str(self.energy))
        self.calculate()

    def clear_results(self):
        self.resNu.setText("")
        self.resMass.setText("")
        self.extraD.setText("")

    def parse_compound(self):
        compound = str(self.compoundEdit.text())
        self.parsedCompound = None
        if len(compound) == 0:
            self.compoundMass.setText("")
            return
        try:
            parsedCompound = xc.formula.parseString(compound, parseAll=True)
            withX = False
            cMass = 0.
            for e in parsedCompound.asList():
                if e[1] < 0:
                    withX = True
                else:
                    cMass += xc.read_atomic_data(e[0])*e[1]
            if withX:
                self.compoundMass.setText("(of matrix) {0}".format(
                    xc.round_to_n(cMass, 5)))
            elif '%' in compound:
                self.compoundEdit.setText(xc.reconstruct(parsedCompound))
                self.calculate()
                return
            else:
                self.compoundMass.setText(
                    '{0}'.format(xc.round_to_n(cMass, 5)))
        except (ParseBaseException, ValueError, ZeroDivisionError):
            self.compoundMass.setText("wrong compound formula")
            return

        if (self.what == XCONTENT) ^ withX:
            if (self.what == XCONTENT):
                outStr = ", give 'x'"
            else:
                outStr = ", remove 'x'"
            self.compoundMass.setText("wrong compound formula"+outStr)
            self.clear_results()
            return

        self.parsedCompound = parsedCompound
        return True

    def calculate(self):
        if not self.parse_compound():
            if self.what == GAS:
                self.setupGasMixtures('clear')
            return
        if self.what == GAS:
            self.setupGasMixtures()
        try:
            E = float(self.energyCB.lineEdit().text())
        except ValueError:
            return
        table = tablesF[self.tableCB.currentIndex()]
        if self.plotDlg is not None:
            self.plotDlg.plotCanvas.plot(self.parsedCompound, E, table)

        self.stepCB.clear()
        try:
            xxx = float(self.muTdEdit.text())
            if xxx < 0:
                xxx = abs(xxx)
                self.muTdEdit.setText(repr(xxx))
            yyy = float(self.areaEdit.text())
            if yyy < 0:
                yyy = abs(yyy)
                self.areaEdit.setText(repr(yyy))
            if self.what == POWDER:
                muTd = xxx
                area = yyy
                rho = self.extraRhoEdit.text()
                if len(rho) == 0:
                    rho = None
                    self.extraD.setText("")
                else:
                    rho = float(rho)
                    if rho < 0:
                        rho = abs(rho)
                        self.extraRhoEdit.setText(repr(rho))
            elif self.what == FOIL:
                muTd = xxx
                rho = yyy
            elif self.what == GAS:
                attenuation = xxx
                if not (0 < attenuation < 1):
                    self.muTdEdit.setText("")
                    self.muTdEdit.setFocus()
                    raise ValueError
                thickness = yyy
            elif self.what == XCONTENT:
                muTd = xxx
                Dmud = yyy
        except ValueError:
            self.clear_results()
            return

        if self.what == XCONTENT:
            try:
                dmud = float(self.dmudEdit.text())
            except ValueError:
                dmud = 0

        if self.what == POWDER:
            res = xc.calculate_powder(
                self.parsedCompound.asList(), E, muTd, area, rho, table=table)
            if isinstance(res, str):
                self.clear_results()
                QMessageBox.critical(self, "Error", res)
                return
            nu, m, th, eDict = res
            self.resNu.setText('{0}'.format(xc.round_to_n(nu, 3)))
            self.resMass.setText("<b>{0}</b>".format(xc.round_to_n(m, 3)))
            if rho is not None:
                self.extraD.setText('{0}'.format(xc.round_to_n(th, 4)))
            iSelect = 0
            for i, (k, v) in enumerate(eDict.items()):
                self.stepCB.addItem("{0}({1}mg={2}wt%): {3}".format(
                    k, xc.round_to_n(v[-2], 3), xc.round_to_n(v[-2]/m*100., 2),
                    xc.round_to_n(v[-1], 3)))
                if v[2] > 0:
                    iSelect = i
            self.stepCB.setCurrentIndex(iSelect)
        elif self.what == FOIL:
            res = xc.calculate_foil(
                self.parsedCompound.asList(), E, muTd, rho, table=table)
            if isinstance(res, str):
                self.clear_results()
                QMessageBox.critical(self, "Error", res)
                return
            th, eDict = res
            self.resMass.setText("<b>{0}</b>".format(xc.round_to_n(th, 3)))
            iSelect = 0
            for i, (k, v) in enumerate(eDict.items()):
                self.stepCB.addItem("{0}: {1}".format(
                    k, xc.round_to_n(v[-1], 3)))
                if v[1] > 0:
                    iSelect = i
            self.stepCB.setCurrentIndex(iSelect)
        elif self.what == GAS:
            res = xc.calculate_gas(
                self.parsedCompound.asList(), E, attenuation, thickness,
                table=table)
            if isinstance(res, str):
                self.clear_results()
                QMessageBox.critical(self, "Error", res)
                return
            P = res
            self.resMass.setText("<b>{0}</b>".format(xc.round_to_n(P, 3)))
        elif self.what == XCONTENT:
            res = xc.calculate_x(
                self.parsedCompound.asList(), E, muTd, Dmud, dmud, table=table)
            if isinstance(res, str):
                self.clear_results()
                QMessageBox.critical(self, "Error", res)
                return
            nu, wt, wt1 = res
            self.resNu.setText('{0}'.format(xc.round_to_n(nu, 3)))
            outStr = '{0}'.format(xc.round_to_n(wt, 5))
            if wt1 > 0:
                outStr += u'±{0}'.format(xc.round_to_n(wt1, 3))
            self.resMass.setText(u"<b>{0}</b>".format(outStr))

    def about(self):
        # https://stackoverflow.com/a/69325836/2696065
        def isWin11():
            return True if sys.getwindowsversion().build > 22000 else False

        import platform
        locos = platform.platform(terse=True)
        if 'Linux' in locos:
            try:
                locos = " ".join(platform.linux_distribution())
            except AttributeError:  # no platform.linux_distribution in py3.8
                try:
                    import distro
                    locos = " ".join(distro.linux_distribution())
                except ImportError:
                    print("do 'pip install distro' for a better view of Linux"
                          " distro string")
        elif 'Windows' in locos:
            if isWin11():
                locos = 'Winows 11'

        if use_pyside:
            Qt_version = QtCore.__version__
            PyQt_version = PySide.__version__
        else:
            Qt_version = QtCore.QT_VERSION_STR
            PyQt_version = QtCore.PYQT_VERSION_STR
        title = "About XAFSmassQt"
        txt = \
            """<b>XAFSmass(Qt)</b> v {0}
            <ul>
            <li>{1[0]}
            <li>{1[1]}
            </ul>
            <p>Open source, {2}. Available at PyPI and GitHub<p>
            <p>Your system:
            <ul>
            <li>{3}
            <li>Python {4}
            <li>Qt {5}
            <li>{6} {7}
            </ul>""".format(
                __version__, __author__.split(','), __license__,
                locos, platform.python_version(),
                Qt_version, QtName, PyQt_version)

        msg = QMessageBox()
        msg.setStyleSheet("#qt_msgbox_label{min-width: 400px;}")
        msg.setWindowIcon(self.windowIcon())
        msg.setIconPixmap(QPixmap(os.path.join(
             'help', '_static', 'XAFSmassQt.ico')))
        msg.setText(txt)
        msg.setWindowTitle(title)
        msg.exec_()

    def myhelp(self):
        hname = os.path.join(os.path.dirname(__file__), 'help', 'index.html')
        webbrowser.open(hname)

    def plotf(self):
        self.parse_compound()
        E = float(self.energyCB.lineEdit().text())
        table = tablesF[self.tableCB.currentIndex()]
        if self.plotDlg is None:
            self.plotDlg = PlotDlg(self, self.parsedCompound, E, table)
        else:
            self.plotDlg.plotCanvas.plot(self.parsedCompound, E, table)


def run():
    app = QApplication(sys.argv)
    form = MainDlg()
    form.show()
    app.exec_()


if __name__ == "__main__":
    run()
