Example #1
0
class BlockingBusy(QDialog):

    def __init__(self, msg, parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.msg = QLabel(msg)
        #self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.start()
        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def reject(self):
        pass # Cannot cancel this dialog
Example #2
0
class BlockingBusy(QDialog):
    def __init__(self, msg, parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.msg = QLabel(msg)
        #self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.start()
        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def reject(self):
        pass  # Cannot cancel this dialog
Example #3
0
class RatingDelegate(QStyledItemDelegate):  # {{{
    def __init__(self, *args, **kwargs):
        QStyledItemDelegate.__init__(self, *args, **kwargs)
        self.rf = QFont(rating_font())
        self.em = Qt.ElideMiddle
        delta = 0
        if iswindows and sys.getwindowsversion().major >= 6:
            delta = 2
        self.rf.setPointSize(
            QFontInfo(QApplication.font()).pointSize() + delta)

    def createEditor(self, parent, option, index):
        sb = QStyledItemDelegate.createEditor(self, parent, option, index)
        sb.setMinimum(0)
        sb.setMaximum(5)
        sb.setSuffix(' ' + _('stars'))
        return sb

    def displayText(self, value, locale):
        r = value.toInt()[0]
        if r < 0 or r > 5:
            r = 0
        return u'\u2605' * r

    def sizeHint(self, option, index):
        option.font = self.rf
        option.textElideMode = self.em
        return QStyledItemDelegate.sizeHint(self, option, index)

    def paint(self, painter, option, index):
        option.font = self.rf
        option.textElideMode = self.em
        return QStyledItemDelegate.paint(self, painter, option, index)
Example #4
0
class RatingDelegate(QStyledItemDelegate):  # {{{

    def __init__(self, *args, **kwargs):
        QStyledItemDelegate.__init__(self, *args, **kwargs)
        self.rf = QFont(rating_font())
        self.em = Qt.ElideMiddle
        delta = 0
        if iswindows and sys.getwindowsversion().major >= 6:
            delta = 2
        self.rf.setPointSize(QFontInfo(QApplication.font()).pointSize()+delta)

    def createEditor(self, parent, option, index):
        sb = QStyledItemDelegate.createEditor(self, parent, option, index)
        sb.setMinimum(0)
        sb.setMaximum(5)
        sb.setSuffix(' ' + _('stars'))
        return sb

    def displayText(self, value, locale):
        r = value.toInt()[0]
        if r < 0 or r > 5:
            r = 0
        return u'\u2605'*r

    def sizeHint(self, option, index):
        option.font = self.rf
        option.textElideMode = self.em
        return QStyledItemDelegate.sizeHint(self, option, index)

    def paint(self, painter, option, index):
        option.font = self.rf
        option.textElideMode = self.em
        return QStyledItemDelegate.paint(self, painter, option, index)
Example #5
0
    def do_paint(self, painter, option, index):
        text = unicode(index.data(Qt.DisplayRole).toString())
        font = QFont(option.font)
        font.setPointSize(QFontInfo(font).pointSize() * 1.5)
        font2 = QFont(font)
        font2.setFamily(text)

        system, has_latin = writing_system_for_font(font2)
        if has_latin:
            font = font2

        r = option.rect

        if option.state & QStyle.State_Selected:
            painter.setPen(QPen(option.palette.highlightedText(), 0))

        if (option.direction == Qt.RightToLeft):
            r.setRight(r.right() - 4)
        else:
            r.setLeft(r.left() + 4)

        painter.setFont(font)
        painter.drawText(r, Qt.AlignVCenter|Qt.AlignLeading|Qt.TextSingleLine, text)

        if (system != QFontDatabase.Any):
            w = painter.fontMetrics().width(text + "  ")
            painter.setFont(font2)
            sample = QFontDatabase().writingSystemSample(system)
            if (option.direction == Qt.RightToLeft):
                r.setRight(r.right() - w)
            else:
                r.setLeft(r.left() + w)
            painter.drawText(r, Qt.AlignVCenter|Qt.AlignLeading|Qt.TextSingleLine, sample)
Example #6
0
 def _getHeaderFont(self):
     """
     Returns the font used for the header.
     
     @return: the header font
     @rtype:  QFont
     """
     font = QFont()
     font.setFamily(PM_HEADER_FONT)
     font.setPointSize(PM_HEADER_FONT_POINT_SIZE)
     font.setBold(PM_HEADER_FONT_BOLD)
     return font
    def __init__(self, parent, icon_name, title):
        QHBoxLayout.__init__(self)
        self.title_image_label = QLabel(parent)
        self.update_title_icon(icon_name)
        self.addWidget(self.title_image_label)

        title_font = QFont()
        title_font.setPointSize(16)
        shelf_label = QLabel(title, parent)
        shelf_label.setFont(title_font)
        self.addWidget(shelf_label)
        self.insertStretch(-1)
Example #8
0
 def _getHeaderFont(self):
     """
     Returns the font used for the header.
     
     @return: the header font
     @rtype:  QFont
     """
     font = QFont()
     font.setFamily(PM_HEADER_FONT)
     font.setPointSize(PM_HEADER_FONT_POINT_SIZE)
     font.setBold(PM_HEADER_FONT_BOLD)
     return font
Example #9
0
    def __init__(self, parent, icon_name, title):
        QHBoxLayout.__init__(self)
        self.title_image_label = QLabel(parent)
        self.update_title_icon(icon_name)
        self.addWidget(self.title_image_label)

        title_font = QFont()
        title_font.setPointSize(16)
        shelf_label = QLabel(title, parent)
        shelf_label.setFont(title_font)
        self.addWidget(shelf_label)
        self.insertStretch(-1)
 def getButtonFont(self):
     """
     Returns the font for the tool buttons in the grid.
     
     @return: Button font.
     @rtype:  U{B{QFont}<http://doc.trolltech.com/4/qfont.html>}
     """
     # Font for tool buttons.
     buttonFont = QFont(self.font())
     buttonFont.setFamily(BUTTON_FONT)
     buttonFont.setPointSize(BUTTON_FONT_POINT_SIZE)
     buttonFont.setBold(BUTTON_FONT_BOLD)
     return buttonFont              
 def getButtonFont(self):
     """
     Returns the font for the tool buttons in the grid.
     
     @return: Button font.
     @rtype:  U{B{QFont}<http://doc.trolltech.com/4/qfont.html>}
     """
     # Font for tool buttons.
     buttonFont = QFont(self.font())
     buttonFont.setFamily(BUTTON_FONT)
     buttonFont.setPointSize(BUTTON_FONT_POINT_SIZE)
     buttonFont.setBold(BUTTON_FONT_BOLD)
     return buttonFont
 def __init__(self, parent, icon_name, title):
     QHBoxLayout.__init__(self)
     title_font = QFont()
     title_font.setPointSize(16)
     title_image_label = QLabel(parent)
     pixmap = get_pixmap(icon_name)
     if pixmap is None:
         error_dialog(parent, _('Restart required'),
                      _('You must restart Calibre before using this plugin!'), show=True)
     else:
         title_image_label.setPixmap(pixmap)
     title_image_label.setMaximumSize(32, 32)
     title_image_label.setScaledContents(True)
     self.addWidget(title_image_label)
     shelf_label = QLabel(title, parent)
     shelf_label.setFont(title_font)
     self.addWidget(shelf_label)
     self.insertStretch(-1)
 def __init__(self, parent, icon_name, title):
     QHBoxLayout.__init__(self)
     title_font = QFont()
     title_font.setPointSize(16)
     title_image_label = QLabel(parent)
     pixmap = QPixmap()
     pixmap.load(I(icon_name))
     if pixmap is None:
         error_dialog(parent, _('Restart required'),
                      _('You must restart Calibre before using this plugin!'), show=True)
     else:
         title_image_label.setPixmap(pixmap)
     title_image_label.setMaximumSize(32, 32)
     title_image_label.setScaledContents(True)
     self.addWidget(title_image_label)
     shelf_label = QLabel(title, parent)
     shelf_label.setFont(title_font)
     self.addWidget(shelf_label)
     self.insertStretch(-1)
Example #14
0
 def __init__(self, widget: Dependency('widget/', lambda v: hasattr(v.view, 'mode'))):
     view = widget.view
     mode = view.mode
     if mode.name in self.mode_lexer_map:
         lexer_cls = self.mode_lexer_map[mode.name]
     else:
         lexer_name_possibilities = ["QsciLexer" + mode.name, "QsciLexer" + mode.name.title(), "QsciLexer" + mode.name.upper()]
         for lexer_name in lexer_name_possibilities:
             if hasattr(Qsci, lexer_name):
                 lexer_cls = getattr(Qsci, lexer_name, None)
                 break
         else:
             lexer_cls = None
     
     if lexer_cls is not None:    
         font = QFont()
         font.setFamily('DejaVu Sans Mono')
         font.setFixedPitch(True)
         font.setPointSize(10)
     
         lexer = lexer_cls(widget)
         lexer.setDefaultFont(font)
         widget.setLexer(lexer)
Example #15
0
    def do_paint(self, painter, option, index):
        text = unicode(index.data(Qt.DisplayRole).toString())
        font = QFont(option.font)
        font.setPointSize(QFontInfo(font).pointSize() * 1.5)
        font2 = QFont(font)
        font2.setFamily(text)

        system, has_latin = writing_system_for_font(font2)
        if has_latin:
            font = font2

        r = option.rect

        if option.state & QStyle.State_Selected:
            painter.setPen(QPen(option.palette.highlightedText(), 0))

        if (option.direction == Qt.RightToLeft):
            r.setRight(r.right() - 4)
        else:
            r.setLeft(r.left() + 4)

        painter.setFont(font)
        painter.drawText(r,
                         Qt.AlignVCenter | Qt.AlignLeading | Qt.TextSingleLine,
                         text)

        if (system != QFontDatabase.Any):
            w = painter.fontMetrics().width(text + "  ")
            painter.setFont(font2)
            sample = QFontDatabase().writingSystemSample(system)
            if (option.direction == Qt.RightToLeft):
                r.setRight(r.right() - w)
            else:
                r.setLeft(r.left() + w)
            painter.drawText(
                r, Qt.AlignVCenter | Qt.AlignLeading | Qt.TextSingleLine,
                sample)
Example #16
0
class TextEdit(PlainTextEdit):

    def __init__(self, parent=None):
        PlainTextEdit.__init__(self, parent)
        self.saved_matches = {}
        self.smarts = NullSmarts(self)
        self.current_cursor_line = None
        self.current_search_mark = None
        self.highlighter = SyntaxHighlighter(self)
        self.line_number_area = LineNumbers(self)
        self.apply_settings()
        self.setMouseTracking(True)
        self.cursorPositionChanged.connect(self.highlight_cursor_line)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.syntax = None

    @dynamic_property
    def is_modified(self):
        ''' True if the document has been modified since it was loaded or since
        the last time is_modified was set to False. '''
        def fget(self):
            return self.document().isModified()
        def fset(self, val):
            self.document().setModified(bool(val))
        return property(fget=fget, fset=fset)

    def sizeHint(self):
        return self.size_hint

    def apply_settings(self, prefs=None):  # {{{
        prefs = prefs or tprefs
        self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
        theme = THEMES.get(prefs['editor_theme'], None)
        if theme is None:
            theme = THEMES[default_theme()]
        self.apply_theme(theme)
        w = self.fontMetrics()
        self.space_width = w.width(' ')
        self.setTabStopWidth(prefs['editor_tab_stop_width'] * self.space_width)

    def apply_theme(self, theme):
        self.theme = theme
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.tooltip_palette = pal = QPalette()
        pal.setColor(pal.ToolTipBase, theme_color(theme, 'Tooltip', 'bg'))
        pal.setColor(pal.ToolTipText, theme_color(theme, 'Tooltip', 'fg'))
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        self.match_paren_format = theme_format(theme, 'MatchParen')
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.tooltip_font = QFont(font)
        self.tooltip_font.setPointSize(font.pointSize() - 1)
        self.setFont(font)
        self.highlighter.apply_theme(theme)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
        self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
        self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg')
        self.highlight_cursor_line()
    # }}}

    def load_text(self, text, syntax='html', process_template=False):
        self.syntax = syntax
        self.highlighter = get_highlighter(syntax)(self)
        self.highlighter.apply_theme(self.theme)
        self.highlighter.setDocument(self.document())
        sclass = {'html':HTMLSmarts, 'xml':HTMLSmarts}.get(syntax, None)
        if sclass is not None:
            self.smarts = sclass(self)
        self.setPlainText(unicodedata.normalize('NFC', text))
        if process_template and QPlainTextEdit.find(self, '%CURSOR%'):
            c = self.textCursor()
            c.insertText('')

    def replace_text(self, text):
        c = self.textCursor()
        pos = c.position()
        c.beginEditBlock()
        c.clearSelection()
        c.select(c.Document)
        c.insertText(unicodedata.normalize('NFC', text))
        c.endEditBlock()
        c.setPosition(min(pos, len(text)))
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def go_to_line(self, lnum, col=None):
        lnum = max(1, min(self.blockCount(), lnum))
        c = self.textCursor()
        c.clearSelection()
        c.movePosition(c.Start)
        c.movePosition(c.NextBlock, n=lnum - 1)
        c.movePosition(c.StartOfLine)
        c.movePosition(c.EndOfLine, c.KeepAnchor)
        text = unicode(c.selectedText().rstrip('\0'))
        if col is None:
            c.movePosition(c.StartOfLine)
            lt = text.lstrip()
            if text and lt and lt != text:
                c.movePosition(c.NextWord)
        else:
            c.setPosition(c.block().position() + col)
            if c.blockNumber() + 1 > lnum:
                # We have moved past the end of the line
                c.setPosition(c.block().position())
                c.movePosition(c.EndOfBlock)
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def update_extra_selections(self):
        sel = []
        if self.current_cursor_line is not None:
            sel.append(self.current_cursor_line)
        if self.current_search_mark is not None:
            sel.append(self.current_search_mark)
        sel.extend(self.smarts.get_extra_selections(self))
        self.setExtraSelections(sel)

    # Search and replace {{{
    def mark_selected_text(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.highlight_color)
        sel.cursor = self.textCursor()
        if sel.cursor.hasSelection():
            self.current_search_mark = sel
            c = self.textCursor()
            c.clearSelection()
            self.setTextCursor(c)
        else:
            self.current_search_mark = None
        self.update_extra_selections()

    def find_in_marked(self, pat, wrap=False, save_match=None):
        if self.current_search_mark is None:
            return False
        csm = self.current_search_mark.cursor
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        m_start = min(csm.position(), csm.anchor())
        m_end = max(csm.position(), csm.anchor())
        if c.position() < m_start:
            c.setPosition(m_start)
        if c.position() > m_end:
            c.setPosition(m_end)
        pos = m_start if reverse else m_end
        if wrap:
            pos = m_end if reverse else m_start
        c.setPosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
            else:
                start, end = m_start + start, m_start + end
        else:
            if reverse:
                start, end = m_start + end, m_start + start
            else:
                start, end = c.anchor() + start, c.anchor() + end

        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        # Center search result on screen
        self.centerCursor()
        if save_match is not None:
            self.saved_matches[save_match] = m
        return True

    def all_in_marked(self, pat, template=None):
        if self.current_search_mark is None:
            return 0
        c = self.current_search_mark.cursor
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
        if template is None:
            count = len(pat.findall(raw))
        else:
            raw, count = pat.subn(template, raw)
            if count > 0:
                c.setKeepPositionOnInsert(True)
                c.insertText(raw)
                c.setKeepPositionOnInsert(False)
                self.update_extra_selections()
        return count

    def find(self, pat, wrap=False, marked=False, complete=False, save_match=None):
        if marked:
            return self.find_in_marked(pat, wrap=wrap, save_match=save_match)
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        if complete:
            # Search the entire text
            c.movePosition(c.End if reverse else c.Start)
        pos = c.Start if reverse else c.End
        if wrap and not complete:
            pos = c.End if reverse else c.Start
        c.movePosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap and not complete:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
        else:
            if reverse:
                # Put the cursor at the start of the match
                start, end = end, start
            else:
                textpos = c.anchor()
                start, end = textpos + start, textpos + end
        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        # Center search result on screen
        self.centerCursor()
        if save_match is not None:
            self.saved_matches[save_match] = m
        return True

    def replace(self, pat, template, saved_match='gui'):
        c = self.textCursor()
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')
        m = pat.fullmatch(raw)
        if m is None:
            # This can happen if either the user changed the selected text or
            # the search expression uses lookahead/lookbehind operators. See if
            # the saved match matches the currently selected text and
            # use it, if so.
            if saved_match is not None and saved_match in self.saved_matches:
                saved = self.saved_matches.pop(saved_match)
                if saved.group() == raw:
                    m = saved
        if m is None:
            return False
        text = m.expand(template)
        c.insertText(text)
        return True

    def go_to_anchor(self, anchor):
        if anchor is TOP:
            c = self.textCursor()
            c.movePosition(c.Start)
            self.setTextCursor(c)
            return True
        base = r'''%%s\s*=\s*['"]{0,1}%s''' % regex.escape(anchor)
        raw = unicode(self.toPlainText())
        m = regex.search(base % 'id', raw)
        if m is None:
            m = regex.search(base % 'name', raw)
        if m is not None:
            c = self.textCursor()
            c.setPosition(m.start())
            self.setTextCursor(c)
            return True
        return False

    # }}}

    # Line numbers and cursor line {{{
    def highlight_cursor_line(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.palette().alternateBase())
        sel.format.setProperty(QTextFormat.FullWidthSelection, True)
        sel.cursor = self.textCursor()
        sel.cursor.clearSelection()
        self.current_cursor_line = sel
        self.update_extra_selections()
        # Update the cursor line's line number in the line number area
        try:
            self.line_number_area.update(0, self.last_current_lnum[0], self.line_number_area.width(), self.last_current_lnum[1])
        except AttributeError:
            pass
        block = self.textCursor().block()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        height = int(self.blockBoundingRect(block).height())
        self.line_number_area.update(0, top, self.line_number_area.width(), height)

    def update_line_number_area_width(self, block_count=0):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def line_number_area_width(self):
        digits = 1
        limit = max(1, self.blockCount())
        while limit >= 10:
            limit /= 10
            digits += 1

        return 8 + self.number_width * digits

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width()

    def resizeEvent(self, ev):
        QPlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        current = self.textCursor().block().blockNumber()
        painter.setPen(self.line_number_palette.color(QPalette.Text))

        while block.isValid() and top <= ev.rect().bottom():
            if block.isVisible() and bottom >= ev.rect().top():
                if current == num:
                    painter.save()
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    self.last_current_lnum = (top, bottom - top)
                painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(),
                              Qt.AlignRight, str(num + 1))
                if current == num:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1
    # }}}

    def event(self, ev):
        if ev.type() == ev.ToolTip:
            self.show_tooltip(ev)
            return True
        if ev.type() == ev.ShortcutOverride:
            if ev in (
                # Let the global cut/copy/paste shortcuts work,this avoids the nbsp
                # problem as well, since they use the overridden copy() method
                # instead of the one from Qt
                QKeySequence.Copy, QKeySequence.Cut, QKeySequence.Paste,
            ) or (
                # This is used to convert typed hex codes into unicode
                # characters
                ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier
            ):
                ev.ignore()
                return False
        return QPlainTextEdit.event(self, ev)

    # Tooltips {{{
    def syntax_format_for_cursor(self, cursor):
        if cursor.isNull():
            return
        pos = cursor.positionInBlock()
        for r in cursor.block().layout().additionalFormats():
            if r.start <= pos < r.start + r.length and r.format.property(SYNTAX_PROPERTY).toBool():
                return r.format

    def show_tooltip(self, ev):
        c = self.cursorForPosition(ev.pos())
        fmt = self.syntax_format_for_cursor(c)
        if fmt is not None:
            tt = unicode(fmt.toolTip())
            if tt:
                QToolTip.setFont(self.tooltip_font)
                QToolTip.setPalette(self.tooltip_palette)
                QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
        QToolTip.hideText()
        ev.ignore()
    # }}}

    def get_range_inside_tag(self):
        c = self.textCursor()
        left = min(c.anchor(), c.position())
        right = max(c.anchor(), c.position())
        # For speed we use QPlainTextEdit's toPlainText as we dont care about
        # spaces in this context
        raw = unicode(QPlainTextEdit.toPlainText(self))
        # Make sure the left edge is not within a <>
        gtpos = raw.find('>', left)
        ltpos = raw.find('<', left)
        if gtpos < ltpos:
            left = gtpos + 1 if gtpos > -1 else left
        right = max(left, right)
        if right != left:
            gtpos = raw.find('>', right)
            ltpos = raw.find('<', right)
            if ltpos > gtpos:
                ltpos = raw.rfind('<', left, right+1)
                right = max(ltpos, left)
        return left, right

    def format_text(self, formatting):
        if self.syntax != 'html':
            return
        color = 'currentColor'
        if formatting in {'color', 'background-color'}:
            color = QColorDialog.getColor(QColor(Qt.black if formatting == 'color' else Qt.white), self, _('Choose color'), QColorDialog.ShowAlphaChannel)
            if not color.isValid():
                return
            r, g, b, a = color.getRgb()
            if a == 255:
                color = 'rgb(%d, %d, %d)' % (r, g, b)
            else:
                color = 'rgba(%d, %d, %d, %.2g)' % (r, g, b, a/255)
        prefix, suffix = {
            'bold': ('<b>', '</b>'),
            'italic': ('<i>', '</i>'),
            'underline': ('<u>', '</u>'),
            'strikethrough': ('<strike>', '</strike>'),
            'superscript': ('<sup>', '</sup>'),
            'subscript': ('<sub>', '</sub>'),
            'color': ('<span style="color: %s">' % color, '</span>'),
            'background-color': ('<span style="background-color: %s">' % color, '</span>'),
        }[formatting]
        left, right = self.get_range_inside_tag()
        c = self.textCursor()
        c.setPosition(left)
        c.setPosition(right, c.KeepAnchor)
        prev_text = unicode(c.selectedText()).rstrip('\0')
        c.insertText(prefix + prev_text + suffix)
        if prev_text:
            right = c.position()
            c.setPosition(left)
            c.setPosition(right, c.KeepAnchor)
        else:
            c.setPosition(c.position() - len(suffix))
        self.setTextCursor(c)

    def insert_image(self, href):
        c = self.textCursor()
        template, alt = 'url(%s)', ''
        left = min(c.position(), c.anchor)
        if self.syntax == 'html':
            left, right = self.get_range_inside_tag()
            c.setPosition(left)
            c.setPosition(right, c.KeepAnchor)
            alt = _('Image')
            template = '<img alt="{0}" src="%s" />'.format(alt)
            href = prepare_string_for_xml(href, True)
        text = template % href
        c.insertText(text)
        if self.syntax == 'html':
            c.setPosition(left + 10)
            c.setPosition(c.position() + len(alt), c.KeepAnchor)
        else:
            c.setPosition(left)
            c.setPosition(left + len(text), c.KeepAnchor)
        self.setTextCursor(c)

    def keyPressEvent(self, ev):
        if ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier:
            if self.replace_possible_unicode_sequence():
                ev.accept()
                return
        QPlainTextEdit.keyPressEvent(self, ev)
        if (ev.key() == Qt.Key_Semicolon or ';' in unicode(ev.text())) and tprefs['replace_entities_as_typed'] and self.syntax == 'html':
            self.replace_possible_entity()

    def replace_possible_unicode_sequence(self):
        c = self.textCursor()
        has_selection = c.hasSelection()
        if has_selection:
            text = unicode(c.selectedText()).rstrip('\0')
        else:
            c.setPosition(c.position() - min(c.positionInBlock(), 6), c.KeepAnchor)
            text = unicode(c.selectedText()).rstrip('\0')
        m = re.search(r'[a-fA-F0-9]{2,6}$', text)
        if m is None:
            return False
        text = m.group()
        try:
            num = int(text, 16)
        except ValueError:
            return False
        if num > 0x10ffff or num < 1:
            return False
        from calibre.gui2.tweak_book.char_select import chr
        end_pos = max(c.anchor(), c.position())
        c.setPosition(end_pos - len(text)), c.setPosition(end_pos, c.KeepAnchor)
        c.insertText(chr(num))
        return True

    def replace_possible_entity(self):
        c = self.textCursor()
        c.setPosition(c.position() - min(c.positionInBlock(), 10), c.KeepAnchor)
        text = unicode(c.selectedText()).rstrip('\0')
        m = entity_pat.search(text)
        if m is None:
            return
        ent = m.group()
        repl = xml_entity_to_unicode(m)
        if repl != ent:
            c.setPosition(c.position() + m.start(), c.KeepAnchor)
            c.insertText(repl)

    def select_all(self):
        c = self.textCursor()
        c.clearSelection()
        c.setPosition(0)
        c.movePosition(c.End, c.KeepAnchor)
        self.setTextCursor(c)

    def rename_block_tag(self, new_name):
        if hasattr(self.smarts, 'rename_block_tag'):
            self.smarts.rename_block_tag(self, new_name)
Example #17
0
    def ui_MMKit_GroupBox(self, MMKitDialog):
        #Start MMKit groupbox (includes atom, clipboard and library tabs)
        self.MMKit_groupBox = QtGui.QGroupBox(MMKitDialog)
        self.MMKit_groupBox.setObjectName("MMKit_groupBox")

        self.MMKit_groupBox.setAutoFillBackground(True)
        palette = MMKitDialog.getGroupBoxPalette()
        self.MMKit_groupBox.setPalette(palette)

        styleSheet = MMKitDialog.getGroupBoxStyleSheet()
        self.MMKit_groupBox.setStyleSheet(styleSheet)

        self.MMKitGrpBox_VBoxLayout = QtGui.QVBoxLayout(self.MMKit_groupBox)
        self.MMKitGrpBox_VBoxLayout.setMargin(pmGrpBoxVboxLayoutMargin)
        self.MMKitGrpBox_VBoxLayout.setSpacing(pmGrpBoxVboxLayoutSpacing)
        self.MMKitGrpBox_VBoxLayout.setObjectName("MMKitGrpBox_VBoxLayout")

        self.MMKitGrpBox_TitleButton = MMKitDialog.getGroupBoxTitleButton("MMKit", self.MMKit_groupBox)

        self.MMKitGrpBox_VBoxLayout.addWidget(self.MMKitGrpBox_TitleButton)

        self.mmkit_tab = QtGui.QTabWidget(self.MMKit_groupBox)
        self.mmkit_tab.setEnabled(True)

        # Height is fixed. Mark 2007-05-29.
        self.mmkit_tab.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.mmkit_tab.setObjectName("mmkit_tab")

        self.atomsPage = QtGui.QWidget()
        self.atomsPage.setObjectName("atomsPage")

        self.mmkit_tab.addTab(self.atomsPage, "")

        self.atomsPageFrame = QtGui.QFrame(self.atomsPage)

        # atomsPageFrame needs to be reviewed carefully. Mark 2007-06-20
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(3),QtGui.QSizePolicy.Policy(1))
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.atomsPageFrame.sizePolicy().hasHeightForWidth())
        self.atomsPageFrame.setSizePolicy(sizePolicy)
        self.atomsPageFrame.setFrameShape(QtGui.QFrame.NoFrame)
        self.atomsPageFrame.setFrameShadow(QtGui.QFrame.Plain)
        self.atomsPageFrame.setMinimumSize(QtCore.QSize(100,100))
        self.atomsPageFrame.setObjectName("atomsPageFrame")

        self.atomsPage_VBoxLayout = QtGui.QVBoxLayout(self.atomsPageFrame)
        self.atomsPage_VBoxLayout.setMargin(pmMMKitPageMargin) # Was 4. Mark 2007-05-30
        self.atomsPage_VBoxLayout.setSpacing(2)

        # Element Button GroupBox begins here. #####################

        self.elementButtonGroup = QtGui.QGroupBox(self.atomsPageFrame)

        sizePolicy = QtGui.QSizePolicy(
            QtGui.QSizePolicy.MinimumExpanding,
            QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.elementButtonGroup.setSizePolicy(sizePolicy)
        self.elementButtonGroup.setMinimumSize(
            QtCore.QSize(pmMMKitButtonWidth  * 4,
                         pmMMKitButtonHeight * 4 + 4))
        self.elementButtonGroup.setObjectName("elementButtonGroup")

        self.MMKit_GridLayout = QtGui.QGridLayout(self.elementButtonGroup)
        self.MMKit_GridLayout.setMargin(1) # Was 0. Mark 2007-05-30
        self.MMKit_GridLayout.setSpacing(0)
        self.MMKit_GridLayout.setObjectName("MMKit_GridLayout")

        # Font for toolbuttons.
        font = QFont(self.atomsPageFrame.font())
        font.setFamily(pmMMKitButtonFont)
        font.setPointSize(pmMMKitButtonFontPointSize)
        font.setBold(pmMMKitButtonFontBold)
        #font.setWeight(75)
        #font.setItalic(False)
        #font.setUnderline(False)
        #font.setStrikeOut(False)

        # All this would be much nicer using a dictionary in a loop.
        # Later, when time permits. Mark 2007-05-30.

        self.toolButton1 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton1.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton1.setCheckable(True)
        self.toolButton1.setFont(font)
        self.toolButton1.setObjectName("toolButton1")
        self.MMKit_GridLayout.addWidget(self.toolButton1,0,4,1,1)

        self.toolButton2 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton2.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton2.setCheckable(True)
        self.toolButton2.setFont(font)
        self.toolButton2.setObjectName("toolButton2")
        self.MMKit_GridLayout.addWidget(self.toolButton2,0,5,1,1)

        self.toolButton6 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton6.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton6.setCheckable(True)
        self.toolButton6.setFont(font)
        self.toolButton6.setObjectName("toolButton6")
        self.MMKit_GridLayout.addWidget(self.toolButton6,1,1,1,1)

        self.toolButton7 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton7.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton7.setCheckable(True)
        self.toolButton7.setFont(font)
        self.toolButton7.setObjectName("toolButton7")
        self.MMKit_GridLayout.addWidget(self.toolButton7,1,2,1,1)

        self.toolButton8 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton8.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton8.setCheckable(True)
        self.toolButton8.setFont(font)
        self.toolButton8.setObjectName("toolButton8")
        self.MMKit_GridLayout.addWidget(self.toolButton8,1,3,1,1)

        self.toolButton10 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton10.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton10.setCheckable(True)
        self.toolButton10.setFont(font)
        self.toolButton10.setObjectName("toolButton10")
        self.MMKit_GridLayout.addWidget(self.toolButton10,1,5,1,1)

        self.toolButton9 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton9.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton9.setCheckable(True)
        self.toolButton9.setFont(font)
        self.toolButton9.setObjectName("toolButton9")
        self.MMKit_GridLayout.addWidget(self.toolButton9,1,4,1,1)

        self.toolButton13 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton13.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton13.setCheckable(True)
        self.toolButton13.setFont(font)
        self.toolButton13.setObjectName("toolButton13")
        self.MMKit_GridLayout.addWidget(self.toolButton13,2,0,1,1)

        self.toolButton17 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton17.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton17.setCheckable(True)
        self.toolButton17.setFont(font)
        self.toolButton17.setObjectName("toolButton17")
        self.MMKit_GridLayout.addWidget(self.toolButton17,2,4,1,1)

        self.toolButton5 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton5.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton5.setCheckable(True)
        self.toolButton5.setFont(font)
        self.toolButton5.setObjectName("toolButton5")
        self.MMKit_GridLayout.addWidget(self.toolButton5,1,0,1,1)

        self.toolButton10_2 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton10_2.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton10_2.setCheckable(True)
        self.toolButton10_2.setFont(font)
        self.toolButton10_2.setObjectName("toolButton10_2")
        self.MMKit_GridLayout.addWidget(self.toolButton10_2,2,5,1,1)

        self.toolButton15 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton15.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton15.setCheckable(True)
        self.toolButton15.setFont(font)
        self.toolButton15.setObjectName("toolButton15")
        self.MMKit_GridLayout.addWidget(self.toolButton15,2,2,1,1)

        self.toolButton16 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton16.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton16.setCheckable(True)
        self.toolButton16.setFont(font)
        self.toolButton16.setObjectName("toolButton16")
        self.MMKit_GridLayout.addWidget(self.toolButton16,2,3,1,1)

        self.toolButton14 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton14.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton14.setCheckable(True)
        self.toolButton14.setFont(font)
        self.toolButton14.setObjectName("toolButton14")
        self.MMKit_GridLayout.addWidget(self.toolButton14,2,1,1,1)

        self.toolButton33 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton33.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton33.setCheckable(True)
        self.toolButton33.setFont(font)
        self.toolButton33.setObjectName("toolButton33")
        self.MMKit_GridLayout.addWidget(self.toolButton33,3,2,1,1)

        self.toolButton34 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton34.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton34.setCheckable(True)
        self.toolButton34.setFont(font)
        self.toolButton34.setObjectName("toolButton34")
        self.MMKit_GridLayout.addWidget(self.toolButton34,3,3,1,1)

        self.toolButton35 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton35.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton35.setCheckable(True)
        self.toolButton35.setFont(font)
        self.toolButton35.setObjectName("toolButton35")
        self.MMKit_GridLayout.addWidget(self.toolButton35,3,4,1,1)

        self.toolButton32 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton32.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton32.setCheckable(True)
        self.toolButton32.setFont(font)
        self.toolButton32.setObjectName("toolButton32")
        self.MMKit_GridLayout.addWidget(self.toolButton32,3,1,1,1)

        self.toolButton36 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton36.setFixedSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.toolButton36.setCheckable(True)
        self.toolButton36.setFont(font)
        self.toolButton36.setObjectName("toolButton36")
        self.MMKit_GridLayout.addWidget(self.toolButton36,3,5,1,1)

        self.atomsPage_VBoxLayout.addWidget(self.elementButtonGroup)

        # Height is fixed (i.e. locked). Mark 2007-05-29.
        self.elementButtonGroup.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))

        # Atomic Hybrid label
        self.atomic_hybrids_label = QtGui.QLabel(self.atomsPageFrame)
        self.atomic_hybrids_label.setText("Atomic Hybrids :")
        self.atomsPage_VBoxLayout.addWidget(self.atomic_hybrids_label)

        # Elements Button GroupBox ends here. #######################

        # This special HBoxLayout contains both the hybrid button group and a
        # vert spacer (width = 0) to keep the Qt layout working properly
        # in certain situations like that described in bug 2407.
        # Mark 2007-06-20.
        self.special_HBoxLayout = QtGui.QHBoxLayout()
        self.special_HBoxLayout.setMargin(0)
        self.special_HBoxLayout.setSpacing(6)
        self.special_HBoxLayout.setObjectName("special_HBoxLayout")
        self.atomsPage_VBoxLayout.addLayout(self.special_HBoxLayout)

        # Hybrid GroupBox begins here ###############################

        self.hybrid_btngrp = QtGui.QGroupBox(self.atomsPageFrame)
        self.hybrid_btngrp.setObjectName("hybrid_btngrp")
        self.special_HBoxLayout.addWidget(self.hybrid_btngrp)

        self.hybridBtns_HBoxLayout = QtGui.QHBoxLayout(self.hybrid_btngrp)
        self.hybridBtns_HBoxLayout.setMargin(2)
        self.hybridBtns_HBoxLayout.setSpacing(0)
        self.hybridBtns_HBoxLayout.setObjectName("hybridBtns_HBoxLayout")

        self.sp3_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.sp3_btn.setMinimumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.sp3_btn.setMaximumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.sp3_btn.setCheckable(True)
        self.sp3_btn.setObjectName("sp3_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.sp3_btn)

        self.sp2_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.sp2_btn.setMinimumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.sp2_btn.setMaximumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.sp2_btn.setCheckable(True)
        self.sp2_btn.setObjectName("sp2_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.sp2_btn)

        self.sp_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.sp_btn.setMinimumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.sp_btn.setMaximumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.sp_btn.setCheckable(True)
        self.sp_btn.setObjectName("sp_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.sp_btn)

        self.graphitic_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.graphitic_btn.setMinimumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.graphitic_btn.setMaximumSize(QtCore.QSize(pmMMKitButtonWidth,pmMMKitButtonHeight))
        self.graphitic_btn.setCheckable(True)
        self.graphitic_btn.setObjectName("graphitic_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.graphitic_btn)

        # This VSpacer is needed to help (but not completely) fix bug 2407. It
        # maintains the height of the layout(s) containing the hybrid button
        # group when it is hidden using hide(). Without this spacer the layout
        # gets screwed up in special situations like that described in bug 2407.
        # The + 10 below is needed to account for the margin (4 pixels) and
        # the additional 6 just help (I have a theory that the height of the
        # frame containing the label above the hybrid group box shrinks when
        # the label is hidden by inserting a space character). I'm
        # not going to worry about this now. +10 works well enough.
        # Mark 2007-06-20.
        VSpacer = QtGui.QSpacerItem(0, pmMMKitButtonHeight + 10,
                                        QtGui.QSizePolicy.Fixed,
                                        QtGui.QSizePolicy.Fixed)
        #self.hybridBtns_HBoxLayout.addItem(VSpacer)
        self.special_HBoxLayout.addItem(VSpacer)

        self.hybridBtns_HBoxLayout.addStretch(0)

        # Height is fixed. Mark 2007-05-29.
        self.hybrid_btngrp.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.MinimumExpanding),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))

        # This spacer keeps the MMKit button grid compressed when
        # the hybrid button group is hidden.
        self.atomsPageBottomVSpacer = \
            QtGui.QSpacerItem(5, 0,
                              QtGui.QSizePolicy.Fixed,
                              QtGui.QSizePolicy.MinimumExpanding)

        self.atomsPage_VBoxLayout.addItem(self.atomsPageBottomVSpacer)

        # Clipboard page begins here ############################################

        self.clipboardPage = QtGui.QWidget()
        self.clipboardPage.setObjectName("clipboardPage")

        self.gridlayout3 = QtGui.QGridLayout(self.clipboardPage)
        self.gridlayout3.setMargin(pmMMKitPageMargin) # Was 4. Mark 2007-05-30
        self.gridlayout3.setSpacing(2)
        self.gridlayout3.setObjectName("gridlayout3")

        self.chunkListBox = QtGui.QListWidget(self.clipboardPage)

        self.chunkListBox.setMinimumSize(QtCore.QSize(100,100))

        # Height is fixed. Mark 2007-05-29.
        self.chunkListBox.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.MinimumExpanding),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.chunkListBox.setObjectName("chunkListBox")
        self.gridlayout3.addWidget(self.chunkListBox,0,0,1,1)
        self.mmkit_tab.addTab(self.clipboardPage, "")

        self.libraryPage = QtGui.QWidget()
        #self.libraryPage = QtGui.QScrollArea()
        #self.libraryPageWidget = QtGui.QWidget()
        #self.libraryPage.setWidget(self.libraryPageWidget)
        self.libraryPage.setObjectName("libraryPage")
        self.mmkit_tab.addTab(self.libraryPage, "")

        self.MMKitGrpBox_VBoxLayout.addWidget(self.mmkit_tab)

        self.transmuteAtomsAction = QtGui.QWidgetAction(self.w)
        self.transmuteAtomsAction.setText("Transmute Atoms")
        self.transmuteAtomsAction.setIcon(geticon(
            'ui/actions/Toolbars/Smart/Transmute_Atoms'))
        self.transmuteAtomsAction.setCheckable(False)

        transmuteBtn_HBoxLayout = QtGui.QHBoxLayout()

        self.transmuteBtn = QtGui.QToolButton(self.MMKit_groupBox)
        self.transmuteBtn.setDefaultAction(self.transmuteAtomsAction)
        self.transmuteBtn.setFixedSize(QtCore.QSize(36, 36))
        self.transmuteBtn.setIconSize(QtCore.QSize(22,22))
        transmuteBtn_HBoxLayout.addWidget(self.transmuteBtn)

        self.browseButton = QtGui.QPushButton(MMKitDialog)
        transmuteBtn_HBoxLayout.addWidget(self.browseButton)

        self.defaultPartLibButton = QtGui.QPushButton(MMKitDialog)
        transmuteBtn_HBoxLayout.addWidget(self.defaultPartLibButton)

        self.atomsPageSpacer = QtGui.QSpacerItem(0, 5,
                                             QtGui.QSizePolicy.Expanding,
                                             QtGui.QSizePolicy.Minimum)

        transmuteBtn_HBoxLayout.addItem(self.atomsPageSpacer)

        self.MMKitGrpBox_VBoxLayout.addLayout(transmuteBtn_HBoxLayout)

        self.transmuteCB = QtGui.QCheckBox(" Force to Keep Bonds", self.MMKit_groupBox)

        self.MMKitGrpBox_VBoxLayout.addWidget(self.transmuteCB)

        #End MMKit groupbox
        self.pmVBoxLayout.addWidget(self.MMKit_groupBox)

        # This line is important. Without it, the MMKit groupbox is
        # too wide by default and causes a horizontal scrollbar
        # to be displayed at the bottom of the PropMgr. Mark 2007-05-30
        self.MMKit_groupBox.setMinimumWidth(200)

        # Height is fixed. Mark 2007-05-29.
        self.MMKit_groupBox.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.MinimumExpanding),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))
