Пример #1
0
class QResizableScrollBar(QtGui.QScrollBar):

    resized = QtCore.Signal()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.resized.emit()
Пример #2
0
class _DropEventEmitter(QtCore.QObject):
    """ Handle object drops on widget. """

    signal = QtCore.Signal(object)

    def __init__(self, widget):
        QtCore.QObject.__init__(self, widget)
        self.widget = widget

        widget.setAcceptDrops(True)
        widget.installEventFilter(self)

    def eventFilter(self, source, event):
        """ Handle drop events on widget. """
        typ = event.type()
        if typ == QtCore.QEvent.DragEnter:
            if hasattr(event.mimeData(), "instance"):
                # It is pymimedata and has instance data
                obj = event.mimeData().instance()
                if obj is not None:
                    event.accept()
                    return True

        elif typ == QtCore.QEvent.Drop:
            if hasattr(event.mimeData(), "instance"):
                # It is pymimedata and has instance data
                obj = event.mimeData().instance()
                if obj is not None:
                    self.signal.emit(obj)
                    event.accept()
                    return True

        return QtCore.QObject.eventFilter(self, source, event)
Пример #3
0
class PythonWidget(HistoryConsoleWidget):
    """ A basic in-process Python interpreter.
    """

    # Emitted when a command has been executed in the interpeter.
    executed = QtCore.Signal()

    #--------------------------------------------------------------------------
    # 'object' interface
    #--------------------------------------------------------------------------

    def __init__(self, parent=None):
        super(PythonWidget, self).__init__(parent)

        # PythonWidget attributes.
        self.locals = dict(__name__='__console__', __doc__=None)
        self.interpreter = InteractiveInterpreter(self.locals)

        # PythonWidget protected attributes.
        self._buffer = StringIO()
        self._bracket_matcher = BracketMatcher(self._control)
        self._call_tip_widget = CallTipWidget(self._control)
        self._completion_lexer = CompletionLexer(PythonLexer())
        self._hidden = False
        self._highlighter = PythonWidgetHighlighter(self)
        self._last_refresh_time = 0

        # file-like object attributes.
        self.encoding = sys.stdin.encoding

        # Configure the ConsoleWidget.
        self.tab_width = 4
        self._set_continuation_prompt('... ')

        # Configure the CallTipWidget.
        self._call_tip_widget.setFont(self.font)
        self.font_changed.connect(self._call_tip_widget.setFont)

        # Connect signal handlers.
        document = self._control.document()
        document.contentsChange.connect(self._document_contents_change)

        # Display the banner and initial prompt.
        self.reset()

    #--------------------------------------------------------------------------
    # file-like object interface
    #--------------------------------------------------------------------------

    def flush(self):
        """ Flush the buffer by writing its contents to the screen.
        """
        self._buffer.seek(0)
        text = self._buffer.getvalue()
        self._buffer.close()
        self._buffer = StringIO()

        self._append_plain_text(text)
        self._control.moveCursor(QtGui.QTextCursor.End)

    def readline(self, prompt=None):
        """ Read and return one line of input from the user.
        """
        return self._readline(prompt)

    def write(self, text, refresh=True):
        """ Write text to the buffer, possibly flushing it if 'refresh' is set.
        """
        if not self._hidden:
            self._buffer.write(text)
            if refresh:
                current_time = time()
                if current_time - self._last_refresh_time > 0.05:
                    self.flush()
                    self._last_refresh_time = current_time

    def writelines(self, lines, refresh=True):
        """ Write a list of lines to the buffer.
        """
        for line in lines:
            self.write(line, refresh=refresh)

    #---------------------------------------------------------------------------
    # 'ConsoleWidget' abstract interface
    #---------------------------------------------------------------------------

    def _is_complete(self, source, interactive):
        """ Returns whether 'source' can be completely processed and a new
            prompt created. When triggered by an Enter/Return key press,
            'interactive' is True; otherwise, it is False.
        """
        if interactive:
            lines = source.splitlines()
            if len(lines) == 1:
                try:
                    return compile_command(source) is not None
                except:
                    # We'll let the interpeter handle the error.
                    return True
            else:
                return lines[-1].strip() == ''
        else:
            return True

    def _execute(self, source, hidden):
        """ Execute 'source'. If 'hidden', do not show any output.

        See parent class :meth:`execute` docstring for full details.
        """
        # Save the current std* and point them here
        old_stdin = sys.stdin
        old_stdout = sys.stdout
        old_stderr = sys.stderr
        sys.stdin = sys.stdout = sys.stderr = self

        # Run the source code in the interpeter
        self._hidden = hidden
        try:
            more = self.interpreter.runsource(source)
        finally:
            self._hidden = False

            # Restore std* unless the executed changed them
            if sys.stdin is self:
                sys.stdin = old_stdin
            if sys.stdout is self:
                sys.stdout = old_stdout
            if sys.stderr is self:
                sys.stderr = old_stderr

            self.executed.emit()
            self._show_interpreter_prompt()

    def _prompt_started_hook(self):
        """ Called immediately after a new prompt is displayed.
        """
        if not self._reading:
            self._highlighter.highlighting_on = True

    def _prompt_finished_hook(self):
        """ Called immediately after a prompt is finished, i.e. when some input
            will be processed and a new prompt displayed.
        """
        if not self._reading:
            self._highlighter.highlighting_on = False

    def _tab_pressed(self):
        """ Called when the tab key is pressed. Returns whether to continue
            processing the event.
        """
        # Perform tab completion if:
        # 1) The cursor is in the input buffer.
        # 2) There is a non-whitespace character before the cursor.
        text = self._get_input_buffer_cursor_line()
        if text is None:
            return False
        complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
        if complete:
            self._complete()
        return not complete

    #---------------------------------------------------------------------------
    # 'ConsoleWidget' protected interface
    #---------------------------------------------------------------------------

    def _event_filter_console_keypress(self, event):
        """ Reimplemented for smart backspace.
        """
        if event.key() == QtCore.Qt.Key_Backspace and \
                not event.modifiers() & QtCore.Qt.AltModifier:
            # Smart backspace: remove four characters in one backspace if:
            # 1) everything left of the cursor is whitespace
            # 2) the four characters immediately left of the cursor are spaces
            col = self._get_input_buffer_cursor_column()
            cursor = self._control.textCursor()
            if col > 3 and not cursor.hasSelection():
                text = self._get_input_buffer_cursor_line()[:col]
                if text.endswith('    ') and not text.strip():
                    cursor.movePosition(QtGui.QTextCursor.Left,
                                        QtGui.QTextCursor.KeepAnchor, 4)
                    cursor.removeSelectedText()
                    return True

        return super(PythonWidget, self)._event_filter_console_keypress(event)

    def _insert_continuation_prompt(self, cursor):
        """ Reimplemented for auto-indentation.
        """
        super(PythonWidget, self)._insert_continuation_prompt(cursor)
        source = self.input_buffer
        space = 0
        for c in source.splitlines()[-1]:
            if c == '\t':
                space += 4
            elif c == ' ':
                space += 1
            else:
                break
        if source.rstrip().endswith(':'):
            space += 4
        cursor.insertText(' ' * space)

    #---------------------------------------------------------------------------
    # 'PythonWidget' public interface
    #---------------------------------------------------------------------------

    def execute_file(self, path, hidden=False):
        """ Attempts to execute file with 'path'. If 'hidden', no output is
            shown.
        """
        self.execute('execfile("%s")' % path, hidden=hidden)

    def reset(self):
        """ Resets the widget to its initial state. Similar to ``clear``, but
            also re-writes the banner.
        """
        self._reading = False
        self._highlighter.highlighting_on = False

        self._control.clear()
        self._append_plain_text(self._get_banner())
        self._show_interpreter_prompt()

    #---------------------------------------------------------------------------
    # 'PythonWidget' protected interface
    #---------------------------------------------------------------------------

    def _call_tip(self):
        """ Shows a call tip, if appropriate, at the current cursor location.
        """
        # Decide if it makes sense to show a call tip
        cursor = self._get_cursor()
        cursor.movePosition(QtGui.QTextCursor.Left)
        if cursor.document().characterAt(cursor.position()) != '(':
            return False
        context = self._get_context(cursor)
        if not context:
            return False

        # Look up the context and show a tip for it
        symbol, leftover = self._get_symbol_from_context(context)
        doc = getattr(symbol, '__doc__', None)
        if doc is not None and not leftover:
            self._call_tip_widget.show_call_info(doc=doc)
            return True
        return False

    def _complete(self):
        """ Performs completion at the current cursor location.
        """
        context = self._get_context()
        if context:
            symbol, leftover = self._get_symbol_from_context(context)
            if len(leftover) == 1:
                leftover = leftover[0]
                if symbol is None:
                    names = self.interpreter.locals.keys()
                    names += __builtin__.__dict__.keys()
                else:
                    names = dir(symbol)
                completions = [n for n in names if n.startswith(leftover)]
                if completions:
                    cursor = self._get_cursor()
                    cursor.movePosition(QtGui.QTextCursor.Left,
                                        n=len(context[-1]))
                    self._complete_with_items(cursor, completions)

    def _get_banner(self):
        """ Gets a banner to display at the beginning of a session.
        """
        banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
            '"license" for more information.'
        return banner % (sys.version, sys.platform)

    def _get_context(self, cursor=None):
        """ Gets the context for the specified cursor (or the current cursor
            if none is specified).
        """
        if cursor is None:
            cursor = self._get_cursor()
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
                            QtGui.QTextCursor.KeepAnchor)
        text = cursor.selection().toPlainText()
        return self._completion_lexer.get_context(text)

    def _get_symbol_from_context(self, context):
        """ Find a python object in the interpeter namespace from a context (a
            list of names).
        """
        context = map(str, context)
        if len(context) == 0:
            return None, context

        base_symbol_string = context[0]
        symbol = self.interpreter.locals.get(base_symbol_string, None)
        if symbol is None:
            symbol = __builtin__.__dict__.get(base_symbol_string, None)
        if symbol is None:
            return None, context

        context = context[1:]
        for i, name in enumerate(context):
            new_symbol = getattr(symbol, name, None)
            if new_symbol is None:
                return symbol, context[i:]
            else:
                symbol = new_symbol

        return symbol, []

    def _show_interpreter_prompt(self):
        """ Shows a prompt for the interpreter.
        """
        self.flush()
        self._show_prompt('>>> ')

    #------ Signal handlers ----------------------------------------------------

    def _document_contents_change(self, position, removed, added):
        """ Called whenever the document's content changes. Display a call tip
            if appropriate.
        """
        # Calculate where the cursor should be *after* the change:
        position += added

        document = self._control.document()
        if position == self._get_cursor().position():
            self._call_tip()
