1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge pull request #264 from apprb/chatform

ChatForm
This commit is contained in:
Tux3 / Mlkj / !Lev.uXFMLA 2014-09-09 15:55:33 +02:00
commit 31af9dc49d
23 changed files with 841 additions and 531 deletions

329
filetransferinstance.cpp Normal file
View File

@ -0,0 +1,329 @@
/*
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 "widget/widget.h"
#include "core.h"
#include "math.h"
#include "style.h"
#include <QFileDialog>
#include <QPixmap>
#include <QPainter>
#include <QMessageBox>
#include <QBuffer>
uint FileTransferInstance::Idconter = 0;
FileTransferInstance::FileTransferInstance(ToxFile File)
: lastUpdate{QDateTime::currentDateTime()}, lastBytesSent{0},
fileNum{File.fileNum}, friendId{File.friendId}, direction{File.direction}
{
id = Idconter++;
state = tsPending;
filename = File.fileName;
size = getHumanReadableSize(File.filesize);
speed = "0B/s";
eta = "00:00";
if (File.direction == ToxFile::SENDING)
{
QImage preview;
File.file->seek(0);
if (preview.loadFromData(File.file->readAll()))
{
pic = preview.scaledToHeight(50);
}
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 newtime = QDateTime::currentDateTime();
int timediff = lastUpdate.secsTo(newtime);
if (timediff <= 0)
return;
qint64 diff = BytesSent - lastBytesSent;
if (diff < 0)
{
qWarning() << "FileTransferInstance::onFileTransferInfo: Negative transfer speed !";
diff = 0;
}
long rawspeed = diff / timediff;
speed = getHumanReadableSize(rawspeed)+"/s";
size = getHumanReadableSize(Filesize);
if (!rawspeed)
return;
int etaSecs = (Filesize - BytesSent) / rawspeed;
QTime etaTime(0,0);
etaTime = etaTime.addSecs(etaSecs);
eta = etaTime.toString("mm:ss");
lastUpdate = newtime;
lastBytesSent = BytesSent;
emit stateUpdated();
}
void FileTransferInstance::onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction)
{
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
return;
disconnect(Widget::getInstance()->getCore(),0,this,0);
state = tsCanceled;
emit stateUpdated();
}
void FileTransferInstance::onFileTransferFinished(ToxFile File)
{
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
return;
disconnect(Widget::getInstance()->getCore(),0,this,0);
if (File.direction == ToxFile::RECEIVING)
{
QImage preview;
QFile previewFile(File.filePath);
if (previewFile.open(QIODevice::ReadOnly) && previewFile.size() <= 1024*1024*25) // Don't preview big (>25MiB) images
{
if (preview.loadFromData(previewFile.readAll()))
{
pic = preview.scaledToHeight(50);
}
previewFile.close();
}
}
state = tsFinished;
emit stateUpdated();
}
void FileTransferInstance::cancelTransfer()
{
Widget::getInstance()->getCore()->cancelFileSend(friendId, fileNum);
state = tsCanceled;
emit stateUpdated();
}
void FileTransferInstance::rejectRecvRequest()
{
Widget::getInstance()->getCore()->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;
while (true)
{
path = QFileDialog::getSaveFileName(Widget::getInstance(), tr("Save a file","Title of the file saving dialog"), QDir::current().filePath(filename));
if (path.isEmpty())
return;
else
{
//bool savable = QFileInfo(path).isWritable();
//qDebug() << path << " is writable: " << savable;
//qDebug() << "/home/bill/bliss.pdf writable: " << QFileInfo("/home/bill/bliss.pdf").isWritable();
if (isFileWritable(path))
break;
else
QMessageBox::warning(Widget::getInstance(), 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;
Widget::getInstance()->getCore()->acceptFileRecvRequest(friendId, fileNum, path);
state = tsProcessing;
emit stateUpdated();
}
void FileTransferInstance::pauseResumeRecv()
{
Widget::getInstance()->getCore()->pauseResumeFileRecv(friendId, fileNum);
if (state == tsProcessing)
state = tsPaused;
else state = tsProcessing;
emit stateUpdated();
}
void FileTransferInstance::pauseResumeSend()
{
Widget::getInstance()->getCore()->pauseResumeFileSend(friendId, fileNum);
if (state == tsProcessing)
state = tsPaused;
else state = tsProcessing;
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()
{
qDebug() << "QString FileTransferInstance::getHtmlImage() " << state;
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
rightBtnImg = QImage(":/ui/fileTransferInstance/acceptFileButton.png");
res = draw2ButtonsForm("silver", 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>" + filename + "</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";
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;
content += "<p>" + filename + "</p>";
content += "<p>" + getHumanReadableSize(lastBytesSent) + " / " + size + "&nbsp;(" + speed + " ETA: " + eta + ")</p>\n";
return wrapIntoForm(content, type, imgAstr, imgBstr);
}
QString FileTransferInstance::wrapIntoForm(const QString& content, const QString &type, const QString &imgAstr, const QString &imgBstr)
{
QString res;
res = "<table widht=100% cellspacing=\"0\">\n";
res += "<tr valign=middle>\n";
res += insertMiniature(type);
res += "<td width=100%>\n";
res += "<div class=" + type + ">";
res += content;
res += "</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;
}

View File

@ -13,11 +13,10 @@
See the COPYING file for more details. See the COPYING file for more details.
*/ */
#ifndef FILETRANSFERINSTANCE_H
#define FILETRANSFERINSTANCE_H
#ifndef FILETRANSFERTWIDGET_H #include <QObject>
#define FILETRANSFERTWIDGET_H
#include <QWidget>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QProgressBar> #include <QProgressBar>
@ -29,17 +28,22 @@
struct ToxFile; struct ToxFile;
class FileTransfertWidget : public QWidget class FileTransferInstance : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
FileTransfertWidget(ToxFile File); explicit FileTransferInstance(ToxFile File);
QString getHtmlImage();
uint getId(){return id;}
public slots: public slots:
void onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction); 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 onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction);
void onFileTransferFinished(ToxFile File); void onFileTransferFinished(ToxFile File);
void pressFromHtml(QString);
signals:
void stateUpdated();
private slots: private slots:
void cancelTransfer(); void cancelTransfer();
@ -50,14 +54,21 @@ private slots:
private: private:
QString getHumanReadableSize(unsigned long long size); 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);
private: private:
QLabel *pic, *filename, *size, *speed, *eta; enum TransfState {tsPending, tsProcessing, tsPaused, tsFinished, tsCanceled};
QPushButton *topright, *bottomright;
QProgressBar *progress; static uint Idconter;
QHBoxLayout *mainLayout, *textLayout; uint id;
QVBoxLayout *infoLayout, *buttonLayout;
QWidget* buttonWidget; TransfState state;
QImage pic;
QString filename, size, speed, eta;
QDateTime lastUpdate; QDateTime lastUpdate;
long long lastBytesSent; long long lastBytesSent;
int fileNum; int fileNum;
@ -65,7 +76,6 @@ private:
QString savePath; QString savePath;
ToxFile::FileDirection direction; ToxFile::FileDirection direction;
QString stopFileButtonStylesheet, pauseFileButtonStylesheet, acceptFileButtonStylesheet; QString stopFileButtonStylesheet, pauseFileButtonStylesheet, acceptFileButtonStylesheet;
void paintEvent(QPaintEvent *);
}; };
#endif // FILETRANSFERTWIDGET_H #endif // FILETRANSFERINSTANCE_H

