Ejemplo n.º 1
0
 def test_overlap(self):
     test_str = 'This is something, string, string.\nparagraph, paragraph!\n    method?'
     test_fmt = [(0, 2, 1), (0, 2, 2), (0, 10, 3), (5, 15, 4), (34, 3, 5)]
     # output formats may be duplicated, because Qt store format this way
     true_result = [
         (0, 2, 1), (0, 2, 2),
         (0, 2, 3), (2, 3, 3), (5, 5, 3),  # from (0, 10, 3), broken into three parts
         (5, 5, 4), (10, 10, 4),  # from (5, 15, 4)
         (35, 2, 5)  # 34 is \n
     ]
     doc = NTextDocument()
     doc.setText(test_str, test_fmt)
     result = NTextDocument.getFormats(doc)
     self.assertEqual(true_result, result)
Ejemplo n.º 2
0
 def test_overlap(self):
     test_str = 'This is something, string, string.\nparagraph, paragraph!\n    method?'
     test_fmt = [(0, 2, 1), (0, 2, 2), (0, 10, 3), (5, 15, 4), (34, 3, 5)]
     # output formats may be duplicated, because Qt store format this way
     true_result = [
         (0, 2, 1),
         (0, 2, 2),
         (0, 2, 3),
         (2, 3, 3),
         (5, 5, 3),  # from (0, 10, 3), broken into three parts
         (5, 5, 4),
         (10, 10, 4),  # from (5, 15, 4)
         (35, 2, 5)  # 34 is \n
     ]
     doc = NTextDocument()
     doc.setText(test_str, test_fmt)
     result = NTextDocument.getFormats(doc)
     self.assertEqual(true_result, result)
Ejemplo n.º 3
0
class NDocumentLabel(QFrame):
    """Simple widget to draw QTextDocument. sizeHint will always related
    to fixed number of lines set. If font fallback happen, it may look bad."""

    def __init__(self, parent=None, lines=None, **kwargs):
        super().__init__(parent, **kwargs)
        self._lines = self._heightHint = None
        self.doc = NTextDocument(self)
        self.doc.setDocumentMargin(0)
        self.doc.setUndoRedoEnabled(False)
        self.setLines(lines if lines else 4)
        self.doc.documentLayout().setPaintDevice(self)  # make difference on high DPI

    def setFont(self, f):
        self.doc.setDefaultFont(f)
        super().setFont(f)
        self.setLines(self._lines)  # refresh size hint

    def setText(self, text, formats):
        self.doc.setText(text, formats)
        # delete exceed lines here using QTextCursor will slow down

    def setLines(self, lines):
        self._lines = lines
        self.doc.setText('\n' * (lines - 1), None)
        self._heightHint = int(self.doc.size().height())
        self.updateGeometry()

    def paintEvent(self, event):
        painter = QPainter(self)
        rect = self.contentsRect()
        painter.translate(rect.topLeft())
        rect.moveTo(0, 0)  # become clip rect
        self.doc.drawContentsPalette(painter, rect, self.palette())

    def resizeEvent(self, event):
        self.doc.setTextWidth(self.contentsRect().width())
        super().resizeEvent(event)

    def sizeHint(self):
        __, top, __, bottom = self.getContentsMargins()
        return QSize(-1, self._heightHint + top + bottom)