class SplitTabWidget(QtGui.QSplitter):
    """ The SplitTabWidget class is a hierarchy of QSplitters the leaves of
    which are QTabWidgets.  Any tab may be moved around with the hierarchy
    automatically extended and reduced as required.
    """

    # Signals for WorkbenchWindowLayout to handle
    new_window_request = QtCore.Signal(QtCore.QPoint, QtGui.QWidget)
    tab_close_request = QtCore.Signal(QtGui.QWidget)
    tab_window_changed = QtCore.Signal(QtGui.QWidget)
    editor_has_focus = QtCore.Signal(QtGui.QWidget)
    focus_changed = QtCore.Signal(QtGui.QWidget, QtGui.QWidget)

    # The different hotspots of a QTabWidget.  An non-negative value is a tab
    # index and the hotspot is to the left of it.

    tabTextChanged = QtCore.Signal(QtGui.QWidget, str)
    _HS_NONE = -1
    _HS_AFTER_LAST_TAB = -2
    _HS_NORTH = -3
    _HS_SOUTH = -4
    _HS_EAST = -5
    _HS_WEST = -6
    _HS_OUTSIDE = -7

    def __init__(self, *args):
        """ Initialise the instance. """

        QtGui.QSplitter.__init__(self, *args)

        self.clear()

        QtGui.QApplication.instance().focusChanged.connect(self._focus_changed)

    def clear(self):
        """ Restore the widget to its pristine state. """

        w = None
        for i in range(self.count()):
            w = self.widget(i)
            w.hide()
            w.deleteLater()
        del w

        self._repeat_focus_changes = True
        self._rband = None
        self._selected_tab_widget = None
        self._selected_hotspot = self._HS_NONE

        self._current_tab_w = None
        self._current_tab_idx = -1

    def saveState(self):
        """ Returns a Python object containing the saved state of the widget.
        Widgets are saved only by their object name.
        """

        return self._save_qsplitter(self)

    def _save_qsplitter(self, qsplitter):
        # A splitter state is a tuple of the QSplitter state (as a string) and
        # the list of child states.
        sp_ch_states = []

        # Save the children.
        for i in range(qsplitter.count()):
            ch = qsplitter.widget(i)

            if isinstance(ch, _TabWidget):
                # A tab widget state is a tuple of the current tab index and
                # the list of individual tab states.
                tab_states = []

                for t in range(ch.count()):
                    # A tab state is a tuple of the widget's object name and
                    # the title.
                    name = str(ch.widget(t).objectName())
                    title = str(ch.tabText(t))

                    tab_states.append((name, title))

                ch_state = (ch.currentIndex(), tab_states)
            else:
                # Recurse down the tree of splitters.
                ch_state = self._save_qsplitter(ch)

            sp_ch_states.append(ch_state)

        return (QtGui.QSplitter.saveState(qsplitter).data(), sp_ch_states)

    def restoreState(self, state, factory):
        """ Restore the contents from the given state (returned by a previous
        call to saveState()).  factory is a callable that is passed the object
        name of the widget that is in the state and needs to be restored.  The
        callable returns the restored widget.
        """

        # Ensure we are not restoring to a non-empty widget.
        assert self.count() == 0

        self._restore_qsplitter(state, factory, self)

    def _restore_qsplitter(self, state, factory, qsplitter):
        sp_qstate, sp_ch_states = state

        # Go through each child state which will consist of a tuple of two
        # objects.  We use the type of the first to determine if the child is a
        # tab widget or another splitter.
        for ch_state in sp_ch_states:
            if isinstance(ch_state[0], int):
                current_idx, tabs = ch_state

                new_tab = _TabWidget(self)

                # Go through each tab and use the factory to restore the page.
                for name, title in tabs:
                    page = factory(name)

                    if page is not None:
                        new_tab.addTab(page, title)

                # Only add the new tab widget if it is used.
                if new_tab.count() > 0:
                    qsplitter.addWidget(new_tab)

                    # Set the correct tab as the current one.
                    new_tab.setCurrentIndex(current_idx)
                else:
                    del new_tab
            else:
                new_qsp = QtGui.QSplitter()

                # Recurse down the tree of splitters.
                self._restore_qsplitter(ch_state, factory, new_qsp)

                # Only add the new splitter if it is used.
                if new_qsp.count() > 0:
                    qsplitter.addWidget(new_qsp)
                else:
                    del new_qsp

        # Restore the QSplitter state (being careful to get the right
        # implementation).
        QtGui.QSplitter.restoreState(qsplitter, sp_qstate)

    def addTab(self, w, text):
        """ Add a new tab to the main tab widget. """

        # Find the first tab widget going down the left of the hierarchy.  This
        # will be the one in the top left corner.
        if self.count() > 0:
            ch = self.widget(0)

            while not isinstance(ch, _TabWidget):
                assert isinstance(ch, QtGui.QSplitter)
                ch = ch.widget(0)
        else:
            # There is no tab widget so create one.
            ch = _TabWidget(self)
            self.addWidget(ch)

        idx = ch.insertTab(self._current_tab_idx + 1, w, text)

        # If the tab has been added to the current tab widget then make it the
        # current tab.
        if ch is not self._current_tab_w:
            self._set_current_tab(ch, idx)
            ch.tabBar().setFocus()

    def _close_tab_request(self, w):
        """ A close button was clicked in one of out _TabWidgets """

        self.tab_close_request.emit(w)

    def setCurrentWidget(self, w):
        """ Make the given widget current. """

        tw, tidx = self._tab_widget(w)

        if tw is not None:
            self._set_current_tab(tw, tidx)

    def setActiveIcon(self, w, icon=None):
        """ Set the active icon on a widget. """

        tw, tidx = self._tab_widget(w)

        if tw is not None:
            if icon is None:
                icon = tw.active_icon()

            tw.setTabIcon(tidx, icon)

    def setTabTextColor(self, w, color=None):
        """ Set the tab text color on a particular widget w
        """
        tw, tidx = self._tab_widget(w)

        if tw is not None:
            if color is None:
                # null color reverts to foreground role color
                color = QtGui.QColor()
            tw.tabBar().setTabTextColor(tidx, color)

    def setWidgetTitle(self, w, title):
        """ Set the title for the given widget. """

        tw, idx = self._tab_widget(w)

        if tw is not None:
            tw.setTabText(idx, title)

    def _tab_widget(self, w):
        """ Return the tab widget and index containing the given widget. """

        for tw in self.findChildren(_TabWidget, None):
            idx = tw.indexOf(w)

            if idx >= 0:
                return (tw, idx)

        return (None, None)

    def _set_current_tab(self, tw, tidx):
        """ Set the new current tab. """

        # Handle the trivial case.
        if self._current_tab_w is tw and self._current_tab_idx == tidx:
            return

        if tw is not None:
            tw.setCurrentIndex(tidx)

        # Save the new current widget.
        self._current_tab_w = tw
        self._current_tab_idx = tidx

    def _set_focus(self):
        """ Set the focus to an appropriate widget in the current tab. """

        # Only try and change the focus if the current focus isn't already a
        # child of the widget.
        w = self._current_tab_w.widget(self._current_tab_idx)
        fw = self.window().focusWidget()

        if fw is not None and not w.isAncestorOf(fw):
            # Find a widget to focus using the same method as
            # QStackedLayout::setCurrentIndex().  First try the last widget
            # with the focus.
            nfw = w.focusWidget()

            if nfw is None:
                # Next, try the first child widget in the focus chain.
                nfw = fw.nextInFocusChain()

                while nfw is not fw:
                    if nfw.focusPolicy() & QtCore.Qt.TabFocus and \
                       nfw.focusProxy() is None and \
                       nfw.isVisibleTo(w) and \
                       nfw.isEnabled() and \
                       w.isAncestorOf(nfw):
                        break

                    nfw = nfw.nextInFocusChain()
                else:
                    # Fallback to the tab page widget.
                    nfw = w

            nfw.setFocus()

    def _focus_changed(self, old, new):
        """ Handle a change in focus that affects the current tab. """

        # It is possible for the C++ layer of this object to be deleted between
        # the time when the focus change signal is emitted and time when the
        # slots are dispatched by the Qt event loop. This may be a bug in PyQt4.
        if qt_api == 'pyqt':
            import sip
            if sip.isdeleted(self):
                return

        if self._repeat_focus_changes:
            self.focus_changed.emit(old, new)

        if new is None:
            return
        elif isinstance(new, _DragableTabBar):
            ntw = new.parent()
            ntidx = ntw.currentIndex()
        else:
            ntw, ntidx = self._tab_widget_of(new)

        if ntw is not None:
            self._set_current_tab(ntw, ntidx)

        # See if the widget that has lost the focus is ours.
        otw, _ = self._tab_widget_of(old)

        if otw is not None or ntw is not None:
            if ntw is None:
                nw = None
            else:
                nw = ntw.widget(ntidx)

            self.editor_has_focus.emit(nw)

    def _tab_widget_of(self, target):
        """ Return the tab widget and index of the widget that contains the
        given widget.
        """

        for tw in self.findChildren(_TabWidget, None):
            for tidx in range(tw.count()):
                w = tw.widget(tidx)

                if w is not None and w.isAncestorOf(target):
                    return (tw, tidx)

        return (None, None)

    def _move_left(self, tw, tidx):
        """ Move the current tab to the one logically to the left. """

        tidx -= 1

        if tidx < 0:
            # Find the tab widget logically to the left.
            twlist = self.findChildren(_TabWidget, None)
            i = twlist.index(tw)
            i -= 1

            if i < 0:
                i = len(twlist) - 1

            tw = twlist[i]

            # Move the to right most tab.
            tidx = tw.count() - 1

        self._set_current_tab(tw, tidx)
        tw.setFocus()

    def _move_right(self, tw, tidx):
        """ Move the current tab to the one logically to the right. """

        tidx += 1

        if tidx >= tw.count():
            # Find the tab widget logically to the right.
            twlist = self.findChildren(_TabWidget, None)
            i = twlist.index(tw)
            i += 1

            if i >= len(twlist):
                i = 0

            tw = twlist[i]

            # Move the to left most tab.
            tidx = 0

        self._set_current_tab(tw, tidx)
        tw.setFocus()

    def _select(self, pos):
        tw, hs, hs_geom = self._hotspot(pos)

        # See if the hotspot has changed.
        if self._selected_tab_widget is not tw or self._selected_hotspot != hs:
            if self._selected_tab_widget is not None:
                self._rband.hide()

            if tw is not None and hs != self._HS_NONE:
                if self._rband:
                    self._rband.deleteLater()
                position = QtCore.QPoint(*hs_geom[0:2])
                window = tw.window()
                self._rband = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle,
                                                window)
                self._rband.move(window.mapFromGlobal(position))
                self._rband.resize(*hs_geom[2:4])
                self._rband.show()

            self._selected_tab_widget = tw
            self._selected_hotspot = hs

    def _drop(self, pos, stab_w, stab):
        self._rband.hide()

        # Get the destination locations.
        dtab_w = self._selected_tab_widget
        dhs = self._selected_hotspot
        if dhs == self._HS_NONE:
            return
        elif dhs != self._HS_OUTSIDE:
            dsplit_w = dtab_w.parent()
            while not isinstance(dsplit_w, SplitTabWidget):
                dsplit_w = dsplit_w.parent()

        self._selected_tab_widget = None
        self._selected_hotspot = self._HS_NONE

        # See if the tab is being moved to a new window.
        if dhs == self._HS_OUTSIDE:
            # Disable tab tear-out for now. It works, but this is something that
            # should be turned on manually. We need an interface for this.
            #ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab)
            #self.new_window_request.emit(pos, twidg)
            return

        # See if the tab is being moved to an existing tab widget.
        if dhs >= 0 or dhs == self._HS_AFTER_LAST_TAB:
            # Make sure it really is being moved.
            if stab_w is dtab_w:
                if stab == dhs:
                    return

                if dhs == self._HS_AFTER_LAST_TAB and stab == stab_w.count(
                ) - 1:
                    return

            QtGui.QApplication.instance().blockSignals(True)

            ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(
                stab_w, stab)

            if dhs == self._HS_AFTER_LAST_TAB:
                idx = dtab_w.addTab(twidg, ticon, ttext)
                dtab_w.tabBar().setTabTextColor(idx, ttextcolor)
            elif dtab_w is stab_w:
                # Adjust the index if necessary in case the removal of the tab
                # from its old position has skewed things.
                dst = dhs

                if dhs > stab:
                    dst -= 1

                idx = dtab_w.insertTab(dst, twidg, ticon, ttext)
                dtab_w.tabBar().setTabTextColor(idx, ttextcolor)
            else:
                idx = dtab_w.insertTab(dhs, twidg, ticon, ttext)
                dtab_w.tabBar().setTabTextColor(idx, ttextcolor)

            if (tbuttn):
                dtab_w.show_button(idx)
            dsplit_w._set_current_tab(dtab_w, idx)

        else:
            # Ignore drops to the same tab widget when it only has one tab.
            if stab_w is dtab_w and stab_w.count() == 1:
                return

            QtGui.QApplication.instance().blockSignals(True)

            # Remove the tab from its current tab widget and create a new one
            # for it.
            ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(
                stab_w, stab)
            new_tw = _TabWidget(dsplit_w)
            idx = new_tw.addTab(twidg, ticon, ttext)
            new_tw.tabBar().setTabTextColor(0, ttextcolor)
            if tbuttn:
                new_tw.show_button(idx)

            # Get the splitter containing the destination tab widget.
            dspl = dtab_w.parent()
            dspl_idx = dspl.indexOf(dtab_w)

            if dhs in (self._HS_NORTH, self._HS_SOUTH):
                dspl, dspl_idx = dsplit_w._horizontal_split(
                    dspl, dspl_idx, dhs)
            else:
                dspl, dspl_idx = dsplit_w._vertical_split(dspl, dspl_idx, dhs)

            # Add the new tab widget in the right place.
            dspl.insertWidget(dspl_idx, new_tw)

            dsplit_w._set_current_tab(new_tw, 0)

        dsplit_w._set_focus()

        # Signal that the tab's SplitTabWidget has changed, if necessary.
        if dsplit_w != self:
            self.tab_window_changed.emit(twidg)

        QtGui.QApplication.instance().blockSignals(False)

    def _horizontal_split(self, spl, idx, hs):
        """ Returns a tuple of the splitter and index where the new tab widget
        should be put.
        """

        if spl.orientation() == QtCore.Qt.Vertical:
            if hs == self._HS_SOUTH:
                idx += 1
        elif spl is self and spl.count() == 1:
            # The splitter is the root and only has one child so we can just
            # change its orientation.
            spl.setOrientation(QtCore.Qt.Vertical)

            if hs == self._HS_SOUTH:
                idx = -1
        else:
            new_spl = QtGui.QSplitter(QtCore.Qt.Vertical)
            new_spl.addWidget(spl.widget(idx))
            spl.insertWidget(idx, new_spl)

            if hs == self._HS_SOUTH:
                idx = -1
            else:
                idx = 0

            spl = new_spl

        return (spl, idx)

    def _vertical_split(self, spl, idx, hs):
        """ Returns a tuple of the splitter and index where the new tab widget
        should be put.
        """

        if spl.orientation() == QtCore.Qt.Horizontal:
            if hs == self._HS_EAST:
                idx += 1
        elif spl is self and spl.count() == 1:
            # The splitter is the root and only has one child so we can just
            # change its orientation.
            spl.setOrientation(QtCore.Qt.Horizontal)

            if hs == self._HS_EAST:
                idx = -1
        else:
            new_spl = QtGui.QSplitter(QtCore.Qt.Horizontal)
            new_spl.addWidget(spl.widget(idx))
            spl.insertWidget(idx, new_spl)

            if hs == self._HS_EAST:
                idx = -1
            else:
                idx = 0

            spl = new_spl

        return (spl, idx)

    def _remove_tab(self, tab_w, tab):
        """ Remove a tab from a tab widget and return a tuple of the icon,
        label text and the widget so that it can be recreated.
        """

        icon = tab_w.tabIcon(tab)
        text = tab_w.tabText(tab)
        text_color = tab_w.tabBar().tabTextColor(tab)
        button = tab_w.tabBar().tabButton(tab, QtGui.QTabBar.LeftSide)
        w = tab_w.widget(tab)
        tab_w.removeTab(tab)

        return (icon, text, text_color, button, w)

    def _hotspot(self, pos):
        """ Return a tuple of the tab widget, hotspot and hostspot geometry (as
        a tuple) at the given position.
        """
        global_pos = self.mapToGlobal(pos)
        miss = (None, self._HS_NONE, None)

        # Get the bounding rect of the cloned QTbarBar.
        top_widget = QtGui.QApplication.instance().topLevelAt(global_pos)
        if isinstance(top_widget, QtGui.QTabBar):
            cloned_rect = top_widget.frameGeometry()
        else:
            cloned_rect = None

        # Determine which visible SplitTabWidget, if any, is under the cursor
        # (compensating for the cloned QTabBar that may be rendered over it).
        split_widget = None
        for top_widget in QtGui.QApplication.instance().topLevelWidgets():
            for split_widget in top_widget.findChildren(SplitTabWidget, None):
                visible_region = split_widget.visibleRegion()
                widget_pos = split_widget.mapFromGlobal(global_pos)
                if cloned_rect and split_widget.geometry().contains(
                        widget_pos):
                    visible_rect = visible_region.boundingRect()
                    widget_rect = QtCore.QRect(
                        split_widget.mapFromGlobal(cloned_rect.topLeft()),
                        split_widget.mapFromGlobal(cloned_rect.bottomRight()))
                    if not visible_rect.intersected(widget_rect).isEmpty():
                        break
                elif visible_region.contains(widget_pos):
                    break
            else:
                split_widget = None
            if split_widget:
                break

        # Handle a drag outside of any split tab widget.
        if not split_widget:
            if self.window().frameGeometry().contains(global_pos):
                return miss
            else:
                return (None, self._HS_OUTSIDE, None)

        # Go through each tab widget.
        pos = split_widget.mapFromGlobal(global_pos)
        for tw in split_widget.findChildren(_TabWidget, None):
            if tw.geometry().contains(tw.parent().mapFrom(split_widget, pos)):
                break
        else:
            return miss

        # See if the hotspot is in the widget area.
        widg = tw.currentWidget()
        if widg is not None:

            # Get the widget's position relative to its parent.
            wpos = widg.parent().mapFrom(split_widget, pos)

            if widg.geometry().contains(wpos):
                # Get the position of the widget relative to itself (ie. the
                # top left corner is (0, 0)).
                p = widg.mapFromParent(wpos)
                x = p.x()
                y = p.y()
                h = widg.height()
                w = widg.width()

                # Get the global position of the widget.
                gpos = widg.mapToGlobal(widg.pos())
                gx = gpos.x()
                gy = gpos.y()

                # The corners of the widget belong to the north and south
                # sides.
                if y < h / 4:
                    return (tw, self._HS_NORTH, (gx, gy, w, h / 4))

                if y >= (3 * h) / 4:
                    return (tw, self._HS_SOUTH, (gx, gy + (3 * h) / 4, w,
                                                 h / 4))

                if x < w / 4:
                    return (tw, self._HS_WEST, (gx, gy, w / 4, h))

                if x >= (3 * w) / 4:
                    return (tw, self._HS_EAST, (gx + (3 * w) / 4, gy, w / 4,
                                                h))

                return miss

        # See if the hotspot is in the tab area.
        tpos = tw.mapFrom(split_widget, pos)
        tab_bar = tw.tabBar()
        top_bottom = tw.tabPosition() in (QtGui.QTabWidget.North,
                                          QtGui.QTabWidget.South)
        for i in range(tw.count()):
            rect = tab_bar.tabRect(i)

            if rect.contains(tpos):
                w = rect.width()
                h = rect.height()

                # Get the global position.
                gpos = tab_bar.mapToGlobal(rect.topLeft())
                gx = gpos.x()
                gy = gpos.y()

                if top_bottom:
                    off = pos.x() - rect.x()
                    ext = w
                    gx -= w / 2
                else:
                    off = pos.y() - rect.y()
                    ext = h
                    gy -= h / 2

                # See if it is in the left (or top) half or the right (or
                # bottom) half.
                if off < ext / 2:
                    return (tw, i, (gx, gy, w, h))

                if top_bottom:
                    gx += w
                else:
                    gy += h

                if i + 1 == tw.count():
                    return (tw, self._HS_AFTER_LAST_TAB, (gx, gy, w, h))

                return (tw, i + 1, (gx, gy, w, h))
        else:
            rect = tab_bar.rect()
            if rect.contains(tpos):
                gpos = tab_bar.mapToGlobal(rect.topLeft())
                gx = gpos.x()
                gy = gpos.y()
                w = rect.width()
                h = rect.height()
                if top_bottom:
                    tab_widths = sum(
                        tab_bar.tabRect(i).width()
                        for i in range(tab_bar.count()))
                    w -= tab_widths
                    gx += tab_widths
                else:
                    tab_heights = sum(
                        tab_bar.tabRect(i).height()
                        for i in range(tab_bar.count()))
                    h -= tab_heights
                    gy -= tab_heights
                return (tw, self._HS_AFTER_LAST_TAB, (gx, gy, w, h))

        return miss