View File

@ -57,7 +57,7 @@ win32 {
LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -lvpx -lopenal -lopencv_core -lopencv_highgui
} }
contains(JENKINS, YES) { contains(JENKINS, YES) {
LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a -lopencv_core -lopencv_highgui -lopenal LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a -lopencv_core -lopencv_highgui -lopenal
} }
} }
@ -80,7 +80,6 @@ HEADERS += widget/form/addfriendform.h \
widget/form/filesform.h \ widget/form/filesform.h \
widget/tool/chattextedit.h \ widget/tool/chattextedit.h \
widget/tool/friendrequestdialog.h \ widget/tool/friendrequestdialog.h \
widget/filetransfertwidget.h \
widget/friendwidget.h \ widget/friendwidget.h \
widget/groupwidget.h \ widget/groupwidget.h \
widget/widget.h \ widget/widget.h \
@ -102,7 +101,10 @@ HEADERS += widget/form/addfriendform.h \
widget/croppinglabel.h \ widget/croppinglabel.h \
widget/friendlistwidget.h \ widget/friendlistwidget.h \
widget/genericchatroomwidget.h \ widget/genericchatroomwidget.h \
widget/form/genericchatform.h widget/form/genericchatform.h \
widget/tool/chataction.h \
widget/chatareawidget.h \
filetransferinstance.h
SOURCES += \ SOURCES += \
widget/form/addfriendform.cpp \ widget/form/addfriendform.cpp \
@ -112,7 +114,6 @@ SOURCES += \
widget/form/filesform.cpp \ widget/form/filesform.cpp \
widget/tool/chattextedit.cpp \ widget/tool/chattextedit.cpp \
widget/tool/friendrequestdialog.cpp \ widget/tool/friendrequestdialog.cpp \
widget/filetransfertwidget.cpp \
widget/friendwidget.cpp \ widget/friendwidget.cpp \
widget/groupwidget.cpp \ widget/groupwidget.cpp \
widget/widget.cpp \ widget/widget.cpp \
@ -136,4 +137,7 @@ SOURCES += \
widget/friendlistwidget.cpp \ widget/friendlistwidget.cpp \
coreav.cpp \ coreav.cpp \
widget/genericchatroomwidget.cpp \ widget/genericchatroomwidget.cpp \
widget/form/genericchatform.cpp widget/form/genericchatform.cpp \
widget/tool/chataction.cpp \
widget/chatareawidget.cpp \
filetransferinstance.cpp

11
res.qrc
View File