Ejemplo n.º 4
0
class NTextEdit(QTextEdit, TextFormatter):
    """The widget used to edit diary contents in Editor window."""
    # spaces that auto-indent can recognize
    SPACE_KINDS = (' ', '\u3000')  # full width space U+3000

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._doc = NTextDocument(self)
        self.setDocument(self._doc)
        # remove highlight color's alpha to avoid alpha loss in copy&paste.
        # NTextDocument should use this color too.
        hl, bg = self.HlColor, self.palette().base().color()
        fac = hl.alpha() / 255
        self.HlColor = QColor(round(hl.red()*fac + bg.red()*(1-fac)),
                              round(hl.green()*fac + bg.green()*(1-fac)),
                              round(hl.blue()*fac + bg.blue()*(1-fac)))

        self.autoIndent = False
        # used by tab indent shortcut
        if QLocale().language() in (QLocale.Chinese, QLocale.Japanese):
            self._indent = '  '   # 2 full width spaces
        else:
            self._indent = '    '  # 4 spaces
        # setup format menu
        onHLAct = lambda: super(NTextEdit, self).setHL(self.hlAct.isChecked())
        onBDAct = lambda: super(NTextEdit, self).setBD(self.bdAct.isChecked())
        onSOAct = lambda: super(NTextEdit, self).setSO(self.soAct.isChecked())
        onULAct = lambda: super(NTextEdit, self).setUL(self.ulAct.isChecked())
        onItaAct = lambda: super(NTextEdit, self).setIta(self.itaAct.isChecked())

        self.fmtMenu = QMenu(self.tr('Format'), self)
        # shortcuts of format actions only used to display shortcut-hint in menu
        self.hlAct = QAction(makeQIcon(':/menu/highlight.png'), self.tr('Highlight'),
                             self, triggered=onHLAct,
                             shortcut=QKeySequence('Ctrl+H'))
        self.bdAct = QAction(makeQIcon(':/menu/bold.png'), self.tr('Bold'),
                             self, triggered=onBDAct,
                             shortcut=QKeySequence.Bold)
        self.soAct = QAction(makeQIcon(':/menu/strikeout.png'), self.tr('Strike out'),
                             self, triggered=onSOAct,
                             shortcut=QKeySequence('Ctrl+T'))
        self.ulAct = QAction(makeQIcon(':/menu/underline.png'), self.tr('Underline'),
                             self, triggered=onULAct,
                             shortcut=QKeySequence.Underline)
        self.itaAct = QAction(makeQIcon(':/menu/italic.png'), self.tr('Italic'),
                              self, triggered=onItaAct,
                              shortcut=QKeySequence.Italic)
        self.clrAct = QAction(self.tr('Clear format'), self,
                              shortcut=QKeySequence('Ctrl+D'),
                              triggered=self.clearFormat)
        self.acts = (self.hlAct, self.bdAct, self.soAct, self.ulAct,
                     self.itaAct)  # excluding uncheckable clrAct
        for a in self.acts:
            self.fmtMenu.addAction(a)
            a.setCheckable(True)
        self.fmtMenu.addSeparator()
        self.addAction(self.clrAct)
        self.fmtMenu.addAction(self.clrAct)
        self.key2act = {
            Qt.Key_H: self.hlAct, Qt.Key_B: self.bdAct, Qt.Key_T: self.soAct,
            Qt.Key_U: self.ulAct, Qt.Key_I: self.itaAct}

    def contextMenuEvent(self, event):
        menu = self.createStandardContextMenu()
        setStdEditMenuIcons(menu)

        if not self.isReadOnly():
            if self.textCursor().hasSelection():
                self._setFmtActs()
                self.fmtMenu.setEnabled(True)
            else:
                self.fmtMenu.setEnabled(False)
            before = menu.actions()[2]
            menu.insertSeparator(before)
            menu.insertMenu(before, self.fmtMenu)

        menu.exec_(event.globalPos())
        menu.deleteLater()

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

        if event.modifiers() == Qt.ControlModifier and event.key() in self.key2act:
            # set actions before calling format methods
            self._setFmtActs()
            self.key2act[event.key()].trigger()
        elif event.key() == Qt.Key_Tab:
            # will not receive event if tabChangesFocus is True
            self.textCursor().insertText(self._indent)
        elif event.key() == Qt.Key_Return and self.autoIndent:
            # auto-indent support
            para = self.textCursor().block().text()
            if len(para) > 0 and para[0] in NTextEdit.SPACE_KINDS:
                space, spaceCount = para[0], 1
                for c in para[1:]:
                    if c != space: break
                    spaceCount += 1
                super().keyPressEvent(event)
                self.textCursor().insertText(space * spaceCount)
            else:
                super().keyPressEvent(event)
        else:
            super().keyPressEvent(event)

    def insertFromMimeData(self, source):
        """Disable some unsupported types"""
        self.insertHtml(source.html() or source.text())

    def setRichText(self, text, formats):
        self._doc.setHlColor(self.HlColor)
        self._doc.setText(text, formats)

    def setAutoIndent(self, enabled):
        assert isinstance(enabled, (bool, int))
        self.autoIndent = enabled

    def getRichText(self):
        # self.document() will return QTextDocument, not NTextDocument
        return self.toPlainText(), self._doc.getFormats()

    def _setFmtActs(self):
        """Check formats in current selection and check or uncheck actions"""
        fmts = [QTextFormat.BackgroundBrush, QTextFormat.FontWeight,
                QTextFormat.FontStrikeOut,
                QTextFormat.TextUnderlineStyle, QTextFormat.FontItalic]

        cur = self.textCursor()
        start, end = cur.anchor(), cur.position()
        if start > end:
            start, end = end, start
        results = [True] * 5
        for pos in range(end, start, -1):
            cur.setPosition(pos)
            charFmt = cur.charFormat()
            for i, f in enumerate(fmts):
                if results[i] and not charFmt.hasProperty(f):
                    results[i] = False
            if not any(results): break
        for i, c in enumerate(results):
            self.acts[i].setChecked(c)

    def clearFormat(self):
        fmt = QTextCharFormat()
        self.textCursor().setCharFormat(fmt)
