diff --git a/core/account.cpp b/core/account.cpp index 724bfa1..3acdeae 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -466,7 +466,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) case QXmppPresence::Available:{ QDateTime lastInteraction = p_presence.lastUserInteraction(); if (!lastInteraction.isValid()) { - lastInteraction = QDateTime::currentDateTime(); + lastInteraction = QDateTime::currentDateTimeUtc(); } emit addPresence(jid, resource, { {"lastActivity", lastInteraction}, @@ -791,7 +791,7 @@ bool Core::Account::handleGroupMessage(const QXmppMessage& msg, bool outgoing, b emit changeMessage(jid, oId, cData); } else { cnt->appendMessageToArchive(sMsg); - QDateTime minAgo = QDateTime::currentDateTime().addSecs(-60); + QDateTime minAgo = QDateTime::currentDateTimeUtc().addSecs(-60); if (sMsg.getTime() > minAgo) { //otherwise it's considered a delayed delivery, most probably MUC history receipt emit message(sMsg); } else { diff --git a/core/conference.cpp b/core/conference.cpp index 2d10273..f0b3b7d 100644 --- a/core/conference.cpp +++ b/core/conference.cpp @@ -140,7 +140,7 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) QXmppPresence pres = room->participantPresence(p_name); QDateTime lastInteraction = pres.lastUserInteraction(); if (!lastInteraction.isValid()) { - lastInteraction = QDateTime::currentDateTime(); + lastInteraction = QDateTime::currentDateTimeUtc(); } QXmppMucItem mi = pres.mucItem(); Archive::AvatarInfo info; @@ -181,7 +181,7 @@ void Core::Conference::onRoomParticipantChanged(const QString& p_name) QXmppPresence pres = room->participantPresence(p_name); QDateTime lastInteraction = pres.lastUserInteraction(); if (!lastInteraction.isValid()) { - lastInteraction = QDateTime::currentDateTime(); + lastInteraction = QDateTime::currentDateTimeUtc(); } QXmppMucItem mi = pres.mucItem(); handlePresence(pres); diff --git a/global.cpp b/global.cpp index ed75abc..9ece2ba 100644 --- a/global.cpp +++ b/global.cpp @@ -368,7 +368,7 @@ bool Shared::Message::change(const QMap& data) if (dItr != data.end()) { correctionDate = dItr.value().toDateTime(); } else { - correctionDate = QDateTime::currentDateTime(); //in case there is no information about time of this correction it's applied + correctionDate = QDateTime::currentDateTimeUtc(); //in case there is no information about time of this correction it's applied } if (!edited || lastModified < correctionDate) { originalMessage = body; @@ -393,7 +393,7 @@ QString Shared::generateUUID() void Shared::Message::setCurrentTime() { - time = QDateTime::currentDateTime(); + time = QDateTime::currentDateTimeUtc(); } QString Shared::Message::getOutOfBandUrl() const @@ -457,7 +457,7 @@ Shared::VCard::VCard(): birthday(), photoType(Avatar::empty), photoPath(), - receivingTime(QDateTime::currentDateTime()), + receivingTime(QDateTime::currentDateTimeUtc()), emails(), phones(), addresses() diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 50d5304..4e47abe 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -32,6 +32,7 @@ set(squawkUI_SRC utils/badge.cpp utils/progress.cpp utils/comboboxdelegate.cpp + utils/dropshadoweffect.cpp ) # Tell CMake to create the helloworld executable diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index de9d312..b968ce4 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -277,7 +277,7 @@ void Models::Contact::addMessage(const Shared::Message& data) Presence* pr = new Presence({}); pr->setName(res); pr->setAvailability(Shared::invisible); - pr->setLastActivity(QDateTime::currentDateTime()); + pr->setLastActivity(QDateTime::currentDateTimeUtc()); presences.insert(res, pr); appendChild(pr); pr->addMessage(data); diff --git a/ui/squawk.cpp b/ui/squawk.cpp index fd9b6ff..601e999 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -59,6 +59,9 @@ Squawk::Squawk(QWidget *parent) : //m_ui->mainToolBar->addWidget(m_ui->comboBox); setWindowTitle(tr("Contact list")); + if (testAttribute(Qt::WA_TranslucentBackground)) { + m_ui->roster->viewport()->setAutoFillBackground(false); + } } Squawk::~Squawk() { diff --git a/ui/utils/dropshadoweffect.cpp b/ui/utils/dropshadoweffect.cpp new file mode 100644 index 0000000..91a0258 --- /dev/null +++ b/ui/utils/dropshadoweffect.cpp @@ -0,0 +1,701 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "dropshadoweffect.h" +#include "QtMath" + +static const int tileSize = 32; +template +static +inline void qt_memrotate90_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride) +{ + sstride /= sizeof(T); + dstride /= sizeof(T); + const int pack = sizeof(quint32) / sizeof(T); + const int unaligned = + qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + if (unaligned) { + for (int x = startx; x >= stopx; --x) { + T *d = dest + (w - x - 1) * dstride; + for (int y = 0; y < unaligned; ++y) { + *d++ = src[y * sstride + x]; + } + } + } + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize + unaligned; + const int stopy = qMin(starty + tileSize, h - unoptimizedY); + for (int x = startx; x >= stopx; --x) { + quint32 *d = reinterpret_cast(dest + (w - x - 1) * dstride + starty); + for (int y = starty; y < stopy; y += pack) { + quint32 c = src[y * sstride + x]; + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(T) * 8 * i); + const T color = src[(y + i) * sstride + x]; + c |= color << shift; + } + *d++ = c; + } + } + } + if (unoptimizedY) { + const int starty = h - unoptimizedY; + for (int x = startx; x >= stopx; --x) { + T *d = dest + (w - x - 1) * dstride + starty; + for (int y = starty; y < h; ++y) { + *d++ = src[y * sstride + x]; + } + } + } + } +} +template +static +inline void qt_memrotate90_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest, + int dstride) +{ + const int numTilesX = (w + tileSize - 1) / tileSize; + const int numTilesY = (h + tileSize - 1) / tileSize; + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = w - tx * tileSize - 1; + const int stopx = qMax(startx - tileSize, 0); + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = ty * tileSize; + const int stopy = qMin(starty + tileSize, h); + for (int x = startx; x >= stopx; --x) { + T *d = (T *)((char*)dest + (w - x - 1) * dstride) + starty; + const char *s = (const char*)(src + x) + starty * sstride; + for (int y = starty; y < stopy; ++y) { + *d++ = *(const T *)(s); + s += sstride; + } + } + } + } +} +template +static +inline void qt_memrotate270_tiled(const T *src, int w, int h, int sstride, T *dest, int dstride) +{ + sstride /= sizeof(T); + dstride /= sizeof(T); + const int pack = sizeof(quint32) / sizeof(T); + const int unaligned = + qMin(uint((quintptr(dest) & (sizeof(quint32)-1)) / sizeof(T)), uint(h)); + const int restX = w % tileSize; + const int restY = (h - unaligned) % tileSize; + const int unoptimizedY = restY % pack; + const int numTilesX = w / tileSize + (restX > 0); + const int numTilesY = (h - unaligned) / tileSize + (restY >= pack); + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + if (unaligned) { + for (int x = startx; x < stopx; ++x) { + T *d = dest + x * dstride; + for (int y = h - 1; y >= h - unaligned; --y) { + *d++ = src[y * sstride + x]; + } + } + } + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - unaligned - ty * tileSize; + const int stopy = qMax(starty - tileSize, unoptimizedY); + for (int x = startx; x < stopx; ++x) { + quint32 *d = reinterpret_cast(dest + x * dstride + + h - 1 - starty); + for (int y = starty; y >= stopy; y -= pack) { + quint32 c = src[y * sstride + x]; + for (int i = 1; i < pack; ++i) { + const int shift = (sizeof(T) * 8 * i); + const T color = src[(y - i) * sstride + x]; + c |= color << shift; + } + *d++ = c; + } + } + } + if (unoptimizedY) { + const int starty = unoptimizedY - 1; + for (int x = startx; x < stopx; ++x) { + T *d = dest + x * dstride + h - 1 - starty; + for (int y = starty; y >= 0; --y) { + *d++ = src[y * sstride + x]; + } + } + } + } +} +template +static +inline void qt_memrotate270_tiled_unpacked(const T *src, int w, int h, int sstride, T *dest, + int dstride) +{ + const int numTilesX = (w + tileSize - 1) / tileSize; + const int numTilesY = (h + tileSize - 1) / tileSize; + for (int tx = 0; tx < numTilesX; ++tx) { + const int startx = tx * tileSize; + const int stopx = qMin(startx + tileSize, w); + for (int ty = 0; ty < numTilesY; ++ty) { + const int starty = h - 1 - ty * tileSize; + const int stopy = qMax(starty - tileSize, 0); + for (int x = startx; x < stopx; ++x) { + T *d = (T*)((char*)dest + x * dstride) + h - 1 - starty; + const char *s = (const char*)(src + x) + starty * sstride; + for (int y = starty; y >= stopy; --y) { + *d++ = *(const T*)s; + s -= sstride; + } + } + } + } +} +template +static +inline void qt_memrotate90_template(const T *src, int srcWidth, int srcHeight, int srcStride, + T *dest, int dstStride) +{ + #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer + if (sizeof(quint32) % sizeof(T) == 0) + qt_memrotate90_tiled(src, srcWidth, srcHeight, srcStride, dest, dstStride); + else + #endif + qt_memrotate90_tiled_unpacked(src, srcWidth, srcHeight, srcStride, dest, dstStride); +} +template <> +inline void qt_memrotate90_template(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride) +{ + // packed algorithm doesn't have any benefit for quint32 + qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +template <> +inline void qt_memrotate90_template(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride) +{ + qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +template +static +inline void qt_memrotate180_template(const T *src, int w, int h, int sstride, T *dest, int dstride) +{ + const char *s = (const char*)(src) + (h - 1) * sstride; + for (int dy = 0; dy < h; ++dy) { + T *d = reinterpret_cast((char *)(dest) + dy * dstride); + src = reinterpret_cast(s); + for (int dx = 0; dx < w; ++dx) { + d[dx] = src[w - 1 - dx]; + } + s -= sstride; + } +} +template +static +inline void qt_memrotate270_template(const T *src, int srcWidth, int srcHeight, int srcStride, + T *dest, int dstStride) +{ + #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + // packed algorithm assumes little endian and that sizeof(quint32)/sizeof(T) is an integer + if (sizeof(quint32) % sizeof(T) == 0) + qt_memrotate270_tiled(src, srcWidth, srcHeight, srcStride, dest, dstStride); + else + #endif + qt_memrotate270_tiled_unpacked(src, srcWidth, srcHeight, srcStride, dest, dstStride); +} +template <> +inline void qt_memrotate270_template(const quint32 *src, int w, int h, int sstride, quint32 *dest, int dstride) +{ + // packed algorithm doesn't have any benefit for quint32 + qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +template <> +inline void qt_memrotate270_template(const quint64 *src, int w, int h, int sstride, quint64 *dest, int dstride) +{ + qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); +} +#define QT_IMPL_MEMROTATE(type) \ +void qt_memrotate90(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate90_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate180(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate270(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate270_template(src, w, h, sstride, dest, dstride); \ +} +#define QT_IMPL_SIMPLE_MEMROTATE(type) \ +void qt_memrotate90(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate90_tiled_unpacked(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate180(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate180_template(src, w, h, sstride, dest, dstride); \ +} \ +void qt_memrotate270(const type *src, int w, int h, int sstride, \ +type *dest, int dstride) \ +{ \ + qt_memrotate270_tiled_unpacked(src, w, h, sstride, dest, dstride); \ +} +QT_IMPL_MEMROTATE(quint64) +QT_IMPL_MEMROTATE(quint32) +QT_IMPL_MEMROTATE(quint16) +QT_IMPL_MEMROTATE(quint8) +void qt_memrotate90_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90(srcPixels, w, h, sbpl, destPixels, dbpl); +} +void qt_memrotate180_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180(srcPixels, w, h, sbpl, destPixels, dbpl); +} +void qt_memrotate270_8(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270(srcPixels, w, h, sbpl, destPixels, dbpl); +} +void qt_memrotate90_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} +void qt_memrotate180_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} +void qt_memrotate270_16(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const ushort *)srcPixels, w, h, sbpl, (ushort *)destPixels, dbpl); +} +void qt_memrotate90_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} +void qt_memrotate180_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} +void qt_memrotate270_32(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const uint *)srcPixels, w, h, sbpl, (uint *)destPixels, dbpl); +} +void qt_memrotate90_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate90((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); +} +void qt_memrotate180_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate180((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); +} +void qt_memrotate270_64(const uchar *srcPixels, int w, int h, int sbpl, uchar *destPixels, int dbpl) +{ + qt_memrotate270((const quint64 *)srcPixels, w, h, sbpl, (quint64 *)destPixels, dbpl); +} + +#define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) ) +#define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) ) +const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); + +QImage qt_halfScaled(const QImage &source) +{ + if (source.width() < 2 || source.height() < 2) + return QImage(); + + QImage srcImage = source; + + if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) { + // assumes grayscale + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + dest.setDevicePixelRatio(source.devicePixelRatioF()); + + const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); + qsizetype sx = srcImage.bytesPerLine(); + qsizetype sx2 = sx << 1; + + uchar *dst = reinterpret_cast(dest.bits()); + qsizetype dx = dest.bytesPerLine(); + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const uchar *p1 = src; + const uchar *p2 = src + sx; + uchar *q = dst; + for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2) + *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2; + } + + return dest; + } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) { + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + dest.setDevicePixelRatio(source.devicePixelRatioF()); + + const uchar *src = reinterpret_cast(const_cast(srcImage).bits()); + qsizetype sx = srcImage.bytesPerLine(); + qsizetype sx2 = sx << 1; + + uchar *dst = reinterpret_cast(dest.bits()); + qsizetype dx = dest.bytesPerLine(); + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const uchar *p1 = src; + const uchar *p2 = src + sx; + uchar *q = dst; + for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) { + // alpha + q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3])); + // rgb + const quint16 p16_1 = (p1[2] << 8) | p1[1]; + const quint16 p16_2 = (p1[5] << 8) | p1[4]; + const quint16 p16_3 = (p2[2] << 8) | p2[1]; + const quint16 p16_4 = (p2[5] << 8) | p2[4]; + const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4)); + q[1] = result & 0xff; + q[2] = result >> 8; + } + } + + return dest; + } else if (source.format() != QImage::Format_ARGB32_Premultiplied + && source.format() != QImage::Format_RGB32) + { + srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + + QImage dest(source.width() / 2, source.height() / 2, srcImage.format()); + dest.setDevicePixelRatio(source.devicePixelRatioF()); + + const quint32 *src = reinterpret_cast(const_cast(srcImage).bits()); + qsizetype sx = srcImage.bytesPerLine() >> 2; + qsizetype sx2 = sx << 1; + + quint32 *dst = reinterpret_cast(dest.bits()); + qsizetype dx = dest.bytesPerLine() >> 2; + int ww = dest.width(); + int hh = dest.height(); + + for (int y = hh; y; --y, dst += dx, src += sx2) { + const quint32 *p1 = src; + const quint32 *p2 = src + sx; + quint32 *q = dst; + for (int x = ww; x; --x, q++, p1 += 2, p2 += 2) + *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1])); + } + + return dest; +} + +template +inline int qt_static_shift(int value) +{ + if (shift == 0) + return value; + else if (shift > 0) + return value << (uint(shift) & 0x1f); + else + return value >> (uint(-shift) & 0x1f); +} + +template +inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha) +{ + QRgb *pixel = (QRgb *)bptr; + + #define Z_MASK (0xff << zprec) + const int A_zprec = qt_static_shift(*pixel) & Z_MASK; + const int R_zprec = qt_static_shift(*pixel) & Z_MASK; + const int G_zprec = qt_static_shift(*pixel) & Z_MASK; + const int B_zprec = qt_static_shift(*pixel) & Z_MASK; + #undef Z_MASK + + const int zR_zprec = zR >> aprec; + const int zG_zprec = zG >> aprec; + const int zB_zprec = zB >> aprec; + const int zA_zprec = zA >> aprec; + + zR += alpha * (R_zprec - zR_zprec); + zG += alpha * (G_zprec - zG_zprec); + zB += alpha * (B_zprec - zB_zprec); + zA += alpha * (A_zprec - zA_zprec); + + #define ZA_MASK (0xff << (zprec + aprec)) + *pixel = + qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK) + | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK) + | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK) + | qt_static_shift<-zprec - aprec>(zB & ZA_MASK); + #undef ZA_MASK +} + +template +inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha) +{ + const int A_zprec = int(*(bptr)) << zprec; + const int z_zprec = z >> aprec; + z += alpha * (A_zprec - z_zprec); + *(bptr) = z >> (zprec + aprec); +} + +template +inline void qt_blurrow(QImage & im, int line, int alpha) +{ + uchar *bptr = im.scanLine(line); + + int zR = 0, zG = 0, zB = 0, zA = 0; + + if (alphaOnly && im.format() != QImage::Format_Indexed8) + bptr += alphaIndex; + + const int stride = im.depth() >> 3; + const int im_width = im.width(); + for (int index = 0; index < im_width; ++index) { + if (alphaOnly) + qt_blurinner_alphaOnly(bptr, zA, alpha); + else + qt_blurinner(bptr, zR, zG, zB, zA, alpha); + bptr += stride; + } + + bptr -= stride; + + for (int index = im_width - 2; index >= 0; --index) { + bptr -= stride; + if (alphaOnly) + qt_blurinner_alphaOnly(bptr, zA, alpha); + else + qt_blurinner(bptr, zR, zG, zB, zA, alpha); + } +} + +template +void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0) +{ + // halve the radius if we're using two passes + if (improvedQuality) + radius *= qreal(0.5); + + Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied + || img.format() == QImage::Format_RGB32 + || img.format() == QImage::Format_Indexed8 + || img.format() == QImage::Format_Grayscale8); + + // choose the alpha such that pixels at radius distance from a fully + // saturated pixel will have an alpha component of no greater than + // the cutOffIntensity + const qreal cutOffIntensity = 2; + int alpha = radius <= qreal(1e-5) + ? ((1 << aprec)-1) + : qRound((1<(img, row, alpha); + } + + QImage temp(img.height(), img.width(), img.format()); + temp.setDevicePixelRatio(img.devicePixelRatioF()); + if (transposed >= 0) { + if (img.depth() == 8) { + qt_memrotate270(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } else { + qt_memrotate270(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } + } else { + if (img.depth() == 8) { + qt_memrotate90(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } else { + qt_memrotate90(reinterpret_cast(img.bits()), + img.width(), img.height(), img.bytesPerLine(), + reinterpret_cast(temp.bits()), + temp.bytesPerLine()); + } + } + + img_height = temp.height(); + for (int row = 0; row < img_height; ++row) { + for (int i = 0; i <= int(improvedQuality); ++i) + qt_blurrow(temp, row, alpha); + } + + if (transposed == 0) { + if (img.depth() == 8) { + qt_memrotate90(reinterpret_cast(temp.bits()), + temp.width(), temp.height(), temp.bytesPerLine(), + reinterpret_cast(img.bits()), + img.bytesPerLine()); + } else { + qt_memrotate90(reinterpret_cast(temp.bits()), + temp.width(), temp.height(), temp.bytesPerLine(), + reinterpret_cast(img.bits()), + img.bytesPerLine()); + } + } else { + img = temp; + } +} + +PixmapFilter::PixmapFilter(QObject* parent):QObject(parent) {} +PixmapFilter::~PixmapFilter(){} +QRectF PixmapFilter::boundingRectFor(const QRectF &rect) const {return rect;} + +PixmapDropShadowFilter::PixmapDropShadowFilter(QObject *parent): + PixmapFilter(parent), + mColor(63, 63, 63, 180), + mRadius(1), + mThickness(2), + top(true), + right(true), + bottom(true), + left(true){} + +PixmapDropShadowFilter::~PixmapDropShadowFilter() {} +qreal PixmapDropShadowFilter::blurRadius() const {return mRadius;} +void PixmapDropShadowFilter::setBlurRadius(qreal radius) {mRadius = radius;} +QColor PixmapDropShadowFilter::color() const {return mColor;} +void PixmapDropShadowFilter::setColor(const QColor &color) {mColor = color;} +qreal PixmapDropShadowFilter::thickness() const {return mThickness;} +void PixmapDropShadowFilter::setThickness(qreal thickness) {mThickness = thickness;} +void PixmapDropShadowFilter::setFrame(bool ptop, bool pright, bool pbottom, bool pleft) +{ + top = ptop; + right = pright; + bottom = pbottom; + left = pleft; +} + +void DropShadowEffect::setThickness(qreal thickness) +{ + if (filter.thickness() == thickness) + return; + + filter.setThickness(thickness); + update(); +} + + +void PixmapDropShadowFilter::draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src) const +{ + if (px.isNull()) + return; + + QImage tmp({px.width(), px.height() + int(mThickness)}, QImage::Format_ARGB32_Premultiplied); + tmp.setDevicePixelRatio(px.devicePixelRatioF()); + tmp.fill(0); + QPainter tmpPainter(&tmp); + tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); + if (top) { + QRectF shadow(0, 0, px.width(), mThickness); + tmpPainter.fillRect(shadow, mColor); + } + if (right) { + QRectF shadow(px.width() - mThickness, 0, mThickness, px.height()); + tmpPainter.fillRect(shadow, mColor); + } + if (bottom) { + QRectF shadow(0, px.height() - mThickness, px.width(), mThickness * 2); //i have no idea why, but it leaves some unpainted stripe without some spare space + tmpPainter.fillRect(shadow, mColor); + } + if (left) { + QRectF shadow(0, 0, mThickness, px.height()); + tmpPainter.fillRect(shadow, mColor); + } + + expblur<12, 10, false>(tmp, mRadius, false, 0); + tmpPainter.end(); + + // Draw the actual pixmap... + p->drawPixmap(pos, px, src); + + // draw the blurred drop shadow... + p->drawImage(pos, tmp); +} + +qreal DropShadowEffect::blurRadius() const {return filter.blurRadius();} +void DropShadowEffect::setBlurRadius(qreal blurRadius) +{ + if (qFuzzyCompare(filter.blurRadius(), blurRadius)) + return; + + filter.setBlurRadius(blurRadius); + updateBoundingRect(); + emit blurRadiusChanged(blurRadius); +} + +void DropShadowEffect::setFrame(bool top, bool right, bool bottom, bool left) +{ + filter.setFrame(top, right, bottom, left); + update(); +} + + +QColor DropShadowEffect::color() const {return filter.color();} +void DropShadowEffect::setColor(const QColor &color) +{ + if (filter.color() == color) + return; + + filter.setColor(color); + update(); + emit colorChanged(color); +} + +void DropShadowEffect::draw(QPainter* painter) +{ + if (filter.blurRadius() <= 0 && filter.thickness() == 0) { + drawSource(painter); + return; + } + + PixmapPadMode mode = PadToEffectiveBoundingRect; + + // Draw pixmap in device coordinates to avoid pixmap scaling. + QPoint offset; + const QPixmap pixmap = sourcePixmap(Qt::DeviceCoordinates, &offset, mode); + if (pixmap.isNull()) + return; + + QTransform restoreTransform = painter->worldTransform(); + painter->setWorldTransform(QTransform()); + filter.draw(painter, offset, pixmap); + painter->setWorldTransform(restoreTransform); +} diff --git a/ui/utils/dropshadoweffect.h b/ui/utils/dropshadoweffect.h new file mode 100644 index 0000000..b2768b7 --- /dev/null +++ b/ui/utils/dropshadoweffect.h @@ -0,0 +1,93 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef DROPSHADOWEFFECT_H +#define DROPSHADOWEFFECT_H + +#include +#include +#include +#include + +class PixmapFilter : public QObject +{ + Q_OBJECT +public: + PixmapFilter(QObject *parent = nullptr); + virtual ~PixmapFilter() = 0; + + virtual QRectF boundingRectFor(const QRectF &rect) const; + virtual void draw(QPainter *painter, const QPointF &p, const QPixmap &src, const QRectF &srcRect = QRectF()) const = 0; +}; + +class PixmapDropShadowFilter : public PixmapFilter +{ + Q_OBJECT + +public: + PixmapDropShadowFilter(QObject *parent = nullptr); + ~PixmapDropShadowFilter(); + + void draw(QPainter *p, const QPointF &pos, const QPixmap &px, const QRectF &src = QRectF()) const override; + + qreal blurRadius() const; + void setBlurRadius(qreal radius); + + QColor color() const; + void setColor(const QColor &color); + + qreal thickness() const; + void setThickness(qreal thickness); + void setFrame(bool top, bool right, bool bottom, bool left); + +protected: + QColor mColor; + qreal mRadius; + qreal mThickness; + bool top; + bool right; + bool bottom; + bool left; +}; + +class DropShadowEffect : public QGraphicsEffect +{ + Q_OBJECT +public: + qreal blurRadius() const; + QColor color() const; + void setFrame(bool top, bool right, bool bottom, bool left); + void setThickness(qreal thickness); + +signals: + void blurRadiusChanged(qreal blurRadius); + void colorChanged(const QColor &color); + +public slots: + void setBlurRadius(qreal blurRadius); + void setColor(const QColor &color); + +protected: + void draw(QPainter * painter) override; + +protected: + PixmapDropShadowFilter filter; + +}; + +#endif // DROPSHADOWEFFECT_H diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp index d941b2e..2077863 100644 --- a/ui/utils/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -40,7 +40,6 @@ MessageLine::MessageLine(bool p_room, QWidget* parent): { setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0); - setBackgroundRole(QPalette::Base); layout->setSpacing(0); layout->addStretch(); } diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 0749daa..340fd82 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -18,13 +18,15 @@ #include "conversation.h" #include "ui_conversation.h" +#include "ui/utils/dropshadoweffect.h" + #include #include #include -#include #include #include #include +#include Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, const QString pRes, QWidget* parent): QWidget(parent), @@ -36,7 +38,6 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c m_ui(new Ui::Conversation()), ker(), scrollResizeCatcher(), - attachResizeCatcher(), vis(), thread(), statusIcon(0), @@ -54,15 +55,11 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c filesLayout = new FlowLayout(m_ui->filesPanel, 0); m_ui->filesPanel->setLayout(filesLayout); - m_ui->splitter->setSizes({300, 0}); - m_ui->splitter->setStretchFactor(1, 0); - statusIcon = m_ui->statusIcon; statusLabel = m_ui->statusLabel; connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed); connect(&scrollResizeCatcher, &Resizer::resized, this, &Conversation::onScrollResize); - connect(&attachResizeCatcher, &Resizer::resized, this, &Conversation::onAttachResize); connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize); connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize); connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); @@ -72,6 +69,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile); connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); connect(m_ui->clearButton, &QPushButton::clicked, this, &Conversation::onClearButton); + connect(m_ui->messageEditor->document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, + this, &Conversation::onTextEditDocSizeChanged); m_ui->messageEditor->installEventFilter(&ker); @@ -79,14 +78,13 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c m_ui->scrollArea->setWidget(line); vs->installEventFilter(&vis); - if (!tsb) { - vs->setBackgroundRole(QPalette::Base); - vs->setAutoFillBackground(true); + if (testAttribute(Qt::WA_TranslucentBackground)) { + m_ui->scrollArea->setAutoFillBackground(false); + m_ui->scrollArea->viewport()->setAutoFillBackground(false); } connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged); m_ui->scrollArea->installEventFilter(&scrollResizeCatcher); - m_ui->filesPanel->installEventFilter(&attachResizeCatcher); line->setMyAvatarPath(acc->getAvatarPath()); line->setMyName(acc->getName()); @@ -100,26 +98,12 @@ Conversation::~Conversation() void Conversation::applyVisualEffects() { - QGraphicsDropShadowEffect *e1 = new QGraphicsDropShadowEffect; + DropShadowEffect *e1 = new DropShadowEffect; e1->setBlurRadius(10); - e1->setXOffset(0); - e1->setYOffset(-2); e1->setColor(Qt::black); - m_ui->bl->setGraphicsEffect(e1); - - QGraphicsDropShadowEffect *e2 = new QGraphicsDropShadowEffect; - e2->setBlurRadius(7); - e2->setXOffset(0); - e2->setYOffset(2); - e2->setColor(Qt::black); - m_ui->ul->setGraphicsEffect(e2); - - QGraphicsDropShadowEffect *e3 = new QGraphicsDropShadowEffect; - e3->setBlurRadius(10); - e3->setXOffset(0); - e3->setYOffset(2); - e3->setColor(Qt::black); - m_ui->ut->setGraphicsEffect(e3); + e1->setThickness(1); + e1->setFrame(true, false, true, false); + m_ui->scrollArea->setGraphicsEffect(e1); } void Conversation::setName(const QString& name) @@ -404,21 +388,9 @@ void Conversation::setAvatar(const QString& path) } } -void Conversation::onAttachResize(const QSize& oldSize, const QSize& newSize) +void Conversation::onTextEditDocSizeChanged(const QSizeF& size) { - int oh = oldSize.height(); - int nh = newSize.height(); - - int d = oh - nh; - - if (d != 0) { - QList cs = m_ui->splitter->sizes(); - cs.first() += d; - cs.last() -=d; - - m_ui->splitter->setSizes(cs); - m_ui->scrollArea->verticalScrollBar()->setValue(m_ui->scrollArea->verticalScrollBar()->maximum()); - } + m_ui->messageEditor->setMaximumHeight(int(size.height())); } @@ -439,3 +411,4 @@ VisibilityCatcher::VisibilityCatcher(QWidget* parent): QObject(parent) { } + diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index d9f6dda..64d7341 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -107,9 +107,9 @@ protected slots: void onAttach(); void onFileSelected(); void onScrollResize(); - void onAttachResize(const QSize& oldSize, const QSize& newSize); void onBadgeClose(); void onClearButton(); + void onTextEditDocSizeChanged(const QSizeF& size); public: const bool isMuc; @@ -127,7 +127,6 @@ protected: QScopedPointer m_ui; KeyEnterReceiver ker; Resizer scrollResizeCatcher; - Resizer attachResizeCatcher; VisibilityCatcher vis; QString thread; QLabel* statusIcon; diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index 75ca7c5..4ff8b34 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -6,11 +6,11 @@ 0 0 - 572 - 484 + 520 + 658 - + 0 @@ -27,321 +27,192 @@ 0 - + - + 0 0 - - QFrame::NoFrame - - - QFrame::Plain - - - Qt::Vertical - - - false - - - - - 0 - 0 - + + + 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 100 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - false - - - Qt::AlignCenter - - - - - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - 0 - 0 - - - - - 60 - 60 - - - - - 50 - 50 - - - - - - - true - - - 5 - - - - - - - - - - - 0 - 0 - - - - - 0 - 2 - - - - - 0 - 2 - - - - true - - - - - - - true - - - QFrame::NoFrame - - + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 100 + + + + false + + + 0 - + 0 - - Qt::ScrollBarAlwaysOff + + 0 - - QAbstractScrollArea::AdjustIgnored + + 0 - - true - - - - - 0 - 0 - 572 - 118 - - - + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + false + + + Qt::AlignCenter + + + + + 0 0 - - 0 - - - 0 - - - 0 - + + + + + + + + + + + + + + - - - - - - - - 0 - 0 - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + 0 + 0 + + + + + 60 + 60 + + + + + 50 + 50 + + + + + + + true + + + 5 + + + + + + + + + + true + + + QFrame::NoFrame + + + 0 + + + 0 + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 520 + 389 + - - - 0 - 2 - - - - - 16777215 - 2 - - - - - 0 - 2 - - - - true - - - - - scrollArea - bl - ul - widget_3 - - - - - 0 - 0 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - + + + 0 + 0 @@ -354,156 +225,213 @@ 0 - - - - - - - - .. - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - .. - - - true - - - - - - - - - - - .. - - - true - - - - - - - - 0 - 0 - - - - - - - - .. - - - true - - - - - - - - true + + + + scrollArea + widget_3 + + + + + + + 0 + 0 + + + + false + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 0 - - - - - - - 0 - 0 - + + 0 - - - 0 - 2 - + + 0 - - - 16777215 - 2 - + + 0 - - - 0 - 2 - - - - true - - - - - - - - 0 - 0 - - - - - 0 - 30 - - - - - 0 - 30 - - - - QFrame::NoFrame - - - Type your message here... - - - - - messageEditor - ut - filesPanel - panel - + + + + + + + + .. + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + .. + + + true + + + + + + + + + + + .. + + + true + + + + + + + + 0 + 0 + + + + + + + + .. + + + true + + + + + + + + + + false + + + + + + + true + + + + 0 + 0 + + + + + 0 + 30 + + + + + 16777215 + 16777215 + + + + + 0 + 30 + + + + false + + + QTextEdit { +background-color: transparent +} + + + QFrame::NoFrame + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Liberation Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + false + + + Type your message here... + + + + + messageEditor + filesPanel + panel