@ -42,6 +42,7 @@
<file>ui/callButton/callButtonYellowHover.png</file> <file>ui/callButton/callButtonYellowHover.png</file>
<file>ui/callButton/callButtonYellowPressed.png</file> <file>ui/callButton/callButtonYellowPressed.png</file>
<file>ui/chatArea/chatArea.css</file> <file>ui/chatArea/chatArea.css</file>
<file>ui/chatArea/innerStyle.css</file>
<file>ui/chatArea/scrollBarDownArrow.png</file> <file>ui/chatArea/scrollBarDownArrow.png</file>
<file>ui/chatArea/scrollBarDownArrowHover.png</file> <file>ui/chatArea/scrollBarDownArrowHover.png</file>
<file>ui/chatArea/scrollBarDownArrowPressed.png</file> <file>ui/chatArea/scrollBarDownArrowPressed.png</file>
@ -57,6 +58,7 @@
<file>ui/fileButton/fileButton.png</file> <file>ui/fileButton/fileButton.png</file>
<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/msgEdit/msgEdit.css</file> <file>ui/msgEdit/msgEdit.css</file>
<file>ui/pauseFileButton/default.png</file> <file>ui/pauseFileButton/default.png</file>
<file>ui/pauseFileButton/hover.png</file> <file>ui/pauseFileButton/hover.png</file>
@ -124,6 +126,13 @@
<file>ui/micButton/micButtonPressed.png</file> <file>ui/micButton/micButtonPressed.png</file>
<file>ui/micButton/micButton.css</file> <file>ui/micButton/micButton.css</file>
<file>ui/volButton/volButton.css</file> <file>ui/volButton/volButton.css</file>
<file>ui/fileButton/fileButtonDisabled.png</file> <file>ui/fileTransferInstance/acceptFileButton.png</file>
<file>ui/fileTransferInstance/pauseFileButton.png</file>
<file>ui/fileTransferInstance/resumeFileButton.png</file>
<file>ui/fileTransferInstance/stopFileButton.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>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -0,0 +1,63 @@
div.name_me {
color: #646464;
font-weight: bold;
padding-right: 3px;
}
div.name {
color: #000000;
font-weight: bold;
padding-right: 3px;
}
div.message {
color: #000000;
padding-right: 3px;
padding-left: 3px;
}
div.date {
color: #000000;
padding-left: 3px;
}
div.quote {
background-color: #6bc260;
}
div.green {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 12px;
margin-right: 12px;
color: #ffffff;
background-color: #6bc260;
font-size: 10px;
}
div.silver {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 12px;
margin-right: 12px;
color: #000000;
background-color: #d1d1d1;
font-size: 10px;
}
div.red {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 12px;
margin-right: 12px;
color: #ffffff;
background-color: rgb(200,78,78);
font-size: 10px;
}
div.button {
margin-top: 0px;
margin-bottom: 0px;
margin-left: 0px;
color: #ffffff;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

105
widget/chatareawidget.cpp Normal file
View File

@ -0,0 +1,105 @@
/*
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 <QAbstractTextDocumentLayout>
#include <QMessageBox>
#include <QScrollBar>
ChatAreaWidget::ChatAreaWidget(QWidget *parent) :
QTextEdit(parent)
{
setReadOnly(true);
viewport()->setCursor(Qt::ArrowCursor);
setContextMenuPolicy(Qt::CustomContextMenu);
}
ChatAreaWidget::~ChatAreaWidget()
{
for (ChatAction *it : messages)
delete it;
}
void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event)
{
QTextEdit::mouseReleaseEvent(event);
int pos = this->document()->documentLayout()->hitTest(event->pos(), 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);
emit onFileTranfertInterract(widgetID, widgetBtn);
}
}
}
}
}
QString ChatAreaWidget::getHtmledMessages()
{
QString res("<table width=100%>\n");
for (ChatAction *it : messages)
{
res += it->getHtml();
}
res += "</table>";
return res;
}
void ChatAreaWidget::insertMessage(ChatAction *msgAction)
{
if (msgAction == nullptr)
return;
messages.append(msgAction);
updateChatContent();
}
void ChatAreaWidget::updateChatContent()
{
QScrollBar* scroll = verticalScrollBar();
lockSliderToBottom = scroll && scroll->value() == scroll->maximum();
setHtml(getHtmledMessages());
if (lockSliderToBottom)
sliderPosition = scroll->maximum();
scroll->setValue(sliderPosition);
}
void ChatAreaWidget::clearMessages()
{
for (ChatAction *it : messages)
delete it;
updateChatContent();
}

49
widget/chatareawidget.h Normal file
View File

@ -0,0 +1,49 @@
/*
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 <QTextEdit>
#include <QList>
#include "widget/tool/chataction.h"
class ChatAreaWidget : public QTextEdit
{
Q_OBJECT
public:
explicit ChatAreaWidget(QWidget *parent = 0);
virtual ~ChatAreaWidget();
void insertMessage(ChatAction *msgAction);
void clearMessages();
signals:
void onFileTranfertInterract(QString widgetName, QString buttonName);
protected:
void mouseReleaseEvent(QMouseEvent * event);
public slots:
void updateChatContent();
private:
QString getHtmledMessages();
QList<ChatAction*> messages;
bool lockSliderToBottom;
int sliderPosition;
};
#endif // CHATAREAWIDGET_H

View File

@ -1,320 +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 "filetransfertwidget.h"
#include "widget.h"
#include "core.h"
#include "math.h"
#include "style.h"
#include <QFileDialog>
#include <QPixmap>
#include <QPainter>
#include <QMessageBox>
FileTransfertWidget::FileTransfertWidget(ToxFile File)
: lastUpdate{QDateTime::currentDateTime()}, lastBytesSent{0},
fileNum{File.fileNum}, friendId{File.friendId}, direction{File.direction}
{
pic=new QLabel(), filename=new QLabel(), size=new QLabel(), speed=new QLabel(), eta=new QLabel();
topright = new QPushButton(), bottomright = new QPushButton();
progress = new QProgressBar();
mainLayout = new QHBoxLayout(), textLayout = new QHBoxLayout();
infoLayout = new QVBoxLayout(), buttonLayout = new QVBoxLayout();
buttonWidget = new QWidget();
QFont prettysmall;
prettysmall.setPixelSize(10);
this->setObjectName("default");
this->setStyleSheet(Style::get(":/ui/fileTransferWidget/fileTransferWidget.css"));
QPalette greybg;
greybg.setColor(QPalette::Window, QColor(209,209,209));
greybg.setColor(QPalette::Base, QColor(150,150,150));
setPalette(greybg);
setAutoFillBackground(true);
setMinimumSize(250,58);
setMaximumHeight(58);
setLayout(mainLayout);
mainLayout->setMargin(0);
pic->setMaximumHeight(40);
pic->setContentsMargins(5,0,0,0);
filename->setText(File.fileName);
filename->setFont(prettysmall);
size->setText(getHumanReadableSize(File.filesize));
size->setFont(prettysmall);
speed->setText("0B/s");
speed->setFont(prettysmall);
eta->setText("00:00");
eta->setFont(prettysmall);
progress->setValue(0);
progress->setMinimumHeight(11);
progress->setFont(prettysmall);
progress->setTextVisible(false);
QPalette whitebg;
whitebg.setColor(QPalette::Window, QColor(255,255,255));
buttonWidget->setPalette(whitebg);
buttonWidget->setAutoFillBackground(true);
buttonWidget->setLayout(buttonLayout);
stopFileButtonStylesheet = Style::get(":/ui/stopFileButton/style.css");
pauseFileButtonStylesheet = Style::get(":/ui/pauseFileButton/style.css");
acceptFileButtonStylesheet = Style::get(":/ui/acceptFileButton/style.css");
topright->setStyleSheet(stopFileButtonStylesheet);
if (File.direction == ToxFile::SENDING)
{
bottomright->setStyleSheet(pauseFileButtonStylesheet);
connect(topright, SIGNAL(clicked()), this, SLOT(cancelTransfer()));
connect(bottomright, SIGNAL(clicked()), this, SLOT(pauseResumeSend()));
QPixmap preview;
File.file->seek(0);
if (preview.loadFromData(File.file->readAll()))
{
preview = preview.scaledToHeight(40);
pic->setPixmap(preview);
}
File.file->seek(0);
}
else
{
bottomright->setStyleSheet(acceptFileButtonStylesheet);
connect(topright, SIGNAL(clicked()), this, SLOT(rejectRecvRequest()));
connect(bottomright, SIGNAL(clicked()), this, SLOT(acceptRecvRequest()));
}
QPalette toxgreen;
toxgreen.setColor(QPalette::Button, QColor(107,194,96)); // Tox Green
topright->setIconSize(QSize(10,10));
topright->setMinimumSize(25,28);
topright->setFlat(true);
topright->setAutoFillBackground(true);
topright->setPalette(toxgreen);
bottomright->setIconSize(QSize(10,10));
bottomright->setMinimumSize(25,28);
bottomright->setFlat(true);
bottomright->setAutoFillBackground(true);
bottomright->setPalette(toxgreen);
mainLayout->addStretch();
mainLayout->addWidget(pic);
mainLayout->addLayout(infoLayout,3);
mainLayout->addStretch();
mainLayout->addWidget(buttonWidget);
mainLayout->setMargin(0);
mainLayout->setSpacing(0);
infoLayout->addWidget(filename);
infoLayout->addLayout(textLayout);
infoLayout->addWidget(progress);
infoLayout->setMargin(4);
infoLayout->setSpacing(4);
textLayout->addWidget(size);
textLayout->addStretch(0);
textLayout->addWidget(speed);
textLayout->addStretch(0);
textLayout->addWidget(eta);
textLayout->setMargin(2);
textLayout->setSpacing(5);
buttonLayout->addWidget(topright);
buttonLayout->addSpacing(2);
buttonLayout->addWidget(bottomright);
buttonLayout->setContentsMargins(2,0,0,0);
buttonLayout->setSpacing(0);
}
QString FileTransfertWidget::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 FileTransfertWidget::onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction)
{
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
return;
QDateTime newtime = QDateTime::currentDateTime();
int timediff = lastUpdate.secsTo(newtime);
if (timediff <= 0)
return;
qint64 diff = BytesSent - lastBytesSent;
if (diff < 0)
{
qWarning() << "FileTransfertWidget::onFileTransferInfo: Negative transfer speed !";
diff = 0;
}
long rawspeed = diff / timediff;
speed->setText(getHumanReadableSize(rawspeed)+"/s");
size->setText(getHumanReadableSize(Filesize));
if (!rawspeed)
return;
int etaSecs = (Filesize - BytesSent) / rawspeed;
QTime etaTime(0,0);
etaTime = etaTime.addSecs(etaSecs);
eta->setText(etaTime.toString("mm:ss"));
if (!Filesize)
{
progress->setValue(0);
qDebug() << QString("FT: received %1 bytes of an empty file, stop sending sequential devices, zetok!").arg(BytesSent);
}
else
{
progress->setValue(BytesSent*100/Filesize);
qDebug() << QString("FT: received %1/%2 bytes, progress is %3%").arg(BytesSent).arg(Filesize).arg(BytesSent*100/Filesize);
}
lastUpdate = newtime;
lastBytesSent = BytesSent;
}
void FileTransfertWidget::onFileTransferCancelled(int FriendId, int FileNum, ToxFile::FileDirection Direction)
{
if (FileNum != fileNum || FriendId != friendId || Direction != direction)
return;
buttonLayout->setContentsMargins(0,0,0,0);
disconnect(topright);
disconnect(Widget::getInstance()->getCore(),0,this,0);
progress->hide();
speed->hide();
eta->hide();
topright->hide();
bottomright->hide();
QPalette whiteText;
whiteText.setColor(QPalette::WindowText, Qt::white);
filename->setPalette(whiteText);
size->setPalette(whiteText);
this->setObjectName("error");
this->style()->polish(this);
//Toggle window visibility to fix draw order bug
this->hide();
this->show();
}
void FileTransfertWidget::onFileTransferFinished(ToxFile File)
{
if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction)
return;
topright->disconnect();
disconnect(Widget::getInstance()->getCore(),0,this,0);
progress->hide();
speed->hide();
eta->hide();
topright->hide();
bottomright->hide();
buttonLayout->setContentsMargins(0,0,0,0);
QPalette whiteText;
whiteText.setColor(QPalette::WindowText, Qt::white);
filename->setPalette(whiteText);
size->setPalette(whiteText);
this->setObjectName("success");
this->style()->polish(this);
//Toggle window visibility to fix draw order bug
this->hide();
this->show();
if (File.direction == ToxFile::RECEIVING)
{
QPixmap preview;
QFile previewFile(File.filePath);
if (previewFile.open(QIODevice::ReadOnly) && previewFile.size() <= 1024*1024*25) // Don't preview big (>25MiB) images
{
if (preview.loadFromData(previewFile.readAll()))
{
preview = preview.scaledToHeight(40);
pic->setPixmap(preview);
}
previewFile.close();
}
}
}
void FileTransfertWidget::cancelTransfer()
{
Widget::getInstance()->getCore()->cancelFileSend(friendId, fileNum);
}
void FileTransfertWidget::rejectRecvRequest()
{
Widget::getInstance()->getCore()->rejectFileRecvRequest(friendId, fileNum);
onFileTransferCancelled(friendId, fileNum, direction);
}
// 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 isWritable(QString& path)
{
QFile file(path);
bool exists = file.exists();
bool out = file.open(QIODevice::WriteOnly);
file.close();
if (!exists)
file.remove();
return out;
}
void FileTransfertWidget::acceptRecvRequest()
{
QString path;
while (true)
{
path = QFileDialog::getSaveFileName(this, tr("Save a file","Title of the file saving dialog"), QDir::current().filePath(filename->text()));
if (path.isEmpty())
return;
else
{
//bool savable = QFileInfo(path).isWritable();
//qDebug() << path << " is writable: " << savable;
//qDebug() << "/home/bill/bliss.pdf writable: " << QFileInfo("/home/bill/bliss.pdf").isWritable();
if (isWritable(path))
break;
else
QMessageBox::warning(this, 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;
bottomright->setStyleSheet(pauseFileButtonStylesheet);
bottomright->disconnect();
connect(bottomright, SIGNAL(clicked()), this, SLOT(pauseResumeRecv()));
Widget::getInstance()->getCore()->acceptFileRecvRequest(friendId, fileNum, path);
}
void FileTransfertWidget::pauseResumeRecv()
{
Widget::getInstance()->getCore()->pauseResumeFileRecv(friendId, fileNum);
}
void FileTransfertWidget::pauseResumeSend()
{
Widget::getInstance()->getCore()->pauseResumeFileSend(friendId, fileNum);
}
void FileTransfertWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

View File

@ -17,8 +17,8 @@
#include "chatform.h" #include "chatform.h"
#include "friend.h" #include "friend.h"
#include "widget/friendwidget.h" #include "widget/friendwidget.h"
#include "filetransferinstance.h"
#include "widget/widget.h" #include "widget/widget.h"
#include "widget/filetransfertwidget.h"
#include <QScrollBar> #include <QScrollBar>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
@ -43,6 +43,7 @@ ChatForm::ChatForm(Friend* chatFriend)
connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered); connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered);
connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered); connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered);
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
connect(chatWidget, SIGNAL(onFileTranfertInterract(QString,QString)), this, SLOT(onFileTansBtnClicked(QString,QString)));
} }
ChatForm::~ChatForm() ChatForm::~ChatForm()
@ -94,36 +95,20 @@ void ChatForm::startFileSend(ToxFile file)
if (file.friendId != f->friendId) if (file.friendId != f->friendId)
return; return;
QLabel *author = new QLabel(Widget::getInstance()->getUsername()); FileTransferInstance* fileTrans = new FileTransferInstance(file);
QLabel *date = new QLabel(QTime::currentTime().toString("hh:mm")); ftransWidgets.insert(fileTrans->getId(), fileTrans);
QScrollBar* scroll = chatArea->verticalScrollBar();
lockSliderToBottom = scroll && scroll->value() == scroll->maximum();
author->setAlignment(Qt::AlignTop | Qt::AlignRight);
date->setAlignment(Qt::AlignTop);
QPalette pal;
pal.setColor(QPalette::WindowText, Qt::gray);
author->setPalette(pal);
if (previousName.isEmpty() || previousName != author->text())
{
if (curRow)
{
mainChatLayout->setRowStretch(curRow, 0);
mainChatLayout->addItem(new QSpacerItem(0,AUTHOR_CHANGE_SPACING),curRow,0,1,3);
curRow++;
}
mainChatLayout->addWidget(author, curRow, 0);
}
FileTransfertWidget* fileTrans = new FileTransfertWidget(file);
previousName = author->text();
mainChatLayout->addWidget(fileTrans, curRow, 1);
mainChatLayout->addWidget(date, curRow, 3);
mainChatLayout->setRowStretch(curRow+1, 1);
mainChatLayout->setRowStretch(curRow, 0);
curRow++;
connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransfertWidget::onFileTransferInfo); connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo);
connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransfertWidget::onFileTransferCancelled); connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled);
connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransfertWidget::onFileTransferFinished); connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished);
connect(fileTrans, SIGNAL(stateUpdated()), chatWidget, SLOT(updateChatContent()));
QString name = Widget::getInstance()->getUsername();
if (name == previousName)
name = "";
previousName = Widget::getInstance()->getUsername();
chatWidget->insertMessage(new FileTransferAction(fileTrans, name, QTime::currentTime().toString("hh:mm"), true));
} }
void ChatForm::onFileRecvRequest(ToxFile file) void ChatForm::onFileRecvRequest(ToxFile file)
@ -131,33 +116,13 @@ void ChatForm::onFileRecvRequest(ToxFile file)
if (file.friendId != f->friendId) if (file.friendId != f->friendId)
return; return;
QLabel *author = new QLabel(f->getName()); FileTransferInstance* fileTrans = new FileTransferInstance(file);
QLabel *date = new QLabel(QTime::currentTime().toString("hh:mm")); ftransWidgets.insert(fileTrans->getId(), fileTrans);
QScrollBar* scroll = chatArea->verticalScrollBar();
lockSliderToBottom = scroll && scroll->value() == scroll->maximum();
author->setAlignment(Qt::AlignTop | Qt::AlignRight);
date->setAlignment(Qt::AlignTop);
if (previousName.isEmpty() || previousName != author->text())
{
if (curRow)
{
mainChatLayout->setRowStretch(curRow, 0);
mainChatLayout->addItem(new QSpacerItem(0,AUTHOR_CHANGE_SPACING),curRow,0,1,3);
curRow++;
}
mainChatLayout->addWidget(author, curRow, 0);
}
FileTransfertWidget* fileTrans = new FileTransfertWidget(file);
previousName = author->text();
mainChatLayout->addWidget(fileTrans, curRow, 1);
mainChatLayout->addWidget(date, curRow, 3);
mainChatLayout->setRowStretch(curRow+1, 1);
mainChatLayout->setRowStretch(curRow, 0);
curRow++;
connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransfertWidget::onFileTransferInfo); connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo);
connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransfertWidget::onFileTransferCancelled); connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled);
connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransfertWidget::onFileTransferFinished); connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished);
connect(fileTrans, SIGNAL(stateUpdated()), chatWidget, SLOT(updateChatContent()));
Widget* w = Widget::getInstance(); Widget* w = Widget::getInstance();
if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow()) if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow())
@ -166,6 +131,13 @@ void ChatForm::onFileRecvRequest(ToxFile file)
f->hasNewEvents=true; f->hasNewEvents=true;
f->widget->updateStatusLight(); f->widget->updateStatusLight();
} }
QString name = f->getName();
if (name == previousName)
name = "";
previousName = f->getName();
chatWidget->insertMessage(new FileTransferAction(fileTrans, name, QTime::currentTime().toString("hh:mm"), false));
} }
void ChatForm::onAvInvite(int FriendId, int CallId, bool video) void ChatForm::onAvInvite(int FriendId, int CallId, bool video)
@ -461,3 +433,14 @@ void ChatForm::onMicMuteToggle()
} }
} }
} }
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;
}

View File

@ -22,6 +22,7 @@
#include "widget/netcamview.h" #include "widget/netcamview.h"
struct Friend; struct Friend;
class FileTransferInstance;
class ChatForm : public GenericChatForm class ChatForm : public GenericChatForm
{ {
@ -63,6 +64,7 @@ private slots:
void onAnswerCallTriggered(); void onAnswerCallTriggered();
void onHangupCallTriggered(); void onHangupCallTriggered();
void onCancelCallTriggered(); void onCancelCallTriggered();
void onFileTansBtnClicked(QString widgetName, QString buttonName);
private: private:
Friend* f; Friend* f;
@ -70,6 +72,8 @@ private:
NetCamView* netcam; NetCamView* netcam;
bool audioInputFlag; bool audioInputFlag;
int callId; int callId;
QHash<uint, FileTransferInstance*> ftransWidgets;
}; };
#endif // CHATFORM_H #endif // CHATFORM_H

View File

@ -16,21 +16,19 @@
#include "genericchatform.h" #include "genericchatform.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include <QScrollBar>
#include <QFileDialog> #include <QFileDialog>
#include <QTextStream>
#include "smileypack.h" #include "smileypack.h"
#include "widget/emoticonswidget.h" #include "widget/emoticonswidget.h"
#include "style.h" #include "style.h"
#include "widget/widget.h" #include "widget/widget.h"
#include "settings.h"
GenericChatForm::GenericChatForm(QObject *parent) : GenericChatForm::GenericChatForm(QObject *parent) :
QObject(parent) QObject(parent)
{ {
lockSliderToBottom = true;
curRow = 0; curRow = 0;
mainWidget = new QWidget(); headWidget = new QWidget(); chatAreaWidget = new QWidget(); mainWidget = new QWidget(); headWidget = new QWidget();
nameLabel = new QLabel(); nameLabel = new QLabel();
avatarLabel = new QLabel(); avatarLabel = new QLabel();
@ -38,7 +36,10 @@ GenericChatForm::GenericChatForm(QObject *parent) :
headTextLayout = new QVBoxLayout(); headTextLayout = new QVBoxLayout();
QVBoxLayout *mainLayout = new QVBoxLayout(); QVBoxLayout *mainLayout = new QVBoxLayout();
QVBoxLayout *footButtonsSmall = new QVBoxLayout(), *volMicLayout = new QVBoxLayout(); QVBoxLayout *footButtonsSmall = new QVBoxLayout(), *volMicLayout = new QVBoxLayout();
mainChatLayout = new QGridLayout();
chatWidget = new ChatAreaWidget();
chatWidget->document()->setDefaultStyleSheet(Style::get(":ui/chatArea/innerStyle.css"));
chatWidget->setStyleSheet(Style::get(":/ui/chatArea/chatArea.css"));
msgEdit = new ChatTextEdit(); msgEdit = new ChatTextEdit();
@ -51,23 +52,10 @@ GenericChatForm::GenericChatForm(QObject *parent) :
volButton = new QPushButton(); volButton = new QPushButton();
micButton = new QPushButton(); micButton = new QPushButton();
chatArea = new QScrollArea();
QFont bold; QFont bold;
bold.setBold(true); bold.setBold(true);
nameLabel->setFont(bold); nameLabel->setFont(bold);
chatAreaWidget->setLayout(mainChatLayout);
chatAreaWidget->setStyleSheet(Style::get(":/ui/chatArea/chatArea.css"));
chatArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
chatArea->setWidgetResizable(true);
chatArea->setContextMenuPolicy(Qt::CustomContextMenu);
chatArea->setFrameStyle(QFrame::NoFrame);
mainChatLayout->setColumnStretch(1,1);
mainChatLayout->setSpacing(5);
footButtonsSmall->setSpacing(2); footButtonsSmall->setSpacing(2);
msgEdit->setStyleSheet(Style::get(":/ui/msgEdit/msgEdit.css")); msgEdit->setStyleSheet(Style::get(":/ui/msgEdit/msgEdit.css"));
@ -111,7 +99,7 @@ GenericChatForm::GenericChatForm(QObject *parent) :
micButton->setStyleSheet(micButtonStylesheet); micButton->setStyleSheet(micButtonStylesheet);
mainWidget->setLayout(mainLayout); mainWidget->setLayout(mainLayout);
mainLayout->addWidget(chatArea); mainLayout->addWidget(chatWidget);
mainLayout->addLayout(mainFootLayout); mainLayout->addLayout(mainFootLayout);
mainLayout->setMargin(0); mainLayout->setMargin(0);
@ -138,8 +126,6 @@ GenericChatForm::GenericChatForm(QObject *parent) :
headTextLayout->addStretch(); headTextLayout->addStretch();
headTextLayout->addWidget(nameLabel); headTextLayout->addWidget(nameLabel);
chatArea->setWidget(chatAreaWidget);
//Fix for incorrect layouts on OS X as per //Fix for incorrect layouts on OS X as per
//https://bugreports.qt-project.org/browse/QTBUG-14591 //https://bugreports.qt-project.org/browse/QTBUG-14591
sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
@ -147,8 +133,7 @@ GenericChatForm::GenericChatForm(QObject *parent) :
emoteButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); emoteButton->setAttribute(Qt::WA_LayoutUsesWidgetRect);
connect(emoteButton, SIGNAL(clicked()), this, SLOT(onEmoteButtonClicked())); connect(emoteButton, SIGNAL(clicked()), this, SLOT(onEmoteButtonClicked()));
connect(chatArea, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint))); connect(chatWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
connect(chatArea->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onSliderRangeChanged()));
} }
void GenericChatForm::setName(const QString &newName) void GenericChatForm::setName(const QString &newName)
@ -174,16 +159,9 @@ void GenericChatForm::onChatContextMenuRequested(QPoint pos)
menu.exec(pos); menu.exec(pos);
} }
void GenericChatForm::onSliderRangeChanged()
{
QScrollBar* scroll = chatArea->verticalScrollBar();
if (lockSliderToBottom)
scroll->setValue(scroll->maximum());
}
void GenericChatForm::onSaveLogClicked() void GenericChatForm::onSaveLogClicked()
{ {
QString path = QFileDialog::getSaveFileName(0,tr("Save chat log")); QString path = QFileDialog::getSaveFileName(0, tr("Save chat log"));
if (path.isEmpty()) if (path.isEmpty())
return; return;
@ -192,90 +170,21 @@ void GenericChatForm::onSaveLogClicked()
return; return;
QString log; QString log;
QList<QLabel*> labels = chatAreaWidget->findChildren<QLabel*>(); log = chatWidget->toPlainText();
int i=0;
for (QLabel* label : labels)
{
log += label->text();
if (i==2)
{
i=0;
log += '\n';
}
else
{
log += '\t';
i++;
}
}
file.write(log.toUtf8()); file.write(log.toUtf8());
file.close(); file.close();
} }
void GenericChatForm::addMessage(QString author, QString message, QString date) void GenericChatForm::addMessage(QString author, QString message, QDateTime datetime)
{ {
QLabel *authorLabel = new QLabel(author); QString date = datetime.toString(Settings::getInstance().getTimestampFormat());
QLabel *messageLabel = new QLabel(); bool isMe = (author == Widget::getInstance()->getUsername());
QLabel *dateLabel = new QLabel(date);
QScrollBar* scroll = chatArea->verticalScrollBar(); if (previousName == author)
lockSliderToBottom = scroll && scroll->value() == scroll->maximum(); chatWidget->insertMessage(new MessageAction("", message, date, isMe));
authorLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); else chatWidget->insertMessage(new MessageAction(author , message, date, isMe));
dateLabel->setAlignment(Qt::AlignTop); previousName = author;
messageLabel->setWordWrap(true);
messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
authorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
dateLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
if (author == Widget::getInstance()->getUsername())
{
QPalette pal;
pal.setColor(QPalette::WindowText, QColor(100,100,100));
authorLabel->setPalette(pal);
messageLabel->setPalette(pal);
}
if (previousName.isEmpty() || previousName != author)
{
if (curRow)
{
mainChatLayout->setRowStretch(curRow, 0);
mainChatLayout->addItem(new QSpacerItem(0,AUTHOR_CHANGE_SPACING),curRow,0,1,3);
}
previousName = author;
curRow++;
}
else if (curRow)// onSaveLogClicked expects 0 or 3 QLabel per line
authorLabel->setText("");
QColor greentext(61,204,61);
QString fontTemplate = "<font color='%1'>%2</font>";
QString finalMessage;
QStringList messageLines = message.split("\n");
for (QString& s : messageLines)
{
if (QRegExp("^[ ]*>.*").exactMatch(s))
finalMessage += fontTemplate.arg(greentext.name(), toHtmlChars(s));
else
finalMessage += toHtmlChars(s);
finalMessage += "<br>";
}
messageLabel->setText(finalMessage.left(finalMessage.length()-4));
messageLabel->setText(SmileyPack::getInstance().smileyfied(messageLabel->text()));
messageLabel->setTextFormat(Qt::RichText);
mainChatLayout->addWidget(authorLabel, curRow, 0);
mainChatLayout->addWidget(messageLabel, curRow, 1);
mainChatLayout->addWidget(dateLabel, curRow, 3);
mainChatLayout->setRowStretch(curRow+1, 1);
mainChatLayout->setRowStretch(curRow, 0);
curRow++;
authorLabel->setContextMenuPolicy(Qt::CustomContextMenu);
messageLabel->setContextMenuPolicy(Qt::CustomContextMenu);
dateLabel->setContextMenuPolicy(Qt::CustomContextMenu);
connect(authorLabel, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
connect(messageLabel, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
connect(dateLabel, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint)));
} }
GenericChatForm::~GenericChatForm() GenericChatForm::~GenericChatForm()
@ -310,14 +219,3 @@ void GenericChatForm::onEmoteInsertRequested(QString str)
msgEdit->setFocus(); // refocus so that we can continue typing msgEdit->setFocus(); // refocus so that we can continue typing
} }
QString GenericChatForm::toHtmlChars(const QString &str)
{
static QList<QPair<QString, QString>> replaceList = {{"&","&amp;"}, {" ","&nbsp;"}, {">","&gt;"}, {"<","&lt;"}};
QString res = str;
for (auto &it : replaceList)
res = res.replace(it.first,it.second);
return res;
}

View File

@ -18,16 +18,14 @@
#define GENERICCHATFORM_H #define GENERICCHATFORM_H
#include <QObject> #include <QObject>
#include <QLabel>
#include <QPoint> #include <QPoint>
#include <QScrollArea>
#include <QTime> #include <QTime>
#include <QLabel> #include <QLabel>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QPushButton> #include <QPushButton>
#include <QGridLayout>
#include "widget/chatareawidget.h"
#include "widget/tool/chattextedit.h" #include "widget/tool/chattextedit.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
@ -46,7 +44,7 @@ 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);
void addMessage(QString author, QString message, QString date=QTime::currentTime().toString("hh:mm")); void addMessage(QString author, QString message, QDateTime datetime=QDateTime::currentDateTime());
signals: signals:
void sendMessage(int, QString); void sendMessage(int, QString);
@ -55,26 +53,20 @@ public slots:
protected slots: protected slots:
void onChatContextMenuRequested(QPoint pos); void onChatContextMenuRequested(QPoint pos);
void onSliderRangeChanged();
void onSaveLogClicked(); void onSaveLogClicked();
void onEmoteButtonClicked(); void onEmoteButtonClicked();
void onEmoteInsertRequested(QString str); void onEmoteInsertRequested(QString str);
protected: protected:
QLabel *nameLabel, *avatarLabel; QLabel *nameLabel, *avatarLabel;
QWidget *mainWidget, *headWidget, *chatAreaWidget; QWidget *mainWidget, *headWidget;
QScrollArea *chatArea;
QPushButton *fileButton, *emoteButton, *callButton, *videoButton, *volButton, *micButton; QPushButton *fileButton, *emoteButton, *callButton, *videoButton, *volButton, *micButton;
QGridLayout *mainChatLayout;
QVBoxLayout *headTextLayout; QVBoxLayout *headTextLayout;
ChatTextEdit *msgEdit; ChatTextEdit *msgEdit;
QPushButton *sendButton; QPushButton *sendButton;
QString previousName; QString previousName;
ChatAreaWidget *chatWidget;
int curRow; int curRow;
bool lockSliderToBottom;
private:
QString toHtmlChars(const QString &str);
}; };
#endif // GENERICCHATFORM_H #endif // GENERICCHATFORM_H

View File

@ -48,9 +48,6 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
msgEdit->setObjectName("group"); msgEdit->setObjectName("group");
mainChatLayout->setColumnStretch(1,1);
mainChatLayout->setHorizontalSpacing(10);
headTextLayout->addWidget(nusersLabel); headTextLayout->addWidget(nusersLabel);
headTextLayout->addWidget(namesList); headTextLayout->addWidget(namesList);
headTextLayout->setMargin(0); headTextLayout->setMargin(0);

120
widget/tool/chataction.cpp Normal file
View File

@ -0,0 +1,120 @@
/*
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 "smileypack.h"
#include <QStringList>
#include <QBuffer>
QString ChatAction::toHtmlChars(const QString &str)
{
static QList<QPair<QString, QString>> replaceList = {{"&","&amp;"}, {" ","&nbsp;"}, {">","&gt;"}, {"<","&lt;"}};
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::wrapName(const QString &name)
{
if (isMe)
return QString("<td><div class=name_me>" + name + "</div></td>\n");
else
return QString("<td><div class=name>" + name + "</div></td>\n");
}
QString ChatAction::wrapDate(const QString &date)
{
QString res = "<td align=right><div class=date>" + date + "</div></td>\n";
return res;
}
QString ChatAction::wrapMessage(const QString &message)
{
QString res = "<td width=100%><div class=message>" + message + "</div></td>\n";
return res;
}
QString ChatAction::wrapWholeLine(const QString &line)
{
QString res = "<tr>\n" + line + "</tr>\n";
return res;
}
MessageAction::MessageAction(const QString &author, const QString &message, const QString &date, const bool &me) :
ChatAction(me)
{
QString message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message));
QStringList messageLines = message_.split("\n");
message_ = "";
for (QString& s : messageLines)
{
if (QRegExp("^[ ]*&gt;.*").exactMatch(s))
message_ += "<div class=quote>" + s.right(s.length()-4) + "</div><br>";
else
message_ += s + "<br>";
}
message_ = message_.left(message_.length()-4);
content = wrapWholeLine(wrapName(author) + wrapMessage(message_) + wrapDate(date));
}
QString MessageAction::getHtml()
{
return content;
}
FileTransferAction::FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me) :
ChatAction(me),
sender(author),
timestamp(date)
{
w = widget;
}
FileTransferAction::~FileTransferAction()
{
}
QString FileTransferAction::getHtml()
{
QString widgetHtml;
if (w != nullptr)
widgetHtml = w->getHtmlImage();
else
widgetHtml = "<div class=quote>EMPTY CONTENT</div>";
QString res = wrapWholeLine(wrapName(sender) + wrapMessage(widgetHtml) + wrapDate(timestamp));;
return res;
}
QString FileTransferAction::wrapMessage(const QString &message)
{
QString res = "<td width=100%>" + message + "</td>\n";
return res;
}

67
widget/tool/chataction.h Normal file
View File

@ -0,0 +1,67 @@
/*
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 "filetransferinstance.h"
class ChatAction
{
public:
ChatAction(const bool &me) : isMe(me) {;}
virtual ~ChatAction(){;}
virtual QString getHtml() = 0;
protected:
QString toHtmlChars(const QString &str);
QString QImage2base64(const QImage &img);
virtual QString wrapName(const QString &name);
virtual QString wrapDate(const QString &date);
virtual QString wrapMessage(const QString &message);
virtual QString wrapWholeLine(const QString &line);
private:
bool isMe;
};
class MessageAction : public ChatAction
{
public:
MessageAction(const QString &author, const QString &message, const QString &date, const bool &me);
virtual ~MessageAction(){;}
virtual QString getHtml();
private:
QString content;
};
class FileTransferAction : public ChatAction
{
public:
FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me);
virtual ~FileTransferAction();
virtual QString getHtml();
virtual QString wrapMessage(const QString &message);
private:
FileTransferInstance *w;
QString sender, timestamp;
};
#endif // CHATACTION_H