Ejemplo n.º 5
0
class DiaryListDelegate(QStyledItemDelegate):
    """ItemDelegate of old theme 'one-pixel-rect' for DiaryList, Using 'traditional'
    painting method compared to colorful theme."""
    def __init__(self):
        super().__init__()  # don't pass parent because of mem problem
        # Because some fonts have much more space at top and bottom, we use ascent instead
        # of height, and add it with a small number.
        magic = int(4 * scaleRatio)
        self.title_h = max(font.title_m.ascent(), font.datetime_m.ascent()) + magic
        self.titleArea_h = self.title_h + 4
        self.text_h = font.text_m.lineSpacing() * settings['Main'].getint('previewLines')
        self.tagPath_h = font.default_m.ascent() + magic
        self.tag_h = self.tagPath_h + 4
        self.dt_w = font.datetime_m.width(datetimeTrans('2000-01-01 00:00')) + 40
        self.all_h = None  # updated in sizeHint before each item being painting
        # doc is used to draw text(diary's body)
        self.doc = NTextDocument()
        self.doc.setDefaultFont(font.text)
        self.doc.setUndoRedoEnabled(False)
        self.doc.setDocumentMargin(0)
        self.doc.documentLayout().setPaintDevice(qApp.desktop())  # refer actual list will cause segfault
        # setup colors
        self.c_text = Qt.black
        self.c_bg = QColor(255, 236, 176)
        self.c_border = QColor(214, 172, 41)
        self.c_inActBg = QColor(255, 236, 176, 40)
        self.c_gray = QColor(93, 73, 57)

    def paint(self, painter, option, index):
        x, y, w = option.rect.x(), option.rect.y(), option.rect.width()-1
        row = index.row()
        dt, text, title, tags, formats = (index.sibling(row, i).data()
                                          for i in range(1, 6))
        selected = bool(option.state & QStyle.State_Selected)
        active = bool(option.state & QStyle.State_Active)
        # draw border and background
        painter.setPen(self.c_border)
        painter.setBrush(self.c_bg if selected and active else
                         self.c_inActBg)
        painter.drawRect(x+1, y, w-2, self.all_h)  # outer border
        if selected:  # draw inner border
            pen = QPen()
            pen.setStyle(Qt.DashLine)
            pen.setColor(self.c_gray)
            painter.setPen(pen)
            painter.drawRect(x+2, y+1, w-4, self.all_h-2)
        # draw datetime and title
        painter.setPen(self.c_gray)
        painter.drawLine(x+10, y+self.titleArea_h, x+w-10, y+self.titleArea_h)
        painter.setPen(self.c_text)
        painter.setFont(font.datetime)
        painter.drawText(x+14, y+self.titleArea_h-self.title_h, self.dt_w, self.title_h,
                         Qt.AlignVCenter, datetimeTrans(dt))
        if title:
            painter.setFont(font.title)
            title_w = w - self.dt_w - 13
            title = font.title_m.elidedText(title, Qt.ElideRight, title_w)
            painter.drawText(x+self.dt_w, y+self.titleArea_h-self.title_h, title_w, self.title_h,
                             Qt.AlignVCenter | Qt.AlignRight, title)
        # draw text
        self.doc.setText(text, formats)
        self.doc.setTextWidth(w-26)
        painter.translate(x+14, y+self.titleArea_h+2)
        self.doc.drawContentsColor(painter, QRect(0, 0, w-26, self.text_h), self.c_text)
        painter.resetTransform()
        # draw tags
        if tags:
            painter.setPen(self.c_gray)
            painter.setFont(font.default)
            painter.translate(x + 15, y+self.titleArea_h+6+self.text_h)
            real_x, max_x = x+15, w-10
            for t in tags.split():
                oneTag_w = font.default_m.width(t) + 4
                real_x += oneTag_w + 15
                if real_x > max_x: break
                tagPath = QPainterPath()
                tagPath.moveTo(8, 0)
                tagPath.lineTo(8+oneTag_w, 0)
                tagPath.lineTo(8+oneTag_w, self.tagPath_h)
                tagPath.lineTo(8, self.tagPath_h)
                tagPath.lineTo(0, self.tagPath_h/2)
                tagPath.closeSubpath()
                painter.drawPath(tagPath)
                painter.drawText(8, 0, oneTag_w, self.tagPath_h, Qt.AlignCenter, t)
                painter.translate(oneTag_w+15, 0)  # translate by offset
            else:
                painter.resetTransform()
                return
            # draw ellipsis if too many tags
            painter.setPen(Qt.DotLine)
            painter.drawLine(-4, self.tagPath_h/2, 2, self.tagPath_h/2)
            painter.resetTransform()

    def sizeHint(self, option, index):
        tag_h = self.tag_h if index.sibling(index.row(), 4).data() else 0
        self.all_h = self.titleArea_h + 2 + self.text_h + tag_h + 6
        return QSize(-1, self.all_h+3)  # 3 is spacing between entries
