Merge branch 'chatlog_merge_v3'
"unconfirmed" -> "Waiting to send..."
45
qtox.pro
|
@ -33,6 +33,7 @@ FORMS += \
|
||||||
src/widget/form/settings/privacysettings.ui \
|
src/widget/form/settings/privacysettings.ui \
|
||||||
src/widget/form/loadhistorydialog.ui \
|
src/widget/form/loadhistorydialog.ui \
|
||||||
src/widget/form/setpassworddialog.ui \
|
src/widget/form/setpassworddialog.ui \
|
||||||
|
src/chatlog/content/filetransferwidget.ui \
|
||||||
src/widget/form/settings/advancedsettings.ui \
|
src/widget/form/settings/advancedsettings.ui \
|
||||||
src/android.ui
|
src/android.ui
|
||||||
|
|
||||||
|
@ -198,17 +199,9 @@ HEADERS += src/widget/form/addfriendform.h \
|
||||||
src/widget/friendlistwidget.h \
|
src/widget/friendlistwidget.h \
|
||||||
src/widget/genericchatroomwidget.h \
|
src/widget/genericchatroomwidget.h \
|
||||||
src/widget/form/genericchatform.h \
|
src/widget/form/genericchatform.h \
|
||||||
src/widget/tool/chatactions/chataction.h \
|
|
||||||
src/widget/chatareawidget.h \
|
|
||||||
src/filetransferinstance.h \
|
|
||||||
src/corestructs.h \
|
src/corestructs.h \
|
||||||
src/coredefines.h \
|
src/coredefines.h \
|
||||||
src/coreav.h \
|
src/coreav.h \
|
||||||
src/widget/tool/chatactions/messageaction.h \
|
|
||||||
src/widget/tool/chatactions/filetransferaction.h \
|
|
||||||
src/widget/tool/chatactions/systemmessageaction.h \
|
|
||||||
src/widget/tool/chatactions/actionaction.h \
|
|
||||||
src/widget/tool/chatactions/alertaction.h \
|
|
||||||
src/widget/maskablepixmapwidget.h \
|
src/widget/maskablepixmapwidget.h \
|
||||||
src/video/videosource.h \
|
src/video/videosource.h \
|
||||||
src/video/cameraworker.h \
|
src/video/cameraworker.h \
|
||||||
|
@ -228,8 +221,22 @@ HEADERS += src/widget/form/addfriendform.h \
|
||||||
src/widget/toxsave.h \
|
src/widget/toxsave.h \
|
||||||
src/autoupdate.h \
|
src/autoupdate.h \
|
||||||
src/misc/serialize.h \
|
src/misc/serialize.h \
|
||||||
|
src/chatlog/chatlog.h \
|
||||||
|
src/chatlog/chatline.h \
|
||||||
|
src/chatlog/chatlinecontent.h \
|
||||||
|
src/chatlog/chatlinecontentproxy.h \
|
||||||
|
src/chatlog/content/text.h \
|
||||||
|
src/chatlog/content/spinner.h \
|
||||||
|
src/chatlog/content/filetransferwidget.h \
|
||||||
|
src/chatlog/chatmessage.h \
|
||||||
|
src/chatlog/content/image.h \
|
||||||
|
src/chatlog/customtextdocument.h \
|
||||||
src/widget/form/settings/advancedform.h \
|
src/widget/form/settings/advancedform.h \
|
||||||
src/audio.h \
|
src/audio.h \
|
||||||
|
src/chatlog/content/notificationicon.h \
|
||||||
|
src/chatlog/content/timestamp.h \
|
||||||
|
src/chatlog/documentcache.h \
|
||||||
|
src/chatlog/pixmapcache.h \
|
||||||
src/widget/callconfirmwidget.h \
|
src/widget/callconfirmwidget.h \
|
||||||
src/widget/systemtrayicon.h \
|
src/widget/systemtrayicon.h \
|
||||||
src/widget/systemtrayicon_private.h \
|
src/widget/systemtrayicon_private.h \
|
||||||
|
@ -274,15 +281,7 @@ SOURCES += \
|
||||||
src/coreav.cpp \
|
src/coreav.cpp \
|
||||||
src/widget/genericchatroomwidget.cpp \
|
src/widget/genericchatroomwidget.cpp \
|
||||||
src/widget/form/genericchatform.cpp \
|
src/widget/form/genericchatform.cpp \
|
||||||
src/widget/tool/chatactions/chataction.cpp \
|
|
||||||
src/widget/chatareawidget.cpp \
|
|
||||||
src/filetransferinstance.cpp \
|
|
||||||
src/corestructs.cpp \
|
src/corestructs.cpp \
|
||||||
src/widget/tool/chatactions/messageaction.cpp \
|
|
||||||
src/widget/tool/chatactions/filetransferaction.cpp \
|
|
||||||
src/widget/tool/chatactions/systemmessageaction.cpp \
|
|
||||||
src/widget/tool/chatactions/actionaction.cpp \
|
|
||||||
src/widget/tool/chatactions/alertaction.cpp \
|
|
||||||
src/widget/maskablepixmapwidget.cpp \
|
src/widget/maskablepixmapwidget.cpp \
|
||||||
src/video/cameraworker.cpp \
|
src/video/cameraworker.cpp \
|
||||||
src/widget/videosurface.cpp \
|
src/widget/videosurface.cpp \
|
||||||
|
@ -302,8 +301,22 @@ SOURCES += \
|
||||||
src/widget/toxsave.cpp \
|
src/widget/toxsave.cpp \
|
||||||
src/autoupdate.cpp \
|
src/autoupdate.cpp \
|
||||||
src/misc/serialize.cpp \
|
src/misc/serialize.cpp \
|
||||||
|
src/chatlog/chatlog.cpp \
|
||||||
|
src/chatlog/chatline.cpp \
|
||||||
|
src/chatlog/chatlinecontent.cpp \
|
||||||
|
src/chatlog/chatlinecontentproxy.cpp \
|
||||||
|
src/chatlog/content/text.cpp \
|
||||||
|
src/chatlog/content/spinner.cpp \
|
||||||
|
src/chatlog/content/filetransferwidget.cpp \
|
||||||
|
src/chatlog/chatmessage.cpp \
|
||||||
|
src/chatlog/content/image.cpp \
|
||||||
|
src/chatlog/customtextdocument.cpp\
|
||||||
src/widget/form/settings/advancedform.cpp \
|
src/widget/form/settings/advancedform.cpp \
|
||||||
src/audio.cpp \
|
src/audio.cpp \
|
||||||
|
src/chatlog/content/notificationicon.cpp \
|
||||||
|
src/chatlog/content/timestamp.cpp \
|
||||||
|
src/chatlog/documentcache.cpp \
|
||||||
|
src/chatlog/pixmapcache.cpp \
|
||||||
src/widget/callconfirmwidget.cpp \
|
src/widget/callconfirmwidget.cpp \
|
||||||
src/widget/systemtrayicon.cpp \
|
src/widget/systemtrayicon.cpp \
|
||||||
src/nexus.cpp \
|
src/nexus.cpp \
|
||||||
|
|
20
res.qrc
|
@ -102,16 +102,6 @@
|
||||||
<file>ui/fileButton/fileButtonHover.png</file>
|
<file>ui/fileButton/fileButtonHover.png</file>
|
||||||
<file>ui/fileButton/fileButtonPressed.png</file>
|
<file>ui/fileButton/fileButtonPressed.png</file>
|
||||||
<file>ui/fileButton/fileButtonDisabled.png</file>
|
<file>ui/fileButton/fileButtonDisabled.png</file>
|
||||||
<file>ui/fileTransferInstance/acceptFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/emptyLGreenFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/emptyLRedFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/emptyRGreenFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/emptyRRedFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/pauseFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/pauseGreyFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/resumeFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/stopFileButton.png</file>
|
|
||||||
<file>ui/fileTransferInstance/sliverRTEdge.png</file>
|
|
||||||
<file>ui/fileTransferWidget/fileTransferWidget.css</file>
|
<file>ui/fileTransferWidget/fileTransferWidget.css</file>
|
||||||
<file>ui/friendList/friendList.css</file>
|
<file>ui/friendList/friendList.css</file>
|
||||||
<file>ui/micButton/micButton.css</file>
|
<file>ui/micButton/micButton.css</file>
|
||||||
|
@ -154,6 +144,16 @@
|
||||||
<file>ui/window/applicationIcon.png</file>
|
<file>ui/window/applicationIcon.png</file>
|
||||||
<file>ui/window/statusPanel.css</file>
|
<file>ui/window/statusPanel.css</file>
|
||||||
<file>ui/window/window.css</file>
|
<file>ui/window/window.css</file>
|
||||||
|
<file>ui/chatArea/info.svg</file>
|
||||||
|
<file>ui/chatArea/spinner.svg</file>
|
||||||
|
<file>ui/chatArea/typing.svg</file>
|
||||||
|
<file>ui/chatArea/error.svg</file>
|
||||||
|
<file>ui/fileTransferInstance/no.svg</file>
|
||||||
|
<file>ui/fileTransferInstance/pause.svg</file>
|
||||||
|
<file>ui/fileTransferInstance/yes.svg</file>
|
||||||
|
<file>ui/fileTransferInstance/arrow_white.svg</file>
|
||||||
|
<file>ui/fileTransferInstance/browse.svg</file>
|
||||||
|
<file>ui/fileTransferInstance/filetransferWidget.css</file>
|
||||||
<file>ui/acceptCall/acceptCall.png</file>
|
<file>ui/acceptCall/acceptCall.png</file>
|
||||||
<file>ui/rejectCall/rejectCall.png</file>
|
<file>ui/rejectCall/rejectCall.png</file>
|
||||||
<file>ui/volButton/volButtonDisabled.png</file>
|
<file>ui/volButton/volButtonDisabled.png</file>
|
||||||
|
|
260
src/chatlog/chatline.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
110
src/chatlog/chatline.h
Normal file
|
@ -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
|
88
src/chatlog/chatlinecontent.cpp
Normal file
|
@ -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();
|
||||||
|
}
|
64
src/chatlog/chatlinecontent.h
Normal file
|
@ -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
|
55
src/chatlog/chatlinecontentproxy.cpp
Normal file
|
@ -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));
|
||||||
|
}
|
41
src/chatlog/chatlinecontentproxy.h
Normal file
|
@ -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
|
822
src/chatlog/chatlog.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
146
src/chatlog/chatlog.h
Normal file
|
@ -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
|
221
src/chatlog/chatmessage.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
62
src/chatlog/chatmessage.h
Normal file
|
@ -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
|
451
src/chatlog/content/filetransferwidget.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
84
src/chatlog/content/filetransferwidget.h
Normal file
|
@ -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
|
445
src/chatlog/content/filetransferwidget.ui
Normal file
|
@ -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>
|
53
src/chatlog/content/image.cpp
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -14,24 +14,27 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SYSTEMMESSAGEACTION_H
|
#ifndef IMAGE_H
|
||||||
#define SYSTEMMESSAGEACTION_H
|
#define IMAGE_H
|
||||||
|
|
||||||
#include "chataction.h"
|
#include "../chatlinecontent.h"
|
||||||
|
|
||||||
class SystemMessageAction : public ChatAction
|
#include <QPixmap>
|
||||||
|
|
||||||
|
class Image : public ChatLineContent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SystemMessageAction(const QString &message, const QString& type, const QString &date);
|
Image(QSize size, const QString &filename);
|
||||||
virtual ~SystemMessageAction(){;}
|
|
||||||
|
|
||||||
protected:
|
virtual QRectF boundingRect() const override;
|
||||||
virtual QString getName() {return QString();}
|
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||||
virtual QString getMessage();
|
virtual void setWidth(qreal width) override;
|
||||||
|
virtual qreal getAscent() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString message;
|
QSize size;
|
||||||
QString type;
|
QPixmap pmap;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SYSTEMMESSAGEACTION_H
|
#endif // IMAGE_H
|
83
src/chatlog/content/notificationicon.cpp
Normal file
|
@ -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());
|
||||||
|
}
|
52
src/chatlog/content/notificationicon.h
Normal file
|
@ -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
|
88
src/chatlog/content/spinner.cpp
Normal file
|
@ -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());
|
||||||
|
}
|
53
src/chatlog/content/spinner.h
Normal file
|
@ -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
|
351
src/chatlog/content/text.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
89
src/chatlog/content/text.h
Normal file
|
@ -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
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
@ -14,14 +14,15 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "alertaction.h"
|
#include "timestamp.h"
|
||||||
|
|
||||||
AlertAction::AlertAction(const QString &author, const QString &message, const QString &date) :
|
Timestamp::Timestamp(const QDateTime &time, const QString &format, const QFont &font)
|
||||||
MessageAction(author, message, date, false)
|
: Text(time.toString(format), font, false, time.toString(format))
|
||||||
{
|
{
|
||||||
|
this->time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AlertAction::getMessage()
|
QDateTime Timestamp::getTime()
|
||||||
{
|
{
|
||||||
return MessageAction::getMessage("alert");
|
return time;
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
@ -14,16 +14,20 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "systemmessageaction.h"
|
#ifndef TIMESTAMP_H
|
||||||
|
#define TIMESTAMP_H
|
||||||
|
|
||||||
SystemMessageAction::SystemMessageAction(const QString &message, const QString &type, const QString &date) :
|
#include <QDateTime>
|
||||||
ChatAction(false, QString(), date),
|
#include "text.h"
|
||||||
message(message),
|
|
||||||
type(type)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SystemMessageAction::getMessage()
|
class Timestamp : public Text
|
||||||
{
|
{
|
||||||
return QString("<table width=100%><tr><td align=center><div class=" + type + ">" + toHtmlChars(message) + "</td><tr></div></table>");
|
public:
|
||||||
}
|
Timestamp(const QDateTime& time, const QString& format, const QFont& font);
|
||||||
|
QDateTime getTime();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QDateTime time;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TIMESTAMP_H
|
44
src/chatlog/customtextdocument.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -14,22 +14,19 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef ALERTACTION_H
|
#ifndef CUSTOMTEXTDOCUMENT_H
|
||||||
#define ALERTACTION_H
|
#define CUSTOMTEXTDOCUMENT_H
|
||||||
|
|
||||||
#include "messageaction.h"
|
#include <QTextDocument>
|
||||||
|
|
||||||
class AlertAction : public MessageAction
|
class CustomTextDocument : public QTextDocument
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AlertAction(const QString &author, const QString &message, const QString& date);
|
explicit CustomTextDocument(QObject *parent = 0);
|
||||||
virtual ~AlertAction(){;}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QString getMessage();
|
virtual QVariant loadResource(int type, const QUrl &name);
|
||||||
|
|
||||||
private:
|
|
||||||
QString message;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MESSAGEACTION_H
|
#endif // CUSTOMTEXTDOCUMENT_H
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
@ -14,26 +14,34 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "actionaction.h"
|
#include "documentcache.h"
|
||||||
#include <QDebug>
|
#include "customtextdocument.h"
|
||||||
|
|
||||||
ActionAction::ActionAction(const QString &author, QString message, const QString &date, const bool& me) :
|
DocumentCache::~DocumentCache()
|
||||||
MessageAction(author, author+" "+message, date, me)
|
|
||||||
{
|
{
|
||||||
rawMessage = message;
|
while(!documents.isEmpty())
|
||||||
|
delete documents.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ActionAction::getName()
|
QTextDocument* DocumentCache::pop()
|
||||||
{
|
{
|
||||||
return QString("<div class=action>*</div>");
|
if(documents.empty())
|
||||||
|
documents.push(new CustomTextDocument);
|
||||||
|
|
||||||
|
return documents.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ActionAction::getMessage()
|
void DocumentCache::push(QTextDocument *doc)
|
||||||
{
|
{
|
||||||
return MessageAction::getMessage("action");
|
if(doc)
|
||||||
|
{
|
||||||
|
doc->clear();
|
||||||
|
documents.push(doc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ActionAction::getRawMessage()
|
DocumentCache &DocumentCache::getInstance()
|
||||||
{
|
{
|
||||||
return rawMessage;
|
static DocumentCache instance;
|
||||||
|
return instance;
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
@ -14,25 +14,29 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef ACTIONACTION_H
|
#ifndef DOCUMENTCACHE_H
|
||||||
#define ACTIONACTION_H
|
#define DOCUMENTCACHE_H
|
||||||
|
|
||||||
#include "messageaction.h"
|
#include <QStack>
|
||||||
|
|
||||||
class ActionAction : public MessageAction
|
class QTextDocument;
|
||||||
|
|
||||||
|
class DocumentCache
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ActionAction(const QString &author, QString message, const QString& date, const bool&);
|
~DocumentCache();
|
||||||
virtual ~ActionAction(){;}
|
static DocumentCache& getInstance();
|
||||||
virtual QString getRawMessage();
|
|
||||||
virtual bool isAction() {return true;}
|
QTextDocument* pop();
|
||||||
|
void push(QTextDocument* doc);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QString getMessage();
|
DocumentCache() {}
|
||||||
virtual QString getName();
|
DocumentCache(DocumentCache&) = delete;
|
||||||
|
DocumentCache& operator=(const DocumentCache&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString message, rawMessage;
|
QStack<QTextDocument*> documents;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MESSAGEACTION_H
|
#endif // DOCUMENTCACHE_H
|
40
src/chatlog/pixmapcache.cpp
Normal file
|
@ -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,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
Copyright (C) 2015 by Project Tox <https://tox.im>
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
||||||
|
|
||||||
|
@ -14,24 +14,26 @@
|
||||||
See the COPYING file for more details.
|
See the COPYING file for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef FILETRANSFERACTION_H
|
#ifndef ICONCACHE_H
|
||||||
#define FILETRANSFERACTION_H
|
#define ICONCACHE_H
|
||||||
|
|
||||||
#include "chataction.h"
|
#include <QIcon>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
class FileTransferAction : public ChatAction
|
class PixmapCache
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
public:
|
public:
|
||||||
FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me);
|
QPixmap get(const QString& filename, QSize size);
|
||||||
virtual ~FileTransferAction();
|
static PixmapCache& getInstance();
|
||||||
virtual bool isInteractive();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual QString getMessage();
|
PixmapCache() {}
|
||||||
|
PixmapCache(PixmapCache&) = delete;
|
||||||
|
PixmapCache& operator=(const PixmapCache&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileTransferInstance *w;
|
QHash<QString, QIcon> cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // FILETRANSFERACTION_H
|
#endif // ICONCACHE_H
|
27
src/core.cpp
|
@ -592,7 +592,7 @@ void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive
|
||||||
qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 cancelled by friend %2")
|
qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 cancelled by friend %2")
|
||||||
.arg(file->fileNum).arg(file->friendId);
|
.arg(file->fileNum).arg(file->friendId);
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit static_cast<Core*>(core)->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit static_cast<Core*>(core)->fileTransferCancelled(*file);
|
||||||
// Wait for sendAllFileData to return before deleting the ToxFile, we MUST ensure this or we'll use after free
|
// Wait for sendAllFileData to return before deleting the ToxFile, we MUST ensure this or we'll use after free
|
||||||
if (file->sendTimer)
|
if (file->sendTimer)
|
||||||
{
|
{
|
||||||
|
@ -619,7 +619,7 @@ void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive
|
||||||
qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 cancelled by friend %2")
|
qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 cancelled by friend %2")
|
||||||
.arg(file->fileNum).arg(file->friendId);
|
.arg(file->fileNum).arg(file->friendId);
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit static_cast<Core*>(core)->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::RECEIVING);
|
emit static_cast<Core*>(core)->fileTransferCancelled(*file);
|
||||||
removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum);
|
removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum);
|
||||||
}
|
}
|
||||||
else if (receive_send == 0 && control_type == TOX_FILECONTROL_FINISHED)
|
else if (receive_send == 0 && control_type == TOX_FILECONTROL_FINISHED)
|
||||||
|
@ -694,8 +694,7 @@ void Core::onFileDataCallback(Tox*, int32_t friendnumber, uint8_t filenumber, co
|
||||||
file->file->write((char*)data,length);
|
file->file->write((char*)data,length);
|
||||||
file->bytesSent += length;
|
file->bytesSent += length;
|
||||||
//qDebug() << QString("Core::onFileDataCallback: received %1/%2 bytes").arg(file->bytesSent).arg(file->filesize);
|
//qDebug() << QString("Core::onFileDataCallback: received %1/%2 bytes").arg(file->bytesSent).arg(file->filesize);
|
||||||
emit static_cast<Core*>(core)->fileTransferInfo(file->friendId, file->fileNum,
|
emit static_cast<Core*>(core)->fileTransferInfo(*file);
|
||||||
file->filesize, file->bytesSent, ToxFile::RECEIVING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::onAvatarInfoCallback(Tox*, int32_t friendnumber, uint8_t format,
|
void Core::onAvatarInfoCallback(Tox*, int32_t friendnumber, uint8_t format,
|
||||||
|
@ -889,7 +888,7 @@ void Core::pauseResumeFileSend(int friendId, int fileNum)
|
||||||
if (file->status == ToxFile::TRANSMITTING)
|
if (file->status == ToxFile::TRANSMITTING)
|
||||||
{
|
{
|
||||||
file->status = ToxFile::PAUSED;
|
file->status = ToxFile::PAUSED;
|
||||||
emit fileTransferPaused(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit fileTransferPaused(*file);
|
||||||
tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_PAUSE, nullptr, 0);
|
tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_PAUSE, nullptr, 0);
|
||||||
}
|
}
|
||||||
else if (file->status == ToxFile::PAUSED)
|
else if (file->status == ToxFile::PAUSED)
|
||||||
|
@ -921,7 +920,7 @@ void Core::pauseResumeFileRecv(int friendId, int fileNum)
|
||||||
if (file->status == ToxFile::TRANSMITTING)
|
if (file->status == ToxFile::TRANSMITTING)
|
||||||
{
|
{
|
||||||
file->status = ToxFile::PAUSED;
|
file->status = ToxFile::PAUSED;
|
||||||
emit fileTransferPaused(file->friendId, file->fileNum, ToxFile::RECEIVING);
|
emit fileTransferPaused(*file);
|
||||||
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_PAUSE, nullptr, 0);
|
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_PAUSE, nullptr, 0);
|
||||||
}
|
}
|
||||||
else if (file->status == ToxFile::PAUSED)
|
else if (file->status == ToxFile::PAUSED)
|
||||||
|
@ -951,7 +950,7 @@ void Core::cancelFileSend(int friendId, int fileNum)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit fileTransferCancelled(*file);
|
||||||
tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
||||||
while (file->sendTimer) QThread::msleep(1); // Wait until sendAllFileData returns before deleting
|
while (file->sendTimer) QThread::msleep(1); // Wait until sendAllFileData returns before deleting
|
||||||
removeFileFromQueue(true, friendId, fileNum);
|
removeFileFromQueue(true, friendId, fileNum);
|
||||||
|
@ -974,7 +973,7 @@ void Core::cancelFileRecv(int friendId, int fileNum)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit fileTransferCancelled(file->friendId, file->fileNum, ToxFile::RECEIVING);
|
emit fileTransferCancelled(*file);
|
||||||
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
||||||
removeFileFromQueue(true, friendId, fileNum);
|
removeFileFromQueue(true, friendId, fileNum);
|
||||||
}
|
}
|
||||||
|
@ -996,7 +995,7 @@ void Core::rejectFileRecvRequest(int friendId, int fileNum)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit fileTransferCancelled(*file);
|
||||||
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
||||||
removeFileFromQueue(false, friendId, fileNum);
|
removeFileFromQueue(false, friendId, fileNum);
|
||||||
}
|
}
|
||||||
|
@ -1095,7 +1094,7 @@ void Core::setAvatar(uint8_t format, const QByteArray& data)
|
||||||
|
|
||||||
ToxID Core::getSelfId() const
|
ToxID Core::getSelfId() const
|
||||||
{
|
{
|
||||||
uint8_t friendAddress[TOX_FRIEND_ADDRESS_SIZE];
|
uint8_t friendAddress[TOX_FRIEND_ADDRESS_SIZE] = {0};
|
||||||
tox_get_address(tox, friendAddress);
|
tox_get_address(tox, friendAddress);
|
||||||
return ToxID::fromString(CFriendAddress::toString(friendAddress));
|
return ToxID::fromString(CFriendAddress::toString(friendAddress));
|
||||||
}
|
}
|
||||||
|
@ -1489,14 +1488,14 @@ void Core::sendAllFileData(Core *core, ToxFile* file)
|
||||||
file->sendTimer = nullptr;
|
file->sendTimer = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit core->fileTransferInfo(file->friendId, file->fileNum, file->filesize, file->bytesSent, ToxFile::SENDING);
|
emit core->fileTransferInfo(*file);
|
||||||
// qApp->processEvents();
|
// qApp->processEvents();
|
||||||
long long chunkSize = tox_file_data_size(core->tox, file->friendId);
|
long long chunkSize = tox_file_data_size(core->tox, file->friendId);
|
||||||
if (chunkSize == -1)
|
if (chunkSize == -1)
|
||||||
{
|
{
|
||||||
qWarning("Core::fileHeartbeat: Error getting preffered chunk size, aborting file send");
|
qWarning("Core::fileHeartbeat: Error getting preffered chunk size, aborting file send");
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit core->fileTransferCancelled(*file);
|
||||||
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
||||||
removeFileFromQueue(true, file->friendId, file->fileNum);
|
removeFileFromQueue(true, file->friendId, file->fileNum);
|
||||||
return;
|
return;
|
||||||
|
@ -1511,7 +1510,7 @@ void Core::sendAllFileData(Core *core, ToxFile* file)
|
||||||
qWarning() << QString("Core::sendAllFileData: Error reading from file: %1").arg(file->file->errorString());
|
qWarning() << QString("Core::sendAllFileData: Error reading from file: %1").arg(file->file->errorString());
|
||||||
delete[] data;
|
delete[] data;
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit core->fileTransferCancelled(*file);
|
||||||
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
||||||
removeFileFromQueue(true, file->friendId, file->fileNum);
|
removeFileFromQueue(true, file->friendId, file->fileNum);
|
||||||
return;
|
return;
|
||||||
|
@ -1521,7 +1520,7 @@ void Core::sendAllFileData(Core *core, ToxFile* file)
|
||||||
qWarning() << QString("Core::sendAllFileData: Nothing to read from file: %1").arg(file->file->errorString());
|
qWarning() << QString("Core::sendAllFileData: Nothing to read from file: %1").arg(file->file->errorString());
|
||||||
delete[] data;
|
delete[] data;
|
||||||
file->status = ToxFile::STOPPED;
|
file->status = ToxFile::STOPPED;
|
||||||
emit core->fileTransferCancelled(file->friendId, file->fileNum, ToxFile::SENDING);
|
emit core->fileTransferCancelled(*file);
|
||||||
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0);
|
||||||
removeFileFromQueue(true, file->friendId, file->fileNum);
|
removeFileFromQueue(true, file->friendId, file->fileNum);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -196,12 +196,12 @@ signals:
|
||||||
void fileSendStarted(ToxFile file);
|
void fileSendStarted(ToxFile file);
|
||||||
void fileReceiveRequested(ToxFile file);
|
void fileReceiveRequested(ToxFile file);
|
||||||
void fileTransferAccepted(ToxFile file);
|
void fileTransferAccepted(ToxFile file);
|
||||||
void fileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection direction);
|
void fileTransferCancelled(ToxFile file);
|
||||||
void fileTransferFinished(ToxFile file);
|
void fileTransferFinished(ToxFile file);
|
||||||
void fileUploadFinished(const QString& path);
|
void fileUploadFinished(const QString& path);
|
||||||
void fileDownloadFinished(const QString& path);
|
void fileDownloadFinished(const QString& path);
|
||||||
void fileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection direction);
|
void fileTransferPaused(ToxFile file);
|
||||||
void fileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection direction);
|
void fileTransferInfo(ToxFile file);
|
||||||
void fileTransferRemotePausedUnpaused(ToxFile file, bool paused);
|
void fileTransferRemotePausedUnpaused(ToxFile file, bool paused);
|
||||||
void fileTransferBrokenUnbroken(ToxFile file, bool broken);
|
void fileTransferBrokenUnbroken(ToxFile file, bool broken);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,16 @@ ToxFile::ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePat
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ToxFile::operator==(const ToxFile &other) const
|
||||||
|
{
|
||||||
|
return (fileNum == other.fileNum) && (friendId == other.friendId) && (direction == other.direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ToxFile::operator!=(const ToxFile &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
void ToxFile::setFilePath(QString path)
|
void ToxFile::setFilePath(QString path)
|
||||||
{
|
{
|
||||||
filePath=path;
|
filePath=path;
|
||||||
|
@ -60,6 +70,11 @@ bool ToxID::isMine() const
|
||||||
return *this == Core::getInstance()->getSelfId();
|
return *this == Core::getInstance()->getSelfId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ToxID::clear()
|
||||||
|
{
|
||||||
|
publicKey.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool ToxID::isToxId(const QString& value)
|
bool ToxID::isToxId(const QString& value)
|
||||||
{
|
{
|
||||||
const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$");
|
const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$");
|
||||||
|
|
|
@ -30,6 +30,7 @@ struct ToxID
|
||||||
bool operator==(const ToxID& other) const;
|
bool operator==(const ToxID& other) const;
|
||||||
bool operator!=(const ToxID& other) const;
|
bool operator!=(const ToxID& other) const;
|
||||||
bool isMine() const;
|
bool isMine() const;
|
||||||
|
void clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DhtServer
|
struct DhtServer
|
||||||
|
@ -59,6 +60,10 @@ struct ToxFile
|
||||||
ToxFile()=default;
|
ToxFile()=default;
|
||||||
ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction);
|
ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction);
|
||||||
~ToxFile(){}
|
~ToxFile(){}
|
||||||
|
|
||||||
|
bool operator==(const ToxFile& other) const;
|
||||||
|
bool operator!=(const ToxFile& other) const;
|
||||||
|
|
||||||
void setFilePath(QString path);
|
void setFilePath(QString path);
|
||||||
bool open(bool write);
|
bool open(bool write);
|
||||||
|
|
||||||
|
@ -67,8 +72,8 @@ struct ToxFile
|
||||||
QByteArray fileName;
|
QByteArray fileName;
|
||||||
QString filePath;
|
QString filePath;
|
||||||
QFile* file;
|
QFile* file;
|
||||||
long long bytesSent;
|
qint64 bytesSent;
|
||||||
long long filesize;
|
qint64 filesize;
|
||||||
FileStatus status;
|
FileStatus status;
|
||||||
FileDirection direction;
|
FileDirection direction;
|
||||||
QTimer* sendTimer;
|
QTimer* sendTimer;
|
||||||
|
|
|
@ -1,507 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "filetransferinstance.h"
|
|
||||||
#include "core.h"
|
|
||||||
#include "misc/settings.h"
|
|
||||||
#include "misc/style.h"
|
|
||||||
#include "src/friendlist.h"
|
|
||||||
#include "src/friend.h"
|
|
||||||
#include <math.h>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
#define MAX_CONTENT_WIDTH 250
|
|
||||||
#define MAX_PREVIEW_SIZE 25*1024*1024
|
|
||||||
|
|
||||||
uint FileTransferInstance::Idconter = 0;
|
|
||||||
|
|
||||||
FileTransferInstance::FileTransferInstance(ToxFile File)
|
|
||||||
: lastBytesSent{0},
|
|
||||||
fileNum{File.fileNum}, friendId{File.friendId}, direction{File.direction}
|
|
||||||
{
|
|
||||||
id = Idconter++;
|
|
||||||
state = tsPending;
|
|
||||||
remotePaused = false;
|
|
||||||
lastUpdateTime = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
filename = File.fileName;
|
|
||||||
|
|
||||||
// update this whenever you change the font in innerStyle.css
|
|
||||||
QFontMetrics fm(Style::getFont(Style::Small));
|
|
||||||
|
|
||||||
filenameElided = fm.elidedText(filename, Qt::ElideRight, MAX_CONTENT_WIDTH);
|
|
||||||
size = getHumanReadableSize(File.filesize);
|
|
||||||
contentPrefWidth = std::max(fm.width(filenameElided), fm.width(size)) + fm.leading();
|
|
||||||
|
|
||||||
speed = "0B/s";
|
|
||||||
eta = "00:00";
|
|
||||||
|
|
||||||
if (File.direction == ToxFile::SENDING)
|
|
||||||
{
|
|
||||||
if (File.file->size() <= MAX_PREVIEW_SIZE)
|
|
||||||
{
|
|
||||||
QImage preview;
|
|
||||||
File.file->seek(0);
|
|
||||||
if (preview.loadFromData(File.file->readAll()))
|
|
||||||
{
|
|
||||||
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File.file->seek(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::getHumanReadableSize(unsigned long long size)
|
|
||||||
{
|
|
||||||
static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"};
|
|
||||||
int exp = 0;
|
|
||||||
if (size)
|
|
||||||
exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1));
|
|
||||||
return QString().setNum(size / pow(1024, exp),'f',2).append(suffix[exp]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction)
|
|
||||||
{
|
|
||||||
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// state = tsProcessing;
|
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
|
||||||
long recenttimediff = lastUpdateTime.msecsTo(now);
|
|
||||||
if (recenttimediff < 1000) //update every 1s
|
|
||||||
return;
|
|
||||||
|
|
||||||
long timediff = effStartTime.msecsTo(now);
|
|
||||||
if (timediff <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
long avgspeed = (BytesSent - previousBytesSent) / timediff * 1000;
|
|
||||||
long recentspeed = (BytesSent - lastBytesSent) / recenttimediff * 1000;
|
|
||||||
|
|
||||||
speed = getHumanReadableSize(recentspeed)+"/s";
|
|
||||||
size = getHumanReadableSize(Filesize);
|
|
||||||
totalBytes = Filesize;
|
|
||||||
|
|
||||||
if (!avgspeed)
|
|
||||||
return;
|
|
||||||
int etaSecs = (Filesize - BytesSent) / avgspeed;
|
|
||||||
QTime etaTime(0,0);
|
|
||||||
etaTime = etaTime.addSecs(etaSecs);
|
|
||||||
eta = etaTime.toString("mm:ss");
|
|
||||||
|
|
||||||
lastBytesSent = BytesSent;
|
|
||||||
lastUpdateTime = now;
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction)
|
|
||||||
{
|
|
||||||
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
|
|
||||||
return;
|
|
||||||
disconnect(Core::getInstance(),0,this,0);
|
|
||||||
state = tsCanceled;
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferFinished(ToxFile File)
|
|
||||||
{
|
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
|
|
||||||
return;
|
|
||||||
disconnect(Core::getInstance(),0,this,0);
|
|
||||||
|
|
||||||
if (File.direction == ToxFile::RECEIVING)
|
|
||||||
{
|
|
||||||
QImage preview;
|
|
||||||
QFile previewFile(File.filePath);
|
|
||||||
if (previewFile.open(QIODevice::ReadOnly) && previewFile.size() <= MAX_PREVIEW_SIZE) // Don't preview big (>25MiB) images
|
|
||||||
{
|
|
||||||
if (preview.loadFromData(previewFile.readAll()))
|
|
||||||
{
|
|
||||||
pic = preview.scaled(100, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
previewFile.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state = tsFinished;
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferAccepted(ToxFile File)
|
|
||||||
{
|
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
|
|
||||||
return;
|
|
||||||
|
|
||||||
remotePaused = false;
|
|
||||||
state = tsProcessing;
|
|
||||||
effStartTime = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferRemotePausedUnpaused(ToxFile File, bool paused)
|
|
||||||
{
|
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
|
|
||||||
return;
|
|
||||||
|
|
||||||
remotePaused = paused;
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection Direction)
|
|
||||||
{
|
|
||||||
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
|
|
||||||
return;
|
|
||||||
|
|
||||||
state = tsPaused;
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::cancelTransfer()
|
|
||||||
{
|
|
||||||
Core::getInstance()->cancelFileSend(friendId, fileNum);
|
|
||||||
state = tsCanceled;
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::rejectRecvRequest()
|
|
||||||
{
|
|
||||||
Core::getInstance()->rejectFileRecvRequest(friendId, fileNum);
|
|
||||||
onFileTransferCancelled(friendId, fileNum, direction);
|
|
||||||
state = tsCanceled;
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
// for whatever the fuck reason, QFileInfo::isWritable() always fails for files that don't exist
|
|
||||||
// which makes it useless for our case
|
|
||||||
// since QDir doesn't have an isWritable(), the only option I can think of is to make/delete the file
|
|
||||||
// surely this is a common problem that has a qt-implemented solution?
|
|
||||||
bool isFileWritable(QString& path)
|
|
||||||
{
|
|
||||||
QFile file(path);
|
|
||||||
bool exists = file.exists();
|
|
||||||
bool out = file.open(QIODevice::WriteOnly);
|
|
||||||
file.close();
|
|
||||||
if (!exists)
|
|
||||||
file.remove();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::acceptRecvRequest()
|
|
||||||
{
|
|
||||||
QString path = Settings::getInstance().getAutoAcceptDir(FriendList::findFriend(friendId)->getToxID());
|
|
||||||
|
|
||||||
if (path.isEmpty() && Settings::getInstance().getAutoSaveEnabled())
|
|
||||||
path = Settings::getInstance().getGlobalAutoAcceptDir();
|
|
||||||
|
|
||||||
if (!path.isEmpty())
|
|
||||||
{
|
|
||||||
QDir dir(path);
|
|
||||||
path = dir.filePath(filename);
|
|
||||||
QFileInfo info(path);
|
|
||||||
if (info.exists()) // emulate chrome
|
|
||||||
{
|
|
||||||
QString name = info.baseName(), ext = info.completeSuffix();
|
|
||||||
int i = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
path = dir.filePath(name + QString(" (%1)").arg(++i) + "." + ext);
|
|
||||||
}
|
|
||||||
while (QFileInfo(path).exists());
|
|
||||||
}
|
|
||||||
qDebug() << "File: auto saving to" << path;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
path = QFileDialog::getSaveFileName(0, tr("Save a file","Title of the file saving dialog"), QDir::home().filePath(filename));
|
|
||||||
if (path.isEmpty())
|
|
||||||
return;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (isFileWritable(path))
|
|
||||||
break;
|
|
||||||
else
|
|
||||||
QMessageBox::warning(0,
|
|
||||||
tr("Location not writable","Title of permissions popup"),
|
|
||||||
tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
savePath = path;
|
|
||||||
|
|
||||||
Core::getInstance()->acceptFileRecvRequest(friendId, fileNum, path);
|
|
||||||
state = tsProcessing;
|
|
||||||
|
|
||||||
effStartTime = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::pauseResumeRecv()
|
|
||||||
{
|
|
||||||
if (!(state == tsProcessing || state == tsPaused))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (remotePaused)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Core::getInstance()->pauseResumeFileRecv(friendId, fileNum);
|
|
||||||
|
|
||||||
if (state == tsPaused)
|
|
||||||
{
|
|
||||||
effStartTime = QDateTime::currentDateTime();
|
|
||||||
previousBytesSent = lastBytesSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::pauseResumeSend()
|
|
||||||
{
|
|
||||||
if (!(state == tsProcessing || state == tsPaused))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (remotePaused)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Core::getInstance()->pauseResumeFileSend(friendId, fileNum);
|
|
||||||
|
|
||||||
if (state == tsPaused)
|
|
||||||
{
|
|
||||||
effStartTime = QDateTime::currentDateTime();
|
|
||||||
previousBytesSent = lastBytesSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::QImage2base64(const QImage &img)
|
|
||||||
{
|
|
||||||
QByteArray ba;
|
|
||||||
QBuffer buffer(&ba);
|
|
||||||
buffer.open(QIODevice::WriteOnly);
|
|
||||||
img.save(&buffer, "PNG"); // writes image into ba in PNG format
|
|
||||||
return ba.toBase64();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::getHtmlImage()
|
|
||||||
{
|
|
||||||
|
|
||||||
QString res;
|
|
||||||
if (state == tsPending || state == tsProcessing || state == tsPaused)
|
|
||||||
{
|
|
||||||
QImage leftBtnImg(":/ui/fileTransferInstance/stopFileButton.png");
|
|
||||||
QImage rightBtnImg;
|
|
||||||
if (state == tsProcessing)
|
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseFileButton.png");
|
|
||||||
else if (state == tsPaused)
|
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/resumeFileButton.png");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (direction == ToxFile::SENDING)
|
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseGreyFileButton.png");
|
|
||||||
else
|
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/acceptFileButton.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remotePaused)
|
|
||||||
rightBtnImg = QImage(":/ui/fileTransferInstance/pauseGreyFileButton.png");
|
|
||||||
|
|
||||||
res = draw2ButtonsForm("silver", leftBtnImg, rightBtnImg);
|
|
||||||
} else if (state == tsBroken)
|
|
||||||
{
|
|
||||||
QImage leftBtnImg(":/ui/fileTransferInstance/stopFileButton.png");
|
|
||||||
QImage rightBtnImg(":/ui/fileTransferInstance/pauseGreyFileButton.png");
|
|
||||||
|
|
||||||
res = draw2ButtonsForm("red", leftBtnImg, rightBtnImg);
|
|
||||||
} else if (state == tsCanceled)
|
|
||||||
{
|
|
||||||
res = drawButtonlessForm("red");
|
|
||||||
} else if (state == tsFinished)
|
|
||||||
{
|
|
||||||
res = drawButtonlessForm("green");
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::pressFromHtml(QString code)
|
|
||||||
{
|
|
||||||
if (state == tsFinished || state == tsCanceled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (direction == ToxFile::SENDING)
|
|
||||||
{
|
|
||||||
if (code == "btnA")
|
|
||||||
cancelTransfer();
|
|
||||||
else if (code == "btnB")
|
|
||||||
pauseResumeSend();
|
|
||||||
} else {
|
|
||||||
if (code == "btnA")
|
|
||||||
rejectRecvRequest();
|
|
||||||
else if (code == "btnB")
|
|
||||||
{
|
|
||||||
if (state == tsPending)
|
|
||||||
acceptRecvRequest();
|
|
||||||
else
|
|
||||||
pauseResumeRecv();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::drawButtonlessForm(const QString &type)
|
|
||||||
{
|
|
||||||
QString imgAStr;
|
|
||||||
QString imgBStr;
|
|
||||||
|
|
||||||
if (type == "red")
|
|
||||||
{
|
|
||||||
imgAStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png")) + "\">";
|
|
||||||
imgBStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyRRedFileButton.png")) + "\">";
|
|
||||||
} else {
|
|
||||||
imgAStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png")) + "\">";
|
|
||||||
imgBStr = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyRGreenFileButton.png")) + "\">";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString content = "<p>" + filenameElided + "</p><p>" + size + "</p>";
|
|
||||||
|
|
||||||
return wrapIntoForm(content, type, imgAStr, imgBStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::insertMiniature(const QString &type)
|
|
||||||
{
|
|
||||||
if (pic == QImage())
|
|
||||||
return QString();
|
|
||||||
|
|
||||||
QString widgetId = QString::number(getId());
|
|
||||||
|
|
||||||
QString res;
|
|
||||||
res = "<td><div class=" + type + ">\n";
|
|
||||||
res += "<img src=\"data:mini." + widgetId + "/png;base64," + QImage2base64(pic) + "\">";
|
|
||||||
res += "</div></td>\n";
|
|
||||||
res += "<td width=5><div class=" + type + ">\n";
|
|
||||||
res += "</div></td>\n";
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::draw2ButtonsForm(const QString &type, const QImage &imgA, const QImage &imgB)
|
|
||||||
{
|
|
||||||
QString widgetId = QString::number(getId());
|
|
||||||
QString imgAstr = "<img src=\"data:ftrans." + widgetId + ".btnA/png;base64," + QImage2base64(imgA) + "\">";
|
|
||||||
QString imgBstr = "<img src=\"data:ftrans." + widgetId + ".btnB/png;base64," + QImage2base64(imgB) + "\">";
|
|
||||||
|
|
||||||
QString content;
|
|
||||||
QString progrBar = "<img src=\"data:progressbar." + widgetId + "/png;base64," +
|
|
||||||
QImage2base64(drawProgressBarImg(double(lastBytesSent)/totalBytes, MAX_CONTENT_WIDTH, 9)) + "\">";
|
|
||||||
|
|
||||||
content = "<p>" + filenameElided + "</p>";
|
|
||||||
content += "<table cellspacing=\"0\"><tr>";
|
|
||||||
content += "<td>" + size + "</td>";
|
|
||||||
content += "<td align=center>" + speed + "</td>";
|
|
||||||
content += "<td align=right>" + tr("ETA") + ": " + eta + "</td>";
|
|
||||||
content += "</tr><tr><td colspan=3>";
|
|
||||||
content += progrBar;
|
|
||||||
content += "</td></tr></table>";
|
|
||||||
|
|
||||||
return wrapIntoForm(content, type, imgAstr, imgBstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferInstance::wrapIntoForm(const QString& content, const QString &type, const QString &imgAstr, const QString &imgBstr)
|
|
||||||
{
|
|
||||||
QString w = QString::number(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").size().width());
|
|
||||||
QString imgLeftA, imgLeftB;
|
|
||||||
|
|
||||||
if (type == "green")
|
|
||||||
{
|
|
||||||
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png").mirrored(true,false)) + "\">";
|
|
||||||
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLGreenFileButton.png").mirrored(true,true)) + "\">";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "silver")
|
|
||||||
{
|
|
||||||
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/sliverRTEdge.png").mirrored(true,false)) + "\">";
|
|
||||||
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/sliverRTEdge.png").mirrored(true,true)) + "\">";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "red")
|
|
||||||
{
|
|
||||||
imgLeftA = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").mirrored(true,false)) + "\">";
|
|
||||||
imgLeftB = "<img src=\"data:placeholder/png;base64," + QImage2base64(QImage(":/ui/fileTransferInstance/emptyLRedFileButton.png").mirrored(true,true)) + "\">";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString res;
|
|
||||||
res = "<table cellspacing=\"0\">\n";
|
|
||||||
res += "<tr valign=middle>\n";
|
|
||||||
res += "<td width=" + w + ">\n";
|
|
||||||
res += "<div class=button>" + imgLeftA + "<br>" + imgLeftB + "</div>\n";
|
|
||||||
res += "</td>\n";
|
|
||||||
res += insertMiniature(type);
|
|
||||||
res += "<td width=" + QString::number(contentPrefWidth) + ">\n";
|
|
||||||
res += "<div class=" + type + ">";
|
|
||||||
res += content;
|
|
||||||
res += "</div>\n";
|
|
||||||
res += "</td>\n";
|
|
||||||
res += "<td width=3>\n";
|
|
||||||
res += "<div class=" + type + "></div>\n";
|
|
||||||
res += "</td>\n";
|
|
||||||
res += "<td>\n";
|
|
||||||
res += "<div class=button>" + imgAstr + "<br>" + imgBstr + "</div>\n";
|
|
||||||
res += "</td>\n";
|
|
||||||
res += "</tr>\n";
|
|
||||||
res += "</table>\n";
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage FileTransferInstance::drawProgressBarImg(const double &part, int w, int h)
|
|
||||||
{
|
|
||||||
QImage progressBar(w, h, QImage::Format_Mono);
|
|
||||||
|
|
||||||
QPainter qPainter(&progressBar);
|
|
||||||
qPainter.setBrush(Qt::NoBrush);
|
|
||||||
qPainter.setPen(Qt::black);
|
|
||||||
qPainter.drawRect(0, 0, w - 1, h - 1);
|
|
||||||
|
|
||||||
qPainter.setBrush(Qt::SolidPattern);
|
|
||||||
qPainter.setPen(Qt::black);
|
|
||||||
qPainter.drawRect(1, 0, (w - 2) * (part), h - 1);
|
|
||||||
|
|
||||||
return progressBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileTransferInstance::onFileTransferBrokenUnbroken(ToxFile File, bool broken)
|
|
||||||
{
|
|
||||||
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (broken)
|
|
||||||
state = tsBroken;
|
|
||||||
else
|
|
||||||
state = tsProcessing;
|
|
||||||
|
|
||||||
emit stateUpdated();
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
#ifndef FILETRANSFERINSTANCE_H
|
|
||||||
#define FILETRANSFERINSTANCE_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QImage>
|
|
||||||
|
|
||||||
#include "corestructs.h"
|
|
||||||
|
|
||||||
struct ToxFile;
|
|
||||||
|
|
||||||
class FileTransferInstance : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum TransfState {tsPending, tsProcessing, tsPaused, tsFinished, tsCanceled, tsBroken};
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FileTransferInstance(ToxFile File);
|
|
||||||
QString getHtmlImage();
|
|
||||||
uint getId(){return id;}
|
|
||||||
TransfState getState() {return state;}
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction);
|
|
||||||
void onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction);
|
|
||||||
void onFileTransferFinished(ToxFile File);
|
|
||||||
void onFileTransferAccepted(ToxFile File);
|
|
||||||
void onFileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection Direction);
|
|
||||||
void onFileTransferRemotePausedUnpaused(ToxFile File, bool paused);
|
|
||||||
void onFileTransferBrokenUnbroken(ToxFile File, bool broken);
|
|
||||||
void pressFromHtml(QString);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void stateUpdated();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void cancelTransfer();
|
|
||||||
void rejectRecvRequest();
|
|
||||||
void acceptRecvRequest();
|
|
||||||
void pauseResumeRecv();
|
|
||||||
void pauseResumeSend();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString getHumanReadableSize(unsigned long long size);
|
|
||||||
QString QImage2base64(const QImage &img);
|
|
||||||
QString drawButtonlessForm(const QString &type);
|
|
||||||
QString draw2ButtonsForm(const QString &type, const QImage &imgA, const QImage &imgB);
|
|
||||||
QString insertMiniature(const QString &type);
|
|
||||||
QString wrapIntoForm(const QString &content, const QString &type, const QString &imgAstr, const QString &imgBstr);
|
|
||||||
QImage drawProgressBarImg(const double &part, int w, int h);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static uint Idconter;
|
|
||||||
uint id;
|
|
||||||
|
|
||||||
TransfState state;
|
|
||||||
bool remotePaused;
|
|
||||||
QImage pic;
|
|
||||||
QString filename, size, speed, eta;
|
|
||||||
QString filenameElided;
|
|
||||||
QDateTime effStartTime, lastUpdateTime;
|
|
||||||
long long lastBytesSent, totalBytes, previousBytesSent = 0; // last var used for eta on resumes
|
|
||||||
int fileNum;
|
|
||||||
int friendId;
|
|
||||||
int contentPrefWidth;
|
|
||||||
QString savePath;
|
|
||||||
ToxFile::FileDirection direction;
|
|
||||||
QString stopFileButtonStylesheet, pauseFileButtonStylesheet, acceptFileButtonStylesheet;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // FILETRANSFERINSTANCE_H
|
|
|
@ -91,7 +91,7 @@ bool SmileyPack::load(const QString& filename)
|
||||||
{
|
{
|
||||||
// discard old data
|
// discard old data
|
||||||
filenameTable.clear();
|
filenameTable.clear();
|
||||||
imgCache.clear();
|
iconCache.clear();
|
||||||
emoticons.clear();
|
emoticons.clear();
|
||||||
path.clear();
|
path.clear();
|
||||||
|
|
||||||
|
@ -135,11 +135,8 @@ bool SmileyPack::load(const QString& filename)
|
||||||
filenameTable.insert(emoticon, file);
|
filenameTable.insert(emoticon, file);
|
||||||
|
|
||||||
cacheSmiley(file); // preload all smileys
|
cacheSmiley(file); // preload all smileys
|
||||||
|
|
||||||
QPixmap pm;
|
if(!getCachedSmiley(emoticon).isNull())
|
||||||
pm.loadFromData(getCachedSmiley(emoticon), "PNG");
|
|
||||||
|
|
||||||
if (pm.size().width() > 0)
|
|
||||||
emoticonSet.push_back(emoticon);
|
emoticonSet.push_back(emoticon);
|
||||||
|
|
||||||
stringElement = stringElement.nextSibling().toElement();
|
stringElement = stringElement.nextSibling().toElement();
|
||||||
|
@ -184,50 +181,36 @@ QList<QStringList> SmileyPack::getEmoticons() const
|
||||||
|
|
||||||
QString SmileyPack::getAsRichText(const QString &key)
|
QString SmileyPack::getAsRichText(const QString &key)
|
||||||
{
|
{
|
||||||
return "<img title=\""%key%"\" src=\"data:image/png;base64," % QString(getCachedSmiley(key).toBase64()) % "\">";
|
return QString("<img title=\"%1\" src=\"key:%1\"\\>").arg(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon SmileyPack::getAsIcon(const QString &key)
|
QIcon SmileyPack::getAsIcon(const QString &key)
|
||||||
{
|
{
|
||||||
QPixmap pm;
|
return getCachedSmiley(key);
|
||||||
pm.loadFromData(getCachedSmiley(key), "PNG");
|
|
||||||
return QIcon(pm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SmileyPack::cacheSmiley(const QString &name)
|
void SmileyPack::cacheSmiley(const QString &name)
|
||||||
{
|
{
|
||||||
// The -1 is to avoid having the space for descenders under images move the text down
|
|
||||||
// We can't remove it because Qt doesn't support CSS display or vertical-align
|
|
||||||
int fontHeight = QFontInfo(Style::getFont(Style::Big)).pixelSize() - 1;
|
|
||||||
QSize size(fontHeight, fontHeight);
|
|
||||||
QString filename = QDir(path).filePath(name);
|
QString filename = QDir(path).filePath(name);
|
||||||
QImage img(filename);
|
|
||||||
|
|
||||||
if (!img.isNull())
|
QIcon icon;
|
||||||
{
|
icon.addFile(filename);
|
||||||
QImage scaledImg = img.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
iconCache.insert(name, icon);
|
||||||
|
|
||||||
QByteArray scaledImgData;
|
|
||||||
QBuffer buffer(&scaledImgData);
|
|
||||||
scaledImg.save(&buffer, "PNG");
|
|
||||||
|
|
||||||
imgCache.insert(name, scaledImgData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray SmileyPack::getCachedSmiley(const QString &key)
|
QIcon SmileyPack::getCachedSmiley(const QString &key)
|
||||||
{
|
{
|
||||||
// valid key?
|
// valid key?
|
||||||
if (!filenameTable.contains(key))
|
if (!filenameTable.contains(key))
|
||||||
return QByteArray();
|
return QPixmap();
|
||||||
|
|
||||||
// cache it if needed
|
// cache it if needed
|
||||||
QString file = filenameTable.value(key);
|
QString file = filenameTable.value(key);
|
||||||
if (!imgCache.contains(file)) {
|
if (!iconCache.contains(file)) {
|
||||||
cacheSmiley(file);
|
cacheSmiley(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return imgCache.value(file);
|
return iconCache.value(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SmileyPack::onSmileyPackChanged()
|
void SmileyPack::onSmileyPackChanged()
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
#define SMILEYPACK_SEARCH_PATHS \
|
#define SMILEYPACK_SEARCH_PATHS \
|
||||||
{ \
|
{ \
|
||||||
|
@ -51,10 +52,10 @@ private:
|
||||||
SmileyPack& operator=(const SmileyPack&) = delete;
|
SmileyPack& operator=(const SmileyPack&) = delete;
|
||||||
|
|
||||||
void cacheSmiley(const QString& name);
|
void cacheSmiley(const QString& name);
|
||||||
QByteArray getCachedSmiley(const QString& key);
|
QIcon getCachedSmiley(const QString& key);
|
||||||
|
|
||||||
QHash<QString, QString> filenameTable; // matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
|
QHash<QString, QString> filenameTable; // matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
|
||||||
QHash<QString, QByteArray> imgCache; // (scaled) representation of a smiley ie. "happy.png" -> data
|
QHash<QString, QIcon> iconCache; // representation of a smiley ie. "happy.png" -> data
|
||||||
QList<QStringList> emoticons; // {{ ":)", ":-)" }, {":(", ...}, ... }
|
QList<QStringList> emoticons; // {{ ":)", ":-)" }, {":(", ...}, ... }
|
||||||
QString path; // directory containing the cfg and image files
|
QString path; // directory containing the cfg and image files
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,15 +47,14 @@ void OfflineMsgEngine::dischargeReceipt(int receipt)
|
||||||
if (msgIt != undeliveredMsgs.end())
|
if (msgIt != undeliveredMsgs.end())
|
||||||
{
|
{
|
||||||
HistoryKeeper::getInstance()->markAsSent(mID);
|
HistoryKeeper::getInstance()->markAsSent(mID);
|
||||||
msgIt.value().msg->markAsSent();
|
msgIt.value().msg->markAsSent(QDateTime::currentDateTime());
|
||||||
msgIt.value().msg->featureUpdate();
|
|
||||||
undeliveredMsgs.erase(msgIt);
|
undeliveredMsgs.erase(msgIt);
|
||||||
}
|
}
|
||||||
receipts.erase(it);
|
receipts.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OfflineMsgEngine::registerReceipt(int receipt, int messageID, MessageActionPtr msg, const QDateTime ×tamp)
|
void OfflineMsgEngine::registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg, const QDateTime ×tamp)
|
||||||
{
|
{
|
||||||
QMutexLocker ml(&mutex);
|
QMutexLocker ml(&mutex);
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ void OfflineMsgEngine::deliverOfflineMsgs()
|
||||||
registerReceipt(iter.value().receipt, iter.key(), iter.value().msg, iter.value().timestamp);
|
registerReceipt(iter.value().receipt, iter.key(), iter.value().msg, iter.value().timestamp);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QString messageText = iter.value().msg->getRawMessage();
|
QString messageText = iter.value().msg->toString();
|
||||||
int rec;
|
int rec;
|
||||||
if (iter.value().msg->isAction())
|
if (iter.value().msg->isAction())
|
||||||
rec = Core::getInstance()->sendAction(f->getFriendID(), messageText);
|
rec = Core::getInstance()->sendAction(f->getFriendID(), messageText);
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include "src/widget/tool/chatactions/messageaction.h"
|
#include <QMap>
|
||||||
|
#include "src/chatlog/chatmessage.h"
|
||||||
|
|
||||||
struct Friend;
|
struct Friend;
|
||||||
class QTimer;
|
class QTimer;
|
||||||
|
@ -35,7 +36,7 @@ public:
|
||||||
static QMutex globalMutex;
|
static QMutex globalMutex;
|
||||||
|
|
||||||
void dischargeReceipt(int receipt);
|
void dischargeReceipt(int receipt);
|
||||||
void registerReceipt(int receipt, int messageID, MessageActionPtr msg, const QDateTime ×tamp = QDateTime::currentDateTime());
|
void registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg, const QDateTime ×tamp = QDateTime::currentDateTime());
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void deliverOfflineMsgs();
|
void deliverOfflineMsgs();
|
||||||
|
@ -43,7 +44,7 @@ public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct MsgPtr {
|
struct MsgPtr {
|
||||||
MessageActionPtr msg;
|
ChatMessage::Ptr msg;
|
||||||
QDateTime timestamp;
|
QDateTime timestamp;
|
||||||
int receipt;
|
int receipt;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,208 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "chatareawidget.h"
|
|
||||||
#include "tool/chatactions/chataction.h"
|
|
||||||
#include <QScrollBar>
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QTextTable>
|
|
||||||
#include <QAbstractTextDocumentLayout>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
ChatAreaWidget::ChatAreaWidget(QWidget *parent)
|
|
||||||
: QTextBrowser(parent)
|
|
||||||
, tableFrmt(nullptr)
|
|
||||||
, nameWidth(75)
|
|
||||||
, empty{true}
|
|
||||||
{
|
|
||||||
setReadOnly(true);
|
|
||||||
viewport()->setCursor(Qt::ArrowCursor);
|
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
|
||||||
setUndoRedoEnabled(false);
|
|
||||||
|
|
||||||
setOpenExternalLinks(false);
|
|
||||||
setOpenLinks(false);
|
|
||||||
setAcceptRichText(false);
|
|
||||||
setFrameStyle(QFrame::NoFrame);
|
|
||||||
|
|
||||||
nameFormat.setAlignment(Qt::AlignRight);
|
|
||||||
nameFormat.setNonBreakableLines(true);
|
|
||||||
dateFormat.setAlignment(Qt::AlignLeft);
|
|
||||||
dateFormat.setNonBreakableLines(true);
|
|
||||||
|
|
||||||
connect(this, &ChatAreaWidget::anchorClicked, this, &ChatAreaWidget::onAnchorClicked);
|
|
||||||
connect(verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onSliderRangeChanged()));
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatAreaWidget::~ChatAreaWidget()
|
|
||||||
{
|
|
||||||
if (tableFrmt)
|
|
||||||
delete tableFrmt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event)
|
|
||||||
{
|
|
||||||
QTextEdit::mouseReleaseEvent(event);
|
|
||||||
QPointF documentHitPost(event->pos().x() + horizontalScrollBar()->value(), event->pos().y() + verticalScrollBar()->value());
|
|
||||||
int pos = this->document()->documentLayout()->hitTest(documentHitPost, Qt::ExactHit);
|
|
||||||
if (pos > 0)
|
|
||||||
{
|
|
||||||
QTextCursor cursor(document());
|
|
||||||
cursor.setPosition(pos);
|
|
||||||
|
|
||||||
if (!cursor.atEnd())
|
|
||||||
{
|
|
||||||
cursor.setPosition(pos+1);
|
|
||||||
|
|
||||||
QTextFormat format = cursor.charFormat();
|
|
||||||
if (format.isImageFormat())
|
|
||||||
{
|
|
||||||
QString imageName = format.toImageFormat().name();
|
|
||||||
if (QRegExp("^data:ftrans.*").exactMatch(imageName))
|
|
||||||
{
|
|
||||||
QString data = imageName.right(imageName.length() - 12);
|
|
||||||
int endpos = data.indexOf("/png;base64");
|
|
||||||
data = data.left(endpos);
|
|
||||||
int middlepos = data.indexOf(".");
|
|
||||||
QString widgetID = data.left(middlepos);
|
|
||||||
QString widgetBtn = data.right(data.length() - middlepos - 1);
|
|
||||||
qDebug() << "ChatAreaWidget::mouseReleaseEvent:" << widgetID << widgetBtn;
|
|
||||||
emit onFileTranfertInterract(widgetID, widgetBtn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit onClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::onAnchorClicked(const QUrl &url)
|
|
||||||
{
|
|
||||||
QDesktopServices::openUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::insertMessage(ChatActionPtr msgAction, QTextCursor::MoveOperation pos)
|
|
||||||
{
|
|
||||||
if (msgAction == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
checkSlider();
|
|
||||||
|
|
||||||
QTextTable *chatTextTable = getMsgTable(pos);
|
|
||||||
msgAction->assignPlace(chatTextTable, this);
|
|
||||||
msgAction->dispaly();
|
|
||||||
|
|
||||||
/*
|
|
||||||
QTextCursor cur = chatTextTable->cellAt(0, 2).firstCursorPosition();
|
|
||||||
cur.clearSelection();
|
|
||||||
cur.setKeepPositionOnInsert(true);
|
|
||||||
chatTextTable->cellAt(0, 0).firstCursorPosition().setBlockFormat(nameFormat);
|
|
||||||
chatTextTable->cellAt(0, 0).firstCursorPosition().insertHtml(msgAction->getName());
|
|
||||||
chatTextTable->cellAt(0, 2).firstCursorPosition().insertHtml(msgAction->getMessage());
|
|
||||||
chatTextTable->cellAt(0, 4).firstCursorPosition().setBlockFormat(dateFormat);
|
|
||||||
chatTextTable->cellAt(0, 4).firstCursorPosition().insertHtml(msgAction->getDate());
|
|
||||||
msgAction->setup(cur, this);
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (msgAction->isInteractive())
|
|
||||||
messages.append(msgAction);
|
|
||||||
|
|
||||||
empty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ChatAreaWidget::isEmpty()
|
|
||||||
{
|
|
||||||
return empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::onSliderRangeChanged()
|
|
||||||
{
|
|
||||||
QScrollBar* scroll = verticalScrollBar();
|
|
||||||
if (lockSliderToBottom)
|
|
||||||
scroll->setValue(scroll->maximum());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::checkSlider()
|
|
||||||
{
|
|
||||||
QScrollBar* scroll = verticalScrollBar();
|
|
||||||
lockSliderToBottom = scroll && scroll->value() == scroll->maximum();
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextTable *ChatAreaWidget::getMsgTable(QTextCursor::MoveOperation pos)
|
|
||||||
{
|
|
||||||
if (tableFrmt == nullptr)
|
|
||||||
{
|
|
||||||
tableFrmt = new QTextTableFormat();
|
|
||||||
tableFrmt->setCellSpacing(2);
|
|
||||||
tableFrmt->setBorderStyle(QTextFrameFormat::BorderStyle_None);
|
|
||||||
tableFrmt->setColumnWidthConstraints({QTextLength(QTextLength::FixedLength,nameWidth),
|
|
||||||
QTextLength(QTextLength::FixedLength,2),
|
|
||||||
QTextLength(QTextLength::PercentageLength,100),
|
|
||||||
QTextLength(QTextLength::FixedLength,2),
|
|
||||||
QTextLength(QTextLength::VariableLength,0)});
|
|
||||||
}
|
|
||||||
|
|
||||||
QTextCursor tc = textCursor();
|
|
||||||
tc.movePosition(pos);
|
|
||||||
|
|
||||||
QTextTable *chatTextTable = tc.insertTable(1, 5, *tableFrmt);
|
|
||||||
|
|
||||||
return chatTextTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::setNameColWidth(int w)
|
|
||||||
{
|
|
||||||
if (tableFrmt != nullptr)
|
|
||||||
{
|
|
||||||
delete tableFrmt;
|
|
||||||
tableFrmt = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
nameWidth = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::clearChatArea()
|
|
||||||
{
|
|
||||||
QList<ChatActionPtr> newMsgs;
|
|
||||||
for (ChatActionPtr message : messages)
|
|
||||||
{
|
|
||||||
if (message->isInteractive())
|
|
||||||
{
|
|
||||||
newMsgs.append(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messages.clear();
|
|
||||||
this->clear();
|
|
||||||
empty = true;
|
|
||||||
|
|
||||||
for (ChatActionPtr message : newMsgs)
|
|
||||||
{
|
|
||||||
insertMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAreaWidget::insertMessagesTop(QList<ChatActionPtr> &list)
|
|
||||||
{
|
|
||||||
std::reverse(list.begin(), list.end());
|
|
||||||
|
|
||||||
for (ChatActionPtr it : list)
|
|
||||||
{
|
|
||||||
insertMessage(it, QTextCursor::Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
empty = false;
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CHATAREAWIDGET_H
|
|
||||||
#define CHATAREAWIDGET_H
|
|
||||||
|
|
||||||
#include <QTextBrowser>
|
|
||||||
#include <QList>
|
|
||||||
#include <src/widget/tool/chatactions/chataction.h>
|
|
||||||
|
|
||||||
class QTextTable;
|
|
||||||
|
|
||||||
class ChatAreaWidget : public QTextBrowser
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ChatAreaWidget(QWidget *parent = 0);
|
|
||||||
virtual ~ChatAreaWidget();
|
|
||||||
void insertMessage(ChatActionPtr msgAction, QTextCursor::MoveOperation pos = QTextCursor::End);
|
|
||||||
void insertMessagesTop(QList<ChatActionPtr> &list);
|
|
||||||
|
|
||||||
int nameColWidth() {return nameWidth;}
|
|
||||||
void setNameColWidth(int w);
|
|
||||||
bool isEmpty();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void clearChatArea();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void onFileTranfertInterract(QString widgetName, QString buttonName);
|
|
||||||
void onClick();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void mouseReleaseEvent(QMouseEvent * event);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onAnchorClicked(const QUrl& url);
|
|
||||||
void onSliderRangeChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void checkSlider();
|
|
||||||
QTextTable* getMsgTable(QTextCursor::MoveOperation pos = QTextCursor::End);
|
|
||||||
|
|
||||||
QTextTableFormat* tableFrmt;
|
|
||||||
QList<ChatActionPtr> messages;
|
|
||||||
bool lockSliderToBottom;
|
|
||||||
int sliderPosition;
|
|
||||||
int nameWidth;
|
|
||||||
QTextBlockFormat nameFormat, dateFormat;
|
|
||||||
bool empty;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // CHATAREAWIDGET_H
|
|
|
@ -81,7 +81,7 @@ EmoticonsWidget::EmoticonsWidget(QWidget *parent) :
|
||||||
for (const QStringList& set : emoticons)
|
for (const QStringList& set : emoticons)
|
||||||
{
|
{
|
||||||
QPushButton* button = new QPushButton;
|
QPushButton* button = new QPushButton;
|
||||||
button->setIcon(SmileyPack::getInstance().getAsIcon(set[0]));
|
button->setIcon(SmileyPack::getInstance().getAsIcon(set[0]).pixmap(QSize(18,18)));
|
||||||
button->setToolTip(set.join(" "));
|
button->setToolTip(set.join(" "));
|
||||||
button->setProperty("sequence", set[0]);
|
button->setProperty("sequence", set[0]);
|
||||||
button->setCursor(Qt::PointingHandCursor);
|
button->setCursor(Qt::PointingHandCursor);
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
#include "chatform.h"
|
#include "chatform.h"
|
||||||
#include "src/core.h"
|
#include "src/core.h"
|
||||||
#include "src/friend.h"
|
#include "src/friend.h"
|
||||||
#include "src/filetransferinstance.h"
|
|
||||||
#include "src/historykeeper.h"
|
#include "src/historykeeper.h"
|
||||||
#include "src/misc/style.h"
|
#include "src/misc/style.h"
|
||||||
#include "src/misc/settings.h"
|
#include "src/misc/settings.h"
|
||||||
|
@ -34,13 +33,16 @@
|
||||||
#include "src/widget/callconfirmwidget.h"
|
#include "src/widget/callconfirmwidget.h"
|
||||||
#include "src/widget/friendwidget.h"
|
#include "src/widget/friendwidget.h"
|
||||||
#include "src/widget/netcamview.h"
|
#include "src/widget/netcamview.h"
|
||||||
#include "src/widget/chatareawidget.h"
|
|
||||||
#include "src/widget/form/loadhistorydialog.h"
|
#include "src/widget/form/loadhistorydialog.h"
|
||||||
#include "src/widget/tool/chattextedit.h"
|
#include "src/widget/tool/chattextedit.h"
|
||||||
#include "src/widget/tool/chatactions/filetransferaction.h"
|
|
||||||
#include "src/widget/widget.h"
|
#include "src/widget/widget.h"
|
||||||
#include "src/widget/maskablepixmapwidget.h"
|
#include "src/widget/maskablepixmapwidget.h"
|
||||||
#include "src/widget/croppinglabel.h"
|
#include "src/widget/croppinglabel.h"
|
||||||
|
#include "src/chatlog/chatmessage.h"
|
||||||
|
#include "src/chatlog/content/filetransferwidget.h"
|
||||||
|
#include "src/chatlog/chatlinecontentproxy.h"
|
||||||
|
#include "src/chatlog/content/text.h"
|
||||||
|
#include "src/chatlog/chatlog.h"
|
||||||
#include "src/offlinemsgengine.h"
|
#include "src/offlinemsgengine.h"
|
||||||
|
|
||||||
ChatForm::ChatForm(Friend* chatFriend)
|
ChatForm::ChatForm(Friend* chatFriend)
|
||||||
|
@ -57,28 +59,21 @@ ChatForm::ChatForm(Friend* chatFriend)
|
||||||
statusMessageLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize());
|
statusMessageLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize());
|
||||||
|
|
||||||
callConfirm = nullptr;
|
callConfirm = nullptr;
|
||||||
|
|
||||||
offlineEngine = new OfflineMsgEngine(f);
|
offlineEngine = new OfflineMsgEngine(f);
|
||||||
|
|
||||||
isTypingLabel = new QLabel();
|
|
||||||
QFont font = isTypingLabel->font();
|
|
||||||
font.setItalic(true);
|
|
||||||
font.setPixelSize(8);
|
|
||||||
isTypingLabel->setFont(font);
|
|
||||||
typingTimer.setSingleShot(true);
|
typingTimer.setSingleShot(true);
|
||||||
|
|
||||||
QVBoxLayout* mainLayout = dynamic_cast<QVBoxLayout*>(layout());
|
|
||||||
mainLayout->insertWidget(1, isTypingLabel);
|
|
||||||
|
|
||||||
netcam = new NetCamView();
|
netcam = new NetCamView();
|
||||||
callDurationTimer = nullptr;
|
callDurationTimer = nullptr;
|
||||||
disableCallButtonsTimer = nullptr;
|
disableCallButtonsTimer = nullptr;
|
||||||
|
|
||||||
|
chatWidget->setTypingNotification(ChatMessage::createTypingNotification());
|
||||||
|
|
||||||
headTextLayout->addWidget(statusMessageLabel);
|
headTextLayout->addWidget(statusMessageLabel);
|
||||||
headTextLayout->addStretch();
|
headTextLayout->addStretch();
|
||||||
callDuration = new QLabel();
|
callDuration = new QLabel();
|
||||||
headTextLayout->addWidget(callDuration, 1, Qt::AlignCenter);
|
headTextLayout->addWidget(callDuration, 1, Qt::AlignCenter);
|
||||||
callDuration->hide();
|
callDuration->hide();
|
||||||
|
|
||||||
menu.addAction(tr("Load chat history..."), this, SLOT(onLoadHistory()));
|
menu.addAction(tr("Load chat history..."), this, SLOT(onLoadHistory()));
|
||||||
|
|
||||||
|
@ -91,12 +86,12 @@ ChatForm::ChatForm(Friend* chatFriend)
|
||||||
connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged);
|
connect(msgEdit, &ChatTextEdit::textChanged, this, &ChatForm::onTextEditChanged);
|
||||||
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
|
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
|
||||||
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
|
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
|
||||||
connect(chatWidget, &ChatAreaWidget::onFileTranfertInterract, this, &ChatForm::onFileTansBtnClicked);
|
|
||||||
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
|
connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed);
|
||||||
connect(this, SIGNAL(chatAreaCleared()), getOfflineMsgEngine(), SLOT(removeAllReciepts()));
|
connect(this, SIGNAL(chatAreaCleared()), getOfflineMsgEngine(), SLOT(removeAllReciepts()));
|
||||||
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig)
|
|
||||||
{if (text != orig) emit aliasChanged(text);} );
|
|
||||||
connect(&typingTimer, &QTimer::timeout, this, [=]{Core::getInstance()->sendTyping(f->getFriendID(), false);});
|
connect(&typingTimer, &QTimer::timeout, this, [=]{Core::getInstance()->sendTyping(f->getFriendID(), false);});
|
||||||
|
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) {
|
||||||
|
if (text != orig) emit aliasChanged(text);
|
||||||
|
} );
|
||||||
|
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +134,7 @@ void ChatForm::onSendTriggered()
|
||||||
int id = HistoryKeeper::getInstance()->addChatEntry(f->getToxID().publicKey, qt_msg_hist,
|
int id = HistoryKeeper::getInstance()->addChatEntry(f->getToxID().publicKey, qt_msg_hist,
|
||||||
Core::getInstance()->getSelfId().publicKey, timestamp, status);
|
Core::getInstance()->getSelfId().publicKey, timestamp, status);
|
||||||
|
|
||||||
MessageActionPtr ma = addSelfMessage(qt_msg, isAction, timestamp, false);
|
ChatMessage::Ptr ma = addSelfMessage(msg, isAction, timestamp, false);
|
||||||
|
|
||||||
int rec;
|
int rec;
|
||||||
if (isAction)
|
if (isAction)
|
||||||
|
@ -209,17 +204,6 @@ void ChatForm::startFileSend(ToxFile file)
|
||||||
if (file.friendId != f->getFriendID())
|
if (file.friendId != f->getFriendID())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileTransferInstance* fileTrans = new FileTransferInstance(file);
|
|
||||||
ftransWidgets.insert(fileTrans->getId(), fileTrans);
|
|
||||||
|
|
||||||
connect(Core::getInstance(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo);
|
|
||||||
connect(Core::getInstance(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled);
|
|
||||||
connect(Core::getInstance(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished);
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferAccepted(ToxFile)), fileTrans, SLOT(onFileTransferAccepted(ToxFile)));
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferPaused(int,int,ToxFile::FileDirection)), fileTrans, SLOT(onFileTransferPaused(int,int,ToxFile::FileDirection)));
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferRemotePausedUnpaused(ToxFile,bool)), fileTrans, SLOT(onFileTransferRemotePausedUnpaused(ToxFile,bool)));
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferBrokenUnbroken(ToxFile, bool)), fileTrans, SLOT(onFileTransferBrokenUnbroken(ToxFile, bool)));
|
|
||||||
|
|
||||||
QString name;
|
QString name;
|
||||||
if (!previousId.isMine())
|
if (!previousId.isMine())
|
||||||
{
|
{
|
||||||
|
@ -228,8 +212,7 @@ void ChatForm::startFileSend(ToxFile file)
|
||||||
previousId = core->getSelfId();
|
previousId = core->getSelfId();
|
||||||
}
|
}
|
||||||
|
|
||||||
chatWidget->insertMessage(ChatActionPtr(new FileTransferAction(fileTrans, getElidedName(name),
|
insertChatMessage(ChatMessage::createFileTransferMessage(name, file, true, QDateTime::currentDateTime()));
|
||||||
QTime::currentTime().toString("hh:mm"), true)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::onFileRecvRequest(ToxFile file)
|
void ChatForm::onFileRecvRequest(ToxFile file)
|
||||||
|
@ -237,17 +220,6 @@ void ChatForm::onFileRecvRequest(ToxFile file)
|
||||||
if (file.friendId != f->getFriendID())
|
if (file.friendId != f->getFriendID())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileTransferInstance* fileTrans = new FileTransferInstance(file);
|
|
||||||
ftransWidgets.insert(fileTrans->getId(), fileTrans);
|
|
||||||
|
|
||||||
connect(Core::getInstance(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo);
|
|
||||||
connect(Core::getInstance(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled);
|
|
||||||
connect(Core::getInstance(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished);
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferAccepted(ToxFile)), fileTrans, SLOT(onFileTransferAccepted(ToxFile)));
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferPaused(int,int,ToxFile::FileDirection)), fileTrans, SLOT(onFileTransferPaused(int,int,ToxFile::FileDirection)));
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferRemotePausedUnpaused(ToxFile,bool)), fileTrans, SLOT(onFileTransferRemotePausedUnpaused(ToxFile,bool)));
|
|
||||||
connect(Core::getInstance(), SIGNAL(fileTransferBrokenUnbroken(ToxFile, bool)), fileTrans, SLOT(onFileTransferBrokenUnbroken(ToxFile, bool)));
|
|
||||||
|
|
||||||
Widget* w = Widget::getInstance();
|
Widget* w = Widget::getInstance();
|
||||||
if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow())
|
if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow())
|
||||||
{
|
{
|
||||||
|
@ -264,13 +236,21 @@ void ChatForm::onFileRecvRequest(ToxFile file)
|
||||||
previousId = friendId;
|
previousId = friendId;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString dateStr = QTime::currentTime().toString(Settings::getInstance().getTimestampFormat());
|
ChatMessage::Ptr msg = ChatMessage::createFileTransferMessage(name, file, false, QDateTime::currentDateTime());
|
||||||
FileTransferAction *fa = new FileTransferAction(fileTrans, getElidedName(name), dateStr, false);
|
insertChatMessage(msg);
|
||||||
chatWidget->insertMessage(ChatActionPtr(fa));
|
|
||||||
|
|
||||||
if (!Settings::getInstance().getAutoAcceptDir(f->getToxID()).isEmpty()
|
if (!Settings::getInstance().getAutoAcceptDir(f->getToxID()).isEmpty()
|
||||||
|| Settings::getInstance().getAutoSaveEnabled())
|
|| Settings::getInstance().getAutoSaveEnabled())
|
||||||
fileTrans->pressFromHtml("btnB");
|
{
|
||||||
|
ChatLineContentProxy* proxy = dynamic_cast<ChatLineContentProxy*>(msg->getContent(1));
|
||||||
|
if(proxy)
|
||||||
|
{
|
||||||
|
FileTransferWidget* tfWidget = dynamic_cast<FileTransferWidget*>(proxy->getWidget());
|
||||||
|
|
||||||
|
if(tfWidget)
|
||||||
|
tfWidget->autoAcceptTransfer(Settings::getInstance().getAutoAcceptDir(f->getToxID()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
|
void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
|
||||||
|
@ -310,7 +290,7 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
|
||||||
callButton->style()->polish(callButton);
|
callButton->style()->polish(callButton);
|
||||||
videoButton->style()->polish(videoButton);
|
videoButton->style()->polish(videoButton);
|
||||||
|
|
||||||
addSystemInfoMessage(tr("%1 is calling").arg(f->getDisplayedName()), "white", QDateTime::currentDateTime());
|
insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime()));
|
||||||
|
|
||||||
Widget* w = Widget::getInstance();
|
Widget* w = Widget::getInstance();
|
||||||
if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow())
|
if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow())
|
||||||
|
@ -333,7 +313,7 @@ void ChatForm::onAvStart(int FriendId, int CallId, bool video)
|
||||||
callId = CallId;
|
callId = CallId;
|
||||||
callButton->disconnect();
|
callButton->disconnect();
|
||||||
videoButton->disconnect();
|
videoButton->disconnect();
|
||||||
|
|
||||||
if (video)
|
if (video)
|
||||||
{
|
{
|
||||||
callButton->setObjectName("grey");
|
callButton->setObjectName("grey");
|
||||||
|
@ -372,7 +352,7 @@ void ChatForm::onAvCancel(int FriendId, int)
|
||||||
|
|
||||||
netcam->hide();
|
netcam->hide();
|
||||||
|
|
||||||
addSystemInfoMessage(tr("%1 stopped calling").arg(f->getDisplayedName()), "white", QDateTime::currentDateTime());
|
addSystemInfoMessage(tr("%1 stopped calling").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::onAvEnd(int FriendId, int)
|
void ChatForm::onAvEnd(int FriendId, int)
|
||||||
|
@ -421,7 +401,7 @@ void ChatForm::onAvRinging(int FriendId, int CallId, bool video)
|
||||||
this, SLOT(onCancelCallTriggered()));
|
this, SLOT(onCancelCallTriggered()));
|
||||||
}
|
}
|
||||||
|
|
||||||
addSystemInfoMessage(tr("Calling to %1").arg(f->getDisplayedName()), "white", QDateTime::currentDateTime());
|
addSystemInfoMessage(tr("Calling to %1").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::onAvStarting(int FriendId, int CallId, bool video)
|
void ChatForm::onAvStarting(int FriendId, int CallId, bool video)
|
||||||
|
@ -468,7 +448,7 @@ void ChatForm::onAvEnding(int FriendId, int)
|
||||||
enableCallButtons();
|
enableCallButtons();
|
||||||
|
|
||||||
netcam->hide();
|
netcam->hide();
|
||||||
|
|
||||||
stopCounter();
|
stopCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +494,7 @@ void ChatForm::onAvRejected(int FriendId, int)
|
||||||
|
|
||||||
enableCallButtons();
|
enableCallButtons();
|
||||||
|
|
||||||
addSystemInfoMessage(tr("Call rejected"), "white", QDateTime::currentDateTime());
|
insertChatMessage(ChatMessage::createChatInfoMessage(tr("Call rejected"), ChatMessage::INFO, QDateTime::currentDateTime()));
|
||||||
|
|
||||||
netcam->hide();
|
netcam->hide();
|
||||||
}
|
}
|
||||||
|
@ -713,24 +693,12 @@ void ChatForm::onVolMuteToggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ChatForm::onFileTansBtnClicked(QString widgetName, QString buttonName)
|
|
||||||
{
|
|
||||||
uint id = widgetName.toUInt();
|
|
||||||
|
|
||||||
auto it = ftransWidgets.find(id);
|
|
||||||
if (it != ftransWidgets.end())
|
|
||||||
it.value()->pressFromHtml(buttonName);
|
|
||||||
else
|
|
||||||
qDebug() << "no filetransferwidget: " << id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatForm::onFileSendFailed(int FriendId, const QString &fname)
|
void ChatForm::onFileSendFailed(int FriendId, const QString &fname)
|
||||||
{
|
{
|
||||||
if (FriendId != f->getFriendID())
|
if (FriendId != f->getFriendID())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fname), "red", QDateTime::currentDateTime());
|
addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fname), ChatMessage::ERROR, QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic)
|
void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic)
|
||||||
|
@ -790,22 +758,23 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered)
|
||||||
if (since > now)
|
if (since > now)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (earliestMessage)
|
if (!earliestMessage.isNull())
|
||||||
{
|
{
|
||||||
if (*earliestMessage < since)
|
if (earliestMessage < since)
|
||||||
return;
|
return;
|
||||||
if (*earliestMessage < now)
|
if (earliestMessage < now)
|
||||||
{
|
{
|
||||||
now = *earliestMessage;
|
now = earliestMessage;
|
||||||
now = now.addMSecs(-1);
|
now = now.addMSecs(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msgs = HistoryKeeper::getInstance()->getChatHistory(HistoryKeeper::ctSingle, f->getToxID().publicKey, since, now);
|
auto msgs = HistoryKeeper::getInstance()->getChatHistory(HistoryKeeper::ctSingle, f->getToxID().publicKey, since, now);
|
||||||
|
|
||||||
ToxID storedPrevId;
|
ToxID storedPrevId = previousId;
|
||||||
std::swap(storedPrevId, previousId);
|
ToxID prevId;
|
||||||
QList<ChatActionPtr> historyMessages;
|
|
||||||
|
QList<ChatLine::Ptr> historyMessages;
|
||||||
|
|
||||||
QDate lastDate(1,0,0);
|
QDate lastDate(1,0,0);
|
||||||
for (const auto &it : msgs)
|
for (const auto &it : msgs)
|
||||||
|
@ -816,36 +785,51 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered)
|
||||||
if (msgDate > lastDate)
|
if (msgDate > lastDate)
|
||||||
{
|
{
|
||||||
lastDate = msgDate;
|
lastDate = msgDate;
|
||||||
historyMessages.append(genSystemInfoAction(msgDate.toString(),"",QDateTime()));
|
historyMessages.append(ChatMessage::createChatInfoMessage(msgDate.toString(), ChatMessage::INFO, QDateTime::currentDateTime()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show each messages
|
// Show each messages
|
||||||
ToxID msgSender = ToxID::fromString(it.sender);
|
ToxID authorId = ToxID::fromString(it.sender);
|
||||||
MessageActionPtr ca = genMessageActionAction(msgSender, it.message, false, msgDateTime);
|
QString authorStr = authorId.isMine() ? Core::getInstance()->getUsername() : resolveToxID(authorId);
|
||||||
if (it.isSent || !msgSender.isMine())
|
bool isAction = it.message.startsWith("/me ");
|
||||||
|
|
||||||
|
ChatMessage::Ptr msg = ChatMessage::createChatMessage(authorStr,
|
||||||
|
isAction ? it.message.right(it.message.length() - 4) : it.message,
|
||||||
|
isAction, false,
|
||||||
|
authorId.isMine(),
|
||||||
|
QDateTime());
|
||||||
|
|
||||||
|
if(!isAction && prevId == authorId)
|
||||||
|
msg->hideSender();
|
||||||
|
|
||||||
|
prevId = authorId;
|
||||||
|
|
||||||
|
if (it.isSent || !authorId.isMine())
|
||||||
|
{
|
||||||
|
msg->markAsSent(msgDateTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
ca->markAsSent();
|
|
||||||
} else {
|
|
||||||
if (processUndelivered)
|
if (processUndelivered)
|
||||||
{
|
{
|
||||||
int rec;
|
int rec;
|
||||||
if (ca->isAction())
|
if (!isAction)
|
||||||
rec = Core::getInstance()->sendAction(f->getFriendID(), ca->getRawMessage());
|
rec = Core::getInstance()->sendMessage(f->getFriendID(), msg->toString());
|
||||||
else
|
else
|
||||||
rec = Core::getInstance()->sendMessage(f->getFriendID(), ca->getRawMessage());
|
rec = Core::getInstance()->sendAction(f->getFriendID(), msg->toString());
|
||||||
getOfflineMsgEngine()->registerReceipt(rec, it.id, ca);
|
|
||||||
|
getOfflineMsgEngine()->registerReceipt(rec, it.id, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
historyMessages.append(ca);
|
historyMessages.append(msg);
|
||||||
}
|
}
|
||||||
std::swap(storedPrevId, previousId);
|
|
||||||
|
|
||||||
|
previousId = storedPrevId;
|
||||||
int savedSliderPos = chatWidget->verticalScrollBar()->maximum() - chatWidget->verticalScrollBar()->value();
|
int savedSliderPos = chatWidget->verticalScrollBar()->maximum() - chatWidget->verticalScrollBar()->value();
|
||||||
|
|
||||||
if (earliestMessage != nullptr)
|
earliestMessage = since;
|
||||||
*earliestMessage = since;
|
|
||||||
|
|
||||||
chatWidget->insertMessagesTop(historyMessages);
|
chatWidget->insertChatlineOnTop(historyMessages);
|
||||||
|
|
||||||
savedSliderPos = chatWidget->verticalScrollBar()->maximum() - savedSliderPos;
|
savedSliderPos = chatWidget->verticalScrollBar()->maximum() - savedSliderPos;
|
||||||
chatWidget->verticalScrollBar()->setValue(savedSliderPos);
|
chatWidget->verticalScrollBar()->setValue(savedSliderPos);
|
||||||
|
@ -878,9 +862,8 @@ void ChatForm::stopCounter()
|
||||||
{
|
{
|
||||||
if (callDurationTimer)
|
if (callDurationTimer)
|
||||||
{
|
{
|
||||||
addSystemInfoMessage(tr("Call with %1 ended. %2").arg(f->getDisplayedName(),
|
addSystemInfoMessage(tr("Call with %1 ended. %2").arg(f->getDisplayedName(),secondsToDHMS(timeElapsed.elapsed()/1000)),
|
||||||
secondsToDHMS(timeElapsed.elapsed()/1000)),
|
ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
"white", QDateTime::currentDateTime());
|
|
||||||
callDurationTimer->stop();
|
callDurationTimer->stop();
|
||||||
callDuration->setText("");
|
callDuration->setText("");
|
||||||
callDuration->hide();
|
callDuration->hide();
|
||||||
|
@ -920,10 +903,12 @@ QString ChatForm::secondsToDHMS(quint32 duration)
|
||||||
|
|
||||||
void ChatForm::setFriendTyping(bool isTyping)
|
void ChatForm::setFriendTyping(bool isTyping)
|
||||||
{
|
{
|
||||||
if (isTyping)
|
chatWidget->setTypingNotificationVisible(isTyping);
|
||||||
isTypingLabel->setText(f->getDisplayedName() + " " + tr("is typing..."));
|
|
||||||
else
|
Text* text = dynamic_cast<Text*>(chatWidget->getTypingNotification()->getContent(1));
|
||||||
isTypingLabel->clear();
|
|
||||||
|
if(text)
|
||||||
|
text->setText("<div class=typing>" + QString("%1 is typing").arg(f->getDisplayedName()) + "</div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatForm::show(Ui::MainWindow &ui)
|
void ChatForm::show(Ui::MainWindow &ui)
|
||||||
|
|
|
@ -91,7 +91,6 @@ private slots:
|
||||||
void onHangupCallTriggered();
|
void onHangupCallTriggered();
|
||||||
void onCancelCallTriggered();
|
void onCancelCallTriggered();
|
||||||
void onRejectCallTriggered();
|
void onRejectCallTriggered();
|
||||||
void onFileTansBtnClicked(QString widgetName, QString buttonName);
|
|
||||||
void onFileSendFailed(int FriendId, const QString &fname);
|
void onFileSendFailed(int FriendId, const QString &fname);
|
||||||
void onLoadHistory();
|
void onLoadHistory();
|
||||||
void onUpdateTime();
|
void onUpdateTime();
|
||||||
|
@ -101,6 +100,7 @@ protected:
|
||||||
// drag & drop
|
// drag & drop
|
||||||
void dragEnterEvent(QDragEnterEvent* ev);
|
void dragEnterEvent(QDragEnterEvent* ev);
|
||||||
void dropEvent(QDropEvent* ev);
|
void dropEvent(QDropEvent* ev);
|
||||||
|
void registerReceipt(int receipt, int messageID, ChatMessage::Ptr msg);
|
||||||
virtual void hideEvent(QHideEvent* event);
|
virtual void hideEvent(QHideEvent* event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -113,7 +113,6 @@ private:
|
||||||
QTimer typingTimer;
|
QTimer typingTimer;
|
||||||
QTimer *disableCallButtonsTimer;
|
QTimer *disableCallButtonsTimer;
|
||||||
QElapsedTimer timeElapsed;
|
QElapsedTimer timeElapsed;
|
||||||
QLabel *isTypingLabel;
|
|
||||||
OfflineMsgEngine *offlineEngine;
|
OfflineMsgEngine *offlineEngine;
|
||||||
|
|
||||||
QHash<uint, FileTransferInstance*> ftransWidgets;
|
QHash<uint, FileTransferInstance*> ftransWidgets;
|
||||||
|
|
|
@ -16,18 +16,16 @@
|
||||||
|
|
||||||
#include "genericchatform.h"
|
#include "genericchatform.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
#include "src/misc/smileypack.h"
|
#include "src/misc/smileypack.h"
|
||||||
#include "src/widget/emoticonswidget.h"
|
#include "src/widget/emoticonswidget.h"
|
||||||
#include "src/misc/style.h"
|
#include "src/misc/style.h"
|
||||||
#include "src/widget/widget.h"
|
#include "src/widget/widget.h"
|
||||||
#include "src/misc/settings.h"
|
#include "src/misc/settings.h"
|
||||||
#include "src/widget/tool/chatactions/messageaction.h"
|
|
||||||
#include "src/widget/tool/chatactions/systemmessageaction.h"
|
|
||||||
#include "src/widget/tool/chatactions/actionaction.h"
|
|
||||||
#include "src/widget/tool/chatactions/alertaction.h"
|
|
||||||
#include "src/widget/chatareawidget.h"
|
|
||||||
#include "src/widget/tool/chattextedit.h"
|
#include "src/widget/tool/chattextedit.h"
|
||||||
#include "src/widget/maskablepixmapwidget.h"
|
#include "src/widget/maskablepixmapwidget.h"
|
||||||
#include "src/core.h"
|
#include "src/core.h"
|
||||||
|
@ -35,10 +33,11 @@
|
||||||
#include "src/group.h"
|
#include "src/group.h"
|
||||||
#include "src/friendlist.h"
|
#include "src/friendlist.h"
|
||||||
#include "src/friend.h"
|
#include "src/friend.h"
|
||||||
|
#include "src/chatlog/chatlog.h"
|
||||||
|
#include "src/chatlog/content/timestamp.h"
|
||||||
|
|
||||||
GenericChatForm::GenericChatForm(QWidget *parent) :
|
GenericChatForm::GenericChatForm(QWidget *parent)
|
||||||
QWidget(parent),
|
: QWidget(parent)
|
||||||
earliestMessage(nullptr)
|
|
||||||
, audioInputFlag(false)
|
, audioInputFlag(false)
|
||||||
, audioOutputFlag(false)
|
, audioOutputFlag(false)
|
||||||
{
|
{
|
||||||
|
@ -53,7 +52,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
|
||||||
|
|
||||||
avatar = new MaskablePixmapWidget(this, QSize(40,40), ":/img/avatar_mask.png");
|
avatar = new MaskablePixmapWidget(this, QSize(40,40), ":/img/avatar_mask.png");
|
||||||
QHBoxLayout *headLayout = new QHBoxLayout(),
|
QHBoxLayout *headLayout = new QHBoxLayout(),
|
||||||
*mainFootLayout = new QHBoxLayout();
|
*mainFootLayout = new QHBoxLayout();
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout(),
|
QVBoxLayout *mainLayout = new QVBoxLayout(),
|
||||||
*footButtonsSmall = new QVBoxLayout(),
|
*footButtonsSmall = new QVBoxLayout(),
|
||||||
|
@ -63,7 +62,8 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
|
||||||
|
|
||||||
headTextLayout = new QVBoxLayout();
|
headTextLayout = new QVBoxLayout();
|
||||||
|
|
||||||
chatWidget = new ChatAreaWidget();
|
chatWidget = new ChatLog(this);
|
||||||
|
chatWidget->setBusyNotification(ChatMessage::createBusyNotification());
|
||||||
|
|
||||||
msgEdit = new ChatTextEdit();
|
msgEdit = new ChatTextEdit();
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
|
||||||
//volButton->setFixedSize(25,20);
|
//volButton->setFixedSize(25,20);
|
||||||
volButton->setToolTip(tr("Toggle speakers volume: RED is OFF"));
|
volButton->setToolTip(tr("Toggle speakers volume: RED is OFF"));
|
||||||
micButton = new QPushButton();
|
micButton = new QPushButton();
|
||||||
// micButton->setFixedSize(25,20);
|
// micButton->setFixedSize(25,20);
|
||||||
micButton->setToolTip(tr("Toggle microphone: RED is OFF"));
|
micButton->setToolTip(tr("Toggle microphone: RED is OFF"));
|
||||||
|
|
||||||
footButtonsSmall->setSpacing(2);
|
footButtonsSmall->setSpacing(2);
|
||||||
|
@ -158,19 +158,17 @@ GenericChatForm::GenericChatForm(QWidget *parent) :
|
||||||
callButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
|
callButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
|
||||||
videoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
|
videoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
|
||||||
|
|
||||||
menu.addAction(tr("Save chat log"), this, SLOT(onSaveLogClicked()));
|
menu.addActions(chatWidget->actions());
|
||||||
menu.addAction(tr("Clear displayed messages"), this, SLOT(clearChatArea(bool)));
|
menu.addSeparator();
|
||||||
|
menu.addAction(QIcon::fromTheme("document-save"), tr("Save chat log"), this, SLOT(onSaveLogClicked()));
|
||||||
|
menu.addAction(QIcon::fromTheme("edit-clear"), tr("Clear displayed messages"), this, SLOT(clearChatArea(bool)));
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
connect(emoteButton, SIGNAL(clicked()), this, SLOT(onEmoteButtonClicked()));
|
connect(emoteButton, &QPushButton::clicked, this, &GenericChatForm::onEmoteButtonClicked);
|
||||||
connect(chatWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
|
connect(chatWidget, &ChatLog::customContextMenuRequested, this, &GenericChatForm::onChatContextMenuRequested);
|
||||||
connect(chatWidget, SIGNAL(onClick()), this, SLOT(onChatWidgetClicked()));
|
|
||||||
|
|
||||||
chatWidget->document()->setDefaultStyleSheet(Style::getStylesheet(":ui/chatArea/innerStyle.css"));
|
|
||||||
chatWidget->setStyleSheet(Style::getStylesheet(":/ui/chatArea/chatArea.css"));
|
chatWidget->setStyleSheet(Style::getStylesheet(":/ui/chatArea/chatArea.css"));
|
||||||
headWidget->setStyleSheet(Style::getStylesheet(":/ui/chatArea/chatHead.css"));
|
headWidget->setStyleSheet(Style::getStylesheet(":/ui/chatArea/chatHead.css"));
|
||||||
|
|
||||||
ChatAction::setupFormat();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GenericChatForm::isEmpty()
|
bool GenericChatForm::isEmpty()
|
||||||
|
@ -178,6 +176,11 @@ bool GenericChatForm::isEmpty()
|
||||||
return chatWidget->isEmpty();
|
return chatWidget->isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatLog *GenericChatForm::getChatLog() const
|
||||||
|
{
|
||||||
|
return chatWidget;
|
||||||
|
}
|
||||||
|
|
||||||
void GenericChatForm::setName(const QString &newName)
|
void GenericChatForm::setName(const QString &newName)
|
||||||
{
|
{
|
||||||
nameLabel->setText(newName);
|
nameLabel->setText(newName);
|
||||||
|
@ -196,55 +199,52 @@ void GenericChatForm::onChatContextMenuRequested(QPoint pos)
|
||||||
{
|
{
|
||||||
QWidget* sender = (QWidget*)QObject::sender();
|
QWidget* sender = (QWidget*)QObject::sender();
|
||||||
pos = sender->mapToGlobal(pos);
|
pos = sender->mapToGlobal(pos);
|
||||||
|
|
||||||
menu.exec(pos);
|
menu.exec(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericChatForm::onSaveLogClicked()
|
ChatMessage::Ptr GenericChatForm::addMessage(const ToxID& author, const QString &message, bool isAction,
|
||||||
{
|
|
||||||
QString path = QFileDialog::getSaveFileName(0, tr("Save chat log"));
|
|
||||||
if (path.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QFile file(path);
|
|
||||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString log;
|
|
||||||
log = chatWidget->toPlainText();
|
|
||||||
|
|
||||||
file.write(log.toUtf8());
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageActionPtr GenericChatForm::addMessage(const ToxID& author, const QString &message, bool isAction,
|
|
||||||
const QDateTime &datetime, bool isSent)
|
const QDateTime &datetime, bool isSent)
|
||||||
{
|
{
|
||||||
MessageActionPtr ca = genMessageActionAction(author, message, isAction, datetime);
|
QString authorStr = author.isMine() ? Core::getInstance()->getUsername() : resolveToxID(author);
|
||||||
if (isSent)
|
|
||||||
ca->markAsSent();
|
ChatMessage::Ptr msg;
|
||||||
chatWidget->insertMessage(ca);
|
if(isAction)
|
||||||
return ca;
|
{
|
||||||
|
msg = ChatMessage::createChatMessage(authorStr, message, true, false, false);
|
||||||
|
previousId.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = ChatMessage::createChatMessage(authorStr, message, false, false, author.isMine());
|
||||||
|
if(author == previousId)
|
||||||
|
msg->hideSender();
|
||||||
|
|
||||||
|
previousId = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertChatMessage(msg);
|
||||||
|
|
||||||
|
if(isSent)
|
||||||
|
msg->markAsSent(datetime);
|
||||||
|
|
||||||
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageActionPtr GenericChatForm::addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent)
|
ChatMessage::Ptr GenericChatForm::addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent)
|
||||||
{
|
{
|
||||||
MessageActionPtr ca = genSelfActionAction(message, isAction, datetime);
|
return addMessage(Core::getInstance()->getSelfId(), message, isAction, datetime, isSent);
|
||||||
if (isSent)
|
|
||||||
ca->markAsSent();
|
|
||||||
chatWidget->insertMessage(ca);
|
|
||||||
return ca;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericChatForm::addAlertMessage(const ToxID &author, QString message, QDateTime datetime)
|
void GenericChatForm::addAlertMessage(const ToxID &author, QString message, QDateTime datetime)
|
||||||
{
|
{
|
||||||
QString authorStr = resolveToxID(author);
|
QString authorStr = resolveToxID(author);
|
||||||
if (authorStr.isEmpty())
|
ChatMessage::Ptr msg = ChatMessage::createChatMessage(authorStr, message, false, true, author.isMine(), datetime);
|
||||||
authorStr = author.publicKey;
|
insertChatMessage(msg);
|
||||||
|
|
||||||
|
if(author == previousId)
|
||||||
|
msg->hideSender();
|
||||||
|
|
||||||
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
|
|
||||||
MessageActionPtr ca = MessageActionPtr(new AlertAction(getElidedName(authorStr), message, date));
|
|
||||||
ca->markAsSent();
|
|
||||||
chatWidget->insertMessage(ca);
|
|
||||||
previousId = author;
|
previousId = author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,12 +265,6 @@ void GenericChatForm::onEmoteButtonClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericChatForm::onChatWidgetClicked()
|
|
||||||
{
|
|
||||||
if (!chatWidget->textCursor().hasSelection())
|
|
||||||
msgEdit->setFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericChatForm::onEmoteInsertRequested(QString str)
|
void GenericChatForm::onEmoteInsertRequested(QString str)
|
||||||
{
|
{
|
||||||
// insert the emoticon
|
// insert the emoticon
|
||||||
|
@ -281,125 +275,67 @@ void GenericChatForm::onEmoteInsertRequested(QString str)
|
||||||
msgEdit->setFocus(); // refocus so that we can continue typing
|
msgEdit->setFocus(); // refocus so that we can continue typing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GenericChatForm::onSaveLogClicked()
|
||||||
|
{
|
||||||
|
QString path = QFileDialog::getSaveFileName(0, tr("Save chat log"));
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString plainText;
|
||||||
|
auto lines = chatWidget->getLines();
|
||||||
|
for(ChatLine::Ptr l : lines)
|
||||||
|
{
|
||||||
|
Timestamp* rightCol = dynamic_cast<Timestamp*>(l->getContent(2));
|
||||||
|
ChatLineContent* middleCol = l->getContent(1);
|
||||||
|
ChatLineContent* leftCol = l->getContent(0);
|
||||||
|
|
||||||
|
QString timestamp = (!rightCol || rightCol->getTime().isNull()) ? tr("Not sent") : rightCol->getText();
|
||||||
|
QString nick = leftCol->getText();
|
||||||
|
QString msg = middleCol->getText();
|
||||||
|
|
||||||
|
plainText += QString("[%2] %1\n%3\n\n").arg(nick, timestamp, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(plainText.toUtf8());
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericChatForm::onCopyLogClicked()
|
||||||
|
{
|
||||||
|
chatWidget->copySelectedText();
|
||||||
|
}
|
||||||
|
|
||||||
void GenericChatForm::focusInput()
|
void GenericChatForm::focusInput()
|
||||||
{
|
{
|
||||||
msgEdit->setFocus();
|
msgEdit->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericChatForm::addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime)
|
void GenericChatForm::addSystemInfoMessage(const QString &message, ChatMessage::SystemMessageType type, const QDateTime &datetime)
|
||||||
{
|
{
|
||||||
ChatActionPtr ca = genSystemInfoAction(message, type, datetime);
|
previousId.clear();
|
||||||
chatWidget->insertMessage(ca);
|
insertChatMessage(ChatMessage::createChatInfoMessage(message, type, datetime));
|
||||||
}
|
|
||||||
|
|
||||||
QString GenericChatForm::getElidedName(const QString& name)
|
|
||||||
{
|
|
||||||
// update this whenever you change the font in innerStyle.css
|
|
||||||
QFontMetrics fm(Style::getFont(Style::BigBold));
|
|
||||||
|
|
||||||
return fm.elidedText(name, Qt::ElideRight, chatWidget->nameColWidth());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericChatForm::clearChatArea(bool notinform)
|
void GenericChatForm::clearChatArea(bool notinform)
|
||||||
{
|
{
|
||||||
chatWidget->clearChatArea();
|
chatWidget->clear();
|
||||||
previousId = ToxID();
|
previousId = ToxID();
|
||||||
|
|
||||||
if (!notinform)
|
if (!notinform)
|
||||||
addSystemInfoMessage(tr("Cleared"), "white", QDateTime::currentDateTime());
|
addSystemInfoMessage(tr("Cleared"), ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
|
|
||||||
if (earliestMessage)
|
earliestMessage = QDateTime(); //null
|
||||||
{
|
|
||||||
delete earliestMessage;
|
|
||||||
earliestMessage = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit chatAreaCleared();
|
emit chatAreaCleared();
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageActionPtr GenericChatForm::genMessageActionAction(const ToxID& author, QString message, bool isAction, const QDateTime &datetime)
|
void GenericChatForm::onSelectAllClicked()
|
||||||
{
|
{
|
||||||
if (earliestMessage == nullptr)
|
chatWidget->selectAll();
|
||||||
{
|
|
||||||
earliestMessage = new QDateTime(datetime);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Core* core = Core::getInstance();
|
|
||||||
|
|
||||||
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
|
|
||||||
bool isMe = (author == core->getSelfId());
|
|
||||||
QString authorStr;
|
|
||||||
if (isMe)
|
|
||||||
authorStr = core->getUsername();
|
|
||||||
else {
|
|
||||||
authorStr = resolveToxID(author);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authorStr.isEmpty()) // Fallback if we can't find a username
|
|
||||||
authorStr = author.toString();
|
|
||||||
|
|
||||||
if (!isAction && message.startsWith("/me "))
|
|
||||||
{ // always render actions regardless of what core thinks
|
|
||||||
isAction = true;
|
|
||||||
message = message.right(message.length()-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAction)
|
|
||||||
{
|
|
||||||
previousId = ToxID(); // next msg has a name regardless
|
|
||||||
return MessageActionPtr(new ActionAction (getElidedName(authorStr), message, date, isMe));
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageActionPtr res;
|
|
||||||
if (previousId == author)
|
|
||||||
res = MessageActionPtr(new MessageAction(QString(), message, date, isMe));
|
|
||||||
else
|
|
||||||
res = MessageActionPtr(new MessageAction(getElidedName(authorStr), message, date, isMe));
|
|
||||||
|
|
||||||
previousId = author;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageActionPtr GenericChatForm::genSelfActionAction(QString message, bool isAction, const QDateTime &datetime)
|
|
||||||
{
|
|
||||||
if (earliestMessage == nullptr)
|
|
||||||
{
|
|
||||||
earliestMessage = new QDateTime(datetime);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Core* core = Core::getInstance();
|
|
||||||
|
|
||||||
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
|
|
||||||
QString author = core->getUsername();;
|
|
||||||
|
|
||||||
if (!isAction && message.startsWith("/me "))
|
|
||||||
{ // always render actions regardless of what core thinks
|
|
||||||
isAction = true;
|
|
||||||
message = message.right(message.length()-4);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAction)
|
|
||||||
{
|
|
||||||
previousId = ToxID(); // next msg has a name regardless
|
|
||||||
return MessageActionPtr(new ActionAction (getElidedName(author), message, date, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageActionPtr res;
|
|
||||||
if (previousId.isMine())
|
|
||||||
res = MessageActionPtr(new MessageAction(QString(), message, date, true));
|
|
||||||
else
|
|
||||||
res = MessageActionPtr(new MessageAction(getElidedName(author), message, date, true));
|
|
||||||
|
|
||||||
previousId = Core::getInstance()->getSelfId();
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatActionPtr GenericChatForm::genSystemInfoAction(const QString &message, const QString &type, const QDateTime &datetime)
|
|
||||||
{
|
|
||||||
previousId = ToxID();
|
|
||||||
QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
|
|
||||||
|
|
||||||
return ChatActionPtr(new SystemMessageAction(message, type, date));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GenericChatForm::resolveToxID(const ToxID &id)
|
QString GenericChatForm::resolveToxID(const ToxID &id)
|
||||||
|
@ -419,3 +355,8 @@ QString GenericChatForm::resolveToxID(const ToxID &id)
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GenericChatForm::insertChatMessage(ChatMessage::Ptr msg)
|
||||||
|
{
|
||||||
|
chatWidget->insertChatlineAtBottom(std::dynamic_pointer_cast<ChatLine>(msg));
|
||||||
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include "src/widget/tool/chatactions/messageaction.h"
|
|
||||||
#include "src/corestructs.h"
|
#include "src/corestructs.h"
|
||||||
|
#include "src/chatlog/chatmessage.h"
|
||||||
|
|
||||||
// Spacing in px inserted when the author of the last message changes
|
// Spacing in px inserted when the author of the last message changes
|
||||||
#define AUTHOR_CHANGE_SPACING 5 // why the hell is this a thing? surely the different font is enough?
|
#define AUTHOR_CHANGE_SPACING 5 // why the hell is this a thing? surely the different font is enough?
|
||||||
|
@ -32,7 +32,7 @@ class QVBoxLayout;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class CroppingLabel;
|
class CroppingLabel;
|
||||||
class ChatTextEdit;
|
class ChatTextEdit;
|
||||||
class ChatAreaWidget;
|
class ChatLog;
|
||||||
class MaskablePixmapWidget;
|
class MaskablePixmapWidget;
|
||||||
struct ToxID;
|
struct ToxID;
|
||||||
|
|
||||||
|
@ -49,12 +49,15 @@ public:
|
||||||
virtual void setName(const QString &newName);
|
virtual void setName(const QString &newName);
|
||||||
virtual void show(Ui::MainWindow &ui);
|
virtual void show(Ui::MainWindow &ui);
|
||||||
|
|
||||||
MessageActionPtr addMessage(const ToxID& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent);
|
ChatMessage::Ptr addMessage(const ToxID& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent);
|
||||||
MessageActionPtr addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent);
|
ChatMessage::Ptr addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent);
|
||||||
void addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime);
|
|
||||||
|
void addSystemInfoMessage(const QString &message, ChatMessage::SystemMessageType type, const QDateTime &datetime);
|
||||||
void addAlertMessage(const ToxID& author, QString message, QDateTime datetime);
|
void addAlertMessage(const ToxID& author, QString message, QDateTime datetime);
|
||||||
bool isEmpty();
|
bool isEmpty();
|
||||||
|
|
||||||
|
ChatLog* getChatLog() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sendMessage(int, QString);
|
void sendMessage(int, QString);
|
||||||
void sendAction(int, QString);
|
void sendAction(int, QString);
|
||||||
|
@ -65,19 +68,16 @@ public slots:
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onChatContextMenuRequested(QPoint pos);
|
void onChatContextMenuRequested(QPoint pos);
|
||||||
void onSaveLogClicked();
|
|
||||||
void onEmoteButtonClicked();
|
void onEmoteButtonClicked();
|
||||||
void onEmoteInsertRequested(QString str);
|
void onEmoteInsertRequested(QString str);
|
||||||
|
void onSaveLogClicked();
|
||||||
|
void onCopyLogClicked();
|
||||||
void clearChatArea(bool);
|
void clearChatArea(bool);
|
||||||
void onChatWidgetClicked();
|
void onSelectAllClicked();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString getElidedName(const QString& name);
|
|
||||||
MessageActionPtr genMessageActionAction(const ToxID& author, QString message, bool isAction, const QDateTime &datetime);
|
|
||||||
MessageActionPtr genSelfActionAction(QString message, bool isAction, const QDateTime &datetime);
|
|
||||||
ChatActionPtr genSystemInfoAction(const QString &message, const QString &type, const QDateTime &datetime);
|
|
||||||
|
|
||||||
QString resolveToxID(const ToxID &id);
|
QString resolveToxID(const ToxID &id);
|
||||||
|
void insertChatMessage(ChatMessage::Ptr msg);
|
||||||
|
|
||||||
ToxID previousId;
|
ToxID previousId;
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
|
@ -89,8 +89,8 @@ protected:
|
||||||
QVBoxLayout *headTextLayout;
|
QVBoxLayout *headTextLayout;
|
||||||
ChatTextEdit *msgEdit;
|
ChatTextEdit *msgEdit;
|
||||||
QPushButton *sendButton;
|
QPushButton *sendButton;
|
||||||
ChatAreaWidget *chatWidget;
|
ChatLog *chatWidget;
|
||||||
QDateTime *earliestMessage;
|
QDateTime earliestMessage;
|
||||||
bool audioInputFlag;
|
bool audioInputFlag;
|
||||||
bool audioOutputFlag;
|
bool audioOutputFlag;
|
||||||
};
|
};
|
||||||
|
|
|
@ -319,14 +319,14 @@ void GeneralForm::reloadSmiles()
|
||||||
for (int i = 0; i < emoticons.size(); i++)
|
for (int i = 0; i < emoticons.size(); i++)
|
||||||
smiles.push_front(emoticons.at(i).first());
|
smiles.push_front(emoticons.at(i).first());
|
||||||
|
|
||||||
int pixSize = 30;
|
const QSize size(18,18);
|
||||||
bodyUI->smile1->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[0]).pixmap(pixSize, pixSize));
|
bodyUI->smile1->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[0]).pixmap(size));
|
||||||
bodyUI->smile2->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[1]).pixmap(pixSize, pixSize));
|
bodyUI->smile2->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[1]).pixmap(size));
|
||||||
bodyUI->smile3->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[2]).pixmap(pixSize, pixSize));
|
bodyUI->smile3->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[2]).pixmap(size));
|
||||||
bodyUI->smile4->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[3]).pixmap(pixSize, pixSize));
|
bodyUI->smile4->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[3]).pixmap(size));
|
||||||
bodyUI->smile5->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[4]).pixmap(pixSize, pixSize));
|
bodyUI->smile5->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[4]).pixmap(size));
|
||||||
|
|
||||||
bodyUI->smile1->setToolTip(smiles[0]);
|
bodyUI->smile1->setToolTip(smiles[0]);
|
||||||
bodyUI->smile2->setToolTip(smiles[1]);
|
bodyUI->smile2->setToolTip(smiles[1]);
|
||||||
bodyUI->smile3->setToolTip(smiles[2]);
|
bodyUI->smile3->setToolTip(smiles[2]);
|
||||||
bodyUI->smile4->setToolTip(smiles[3]);
|
bodyUI->smile4->setToolTip(smiles[3]);
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
FriendWidget::FriendWidget(int FriendId, QString id)
|
FriendWidget::FriendWidget(int FriendId, QString id)
|
||||||
: friendId(FriendId)
|
: friendId(FriendId)
|
||||||
, isDefaultAvatar{true}
|
, isDefaultAvatar{true}
|
||||||
|
, historyLoaded{false}
|
||||||
{
|
{
|
||||||
avatar->setPixmap(QPixmap(":img/contact.png"), Qt::transparent);
|
avatar->setPixmap(QPixmap(":img/contact.png"), Qt::transparent);
|
||||||
statusPic.setPixmap(QPixmap(":img/status/dot_away.png"));
|
statusPic.setPixmap(QPixmap(":img/status/dot_away.png"));
|
||||||
|
@ -126,6 +127,16 @@ void FriendWidget::setAsActiveChatroom()
|
||||||
|
|
||||||
if (isDefaultAvatar)
|
if (isDefaultAvatar)
|
||||||
avatar->setPixmap(QPixmap(":img/contact_dark.png"), Qt::transparent);
|
avatar->setPixmap(QPixmap(":img/contact_dark.png"), Qt::transparent);
|
||||||
|
|
||||||
|
if(!historyLoaded)
|
||||||
|
{
|
||||||
|
Friend* f = FriendList::findFriend(friendId);
|
||||||
|
if (Settings::getInstance().getEnableLogging())
|
||||||
|
{
|
||||||
|
f->getChatForm()->loadHistory(QDateTime::currentDateTime().addDays(-7), true);
|
||||||
|
historyLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FriendWidget::setAsInactiveChatroom()
|
void FriendWidget::setAsInactiveChatroom()
|
||||||
|
|
|
@ -54,6 +54,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
int friendId;
|
int friendId;
|
||||||
bool isDefaultAvatar;
|
bool isDefaultAvatar;
|
||||||
|
bool historyLoaded;
|
||||||
QPoint dragStartPos;
|
QPoint dragStartPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "chataction.h"
|
|
||||||
#include <QStringList>
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QTextTable>
|
|
||||||
#include <QScrollBar>
|
|
||||||
#include <QTextEdit>
|
|
||||||
|
|
||||||
QTextBlockFormat ChatAction::nameFormat, ChatAction::dateFormat;
|
|
||||||
|
|
||||||
QString ChatAction::toHtmlChars(const QString &str)
|
|
||||||
{
|
|
||||||
static QList<QPair<QString, QString>> replaceList = {{"&","&"}, {">",">"}, {"<","<"}};
|
|
||||||
QString res = str;
|
|
||||||
|
|
||||||
for (auto &it : replaceList)
|
|
||||||
res = res.replace(it.first,it.second);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatAction::QImage2base64(const QImage &img)
|
|
||||||
{
|
|
||||||
QByteArray ba;
|
|
||||||
QBuffer buffer(&ba);
|
|
||||||
buffer.open(QIODevice::WriteOnly);
|
|
||||||
img.save(&buffer, "PNG"); // writes image into ba in PNG format
|
|
||||||
return ba.toBase64();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatAction::getName()
|
|
||||||
{
|
|
||||||
if (isMe)
|
|
||||||
return QString("<div class=%1>%2</div>").arg("name_me").arg(toHtmlChars(name));
|
|
||||||
else
|
|
||||||
return QString("<div class=%1>%2</div>").arg("name").arg(toHtmlChars(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatAction::getDate()
|
|
||||||
{
|
|
||||||
if (isMe)
|
|
||||||
return QString("<div class=date_me>" + toHtmlChars(date) + "</div>");
|
|
||||||
else
|
|
||||||
return QString("<div class=date>" + toHtmlChars(date) + "</div>");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAction::assignPlace(QTextTable *position, QTextEdit *te)
|
|
||||||
{
|
|
||||||
textTable = position;
|
|
||||||
cur = position->cellAt(0, 2).firstCursorPosition();
|
|
||||||
cur.clearSelection();
|
|
||||||
cur.setKeepPositionOnInsert(true);
|
|
||||||
textEdit = te;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAction::dispaly()
|
|
||||||
{
|
|
||||||
textTable->cellAt(0, 0).firstCursorPosition().setBlockFormat(nameFormat);
|
|
||||||
textTable->cellAt(0, 0).firstCursorPosition().insertHtml(getName());
|
|
||||||
textTable->cellAt(0, 2).firstCursorPosition().insertHtml(getMessage());
|
|
||||||
textTable->cellAt(0, 4).firstCursorPosition().setBlockFormat(dateFormat);
|
|
||||||
textTable->cellAt(0, 4).firstCursorPosition().insertHtml(getDate());
|
|
||||||
|
|
||||||
cur.setKeepPositionOnInsert(true);
|
|
||||||
int end=cur.selectionEnd();
|
|
||||||
cur.setPosition(cur.position());
|
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
featureUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAction::setupFormat()
|
|
||||||
{
|
|
||||||
nameFormat.setAlignment(Qt::AlignRight);
|
|
||||||
nameFormat.setNonBreakableLines(true);
|
|
||||||
dateFormat.setAlignment(Qt::AlignLeft);
|
|
||||||
dateFormat.setNonBreakableLines(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatAction::updateContent()
|
|
||||||
{
|
|
||||||
if (cur.isNull() || !textEdit)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int vSliderVal = textEdit->verticalScrollBar()->value();
|
|
||||||
|
|
||||||
// update content
|
|
||||||
int pos = cur.selectionStart();
|
|
||||||
cur.removeSelectedText();
|
|
||||||
cur.setKeepPositionOnInsert(false);
|
|
||||||
cur.insertHtml(getMessage());
|
|
||||||
cur.setKeepPositionOnInsert(true);
|
|
||||||
int end = cur.position();
|
|
||||||
cur.setPosition(pos);
|
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
// restore old slider value
|
|
||||||
textEdit->verticalScrollBar()->setValue(vSliderVal);
|
|
||||||
|
|
||||||
featureUpdate();
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CHATACTION_H
|
|
||||||
#define CHATACTION_H
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <QTextCursor>
|
|
||||||
#include <QSharedPointer>
|
|
||||||
|
|
||||||
class FileTransferInstance;
|
|
||||||
class QTextEdit;
|
|
||||||
class QTextTable;
|
|
||||||
|
|
||||||
class ChatAction : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ChatAction(const bool &me, const QString &author, const QString &date)
|
|
||||||
: isMe{me}, name{author}, date{date}, textTable{nullptr}, textEdit{nullptr} {;}
|
|
||||||
virtual ~ChatAction(){;}
|
|
||||||
|
|
||||||
void assignPlace(QTextTable *position, QTextEdit* te);
|
|
||||||
virtual void dispaly();
|
|
||||||
virtual bool isInteractive(){return false;}
|
|
||||||
virtual void featureUpdate() {;}
|
|
||||||
|
|
||||||
static void setupFormat();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void updateContent();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual QString getName();
|
|
||||||
virtual QString getMessage() = 0;
|
|
||||||
virtual QString getDate();
|
|
||||||
|
|
||||||
QString toHtmlChars(const QString &str);
|
|
||||||
QString QImage2base64(const QImage &img);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool isMe;
|
|
||||||
QString name, date;
|
|
||||||
|
|
||||||
QTextTable *textTable;
|
|
||||||
QTextEdit *textEdit;
|
|
||||||
QTextCursor cur;
|
|
||||||
|
|
||||||
static QTextBlockFormat nameFormat, dateFormat;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef QSharedPointer<ChatAction> ChatActionPtr;
|
|
||||||
|
|
||||||
#endif // CHATACTION_H
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "filetransferaction.h"
|
|
||||||
#include "src/filetransferinstance.h"
|
|
||||||
|
|
||||||
#include <QTextEdit>
|
|
||||||
#include <QScrollBar>
|
|
||||||
|
|
||||||
FileTransferAction::FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me)
|
|
||||||
: ChatAction(me, author, date)
|
|
||||||
{
|
|
||||||
w = widget;
|
|
||||||
|
|
||||||
connect(w, &FileTransferInstance::stateUpdated, this, &FileTransferAction::updateContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileTransferAction::~FileTransferAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QString FileTransferAction::getMessage()
|
|
||||||
{
|
|
||||||
QString widgetHtml;
|
|
||||||
if (w != nullptr)
|
|
||||||
widgetHtml = w->getHtmlImage();
|
|
||||||
else
|
|
||||||
widgetHtml = "<div class=quote>EMPTY CONTENT</div>";
|
|
||||||
return widgetHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
void FileTransferAction::setup(QTextCursor cursor, QTextEdit *textEdit)
|
|
||||||
{
|
|
||||||
cur = cursor;
|
|
||||||
cur.setKeepPositionOnInsert(true);
|
|
||||||
int end=cur.selectionEnd();
|
|
||||||
cur.setPosition(cur.position());
|
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
edit = textEdit;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
void FileTransferAction::updateHtml()
|
|
||||||
{
|
|
||||||
if (cur.isNull() || !edit)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// save old slider value
|
|
||||||
int vSliderVal = edit->verticalScrollBar()->value();
|
|
||||||
|
|
||||||
// update content
|
|
||||||
int pos = cur.selectionStart();
|
|
||||||
cur.removeSelectedText();
|
|
||||||
cur.setKeepPositionOnInsert(false);
|
|
||||||
cur.insertHtml(getMessage());
|
|
||||||
cur.setKeepPositionOnInsert(true);
|
|
||||||
int end = cur.position();
|
|
||||||
cur.setPosition(pos);
|
|
||||||
cur.setPosition(end, QTextCursor::KeepAnchor);
|
|
||||||
|
|
||||||
// restore old slider value
|
|
||||||
edit->verticalScrollBar()->setValue(vSliderVal);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
bool FileTransferAction::isInteractive()
|
|
||||||
{
|
|
||||||
if (w->getState() == FileTransferInstance::TransfState::tsCanceled
|
|
||||||
|| w->getState() == FileTransferInstance::TransfState::tsFinished)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "messageaction.h"
|
|
||||||
#include "src/misc/smileypack.h"
|
|
||||||
#include "src/misc/settings.h"
|
|
||||||
#include <QTextTable>
|
|
||||||
|
|
||||||
MessageAction::MessageAction(const QString &author, const QString &message, const QString &date, const bool &me) :
|
|
||||||
ChatAction(me, author, date),
|
|
||||||
message(message)
|
|
||||||
{
|
|
||||||
isProcessed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MessageAction::getMessage(QString div)
|
|
||||||
{
|
|
||||||
QString message_;
|
|
||||||
if (Settings::getInstance().getUseEmoticons())
|
|
||||||
message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message));
|
|
||||||
else
|
|
||||||
message_ = toHtmlChars(message);
|
|
||||||
|
|
||||||
// detect urls
|
|
||||||
QRegExp exp("(?:\\b)(www\\.|http[s]?:\\/\\/|ftp:\\/\\/|tox:\\/\\/|tox:)\\S+");
|
|
||||||
int offset = 0;
|
|
||||||
while ((offset = exp.indexIn(message_, offset)) != -1)
|
|
||||||
{
|
|
||||||
QString url = exp.cap();
|
|
||||||
|
|
||||||
// If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:"
|
|
||||||
if (url == "tox:\"")
|
|
||||||
{
|
|
||||||
offset += url.length();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add scheme if not specified
|
|
||||||
if (exp.cap(1) == "www.")
|
|
||||||
url.prepend("http://");
|
|
||||||
|
|
||||||
QString htmledUrl = QString("<a href=\"%1\">%1</a>").arg(url);
|
|
||||||
message_.replace(offset, exp.cap().length(), htmledUrl);
|
|
||||||
|
|
||||||
offset += htmledUrl.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
// detect text quotes
|
|
||||||
QStringList messageLines = message_.split("\n");
|
|
||||||
message_ = "";
|
|
||||||
for (QString& s : messageLines)
|
|
||||||
{
|
|
||||||
if (QRegExp("^>( |[[]|>|[^_\\d\\W]).*").exactMatch(s))
|
|
||||||
message_ += "<span class=quote>" + s + "</span><br/>";
|
|
||||||
else
|
|
||||||
message_ += s + "<br/>";
|
|
||||||
}
|
|
||||||
message_ = message_.left(message_.length()-4);
|
|
||||||
|
|
||||||
return QString(QString("<div class=%1>").arg(div) + message_ + "</div>");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MessageAction::getMessage()
|
|
||||||
{
|
|
||||||
if (isMe)
|
|
||||||
return getMessage("message_me");
|
|
||||||
else
|
|
||||||
return getMessage("message");
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageAction::featureUpdate()
|
|
||||||
{
|
|
||||||
QTextTableCell cell = textTable->cellAt(0,3);
|
|
||||||
QTextTableCellFormat format;
|
|
||||||
if (!isProcessed)
|
|
||||||
format.setBackground(QColor(Qt::red));
|
|
||||||
else
|
|
||||||
format.setBackground(QColor(Qt::white));
|
|
||||||
cell.setFormat(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageAction::markAsSent()
|
|
||||||
{
|
|
||||||
isProcessed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MessageAction::getRawMessage()
|
|
||||||
{
|
|
||||||
return message;
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2014 by Project Tox <https://tox.im>
|
|
||||||
|
|
||||||
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
||||||
|
|
||||||
This program is libre software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
See the COPYING file for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MESSAGEACTION_H
|
|
||||||
#define MESSAGEACTION_H
|
|
||||||
|
|
||||||
#include "chataction.h"
|
|
||||||
|
|
||||||
class MessageAction : public ChatAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MessageAction(const QString &author, const QString &message, const QString &date, const bool &me);
|
|
||||||
virtual ~MessageAction(){;}
|
|
||||||
virtual void featureUpdate();
|
|
||||||
void markAsSent();
|
|
||||||
virtual QString getRawMessage();
|
|
||||||
virtual bool isAction() {return false;}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual QString getMessage();
|
|
||||||
virtual QString getMessage(QString div);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QString message;
|
|
||||||
bool isProcessed;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef QSharedPointer<MessageAction> MessageActionPtr;
|
|
||||||
|
|
||||||
#endif // MESSAGEACTION_H
|
|
|
@ -595,9 +595,6 @@ void Widget::addFriend(int friendId, const QString &userId)
|
||||||
QLayout* layout = contactListWidget->getFriendLayout(Status::Offline);
|
QLayout* layout = contactListWidget->getFriendLayout(Status::Offline);
|
||||||
layout->addWidget(newfriend->getFriendWidget());
|
layout->addWidget(newfriend->getFriendWidget());
|
||||||
|
|
||||||
if (Settings::getInstance().getEnableLogging())
|
|
||||||
newfriend->getChatForm()->loadHistory(QDateTime::currentDateTime().addDays(-7), true);
|
|
||||||
|
|
||||||
Core* core = Nexus::getCore();
|
Core* core = Nexus::getCore();
|
||||||
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::onCompactChanged);
|
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::onCompactChanged);
|
||||||
connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*)));
|
connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*)));
|
||||||
|
@ -684,7 +681,7 @@ void Widget::onFriendStatusChanged(int friendId, Status status)
|
||||||
}
|
}
|
||||||
if (isActualChange)
|
if (isActualChange)
|
||||||
f->getChatForm()->addSystemInfoMessage(tr("%1 is now %2", "e.g. \"Dubslow is now online\"").arg(f->getDisplayedName()).arg(fStatus),
|
f->getChatForm()->addSystemInfoMessage(tr("%1 is now %2", "e.g. \"Dubslow is now online\"").arg(f->getDisplayedName()).arg(fStatus),
|
||||||
"white", QDateTime::currentDateTime());
|
ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActualChange && status != Status::Offline)
|
if (isActualChange && status != Status::Offline)
|
||||||
|
@ -912,9 +909,10 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha
|
||||||
{
|
{
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
name = tr("<Unknown>", "Placeholder when we don't know someone's name in a group chat");
|
name = tr("<Unknown>", "Placeholder when we don't know someone's name in a group chat");
|
||||||
|
|
||||||
// g->addPeer(peernumber,name);
|
// g->addPeer(peernumber,name);
|
||||||
g->regeneratePeerList();
|
g->regeneratePeerList();
|
||||||
//g->chatForm->addSystemInfoMessage(tr("%1 has joined the chat").arg(name), "green");
|
// g->getChatForm()->addSystemInfoMessage(tr("%1 has joined the chat").arg(name), "white", QDateTime::currentDateTime());
|
||||||
// we can't display these messages until irungentoo fixes peernumbers
|
// we can't display these messages until irungentoo fixes peernumbers
|
||||||
// https://github.com/irungentoo/toxcore/issues/1128
|
// https://github.com/irungentoo/toxcore/issues/1128
|
||||||
}
|
}
|
||||||
|
@ -922,7 +920,7 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha
|
||||||
{
|
{
|
||||||
// g->removePeer(peernumber);
|
// g->removePeer(peernumber);
|
||||||
g->regeneratePeerList();
|
g->regeneratePeerList();
|
||||||
//g->chatForm->addSystemInfoMessage(tr("%1 has left the chat").arg(name), "silver");
|
// g->getChatForm()->addSystemInfoMessage(tr("%1 has left the chat").arg(name), "white", QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
else if (change == TOX_CHAT_CHANGE_PEER_NAME) // core overwrites old name before telling us it changed...
|
else if (change == TOX_CHAT_CHANGE_PEER_NAME) // core overwrites old name before telling us it changed...
|
||||||
g->updatePeer(peernumber,Nexus::getCore()->getGroupPeerName(groupnumber, peernumber));
|
g->updatePeer(peernumber,Nexus::getCore()->getGroupPeerName(groupnumber, peernumber));
|
||||||
|
@ -936,7 +934,7 @@ void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const Q
|
||||||
|
|
||||||
g->setName(title);
|
g->setName(title);
|
||||||
if (!author.isEmpty())
|
if (!author.isEmpty())
|
||||||
g->getChatForm()->addSystemInfoMessage(tr("%1 has set the title to %2").arg(author, title), "silver", QDateTime::currentDateTime());
|
g->getChatForm()->addSystemInfoMessage(tr("%1 has set the title to %2").arg(author, title), ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::removeGroup(Group* g, bool fake)
|
void Widget::removeGroup(Group* g, bool fake)
|
||||||
|
@ -1092,7 +1090,7 @@ void Widget::onGroupSendResult(int groupId, const QString& message, int result)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (result == -1)
|
if (result == -1)
|
||||||
g->getChatForm()->addSystemInfoMessage(tr("Message failed to send"), "red", QDateTime::currentDateTime());
|
g->getChatForm()->addSystemInfoMessage(tr("Message failed to send"), ChatMessage::INFO, QDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::onFriendTypingChanged(int friendId, bool isTyping)
|
void Widget::onFriendTypingChanged(int friendId, bool isTyping)
|
||||||
|
|
|
@ -4,7 +4,7 @@ QTextEdit
|
||||||
color: back;
|
color: back;
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextBrowser
|
QGraphicsView
|
||||||
{
|
{
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
62
ui/chatArea/error.svg
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
id="svg2">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
transform="translate(0,-1034.3622)"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.06327074,0,0,0.06293016,-3.8785809,1012.9172)"
|
||||||
|
id="info">
|
||||||
|
<g
|
||||||
|
id="g3770">
|
||||||
|
<path
|
||||||
|
d="m 18.056477,9.0033293 a 9.0282383,9.0282383 0 1 1 -18.056477,0 9.0282383,9.0282383 0 1 1 18.056477,0 z"
|
||||||
|
transform="matrix(15.755659,0,0,15.84093,61.301336,341.16918)"
|
||||||
|
id="path3000"
|
||||||
|
style="fill:#c84e4e;fill-opacity:1;stroke:none" />
|
||||||
|
<g
|
||||||
|
transform="translate(5.8400555,0.7447856)"
|
||||||
|
id="g3788"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<path
|
||||||
|
d="m 208.10484,437.30994 a 24.239483,24.239483 0 1 1 -48.47897,0 24.239483,24.239483 0 1 1 48.47897,0 z"
|
||||||
|
transform="translate(13.841768,-25.125222)"
|
||||||
|
id="path3016"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
<rect
|
||||||
|
width="39.413795"
|
||||||
|
height="121.56454"
|
||||||
|
ry="8.9924459"
|
||||||
|
x="178.00023"
|
||||||
|
y="456.58124"
|
||||||
|
id="rect3786"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
62
ui/chatArea/info.svg
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
id="svg2">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
transform="translate(0,-1034.3622)"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
transform="matrix(0.06327074,0,0,0.06293016,-3.8785809,1012.9172)"
|
||||||
|
id="info">
|
||||||
|
<g
|
||||||
|
id="g3770">
|
||||||
|
<path
|
||||||
|
d="m 18.056477,9.0033293 a 9.0282383,9.0282383 0 1 1 -18.056477,0 9.0282383,9.0282383 0 1 1 18.056477,0 z"
|
||||||
|
transform="matrix(15.755659,0,0,15.84093,61.301336,341.16918)"
|
||||||
|
id="path3000"
|
||||||
|
style="fill:#6bc260;fill-opacity:1;stroke:none" />
|
||||||
|
<g
|
||||||
|
transform="translate(5.8400555,0.7447856)"
|
||||||
|
id="g3788"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<path
|
||||||
|
d="m 208.10484,437.30994 a 24.239483,24.239483 0 1 1 -48.47897,0 24.239483,24.239483 0 1 1 48.47897,0 z"
|
||||||
|
transform="translate(13.841768,-25.125222)"
|
||||||
|
id="path3016"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
<rect
|
||||||
|
width="39.413795"
|
||||||
|
height="121.56454"
|
||||||
|
ry="8.9924459"
|
||||||
|
x="178.00023"
|
||||||
|
y="456.58124"
|
||||||
|
id="rect3786"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -1,8 +1,3 @@
|
||||||
div.name {
|
|
||||||
color: @black;
|
|
||||||
font: @bigBold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.message {
|
div.message {
|
||||||
color: @black;
|
color: @black;
|
||||||
font: @big;
|
font: @big;
|
||||||
|
@ -13,23 +8,8 @@ div.action {
|
||||||
font: @big;
|
font: @big;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.date {
|
div.typing {
|
||||||
color: @black;
|
color: @mediumGreyLight;
|
||||||
font: @big;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.name_me {
|
|
||||||
color: @mediumGrey;
|
|
||||||
font: @big;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.message_me {
|
|
||||||
color: @black;
|
|
||||||
font: @big;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.date_me {
|
|
||||||
color: @black;
|
|
||||||
font: @big;
|
font: @big;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,36 +17,6 @@ span.quote {
|
||||||
color: #279419;
|
color: #279419;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.green {
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
margin-left: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
color: @white;
|
|
||||||
background-color: @green;
|
|
||||||
font: @small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.silver {
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
margin-left: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
color: @black;
|
|
||||||
background-color: @lightGrey;
|
|
||||||
font: @small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.red {
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
margin-left: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
color: @white;
|
|
||||||
background-color: @red;
|
|
||||||
font: @small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.alert {
|
div.alert {
|
||||||
margin-left: 0px;
|
margin-left: 0px;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
|
@ -81,13 +31,7 @@ div.alert_name {
|
||||||
font: @bigBold;
|
font: @bigBold;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.button {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
margin-left: 0px;
|
|
||||||
color: @white;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: blue;
|
color: blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
ui/chatArea/spinner.svg
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="svg3849"
|
||||||
|
height="18"
|
||||||
|
width="18"
|
||||||
|
version="1.1">
|
||||||
|
<defs
|
||||||
|
id="defs3851">
|
||||||
|
<filter
|
||||||
|
id="filter4418"
|
||||||
|
color-interpolation-filters="sRGB">
|
||||||
|
<feGaussianBlur
|
||||||
|
stdDeviation="0.10811274"
|
||||||
|
id="feGaussianBlur4420" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata3854">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0.1875,-1034.3622)">
|
||||||
|
<path
|
||||||
|
style="fill:#333333;fill-opacity:1;stroke:none;filter:url(#filter4418)"
|
||||||
|
id="path3857"
|
||||||
|
transform="translate(0,1034.3622)"
|
||||||
|
d="M 9,1 C 4.581722,1 1,4.581722 1,9 c 0,4.418278 3.581722,8 8,8 3.600455,0 6.621126,-2.384809 7.625,-5.65625 l -2.09375,0 C 13.6204,13.49724 11.485281,15 9,15 5.6862916,15 3,12.313708 3,9 3,5.6862915 5.6862916,3 9,3 c 2.485281,0 4.6204,1.5027597 5.53125,3.65625 l 2.09375,0 C 15.621126,3.3848094 12.600455,1 9,1 Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
208
ui/chatArea/symbols.svg
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.2"
|
||||||
|
width="744.09448"
|
||||||
|
height="1052.3622"
|
||||||
|
id="svg2"
|
||||||
|
inkscape:version="0.48.5 r10040"
|
||||||
|
sodipodi:docname="symbols.svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1386"
|
||||||
|
id="namedview39"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="0.89702957"
|
||||||
|
inkscape:cx="120.52364"
|
||||||
|
inkscape:cy="577.58784"
|
||||||
|
inkscape:window-x="-2"
|
||||||
|
inkscape:window-y="-3"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
width="240.94118"
|
||||||
|
height="240.94118"
|
||||||
|
x="419.90225"
|
||||||
|
y="358.90332"
|
||||||
|
id="rect3804"
|
||||||
|
style="fill:#000000;fill-opacity:0;stroke:none" />
|
||||||
|
<g
|
||||||
|
transform="translate(-19.999993,-14.285714)"
|
||||||
|
id="g3864">
|
||||||
|
<path
|
||||||
|
d="m 205.71429,686.64789 160,320.00001 -320.000004,0 z"
|
||||||
|
id="rect2985"
|
||||||
|
style="fill:#c84e4e;fill-opacity:1;stroke:#000000;stroke-width:16;stroke-linejoin:round;stroke-opacity:1" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(8.7750279,0,0,6.5433553,-681.18052,-5346.0366)"
|
||||||
|
id="flowRoot2988"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||||
|
id="flowRegion2990"><rect
|
||||||
|
width="125.71429"
|
||||||
|
height="85.714287"
|
||||||
|
x="95.714287"
|
||||||
|
y="926.64789"
|
||||||
|
id="rect2992" /></flowRegion><flowPara
|
||||||
|
id="flowPara2994">!</flowPara></flowRoot> </g>
|
||||||
|
<g
|
||||||
|
transform="translate(-18.571423,8.5714286)"
|
||||||
|
id="info"
|
||||||
|
inkscape:export-xdpi="11.328032"
|
||||||
|
inkscape:export-ydpi="11.328032">
|
||||||
|
<path
|
||||||
|
d="m 448.57143,603.07648 a 189.28572,189.28572 0 1 1 -378.571445,0 189.28572,189.28572 0 1 1 378.571445,0 z"
|
||||||
|
transform="matrix(0.72483708,0,0,0.72483708,16.345817,46.658532)"
|
||||||
|
id="path3764"
|
||||||
|
style="fill:#6bc260;fill-opacity:1;stroke:#1c1c1c;stroke-width:16;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:none;stroke-dashoffset:0" />
|
||||||
|
<g
|
||||||
|
id="g3788"
|
||||||
|
transform="translate(6.5785877,0.74522643)"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<path
|
||||||
|
transform="translate(13.841768,-25.125222)"
|
||||||
|
d="m 208.10484,437.30994 c 0,13.38709 -10.85239,24.23948 -24.23948,24.23948 -13.3871,0 -24.23949,-10.85239 -24.23949,-24.23948 0,-13.3871 10.85239,-24.23949 24.23949,-24.23949 13.38709,0 24.23948,10.85239 24.23948,24.23949 z"
|
||||||
|
sodipodi:ry="24.239483"
|
||||||
|
sodipodi:rx="24.239483"
|
||||||
|
sodipodi:cy="437.30994"
|
||||||
|
sodipodi:cx="183.86536"
|
||||||
|
id="path3016"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
sodipodi:type="arc" />
|
||||||
|
<rect
|
||||||
|
ry="8.9924459"
|
||||||
|
y="456.58124"
|
||||||
|
x="178.00023"
|
||||||
|
height="121.56454"
|
||||||
|
width="39.413795"
|
||||||
|
id="rect3786"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(-342.85714,-295.71428)"
|
||||||
|
id="g3850">
|
||||||
|
<path
|
||||||
|
d="m 448.57143,603.07648 a 189.28572,189.28572 0 1 1 -378.571445,0 189.28572,189.28572 0 1 1 378.571445,0 z"
|
||||||
|
transform="matrix(0.72483708,0,0,0.72483708,340.63154,52.37285)"
|
||||||
|
id="path3764-1"
|
||||||
|
style="fill:#414141;fill-opacity:1;stroke:none" />
|
||||||
|
<flowRoot
|
||||||
|
transform="matrix(8.7750279,0,0,6.1071156,-354.88741,-5316.9084)"
|
||||||
|
id="flowRoot2988-9-1"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||||
|
id="flowRegion2990-3-0"><rect
|
||||||
|
width="125.71429"
|
||||||
|
height="85.714287"
|
||||||
|
x="95.714287"
|
||||||
|
y="926.64789"
|
||||||
|
id="rect2992-2-08"
|
||||||
|
style="fill:#ffffff;fill-opacity:1" /></flowRegion><flowPara
|
||||||
|
id="flowPara2994-4-8"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#ffffff;fill-opacity:1;font-family:Aller;-inkscape-font-specification:Aller">i</flowPara></flowRoot> </g>
|
||||||
|
<g
|
||||||
|
transform="translate(330.16534,365.19627)"
|
||||||
|
id="error"
|
||||||
|
inkscape:export-xdpi="5.6643357"
|
||||||
|
inkscape:export-ydpi="5.6643357">
|
||||||
|
<path
|
||||||
|
d="m 341.48702,483.79073 a 137.20131,137.20131 0 0 1 -274.402618,0 137.20131,137.20131 0 1 1 274.402618,0 z"
|
||||||
|
id="path3764-6"
|
||||||
|
style="fill:#c84e4e;fill-opacity:1;stroke:#1c1c1c;stroke-width:11.59739304;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0;stroke-dasharray:none;stroke-dashoffset:0" />
|
||||||
|
<g
|
||||||
|
id="g3788-0"
|
||||||
|
transform="translate(6.5785866,0.72866166)"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<path
|
||||||
|
transform="translate(13.841768,-25.125222)"
|
||||||
|
d="m 208.10484,437.30994 c 0,13.38709 -10.85239,24.23948 -24.23948,24.23948 -13.3871,0 -24.23949,-10.85239 -24.23949,-24.23948 0,-13.3871 10.85239,-24.23949 24.23949,-24.23949 13.38709,0 24.23948,10.85239 24.23948,24.23949 z"
|
||||||
|
sodipodi:ry="24.239483"
|
||||||
|
sodipodi:rx="24.239483"
|
||||||
|
sodipodi:cy="437.30994"
|
||||||
|
sodipodi:cx="183.86536"
|
||||||
|
id="path3016-8"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
sodipodi:type="arc" />
|
||||||
|
<rect
|
||||||
|
ry="8.9924459"
|
||||||
|
y="456.58124"
|
||||||
|
x="178.00023"
|
||||||
|
height="121.56454"
|
||||||
|
width="39.413795"
|
||||||
|
id="rect3786-2"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d="m 591.05751,386.8743 36.87891,5.31312 m -55.64044,122.34813 36.87889,5.31308 m -15.45524,-156.42803 -21.93151,152.22907 14.10828,35.26976 23.49041,-29.85297 21.93142,-152.22911 z"
|
||||||
|
id="path3012-0"
|
||||||
|
style="fill:#cccccc;stroke:#6e6e6e;stroke-width:4.51865435;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||||
|
<path
|
||||||
|
d="m 494.05192,469.82629 a 2.5619,2.5619 0 1 1 -5.1238,0 2.5619,2.5619 0 1 1 5.1238,0 z"
|
||||||
|
transform="matrix(4.5186544,0,0,4.5186544,-1726.5774,-1541.9613)"
|
||||||
|
id="path3806"
|
||||||
|
style="fill:#6e6e6e;fill-opacity:1;stroke:#6e6e6e;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
|
||||||
|
<path
|
||||||
|
d="m 494.05192,469.82629 a 2.5619,2.5619 0 1 1 -5.1238,0 2.5619,2.5619 0 1 1 5.1238,0 z"
|
||||||
|
transform="matrix(4.5186544,0,0,4.5186544,-1683.834,-1541.9613)"
|
||||||
|
id="path3806-6"
|
||||||
|
style="fill:#6e6e6e;fill-opacity:1;stroke:#6e6e6e;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
|
||||||
|
<path
|
||||||
|
d="m 494.05192,469.82629 a 2.5619,2.5619 0 1 1 -5.1238,0 2.5619,2.5619 0 1 1 5.1238,0 z"
|
||||||
|
transform="matrix(4.5186544,0,0,4.5186544,-1638.4193,-1541.9613)"
|
||||||
|
id="path3806-6-2"
|
||||||
|
style="fill:#6e6e6e;fill-opacity:1;stroke:#6e6e6e;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
|
||||||
|
<g
|
||||||
|
id="notification">
|
||||||
|
<path
|
||||||
|
d="m 494.05192,469.82629 a 2.5619,2.5619 0 1 1 -5.1238,0 2.5619,2.5619 0 1 1 5.1238,0 z"
|
||||||
|
transform="matrix(8.782556,0,0,8.782556,-3756.8812,-3871.6691)"
|
||||||
|
id="path3806-4"
|
||||||
|
style="fill:#6bc260;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
d="m 494.05192,469.82629 a 2.5619,2.5619 0 1 1 -5.1238,0 2.5619,2.5619 0 1 1 5.1238,0 z"
|
||||||
|
transform="matrix(8.782556,0,0,8.782556,-3784.3812,-3816.6691)"
|
||||||
|
id="path3806-2"
|
||||||
|
style="fill:#cebf44;fill-opacity:1;stroke:none" />
|
||||||
|
<path
|
||||||
|
d="m 494.05192,469.82629 a 2.5619,2.5619 0 1 1 -5.1238,0 2.5619,2.5619 0 1 1 5.1238,0 z"
|
||||||
|
transform="matrix(8.782556,0,0,8.782556,-3729.3812,-3816.6688)"
|
||||||
|
id="path3806-6-4"
|
||||||
|
style="fill:#c84e4e;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
d="m 458.1875,12.1875 0,209.46875 200.21875,0 0,-209.46875 -200.21875,0 z m 43.9375,137.125 a 25,25 0 0 1 24.28125,25 25,25 0 0 1 -50,0 25,25 0 0 1 25.71875,-25 z m 56.90625,0 a 25,25 0 0 1 24.28125,25 25,25 0 0 1 -50,0 25,25 0 0 1 25.71875,-25 z m 56.90625,0 a 25,25 0 0 1 24.25,25 25,25 0 0 1 -50,0 25,25 0 0 1 25.75,-25 z"
|
||||||
|
id="mask"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
61
ui/chatArea/typing.svg
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
id="svg3815"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.5 r10040"
|
||||||
|
sodipodi:docname="Neues Dokument 5">
|
||||||
|
<defs
|
||||||
|
id="defs3817" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.4"
|
||||||
|
inkscape:cx="23.994336"
|
||||||
|
inkscape:cy="16.050687"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1386"
|
||||||
|
inkscape:window-x="-2"
|
||||||
|
inkscape:window-y="-3"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata3820">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-1034.3622)">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
d="m 0,1034.3622 0,18 18,0 0,-18 -18,0 z m 3.9500533,11.7834 a 2.2475413,2.148285 0 0 1 2.1829252,2.1482 2.2475416,2.1482852 0 0 1 -4.4950832,0 2.2475413,2.148285 0 0 1 2.312158,-2.1482 z m 5.1159617,0 a 2.2475413,2.148285 0 0 1 2.18293,2.1482 2.2475417,2.1482853 0 0 1 -4.4950834,0 2.2475413,2.148285 0 0 1 2.3121534,-2.1482 z m 5.11597,0 a 2.2475413,2.148285 0 0 1 2.18012,2.1482 2.2475435,2.1482871 0 0 1 -4.495087,0 2.2475413,2.148285 0 0 1 2.314967,-2.1482 z"
|
||||||
|
id="mask"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 449 B |
23
ui/fileTransferInstance/arrow_white.svg
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
id="Layer_1"
|
||||||
|
xml:space="preserve"><metadata
|
||||||
|
id="metadata9"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs7" /><polygon
|
||||||
|
points="3.572,6.187 0,0 7.145,0 "
|
||||||
|
transform="matrix(0,-1.3815919,1.3815919,0,1.7260455,10.935737)"
|
||||||
|
id="polygon3"
|
||||||
|
style="fill:#ffffff" /></svg>
|
After Width: | Height: | Size: 888 B |
147
ui/fileTransferInstance/browse.svg
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
id="svg2"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="browse.svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
id="namedview22"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="27.812867"
|
||||||
|
inkscape:cx="7.3552517"
|
||||||
|
inkscape:cy="6.238286"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<marker
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
orient="auto"
|
||||||
|
id="TriangleOutS"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
|
||||||
|
transform="scale(0.2,0.2)"
|
||||||
|
id="path3926"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
orient="auto"
|
||||||
|
id="Arrow2Send"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
|
||||||
|
transform="matrix(-0.3,0,0,-0.3,0.69,0)"
|
||||||
|
id="path3811"
|
||||||
|
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
orient="auto"
|
||||||
|
id="TriangleInS"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
|
||||||
|
transform="scale(-0.2,-0.2)"
|
||||||
|
id="path3917"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
orient="auto"
|
||||||
|
id="DiamondSstart"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 z"
|
||||||
|
transform="matrix(0.2,0,0,0.2,1.2,0)"
|
||||||
|
id="path3872"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
orient="auto"
|
||||||
|
id="TriangleOutL"
|
||||||
|
style="overflow:visible">
|
||||||
|
<path
|
||||||
|
d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
|
||||||
|
transform="scale(0.8,0.8)"
|
||||||
|
id="path3920"
|
||||||
|
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
transform="matrix(1.1423998,0,0,1.1423998,-0.85439881,-2392.0444)"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
id="g6659"
|
||||||
|
style="fill:#999999">
|
||||||
|
<path
|
||||||
|
d="m 0.99206881,2098.1979 c -0.10310776,0 -0.18644079,0.07 -0.18644079,0.1559 l 0,5.9208 c 0,0.087 0.0833323,0.1566 0.18644079,0.1566 l 10.01586219,0 c 0.103108,0 0.186441,-0.07 0.186441,-0.1566 l 0,-5.9208 c 0,-0.087 -0.08334,-0.1559 -0.186441,-0.1559 l -1.8913078,0 0,4.4022 c 0,0.087 -0.083329,0.1567 -0.1864405,0.1567 l -5.8603649,0 c -0.1031078,0 -0.1864408,-0.07 -0.1864408,-0.1567 l 0,-4.4022 -1.8913082,0 z"
|
||||||
|
id="rect2985"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 250,902.36218 a 80,80 0 1 1 -160,0 80,80 0 1 1 160,0 z"
|
||||||
|
transform="matrix(0.02146655,0,0,0.02146655,2.3506865,2081.0532)"
|
||||||
|
id="path2990"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<g
|
||||||
|
transform="matrix(1.7576144,0,0,1,-4.6547815,0)"
|
||||||
|
id="g6651"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none">
|
||||||
|
<path
|
||||||
|
d="m 5.5625,2094.9688 0,3.1874 1,0 0,-3.1874 -1,0 z"
|
||||||
|
id="path4954"
|
||||||
|
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;marker:none;enable-background:accumulate"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 6.06207,2093.8266 1,1.73 -2,0 1,-1.73 z"
|
||||||
|
id="path6657"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 229 B |
Before Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 211 B |
27
ui/fileTransferInstance/filetransferWidget.css
Normal file
|
@ -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;
|
||||||
|
}
|
46
ui/fileTransferInstance/no.svg
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 11.999999 12"
|
||||||
|
id="Layer_1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="no.svg"><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
id="namedview6"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="27.812867"
|
||||||
|
inkscape:cx="7.1318585"
|
||||||
|
inkscape:cy="6.2233931"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="Layer_1" /><metadata
|
||||||
|
id="metadata9"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs7" /><polygon
|
||||||
|
points="1.693,0.001 0,1.693 3.35,5.043 0,8.394 1.693,10.086 5.043,6.736 8.395,10.087 10.086,8.394 6.734,5.043 10.086,1.692 8.395,0 5.043,3.351 "
|
||||||
|
transform="matrix(1.1123133,0,0,1.1054869,0.39060355,0.42447682)"
|
||||||
|
id="polygon3"
|
||||||
|
style="fill:#ffffff" /></svg>
|
After Width: | Height: | Size: 1.7 KiB |
56
ui/fileTransferInstance/pause.svg
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12.000001 12"
|
||||||
|
id="Layer_1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="pause.svg"><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
id="namedview8"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="39.333333"
|
||||||
|
inkscape:cx="0.67502197"
|
||||||
|
inkscape:cy="4.3268875"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="Layer_1" /><metadata
|
||||||
|
id="metadata13"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs11" /><g
|
||||||
|
transform="matrix(0.91525423,0,0,0.91525423,2.1550175,0.69830581)"
|
||||||
|
id="g3"><rect
|
||||||
|
width="2.1851854"
|
||||||
|
height="10.925926"
|
||||||
|
x="-0.16937082"
|
||||||
|
y="0.32962826"
|
||||||
|
id="rect5"
|
||||||
|
style="fill:#ffffff" /><rect
|
||||||
|
width="2.1851854"
|
||||||
|
height="10.925926"
|
||||||
|
x="6.3861852"
|
||||||
|
y="0.32962897"
|
||||||
|
id="rect7"
|
||||||
|
style="fill:#ffffff" /></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
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 |
46
ui/fileTransferInstance/yes.svg
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 11.999999 12"
|
||||||
|
id="Layer_1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="yes.svg"><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
id="namedview6"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="39.333333"
|
||||||
|
inkscape:cx="-10.242096"
|
||||||
|
inkscape:cy="5.4642197"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="Layer_1" /><metadata
|
||||||
|
id="metadata9"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs7" /><path
|
||||||
|
d="M 11.17966,1.8934443 4.1597484,9.1919256 0.79836133,5.8676812"
|
||||||
|
id="path2999"
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
inkscape:connector-curvature="0" /></svg>
|
After Width: | Height: | Size: 1.7 KiB |