Source code for tools.doi

#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''Pychemqt, Chemical Engineering Process simulator
Copyright (C) 2009-2025, Juan José Gómez Romera <jjgomera@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.


Tool to show the academic references for the calculation methods implemented \
in the code.

.. image:: images/tools_doi.png
    :alt: tools/doi

Double clik over a element open the link with the paper, or if the file is \
available in local system open the document. By default search the file with \
the doi code name or the title if doi is unavailable in the folder doc of \
main program.

`Here <references.html>`__ is the complete list of references.


API reference
-------------

  * :class:`ShowReference`: Dialog to show the references used in the program

Miscellaneous utilities

  * :class:`QLineEditClickable`: Custom QLineEdit to catch Enter key and set \
  focus to list and avoid close dialog
  * :class:`QTreeWidgetClickable`: Custom QTreeWidget to catch Enter key and \
  open file with it
  * :func:`findFile`: Search reference paper path in documentation forder \
  and return boolean if it's available

'''


import os

from tools.qt import QtCore, QtGui, QtWidgets
from tools.pdf import openPDF

import lib
from lib.config import IMAGE_PATH
from equipment import equipments, widget


[docs] class QLineEditClickable(QtWidgets.QLineEdit): """Custom QLineEdit to catch Enter key and set focus to list and avoid close dialog"""
[docs] def keyPressEvent(self, event): """Rewrite keyPressEvent to setFocus when Enter key is pressed""" if event.key() == QtCore.Qt.Key.Key_Return: self.parent().parent().tree.setFocus() else: super().keyPressEvent(event)
[docs] class QTreeWidgetClickable(QtWidgets.QTreeWidget): """Custom QTreeWidget to catch Enter key and open file with it"""
[docs] def keyPressEvent(self, event): """Rewrite keyPressEvent to expand or open item""" if event.key() == QtCore.Qt.Key.Key_Return: if self.currentItem().childCount(): self.currentItem().setExpanded(True) else: self.parent().open(self.currentItem()) else: super().keyPressEvent(event)
[docs] class ShowReference(QtWidgets.QDialog): """Dialog to show the references used in the program""" searchIndex = -1 searchResults = []
[docs] def __init__(self, parent=None): super().__init__(parent) self.setWindowIcon(QtGui.QIcon(QtGui.QPixmap( os.path.join(IMAGE_PATH, "button", "help.png")))) self.setWindowTitle(self.tr("Reference Paper Show Dialog")) layout = QtWidgets.QGridLayout(self) self.tree = QTreeWidgetClickable() header = QtWidgets.QTreeWidgetItem( ["id", self.tr("Autor"), self.tr("Title"), self.tr("Reference"), self.tr("doi")]) self.tree.setHeaderItem(header) layout.addWidget(self.tree, 1, 1, 2, 2) self.searchWidget = QtWidgets.QWidget() self.searchWidget.setMaximumSize(200, 25) searchlayout = QtWidgets.QHBoxLayout(self.searchWidget) searchlayout.setSpacing(0) searchlayout.setContentsMargins(0, 0, 0, 0) self.searchTxt = QLineEditClickable() self.searchTxt.textChanged.connect(self.search) searchlayout.addWidget(self.searchTxt) self.btnPrevious = QtWidgets.QToolButton() self.btnPrevious.setIcon(QtGui.QIcon(QtGui.QPixmap( os.path.join(IMAGE_PATH, "button", "arrow-left.png")))) self.btnPrevious.setEnabled(False) self.btnPrevious.clicked.connect(self.searchPrevious) searchlayout.addWidget(self.btnPrevious) self.btnNext = QtWidgets.QToolButton() self.btnNext.setIcon(QtGui.QIcon(QtGui.QPixmap( os.path.join(IMAGE_PATH, "button", "arrow-right.png")))) self.btnNext.setEnabled(False) self.btnNext.clicked.connect(self.searchNext) searchlayout.addWidget(self.btnNext) self.searchWidget.hide() layout.addWidget(self.searchWidget, 3, 1) bttBox = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.StandardButton.Close) bttBox.rejected.connect(self.reject) layout.addWidget(bttBox, 3, 2) shSearch = QtGui.QShortcut(QtGui.QKeySequence.StandardKey.Find, self) shSearch.activated.connect(self.enableSearch) shHide = QtGui.QShortcut(QtGui.QKeySequence.StandardKey.Cancel, self) shHide.activated.connect(self.disableSearch) self.shNext = QtGui.QShortcut( QtGui.QKeySequence.StandardKey.FindNext, self) self.shNext.activated.connect(self.searchNext) self.shNext.setEnabled(False) self.shPrevious = QtGui.QShortcut( QtGui.QKeySequence.StandardKey.FindPrevious, self) self.shPrevious.activated.connect(self.searchPrevious) self.shPrevious.setEnabled(False) self.fill() self.tree.sortItems(0, QtCore.Qt.SortOrder.AscendingOrder) self.tree.itemDoubleClicked.connect(self.open) self.tree.setColumnWidth(0, 200) self.tree.setColumnWidth(1, 200) self.tree.setColumnWidth(2, 200) self.tree.setColumnWidth(3, 200)
# Search functionality
[docs] def enableSearch(self): """Show search widgets""" self.searchWidget.show() self.searchTxt.setFocus(QtCore.Qt.FocusReason.ShortcutFocusReason)
[docs] def disableSearch(self): """Hide search widgets""" self.searchWidget.hide() self.searchIndex = -1 self.searchResults = [] self.shNext.setEnabled(False) self.shPrevious.setEnabled(False)
[docs] def search(self, txt): """Search txt in tree widget contents""" self.searchIndex = -1 self.searchResults = [] for col in range(4): flags = (QtCore.Qt.MatchFlag.MatchContains | QtCore.Qt.MatchFlag.MatchRecursive) self.searchResults += self.tree.findItems(txt, flags, col) # Enable navitation in search results if search if successful if self.searchResults: self.searchNext() if len(self.searchResults) > 1: self.shNext.setEnabled(True) self.shPrevious.setEnabled(True) self.searchNext() self.btnPrevious.setEnabled(True) self.btnNext.setEnabled(True) else: self.shNext.setEnabled(False) self.shPrevious.setEnabled(False) self.btnPrevious.setEnabled(False) self.btnNext.setEnabled(False)
[docs] def searchPrevious(self): """Set previous result in search""" self.searchIndex -= 1 if self.searchIndex < 0: self.searchIndex = len(self.searchResults)-1 self.tree.setCurrentItem(self.searchResults[self.searchIndex])
[docs] def searchNext(self): """Set next result in search""" self.searchIndex += 1 if self.searchIndex >= len(self.searchResults): self.searchIndex = 0 self.tree.setCurrentItem(self.searchResults[self.searchIndex]) self.tree.scrollToItem( self.searchResults[self.searchIndex], QtWidgets.QAbstractItemView.ScrollHint.PositionAtCenter)
[docs] def fill(self): """Fill tree with documentation entries""" # General population Library itemLibrary = QtWidgets.QTreeWidgetItem([self.tr("library")]) self.tree.addTopLevelItem(itemLibrary) itemLibrary.setExpanded(True) for library in lib.__all__: __import__(f"lib.{library}") module = lib.__getattribute__(library) if hasattr(module, "__doi__") and module.__doi__: itemModule = QtWidgets.QTreeWidgetItem(["lib/"+library]) itemLibrary.addChild(itemModule) for key in sorted(module.__doi__.keys()): if key == "lib.EoS.Cubic": itemSubModule = QtWidgets.QTreeWidgetItem([key]) itemModule.addChildren([itemSubModule]) for key2 in sorted(module.__doi__[key].keys()): itemSubModule2 = QtWidgets.QTreeWidgetItem([key2]) itemSubModule.addChildren([itemSubModule2]) for link in module.__doi__[key][key2]: item = QtWidgets.QTreeWidgetItem([ "", link["autor"], link["title"], link["ref"], link["doi"]]) if findFile(link): icon = QtGui.QIcon(QtGui.QPixmap( os.path.join( IMAGE_PATH, "button", "ok.png"))) item.setIcon(0, icon) itemSubModule2.addChild(item) # Special case for submodules elif library in ["EoS", "mEoS", "newComponent"]: itemSubModule = QtWidgets.QTreeWidgetItem([key]) itemModule.addChildren([itemSubModule]) if library == "EoS": itemModule.setExpanded(True) for key2 in sorted(module.__doi__[key].keys()): link = module.__doi__[key][key2] if key == "lib.EoS.Cubic": title = key2 elif library == "EoS": title = "" elif library == "newComponent": title = "" else: title = key2.replace("_", "") item = QtWidgets.QTreeWidgetItem([ title, link["autor"], link["title"], link["ref"], link["doi"]]) if findFile(link): icon = QtGui.QIcon(QtGui.QPixmap(os.path.join( IMAGE_PATH, "button", "ok.png"))) item.setIcon(0, icon) itemSubModule.addChild(item) else: link = module.__doi__[key] if isinstance(key, int): header = "" else: header = key item = QtWidgets.QTreeWidgetItem([ header, link["autor"], link["title"], link["ref"], link["doi"]]) if findFile(link): icon = QtGui.QIcon(QtGui.QPixmap(os.path.join( IMAGE_PATH, "button", "ok.png"))) item.setIcon(0, icon) itemModule.addChild(item) # Show item sorted by autor in libraries if "EoS" not in library: itemModule.sortChildren( 1, QtCore.Qt.SortOrder.AscendingOrder) # Equipment itemEquipment = QtWidgets.QTreeWidgetItem([self.tr("Equipments")]) self.tree.addTopLevelItem(itemEquipment) itemEquipment.setExpanded(True) for equip in equipments: itemequip = QtWidgets.QTreeWidgetItem([equip.__name__]) itemEquipment.addChild(itemequip) for link in equip.__doi__: item = QtWidgets.QTreeWidgetItem([ "", link["autor"], link["title"], link["ref"], link["doi"]]) if findFile(link): icon = QtGui.QIcon(QtGui.QPixmap(os.path.join( IMAGE_PATH, "button", "ok.png"))) item.setIcon(0, icon) itemequip.addChild(item) # Add accesorios subfolder itemAccesories = QtWidgets.QTreeWidgetItem([self.tr("Accesories")]) itemEquipment.addChild(itemAccesories) itemAccesories.setExpanded(True) for equip in widget.__all__: itemequip = QtWidgets.QTreeWidgetItem([equip]) itemAccesories.addChild(itemequip) module = widget.__getattribute__(equip) for idx, link in module.__doi__.items(): item = QtWidgets.QTreeWidgetItem([ "", link["autor"], link["title"], link["ref"], link["doi"]]) if findFile(link): icon = QtGui.QIcon(QtGui.QPixmap(os.path.join( IMAGE_PATH, "button", "ok.png"))) item.setIcon(0, icon) itemequip.addChild(item)
[docs] def open(self, item): """Open file if exist in doc/doi folder or open a browser with link""" if item.parent() and not item.icon(0).isNull(): title = item.text(2) text = item.text(4) code = str(text).replace("/", "_") file = os.path.join("doc", code) + ".pdf" file2 = os.path.join("doc", title) + ".pdf" if os.path.isfile(file): openPDF(file, title) elif os.path.isfile(file2): openPDF(file2, title) elif item.parent(): url = QtCore.QUrl(f"http://dx.doi.org/{item.text(4)}") QtGui.QDesktopServices.openUrl(url)
[docs] def findFile(ref): """Search reference paper path in documentation forder and return boolean if it's available""" code = ref["doi"].replace("/", "_") file = os.path.join("doc", code) + ".pdf" file2 = os.path.join("doc", ref["title"]) + ".pdf" return os.path.isfile(file) or os.path.isfile(file2)
if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) dialog = ShowReference() dialog.show() sys.exit(app.exec())