Пример #5
0
class CodeWidget(QtGui.QPlainTextEdit):
    """ A widget for viewing and editing code.
    """

    ###########################################################################
    # CodeWidget interface
    ###########################################################################
    focus_lost = QtCore.Signal()

    def __init__(self,
                 parent,
                 should_highlight_current_line=True,
                 font=None,
                 lexer=None):
        super(CodeWidget, self).__init__(parent)

        self.highlighter = PygmentsHighlighter(self.document(), lexer)
        self.line_number_widget = LineNumberWidget(self)
        self.status_widget = StatusGutterWidget(self)

        if font is None:
            # Set a decent fixed width font for this platform.
            font = QtGui.QFont()
            if sys.platform == 'win32':
                # Prefer Consolas, but fall back to Courier if necessary.
                font.setFamily('Consolas')
                if not font.exactMatch():
                    font.setFamily('Courier')
            elif sys.platform == 'darwin':
                font.setFamily('Monaco')
            else:
                font.setFamily('Monospace')
            font.setStyleHint(QtGui.QFont.TypeWriter)
        self.set_font(font)

        # Whether we should highlight the current line or not.
        self.should_highlight_current_line = should_highlight_current_line

        # What that highlight color should be.
        self.line_highlight_color = QtGui.QColor(QtCore.Qt.yellow).lighter(160)

        # Auto-indentation behavior
        self.auto_indent = True
        self.smart_backspace = True

        # Tab settings
        self.tabs_as_spaces = True
        self.tab_width = 4

        self.indent_character = ':'
        self.comment_character = '#'

        # Set up gutter widget and current line highlighting
        self.blockCountChanged.connect(self.update_line_number_width)
        self.updateRequest.connect(self.update_line_numbers)
        self.cursorPositionChanged.connect(self.highlight_current_line)

        self.update_line_number_width()
        self.highlight_current_line()

        # Don't wrap text
        self.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap)

        # Key bindings
        self.indent_key = QtGui.QKeySequence(QtCore.Qt.Key_Tab)
        self.unindent_key = QtGui.QKeySequence(QtCore.Qt.SHIFT +
                                               QtCore.Qt.Key_Backtab)
        self.comment_key = QtGui.QKeySequence(QtCore.Qt.CTRL +
                                              QtCore.Qt.Key_Slash)
        self.backspace_key = QtGui.QKeySequence(QtCore.Qt.Key_Backspace)

    def lines(self):
        """ Return the number of lines.
        """
        return self.blockCount()

    def set_line_column(self, line, column):
        """ Move the cursor to a particular line/column number.

        These line and column numbers are 1-indexed.
        """
        # Allow the caller to ignore either line or column by passing None.
        line0, col0 = self.get_line_column()
        if line is None:
            line = line0
        if column is None:
            column = col0
        line -= 1
        column -= 1
        block = self.document().findBlockByLineNumber(line)
        line_start = block.position()
        position = line_start + column
        cursor = self.textCursor()
        cursor.setPosition(position)
        self.setTextCursor(cursor)

    def get_line_column(self):
        """ Get the current line and column numbers.

        These line and column numbers are 1-indexed.
        """
        cursor = self.textCursor()
        pos = cursor.position()
        line = cursor.blockNumber() + 1
        line_start = cursor.block().position()
        column = pos - line_start + 1
        return line, column

    def get_selected_text(self):
        """ Return the currently selected text.
        """
        return unicode(self.textCursor().selectedText())

    def set_font(self, font):
        """ Set the new QFont.
        """
        self.document().setDefaultFont(font)
        self.line_number_widget.set_font(font)
        self.update_line_number_width()

    def update_line_number_width(self, nblocks=0):
        """ Update the width of the line number widget.
        """
        left = 0
        if not self.line_number_widget.isHidden():
            left = self.line_number_widget.digits_width()
        self.setViewportMargins(left, 0, 0, 0)

    def update_line_numbers(self, rect, dy):
        """ Update the line numbers.
        """
        if dy:
            self.line_number_widget.scroll(0, dy)
        self.line_number_widget.update(0, rect.y(),
                                       self.line_number_widget.width(),
                                       rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_width()

    def set_info_lines(self, info_lines):
        self.status_widget.info_lines = info_lines
        self.status_widget.update()

    def set_warn_lines(self, warn_lines):
        self.status_widget.warn_lines = warn_lines
        self.status_widget.update()

    def set_error_lines(self, error_lines):
        self.status_widget.error_lines = error_lines
        self.status_widget.update()

    def highlight_current_line(self):
        """ Highlight the line with the cursor.
        """
        if self.should_highlight_current_line:
            selection = QtGui.QTextEdit.ExtraSelection()
            selection.format.setBackground(self.line_highlight_color)
            selection.format.setProperty(QtGui.QTextFormat.FullWidthSelection,
                                         True)
            selection.cursor = self.textCursor()
            selection.cursor.clearSelection()
            self.setExtraSelections([selection])

    def autoindent_newline(self):
        tab = '\t'
        if self.tabs_as_spaces:
            tab = ' ' * self.tab_width

        cursor = self.textCursor()
        text = cursor.block().text()
        trimmed = text.rstrip()
        current_indent_pos = self._get_indent_position(text)

        cursor.beginEditBlock()

        # Create the new line. There is no need to move to the new block, as
        # the insertBlock will do that automatically
        cursor.insertBlock()

        # Remove any leading whitespace from the current line
        after = cursor.block().text()
        trimmed_after = after.rstrip()
        pos = after.index(trimmed_after)
        for i in range(pos):
            cursor.deleteChar()

        if self.indent_character and trimmed.endswith(self.indent_character):
            # indent one level
            indent = text[:current_indent_pos] + tab
        else:
            # indent to the same level
            indent = text[:current_indent_pos]
        cursor.insertText(indent)

        cursor.endEditBlock()
        self.ensureCursorVisible()

    def block_indent(self):
        cursor = self.textCursor()

        if not cursor.hasSelection():
            # Insert a tabulator
            self.line_indent(cursor)

        else:
            # Indent every selected line
            sel_blocks = self._get_selected_blocks()

            cursor.clearSelection()
            cursor.beginEditBlock()

            for block in sel_blocks:
                cursor.setPosition(block.position())
                self.line_indent(cursor)

            cursor.endEditBlock()
            self._show_selected_blocks(sel_blocks)

    def block_unindent(self):
        cursor = self.textCursor()

        if not cursor.hasSelection():
            # Unindent current line
            position = cursor.position()
            cursor.beginEditBlock()

            removed = self.line_unindent(cursor)
            position = max(position - removed, 0)

            cursor.endEditBlock()
            cursor.setPosition(position)
            self.setTextCursor(cursor)

        else:
            # Unindent every selected line
            sel_blocks = self._get_selected_blocks()

            cursor.clearSelection()
            cursor.beginEditBlock()

            for block in sel_blocks:
                cursor.setPosition(block.position())
                self.line_unindent(cursor)

            cursor.endEditBlock()
            self._show_selected_blocks(sel_blocks)

    def block_comment(self):
        """the comment char will be placed at the first non-whitespace
            char of the first line. For example:
                if foo:
                    bar
            will be commented as:
                #if foo:
                #    bar
        """
        cursor = self.textCursor()

        if not cursor.hasSelection():
            text = cursor.block().text()
            current_indent_pos = self._get_indent_position(text)

            if text[current_indent_pos] == self.comment_character:
                self.line_uncomment(cursor, current_indent_pos)
            else:
                self.line_comment(cursor, current_indent_pos)

        else:
            sel_blocks = self._get_selected_blocks()
            text = sel_blocks[0].text()
            indent_pos = self._get_indent_position(text)

            comment = True
            for block in sel_blocks:
                text = block.text()
                if len(text) > indent_pos and \
                        text[indent_pos] == self.comment_character:
                    # Already commented.
                    comment = False
                    break

            cursor.clearSelection()
            cursor.beginEditBlock()

            for block in sel_blocks:
                cursor.setPosition(block.position())
                if comment:
                    if block.length() < indent_pos:
                        cursor.insertText(' ' * indent_pos)
                    self.line_comment(cursor, indent_pos)
                else:
                    self.line_uncomment(cursor, indent_pos)
            cursor.endEditBlock()
            self._show_selected_blocks(sel_blocks)

    def line_comment(self, cursor, position):
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
        cursor.movePosition(QtGui.QTextCursor.Right,
                            QtGui.QTextCursor.MoveAnchor, position)
        cursor.insertText(self.comment_character)

    def line_uncomment(self, cursor, position=0):
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
        text = cursor.block().text()
        new_text = text[:position] + text[position + 1:]
        cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
                            QtGui.QTextCursor.KeepAnchor)
        cursor.removeSelectedText()
        cursor.insertText(new_text)

    def line_indent(self, cursor):
        tab = '\t'
        if self.tabs_as_spaces:
            tab = '    '

        cursor.insertText(tab)

    def line_unindent(self, cursor):
        """ Unindents the cursor's line. Returns the number of characters
            removed.
        """
        tab = '\t'
        if self.tabs_as_spaces:
            tab = '    '

        cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
        if cursor.block().text().startswith(tab):
            new_text = cursor.block().text()[len(tab):]
            cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
                                QtGui.QTextCursor.KeepAnchor)
            cursor.removeSelectedText()
            cursor.insertText(new_text)
            return len(tab)
        else:
            return 0

    def word_under_cursor(self):
        """ Return the word under the cursor.
        """
        cursor = self.textCursor()
        cursor.select(QtGui.QTextCursor.WordUnderCursor)
        return unicode(cursor.selectedText())

    ###########################################################################
    # QWidget interface
    ###########################################################################

    # FIXME: This is a quick hack to be able to access the keyPressEvent
    # from the rest editor. This should be changed to work within the traits
    # framework.
    def keyPressEvent_action(self, event):
        pass

    def keyPressEvent(self, event):
        if self.isReadOnly():
            return super(CodeWidget, self).keyPressEvent(event)

        key_sequence = QtGui.QKeySequence(event.key() + int(event.modifiers()))

        self.keyPressEvent_action(event)  # FIXME: see above

        # If the cursor is in the middle of the first line, pressing the "up"
        # key causes the cursor to go to the start of the first line, i.e. the
        # beginning of the document. Likewise, if the cursor is somewhere in the
        # last line, the "down" key causes it to go to the end.
        cursor = self.textCursor()
        if key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Up)):
            cursor.movePosition(QtGui.QTextCursor.StartOfLine)
            if cursor.atStart():
                self.setTextCursor(cursor)
                event.accept()
        elif key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Down)):
            cursor.movePosition(QtGui.QTextCursor.EndOfLine)
            if cursor.atEnd():
                self.setTextCursor(cursor)
                event.accept()

        elif self.auto_indent and \
                key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Return)):
            event.accept()
            return self.autoindent_newline()
        elif key_sequence.matches(self.indent_key):
            event.accept()
            return self.block_indent()
        elif key_sequence.matches(self.unindent_key):
            event.accept()
            return self.block_unindent()
        elif key_sequence.matches(self.comment_key):
            event.accept()
            return self.block_comment()
        elif self.auto_indent and self.smart_backspace and \
                key_sequence.matches(self.backspace_key) and \
                self._backspace_should_unindent():
            event.accept()
            return self.block_unindent()

        return super(CodeWidget, self).keyPressEvent(event)

    def resizeEvent(self, event):
        QtGui.QPlainTextEdit.resizeEvent(self, event)
        contents = self.contentsRect()
        self.line_number_widget.setGeometry(
            QtCore.QRect(contents.left(), contents.top(),
                         self.line_number_widget.digits_width(),
                         contents.height()))

        # use the viewport width to determine the right edge. This allows for
        # the propper placement w/ and w/o the scrollbar
        right_pos = self.viewport().width() + self.line_number_widget.width() + 1\
                    - self.status_widget.sizeHint().width()
        self.status_widget.setGeometry(
            QtCore.QRect(right_pos, contents.top(),
                         self.status_widget.sizeHint().width(),
                         contents.height()))

    def focusOutEvent(self, event):
        QtGui.QPlainTextEdit.focusOutEvent(self, event)
        self.focus_lost.emit()

    def sizeHint(self):
        # Suggest a size that is 80 characters wide and 40 lines tall.
        style = self.style()
        opt = QtGui.QStyleOptionHeader()
        font_metrics = QtGui.QFontMetrics(self.document().defaultFont())
        width = font_metrics.width(' ') * 80
        width += self.line_number_widget.sizeHint().width()
        width += self.status_widget.sizeHint().width()
        width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self)
        height = font_metrics.height() * 40
        return QtCore.QSize(width, height)

    ###########################################################################
    # Private methods
    ###########################################################################

    def _get_indent_position(self, line):
        trimmed = line.rstrip()
        if len(trimmed) != 0:
            return line.index(trimmed)
        else:
            # if line is all spaces, treat it as the indent position
            return len(line)

    def _show_selected_blocks(self, selected_blocks):
        """ Assumes contiguous blocks
        """
        cursor = self.textCursor()
        cursor.clearSelection()
        cursor.setPosition(selected_blocks[0].position())
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
        cursor.movePosition(QtGui.QTextCursor.NextBlock,
                            QtGui.QTextCursor.KeepAnchor, len(selected_blocks))
        cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
                            QtGui.QTextCursor.KeepAnchor)

        self.setTextCursor(cursor)

    def _get_selected_blocks(self):
        cursor = self.textCursor()
        if cursor.position() > cursor.anchor():
            move_op = QtGui.QTextCursor.PreviousBlock
            start_pos = cursor.anchor()
            end_pos = cursor.position()
        else:
            move_op = QtGui.QTextCursor.NextBlock
            start_pos = cursor.position()
            end_pos = cursor.anchor()

        cursor.setPosition(start_pos)
        cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
        blocks = [cursor.block()]

        while cursor.movePosition(QtGui.QTextCursor.NextBlock):
            block = cursor.block()
            if block.position() < end_pos:
                blocks.append(block)

        return blocks

    def _backspace_should_unindent(self):
        cursor = self.textCursor()
        # Don't unindent if we have a selection.
        if cursor.hasSelection():
            return False
        column = cursor.columnNumber()
        # Don't unindent if we are at the beggining of the line
        if column < self.tab_width:
            return False
        else:
            # Unindent if we are at the indent position
            return column == self._get_indent_position(cursor.block().text())