Ejemplo n.º 6
0
class DiaryListDelegate(QStyledItemDelegate):
    """ItemDelegate of old theme 'one-pixel-rect' for DiaryList, Using 'traditional'
    painting method compared to colorful theme."""
    def __init__(self):
        super().__init__()  # don't pass parent because of mem problem
        # Because some fonts have much more space at top and bottom, we use ascent instead
        # of height, and add it with a small number.
        magic = int(4 * scaleRatio)
        self.title_h = max(font.title_m.ascent(),
                           font.datetime_m.ascent()) + magic
        self.titleArea_h = self.title_h + 4
        self.text_h = font.text_m.lineSpacing() * settings['Main'].getint(
            'previewLines')
        self.tagPath_h = font.default_m.ascent() + magic
        self.tag_h = self.tagPath_h + 4
        self.dt_w = font.datetime_m.width(
            datetimeTrans('2000-01-01 00:00')) + 40
        self.all_h = None  # updated in sizeHint before each item being painting
        # doc is used to draw text(diary's body)
        self.doc = NTextDocument()
        self.doc.setDefaultFont(font.text)
        self.doc.setUndoRedoEnabled(False)
        self.doc.setDocumentMargin(0)
        self.doc.documentLayout().setPaintDevice(
            qApp.desktop())  # refer actual list will cause segfault
        # setup colors
        self.c_text = Qt.black
        self.c_bg = QColor(255, 236, 176)
        self.c_border = QColor(214, 172, 41)
        self.c_inActBg = QColor(255, 236, 176, 40)
        self.c_gray = QColor(93, 73, 57)

    def paint(self, painter, option, index):
        x, y, w = option.rect.x(), option.rect.y(), option.rect.width() - 1
        row = index.row()
        dt, text, title, tags, formats = (index.sibling(row, i).data()
                                          for i in range(1, 6))
        selected = bool(option.state & QStyle.State_Selected)
        active = bool(option.state & QStyle.State_Active)
        # draw border and background
        painter.setPen(self.c_border)
        painter.setBrush(self.c_bg if selected and active else self.c_inActBg)
        painter.drawRect(x + 1, y, w - 2, self.all_h)  # outer border
        if selected:  # draw inner border
            pen = QPen()
            pen.setStyle(Qt.DashLine)
            pen.setColor(self.c_gray)
            painter.setPen(pen)
            painter.drawRect(x + 2, y + 1, w - 4, self.all_h - 2)
        # draw datetime and title
        painter.setPen(self.c_gray)
        painter.drawLine(x + 10, y + self.titleArea_h, x + w - 10,
                         y + self.titleArea_h)
        painter.setPen(self.c_text)
        painter.setFont(font.datetime)
        painter.drawText(x + 14, y + self.titleArea_h - self.title_h,
                         self.dt_w, self.title_h, Qt.AlignVCenter,
                         datetimeTrans(dt))
        if title:
            painter.setFont(font.title)
            title_w = w - self.dt_w - 13
            title = font.title_m.elidedText(title, Qt.ElideRight, title_w)
            painter.drawText(x + self.dt_w,
                             y + self.titleArea_h - self.title_h, title_w,
                             self.title_h, Qt.AlignVCenter | Qt.AlignRight,
                             title)
        # draw text
        self.doc.setText(text, formats)
        self.doc.setTextWidth(w - 26)
        painter.translate(x + 14, y + self.titleArea_h + 2)
        self.doc.drawContentsColor(painter, QRect(0, 0, w - 26, self.text_h),
                                   self.c_text)
        painter.resetTransform()
        # draw tags
        if tags:
            painter.setPen(self.c_gray)
            painter.setFont(font.default)
            painter.translate(x + 15, y + self.titleArea_h + 6 + self.text_h)
            real_x, max_x = x + 15, w - 10
            for t in tags.split():
                oneTag_w = font.default_m.width(t) + 4
                real_x += oneTag_w + 15
                if real_x > max_x: break
                tagPath = QPainterPath()
                tagPath.moveTo(8, 0)
                tagPath.lineTo(8 + oneTag_w, 0)
                tagPath.lineTo(8 + oneTag_w, self.tagPath_h)
                tagPath.lineTo(8, self.tagPath_h)
                tagPath.lineTo(0, self.tagPath_h / 2)
                tagPath.closeSubpath()
                painter.drawPath(tagPath)
                painter.drawText(8, 0, oneTag_w, self.tagPath_h,
                                 Qt.AlignCenter, t)
                painter.translate(oneTag_w + 15, 0)  # translate by offset
            else:
                painter.resetTransform()
                return
            # draw ellipsis if too many tags
            painter.setPen(Qt.DotLine)
            painter.drawLine(-4, self.tagPath_h / 2, 2, self.tagPath_h / 2)
            painter.resetTransform()

    def sizeHint(self, option, index):
        tag_h = self.tag_h if index.sibling(index.row(), 4).data() else 0
        self.all_h = self.titleArea_h + 2 + self.text_h + tag_h + 6
        return QSize(-1, self.all_h + 3)  # 3 is spacing between entries