diff --git a/src/jamichatview/bubble.cpp b/src/jamichatview/bubble.cpp index 1e66c0c1..9777ed0c 100644 --- a/src/jamichatview/bubble.cpp +++ b/src/jamichatview/bubble.cpp @@ -1,193 +1,221 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #include "bubble.h" #include #include #include #include #include #include class BubblePrivate final { public: int m_Align {Qt::AlignmentFlag::AlignLeft}; QColor m_Color; QString m_Text; QFont m_Font {QStringLiteral("Noto Color Emoji")}; + QFont m_DateFont; QFontMetricsF m_FontMetrics{m_Font}; + QFontMetricsF m_DateFontMetrics{m_DateFont}; qreal m_MaximumWidth {-1}; + qreal m_SideMargins {30}; }; Bubble::Bubble(QQuickItem* parent) : QQuickPaintedItem(parent), d_ptr(new BubblePrivate) { d_ptr->m_Color = QGuiApplication::palette().base().color(); connect(this, &Bubble::windowChanged, this, &Bubble::slotWindowChanged); } Bubble::~Bubble() { delete d_ptr; } void Bubble::paint(QPainter *painter) { painter->setWorldMatrixEnabled(true); const qreal w(boundingRect().width()), h(boundingRect().height()); // arrow size width, height, radius, bottom padding const qreal aw(10), ah(0), r(10), p(10); // Point left or right if (d_ptr->m_Align == Qt::AlignmentFlag::AlignRight) { painter->scale(-1, 1); painter->translate(QPointF{-w, 0}); } QPainterPath path; path.moveTo(0, h-ah-p-r); path.lineTo(aw, h-ah-r); path.lineTo(aw, h-r); path.cubicTo( path.currentPosition(), {aw, h}, {aw+r, h} ); path.lineTo(w- r, h); path.cubicTo( path.currentPosition(), {w, h}, {w, h-r} ); path.lineTo(w, r); path.cubicTo( path.currentPosition(), {w, 0}, {w-r, 0} ); path.lineTo(aw+r, 0); path.cubicTo( path.currentPosition(), {aw, 0}, {aw, r} ); path.lineTo(aw, h-r-p-ah-r); path.lineTo(0, h-ah-p-r); painter->setPen({}); painter->setRenderHint(QPainter::Antialiasing, true); painter->setBrush(d_ptr->m_Color); painter->drawPath(path); } int Bubble::alignment() const { return d_ptr->m_Align; } void Bubble::setAlignment(int a) { d_ptr->m_Align = a; } QColor Bubble::color() const { return d_ptr->m_Color; } void Bubble::setColor(const QColor& c) { d_ptr->m_Color = c; update(); } qreal Bubble::maximumWidth() const { return d_ptr->m_MaximumWidth; } void Bubble::setMaximumWidth(qreal value) { d_ptr->m_MaximumWidth = value; slotWindowChanged(window()); update(); } +qreal Bubble::sideMargins() const +{ + return d_ptr->m_SideMargins; +} + +void Bubble::setSideMargins(qreal m) +{ + d_ptr->m_SideMargins = m; + update(); + emit changed(); +} + QString Bubble::text() const { return d_ptr->m_Text; } void Bubble::setText(const QString& c) { d_ptr->m_Text = c; slotWindowChanged(window()); update(); } QFont& Bubble::font() const { return d_ptr->m_Font; } void Bubble::setFont(const QFont& f) { d_ptr->m_Font = f; d_ptr->m_FontMetrics = QFontMetricsF(f); slotWindowChanged(nullptr); emit fontChanged(d_ptr->m_Font); update(); } +QFont Bubble::dateFont() const +{ + return d_ptr->m_DateFont; +} + +static qreal ratio = 0; + void Bubble::slotWindowChanged(QQuickWindow *w) { w = window(); - static qreal ratio = 0; static qreal arrow = 20; - static qreal margins = 30; static qreal dateW = 0; // There is a race condition, the item are created before the window if (!ratio) { ratio = w ? w->effectiveDevicePixelRatio():0; - //TODO make point size configurable - QFont f = d_ptr->m_Font; - f.setPointSize(8); - QFontMetricsF fm(f); - dateW = fm.width(QDateTime::currentDateTime().toString()); + //TODO use the message date and store the result + dateW = d_ptr->m_DateFontMetrics.width( + QDateTime::currentDateTime().toString() + ); } // At first, don't limit the height const auto r = d_ptr->m_FontMetrics.boundingRect( QRectF {0, 0, d_ptr->m_MaximumWidth, 9999.0}, Qt::AlignLeft|Qt::TextWordWrap, d_ptr->m_Text ); // Prevent bubble larger than the screen - const qreal mw = std::max(dateW*1.66, r.width())+arrow+2*margins; + const qreal mw = std::max(dateW*1.05, r.width())+arrow+2*d_ptr->m_SideMargins; setWidth(std::min(d_ptr->m_MaximumWidth, mw)); } + +void Bubble::setDateFont(const QFont& f) +{ + d_ptr->m_DateFont = f; + d_ptr->m_DateFontMetrics = QFontMetricsF(f); + ratio = 0; + update(); + emit fontChanged(d_ptr->m_Font); +} diff --git a/src/jamichatview/bubble.h b/src/jamichatview/bubble.h index 9dd443d7..2b3cc9c6 100644 --- a/src/jamichatview/bubble.h +++ b/src/jamichatview/bubble.h @@ -1,64 +1,73 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #pragma once #include class BubblePrivate; class Bubble : public QQuickPaintedItem { Q_OBJECT public: Q_PROPERTY(int alignment READ alignment WRITE setAlignment) Q_PROPERTY(QColor color READ color WRITE setColor) Q_PROPERTY(QString text READ text WRITE setText) Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) + Q_PROPERTY(QFont dateFont READ dateFont WRITE setDateFont NOTIFY fontChanged) Q_PROPERTY(qreal maximumWidth READ maximumWidth WRITE setMaximumWidth) + Q_PROPERTY(qreal sideMargins READ sideMargins WRITE setSideMargins NOTIFY changed) explicit Bubble(QQuickItem* parent = nullptr); virtual ~Bubble(); virtual void paint(QPainter *painter) override; int alignment() const; void setAlignment(int a); QColor color() const; void setColor(const QColor& c); QString text() const; void setText(const QString& c); qreal maximumWidth() const; void setMaximumWidth(qreal value); QFont& font() const; void setFont(const QFont& f); + QFont dateFont() const; + void setDateFont(const QFont& f); + + qreal sideMargins() const; + void setSideMargins(qreal m); + private: BubblePrivate* d_ptr; Q_DECLARE_PRIVATE(Bubble) private Q_SLOTS: void slotWindowChanged(QQuickWindow *window); Q_SIGNALS: void fontChanged(const QFont& font); + void changed(); }; diff --git a/src/jamichatview/qml/textbubble.qml b/src/jamichatview/qml/textbubble.qml index 4fa52186..a0a7a229 100644 --- a/src/jamichatview/qml/textbubble.qml +++ b/src/jamichatview/qml/textbubble.qml @@ -1,175 +1,177 @@ /*************************************************************************** * Copyright (C) 2017 by Bluesystems * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ import QtQuick 2.7 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.2 as Kirigami import org.kde.ringkde.jamicontactview 1.0 as JamiContactView import org.kde.ringkde.jamichatview 1.0 as JamiChatView Item { id: chatMessage width: parent.width property color background property color foreground property var cm: contactMethod signal clicked() Behavior on background { ColorAnimation {duration: 300; easing.type: Easing.InQuad} } height: bubble.height + 10 function getFactor() { return (height > (chatMessage.width*0.5)) ? 0.9 : 0.7 } // Prevent a binding loop. the "current" height isn't required anyway onWidthChanged: { bubble.maximumWidth = chatMessage.width*getFactor() } RowLayout { anchors.fill: parent JamiContactView.ContactPhoto { width: 50 height: 50 visible: direction == 0 drawEmptyOutline: false tracked: false contactMethod: chatMessage.cm Layout.alignment: Qt.AlignBottom Layout.bottomMargin: 20 defaultColor: Kirigami.Theme.textColor } Item { Layout.fillWidth: true Layout.fillHeight: true JamiChatView.Bubble { id: bubble anchors.margins: 5 + sideMargins: 30 anchors.right: direction == 1 ? parent.right : undefined anchors.left : direction == 1 ? undefined : parent.left font.pointSize: Kirigami.Theme.defaultFont.pointSize*1.2 font.family: "Noto Color Emoji" + dateFont: dateLabel.font z: 1 alignment: direction == 1 ? Text.AlignRight : Text.AlignLeft color: background text: display != undefined ? display : "N/A" height: Math.max(50, label.implicitHeight + dateLabel.implicitHeight + 5) Text { id: label width: parent.width - anchors.leftMargin: 30 - anchors.rightMargin: 30 + anchors.leftMargin: bubble.sideMargins + anchors.rightMargin: bubble.sideMargins anchors.topMargin: 5 anchors.bottomMargin: 5 anchors.verticalCenter: bubble.verticalCenter anchors.left: direction == 1 ? undefined : bubble.left anchors.right: direction == 1 ? bubble.right : undefined horizontalAlignment: direction == 1 ? Text.AlignRight : Text.AlignLeft font: bubble.font text: display != undefined ? display : "N/A" color: foreground wrapMode: Text.WordWrap transitions: Transition { AnchorAnimation {duration: 200; easing.type: Easing.OutQuad } } states: [ State { name: "" when: !chatView.displayExtraTime AnchorChanges { target: label anchors.verticalCenter: bubble.verticalCenter anchors.top: undefined } PropertyChanges { target: label anchors.topMargin: 5 anchors.bottomMargin: 5 } }, State { name: "showtime" when: chatView.displayExtraTime AnchorChanges { target: label anchors.verticalCenter: undefined anchors.top: bubble.top } PropertyChanges { target: label anchors.topMargin: 0 anchors.bottomMargin: 0 } } ] } Text { id: dateLabel anchors.bottom: parent.bottom anchors.left: direction == 1 ? parent.left : undefined anchors.right: direction == 0 ? parent.right : undefined anchors.bottomMargin: 4 anchors.leftMargin: direction == 1 ? 4 : undefined anchors.rightMargin: direction == 0 ? 4 : undefined text: formattedDate != undefined ? formattedDate : "N/A" color: Kirigami.Theme.highlightedTextColor opacity: chatView.displayExtraTime ? 0.75 : 0 Behavior on opacity { NumberAnimation {duration: 200} } } MouseArea { anchors.fill: parent onClicked: { chatMessage.clicked() } } } } JamiContactView.ContactPhoto { width: 50 height: 50 visible: direction == 1 drawEmptyOutline: false tracked: false contactMethod: chatMessage.cm Layout.alignment: Qt.AlignBottom Layout.bottomMargin: 20 defaultColor: Kirigami.Theme.textColor } } }