Skip to content

Commit

Permalink
Basic Qt6 support
Browse files Browse the repository at this point in the history
Co-authored-by: Jarrett Johnson <[email protected]>
Co-authored-by: Thomas Holder <[email protected]>
  • Loading branch information
JarrettSJohnson and speleo3 committed Aug 16, 2024
1 parent 97a53fb commit 996b701
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 72 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
shell: cmd
run: |-
CALL %CONDA_ROOT%\\Scripts\\activate.bat
conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyqt glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest pip python-build
conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyside6 glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest pip python-build
- name: Conda info
shell: cmd
Expand Down Expand Up @@ -117,7 +117,7 @@ jobs:
bash $CONDA_ROOT.sh -b -p $CONDA_ROOT
export PATH="$CONDA_ROOT/bin:$PATH"
conda config --set quiet yes
conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyqt glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest pip python-build
conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyside6 glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest pip python-build
conda info
- name: Get additional sources
Expand Down
2 changes: 1 addition & 1 deletion INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ REQUIREMENTS
- msgpack-c 2.1.5+ (optional, for fast MMTF loading and export,
disable with --use-msgpackc=no)
- mmtf-cpp (for fast MMTF export, disable with --use-msgpackc=no)
- PyQt5, PyQt4, PySide2 or PySide (optional, will fall back to Tk
- PyQt5, PyQt6, PySide2 or PySide6 (optional, will fall back to Tk
interface if compiled with --glut)
- glm
- catch2 (optional, enable with --testing)
Expand Down
2 changes: 1 addition & 1 deletion modules/pmg_qt/TextEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,4 @@ def _edit_pymolrc(app, _list=()):
filename = ''
app = QtWidgets.QApplication(['Test'])
edit_pymolrc()
app.exec_()
app.exec()
2 changes: 1 addition & 1 deletion modules/pmg_qt/file_dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def ask_partial(parent, kwargs, fname):
form.check_rename.setChecked(parent.cmd.get_setting_boolean(
'auto_rename_duplicate_objects'))

if not form._dialog.exec_():
if not form._dialog.exec():
return False

if form.check_partial.isChecked():
Expand Down
35 changes: 1 addition & 34 deletions modules/pmg_qt/forms/render.ui
Original file line number Diff line number Diff line change
Expand Up @@ -419,38 +419,5 @@ alpha-channel background.</string>
<tabstop>button_back</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>input_units</sender>
<signal>currentIndexChanged(QString)</signal>
<receiver>input_height_units</receiver>
<slot>setSuffix(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>102</x>
<y>119</y>
</hint>
<hint type="destinationlabel">
<x>237</x>
<y>57</y>
</hint>
</hints>
</connection>
<connection>
<sender>input_units</sender>
<signal>currentIndexChanged(QString)</signal>
<receiver>input_width_units</receiver>
<slot>setSuffix(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>102</x>
<y>119</y>
</hint>
<hint type="destinationlabel">
<x>237</x>
<y>30</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>
8 changes: 5 additions & 3 deletions modules/pmg_qt/pymol_gl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
# no stereo support)
USE_QOPENGLWIDGET = int(
os.getenv("PYMOL_USE_QOPENGLWIDGET") or
(pymol.IS_MACOS and QtCore.QT_VERSION >= 0x50400))
(QtCore.QT_VERSION >= 0x50400 and pymol.IS_MACOS or
QtCore.QT_VERSION >= 0x60000))

if USE_QOPENGLWIDGET:
BaseGLWidget = QtWidgets.QOpenGLWidget
Expand Down Expand Up @@ -167,9 +168,10 @@ def gestureEvent(self, ev):
return True

def _event_x_y_mod(self, ev):
pos = ev.position() if hasattr(ev, "position") else ev.pos()
return (
int(self.fb_scale * ev.x()),
int(self.fb_scale * (self.height() - ev.y())),
int(self.fb_scale * pos.x()),
int(self.fb_scale * (self.height() - pos.y())),
get_modifiers(ev),
)

Expand Down
12 changes: 9 additions & 3 deletions modules/pmg_qt/pymol_qt_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,9 @@ def _():
# some experimental window control
menu = self.menudict['Display'].addSeparator()
menu = self.menudict['Display'].addMenu('External GUI')
menu.addAction('Toggle floating', self.toggle_ext_window_dockable,
QtGui.QKeySequence('Ctrl+E'))
menu.addAction('Toggle dockable', self.toggle_ext_window_dockable).setShortcut(
QtGui.QKeySequence('Ctrl+E'))

ext_vis_action = self.ext_window.toggleViewAction()
ext_vis_action.setText('Visible')
menu.addAction(ext_vis_action)
Expand Down Expand Up @@ -757,6 +758,10 @@ def run_copy_clipboard():
form.input_dpi.setEditText(str(dpi))
form.input_dpi.setValidator(QtGui.QIntValidator())

# This connection used to be in the .ui file, but that fails with Qt6
form.input_units.currentTextChanged.connect(lambda s: form.input_height_units.setSuffix(s))
form.input_units.currentTextChanged.connect(lambda s: form.input_width_units.setSuffix(s))

form.input_units.currentIndexChanged.connect(update_units)
form.input_dpi.editTextChanged.connect(update_pixels)
form.input_width.valueChanged.connect(update_units)
Expand All @@ -782,6 +787,7 @@ def run_copy_clipboard():

if widget is None:
form._dialog.show()
return form

@PopupOnException.decorator
def _file_save(self, filter, format):
Expand Down Expand Up @@ -1255,4 +1261,4 @@ def _call_with_opengl_context_gui_thread(func):
if options.plugins:
window.initializePlugins()

app.exec_()
app.exec()
80 changes: 60 additions & 20 deletions modules/pymol/Qt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@
http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html
"""

DEBUG = False
DEBUG = True # Turn off for open-source

PYQT_NAME = None
QtWidgets = None

try:
from pymol._Qt_pre import *
except ImportError:
if DEBUG:
print('import _Qt_pre failed')

PYQT_NAME = None
QtWidgets = None
Expand Down Expand Up @@ -39,26 +48,23 @@
if DEBUG:
print('import PySide2 failed')

if not PYQT_NAME and qt_api in ('', 'pyqt4'):
if not PYQT_NAME and qt_api in ('', 'pyqt6'):
try:
try:
import PyQt4.sip as sip
except ImportError:
import sip
sip.setapi("QString", 2)
from PyQt4 import QtGui, QtCore, QtOpenGL
PYQT_NAME = 'PyQt4'
from PyQt6 import QtGui, QtCore, QtOpenGL, QtWidgets
from PyQt6 import QtOpenGLWidgets
PYQT_NAME = 'PyQt6'
except ImportError:
if DEBUG:
print('import PyQt4 failed')
print('import PyQt6 failed')

if not PYQT_NAME and qt_api in ('', 'pyside'):
if not PYQT_NAME and qt_api in ('', 'pyside6'):
try:
from PySide import QtGui, QtCore, QtOpenGL
PYQT_NAME = 'PySide'
from PySide6 import QtGui, QtCore, QtOpenGL, QtWidgets
from PySide6 import QtOpenGLWidgets
PYQT_NAME = 'PySide6'
except ImportError:
if DEBUG:
print('import PySide failed')
print('import PySide6 failed')

if not PYQT_NAME:
raise ImportError(__name__)
Expand All @@ -74,12 +80,46 @@
else:
QtCoreModels = QtGui

if PYQT_NAME == 'PyQt4':
QFileDialog = QtWidgets.QFileDialog
QFileDialog.getOpenFileName = QFileDialog.getOpenFileNameAndFilter
QFileDialog.getOpenFileNames = QFileDialog.getOpenFileNamesAndFilter
QFileDialog.getSaveFileName = QFileDialog.getSaveFileNameAndFilter
del QFileDialog
if PYQT_NAME.endswith('6'):
QtWidgets.QOpenGLWidget = QtOpenGLWidgets.QOpenGLWidget
QtWidgets.QActionGroup = QtGui.QActionGroup
QtWidgets.QAction = QtGui.QAction
QtWidgets.QShortcut = QtGui.QShortcut
QtCore.QSortFilterProxyModel.setFilterRegExp = QtCore.QSortFilterProxyModel.setFilterRegularExpression
QtGui.QFont.Monospace = QtGui.QFont.StyleHint.Monospace

def copy_attributes(target_class, source_class):
for attr in dir(source_class):
if not attr.startswith('_'):
setattr(target_class, attr, getattr(source_class, attr))

copy_attributes(QtCore.QEvent, QtCore.QEvent.Type)
copy_attributes(QtCore.Qt, QtCore.Qt.AlignmentFlag)
copy_attributes(QtCore.Qt, QtCore.Qt.CaseSensitivity)
copy_attributes(QtCore.Qt, QtCore.Qt.CheckState)
copy_attributes(QtCore.Qt, QtCore.Qt.ContextMenuPolicy)
copy_attributes(QtCore.Qt, QtCore.Qt.DockWidgetArea)
copy_attributes(QtCore.Qt, QtCore.Qt.FocusPolicy)
copy_attributes(QtCore.Qt, QtCore.Qt.GestureType)
copy_attributes(QtCore.Qt, QtCore.Qt.ItemFlag)
copy_attributes(QtCore.Qt, QtCore.Qt.Key)
copy_attributes(QtCore.Qt, QtCore.Qt.KeyboardModifier)
copy_attributes(QtCore.Qt, QtCore.Qt.MouseButton)
copy_attributes(QtCore.Qt, QtCore.Qt.Orientation)
copy_attributes(QtCore.Qt, QtCore.Qt.WindowType)
copy_attributes(QtGui.QFont, QtGui.QFont.StyleHint)
copy_attributes(QtWidgets.QAbstractItemView, QtWidgets.QAbstractItemView.ScrollHint)
copy_attributes(QtWidgets.QAbstractItemView, QtWidgets.QAbstractItemView.SelectionBehavior)
copy_attributes(QtWidgets.QAbstractItemView, QtWidgets.QAbstractItemView.SelectionMode)
copy_attributes(QtWidgets.QBoxLayout, QtWidgets.QBoxLayout.Direction)
copy_attributes(QtWidgets.QMainWindow, QtWidgets.QMainWindow.DockOption)
copy_attributes(QtWidgets.QOpenGLWidget, QtOpenGLWidgets.QOpenGLWidget.UpdateBehavior)
copy_attributes(QtWidgets.QSizePolicy, QtWidgets.QSizePolicy.Policy)
copy_attributes(QtWidgets.QTreeWidgetItem, QtWidgets.QTreeWidgetItem.ChildIndicatorPolicy)

QtCore.Qt.MidButton = QtCore.Qt.MiddleButton
QtCore.Qt.WA_LayoutUsesWidgetRect = QtCore.Qt.WidgetAttribute.WA_LayoutUsesWidgetRect


if PYQT_NAME[:4] == 'PyQt':
QtCore.Signal = QtCore.pyqtSignal
Expand Down
13 changes: 8 additions & 5 deletions modules/pymol/Qt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ def setSetupUi(self, setupUi):
@self.aboutToShow.connect
def _():
self.aboutToShow.disconnect()
widget = QtWidgets.QWidget()
setupUi(widget)
self.setWidget(widget)
self._widget = QtWidgets.QWidget()
form = setupUi(self._widget)
self.setWidget(form)

return self

Expand Down Expand Up @@ -272,7 +272,10 @@ def loadUi(uifile, widget):
@type uifile: str
@type widget: QtWidgets.QWidget
"""
if PYQT_NAME.startswith('PyQt'):
if PYQT_NAME == "PySide6":
from PySide6.QtUiTools import QUiLoader
return QUiLoader().load(uifile, widget)
elif PYQT_NAME.startswith('PyQt'):
m = __import__(PYQT_NAME + '.uic')
return m.uic.loadUi(uifile, widget)
elif PYQT_NAME == 'PySide2':
Expand Down Expand Up @@ -343,7 +346,7 @@ def __exit__(self, exc_type, e, tb):
msg = str(e) or 'unknown error'
msgbox = QMB(QMB.Critical, 'Error', msg, QMB.Close, parent)
msgbox.setDetailedText(''.join(traceback.format_tb(tb)))
msgbox.exec_()
msgbox.exec()

return True

Expand Down
2 changes: 1 addition & 1 deletion modules/pymol/diagnosing.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def diagnostics_qt():
from pymol.Qt import QtCore
return u'{} {} (Qt {})\n'.format(
QtCore.__name__.split('.')[0],
QtCore.PYQT_VERSION_STR,
getattr(QtCore, "PYQT_VERSION_STR", "<N/A>"),
QtCore.QT_VERSION_STR)
except Exception as e:
return u'({})\n'.format(e)
Expand Down
2 changes: 1 addition & 1 deletion modules/pymol/plugins/managergui_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ def add_line(label, text):
table.resizeColumnsToContents()

dialog.resize(600, dialog.height())
dialog.exec_()
dialog.exec()

def add_path(self):
from .installation import get_default_user_plugin_path as userpath
Expand Down

0 comments on commit 996b701

Please sign in to comment.