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
示例#2
0
    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)
示例#3
0
    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)
示例#4
0
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)
示例#5
0
        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
示例#6
0
    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)