Example #18
0
    def __init__(self, parent = None, desc = None, name = None, modal = 0, fl = 0, env = None, type = "QDialog"):
        if env is None:
            import foundation.env as env # this is a little weird... probably it'll be ok, and logically it seems correct.
        
        self.desc = desc

        self.typ = type
        if type == "QDialog":
            QDialog.__init__(self,parent,name,modal,fl)
        elif type == "QTextEdit":
            QTextEdit.__init__(self, parent, name)
        elif type == "QFrame":
            QFrame.__init__(self,parent,name)
        else:
            print "don't know about type == %r" % (type,)
        
        self.image1 = QPixmap()
        self.image1.loadFromData(image1_data,"PNG") # should be: title_icon ####
        self.image3 = QPixmap()
        self.image3.loadFromData(image3_data,"PNG")
        self.image4 = QPixmap()
        self.image4.loadFromData(image4_data,"PNG")
        self.image5 = QPixmap()
        self.image5.loadFromData(image5_data,"PNG")
        self.image6 = QPixmap()
        self.image6.loadFromData(image6_data,"PNG")
        self.image7 = QPixmap()
        self.image7.loadFromData(image7_data,"PNG")
        self.image0 = QPixmap(image0_data) # should be: border_icon ####
        self.image2 = QPixmap(image2_data) # should be: sponsor_pixmap ####

        try:
            ####@@@@
            title_icon_name = self.desc.options.get('title_icon')
            border_icon_name = self.desc.options.get('border_icon')
            if title_icon_name:
                self.image1 = imagename_to_pixmap(title_icon_name) ###@@@ pass icon_path
                    ###@@@ import imagename_to_pixmap or use env function
                    # or let that func itself be an arg, or have an env arg for it
                    ###e rename it icon_name_to_pixmap, or find_icon? (the latter only if it's ok if it returns an iconset)
                    ###e use iconset instead?
            if border_icon_name:
                self.image0 = imagename_to_pixmap(border_icon_name)
        except:
            print_compact_traceback("bug in icon-setting code, using fallback icons: ")
            pass

        if not name:
            self.setName("parameter_dialog_or_frame") ###

        ###k guess this will need: if type == 'QDialog'
        self.setIcon(self.image0) # should be: border_icon ####

        nanotube_dialogLayout = QVBoxLayout(self,0,0,"nanotube_dialogLayout")

        self.heading_frame = QFrame(self,"heading_frame")
        self.heading_frame.setPaletteBackgroundColor(QColor(122,122,122))
        self.heading_frame.setFrameShape(QFrame.NoFrame)
        self.heading_frame.setFrameShadow(QFrame.Plain)
        heading_frameLayout = QHBoxLayout(self.heading_frame,0,3,"heading_frameLayout")

        self.heading_pixmap = QLabel(self.heading_frame,"heading_pixmap")
        self.heading_pixmap.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,QSizePolicy.Fixed,0,0,self.heading_pixmap.sizePolicy().hasHeightForWidth()))
        self.heading_pixmap.setPixmap(self.image1) # should be: title_icon ####
        self.heading_pixmap.setScaledContents(1)
        heading_frameLayout.addWidget(self.heading_pixmap)

        self.heading_label = QLabel(self.heading_frame,"heading_label")
        self.heading_label.setPaletteForegroundColor(QColor(255,255,255))
        heading_label_font = QFont(self.heading_label.font())
        heading_label_font.setPointSize(12)
        heading_label_font.setBold(1)
        self.heading_label.setFont(heading_label_font)
        heading_frameLayout.addWidget(self.heading_label)
        nanotube_dialogLayout.addWidget(self.heading_frame)

        self.body_frame = QFrame(self,"body_frame")
        self.body_frame.setFrameShape(QFrame.StyledPanel)
        self.body_frame.setFrameShadow(QFrame.Raised)
        body_frameLayout = QVBoxLayout(self.body_frame,3,3,"body_frameLayout")

        self.sponsor_frame = QFrame(self.body_frame,"sponsor_frame")
        self.sponsor_frame.setPaletteBackgroundColor(QColor(255,255,255))
        self.sponsor_frame.setFrameShape(QFrame.StyledPanel)
        self.sponsor_frame.setFrameShadow(QFrame.Raised)
        sponsor_frameLayout = QHBoxLayout(self.sponsor_frame,0,0,"sponsor_frameLayout")

        self.sponsor_btn = QPushButton(self.sponsor_frame,"sponsor_btn")
        self.sponsor_btn.setAutoDefault(0) #bruce 060703 bugfix
        self.sponsor_btn.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,QSizePolicy.Preferred,0,0,self.sponsor_btn.sizePolicy().hasHeightForWidth()))
        self.sponsor_btn.setPaletteBackgroundColor(QColor(255,255,255))
        self.sponsor_btn.setPixmap(self.image2) # should be: sponsor_pixmap #### [also we'll need to support >1 sponsor]
        self.sponsor_btn.setFlat(1)
        sponsor_frameLayout.addWidget(self.sponsor_btn)
        body_frameLayout.addWidget(self.sponsor_frame)

        layout59 = QHBoxLayout(None,0,6,"layout59")
        left_spacer = QSpacerItem(20,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
        layout59.addItem(left_spacer)

        self.done_btn = QToolButton(self.body_frame,"done_btn")
        self.done_btn.setIcon(QIcon(self.image3))
        layout59.addWidget(self.done_btn)

        self.abort_btn = QToolButton(self.body_frame,"abort_btn")
        self.abort_btn.setIcon(QIcon(self.image4))
        layout59.addWidget(self.abort_btn)

        self.preview_btn = QToolButton(self.body_frame,"preview_btn")
        self.preview_btn.setIcon(QIcon(self.image5))
        layout59.addWidget(self.preview_btn)

        self.whatsthis_btn = QToolButton(self.body_frame,"whatsthis_btn")
        self.whatsthis_btn.setIcon(QIcon(self.image6))
        layout59.addWidget(self.whatsthis_btn)
        right_spacer = QSpacerItem(20,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
        layout59.addItem(right_spacer)
        body_frameLayout.addLayout(layout59)

        self.groups = []
        self.param_getters = {} # map from param name to get-function (which gets current value out of its widget or controller)

        for group_desc in self.desc.kids('group'):
            
            # == start parameters_grpbox ### this will differ for Windows style

            header_refs = [] # keep python refcounted refs to all objects we make (at least the ones pyuic stored in self attrs)
            
            self.parameters_grpbox = QGroupBox(self.body_frame,"parameters_grpbox")
            self.parameters_grpbox.setFrameShape(QGroupBox.StyledPanel)
            self.parameters_grpbox.setFrameShadow(QGroupBox.Sunken)
            self.parameters_grpbox.setMargin(0)
            self.parameters_grpbox.setColumnLayout(0,Qt.Vertical)
            self.parameters_grpbox.layout().setSpacing(1)
            self.parameters_grpbox.layout().setMargin(4)
            parameters_grpboxLayout = QVBoxLayout(self.parameters_grpbox.layout())
            parameters_grpboxLayout.setAlignment(Qt.AlignTop)

            layout20 = QHBoxLayout(None,0,6,"layout20")

            self.nt_parameters_grpbtn = QPushButton(self.parameters_grpbox,"nt_parameters_grpbtn")
            self.nt_parameters_grpbtn.setSizePolicy(QSizePolicy(QSizePolicy.Minimum,QSizePolicy.Fixed,0,0,self.nt_parameters_grpbtn.sizePolicy().hasHeightForWidth()))
            self.nt_parameters_grpbtn.setMaximumSize(QSize(16,16))
            self.nt_parameters_grpbtn.setAutoDefault(0)
            self.nt_parameters_grpbtn.setIcon(QIcon(self.image7)) ### not always right, but doesn't matter
            self.nt_parameters_grpbtn.setFlat(1)
            layout20.addWidget(self.nt_parameters_grpbtn)

            self.parameters_grpbox_label = QLabel(self.parameters_grpbox,"parameters_grpbox_label")
            self.parameters_grpbox_label.setSizePolicy(QSizePolicy(QSizePolicy.Preferred,QSizePolicy.Minimum,0,0,self.parameters_grpbox_label.sizePolicy().hasHeightForWidth()))
            self.parameters_grpbox_label.setAlignment(QLabel.AlignVCenter)
            layout20.addWidget(self.parameters_grpbox_label)
            gbx_spacer1 = QSpacerItem(67,16,QSizePolicy.Expanding,QSizePolicy.Minimum)
            layout20.addItem(gbx_spacer1)
            parameters_grpboxLayout.addLayout(layout20)

            nt_parameters_body_layout = QGridLayout(None,1,1,0,6,"nt_parameters_body_layout") ### what is 6 -- is it related to number of items???
                # is it 6 in all the ones we got, but that could be a designer error so i better look it up sometime.

            # == start its kids

            # will use from above: self.parameters_grpbox, nt_parameters_body_layout
            
            nextrow = 0 # which row of the QGridLayout to start filling next (loop variable)
            hidethese = [] # set of objects to hide or show, when this group is closed or opened
            
            for param in group_desc.kids('parameter'):
                # param (a group subobj desc) is always a parameter, but we already plan to extend this beyond that,
                # so we redundantly test for this here.
                getter = None
                paramname = None
                # set these for use by uniform code at the end (e.g. for tooltips)
                editfield = None
                label = None
                if param.isa('parameter'):
                    label = QLabel(self.parameters_grpbox,"members_label")
                    label.setAlignment(QLabel.AlignVCenter | QLabel.AlignRight)
                    nt_parameters_body_layout.addWidget(label,nextrow,0)
                    hidethese.append(label)
                    thisrow = nextrow
                    nextrow += 1
                    #e following should be known in a place that knows the input language, not here
                    paramname = param.options.get('name') or (param.args and param.args[0]) or "?"
                    paramlabel = param.options.get('label') or paramname ##e wrong, label "" or none ought to be possible
                    # QtGui.QApplication.translate(self.__class__.__name__, "xyz")
                    label.setText(QtGui.QApplication.translate(self.__class__.__name__, paramlabel))
                    
                if param.isa('parameter', widget = 'combobox', type = ('str',None)):
                    self.members_combox = QComboBox(0,self.parameters_grpbox,"members_combox") ###k  what's 0?
                    editfield = self.members_combox
                    #### it probably needs a handler class, and then that could do this setup
                    self.members_combox.clear()
                    default = param.options.get('default', None) # None is not equal to any string
                    thewidgetkid = param.kids('widget')[-1] # kluge; need to think what the desc method for this should be
                    for item in thewidgetkid.kids('item'):
                        itemval = item.args[0]
                        itemtext = itemval
                        self.members_combox.insertItem(QtGui.QApplication.translate(self.__class__.__name__, itemtext)) #k __tr ok??
                        if itemval == default: #k or itemtext?
                            pass ##k i find no setItem in our py code, so not sure yet what to do for this.
                    nt_parameters_body_layout.addWidget(self.members_combox,thisrow,1)
                    hidethese.append(self.members_combox)
                    getter = (lambda combobox = self.members_combox: str(combobox.currentText()))
                        ##e due to __tr or non-str values, it might be better to use currentIndex and look it up in a table
                        # (though whether __tr is good here might depend on what it's used for)
                                    
                elif param.isa('parameter', widget = ('lineedit', None), type = ('str',None)):
                    # this covers explicit str|lineedit, and 3 default cases str, lineedit, neither.
                    # (i.e. if you say parameter and nothing else, it's str lineedit by default.)
                    self.length_linedit = QLineEdit(self.parameters_grpbox,"length_linedit")
                    editfield = self.length_linedit
                    nt_parameters_body_layout.addWidget(self.length_linedit,thisrow,1)
                    hidethese.append(self.length_linedit)
                    default = str(param.options.get('default', ""))
                    self.length_linedit.setText(QtGui.QApplication.translate(self.__class__.__name__, default)) # __tr ok?
                    getter = (lambda lineedit = self.length_linedit: str(lineedit.text()))
                    
                elif param.isa('parameter', widget = ('lineedit', None), type = 'float'):
                    self.length_linedit = QLineEdit(self.parameters_grpbox,"length_linedit")
                    editfield = self.length_linedit
                    nt_parameters_body_layout.addWidget(self.length_linedit,thisrow,1)
                    hidethese.append(self.length_linedit)
                    controller = FloatLineeditController_Qt(self, param, self.length_linedit)
                    header_refs.append(controller)
                    getter = controller.get_value
                    
                elif param.isa('parameter', widget = ('spinbox', None), type = 'int') or \
                     param.isa('parameter', widget = ('spinbox'), type = None):
                    self.chirality_N_spinbox = QSpinBox(self.parameters_grpbox,"chirality_N_spinbox") # was chirality_m_spinbox, now chirality_N_spinbox
                    editfield = self.chirality_N_spinbox
                    ### seems like Qt defaults for min and max are 0,100 -- way too small a range!
                    if param.options.has_key('min') or 1:
                        self.chirality_N_spinbox.setMinimum(param.options.get('min', -999999999)) # was 0
                    if param.options.has_key('max') or 1:
                        self.chirality_N_spinbox.setMaximum(param.options.get('max', +999999999)) # wasn't in egcode, but needed
                    self.chirality_N_spinbox.setValue(param.options.get('default', 0)) # was 5
                        ##e note: i suspect this default 0 should come from something that knows this desc grammar.
                    suffix = param.options.get('suffix', '')
                    if suffix:
                        self.chirality_N_spinbox.setSuffix(QtGui.QApplication.translate(self.__class__.__name__, suffix))
                    else:
                        self.chirality_N_spinbox.setSuffix(QString.null) # probably not needed
                    nt_parameters_body_layout.addWidget(self.chirality_N_spinbox,thisrow,1)
                    hidethese.append(self.chirality_N_spinbox)
                    getter = self.chirality_N_spinbox.value # note: it also has .text, which includes suffix
                    
                else:
                    print "didn't match:",param ###e improve this

                # things done the same way for all kinds of param-editing widgets
                if 1: #bruce 060703 moved this down here, as bugfix
                    # set tooltip (same one for editfield and label)
                    tooltip = param.options.get('tooltip', '')
                    ###e do it for more kinds of params; share the code somehow; do it in controller, or setup-aid?
                    ###k QToolTip appropriateness; tooltip option might be entirely untested
                    if tooltip and label:
                        QToolTip.add(label, QtGui.QApplication.translate(self.__class__.__name__, tooltip))
                    if tooltip and editfield:
                        QToolTip.add(editfield, QtGui.QApplication.translate(self.__class__.__name__, tooltip)) ##k ok?? review once not all params have same-row labels.
                
                if getter and paramname and paramname != '?':
                    self.param_getters[paramname] = getter
                ### also bind these params to actions...
                continue # next param

            header_refs.extend( [self.parameters_grpbox, self.nt_parameters_grpbtn, self.parameters_grpbox_label] )
            
            # now create the logic/control object for the group
            group = CollapsibleGroupController_Qt(self, group_desc, header_refs, hidethese, self.nt_parameters_grpbtn)
                ### maybe ask env for the class to use for this?
            self.groups.append(group) ### needed?? only for scanning the params, AFAIK -- oh, and to maintain a python refcount.

            # from languageChange:
            if 1: # i don't know if these are needed:
                self.parameters_grpbox.setTitle(QString.null)
                self.nt_parameters_grpbtn.setText(QString.null)
            self.parameters_grpbox_label.setText(QtGui.QApplication.translate(self.__class__.__name__, group_desc.args[0])) # was "Nanotube Parameters"
                ##e note that it's questionable in the syntax design for this property of a group (overall group label)
                # to be in that position (desc arg 0).
        
            # == end its kids
            
            parameters_grpboxLayout.addLayout(nt_parameters_body_layout)
            body_frameLayout.addWidget(self.parameters_grpbox)

            # == end parameters groupbox
            
            continue # next group

        nanotube_dialogLayout.addWidget(self.body_frame)
        spacer14 = QSpacerItem(20,20,QSizePolicy.Minimum,QSizePolicy.Expanding)
        nanotube_dialogLayout.addItem(spacer14)

        layout42 = QHBoxLayout(None,4,6,"layout42")
        btm_spacer = QSpacerItem(59,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
        layout42.addItem(btm_spacer)

        self.cancel_btn = QPushButton(self,"cancel_btn")
        self.cancel_btn.setAutoDefault(0) #bruce 060703 bugfix
        layout42.addWidget(self.cancel_btn)

        self.ok_btn = QPushButton(self,"ok_btn")
        self.ok_btn.setAutoDefault(0) #bruce 060703 bugfix
        layout42.addWidget(self.ok_btn)
        nanotube_dialogLayout.addLayout(layout42)

        self.languageChange()

        self.resize(QSize(246,618).expandedTo(self.minimumSizeHint())) ### this size will need to be adjusted (guess -- it's only place overall size is set)
        qt4todo('self.clearWState(Qt.WState_Polished)')

        ## self.connect(self.nt_parameters_grpbtn,SIGNAL("clicked()"),self.toggle_nt_parameters_grpbtn) ####

        # new:
        for button, methodname in ((self.sponsor_btn, 'do_sponsor_btn'),  #e generalize to more than one sponsor button
                                   (self.done_btn, 'do_done_btn'),
                                   (self.abort_btn, 'do_abort_btn'),
                                   (self.preview_btn, 'do_preview_btn'),
                                   (self.whatsthis_btn, 'do_whatsthis_btn'),
                                   (self.cancel_btn, 'do_cancel_btn'),
                                   (self.ok_btn, 'do_ok_btn')):
            if hasattr(self, methodname):
                self.connect(button, SIGNAL("clicked()"), getattr(self, methodname))
        return
    def ui_MMKit_GroupBox(self, MMKitDialog):
        #Start MMKit groupbox (includes atom, clipboard and library tabs)
        self.MMKit_groupBox = QtGui.QGroupBox(MMKitDialog)
        self.MMKit_groupBox.setObjectName("MMKit_groupBox")

        self.MMKit_groupBox.setAutoFillBackground(True)
        palette = MMKitDialog.getGroupBoxPalette()
        self.MMKit_groupBox.setPalette(palette)

        styleSheet = MMKitDialog.getGroupBoxStyleSheet()
        self.MMKit_groupBox.setStyleSheet(styleSheet)

        self.MMKitGrpBox_VBoxLayout = QtGui.QVBoxLayout(self.MMKit_groupBox)
        self.MMKitGrpBox_VBoxLayout.setMargin(pmGrpBoxVboxLayoutMargin)
        self.MMKitGrpBox_VBoxLayout.setSpacing(pmGrpBoxVboxLayoutSpacing)
        self.MMKitGrpBox_VBoxLayout.setObjectName("MMKitGrpBox_VBoxLayout")

        self.MMKitGrpBox_TitleButton = MMKitDialog.getGroupBoxTitleButton(
            "MMKit", self.MMKit_groupBox)

        self.MMKitGrpBox_VBoxLayout.addWidget(self.MMKitGrpBox_TitleButton)

        self.mmkit_tab = QtGui.QTabWidget(self.MMKit_groupBox)
        self.mmkit_tab.setEnabled(True)

        # Height is fixed. Mark 2007-05-29.
        self.mmkit_tab.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.mmkit_tab.setObjectName("mmkit_tab")

        self.atomsPage = QtGui.QWidget()
        self.atomsPage.setObjectName("atomsPage")

        self.mmkit_tab.addTab(self.atomsPage, "")

        self.atomsPageFrame = QtGui.QFrame(self.atomsPage)

        # atomsPageFrame needs to be reviewed carefully. Mark 2007-06-20
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(3),
                                       QtGui.QSizePolicy.Policy(1))
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.atomsPageFrame.sizePolicy().hasHeightForWidth())
        self.atomsPageFrame.setSizePolicy(sizePolicy)
        self.atomsPageFrame.setFrameShape(QtGui.QFrame.NoFrame)
        self.atomsPageFrame.setFrameShadow(QtGui.QFrame.Plain)
        self.atomsPageFrame.setMinimumSize(QtCore.QSize(100, 100))
        self.atomsPageFrame.setObjectName("atomsPageFrame")

        self.atomsPage_VBoxLayout = QtGui.QVBoxLayout(self.atomsPageFrame)
        self.atomsPage_VBoxLayout.setMargin(
            pmMMKitPageMargin)  # Was 4. Mark 2007-05-30
        self.atomsPage_VBoxLayout.setSpacing(2)

        # Element Button GroupBox begins here. #####################

        self.elementButtonGroup = QtGui.QGroupBox(self.atomsPageFrame)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
                                       QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.elementButtonGroup.setSizePolicy(sizePolicy)
        self.elementButtonGroup.setMinimumSize(
            QtCore.QSize(pmMMKitButtonWidth * 4, pmMMKitButtonHeight * 4 + 4))
        self.elementButtonGroup.setObjectName("elementButtonGroup")

        self.MMKit_GridLayout = QtGui.QGridLayout(self.elementButtonGroup)
        self.MMKit_GridLayout.setMargin(1)  # Was 0. Mark 2007-05-30
        self.MMKit_GridLayout.setSpacing(0)
        self.MMKit_GridLayout.setObjectName("MMKit_GridLayout")

        # Font for toolbuttons.
        font = QFont(self.atomsPageFrame.font())
        font.setFamily(pmMMKitButtonFont)
        font.setPointSize(pmMMKitButtonFontPointSize)
        font.setBold(pmMMKitButtonFontBold)
        #font.setWeight(75)
        #font.setItalic(False)
        #font.setUnderline(False)
        #font.setStrikeOut(False)

        # All this would be much nicer using a dictionary in a loop.
        # Later, when time permits. Mark 2007-05-30.

        self.toolButton1 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton1.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton1.setCheckable(True)
        self.toolButton1.setFont(font)
        self.toolButton1.setObjectName("toolButton1")
        self.MMKit_GridLayout.addWidget(self.toolButton1, 0, 4, 1, 1)

        self.toolButton2 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton2.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton2.setCheckable(True)
        self.toolButton2.setFont(font)
        self.toolButton2.setObjectName("toolButton2")
        self.MMKit_GridLayout.addWidget(self.toolButton2, 0, 5, 1, 1)

        self.toolButton6 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton6.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton6.setCheckable(True)
        self.toolButton6.setFont(font)
        self.toolButton6.setObjectName("toolButton6")
        self.MMKit_GridLayout.addWidget(self.toolButton6, 1, 1, 1, 1)

        self.toolButton7 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton7.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton7.setCheckable(True)
        self.toolButton7.setFont(font)
        self.toolButton7.setObjectName("toolButton7")
        self.MMKit_GridLayout.addWidget(self.toolButton7, 1, 2, 1, 1)

        self.toolButton8 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton8.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton8.setCheckable(True)
        self.toolButton8.setFont(font)
        self.toolButton8.setObjectName("toolButton8")
        self.MMKit_GridLayout.addWidget(self.toolButton8, 1, 3, 1, 1)

        self.toolButton10 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton10.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton10.setCheckable(True)
        self.toolButton10.setFont(font)
        self.toolButton10.setObjectName("toolButton10")
        self.MMKit_GridLayout.addWidget(self.toolButton10, 1, 5, 1, 1)

        self.toolButton9 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton9.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton9.setCheckable(True)
        self.toolButton9.setFont(font)
        self.toolButton9.setObjectName("toolButton9")
        self.MMKit_GridLayout.addWidget(self.toolButton9, 1, 4, 1, 1)

        self.toolButton13 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton13.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton13.setCheckable(True)
        self.toolButton13.setFont(font)
        self.toolButton13.setObjectName("toolButton13")
        self.MMKit_GridLayout.addWidget(self.toolButton13, 2, 0, 1, 1)

        self.toolButton17 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton17.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton17.setCheckable(True)
        self.toolButton17.setFont(font)
        self.toolButton17.setObjectName("toolButton17")
        self.MMKit_GridLayout.addWidget(self.toolButton17, 2, 4, 1, 1)

        self.toolButton5 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton5.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton5.setCheckable(True)
        self.toolButton5.setFont(font)
        self.toolButton5.setObjectName("toolButton5")
        self.MMKit_GridLayout.addWidget(self.toolButton5, 1, 0, 1, 1)

        self.toolButton10_2 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton10_2.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton10_2.setCheckable(True)
        self.toolButton10_2.setFont(font)
        self.toolButton10_2.setObjectName("toolButton10_2")
        self.MMKit_GridLayout.addWidget(self.toolButton10_2, 2, 5, 1, 1)

        self.toolButton15 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton15.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton15.setCheckable(True)
        self.toolButton15.setFont(font)
        self.toolButton15.setObjectName("toolButton15")
        self.MMKit_GridLayout.addWidget(self.toolButton15, 2, 2, 1, 1)

        self.toolButton16 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton16.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton16.setCheckable(True)
        self.toolButton16.setFont(font)
        self.toolButton16.setObjectName("toolButton16")
        self.MMKit_GridLayout.addWidget(self.toolButton16, 2, 3, 1, 1)

        self.toolButton14 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton14.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton14.setCheckable(True)
        self.toolButton14.setFont(font)
        self.toolButton14.setObjectName("toolButton14")
        self.MMKit_GridLayout.addWidget(self.toolButton14, 2, 1, 1, 1)

        self.toolButton33 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton33.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton33.setCheckable(True)
        self.toolButton33.setFont(font)
        self.toolButton33.setObjectName("toolButton33")
        self.MMKit_GridLayout.addWidget(self.toolButton33, 3, 2, 1, 1)

        self.toolButton34 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton34.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton34.setCheckable(True)
        self.toolButton34.setFont(font)
        self.toolButton34.setObjectName("toolButton34")
        self.MMKit_GridLayout.addWidget(self.toolButton34, 3, 3, 1, 1)

        self.toolButton35 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton35.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton35.setCheckable(True)
        self.toolButton35.setFont(font)
        self.toolButton35.setObjectName("toolButton35")
        self.MMKit_GridLayout.addWidget(self.toolButton35, 3, 4, 1, 1)

        self.toolButton32 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton32.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton32.setCheckable(True)
        self.toolButton32.setFont(font)
        self.toolButton32.setObjectName("toolButton32")
        self.MMKit_GridLayout.addWidget(self.toolButton32, 3, 1, 1, 1)

        self.toolButton36 = QtGui.QToolButton(self.elementButtonGroup)
        self.toolButton36.setFixedSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.toolButton36.setCheckable(True)
        self.toolButton36.setFont(font)
        self.toolButton36.setObjectName("toolButton36")
        self.MMKit_GridLayout.addWidget(self.toolButton36, 3, 5, 1, 1)

        self.atomsPage_VBoxLayout.addWidget(self.elementButtonGroup)

        # Height is fixed (i.e. locked). Mark 2007-05-29.
        self.elementButtonGroup.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        # Atomic Hybrid label
        self.atomic_hybrids_label = QtGui.QLabel(self.atomsPageFrame)
        self.atomic_hybrids_label.setText("Atomic Hybrids :")
        self.atomsPage_VBoxLayout.addWidget(self.atomic_hybrids_label)

        # Elements Button GroupBox ends here. #######################

        # This special HBoxLayout contains both the hybrid button group and a
        # vert spacer (width = 0) to keep the Qt layout working properly
        # in certain situations like that described in bug 2407.
        # Mark 2007-06-20.
        self.special_HBoxLayout = QtGui.QHBoxLayout()
        self.special_HBoxLayout.setMargin(0)
        self.special_HBoxLayout.setSpacing(6)
        self.special_HBoxLayout.setObjectName("special_HBoxLayout")
        self.atomsPage_VBoxLayout.addLayout(self.special_HBoxLayout)

        # Hybrid GroupBox begins here ###############################

        self.hybrid_btngrp = QtGui.QGroupBox(self.atomsPageFrame)
        self.hybrid_btngrp.setObjectName("hybrid_btngrp")
        self.special_HBoxLayout.addWidget(self.hybrid_btngrp)

        self.hybridBtns_HBoxLayout = QtGui.QHBoxLayout(self.hybrid_btngrp)
        self.hybridBtns_HBoxLayout.setMargin(2)
        self.hybridBtns_HBoxLayout.setSpacing(0)
        self.hybridBtns_HBoxLayout.setObjectName("hybridBtns_HBoxLayout")

        self.sp3_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.sp3_btn.setMinimumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.sp3_btn.setMaximumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.sp3_btn.setCheckable(True)
        self.sp3_btn.setObjectName("sp3_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.sp3_btn)

        self.sp2_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.sp2_btn.setMinimumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.sp2_btn.setMaximumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.sp2_btn.setCheckable(True)
        self.sp2_btn.setObjectName("sp2_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.sp2_btn)

        self.sp_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.sp_btn.setMinimumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.sp_btn.setMaximumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.sp_btn.setCheckable(True)
        self.sp_btn.setObjectName("sp_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.sp_btn)

        self.graphitic_btn = QtGui.QToolButton(self.hybrid_btngrp)
        self.graphitic_btn.setMinimumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.graphitic_btn.setMaximumSize(
            QtCore.QSize(pmMMKitButtonWidth, pmMMKitButtonHeight))
        self.graphitic_btn.setCheckable(True)
        self.graphitic_btn.setObjectName("graphitic_btn")
        self.hybridBtns_HBoxLayout.addWidget(self.graphitic_btn)

        # This VSpacer is needed to help (but not completely) fix bug 2407. It
        # maintains the height of the layout(s) containing the hybrid button
        # group when it is hidden using hide(). Without this spacer the layout
        # gets screwed up in special situations like that described in bug 2407.
        # The + 10 below is needed to account for the margin (4 pixels) and
        # the additional 6 just help (I have a theory that the height of the
        # frame containing the label above the hybrid group box shrinks when
        # the label is hidden by inserting a space character). I'm
        # not going to worry about this now. +10 works well enough.
        # Mark 2007-06-20.
        VSpacer = QtGui.QSpacerItem(0, pmMMKitButtonHeight + 10,
                                    QtGui.QSizePolicy.Fixed,
                                    QtGui.QSizePolicy.Fixed)
        #self.hybridBtns_HBoxLayout.addItem(VSpacer)
        self.special_HBoxLayout.addItem(VSpacer)

        self.hybridBtns_HBoxLayout.addStretch(0)

        # Height is fixed. Mark 2007-05-29.
        self.hybrid_btngrp.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.MinimumExpanding),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        # This spacer keeps the MMKit button grid compressed when
        # the hybrid button group is hidden.
        self.atomsPageBottomVSpacer = \
            QtGui.QSpacerItem(5, 0,
                              QtGui.QSizePolicy.Fixed,
                              QtGui.QSizePolicy.MinimumExpanding)

        self.atomsPage_VBoxLayout.addItem(self.atomsPageBottomVSpacer)

        # Clipboard page begins here ############################################

        self.clipboardPage = QtGui.QWidget()
        self.clipboardPage.setObjectName("clipboardPage")

        self.gridlayout3 = QtGui.QGridLayout(self.clipboardPage)
        self.gridlayout3.setMargin(pmMMKitPageMargin)  # Was 4. Mark 2007-05-30
        self.gridlayout3.setSpacing(2)
        self.gridlayout3.setObjectName("gridlayout3")

        self.chunkListBox = QtGui.QListWidget(self.clipboardPage)

        self.chunkListBox.setMinimumSize(QtCore.QSize(100, 100))

        # Height is fixed. Mark 2007-05-29.
        self.chunkListBox.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.MinimumExpanding),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.chunkListBox.setObjectName("chunkListBox")
        self.gridlayout3.addWidget(self.chunkListBox, 0, 0, 1, 1)
        self.mmkit_tab.addTab(self.clipboardPage, "")

        self.libraryPage = QtGui.QWidget()
        #self.libraryPage = QtGui.QScrollArea()
        #self.libraryPageWidget = QtGui.QWidget()
        #self.libraryPage.setWidget(self.libraryPageWidget)
        self.libraryPage.setObjectName("libraryPage")
        self.mmkit_tab.addTab(self.libraryPage, "")

        self.MMKitGrpBox_VBoxLayout.addWidget(self.mmkit_tab)

        self.transmuteAtomsAction = QtGui.QWidgetAction(self.w)
        self.transmuteAtomsAction.setText("Transmute Atoms")
        self.transmuteAtomsAction.setIcon(
            geticon('ui/actions/Toolbars/Smart/Transmute_Atoms'))
        self.transmuteAtomsAction.setCheckable(False)

        transmuteBtn_HBoxLayout = QtGui.QHBoxLayout()

        self.transmuteBtn = QtGui.QToolButton(self.MMKit_groupBox)
        self.transmuteBtn.setDefaultAction(self.transmuteAtomsAction)
        self.transmuteBtn.setFixedSize(QtCore.QSize(36, 36))
        self.transmuteBtn.setIconSize(QtCore.QSize(22, 22))
        transmuteBtn_HBoxLayout.addWidget(self.transmuteBtn)

        self.browseButton = QtGui.QPushButton(MMKitDialog)
        transmuteBtn_HBoxLayout.addWidget(self.browseButton)

        self.defaultPartLibButton = QtGui.QPushButton(MMKitDialog)
        transmuteBtn_HBoxLayout.addWidget(self.defaultPartLibButton)

        self.atomsPageSpacer = QtGui.QSpacerItem(0, 5,
                                                 QtGui.QSizePolicy.Expanding,
                                                 QtGui.QSizePolicy.Minimum)

        transmuteBtn_HBoxLayout.addItem(self.atomsPageSpacer)

        self.MMKitGrpBox_VBoxLayout.addLayout(transmuteBtn_HBoxLayout)

        self.transmuteCB = QtGui.QCheckBox(" Force to Keep Bonds",
                                           self.MMKit_groupBox)

        self.MMKitGrpBox_VBoxLayout.addWidget(self.transmuteCB)

        #End MMKit groupbox
        self.pmVBoxLayout.addWidget(self.MMKit_groupBox)

        # This line is important. Without it, the MMKit groupbox is
        # too wide by default and causes a horizontal scrollbar
        # to be displayed at the bottom of the PropMgr. Mark 2007-05-30
        self.MMKit_groupBox.setMinimumWidth(200)

        # Height is fixed. Mark 2007-05-29.
        self.MMKit_groupBox.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.MinimumExpanding),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))