Пример #6
0
class myCodeWidget(CodeWidget):
    dclicked = QtCore.Signal((str, ))
    modified_select = QtCore.Signal((str, ))
    alt_select = QtCore.Signal((str, int, int))

    _current_pos = None
    gotos = ['gosub']

    popup = None

    def __init__(self, *args, **kw):
        super(myCodeWidget, self).__init__(*args, **kw)

        self.setMouseTracking(True)

    def keyPressEvent(self, event):
        super(myCodeWidget, self).keyPressEvent(event)

        if event.modifiers() & Qt.ControlModifier:
            QApplication.setOverrideCursor(QCursor(Qt.PointingHandCursor))
            # self.setMouseTracking(True)

    def keyReleaseEvent(self, event):
        super(myCodeWidget, self).keyReleaseEvent(event)
        # self.setMouseTracking(False)
        QApplication.restoreOverrideCursor()
        if self.popup:
            self.popup.close()
            self.popup = None

    def clear_selected(self):
        # self.setMouseTracking(False)
        self.clear_underline()
        QApplication.restoreOverrideCursor()

    def clear_underline(self):
        cursor = self.textCursor()
        cursor.select(QTextCursor.Document)
        fmt = cursor.charFormat()
        fmt.setFontUnderline(False)
        cursor.beginEditBlock()
        cursor.setCharFormat(fmt)
        cursor.endEditBlock()

    def replace_selection(self, txt):

        cursor = self.textCursor()
        #  #     QtGui.QTextCursor.StartOfLine, QtGui.QTextCursor.KeepAnchor, txt.count('\n'))
        cursor.beginEditBlock()
        cursor.removeSelectedText()
        cursor.insertText(txt)
        cursor.endEditBlock()
        # cursor.movePosition(
        #     QtGui.QTextCursor.Left, QtGui.QTextCursor.MoveAnchor,len(txt))
        # cursor.movePosition(
        #     QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor,len(txt))
        self.setTextCursor(cursor)

    def mouseMoveEvent(self, event):
        if event.modifiers() & Qt.ControlModifier:
            self.clear_underline()
            cursor, line = self._get_line_cursor(event.pos())

            for goto in self.gotos:
                if line.strip().startswith(goto):
                    fmt = QTextCharFormat()
                    fmt.setFontUnderline(True)
                    fmt.setUnderlineStyle(QTextCharFormat.WaveUnderline)
                    fmt.setUnderlineColor(QtGui.QColor('blue'))
                    # cursor.clearSelection()
                    cursor.select(QTextCursor.BlockUnderCursor)

                    cursor.beginEditBlock()
                    cursor.setCharFormat(fmt)
                    cursor.endEditBlock()

                    break

        super(myCodeWidget, self).mouseMoveEvent(event)

    # def mouseReleaseEvent(self, event):
    #     if event.modifiers() & Qt.AltModifier:
    #         print 'popup', self.popup
    #         if self.popup:
    #             self.popup.close()
    #             self.popup = None

    def mousePressEvent(self, event):
        if event.modifiers() & Qt.ControlModifier:  # on Mac OSX "command"
            cursor, line = self._get_line_cursor(event.pos())
            self.modified_select.emit(line.strip())
            self.clear_selected()
        elif event.modifiers() & Qt.AltModifier:  # On Mac OSX "option"
            cursor, line = self._get_line_cursor(event.pos())
            pt = self.mapToGlobal(event.pos())
            self.alt_select.emit(line.strip(), pt.x(), pt.y())

        self._current_pos = None
        super(myCodeWidget, self).mousePressEvent(event)

    def get_current_line(self):
        cursor = self.textCursor()
        cursor.select(QTextCursor.LineUnderCursor)
        line = cursor.selectedText()
        return line.strip()

    def _get_line_cursor(self, pos):
        cursor = self.cursorForPosition(pos)
        cursor.select(QTextCursor.LineUnderCursor)
        line = cursor.selectedText()
        return cursor, line

    def mouseDoubleClickEvent(self, event):
        self.clear_selected()

        self._current_pos = event.pos()
        cursor, line = self._get_line_cursor(self._current_pos)
        self.dclicked.emit(line.strip())

    def replace_command(self, cmd):
        if self._current_pos:
            cursor, line = self._get_line_cursor(self._current_pos)
            lead = len(line) - len(line.lstrip())
            cursor.beginEditBlock()
            cursor.removeSelectedText()
            cursor.insertText('{}{}'.format(' ' * lead, cmd))
            cursor.endEditBlock()