def root(self): if self._root is None: from pymol.Qt import QtCore tkinter_init() # create Tk instance in this thread self._root = tkinter.Tk() self._root.tk = tkapp_proxy(self._root.tk, self) self._root.withdraw() # feed Tk event loop from this thread timer = QtCore.QTimer() @timer.timeout.connect def _(): if not self._tk_update_paused: self._root.update() timer.start() timer.setSingleShot(True) timer.start(50) # keep reference to timer self._tk_update_timer = timer import Pmw Pmw.initialise(self._root) return self._root
def __init__(self, parent): self.gui = parent self.fb_scale = 1.0 # OpenGL context setup if USE_QOPENGLWIDGET: f = QtGui.QSurfaceFormat() else: f = QtOpenGL.QGLFormat() from pymol.invocation import options # logic equivalent to layer5/main.cpp:launch if options.multisample: f.setSamples(4) if options.force_stereo != -1: # See layer1/Setting.h for stereo modes if options.stereo_mode in (1, 12) or ( options.stereo_mode == 0 and AUTO_DETECT_STEREO): f.setStereo(True) if options.stereo_mode in (11, 12) and not USE_QOPENGLWIDGET: f.setAccum(True) if USE_QOPENGLWIDGET: super(PyMOLGLWidget, self).__init__(parent=parent) self.setFormat(f) self.setUpdateBehavior(QtWidgets.QOpenGLWidget.PartialUpdate) else: super(PyMOLGLWidget, self).__init__(f, parent=parent) # pymol instance self.pymol = PyMOL() self.pymol.start() self.cmd = self.pymol.cmd # capture python output for feedback import pcatch pcatch._install() # for passive move drag self.setMouseTracking(True) # for accepting keyboard input (command line, shortcuts) self.setFocusPolicy(Qt.ClickFocus) # for idle rendering self._timer = QtCore.QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self._pymolProcess) # drag n drop self.setAcceptDrops(True) # pinch-zoom self.grabGesture(Qt.PinchGesture)
def __init__(self): # noqa QtWidgets.QMainWindow.__init__(self) self.setDockOptions(QtWidgets.QMainWindow.AllowTabbedDocks | QtWidgets.QMainWindow.AllowNestedDocks) # resize Window before it is shown options = pymol.invocation.options self.resize(options.win_x + (220 if options.internal_gui else 0), options.win_y + (246 if options.external_gui else 18)) # for thread-safe viewport command self.viewportsignal.connect(self.pymolviewport) # reusable dialogs self.dialog_png = None self.advanced_settings_dialog = None self.props_dialog = None self.builder = None # setting index -> callable self.setting_callbacks = defaultdict(list) # "session_file" setting in window title self.setting_callbacks[440].append(lambda v: self.setWindowTitle( "PyMOL (" + os.path.basename(v) + ")")) # "External" Command Line and Loggin Widget self._setup_history() self.lineedit = CommandLineEdit() self.lineedit.setObjectName("command_line") self.browser = QtWidgets.QPlainTextEdit() self.browser.setObjectName("feedback_browser") self.browser.setReadOnly(True) # convenience: clicking into feedback browser gives focus to command # line. Drawback: Copying with CTRL+C doesn't work in feedback # browser -> clear focus proxy while text selected self.browser.setFocusProxy(self.lineedit) @self.browser.copyAvailable.connect def _(yes): self.browser.setFocusProxy(None if yes else self.lineedit) self.browser.setFocus() # Font self.browser.setFont(getMonospaceFont()) connectFontContextMenu(self.browser) lineeditlayout = QtWidgets.QHBoxLayout() command_label = QtWidgets.QLabel("PyMOL>") command_label.setObjectName("command_label") lineeditlayout.addWidget(command_label) lineeditlayout.addWidget(self.lineedit) self.lineedit.setToolTip('''Command Input Area Get the list of commands by hitting <TAB> Get the list of arguments for one command with a question mark: PyMOL> color ? Read the online help for a command with "help": PyMOL> help color Get autocompletion for many arguments by hitting <TAB> PyMOL> color ye<TAB> (will autocomplete "yellow") ''') layout = QtWidgets.QVBoxLayout() layout.addWidget(self.browser) layout.addLayout(lineeditlayout) quickbuttonslayout = QtWidgets.QVBoxLayout() quickbuttonslayout.setSpacing(2) extguilayout = QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight) extguilayout.setContentsMargins(2, 2, 2, 2) extguilayout.addLayout(layout) extguilayout.addLayout(quickbuttonslayout) class ExtGuiFrame(QtWidgets.QFrame): def mouseDoubleClickEvent(_, event): self.toggle_ext_window_dockable(True) _size_hint = QtCore.QSize(options.win_x, options.ext_y) def sizeHint(self): return self._size_hint dockWidgetContents = ExtGuiFrame(self) dockWidgetContents.setLayout(extguilayout) dockWidgetContents.setObjectName("extgui") self.ext_window = \ dockWidget = QtWidgets.QDockWidget(self) dockWidget.setWindowTitle("External GUI") dockWidget.setWidget(dockWidgetContents) if options.external_gui: dockWidget.setTitleBarWidget(QtWidgets.QWidget()) else: dockWidget.hide() self.addDockWidget(Qt.TopDockWidgetArea, dockWidget) # rearrange vertically if docking left or right @dockWidget.dockLocationChanged.connect def _(area): if area == Qt.LeftDockWidgetArea or area == Qt.RightDockWidgetArea: extguilayout.setDirection(QtWidgets.QBoxLayout.BottomToTop) quickbuttonslayout.takeAt(quickbuttons_stretch_index) else: extguilayout.setDirection(QtWidgets.QBoxLayout.LeftToRight) if quickbuttons_stretch_index >= quickbuttonslayout.count(): quickbuttonslayout.addStretch() # OpenGL Widget self.pymolwidget = PyMOLGLWidget(self) self.setCentralWidget(self.pymolwidget) cmd = self.cmd = self.pymolwidget.cmd ''' # command completion completer = QtWidgets.QCompleter(cmd.kwhash.keywords, self) self.lineedit.setCompleter(completer) ''' # overload <Tab> action self.lineedit.installEventFilter(self) self.pymolwidget.installEventFilter(self) # Quick Buttons for row in [ [ ('Reset', cmd.reset), ('Zoom', lambda: cmd.zoom(animate=1.0)), ('Orient', lambda: cmd.orient(animate=1.0)), # render dialog will be constructed when the menu is shown # for the first time. This way it's populated with the current # viewport and settings. Also defers parsing of the ui file. ('Draw/Ray', WidgetMenu(self).setSetupUi(self.render_dialog)), ], [ ('Unpick', cmd.unpick), ('Deselect', cmd.deselect), ('Rock', cmd.rock), ('Get View', self.get_view), ], [ ('|<', cmd.rewind), ('<', cmd.backward), ('Stop', cmd.mstop), ('Play', cmd.mplay), ('>', cmd.forward), ('>|', cmd.ending), ('MClear', cmd.mclear), ], [ ('Builder', self.open_builder_panel), ('Properties', self.open_props_dialog), ('Rebuild', cmd.rebuild), ], ]: hbox = QtWidgets.QHBoxLayout() hbox.setSpacing(2) for name, callback in row: btn = QtWidgets.QPushButton(name) btn.setProperty("quickbutton", True) btn.setAttribute(Qt.WA_LayoutUsesWidgetRect) # OS X workaround hbox.addWidget(btn) if callback is None: btn.setEnabled(False) elif isinstance(callback, QtWidgets.QMenu): btn.setMenu(callback) else: btn.released.connect(callback) quickbuttonslayout.addLayout(hbox) # progress bar hbox = QtWidgets.QHBoxLayout() self.progressbar = QtWidgets.QProgressBar() self.progressbar.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) hbox.addWidget(self.progressbar) self.abortbutton = QtWidgets.QPushButton('Abort') self.abortbutton.setStyleSheet("background: #FF0000; color: #FFFFFF") self.abortbutton.released.connect(cmd.interrupt) hbox.addWidget(self.abortbutton) quickbuttonslayout.addLayout(hbox) quickbuttonslayout.addStretch() quickbuttons_stretch_index = quickbuttonslayout.count() - 1 # menu top level self.menubar = menubar = self.menuBar() # action groups actiongroups = {} def _addmenu(data, menu): '''Fill a menu from "data"''' menu.setTearOffEnabled(True) menu.setWindowTitle(menu.title()) # needed for Windows for item in data: if item[0] == 'separator': menu.addSeparator() elif item[0] == 'menu': _addmenu(item[2], menu.addMenu(item[1].replace('&', '&&'))) elif item[0] == 'command': command = item[2] if command is None: print('warning: skipping', item) else: if isinstance(command, str): command = lambda c=command: cmd.do(c) menu.addAction(item[1], command) elif item[0] == 'check': if len(item) > 4: menu.addAction( SettingAction(self, cmd, item[2], item[1], item[3], item[4])) else: menu.addAction( SettingAction(self, cmd, item[2], item[1])) elif item[0] == 'radio': label, name, value = item[1:4] try: group, type_, values = actiongroups[item[2]] except KeyError: group = QtWidgets.QActionGroup(self) type_, values = cmd.get_setting_tuple(name) actiongroups[item[2]] = group, type_, values action = QtWidgets.QAction(label, self) action.triggered.connect(lambda _=0, args=(name, value): cmd.set(*args, log=1, quiet=0)) self.setting_callbacks[cmd.setting._get_index( name)].append( lambda v, V=value, a=action: a.setChecked(v == V)) group.addAction(action) menu.addAction(action) action.setCheckable(True) if values[0] == value: action.setChecked(True) elif item[0] == 'open_recent_menu': self.open_recent_menu = menu.addMenu('Open Recent...') else: print('error:', item) # recent files menu self.open_recent_menu = None # for plugins self.menudict = {'': menubar} # menu for _, label, data in self.get_menudata(cmd): assert _ == 'menu' menu = menubar.addMenu(label) self.menudict[label] = menu _addmenu(data, menu) # hack for macOS to hide "Edit > Start Dictation" # https://bugreports.qt.io/browse/QTBUG-43217 if pymol.IS_MACOS: self.menudict['Edit'].setTitle('Edit_') QtCore.QTimer.singleShot( 10, lambda: self.menudict['Edit'].setTitle('Edit')) # recent files menu if self.open_recent_menu: @self.open_recent_menu.aboutToShow.connect def _(): self.open_recent_menu.clear() for fname in self.recent_filenames: self.open_recent_menu.addAction( fname if len(fname) < 128 else '...' + fname[-120:], lambda fname=fname: self.load_dialog(fname)) # 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')) ext_vis_action = self.ext_window.toggleViewAction() ext_vis_action.setText('Visible') menu.addAction(ext_vis_action) # extra key mappings (MacPyMOL compatible) QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+O'), self).activated.connect(self.file_open) QtWidgets.QShortcut(QtGui.QKeySequence('Ctrl+S'), self).activated.connect(self.session_save) # feedback self.feedback_timer = QtCore.QTimer() self.feedback_timer.setSingleShot(True) self.feedback_timer.timeout.connect(self.update_feedback) self.feedback_timer.start(100) # legacy plugin system self.menudict['Plugin'].addAction('Initialize Plugin System', self.initializePlugins) # focus in command line if options.external_gui: self.lineedit.setFocus() else: self.pymolwidget.setFocus() # Apply PyMOL stylesheet try: with open( cmd.exp_path('$PYMOL_DATA/pmg_qt/styles/pymol.sty')) as f: style = f.read() except IOError: print('Could not read PyMOL stylesheet.') print('DEBUG: PYMOL_DATA=' + repr(os.getenv('PYMOL_DATA'))) style = "" if style: self.setStyleSheet(style)
def dialog(_self=None): if _self is None: from pymol import cmd as _self dialog = QtWidgets.QDialog() uifile = os.path.join(os.path.dirname(__file__), 'apbs.ui') form = loadUi(uifile, dialog) form._dialog = dialog form._proclist = [] def set_apbs_in(contents): form.apbs_template.setPlainText(contents.strip()) # hide options widgets form.optarea_prep.setVisible(False) form.optarea_apbs.setVisible(False) form.optarea_surf.setVisible(False) form.optarea_other.setVisible(False) # pre-fill form with likely data names = _self.get_object_list() names += ['(' + n + ')' for n in _self.get_names('public_selections')] if names: form.input_sele.clear() form.input_sele.addItems([ ('polymer & ' + name) if _self.count_atoms('polymer & ' + name) > 0 else name for name in names ]) form.surf_map.addItems(_self.get_names_of_type('object:map')) set_apbs_in(electrostatics.template_apbs_in) # executables from distutils.spawn import find_executable form.apbs_exe.setText(electrostatics.find_apbs_exe() or 'apbs') form.pdb2pqr_exe.setText( find_executable('pdb2pqr') or # acellera::htmd-pdb2pqr provides pdb2pqr_cli find_executable('pdb2pqr_cli') or find_executable( 'share/pdb2pqr/pdb2pqr.py', os.getenv('FREEMOL', '/usr')) or 'pdb2pqr') # for async panels form._callInMainThread = MainThreadCaller() run_impl_async = AsyncFunc(run_impl) # "Run" button callback def run(): form.tabWidget.setEnabled(False) form.button_ok.clicked.disconnect() form.button_ok.clicked.connect(abort) form.button_ok.setText('Abort') form._capture = StdOutCapture() # detach from main thread run_impl_async(form, _self) # "Run" button "finally" actions (main thread) @run_impl_async.finished.connect def run_finally(args): _, exception = args form._proclist[:] = [] stdout = form._capture.release() print(stdout) form.button_ok.setText('Run') form.button_ok.clicked.disconnect() form.button_ok.clicked.connect(run) form.button_ok.setEnabled(True) form.tabWidget.setEnabled(True) if exception is not None: handle_exception(exception, stdout) return quit_msg = "Finished with Success. Close the APBS dialog?" if QMessageBox.Yes == QMessageBox.question(form._dialog, 'Finished', quit_msg, QMessageBox.Yes, QMessageBox.No): form._dialog.close() def handle_exception(e, stdout): if isinstance(e, SilentAbort): return msg = str(e) or 'unknown error' msgbox = QMessageBox(QMessageBox.Critical, 'Error', msg, QMessageBox.Close, form._dialog) if stdout.strip(): msgbox.setDetailedText(stdout) msgbox.exec_() # "Abort" button callback def abort(): form.button_ok.setEnabled(False) while form._proclist: p = form._proclist.pop() try: p.terminate() p.returncode = -15 # SIGTERM except OSError as e: print(e) # selection checker check_sele_timer = QtCore.QTimer() check_sele_timer.setSingleShot(True) # grid auto-value form.apbs_grid_userchanged = False form.apbs_grid.setStyleSheet('background: #ff6') @form.apbs_grid.editingFinished.connect def _(): form.apbs_grid_userchanged = True form.apbs_grid.setStyleSheet('') @check_sele_timer.timeout.connect def _(): has_props = ['no', 'no'] def callback(partial_charge, elec_radius): if partial_charge: has_props[0] = 'YES' if elec_radius > 0: has_props[1] = 'YES' n = _self.iterate(form.input_sele.currentText(), 'callback(partial_charge, elec_radius)', space={'callback': callback}) # grid auto-value (keep map size in the order of 200x200x200) if n > 1 and not form.apbs_grid_userchanged: e = _self.get_extent(form.input_sele.currentText()) volume = (e[1][0] - e[0][0]) * (e[1][1] - e[0][1]) * (e[1][2] - e[0][2]) grid = max(0.5, volume**0.333 / 200.0) form.apbs_grid.setValue(grid) if n < 1: label = 'Selection is invalid' color = '#f66' elif has_props == ['YES', 'YES']: label = 'No preparation necessary, selection has charges and radii' form.do_prepare.setChecked(False) color = '#6f6' else: label = 'Selection needs preparation (partial_charge: %s, elec_radius: %s)' % tuple( has_props) form.do_prepare.setChecked(True) color = '#fc6' form.label_sele_has.setText(label) form.label_sele_has.setStyleSheet('background: %s; padding: 5' % color) check_sele_timer.start(0) @form.apbs_exe_browse.clicked.connect def _(): fnames = getOpenFileNames(None, filter='apbs (apbs*);;All Files (*)')[0] if fnames: form.apbs_exe.setText(fnames[0]) @form.pdb2pqr_exe_browse.clicked.connect def _(): fnames = getOpenFileNames( None, filter='pdb2pqr (pdb2pqr*);;All Files (*)')[0] if fnames: form.pdb2pqr_exe.setText(fnames[0]) # hook up events form.input_sele.currentIndexChanged.connect( lambda: check_sele_timer.start(0)) form.input_sele.editTextChanged.connect( lambda: check_sele_timer.start(1000)) form.button_ok.clicked.connect(run) # "Register" opens a web browser @form.button_register.clicked.connect def _(): import webbrowser webbrowser.open("http://www.poissonboltzmann.org/") @form.button_load.clicked.connect def _(): fnames = getOpenFileNames(None, filter='APBS Input (*.in);;All Files (*)')[0] if fnames: contents = load_apbs_in(form, fnames[0]) set_apbs_in(contents) @form.button_reset.clicked.connect def _(): set_apbs_in(electrostatics.template_apbs_in) form._dialog.show() form._dialog.resize(500, 600)
def __init__(self): QtWidgets.QWidget.__init__(self, parent, Qt.Window) self.setMinimumSize(400, 500) self.setWindowTitle('Register File Extensions') self.model = QtGui.QStandardItemModel(self) layout = QtWidgets.QVBoxLayout(self) self.setLayout(layout) label = QtWidgets.QLabel( "Select file types to register them with PyMOL", self) layout.addWidget(label) alluserslayout = QtWidgets.QHBoxLayout() alluserslayout.setObjectName("alluserslayout") layout.addLayout(alluserslayout) buttonlayout = QtWidgets.QHBoxLayout() buttonlayout.setObjectName("buttonlayout") layout.addLayout(buttonlayout) self.table = QtWidgets.QTableView(self) self.table.setModel(self.model) layout.addWidget(self.table) button = QtWidgets.QPushButton("Register Recommended (*)", self) buttonlayout.addWidget(button) button.pressed.connect(self.setRecommended) button = QtWidgets.QPushButton("Register All", self) buttonlayout.addWidget(button) button.pressed.connect(self.setAll) button = QtWidgets.QPushButton("Clear", self) button.setToolTip("Clean up Registry") buttonlayout.addWidget(button) button.pressed.connect(self.clear) if isAdmin(): r0 = QtWidgets.QRadioButton("Only for me") r0.setToolTip("HKEY_CURRENT_USER registry branch") r0.setChecked(True) r1 = QtWidgets.QRadioButton("For all users") r1.setToolTip("HKEY_LOCAL_MACHINE registry branch") allusersgroup = QtWidgets.QButtonGroup(self) allusersgroup.addButton(r0) allusersgroup.addButton(r1) allusersgroup.buttonClicked.connect(self.populateData) alluserslayout.addWidget(r0) alluserslayout.addWidget(r1) alluserslayout.addStretch() self.allusersbutton = r1 else: self.allusersbutton = None self.finalize_timer = QtCore.QTimer() self.finalize_timer.setSingleShot(True) self.finalize_timer.setInterval(500) self.finalize_timer.timeout.connect(finalize) self.populateData() # keep reference to window, otherwise Qt will auto-close it self._self_ref = self
def __init__(self, parent): self.gui = parent # OpenGL context setup f = QtOpenGL.QGLFormat() f.setRgba(True) f.setDepth(True) f.setDoubleBuffer(True) from pymol.invocation import options # logic equivalent to layer5/main.cpp:launch if options.multisample: f.setSampleBuffers(True) if options.force_stereo != -1: # See layer1/Setting.h for stereo modes if options.stereo_mode in (0, 1, 12): # this effectively disables stereo detection # on Linux that is faulty in QGLWidget / PyQt5 if not (options.stereo_mode == 0 and sys.platform.startswith("linux")): f.setStereo(True) if options.stereo_mode in (11, 12): f.setAccum(True) if options.stereo_mode in (0, 6, 7, 8, 9): f.setStencil(True) QtOpenGL.QGLWidget.__init__(self, f, parent=parent) if not self.isValid(): raise RuntimeError('OpenGL initialization failed') f_actual = self.format() # report if quad buffer available options.stereo_capable = int(f_actual.stereo() or (options.force_stereo == 1)) # feedback if stereo request failed if options.stereo_mode and ( # QTBUG-59636 f.stereo() and not f_actual.stereo() or f.accum() and not f_actual.accum() or f.stencil() and not f_actual.stencil()): # cPyMOLGlobals_LaunchStatus_StereoFailed options.launch_status |= 0x1 # feedback if multisample request failed if options.multisample and not f_actual.sampleBuffers(): # cPyMOLGlobals_LaunchStatus_MultisampleFailed options.launch_status |= 0x2 # pymol instance self.pymol = PyMOL() self.pymol.start() self.cmd = self.pymol.cmd # capture python output for feedback import pcatch pcatch._install() # for passive move drag self.setMouseTracking(True) # for accepting keyboard input (command line, shortcuts) self.setFocusPolicy(Qt.ClickFocus) # for idle rendering self._timer = QtCore.QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self._pymolProcess) # drag n drop self.setAcceptDrops(True) # pinch-zoom self.grabGesture(Qt.PinchGesture)