Example #20
0
class TextEdit(PlainTextEdit):
    def __init__(self, parent=None):
        PlainTextEdit.__init__(self, parent)
        self.saved_matches = {}
        self.smarts = NullSmarts(self)
        self.current_cursor_line = None
        self.current_search_mark = None
        self.smarts_highlight_timer = t = QTimer()
        t.setInterval(750), t.setSingleShot(True), t.timeout.connect(self.update_extra_selections)
        self.highlighter = SyntaxHighlighter()
        self.line_number_area = LineNumbers(self)
        self.apply_settings()
        self.setMouseTracking(True)
        self.cursorPositionChanged.connect(self.highlight_cursor_line)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.syntax = None

    @dynamic_property
    def is_modified(self):
        """ True if the document has been modified since it was loaded or since
        the last time is_modified was set to False. """

        def fget(self):
            return self.document().isModified()

        def fset(self, val):
            self.document().setModified(bool(val))

        return property(fget=fget, fset=fset)

    def sizeHint(self):
        return self.size_hint

    def apply_settings(self, prefs=None, dictionaries_changed=False):  # {{{
        prefs = prefs or tprefs
        self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs["editor_line_wrap"] else QPlainTextEdit.NoWrap)
        theme = get_theme(prefs["editor_theme"])
        self.apply_theme(theme)
        w = self.fontMetrics()
        self.space_width = w.width(" ")
        self.setTabStopWidth(prefs["editor_tab_stop_width"] * self.space_width)
        if dictionaries_changed:
            self.highlighter.rehighlight()

    def apply_theme(self, theme):
        self.theme = theme
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, "Normal", "bg"))
        pal.setColor(pal.AlternateBase, theme_color(theme, "CursorLine", "bg"))
        pal.setColor(pal.Text, theme_color(theme, "Normal", "fg"))
        pal.setColor(pal.Highlight, theme_color(theme, "Visual", "bg"))
        pal.setColor(pal.HighlightedText, theme_color(theme, "Visual", "fg"))
        self.setPalette(pal)
        self.tooltip_palette = pal = QPalette()
        pal.setColor(pal.ToolTipBase, theme_color(theme, "Tooltip", "bg"))
        pal.setColor(pal.ToolTipText, theme_color(theme, "Tooltip", "fg"))
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, "LineNr", "bg"))
        pal.setColor(pal.Text, theme_color(theme, "LineNr", "fg"))
        pal.setColor(pal.BrightText, theme_color(theme, "LineNrC", "fg"))
        self.match_paren_format = theme_format(theme, "MatchParen")
        font = self.font()
        ff = tprefs["editor_font_family"]
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs["editor_font_size"])
        self.tooltip_font = QFont(font)
        self.tooltip_font.setPointSize(font.pointSize() - 1)
        self.setFont(font)
        self.highlighter.apply_theme(theme)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x: w.width(str(x)), xrange(10)))
        self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
        self.highlight_color = theme_color(theme, "HighlightRegion", "bg")
        self.highlight_cursor_line()

    # }}}

    def load_text(self, text, syntax="html", process_template=False):
        self.syntax = syntax
        self.highlighter = get_highlighter(syntax)()
        self.highlighter.apply_theme(self.theme)
        self.highlighter.set_document(self.document())
        sclass = {"html": HTMLSmarts, "xml": HTMLSmarts, "css": CSSSmarts}.get(syntax, None)
        if sclass is not None:
            self.smarts = sclass(self)
        self.setPlainText(unicodedata.normalize("NFC", text))
        if process_template and QPlainTextEdit.find(self, "%CURSOR%"):
            c = self.textCursor()
            c.insertText("")

    def replace_text(self, text):
        c = self.textCursor()
        pos = c.position()
        c.beginEditBlock()
        c.clearSelection()
        c.select(c.Document)
        c.insertText(unicodedata.normalize("NFC", text))
        c.endEditBlock()
        c.setPosition(min(pos, len(text)))
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def simple_replace(self, text):
        c = self.textCursor()
        c.insertText(unicodedata.normalize("NFC", text))
        self.setTextCursor(c)

    def go_to_line(self, lnum, col=None):
        lnum = max(1, min(self.blockCount(), lnum))
        c = self.textCursor()
        c.clearSelection()
        c.movePosition(c.Start)
        c.movePosition(c.NextBlock, n=lnum - 1)
        c.movePosition(c.StartOfLine)
        c.movePosition(c.EndOfLine, c.KeepAnchor)
        text = unicode(c.selectedText()).rstrip("\0")
        if col is None:
            c.movePosition(c.StartOfLine)
            lt = text.lstrip()
            if text and lt and lt != text:
                c.movePosition(c.NextWord)
        else:
            c.setPosition(c.block().position() + col)
            if c.blockNumber() + 1 > lnum:
                # We have moved past the end of the line
                c.setPosition(c.block().position())
                c.movePosition(c.EndOfBlock)
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def update_extra_selections(self, instant=True):
        sel = []
        if self.current_cursor_line is not None:
            sel.append(self.current_cursor_line)
        if self.current_search_mark is not None:
            sel.append(self.current_search_mark)
        if instant:
            sel.extend(self.smarts.get_extra_selections(self))
        else:
            self.smarts_highlight_timer.start()
        self.setExtraSelections(sel)

    # Search and replace {{{
    def mark_selected_text(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.highlight_color)
        sel.cursor = self.textCursor()
        if sel.cursor.hasSelection():
            self.current_search_mark = sel
            c = self.textCursor()
            c.clearSelection()
            self.setTextCursor(c)
        else:
            self.current_search_mark = None
        self.update_extra_selections()

    def find_in_marked(self, pat, wrap=False, save_match=None):
        if self.current_search_mark is None:
            return False
        csm = self.current_search_mark.cursor
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        m_start = min(csm.position(), csm.anchor())
        m_end = max(csm.position(), csm.anchor())
        if c.position() < m_start:
            c.setPosition(m_start)
        if c.position() > m_end:
            c.setPosition(m_end)
        pos = m_start if reverse else m_end
        if wrap:
            pos = m_end if reverse else m_start
        c.setPosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, "\n").rstrip("\0")
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
            else:
                start, end = m_start + start, m_start + end
        else:
            if reverse:
                start, end = m_start + end, m_start + start
            else:
                start, end = c.anchor() + start, c.anchor() + end

        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        # Center search result on screen
        self.centerCursor()
        if save_match is not None:
            self.saved_matches[save_match] = (pat, m)
        return True

    def all_in_marked(self, pat, template=None):
        if self.current_search_mark is None:
            return 0
        c = self.current_search_mark.cursor
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, "\n").rstrip("\0")
        if template is None:
            count = len(pat.findall(raw))
        else:
            raw, count = pat.subn(template, raw)
            if count > 0:
                c.setKeepPositionOnInsert(True)
                c.insertText(raw)
                c.setKeepPositionOnInsert(False)
                self.update_extra_selections()
        return count

    def find(self, pat, wrap=False, marked=False, complete=False, save_match=None):
        if marked:
            return self.find_in_marked(pat, wrap=wrap, save_match=save_match)
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        if complete:
            # Search the entire text
            c.movePosition(c.End if reverse else c.Start)
        pos = c.Start if reverse else c.End
        if wrap and not complete:
            pos = c.End if reverse else c.Start
        c.movePosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, "\n").rstrip("\0")
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap and not complete:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
        else:
            if reverse:
                # Put the cursor at the start of the match
                start, end = end, start
            else:
                textpos = c.anchor()
                start, end = textpos + start, textpos + end
        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        # Center search result on screen
        self.centerCursor()
        if save_match is not None:
            self.saved_matches[save_match] = (pat, m)
        return True

    def find_spell_word(self, original_words, lang, from_cursor=True, center_on_cursor=True):
        c = self.textCursor()
        c.setPosition(c.position())
        if not from_cursor:
            c.movePosition(c.Start)
        c.movePosition(c.End, c.KeepAnchor)

        def find_first_word(haystack):
            match_pos, match_word = -1, None
            for w in original_words:
                idx = index_of(w, haystack, lang=lang)
                if idx > -1 and (match_pos == -1 or match_pos > idx):
                    match_pos, match_word = idx, w
            return match_pos, match_word

        while True:
            text = unicode(c.selectedText()).rstrip("\0")
            idx, word = find_first_word(text)
            if idx == -1:
                return False
            c.setPosition(c.anchor() + idx)
            c.setPosition(c.position() + string_length(word), c.KeepAnchor)
            if self.smarts.verify_for_spellcheck(c, self.highlighter):
                self.setTextCursor(c)
                if center_on_cursor:
                    self.centerCursor()
                return True
            c.setPosition(c.position())
            c.movePosition(c.End, c.KeepAnchor)

        return False

    def find_next_spell_error(self, from_cursor=True):
        c = self.textCursor()
        if not from_cursor:
            c.movePosition(c.Start)
        block = c.block()
        while block.isValid():
            for r in block.layout().additionalFormats():
                if r.format.property(SPELL_PROPERTY).toPyObject() is not None:
                    if not from_cursor or block.position() + r.start + r.length > c.position():
                        c.setPosition(block.position() + r.start)
                        c.setPosition(c.position() + r.length, c.KeepAnchor)
                        self.setTextCursor(c)
                        return True
            block = block.next()
        return False

    def replace(self, pat, template, saved_match="gui"):
        c = self.textCursor()
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, "\n").rstrip("\0")
        m = pat.fullmatch(raw)
        if m is None:
            # This can happen if either the user changed the selected text or
            # the search expression uses lookahead/lookbehind operators. See if
            # the saved match matches the currently selected text and
            # use it, if so.
            if saved_match is not None and saved_match in self.saved_matches:
                saved_pat, saved = self.saved_matches.pop(saved_match)
                if saved_pat == pat and saved.group() == raw:
                    m = saved
        if m is None:
            return False
        text = m.expand(template)
        c.insertText(text)
        return True

    def go_to_anchor(self, anchor):
        if anchor is TOP:
            c = self.textCursor()
            c.movePosition(c.Start)
            self.setTextCursor(c)
            return True
        base = r"""%%s\s*=\s*['"]{0,1}%s""" % regex.escape(anchor)
        raw = unicode(self.toPlainText())
        m = regex.search(base % "id", raw)
        if m is None:
            m = regex.search(base % "name", raw)
        if m is not None:
            c = self.textCursor()
            c.setPosition(m.start())
            self.setTextCursor(c)
            return True
        return False

    # }}}

    # Line numbers and cursor line {{{
    def highlight_cursor_line(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.palette().alternateBase())
        sel.format.setProperty(QTextFormat.FullWidthSelection, True)
        sel.cursor = self.textCursor()
        sel.cursor.clearSelection()
        self.current_cursor_line = sel
        self.update_extra_selections(instant=False)
        # Update the cursor line's line number in the line number area
        try:
            self.line_number_area.update(
                0, self.last_current_lnum[0], self.line_number_area.width(), self.last_current_lnum[1]
            )
        except AttributeError:
            pass
        block = self.textCursor().block()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        height = int(self.blockBoundingRect(block).height())
        self.line_number_area.update(0, top, self.line_number_area.width(), height)

    def update_line_number_area_width(self, block_count=0):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def line_number_area_width(self):
        digits = 1
        limit = max(1, self.blockCount())
        while limit >= 10:
            limit /= 10
            digits += 1

        return 8 + self.number_width * digits

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width()

    def resizeEvent(self, ev):
        QPlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        current = self.textCursor().block().blockNumber()
        painter.setPen(self.line_number_palette.color(QPalette.Text))

        while block.isValid() and top <= ev.rect().bottom():
            if block.isVisible() and bottom >= ev.rect().top():
                if current == num:
                    painter.save()
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    self.last_current_lnum = (top, bottom - top)
                painter.drawText(
                    0, top, self.line_number_area.width() - 5, self.fontMetrics().height(), Qt.AlignRight, str(num + 1)
                )
                if current == num:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1

    # }}}

    def event(self, ev):
        if ev.type() == ev.ToolTip:
            self.show_tooltip(ev)
            return True
        if ev.type() == ev.ShortcutOverride:
            if ev in (
                # Let the global cut/copy/paste shortcuts work,this avoids the nbsp
                # problem as well, since they use the overridden copy() method
                # instead of the one from Qt
                QKeySequence.Copy,
                QKeySequence.Cut,
                QKeySequence.Paste,
            ) or (
                # This is used to convert typed hex codes into unicode
                # characters
                ev.key() == Qt.Key_X
                and ev.modifiers() == Qt.AltModifier
            ):
                ev.ignore()
                return False
        return QPlainTextEdit.event(self, ev)

    def recheck_word(self, word, locale):
        c = self.textCursor()
        c.movePosition(c.Start)
        block = c.block()
        while block.isValid():
            for r in block.layout().additionalFormats():
                x = r.format.property(SPELL_PROPERTY).toPyObject()
                if x is not None and word == x[0]:
                    self.highlighter.reformat_block(block)
                    break
            block = block.next()

    # Tooltips {{{
    def syntax_format_for_cursor(self, cursor):
        if cursor.isNull():
            return
        pos = cursor.positionInBlock()
        for r in cursor.block().layout().additionalFormats():
            if r.start <= pos < r.start + r.length and r.format.property(SYNTAX_PROPERTY).toBool():
                return r.format

    def show_tooltip(self, ev):
        c = self.cursorForPosition(ev.pos())
        fmt = self.syntax_format_for_cursor(c)
        if fmt is not None:
            tt = unicode(fmt.toolTip())
            if tt:
                QToolTip.setFont(self.tooltip_font)
                QToolTip.setPalette(self.tooltip_palette)
                QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
        QToolTip.hideText()
        ev.ignore()

    # }}}

    def get_range_inside_tag(self):
        c = self.textCursor()
        left = min(c.anchor(), c.position())
        right = max(c.anchor(), c.position())
        # For speed we use QPlainTextEdit's toPlainText as we dont care about
        # spaces in this context
        raw = unicode(QPlainTextEdit.toPlainText(self))
        # Make sure the left edge is not within a <>
        gtpos = raw.find(">", left)
        ltpos = raw.find("<", left)
        if gtpos < ltpos:
            left = gtpos + 1 if gtpos > -1 else left
        right = max(left, right)
        if right != left:
            gtpos = raw.find(">", right)
            ltpos = raw.find("<", right)
            if ltpos > gtpos:
                ltpos = raw.rfind("<", left, right + 1)
                right = max(ltpos, left)
        return left, right

    def format_text(self, formatting):
        if self.syntax != "html":
            return
        color = "currentColor"
        if formatting in {"color", "background-color"}:
            color = QColorDialog.getColor(
                QColor(Qt.black if formatting == "color" else Qt.white),
                self,
                _("Choose color"),
                QColorDialog.ShowAlphaChannel,
            )
            if not color.isValid():
                return
            r, g, b, a = color.getRgb()
            if a == 255:
                color = "rgb(%d, %d, %d)" % (r, g, b)
            else:
                color = "rgba(%d, %d, %d, %.2g)" % (r, g, b, a / 255)
        prefix, suffix = {
            "bold": ("<b>", "</b>"),
            "italic": ("<i>", "</i>"),
            "underline": ("<u>", "</u>"),
            "strikethrough": ("<strike>", "</strike>"),
            "superscript": ("<sup>", "</sup>"),
            "subscript": ("<sub>", "</sub>"),
            "color": ('<span style="color: %s">' % color, "</span>"),
            "background-color": ('<span style="background-color: %s">' % color, "</span>"),
        }[formatting]
        left, right = self.get_range_inside_tag()
        c = self.textCursor()
        c.setPosition(left)
        c.setPosition(right, c.KeepAnchor)
        prev_text = unicode(c.selectedText()).rstrip("\0")
        c.insertText(prefix + prev_text + suffix)
        if prev_text:
            right = c.position()
            c.setPosition(left)
            c.setPosition(right, c.KeepAnchor)
        else:
            c.setPosition(c.position() - len(suffix))
        self.setTextCursor(c)

    def insert_image(self, href):
        c = self.textCursor()
        template, alt = "url(%s)", ""
        left = min(c.position(), c.anchor)
        if self.syntax == "html":
            left, right = self.get_range_inside_tag()
            c.setPosition(left)
            c.setPosition(right, c.KeepAnchor)
            alt = _("Image")
            template = '<img alt="{0}" src="%s" />'.format(alt)
            href = prepare_string_for_xml(href, True)
        text = template % href
        c.insertText(text)
        if self.syntax == "html":
            c.setPosition(left + 10)
            c.setPosition(c.position() + len(alt), c.KeepAnchor)
        else:
            c.setPosition(left)
            c.setPosition(left + len(text), c.KeepAnchor)
        self.setTextCursor(c)

    def insert_hyperlink(self, target, text):
        if hasattr(self.smarts, "insert_hyperlink"):
            self.smarts.insert_hyperlink(self, target, text)

    def insert_tag(self, tag):
        if hasattr(self.smarts, "insert_tag"):
            self.smarts.insert_tag(self, tag)

    def keyPressEvent(self, ev):
        if ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier:
            if self.replace_possible_unicode_sequence():
                ev.accept()
                return
        if ev.key() == Qt.Key_Insert:
            self.setOverwriteMode(self.overwriteMode() ^ True)
            ev.accept()
            return
        QPlainTextEdit.keyPressEvent(self, ev)
        if (
            (ev.key() == Qt.Key_Semicolon or ";" in unicode(ev.text()))
            and tprefs["replace_entities_as_typed"]
            and self.syntax == "html"
        ):
            self.replace_possible_entity()

    def replace_possible_unicode_sequence(self):
        c = self.textCursor()
        has_selection = c.hasSelection()
        if has_selection:
            text = unicode(c.selectedText()).rstrip("\0")
        else:
            c.setPosition(c.position() - min(c.positionInBlock(), 6), c.KeepAnchor)
            text = unicode(c.selectedText()).rstrip("\0")
        m = re.search(r"[a-fA-F0-9]{2,6}$", text)
        if m is None:
            return False
        text = m.group()
        try:
            num = int(text, 16)
        except ValueError:
            return False
        if num > 0x10FFFF or num < 1:
            return False
        end_pos = max(c.anchor(), c.position())
        c.setPosition(end_pos - len(text)), c.setPosition(end_pos, c.KeepAnchor)
        c.insertText(safe_chr(num))
        return True

    def replace_possible_entity(self):
        c = self.textCursor()
        c.setPosition(c.position() - min(c.positionInBlock(), 10), c.KeepAnchor)
        text = unicode(c.selectedText()).rstrip("\0")
        m = entity_pat.search(text)
        if m is None:
            return
        ent = m.group()
        repl = xml_entity_to_unicode(m)
        if repl != ent:
            c.setPosition(c.position() + m.start(), c.KeepAnchor)
            c.insertText(repl)

    def select_all(self):
        c = self.textCursor()
        c.clearSelection()
        c.setPosition(0)
        c.movePosition(c.End, c.KeepAnchor)
        self.setTextCursor(c)

    def rename_block_tag(self, new_name):
        if hasattr(self.smarts, "rename_block_tag"):
            self.smarts.rename_block_tag(self, new_name)

    def current_tag(self):
        return self.smarts.cursor_position_with_sourceline(self.textCursor())

    def goto_sourceline(self, sourceline, tags, attribute=None):
        return self.smarts.goto_sourceline(self, sourceline, tags, attribute=attribute)

    def get_tag_contents(self):
        c = self.smarts.get_inner_HTML(self)
        if c is not None:
            return self.selected_text_from_cursor(c)

    def goto_css_rule(self, rule_address, sourceline_address=None):
        from calibre.gui2.tweak_book.editor.smart.css import find_rule

        block = None
        if self.syntax == "css":
            raw = unicode(self.toPlainText())
            line, col = find_rule(raw, rule_address)
            if line is not None:
                block = self.document().findBlockByNumber(line - 1)
        elif sourceline_address is not None:
            sourceline, tags = sourceline_address
            if self.goto_sourceline(sourceline, tags):
                c = self.textCursor()
                c.setPosition(c.position() + 1)
                self.setTextCursor(c)
                raw = self.get_tag_contents()
                line, col = find_rule(raw, rule_address)
                if line is not None:
                    block = self.document().findBlockByNumber(c.blockNumber() + line - 1)

        if block is not None and block.isValid():
            c = self.textCursor()
            c.setPosition(block.position() + col)
            self.setTextCursor(c)
