Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Implementing chat type listview with text bubbles

Implementing chat type listview with text bubbles

Scheduled Pinned Locked Moved Solved General and Desktop
custom itemqlistviewmodel-viewchattingtextsize
12 Posts 4 Posters 5.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    bepaald
    wrote on last edited by
    #3

    Thank you very much! After your message I totally ran with it (and that example you provided helped a great deal), and got it mostly working. I have a few small problems still that I need to work out, but I think they would be better off in a new thread as they would be off topic here.

    Thanks again!

    T 1 Reply Last reply
    0
    • B bepaald

      Thank you very much! After your message I totally ran with it (and that example you provided helped a great deal), and got it mostly working. I have a few small problems still that I need to work out, but I think they would be better off in a new thread as they would be off topic here.

      Thanks again!

      T Offline
      T Offline
      Taytoo
      wrote on last edited by
      #4

      @bepaald said in Implementing chat type listview with text bubbles:

      Thank you very much! After your message I totally ran with it (and that example you provided helped a great deal), and got it mostly working. I have a few small problems still that I need to work out, but I think they would be better off in a new thread as they would be off topic here.

      Thanks again!

      I'm trying to create bubble chat for my desktop application. Can you please share how you end up implementing it? Would you mind sharing some of the code? Thanks!

      B 1 Reply Last reply
      0
      • B Offline
        B Offline
        bepaald
        wrote on last edited by
        #5

        @Taytoo said in Implementing chat type listview with text bubbles:

        @bepaald said in Implementing chat type listview with text bubbles:

        [...]

        I'm trying to create bubble chat for my desktop application. Can you please share how you end up implementing it? Would you mind sharing some of the code? Thanks!

        Hi Taytoo! As I said I totally ran with it and, after 7 months, the project is now huge (though still far from finished). As it is now sharing the code is impossible, it is thousands of lines of code and probably totally unreadable. I will try to come up with some minimal example in the near future and post it back here, but it will probably take a while as I am very busy.

        Just as a starting point, you might want to check out this link https://stackoverflow.com/questions/1956542/how-to-make-item-view-render-rich-html-text-in-qt/1956781#1956781, I think it was how I started as well.

        1 Reply Last reply
        2
        • T Taytoo

          @bepaald said in Implementing chat type listview with text bubbles:

          Thank you very much! After your message I totally ran with it (and that example you provided helped a great deal), and got it mostly working. I have a few small problems still that I need to work out, but I think they would be better off in a new thread as they would be off topic here.

          Thanks again!

          I'm trying to create bubble chat for my desktop application. Can you please share how you end up implementing it? Would you mind sharing some of the code? Thanks!

          B Offline
          B Offline
          bepaald
          wrote on last edited by
          #6

          @Taytoo
          Ok, I got around to creating a little example. It shows just a normal QListView, but painting the data in its model with a custom delegate. This delegate has two main functions: first it needs to report the size in the view an item is going to need to be painted. Second, it needs to paint.

          The main file just sets up some dummy data in a standard QStandardItemModel. Make sure you add any data to the model that your delegate might need to paint the item properly. In this example I only added an "outgoing" or "incoming" datafield to get some different messages, but you may need a ton more dataroles (I did!). Then it creates a pretty standard QListView. The only custom stuff is the ListViewDelegate set on the view using QListView::setItemDelegate().

          //main.cc
          
          #include <QtWidgets>
          
          #include "listviewdelegate.h"
          
          int main(int argc, char *argv[])
          {
            QApplication app(argc, argv);
          
            // create some data and put it in a model
            QStandardItemModel mymodel;
            QStandardItem *item1 = new QStandardItem("This is item one");
            item1->setData("Incoming", Qt::UserRole + 1);
            mymodel.appendRow(item1);
            QStandardItem *item2 = new QStandardItem("This is item two, it is a very long item, but it's not the item's fault, it is me typing all this text.");
            item2->setData("Outgoing", Qt::UserRole + 1);
            mymodel.appendRow(item2);
            QStandardItem *item3 = new QStandardItem("This is the third item");
            item3->setData("Incoming", Qt::UserRole + 1);
            mymodel.appendRow(item3);
          
            // create a view and set our data
            QListView listview;
            listview.setResizeMode(QListView::Adjust);
            listview.setWordWrap(true);
            listview.setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
            listview.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            listview.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            listview.setModel(&mymodel);
            listview.setMinimumSize(200,350);
          
            // NOW tell the view to rely on out custom delegate for drawing
            listview.setItemDelegate(new ListViewDelegate());
          
            // show it
            listview.show();
          
            return app.exec();
          }
          

          As said, the delegate just gives size hints and paints. You can paint anything you want in the items, just make sure to also update the sizeHint() for any new elements you're painting. I am using a QTextDocument to store the body text, as it can easily report its own size (especially its height given a certain width). Also, the HTML capabilities make it easy to add font styles and possibly inline images. In my own code I did end up implementing my own class (derived from QTextDocument) to support more content and work around an annoying bug.

          // listviewdelegate.h
          #ifndef LISTVIEWDELEGATE_H_
          #define LISTVIEWDELEGATE_H_
          
          #include <QAbstractItemDelegate>
          #include <QPainter>
          
          class ListViewDelegate : public QAbstractItemDelegate
          {
            int d_radius;
            int d_toppadding;
            int d_bottompadding;
            int d_leftpadding;
            int d_rightpadding;
            int d_verticalmargin;
            int d_horizontalmargin;
            int d_pointerwidth;
            int d_pointerheight;
            float d_widthfraction;
           public:
            inline ListViewDelegate(QObject *parent = nullptr);
           protected:
            inline void paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const;
            inline QSize sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const;
          };
          
          inline ListViewDelegate::ListViewDelegate(QObject *parent)
            :
            QAbstractItemDelegate(parent),
            d_radius(5),
            d_toppadding(5),
            d_bottompadding(3),
            d_leftpadding(5),
            d_rightpadding(5),
            d_verticalmargin(15),
            d_horizontalmargin(10),
            d_pointerwidth(10),
            d_pointerheight(17),
            d_widthfraction(.7)
          {}
          
          inline void ListViewDelegate::paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const
          {
            QTextDocument bodydoc;
            QTextOption textOption(bodydoc.defaultTextOption());
            textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
            bodydoc.setDefaultTextOption(textOption);
            bodydoc.setDefaultFont(QFont("Roboto", 12));
            QString bodytext(index.data(Qt::DisplayRole).toString());
            bodydoc.setHtml(bodytext);
          
            qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
            bodydoc.setTextWidth(contentswidth);
            qreal bodyheight = bodydoc.size().height();
          
            bool outgoing = index.data(Qt::UserRole + 1).toString() == "Outgoing";
          
            painter->save();
            painter->setRenderHint(QPainter::Antialiasing);
          
            // uncomment to see the area provided to paint this item
            //painter->drawRect(option.rect);
          
            painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
          
            // background color for chat bubble
            QColor bgcolor("#DD1212");
            if (outgoing)
              bgcolor = "#DDDDDD";
          
            // create chat bubble
            QPainterPath pointie;
          
            // left bottom
            pointie.moveTo(0, bodyheight + d_toppadding + d_bottompadding);
          
            // right bottom
            pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - d_radius,
                           bodyheight + d_toppadding + d_bottompadding);
            pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius,
                          bodyheight + d_toppadding + d_bottompadding - 2 * d_radius,
                          2 * d_radius, 2 * d_radius, 270, 90);
          
            // right top
            pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding, 0 + d_radius);
            pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius, 0,
                          2 * d_radius, 2 * d_radius, 0, 90);
          
            // left top
            pointie.lineTo(0 + d_pointerwidth + d_radius, 0);
            pointie.arcTo(0 + d_pointerwidth, 0, 2 * d_radius, 2 * d_radius, 90, 90);
          
            // left bottom almost (here is the pointie)
            pointie.lineTo(0 + d_pointerwidth, bodyheight + d_toppadding + d_bottompadding - d_pointerheight);
            pointie.closeSubpath();
          
            // rotate bubble for outgoing messages
            if (outgoing)
            {
              painter->translate(option.rect.width() - pointie.boundingRect().width() - d_horizontalmargin - d_pointerwidth, 0);
              painter->translate(pointie.boundingRect().center());
              painter->rotate(180);
              painter->translate(-pointie.boundingRect().center());
            }
          
            // now paint it!
            painter->setPen(QPen(bgcolor));
            painter->drawPath(pointie);
            painter->fillPath(pointie, QBrush(bgcolor));
          
            // rotate back or painter is going to paint the text rotated...
            if (outgoing)
            {
              painter->translate(pointie.boundingRect().center());
              painter->rotate(-180);
              painter->translate(-pointie.boundingRect().center());
            }
          
            // set text color used to draw message body
            QAbstractTextDocumentLayout::PaintContext ctx;
            if (outgoing)
              ctx.palette.setColor(QPalette::Text, QColor("black"));
            else
              ctx.palette.setColor(QPalette::Text, QColor("white"));
          
            // draw body text
            painter->translate((outgoing ? 0 : d_pointerwidth) + d_leftpadding, 0);
            bodydoc.documentLayout()->draw(painter, ctx);
          
            painter->restore();
          }
          
          inline QSize ListViewDelegate::sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const
          {
            QTextDocument bodydoc;
            QTextOption textOption(bodydoc.defaultTextOption());
            textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
            bodydoc.setDefaultTextOption(textOption);
            bodydoc.setDefaultFont(QFont("Roboto", 12));
            QString bodytext(index.data(Qt::DisplayRole).toString());
            bodydoc.setHtml(bodytext);
          
            // the width of the contents are the (a fraction of the window width) minus (margins + padding + width of the bubble's tail)
            qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
          
            // set this available width on the text document
            bodydoc.setTextWidth(contentswidth);
          
            QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                       bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin + 1); // I dont remember why +1, haha, might not be necessary
          
            if (index.row() == 0) // have extra margin at top of first item
              size += QSize(0, d_verticalmargin);
          
            return size;
          }
          
          #endif
          

          It should compile with

          qmake -project QT+=widgets
          qmake
          make
          
          T 1 Reply Last reply
          5
          • B bepaald

            @Taytoo
            Ok, I got around to creating a little example. It shows just a normal QListView, but painting the data in its model with a custom delegate. This delegate has two main functions: first it needs to report the size in the view an item is going to need to be painted. Second, it needs to paint.

            The main file just sets up some dummy data in a standard QStandardItemModel. Make sure you add any data to the model that your delegate might need to paint the item properly. In this example I only added an "outgoing" or "incoming" datafield to get some different messages, but you may need a ton more dataroles (I did!). Then it creates a pretty standard QListView. The only custom stuff is the ListViewDelegate set on the view using QListView::setItemDelegate().

            //main.cc
            
            #include <QtWidgets>
            
            #include "listviewdelegate.h"
            
            int main(int argc, char *argv[])
            {
              QApplication app(argc, argv);
            
              // create some data and put it in a model
              QStandardItemModel mymodel;
              QStandardItem *item1 = new QStandardItem("This is item one");
              item1->setData("Incoming", Qt::UserRole + 1);
              mymodel.appendRow(item1);
              QStandardItem *item2 = new QStandardItem("This is item two, it is a very long item, but it's not the item's fault, it is me typing all this text.");
              item2->setData("Outgoing", Qt::UserRole + 1);
              mymodel.appendRow(item2);
              QStandardItem *item3 = new QStandardItem("This is the third item");
              item3->setData("Incoming", Qt::UserRole + 1);
              mymodel.appendRow(item3);
            
              // create a view and set our data
              QListView listview;
              listview.setResizeMode(QListView::Adjust);
              listview.setWordWrap(true);
              listview.setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
              listview.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
              listview.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
              listview.setModel(&mymodel);
              listview.setMinimumSize(200,350);
            
              // NOW tell the view to rely on out custom delegate for drawing
              listview.setItemDelegate(new ListViewDelegate());
            
              // show it
              listview.show();
            
              return app.exec();
            }
            

            As said, the delegate just gives size hints and paints. You can paint anything you want in the items, just make sure to also update the sizeHint() for any new elements you're painting. I am using a QTextDocument to store the body text, as it can easily report its own size (especially its height given a certain width). Also, the HTML capabilities make it easy to add font styles and possibly inline images. In my own code I did end up implementing my own class (derived from QTextDocument) to support more content and work around an annoying bug.

            // listviewdelegate.h
            #ifndef LISTVIEWDELEGATE_H_
            #define LISTVIEWDELEGATE_H_
            
            #include <QAbstractItemDelegate>
            #include <QPainter>
            
            class ListViewDelegate : public QAbstractItemDelegate
            {
              int d_radius;
              int d_toppadding;
              int d_bottompadding;
              int d_leftpadding;
              int d_rightpadding;
              int d_verticalmargin;
              int d_horizontalmargin;
              int d_pointerwidth;
              int d_pointerheight;
              float d_widthfraction;
             public:
              inline ListViewDelegate(QObject *parent = nullptr);
             protected:
              inline void paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const;
              inline QSize sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const;
            };
            
            inline ListViewDelegate::ListViewDelegate(QObject *parent)
              :
              QAbstractItemDelegate(parent),
              d_radius(5),
              d_toppadding(5),
              d_bottompadding(3),
              d_leftpadding(5),
              d_rightpadding(5),
              d_verticalmargin(15),
              d_horizontalmargin(10),
              d_pointerwidth(10),
              d_pointerheight(17),
              d_widthfraction(.7)
            {}
            
            inline void ListViewDelegate::paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const
            {
              QTextDocument bodydoc;
              QTextOption textOption(bodydoc.defaultTextOption());
              textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
              bodydoc.setDefaultTextOption(textOption);
              bodydoc.setDefaultFont(QFont("Roboto", 12));
              QString bodytext(index.data(Qt::DisplayRole).toString());
              bodydoc.setHtml(bodytext);
            
              qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
              bodydoc.setTextWidth(contentswidth);
              qreal bodyheight = bodydoc.size().height();
            
              bool outgoing = index.data(Qt::UserRole + 1).toString() == "Outgoing";
            
              painter->save();
              painter->setRenderHint(QPainter::Antialiasing);
            
              // uncomment to see the area provided to paint this item
              //painter->drawRect(option.rect);
            
              painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
            
              // background color for chat bubble
              QColor bgcolor("#DD1212");
              if (outgoing)
                bgcolor = "#DDDDDD";
            
              // create chat bubble
              QPainterPath pointie;
            
              // left bottom
              pointie.moveTo(0, bodyheight + d_toppadding + d_bottompadding);
            
              // right bottom
              pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - d_radius,
                             bodyheight + d_toppadding + d_bottompadding);
              pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius,
                            bodyheight + d_toppadding + d_bottompadding - 2 * d_radius,
                            2 * d_radius, 2 * d_radius, 270, 90);
            
              // right top
              pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding, 0 + d_radius);
              pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius, 0,
                            2 * d_radius, 2 * d_radius, 0, 90);
            
              // left top
              pointie.lineTo(0 + d_pointerwidth + d_radius, 0);
              pointie.arcTo(0 + d_pointerwidth, 0, 2 * d_radius, 2 * d_radius, 90, 90);
            
              // left bottom almost (here is the pointie)
              pointie.lineTo(0 + d_pointerwidth, bodyheight + d_toppadding + d_bottompadding - d_pointerheight);
              pointie.closeSubpath();
            
              // rotate bubble for outgoing messages
              if (outgoing)
              {
                painter->translate(option.rect.width() - pointie.boundingRect().width() - d_horizontalmargin - d_pointerwidth, 0);
                painter->translate(pointie.boundingRect().center());
                painter->rotate(180);
                painter->translate(-pointie.boundingRect().center());
              }
            
              // now paint it!
              painter->setPen(QPen(bgcolor));
              painter->drawPath(pointie);
              painter->fillPath(pointie, QBrush(bgcolor));
            
              // rotate back or painter is going to paint the text rotated...
              if (outgoing)
              {
                painter->translate(pointie.boundingRect().center());
                painter->rotate(-180);
                painter->translate(-pointie.boundingRect().center());
              }
            
              // set text color used to draw message body
              QAbstractTextDocumentLayout::PaintContext ctx;
              if (outgoing)
                ctx.palette.setColor(QPalette::Text, QColor("black"));
              else
                ctx.palette.setColor(QPalette::Text, QColor("white"));
            
              // draw body text
              painter->translate((outgoing ? 0 : d_pointerwidth) + d_leftpadding, 0);
              bodydoc.documentLayout()->draw(painter, ctx);
            
              painter->restore();
            }
            
            inline QSize ListViewDelegate::sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const
            {
              QTextDocument bodydoc;
              QTextOption textOption(bodydoc.defaultTextOption());
              textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
              bodydoc.setDefaultTextOption(textOption);
              bodydoc.setDefaultFont(QFont("Roboto", 12));
              QString bodytext(index.data(Qt::DisplayRole).toString());
              bodydoc.setHtml(bodytext);
            
              // the width of the contents are the (a fraction of the window width) minus (margins + padding + width of the bubble's tail)
              qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
            
              // set this available width on the text document
              bodydoc.setTextWidth(contentswidth);
            
              QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                         bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin + 1); // I dont remember why +1, haha, might not be necessary
            
              if (index.row() == 0) // have extra margin at top of first item
                size += QSize(0, d_verticalmargin);
            
              return size;
            }
            
            #endif
            

            It should compile with

            qmake -project QT+=widgets
            qmake
            make
            
            T Offline
            T Offline
            Taytoo
            wrote on last edited by
            #7

            @bepaald

            Sorry, I just saw that you responded with a solution. It works really well :)

            One issue though, since the text is painted, it doesn't allow selecting and copying text from the chat bubble. How did you solve that limitation?

            S 1 Reply Last reply
            0
            • S Offline
              S Offline
              steveq
              wrote on last edited by
              #8

              Hi @bepaald ,

              Firstly, thank you for your delegate. It works really well! I have added several more UserRoles and modified your code accordingly. I'm really happy with the way it is working.

              A question though, embedding html works really well, as does embedding inline images using the html tag <img src= etc.

              A can also embed a hyperlink, however the link is not clickable, probably because it painted into the bubble.

              Do you have a suggestion as to how I can get around this please?

              Thanks so much,

              Steve Q.

              1 Reply Last reply
              0
              • SGaistS Offline
                SGaistS Offline
                SGaist
                Lifetime Qt Champion
                wrote on last edited by
                #9

                Hi,

                You need to add custom handling of mouse click and check whether what's under it matches the position of an URL and then act accordingly. For example use QDesktopServices::openUrl to open it.

                Interested in AI ? www.idiap.ch
                Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                S 1 Reply Last reply
                1
                • SGaistS SGaist

                  Hi,

                  You need to add custom handling of mouse click and check whether what's under it matches the position of an URL and then act accordingly. For example use QDesktopServices::openUrl to open it.

                  S Offline
                  S Offline
                  steveq
                  wrote on last edited by
                  #10

                  @SGaist thank you, it has worked a treat!

                  I appreciate your help.

                  Steve Q.

                  1 Reply Last reply
                  0
                  • T Taytoo

                    @bepaald

                    Sorry, I just saw that you responded with a solution. It works really well :)

                    One issue though, since the text is painted, it doesn't allow selecting and copying text from the chat bubble. How did you solve that limitation?

                    S Offline
                    S Offline
                    steveq
                    wrote on last edited by steveq
                    #11

                    @Taytoo
                    As requested, here is my modified chatbubbledelegate.h:

                    #ifndef CHATBUBBLEDELEGATE_H
                    #define CHATBUBBLEDELEGATE_H
                    
                    #include <QAbstractItemDelegate>
                    #include <QPainter>
                    #include <QTextDocument>
                    #include <QAbstractTextDocumentLayout>
                    
                    class ChatBubbleDelegate : public QAbstractItemDelegate
                    {
                      int d_radius;
                      int d_toppadding;
                      int d_bottompadding;
                      int d_leftpadding;
                      int d_rightpadding;
                      int d_verticalmargin;
                      int d_horizontalmargin;
                      int d_pointerwidth;
                      int d_pointerheight;
                      float d_widthfraction;
                     public:
                      inline ChatBubbleDelegate(QObject *parent = nullptr);
                     protected:
                      inline void paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const;
                      inline QSize sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const;
                    };
                    
                    inline ChatBubbleDelegate::ChatBubbleDelegate(QObject *parent)
                      :
                      QAbstractItemDelegate(parent),
                      d_radius(5),
                      d_toppadding(2),
                      d_bottompadding(1),
                      d_leftpadding(5),
                      d_rightpadding(5),
                      d_verticalmargin(10),
                      d_horizontalmargin(10),
                      d_pointerwidth(10),
                      d_pointerheight(17),
                      d_widthfraction(.7)
                    {}
                    
                    inline void ChatBubbleDelegate::paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const
                    {
                    
                    //	if (!index.data(Qt::UserRole + 3).toString().isEmpty())
                    //	{
                    //		bool outgoing = index.data(Qt::UserRole + 3).toString() == "Outgoing";
                    //		QTextDocument bodydoc;
                    //		QTextOption textOption(bodydoc.defaultTextOption());
                    //		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                    //		bodydoc.setDefaultTextOption(textOption);
                    //#if defined (Q_OS_OSX)
                    //		bodydoc.setDefaultFont(QFont("Roboto", 13));
                    //#elif defined (Q_OS_WIN)
                    //		bodydoc.setDefaultFont(QFont("Roboto", 8));
                    //#endif
                    //		QString bodytext(index.data(Qt::DisplayRole).toString());
                    //		bodydoc.setHtml(bodytext);
                    
                    //		painter->save();
                    //		painter->setRenderHint(QPainter::Antialiasing);
                    
                    //		painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                    ////			  painter->drawRect(option.rect);
                    
                    //		QAbstractTextDocumentLayout::PaintContext ctx;
                    
                    //		ctx.palette.setColor(QPalette::Text, QColor("grey"));
                    
                    //		// draw body text
                    //		painter->translate((outgoing ? 0 : d_pointerwidth) + d_leftpadding, 0);
                    
                    //		bodydoc.documentLayout()->draw(painter, ctx);
                    
                    //		painter->restore();
                    //	}
                    
                    	if (!index.data(Qt::UserRole + 3).toString().isEmpty())
                    	{
                    		bool outgoing = index.data(Qt::UserRole + 3).toString() == "Outgoing";
                    
                    		QTextDocument bodydoc;
                    		QTextOption textOption(bodydoc.defaultTextOption());
                    		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                    		bodydoc.setDefaultTextOption(textOption);
                    #if defined (Q_OS_OSX)
                    		bodydoc.setDefaultFont(QFont("Roboto", 13));
                    #elif defined (Q_OS_WIN)
                    		bodydoc.setDefaultFont(QFont("Roboto", 8));
                    #endif
                    		QString bodytext(index.data(Qt::DisplayRole).toString());
                    		bodydoc.setHtml(bodytext);
                    
                    		qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
                    		bodydoc.setTextWidth(contentswidth);
                    
                    		qreal bodyheight = bodydoc.size().height();
                    
                    		painter->save();
                    		painter->setRenderHint(QPainter::Antialiasing);
                    
                    		painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                    
                    		// Start drawing the chat bubble, but we don't use it. This is just to get the correct horizontal offset position for the
                    		// name and/or timestamp text
                    		QPainterPath pointie;
                    
                    		// left bottom
                    		pointie.moveTo(0, bodyheight + d_toppadding + d_bottompadding);
                    
                    		// right bottom
                    		pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - d_radius, bodyheight + d_toppadding + d_bottompadding);
                    
                    		pointie.closeSubpath();
                    
                    		if (outgoing) painter->translate(option.rect.width() - pointie.boundingRect().width() - d_horizontalmargin - d_pointerwidth, 0);
                    
                    		// uncomment to see the area provided to paint this item
                    		// painter->drawRect(option.rect);
                    
                    		QAbstractTextDocumentLayout::PaintContext ctx;
                    
                    		ctx.palette.setColor(QPalette::Text, QColor("grey"));
                    
                    		// draw body text
                    		painter->translate((outgoing ? 0 : d_pointerwidth + d_leftpadding), 0);
                    
                    		bodydoc.documentLayout()->draw(painter, ctx);
                    
                    		painter->restore();
                    	}
                    
                    	else
                    	{
                    		QTextDocument bodydoc;
                    		QTextOption textOption(bodydoc.defaultTextOption());
                    		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                    		bodydoc.setDefaultTextOption(textOption);
                    #if defined (Q_OS_OSX)
                    		bodydoc.setDefaultFont(QFont("Roboto", 14));
                    #elif defined (Q_OS_WIN)
                    		bodydoc.setDefaultFont(QFont("Roboto", 9));
                    #endif
                    		QString bodytext(index.data(Qt::DisplayRole).toString());
                    		bodydoc.setHtml(bodytext);
                    
                    		if ( index.data(Qt::UserRole + 1).toString().contains("Status") )
                    		{
                    			painter->save();
                    			painter->setRenderHint(QPainter::Antialiasing);
                    
                    			painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                    			//painter->drawRect(option.rect);
                    
                    			QAbstractTextDocumentLayout::PaintContext ctx;
                    
                    			ctx.palette.setColor(QPalette::Text, QColor("grey"));
                    
                    			// draw body text
                    			painter->translate((false ? 0 : d_pointerwidth) + d_leftpadding, 0);
                    
                    			bodydoc.documentLayout()->draw(painter, ctx);
                    
                    			painter->restore();
                    		}
                    
                    		else
                    		{
                    			qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
                    			bodydoc.setTextWidth(contentswidth);
                    
                    			qreal bodyheight = bodydoc.size().height();
                    
                    			bool outgoing = index.data(Qt::UserRole + 1).toString() == "Outgoing";
                    			QString colour = index.data(Qt::UserRole + 2).toString();
                    
                    			painter->save();
                    			painter->setRenderHint(QPainter::Antialiasing);
                    
                    			// uncomment to see the area provided to paint this item
                    			//painter->drawRect(option.rect);
                    
                    			painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                    
                    			// background color for chat bubble
                    			QColor bgcolor(colour);
                    			//  if (outgoing)
                    			//	bgcolor = "#DDDDDD";
                    
                    			// create chat bubble
                    			QPainterPath pointie;
                    
                    			// left bottom
                    			pointie.moveTo(0, bodyheight + d_toppadding + d_bottompadding);
                    
                    			// right bottom
                    			pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - d_radius,
                    						 bodyheight + d_toppadding + d_bottompadding);
                    			pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius,
                    						bodyheight + d_toppadding + d_bottompadding - 2 * d_radius,
                    						2 * d_radius, 2 * d_radius, 270, 90);
                    
                    			// right top
                    			pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding, 0 + d_radius);
                    			pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius, 0,
                    						2 * d_radius, 2 * d_radius, 0, 90);
                    
                    			// left top
                    			pointie.lineTo(0 + d_pointerwidth + d_radius, 0);
                    			pointie.arcTo(0 + d_pointerwidth, 0, 2 * d_radius, 2 * d_radius, 90, 90);
                    
                    			// left bottom almost (here is the pointie)
                    			pointie.lineTo(0 + d_pointerwidth, bodyheight + d_toppadding + d_bottompadding - d_pointerheight);
                    			pointie.closeSubpath();
                    
                    			// rotate bubble for outgoing messages
                    			if (outgoing)
                    			{
                    				painter->translate(option.rect.width() - pointie.boundingRect().width() - d_horizontalmargin - d_pointerwidth, 0);
                    				painter->translate(pointie.boundingRect().center());
                    				painter->rotate(180);
                    				painter->translate(-pointie.boundingRect().center());
                    			}
                    
                    			// now paint it!
                    			painter->setPen(QPen(bgcolor));
                    			painter->drawPath(pointie);
                    			painter->fillPath(pointie, QBrush(bgcolor));
                    
                    			// rotate back or painter is going to paint the text rotated...
                    			if (outgoing)
                    			{
                    				painter->translate(pointie.boundingRect().center());
                    				painter->rotate(-180);
                    				painter->translate(-pointie.boundingRect().center());
                    			}
                    
                    			// set text color used to draw message body
                    			QAbstractTextDocumentLayout::PaintContext ctx;
                    
                    			ctx.palette.setColor(QPalette::Text, QColor("black"));
                    
                    			// draw body text
                    			painter->translate((outgoing ? 0 : d_pointerwidth) + d_leftpadding, 0);
                    			bodydoc.documentLayout()->draw(painter, ctx);
                    
                    			painter->restore();
                    		}
                    	}
                    }
                    
                    inline QSize ChatBubbleDelegate::sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const
                    {
                    	if (!index.data(Qt::UserRole + 3).toString().isEmpty())
                    	{
                    		QTextDocument bodydoc;
                    		QTextOption textOption(bodydoc.defaultTextOption());
                    		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                    		bodydoc.setDefaultTextOption(textOption);
                    #if defined (Q_OS_OSX)
                    		bodydoc.setDefaultFont(QFont("Roboto", 13));
                    #elif defined (Q_OS_WIN)
                    		bodydoc.setDefaultFont(QFont("Roboto", 8));
                    #endif
                    		QString bodytext(index.data(Qt::DisplayRole).toString());
                    		bodydoc.setHtml(bodytext);
                    
                    		QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                    			 bodydoc.size().height() + d_bottompadding);
                    
                    		return size;
                    	}
                    
                    	else
                    	{
                    		QTextDocument bodydoc;
                    		QTextOption textOption(bodydoc.defaultTextOption());
                    		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                    		bodydoc.setDefaultTextOption(textOption);
                    #if defined (Q_OS_OSX)
                    		bodydoc.setDefaultFont(QFont("Roboto", 14));
                    #elif defined (Q_OS_WIN)
                    		bodydoc.setDefaultFont(QFont("Roboto", 9));
                    #endif
                    		QString bodytext(index.data(Qt::DisplayRole).toString());
                    		bodydoc.setHtml(bodytext);
                    
                    		// the width of the contents are the (a fraction of the window width) minus (margins + padding + width of the bubble's tail)
                    		qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
                    
                    		// set this available width on the text document
                    		bodydoc.setTextWidth(contentswidth);
                    
                    		if ( index.data(Qt::UserRole + 1).toString().contains("Status") )
                    		{
                    			QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                    				 bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin);
                    
                    			return size;
                    		}
                    
                    		else
                    		{
                    			if ( index.data(Qt::UserRole + 1).toString().contains("Outgoing") )
                    			{
                    				QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                    						bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin + 1);
                    
                    				return size;
                    			}
                    
                    			else
                    			{
                    				QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                    						bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin + 1);
                    
                    				return size;
                    			}
                    		}
                    	}
                    }
                    
                    #endif // CHATBUBBLEDELEGATE_H
                    
                    

                    And this is my function for writing a message to the chat bubble:

                    void ChatTab::addMessage(QString userColour, QString username, QString message, int itemType, QString itemData)
                    {
                    	QString roleMode;
                    
                    	QColor color(userColour), newColor = color;
                    
                    	int lightnessAmount = 100;
                    
                    	// Convert carriage returns to html carriage returns so they are displayed properly
                    	message.replace("\n", "<br>");
                    
                    	// Crappy way of doing it, but make sure the user colour has a lightness colour of 230 or greater.
                    	// 230 is an arbitrary colour I have chosen
                    	for ( ;; )
                    	{
                    		if ( newColor.lightness() < 230 )
                    		{
                    			lightnessAmount += 10;
                    
                    			newColor = color.lighter(lightnessAmount);
                    		}
                    
                    		else break;
                    	}
                    
                    	// Print the senders name and or time stamp
                    	QString statusText;
                    
                    	if ( m_settings->username == username ) roleMode = "Outgoing";
                    	else roleMode = "Incoming";
                    
                    	if ( m_membersList->count() > 1  && roleMode == "Incoming" ) statusText = username + " ";
                    
                    	statusText += QTime::currentTime().toString( "HH:mm:ss");
                    
                    	struct ChatListItem *cli;
                    
                    	QStandardItem *item1 = new QStandardItem(statusText);
                    
                    	item1->setData(roleMode, Qt::UserRole + 3);
                    
                    	cli = new struct ChatListItem;
                    
                    	cli->itemData = "";
                    	cli->itemText = username;
                    	cli->itemType = CHATLISTITEMTYPE_STATUSMESSAGE;
                    
                    	item1->setData(  QVariant(QVariant::fromValue(static_cast<void*>(cli))), Qt::UserRole + 20 );
                    
                    	m_mymodel.appendRow(item1);
                    
                    	// Print the message
                    	if ( m_settings->username == username ) roleMode = "Outgoing";
                    	else roleMode = "Incoming";
                    
                    	QStandardItem *item2 = new QStandardItem(message);
                    
                    	item2->setData(roleMode, Qt::UserRole + 1);
                    
                    	item2->setData(newColor.name(), Qt::UserRole + 2);
                    
                    	item2->setData("", Qt::UserRole + 3);
                    
                    	cli = new struct ChatListItem;
                    
                    	cli->itemData = itemData;
                    	cli->itemText = message;
                    	cli->itemType = itemType;
                    
                    	item2->setData(  QVariant(QVariant::fromValue(static_cast<void*>(cli))), Qt::UserRole + 20 );
                    
                    	m_mymodel.appendRow(item2);
                    
                    	m_chatList->scrollToBottom();
                    
                    	addToTranscript( username, message );
                    }
                    

                    As I said in my email, it was a while ago when I wrote this. If you have any questions, let me know and I'll try and figure out what I was doing! Lol

                    Small Talk.png

                    This is a screen grab of my chat window. I couldn't grab it with the context menu being displayed, but each bubble has its own context menu which do various things depending on the content of the bubble. Such as downloading an image and displaying it, opening a web page, or copying the bubble text to the clipboard. HTML codes can also be embedded into the bubble and display correctly.
                    I hope all this helps.
                    Steve Q.

                    T 1 Reply Last reply
                    0
                    • S steveq

                      @Taytoo
                      As requested, here is my modified chatbubbledelegate.h:

                      #ifndef CHATBUBBLEDELEGATE_H
                      #define CHATBUBBLEDELEGATE_H
                      
                      #include <QAbstractItemDelegate>
                      #include <QPainter>
                      #include <QTextDocument>
                      #include <QAbstractTextDocumentLayout>
                      
                      class ChatBubbleDelegate : public QAbstractItemDelegate
                      {
                        int d_radius;
                        int d_toppadding;
                        int d_bottompadding;
                        int d_leftpadding;
                        int d_rightpadding;
                        int d_verticalmargin;
                        int d_horizontalmargin;
                        int d_pointerwidth;
                        int d_pointerheight;
                        float d_widthfraction;
                       public:
                        inline ChatBubbleDelegate(QObject *parent = nullptr);
                       protected:
                        inline void paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const;
                        inline QSize sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const;
                      };
                      
                      inline ChatBubbleDelegate::ChatBubbleDelegate(QObject *parent)
                        :
                        QAbstractItemDelegate(parent),
                        d_radius(5),
                        d_toppadding(2),
                        d_bottompadding(1),
                        d_leftpadding(5),
                        d_rightpadding(5),
                        d_verticalmargin(10),
                        d_horizontalmargin(10),
                        d_pointerwidth(10),
                        d_pointerheight(17),
                        d_widthfraction(.7)
                      {}
                      
                      inline void ChatBubbleDelegate::paint(QPainter *painter, QStyleOptionViewItem const &option, QModelIndex const &index) const
                      {
                      
                      //	if (!index.data(Qt::UserRole + 3).toString().isEmpty())
                      //	{
                      //		bool outgoing = index.data(Qt::UserRole + 3).toString() == "Outgoing";
                      //		QTextDocument bodydoc;
                      //		QTextOption textOption(bodydoc.defaultTextOption());
                      //		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                      //		bodydoc.setDefaultTextOption(textOption);
                      //#if defined (Q_OS_OSX)
                      //		bodydoc.setDefaultFont(QFont("Roboto", 13));
                      //#elif defined (Q_OS_WIN)
                      //		bodydoc.setDefaultFont(QFont("Roboto", 8));
                      //#endif
                      //		QString bodytext(index.data(Qt::DisplayRole).toString());
                      //		bodydoc.setHtml(bodytext);
                      
                      //		painter->save();
                      //		painter->setRenderHint(QPainter::Antialiasing);
                      
                      //		painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                      ////			  painter->drawRect(option.rect);
                      
                      //		QAbstractTextDocumentLayout::PaintContext ctx;
                      
                      //		ctx.palette.setColor(QPalette::Text, QColor("grey"));
                      
                      //		// draw body text
                      //		painter->translate((outgoing ? 0 : d_pointerwidth) + d_leftpadding, 0);
                      
                      //		bodydoc.documentLayout()->draw(painter, ctx);
                      
                      //		painter->restore();
                      //	}
                      
                      	if (!index.data(Qt::UserRole + 3).toString().isEmpty())
                      	{
                      		bool outgoing = index.data(Qt::UserRole + 3).toString() == "Outgoing";
                      
                      		QTextDocument bodydoc;
                      		QTextOption textOption(bodydoc.defaultTextOption());
                      		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                      		bodydoc.setDefaultTextOption(textOption);
                      #if defined (Q_OS_OSX)
                      		bodydoc.setDefaultFont(QFont("Roboto", 13));
                      #elif defined (Q_OS_WIN)
                      		bodydoc.setDefaultFont(QFont("Roboto", 8));
                      #endif
                      		QString bodytext(index.data(Qt::DisplayRole).toString());
                      		bodydoc.setHtml(bodytext);
                      
                      		qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
                      		bodydoc.setTextWidth(contentswidth);
                      
                      		qreal bodyheight = bodydoc.size().height();
                      
                      		painter->save();
                      		painter->setRenderHint(QPainter::Antialiasing);
                      
                      		painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                      
                      		// Start drawing the chat bubble, but we don't use it. This is just to get the correct horizontal offset position for the
                      		// name and/or timestamp text
                      		QPainterPath pointie;
                      
                      		// left bottom
                      		pointie.moveTo(0, bodyheight + d_toppadding + d_bottompadding);
                      
                      		// right bottom
                      		pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - d_radius, bodyheight + d_toppadding + d_bottompadding);
                      
                      		pointie.closeSubpath();
                      
                      		if (outgoing) painter->translate(option.rect.width() - pointie.boundingRect().width() - d_horizontalmargin - d_pointerwidth, 0);
                      
                      		// uncomment to see the area provided to paint this item
                      		// painter->drawRect(option.rect);
                      
                      		QAbstractTextDocumentLayout::PaintContext ctx;
                      
                      		ctx.palette.setColor(QPalette::Text, QColor("grey"));
                      
                      		// draw body text
                      		painter->translate((outgoing ? 0 : d_pointerwidth + d_leftpadding), 0);
                      
                      		bodydoc.documentLayout()->draw(painter, ctx);
                      
                      		painter->restore();
                      	}
                      
                      	else
                      	{
                      		QTextDocument bodydoc;
                      		QTextOption textOption(bodydoc.defaultTextOption());
                      		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                      		bodydoc.setDefaultTextOption(textOption);
                      #if defined (Q_OS_OSX)
                      		bodydoc.setDefaultFont(QFont("Roboto", 14));
                      #elif defined (Q_OS_WIN)
                      		bodydoc.setDefaultFont(QFont("Roboto", 9));
                      #endif
                      		QString bodytext(index.data(Qt::DisplayRole).toString());
                      		bodydoc.setHtml(bodytext);
                      
                      		if ( index.data(Qt::UserRole + 1).toString().contains("Status") )
                      		{
                      			painter->save();
                      			painter->setRenderHint(QPainter::Antialiasing);
                      
                      			painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                      			//painter->drawRect(option.rect);
                      
                      			QAbstractTextDocumentLayout::PaintContext ctx;
                      
                      			ctx.palette.setColor(QPalette::Text, QColor("grey"));
                      
                      			// draw body text
                      			painter->translate((false ? 0 : d_pointerwidth) + d_leftpadding, 0);
                      
                      			bodydoc.documentLayout()->draw(painter, ctx);
                      
                      			painter->restore();
                      		}
                      
                      		else
                      		{
                      			qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
                      			bodydoc.setTextWidth(contentswidth);
                      
                      			qreal bodyheight = bodydoc.size().height();
                      
                      			bool outgoing = index.data(Qt::UserRole + 1).toString() == "Outgoing";
                      			QString colour = index.data(Qt::UserRole + 2).toString();
                      
                      			painter->save();
                      			painter->setRenderHint(QPainter::Antialiasing);
                      
                      			// uncomment to see the area provided to paint this item
                      			//painter->drawRect(option.rect);
                      
                      			painter->translate(option.rect.left() + d_horizontalmargin, option.rect.top() + ((index.row() == 0) ? d_verticalmargin : 0));
                      
                      			// background color for chat bubble
                      			QColor bgcolor(colour);
                      			//  if (outgoing)
                      			//	bgcolor = "#DDDDDD";
                      
                      			// create chat bubble
                      			QPainterPath pointie;
                      
                      			// left bottom
                      			pointie.moveTo(0, bodyheight + d_toppadding + d_bottompadding);
                      
                      			// right bottom
                      			pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - d_radius,
                      						 bodyheight + d_toppadding + d_bottompadding);
                      			pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius,
                      						bodyheight + d_toppadding + d_bottompadding - 2 * d_radius,
                      						2 * d_radius, 2 * d_radius, 270, 90);
                      
                      			// right top
                      			pointie.lineTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding, 0 + d_radius);
                      			pointie.arcTo(0 + contentswidth + d_pointerwidth + d_leftpadding + d_rightpadding - 2 * d_radius, 0,
                      						2 * d_radius, 2 * d_radius, 0, 90);
                      
                      			// left top
                      			pointie.lineTo(0 + d_pointerwidth + d_radius, 0);
                      			pointie.arcTo(0 + d_pointerwidth, 0, 2 * d_radius, 2 * d_radius, 90, 90);
                      
                      			// left bottom almost (here is the pointie)
                      			pointie.lineTo(0 + d_pointerwidth, bodyheight + d_toppadding + d_bottompadding - d_pointerheight);
                      			pointie.closeSubpath();
                      
                      			// rotate bubble for outgoing messages
                      			if (outgoing)
                      			{
                      				painter->translate(option.rect.width() - pointie.boundingRect().width() - d_horizontalmargin - d_pointerwidth, 0);
                      				painter->translate(pointie.boundingRect().center());
                      				painter->rotate(180);
                      				painter->translate(-pointie.boundingRect().center());
                      			}
                      
                      			// now paint it!
                      			painter->setPen(QPen(bgcolor));
                      			painter->drawPath(pointie);
                      			painter->fillPath(pointie, QBrush(bgcolor));
                      
                      			// rotate back or painter is going to paint the text rotated...
                      			if (outgoing)
                      			{
                      				painter->translate(pointie.boundingRect().center());
                      				painter->rotate(-180);
                      				painter->translate(-pointie.boundingRect().center());
                      			}
                      
                      			// set text color used to draw message body
                      			QAbstractTextDocumentLayout::PaintContext ctx;
                      
                      			ctx.palette.setColor(QPalette::Text, QColor("black"));
                      
                      			// draw body text
                      			painter->translate((outgoing ? 0 : d_pointerwidth) + d_leftpadding, 0);
                      			bodydoc.documentLayout()->draw(painter, ctx);
                      
                      			painter->restore();
                      		}
                      	}
                      }
                      
                      inline QSize ChatBubbleDelegate::sizeHint(QStyleOptionViewItem const &option, QModelIndex const &index) const
                      {
                      	if (!index.data(Qt::UserRole + 3).toString().isEmpty())
                      	{
                      		QTextDocument bodydoc;
                      		QTextOption textOption(bodydoc.defaultTextOption());
                      		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                      		bodydoc.setDefaultTextOption(textOption);
                      #if defined (Q_OS_OSX)
                      		bodydoc.setDefaultFont(QFont("Roboto", 13));
                      #elif defined (Q_OS_WIN)
                      		bodydoc.setDefaultFont(QFont("Roboto", 8));
                      #endif
                      		QString bodytext(index.data(Qt::DisplayRole).toString());
                      		bodydoc.setHtml(bodytext);
                      
                      		QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                      			 bodydoc.size().height() + d_bottompadding);
                      
                      		return size;
                      	}
                      
                      	else
                      	{
                      		QTextDocument bodydoc;
                      		QTextOption textOption(bodydoc.defaultTextOption());
                      		textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
                      		bodydoc.setDefaultTextOption(textOption);
                      #if defined (Q_OS_OSX)
                      		bodydoc.setDefaultFont(QFont("Roboto", 14));
                      #elif defined (Q_OS_WIN)
                      		bodydoc.setDefaultFont(QFont("Roboto", 9));
                      #endif
                      		QString bodytext(index.data(Qt::DisplayRole).toString());
                      		bodydoc.setHtml(bodytext);
                      
                      		// the width of the contents are the (a fraction of the window width) minus (margins + padding + width of the bubble's tail)
                      		qreal contentswidth = option.rect.width() * d_widthfraction - d_horizontalmargin - d_pointerwidth - d_leftpadding - d_rightpadding;
                      
                      		// set this available width on the text document
                      		bodydoc.setTextWidth(contentswidth);
                      
                      		if ( index.data(Qt::UserRole + 1).toString().contains("Status") )
                      		{
                      			QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                      				 bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin);
                      
                      			return size;
                      		}
                      
                      		else
                      		{
                      			if ( index.data(Qt::UserRole + 1).toString().contains("Outgoing") )
                      			{
                      				QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                      						bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin + 1);
                      
                      				return size;
                      			}
                      
                      			else
                      			{
                      				QSize size(bodydoc.idealWidth() + d_horizontalmargin + d_pointerwidth + d_leftpadding + d_rightpadding,
                      						bodydoc.size().height() + d_bottompadding + d_toppadding + d_verticalmargin + 1);
                      
                      				return size;
                      			}
                      		}
                      	}
                      }
                      
                      #endif // CHATBUBBLEDELEGATE_H
                      
                      

                      And this is my function for writing a message to the chat bubble:

                      void ChatTab::addMessage(QString userColour, QString username, QString message, int itemType, QString itemData)
                      {
                      	QString roleMode;
                      
                      	QColor color(userColour), newColor = color;
                      
                      	int lightnessAmount = 100;
                      
                      	// Convert carriage returns to html carriage returns so they are displayed properly
                      	message.replace("\n", "<br>");
                      
                      	// Crappy way of doing it, but make sure the user colour has a lightness colour of 230 or greater.
                      	// 230 is an arbitrary colour I have chosen
                      	for ( ;; )
                      	{
                      		if ( newColor.lightness() < 230 )
                      		{
                      			lightnessAmount += 10;
                      
                      			newColor = color.lighter(lightnessAmount);
                      		}
                      
                      		else break;
                      	}
                      
                      	// Print the senders name and or time stamp
                      	QString statusText;
                      
                      	if ( m_settings->username == username ) roleMode = "Outgoing";
                      	else roleMode = "Incoming";
                      
                      	if ( m_membersList->count() > 1  && roleMode == "Incoming" ) statusText = username + " ";
                      
                      	statusText += QTime::currentTime().toString( "HH:mm:ss");
                      
                      	struct ChatListItem *cli;
                      
                      	QStandardItem *item1 = new QStandardItem(statusText);
                      
                      	item1->setData(roleMode, Qt::UserRole + 3);
                      
                      	cli = new struct ChatListItem;
                      
                      	cli->itemData = "";
                      	cli->itemText = username;
                      	cli->itemType = CHATLISTITEMTYPE_STATUSMESSAGE;
                      
                      	item1->setData(  QVariant(QVariant::fromValue(static_cast<void*>(cli))), Qt::UserRole + 20 );
                      
                      	m_mymodel.appendRow(item1);
                      
                      	// Print the message
                      	if ( m_settings->username == username ) roleMode = "Outgoing";
                      	else roleMode = "Incoming";
                      
                      	QStandardItem *item2 = new QStandardItem(message);
                      
                      	item2->setData(roleMode, Qt::UserRole + 1);
                      
                      	item2->setData(newColor.name(), Qt::UserRole + 2);
                      
                      	item2->setData("", Qt::UserRole + 3);
                      
                      	cli = new struct ChatListItem;
                      
                      	cli->itemData = itemData;
                      	cli->itemText = message;
                      	cli->itemType = itemType;
                      
                      	item2->setData(  QVariant(QVariant::fromValue(static_cast<void*>(cli))), Qt::UserRole + 20 );
                      
                      	m_mymodel.appendRow(item2);
                      
                      	m_chatList->scrollToBottom();
                      
                      	addToTranscript( username, message );
                      }
                      

                      As I said in my email, it was a while ago when I wrote this. If you have any questions, let me know and I'll try and figure out what I was doing! Lol

                      Small Talk.png

                      This is a screen grab of my chat window. I couldn't grab it with the context menu being displayed, but each bubble has its own context menu which do various things depending on the content of the bubble. Such as downloading an image and displaying it, opening a web page, or copying the bubble text to the clipboard. HTML codes can also be embedded into the bubble and display correctly.
                      I hope all this helps.
                      Steve Q.

                      T Offline
                      T Offline
                      Taytoo
                      wrote on last edited by
                      #12

                      @steveq Thanks so much! Were u able to figure out how to make URLs, sandwiched between other text, clickable? e.g. if message was:

                      Hey, checkout this website: http://forum.qt.io, its really cool!

                      In that only the url should be click, and maybe show hover effect as well. I read some suggestions about using mouse hit-testing to achieve that, but it seems like a lot of work.

                      Another thing I really wanted to do was to make text selectable, but even that seems complicated.

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved