@ -0,0 +1,260 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "chatline.h" |
||||||
|
#include "chatlinecontent.h" |
||||||
|
|
||||||
|
#include <QDebug> |
||||||
|
#include <QGraphicsScene> |
||||||
|
|
||||||
|
ChatLine::ChatLine() |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
ChatLine::~ChatLine() |
||||||
|
{ |
||||||
|
for(ChatLineContent* c : content) |
||||||
|
{ |
||||||
|
if(c->scene()) |
||||||
|
c->scene()->removeItem(c); |
||||||
|
|
||||||
|
delete c; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::setRow(int idx) |
||||||
|
{ |
||||||
|
row = idx; |
||||||
|
|
||||||
|
for(int c = 0; c < static_cast<int>(content.size()); ++c) |
||||||
|
content[c]->setIndex(row, c); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::visibilityChanged(bool visible) |
||||||
|
{ |
||||||
|
if(isVisible != visible) |
||||||
|
{ |
||||||
|
for(ChatLineContent* c : content) |
||||||
|
c->visibilityChanged(visible); |
||||||
|
} |
||||||
|
|
||||||
|
isVisible = visible; |
||||||
|
} |
||||||
|
|
||||||
|
int ChatLine::getRow() const |
||||||
|
{ |
||||||
|
return row; |
||||||
|
} |
||||||
|
|
||||||
|
ChatLineContent *ChatLine::getContent(int col) const |
||||||
|
{ |
||||||
|
if(col < static_cast<int>(content.size()) && col >= 0) |
||||||
|
return content[col]; |
||||||
|
|
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
ChatLineContent *ChatLine::getContent(QPointF scenePos) const |
||||||
|
{ |
||||||
|
for(ChatLineContent* c: content) |
||||||
|
{ |
||||||
|
if(c->sceneBoundingRect().contains(scenePos)) |
||||||
|
return c; |
||||||
|
} |
||||||
|
|
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::removeFromScene() |
||||||
|
{ |
||||||
|
for(ChatLineContent* c : content) |
||||||
|
{ |
||||||
|
if(c->scene()) |
||||||
|
c->scene()->removeItem(c); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::addToScene(QGraphicsScene *scene) |
||||||
|
{ |
||||||
|
if(!scene) |
||||||
|
return; |
||||||
|
|
||||||
|
for(ChatLineContent* c : content) |
||||||
|
scene->addItem(c); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::setVisible(bool visible) |
||||||
|
{ |
||||||
|
for(ChatLineContent* c : content) |
||||||
|
c->setVisible(visible); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::selectionCleared() |
||||||
|
{ |
||||||
|
for(ChatLineContent* c : content) |
||||||
|
c->selectionCleared(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::selectionFocusChanged(bool focusIn) |
||||||
|
{ |
||||||
|
for(ChatLineContent* c : content) |
||||||
|
c->selectionFocusChanged(focusIn); |
||||||
|
} |
||||||
|
|
||||||
|
int ChatLine::getColumnCount() |
||||||
|
{ |
||||||
|
return content.size(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::updateBBox() |
||||||
|
{ |
||||||
|
bbox.setHeight(0); |
||||||
|
bbox.setWidth(width); |
||||||
|
|
||||||
|
for(ChatLineContent* c : content) |
||||||
|
bbox.setHeight(qMax(c->sceneBoundingRect().top() - bbox.top() + c->sceneBoundingRect().height(), bbox.height())); |
||||||
|
} |
||||||
|
|
||||||
|
QRectF ChatLine::sceneBoundingRect() const |
||||||
|
{ |
||||||
|
return bbox; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::addColumn(ChatLineContent* item, ColumnFormat fmt) |
||||||
|
{ |
||||||
|
if(!item) |
||||||
|
return; |
||||||
|
|
||||||
|
format.push_back(fmt); |
||||||
|
content.push_back(item); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::replaceContent(int col, ChatLineContent *lineContent) |
||||||
|
{ |
||||||
|
if(col >= 0 && col < static_cast<int>(content.size()) && lineContent) |
||||||
|
{ |
||||||
|
QGraphicsScene* scene = content[col]->scene(); |
||||||
|
delete content[col]; |
||||||
|
|
||||||
|
content[col] = lineContent; |
||||||
|
lineContent->setIndex(row, col); |
||||||
|
|
||||||
|
if(scene) |
||||||
|
scene->addItem(content[col]); |
||||||
|
|
||||||
|
layout(width, bbox.topLeft()); |
||||||
|
content[col]->visibilityChanged(isVisible); |
||||||
|
content[col]->update(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::layout(qreal w, QPointF scenePos) |
||||||
|
{ |
||||||
|
width = w; |
||||||
|
bbox.setTopLeft(scenePos); |
||||||
|
|
||||||
|
qreal fixedWidth = (content.size()-1) * columnSpacing; |
||||||
|
qreal varWidth = 0.0; // used for normalisation
|
||||||
|
|
||||||
|
for(int i = 0; i < static_cast<int>(format.size()); ++i) |
||||||
|
{ |
||||||
|
if(format[i].policy == ColumnFormat::FixedSize) |
||||||
|
fixedWidth += format[i].size; |
||||||
|
else |
||||||
|
varWidth += format[i].size; |
||||||
|
} |
||||||
|
|
||||||
|
if(varWidth == 0.0) |
||||||
|
varWidth = 1.0; |
||||||
|
|
||||||
|
qreal leftover = qMax(0.0, width - fixedWidth); |
||||||
|
|
||||||
|
qreal maxVOffset = 0.0; |
||||||
|
qreal xOffset = 0.0; |
||||||
|
qreal xPos[content.size()]; |
||||||
|
|
||||||
|
|
||||||
|
for(int i = 0; i < static_cast<int>(content.size()); ++i) |
||||||
|
{ |
||||||
|
// calculate the effective width of the current column
|
||||||
|
qreal width; |
||||||
|
if(format[i].policy == ColumnFormat::FixedSize) |
||||||
|
width = format[i].size; |
||||||
|
else |
||||||
|
width = format[i].size / varWidth * leftover; |
||||||
|
|
||||||
|
// set the width of the current column
|
||||||
|
content[i]->setWidth(width); |
||||||
|
|
||||||
|
// calculate horizontal alignment
|
||||||
|
qreal xAlign = 0.0; |
||||||
|
|
||||||
|
switch(format[i].hAlign) |
||||||
|
{ |
||||||
|
case ColumnFormat::Right: |
||||||
|
xAlign = width - content[i]->boundingRect().width(); |
||||||
|
break; |
||||||
|
case ColumnFormat::Center: |
||||||
|
xAlign = (width - content[i]->boundingRect().width()) / 2.0; |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
// reposition
|
||||||
|
xPos[i] = scenePos.x() + xOffset + xAlign; |
||||||
|
|
||||||
|
xOffset += width + columnSpacing; |
||||||
|
maxVOffset = qMax(maxVOffset, content[i]->getAscent()); |
||||||
|
} |
||||||
|
|
||||||
|
for(int i = 0; i < static_cast<int>(content.size()); ++i) |
||||||
|
{ |
||||||
|
// calculate vertical alignment
|
||||||
|
// vertical alignment may depend on width, so we do it in a second pass
|
||||||
|
qreal yOffset = maxVOffset - content[i]->getAscent(); |
||||||
|
|
||||||
|
// reposition
|
||||||
|
content[i]->setPos(xPos[i], scenePos.y() + yOffset); |
||||||
|
} |
||||||
|
|
||||||
|
updateBBox(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLine::moveBy(qreal deltaY) |
||||||
|
{ |
||||||
|
// reposition only
|
||||||
|
for(ChatLineContent* c : content) |
||||||
|
c->moveBy(0, deltaY); |
||||||
|
|
||||||
|
bbox.moveTop(bbox.top() + deltaY); |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLine::lessThanBSRectTop(const ChatLine::Ptr lhs, const qreal rhs) |
||||||
|
{ |
||||||
|
return lhs->sceneBoundingRect().top() < rhs; |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLine::lessThanBSRectBottom(const ChatLine::Ptr lhs, const qreal rhs) |
||||||
|
{ |
||||||
|
return lhs->sceneBoundingRect().bottom() < rhs; |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLine::lessThanRowIndex(const ChatLine::Ptr lhs, const ChatLine::Ptr rhs) |
||||||
|
{ |
||||||
|
return lhs->getRow() < rhs->getRow(); |
||||||
|
} |
@ -0,0 +1,110 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef CHATLINE_H |
||||||
|
#define CHATLINE_H |
||||||
|
|
||||||
|
#include <memory> |
||||||
|
#include <vector> |
||||||
|
#include <QPointF> |
||||||
|
#include <QRectF> |
||||||
|
|
||||||
|
class ChatLog; |
||||||
|
class ChatLineContent; |
||||||
|
class QGraphicsScene; |
||||||
|
class QStyleOptionGraphicsItem; |
||||||
|
|
||||||
|
struct ColumnFormat |
||||||
|
{ |
||||||
|
enum Policy { |
||||||
|
FixedSize, |
||||||
|
VariableSize, |
||||||
|
}; |
||||||
|
|
||||||
|
enum Align { |
||||||
|
Left, |
||||||
|
Center, |
||||||
|
Right, |
||||||
|
}; |
||||||
|
|
||||||
|
ColumnFormat() {} |
||||||
|
ColumnFormat(qreal s, Policy p, Align halign = Left) |
||||||
|
: size(s) |
||||||
|
, policy(p) |
||||||
|
, hAlign(halign) |
||||||
|
{} |
||||||
|
|
||||||
|
qreal size = 1.0; |
||||||
|
Policy policy = VariableSize; |
||||||
|
Align hAlign = Left; |
||||||
|
}; |
||||||
|
|
||||||
|
using ColumnFormats = QVector<ColumnFormat>; |
||||||
|
|
||||||
|
class ChatLine |
||||||
|
{ |
||||||
|
public: |
||||||
|
using Ptr = std::shared_ptr<ChatLine>; |
||||||
|
|
||||||
|
ChatLine(); |
||||||
|
virtual ~ChatLine(); |
||||||
|
|
||||||
|
QRectF sceneBoundingRect() const; |
||||||
|
|
||||||
|
void replaceContent(int col, ChatLineContent* lineContent); |
||||||
|
void layout(qreal width, QPointF scenePos); |
||||||
|
void moveBy(qreal deltaY); |
||||||
|
void removeFromScene(); |
||||||
|
void addToScene(QGraphicsScene* scene); |
||||||
|
void setVisible(bool visible); |
||||||
|
void selectionCleared(); |
||||||
|
void selectionFocusChanged(bool focusIn); |
||||||
|
|
||||||
|
int getColumnCount(); |
||||||
|
int getRow() const; |
||||||
|
|
||||||
|
ChatLineContent* getContent(int col) const; |
||||||
|
ChatLineContent* getContent(QPointF scenePos) const; |
||||||
|
|
||||||
|
bool isOverSelection(QPointF scenePos); |
||||||
|
|
||||||
|
//comparators
|
||||||
|
static bool lessThanBSRectTop(const ChatLine::Ptr lhs, const qreal rhs); |
||||||
|
static bool lessThanBSRectBottom(const ChatLine::Ptr lhs, const qreal rhs); |
||||||
|
static bool lessThanRowIndex(const ChatLine::Ptr lhs, const ChatLine::Ptr rhs); |
||||||
|
|
||||||
|
protected: |
||||||
|
friend class ChatLog; |
||||||
|
|
||||||
|
QPointF mapToContent(ChatLineContent* c, QPointF pos); |
||||||
|
|
||||||
|
void addColumn(ChatLineContent* item, ColumnFormat fmt); |
||||||
|
void updateBBox(); |
||||||
|
void setRow(int idx); |
||||||
|
void visibilityChanged(bool visible); |
||||||
|
|
||||||
|
private: |
||||||
|
int row = -1; |
||||||
|
std::vector<ChatLineContent*> content; |
||||||
|
std::vector<ColumnFormat> format; |
||||||
|
qreal width = 100.0; |
||||||
|
qreal columnSpacing = 15.0; |
||||||
|
QRectF bbox; |
||||||
|
bool isVisible = false; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // CHATLINE_H
|
@ -0,0 +1,88 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "chatlinecontent.h" |
||||||
|
|
||||||
|
void ChatLineContent::setIndex(int r, int c) |
||||||
|
{ |
||||||
|
row = r; |
||||||
|
col = c; |
||||||
|
} |
||||||
|
|
||||||
|
int ChatLineContent::getColumn() const |
||||||
|
{ |
||||||
|
return col; |
||||||
|
} |
||||||
|
|
||||||
|
int ChatLineContent::getRow() const |
||||||
|
{ |
||||||
|
return row; |
||||||
|
} |
||||||
|
|
||||||
|
int ChatLineContent::type() const |
||||||
|
{ |
||||||
|
return GraphicsItemType::ChatLineContentType; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContent::selectionMouseMove(QPointF) |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContent::selectionStarted(QPointF) |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContent::selectionCleared() |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContent::selectionDoubleClick(QPointF) |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContent::selectionFocusChanged(bool) |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLineContent::isOverSelection(QPointF) const |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
QString ChatLineContent::getSelectedText() const |
||||||
|
{ |
||||||
|
return QString(); |
||||||
|
} |
||||||
|
|
||||||
|
qreal ChatLineContent::getAscent() const |
||||||
|
{ |
||||||
|
return 0.0; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContent::visibilityChanged(bool) |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
QString ChatLineContent::getText() const |
||||||
|
{ |
||||||
|
return QString(); |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef CHATLINECONTENT_H |
||||||
|
#define CHATLINECONTENT_H |
||||||
|
|
||||||
|
#include <QGraphicsItem> |
||||||
|
|
||||||
|
class ChatLine; |
||||||
|
|
||||||
|
class ChatLineContent : public QGraphicsItem |
||||||
|
{ |
||||||
|
public: |
||||||
|
enum GraphicsItemType |
||||||
|
{ |
||||||
|
ChatLineContentType = QGraphicsItem::UserType + 1, |
||||||
|
}; |
||||||
|
|
||||||
|
int getColumn() const; |
||||||
|
int getRow() const; |
||||||
|
|
||||||
|
virtual void setWidth(qreal width) = 0; |
||||||
|
virtual int type() const final; |
||||||
|
|
||||||
|
virtual void selectionMouseMove(QPointF scenePos); |
||||||
|
virtual void selectionStarted(QPointF scenePos); |
||||||
|
virtual void selectionCleared(); |
||||||
|
virtual void selectionDoubleClick(QPointF scenePos); |
||||||
|
virtual void selectionFocusChanged(bool focusIn); |
||||||
|
virtual bool isOverSelection(QPointF scenePos) const; |
||||||
|
virtual QString getSelectedText() const; |
||||||
|
|
||||||
|
virtual QString getText() const; |
||||||
|
|
||||||
|
virtual qreal getAscent() const; |
||||||
|
|
||||||
|
virtual QRectF boundingRect() const = 0; |
||||||
|
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) = 0; |
||||||
|
|
||||||
|
virtual void visibilityChanged(bool visible); |
||||||
|
|
||||||
|
private: |
||||||
|
friend class ChatLine; |
||||||
|
void setIndex(int row, int col); |
||||||
|
|
||||||
|
private: |
||||||
|
int row = -1; |
||||||
|
int col = -1; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // CHATLINECONTENT_H
|
@ -0,0 +1,55 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "chatlinecontentproxy.h" |
||||||
|
#include <QLayout> |
||||||
|
#include <QWidget> |
||||||
|
#include <QPainter> |
||||||
|
#include <QDebug> |
||||||
|
|
||||||
|
ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, int minWidth, float widthInPercent) |
||||||
|
: widthMin(minWidth) |
||||||
|
, widthPercent(widthInPercent) |
||||||
|
{ |
||||||
|
proxy = new QGraphicsProxyWidget(this); |
||||||
|
proxy->setWidget(widget); |
||||||
|
} |
||||||
|
|
||||||
|
QRectF ChatLineContentProxy::boundingRect() const |
||||||
|
{ |
||||||
|
return proxy->boundingRect(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContentProxy::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) |
||||||
|
{ |
||||||
|
painter->setClipRect(boundingRect()); |
||||||
|
proxy->paint(painter, option, widget); |
||||||
|
} |
||||||
|
|
||||||
|
qreal ChatLineContentProxy::getAscent() const |
||||||
|
{ |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
QWidget *ChatLineContentProxy::getWidget() const |
||||||
|
{ |
||||||
|
return proxy->widget(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLineContentProxy::setWidth(qreal width) |
||||||
|
{ |
||||||
|
proxy->widget()->setFixedWidth(qMax(static_cast<int>(width*widthPercent), widthMin)); |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef CHATLINECONTENTPROXY_H |
||||||
|
#define CHATLINECONTENTPROXY_H |
||||||
|
|
||||||
|
#include <QGraphicsProxyWidget> |
||||||
|
#include "chatlinecontent.h" |
||||||
|
|
||||||
|
class ChatLineContentProxy : public ChatLineContent |
||||||
|
{ |
||||||
|
public: |
||||||
|
ChatLineContentProxy(QWidget* widget, int minWidth, float widthInPercent = 1.0f); |
||||||
|
|
||||||
|
virtual QRectF boundingRect() const; |
||||||
|
virtual void setWidth(qreal width); |
||||||
|
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); |
||||||
|
virtual qreal getAscent() const; |
||||||
|
|
||||||
|
QWidget* getWidget() const; |
||||||
|
|
||||||
|
private: |
||||||
|
QGraphicsProxyWidget* proxy; |
||||||
|
int widthMin; |
||||||
|
float widthPercent; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // CHATLINECONTENTPROXY_H
|
@ -0,0 +1,822 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "chatlog.h" |
||||||
|
#include "chatmessage.h" |
||||||
|
#include "chatlinecontent.h" |
||||||
|
|
||||||
|
#include <QDebug> |
||||||
|
#include <QScrollBar> |
||||||
|
#include <QApplication> |
||||||
|
#include <QClipboard> |
||||||
|
#include <QAction> |
||||||
|
#include <QTimer> |
||||||
|
#include <QMouseEvent> |
||||||
|
#include <QShortcut> |
||||||
|
|
||||||
|
template<class T> |
||||||
|
T clamp(T x, T min, T max) |
||||||
|
{ |
||||||
|
if(x > max) |
||||||
|
return max; |
||||||
|
if(x < min) |
||||||
|
return min; |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
ChatLog::ChatLog(QWidget* parent) |
||||||
|
: QGraphicsView(parent) |
||||||
|
{ |
||||||
|
// Create the scene
|
||||||
|
busyScene = new QGraphicsScene(this); |
||||||
|
scene = new QGraphicsScene(this); |
||||||
|
scene->setItemIndexMethod(QGraphicsScene::BspTreeIndex); |
||||||
|
setScene(scene); |
||||||
|
|
||||||
|
// Cfg.
|
||||||
|
setInteractive(true); |
||||||
|
setAcceptDrops(false); |
||||||
|
setAlignment(Qt::AlignTop | Qt::AlignLeft); |
||||||
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
||||||
|
setDragMode(QGraphicsView::NoDrag); |
||||||
|
setViewportUpdateMode(MinimalViewportUpdate); |
||||||
|
setContextMenuPolicy(Qt::CustomContextMenu); |
||||||
|
setBackgroundBrush(QBrush(Qt::white, Qt::SolidPattern)); |
||||||
|
|
||||||
|
// The selection rect for multi-line selection
|
||||||
|
selGraphItem = scene->addRect(0,0,0,0,selectionRectColor.darker(120),selectionRectColor); |
||||||
|
selGraphItem->setZValue(-1.0); // behind all other items
|
||||||
|
|
||||||
|
// copy action (ie. Ctrl+C)
|
||||||
|
copyAction = new QAction(this); |
||||||
|
copyAction->setIcon(QIcon::fromTheme("edit-copy")); |
||||||
|
copyAction->setText(tr("Copy")); |
||||||
|
copyAction->setShortcut(QKeySequence::Copy); |
||||||
|
copyAction->setEnabled(false); |
||||||
|
connect(copyAction, &QAction::triggered, this, [this]() { copySelectedText(); }); |
||||||
|
addAction(copyAction); |
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX |
||||||
|
// Ctrl+Insert shortcut
|
||||||
|
QShortcut* copyCtrlInsShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Insert), this); |
||||||
|
connect(copyCtrlInsShortcut, &QShortcut::activated, this, [this]() { copySelectedText(); }); |
||||||
|
#endif |
||||||
|
|
||||||
|
// select all action (ie. Ctrl+A)
|
||||||
|
QAction* selectAllAction = new QAction(this); |
||||||
|
selectAllAction->setIcon(QIcon::fromTheme("edit-select-all")); |
||||||
|
selectAllAction->setText(tr("Select all")); |
||||||
|
selectAllAction->setShortcut(QKeySequence::SelectAll); |
||||||
|
connect(selectAllAction, &QAction::triggered, this, [this]() { selectAll(); }); |
||||||
|
addAction(selectAllAction); |
||||||
|
|
||||||
|
// This timer is used to scroll the view while the user is
|
||||||
|
// moving the mouse past the top/bottom edge of the widget while selecting.
|
||||||
|
selectionTimer = new QTimer(this); |
||||||
|
selectionTimer->setInterval(1000/30); |
||||||
|
selectionTimer->setSingleShot(false); |
||||||
|
selectionTimer->start(); |
||||||
|
connect(selectionTimer, &QTimer::timeout, this, &ChatLog::onSelectionTimerTimeout); |
||||||
|
|
||||||
|
// Background worker
|
||||||
|
// Updates the layout of all chat-lines after a resize
|
||||||
|
workerTimer = new QTimer(this); |
||||||
|
workerTimer->setSingleShot(false); |
||||||
|
workerTimer->setInterval(5); |
||||||
|
connect(workerTimer, &QTimer::timeout, this, &ChatLog::onWorkerTimeout); |
||||||
|
|
||||||
|
// selection
|
||||||
|
connect(this, &ChatLog::selectionChanged, this, [this]() { |
||||||
|
copyAction->setEnabled(hasTextToBeCopied()); |
||||||
|
#ifdef Q_OS_LINUX |
||||||
|
copySelectedText(true); |
||||||
|
#endif |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
ChatLog::~ChatLog() |
||||||
|
{ |
||||||
|
// Remove chatlines from scene
|
||||||
|
for(ChatLine::Ptr l : lines) |
||||||
|
l->removeFromScene(); |
||||||
|
|
||||||
|
if(busyNotification) |
||||||
|
busyNotification->removeFromScene(); |
||||||
|
|
||||||
|
if(typingNotification) |
||||||
|
typingNotification->removeFromScene(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::clearSelection() |
||||||
|
{ |
||||||
|
if(selectionMode == None) |
||||||
|
return; |
||||||
|
|
||||||
|
for(int i=selFirstRow; i<=selLastRow; ++i) |
||||||
|
lines[i]->selectionCleared(); |
||||||
|
|
||||||
|
selFirstRow = -1; |
||||||
|
selLastRow = -1; |
||||||
|
selClickedCol = -1; |
||||||
|
selClickedRow = -1; |
||||||
|
|
||||||
|
selectionMode = None; |
||||||
|
emit selectionChanged(); |
||||||
|
|
||||||
|
updateMultiSelectionRect(); |
||||||
|
} |
||||||
|
|
||||||
|
QRect ChatLog::getVisibleRect() const |
||||||
|
{ |
||||||
|
return mapToScene(viewport()->rect()).boundingRect().toRect(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::updateSceneRect() |
||||||
|
{ |
||||||
|
setSceneRect(calculateSceneRect()); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::layout(int start, int end, qreal width) |
||||||
|
{ |
||||||
|
if(lines.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
qreal h = 0.0; |
||||||
|
|
||||||
|
// Line at start-1 is considered to have the correct position. All following lines are
|
||||||
|
// positioned in respect to this line.
|
||||||
|
if(start - 1 >= 0) |
||||||
|
h = lines[start - 1]->sceneBoundingRect().bottom() + lineSpacing; |
||||||
|
|
||||||
|
start = clamp<int>(start, 0, lines.size()); |
||||||
|
end = clamp<int>(end + 1, 0, lines.size()); |
||||||
|
|
||||||
|
for(int i = start; i < end; ++i) |
||||||
|
{ |
||||||
|
ChatLine* l = lines[i].get(); |
||||||
|
|
||||||
|
l->layout(width, QPointF(0.0, h)); |
||||||
|
h += l->sceneBoundingRect().height() + lineSpacing; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::mousePressEvent(QMouseEvent* ev) |
||||||
|
{ |
||||||
|
QGraphicsView::mousePressEvent(ev); |
||||||
|
|
||||||
|
QPointF scenePos = mapToScene(ev->pos()); |
||||||
|
|
||||||
|
if(ev->button() == Qt::LeftButton) |
||||||
|
{ |
||||||
|
clickPos = ev->pos(); |
||||||
|
clearSelection(); |
||||||
|
} |
||||||
|
|
||||||
|
if(ev->button() == Qt::RightButton) |
||||||
|
{ |
||||||
|
if(!isOverSelection(scenePos)) |
||||||
|
clearSelection(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::mouseReleaseEvent(QMouseEvent* ev) |
||||||
|
{ |
||||||
|
QGraphicsView::mouseReleaseEvent(ev); |
||||||
|
|
||||||
|
QPointF scenePos = mapToScene(ev->pos()); |
||||||
|
|
||||||
|
if(ev->button() == Qt::RightButton) |
||||||
|
{ |
||||||
|
if(!isOverSelection(scenePos)) |
||||||
|
clearSelection(); |
||||||
|
|
||||||
|
emit customContextMenuRequested(ev->pos()); |
||||||
|
} |
||||||
|
|
||||||
|
selectionScrollDir = NoDirection; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::mouseMoveEvent(QMouseEvent* ev) |
||||||
|
{ |
||||||
|
QGraphicsView::mouseMoveEvent(ev); |
||||||
|
|
||||||
|
QPointF scenePos = mapToScene(ev->pos()); |
||||||
|
|
||||||
|
if(ev->buttons() & Qt::LeftButton) |
||||||
|
{ |
||||||
|
//autoscroll
|
||||||
|
if(ev->pos().y() < 0) |
||||||
|
selectionScrollDir = Up; |
||||||
|
else if(ev->pos().y() > height()) |
||||||
|
selectionScrollDir = Down; |
||||||
|
else |
||||||
|
selectionScrollDir = NoDirection; |
||||||
|
|
||||||
|
//select
|
||||||
|
if(selectionMode == None && (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) |
||||||
|
{ |
||||||
|
QPointF sceneClickPos = mapToScene(clickPos.toPoint()); |
||||||
|
ChatLine::Ptr line = findLineByPosY(scenePos.y()); |
||||||
|
|
||||||
|
ChatLineContent* content = getContentFromPos(sceneClickPos); |
||||||
|
if(content) |
||||||
|
{ |
||||||
|
selClickedRow = content->getRow(); |
||||||
|
selClickedCol = content->getColumn(); |
||||||
|
selFirstRow = content->getRow(); |
||||||
|
selLastRow = content->getRow(); |
||||||
|
|
||||||
|
content->selectionStarted(sceneClickPos); |
||||||
|
|
||||||
|
selectionMode = Precise; |
||||||
|
|
||||||
|
// ungrab mouse grabber
|
||||||
|
if(scene->mouseGrabberItem()) |
||||||
|
scene->mouseGrabberItem()->ungrabMouse(); |
||||||
|
} |
||||||
|
else if(line.get()) |
||||||
|
{ |
||||||
|
selClickedRow = line->getRow(); |
||||||
|
selFirstRow = selClickedRow; |
||||||
|
selLastRow = selClickedRow; |
||||||
|
|
||||||
|
selectionMode = Multi; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(selectionMode != None) |
||||||
|
{ |
||||||
|
ChatLineContent* content = getContentFromPos(scenePos); |
||||||
|
ChatLine::Ptr line = findLineByPosY(scenePos.y()); |
||||||
|
|
||||||
|
if(!content && !line.get()) |
||||||
|
return; |
||||||
|
|
||||||
|
int row; |
||||||
|
|
||||||
|
if(content) |
||||||
|
{ |
||||||
|
row = content->getRow(); |
||||||
|
int col = content->getColumn(); |
||||||
|
|
||||||
|
if(row == selClickedRow && col == selClickedCol) |
||||||
|
{ |
||||||
|
selectionMode = Precise; |
||||||
|
|
||||||
|
content->selectionMouseMove(scenePos); |
||||||
|
selGraphItem->hide(); |
||||||
|
} |
||||||
|
else if(col != selClickedCol) |
||||||
|
{ |
||||||
|
selectionMode = Multi; |
||||||
|
|
||||||
|
lines[selClickedRow]->selectionCleared(); |
||||||
|
} |
||||||
|
} |
||||||
|
else if(line.get()) |
||||||
|
{ |
||||||
|
row = line->getRow(); |
||||||
|
|
||||||
|
if(row != selClickedRow) |
||||||
|
{ |
||||||
|
selectionMode = Multi; |
||||||
|
|
||||||
|
lines[selClickedRow]->selectionCleared(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if(row >= selClickedRow) |
||||||
|
selLastRow = row; |
||||||
|
|
||||||
|
if(row <= selClickedRow) |
||||||
|
selFirstRow = row; |
||||||
|
|
||||||
|
updateMultiSelectionRect(); |
||||||
|
} |
||||||
|
|
||||||
|
emit selectionChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//Much faster than QGraphicsScene::itemAt()!
|
||||||
|
ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const |
||||||
|
{ |
||||||
|
if(lines.empty()) |
||||||
|
return nullptr; |
||||||
|
|
||||||
|
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom); |
||||||
|
|
||||||
|
//find content
|
||||||
|
if(itr != lines.cend() && (*itr)->sceneBoundingRect().contains(scenePos)) |
||||||
|
return (*itr)->getContent(scenePos); |
||||||
|
|
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLog::isOverSelection(QPointF scenePos) const |
||||||
|
{ |
||||||
|
if(selectionMode == Precise) |
||||||
|
{ |
||||||
|
ChatLineContent* content = getContentFromPos(scenePos); |
||||||
|
|
||||||
|
if(content) |
||||||
|
return content->isOverSelection(scenePos); |
||||||
|
} |
||||||
|
else if(selectionMode == Multi) |
||||||
|
{ |
||||||
|
if(selGraphItem->rect().contains(scenePos)) |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
qreal ChatLog::useableWidth() const |
||||||
|
{ |
||||||
|
return width() - verticalScrollBar()->sizeHint().width() - margins.right() - margins.left(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::reposition(int start, int end, qreal deltaY) |
||||||
|
{ |
||||||
|
if(lines.isEmpty()) |
||||||
|
return; |
||||||
|
|
||||||
|
start = clamp<int>(start, 0, lines.size() - 1); |
||||||
|
end = clamp<int>(end + 1, 0, lines.size()); |
||||||
|
|
||||||
|
for(int i = start; i < end; ++i) |
||||||
|
{ |
||||||
|
ChatLine* l = lines[i].get(); |
||||||
|
l->moveBy(deltaY); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l) |
||||||
|
{ |
||||||
|
if(!l.get()) |
||||||
|
return; |
||||||
|
|
||||||
|
bool stickToBtm = stickToBottom(); |
||||||
|
|
||||||
|
//insert
|
||||||
|
l->setRow(lines.size()); |
||||||
|
l->addToScene(scene); |
||||||
|
lines.append(l); |
||||||
|
|
||||||
|
//partial refresh
|
||||||
|
layout(lines.last()->getRow(), lines.size(), useableWidth()); |
||||||
|
updateSceneRect(); |
||||||
|
|
||||||
|
if(stickToBtm) |
||||||
|
scrollToBottom(); |
||||||
|
|
||||||
|
checkVisibility(); |
||||||
|
updateTypingNotification(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::insertChatlineOnTop(ChatLine::Ptr l) |
||||||
|
{ |
||||||
|
if(!l.get()) |
||||||
|
return; |
||||||
|
|
||||||
|
insertChatlineOnTop(QList<ChatLine::Ptr>() << l); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines) |
||||||
|
{ |
||||||
|
if(newLines.isEmpty()) |
||||||
|
return; |
||||||
|
|
||||||
|
QGraphicsScene::ItemIndexMethod oldIndexMeth = scene->itemIndexMethod(); |
||||||
|
scene->setItemIndexMethod(QGraphicsScene::NoIndex); |
||||||
|
|
||||||
|
// alloc space for old and new lines
|
||||||
|
QVector<ChatLine::Ptr> combLines; |
||||||
|
combLines.reserve(newLines.size() + lines.size()); |
||||||
|
|
||||||
|
// add the new lines
|
||||||
|
int i = 0; |
||||||
|
for(ChatLine::Ptr l : newLines) |
||||||
|
{ |
||||||
|
l->addToScene(scene); |
||||||
|
l->visibilityChanged(false); |
||||||
|
l->setRow(i++); |
||||||
|
combLines.push_back(l); |
||||||
|
} |
||||||
|
|
||||||
|
// add the old lines
|
||||||
|
for(ChatLine::Ptr l : lines) |
||||||
|
{ |
||||||
|
l->setRow(i++); |
||||||
|
combLines.push_back(l); |
||||||
|
} |
||||||
|
|
||||||
|
lines = combLines; |
||||||
|
|
||||||
|
scene->setItemIndexMethod(oldIndexMeth); |
||||||
|
|
||||||
|
// redo layout
|
||||||
|
startResizeWorker(); |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLog::stickToBottom() const |
||||||
|
{ |
||||||
|
return verticalScrollBar()->value() == verticalScrollBar()->maximum(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::scrollToBottom() |
||||||
|
{ |
||||||
|
updateSceneRect(); |
||||||
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum()); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::startResizeWorker() |
||||||
|
{ |
||||||
|
if(lines.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
// (re)start the worker
|
||||||
|
if(!workerTimer->isActive()) |
||||||
|
{ |
||||||
|
// these values must not be reevaluated while the worker is running
|
||||||
|
workerStb = stickToBottom(); |
||||||
|
|
||||||
|
if(!visibleLines.empty()) |
||||||
|
workerAnchorLine = visibleLines.first(); |
||||||
|
} |
||||||
|
|
||||||
|
workerLastIndex = 0; |
||||||
|
workerTimer->start(); |
||||||
|
|
||||||
|
// switch to busy scene displaying the busy notification
|
||||||
|
setScene(busyScene); |
||||||
|
verticalScrollBar()->hide(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::mouseDoubleClickEvent(QMouseEvent *ev) |
||||||
|
{ |
||||||
|
QPointF scenePos = mapToScene(ev->pos()); |
||||||
|
ChatLineContent* content = getContentFromPos(scenePos); |
||||||
|
|
||||||
|
if(content) |
||||||
|
{ |
||||||
|
content->selectionDoubleClick(scenePos); |
||||||
|
selClickedCol = content->getColumn(); |
||||||
|
selClickedRow = content->getRow(); |
||||||
|
selFirstRow = content->getRow(); |
||||||
|
selLastRow = content->getRow(); |
||||||
|
selectionMode = Precise; |
||||||
|
|
||||||
|
emit selectionChanged(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QString ChatLog::getSelectedText() const |
||||||
|
{ |
||||||
|
if(selectionMode == Precise) |
||||||
|
{ |
||||||
|
return lines[selClickedRow]->content[selClickedCol]->getSelectedText(); |
||||||
|
} |
||||||
|
else if(selectionMode == Multi) |
||||||
|
{ |
||||||
|
// build a nicely formatted message
|
||||||
|
QString out; |
||||||
|
|
||||||
|
for(int i=selFirstRow; i<=selLastRow; ++i) |
||||||
|
{ |
||||||
|
if(lines[i]->content[1]->getText().isEmpty()) |
||||||
|
continue; |
||||||
|
|
||||||
|
QString timestamp = lines[i]->content[2]->getText().isEmpty() ? tr("pending") : lines[i]->content[2]->getText(); |
||||||
|
QString author = lines[i]->content[0]->getText(); |
||||||
|
QString msg = lines[i]->content[1]->getText(); |
||||||
|
|
||||||
|
out += QString(out.isEmpty() ? "[%2] %1:\n%3" : "\n[%2] %1:\n%3").arg(author, timestamp, msg); |
||||||
|
|
||||||
|
if(i != selLastRow) |
||||||
|
out += "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
return QString(); |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLog::isEmpty() const |
||||||
|
{ |
||||||
|
return lines.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatLog::hasTextToBeCopied() const |
||||||
|
{ |
||||||
|
return selectionMode != None; |
||||||
|
} |
||||||
|
|
||||||
|
ChatLine::Ptr ChatLog::getTypingNotification() const |
||||||
|
{ |
||||||
|
return typingNotification; |
||||||
|
} |
||||||
|
|
||||||
|
QVector<ChatLine::Ptr> ChatLog::getLines() |
||||||
|
{ |
||||||
|
return lines; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::clear() |
||||||
|
{ |
||||||
|
clearSelection(); |
||||||
|
|
||||||
|
for(ChatLine::Ptr l : lines) |
||||||
|
l->removeFromScene(); |
||||||
|
|
||||||
|
lines.clear(); |
||||||
|
visibleLines.clear(); |
||||||
|
|
||||||
|
updateSceneRect(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::copySelectedText(bool toSelectionBuffer) const |
||||||
|
{ |
||||||
|
QString text = getSelectedText(); |
||||||
|
QClipboard* clipboard = QApplication::clipboard(); |
||||||
|
|
||||||
|
if(clipboard && !text.isNull()) |
||||||
|
clipboard->setText(text, toSelectionBuffer ? QClipboard::Selection : QClipboard::Clipboard); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::setBusyNotification(ChatLine::Ptr notification) |
||||||
|
{ |
||||||
|
if(!notification.get()) |
||||||
|
return; |
||||||
|
|
||||||
|
busyNotification = notification; |
||||||
|
busyNotification->addToScene(busyScene); |
||||||
|
busyNotification->visibilityChanged(true); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::setTypingNotification(ChatLine::Ptr notification) |
||||||
|
{ |
||||||
|
typingNotification = notification; |
||||||
|
typingNotification->visibilityChanged(true); |
||||||
|
typingNotification->setVisible(false); |
||||||
|
typingNotification->addToScene(scene); |
||||||
|
updateTypingNotification(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::setTypingNotificationVisible(bool visible) |
||||||
|
{ |
||||||
|
if(typingNotification.get()) |
||||||
|
{ |
||||||
|
typingNotification->setVisible(visible); |
||||||
|
updateTypingNotification(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::scrollToLine(ChatLine::Ptr line) |
||||||
|
{ |
||||||
|
if(!line.get()) |
||||||
|
return; |
||||||
|
|
||||||
|
updateSceneRect(); |
||||||
|
verticalScrollBar()->setValue(line->sceneBoundingRect().top()); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::selectAll() |
||||||
|
{ |
||||||
|
if(lines.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
clearSelection(); |
||||||
|
|
||||||
|
selectionMode = Multi; |
||||||
|
selFirstRow = 0; |
||||||
|
selLastRow = lines.size()-1; |
||||||
|
|
||||||
|
emit selectionChanged(); |
||||||
|
updateMultiSelectionRect(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::checkVisibility() |
||||||
|
{ |
||||||
|
if(lines.empty()) |
||||||
|
return; |
||||||
|
|
||||||
|
// find first visible line
|
||||||
|
auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(), ChatLine::lessThanBSRectBottom); |
||||||
|
|
||||||
|
// find last visible line
|
||||||
|
auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(), ChatLine::lessThanBSRectTop); |
||||||
|
|
||||||
|
// set visibilty
|
||||||
|
QList<ChatLine::Ptr> newVisibleLines; |
||||||
|
for(auto itr = lowerBound; itr != upperBound; ++itr) |
||||||
|
{ |
||||||
|
newVisibleLines.append(*itr); |
||||||
|
|
||||||
|
if(!visibleLines.contains(*itr)) |
||||||
|
(*itr)->visibilityChanged(true); |
||||||
|
|
||||||
|
visibleLines.removeOne(*itr); |
||||||
|
} |
||||||
|
|
||||||
|
// these lines are no longer visible
|
||||||
|
for(ChatLine::Ptr line : visibleLines) |
||||||
|
line->visibilityChanged(false); |
||||||
|
|
||||||
|
visibleLines = newVisibleLines; |
||||||
|
|
||||||
|
// enforce order
|
||||||
|
std::sort(visibleLines.begin(), visibleLines.end(), ChatLine::lessThanRowIndex); |
||||||
|
|
||||||
|
//if(!visibleLines.empty())
|
||||||
|
// qDebug() << "visible from " << visibleLines.first()->getRow() << "to " << visibleLines.last()->getRow() << " total " << visibleLines.size();
|
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::scrollContentsBy(int dx, int dy) |
||||||
|
{ |
||||||
|
QGraphicsView::scrollContentsBy(dx, dy); |
||||||
|
checkVisibility(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::resizeEvent(QResizeEvent* ev) |
||||||
|
{ |
||||||
|
bool stb = stickToBottom(); |
||||||
|
|
||||||
|
if(ev->size().width() != ev->oldSize().width()) |
||||||
|
{ |
||||||
|
startResizeWorker(); |
||||||
|
stb = false; // let the resize worker handle it
|
||||||
|
} |
||||||
|
|
||||||
|
QGraphicsView::resizeEvent(ev); |
||||||
|
|
||||||
|
if(stb) |
||||||
|
scrollToBottom(); |
||||||
|
|
||||||
|
updateBusyNotification(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::updateMultiSelectionRect() |
||||||
|
{ |
||||||
|
if(selectionMode == Multi && selFirstRow >= 0 && selLastRow >= 0) |
||||||
|
{ |
||||||
|
QRectF selBBox; |
||||||
|
selBBox = selBBox.united(lines[selFirstRow]->sceneBoundingRect()); |
||||||
|
selBBox = selBBox.united(lines[selLastRow]->sceneBoundingRect()); |
||||||
|
|
||||||
|
if(selGraphItem->rect() != selBBox) |
||||||
|
scene->invalidate(selGraphItem->rect()); |
||||||
|
|
||||||
|
selGraphItem->setRect(selBBox); |
||||||
|
selGraphItem->show(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
selGraphItem->hide(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::updateTypingNotification() |
||||||
|
{ |
||||||
|
ChatLine* notification = typingNotification.get(); |
||||||
|
if(!notification) |
||||||
|
return; |
||||||
|
|
||||||
|
qreal posY = 0.0; |
||||||
|
|
||||||
|
if(!lines.empty()) |
||||||
|
posY = lines.last()->sceneBoundingRect().bottom() + lineSpacing; |
||||||
|
|
||||||
|
notification->layout(useableWidth(), QPointF(0.0, posY)); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::updateBusyNotification() |
||||||
|
{ |
||||||
|
if(busyNotification.get()) |
||||||
|
{ |
||||||
|
//repoisition the busy notification (centered)
|
||||||
|
busyNotification->layout(useableWidth(), getVisibleRect().topLeft() + QPointF(0, getVisibleRect().height()/2.0)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ChatLine::Ptr ChatLog::findLineByPosY(qreal yPos) const |
||||||
|
{ |
||||||
|
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), yPos, ChatLine::lessThanBSRectBottom); |
||||||
|
|
||||||
|
if(itr != lines.cend()) |
||||||
|
return *itr; |
||||||
|
|
||||||
|
return ChatLine::Ptr(); |
||||||
|
} |
||||||
|
|
||||||
|
QRectF ChatLog::calculateSceneRect() const |
||||||
|
{ |
||||||
|
qreal bottom = (lines.empty() ? 0.0 : lines.last()->sceneBoundingRect().bottom()); |
||||||
|
|
||||||
|
if(typingNotification.get() != nullptr) |
||||||
|
bottom += typingNotification->sceneBoundingRect().height() + lineSpacing; |
||||||
|
|
||||||
|
return QRectF(-margins.left(), -margins.top(), useableWidth(), bottom + margins.bottom() + margins.top()); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::onSelectionTimerTimeout() |
||||||
|
{ |
||||||
|
const int scrollSpeed = 10; |
||||||
|
|
||||||
|
switch(selectionScrollDir) |
||||||
|
{ |
||||||
|
case Up: |
||||||
|
verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed); |
||||||
|
break; |
||||||
|
case Down: |
||||||
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + scrollSpeed); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::onWorkerTimeout() |
||||||
|
{ |
||||||
|
// Fairly arbitrary but
|
||||||
|
// large values will make the UI unresponsive
|
||||||
|
const int stepSize = 50; |
||||||
|
|
||||||
|
layout(workerLastIndex, workerLastIndex+stepSize, useableWidth()); |
||||||
|
workerLastIndex += stepSize; |
||||||
|
|
||||||
|
// done?
|
||||||
|
if(workerLastIndex >= lines.size()) |
||||||
|
{ |
||||||
|
workerTimer->stop(); |
||||||
|
|
||||||
|
// switch back to the scene containing the chat messages
|
||||||
|
setScene(scene); |
||||||
|
|
||||||
|
// make sure everything gets updated
|
||||||
|
updateSceneRect(); |
||||||
|
checkVisibility(); |
||||||
|
updateTypingNotification(); |
||||||
|
updateMultiSelectionRect(); |
||||||
|
|
||||||
|
// scroll
|
||||||
|
if(workerStb) |
||||||
|
scrollToBottom(); |
||||||
|
else |
||||||
|
scrollToLine(workerAnchorLine); |
||||||
|
|
||||||
|
// don't keep a Ptr to the anchor line
|
||||||
|
workerAnchorLine = ChatLine::Ptr(); |
||||||
|
|
||||||
|
// hidden during busy screen
|
||||||
|
verticalScrollBar()->show(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::showEvent(QShowEvent*) |
||||||
|
{ |
||||||
|
// Empty.
|
||||||
|
// The default implementation calls centerOn - for some reason - causing
|
||||||
|
// the scrollbar to move.
|
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::focusInEvent(QFocusEvent* ev) |
||||||
|
{ |
||||||
|
QGraphicsView::focusInEvent(ev); |
||||||
|
|
||||||
|
if(selectionMode != None) |
||||||
|
{ |
||||||
|
selGraphItem->setBrush(QBrush(selectionRectColor)); |
||||||
|
|
||||||
|
for(int i=selFirstRow; i<=selLastRow; ++i) |
||||||
|
lines[i]->selectionFocusChanged(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChatLog::focusOutEvent(QFocusEvent* ev) |
||||||
|
{ |
||||||
|
QGraphicsView::focusOutEvent(ev); |
||||||
|
|
||||||
|
if(selectionMode != None) |
||||||
|
{ |
||||||
|
selGraphItem->setBrush(QBrush(selectionRectColor.lighter(120))); |
||||||
|
|
||||||
|
for(int i=selFirstRow; i<=selLastRow; ++i) |
||||||
|
lines[i]->selectionFocusChanged(false); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef CHATLOG_H |
||||||
|
#define CHATLOG_H |
||||||
|
|
||||||
|
#include <QGraphicsView> |
||||||
|
#include <QDateTime> |
||||||
|
#include <QMargins> |
||||||
|
|
||||||
|
#include "chatline.h" |
||||||
|
#include "chatmessage.h" |
||||||
|
|
||||||
|
class QGraphicsScene; |
||||||
|
class QGraphicsRectItem; |
||||||
|
class QMouseEvent; |
||||||
|
class QTimer; |
||||||
|
class ChatLineContent; |
||||||
|
class ToxFile; |
||||||
|
|
||||||
|
class ChatLog : public QGraphicsView |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
explicit ChatLog(QWidget* parent = 0); |
||||||
|
virtual ~ChatLog(); |
||||||
|
|
||||||
|
void insertChatlineAtBottom(ChatLine::Ptr l); |
||||||
|
void insertChatlineOnTop(ChatLine::Ptr l); |
||||||
|
void insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines); |
||||||
|
void clearSelection(); |
||||||
|
void clear(); |
||||||
|
void copySelectedText(bool toSelectionBuffer = false) const; |
||||||
|
void setBusyNotification(ChatLine::Ptr notification); |
||||||
|
void setTypingNotification(ChatLine::Ptr notification); |
||||||
|
void setTypingNotificationVisible(bool visible); |
||||||
|
void scrollToLine(ChatLine::Ptr line); |
||||||
|
void selectAll(); |
||||||
|
|
||||||
|
QString getSelectedText() const; |
||||||
|
|
||||||
|
bool isEmpty() const; |
||||||
|
bool hasTextToBeCopied() const; |
||||||
|
|
||||||
|
ChatLine::Ptr getTypingNotification() const; |
||||||
|
QVector<ChatLine::Ptr> getLines(); |
||||||
|
|
||||||
|
signals: |
||||||
|
void selectionChanged(); |
||||||
|
|
||||||
|
protected: |
||||||
|
QRectF calculateSceneRect() const; |
||||||
|
QRect getVisibleRect() const; |
||||||
|
ChatLineContent* getContentFromPos(QPointF scenePos) const; |
||||||
|
|
||||||
|
void layout(int start, int end, qreal width); |
||||||
|
bool isOverSelection(QPointF scenePos) const; |
||||||
|
bool stickToBottom() const; |
||||||
|
|
||||||
|
qreal useableWidth() const; |
||||||
|
|
||||||
|
void reposition(int start, int end, qreal deltaY); |
||||||
|
void updateSceneRect(); |
||||||
|
void checkVisibility(); |
||||||
|
void scrollToBottom(); |
||||||
|
void startResizeWorker(); |
||||||
|
|
||||||
|
virtual void mouseDoubleClickEvent(QMouseEvent* ev); |
||||||
|
virtual void mousePressEvent(QMouseEvent* ev); |
||||||
|
virtual void mouseReleaseEvent(QMouseEvent* ev); |
||||||
|
virtual void mouseMoveEvent(QMouseEvent* ev); |
||||||
|
virtual void scrollContentsBy(int dx, int dy); |
||||||
|
virtual void resizeEvent(QResizeEvent* ev); |
||||||
|
virtual void showEvent(QShowEvent*); |
||||||
|
virtual void focusInEvent(QFocusEvent* ev); |
||||||
|
virtual void focusOutEvent(QFocusEvent* ev); |
||||||
|
|
||||||
|
void updateMultiSelectionRect(); |
||||||
|
void updateTypingNotification(); |
||||||
|
void updateBusyNotification(); |
||||||
|
|
||||||
|
ChatLine::Ptr findLineByPosY(qreal yPos) const; |
||||||
|
|
||||||
|
private slots: |
||||||
|
void onSelectionTimerTimeout(); |
||||||
|
void onWorkerTimeout(); |
||||||
|
|
||||||
|
private: |
||||||
|
enum SelectionMode { |
||||||
|
None, |
||||||
|
Precise, |
||||||
|
Multi, |
||||||
|
}; |
||||||
|
|
||||||
|
enum AutoScrollDirection { |
||||||
|
NoDirection, |
||||||
|
Up, |
||||||
|
Down, |
||||||
|
}; |
||||||
|
|
||||||
|
QAction* copyAction = nullptr; |
||||||
|
QGraphicsScene* scene = nullptr; |
||||||
|
QGraphicsScene* busyScene = nullptr; |
||||||
|
QVector<ChatLine::Ptr> lines; |
||||||
|
QList<ChatLine::Ptr> visibleLines; |
||||||
|
ChatLine::Ptr typingNotification; |
||||||
|
ChatLine::Ptr busyNotification; |
||||||
|
|
||||||
|
// selection
|
||||||
|
int selClickedRow = -1; //These 4 are only valid while selectionMode != None
|
||||||
|
int selClickedCol = -1; |
||||||
|
int selFirstRow = -1; |
||||||
|
int selLastRow = -1; |
||||||
|
QColor selectionRectColor = QColor::fromRgbF(0.23, 0.68, 0.91).lighter(150); |
||||||
|
SelectionMode selectionMode = None; |
||||||
|
QPointF clickPos; |
||||||
|
QGraphicsRectItem* selGraphItem = nullptr; |
||||||
|
QTimer* selectionTimer = nullptr; |
||||||
|
QTimer* workerTimer = nullptr; |
||||||
|
AutoScrollDirection selectionScrollDir = NoDirection; |
||||||
|
|
||||||
|
//worker vars
|
||||||
|
int workerLastIndex = 0; |
||||||
|
bool workerStb = false; |
||||||
|
ChatLine::Ptr workerAnchorLine; |
||||||
|
|
||||||
|
// layout
|
||||||
|
QMargins margins = QMargins(10.0,10.0,10.0,10.0); |
||||||
|
qreal lineSpacing = 5.0f; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // CHATLOG_H
|
@ -0,0 +1,221 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "chatmessage.h" |
||||||
|
#include "chatlinecontentproxy.h" |
||||||
|
#include "content/text.h" |
||||||
|
#include "content/timestamp.h" |
||||||
|
#include "content/spinner.h" |
||||||
|
#include "content/filetransferwidget.h" |
||||||
|
#include "content/image.h" |
||||||
|
#include "content/notificationicon.h" |
||||||
|
|
||||||
|
#include "src/misc/settings.h" |
||||||
|
#include "src/misc/smileypack.h" |
||||||
|
#include "src/misc/style.h" |
||||||
|
|
||||||
|
#define NAME_COL_WIDTH 90.0 |
||||||
|
#define TIME_COL_WIDTH 90.0 |
||||||
|
|
||||||
|
ChatMessage::ChatMessage() |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QString &rawMessage, bool isAction, bool alert, bool isMe, const QDateTime &date) |
||||||
|
{ |
||||||
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); |
||||||
|
|
||||||
|
QString text = toHtmlChars(rawMessage); |
||||||
|
|
||||||
|
//smileys
|
||||||
|
if(Settings::getInstance().getUseEmoticons()) |
||||||
|
text = SmileyPack::getInstance().smileyfied(text); |
||||||
|
|
||||||
|
//quotes (green text)
|
||||||
|
text = detectQuotes(detectAnchors(text)); |
||||||
|
|
||||||
|
if(isAction) |
||||||
|
{ |
||||||
|
text = QString("<div class=action>%1 %2</div>").arg(sender, text); |
||||||
|
msg->setAsAction(); |
||||||
|
} |
||||||
|
else if(alert) |
||||||
|
{ |
||||||
|
text = "<div class=alert>" + text + "</div>"; |
||||||
|
} |
||||||
|
|
||||||
|
msg->addColumn(new Text(isAction ? "<div class=action>*</div>" : sender, isMe ? Style::getFont(Style::BigBold) : Style::getFont(Style::Big), isAction ? false : true, sender), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
msg->addColumn(new Text(text, Style::getFont(Style::Big), false, isAction ? QString("*%1 %2*").arg(sender, rawMessage) : rawMessage), ColumnFormat(1.0, ColumnFormat::VariableSize)); |
||||||
|
msg->addColumn(new Spinner(":/ui/chatArea/spinner.svg", QSize(16, 16), 360.0/1.6), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
|
||||||
|
if(!date.isNull()) |
||||||
|
msg->markAsSent(date); |
||||||
|
|
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
ChatMessage::Ptr ChatMessage::createChatInfoMessage(const QString &rawMessage, SystemMessageType type, const QDateTime &date) |
||||||
|
{ |
||||||
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); |
||||||
|
QString text = toHtmlChars(rawMessage); |
||||||
|
|
||||||
|
QString img; |
||||||
|
switch(type) |
||||||
|
{ |
||||||
|
case INFO: img = ":/ui/chatArea/info.svg"; break; |
||||||
|
case ERROR: img = ":/ui/chatArea/error.svg"; break; |
||||||
|
case TYPING: img = ":/ui/chatArea/typing.svg"; break; |
||||||
|
} |
||||||
|
|
||||||
|
msg->addColumn(new Image(QSize(18, 18), img), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
msg->addColumn(new Text("<b>" + text + "</b>", Style::getFont(Style::Big), false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left)); |
||||||
|
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), Style::getFont(Style::Big)), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
|
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
ChatMessage::Ptr ChatMessage::createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date) |
||||||
|
{ |
||||||
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); |
||||||
|
|
||||||
|
msg->addColumn(new Text(sender, isMe ? Style::getFont(Style::BigBold) : Style::getFont(Style::Big), true), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
msg->addColumn(new ChatLineContentProxy(new FileTransferWidget(0, file), 350, 0.6f), ColumnFormat(1.0, ColumnFormat::VariableSize)); |
||||||
|
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), Style::getFont(Style::Big)), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
|
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
ChatMessage::Ptr ChatMessage::createTypingNotification() |
||||||
|
{ |
||||||
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); |
||||||
|
|
||||||
|
// Note: "[user]..." is just a placeholder. The actual text is set in ChatForm::setFriendTyping()
|
||||||
|
msg->addColumn(new NotificationIcon(QSize(18, 18)), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); |
||||||
|
msg->addColumn(new Text("[user]...", Style::getFont(Style::Big), false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left)); |
||||||
|
|
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
ChatMessage::Ptr ChatMessage::createBusyNotification() |
||||||
|
{ |
||||||
|
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage); |
||||||
|
|
||||||
|
// TODO: Bigger font
|
||||||
|
msg->addColumn(new Text(QObject::tr("Busy..."), Style::getFont(Style::ExtraBig), false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Center)); |
||||||
|
|
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatMessage::markAsSent(const QDateTime &time) |
||||||
|
{ |
||||||
|
// remove the spinner and replace it by $time
|
||||||
|
replaceContent(2, new Timestamp(time, Settings::getInstance().getTimestampFormat(), Style::getFont(Style::Big))); |
||||||
|
} |
||||||
|
|
||||||
|
QString ChatMessage::toString() const |
||||||
|
{ |
||||||
|
ChatLineContent* c = getContent(1); |
||||||
|
if(c) |
||||||
|
return c->getText(); |
||||||
|
|
||||||
|
return QString(); |
||||||
|
} |
||||||
|
|
||||||
|
bool ChatMessage::isAction() const |
||||||
|
{ |
||||||
|
return action; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatMessage::setAsAction() |
||||||
|
{ |
||||||
|
action = true; |
||||||
|
} |
||||||
|
|
||||||
|
void ChatMessage::hideSender() |
||||||
|
{ |
||||||
|
ChatLineContent* c = getContent(0); |
||||||
|
if(c) |
||||||
|
c->hide(); |
||||||
|
} |
||||||
|
|
||||||
|
void ChatMessage::hideDate() |
||||||
|
{ |
||||||
|
ChatLineContent* c = getContent(2); |
||||||
|
if(c) |
||||||
|
c->hide(); |
||||||
|
} |
||||||
|
|
||||||
|
QString ChatMessage::detectAnchors(const QString &str) |
||||||
|
{ |
||||||
|
QString out = str; |
||||||
|
|
||||||
|
// detect urls
|
||||||
|
QRegExp exp("(?:\\b)(www\\.|http[s]?:\\/\\/|ftp:\\/\\/|tox:\\/\\/|tox:)\\S+"); |
||||||
|
int offset = 0; |
||||||
|
while ((offset = exp.indexIn(out, offset)) != -1) |
||||||
|
{ |
||||||
|
QString url = exp.cap(); |
||||||
|
|
||||||
|
// If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:"
|
||||||
|
if (url == "tox:\"") |
||||||
|
{ |
||||||
|
offset += url.length(); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// add scheme if not specified
|
||||||
|
if (exp.cap(1) == "www.") |
||||||
|
url.prepend("http://"); |
||||||
|
|
||||||
|
QString htmledUrl = QString("<a href=\"%1\">%1</a>").arg(url); |
||||||
|
out.replace(offset, exp.cap().length(), htmledUrl); |
||||||
|
|
||||||
|
offset += htmledUrl.length(); |
||||||
|
} |
||||||
|
|
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
QString ChatMessage::detectQuotes(const QString& str) |
||||||
|
{ |
||||||
|
// detect text quotes
|
||||||
|
QStringList messageLines = str.split("\n"); |
||||||
|
QString quotedText; |
||||||
|
for (int i=0;i<messageLines.size();++i) |
||||||
|
{ |
||||||
|
if (QRegExp("^>( |[[]|>|[^_\\d\\W]).*").exactMatch(messageLines[i])) |
||||||
|
quotedText += "<span class=quote>" + messageLines[i] + "</span>"; |
||||||
|
else |
||||||
|
quotedText += messageLines[i]; |
||||||
|
|
||||||
|
if (i < messageLines.size() - 1) |
||||||
|
quotedText += "<br/>"; |
||||||
|
} |
||||||
|
|
||||||
|
return quotedText; |
||||||
|
} |
||||||
|
|
||||||
|
QString ChatMessage::toHtmlChars(const QString &str) |
||||||
|
{ |
||||||
|
static QList<QPair<QString, QString>> replaceList = {{"&","&"}, {">",">"}, {"<","<"}}; |
||||||
|
QString res = str; |
||||||
|
|
||||||
|
for (auto &it : replaceList) |
||||||
|
res = res.replace(it.first,it.second); |
||||||
|
|
||||||
|
return res; |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef CHATMESSAGE_H |
||||||
|
#define CHATMESSAGE_H |
||||||
|
|
||||||
|
#include "chatline.h" |
||||||
|
#include "src/corestructs.h" |
||||||
|
#include <QDateTime> |
||||||
|
|
||||||
|
class QGraphicsScene; |
||||||
|
|
||||||
|
class ChatMessage : public ChatLine |
||||||
|
{ |
||||||
|
public: |
||||||
|
using Ptr = std::shared_ptr<ChatMessage>; |
||||||
|
|
||||||
|
enum SystemMessageType |
||||||
|
{ |
||||||
|
INFO, |
||||||
|
ERROR, |
||||||
|
TYPING, |
||||||
|
}; |
||||||
|
|
||||||
|
ChatMessage(); |
||||||
|
|
||||||
|
static ChatMessage::Ptr createChatMessage(const QString& sender, const QString& rawMessage, bool isAction, bool alert, bool isMe, const QDateTime& date = QDateTime()); |
||||||
|
static ChatMessage::Ptr createChatInfoMessage(const QString& rawMessage, SystemMessageType type, const QDateTime& date); |
||||||
|
static ChatMessage::Ptr createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date); |
||||||
|
static ChatMessage::Ptr createTypingNotification(); |
||||||
|
static ChatMessage::Ptr createBusyNotification(); |
||||||
|
|
||||||
|
void markAsSent(const QDateTime& time); |
||||||
|
QString toString() const; |
||||||
|
bool isAction() const; |
||||||
|
void setAsAction(); |
||||||
|
void hideSender(); |
||||||
|
void hideDate(); |
||||||
|
|
||||||
|
protected: |
||||||
|
static QString detectAnchors(const QString& str); |
||||||
|
static QString detectQuotes(const QString& str); |
||||||
|
static QString toHtmlChars(const QString& str); |
||||||
|
|
||||||
|
private: |
||||||
|
bool action = false; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // CHATMESSAGE_H
|
@ -0,0 +1,451 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "filetransferwidget.h" |
||||||
|
#include "ui_filetransferwidget.h" |
||||||
|
|
||||||
|
#include "src/core.h" |
||||||
|
#include "src/misc/style.h" |
||||||
|
|
||||||
|
#include <QMouseEvent> |
||||||
|
#include <QFileDialog> |
||||||
|
#include <QFile> |
||||||
|
#include <QMessageBox> |
||||||
|
#include <QDesktopServices> |
||||||
|
#include <QPainter> |
||||||
|
#include <QVariantAnimation> |
||||||
|
#include <QDebug> |
||||||
|
|
||||||
|
FileTransferWidget::FileTransferWidget(QWidget *parent, ToxFile file) |
||||||
|
: QWidget(parent) |
||||||
|
, ui(new Ui::FileTransferWidget) |
||||||
|
, fileInfo(file) |
||||||
|
, lastTick(QTime::currentTime()) |
||||||
|
, backgroundColor(Style::getColor(Style::LightGrey)) |
||||||
|
, buttonColor(Style::getColor(Style::Yellow)) |
||||||
|
{ |
||||||
|
ui->setupUi(this); |
||||||
|
|
||||||
|
// hide the QWidget background (background-color: transparent doesn't seem to work)
|
||||||
|
setAttribute(Qt::WA_TranslucentBackground, true); |
||||||
|
|
||||||
|
ui->previewLabel->hide(); |
||||||
|
ui->filenameLabel->setText(file.fileName); |
||||||
|
ui->progressBar->setValue(0); |
||||||
|
ui->fileSizeLabel->setText(getHumanReadableSize(file.filesize)); |
||||||
|
ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget")); |
||||||
|
ui->etaLabel->setText(""); |
||||||
|
|
||||||
|
backgroundColorAnimation = new QVariantAnimation(this); |
||||||
|
backgroundColorAnimation->setDuration(500); |
||||||
|
backgroundColorAnimation->setEasingCurve(QEasingCurve::OutCubic); |
||||||
|
connect(backgroundColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { |
||||||
|
backgroundColor = val.value<QColor>(); |
||||||
|
update(); |
||||||
|
}); |
||||||
|
|
||||||
|
buttonColorAnimation = new QVariantAnimation(this); |
||||||
|
buttonColorAnimation->setDuration(500); |
||||||
|
buttonColorAnimation->setEasingCurve(QEasingCurve::OutCubic); |
||||||
|
connect(buttonColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { |
||||||
|
buttonColor = val.value<QColor>(); |
||||||
|
update(); |
||||||
|
}); |
||||||
|
|
||||||
|
setBackgroundColor(Style::getColor(Style::LightGrey), false); |
||||||
|
|
||||||
|
connect(Core::getInstance(), &Core::fileTransferInfo, this, &FileTransferWidget::onFileTransferInfo); |
||||||
|
connect(Core::getInstance(), &Core::fileTransferAccepted, this, &FileTransferWidget::onFileTransferAccepted); |
||||||
|
connect(Core::getInstance(), &Core::fileTransferCancelled, this, &FileTransferWidget::onFileTransferCancelled); |
||||||
|
connect(Core::getInstance(), &Core::fileTransferPaused, this, &FileTransferWidget::onFileTransferPaused); |
||||||
|
connect(Core::getInstance(), &Core::fileTransferFinished, this, &FileTransferWidget::onFileTransferFinished); |
||||||
|
|
||||||
|
setupButtons(); |
||||||
|
|
||||||
|
//preview
|
||||||
|
if(fileInfo.direction == ToxFile::SENDING) |
||||||
|
showPreview(fileInfo.filePath); |
||||||
|
|
||||||
|
setFixedHeight(78); |
||||||
|
} |
||||||
|
|
||||||
|
FileTransferWidget::~FileTransferWidget() |
||||||
|
{ |
||||||
|
delete ui; |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::autoAcceptTransfer(const QString &path) |
||||||
|
{ |
||||||
|
QString filepath; |
||||||
|
int number = 0; |
||||||
|
|
||||||
|
QString suffix = QFileInfo(fileInfo.fileName).completeSuffix(); |
||||||
|
QString base = QFileInfo(fileInfo.fileName).baseName(); |
||||||
|
|
||||||
|
do |
||||||
|
{ |
||||||
|
filepath = QString("%1/%2%3.%4").arg(path, base, number > 0 ? QString(" (%1)").arg(QString::number(number)) : QString(), suffix); |
||||||
|
number++; |
||||||
|
} |
||||||
|
while(QFileInfo(filepath).exists()); |
||||||
|
|
||||||
|
//Do not automatically accept the file-transfer if the path is not writable.
|
||||||
|
//The user can still accept it manually.
|
||||||
|
if(isFilePathWritable(filepath)) |
||||||
|
Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath); |
||||||
|
else |
||||||
|
qDebug() << "Warning: Cannot write to " << filepath; |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::acceptTransfer(const QString &filepath) |
||||||
|
{ |
||||||
|
if(filepath.isEmpty()) |
||||||
|
return; |
||||||
|
|
||||||
|
//test if writable
|
||||||
|
if(!isFilePathWritable(filepath)) |
||||||
|
{ |
||||||
|
QMessageBox::warning(0, |
||||||
|
tr("Location not writable","Title of permissions popup"), |
||||||
|
tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup")); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
//everything ok!
|
||||||
|
Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::setBackgroundColor(const QColor &c, bool whiteFont) |
||||||
|
{ |
||||||
|
if(c != backgroundColor) |
||||||
|
{ |
||||||
|
backgroundColorAnimation->setStartValue(backgroundColor); |
||||||
|
backgroundColorAnimation->setEndValue(c); |
||||||
|
backgroundColorAnimation->start(); |
||||||
|
} |
||||||
|
|
||||||
|
setProperty("fontColor", whiteFont ? "white" : "black"); |
||||||
|
|
||||||
|
setStyleSheet(Style::getStylesheet(":/ui/fileTransferInstance/filetransferWidget.css")); |
||||||
|
Style::repolish(this); |
||||||
|
|
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::setButtonColor(const QColor &c) |
||||||
|
{ |
||||||
|
if(c != buttonColor) |
||||||
|
{ |
||||||
|
buttonColorAnimation->setStartValue(buttonColor); |
||||||
|
buttonColorAnimation->setEndValue(c); |
||||||
|
buttonColorAnimation->start(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool FileTransferWidget::isFilePathWritable(const QString &filepath) const |
||||||
|
{ |
||||||
|
QFile tmp(filepath); |
||||||
|
bool writable = tmp.open(QIODevice::WriteOnly); |
||||||
|
tmp.remove(); |
||||||
|
return writable; |
||||||
|
} |
||||||
|
|
||||||
|
bool FileTransferWidget::drawButtonAreaNeeded() const |
||||||
|
{ |
||||||
|
return (ui->bottomButton->isVisible() || ui->topButton->isVisible()) && |
||||||
|
!(ui->topButton->isVisible() && ui->topButton->objectName() == "ok"); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::paintEvent(QPaintEvent *) |
||||||
|
{ |
||||||
|
// required by Hi-DPI support as border-image doesn't work.
|
||||||
|
QPainter painter(this); |
||||||
|
painter.setRenderHint(QPainter::Antialiasing); |
||||||
|
painter.setPen(Qt::NoPen); |
||||||
|
|
||||||
|
qreal ratio = static_cast<qreal>(geometry().height()) / static_cast<qreal>(geometry().width()); |
||||||
|
const int r = 24; |
||||||
|
const int buttonFieldWidth = 34; |
||||||
|
const int lineWidth = 1; |
||||||
|
|
||||||
|
// draw background
|
||||||
|
if(drawButtonAreaNeeded()) |
||||||
|
painter.setClipRect(QRect(0,0,width()-buttonFieldWidth,height())); |
||||||
|
painter.setBrush(QBrush(backgroundColor)); |
||||||
|
painter.drawRoundRect(geometry(), r * ratio, r); |
||||||
|
|
||||||
|
if(drawButtonAreaNeeded()) |
||||||
|
{ |
||||||
|
// draw button background (top)
|
||||||
|
painter.setBrush(QBrush(buttonColor)); |
||||||
|
painter.setClipRect(QRect(width()-buttonFieldWidth+lineWidth,0,buttonFieldWidth,height()/2-ceil(lineWidth/2.0))); |
||||||
|
painter.drawRoundRect(geometry(), r * ratio, r); |
||||||
|
|
||||||
|
// draw button background (bottom)
|
||||||
|
painter.setBrush(QBrush(buttonColor)); |
||||||
|
painter.setClipRect(QRect(width()-buttonFieldWidth+lineWidth,height()/2+lineWidth/2,buttonFieldWidth,height()/2)); |
||||||
|
painter.drawRoundRect(geometry(), r * ratio, r); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::onFileTransferInfo(ToxFile file) |
||||||
|
{ |
||||||
|
QTime now = QTime::currentTime(); |
||||||
|
qint64 dt = lastTick.msecsTo(now); //ms
|
||||||
|
|
||||||
|
if(fileInfo != file || dt < 1000) |
||||||
|
return; |
||||||
|
|
||||||
|
fileInfo = file; |
||||||
|
|
||||||
|
if(fileInfo.status == ToxFile::TRANSMITTING) |
||||||
|
{ |
||||||
|
// update progress
|
||||||
|
qreal progress = static_cast<qreal>(file.bytesSent) / static_cast<qreal>(file.filesize); |
||||||
|
ui->progressBar->setValue(static_cast<int>(progress * 100.0)); |
||||||
|
|
||||||
|
// ETA, speed
|
||||||
|
qreal deltaSecs = dt / 1000.0; |
||||||
|
|
||||||
|
qint64 deltaBytes = qMax(file.bytesSent - lastBytesSent, qint64(0)); |
||||||
|
qreal bytesPerSec = static_cast<int>(static_cast<qreal>(deltaBytes) / deltaSecs); |
||||||
|
|
||||||
|
// calculate mean
|
||||||
|
meanIndex = meanIndex % TRANSFER_ROLLING_AVG_COUNT; |
||||||
|
meanData[meanIndex++] = bytesPerSec; |
||||||
|
|
||||||
|
qreal meanBytesPerSec = 0.0; |
||||||
|
for(size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i) |
||||||
|
meanBytesPerSec += meanData[i]; |
||||||
|
|
||||||
|
meanBytesPerSec /= static_cast<qreal>(TRANSFER_ROLLING_AVG_COUNT); |
||||||
|
|
||||||
|
// update UI
|
||||||
|
if(meanBytesPerSec > 0) |
||||||
|
{ |
||||||
|
// ETA
|
||||||
|
QTime toGo = QTime(0,0).addSecs((file.filesize - file.bytesSent) / meanBytesPerSec); |
||||||
|
QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss"; |
||||||
|
ui->etaLabel->setText(toGo.toString(format)); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
ui->etaLabel->setText(""); |
||||||
|
} |
||||||
|
|
||||||
|
ui->progressLabel->setText(getHumanReadableSize(meanBytesPerSec) + "/s"); |
||||||
|
|
||||||
|
lastBytesSent = file.bytesSent; |
||||||
|
} |
||||||
|
|
||||||
|
lastTick = now; |
||||||
|
|
||||||
|
// trigger repaint
|
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::onFileTransferAccepted(ToxFile file) |
||||||
|
{ |
||||||
|
if(fileInfo != file) |
||||||
|
return; |
||||||
|
|
||||||
|
fileInfo = file; |
||||||
|
|
||||||
|
setBackgroundColor(Style::getColor(Style::LightGrey), false); |
||||||
|
|
||||||
|
setupButtons(); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::onFileTransferCancelled(ToxFile file) |
||||||
|
{ |
||||||
|
if(fileInfo != file) |
||||||
|
return; |
||||||
|
|
||||||
|
fileInfo = file; |
||||||
|
|
||||||
|
setBackgroundColor(Style::getColor(Style::Red), true); |
||||||
|
|
||||||
|
setupButtons(); |
||||||
|
hideWidgets(); |
||||||
|
|
||||||
|
disconnect(Core::getInstance(), 0, this, 0); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::onFileTransferPaused(ToxFile file) |
||||||
|
{ |
||||||
|
if(fileInfo != file) |
||||||
|
return; |
||||||
|
|
||||||
|
fileInfo = file; |
||||||
|
|
||||||
|
ui->etaLabel->setText(""); |
||||||
|
ui->progressLabel->setText(tr("paused", "file transfer widget")); |
||||||
|
|
||||||
|
// reset mean
|
||||||
|
meanIndex = 0; |
||||||
|
for(size_t i=0; i<TRANSFER_ROLLING_AVG_COUNT; ++i) |
||||||
|
meanData[i] = 0.0; |
||||||
|
|
||||||
|
setBackgroundColor(Style::getColor(Style::LightGrey), false); |
||||||
|
|
||||||
|
setupButtons(); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::onFileTransferFinished(ToxFile file) |
||||||
|
{ |
||||||
|
if(fileInfo != file) |
||||||
|
return; |
||||||
|
|
||||||
|
fileInfo = file; |
||||||
|
|
||||||
|
setBackgroundColor(Style::getColor(Style::Green), true); |
||||||
|
|
||||||
|
setupButtons(); |
||||||
|
hideWidgets(); |
||||||
|
|
||||||
|
static const QStringList openExtensions = { "png", "jpeg", "jpg", "gif", "zip", "rar" }; |
||||||
|
|
||||||
|
if(openExtensions.contains(QFileInfo(file.fileName).suffix())) |
||||||
|
{ |
||||||
|
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/yes.svg")); |
||||||
|
ui->topButton->setObjectName("ok"); |
||||||
|
ui->topButton->show(); |
||||||
|
} |
||||||
|
|
||||||
|
// preview
|
||||||
|
if(fileInfo.direction == ToxFile::RECEIVING) |
||||||
|
showPreview(fileInfo.filePath); |
||||||
|
|
||||||
|
disconnect(Core::getInstance(), 0, this, 0); |
||||||
|
} |
||||||
|
|
||||||
|
QString FileTransferWidget::getHumanReadableSize(qint64 size) |
||||||
|
{ |
||||||
|
static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"}; |
||||||
|
int exp = 0; |
||||||
|
|
||||||
|
if (size > 0) |
||||||
|
exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1)); |
||||||
|
|
||||||
|
return QString().setNum(size / pow(1024, exp),'f', exp > 1 ? 2 : 0).append(suffix[exp]); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::hideWidgets() |
||||||
|
{ |
||||||
|
ui->topButton->hide(); |
||||||
|
ui->bottomButton->hide(); |
||||||
|
ui->progressBar->hide(); |
||||||
|
ui->progressLabel->hide(); |
||||||
|
ui->etaLabel->hide(); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::setupButtons() |
||||||
|
{ |
||||||
|
switch(fileInfo.status) |
||||||
|
{ |
||||||
|
case ToxFile::TRANSMITTING: |
||||||
|
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/no.svg")); |
||||||
|
ui->topButton->setObjectName("cancel"); |
||||||
|
|
||||||
|
ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/pause.svg")); |
||||||
|
ui->bottomButton->setObjectName("pause"); |
||||||
|
|
||||||
|
setButtonColor(Style::getColor(Style::Green)); |
||||||
|
|
||||||
|
break; |
||||||
|
case ToxFile::PAUSED: |
||||||
|
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/no.svg")); |
||||||
|
ui->topButton->setObjectName("cancel"); |
||||||
|
|
||||||
|
ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/arrow_white.svg")); |
||||||
|
ui->bottomButton->setObjectName("resume"); |
||||||
|
|
||||||
|
setButtonColor(Style::getColor(Style::LightGrey)); |
||||||
|
|
||||||
|
break; |
||||||
|
case ToxFile::STOPPED: |
||||||
|
case ToxFile::BROKEN: //TODO: ?
|
||||||
|
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/no.svg")); |
||||||
|
ui->topButton->setObjectName("cancel"); |
||||||
|
|
||||||
|
if(fileInfo.direction == ToxFile::SENDING) |
||||||
|
{ |
||||||
|
ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/pause.svg")); |
||||||
|
ui->bottomButton->setObjectName("pause"); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/yes.svg")); |
||||||
|
ui->bottomButton->setObjectName("accept"); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::handleButton(QPushButton *btn) |
||||||
|
{ |
||||||
|
if(fileInfo.direction == ToxFile::SENDING) |
||||||
|
{ |
||||||
|
if(btn->objectName() == "cancel") |
||||||
|
Core::getInstance()->cancelFileSend(fileInfo.friendId, fileInfo.fileNum); |
||||||
|
else if(btn->objectName() == "pause") |
||||||
|
Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum); |
||||||
|
else if(btn->objectName() == "resume") |
||||||
|
Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
if(btn->objectName() == "cancel") |
||||||
|
Core::getInstance()->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum); |
||||||
|
else if(btn->objectName() == "pause") |
||||||
|
Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum); |
||||||
|
else if(btn->objectName() == "resume") |
||||||
|
Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum); |
||||||
|
else if(btn->objectName() == "accept") |
||||||
|
{ |
||||||
|
QString path = QFileDialog::getSaveFileName(0, tr("Save a file","Title of the file saving dialog"), QDir::home().filePath(fileInfo.fileName)); |
||||||
|
acceptTransfer(path); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(btn->objectName() == "ok") |
||||||
|
{ |
||||||
|
QDesktopServices::openUrl("file://" + fileInfo.filePath); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::showPreview(const QString &filename) |
||||||
|
{ |
||||||
|
static const QStringList previewExtensions = { "png", "jpeg", "jpg", "gif" }; |
||||||
|
|
||||||
|
if(previewExtensions.contains(QFileInfo(filename).suffix())) |
||||||
|
{ |
||||||
|
const int size = qMax(ui->previewLabel->width(), ui->previewLabel->height()); |
||||||
|
QPixmap pmap = QPixmap(filename).scaled(QSize(size, size), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); |
||||||
|
ui->previewLabel->setPixmap(pmap); |
||||||
|
ui->previewLabel->show(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::on_topButton_clicked() |
||||||
|
{ |
||||||
|
handleButton(ui->topButton); |
||||||
|
} |
||||||
|
|
||||||
|
void FileTransferWidget::on_bottomButton_clicked() |
||||||
|
{ |
||||||
|
handleButton(ui->bottomButton); |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef FILETRANSFERWIDGET_H |
||||||
|
#define FILETRANSFERWIDGET_H |
||||||
|
|
||||||
|
#include <QWidget> |
||||||
|
#include <QTime> |
||||||
|
|
||||||
|
#include "../chatlinecontent.h" |
||||||
|
#include "../../corestructs.h" |
||||||
|
|
||||||
|
|
||||||
|
namespace Ui { |
||||||
|
class FileTransferWidget; |
||||||
|
} |
||||||
|
|
||||||
|
class QVariantAnimation; |
||||||
|
class QPushButton; |
||||||
|
|
||||||
|
class FileTransferWidget : public QWidget |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
|
||||||
|
public: |
||||||
|
explicit FileTransferWidget(QWidget *parent, ToxFile file); |
||||||
|
virtual ~FileTransferWidget(); |
||||||
|
void autoAcceptTransfer(const QString& path); |
||||||
|
|
||||||
|
protected slots: |
||||||
|
void onFileTransferInfo(ToxFile file); |
||||||
|
void onFileTransferAccepted(ToxFile file); |
||||||
|
void onFileTransferCancelled(ToxFile file); |
||||||
|
void onFileTransferPaused(ToxFile file); |
||||||
|
void onFileTransferFinished(ToxFile file); |
||||||
|
|
||||||
|
protected: |
||||||
|
QString getHumanReadableSize(qint64 size); |
||||||
|
void hideWidgets(); |
||||||
|
void setupButtons(); |
||||||
|
void handleButton(QPushButton* btn); |
||||||
|
void showPreview(const QString& filename); |
||||||
|
void acceptTransfer(const QString& filepath); |
||||||
|
void setBackgroundColor(const QColor& c, bool whiteFont); |
||||||
|
void setButtonColor(const QColor& c); |
||||||
|
|
||||||
|
bool isFilePathWritable(const QString& filepath) const; |
||||||
|
bool drawButtonAreaNeeded() const; |
||||||
|
|
||||||
|
virtual void paintEvent(QPaintEvent*); |
||||||
|
|
||||||
|
private slots: |
||||||
|
void on_topButton_clicked(); |
||||||
|
void on_bottomButton_clicked(); |
||||||
|
|
||||||
|
private: |
||||||
|
Ui::FileTransferWidget *ui; |
||||||
|
ToxFile fileInfo; |
||||||
|
QTime lastTick; |
||||||
|
qint64 lastBytesSent = 0; |
||||||
|
QVariantAnimation* backgroundColorAnimation = nullptr; |
||||||
|
QVariantAnimation* buttonColorAnimation = nullptr; |
||||||
|
QColor backgroundColor; |
||||||
|
QColor buttonColor; |
||||||
|
|
||||||
|
static const uint8_t TRANSFER_ROLLING_AVG_COUNT = 4; |
||||||
|
uint8_t meanIndex = 0; |
||||||
|
qreal meanData[TRANSFER_ROLLING_AVG_COUNT] = {0.0}; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // FILETRANSFERWIDGET_H
|
@ -0,0 +1,445 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<ui version="4.0"> |
||||||
|
<class>FileTransferWidget</class> |
||||||
|
<widget class="QWidget" name="FileTransferWidget"> |
||||||
|
<property name="geometry"> |
||||||
|
<rect> |
||||||
|
<x>0</x> |
||||||
|
<y>0</y> |
||||||
|
<width>625</width> |
||||||
|
<height>243</height> |
||||||
|
</rect> |
||||||
|
</property> |
||||||
|
<property name="windowTitle"> |
||||||
|
<string>Form</string> |
||||||
|
</property> |
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2"> |
||||||
|
<property name="spacing"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="leftMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="topMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="rightMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="bottomMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<item> |
||||||
|
<widget class="QFrame" name="frame_3"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="styleSheet"> |
||||||
|
<string notr="true"/> |
||||||
|
</property> |
||||||
|
<property name="frameShape"> |
||||||
|
<enum>QFrame::NoFrame</enum> |
||||||
|
</property> |
||||||
|
<property name="frameShadow"> |
||||||
|
<enum>QFrame::Plain</enum> |
||||||
|
</property> |
||||||
|
<property name="lineWidth"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout"> |
||||||
|
<property name="spacing"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="leftMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="topMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="rightMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="bottomMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<item> |
||||||
|
<layout class="QGridLayout" name="gridLayout_3" rowstretch="1,0,1"> |
||||||
|
<property name="leftMargin"> |
||||||
|
<number>10</number> |
||||||
|
</property> |
||||||
|
<property name="rightMargin"> |
||||||
|
<number>6</number> |
||||||
|
</property> |
||||||
|
<property name="bottomMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="horizontalSpacing"> |
||||||
|
<number>6</number> |
||||||
|
</property> |
||||||
|
<property name="verticalSpacing"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<item row="0" column="1"> |
||||||
|
<spacer name="verticalSpacer_2"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>20</width> |
||||||
|
<height>40</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
<item row="1" column="1"> |
||||||
|
<layout class="QGridLayout" name="gridLayout_2"> |
||||||
|
<property name="sizeConstraint"> |
||||||
|
<enum>QLayout::SetDefaultConstraint</enum> |
||||||
|
</property> |
||||||
|
<property name="leftMargin"> |
||||||
|
<number>6</number> |
||||||
|
</property> |
||||||
|
<property name="topMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="rightMargin"> |
||||||
|
<number>6</number> |
||||||
|
</property> |
||||||
|
<property name="bottomMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="horizontalSpacing"> |
||||||
|
<number>6</number> |
||||||
|
</property> |
||||||
|
<property name="verticalSpacing"> |
||||||
|
<number>3</number> |
||||||
|
</property> |
||||||
|
<item row="1" column="0"> |
||||||
|
<widget class="QWidget" name="statusWidget" native="true"> |
||||||
|
<layout class="QHBoxLayout" name="_2"> |
||||||
|
<property name="spacing"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="leftMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="topMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="rightMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="bottomMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<item> |
||||||
|
<widget class="QLabel" name="fileSizeLabel"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string>10Mb</string> |
||||||
|
</property> |
||||||
|
<property name="alignment"> |
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item> |
||||||
|
<widget class="QLabel" name="progressLabel"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string>0kb/s</string> |
||||||
|
</property> |
||||||
|
<property name="alignment"> |
||||||
|
<set>Qt::AlignCenter</set> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item> |
||||||
|
<widget class="QLabel" name="etaLabel"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string>ETA:10:10</string> |
||||||
|
</property> |
||||||
|
<property name="alignment"> |
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
</layout> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item row="2" column="0"> |
||||||
|
<widget class="QProgressBar" name="progressBar"> |
||||||
|
<property name="maximumSize"> |
||||||
|
<size> |
||||||
|
<width>16777215</width> |
||||||
|
<height>12</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
<property name="styleSheet"> |
||||||
|
<string notr="true"/> |
||||||
|
</property> |
||||||
|
<property name="value"> |
||||||
|
<number>24</number> |
||||||
|
</property> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Horizontal</enum> |
||||||
|
</property> |
||||||
|
<property name="invertedAppearance"> |
||||||
|
<bool>false</bool> |
||||||
|
</property> |
||||||
|
<property name="format"> |
||||||
|
<string/> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item row="0" column="0"> |
||||||
|
<widget class="CroppingLabel" name="filenameLabel"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string>Filename</string> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item row="0" column="1"> |
||||||
|
<spacer name="verticalSpacer_5"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>1</width> |
||||||
|
<height>20</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
<item row="1" column="1"> |
||||||
|
<spacer name="verticalSpacer_6"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>1</width> |
||||||
|
<height>21</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
<item row="2" column="1"> |
||||||
|
<spacer name="verticalSpacer_7"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeType"> |
||||||
|
<enum>QSizePolicy::Preferred</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>1</width> |
||||||
|
<height>20</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
</layout> |
||||||
|
</item> |
||||||
|
<item row="1" column="0"> |
||||||
|
<widget class="QLabel" name="previewLabel"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="minimumSize"> |
||||||
|
<size> |
||||||
|
<width>60</width> |
||||||
|
<height>60</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
<property name="maximumSize"> |
||||||
|
<size> |
||||||
|
<width>60</width> |
||||||
|
<height>60</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
<property name="frameShape"> |
||||||
|
<enum>QFrame::Box</enum> |
||||||
|
</property> |
||||||
|
<property name="lineWidth"> |
||||||
|
<number>2</number> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string>[preview]</string> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item row="2" column="1"> |
||||||
|
<spacer name="verticalSpacer"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>20</width> |
||||||
|
<height>40</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
</layout> |
||||||
|
</item> |
||||||
|
<item> |
||||||
|
<widget class="QWidget" name="buttonWidget" native="true"> |
||||||
|
<layout class="QGridLayout" name="gridLayout"> |
||||||
|
<property name="leftMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="topMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="rightMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="bottomMargin"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<property name="spacing"> |
||||||
|
<number>0</number> |
||||||
|
</property> |
||||||
|
<item row="1" column="0"> |
||||||
|
<widget class="QPushButton" name="bottomButton"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Expanding"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="maximumSize"> |
||||||
|
<size> |
||||||
|
<width>32</width> |
||||||
|
<height>16777215</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
<property name="cursor"> |
||||||
|
<cursorShape>PointingHandCursor</cursorShape> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string/> |
||||||
|
</property> |
||||||
|
<property name="icon"> |
||||||
|
<iconset resource="../../../res.qrc"> |
||||||
|
<normaloff>:/ui/fileTransferInstance/no.svg</normaloff>:/ui/fileTransferInstance/no.svg</iconset> |
||||||
|
</property> |
||||||
|
<property name="iconSize"> |
||||||
|
<size> |
||||||
|
<width>32</width> |
||||||
|
<height>18</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item row="0" column="0"> |
||||||
|
<widget class="QPushButton" name="topButton"> |
||||||
|
<property name="sizePolicy"> |
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Expanding"> |
||||||
|
<horstretch>0</horstretch> |
||||||
|
<verstretch>0</verstretch> |
||||||
|
</sizepolicy> |
||||||
|
</property> |
||||||
|
<property name="maximumSize"> |
||||||
|
<size> |
||||||
|
<width>32</width> |
||||||
|
<height>16777215</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
<property name="cursor"> |
||||||
|
<cursorShape>PointingHandCursor</cursorShape> |
||||||
|
</property> |
||||||
|
<property name="text"> |
||||||
|
<string/> |
||||||
|
</property> |
||||||
|
<property name="icon"> |
||||||
|
<iconset resource="../../../res.qrc"> |
||||||
|
<normaloff>:/ui/fileTransferInstance/no.svg</normaloff>:/ui/fileTransferInstance/no.svg</iconset> |
||||||
|
</property> |
||||||
|
<property name="iconSize"> |
||||||
|
<size> |
||||||
|
<width>32</width> |
||||||
|
<height>18</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
<item row="0" column="1"> |
||||||
|
<spacer name="verticalSpacer_3"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>0</width> |
||||||
|
<height>40</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
<item row="1" column="1"> |
||||||
|
<spacer name="verticalSpacer_4"> |
||||||
|
<property name="orientation"> |
||||||
|
<enum>Qt::Vertical</enum> |
||||||
|
</property> |
||||||
|
<property name="sizeHint" stdset="0"> |
||||||
|
<size> |
||||||
|
<width>0</width> |
||||||
|
<height>40</height> |
||||||
|
</size> |
||||||
|
</property> |
||||||
|
</spacer> |
||||||
|
</item> |
||||||
|
</layout> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
</layout> |
||||||
|
</widget> |
||||||
|
</item> |
||||||
|
</layout> |
||||||
|
</widget> |
||||||
|
<customwidgets> |
||||||
|
<customwidget> |
||||||
|
<class>CroppingLabel</class> |
||||||
|
<extends>QLabel</extends> |
||||||
|
<header>src/widget/croppinglabel.h</header> |
||||||
|
</customwidget> |
||||||
|
</customwidgets> |
||||||
|
<resources> |
||||||
|
<include location="../../../res.qrc"/> |
||||||
|
</resources> |
||||||
|
<connections/> |
||||||
|
</ui> |
@ -0,0 +1,53 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "image.h" |
||||||
|
#include "../pixmapcache.h" |
||||||
|
|
||||||
|
#include <QPainter> |
||||||
|
|
||||||
|
Image::Image(QSize Size, const QString& filename) |
||||||
|
: size(Size) |
||||||
|
{ |
||||||
|
pmap = PixmapCache::getInstance().get(filename, size); |
||||||
|
} |
||||||
|
|
||||||
|
QRectF Image::boundingRect() const |
||||||
|
{ |
||||||
|
return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); |
||||||
|
} |
||||||
|
|
||||||
|
qreal Image::getAscent() const |
||||||
|
{ |
||||||
|
return 0.0; |
||||||
|
} |
||||||
|
|
||||||
|
void Image::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) |
||||||
|
{ |
||||||
|
painter->setClipRect(boundingRect()); |
||||||
|
|
||||||
|
painter->setRenderHint(QPainter::SmoothPixmapTransform); |
||||||
|
painter->translate(-size.width() / 2.0, -size.height() / 2.0); |
||||||
|
painter->drawPixmap(0, 0, pmap); |
||||||
|
|
||||||
|
Q_UNUSED(option) |
||||||
|
Q_UNUSED(widget) |
||||||
|
} |
||||||
|
|
||||||
|
void Image::setWidth(qreal width) |
||||||
|
{ |
||||||
|
Q_UNUSED(width) |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "notificationicon.h" |
||||||
|
#include "../pixmapcache.h" |
||||||
|
|
||||||
|
#include <QPainter> |
||||||
|
#include <QTimer> |
||||||
|
#include <QGraphicsScene> |
||||||
|
|
||||||
|
NotificationIcon::NotificationIcon(QSize Size) |
||||||
|
: size(Size) |
||||||
|
{ |
||||||
|
pmap = PixmapCache::getInstance().get(":/ui/chatArea/typing.svg", size); |
||||||
|
|
||||||
|
updateTimer = new QTimer(this); |
||||||
|
updateTimer->setInterval(1000/30); |
||||||
|
updateTimer->setSingleShot(false); |
||||||
|
|
||||||
|
updateTimer->start(); |
||||||
|
|
||||||
|
connect(updateTimer, &QTimer::timeout, this, &NotificationIcon::updateGradient); |
||||||
|
} |
||||||
|
|
||||||
|
QRectF NotificationIcon::boundingRect() const |
||||||
|
{ |
||||||
|
return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); |
||||||
|
} |
||||||
|
|
||||||
|
void NotificationIcon::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) |
||||||
|
{ |
||||||
|
painter->setClipRect(boundingRect()); |
||||||
|
|
||||||
|
painter->setRenderHint(QPainter::SmoothPixmapTransform); |
||||||
|
painter->translate(-size.width() / 2.0, -size.height() / 2.0); |
||||||
|
|
||||||
|
painter->fillRect(QRect(0, 0, size.width(), size.height()), grad); |
||||||
|
painter->drawPixmap(0, 0, size.width(), size.height(), pmap); |
||||||
|
|
||||||
|
Q_UNUSED(option) |
||||||
|
Q_UNUSED(widget) |
||||||
|
} |
||||||
|
|
||||||
|
void NotificationIcon::setWidth(qreal width) |
||||||
|
{ |
||||||
|
Q_UNUSED(width) |
||||||
|
} |
||||||
|
|
||||||
|
qreal NotificationIcon::getAscent() const |
||||||
|
{ |
||||||
|
return 3.0; |
||||||
|
} |
||||||
|
|
||||||
|
void NotificationIcon::updateGradient() |
||||||
|
{ |
||||||
|
alpha += 0.01; |
||||||
|
|
||||||
|
if(alpha + dotWidth >= 1.0) |
||||||
|
alpha = 0.0; |
||||||
|
|
||||||
|
grad = QLinearGradient(QPointF(-0.5*size.width(),0), QPointF(3.0/2.0*size.width(),0)); |
||||||
|
grad.setColorAt(0, Qt::lightGray); |
||||||
|
grad.setColorAt(qMax(0.0, alpha - dotWidth), Qt::lightGray); |
||||||
|
grad.setColorAt(alpha, Qt::black); |
||||||
|
grad.setColorAt(qMin(1.0, alpha + dotWidth), Qt::lightGray); |
||||||
|
grad.setColorAt(1, Qt::lightGray); |
||||||
|
|
||||||
|
if(scene() && isVisible()) |
||||||
|
scene()->invalidate(sceneBoundingRect()); |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef NOTIFICATIONICON_H |
||||||
|
#define NOTIFICATIONICON_H |
||||||
|
|
||||||
|
#include "../chatlinecontent.h" |
||||||
|
|
||||||
|
#include <QLinearGradient> |
||||||
|
#include <QPixmap> |
||||||
|
|
||||||
|
class QTimer; |
||||||
|
|
||||||
|
class NotificationIcon : public QObject, public ChatLineContent |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
NotificationIcon(QSize size); |
||||||
|
|
||||||
|
virtual QRectF boundingRect() const override; |
||||||
|
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; |
||||||
|
virtual void setWidth(qreal width) override; |
||||||
|
virtual qreal getAscent() const override; |
||||||
|
|
||||||
|
private slots: |
||||||
|
void updateGradient(); |
||||||
|
|
||||||
|
private: |
||||||
|
QSize size; |
||||||
|
QPixmap pmap; |
||||||
|
QLinearGradient grad; |
||||||
|
QTimer* updateTimer = nullptr; |
||||||
|
|
||||||
|
qreal dotWidth = 0.2; |
||||||
|
qreal alpha = 0.0; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // NOTIFICATIONICON_H
|
@ -0,0 +1,88 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "spinner.h" |
||||||
|
#include "../pixmapcache.h" |
||||||
|
|
||||||
|
#include <QPainter> |
||||||
|
#include <QGraphicsScene> |
||||||
|
#include <QTime> |
||||||
|
#include <QVariantAnimation> |
||||||
|
#include <QDebug> |
||||||
|
|
||||||
|
Spinner::Spinner(const QString &img, QSize Size, qreal speed) |
||||||
|
: size(Size) |
||||||
|
, rotSpeed(speed) |
||||||
|
{ |
||||||
|
pmap = PixmapCache::getInstance().get(img, size); |
||||||
|
|
||||||
|
timer.setInterval(1000/30); // 30Hz
|
||||||
|
timer.setSingleShot(false); |
||||||
|
|
||||||
|
blendAnimation = new QVariantAnimation(this); |
||||||
|
blendAnimation->setStartValue(0.0); |
||||||
|
blendAnimation->setEndValue(1.0); |
||||||
|
blendAnimation->setDuration(350); |
||||||
|
blendAnimation->setEasingCurve(QEasingCurve::InCubic); |
||||||
|
blendAnimation->start(QAbstractAnimation::DeleteWhenStopped); |
||||||
|
connect(blendAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { alpha = val.toDouble(); }); |
||||||
|
|
||||||
|
QObject::connect(&timer, &QTimer::timeout, this, &Spinner::timeout); |
||||||
|
} |
||||||
|
|
||||||
|
QRectF Spinner::boundingRect() const |
||||||
|
{ |
||||||
|
return QRectF(QPointF(-size.width() / 2.0, -size.height() / 2.0), size); |
||||||
|
} |
||||||
|
|
||||||
|
void Spinner::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) |
||||||
|
{ |
||||||
|
painter->setClipRect(boundingRect()); |
||||||
|
|
||||||
|
QTransform trans = QTransform().rotate(QTime::currentTime().msecsSinceStartOfDay() / 1000.0 * rotSpeed) |
||||||
|
.translate(-size.width()/2.0, -size.height()/2.0); |
||||||
|
painter->setOpacity(alpha); |
||||||
|
painter->setTransform(trans, true); |
||||||
|
painter->setRenderHint(QPainter::SmoothPixmapTransform); |
||||||
|
painter->drawPixmap(0, 0, pmap); |
||||||
|
|
||||||
|
Q_UNUSED(option) |
||||||
|
Q_UNUSED(widget) |
||||||
|
} |
||||||
|
|
||||||
|
void Spinner::setWidth(qreal width) |
||||||
|
{ |
||||||
|
Q_UNUSED(width) |
||||||
|
} |
||||||
|
|
||||||
|
void Spinner::visibilityChanged(bool visible) |
||||||
|
{ |
||||||
|
if(visible) |
||||||
|
timer.start(); |
||||||
|
else |
||||||
|
timer.stop(); |
||||||
|
} |
||||||
|
|
||||||
|
qreal Spinner::getAscent() const |
||||||
|
{ |
||||||
|
return 0.0; |
||||||
|
} |
||||||
|
|
||||||
|
void Spinner::timeout() |
||||||
|
{ |
||||||
|
if(scene()) |
||||||
|
scene()->invalidate(sceneBoundingRect()); |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef SPINNER_H |
||||||
|
#define SPINNER_H |
||||||
|
|
||||||
|
#include "../chatlinecontent.h" |
||||||
|
|
||||||
|
#include <QTimer> |
||||||
|
#include <QObject> |
||||||
|
#include <QPixmap> |
||||||
|
|
||||||
|
class QVariantAnimation; |
||||||
|
|
||||||
|
class Spinner : public QObject, public ChatLineContent |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
Spinner(const QString& img, QSize size, qreal speed); |
||||||
|
|
||||||
|
virtual QRectF boundingRect() const override; |
||||||
|
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; |
||||||
|
virtual void setWidth(qreal width) override; |
||||||
|
virtual void visibilityChanged(bool visible) override; |
||||||
|
virtual qreal getAscent() const override; |
||||||
|
|
||||||
|
private slots: |
||||||
|
void timeout(); |
||||||
|
|
||||||
|
private: |
||||||
|
QSize size; |
||||||
|
QPixmap pmap; |
||||||
|
qreal rotSpeed; |
||||||
|
QTimer timer; |
||||||
|
qreal alpha = 0.0; |
||||||
|
QVariantAnimation* blendAnimation; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // SPINNER_H
|
@ -0,0 +1,351 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "text.h" |
||||||
|
#include "../documentcache.h" |
||||||
|
|
||||||
|
#include <QFontMetrics> |
||||||
|
#include <QPainter> |
||||||
|
#include <QPalette> |
||||||
|
#include <QDebug> |
||||||
|
#include <QTextBlock> |
||||||
|
#include <QAbstractTextDocumentLayout> |
||||||
|
#include <QApplication> |
||||||
|
#include <QGraphicsSceneMouseEvent> |
||||||
|
#include <QDesktopServices> |
||||||
|
#include <QTextFragment> |
||||||
|
|
||||||
|
Text::Text(const QString& txt, QFont font, bool enableElide, const QString &rwText) |
||||||
|
: rawText(rwText) |
||||||
|
, elide(enableElide) |
||||||
|
, defFont(font) |
||||||
|
{ |
||||||
|
setText(txt); |
||||||
|
setAcceptedMouseButtons(Qt::LeftButton); |
||||||
|
setAcceptHoverEvents(true); |
||||||
|
} |
||||||
|
|
||||||
|
Text::~Text() |
||||||
|
{ |
||||||
|
if(doc) |
||||||
|
DocumentCache::getInstance().push(doc); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::setText(const QString& txt) |
||||||
|
{ |
||||||
|
text = txt; |
||||||
|
dirty = true; |
||||||
|
} |
||||||
|
|
||||||
|
void Text::setWidth(qreal w) |
||||||
|
{ |
||||||
|
if(w == width) |
||||||
|
return; |
||||||
|
|
||||||
|
width = w; |
||||||
|
dirty = true; |
||||||
|
|
||||||
|
if(elide) |
||||||
|
{ |
||||||
|
QFontMetrics metrics = QFontMetrics(defFont); |
||||||
|
elidedText = metrics.elidedText(text, Qt::ElideRight, width); |
||||||
|
} |
||||||
|
|
||||||
|
regenerate(); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::selectionMouseMove(QPointF scenePos) |
||||||
|
{ |
||||||
|
if(!doc) |
||||||
|
return; |
||||||
|
|
||||||
|
int cur = cursorFromPos(scenePos); |
||||||
|
if(cur >= 0) |
||||||
|
{ |
||||||
|
selectionEnd = cur; |
||||||
|
selectedText = extractSanitizedText(getSelectionStart(), getSelectionEnd()); |
||||||
|
} |
||||||
|
|
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::selectionStarted(QPointF scenePos) |
||||||
|
{ |
||||||
|
int cur = cursorFromPos(scenePos); |
||||||
|
if(cur >= 0) |
||||||
|
{ |
||||||
|
selectionEnd = cur; |
||||||
|
selectionAnchor = cur; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Text::selectionCleared() |
||||||
|
{ |
||||||
|
selectedText.clear(); |
||||||
|
selectedText.squeeze(); |
||||||
|
|
||||||
|
// Do not reset selectionAnchor!
|
||||||
|
selectionEnd = -1; |
||||||
|
|
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::selectionDoubleClick(QPointF scenePos) |
||||||
|
{ |
||||||
|
if(!doc) |
||||||
|
return; |
||||||
|
|
||||||
|
int cur = cursorFromPos(scenePos); |
||||||
|
|
||||||
|
if(cur >= 0) |
||||||
|
{ |
||||||
|
QTextCursor cursor(doc); |
||||||
|
cursor.setPosition(cur); |
||||||
|
cursor.select(QTextCursor::WordUnderCursor); |
||||||
|
|
||||||
|
selectionAnchor = cursor.selectionStart(); |
||||||
|
selectionEnd = cursor.selectionEnd(); |
||||||
|
|
||||||
|
selectedText = extractSanitizedText(getSelectionStart(), getSelectionEnd()); |
||||||
|
} |
||||||
|
|
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::selectionFocusChanged(bool focusIn) |
||||||
|
{ |
||||||
|
selectionHasFocus = focusIn; |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
bool Text::isOverSelection(QPointF scenePos) const |
||||||
|
{ |
||||||
|
int cur = cursorFromPos(scenePos); |
||||||
|
if(getSelectionStart() < cur && getSelectionEnd() >= cur) |
||||||
|
return true; |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
QString Text::getSelectedText() const |
||||||
|
{ |
||||||
|
return selectedText; |
||||||
|
} |
||||||
|
|
||||||
|
QRectF Text::boundingRect() const |
||||||
|
{ |
||||||
|
return QRectF(QPointF(0, 0), size); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) |
||||||
|
{ |
||||||
|
if(doc) |
||||||
|
{ |
||||||
|
painter->setClipRect(boundingRect()); |
||||||
|
|
||||||
|
// draw selection
|
||||||
|
QAbstractTextDocumentLayout::PaintContext ctx; |
||||||
|
QAbstractTextDocumentLayout::Selection sel; |
||||||
|
|
||||||
|
if(hasSelection()) |
||||||
|
{ |
||||||
|
sel.cursor = QTextCursor(doc); |
||||||
|
sel.cursor.setPosition(getSelectionStart()); |
||||||
|
sel.cursor.setPosition(getSelectionEnd(), QTextCursor::KeepAnchor); |
||||||
|
} |
||||||
|
|
||||||
|
const QColor selectionColor = QColor::fromRgbF(0.23, 0.68, 0.91); |
||||||
|
sel.format.setBackground(selectionColor.lighter(selectionHasFocus ? 100 : 160)); |
||||||
|
sel.format.setForeground(selectionHasFocus ? Qt::white : Qt::black); |
||||||
|
ctx.selections.append(sel); |
||||||
|
|
||||||
|
// draw text
|
||||||
|
doc->documentLayout()->draw(painter, ctx); |
||||||
|
} |
||||||
|
|
||||||
|
Q_UNUSED(option) |
||||||
|
Q_UNUSED(widget) |
||||||
|
} |
||||||
|
|
||||||
|
void Text::visibilityChanged(bool visible) |
||||||
|
{ |
||||||
|
keepInMemory = visible; |
||||||
|
|
||||||
|
regenerate(); |
||||||
|
update(); |
||||||
|
} |
||||||
|
|
||||||
|
qreal Text::getAscent() const |
||||||
|
{ |
||||||
|
return ascent; |
||||||
|
} |
||||||
|
|
||||||
|
void Text::mousePressEvent(QGraphicsSceneMouseEvent *event) |
||||||
|
{ |
||||||
|
if(event->button() == Qt::LeftButton) |
||||||
|
event->accept(); // grabber
|
||||||
|
} |
||||||
|
|
||||||
|
void Text::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
||||||
|
{ |
||||||
|
if(!doc) |
||||||
|
return; |
||||||
|
|
||||||
|
QString anchor = doc->documentLayout()->anchorAt(event->pos()); |
||||||
|
|
||||||
|
// open anchor in browser
|
||||||
|
if(!anchor.isEmpty()) |
||||||
|
QDesktopServices::openUrl(anchor); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::hoverMoveEvent(QGraphicsSceneHoverEvent *event) |
||||||
|
{ |
||||||
|
if(!doc) |
||||||
|
return; |
||||||
|
|
||||||
|
QString anchor = doc->documentLayout()->anchorAt(event->pos()); |
||||||
|
|
||||||
|
if(!anchor.isEmpty()) |
||||||
|
setCursor(QCursor(Qt::PointingHandCursor)); |
||||||
|
else |
||||||
|
setCursor(QCursor()); |
||||||
|
} |
||||||
|
|
||||||
|
QString Text::getText() const |
||||||
|
{ |
||||||
|
return rawText; |
||||||
|
} |
||||||
|
|
||||||
|
void Text::regenerate() |
||||||
|
{ |
||||||
|
if(!doc) |
||||||
|
{ |
||||||
|
doc = DocumentCache::getInstance().pop(); |
||||||
|
dirty = true; |
||||||
|
} |
||||||
|
|
||||||
|
if(dirty) |
||||||
|
{ |
||||||
|
doc->setDefaultFont(defFont); |
||||||
|
|
||||||
|
if(!elide) |
||||||
|
doc->setHtml(text); |
||||||
|
else |
||||||
|
doc->setPlainText(elidedText); |
||||||
|
|
||||||
|
// wrap mode
|
||||||
|
QTextOption opt; |
||||||
|
opt.setWrapMode(elide ? QTextOption::NoWrap : QTextOption::WrapAtWordBoundaryOrAnywhere); |
||||||
|
doc->setDefaultTextOption(opt); |
||||||
|
|
||||||
|
// width
|
||||||
|
doc->setTextWidth(width); |
||||||
|
doc->documentLayout()->update(); |
||||||
|
|
||||||
|
// update ascent
|
||||||
|
if(doc->firstBlock().layout()->lineCount() > 0) |
||||||
|
ascent = doc->firstBlock().layout()->lineAt(0).ascent(); |
||||||
|
|
||||||
|
// let the scene know about our change in size
|
||||||
|
if(size != idealSize()) |
||||||
|
prepareGeometryChange(); |
||||||
|
|
||||||
|
// get the new width and height
|
||||||
|
size = idealSize(); |
||||||
|
|
||||||
|
dirty = false; |
||||||
|
} |
||||||
|
|
||||||
|
// if we are not visible -> free mem
|
||||||
|
if(!keepInMemory) |
||||||
|
freeResources(); |
||||||
|
} |
||||||
|
|
||||||
|
void Text::freeResources() |
||||||
|
{ |
||||||
|
DocumentCache::getInstance().push(doc); |
||||||
|
doc = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
QSizeF Text::idealSize() |
||||||
|
{ |
||||||
|
if(doc) |
||||||
|
return QSizeF(qMin(doc->idealWidth(), width), doc->size().height()); |
||||||
|
|
||||||
|
return size; |
||||||
|
} |
||||||
|
|
||||||
|
int Text::cursorFromPos(QPointF scenePos) const |
||||||
|
{ |
||||||
|
if(doc) |
||||||
|
return doc->documentLayout()->hitTest(mapFromScene(scenePos), Qt::FuzzyHit); |
||||||
|
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int Text::getSelectionEnd() const |
||||||
|
{ |
||||||
|
return qMax(selectionAnchor, selectionEnd); |
||||||
|
} |
||||||
|
|
||||||
|
int Text::getSelectionStart() const |
||||||
|
{ |
||||||
|
return qMin(selectionAnchor, selectionEnd); |
||||||
|
} |
||||||
|
|
||||||
|
bool Text::hasSelection() const |
||||||
|
{ |
||||||
|
return selectionEnd >= 0; |
||||||
|
} |
||||||
|
|
||||||
|
QString Text::extractSanitizedText(int from, int to) const |
||||||
|
{ |
||||||
|
if(!doc) |
||||||
|
return ""; |
||||||
|
|
||||||
|
QString txt; |
||||||
|
QTextBlock block = doc->firstBlock(); |
||||||
|
|
||||||
|
for(QTextBlock::Iterator itr = block.begin(); itr!=block.end(); ++itr) |
||||||
|
{ |
||||||
|
int pos = itr.fragment().position(); //fragment position -> position of the first character in the fragment
|
||||||
|
|
||||||
|
if(itr.fragment().charFormat().isImageFormat()) |
||||||
|
{ |
||||||
|
QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat(); |
||||||
|
QString key = imgFmt.name(); //img key (eg. key::D for :D)
|
||||||
|
QString rune = key.mid(4); |
||||||
|
|
||||||
|
if(pos >= from && pos < to) |
||||||
|
{ |
||||||
|
txt += rune; |
||||||
|
pos++; |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
for(QChar c : itr.fragment().text()) |
||||||
|
{ |
||||||
|
if(pos >= from && pos < to) |
||||||
|
txt += c; |
||||||
|
|
||||||
|
pos++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return txt; |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef TEXT_H |
||||||
|
#define TEXT_H |
||||||
|
|
||||||
|
#include "../chatlinecontent.h" |
||||||
|
|
||||||
|
#include <QFont> |
||||||
|
|
||||||
|
class QTextDocument; |
||||||
|
|
||||||
|
class Text : public ChatLineContent |
||||||
|
{ |
||||||
|
public: |
||||||
|
// txt: may contain html code
|
||||||
|
// rawText: does not contain html code
|
||||||
|
Text(const QString& txt = "", QFont font = QFont(), bool enableElide = false, const QString& rawText = QString()); |
||||||
|
virtual ~Text(); |
||||||
|
|
||||||
|
void setText(const QString& txt); |
||||||
|
|
||||||
|
virtual void setWidth(qreal width) override; |
||||||
|
|
||||||
|
virtual void selectionMouseMove(QPointF scenePos) override; |
||||||
|
virtual void selectionStarted(QPointF scenePos) override; |
||||||
|
virtual void selectionCleared() override; |
||||||
|
virtual void selectionDoubleClick(QPointF scenePos) override; |
||||||
|
virtual void selectionFocusChanged(bool focusIn) override; |
||||||
|
virtual bool isOverSelection(QPointF scenePos) const override; |
||||||
|
virtual QString getSelectedText() const override; |
||||||
|
|
||||||
|
virtual QRectF boundingRect() const override; |
||||||
|
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; |
||||||
|
|
||||||
|
virtual void visibilityChanged(bool keepInMemory) override; |
||||||
|
|
||||||
|
virtual qreal getAscent() const override; |
||||||
|
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; |
||||||
|
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; |
||||||
|
virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event) override; |
||||||
|
|
||||||
|
virtual QString getText() const override; |
||||||
|
|
||||||
|
protected: |
||||||
|
// dynamic resource management
|
||||||
|
void regenerate(); |
||||||
|
void freeResources(); |
||||||
|
|
||||||
|
QSizeF idealSize(); |
||||||
|
int cursorFromPos(QPointF scenePos) const; |
||||||
|
int getSelectionEnd() const; |
||||||
|
int getSelectionStart() const; |
||||||
|
bool hasSelection() const; |
||||||
|
QString extractSanitizedText(int from, int to) const; |
||||||
|
|
||||||
|
private: |
||||||
|
QTextDocument* doc = nullptr; |
||||||
|
QString text; |
||||||
|
QString rawText; |
||||||
|
QString elidedText; |
||||||
|
QString selectedText; |
||||||
|
QSizeF size; |
||||||
|
bool keepInMemory = false; |
||||||
|
bool elide = false; |
||||||
|
bool dirty = false; |
||||||
|
bool selectionHasFocus = true; |
||||||
|
int selectionEnd = -1; |
||||||
|
int selectionAnchor = -1; |
||||||
|
qreal ascent = 0.0; |
||||||
|
qreal width = 0.0; |
||||||
|
QFont defFont; |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // TEXT_H
|
@ -0,0 +1,44 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2014 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "customtextdocument.h" |
||||||
|
#include "../misc/settings.h" |
||||||
|
#include "../misc/smileypack.h" |
||||||
|
#include "../misc/style.h" |
||||||
|
|
||||||
|
#include <QIcon> |
||||||
|
#include <QDebug> |
||||||
|
|
||||||
|
CustomTextDocument::CustomTextDocument(QObject *parent) |
||||||
|
: QTextDocument(parent) |
||||||
|
{ |
||||||
|
static QString css = Style::getStylesheet(":ui/chatArea/innerStyle.css"); |
||||||
|
|
||||||
|
setDefaultStyleSheet(css); |
||||||
|
setUndoRedoEnabled(false); |
||||||
|
setUseDesignMetrics(false); |
||||||
|
} |
||||||
|
|
||||||
|
QVariant CustomTextDocument::loadResource(int type, const QUrl &name) |
||||||
|
{ |
||||||
|
if (type == QTextDocument::ImageResource && name.scheme() == "key") |
||||||
|
{ |
||||||
|
QSize size = QSize(Settings::getInstance().getEmojiFontPointSize(),Settings::getInstance().getEmojiFontPointSize()); |
||||||
|
return SmileyPack::getInstance().getAsIcon(name.fileName()).pixmap(size); |
||||||
|
} |
||||||
|
|
||||||
|
return QTextDocument::loadResource(type, name); |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/*
|
||||||
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
|
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||||
|
|
||||||
|
This program is libre 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 COPYING file for more details. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "pixmapcache.h" |
||||||
|
|
||||||
|
QPixmap PixmapCache::get(const QString &filename, QSize size) |
||||||
|
{ |
||||||
|
auto itr = cache.find(filename); |
||||||
|
|
||||||
|
if(itr == cache.end()) |
||||||
|
{ |
||||||
|
QIcon icon; |
||||||
|
icon.addFile(filename); |
||||||
|
|
||||||
|
cache.insert(filename, icon); |
||||||
|
return icon.pixmap(size); |
||||||
|
} |
||||||
|
|
||||||
|
return itr.value().pixmap(size); |
||||||
|
} |
||||||
|
|
||||||
|
PixmapCache &PixmapCache::getInstance() |
||||||
|
{ |
||||||
|
static PixmapCache instance; |
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
@ -1,507 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "filetransferinstance.h" |
|
||||||
#include "core.h" |
|
||||||
#include "misc/settings.h" |
|
||||||
#include "misc/style.h" |
|
||||||
#include "src/friendlist.h" |
|
||||||
#include "src/friend.h" |
|
||||||
#include <math.h> |
|
||||||
#include <QFileDialog> |
|
||||||
#include <QMessageBox> |
|
||||||
#include <QBuffer> |
|
||||||
#include <QDebug> |
|
||||||
#include <QPainter> |
|
||||||
|
|
||||||
#define MAX_CONTENT_WIDTH 250 |
|
||||||
#define MAX_PREVIEW_SIZE 25*1024*1024 |
|
||||||
|
|
||||||
uint FileTransferInstance::Idconter = 0; |
|
||||||
|
|
||||||
FileTransferInstance::FileTransferInstance(ToxFile File) |
|
||||||
: lastBytesSent{0}, |
|
||||||
fileNum{File.fileNum}, friendId{File.friendId}, direction{File.direction} |
|
||||||
{ |
|
||||||
id = Idconter++; |
|
||||||
state = tsPending; |
|
||||||
remotePaused = false; |
|
||||||
lastUpdateTime = QDateTime::currentDateTime(); |
|
||||||
|
|
||||||
filename = File.fileName; |
|
||||||
|
|
||||||
// update this whenever you change the font in innerStyle.css
|
|
||||||
QFontMetrics fm(Style::getFont(Style::Small)); |
|
||||||
|
|
||||||
filenameElided = fm.elidedText(filename, Qt::ElideRight, MAX_CONTENT_WIDTH); |
|
||||||
size = getHumanReadableSize(File.filesize); |
|
||||||
contentPrefWidth = std::max(fm.width(filenameElided), fm.width(size)) + fm.leading(); |
|
||||||
|
|
||||||
speed = "0B/s"; |
|
||||||
eta = "00:00"; |
|
||||||
|
|
||||||
if (File.direction == ToxFile::SENDING) |
|
||||||
{ |
|
||||||
if (File.file->size() <= MAX_PREVIEW_SIZE) |
|
||||||
{ |
|
||||||
QImage preview; |
|
||||||
File.file->seek(0); |
|
||||||
if (preview.loadFromData(File.file->readAll())) |
|
||||||
{ |
|
||||||
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation); |
|
||||||
} |
|
||||||
} |
|
||||||
File.file->seek(0); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::getHumanReadableSize(unsigned long long size) |
|
||||||
{ |
|
||||||
static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"}; |
|
||||||
int exp = 0; |
|
||||||
if (size) |
|
||||||
exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1)); |
|
||||||
return QString().setNum(size / pow(1024, exp),'f',2).append(suffix[exp]); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction) |
|
||||||
{ |
|
||||||
if (FileNum != fileNum || FriendId != friendId || Direction != direction) |
|
||||||
return; |
|
||||||
|
|
||||||
// state = tsProcessing;
|
|
||||||
QDateTime now = QDateTime::currentDateTime(); |
|
||||||
long recenttimediff = lastUpdateTime.msecsTo(now); |
|
||||||
if (recenttimediff < 1000) //update every 1s
|
|
||||||
return; |
|
||||||
|
|
||||||
long timediff = effStartTime.msecsTo(now); |
|
||||||
if (timediff <= 0) |
|
||||||
return; |
|
||||||
|
|
||||||
long avgspeed = (BytesSent - previousBytesSent) / timediff * 1000; |
|
||||||
long recentspeed = (BytesSent - lastBytesSent) / recenttimediff * 1000; |
|
||||||
|
|
||||||
speed = getHumanReadableSize(recentspeed)+"/s"; |
|
||||||
size = getHumanReadableSize(Filesize); |
|
||||||
totalBytes = Filesize; |
|
||||||
|
|
||||||
if (!avgspeed) |
|
||||||
return; |
|
||||||
int etaSecs = (Filesize - BytesSent) / avgspeed; |
|
||||||
QTime etaTime(0,0); |
|
||||||
etaTime = etaTime.addSecs(etaSecs); |
|
||||||
eta = etaTime.toString("mm:ss"); |
|
||||||
|
|
||||||
lastBytesSent = BytesSent; |
|
||||||
lastUpdateTime = now; |
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction) |
|
||||||
{ |
|
||||||
if (FileNum != fileNum || FriendId != friendId || Direction != direction) |
|
||||||
return; |
|
||||||
disconnect(Core::getInstance(),0,this,0); |
|
||||||
state = tsCanceled; |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferFinished(ToxFile File) |
|
||||||
{ |
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction) |
|
||||||
return; |
|
||||||
disconnect(Core::getInstance(),0,this,0); |
|
||||||
|
|
||||||
if (File.direction == ToxFile::RECEIVING) |
|
||||||
{ |
|
||||||
QImage preview; |
|
||||||
QFile previewFile(File.filePath); |
|
||||||
if (previewFile.open(QIODevice::ReadOnly) && previewFile.size() <= MAX_PREVIEW_SIZE) // Don't preview big (>25MiB) images
|
|
||||||
{ |
|
||||||
if (preview.loadFromData(previewFile.readAll())) |
|
||||||
{ |
|
||||||
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation); |
|
||||||
} |
|
||||||
previewFile.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
state = tsFinished; |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferAccepted(ToxFile File) |
|
||||||
{ |
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction) |
|
||||||
return; |
|
||||||
|
|
||||||
remotePaused = false; |
|
||||||
state = tsProcessing; |
|
||||||
effStartTime = QDateTime::currentDateTime(); |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferRemotePausedUnpaused(ToxFile File, bool paused) |
|
||||||
{ |
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction) |
|
||||||
return; |
|
||||||
|
|
||||||
remotePaused = paused; |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection Direction) |
|
||||||
{ |
|
||||||
if (FileNum != fileNum || FriendId != friendId || Direction != direction) |
|
||||||
return; |
|
||||||
|
|
||||||
state = tsPaused; |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::cancelTransfer() |
|
||||||
{ |
|
||||||
Core::getInstance()->cancelFileSend(friendId, fileNum); |
|
||||||
state = tsCanceled; |
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::rejectRecvRequest() |
|
||||||
{ |
|
||||||
Core::getInstance()->rejectFileRecvRequest(friendId, fileNum); |
|
||||||
onFileTransferCancelled(friendId, fileNum, direction); |
|
||||||
state = tsCanceled; |
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
// for whatever the fuck reason, QFileInfo::isWritable() always fails for files that don't exist
|
|
||||||
// which makes it useless for our case
|
|
||||||
// since QDir doesn't have an isWritable(), the only option I can think of is to make/delete the file
|
|
||||||
// surely this is a common problem that has a qt-implemented solution?
|
|
||||||
bool isFileWritable(QString& path) |
|
||||||
{ |
|
||||||
QFile file(path); |
|
||||||
bool exists = file.exists(); |
|
||||||
bool out = file.open(QIODevice::WriteOnly); |
|
||||||
file.close(); |
|
||||||
if (!exists) |
|
||||||
file.remove(); |
|
||||||
return out; |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::acceptRecvRequest() |
|
||||||
{ |
|
||||||
QString path = Settings::getInstance().getAutoAcceptDir(FriendList::findFriend(friendId)->getToxID()); |
|
||||||
|
|
||||||
if (path.isEmpty() && Settings::getInstance().getAutoSaveEnabled()) |
|
||||||
path = Settings::getInstance().getGlobalAutoAcceptDir(); |
|
||||||
|
|
||||||
if (!path.isEmpty()) |
|
||||||
{ |
|
||||||
QDir dir(path); |
|
||||||
path = dir.filePath(filename); |
|
||||||
QFileInfo info(path); |
|
||||||
if (info.exists()) // emulate chrome
|
|
||||||
{ |
|
||||||
QString name = info.baseName(), ext = info.completeSuffix(); |
|
||||||
int i = 0; |
|
||||||
do |
|
||||||
{ |
|
||||||
path = dir.filePath(name + QString(" (%1)").arg(++i) + "." + ext); |
|
||||||
} |
|
||||||
while (QFileInfo(path).exists()); |
|
||||||
} |
|
||||||
qDebug() << "File: auto saving to" << path; |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
while (true) |
|
||||||
{ |
|
||||||
path = QFileDialog::getSaveFileName(0, tr("Save a file","Title of the file saving dialog"), QDir::home().filePath(filename)); |
|
||||||
if (path.isEmpty()) |
|
||||||
return; |
|
||||||
else |
|
||||||
{ |
|
||||||
if (isFileWritable(path)) |
|
||||||
break; |
|
||||||
else |
|
||||||
QMessageBox::warning(0, |
|
||||||
tr("Location not writable","Title of permissions popup"), |
|
||||||
tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup")); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
savePath = path; |
|
||||||
|
|
||||||
Core::getInstance()->acceptFileRecvRequest(friendId, fileNum, path); |
|
||||||
state = tsProcessing; |
|
||||||
|
|
||||||
effStartTime = QDateTime::currentDateTime(); |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::pauseResumeRecv() |
|
||||||
{ |
|
||||||
if (!(state == tsProcessing || state == tsPaused)) |
|
||||||
return; |
|
||||||
|
|
||||||
if (remotePaused) |
|
||||||
return; |
|
||||||
|
|
||||||
Core::getInstance()->pauseResumeFileRecv(friendId, fileNum); |
|
||||||
|
|
||||||
if (state == tsPaused) |
|
||||||
{ |
|
||||||
effStartTime = QDateTime::currentDateTime(); |
|
||||||
previousBytesSent = lastBytesSent; |
|
||||||
} |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::pauseResumeSend() |
|
||||||
{ |
|
||||||
if (!(state == tsProcessing || state == tsPaused)) |
|
||||||
return; |
|
||||||
|
|
||||||
if (remotePaused) |
|
||||||
return; |
|
||||||
|
|
||||||
Core::getInstance()->pauseResumeFileSend(friendId, fileNum); |
|
||||||
|
|
||||||
if (state == tsPaused) |
|
||||||
{ |
|
||||||
effStartTime = QDateTime::currentDateTime(); |
|
||||||
previousBytesSent = lastBytesSent; |
|
||||||
} |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::QImage2base64(const QImage &img) |
|
||||||
{ |
|
||||||
QByteArray ba; |
|
||||||
QBuffer buffer(&ba); |
|
||||||
buffer.open(QIODevice::WriteOnly); |
|
||||||
img.save(&buffer, "PNG"); // writes image into ba in PNG format
|
|
||||||
return ba.toBase64(); |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::getHtmlImage() |
|
||||||
{ |
|
||||||
|
|
||||||
QString res; |
|
||||||
if (state == tsPending || state == tsProcessing || state == tsPaused) |
|
||||||
{ |
|
||||||
QImage leftBtnImg(":/ui/fileTransferInstance/stopFileButton.png"); |
|
||||||
QImage rightBtnImg; |
|
||||||
if (state == tsProcessing) |
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseFileButton.png"); |
|
||||||
else if (state == tsPaused) |
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/resumeFileButton.png"); |
|
||||||
else |
|
||||||
{ |
|
||||||
if (direction == ToxFile::SENDING) |
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseGreyFileButton.png"); |
|
||||||
else |
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/acceptFileButton.png"); |
|
||||||
} |
|
||||||
|
|
||||||
if (remotePaused) |
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseGreyFileButton.png"); |
|
||||||
|
|
||||||
res = draw2ButtonsForm("silver", leftBtnImg, rightBtnImg); |
|
||||||
} else if (state == tsBroken) |
|
||||||
{ |
|
||||||
QImage leftBtnImg(":/ui/fileTransferInstance/stopFileButton.png"); |
|
||||||
QImage rightBtnImg(":/ui/fileTransferInstance/pauseGreyFileButton.png"); |
|
||||||
|
|
||||||
res = draw2ButtonsForm("red", leftBtnImg, rightBtnImg); |
|
||||||
} else if (state == tsCanceled) |
|
||||||
{ |
|
||||||
res = drawButtonlessForm("red"); |
|
||||||
} else if (state == tsFinished) |
|
||||||
{ |
|
||||||
res = drawButtonlessForm("green"); |
|
||||||
} |
|
||||||
|
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::pressFromHtml(QString code) |
|
||||||
{ |
|
||||||
if (state == tsFinished || state == tsCanceled) |
|
||||||
return; |
|
||||||
|
|
||||||
if (direction == ToxFile::SENDING) |
|
||||||
{ |
|
||||||
if (code == "btnA") |
|
||||||
cancelTransfer(); |
|
||||||
else if (code == "btnB") |
|
||||||
pauseResumeSend(); |
|
||||||
} else { |
|
||||||
if (code == "btnA") |
|
||||||
rejectRecvRequest(); |
|
||||||
else if (code == "btnB") |
|
||||||
{ |
|
||||||
if (state == tsPending) |
|
||||||
acceptRecvRequest(); |
|
||||||
else |
|
||||||
pauseResumeRecv(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::drawButtonlessForm(const QString &type) |
|
||||||
{ |
|
||||||
QString imgAStr; |
|
||||||
QString imgBStr; |
|
||||||
|
|
||||||
if (type == "red") |
|
||||||
{ |
|
||||||
imgAStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png")) + "\">"; |
|
||||||
imgBStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyRRedFileButton.png")) + "\">"; |
|
||||||
} else { |
|
||||||
imgAStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png")) + "\">"; |
|
||||||
imgBStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyRGreenFileButton.png")) + "\">"; |
|
||||||
} |
|
||||||
|
|
||||||
QString content = "<p>" + filenameElided + "</p><p>" + size + "</p>"; |
|
||||||
|
|
||||||
return wrapIntoForm(content, type, imgAStr, imgBStr); |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::insertMiniature(const QString &type) |
|
||||||
{ |
|
||||||
if (pic == QImage()) |
|
||||||
return QString(); |
|
||||||
|
|
||||||
QString widgetId = QString::number(getId()); |
|
||||||
|
|
||||||
QString res; |
|
||||||
res = "<td><div class=" + type + ">\n"; |
|
||||||
res += "<img src=\"data:mini." + widgetId + "/png;base64," + QImage2base64(pic) + "\">"; |
|
||||||
res += "</div></td>\n"; |
|
||||||
res += "<td width=5><div class=" + type + ">\n"; |
|
||||||
res += "</div></td>\n"; |
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::draw2ButtonsForm(const QString &type, const QImage &imgA, const QImage &imgB) |
|
||||||
{ |
|
||||||
QString widgetId = QString::number(getId()); |
|
||||||
QString imgAstr = "<img src=\"data:ftrans." + widgetId + ".btnA/png;base64," + QImage2base64(imgA) + "\">"; |
|
||||||
QString imgBstr = "<img src=\"data:ftrans." + widgetId + ".btnB/png;base64," + QImage2base64(imgB) + "\">"; |
|
||||||
|
|
||||||
QString content; |
|
||||||
QString progrBar = "<img src=\"data:progressbar." + widgetId + "/png;base64," + |
|
||||||
QImage2base64(drawProgressBarImg(double(lastBytesSent)/totalBytes, MAX_CONTENT_WIDTH, 9)) + "\">"; |
|
||||||
|
|
||||||
content = "<p>" + filenameElided + "</p>"; |
|
||||||
content += "<table cellspacing=\"0\"><tr>"; |
|
||||||
content += "<td>" + size + "</td>"; |
|
||||||
content += "<td align=center>" + speed + "</td>"; |
|
||||||
content += "<td align=right>" + tr("ETA") + ": " + eta + "</td>"; |
|
||||||
content += "</tr><tr><td colspan=3>"; |
|
||||||
content += progrBar; |
|
||||||
content += "</td></tr></table>"; |
|
||||||
|
|
||||||
return wrapIntoForm(content, type, imgAstr, imgBstr); |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferInstance::wrapIntoForm(const QString& content, const QString &type, const QString &imgAstr, const QString &imgBstr) |
|
||||||
{ |
|
||||||
QString w = QString::number(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").size().width()); |
|
||||||
QString imgLeftA, imgLeftB; |
|
||||||
|
|
||||||
if (type == "green") |
|
||||||
{ |
|
||||||
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png").mirrored(true,false)) + "\">"; |
|
||||||
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png").mirrored(true,true)) + "\">"; |
|
||||||
} |
|
||||||
|
|
||||||
if (type == "silver") |
|
||||||
{ |
|
||||||
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/sliverRTEdge.png").mirrored(true,false)) + "\">"; |
|
||||||
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/sliverRTEdge.png").mirrored(true,true)) + "\">"; |
|
||||||
} |
|
||||||
|
|
||||||
if (type == "red") |
|
||||||
{ |
|
||||||
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").mirrored(true,false)) + "\">"; |
|
||||||
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").mirrored(true,true)) + "\">"; |
|
||||||
} |
|
||||||
|
|
||||||
QString res; |
|
||||||
res = "<table cellspacing=\"0\">\n"; |
|
||||||
res += "<tr valign=middle>\n"; |
|
||||||
res += "<td width=" + w + ">\n"; |
|
||||||
res += "<div class=button>" + imgLeftA + "<br>" + imgLeftB + "</div>\n"; |
|
||||||
res += "</td>\n"; |
|
||||||
res += insertMiniature(type); |
|
||||||
res += "<td width=" + QString::number(contentPrefWidth) + ">\n"; |
|
||||||
res += "<div class=" + type + ">"; |
|
||||||
res += content; |
|
||||||
res += "</div>\n"; |
|
||||||
res += "</td>\n"; |
|
||||||
res += "<td width=3>\n"; |
|
||||||
res += "<div class=" + type + "></div>\n"; |
|
||||||
res += "</td>\n"; |
|
||||||
res += "<td>\n"; |
|
||||||
res += "<div class=button>" + imgAstr + "<br>" + imgBstr + "</div>\n"; |
|
||||||
res += "</td>\n"; |
|
||||||
res += "</tr>\n"; |
|
||||||
res += "</table>\n"; |
|
||||||
|
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
QImage FileTransferInstance::drawProgressBarImg(const double &part, int w, int h) |
|
||||||
{ |
|
||||||
QImage progressBar(w, h, QImage::Format_Mono); |
|
||||||
|
|
||||||
QPainter qPainter(&progressBar); |
|
||||||
qPainter.setBrush(Qt::NoBrush); |
|
||||||
qPainter.setPen(Qt::black); |
|
||||||
qPainter.drawRect(0, 0, w - 1, h - 1); |
|
||||||
|
|
||||||
qPainter.setBrush(Qt::SolidPattern); |
|
||||||
qPainter.setPen(Qt::black); |
|
||||||
qPainter.drawRect(1, 0, (w - 2) * (part), h - 1); |
|
||||||
|
|
||||||
return progressBar; |
|
||||||
} |
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferBrokenUnbroken(ToxFile File, bool broken) |
|
||||||
{ |
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction) |
|
||||||
return; |
|
||||||
|
|
||||||
if (broken) |
|
||||||
state = tsBroken; |
|
||||||
else |
|
||||||
state = tsProcessing; |
|
||||||
|
|
||||||
emit stateUpdated(); |
|
||||||
} |
|
@ -1,87 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
#ifndef FILETRANSFERINSTANCE_H |
|
||||||
#define FILETRANSFERINSTANCE_H |
|
||||||
|
|
||||||
#include <QObject> |
|
||||||
#include <QDateTime> |
|
||||||
#include <QImage> |
|
||||||
|
|
||||||
#include "corestructs.h" |
|
||||||
|
|
||||||
struct ToxFile; |
|
||||||
|
|
||||||
class FileTransferInstance : public QObject |
|
||||||
{ |
|
||||||
Q_OBJECT |
|
||||||
public: |
|
||||||
enum TransfState {tsPending, tsProcessing, tsPaused, tsFinished, tsCanceled, tsBroken}; |
|
||||||
|
|
||||||
public: |
|
||||||
explicit FileTransferInstance(ToxFile File); |
|
||||||
QString getHtmlImage(); |
|
||||||
uint getId(){return id;} |
|
||||||
TransfState getState() {return state;} |
|
||||||
|
|
||||||
public slots: |
|
||||||
void onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction); |
|
||||||
void onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction); |
|
||||||
void onFileTransferFinished(ToxFile File); |
|
||||||
void onFileTransferAccepted(ToxFile File); |
|
||||||
void onFileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection Direction); |
|
||||||
void onFileTransferRemotePausedUnpaused(ToxFile File, bool paused); |
|
||||||
void onFileTransferBrokenUnbroken(ToxFile File, bool broken); |
|
||||||
void pressFromHtml(QString); |
|
||||||
|
|
||||||
signals: |
|
||||||
void stateUpdated(); |
|
||||||
|
|
||||||
private slots: |
|
||||||
void cancelTransfer(); |
|
||||||
void rejectRecvRequest(); |
|
||||||
void acceptRecvRequest(); |
|
||||||
void pauseResumeRecv(); |
|
||||||
void pauseResumeSend(); |
|
||||||
|
|
||||||
private: |
|
||||||
QString getHumanReadableSize(unsigned long long size); |
|
||||||
QString QImage2base64(const QImage &img); |
|
||||||
QString drawButtonlessForm(const QString &type); |
|
||||||
QString draw2ButtonsForm(const QString &type, const QImage &imgA, const QImage &imgB); |
|
||||||
QString insertMiniature(const QString &type); |
|
||||||
QString wrapIntoForm(const QString &content, const QString &type, const QString &imgAstr, const QString &imgBstr); |
|
||||||
QImage drawProgressBarImg(const double &part, int w, int h); |
|
||||||
|
|
||||||
private: |
|
||||||
static uint Idconter; |
|
||||||
uint id; |
|
||||||
|
|
||||||
TransfState state; |
|
||||||
bool remotePaused; |
|
||||||
QImage pic; |
|
||||||
QString filename, size, speed, eta; |
|
||||||
QString filenameElided; |
|
||||||
QDateTime effStartTime, lastUpdateTime; |
|
||||||
long long lastBytesSent, totalBytes, previousBytesSent = 0; // last var used for eta on resumes
|
|
||||||
int fileNum; |
|
||||||
int friendId; |
|
||||||
int contentPrefWidth; |
|
||||||
QString savePath; |
|
||||||
ToxFile::FileDirection direction; |
|
||||||
QString stopFileButtonStylesheet, pauseFileButtonStylesheet, acceptFileButtonStylesheet; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif // FILETRANSFERINSTANCE_H
|
|
@ -1,208 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "chatareawidget.h" |
|
||||||
#include "tool/chatactions/chataction.h" |
|
||||||
#include <QScrollBar> |
|
||||||
#include <QDesktopServices> |
|
||||||
#include <QTextTable> |
|
||||||
#include <QAbstractTextDocumentLayout> |
|
||||||
#include <QCoreApplication> |
|
||||||
#include <QDebug> |
|
||||||
#include <algorithm> |
|
||||||
|
|
||||||
ChatAreaWidget::ChatAreaWidget(QWidget *parent) |
|
||||||
: QTextBrowser(parent) |
|
||||||
, tableFrmt(nullptr) |
|
||||||
, nameWidth(75) |
|
||||||
, empty{true} |
|
||||||
{ |
|
||||||
setReadOnly(true); |
|
||||||
viewport()->setCursor(Qt::ArrowCursor); |
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu); |
|
||||||
setUndoRedoEnabled(false); |
|
||||||
|
|
||||||
setOpenExternalLinks(false); |
|
||||||
setOpenLinks(false); |
|
||||||
setAcceptRichText(false); |
|
||||||
setFrameStyle(QFrame::NoFrame); |
|
||||||
|
|
||||||
nameFormat.setAlignment(Qt::AlignRight); |
|
||||||
nameFormat.setNonBreakableLines(true); |
|
||||||
dateFormat.setAlignment(Qt::AlignLeft); |
|
||||||
dateFormat.setNonBreakableLines(true); |
|
||||||
|
|
||||||
connect(this, &ChatAreaWidget::anchorClicked, this, &ChatAreaWidget::onAnchorClicked); |
|
||||||
connect(verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onSliderRangeChanged())); |
|
||||||
} |
|
||||||
|
|
||||||
ChatAreaWidget::~ChatAreaWidget() |
|
||||||
{ |
|
||||||
if (tableFrmt) |
|
||||||
delete tableFrmt; |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event) |
|
||||||
{ |
|
||||||
QTextEdit::mouseReleaseEvent(event); |
|
||||||
QPointF documentHitPost(event->pos().x() + horizontalScrollBar()->value(), event->pos().y() + verticalScrollBar()->value()); |
|
||||||
int pos = this->document()->documentLayout()->hitTest(documentHitPost, Qt::ExactHit); |
|
||||||
if (pos > 0) |
|
||||||
{ |
|
||||||
QTextCursor cursor(document()); |
|
||||||
cursor.setPosition(pos); |
|
||||||
|
|
||||||
if (!cursor.atEnd()) |
|
||||||
{ |
|
||||||
cursor.setPosition(pos+1); |
|
||||||
|
|
||||||
QTextFormat format = cursor.charFormat(); |
|
||||||
if (format.isImageFormat()) |
|
||||||
{ |
|
||||||
QString imageName = format.toImageFormat().name(); |
|
||||||
if (QRegExp("^data:ftrans.*").exactMatch(imageName)) |
|
||||||
{ |
|
||||||
QString data = imageName.right(imageName.length() - 12); |
|
||||||
int endpos = data.indexOf("/png;base64"); |
|
||||||
data = data.left(endpos); |
|
||||||
int middlepos = data.indexOf("."); |
|
||||||
QString widgetID = data.left(middlepos); |
|
||||||
QString widgetBtn = data.right(data.length() - middlepos - 1); |
|
||||||
qDebug() << "ChatAreaWidget::mouseReleaseEvent:" << widgetID << widgetBtn; |
|
||||||
emit onFileTranfertInterract(widgetID, widgetBtn); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
emit onClick(); |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::onAnchorClicked(const QUrl &url) |
|
||||||
{ |
|
||||||
QDesktopServices::openUrl(url); |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::insertMessage(ChatActionPtr msgAction, QTextCursor::MoveOperation pos) |
|
||||||
{ |
|
||||||
if (msgAction == nullptr) |
|
||||||
return; |
|
||||||
|
|
||||||
checkSlider(); |
|
||||||
|
|
||||||
QTextTable *chatTextTable = getMsgTable(pos); |
|
||||||
msgAction->assignPlace(chatTextTable, this); |
|
||||||
msgAction->dispaly(); |
|
||||||
|
|
||||||
/*
|
|
||||||
QTextCursor cur = chatTextTable->cellAt(0, 2).firstCursorPosition(); |
|
||||||
cur.clearSelection(); |
|
||||||
cur.setKeepPositionOnInsert(true); |
|
||||||
chatTextTable->cellAt(0, 0).firstCursorPosition().setBlockFormat(nameFormat); |
|
||||||
chatTextTable->cellAt(0, 0).firstCursorPosition().insertHtml(msgAction->getName()); |
|
||||||
chatTextTable->cellAt(0, 2).firstCursorPosition().insertHtml(msgAction->getMessage()); |
|
||||||
chatTextTable->cellAt(0, 4).firstCursorPosition().setBlockFormat(dateFormat); |
|
||||||
chatTextTable->cellAt(0, 4).firstCursorPosition().insertHtml(msgAction->getDate()); |
|
||||||
msgAction->setup(cur, this); |
|
||||||
*/ |
|
||||||
|
|
||||||
if (msgAction->isInteractive()) |
|
||||||
messages.append(msgAction); |
|
||||||
|
|
||||||
empty = false; |
|
||||||
} |
|
||||||
|
|
||||||
bool ChatAreaWidget::isEmpty() |
|
||||||
{ |
|
||||||
return empty; |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::onSliderRangeChanged() |
|
||||||
{ |
|
||||||
QScrollBar* scroll = verticalScrollBar(); |
|
||||||
if (lockSliderToBottom) |
|
||||||
scroll->setValue(scroll->maximum()); |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::checkSlider() |
|
||||||
{ |
|
||||||
QScrollBar* scroll = verticalScrollBar(); |
|
||||||
lockSliderToBottom = scroll && scroll->value() == scroll->maximum(); |
|
||||||
} |
|
||||||
|
|
||||||
QTextTable *ChatAreaWidget::getMsgTable(QTextCursor::MoveOperation pos) |
|
||||||
{ |
|
||||||
if (tableFrmt == nullptr) |
|
||||||
{ |
|
||||||
tableFrmt = new QTextTableFormat(); |
|
||||||
tableFrmt->setCellSpacing(2); |
|
||||||
tableFrmt->setBorderStyle(QTextFrameFormat::BorderStyle_None); |
|
||||||
tableFrmt->setColumnWidthConstraints({QTextLength(QTextLength::FixedLength,nameWidth), |
|
||||||
QTextLength(QTextLength::FixedLength,2), |
|
||||||
QTextLength(QTextLength::PercentageLength,100), |
|
||||||
QTextLength(QTextLength::FixedLength,2), |
|
||||||
QTextLength(QTextLength::VariableLength,0)}); |
|
||||||
} |
|
||||||
|
|
||||||
QTextCursor tc = textCursor(); |
|
||||||
tc.movePosition(pos); |
|
||||||
|
|
||||||
QTextTable *chatTextTable = tc.insertTable(1, 5, *tableFrmt); |
|
||||||
|
|
||||||
return chatTextTable; |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::setNameColWidth(int w) |
|
||||||
{ |
|
||||||
if (tableFrmt != nullptr) |
|
||||||
{ |
|
||||||
delete tableFrmt; |
|
||||||
tableFrmt = nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
nameWidth = w; |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::clearChatArea() |
|
||||||
{ |
|
||||||
QList<ChatActionPtr> newMsgs; |
|
||||||
for (ChatActionPtr message : messages) |
|
||||||
{ |
|
||||||
if (message->isInteractive()) |
|
||||||
{ |
|
||||||
newMsgs.append(message); |
|
||||||
} |
|
||||||
} |
|
||||||
messages.clear(); |
|
||||||
this->clear(); |
|
||||||
empty = true; |
|
||||||
|
|
||||||
for (ChatActionPtr message : newMsgs) |
|
||||||
{ |
|
||||||
insertMessage(message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAreaWidget::insertMessagesTop(QList<ChatActionPtr> &list) |
|
||||||
{ |
|
||||||
std::reverse(list.begin(), list.end()); |
|
||||||
|
|
||||||
for (ChatActionPtr it : list) |
|
||||||
{ |
|
||||||
insertMessage(it, QTextCursor::Start); |
|
||||||
} |
|
||||||
|
|
||||||
empty = false; |
|
||||||
} |
|
@ -1,66 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef CHATAREAWIDGET_H |
|
||||||
#define CHATAREAWIDGET_H |
|
||||||
|
|
||||||
#include <QTextBrowser> |
|
||||||
#include <QList> |
|
||||||
#include <src/widget/tool/chatactions/chataction.h> |
|
||||||
|
|
||||||
class QTextTable; |
|
||||||
|
|
||||||
class ChatAreaWidget : public QTextBrowser |
|
||||||
{ |
|
||||||
Q_OBJECT |
|
||||||
public: |
|
||||||
explicit ChatAreaWidget(QWidget *parent = 0); |
|
||||||
virtual ~ChatAreaWidget(); |
|
||||||
void insertMessage(ChatActionPtr msgAction, QTextCursor::MoveOperation pos = QTextCursor::End); |
|
||||||
void insertMessagesTop(QList<ChatActionPtr> &list); |
|
||||||
|
|
||||||
int nameColWidth() {return nameWidth;} |
|
||||||
void setNameColWidth(int w); |
|
||||||
bool isEmpty(); |
|
||||||
|
|
||||||
public slots: |
|
||||||
void clearChatArea(); |
|
||||||
|
|
||||||
signals: |
|
||||||
void onFileTranfertInterract(QString widgetName, QString buttonName); |
|
||||||
void onClick(); |
|
||||||
|
|
||||||
protected: |
|
||||||
void mouseReleaseEvent(QMouseEvent * event); |
|
||||||
|
|
||||||
private slots: |
|
||||||
void onAnchorClicked(const QUrl& url); |
|
||||||
void onSliderRangeChanged(); |
|
||||||
|
|
||||||
private: |
|
||||||
void checkSlider(); |
|
||||||
QTextTable* getMsgTable(QTextCursor::MoveOperation pos = QTextCursor::End); |
|
||||||
|
|
||||||
QTextTableFormat* tableFrmt; |
|
||||||
QList<ChatActionPtr> messages; |
|
||||||
bool lockSliderToBottom; |
|
||||||
int sliderPosition; |
|
||||||
int nameWidth; |
|
||||||
QTextBlockFormat nameFormat, dateFormat; |
|
||||||
bool empty; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif // CHATAREAWIDGET_H
|
|
@ -1,116 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "chataction.h" |
|
||||||
#include <QStringList> |
|
||||||
#include <QBuffer> |
|
||||||
#include <QTextTable> |
|
||||||
#include <QScrollBar> |
|
||||||
#include <QTextEdit> |
|
||||||
|
|
||||||
QTextBlockFormat ChatAction::nameFormat, ChatAction::dateFormat; |
|
||||||
|
|
||||||
QString ChatAction::toHtmlChars(const QString &str) |
|
||||||
{ |
|
||||||
static QList<QPair<QString, QString>> replaceList = {{"&","&"}, {">",">"}, {"<","<"}}; |
|
||||||
QString res = str; |
|
||||||
|
|
||||||
for (auto &it : replaceList) |
|
||||||
res = res.replace(it.first,it.second); |
|
||||||
|
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
QString ChatAction::QImage2base64(const QImage &img) |
|
||||||
{ |
|
||||||
QByteArray ba; |
|
||||||
QBuffer buffer(&ba); |
|
||||||
buffer.open(QIODevice::WriteOnly); |
|
||||||
img.save(&buffer, "PNG"); // writes image into ba in PNG format
|
|
||||||
return ba.toBase64(); |
|
||||||
} |
|
||||||
|
|
||||||
QString ChatAction::getName() |
|
||||||
{ |
|
||||||
if (isMe) |
|
||||||
return QString("<div class=%1>%2</div>").arg("name_me").arg(toHtmlChars(name)); |
|
||||||
else |
|
||||||
return QString("<div class=%1>%2</div>").arg("name").arg(toHtmlChars(name)); |
|
||||||
} |
|
||||||
|
|
||||||
QString ChatAction::getDate() |
|
||||||
{ |
|
||||||
if (isMe) |
|
||||||
return QString("<div class=date_me>" + toHtmlChars(date) + "</div>"); |
|
||||||
else |
|
||||||
return QString("<div class=date>" + toHtmlChars(date) + "</div>"); |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAction::assignPlace(QTextTable *position, QTextEdit *te) |
|
||||||
{ |
|
||||||
textTable = position; |
|
||||||
cur = position->cellAt(0, 2).firstCursorPosition(); |
|
||||||
cur.clearSelection(); |
|
||||||
cur.setKeepPositionOnInsert(true); |
|
||||||
textEdit = te; |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAction::dispaly() |
|
||||||
{ |
|
||||||
textTable->cellAt(0, 0).firstCursorPosition().setBlockFormat(nameFormat); |
|
||||||
textTable->cellAt(0, 0).firstCursorPosition().insertHtml(getName()); |
|
||||||
textTable->cellAt(0, 2).firstCursorPosition().insertHtml(getMessage()); |
|
||||||
textTable->cellAt(0, 4).firstCursorPosition().setBlockFormat(dateFormat); |
|
||||||
textTable->cellAt(0, 4).firstCursorPosition().insertHtml(getDate()); |
|
||||||
|
|
||||||
cur.setKeepPositionOnInsert(true); |
|
||||||
int end=cur.selectionEnd(); |
|
||||||
cur.setPosition(cur.position()); |
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor); |
|
||||||
|
|
||||||
featureUpdate(); |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAction::setupFormat() |
|
||||||
{ |
|
||||||
nameFormat.setAlignment(Qt::AlignRight); |
|
||||||
nameFormat.setNonBreakableLines(true); |
|
||||||
dateFormat.setAlignment(Qt::AlignLeft); |
|
||||||
dateFormat.setNonBreakableLines(true); |
|
||||||
} |
|
||||||
|
|
||||||
void ChatAction::updateContent() |
|
||||||
{ |
|
||||||
if (cur.isNull() || !textEdit) |
|
||||||
return; |
|
||||||
|
|
||||||
int vSliderVal = textEdit->verticalScrollBar()->value(); |
|
||||||
|
|
||||||
// update content
|
|
||||||
int pos = cur.selectionStart(); |
|
||||||
cur.removeSelectedText(); |
|
||||||
cur.setKeepPositionOnInsert(false); |
|
||||||
cur.insertHtml(getMessage()); |
|
||||||
cur.setKeepPositionOnInsert(true); |
|
||||||
int end = cur.position(); |
|
||||||
cur.setPosition(pos); |
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor); |
|
||||||
|
|
||||||
// restore old slider value
|
|
||||||
textEdit->verticalScrollBar()->setValue(vSliderVal); |
|
||||||
|
|
||||||
featureUpdate(); |
|
||||||
} |
|
@ -1,67 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef CHATACTION_H |
|
||||||
#define CHATACTION_H |
|
||||||
|
|
||||||
#include <QString> |
|
||||||
#include <QTextCursor> |
|
||||||
#include <QSharedPointer> |
|
||||||
|
|
||||||
class FileTransferInstance; |
|
||||||
class QTextEdit; |
|
||||||
class QTextTable; |
|
||||||
|
|
||||||
class ChatAction : public QObject |
|
||||||
{ |
|
||||||
Q_OBJECT |
|
||||||
public: |
|
||||||
ChatAction(const bool &me, const QString &author, const QString &date) |
|
||||||
: isMe{me}, name{author}, date{date}, textTable{nullptr}, textEdit{nullptr} {;} |
|
||||||
virtual ~ChatAction(){;} |
|
||||||
|
|
||||||
void assignPlace(QTextTable *position, QTextEdit* te); |
|
||||||
virtual void dispaly(); |
|
||||||
virtual bool isInteractive(){return false;} |
|
||||||
virtual void featureUpdate() {;} |
|
||||||
|
|
||||||
static void setupFormat(); |
|
||||||
|
|
||||||
public slots: |
|
||||||
void updateContent(); |
|
||||||
|
|
||||||
protected: |
|
||||||
virtual QString getName(); |
|
||||||
virtual QString getMessage() = 0; |
|
||||||
virtual QString getDate(); |
|
||||||
|
|
||||||
QString toHtmlChars(const QString &str); |
|
||||||
QString QImage2base64(const QImage &img); |
|
||||||
|
|
||||||
protected: |
|
||||||
bool isMe; |
|
||||||
QString name, date; |
|
||||||
|
|
||||||
QTextTable *textTable; |
|
||||||
QTextEdit *textEdit; |
|
||||||
QTextCursor cur; |
|
||||||
|
|
||||||
static QTextBlockFormat nameFormat, dateFormat; |
|
||||||
}; |
|
||||||
|
|
||||||
typedef QSharedPointer<ChatAction> ChatActionPtr; |
|
||||||
|
|
||||||
#endif // CHATACTION_H
|
|
@ -1,89 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "filetransferaction.h" |
|
||||||
#include "src/filetransferinstance.h" |
|
||||||
|
|
||||||
#include <QTextEdit> |
|
||||||
#include <QScrollBar> |
|
||||||
|
|
||||||
FileTransferAction::FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me) |
|
||||||
: ChatAction(me, author, date) |
|
||||||
{ |
|
||||||
w = widget; |
|
||||||
|
|
||||||
connect(w, &FileTransferInstance::stateUpdated, this, &FileTransferAction::updateContent); |
|
||||||
} |
|
||||||
|
|
||||||
FileTransferAction::~FileTransferAction() |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
QString FileTransferAction::getMessage() |
|
||||||
{ |
|
||||||
QString widgetHtml; |
|
||||||
if (w != nullptr) |
|
||||||
widgetHtml = w->getHtmlImage(); |
|
||||||
else |
|
||||||
widgetHtml = "<div class=quote>EMPTY CONTENT</div>"; |
|
||||||
return widgetHtml; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
void FileTransferAction::setup(QTextCursor cursor, QTextEdit *textEdit) |
|
||||||
{ |
|
||||||
cur = cursor; |
|
||||||
cur.setKeepPositionOnInsert(true); |
|
||||||
int end=cur.selectionEnd(); |
|
||||||
cur.setPosition(cur.position()); |
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor); |
|
||||||
|
|
||||||
edit = textEdit; |
|
||||||
} |
|
||||||
*/ |
|
||||||
/*
|
|
||||||
void FileTransferAction::updateHtml() |
|
||||||
{ |
|
||||||
if (cur.isNull() || !edit) |
|
||||||
return; |
|
||||||
|
|
||||||
// save old slider value
|
|
||||||
int vSliderVal = edit->verticalScrollBar()->value(); |
|
||||||
|
|
||||||
// update content
|
|
||||||
int pos = cur.selectionStart(); |
|
||||||
cur.removeSelectedText(); |
|
||||||
cur.setKeepPositionOnInsert(false); |
|
||||||
cur.insertHtml(getMessage()); |
|
||||||
cur.setKeepPositionOnInsert(true); |
|
||||||
int end = cur.position(); |
|
||||||
cur.setPosition(pos); |
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor); |
|
||||||
|
|
||||||
// restore old slider value
|
|
||||||
edit->verticalScrollBar()->setValue(vSliderVal); |
|
||||||
} |
|
||||||
*/ |
|
||||||
bool FileTransferAction::isInteractive() |
|
||||||
{ |
|
||||||
if (w->getState() == FileTransferInstance::TransfState::tsCanceled |
|
||||||
|| w->getState() == FileTransferInstance::TransfState::tsFinished) |
|
||||||
{ |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
@ -1,103 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "messageaction.h" |
|
||||||
#include "src/misc/smileypack.h" |
|
||||||
#include "src/misc/settings.h" |
|
||||||
#include <QTextTable> |
|
||||||
|
|
||||||
MessageAction::MessageAction(const QString &author, const QString &message, const QString &date, const bool &me) : |
|
||||||
ChatAction(me, author, date), |
|
||||||
message(message) |
|
||||||
{ |
|
||||||
isProcessed = false; |
|
||||||
} |
|
||||||
|
|
||||||
QString MessageAction::getMessage(QString div) |
|
||||||
{ |
|
||||||
QString message_; |
|
||||||
if (Settings::getInstance().getUseEmoticons()) |
|
||||||
message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message)); |
|
||||||
else |
|
||||||
message_ = toHtmlChars(message); |
|
||||||
|
|
||||||
// detect urls
|
|
||||||
QRegExp exp("(?:\\b)(www\\.|http[s]?:\\/\\/|ftp:\\/\\/|tox:\\/\\/|tox:)\\S+"); |
|
||||||
int offset = 0; |
|
||||||
while ((offset = exp.indexIn(message_, offset)) != -1) |
|
||||||
{ |
|
||||||
QString url = exp.cap(); |
|
||||||
|
|
||||||
// If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:"
|
|
||||||
if (url == "tox:\"") |
|
||||||
{ |
|
||||||
offset += url.length(); |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
// add scheme if not specified
|
|
||||||
if (exp.cap(1) == "www.") |
|
||||||
url.prepend("http://"); |
|
||||||
|
|
||||||
QString htmledUrl = QString("<a href=\"%1\">%1</a>").arg(url); |
|
||||||
message_.replace(offset, exp.cap().length(), htmledUrl); |
|
||||||
|
|
||||||
offset += htmledUrl.length(); |
|
||||||
} |
|
||||||
|
|
||||||
// detect text quotes
|
|
||||||
QStringList messageLines = message_.split("\n"); |
|
||||||
message_ = ""; |
|
||||||
for (QString& s : messageLines) |
|
||||||
{ |
|
||||||
if (QRegExp("^>( |[[]|>|[^_\\d\\W]).*").exactMatch(s)) |
|
||||||
message_ += "<span class=quote>" + s + "</span><br/>"; |
|
||||||
else |
|
||||||
message_ += s + "<br/>"; |
|
||||||
} |
|
||||||
message_ = message_.left(message_.length()-4); |
|
||||||
|
|
||||||
return QString(QString("<div class=%1>").arg(div) + message_ + "</div>"); |
|
||||||
} |
|
||||||
|
|
||||||
QString MessageAction::getMessage() |
|
||||||
{ |
|
||||||
if (isMe) |
|
||||||
return getMessage("message_me"); |
|
||||||
else |
|
||||||
return getMessage("message"); |
|
||||||
} |
|
||||||
|
|
||||||
void MessageAction::featureUpdate() |
|
||||||
{ |
|
||||||
QTextTableCell cell = textTable->cellAt(0,3); |
|
||||||
QTextTableCellFormat format; |
|
||||||
if (!isProcessed) |
|
||||||
format.setBackground(QColor(Qt::red)); |
|
||||||
else |
|
||||||
format.setBackground(QColor(Qt::white)); |
|
||||||
cell.setFormat(format); |
|
||||||
} |
|
||||||
|
|
||||||
void MessageAction::markAsSent() |
|
||||||
{ |
|
||||||
isProcessed = true; |
|
||||||
} |
|
||||||
|
|
||||||
QString MessageAction::getRawMessage() |
|
||||||
{ |
|
||||||
return message; |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
|
||||||
|
|
||||||
This program is libre 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 COPYING file for more details. |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef MESSAGEACTION_H |
|
||||||
#define MESSAGEACTION_H |
|
||||||
|
|
||||||
#include "chataction.h" |
|
||||||
|
|
||||||
class MessageAction : public ChatAction |
|
||||||
{ |
|
||||||
public: |
|
||||||
MessageAction(const QString &author, const QString &message, const QString &date, const bool &me); |
|
||||||
virtual ~MessageAction(){;} |
|
||||||
virtual void featureUpdate(); |
|
||||||
void markAsSent(); |
|
||||||
virtual QString getRawMessage(); |
|
||||||
virtual bool isAction() {return false;} |
|
||||||
|
|
||||||
protected: |
|
||||||
virtual QString getMessage(); |
|
||||||
virtual QString getMessage(QString div); |
|
||||||
|
|
||||||
protected: |
|
||||||
QString message; |
|
||||||
bool isProcessed; |
|
||||||
}; |
|
||||||
|
|
||||||
typedef QSharedPointer<MessageAction> MessageActionPtr; |
|
||||||
|
|
||||||
#endif // MESSAGEACTION_H
|
|
Before Width: | Height: | Size: 449 B |
Before Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 229 B |
Before Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 211 B |
@ -0,0 +1,27 @@ |
|||||||
|
[fontColor="white"] QLabel { |
||||||
|
color:white; |
||||||
|
font:@big; |
||||||
|
} |
||||||
|
|
||||||
|
[fontColor="black"] QLabel { |
||||||
|
color:@mediumGrey; |
||||||
|
font:@big; |
||||||
|
} |
||||||
|
|
||||||
|
QPushButton { |
||||||
|
margin:0; |
||||||
|
border: none; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
QProgressBar { |
||||||
|
border: 2px solid @mediumGrey; |
||||||
|
border-radius: 0px; |
||||||
|
background-color: @mediumGrey; |
||||||
|
} |
||||||
|
|
||||||
|
QProgressBar::chunk { |
||||||
|
background-color: @lightGrey; |
||||||
|
width: 1px; |
||||||
|
} |
Before Width: | Height: | Size: 308 B |
Before Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 403 B |
Before Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 516 B |