@ -0,0 +1,260 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 |