Remove nick colors

remotes/origin/HEAD
Konstantinos Sideris 7 years ago
parent 18625d9d27
commit 43b1bdfe63
  1. 10
      .clang-format
  2. 1
      .gitignore
  3. 74
      include/TimelineItem.h
  4. 159
      include/TimelineView.h
  5. 42
      include/TimelineViewManager.h
  6. 438
      src/TimelineItem.cc
  7. 552
      src/TimelineView.cc
  8. 262
      src/TimelineViewManager.cc

@ -1,10 +1,12 @@
---
Language: Cpp
AccessModifierOffset: -8
AlignAfterOpenBracket: true
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignEscapedNewlinesLeft: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
BasedOnStyle: Mozilla
@ -12,10 +14,10 @@ BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Linux
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 120
ContinuationIndentWidth: 8
ColumnLimit: 100
CompactNamespaces: false
IndentCaseLabels: false
IndentWidth: 8
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
PointerAlignment: Right
UseTab: Always

1
.gitignore vendored

@ -77,3 +77,4 @@ result
*.dmg
dist/MacOS/nheko.app/Contents/MacOS/nheko
.clang

@ -32,65 +32,65 @@
#include "Text.h"
namespace events = matrix::events;
namespace msgs = matrix::events::messages;
namespace msgs = matrix::events::messages;
class TimelineItem : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
TimelineItem(const events::MessageEvent<msgs::Notice> &e,
bool with_sender,
const QString &color,
QWidget *parent = 0);
TimelineItem(const events::MessageEvent<msgs::Text> &e,
bool with_sender,
const QString &color,
QWidget *parent = 0);
TimelineItem(const events::MessageEvent<msgs::Notice> &e,
bool with_sender,
QWidget *parent = 0);
TimelineItem(const events::MessageEvent<msgs::Text> &e,
bool with_sender,
QWidget *parent = 0);
// For local messages.
TimelineItem(const QString &userid, const QString &color, QString body, QWidget *parent = 0);
TimelineItem(QString body, QWidget *parent = 0);
// For local messages.
TimelineItem(const QString &userid, QString body, QWidget *parent = 0);
TimelineItem(QString body, QWidget *parent = 0);
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, const QString &color, QWidget *parent);
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, QWidget *parent);
TimelineItem(ImageItem *img,
const events::MessageEvent<msgs::Image> &e,
bool with_sender,
QWidget *parent);
void setUserAvatar(const QImage &pixmap);
inline DescInfo descriptionMessage() const;
void setUserAvatar(const QImage &pixmap);
inline DescInfo descriptionMessage() const;
~TimelineItem();
~TimelineItem();
private:
void init();
void init();
void generateBody(const QString &body);
void generateBody(const QString &userid, const QString &color, const QString &body);
void generateTimestamp(const QDateTime &time);
QString descriptiveTime(const QDateTime &then);
void generateBody(const QString &body);
void generateBody(const QString &userid, const QString &body);
void generateTimestamp(const QDateTime &time);
QString descriptiveTime(const QDateTime &then);
void setupAvatarLayout(const QString &userName);
void setupSimpleLayout();
void setupAvatarLayout(const QString &userName);
void setupSimpleLayout();
QString replaceEmoji(const QString &body);
QString replaceEmoji(const QString &body);
DescInfo descriptionMsg_;
DescInfo descriptionMsg_;
QHBoxLayout *topLayout_;
QVBoxLayout *sideLayout_; // Avatar or Timestamp
QVBoxLayout *mainLayout_; // Header & Message body
QHBoxLayout *topLayout_;
QVBoxLayout *sideLayout_; // Avatar or Timestamp
QVBoxLayout *mainLayout_; // Header & Message body
QHBoxLayout *headerLayout_; // Username (&) Timestamp
QHBoxLayout *headerLayout_; // Username (&) Timestamp
Avatar *userAvatar_;
Avatar *userAvatar_;
QFont font_;
QFont font_;
QLabel *timestamp_;
QLabel *userName_;
QLabel *body_;
QLabel *timestamp_;
QLabel *userName_;
QLabel *body_;
};
inline DescInfo
TimelineItem::descriptionMessage() const
{
return descriptionMsg_;
return descriptionMsg_;
}

@ -32,122 +32,121 @@
#include "RoomInfoListItem.h"
#include "Text.h"
namespace msgs = matrix::events::messages;
namespace msgs = matrix::events::messages;
namespace events = matrix::events;
// Contains info about a message shown in the history view
// but not yet confirmed by the homeserver through sync.
struct PendingMessage {
int txn_id;
QString body;
QString event_id;
TimelineItem *widget;
PendingMessage(int txn_id, QString body, QString event_id, TimelineItem *widget)
: txn_id(txn_id)
, body(body)
, event_id(event_id)
, widget(widget)
{
}
int txn_id;
QString body;
QString event_id;
TimelineItem *widget;
PendingMessage(int txn_id, QString body, QString event_id, TimelineItem *widget)
: txn_id(txn_id)
, body(body)
, event_id(event_id)
, widget(widget)
{
}
};
// In which place new TimelineItems should be inserted.
enum class TimelineDirection {
Top,
Bottom,
Top,
Bottom,
};
class TimelineView : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
TimelineView(const Timeline &timeline,
QSharedPointer<MatrixClient> client,
const QString &room_id,
QWidget *parent = 0);
TimelineView(QSharedPointer<MatrixClient> client, const QString &room_id, QWidget *parent = 0);
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Image> &e,
const QString &color,
bool with_sender);
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Notice> &e,
const QString &color,
bool with_sender);
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Text> &e,
const QString &color,
bool with_sender);
// Add new events at the end of the timeline.
int addEvents(const Timeline &timeline);
void addUserTextMessage(const QString &msg, int txn_id);
void updatePendingMessage(int txn_id, QString event_id);
void scrollDown();
TimelineView(const Timeline &timeline,
QSharedPointer<MatrixClient> client,
const QString &room_id,
QWidget *parent = 0);
TimelineView(QSharedPointer<MatrixClient> client,
const QString &room_id,
QWidget *parent = 0);
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Image> &e,
bool with_sender);
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Notice> &e,
bool with_sender);
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Text> &e,
bool with_sender);
// Add new events at the end of the timeline.
int addEvents(const Timeline &timeline);
void addUserTextMessage(const QString &msg, int txn_id);
void updatePendingMessage(int txn_id, QString event_id);
void scrollDown();
public slots:
void sliderRangeChanged(int min, int max);
void sliderMoved(int position);
void fetchHistory();
void sliderRangeChanged(int min, int max);
void sliderMoved(int position);
void fetchHistory();
// Add old events at the top of the timeline.
void addBackwardsEvents(const QString &room_id, const RoomMessages &msgs);
// Add old events at the top of the timeline.
void addBackwardsEvents(const QString &room_id, const RoomMessages &msgs);
signals:
void updateLastTimelineMessage(const QString &user, const DescInfo &info);
void updateLastTimelineMessage(const QString &user, const DescInfo &info);
private:
void init();
void removePendingMessage(const events::MessageEvent<msgs::Text> &e);
void addTimelineItem(TimelineItem *item, TimelineDirection direction);
void updateLastSender(const QString &user_id, TimelineDirection direction);
void notifyForLastEvent();
void init();
void removePendingMessage(const events::MessageEvent<msgs::Text> &e);
void addTimelineItem(TimelineItem *item, TimelineDirection direction);
void updateLastSender(const QString &user_id, TimelineDirection direction);
void notifyForLastEvent();
// Used to determine whether or not we should prefix a message with the sender's name.
bool isSenderRendered(const QString &user_id, TimelineDirection direction);
bool isPendingMessage(const events::MessageEvent<msgs::Text> &e, const QString &userid);
inline bool isDuplicate(const QString &event_id);
// Used to determine whether or not we should prefix a message with the sender's name.
bool isSenderRendered(const QString &user_id, TimelineDirection direction);
bool isPendingMessage(const events::MessageEvent<msgs::Text> &e, const QString &userid);
inline bool isDuplicate(const QString &event_id);
// Return nullptr if the event couldn't be parsed.
TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction);
// Return nullptr if the event couldn't be parsed.
TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction);
QVBoxLayout *top_layout_;
QVBoxLayout *scroll_layout_;
QVBoxLayout *top_layout_;
QVBoxLayout *scroll_layout_;
QScrollArea *scroll_area_;
ScrollBar *scrollbar_;
QWidget *scroll_widget_;
QScrollArea *scroll_area_;
ScrollBar *scrollbar_;
QWidget *scroll_widget_;
QString lastSender_;
QString firstSender_;
QString room_id_;
QString prev_batch_token_;
QString local_user_;
QString lastSender_;
QString firstSender_;
QString room_id_;
QString prev_batch_token_;
QString local_user_;
bool isPaginationInProgress_ = false;
bool isInitialized = false;
bool isTimelineFinished = false;
bool isInitialSync = true;
bool isPaginationScrollPending_ = false;
bool isPaginationInProgress_ = false;
bool isInitialized = false;
bool isTimelineFinished = false;
bool isInitialSync = true;
bool isPaginationScrollPending_ = false;
const int SCROLL_BAR_GAP = 400;
const int SCROLL_BAR_GAP = 400;
QTimer *paginationTimer_;
QTimer *paginationTimer_;
int scroll_height_ = 0;
int previous_max_height_ = 0;
int scroll_height_ = 0;
int previous_max_height_ = 0;
int oldPosition_;
int oldHeight_;
int oldPosition_;
int oldHeight_;
// The events currently rendered. Used for duplicate detection.
QMap<QString, bool> eventIds_;
QList<PendingMessage> pending_msgs_;
QSharedPointer<MatrixClient> client_;
// The events currently rendered. Used for duplicate detection.
QMap<QString, bool> eventIds_;
QList<PendingMessage> pending_msgs_;
QSharedPointer<MatrixClient> client_;
};
inline bool
TimelineView::isDuplicate(const QString &event_id)
{
return eventIds_.contains(event_id);
return eventIds_.contains(event_id);
}