Example #21
0
class TextEdit(QPlainTextEdit):

    def __init__(self, parent=None):
        QPlainTextEdit.__init__(self, parent)
        self.highlighter = SyntaxHighlighter(self)
        self.apply_settings()
        self.setMouseTracking(True)
        self.cursorPositionChanged.connect(self.highlight_cursor_line)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.line_number_area = LineNumbers(self)

    @dynamic_property
    def is_modified(self):
        ''' True if the document has been modified since it was loaded or since
        the last time is_modified was set to False. '''
        def fget(self):
            return self.document().isModified()
        def fset(self, val):
            self.document().setModified(bool(val))
        return property(fget=fget, fset=fset)

    def sizeHint(self):
        return self.size_hint

    def apply_settings(self, prefs=None):  # {{{
        prefs = prefs or tprefs
        self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
        theme = THEMES.get(prefs['editor_theme'], None)
        if theme is None:
            theme = THEMES[default_theme()]
        self.apply_theme(theme)

    def apply_theme(self, theme):
        self.theme = theme
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.tooltip_palette = pal = QPalette()
        pal.setColor(pal.ToolTipBase, theme_color(theme, 'Tooltip', 'bg'))
        pal.setColor(pal.ToolTipText, theme_color(theme, 'Tooltip', 'fg'))
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.tooltip_font = QFont(font)
        self.tooltip_font.setPointSize(font.pointSize() - 1)
        self.setFont(font)
        self.highlighter.apply_theme(theme)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
        self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
    # }}}

    def load_text(self, text, syntax='html'):
        self.highlighter = {'html':HTMLHighlighter, 'css':CSSHighlighter, 'xml':XMLHighlighter}.get(syntax, SyntaxHighlighter)(self)
        self.highlighter.apply_theme(self.theme)
        self.highlighter.setDocument(self.document())
        self.setPlainText(text)

    def replace_text(self, text):
        c = self.textCursor()
        pos = c.position()
        c.beginEditBlock()
        c.clearSelection()
        c.select(c.Document)
        c.insertText(text)
        c.endEditBlock()
        c.setPosition(min(pos, len(text)))
        self.setTextCursor(c)
        self.ensureCursorVisible()

    # Line numbers and cursor line {{{
    def highlight_cursor_line(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.palette().alternateBase())
        sel.format.setProperty(QTextFormat.FullWidthSelection, True)
        sel.cursor = self.textCursor()
        sel.cursor.clearSelection()
        self.setExtraSelections([sel])
        # Update the cursor line's line number in the line number area
        try:
            self.line_number_area.update(0, self.last_current_lnum[0], self.line_number_area.width(), self.last_current_lnum[1])
        except AttributeError:
            pass
        block = self.textCursor().block()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        height = int(self.blockBoundingRect(block).height())
        self.line_number_area.update(0, top, self.line_number_area.width(), height)

    def update_line_number_area_width(self, block_count=0):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def line_number_area_width(self):
        digits = 1
        limit = max(1, self.blockCount())
        while limit >= 10:
            limit /= 10
            digits += 1

        return 8 + self.number_width * digits

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width()

    def resizeEvent(self, ev):
        QPlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        current = self.textCursor().block().blockNumber()
        painter.setPen(self.line_number_palette.color(QPalette.Text))

        while block.isValid() and top <= ev.rect().bottom():
            if block.isVisible() and bottom >= ev.rect().top():
                if current == num:
                    painter.save()
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    self.last_current_lnum = (top, bottom - top)
                painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(),
                              Qt.AlignRight, str(num + 1))
                if current == num:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1
    # }}}

    # Tooltips {{{
    def event(self, ev):
        if ev.type() == ev.ToolTip:
            self.show_tooltip(ev)
            return True
        return QPlainTextEdit.event(self, ev)

    def syntax_format_for_cursor(self, cursor):
        if cursor.isNull():
            return
        pos = cursor.positionInBlock()
        for r in cursor.block().layout().additionalFormats():
            if r.start <= pos < r.start + r.length:
                return r.format

    def show_tooltip(self, ev):
        c = self.cursorForPosition(ev.pos())
        fmt = self.syntax_format_for_cursor(c)
        if fmt is not None:
            tt = unicode(fmt.toolTip())
            if tt:
                QToolTip.setFont(self.tooltip_font)
                QToolTip.setPalette(self.tooltip_palette)
                QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
        QToolTip.hideText()
        ev.ignore()
