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, pymod, parent=None): super().__init__(parent) # Initial settings. self.pymod = pymod self.title = self.pymod.pymod_plugin_name + "." + self.pymod.pymod_revision self.statusBar_message = "Ready" self.left = 50 self.top = 50 self.width = 1100 # 2200, 1100 self.height = 800 # 400 self.bg_color = 'rgb(0, 0, 0)' self.text_color = "rgb(255, 255, 255)" # Fonts and styles. self.font_weight = "bold" self.font_size = 10 # 9, 10, 12 self.font = "courier new" self.vertical_spacing = 1 self.horizontal_spacing = 20 # Creating a menu bar. self.make_main_menu() # create_menu_bar() # Creating central widget and setting it as central. self.central_widget = Centralwid(self) self.setCentralWidget(self.central_widget) self.update_qt_font() # Creating status bar. self.statusBar().showMessage(self.statusBar_message, 3000) self.statusBar().setSizeGripEnabled(1) # Initialize User Interface. self.initUI() # Set the layout. self.central_widget.id_form_layout.setFormAlignment( QtCore.Qt.AlignLeft) self.central_widget.id_form_layout.setVerticalSpacing( self.vertical_spacing) self.central_widget.sequence_ID_groupbox.setLayout( self.central_widget.id_form_layout) self.central_widget.seq_form_layout.setHorizontalSpacing( self.horizontal_spacing) self.central_widget.seq_form_layout.setFormAlignment( QtCore.Qt.AlignLeft) self.central_widget.seq_form_layout.setLabelAlignment( QtCore.Qt.AlignLeft) self.central_widget.seq_form_layout.setVerticalSpacing( self.vertical_spacing) self.central_widget.sequence_SEQ_groupbox.setLayout( self.central_widget.seq_form_layout) # Initializes a clipboard. self.clipboard = QtWidgets.QApplication.clipboard() #---------------- # Key bindings. - #---------------- # Select elements. self.select_all_shortcut = QtWidgets.QShortcut( QtGui.QKeySequence("Ctrl+A"), self) self.select_all_shortcut.activated.connect(self.select_all_binding) self.show_all_structures_shortcut = QtWidgets.QShortcut( QtGui.QKeySequence("Ctrl+S"), self) self.show_all_structures_shortcut.activated.connect( self.show_all_structures_binding) self.hide_all_structures_shortcut = QtWidgets.QShortcut( QtGui.QKeySequence("Ctrl+H"), self) self.hide_all_structures_shortcut.activated.connect( self.hide_all_structures_binding) self.deselect_all_shortcut = QtWidgets.QShortcut( QtGui.QKeySequence("Esc"), self) self.deselect_all_shortcut.activated.connect(self.deselect_all_binding) # Move elements. self.press_up_key_shortcut = QtWidgets.QShortcut( QtGui.QKeySequence("up"), self) self.press_up_key_shortcut.activated.connect(self.press_up_key_binding) self.press_down_key_shortcut = QtWidgets.QShortcut( QtGui.QKeySequence("down"), self) self.press_down_key_shortcut.activated.connect( self.press_down_key_binding)