@ -29,39 +29,37 @@
class TimelineViewManager : public QStackedWidget
{
Q_OBJECT
Q_OBJECT
public:
TimelineViewManager(QSharedPointer<MatrixClient> client, QWidget *parent);
~TimelineViewManager();
TimelineViewManager(QSharedPointer<MatrixClient> client, QWidget *parent);
~TimelineViewManager();
// Initialize with timeline events.
void initialize(const Rooms &rooms);
// Empty initialization.
void initialize(const QList<QString> &rooms);
void sync(const Rooms &rooms);
void clearAll();
// Initialize with timeline events.
void initialize(const Rooms &rooms);
// Empty initialization.
void initialize(const QList<QString> &rooms);
void sync(const Rooms &rooms);
void clearAll();
static QString chooseRandomColor();
static QString getUserColor(const QString &userid);
static QString displayName(const QString &userid);
static QString chooseRandomColor();
static QString displayName(const QString &userid);
static QMap<QString, QString> NICK_COLORS;
static QMap<QString, QString> DISPLAY_NAMES;
static QMap<QString, QString> DISPLAY_NAMES;
signals:
void unreadMessages(QString roomid, int count);
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
void unreadMessages(QString roomid, int count);
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
public slots:
void setHistoryView(const QString &room_id);
void sendTextMessage(const QString &msg);
void setHistoryView(const QString &room_id);
void sendTextMessage(const QString &msg);
private slots:
void messageSent(const QString &eventid, const QString &roomid, int txnid);
void messageSent(const QString &eventid, const QString &roomid, int txnid);
private:
QString active_room_;
QMap<QString, QSharedPointer<TimelineView>> views_;
QSharedPointer<MatrixClient> client_;
QString active_room_;
QMap<QString, QSharedPointer<TimelineView>> views_;
QSharedPointer<MatrixClient> client_;
};