Example #22
0
class MyBlockingBusy(QDialog):  # {{{

    all_done = pyqtSignal()

    def __init__(self, args, ids, db, refresh_books, cc_widgets, s_r_func, do_sr, sr_calls, parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout =  l = QVBoxLayout()
        self.setLayout(l)

        self.msg = QLabel(_('Processing %d books, please wait...') % len(ids))
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.setWindowTitle(window_title + '...')
        self.setMinimumWidth(200)
        self.resize(self.sizeHint())
        self.error = None
        self.all_done.connect(self.on_all_done, type=Qt.QueuedConnection)
        self.args, self.ids = args, ids
        self.db, self.cc_widgets = db, cc_widgets
        self.s_r_func = FunctionDispatcher(s_r_func)
        self.do_sr = do_sr
        self.sr_calls = sr_calls
        self.refresh_books = refresh_books

    def accept(self):
        pass

    def reject(self):
        pass

    def on_all_done(self):
        if not self.error:
            # The cc widgets can only be accessed in the GUI thread
            try:
                for w in self.cc_widgets:
                    w.commit(self.ids)
            except Exception as err:
                import traceback
                self.error = (err, traceback.format_exc())
        self.pi.stopAnimation()
        QDialog.accept(self)

    def exec_(self):
        self.thread = Thread(target=self.do_it)
        self.thread.start()
        self.pi.startAnimation()
        return QDialog.exec_(self)

    def do_it(self):
        try:
            self.do_all()
        except Exception as err:
            import traceback
            try:
                err = unicode(err)
            except:
                err = repr(err)
            self.error = (err, traceback.format_exc())

        self.all_done.emit()

    def do_all(self):
        cache = self.db.new_api
        args = self.args

        # Title and authors
        if args.do_swap_ta:
            title_map = cache.all_field_for('title', self.ids)
            authors_map = cache.all_field_for('authors', self.ids)
            def new_title(authors):
                ans = authors_to_string(authors)
                return titlecase(ans) if args.do_title_case else ans
            new_title_map = {bid:new_title(authors) for bid, authors in authors_map.iteritems()}
            new_authors_map = {bid:string_to_authors(title) for bid, title in title_map.iteritems()}
            cache.set_field('authors', new_authors_map)
            cache.set_field('title', new_title_map)

        if args.do_title_case and not args.do_swap_ta:
            title_map = cache.all_field_for('title', self.ids)
            cache.set_field('title', {bid:titlecase(title) for bid, title in title_map.iteritems()})

        if args.do_title_sort:
            lang_map = cache.all_field_for('languages', self.ids)
            title_map = cache.all_field_for('title', self.ids)
            def get_sort(book_id):
                if args.languages:
                    lang = args.languages[0]
                else:
                    try:
                        lang = lang_map[book_id][0]
                    except (KeyError, IndexError, TypeError, AttributeError):
                        lang = 'eng'
                return title_sort(title_map[book_id], lang=lang)
            cache.set_field('sort', {bid:get_sort(bid) for bid in self.ids})

        if args.au:
            authors = string_to_authors(args.au)
            cache.set_field('authors', {bid:authors for bid in self.ids})

        if args.do_auto_author:
            aus_map = cache.author_sort_strings_for_books(self.ids)
            cache.set_field('author_sort', {book_id:' & '.join(aus_map[book_id]) for book_id in aus_map})

        if args.aus and args.do_aus:
            cache.set_field('author_sort', {bid:args.aus for bid in self.ids})

        # Covers
        if args.cover_action == 'remove':
            cache.set_cover({bid:None for bid in self.ids})
        elif args.cover_action == 'generate':
            from calibre.ebooks import calibre_cover
            from calibre.ebooks.metadata import fmt_sidx
            from calibre.gui2 import config
            for book_id in self.ids:
                mi = self.db.get_metadata(book_id, index_is_id=True)
                series_string = None
                if mi.series:
                    series_string = _('Book %(sidx)s of %(series)s')%dict(
                        sidx=fmt_sidx(mi.series_index,
                        use_roman=config['use_roman_numerals_for_series_number']),
                        series=mi.series)

                cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
                        series_string=series_string)
                cache.set_cover({book_id:cdata})
        elif args.cover_action == 'fromfmt':
            for book_id in self.ids:
                fmts = cache.formats(book_id, verify_formats=False)
                if fmts:
                    covers = []
                    for fmt in fmts:
                        fmtf = cache.format(book_id, fmt, as_file=True)
                        if fmtf is None:
                            continue
                        cdata, area = get_cover_data(fmtf, fmt)
                        if cdata:
                            covers.append((cdata, area))
                    covers.sort(key=lambda x: x[1])
                    if covers:
                        cache.set_cover({book_id:covers[-1][0]})
        elif args.cover_action == 'trim':
            from calibre.utils.magick import Image
            for book_id in self.ids:
                cdata = cache.cover(book_id)
                if cdata:
                    im = Image()
                    im.load(cdata)
                    im.trim(tweaks['cover_trim_fuzz_value'])
                    cdata = im.export('jpg')
                    cache.set_cover({book_id:cdata})
        elif args.cover_action == 'clone':
            cdata = None
            for book_id in self.ids:
                cdata = cache.cover(book_id)
                if cdata:
                    break
            if cdata:
                cache.set_cover({bid:cdata for bid in self.ids if bid != book_id})

        # Formats
        if args.do_remove_format:
            cache.remove_formats({bid:(args.remove_format,) for bid in self.ids})

        if args.restore_original:
            for book_id in self.ids:
                formats = cache.formats(book_id)
                originals = tuple(x.upper() for x in formats if x.upper().startswith('ORIGINAL_'))
                for ofmt in originals:
                    cache.restore_original_format(book_id, ofmt)

        # Various fields
        if args.rating != -1:
            cache.set_field('rating', {bid:args.rating*2 for bid in self.ids})

        if args.clear_pub:
            cache.set_field('publisher', {bid:'' for bid in self.ids})

        if args.pub:
            cache.set_field('publisher', {bid:args.pub for bid in self.ids})

        if args.clear_series:
            cache.set_field('series', {bid:'' for bid in self.ids})

        if args.pubdate is not None:
            cache.set_field('pubdate', {bid:args.pubdate for bid in self.ids})

        if args.adddate is not None:
            cache.set_field('timestamp', {bid:args.adddate for bid in self.ids})

        if args.do_series:
            sval = args.series_start_value if args.do_series_restart else cache.get_next_series_num_for(args.series, current_indices=True)
            cache.set_field('series', {bid:args.series for bid in self.ids})
            if not args.series:
                cache.set_field('series_index', {bid:1.0 for bid in self.ids})
            else:
                def next_series_num(bid, i):
                    if args.do_series_restart:
                        return sval + i
                    next_num = _get_next_series_num_for_list(sorted(sval.itervalues()), unwrap=False)
                    sval[bid] = next_num
                    return next_num

                smap = {bid:next_series_num(bid, i) for i, bid in enumerate(self.ids)}
                if args.do_autonumber:
                    cache.set_field('series_index', smap)
                elif tweaks['series_index_auto_increment'] != 'no_change':
                    cache.set_field('series_index', {bid:1.0 for bid in self.ids})

        if args.comments is not null:
            cache.set_field('comments', {bid:args.comments for bid in self.ids})

        if args.do_remove_conv:
            cache.delete_conversion_options(self.ids)

        if args.clear_languages:
            cache.set_field('languages', {bid:() for bid in self.ids})
        elif args.languages:
            cache.set_field('languages', {bid:args.languages for bid in self.ids})

        if args.remove_all:
            cache.set_field('tags', {bid:() for bid in self.ids})
        if args.add or args.remove:
            self.db.bulk_modify_tags(self.ids, add=args.add, remove=args.remove)

        if self.do_sr:
            for book_id in self.ids:
                self.s_r_func(book_id)
            if self.sr_calls:
                for field, book_id_val_map in self.sr_calls.iteritems():
                    self.refresh_books.update(self.db.new_api.set_field(field, book_id_val_map))
Example #23
0
 def do_size_hint(self, option, index):
     text = index.data(Qt.DisplayRole).toString()
     font = QFont(option.font)
     font.setPointSize(QFontInfo(font).pointSize() * 1.5)
     m = QFontMetrics(font)
     return QSize(m.width(text), m.height())
