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)
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
def make_pymol_qicon(): icons_dir = os.path.expandvars('$PYMOL_DATA/pymol/icons') icon = QtGui.QIcon() for size in (16, 32, 128): icon.addFile(os.path.join(icons_dir, 'icon2_%dx%d.png' % (size, size)), QtCore.QSize(size, size)) return icon
def __init__(self, i, j, tags, mark_size, parent_window, pen=None, brush=None): # Build the polygon object representing a triangle. with warnings.catch_warnings(): warnings.simplefilter("ignore") polygon = QtGui.QPolygonF([ QtCore.QPoint(i, j - 2 * mark_size), QtCore.QPoint(i - 1.7 * mark_size, j + mark_size), QtCore.QPoint(i + 1.7 * mark_size, j + mark_size) ]) # Initialize. super(Ramachandran_plot_triangle, self).__init__(polygon) self.common_initialization(i, j, tags, parent_window, pen, brush)
def paintValueBox(self, painter, font_metrics, x, y, right_just, value, format="%.3f"): s = format % value sw = font_metrics.width(s) sh = font_metrics.height() if right_just: rect = QtCore.QRect(x - sw - 4, y - sh, sw + 4, sh + 2) else: rect = QtCore.QRect(x, y - sh, sw + 4, sh + 2) painter.fillRect(rect, QtGui.QColor(96, 96, 128) if self.line_color == Qt.lightGray else QtGui.QColor(0xFF, 0xFF, 0xFF)) painter.drawRect(rect) painter.drawText(rect.x() + 2, y - 2, s) return rect
class PyMod_protocol_thread(QtCore.QThread): """ Class for a 'QThread' to launch a function where a time-consuming process is executed. It is used when showing a 'Protocol_exec_dialog' dialog. """ # Signals. terminate_thread_signal = QtCore.pyqtSignal(int) exception_thread_signal = QtCore.pyqtSignal(Exception) def set_params(self, function, args, wait_start, wait_end): self.function = function self.args = args self.wait_start = wait_start self.wait_end = wait_end def run(self): if self.wait_start is not None: time.sleep(self.wait_start) # Attempts to execute the function in this thread. try: if type(self.args) is dict: self.function(**self.args) else: self.function(*self.args) self._wait_end() self.terminate_thread_signal.emit(0) # Terminate sucessully. # If there was an error, emit the exception, so that it can be handled in the main thread. except Exception as e: self._wait_end() self.exception_thread_signal.emit( e) # Terminate by raising an exception. def _wait_end(self): if self.wait_end is not None: time.sleep(self.wait_end)
def update_pyvol(form): """ GUI wrapper for updating PyVOL using pip """ update_pypi_pyvol() msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setWindowTitle("PyVOL Updated") msg.setInformativeText("The PyVOL backend has been updated; however, PyMOL will not load the new code until it is restarted.") msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.setMinimumSize(QtCore.QSize(600, 200)) # Doesn't seem to work msg.exec_() refresh_installation_status(form)
def uninstall_pyvol(form): """ Attempts to uninstall PyVOL using pip """ subprocess.check_output([sys.executable, "-m", "pip", "uninstall", "-y", "bio-pyvol"]) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setWindowTitle("PyVOL Backend Uninstalled") msg.setInformativeText("The PyVOL backend has been uninstalled; however, the plugin must also be uninstalled using PyMOL's plugin manager.") msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.setMinimumSize(QtCore.QSize(600, 200)) # Doesn't seem to work msg.exec_() refresh_installation_status(form)
def update_pyvol(): """ Attempts to update PyVOL using conda """ from conda.cli import python_api python_api.run_command(python_api.Commands.UPDATE, "bio-pyvol") msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setWindowTitle("PyVOL Updated") msg.setInformativeText( "The PyVOL backend has been updated; however, PyMOL will not load the new code until it is restarted." ) msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.setMinimumSize(QtCore.QSize(600, 200)) # Doesn't seem to work msg.exec_() refresh_installation_status(form)
def pymolviewport(self, w, h): cw, ch = self.cmd.get_viewport() pw = self.pymolwidget scale = pw.fb_scale # maintain aspect ratio if h < 1: if w < 1: pw.resizeGL(int(scale * pw.width()), int(scale * pw.height())) return h = (w * ch) / cw if w < 1: w = (h * cw) / ch win_size = self.size() delta = QtCore.QSize(w - cw, h - ch) / scale # window resize self.resize(delta + win_size)
def update_pyvol(form): """ Attempts to update PyVOL using pip """ import subprocess import sys form.status_label.setText("Updating PyVOL") subprocess.check_output( [sys.executable, "-m", "pip", "install", "--upgrade", "bio-pyvol"]) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Information) msg.setWindowTitle("PyVOL Updated") msg.setInformativeText( "The PyVOL backend has been updated; however, PyMOL will not load the new code until it is restarted." ) msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.setMinimumSize(QtCore.QSize(600, 200)) # Doesn't seem to work msg.exec_() refresh_installation_status(form)
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): # 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 __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 sizeHint(self): # default 640 + internal_gui, 480 + internal_feedback return QtCore.QSize(860, 498)
class InstallerQThread(QtCore.QThread): """ A QThread wrapper for the installers. Contains methods that make sense only when this class is extended together with a PyMod_component_installer (or sub) class, creating a subclass that inherits both from this class and PyMod_component_installer. """ critical_error = QtCore.pyqtSignal(str, PyMod_component) info_message = QtCore.pyqtSignal(str, PyMod_component) established_connection = QtCore.pyqtSignal(PyMod_component) retrieved_size = QtCore.pyqtSignal(PyMod_component, int) set_update_status = QtCore.pyqtSignal(PyMod_component, str, str) terminated_installation = QtCore.pyqtSignal(PyMod_component) freeze_thread = QtCore.pyqtSignal(PyMod_component, int) def run(self): """this method is never called alone (see QThread documentation) but is executed when the Qthread.start() method is called if the flag 'install_mode' is True, the thread will install the databases, if not, it will only retrieve the file size from the server.""" # Only pings the remote source. if self.install_mode == "ping": # Returns 'False' if there is a connection error. time.sleep(0.5) connection = self.ping() if connection: self.component.can_be_downloaded = True else: try: # Actually performs the installation. self.download_and_install() except TerminateQThread as e: pass # emette un type error, oltre al gaierror, se non c'e' connessione internet. # Emette anche un EOFError e un TimeoutError se non fa in tempo a scaricare. except (gaierror, TypeError) as e: self.critical_error.emit( "Cannot connect to server. Please check Internet connection.", self.component) except EOFError as e: self.critical_error.emit( "Timeout expired for connection. Try again later.", self.component) except Exception as e: msg = str(e) self.critical_error.emit(msg, self.component) traceback.print_exc() ########################################################## # Wrapper for the signals. Used in installer subclasses. # ########################################################## def emit_signal(self, signal_name, *args, **kwargs): # getattr(self, signal_name).emit(*args, **kwargs) if signal_name == "set_update_status": self.set_update_status.emit(*args, **kwargs) elif signal_name == "retrieved_size": self.retrieved_size.emit(*args, **kwargs) elif signal_name == "critical_error": self.critical_error.emit(*args, **kwargs) elif signal_name == "terminated_installation": self.terminated_installation.emit(*args, **kwargs) else: raise KeyError("Unknown 'signal_name': %s" % signal_name)
class PyMOLQtGUI(QtWidgets.QMainWindow, pymol._gui.PyMOLDesktopGUI): ''' PyMOL QMainWindow GUI ''' from pmg_qt.file_dialogs import (load_dialog, load_mae_dialog, file_fetch_pdb, file_save_png, file_save_mpeg, file_save_map, file_save_aln, file_save) _ext_window_visible = True _initialdir = '' def keyPressEvent(self, ev): args = keymapping.keyPressEventToPyMOLButtonArgs(ev) if args is not None: self.pymolwidget.pymol.button(*args) def closeEvent(self, event): self.cmd.quit() # for thread-safe viewport command viewportsignal = QtCore.Signal(int, int) def pymolviewport(self, w, h): cw, ch = self.cmd.get_viewport() pw = self.pymolwidget scale = pw.fb_scale # maintain aspect ratio if h < 1: if w < 1: pw.pymol.reshape(int(scale * pw.width()), int(scale * pw.height()), True) return h = (w * ch) / cw if w < 1: w = (h * cw) / ch win_size = self.size() delta = QtCore.QSize(w - cw, h - ch) / scale # window resize self.resize(delta + win_size) def get_view(self): self.cmd.get_view(2, quiet=0) QtWidgets.QApplication.clipboard().setText(self.cmd.get_view(3)) print(" get_view: matrix copied to clipboard.") 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 lineeditKeyPressEventFilter(self, watched, event): key = event.key() if key == Qt.Key_Tab: self.complete() elif key == Qt.Key_Up: if event.modifiers() & Qt.ControlModifier: self.back_search() else: self.back() elif key == Qt.Key_Down: self.forward() elif key == Qt.Key_Return or key == Qt.Key_Enter: # filter out "Return" instead of binding lineedit.returnPressed, # because otherwise OrthoKey would capture it as well. self.doPrompt() else: return False return True def eventFilter(self, watched, event): ''' Filter out <Tab> event to do tab-completion instead of move focus ''' type_ = event.type() if type_ == QtCore.QEvent.KeyRelease: if event.key() == Qt.Key_Tab: # silently skip tab release return True elif type_ == QtCore.QEvent.KeyPress: if watched is self.lineedit: return self.lineeditKeyPressEventFilter(watched, event) elif event.key() == Qt.Key_Tab: self.keyPressEvent(event) return True return False def toggle_ext_window_dockable(self, neverfloat=False): ''' Toggle whether the "external" GUI is dockable ''' dockWidget = self.ext_window if dockWidget.titleBarWidget() is None: tbw = QtWidgets.QWidget() else: tbw = None dockWidget.setFloating(tbw is None and not neverfloat) dockWidget.setTitleBarWidget(tbw) dockWidget.show() def toggle_fullscreen(self, toggle=-1): ''' Full screen ''' is_fullscreen = self.windowState() == Qt.WindowFullScreen if toggle == -1: toggle = not is_fullscreen if not is_fullscreen: self._ext_window_visible = self.ext_window.isVisible() if toggle: self.menubar.hide() self.ext_window.hide() self.showFullScreen() self.pymolwidget.setFocus() else: self.menubar.show() if self._ext_window_visible: self.ext_window.show() self.showNormal() @property def initialdir(self): ''' Be in sync with cd/pwd on the console until the first file has been browsed, then remember the last directory. ''' return self._initialdir or os.getcwd() @initialdir.setter def initialdir(self, value): self._initialdir = value ################## # UI Forms ################## def load_form(self, name, dialog=None): '''Load a form from pmg_qt/forms/{name}.py''' import importlib if dialog is None: dialog = QtWidgets.QDialog(self) widget = dialog elif dialog == 'floating': widget = QtWidgets.QWidget(self) else: widget = dialog try: m = importlib.import_module('.forms.' + name, 'pmg_qt') except ImportError as e: if pymol.Qt.DEBUG: print('load_form import failed (%s)' % (e, )) uifile = os.path.join(os.path.dirname(__file__), 'forms', '%s.ui' % name) form = pymol.Qt.utils.loadUi(uifile, widget) else: if hasattr(m, 'Ui_Form'): form = m.Ui_Form() else: form = m.Ui_Dialog() form.setupUi(widget) if dialog == 'floating': dialog = QtWidgets.QDockWidget(widget.windowTitle(), self) dialog.setFloating(True) dialog.setWidget(widget) dialog.resize(widget.size()) form._dialog = dialog return form def open_props_dialog(self): #noqa if not self.props_dialog: self.props_dialog = properties_dialog.props_dialog(self) self.props_dialog.show() self.props_dialog.raise_() def edit_colors_dialog(self): form = self.load_form('colors') form.list_colors.setSortingEnabled(True) # populate list with named colors for color_index in self.cmd.get_color_indices(): form.list_colors.addItem(color_index[0]) # update spinboxes for given color def load_color(name): index = self.cmd.get_color_index(name) if index == -1: return rgb = self.cmd.get_color_tuple(index) form.input_R.setValue(rgb[0]) form.input_G.setValue(rgb[1]) form.input_B.setValue(rgb[2]) # update spinbox from slider spinbox_lock = [False] def update_spinbox(spinbox, value): if not spinbox_lock[0]: spinbox.setValue(value / 100.) # update sliders and colored frame def update_gui(*args): spinbox_lock[0] = True R = form.input_R.value() G = form.input_G.value() B = form.input_B.value() form.slider_R.setValue(R * 100) form.slider_G.setValue(G * 100) form.slider_B.setValue(B * 100) form.frame_color.setStyleSheet("background-color: rgb(%d,%d,%d)" % (R * 0xFF, G * 0xFF, B * 0xFF)) spinbox_lock[0] = False def run(): name = form.input_name.text() R = form.input_R.value() G = form.input_G.value() B = form.input_B.value() self.cmd.do('set_color %s, [%.2f, %.2f, %.2f]\nrecolor' % (name, R, G, B)) # if new color, insert and make current row if not form.list_colors.findItems(name, Qt.MatchExactly): form.list_colors.addItem(name) form.list_colors.setCurrentItem( form.list_colors.findItems(name, Qt.MatchExactly)[0]) # hook up events form.slider_R.valueChanged.connect( lambda v: update_spinbox(form.input_R, v)) form.slider_G.valueChanged.connect( lambda v: update_spinbox(form.input_G, v)) form.slider_B.valueChanged.connect( lambda v: update_spinbox(form.input_B, v)) form.input_R.valueChanged.connect(update_gui) form.input_G.valueChanged.connect(update_gui) form.input_B.valueChanged.connect(update_gui) form.input_name.textChanged.connect(load_color) form.list_colors.currentTextChanged.connect(form.input_name.setText) form.button_apply.clicked.connect(run) form._dialog.show() def open_builder_panel(self): from pmg_qt.builder import BuilderPanelDocked from pymol import plugins app = plugins.get_pmgapp() if not self.builder: self.builder = BuilderPanelDocked(self, app) self.addDockWidget(Qt.TopDockWidgetArea, self.builder) self.builder.show() self.builder.raise_() def edit_pymolrc(self): from . import TextEditor from pymol import plugins TextEditor.edit_pymolrc(plugins.get_pmgapp()) ################## # Menu callbacks ################## def file_open(self): fnames = getOpenFileNames(self, 'Open file', self.initialdir)[0] partial = 0 for fname in fnames: if not self.load_dialog(fname, partial=partial): break partial = 1 def session_save(self): fname = self.cmd.get('session_file') fname = self.cmd.as_pathstr(fname) return self.session_save_as(fname) @PopupOnException.decorator def session_save_as(self, fname=''): formats = [ 'PyMOL Session File (*.pse *.pze *.pse.gz)', 'PyMOL Show File (*.psw *.pzw *.psw.gz)', ] if not fname: fname = getSaveFileNameWithExt(self, 'Save Session As...', self.initialdir, filter=';;'.join(formats)) if fname: self.initialdir = os.path.dirname(fname) self.cmd.save(fname, format='pse', quiet=0) self.recent_filenames_add(fname) def render_dialog(self, widget=None): form = self.load_form('render', widget) lock = UpdateLock([ZeroDivisionError]) def get_factor(): units = form.input_units.currentText() factor = 1.0 if units == 'inch' else 2.54 return factor / float(form.input_dpi.currentText()) @lock.skipIfCircular def update_units(*args): width = form.input_width.value() height = form.input_height.value() factor = get_factor() form.input_width_units.setValue(width * factor) form.input_height_units.setValue(height * factor) @lock.skipIfCircular def update_pixels(*args): width = form.input_width_units.value() height = form.input_height_units.value() factor = get_factor() form.input_width.setValue(width / factor) form.input_height.setValue(height / factor) @lock.skipIfCircular def update_width(*args): if form.aspectratio > 0: width = form.input_height.value() * form.aspectratio form.input_width.setValue(int(width)) form.input_width_units.setValue(width * get_factor()) @lock.skipIfCircular def update_height(*args): if form.aspectratio > 0: height = form.input_width.value() / form.aspectratio form.input_height.setValue(int(height)) form.input_height_units.setValue(height * get_factor()) def update_aspectratio(checked=True): if checked: try: form.aspectratio = (float(form.input_width.value()) / float(form.input_height.value())) except ZeroDivisionError: form.button_lock.setChecked(False) else: form.aspectratio = 0 def update_from_viewport(): w, h = self.cmd.get_viewport() form.aspectratio = 0 form.input_width.setValue(w) form.input_height.setValue(h) update_aspectratio(form.button_lock.isChecked()) def run_draw(ray=False): width = form.input_width.value() height = form.input_height.value() if ray: self.cmd.set('opaque_background', not form.input_transparent.isChecked()) self.cmd.do('ray %d, %d, async=1' % (width, height)) else: self.cmd.do('draw %d, %d' % (width, height)) form.stack.setCurrentIndex(1) def run_ray(): run_draw(ray=True) def run_save(): fname = getSaveFileNameWithExt(self, 'Save As...', self.initialdir, filter='PNG File (*.png)') if not fname: return self.initialdir = os.path.dirname(fname) self.cmd.png(fname, prior=1, dpi=form.input_dpi.currentText()) def run_copy_clipboard(): with PopupOnException(): _copy_image(self.cmd, False, form.input_dpi.currentText()) dpi = self.cmd.get_setting_int('image_dots_per_inch') if dpi > 0: form.input_dpi.setEditText(str(dpi)) form.input_dpi.setValidator(QtGui.QIntValidator()) form.input_units.currentIndexChanged.connect(update_units) form.input_dpi.editTextChanged.connect(update_pixels) form.input_width.valueChanged.connect(update_units) form.input_height.valueChanged.connect(update_units) form.input_width_units.valueChanged.connect(update_pixels) form.input_height_units.valueChanged.connect(update_pixels) # set values before connecting mutual width<->height updates update_from_viewport() form.input_width.valueChanged.connect(update_height) form.input_height.valueChanged.connect(update_width) form.input_width_units.valueChanged.connect(update_height) form.input_height_units.valueChanged.connect(update_width) form.button_lock.toggled.connect(update_aspectratio) form.button_draw.clicked.connect(run_draw) form.button_ray.clicked.connect(run_ray) form.button_current.clicked.connect(update_from_viewport) form.button_back.clicked.connect(lambda: form.stack.setCurrentIndex(0)) form.button_clip.clicked.connect(run_copy_clipboard) form.button_save.clicked.connect(run_save) if widget is None: form._dialog.show() def _file_save(self, filter, format): fname = getSaveFileNameWithExt(self, 'Save As...', self.initialdir, filter=filter) if fname: self.cmd.save(fname, format=format, quiet=0) def file_save_wrl(self): self._file_save('VRML 2 WRL File (*.wrl)', 'wrl') def file_save_dae(self): self._file_save('COLLADA File (*.dae)', 'dae') def file_save_pov(self): self._file_save('POV File (*.pov)', 'pov') def file_save_mpng(self): self.file_save_mpeg('png') def file_save_mov(self): self.file_save_mpeg('mov') def file_save_stl(self): self._file_save('STL File (*.stl)', 'stl') def file_save_gltf(self): self._file_save('GLTF File (*.gltf)', 'gltf') LOG_FORMATS = [ 'PyMOL Script (*.pml)', 'Python Script (*.py *.pym)', 'All (*)', ] def log_open(self, fname='', mode='w'): if not fname: fname = getSaveFileNameWithExt(self, 'Open Logfile...', self.initialdir, filter=';;'.join(self.LOG_FORMATS)) if fname: self.initialdir = os.path.dirname(fname) self.cmd.log_open(fname, mode) def log_append(self): return self.log_open(mode='a') def log_resume(self): fname = getSaveFileNameWithExt(self, 'Open Logfile...', self.initialdir, filter=';;'.join(self.LOG_FORMATS)) if fname: self.initialdir = os.path.dirname(fname) self.cmd.resume(fname) def file_run(self): formats = [ 'All Runnable (*.pml *.py *.pym)', 'PyMOL Command Script (*.pml)', 'PyMOL Command Script (*.txt)', 'Python Script (*.py *.pym)', 'Python Script (*.txt)', 'All Files(*)', ] fnames, selectedfilter = getOpenFileNames(self, 'Open file', self.initialdir, filter=';;'.join(formats)) is_py = selectedfilter.startswith('Python') with PopupOnException(): for fname in fnames: self.initialdir = os.path.dirname(fname) self.cmd.cd(self.initialdir, quiet=0) # detect: .py, .pym, .pyc, .pyo, .py.txt if is_py or re.search(r'\.py(|m|c|o|\.txt)$', fname, re.I): self.cmd.run(fname) else: self.cmd.do("@" + fname) def cd_dialog(self): dname = QFileDialog.getExistingDirectory(self, "Change Working Directory", self.initialdir) self.cmd.cd(dname or '.', quiet=0) def confirm_quit(self): QtWidgets.qApp.quit() def settings_edit_all_dialog(self): from .advanced_settings_gui import PyMOLAdvancedSettings if self.advanced_settings_dialog is None: self.advanced_settings_dialog = PyMOLAdvancedSettings( self, self.cmd) self.advanced_settings_dialog.show() def show_about(self): msg = [ 'The PyMOL Molecular Graphics System\n', 'Version %s' % (self.cmd.get_version()[0]), u'Copyright (C) Schr\xF6dinger, LLC.', 'All rights reserved.\n', 'License information:', ] msg.append('Open-Source Build') msg += [ '', 'For more information:', 'https://pymol.org', '*****@*****.**', ] QtWidgets.QMessageBox.about(self, "About PyMOL", '\n'.join(msg)) ################# # GUI callbacks ################# if sys.version_info[0] < 3: def command_get(self): return self.lineedit.text().encode('utf-8') else: def command_get(self): return self.lineedit.text() def command_set(self, v): return self.lineedit.setText(v) def command_set_cursor(self, i): return self.lineedit.setCursorPosition(i) def update_progress(self): progress = self.cmd.get_progress() if progress >= 0: self.progressbar.setValue(progress * 100) self.progressbar.show() self.abortbutton.show() else: self.progressbar.hide() self.abortbutton.hide() def update_feedback(self): self.update_progress() feedback = self.cmd._get_feedback() if feedback: html = colorprinting.text2html('\n'.join(feedback)) self.browser.appendHtml(html) scrollbar = self.browser.verticalScrollBar() scrollbar.setValue(scrollbar.maximum()) for setting in self.cmd.get_setting_updates() or (): if setting in self.setting_callbacks: current_value = self.cmd.get_setting_tuple(setting)[1][0] for callback in self.setting_callbacks[setting]: callback(current_value) self.feedback_timer.start(500) def doPrompt(self): self.doTypedCommand(self.command_get()) self.pymolwidget._pymolProcess() self.lineedit.clear() self.feedback_timer.start(0) ########################## # legacy plugin system ########################## @PopupOnException.decorator def initializePlugins(self): from pymol import plugins from . import mimic_tk self.menudict['Plugin'].clear() app = plugins.get_pmgapp() plugins.legacysupport.addPluginManagerMenuItem() # Redirect to Legacy submenu self.menudict['PluginQt'] = self.menudict['Plugin'] self.menudict['Plugin'] = self.menudict['PluginQt'].addMenu( 'Legacy Plugins') self.menudict['Plugin'].setTearOffEnabled(True) self.menudict['PluginQt'].addSeparator() plugins.HAVE_QT = True plugins.initialize(app) def createlegacypmgapp(self): from . import mimic_pmg_tk as mimic pmgapp = mimic.PMGApp() pmgapp.menuBar = mimic.PmwMenuBar(self.menudict) return pmgapp def window_cmd(self, action, x, y, w, h): if action == 0: # hide self.hide() elif action == 1: # show self.show() elif action == 2: # position self.move(x, y) elif action == 3: # size (first two arguments) self.resize(x, y) elif action == 4: # box self.move(x, y) self.resize(w, h) elif action == 5: # maximize self.showMaximized() elif action == 6: # fit if hasattr(QtGui, 'QWindow') and self.windowHandle().visibility() in ( QtGui.QWindow.Maximized, QtGui.QWindow.FullScreen): return a = QtWidgets.QApplication.desktop().availableGeometry(self) g = self.geometry() f = self.frameGeometry() w = min(f.width(), a.width()) h = min(f.height(), a.height()) x = max(min(f.x(), a.right() - w), a.x()) y = max(min(f.y(), a.bottom() - h), a.y()) self.setGeometry( x - f.x() + g.x(), y - f.y() + g.y(), w - f.width() + g.width(), h - f.height() + g.height(), ) elif action == 7: # focus self.setFocus(Qt.OtherFocusReason) elif action == 8: # defocus self.clearFocus()
def __init__(self, parent=None, app=None): super(_BuilderPanel, self).__init__(parent) self.setWindowTitle("Builder") self.setObjectName("builder") self.cmd = app.pymol.cmd self.layout = QtWidgets.QVBoxLayout() self.setLayout(self.layout) self.buttons_layout = QtWidgets.QVBoxLayout() self.tabs = QtWidgets.QTabWidget(self) self.layout.setContentsMargins(5, 5, 5, 5); self.layout.setSpacing(5); self.layout.addWidget(self.tabs) self.layout.addLayout(self.buttons_layout) self.layout.addStretch() self.fragments_layout = QtWidgets.QGridLayout() self.fragments_layout.setContentsMargins(5, 5, 5, 5); self.fragments_layout.setSpacing(5); self.fragments_tab = QtWidgets.QWidget() self.fragments_tab.setLayout(self.fragments_layout) self.protein_layout = QtWidgets.QGridLayout() self.protein_layout.setContentsMargins(5, 5, 5, 5); self.protein_layout.setSpacing(5); self.protein_tab = QtWidgets.QWidget() self.protein_tab.setLayout(self.protein_layout) self.tabs.addTab(self.fragments_tab, "Chemical") self.tabs.addTab(self.protein_tab, "Protein") self.getIcons() buttons = [ [ ("H", "Hydrogen", lambda: self.replace("H", 1, 1, "Hydrogen")), ("C", "Carbon", lambda: self.replace("C", 4, 4, "Carbon")), ("N", "Nitrogen", lambda: self.replace("N", 4, 3, "Nitrogen")), ("O", "Oxygen", lambda: self.replace("O", 4, 2, "Oxygen")), ("P", "Phosphorus", lambda: self.replace("P",4,3, "Phosphorous")), ("S", "Sulfur", lambda: self.replace("S",2,2, "Sulfur")), ("F", "Fluorine", lambda: self.replace("F",1,1, "Fluorine")), ("Cl", "Chlorrine", lambda: self.replace("Cl",1,1, "Chlorine")), ("Br", "Bromine", lambda: self.replace("Br",1,1, "Bromine")), ("I", "Iodine", lambda: self.replace("I",1,1, "Iodine")), ("-CF3", "Trifluoromethane", lambda: self.replace("trifluoromethane",4,0, "trifluoro")), ("-OMe", "Methanol", lambda: self.replace("methanol",5,0, "methoxy")), ], [ ("CH4", "Methyl", lambda: self.grow("methane",1,0,"methyl")), ("C=C", "Ethylene", lambda: self.grow("ethylene",4,0,"vinyl")), ("C#C", "Acetylene", lambda: self.grow("acetylene",2,0,"alkynl")), ("C#N", "Cyanide", lambda: self.grow("cyanide",2,0,"cyano")), ("C=O", "Aldehyde", lambda: self.grow("formaldehyde",2,0,"carbonyl",)), ("C=OO", "Formic Acid", lambda: self.grow("formic",4,0,"carboxyl")), ("C=ON", "C->N amide", lambda: self.grow("formamide",5,0,"C->N amide")), ("NC=O", "N->C amide", lambda: self.grow("formamide",3,1,"N->C amide")), ("S=O2", "Sulfone", lambda: self.grow("sulfone",3,1,"sulfonyl")), ("P=O3", "Phosphite", lambda: self.grow("phosphite",4,0,"phosphoryl")), ("N=O2", "Nitro", lambda: self.grow("nitro",3,0,"nitro")), ], [ ("#cyc3", "Cyclopropane", lambda: self.grow("cyclopropane",4,0,"cyclopropyl")), ("#cyc4", "Cyclobutane", lambda: self.grow("cyclobutane",4,0,"cyclobutyl")), ("#cyc5", "Cyclopentane", lambda: self.grow("cyclopentane",5,0,"cyclopentyl")), ("#cyc6", "Cyclohexane", lambda: self.grow("cyclohexane",7,0,"cyclohexyl")), ("#cyc7", "Cycloheptane", lambda: self.grow("cycloheptane",8,0,"cycloheptyl")), ("#aro5", "Cyclopentadiene", lambda: self.grow("cyclopentadiene",5,0,"cyclopentadienyl")), ("#aro6", "Benzene", lambda: self.grow("benzene",6,0,"phenyl")), ("#aro65", "Indane", lambda: self.grow("indane",12,0,"indanyl")), ("#aro66", "Napthylene", lambda: self.grow("napthylene",13,0,"napthyl")), ("#aro67", "Benzocycloheptane", lambda: self.grow("benzocycloheptane",13,0, "benzocycloheptyl")), ] ] self.btn_icons = {} requestsize = QtCore.QSize(48, 48) for row, btn_row in enumerate(buttons): for col, bb in enumerate(btn_row): btn_label, btn_tooltip, btn_command = bb btn = makeFragmentButton() if btn_label.startswith('#'): icons = self.icons[btn_label[1:]] btn.setIcon(icons[0]) btn.setIconSize(icons[1].actualSize(requestsize)) self.btn_icons[btn] = icons else: btn.setText(btn_label) btn.setToolTip(btn_tooltip) btn.clicked.connect(btn_command) self.fragments_layout.addWidget(btn, row, col) buttons = [ [ 'Ace', 'Ala', 'Arg', 'Asn', 'Asp', 'Cys', 'Gln', 'Glu', 'Gly', 'His', 'Ile', 'Leu' ], [ 'Lys', 'Met', 'Phe', 'Pro', 'Ser', 'Thr', 'Trp', 'Tyr', 'Val', 'NMe', 'NHH' ] ] for row, btn_row in enumerate(buttons): for col, btn_label in enumerate(btn_row): btn = makeFragmentButton() btn.setText(btn_label) btn.setToolTip("Build %s residue" % btn_label) res = btn_label.lower() slot = lambda val=None, s=self,r=res: s.attach(r) btn.clicked.connect(slot) self.protein_layout.addWidget(btn, row, col) lab = QtWidgets.QLabel('Secondary Structure:') lab_cols = 3 self.ss_cbox = QtWidgets.QComboBox() self.ss_cbox.addItem("Alpha Helix") self.ss_cbox.addItem("Beta Sheet (Anti-Parallel)") self.ss_cbox.addItem("Beta Sheet (Parallel)") self.protein_layout.addWidget(lab, 2, 0, 1, lab_cols) self.protein_layout.addWidget(self.ss_cbox, 2, lab_cols, 1, 4) self.ss_cbox.currentIndexChanged[int].connect(self.ssIndexChanged) buttons = [ [ ( "@Atoms:", None, None), ( "Fix H", "Fix hydrogens on picked atoms", self.fixH), ( "Add H", "Add hydrogens to entire molecule", self.addH), ( "Invert", "Invert stereochemistry around pk1 (pk2 and pk3 will remain fixed)", self.invert), ( "Delete", "Remove atoms", self.removeAtom), ( "Clear", "Delete everything", self.clear), ( "@ Charge:", None, None), ( " +1 ", "Positive Charge", lambda: self.setCharge(1,"+1")), ( " 0 ", "Neutral Charge", lambda: self.setCharge(0,"neutral")), ( " -1 ", "Negative Charge", lambda: self.setCharge(-1,"-1")), ], [ ( "@Bonds:", None, None), ( "Create", "Create bond between pk1 and pk2", self.createBond), ( "Delete", "Delete bond between pk1 and pk2", self.deleteBond), ( "Cycle", "Cycle bond valence", self.cycleBond), ( " | ", "Create single bond", lambda: self.setOrder("1", "single")), ( " || ", "Create double bond", lambda: self.setOrder("2", "double")), ( " ||| ", "Create triple bond", lambda: self.setOrder("3", "triple")), ( "Arom", "Create aromatic bond", lambda: self.setOrder("4", "aromatic")), ( "@ Model:", None, None), ( "Clean", "Cleanup structure", self.clean), ( "Sculpt", "Molecular sculpting", self.sculpt), ( "Fix", "Fix atom positions", self.fix), ( "Rest", "Restrain atom positions", self.rest), ], [ ( "$El-stat", "Electrostatics term for 'Clean' action", "clean_electro_mode"), ( "@ ", None, None), ( "$Bumps", "Show VDW contacts during sculpting", "sculpt_vdw_vis_mode"), ( "@ ", None, None), ( "#Undo Enabled", "", "suspend_undo"), ( "Undo", "Undo last change", self.undo), ( "Redo", "Redo last change", self.redo), ] ] for row, btn_row in enumerate(buttons): btn_row_layout = QtWidgets.QHBoxLayout() self.buttons_layout.addLayout(btn_row_layout) for col, bb in enumerate(btn_row): btn_label, btn_tooltip, btn_command = bb if btn_label[0] == '@': btn = QtWidgets.QLabel(btn_label[1:]) elif btn_label[0] in ('#', '$'): btn = QtWidgets.QCheckBox(btn_label[1:]) setting = btn_command value = self.cmd.get_setting_int(setting) if btn_label[0] == '$': btn.setChecked(bool(value)) @btn.toggled.connect def _(checked, n=setting): self.cmd.set(n, checked, quiet=0) else: btn.setChecked(not value) @btn.toggled.connect def _(checked, n=setting): self.cmd.set(n, not checked, quiet=0) else: btn = makeFragmentButton() btn.setText(btn_label) btn.clicked.connect(btn_command) if btn_tooltip: btn.setToolTip(btn_tooltip) btn_row_layout.addWidget(btn) btn_row_layout.addStretch()
def sizeHint(self): return QtCore.QSize(600, 200)
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)