@ -31,62 +31,62 @@ static const QRegExp URL_REGEX("((?:https?|ftp)://\\S+)");
static const QString URL_HTML = "<a href=\"\\1\" style=\"color: #333333\">\\1</a>";
namespace events = matrix::events;
namespace msgs = matrix::events::messages;
namespace msgs = matrix::events::messages;
void
TimelineItem::init()
{
userAvatar_ = nullptr;
timestamp_ = nullptr;
userName_ = nullptr;
body_ = nullptr;
userAvatar_ = nullptr;
timestamp_ = nullptr;
userName_ = nullptr;
body_ = nullptr;
font_.setPixelSize(conf::fontSize);
font_.setPixelSize(conf::fontSize);
QFontMetrics fm(font_);
QFontMetrics fm(font_);
topLayout_ = new QHBoxLayout(this);
sideLayout_ = new QVBoxLayout();
mainLayout_ = new QVBoxLayout();
headerLayout_ = new QHBoxLayout();
topLayout_ = new QHBoxLayout(this);
sideLayout_ = new QVBoxLayout();
mainLayout_ = new QVBoxLayout();
headerLayout_ = new QHBoxLayout();
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0);
topLayout_->setSpacing(0);
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0);
topLayout_->setSpacing(0);
topLayout_->addLayout(sideLayout_);
topLayout_->addLayout(mainLayout_, 1);
topLayout_->addLayout(sideLayout_);
topLayout_->addLayout(mainLayout_, 1);
sideLayout_->setMargin(0);
sideLayout_->setSpacing(0);
sideLayout_->setMargin(0);
sideLayout_->setSpacing(0);
mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0);
mainLayout_->setSpacing(0);
mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0);
mainLayout_->setSpacing(0);
headerLayout_->setMargin(0);
headerLayout_->setSpacing(conf::timeline::headerSpacing);
headerLayout_->setMargin(0);
headerLayout_->setSpacing(conf::timeline::headerSpacing);
}
/*
* For messages created locally. The avatar and the username are displayed.
*/
TimelineItem::TimelineItem(const QString &userid, const QString &color, QString body, QWidget *parent)
TimelineItem::TimelineItem(const QString &userid, QString body, QWidget *parent)
: QWidget(parent)
{
init();
descriptionMsg_ = { "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) };
init();
descriptionMsg_ = { "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) };
body.replace(URL_REGEX, URL_HTML);
auto displayName = TimelineViewManager::displayName(userid);
body.replace(URL_REGEX, URL_HTML);
auto displayName = TimelineViewManager::displayName(userid);
generateTimestamp(QDateTime::currentDateTime());
generateBody(displayName, color, body);
generateTimestamp(QDateTime::currentDateTime());
generateBody(displayName, body);
setupAvatarLayout(displayName);
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
mainLayout_->addWidget(body_);
mainLayout_->addLayout(headerLayout_);
mainLayout_->addWidget(body_);
AvatarProvider::resolve(userid, this);
AvatarProvider::resolve(userid, this);
}
/*
@ -95,323 +95,301 @@ TimelineItem::TimelineItem(const QString &userid, const QString &color, QString
TimelineItem::TimelineItem(QString body, QWidget *parent)
: QWidget(parent)
{
QSettings settings;
auto userid = settings.value("auth/user_id").toString();
QSettings settings;
auto userid = settings.value("auth/user_id").toString();
init();
descriptionMsg_ = { "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) };
init();
descriptionMsg_ = { "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) };
body.replace(URL_REGEX, URL_HTML);
body.replace(URL_REGEX, URL_HTML);
generateTimestamp(QDateTime::currentDateTime());
generateBody(body);
generateTimestamp(QDateTime::currentDateTime());
generateBody(body);
setupSimpleLayout();
setupSimpleLayout();
mainLayout_->addWidget(body_);
mainLayout_->addWidget(body_);
}
/*
* Used to display images. The avatar and the username are displayed.
*/
TimelineItem::TimelineItem(ImageItem *image,
const events::MessageEvent<msgs::Image> &event,
const QString &color,
QWidget *parent)
const events::MessageEvent<msgs::Image> &event,
bool with_sender,
QWidget *parent)
: QWidget(parent)
{
init();
init();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender());
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender());
QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(),
" sent an image",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(),
" sent an image",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
generateTimestamp(timestamp);
generateBody(displayName, color, "");
generateTimestamp(timestamp);
setupAvatarLayout(displayName);
auto imageLayout = new QHBoxLayout();
imageLayout->setMargin(0);
imageLayout->addWidget(image);
imageLayout->addStretch(1);
auto imageLayout = new QHBoxLayout();
imageLayout->addWidget(image);
imageLayout->addStretch(1);
if (with_sender) {
generateBody(displayName, "");
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
mainLayout_->addLayout(imageLayout);
mainLayout_->addLayout(headerLayout_);
AvatarProvider::resolve(event.sender(), this);
}
/*
* Used to display images. Only the image is displayed.
*/
TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, QWidget *parent)
: QWidget(parent)
{
init();
auto displayName = TimelineViewManager::displayName(event.sender());
AvatarProvider::resolve(event.sender(), this);
} else {
setupSimpleLayout();
}
QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(),
" sent an image",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
generateTimestamp(timestamp);
setupSimpleLayout();
auto imageLayout = new QHBoxLayout();
imageLayout->setMargin(0);
imageLayout->addWidget(image);
imageLayout->addStretch(1);
mainLayout_->addLayout(imageLayout);
mainLayout_->addLayout(imageLayout);
}
/*
* Used to display remote notice messages.
*/
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
bool with_sender,
const QString &color,
QWidget *parent)
bool with_sender,
QWidget *parent)
: QWidget(parent)
{
init();
descriptionMsg_ = { TimelineViewManager::displayName(event.sender()),
event.sender(),
" sent a notification",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
init();
descriptionMsg_ = { TimelineViewManager::displayName(event.sender()),
event.sender(),
" sent a notification",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
generateTimestamp(timestamp);
generateTimestamp(timestamp);
body.replace(URL_REGEX, URL_HTML);
body = "<i style=\"color: #565E5E\">" + body + "</i>";
body.replace(URL_REGEX, URL_HTML);
body = "<i style=\"color: #565E5E\">" + body + "</i>";
if (with_sender) {
auto displayName = TimelineViewManager::displayName(event.sender());
if (with_sender) {
auto displayName = TimelineViewManager::displayName(event.sender());
generateBody(displayName, color, body);
setupAvatarLayout(displayName);
generateBody(displayName, body);
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
mainLayout_->addLayout(headerLayout_);
AvatarProvider::resolve(event.sender(), this);
} else {
generateBody(body);
setupSimpleLayout();
}
AvatarProvider::resolve(event.sender(), this);
} else {
generateBody(body);
setupSimpleLayout();
}
mainLayout_->addWidget(body_);
mainLayout_->addWidget(body_);
}
/*
* Used to display remote text messages.
*/
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event,
bool with_sender,
const QString &color,
QWidget *parent)
bool with_sender,
QWidget *parent)
: QWidget(parent)
{
init();
init();
auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender());
auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender());
QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(),
QString(": %1").arg(body),
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(),
QString(": %1").arg(body),
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) };
generateTimestamp(timestamp);
generateTimestamp(timestamp);
body.replace(URL_REGEX, URL_HTML);
body.replace(URL_REGEX, URL_HTML);
if (with_sender) {
auto displayName = TimelineViewManager::displayName(event.sender());
if (with_sender) {
auto displayName = TimelineViewManager::displayName(event.sender());
generateBody(displayName, color, body);
setupAvatarLayout(displayName);
generateBody(displayName, body);
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
mainLayout_->addLayout(headerLayout_);
AvatarProvider::resolve(event.sender(), this);
} else {
generateBody(body);
setupSimpleLayout();
}
AvatarProvider::resolve(event.sender(), this);
} else {
generateBody(body);
setupSimpleLayout();
}
mainLayout_->addWidget(body_);
mainLayout_->addWidget(body_);
}
// Only the body is displayed.
void
TimelineItem::generateBody(const QString &body)
{
QString content("<span style=\"color: black;\"> %1 </span>");
QString content("<span style=\"color: black;\"> %1 </span>");
body_ = new QLabel(this);
body_->setFont(font_);
body_->setWordWrap(true);
body_->setText(content.arg(replaceEmoji(body)));
body_->setMargin(0);
body_ = new QLabel(this);
body_->setFont(font_);
body_->setWordWrap(true);
body_->setText(content.arg(replaceEmoji(body)));
body_->setMargin(0);
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
body_->setOpenExternalLinks(true);
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
body_->setOpenExternalLinks(true);
}
// The username/timestamp is displayed along with the message body.
void
TimelineItem::generateBody(const QString &userid, const QString &color, const QString &body)
TimelineItem::generateBody(const QString &userid, const QString &body)
{
auto sender = userid;
auto sender = userid;
// TODO: Fix this by using a UserId type.
if (userid.split(":")[0].split("@").size() > 1)
sender = userid.split(":")[0].split("@")[1];
// TODO: Fix this by using a UserId type.
if (userid.split(":")[0].split("@").size() > 1)
sender = userid.split(":")[0].split("@")[1];
QString userContent("<span style=\"color: %1\"> %2 </span>");
QString bodyContent("<span style=\"color: #171717;\"> %1 </span>");
QString userContent("<span style=\"color: #171717\"> %1 </span>");
QString bodyContent("<span style=\"color: #171717;\"> %1 </span>");
QFont usernameFont = font_;
usernameFont.setBold(true);
QFont usernameFont = font_;
usernameFont.setBold(true);
userName_ = new QLabel(this);
userName_->setFont(usernameFont);
userName_->setText(userContent.arg(color).arg(sender));
userName_ = new QLabel(this);
userName_->setFont(usernameFont);
userName_->setText(userContent.arg(sender));
if (body.isEmpty())
return;
if (body.isEmpty())
return;
body_ = new QLabel(this);
body_->setFont(font_);
body_->setWordWrap(true);
body_->setText(bodyContent.arg(replaceEmoji(body)));
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
body_->setOpenExternalLinks(true);
body_->setMargin(0);
body_ = new QLabel(this);
body_->setFont(font_);
body_->setWordWrap(true);
body_->setText(bodyContent.arg(replaceEmoji(body)));
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
body_->setOpenExternalLinks(true);
body_->setMargin(0);
}
void
TimelineItem::generateTimestamp(const QDateTime &time)
{
QString msg("<span style=\"color: #5d6565;\"> %1 </span>");
QString msg("<span style=\"color: #5d6565;\"> %1 </span>");
QFont timestampFont;
timestampFont.setPixelSize(conf::timeline::fonts::timestamp);
QFont timestampFont;
timestampFont.setPixelSize(conf::timeline::fonts::timestamp);
QFontMetrics fm(timestampFont);
int topMargin = QFontMetrics(font_).ascent() - fm.ascent();
QFontMetrics fm(timestampFont);
int topMargin = QFontMetrics(font_).ascent() - fm.ascent();
timestamp_ = new QLabel(this);
timestamp_->setFont(timestampFont);
timestamp_->setText(msg.arg(time.toString("HH:mm")));
timestamp_->setContentsMargins(0, topMargin, 0, 0);
timestamp_ = new QLabel(this);
timestamp_->setFont(timestampFont);
timestamp_->setText(msg.arg(time.toString("HH:mm")));
timestamp_->setContentsMargins(0, topMargin, 0, 0);
}
QString
TimelineItem::replaceEmoji(const QString &body)
{
QString fmtBody = "";
for (auto &c : body) {
int code = c.unicode();
// TODO: Be more precise here.
if (code > 9000)
fmtBody += QString("<span style=\"font-family: Emoji One; font-size: %1px\">")
.arg(conf::emojiSize) +
QString(c) + "</span>";
else
fmtBody += c;
}
return fmtBody;
QString fmtBody = "";
for (auto &c : body) {
int code = c.unicode();
// TODO: Be more precise here.
if (code > 9000)
fmtBody += QString("<span style=\"font-family: Emoji "
"One; font-size: %1px\">")
.arg(conf::emojiSize) +
QString(c) + "</span>";
else
fmtBody += c;
}
return fmtBody;
}
void
TimelineItem::setupAvatarLayout(const QString &userName)
{
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0);
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin, 0, 0);
userAvatar_ = new Avatar(this);
userAvatar_->setLetter(QChar(userName[0]).toUpper());
userAvatar_->setBackgroundColor(QColor("#eee"));
userAvatar_->setTextColor(QColor("black"));
userAvatar_->setSize(conf::timeline::avatarSize);
userAvatar_ = new Avatar(this);
userAvatar_->setLetter(QChar(userName[0]).toUpper());
userAvatar_->setBackgroundColor(QColor("#eee"));
userAvatar_->setTextColor(QColor("black"));
userAvatar_->setSize(conf::timeline::avatarSize);
// TODO: The provided user name should be a UserId class
if (userName[0] == '@' && userName.size() > 1)
userAvatar_->setLetter(QChar(userName[1]).toUpper());
// TODO: The provided user name should be a UserId class
if (userName[0] == '@' && userName.size() > 1)
userAvatar_->setLetter(QChar(userName[1]).toUpper());
sideLayout_->addWidget(userAvatar_);
sideLayout_->addStretch(1);
sideLayout_->addWidget(userAvatar_);
sideLayout_->addStretch(1);
headerLayout_->addWidget(userName_);
headerLayout_->addWidget(timestamp_, 1);
headerLayout_->addWidget(userName_);
headerLayout_->addWidget(timestamp_, 1);
}
void
TimelineItem::setupSimpleLayout()
{
sideLayout_->addWidget(timestamp_);
sideLayout_->addWidget(timestamp_);
// Keep only the time in plain text.
QTextEdit htmlText(timestamp_->text());
QString plainText = htmlText.toPlainText();
// Keep only the time in plain text.
QTextEdit htmlText(timestamp_->text());
QString plainText = htmlText.toPlainText();
timestamp_->adjustSize();
timestamp_->adjustSize();
// Align the end of the avatar bubble with the end of the timestamp for
// messages with and without avatar. Otherwise their bodies would not be aligned.
int tsWidth = timestamp_->fontMetrics().width(plainText);
int offset = std::max(0, conf::timeline::avatarSize - tsWidth);
// Align the end of the avatar bubble with the end of the timestamp for
// messages with and without avatar. Otherwise their bodies would not be
// aligned.
int tsWidth = timestamp_->fontMetrics().width(plainText);
int offset = std::max(0, conf::timeline::avatarSize - tsWidth);
int defaultFontHeight = QFontMetrics(font_).ascent();
int defaultFontHeight = QFontMetrics(font_).ascent();
timestamp_->setAlignment(Qt::AlignTop);
timestamp_->setContentsMargins(offset + 1, defaultFontHeight - timestamp_->fontMetrics().ascent(), 0, 0);
topLayout_->setContentsMargins(conf::timeline::msgMargin, conf::timeline::msgMargin / 3, 0, 0);
timestamp_->setAlignment(Qt::AlignTop);
timestamp_->setContentsMargins(
offset + 1, defaultFontHeight - timestamp_->fontMetrics().ascent(), 0, 0);
topLayout_->setContentsMargins(
conf::timeline::msgMargin, conf::timeline::msgMargin / 3, 0, 0);
}
void
TimelineItem::setUserAvatar(const QImage &avatar)
{
if (userAvatar_ == nullptr)
return;
if (userAvatar_ == nullptr)
return;
userAvatar_->setImage(avatar);
userAvatar_->setImage(avatar);
}
QString
TimelineItem::descriptiveTime(const QDateTime &then)
{
auto now = QDateTime::currentDateTime();
auto now = QDateTime::currentDateTime();
auto days = then.daysTo(now);
auto days = then.daysTo(now);
if (days == 0) {
return then.toString("HH:mm");
} else if (days < 2) {
return QString("Yesterday");
} else if (days < 365) {
return then.toString("dd/MM");
}
if (days == 0)
return then.toString("HH:mm");
else if (days < 2)
return QString("Yesterday");
else if (days < 365)
return then.toString("dd/MM");
return then.toString("dd/MM/yy");
return then.toString("dd/MM/yy");
}
TimelineItem::~TimelineItem()