Example #24
0
class TextEdit(PlainTextEdit):
    def __init__(self, parent=None):
        PlainTextEdit.__init__(self, parent)
        self.saved_matches = {}
        self.smarts = NullSmarts(self)
        self.current_cursor_line = None
        self.current_search_mark = None
        self.smarts_highlight_timer = t = QTimer()
        t.setInterval(750), t.setSingleShot(True), t.timeout.connect(
            self.update_extra_selections)
        self.highlighter = SyntaxHighlighter()
        self.line_number_area = LineNumbers(self)
        self.apply_settings()
        self.setMouseTracking(True)
        self.cursorPositionChanged.connect(self.highlight_cursor_line)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.syntax = None

    @dynamic_property
    def is_modified(self):
        ''' True if the document has been modified since it was loaded or since
        the last time is_modified was set to False. '''
        def fget(self):
            return self.document().isModified()

        def fset(self, val):
            self.document().setModified(bool(val))

        return property(fget=fget, fset=fset)

    def sizeHint(self):
        return self.size_hint

    def apply_settings(self, prefs=None, dictionaries_changed=False):  # {{{
        prefs = prefs or tprefs
        self.setLineWrapMode(
            QPlainTextEdit.WidgetWidth
            if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
        theme = THEMES.get(prefs['editor_theme'], None)
        if theme is None:
            theme = THEMES[default_theme()]
        self.apply_theme(theme)
        w = self.fontMetrics()
        self.space_width = w.width(' ')
        self.setTabStopWidth(prefs['editor_tab_stop_width'] * self.space_width)
        if dictionaries_changed:
            self.highlighter.rehighlight()

    def apply_theme(self, theme):
        self.theme = theme
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.tooltip_palette = pal = QPalette()
        pal.setColor(pal.ToolTipBase, theme_color(theme, 'Tooltip', 'bg'))
        pal.setColor(pal.ToolTipText, theme_color(theme, 'Tooltip', 'fg'))
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        self.match_paren_format = theme_format(theme, 'MatchParen')
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.tooltip_font = QFont(font)
        self.tooltip_font.setPointSize(font.pointSize() - 1)
        self.setFont(font)
        self.highlighter.apply_theme(theme)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x: w.width(str(x)), xrange(10)))
        self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
        self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg')
        self.highlight_cursor_line()

    # }}}

    def load_text(self, text, syntax='html', process_template=False):
        self.syntax = syntax
        self.highlighter = get_highlighter(syntax)()
        self.highlighter.apply_theme(self.theme)
        self.highlighter.set_document(self.document())
        sclass = {'html': HTMLSmarts, 'xml': HTMLSmarts}.get(syntax, None)
        if sclass is not None:
            self.smarts = sclass(self)
        self.setPlainText(unicodedata.normalize('NFC', text))
        if process_template and QPlainTextEdit.find(self, '%CURSOR%'):
            c = self.textCursor()
            c.insertText('')

    def replace_text(self, text):
        c = self.textCursor()
        pos = c.position()
        c.beginEditBlock()
        c.clearSelection()
        c.select(c.Document)
        c.insertText(unicodedata.normalize('NFC', text))
        c.endEditBlock()
        c.setPosition(min(pos, len(text)))
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def go_to_line(self, lnum, col=None):
        lnum = max(1, min(self.blockCount(), lnum))
        c = self.textCursor()
        c.clearSelection()
        c.movePosition(c.Start)
        c.movePosition(c.NextBlock, n=lnum - 1)
        c.movePosition(c.StartOfLine)
        c.movePosition(c.EndOfLine, c.KeepAnchor)
        text = unicode(c.selectedText()).rstrip('\0')
        if col is None:
            c.movePosition(c.StartOfLine)
            lt = text.lstrip()
            if text and lt and lt != text:
                c.movePosition(c.NextWord)
        else:
            c.setPosition(c.block().position() + col)
            if c.blockNumber() + 1 > lnum:
                # We have moved past the end of the line
                c.setPosition(c.block().position())
                c.movePosition(c.EndOfBlock)
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def update_extra_selections(self, instant=True):
        sel = []
        if self.current_cursor_line is not None:
            sel.append(self.current_cursor_line)
        if self.current_search_mark is not None:
            sel.append(self.current_search_mark)
        if instant:
            sel.extend(self.smarts.get_extra_selections(self))
        else:
            self.smarts_highlight_timer.start()
        self.setExtraSelections(sel)

    # Search and replace {{{
    def mark_selected_text(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.highlight_color)
        sel.cursor = self.textCursor()
        if sel.cursor.hasSelection():
            self.current_search_mark = sel
            c = self.textCursor()
            c.clearSelection()
            self.setTextCursor(c)
        else:
            self.current_search_mark = None
        self.update_extra_selections()

    def find_in_marked(self, pat, wrap=False, save_match=None):
        if self.current_search_mark is None:
            return False
        csm = self.current_search_mark.cursor
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        m_start = min(csm.position(), csm.anchor())
        m_end = max(csm.position(), csm.anchor())
        if c.position() < m_start:
            c.setPosition(m_start)
        if c.position() > m_end:
            c.setPosition(m_end)
        pos = m_start if reverse else m_end
        if wrap:
            pos = m_end if reverse else m_start
        c.setPosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR,
                                                '\n').rstrip('\0')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
            else:
                start, end = m_start + start, m_start + end
        else:
            if reverse:
                start, end = m_start + end, m_start + start
            else:
                start, end = c.anchor() + start, c.anchor() + end

        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        # Center search result on screen
        self.centerCursor()
        if save_match is not None:
            self.saved_matches[save_match] = (pat, m)
        return True

    def all_in_marked(self, pat, template=None):
        if self.current_search_mark is None:
            return 0
        c = self.current_search_mark.cursor
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR,
                                                '\n').rstrip('\0')
        if template is None:
            count = len(pat.findall(raw))
        else:
            raw, count = pat.subn(template, raw)
            if count > 0:
                c.setKeepPositionOnInsert(True)
                c.insertText(raw)
                c.setKeepPositionOnInsert(False)
                self.update_extra_selections()
        return count

    def find(self,
             pat,
             wrap=False,
             marked=False,
             complete=False,
             save_match=None):
        if marked:
            return self.find_in_marked(pat, wrap=wrap, save_match=save_match)
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        if complete:
            # Search the entire text
            c.movePosition(c.End if reverse else c.Start)
        pos = c.Start if reverse else c.End
        if wrap and not complete:
            pos = c.End if reverse else c.Start
        c.movePosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR,
                                                '\n').rstrip('\0')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap and not complete:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
        else:
            if reverse:
                # Put the cursor at the start of the match
                start, end = end, start
            else:
                textpos = c.anchor()
                start, end = textpos + start, textpos + end
        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        # Center search result on screen
        self.centerCursor()
        if save_match is not None:
            self.saved_matches[save_match] = (pat, m)
        return True

    def find_spell_word(self, original_words, lang, from_cursor=True):
        c = self.textCursor()
        c.setPosition(c.position())
        if not from_cursor:
            c.movePosition(c.Start)
        c.movePosition(c.End, c.KeepAnchor)

        def find_first_word(haystack):
            match_pos, match_word = -1, None
            for w in original_words:
                idx = index_of(w, haystack, lang=lang)
                if idx > -1 and (match_pos == -1 or match_pos > idx):
                    match_pos, match_word = idx, w
            return match_pos, match_word

        while True:
            text = unicode(c.selectedText()).rstrip('\0')
            idx, word = find_first_word(text)
            if idx == -1:
                return False
            c.setPosition(c.anchor() + idx)
            c.setPosition(c.position() + string_length(word), c.KeepAnchor)
            if self.smarts.verify_for_spellcheck(c, self.highlighter):
                self.setTextCursor(c)
                self.centerCursor()
                return True
            c.setPosition(c.position())
            c.movePosition(c.End, c.KeepAnchor)

        return False

    def replace(self, pat, template, saved_match='gui'):
        c = self.textCursor()
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR,
                                                '\n').rstrip('\0')
        m = pat.fullmatch(raw)
        if m is None:
            # This can happen if either the user changed the selected text or
            # the search expression uses lookahead/lookbehind operators. See if
            # the saved match matches the currently selected text and
            # use it, if so.
            if saved_match is not None and saved_match in self.saved_matches:
                saved_pat, saved = self.saved_matches.pop(saved_match)
                if saved_pat == pat and saved.group() == raw:
                    m = saved
        if m is None:
            return False
        text = m.expand(template)
        c.insertText(text)
        return True

    def go_to_anchor(self, anchor):
        if anchor is TOP:
            c = self.textCursor()
            c.movePosition(c.Start)
            self.setTextCursor(c)
            return True
        base = r'''%%s\s*=\s*['"]{0,1}%s''' % regex.escape(anchor)
        raw = unicode(self.toPlainText())
        m = regex.search(base % 'id', raw)
        if m is None:
            m = regex.search(base % 'name', raw)
        if m is not None:
            c = self.textCursor()
            c.setPosition(m.start())
            self.setTextCursor(c)
            return True
        return False

    # }}}

    # Line numbers and cursor line {{{
    def highlight_cursor_line(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.palette().alternateBase())
        sel.format.setProperty(QTextFormat.FullWidthSelection, True)
        sel.cursor = self.textCursor()
        sel.cursor.clearSelection()
        self.current_cursor_line = sel
        self.update_extra_selections(instant=False)
        # Update the cursor line's line number in the line number area
        try:
            self.line_number_area.update(0, self.last_current_lnum[0],
                                         self.line_number_area.width(),
                                         self.last_current_lnum[1])
        except AttributeError:
            pass
        block = self.textCursor().block()
        top = int(
            self.blockBoundingGeometry(block).translated(
                self.contentOffset()).top())
        height = int(self.blockBoundingRect(block).height())
        self.line_number_area.update(0, top, self.line_number_area.width(),
                                     height)

    def update_line_number_area_width(self, block_count=0):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def line_number_area_width(self):
        digits = 1
        limit = max(1, self.blockCount())
        while limit >= 10:
            limit /= 10
            digits += 1

        return 8 + self.number_width * digits

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            self.line_number_area.update(0, rect.y(),
                                         self.line_number_area.width(),
                                         rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width()

    def resizeEvent(self, ev):
        QPlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        self.line_number_area.setGeometry(
            QRect(cr.left(), cr.top(), self.line_number_area_width(),
                  cr.height()))

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(),
                         self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(
            self.blockBoundingGeometry(block).translated(
                self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        current = self.textCursor().block().blockNumber()
        painter.setPen(self.line_number_palette.color(QPalette.Text))

        while block.isValid() and top <= ev.rect().bottom():
            if block.isVisible() and bottom >= ev.rect().top():
                if current == num:
                    painter.save()
                    painter.setPen(
                        self.line_number_palette.color(QPalette.BrightText))
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    self.last_current_lnum = (top, bottom - top)
                painter.drawText(0, top,
                                 self.line_number_area.width() - 5,
                                 self.fontMetrics().height(), Qt.AlignRight,
                                 str(num + 1))
                if current == num:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1

    # }}}

    def event(self, ev):
        if ev.type() == ev.ToolTip:
            self.show_tooltip(ev)
            return True
        if ev.type() == ev.ShortcutOverride:
            if ev in (
                    # Let the global cut/copy/paste shortcuts work,this avoids the nbsp
                    # problem as well, since they use the overridden copy() method
                    # instead of the one from Qt
                    QKeySequence.Copy,
                    QKeySequence.Cut,
                    QKeySequence.Paste,
            ) or (
                    # This is used to convert typed hex codes into unicode
                    # characters
                    ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier):
                ev.ignore()
                return False
        return QPlainTextEdit.event(self, ev)

    # Tooltips {{{
    def syntax_format_for_cursor(self, cursor):
        if cursor.isNull():
            return
        pos = cursor.positionInBlock()
        for r in cursor.block().layout().additionalFormats():
            if r.start <= pos < r.start + r.length and r.format.property(
                    SYNTAX_PROPERTY).toBool():
                return r.format

    def show_tooltip(self, ev):
        c = self.cursorForPosition(ev.pos())
        fmt = self.syntax_format_for_cursor(c)
        if fmt is not None:
            tt = unicode(fmt.toolTip())
            if tt:
                QToolTip.setFont(self.tooltip_font)
                QToolTip.setPalette(self.tooltip_palette)
                QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
        QToolTip.hideText()
        ev.ignore()

    # }}}

    def get_range_inside_tag(self):
        c = self.textCursor()
        left = min(c.anchor(), c.position())
        right = max(c.anchor(), c.position())
        # For speed we use QPlainTextEdit's toPlainText as we dont care about
        # spaces in this context
        raw = unicode(QPlainTextEdit.toPlainText(self))
        # Make sure the left edge is not within a <>
        gtpos = raw.find('>', left)
        ltpos = raw.find('<', left)
        if gtpos < ltpos:
            left = gtpos + 1 if gtpos > -1 else left
        right = max(left, right)
        if right != left:
            gtpos = raw.find('>', right)
            ltpos = raw.find('<', right)
            if ltpos > gtpos:
                ltpos = raw.rfind('<', left, right + 1)
                right = max(ltpos, left)
        return left, right

    def format_text(self, formatting):
        if self.syntax != 'html':
            return
        color = 'currentColor'
        if formatting in {'color', 'background-color'}:
            color = QColorDialog.getColor(
                QColor(Qt.black if formatting == 'color' else Qt.white), self,
                _('Choose color'), QColorDialog.ShowAlphaChannel)
            if not color.isValid():
                return
            r, g, b, a = color.getRgb()
            if a == 255:
                color = 'rgb(%d, %d, %d)' % (r, g, b)
            else:
                color = 'rgba(%d, %d, %d, %.2g)' % (r, g, b, a / 255)
        prefix, suffix = {
            'bold': ('<b>', '</b>'),
            'italic': ('<i>', '</i>'),
            'underline': ('<u>', '</u>'),
            'strikethrough': ('<strike>', '</strike>'),
            'superscript': ('<sup>', '</sup>'),
            'subscript': ('<sub>', '</sub>'),
            'color': ('<span style="color: %s">' % color, '</span>'),
            'background-color':
            ('<span style="background-color: %s">' % color, '</span>'),
        }[formatting]
        left, right = self.get_range_inside_tag()
        c = self.textCursor()
        c.setPosition(left)
        c.setPosition(right, c.KeepAnchor)
        prev_text = unicode(c.selectedText()).rstrip('\0')
        c.insertText(prefix + prev_text + suffix)
        if prev_text:
            right = c.position()
            c.setPosition(left)
            c.setPosition(right, c.KeepAnchor)
        else:
            c.setPosition(c.position() - len(suffix))
        self.setTextCursor(c)

    def insert_image(self, href):
        c = self.textCursor()
        template, alt = 'url(%s)', ''
        left = min(c.position(), c.anchor)
        if self.syntax == 'html':
            left, right = self.get_range_inside_tag()
            c.setPosition(left)
            c.setPosition(right, c.KeepAnchor)
            alt = _('Image')
            template = '<img alt="{0}" src="%s" />'.format(alt)
            href = prepare_string_for_xml(href, True)
        text = template % href
        c.insertText(text)
        if self.syntax == 'html':
            c.setPosition(left + 10)
            c.setPosition(c.position() + len(alt), c.KeepAnchor)
        else:
            c.setPosition(left)
            c.setPosition(left + len(text), c.KeepAnchor)
        self.setTextCursor(c)

    def insert_hyperlink(self, target, text):
        if hasattr(self.smarts, 'insert_hyperlink'):
            self.smarts.insert_hyperlink(self, target, text)

    def insert_tag(self, tag):
        if hasattr(self.smarts, 'insert_tag'):
            self.smarts.insert_tag(self, tag)

    def keyPressEvent(self, ev):
        if ev.key() == Qt.Key_X and ev.modifiers() == Qt.AltModifier:
            if self.replace_possible_unicode_sequence():
                ev.accept()
                return
        QPlainTextEdit.keyPressEvent(self, ev)
        if (ev.key() == Qt.Key_Semicolon or ';' in unicode(ev.text(
        ))) and tprefs['replace_entities_as_typed'] and self.syntax == 'html':
            self.replace_possible_entity()

    def replace_possible_unicode_sequence(self):
        c = self.textCursor()
        has_selection = c.hasSelection()
        if has_selection:
            text = unicode(c.selectedText()).rstrip('\0')
        else:
            c.setPosition(c.position() - min(c.positionInBlock(), 6),
                          c.KeepAnchor)
            text = unicode(c.selectedText()).rstrip('\0')
        m = re.search(r'[a-fA-F0-9]{2,6}$', text)
        if m is None:
            return False
        text = m.group()
        try:
            num = int(text, 16)
        except ValueError:
            return False
        if num > 0x10ffff or num < 1:
            return False
        end_pos = max(c.anchor(), c.position())
        c.setPosition(end_pos - len(text)), c.setPosition(
            end_pos, c.KeepAnchor)
        c.insertText(safe_chr(num))
        return True

    def replace_possible_entity(self):
        c = self.textCursor()
        c.setPosition(c.position() - min(c.positionInBlock(), 10),
                      c.KeepAnchor)
        text = unicode(c.selectedText()).rstrip('\0')
        m = entity_pat.search(text)
        if m is None:
            return
        ent = m.group()
        repl = xml_entity_to_unicode(m)
        if repl != ent:
            c.setPosition(c.position() + m.start(), c.KeepAnchor)
            c.insertText(repl)

    def select_all(self):
        c = self.textCursor()
        c.clearSelection()
        c.setPosition(0)
        c.movePosition(c.End, c.KeepAnchor)
        self.setTextCursor(c)

    def rename_block_tag(self, new_name):
        if hasattr(self.smarts, 'rename_block_tag'):
            self.smarts.rename_block_tag(self, new_name)
Example #25
0
class MyBlockingBusy(QDialog):

    NORMAL = 0
    REQUESTED = 1
    ACKNOWLEDGED = 2

    def __init__(self,
                 gui,
                 msg,
                 size=100,
                 window_title='Marvin XD',
                 show_cancel=False,
                 on_top=False):
        flags = Qt.FramelessWindowHint
        if on_top:
            flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
        QDialog.__init__(self, gui, flags)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.cancel_status = 0
        self.is_running = False

        # Add the spinner
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(size)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)

        # Fiddle with the message
        self.msg = QLabel(msg)
        #self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 2)
        self.msg.setFont(self.font)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        sp = QSizePolicy()
        sp.setHorizontalStretch(True)
        sp.setVerticalStretch(False)
        sp.setHeightForWidth(False)
        self.msg.setSizePolicy(sp)
        self.msg.setMinimumHeight(self.font.pointSize() + 8)

        self._layout.addSpacing(15)

        if show_cancel:
            self.bb = QDialogButtonBox()
            self.cancel_button = QPushButton(QIcon(I('window-close.png')),
                                             'Cancel')
            self.bb.addButton(self.cancel_button, self.bb.RejectRole)
            self.bb.clicked.connect(self.button_handler)
            self._layout.addWidget(self.bb)

        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def button_handler(self, button):
        '''
        Only change cancel_status from NORMAL to REQUESTED
        '''
        if self.bb.buttonRole(button) == QDialogButtonBox.RejectRole:
            if self.cancel_status == self.NORMAL:
                self.cancel_status = self.REQUESTED
                self.cancel_button.setEnabled(False)

    def reject(self):
        '''
        Cannot cancel this dialog manually
        '''
        pass

    def set_text(self, text):
        self.msg.setText(text)

    def start(self):
        self.is_running = True
        self.pi.startAnimation()

    def stop(self):
        self.is_running = False
        self.pi.stopAnimation()
Example #26
0
 def do_size_hint(self, option, index):
     text = index.data(Qt.DisplayRole).toString()
     font = QFont(option.font)
     font.setPointSize(QFontInfo(font).pointSize() * 1.5)
     m = QFontMetrics(font)
     return QSize(m.width(text), m.height())
Example #27
0
    def __init__(self,
                 parent=None,
                 desc=None,
                 name=None,
                 modal=0,
                 fl=0,
                 env=None,
                 type="QDialog"):
        if env is None:
            import foundation.env as env  # this is a little weird... probably it'll be ok, and logically it seems correct.

        self.desc = desc

        self.typ = type
        if type == "QDialog":
            QDialog.__init__(self, parent, name, modal, fl)
        elif type == "QTextEdit":
            QTextEdit.__init__(self, parent, name)
        elif type == "QFrame":
            QFrame.__init__(self, parent, name)
        else:
            print "don't know about type == %r" % (type, )

        self.image1 = QPixmap()
        self.image1.loadFromData(image1_data,
                                 "PNG")  # should be: title_icon ####
        self.image3 = QPixmap()
        self.image3.loadFromData(image3_data, "PNG")
        self.image4 = QPixmap()
        self.image4.loadFromData(image4_data, "PNG")
        self.image5 = QPixmap()
        self.image5.loadFromData(image5_data, "PNG")
        self.image6 = QPixmap()
        self.image6.loadFromData(image6_data, "PNG")
        self.image7 = QPixmap()
        self.image7.loadFromData(image7_data, "PNG")
        self.image0 = QPixmap(image0_data)  # should be: border_icon ####
        self.image2 = QPixmap(image2_data)  # should be: sponsor_pixmap ####

        try:
            ####@@@@
            title_icon_name = self.desc.options.get('title_icon')
            border_icon_name = self.desc.options.get('border_icon')
            if title_icon_name:
                self.image1 = imagename_to_pixmap(
                    title_icon_name)  ###@@@ pass icon_path
                ###@@@ import imagename_to_pixmap or use env function
                # or let that func itself be an arg, or have an env arg for it
                ###e rename it icon_name_to_pixmap, or find_icon? (the latter only if it's ok if it returns an iconset)
                ###e use iconset instead?
            if border_icon_name:
                self.image0 = imagename_to_pixmap(border_icon_name)
        except:
            print_compact_traceback(
                "bug in icon-setting code, using fallback icons: ")
            pass

        if not name:
            self.setName("parameter_dialog_or_frame")  ###

        ###k guess this will need: if type == 'QDialog'
        self.setIcon(self.image0)  # should be: border_icon ####

        nanotube_dialogLayout = QVBoxLayout(self, 0, 0,
                                            "nanotube_dialogLayout")

        self.heading_frame = QFrame(self, "heading_frame")
        self.heading_frame.setPaletteBackgroundColor(QColor(122, 122, 122))
        self.heading_frame.setFrameShape(QFrame.NoFrame)
        self.heading_frame.setFrameShadow(QFrame.Plain)
        heading_frameLayout = QHBoxLayout(self.heading_frame, 0, 3,
                                          "heading_frameLayout")

        self.heading_pixmap = QLabel(self.heading_frame, "heading_pixmap")
        self.heading_pixmap.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed, 0, 0,
                        self.heading_pixmap.sizePolicy().hasHeightForWidth()))
        self.heading_pixmap.setPixmap(
            self.image1)  # should be: title_icon ####
        self.heading_pixmap.setScaledContents(1)
        heading_frameLayout.addWidget(self.heading_pixmap)

        self.heading_label = QLabel(self.heading_frame, "heading_label")
        self.heading_label.setPaletteForegroundColor(QColor(255, 255, 255))
        heading_label_font = QFont(self.heading_label.font())
        heading_label_font.setPointSize(12)
        heading_label_font.setBold(1)
        self.heading_label.setFont(heading_label_font)
        heading_frameLayout.addWidget(self.heading_label)
        nanotube_dialogLayout.addWidget(self.heading_frame)

        self.body_frame = QFrame(self, "body_frame")
        self.body_frame.setFrameShape(QFrame.StyledPanel)
        self.body_frame.setFrameShadow(QFrame.Raised)
        body_frameLayout = QVBoxLayout(self.body_frame, 3, 3,
                                       "body_frameLayout")

        self.sponsor_frame = QFrame(self.body_frame, "sponsor_frame")
        self.sponsor_frame.setPaletteBackgroundColor(QColor(255, 255, 255))
        self.sponsor_frame.setFrameShape(QFrame.StyledPanel)
        self.sponsor_frame.setFrameShadow(QFrame.Raised)
        sponsor_frameLayout = QHBoxLayout(self.sponsor_frame, 0, 0,
                                          "sponsor_frameLayout")

        self.sponsor_btn = QPushButton(self.sponsor_frame, "sponsor_btn")
        self.sponsor_btn.setAutoDefault(0)  #bruce 060703 bugfix
        self.sponsor_btn.setSizePolicy(
            QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred, 0, 0,
                        self.sponsor_btn.sizePolicy().hasHeightForWidth()))
        self.sponsor_btn.setPaletteBackgroundColor(QColor(255, 255, 255))
        self.sponsor_btn.setPixmap(
            self.image2
        )  # should be: sponsor_pixmap #### [also we'll need to support >1 sponsor]
        self.sponsor_btn.setFlat(1)
        sponsor_frameLayout.addWidget(self.sponsor_btn)
        body_frameLayout.addWidget(self.sponsor_frame)

        layout59 = QHBoxLayout(None, 0, 6, "layout59")
        left_spacer = QSpacerItem(20, 20, QSizePolicy.Expanding,
                                  QSizePolicy.Minimum)
        layout59.addItem(left_spacer)

        self.done_btn = QToolButton(self.body_frame, "done_btn")
        self.done_btn.setIcon(QIcon(self.image3))
        layout59.addWidget(self.done_btn)

        self.abort_btn = QToolButton(self.body_frame, "abort_btn")
        self.abort_btn.setIcon(QIcon(self.image4))
        layout59.addWidget(self.abort_btn)

        self.preview_btn = QToolButton(self.body_frame, "preview_btn")
        self.preview_btn.setIcon(QIcon(self.image5))
        layout59.addWidget(self.preview_btn)

        self.whatsthis_btn = QToolButton(self.body_frame, "whatsthis_btn")
        self.whatsthis_btn.setIcon(QIcon(self.image6))
        layout59.addWidget(self.whatsthis_btn)
        right_spacer = QSpacerItem(20, 20, QSizePolicy.Expanding,
                                   QSizePolicy.Minimum)
        layout59.addItem(right_spacer)
        body_frameLayout.addLayout(layout59)

        self.groups = []
        self.param_getters = {
        }  # map from param name to get-function (which gets current value out of its widget or controller)

        for group_desc in self.desc.kids('group'):

            # == start parameters_grpbox ### this will differ for Windows style

            header_refs = [
            ]  # keep python refcounted refs to all objects we make (at least the ones pyuic stored in self attrs)

            self.parameters_grpbox = QGroupBox(self.body_frame,
                                               "parameters_grpbox")
            self.parameters_grpbox.setFrameShape(QGroupBox.StyledPanel)
            self.parameters_grpbox.setFrameShadow(QGroupBox.Sunken)
            self.parameters_grpbox.setMargin(0)
            self.parameters_grpbox.setColumnLayout(0, Qt.Vertical)
            self.parameters_grpbox.layout().setSpacing(1)
            self.parameters_grpbox.layout().setMargin(4)
            parameters_grpboxLayout = QVBoxLayout(
                self.parameters_grpbox.layout())
            parameters_grpboxLayout.setAlignment(Qt.AlignTop)

            layout20 = QHBoxLayout(None, 0, 6, "layout20")

            self.nt_parameters_grpbtn = QPushButton(self.parameters_grpbox,
                                                    "nt_parameters_grpbtn")
            self.nt_parameters_grpbtn.setSizePolicy(
                QSizePolicy(
                    QSizePolicy.Minimum, QSizePolicy.Fixed, 0, 0,
                    self.nt_parameters_grpbtn.sizePolicy().hasHeightForWidth())
            )
            self.nt_parameters_grpbtn.setMaximumSize(QSize(16, 16))
            self.nt_parameters_grpbtn.setAutoDefault(0)
            self.nt_parameters_grpbtn.setIcon(QIcon(
                self.image7))  ### not always right, but doesn't matter
            self.nt_parameters_grpbtn.setFlat(1)
            layout20.addWidget(self.nt_parameters_grpbtn)

            self.parameters_grpbox_label = QLabel(self.parameters_grpbox,
                                                  "parameters_grpbox_label")
            self.parameters_grpbox_label.setSizePolicy(
                QSizePolicy(
                    QSizePolicy.Preferred, QSizePolicy.Minimum, 0, 0,
                    self.parameters_grpbox_label.sizePolicy().
                    hasHeightForWidth()))
            self.parameters_grpbox_label.setAlignment(QLabel.AlignVCenter)
            layout20.addWidget(self.parameters_grpbox_label)
            gbx_spacer1 = QSpacerItem(67, 16, QSizePolicy.Expanding,
                                      QSizePolicy.Minimum)
            layout20.addItem(gbx_spacer1)
            parameters_grpboxLayout.addLayout(layout20)

            nt_parameters_body_layout = QGridLayout(
                None, 1, 1, 0, 6, "nt_parameters_body_layout"
            )  ### what is 6 -- is it related to number of items???
            # is it 6 in all the ones we got, but that could be a designer error so i better look it up sometime.

            # == start its kids

            # will use from above: self.parameters_grpbox, nt_parameters_body_layout

            nextrow = 0  # which row of the QGridLayout to start filling next (loop variable)
            hidethese = [
            ]  # set of objects to hide or show, when this group is closed or opened

            for param in group_desc.kids('parameter'):
                # param (a group subobj desc) is always a parameter, but we already plan to extend this beyond that,
                # so we redundantly test for this here.
                getter = None
                paramname = None
                # set these for use by uniform code at the end (e.g. for tooltips)
                editfield = None
                label = None
                if param.isa('parameter'):
                    label = QLabel(self.parameters_grpbox, "members_label")
                    label.setAlignment(QLabel.AlignVCenter | QLabel.AlignRight)
                    nt_parameters_body_layout.addWidget(label, nextrow, 0)
                    hidethese.append(label)
                    thisrow = nextrow
                    nextrow += 1
                    #e following should be known in a place that knows the input language, not here
                    paramname = param.options.get('name') or (
                        param.args and param.args[0]) or "?"
                    paramlabel = param.options.get(
                        'label'
                    ) or paramname  ##e wrong, label "" or none ought to be possible
                    # QtGui.QApplication.translate(self.__class__.__name__, "xyz")
                    label.setText(
                        QtGui.QApplication.translate(self.__class__.__name__,
                                                     paramlabel))

                if param.isa('parameter',
                             widget='combobox',
                             type=('str', None)):
                    self.members_combox = QComboBox(
                        0, self.parameters_grpbox,
                        "members_combox")  ###k  what's 0?
                    editfield = self.members_combox
                    #### it probably needs a handler class, and then that could do this setup
                    self.members_combox.clear()
                    default = param.options.get(
                        'default', None)  # None is not equal to any string
                    thewidgetkid = param.kids(
                        'widget'
                    )[-1]  # kluge; need to think what the desc method for this should be
                    for item in thewidgetkid.kids('item'):
                        itemval = item.args[0]
                        itemtext = itemval
                        self.members_combox.insertItem(
                            QtGui.QApplication.translate(
                                self.__class__.__name__,
                                itemtext))  #k __tr ok??
                        if itemval == default:  #k or itemtext?
                            pass  ##k i find no setItem in our py code, so not sure yet what to do for this.
                    nt_parameters_body_layout.addWidget(
                        self.members_combox, thisrow, 1)
                    hidethese.append(self.members_combox)
                    getter = (lambda combobox=self.members_combox: str(
                        combobox.currentText()))
                    ##e due to __tr or non-str values, it might be better to use currentIndex and look it up in a table
                    # (though whether __tr is good here might depend on what it's used for)

                elif param.isa('parameter',
                               widget=('lineedit', None),
                               type=('str', None)):
                    # this covers explicit str|lineedit, and 3 default cases str, lineedit, neither.
                    # (i.e. if you say parameter and nothing else, it's str lineedit by default.)
                    self.length_linedit = QLineEdit(self.parameters_grpbox,
                                                    "length_linedit")
                    editfield = self.length_linedit
                    nt_parameters_body_layout.addWidget(
                        self.length_linedit, thisrow, 1)
                    hidethese.append(self.length_linedit)
                    default = str(param.options.get('default', ""))
                    self.length_linedit.setText(
                        QtGui.QApplication.translate(self.__class__.__name__,
                                                     default))  # __tr ok?
                    getter = (lambda lineedit=self.length_linedit: str(
                        lineedit.text()))

                elif param.isa('parameter',
                               widget=('lineedit', None),
                               type='float'):
                    self.length_linedit = QLineEdit(self.parameters_grpbox,
                                                    "length_linedit")
                    editfield = self.length_linedit
                    nt_parameters_body_layout.addWidget(
                        self.length_linedit, thisrow, 1)
                    hidethese.append(self.length_linedit)
                    controller = FloatLineeditController_Qt(
                        self, param, self.length_linedit)
                    header_refs.append(controller)
                    getter = controller.get_value

                elif param.isa('parameter', widget = ('spinbox', None), type = 'int') or \
                     param.isa('parameter', widget = ('spinbox'), type = None):
                    self.chirality_N_spinbox = QSpinBox(
                        self.parameters_grpbox, "chirality_N_spinbox"
                    )  # was chirality_m_spinbox, now chirality_N_spinbox
                    editfield = self.chirality_N_spinbox
                    ### seems like Qt defaults for min and max are 0,100 -- way too small a range!
                    if param.options.has_key('min') or 1:
                        self.chirality_N_spinbox.setMinimum(
                            param.options.get('min', -999999999))  # was 0
                    if param.options.has_key('max') or 1:
                        self.chirality_N_spinbox.setMaximum(
                            param.options.get(
                                'max',
                                +999999999))  # wasn't in egcode, but needed
                    self.chirality_N_spinbox.setValue(
                        param.options.get('default', 0))  # was 5
                    ##e note: i suspect this default 0 should come from something that knows this desc grammar.
                    suffix = param.options.get('suffix', '')
                    if suffix:
                        self.chirality_N_spinbox.setSuffix(
                            QtGui.QApplication.translate(
                                self.__class__.__name__, suffix))
                    else:
                        self.chirality_N_spinbox.setSuffix(
                            QString.null)  # probably not needed
                    nt_parameters_body_layout.addWidget(
                        self.chirality_N_spinbox, thisrow, 1)
                    hidethese.append(self.chirality_N_spinbox)
                    getter = self.chirality_N_spinbox.value  # note: it also has .text, which includes suffix

                else:
                    print "didn't match:", param  ###e improve this

                # things done the same way for all kinds of param-editing widgets
                if 1:  #bruce 060703 moved this down here, as bugfix
                    # set tooltip (same one for editfield and label)
                    tooltip = param.options.get('tooltip', '')
                    ###e do it for more kinds of params; share the code somehow; do it in controller, or setup-aid?
                    ###k QToolTip appropriateness; tooltip option might be entirely untested
                    if tooltip and label:
                        QToolTip.add(
                            label,
                            QtGui.QApplication.translate(
                                self.__class__.__name__, tooltip))
                    if tooltip and editfield:
                        QToolTip.add(
                            editfield,
                            QtGui.QApplication.translate(
                                self.__class__.__name__, tooltip)
                        )  ##k ok?? review once not all params have same-row labels.

                if getter and paramname and paramname != '?':
                    self.param_getters[paramname] = getter
                ### also bind these params to actions...
                continue  # next param

            header_refs.extend([
                self.parameters_grpbox, self.nt_parameters_grpbtn,
                self.parameters_grpbox_label
            ])

            # now create the logic/control object for the group
            group = CollapsibleGroupController_Qt(self, group_desc,
                                                  header_refs, hidethese,
                                                  self.nt_parameters_grpbtn)
            ### maybe ask env for the class to use for this?
            self.groups.append(
                group
            )  ### needed?? only for scanning the params, AFAIK -- oh, and to maintain a python refcount.

            # from languageChange:
            if 1:  # i don't know if these are needed:
                self.parameters_grpbox.setTitle(QString.null)
                self.nt_parameters_grpbtn.setText(QString.null)
            self.parameters_grpbox_label.setText(
                QtGui.QApplication.translate(
                    self.__class__.__name__,
                    group_desc.args[0]))  # was "Nanotube Parameters"
            ##e note that it's questionable in the syntax design for this property of a group (overall group label)
            # to be in that position (desc arg 0).

            # == end its kids

            parameters_grpboxLayout.addLayout(nt_parameters_body_layout)
            body_frameLayout.addWidget(self.parameters_grpbox)

            # == end parameters groupbox

            continue  # next group

        nanotube_dialogLayout.addWidget(self.body_frame)
        spacer14 = QSpacerItem(20, 20, QSizePolicy.Minimum,
                               QSizePolicy.Expanding)
        nanotube_dialogLayout.addItem(spacer14)

        layout42 = QHBoxLayout(None, 4, 6, "layout42")
        btm_spacer = QSpacerItem(59, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        layout42.addItem(btm_spacer)

        self.cancel_btn = QPushButton(self, "cancel_btn")
        self.cancel_btn.setAutoDefault(0)  #bruce 060703 bugfix
        layout42.addWidget(self.cancel_btn)

        self.ok_btn = QPushButton(self, "ok_btn")
        self.ok_btn.setAutoDefault(0)  #bruce 060703 bugfix
        layout42.addWidget(self.ok_btn)
        nanotube_dialogLayout.addLayout(layout42)

        self.languageChange()

        self.resize(
            QSize(246, 618).expandedTo(self.minimumSizeHint())
        )  ### this size will need to be adjusted (guess -- it's only place overall size is set)
        qt4todo('self.clearWState(Qt.WState_Polished)')

        ## self.connect(self.nt_parameters_grpbtn,SIGNAL("clicked()"),self.toggle_nt_parameters_grpbtn) ####

        # new:
        for button, methodname in (
            (self.sponsor_btn,
             'do_sponsor_btn'),  #e generalize to more than one sponsor button
            (self.done_btn, 'do_done_btn'),
            (self.abort_btn, 'do_abort_btn'),
            (self.preview_btn, 'do_preview_btn'),
            (self.whatsthis_btn, 'do_whatsthis_btn'),
            (self.cancel_btn, 'do_cancel_btn'),
            (self.ok_btn, 'do_ok_btn')):
            if hasattr(self, methodname):
                self.connect(button, SIGNAL("clicked()"),
                             getattr(self, methodname))
        return
class MyBlockingBusy(QDialog):

    NORMAL = 0
    REQUESTED = 1
    ACKNOWLEDGED = 2

    def __init__(self, gui, msg, size=100, window_title="Marvin XD", show_cancel=False, on_top=False):
        flags = Qt.FramelessWindowHint
        if on_top:
            flags = Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
        QDialog.__init__(self, gui, flags)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.cancel_status = 0
        self.is_running = False

        # Add the spinner
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(size)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)

        # Fiddle with the message
        self.msg = QLabel(msg)
        # self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 2)
        self.msg.setFont(self.font)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        sp = QSizePolicy()
        sp.setHorizontalStretch(True)
        sp.setVerticalStretch(False)
        sp.setHeightForWidth(False)
        self.msg.setSizePolicy(sp)
        self.msg.setMinimumHeight(self.font.pointSize() + 8)

        self._layout.addSpacing(15)

        if show_cancel:
            self.bb = QDialogButtonBox()
            self.cancel_button = QPushButton(QIcon(I("window-close.png")), "Cancel")
            self.bb.addButton(self.cancel_button, self.bb.RejectRole)
            self.bb.clicked.connect(self.button_handler)
            self._layout.addWidget(self.bb)

        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def button_handler(self, button):
        """
        Only change cancel_status from NORMAL to REQUESTED
        """
        if self.bb.buttonRole(button) == QDialogButtonBox.RejectRole:
            if self.cancel_status == self.NORMAL:
                self.cancel_status = self.REQUESTED
                self.cancel_button.setEnabled(False)

    def reject(self):
        """
        Cannot cancel this dialog manually
        """
        pass

    def set_text(self, text):
        self.msg.setText(text)

    def start(self):
        self.is_running = True
        self.pi.startAnimation()

    def stop(self):
        self.is_running = False
        self.pi.stopAnimation()
Example #29
0
class TextEdit(QPlainTextEdit):

    def __init__(self, parent=None):
        QPlainTextEdit.__init__(self, parent)
        self.current_cursor_line = None
        self.current_search_mark = None
        self.highlighter = SyntaxHighlighter(self)
        self.line_number_area = LineNumbers(self)
        self.apply_settings()
        self.setMouseTracking(True)
        self.cursorPositionChanged.connect(self.highlight_cursor_line)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.selectionChanged.connect(self.selection_changed)
        self.updateRequest.connect(self.update_line_number_area)
        self.syntax = None

    @dynamic_property
    def is_modified(self):
        ''' True if the document has been modified since it was loaded or since
        the last time is_modified was set to False. '''
        def fget(self):
            return self.document().isModified()
        def fset(self, val):
            self.document().setModified(bool(val))
        return property(fget=fget, fset=fset)

    @property
    def selected_text(self):
        return unicodedata.normalize('NFC', unicode(self.textCursor().selectedText()).replace(PARAGRAPH_SEPARATOR, '\n'))

    def sizeHint(self):
        return self.size_hint

    def apply_settings(self, prefs=None):  # {{{
        prefs = prefs or tprefs
        self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
        theme = THEMES.get(prefs['editor_theme'], None)
        if theme is None:
            theme = THEMES[default_theme()]
        self.apply_theme(theme)
        w = self.fontMetrics()
        self.space_width = w.width(' ')
        self.setTabStopWidth(prefs['editor_tab_stop_width'] * self.space_width)

    def apply_theme(self, theme):
        self.theme = theme
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.tooltip_palette = pal = QPalette()
        pal.setColor(pal.ToolTipBase, theme_color(theme, 'Tooltip', 'bg'))
        pal.setColor(pal.ToolTipText, theme_color(theme, 'Tooltip', 'fg'))
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.tooltip_font = QFont(font)
        self.tooltip_font.setPointSize(font.pointSize() - 1)
        self.setFont(font)
        self.highlighter.apply_theme(theme)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
        self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
        self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg')
        self.highlight_cursor_line()
    # }}}

    def toPlainText(self):
        # QPlainTextEdit's toPlainText implementation replaces nbsp with normal
        # space, so we re-implement it using QTextCursor, which does not do
        # that
        c = self.textCursor()
        c.clearSelection()
        c.movePosition(c.Start)
        c.movePosition(c.End, c.KeepAnchor)
        return c.selectedText().replace(PARAGRAPH_SEPARATOR, '\n')

    @pyqtSlot()
    def copy(self):
        # Workaround Qt replacing nbsp with normal spaces on copy
        c = self.textCursor()
        if not c.hasSelection():
            return
        md = QMimeData()
        md.setText(self.selected_text)
        QApplication.clipboard().setMimeData(md)

    @pyqtSlot()
    def cut(self):
        # Workaround Qt replacing nbsp with normal spaces on copy
        self.copy()
        self.textCursor().removeSelectedText()

    def selection_changed(self):
        # Workaround Qt replacing nbsp with normal spaces on copy
        clipboard = QApplication.clipboard()
        if clipboard.supportsSelection() and self.textCursor().hasSelection():
            md = QMimeData()
            md.setText(self.selected_text)
            clipboard.setMimeData(md, clipboard.Selection)

    def load_text(self, text, syntax='html', process_template=False):
        self.highlighter = {'html':HTMLHighlighter, 'css':CSSHighlighter, 'xml':XMLHighlighter}.get(syntax, SyntaxHighlighter)(self)
        self.highlighter.apply_theme(self.theme)
        self.highlighter.setDocument(self.document())
        self.setPlainText(unicodedata.normalize('NFC', text))
        if process_template and QPlainTextEdit.find(self, '%CURSOR%'):
            c = self.textCursor()
            c.insertText('')

    def replace_text(self, text):
        c = self.textCursor()
        pos = c.position()
        c.beginEditBlock()
        c.clearSelection()
        c.select(c.Document)
        c.insertText(unicodedata.normalize('NFC', text))
        c.endEditBlock()
        c.setPosition(min(pos, len(text)))
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def go_to_line(self, lnum, col=None):
        lnum = max(1, min(self.blockCount(), lnum))
        c = self.textCursor()
        c.clearSelection()
        c.movePosition(c.Start)
        c.movePosition(c.NextBlock, n=lnum - 1)
        c.movePosition(c.StartOfLine)
        c.movePosition(c.EndOfLine, c.KeepAnchor)
        text = unicode(c.selectedText())
        if col is None:
            c.movePosition(c.StartOfLine)
            lt = text.lstrip()
            if text and lt and lt != text:
                c.movePosition(c.NextWord)
        else:
            c.setPosition(c.block().position() + col)
            if c.blockNumber() + 1 > lnum:
                # We have moved past the end of the line
                c.setPosition(c.block().position())
                c.movePosition(c.EndOfBlock)
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def update_extra_selections(self):
        sel = []
        if self.current_cursor_line is not None:
            sel.append(self.current_cursor_line)
        if self.current_search_mark is not None:
            sel.append(self.current_search_mark)
        self.setExtraSelections(sel)

    # Search and replace {{{
    def mark_selected_text(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.highlight_color)
        sel.cursor = self.textCursor()
        if sel.cursor.hasSelection():
            self.current_search_mark = sel
            c = self.textCursor()
            c.clearSelection()
            self.setTextCursor(c)
        else:
            self.current_search_mark = None
        self.update_extra_selections()

    def find_in_marked(self, pat, wrap=False):
        if self.current_search_mark is None:
            return False
        csm = self.current_search_mark.cursor
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        m_start = min(csm.position(), csm.anchor())
        m_end = max(csm.position(), csm.anchor())
        if c.position() < m_start:
            c.setPosition(m_start)
        if c.position() > m_end:
            c.setPosition(m_end)
        pos = m_start if reverse else m_end
        if wrap:
            pos = m_end if reverse else m_start
        c.setPosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
            else:
                start, end = m_start + start, m_start + end
        else:
            if reverse:
                start, end = m_start + end, m_start + start
            else:
                start, end = c.anchor() + start, c.anchor() + end

        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        return True

    def all_in_marked(self, pat, template=None):
        if self.current_search_mark is None:
            return 0
        c = self.current_search_mark.cursor
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        if template is None:
            count = len(pat.findall(raw))
        else:
            raw, count = pat.subn(template, raw)
            if count > 0:
                c.setKeepPositionOnInsert(True)
                c.insertText(raw)
                c.setKeepPositionOnInsert(False)
                self.update_extra_selections()
        return count

    def find(self, pat, wrap=False, marked=False, complete=False):
        if marked:
            return self.find_in_marked(pat, wrap=wrap)
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        if complete:
            # Search the entire text
            c.movePosition(c.End if reverse else c.Start)
        pos = c.Start if reverse else c.End
        if wrap and not complete:
            pos = c.End if reverse else c.Start
        c.movePosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap and not complete:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
        else:
            if reverse:
                # Put the cursor at the start of the match
                start, end = end, start
            else:
                textpos = c.anchor()
                start, end = textpos + start, textpos + end
        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        return True

    def replace(self, pat, template):
        c = self.textCursor()
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        m = pat.fullmatch(raw)
        if m is None:
            return False
        text = m.expand(template)
        c.insertText(text)
        return True

    def go_to_anchor(self, anchor):
        if anchor is TOP:
            c = self.textCursor()
            c.movePosition(c.Start)
            self.setTextCursor(c)
            return True
        base = r'''%%s\s*=\s*['"]{0,1}%s''' % regex.escape(anchor)
        raw = unicode(self.toPlainText())
        m = regex.search(base % 'id', raw)
        if m is None:
            m = regex.search(base % 'name', raw)
        if m is not None:
            c = self.textCursor()
            c.setPosition(m.start())
            self.setTextCursor(c)
            return True
        return False

    # }}}

    # Line numbers and cursor line {{{
    def highlight_cursor_line(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.palette().alternateBase())
        sel.format.setProperty(QTextFormat.FullWidthSelection, True)
        sel.cursor = self.textCursor()
        sel.cursor.clearSelection()
        self.current_cursor_line = sel
        self.update_extra_selections()
        # Update the cursor line's line number in the line number area
        try:
            self.line_number_area.update(0, self.last_current_lnum[0], self.line_number_area.width(), self.last_current_lnum[1])
        except AttributeError:
            pass
        block = self.textCursor().block()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        height = int(self.blockBoundingRect(block).height())
        self.line_number_area.update(0, top, self.line_number_area.width(), height)

    def update_line_number_area_width(self, block_count=0):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def line_number_area_width(self):
        digits = 1
        limit = max(1, self.blockCount())
        while limit >= 10:
            limit /= 10
            digits += 1

        return 8 + self.number_width * digits

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width()

    def resizeEvent(self, ev):
        QPlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        current = self.textCursor().block().blockNumber()
        painter.setPen(self.line_number_palette.color(QPalette.Text))

        while block.isValid() and top <= ev.rect().bottom():
            if block.isVisible() and bottom >= ev.rect().top():
                if current == num:
                    painter.save()
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    self.last_current_lnum = (top, bottom - top)
                painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(),
                              Qt.AlignRight, str(num + 1))
                if current == num:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1
    # }}}

    def event(self, ev):
        if ev.type() == ev.ToolTip:
            self.show_tooltip(ev)
            return True
        if ev.type() == ev.ShortcutOverride and ev in (
            QKeySequence.Copy, QKeySequence.Cut, QKeySequence.Paste):
            # Let the global cut/copy/paste shortcuts work,this avoids the nbsp
            # problem as well, since they use the overridden copy() method
            # instead of the one from Qt
            ev.ignore()
            return False
        return QPlainTextEdit.event(self, ev)

    # Tooltips {{{
    def syntax_format_for_cursor(self, cursor):
        if cursor.isNull():
            return
        pos = cursor.positionInBlock()
        for r in cursor.block().layout().additionalFormats():
            if r.start <= pos < r.start + r.length and r.format.property(SYNTAX_PROPERTY).toBool():
                return r.format

    def show_tooltip(self, ev):
        c = self.cursorForPosition(ev.pos())
        fmt = self.syntax_format_for_cursor(c)
        if fmt is not None:
            tt = unicode(fmt.toolTip())
            if tt:
                QToolTip.setFont(self.tooltip_font)
                QToolTip.setPalette(self.tooltip_palette)
                QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
        QToolTip.hideText()
        ev.ignore()
Example #30
0
class MyBlockingBusy(QDialog): # {{{

    do_one_signal = pyqtSignal()

    phases = ['',
              _('Title/Author'),
              _('Standard metadata'),
              _('Custom metadata'),
              _('Search/Replace'),
    ]

    def __init__(self, msg, args, db, ids, cc_widgets, s_r_func,
                 parent=None, window_title=_('Working')):
        QDialog.__init__(self, parent)

        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self.msg_text = msg
        self.msg = QLabel(msg+'        ') # Ensure dialog is wide enough
        #self.msg.setWordWrap(True)
        self.font = QFont()
        self.font.setPointSize(self.font.pointSize() + 8)
        self.msg.setFont(self.font)
        self.pi = ProgressIndicator(self)
        self.pi.setDisplaySize(100)
        self._layout.addWidget(self.pi, 0, Qt.AlignHCenter)
        self._layout.addSpacing(15)
        self._layout.addWidget(self.msg, 0, Qt.AlignHCenter)
        self.setWindowTitle(window_title)
        self.resize(self.sizeHint())
        self.start()

        self.args = args
        self.series_start_value = None
        self.db = db
        self.ids = ids
        self.error = None
        self.cc_widgets = cc_widgets
        self.s_r_func = s_r_func
        self.do_one_signal.connect(self.do_one_safe, Qt.QueuedConnection)

    def start(self):
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()

    def accept(self):
        self.stop()
        return QDialog.accept(self)

    def exec_(self):
        self.current_index = 0
        self.current_phase = 1
        self.do_one_signal.emit()
        return QDialog.exec_(self)

    def do_one_safe(self):
        try:
            if self.current_index >= len(self.ids):
                self.current_phase += 1
                self.current_index = 0
                if self.current_phase > 4:
                    self.db.commit()
                    return self.accept()
            id = self.ids[self.current_index]
            percent = int((self.current_index*100)/float(len(self.ids)))
            self.msg.setText(self.msg_text.format(self.phases[self.current_phase],
                                        percent))
            self.do_one(id)
        except Exception as err:
            import traceback
            try:
                err = unicode(err)
            except:
                err = repr(err)
            self.error = (err, traceback.format_exc())
            return self.accept()

    def do_one(self, id):
        remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \
            do_autonumber, do_remove_format, remove_format, do_swap_ta, \
            do_remove_conv, do_auto_author, series, do_series_restart, \
            series_start_value, do_title_case, cover_action, clear_series, \
            pubdate, adddate, do_title_sort, languages, clear_languages, \
            restore_original = self.args


        # first loop: All changes that modify the filesystem and commit
        # immediately. We want to
        # try hard to keep the DB and the file system in sync, even in the face
        # of exceptions or forced exits.
        if self.current_phase == 1:
            title_set = False
            if do_swap_ta:
                title = self.db.title(id, index_is_id=True)
                aum = self.db.authors(id, index_is_id=True)
                if aum:
                    aum = [a.strip().replace('|', ',') for a in aum.split(',')]
                    new_title = authors_to_string(aum)
                    if do_title_case:
                        new_title = titlecase(new_title)
                    self.db.set_title(id, new_title, notify=False)
                    title_set = True
                if title:
                    new_authors = string_to_authors(title)
                    self.db.set_authors(id, new_authors, notify=False)
            if do_title_case and not title_set:
                title = self.db.title(id, index_is_id=True)
                self.db.set_title(id, titlecase(title), notify=False)
            if do_title_sort:
                title = self.db.title(id, index_is_id=True)
                if languages:
                    lang = languages[0]
                else:
                    lang = self.db.languages(id, index_is_id=True)
                    if lang:
                        lang = lang.partition(',')[0]
                self.db.set_title_sort(id, title_sort(title, lang=lang),
                        notify=False)
            if au:
                self.db.set_authors(id, string_to_authors(au), notify=False)
            if cover_action == 'remove':
                self.db.remove_cover(id)
            elif cover_action == 'generate':
                from calibre.ebooks import calibre_cover
                from calibre.ebooks.metadata import fmt_sidx
                from calibre.gui2 import config
                mi = self.db.get_metadata(id, index_is_id=True)
                series_string = None
                if mi.series:
                    series_string = _('Book %(sidx)s of %(series)s')%dict(
                        sidx=fmt_sidx(mi.series_index,
                        use_roman=config['use_roman_numerals_for_series_number']),
                        series=mi.series)

                cdata = calibre_cover(mi.title, mi.format_field('authors')[-1],
                        series_string=series_string)
                self.db.set_cover(id, cdata)
            elif cover_action == 'fromfmt':
                fmts = self.db.formats(id, index_is_id=True, verify_formats=False)
                if fmts:
                    covers = []
                    for fmt in fmts.split(','):
                        fmtf = self.db.format(id, fmt, index_is_id=True,
                                as_file=True)
                        if fmtf is None: continue
                        cdata, area = get_cover_data(fmtf, fmt)
                        if cdata:
                            covers.append((cdata, area))
                    covers.sort(key=lambda x: x[1])
                    if covers:
                        self.db.set_cover(id, covers[-1][0])
                    covers = []

            if do_remove_format:
                self.db.remove_format(id, remove_format, index_is_id=True,
                        notify=False, commit=True)

            if restore_original:
                formats = self.db.formats(id, index_is_id=True)
                formats = formats.split(',') if formats else []
                originals = [x.upper() for x in formats if
                        x.upper().startswith('ORIGINAL_')]
                for ofmt in originals:
                    fmt = ofmt.replace('ORIGINAL_', '')
                    with SpooledTemporaryFile(SPOOL_SIZE) as stream:
                        self.db.copy_format_to(id, ofmt, stream,
                                index_is_id=True)
                        stream.seek(0)
                        self.db.add_format(id, fmt, stream, index_is_id=True,
                                notify=False)
                    self.db.remove_format(id, ofmt, index_is_id=True,
                            notify=False, commit=True)

        elif self.current_phase == 2:
            # All of these just affect the DB, so we can tolerate a total rollback
            if do_auto_author:
                x = self.db.author_sort_from_book(id, index_is_id=True)
                if x:
                    self.db.set_author_sort(id, x, notify=False, commit=False)

            if aus and do_aus:
                self.db.set_author_sort(id, aus, notify=False, commit=False)

            if rating != -1:
                self.db.set_rating(id, 2*rating, notify=False, commit=False)

            if pub:
                self.db.set_publisher(id, pub, notify=False, commit=False)

            if clear_series:
                self.db.set_series(id, '', notify=False, commit=False)

            if pubdate is not None:
                self.db.set_pubdate(id, pubdate, notify=False, commit=False)

            if adddate is not None:
                self.db.set_timestamp(id, adddate, notify=False, commit=False)

            if do_series:
                if do_series_restart:
                    if self.series_start_value is None:
                        self.series_start_value = series_start_value
                    next = self.series_start_value
                    self.series_start_value += 1
                else:
                    next = self.db.get_next_series_num_for(series)
                self.db.set_series(id, series, notify=False, commit=False)
                if not series:
                    self.db.set_series_index(id, 1.0, notify=False, commit=False)
                elif do_autonumber: # is True if do_series_restart is True
                    self.db.set_series_index(id, next, notify=False, commit=False)
                elif tweaks['series_index_auto_increment'] != 'no_change':
                    self.db.set_series_index(id, 1.0, notify=False, commit=False)

            if do_remove_conv:
                self.db.delete_conversion_options(id, 'PIPE', commit=False)

            if clear_languages:
                self.db.set_languages(id, [], notify=False, commit=False)
            elif languages:
                self.db.set_languages(id, languages, notify=False, commit=False)

        elif self.current_phase == 3:
            # both of these are fast enough to just do them all
            for w in self.cc_widgets:
                w.commit(self.ids)
            if remove_all:
                self.db.remove_all_tags(self.ids)
            self.db.bulk_modify_tags(self.ids, add=add, remove=remove,
                                         notify=False)
            self.current_index = len(self.ids)
        elif self.current_phase == 4:
            self.s_r_func(id)
        # do the next one
        self.current_index += 1
        self.do_one_signal.emit()