@ -32,455 +32,457 @@
#include "TimelineViewManager.h"
namespace events = matrix::events;
namespace msgs = matrix::events::messages;
namespace msgs = matrix::events::messages;
TimelineView::TimelineView(const Timeline &timeline,
QSharedPointer<MatrixClient> client,
const QString &room_id,
QWidget *parent)
QSharedPointer<MatrixClient> client,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{ room_id }
, client_{ client }
{
QSettings settings;
local_user_ = settings.value("auth/user_id").toString();
QSettings settings;
local_user_ = settings.value("auth/user_id").toString();
init();
addEvents(timeline);
init();
addEvents(timeline);
}
TimelineView::TimelineView(QSharedPointer<MatrixClient> client, const QString &room_id, QWidget *parent)
TimelineView::TimelineView(QSharedPointer<MatrixClient> client,
const QString &room_id,
QWidget *parent)
: QWidget(parent)
, room_id_{ room_id }
, client_{ client }
{
QSettings settings;
local_user_ = settings.value("auth/user_id").toString();
QSettings settings;
local_user_ = settings.value("auth/user_id").toString();
init();
client_->messages(room_id_, "");
init();
client_->messages(room_id_, "");
}
void
TimelineView::sliderRangeChanged(int min, int max)
{
Q_UNUSED(min);
Q_UNUSED(min);
if (!scroll_area_->verticalScrollBar()->isVisible()) {
scroll_area_->verticalScrollBar()->setValue(max);
return;
}
if (!scroll_area_->verticalScrollBar()->isVisible()) {
scroll_area_->verticalScrollBar()->setValue(max);
return;
}
if (max - scroll_area_->verticalScrollBar()->value() < SCROLL_BAR_GAP)
scroll_area_->verticalScrollBar()->setValue(max);
if (max - scroll_area_->verticalScrollBar()->value() < SCROLL_BAR_GAP)
scroll_area_->verticalScrollBar()->setValue(max);
if (isPaginationScrollPending_) {
isPaginationScrollPending_ = false;
if (isPaginationScrollPending_) {
isPaginationScrollPending_ = false;
int currentHeight = scroll_widget_->size().height();
int diff = currentHeight - oldHeight_;
int newPosition = oldPosition_ + diff;
int currentHeight = scroll_widget_->size().height();
int diff = currentHeight - oldHeight_;
int newPosition = oldPosition_ + diff;
// Keep the scroll bar to the bottom if we are coming from
// an scrollbar without height i.e scrollbar->value() == 0
if (oldPosition_ == 0)
newPosition = max;
// Keep the scroll bar to the bottom if we are coming from
// an scrollbar without height i.e scrollbar->value() == 0
if (oldPosition_ == 0)
newPosition = max;
scroll_area_->verticalScrollBar()->setValue(newPosition);
}
scroll_area_->verticalScrollBar()->setValue(newPosition);
}
}
void
TimelineView::fetchHistory()
{
bool hasEnoughMessages = scroll_area_->verticalScrollBar()->value() != 0;
bool hasEnoughMessages = scroll_area_->verticalScrollBar()->value() != 0;
if (!hasEnoughMessages && !isTimelineFinished) {
isPaginationInProgress_ = true;
client_->messages(room_id_, prev_batch_token_);
paginationTimer_->start(500);
return;
}
if (!hasEnoughMessages && !isTimelineFinished) {
isPaginationInProgress_ = true;
client_->messages(room_id_, prev_batch_token_);
paginationTimer_->start(500);
return;
}
paginationTimer_->stop();
paginationTimer_->stop();
}
void
TimelineView::scrollDown()
{
int current = scroll_area_->verticalScrollBar()->value();
int max = scroll_area_->verticalScrollBar()->maximum();
// The first time we enter the room move the scroll bar to the bottom.
if (!isInitialized) {
scroll_area_->verticalScrollBar()->setValue(max);
isInitialized = true;
return;
}
// If the gap is small enough move the scroll bar down. e.g when a new message appears.
if (max - current < SCROLL_BAR_GAP)
scroll_area_->verticalScrollBar()->setValue(max);
int current = scroll_area_->verticalScrollBar()->value();
int max = scroll_area_->verticalScrollBar()->maximum();
// The first time we enter the room move the scroll bar to the bottom.
if (!isInitialized) {
scroll_area_->verticalScrollBar()->setValue(max);
isInitialized = true;
return;
}
// If the gap is small enough move the scroll bar down. e.g when a new
// message appears.
if (max - current < SCROLL_BAR_GAP)
scroll_area_->verticalScrollBar()->setValue(max);
}
void
TimelineView::sliderMoved(int position)
{
if (!scroll_area_->verticalScrollBar()->isVisible())
return;
// The scrollbar is high enough so we can start retrieving old events.
if (position < SCROLL_BAR_GAP) {
if (isTimelineFinished)
return;
// Prevent user from moving up when there is pagination in progress.
// TODO: Keep a map of the event ids to filter out duplicates.
if (isPaginationInProgress_)
return;
isPaginationInProgress_ = true;
// FIXME: Maybe move this to TimelineViewManager to remove the extra calls?
client_->messages(room_id_, prev_batch_token_);
}
if (!scroll_area_->verticalScrollBar()->isVisible())
return;
// The scrollbar is high enough so we can start retrieving old events.
if (position < SCROLL_BAR_GAP) {
if (isTimelineFinished)
return;
// Prevent user from moving up when there is pagination in
// progress.
// TODO: Keep a map of the event ids to filter out duplicates.
if (isPaginationInProgress_)
return;
isPaginationInProgress_ = true;
// FIXME: Maybe move this to TimelineViewManager to remove the
// extra calls?
client_->messages(room_id_, prev_batch_token_);
}
}
void
TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msgs)
{
if (room_id_ != room_id)
return;
if (room_id_ != room_id)
return;
if (msgs.chunk().count() == 0) {
isTimelineFinished = true;
return;
}
if (msgs.chunk().count() == 0) {
isTimelineFinished = true;
return;
}
isTimelineFinished = false;
QList<TimelineItem *> items;
isTimelineFinished = false;
QList<TimelineItem *> items;
// Parse in reverse order to determine where we should not show sender's name.
auto it = msgs.chunk().constEnd();
while (it != msgs.chunk().constBegin()) {
--it;
// Parse in reverse order to determine where we should not show sender's
// name.
auto it = msgs.chunk().constEnd();
while (it != msgs.chunk().constBegin()) {
--it;
TimelineItem *item = parseMessageEvent((*it).toObject(), TimelineDirection::Top);
TimelineItem *item = parseMessageEvent((*it).toObject(), TimelineDirection::Top);
if (item != nullptr)
items.push_back(item);
}
if (item != nullptr)
items.push_back(item);
}
// Reverse again to render them.
std::reverse(items.begin(), items.end());
// Reverse again to render them.
std::reverse(items.begin(), items.end());
oldPosition_ = scroll_area_->verticalScrollBar()->value();
oldHeight_ = scroll_widget_->size().height();
oldPosition_ = scroll_area_->verticalScrollBar()->value();
oldHeight_ = scroll_widget_->size().height();
for (const auto &item : items)
addTimelineItem(item, TimelineDirection::Top);
for (const auto &item : items)
addTimelineItem(item, TimelineDirection::Top);
prev_batch_token_ = msgs.end();
isPaginationInProgress_ = false;
isPaginationScrollPending_ = true;
prev_batch_token_ = msgs.end();
isPaginationInProgress_ = false;
isPaginationScrollPending_ = true;
// Exclude the top stretch.
if (!msgs.chunk().isEmpty() && scroll_layout_->count() > 1)
notifyForLastEvent();
// Exclude the top stretch.
if (!msgs.chunk().isEmpty() && scroll_layout_->count() > 1)
notifyForLastEvent();
// If this batch is the first being rendered (i.e the first and the last events
// originate from this batch), set the last sender.
if (lastSender_.isEmpty() && !items.isEmpty())
lastSender_ = items.constFirst()->descriptionMessage().userid;
// If this batch is the first being rendered (i.e the first and the last
// events originate from this batch), set the last sender.
if (lastSender_.isEmpty() && !items.isEmpty())
lastSender_ = items.constFirst()->descriptionMessage().userid;
}
TimelineItem *
TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection direction)
{
events::EventType ty = events::extractEventType(event);
events::EventType ty = events::extractEventType(event);
if (ty == events::EventType::RoomMessage) {
events::MessageEventType msg_type = events::extractMessageEventType(event);
if (ty == events::EventType::RoomMessage) {
events::MessageEventType msg_type = events::extractMessageEventType(event);
if (msg_type == events::MessageEventType::Text) {
events::MessageEvent<msgs::Text> text;
if (msg_type == events::MessageEventType::Text) {
events::MessageEvent<msgs::Text> text;
try {
text.deserialize(event);
} catch (const DeserializationException &e) {
qWarning() << e.what() << event;
return nullptr;
}
try {
text.deserialize(event);
} catch (const DeserializationException &e) {
qWarning() << e.what() << event;
return nullptr;
}
if (isDuplicate(text.eventId()))
return nullptr;
if (isDuplicate(text.eventId()))
return nullptr;
eventIds_[text.eventId()] = true;
eventIds_[text.eventId()] = true;
if (isPendingMessage(text, local_user_)) {
removePendingMessage(text);
return nullptr;
}
if (isPendingMessage(text, local_user_)) {
removePendingMessage(text);
return nullptr;
}
auto with_sender = isSenderRendered(text.sender(), direction);
auto color = TimelineViewManager::getUserColor(text.sender());
auto with_sender = isSenderRendered(text.sender(), direction);
updateLastSender(text.sender(), direction);
updateLastSender(text.sender(), direction);
return createTimelineItem(text, color, with_sender);
} else if (msg_type == events::MessageEventType::Notice) {
events::MessageEvent<msgs::Notice> notice;
return createTimelineItem(text, with_sender);
} else if (msg_type == events::MessageEventType::Notice) {
events::MessageEvent<msgs::Notice> notice;
try {
notice.deserialize(event);
} catch (const DeserializationException &e) {
qWarning() << e.what() << event;
return nullptr;
}
try {
notice.deserialize(event);
} catch (const DeserializationException &e) {
qWarning() << e.what() << event;
return nullptr;
}
if (isDuplicate(notice.eventId()))
return nullptr;
;
if (isDuplicate(notice.eventId()))
return nullptr;
;
eventIds_[notice.eventId()] = true;
eventIds_[notice.eventId()] = true;
auto with_sender = isSenderRendered(notice.sender(), direction);
auto color = TimelineViewManager::getUserColor(notice.sender());
auto with_sender = isSenderRendered(notice.sender(), direction);
updateLastSender(notice.sender(), direction);
updateLastSender(notice.sender(), direction);
return createTimelineItem(notice, color, with_sender);
} else if (msg_type == events::MessageEventType::Image) {
events::MessageEvent<msgs::Image> img;
return createTimelineItem(notice, with_sender);
} else if (msg_type == events::MessageEventType::Image) {
events::MessageEvent<msgs::Image> img;
try {
img.deserialize(event);
} catch (const DeserializationException &e) {
qWarning() << e.what() << event;
return nullptr;
}
try {
img.deserialize(event);
} catch (const DeserializationException &e) {
qWarning() << e.what() << event;
return nullptr;
}
if (isDuplicate(img.eventId()))
return nullptr;
if (isDuplicate(img.eventId()))
return nullptr;
eventIds_[img.eventId()] = true;
eventIds_[img.eventId()] = true;
auto with_sender = isSenderRendered(img.sender(), direction);
auto color = TimelineViewManager::getUserColor(img.sender());
auto with_sender = isSenderRendered(img.sender(), direction);
updateLastSender(img.sender(), direction);
updateLastSender(img.sender(), direction);
return createTimelineItem(img, color, with_sender);
} else if (msg_type == events::MessageEventType::Unknown) {
qWarning() << "Unknown message type" << event;
return nullptr;
}
}
return createTimelineItem(img, with_sender);
} else if (msg_type == events::MessageEventType::Unknown) {
qWarning() << "Unknown message type" << event;
return nullptr;
}
}
return nullptr;
return nullptr;
}
int
TimelineView::addEvents(const Timeline &timeline)
{
int message_count = 0;
int message_count = 0;
QSettings settings;
QString localUser = settings.value("auth/user_id").toString();
QSettings settings;
QString localUser = settings.value("auth/user_id").toString();
for (const auto &event : timeline.events()) {
TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom);
for (const auto &event : timeline.events()) {
TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom);
if (item != nullptr) {
addTimelineItem(item, TimelineDirection::Bottom);
if (item != nullptr) {
addTimelineItem(item, TimelineDirection::Bottom);
if (localUser != event.toObject().value("sender").toString())
message_count += 1;
}
}
if (localUser != event.toObject().value("sender").toString())
message_count += 1;
}
}
if (isInitialSync) {
prev_batch_token_ = timeline.previousBatch();
isInitialSync = false;
if (isInitialSync) {
prev_batch_token_ = timeline.previousBatch();
isInitialSync = false;
client_->messages(room_id_, prev_batch_token_);
}
client_->messages(room_id_, prev_batch_token_);
}
// Exclude the top stretch.
if (!timeline.events().isEmpty() && scroll_layout_->count() > 1)
notifyForLastEvent();
// Exclude the top stretch.
if (!timeline.events().isEmpty() && scroll_layout_->count() > 1)
notifyForLastEvent();
return message_count;
return message_count;
}
void
TimelineView::init()
{
top_layout_ = new QVBoxLayout(this);
top_layout_->setSpacing(0);
top_layout_->setMargin(0);
top_layout_ = new QVBoxLayout(this);
top_layout_->setSpacing(0);
top_layout_->setMargin(0);
scroll_area_ = new QScrollArea(this);
scroll_area_->setWidgetResizable(true);
scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area_ = new QScrollArea(this);
scroll_area_->setWidgetResizable(true);
scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollbar_ = new ScrollBar(scroll_area_);
scroll_area_->setVerticalScrollBar(scrollbar_);
scrollbar_ = new ScrollBar(scroll_area_);
scroll_area_->setVerticalScrollBar(scrollbar_);
scroll_widget_ = new QWidget();
scroll_widget_ = new QWidget();
scroll_layout_ = new QVBoxLayout();
scroll_layout_->addStretch(1);
scroll_layout_->setSpacing(0);
scroll_layout_ = new QVBoxLayout();
scroll_layout_->addStretch(1);
scroll_layout_->setSpacing(0);
scroll_widget_->setLayout(scroll_layout_);
scroll_widget_->setLayout(scroll_layout_);
scroll_area_->setWidget(scroll_widget_);
scroll_area_->setWidget(scroll_widget_);
top_layout_->addWidget(scroll_area_);
top_layout_->addWidget(scroll_area_);
setLayout(top_layout_);
setLayout(top_layout_);
paginationTimer_ = new QTimer(this);
connect(paginationTimer_, &QTimer::timeout, this, &TimelineView::fetchHistory);
paginationTimer_ = new QTimer(this);
connect(paginationTimer_, &QTimer::timeout, this, &TimelineView::fetchHistory);
connect(client_.data(), &MatrixClient::messagesRetrieved, this, &TimelineView::addBackwardsEvents);
connect(client_.data(),
&MatrixClient::messagesRetrieved,
this,
&TimelineView::addBackwardsEvents);
connect(scroll_area_->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(sliderMoved(int)));
connect(scroll_area_->verticalScrollBar(),
SIGNAL(rangeChanged(int, int)),
this,
SLOT(sliderRangeChanged(int, int)));
connect(scroll_area_->verticalScrollBar(),
SIGNAL(valueChanged(int)),
this,
SLOT(sliderMoved(int)));
connect(scroll_area_->verticalScrollBar(),
SIGNAL(rangeChanged(int, int)),
this,
SLOT(sliderRangeChanged(int, int)));
}
void
TimelineView::updateLastSender(const QString &user_id, TimelineDirection direction)
{
if (direction == TimelineDirection::Bottom)
lastSender_ = user_id;
else
firstSender_ = user_id;
if (direction == TimelineDirection::Bottom)
lastSender_ = user_id;
else
firstSender_ = user_id;
}
bool
TimelineView::isSenderRendered(const QString &user_id, TimelineDirection direction)
{
if (direction == TimelineDirection::Bottom)
return lastSender_ != user_id;
else
return firstSender_ != user_id;
if (direction == TimelineDirection::Bottom)
return lastSender_ != user_id;
else
return firstSender_ != user_id;
}
TimelineItem *
TimelineView::createTimelineItem(const events::MessageEvent<msgs::Image> &event, const QString &color, bool with_sender)
TimelineView::createTimelineItem(const events::MessageEvent<msgs::Image> &event, bool with_sender)
{
auto image = new ImageItem(client_, event);
if (with_sender) {
auto item = new TimelineItem(image, event, color, scroll_widget_);
return item;
}
auto image = new ImageItem(client_, event);
auto item = new TimelineItem(image, event, with_sender, scroll_widget_);
auto item = new TimelineItem(image, event, scroll_widget_);
return item;
return item;
}
TimelineItem *
TimelineView::createTimelineItem(const events::MessageEvent<msgs::Notice> &event,
const QString &color,
bool with_sender)
TimelineView::createTimelineItem(const events::MessageEvent<msgs::Notice> &event, bool with_sender)
{
TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_);
return item;
TimelineItem *item = new TimelineItem(event, with_sender, scroll_widget_);
return item;
}
TimelineItem *
TimelineView::createTimelineItem(const events::MessageEvent<msgs::Text> &event, const QString &color, bool with_sender)
TimelineView::createTimelineItem(const events::MessageEvent<msgs::Text> &event, bool with_sender)
{
TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_);
return item;
TimelineItem *item = new TimelineItem(event, with_sender, scroll_widget_);
return item;
}
void
TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
{
if (direction == TimelineDirection::Bottom)
scroll_layout_->addWidget(item);
else
scroll_layout_->insertWidget(1, item);
if (direction == TimelineDirection::Bottom)
scroll_layout_->addWidget(item);
else
scroll_layout_->insertWidget(1, item);
}
void
TimelineView::updatePendingMessage(int txn_id, QString event_id)
{
for (auto &msg : pending_msgs_) {
if (msg.txn_id == txn_id) {
msg.event_id = event_id;
break;
}
}
for (auto &msg : pending_msgs_) {
if (msg.txn_id == txn_id) {
msg.event_id = event_id;
break;
}
}
}
bool
TimelineView::isPendingMessage(const events::MessageEvent<msgs::Text> &e, const QString &local_userid)
TimelineView::isPendingMessage(const events::MessageEvent<msgs::Text> &e,
const QString &local_userid)
{
if (e.sender() != local_userid)
return false;
if (e.sender() != local_userid)
return false;
for (const auto &msg : pending_msgs_) {
if (msg.event_id == e.eventId() || msg.body == e.content().body())
return true;
}
for (const auto &msg : pending_msgs_) {
if (msg.event_id == e.eventId() || msg.body == e.content().body())
return true;
}
return false;
return false;
}
void
TimelineView::removePendingMessage(const events::MessageEvent<msgs::Text> &e)
{
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) {
int index = std::distance(pending_msgs_.begin(), it);
if (it->event_id == e.eventId() || it->body == e.content().body()) {
pending_msgs_.removeAt(index);
break;
}
}
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) {
int index = std::distance(pending_msgs_.begin(), it);
if (it->event_id == e.eventId() || it->body == e.content().body()) {
pending_msgs_.removeAt(index);
break;
}
}
}
void
TimelineView::addUserTextMessage(const QString &body, int txn_id)
{
QSettings settings;
auto user_id = settings.value("auth/user_id").toString();
QSettings settings;
auto user_id = settings.value("auth/user_id").toString();
auto with_sender = lastSender_ != user_id;
auto color = TimelineViewManager::getUserColor(user_id);
auto with_sender = lastSender_ != user_id;
TimelineItem *view_item;
TimelineItem *view_item;
if (with_sender)
view_item = new TimelineItem(user_id, color, body, scroll_widget_);
else
view_item = new TimelineItem(body, scroll_widget_);
if (with_sender)
view_item = new TimelineItem(user_id, body, scroll_widget_);
else
view_item = new TimelineItem(body, scroll_widget_);
scroll_layout_->addWidget(view_item);
scroll_layout_->addWidget(view_item);
lastSender_ = user_id;
lastSender_ = user_id;
PendingMessage message(txn_id, body, "", view_item);
PendingMessage message(txn_id, body, "", view_item);
pending_msgs_.push_back(message);
pending_msgs_.push_back(message);
}
void
TimelineView::notifyForLastEvent()
{
auto lastItem = scroll_layout_->itemAt(scroll_layout_->count() - 1);
auto *lastTimelineItem = qobject_cast<TimelineItem *>(lastItem->widget());
auto lastItem = scroll_layout_->itemAt(scroll_layout_->count() - 1);
auto *lastTimelineItem = qobject_cast<TimelineItem *>(lastItem->widget());
if (lastTimelineItem)
emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage());
else
qWarning() << "Cast to TimelineView failed" << room_id_;
if (lastTimelineItem)
emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage());
else
qWarning() << "Cast to TimelineView failed" << room_id_;
}

@ -30,12 +30,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<MatrixClient> client, QW
: QStackedWidget(parent)
, client_(client)
{
setStyleSheet("QWidget { background: #f8fbfe; color: #e8e8e8; border: none;}");
setStyleSheet("QWidget { background: #f8fbfe; color: #e8e8e8; border: none;}");
connect(client_.data(),
SIGNAL(messageSent(const QString &, const QString &, int)),
this,
SLOT(messageSent(const QString &, const QString &, int)));
connect(client_.data(),
SIGNAL(messageSent(const QString &, const QString &, int)),
this,
SLOT(messageSent(const QString &, const QString &, int)));
}
TimelineViewManager::~TimelineViewManager()
@ -45,195 +45,179 @@ TimelineViewManager::~TimelineViewManager()
void
TimelineViewManager::messageSent(const QString &event_id, const QString &roomid, int txn_id)
{
// We save the latest valid transaction ID for later use.
QSettings settings;
settings.setValue("client/transaction_id", txn_id + 1);
// We save the latest valid transaction ID for later use.
QSettings settings;
settings.setValue("client/transaction_id", txn_id + 1);
auto view = views_[roomid];
view->updatePendingMessage(txn_id, event_id);
auto view = views_[roomid];
view->updatePendingMessage(txn_id, event_id);
}
void
TimelineViewManager::sendTextMessage(const QString &msg)
{
auto room_id = active_room_;
auto view = views_[room_id];
auto room_id = active_room_;
auto view = views_[room_id];
view->addUserTextMessage(msg, client_->transactionId());
client_->sendTextMessage(room_id, msg);
view->addUserTextMessage(msg, client_->transactionId());
client_->sendTextMessage(room_id, msg);
}
void
TimelineViewManager::clearAll()
{
NICK_COLORS.clear();
for (auto view : views_)
removeWidget(view.data());
for (auto view : views_)
removeWidget(view.data());
views_.clear();
views_.clear();
}
void
TimelineViewManager::initialize(const Rooms &rooms)
{
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
auto roomid = it.key();
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
auto roomid = it.key();
// Create a history view with the room events.
TimelineView *view = new TimelineView(it.value().timeline(), client_, it.key());
views_.insert(it.key(), QSharedPointer<TimelineView>(view));
// Create a history view with the room events.
TimelineView *view = new TimelineView(it.value().timeline(), client_, it.key());
views_.insert(it.key(), QSharedPointer<TimelineView>(view));
connect(view,
&TimelineView::updateLastTimelineMessage,
this,
&TimelineViewManager::updateRoomsLastMessage);
connect(view,
&TimelineView::updateLastTimelineMessage,
this,
&TimelineViewManager::updateRoomsLastMessage);
// Add the view in the widget stack.
addWidget(view);
}
// Add the view in the widget stack.
addWidget(view);
}
}
void
TimelineViewManager::initialize(const QList<QString> &rooms)
{
for (const auto &roomid : rooms) {
// Create a history view without any events.
TimelineView *view = new TimelineView(client_, roomid);
views_.insert(roomid, QSharedPointer<TimelineView>(view));
connect(view,
&TimelineView::updateLastTimelineMessage,
this,
&TimelineViewManager::updateRoomsLastMessage);
// Add the view in the widget stack.
addWidget(view);
}
for (const auto &roomid : rooms) {
// Create a history view without any events.
TimelineView *view = new TimelineView(client_, roomid);
views_.insert(roomid, QSharedPointer<TimelineView>(view));
connect(view,
&TimelineView::updateLastTimelineMessage,
this,
&TimelineViewManager::updateRoomsLastMessage);
// Add the view in the widget stack.
addWidget(view);
}
}
void
TimelineViewManager::sync(const Rooms &rooms)
{
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
auto roomid = it.key();
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
auto roomid = it.key();
if (!views_.contains(roomid)) {
qDebug() << "Ignoring event from unknown room" << roomid;
continue;
}
if (!views_.contains(roomid)) {
qDebug() << "Ignoring event from unknown room" << roomid;
continue;
}
auto view = views_.value(roomid);
auto view = views_.value(roomid);
int msgs_added = view->addEvents(it.value().timeline());
int msgs_added = view->addEvents(it.value().timeline());
if (msgs_added > 0) {
// TODO: When the app window gets active the current
// unread count (if any) should be cleared.
auto isAppActive = QApplication::activeWindow() != nullptr;
if (msgs_added > 0) {
// TODO: When the app window gets active the current
// unread count (if any) should be cleared.
auto isAppActive = QApplication::activeWindow() != nullptr;
if (roomid != active_room_ || !isAppActive)
emit unreadMessages(roomid, msgs_added);
}
}
if (roomid != active_room_ || !isAppActive)
emit unreadMessages(roomid, msgs_added);
}
}
}
void
TimelineViewManager::setHistoryView(const QString &room_id)
{
if (!views_.contains(room_id)) {
qDebug() << "Room ID from RoomList is not present in ViewManager" << room_id;
return;
}
if (!views_.contains(room_id)) {
qDebug() << "Room ID from RoomList is not present in ViewManager" << room_id;
return;
}
active_room_ = room_id;
auto view = views_.value(room_id);
active_room_ = room_id;
auto view = views_.value(room_id);
setCurrentWidget(view.data());
setCurrentWidget(view.data());
view->fetchHistory();
view->scrollDown();
view->fetchHistory();
view->scrollDown();
}
QMap<QString, QString> TimelineViewManager::NICK_COLORS;
QMap<QString, QString> TimelineViewManager::DISPLAY_NAMES;
QString
TimelineViewManager::chooseRandomColor()
{
std::random_device random_device;
std::mt19937 engine{ random_device() };
std::uniform_real_distribution<float> dist(0, 1);
float hue = dist(engine);
float saturation = 0.9;
float value = 0.7;
int hue_i = hue * 6;
float f = hue * 6 - hue_i;
float p = value * (1 - saturation);
float q = value * (1 - f * saturation);
float t = value * (1 - (1 - f) * saturation);
float r = 0;
float g = 0;
float b = 0;
if (hue_i == 0) {
r = value;
g = t;
b = p;
} else if (hue_i == 1) {
r = q;
g = value;
b = p;
} else if (hue_i == 2) {
r = p;
g = value;
b = t;
} else if (hue_i == 3) {
r = p;
g = q;
b = value;
} else if (hue_i == 4) {
r = t;
g = p;
b = value;
} else if (hue_i == 5) {
r = value;
g = p;
b = q;
}
int ri = r * 256;
int gi = g * 256;
int bi = b * 256;
QColor color(ri, gi, bi);
return color.name();
}
QString
TimelineViewManager::getUserColor(const QString &userid)
{
auto color = NICK_COLORS.value(userid);
if (color.isEmpty()) {
color = chooseRandomColor();
NICK_COLORS.insert(userid, color);
}
return color;
std::random_device random_device;
std::mt19937 engine{ random_device() };
std::uniform_real_distribution<float> dist(0, 1);
float hue = dist(engine);
float saturation = 0.9;
float value = 0.7;
int hue_i = hue * 6;
float f = hue * 6 - hue_i;
float p = value * (1 - saturation);
float q = value * (1 - f * saturation);
float t = value * (1 - (1 - f) * saturation);
float r = 0;
float g = 0;
float b = 0;
if (hue_i == 0) {
r = value;
g = t;
b = p;
} else if (hue_i == 1) {
r = q;
g = value;
b = p;
} else if (hue_i == 2) {
r = p;
g = value;
b = t;
} else if (hue_i == 3) {
r = p;
g = q;
b = value;
} else if (hue_i == 4) {
r = t;
g = p;
b = value;
} else if (hue_i == 5) {
r = value;
g = p;
b = q;
}
int ri = r * 256;
int gi = g * 256;
int bi = b * 256;
QColor color(ri, gi, bi);
return color.name();
}
QString
TimelineViewManager::displayName(const QString &userid)
{
if (DISPLAY_NAMES.contains(userid))
return DISPLAY_NAMES.value(userid);
if (DISPLAY_NAMES.contains(userid))
return DISPLAY_NAMES.value(userid);
return userid;
return userid;
}

Loading…
Cancel
Save