Example #31
0
class TextEdit(QPlainTextEdit):

    def __init__(self, parent=None):
        QPlainTextEdit.__init__(self, parent)
        self.current_cursor_line = None
        self.current_search_mark = None
        self.highlighter = SyntaxHighlighter(self)
        self.line_number_area = LineNumbers(self)
        self.apply_settings()
        self.setMouseTracking(True)
        self.cursorPositionChanged.connect(self.highlight_cursor_line)
        self.blockCountChanged[int].connect(self.update_line_number_area_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.syntax = None

    @dynamic_property
    def is_modified(self):
        ''' True if the document has been modified since it was loaded or since
        the last time is_modified was set to False. '''
        def fget(self):
            return self.document().isModified()
        def fset(self, val):
            self.document().setModified(bool(val))
        return property(fget=fget, fset=fset)

    @property
    def selected_text(self):
        return unicodedata.normalize('NFC', unicode(self.textCursor().selectedText()))

    def sizeHint(self):
        return self.size_hint

    def apply_settings(self, prefs=None):  # {{{
        prefs = prefs or tprefs
        self.setLineWrapMode(QPlainTextEdit.WidgetWidth if prefs['editor_line_wrap'] else QPlainTextEdit.NoWrap)
        theme = THEMES.get(prefs['editor_theme'], None)
        if theme is None:
            theme = THEMES[default_theme()]
        self.apply_theme(theme)
        w = self.fontMetrics()
        self.space_width = w.width(' ')
        self.setTabStopWidth(prefs['editor_tab_stop_width'] * self.space_width)

    def apply_theme(self, theme):
        self.theme = theme
        pal = self.palette()
        pal.setColor(pal.Base, theme_color(theme, 'Normal', 'bg'))
        pal.setColor(pal.AlternateBase, theme_color(theme, 'CursorLine', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'Normal', 'fg'))
        pal.setColor(pal.Highlight, theme_color(theme, 'Visual', 'bg'))
        pal.setColor(pal.HighlightedText, theme_color(theme, 'Visual', 'fg'))
        self.setPalette(pal)
        self.tooltip_palette = pal = QPalette()
        pal.setColor(pal.ToolTipBase, theme_color(theme, 'Tooltip', 'bg'))
        pal.setColor(pal.ToolTipText, theme_color(theme, 'Tooltip', 'fg'))
        self.line_number_palette = pal = QPalette()
        pal.setColor(pal.Base, theme_color(theme, 'LineNr', 'bg'))
        pal.setColor(pal.Text, theme_color(theme, 'LineNr', 'fg'))
        pal.setColor(pal.BrightText, theme_color(theme, 'LineNrC', 'fg'))
        font = self.font()
        ff = tprefs['editor_font_family']
        if ff is None:
            ff = default_font_family()
        font.setFamily(ff)
        font.setPointSize(tprefs['editor_font_size'])
        self.tooltip_font = QFont(font)
        self.tooltip_font.setPointSize(font.pointSize() - 1)
        self.setFont(font)
        self.highlighter.apply_theme(theme)
        w = self.fontMetrics()
        self.number_width = max(map(lambda x:w.width(str(x)), xrange(10)))
        self.size_hint = QSize(100 * w.averageCharWidth(), 50 * w.height())
        self.highlight_color = theme_color(theme, 'HighlightRegion', 'bg')
        self.highlight_cursor_line()
    # }}}

    def load_text(self, text, syntax='html', process_template=False):
        self.highlighter = {'html':HTMLHighlighter, 'css':CSSHighlighter, 'xml':XMLHighlighter}.get(syntax, SyntaxHighlighter)(self)
        self.highlighter.apply_theme(self.theme)
        self.highlighter.setDocument(self.document())
        self.setPlainText(unicodedata.normalize('NFC', text))
        if process_template and QPlainTextEdit.find(self, '%CURSOR%'):
            c = self.textCursor()
            c.insertText('')

    def replace_text(self, text):
        c = self.textCursor()
        pos = c.position()
        c.beginEditBlock()
        c.clearSelection()
        c.select(c.Document)
        c.insertText(unicodedata.normalize('NFC', text))
        c.endEditBlock()
        c.setPosition(min(pos, len(text)))
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def go_to_line(self, lnum):
        lnum = max(1, min(self.blockCount(), lnum))
        c = self.textCursor()
        c.clearSelection()
        c.movePosition(c.Start)
        c.movePosition(c.NextBlock, n=lnum - 1)
        c.movePosition(c.StartOfLine)
        c.movePosition(c.EndOfLine, c.KeepAnchor)
        text = unicode(c.selectedText())
        c.movePosition(c.StartOfLine)
        lt = text.lstrip()
        if text and lt and lt != text:
            c.movePosition(c.NextWord)
        self.setTextCursor(c)
        self.ensureCursorVisible()

    def update_extra_selections(self):
        sel = []
        if self.current_cursor_line is not None:
            sel.append(self.current_cursor_line)
        if self.current_search_mark is not None:
            sel.append(self.current_search_mark)
        self.setExtraSelections(sel)

    # Search and replace {{{
    def mark_selected_text(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.highlight_color)
        sel.cursor = self.textCursor()
        if sel.cursor.hasSelection():
            self.current_search_mark = sel
            c = self.textCursor()
            c.clearSelection()
            self.setTextCursor(c)
        else:
            self.current_search_mark = None
        self.update_extra_selections()

    def find_in_marked(self, pat, wrap=False):
        if self.current_search_mark is None:
            return False
        csm = self.current_search_mark.cursor
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        m_start = min(csm.position(), csm.anchor())
        m_end = max(csm.position(), csm.anchor())
        if c.position() < m_start:
            c.setPosition(m_start)
        if c.position() > m_end:
            c.setPosition(m_end)
        pos = m_start if reverse else m_end
        if wrap:
            pos = m_end if reverse else m_start
        c.setPosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
            else:
                start, end = m_start + start, m_start + end
        else:
            if reverse:
                start, end = m_start + end, m_start + start
            else:
                start, end = c.anchor() + start, c.anchor() + end

        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        return True

    def all_in_marked(self, pat, template=None):
        if self.current_search_mark is None:
            return 0
        c = self.current_search_mark.cursor
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        if template is None:
            count = len(pat.findall(raw))
        else:
            raw, count = pat.subn(template, raw)
            if count > 0:
                c.setKeepPositionOnInsert(True)
                c.insertText(raw)
                c.setKeepPositionOnInsert(False)
                self.update_extra_selections()
        return count

    def find(self, pat, wrap=False, marked=False, complete=False):
        if marked:
            return self.find_in_marked(pat, wrap=wrap)
        reverse = pat.flags & regex.REVERSE
        c = self.textCursor()
        c.clearSelection()
        if complete:
            # Search the entire text
            c.movePosition(c.End if reverse else c.Start)
        pos = c.Start if reverse else c.End
        if wrap and not complete:
            pos = c.End if reverse else c.Start
        c.movePosition(pos, c.KeepAnchor)
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        m = pat.search(raw)
        if m is None:
            return False
        start, end = m.span()
        if start == end:
            return False
        if wrap and not complete:
            if reverse:
                textpos = c.anchor()
                start, end = textpos + end, textpos + start
        else:
            if reverse:
                # Put the cursor at the start of the match
                start, end = end, start
            else:
                textpos = c.anchor()
                start, end = textpos + start, textpos + end
        c.clearSelection()
        c.setPosition(start)
        c.setPosition(end, c.KeepAnchor)
        self.setTextCursor(c)
        return True

    def replace(self, pat, template):
        c = self.textCursor()
        raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n')
        m = pat.fullmatch(raw)
        if m is None:
            return False
        text = m.expand(template)
        c.insertText(text)
        return True
    # }}}

    # Line numbers and cursor line {{{
    def highlight_cursor_line(self):
        sel = QTextEdit.ExtraSelection()
        sel.format.setBackground(self.palette().alternateBase())
        sel.format.setProperty(QTextFormat.FullWidthSelection, True)
        sel.cursor = self.textCursor()
        sel.cursor.clearSelection()
        self.current_cursor_line = sel
        self.update_extra_selections()
        # Update the cursor line's line number in the line number area
        try:
            self.line_number_area.update(0, self.last_current_lnum[0], self.line_number_area.width(), self.last_current_lnum[1])
        except AttributeError:
            pass
        block = self.textCursor().block()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        height = int(self.blockBoundingRect(block).height())
        self.line_number_area.update(0, top, self.line_number_area.width(), height)

    def update_line_number_area_width(self, block_count=0):
        self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)

    def line_number_area_width(self):
        digits = 1
        limit = max(1, self.blockCount())
        while limit >= 10:
            limit /= 10
            digits += 1

        return 8 + self.number_width * digits

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_number_area.scroll(0, dy)
        else:
            self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height())
        if rect.contains(self.viewport().rect()):
            self.update_line_number_area_width()

    def resizeEvent(self, ev):
        QPlainTextEdit.resizeEvent(self, ev)
        cr = self.contentsRect()
        self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))

    def paint_line_numbers(self, ev):
        painter = QPainter(self.line_number_area)
        painter.fillRect(ev.rect(), self.line_number_palette.color(QPalette.Base))

        block = self.firstVisibleBlock()
        num = block.blockNumber()
        top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
        bottom = top + int(self.blockBoundingRect(block).height())
        current = self.textCursor().block().blockNumber()
        painter.setPen(self.line_number_palette.color(QPalette.Text))

        while block.isValid() and top <= ev.rect().bottom():
            if block.isVisible() and bottom >= ev.rect().top():
                if current == num:
                    painter.save()
                    painter.setPen(self.line_number_palette.color(QPalette.BrightText))
                    f = QFont(self.font())
                    f.setBold(True)
                    painter.setFont(f)
                    self.last_current_lnum = (top, bottom - top)
                painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(),
                              Qt.AlignRight, str(num + 1))
                if current == num:
                    painter.restore()
            block = block.next()
            top = bottom
            bottom = top + int(self.blockBoundingRect(block).height())
            num += 1
    # }}}

    # Tooltips {{{
    def event(self, ev):
        if ev.type() == ev.ToolTip:
            self.show_tooltip(ev)
            return True
        return QPlainTextEdit.event(self, ev)

    def syntax_format_for_cursor(self, cursor):
        if cursor.isNull():
            return
        pos = cursor.positionInBlock()
        for r in cursor.block().layout().additionalFormats():
            if r.start <= pos < r.start + r.length and r.format.property(SYNTAX_PROPERTY).toBool():
                return r.format

    def show_tooltip(self, ev):
        c = self.cursorForPosition(ev.pos())
        fmt = self.syntax_format_for_cursor(c)
        if fmt is not None:
            tt = unicode(fmt.toolTip())
            if tt:
                QToolTip.setFont(self.tooltip_font)
                QToolTip.setPalette(self.tooltip_palette)
                QToolTip.showText(ev.globalPos(), textwrap.fill(tt))
        QToolTip.hideText()
        ev.ignore()
Example #32
0
class MainView(QMainWindow):
    
    # Signals
    startPressed = pyqtSignal(bool)
    resetPressed = pyqtSignal()
    broadcastPressed = pyqtSignal(bool)
    
    def __init__(self):
        super(MainView, self).__init__()
        
        self.initUI()
        
        # Private variables
        self._running = False
        
        # ADE7753 instance
        self.meter = ade.ADE7753()
        
        # Connect signals and slots
        self.startPressed.connect(self.meter.startStopSlot)
        self.resetPressed.connect(self.meter.resetSlot)
        self.broadcastPressed.connect(self.meter.broadcastSlot)
        self.meter.varData.connect(self.updateData)
        self.meter.varPower.connect(self.updatePower)
        self.meter.varEnergy.connect(self.updateEnergy)
        
        
    # Slots
    
    # Update Vrms, Irms and Frequency
    
    @pyqtSlot(float, float, float, float)
    def updateData(self, vrms, irms, frequency, period):
        vrms = '{:.2f}'.format(vrms)
        irms = '{:.3f}'.format(irms)
        frequency = '{:.2f}'.format(frequency)
        
        self.voltageLabel.setText(vrms)
        self.currentLabel.setText(irms)
        self.frequencyLabel.setText(frequency)
        
    # Update energy
    
    @pyqtSlot(float, float, float)
    def updateEnergy(self, activeEnergy, apparentEnergy, reactiveEnergy):
        activeEnergy = '{:.3f}'.format(activeEnergy)
        apparentEnergy = '{:.3f}'.format(apparentEnergy)
        reactiveEnergy = '{:.3f}'.format(reactiveEnergy)
        self.activeEnergyLabel.setText(activeEnergy)
        self.apparentEnergyLabel.setText(apparentEnergy)
        self.reactiveEnergyLabel.setText(reactiveEnergy)
    
    # Update power
    
    @pyqtSlot(float, float, float)
    def updatePower(self, activePower, apparentPower, reactivePower):
        activePower = '{:.3f}'.format(activePower)
        apparentPower = '{:.3f}'.format(apparentPower)
        reactivePower = '{:.3f}'.format(reactivePower)
        self.activePowerLabel.setText(activePower)
        self.apparentPowerLabel.setText(apparentPower)
        self.reactivePowerLabel.setText(reactivePower)
    
    def initUI(self):
        # Set central widget
        centralWidget = QWidget()
        self.setCentralWidget(centralWidget)
        
        # Load LCD fonts
        lcdNumbersFontID = QFontDatabase().addApplicationFont("lcdmn.ttf")
        #fontNames = QFontDatabase().applicationFontFamilies(lcdNumbersFontID)
        
        # Change font size and use italic for strings
        #self.lcdNumbersFont = QFont(fontNames[0])
        self.lcdNumbersFont = QFont()
        self.lcdNumbersFont.setPointSize(40)
        #self.lcdStringFont = QFont(fontNames[0])
        self.lcdStringFont = QFont()
        self.lcdStringFont.setPointSize(35)
        self.lcdStringFont.setItalic(True)
        
       # self.lcdBoldFont = QFont(fontNames[0])
        self.lcdBoldFont = QFont()
        self.lcdBoldFont.setPointSize(50)
        self.lcdBoldFont.setBold(True)
        
        # Labels
        dataLabel = QLabel("DATA")
        dataLabel.setStyleSheet("""
           QLabel {color:red}
        """)
        energyLabel = QLabel("ENERGY")
        energyLabel.setStyleSheet("""
           QLabel {color:red}
        """)
        powerLabel = QLabel("POWER")
        powerLabel.setStyleSheet("""
           QLabel {color:red}
        """)
        dataLabel.setFont(self.lcdBoldFont)
        powerLabel.setFont(self.lcdBoldFont)
        energyLabel.setFont(self.lcdBoldFont)
        
        self.voltageLabel = QLabel("0.00")
        self.voltageLabel.setFont(self.lcdNumbersFont)
        self.voltageString = QLabel("V")
        self.voltageString.setFont(self.lcdStringFont)
        
        self.currentLabel = QLabel("0.00")
        self.currentLabel.setFont(self.lcdNumbersFont)
        self.currentString = QLabel("A")
        self.currentString.setFont(self.lcdStringFont)
        
        self.frequencyLabel = QLabel("0.00")
        self.frequencyLabel.setFont(self.lcdNumbersFont)
        self.frequencyString = QLabel("Hz")
        self.frequencyString.setFont(self.lcdStringFont)
        
        self.activeEnergyLabel = QLabel("0.00")
        self.activeEnergyLabel.setFont(self.lcdNumbersFont)
        self.activeEnergyString = QLabel("Wh")
        self.activeEnergyString.setFont(self.lcdStringFont)
        
        self.apparentEnergyLabel = QLabel("0.00")
        self.apparentEnergyLabel.setFont(self.lcdNumbersFont)
        self.apparentEnergyString = QLabel("VAh")
        self.apparentEnergyString.setFont(self.lcdStringFont)
        
        self.reactiveEnergyLabel = QLabel("0.00")
        self.reactiveEnergyLabel.setFont(self.lcdNumbersFont)
        self.reactiveEnergyString = QLabel("VARh")
        self.reactiveEnergyString.setFont(self.lcdStringFont)
        
        self.activePowerLabel = QLabel("0.00")
        self.activePowerLabel.setFont(self.lcdNumbersFont)
        self.activePowerString = QLabel("W")
        self.activePowerString.setFont(self.lcdStringFont)
        
        self.apparentPowerLabel = QLabel("0.00")
        self.apparentPowerLabel.setFont(self.lcdNumbersFont)
        self.apparentPowerString = QLabel("VA")
        self.apparentPowerString.setFont(self.lcdStringFont)
        
        self.reactivePowerLabel = QLabel("0.00")
        self.reactivePowerLabel.setFont(self.lcdNumbersFont)
        self.reactivePowerString = QLabel("VAR")
        self.reactivePowerString.setFont(self.lcdStringFont)
        
        # Horizontal lines
        hline1 = self.HLine()
        hline2 = self.HLine()
        hline3 = self.HLine()
        hline4 = self.HLine()
        # Vertical lines
        vline1 = self.VLine()
        vline2 = self.VLine()
        vline3 = self.VLine()
        
        # Central grid layout for central widget
        self.centralGridLayout = QGridLayout(self.centralWidget())
        self.centralGridLayout.setHorizontalSpacing(20)
        self.centralGridLayout.setVerticalSpacing(5)
        
        # Add labels
        self.centralGridLayout.addWidget(dataLabel,0,0,1,2, QtCore.Qt.AlignCenter)
        self.centralGridLayout.addWidget(energyLabel,0,3,1,2, QtCore.Qt.AlignCenter)
        self.centralGridLayout.addWidget(powerLabel,0,6,1,2, QtCore.Qt.AlignCenter)
        
        self.centralGridLayout.addWidget(hline1, 1, 0, 1, -1)
        
        self.centralGridLayout.addWidget(self.voltageLabel, 2, 0, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.voltageString, 2, 1, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(hline2, 3, 0, 1, -1)
        
        self.centralGridLayout.addWidget(self.currentLabel, 4, 0, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.currentString, 4, 1, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(hline3, 5, 0, 1, -1)
        
        self.centralGridLayout.addWidget(self.frequencyLabel, 6, 0, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.frequencyString, 6, 1, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(hline4, 7, 0, 1, -1)
        
        self.centralGridLayout.addWidget(vline1, 0, 2, -1, 1)
        
        self.centralGridLayout.addWidget(self.activeEnergyLabel, 2, 3, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.activeEnergyString, 2, 4, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(self.apparentEnergyLabel, 4, 3, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.apparentEnergyString, 4, 4, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(self.reactiveEnergyLabel, 6, 3, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.reactiveEnergyString, 6, 4, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(vline2, 0, 5, -1, 1)
        
        self.centralGridLayout.addWidget(self.activePowerLabel, 2, 6, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.activePowerString, 2, 7, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(self.apparentPowerLabel, 4, 6, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.apparentPowerString, 4, 7, 1, 1, QtCore.Qt.AlignLeft)
        
        self.centralGridLayout.addWidget(self.reactivePowerLabel, 6, 6, 1, 1, QtCore.Qt.AlignLeft)
        self.centralGridLayout.addWidget(self.reactivePowerString, 6, 7, 1, 1, QtCore.Qt.AlignLeft)
        
        # self.centralGridLayout.addWidget(vline3, 0, 8, -1, 1)
        
        # Buttons
        self.startStopButton = QPushButton("START")
        self.startStopButton.setFont(self.lcdStringFont)
        buttonPolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.startStopButton.setSizePolicy(buttonPolicy)
        self.startStopButton.clicked.connect(self.startStopClicked)
        
        self.resetButton = QPushButton("RESET")
        self.resetButton.setFont(self.lcdStringFont)
        self.resetButton.setSizePolicy(buttonPolicy)
        self.resetButton.clicked.connect(self.resetButtonClicked)
        
        self.broadcastButton = QPushButton("BROADCAST")
        self.broadcastButton.setFont(self.lcdStringFont)
        self.broadcastButton.setSizePolicy(buttonPolicy)
        self.broadcastButton.setCheckable(True)
        self.broadcastButton.toggled.connect(self.broadcastButtonClicked)
        
        self.centralGridLayout.addWidget(self.startStopButton, 8, 0, 1, 2)
        self.centralGridLayout.addWidget(self.resetButton, 8, 3, 1, 2)
        self.centralGridLayout.addWidget(self.broadcastButton, 8, 6, 1, 2)
        
        # Status bar and show window
        self.statusBar().showMessage('Ready')
        self.setWindowTitle("ADE7753 Power Meter")
        self.show()
    
# Button slots    

    def startStopClicked(self):
        if(self._running):
            self._running = False
            self.startStopButton.setText("START")
            self.startPressed.emit(False)
        else:
            self._running = True
            self.startStopButton.setText("STOP")
            self.startPressed.emit(True)
            
    def resetButtonClicked(self):
        self.resetPressed.emit()
        
    def broadcastButtonClicked(self, s):
        self.broadcastPressed.emit(s) 
    
    # Helper functions
    
    def HLine(self):
        line = QFrame()
        line.setFrameStyle(QFrame.HLine)
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)
        return line
    
    def VLine(self):
        line = QFrame()
        line.setFrameStyle(QFrame.VLine)
        line.setFrameShape(QFrame.VLine)
        line.setFrameShadow(QFrame.Sunken)
        return line