From afa794d92abb2206f356e121794319e09490d002 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 15:42:22 +0200 Subject: [PATCH 01/26] Fix #266 --- widget/chatareawidget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index 9f8633b97..bb3a2904d 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -77,7 +77,7 @@ QString ChatAreaWidget::getHtmledMessages() return res; } -void ChatAreaWidget::insertMessage(ChatAction *msgAction) +void ChatAreaWidget::insertMessage(ChatAction* msgAction) { if (msgAction == nullptr) return; @@ -88,6 +88,8 @@ void ChatAreaWidget::insertMessage(ChatAction *msgAction) moveCursor(QTextCursor::End); moveCursor(QTextCursor::PreviousCell); insertHtml(msgAction->getHtml()); + + delete msgAction; } void ChatAreaWidget::updateChatContent() From 88eb571b9293bc0184b1cac88fafda5c16ba8595 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 10 Sep 2014 09:12:15 -0500 Subject: [PATCH 02/26] minutiae --- .gitignore | 6 ++++++ bootstrap.sh | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index e92651a06..228d0418e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ *.pro.user* libs +*.o +moc_* +ui_* +qrc_* +Makefile +qtox diff --git a/bootstrap.sh b/bootstrap.sh index 2f7067fb6..e2b6c08c4 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -147,6 +147,10 @@ fi popd +if [[ $GLOBAL = "true" ]]; then + sudo ldconfig +fi + ############### cleanup step ############### # remove cloned repositories if [[ $KEEP = "false" ]]; then From 737e76c72fdd966014744b17b80360d9e6258c71 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 16:17:44 +0200 Subject: [PATCH 03/26] Undo #266 fix --- widget/chatareawidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index bb3a2904d..defe316eb 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -89,7 +89,7 @@ void ChatAreaWidget::insertMessage(ChatAction* msgAction) moveCursor(QTextCursor::PreviousCell); insertHtml(msgAction->getHtml()); - delete msgAction; + //delete msgAction; } void ChatAreaWidget::updateChatContent() From 7f2edfae7dbb8ce554d1be8dc8ac6c57fb3f8639 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 18:30:03 +0200 Subject: [PATCH 04/26] Fix #266 --- filetransferinstance.h | 2 ++ widget/chatareawidget.cpp | 49 +++++++-------------------------- widget/chatareawidget.h | 9 ++---- widget/form/chatform.cpp | 3 +- widget/form/genericchatform.cpp | 1 + widget/tool/chataction.cpp | 41 +++++++++++++++++++++++++-- widget/tool/chataction.h | 14 ++++++++-- 7 files changed, 68 insertions(+), 51 deletions(-) diff --git a/filetransferinstance.h b/filetransferinstance.h index ff5d85937..e17f03327 100644 --- a/filetransferinstance.h +++ b/filetransferinstance.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "core.h" @@ -35,6 +36,7 @@ public: explicit FileTransferInstance(ToxFile File); QString getHtmlImage(); uint getId(){return id;} + void setTextCursor(QTextCursor cursor); public slots: void onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction); diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index defe316eb..cb9037e92 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -15,9 +15,11 @@ */ #include "chatareawidget.h" +#include "widget/tool/chataction.h" #include #include #include +#include ChatAreaWidget::ChatAreaWidget(QWidget *parent) : QTextEdit(parent) @@ -30,8 +32,9 @@ ChatAreaWidget::ChatAreaWidget(QWidget *parent) : ChatAreaWidget::~ChatAreaWidget() { - for (ChatAction *it : messages) - delete it; + for (ChatAction* action : messages) + delete action; + messages.clear(); } void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event) @@ -65,50 +68,18 @@ void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event) } } -QString ChatAreaWidget::getHtmledMessages() -{ - QString res("\n"); - - for (ChatAction *it : messages) - { - res += it->getHtml(); - } - res += "
"; - return res; -} - void ChatAreaWidget::insertMessage(ChatAction* msgAction) { if (msgAction == nullptr) return; - messages.append(msgAction); - //updateChatContent(); - moveCursor(QTextCursor::End); moveCursor(QTextCursor::PreviousCell); + QTextCursor cur = textCursor(); + cur.clearSelection(); + cur.setKeepPositionOnInsert(true); insertHtml(msgAction->getHtml()); + msgAction->setTextCursor(cur); - //delete msgAction; -} - -void ChatAreaWidget::updateChatContent() -{ - QScrollBar* scroll = verticalScrollBar(); - lockSliderToBottom = scroll && scroll->value() == scroll->maximum(); - - setUpdatesEnabled(false); - setHtml(getHtmledMessages()); - setUpdatesEnabled(true); - if (lockSliderToBottom) - sliderPosition = scroll->maximum(); - - scroll->setValue(sliderPosition); -} - -void ChatAreaWidget::clearMessages() -{ - for (ChatAction *it : messages) - delete it; - updateChatContent(); + messages.append(msgAction); } diff --git a/widget/chatareawidget.h b/widget/chatareawidget.h index 2d85fc453..a5e531859 100644 --- a/widget/chatareawidget.h +++ b/widget/chatareawidget.h @@ -19,7 +19,9 @@ #include #include -#include "widget/tool/chataction.h" +#include + +class ChatAction; class ChatAreaWidget : public QTextEdit { @@ -28,7 +30,6 @@ public: explicit ChatAreaWidget(QWidget *parent = 0); virtual ~ChatAreaWidget(); void insertMessage(ChatAction *msgAction); - void clearMessages(); signals: void onFileTranfertInterract(QString widgetName, QString buttonName); @@ -36,11 +37,7 @@ signals: protected: void mouseReleaseEvent(QMouseEvent * event); -public slots: - void updateChatContent(); - private: - QString getHtmledMessages(); QList messages; bool lockSliderToBottom; int sliderPosition; diff --git a/widget/form/chatform.cpp b/widget/form/chatform.cpp index 229d6c8d4..d52fd9d53 100644 --- a/widget/form/chatform.cpp +++ b/widget/form/chatform.cpp @@ -19,6 +19,7 @@ #include "widget/friendwidget.h" #include "filetransferinstance.h" #include "widget/widget.h" +#include "widget/tool/chataction.h" #include #include #include @@ -101,7 +102,6 @@ void ChatForm::startFileSend(ToxFile file) connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo); connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled); connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished); - connect(fileTrans, SIGNAL(stateUpdated()), chatWidget, SLOT(updateChatContent())); QString name = Widget::getInstance()->getUsername(); if (name == previousName) @@ -122,7 +122,6 @@ void ChatForm::onFileRecvRequest(ToxFile file) connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo); connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled); connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished); - connect(fileTrans, SIGNAL(stateUpdated()), chatWidget, SLOT(updateChatContent())); Widget* w = Widget::getInstance(); if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow()) diff --git a/widget/form/genericchatform.cpp b/widget/form/genericchatform.cpp index fcffb2385..dd3b793ed 100644 --- a/widget/form/genericchatform.cpp +++ b/widget/form/genericchatform.cpp @@ -22,6 +22,7 @@ #include "style.h" #include "widget/widget.h" #include "settings.h" +#include "widget/tool/chataction.h" GenericChatForm::GenericChatForm(QObject *parent) : QObject(parent) diff --git a/widget/tool/chataction.cpp b/widget/tool/chataction.cpp index c1db67b46..5ac71ca9a 100644 --- a/widget/tool/chataction.cpp +++ b/widget/tool/chataction.cpp @@ -18,6 +18,8 @@ #include "smileypack.h" #include #include +#include +#include "filetransferinstance.h" QString ChatAction::toHtmlChars(const QString &str) { @@ -84,6 +86,17 @@ MessageAction::MessageAction(const QString &author, const QString &message, cons content = wrapWholeLine(wrapName(author) + wrapMessage(message_) + wrapDate(date)); } +void MessageAction::setTextCursor(QTextCursor cursor) +{ + // When this function is called, we're supposed to only update ourselve when needed + // Nobody should ask us to do anything with our content, we're on our own + // Except we never udpate on our own, so we can safely free our resources + + (void) cursor; + content.clear(); + content.squeeze(); +} + QString MessageAction::getHtml() { return content; @@ -95,11 +108,12 @@ FileTransferAction::FileTransferAction(FileTransferInstance *widget, const QStri timestamp(date) { w = widget; + + connect(w, &FileTransferInstance::stateUpdated, this, &FileTransferAction::updateHtml); } FileTransferAction::~FileTransferAction() { - } QString FileTransferAction::getHtml() @@ -109,7 +123,7 @@ QString FileTransferAction::getHtml() widgetHtml = w->getHtmlImage(); else widgetHtml = "
EMPTY CONTENT
"; - QString res = wrapWholeLine(wrapName(sender) + wrapMessage(widgetHtml) + wrapDate(timestamp));; + QString res = wrapWholeLine(wrapName(sender) + wrapMessage(widgetHtml) + wrapDate(timestamp)); return res; } @@ -118,3 +132,26 @@ QString FileTransferAction::wrapMessage(const QString &message) QString res = "" + message + "\n"; return res; } +void FileTransferAction::setTextCursor(QTextCursor cursor) +{ + cur = cursor; + cur.setKeepPositionOnInsert(true); + int end=cur.selectionEnd(); + cur.setPosition(cur.position()); + cur.setPosition(end, QTextCursor::KeepAnchor); +} + +void FileTransferAction::updateHtml() +{ + if (cur.isNull()) + return; + + int pos = cur.selectionStart(); + cur.removeSelectedText(); + cur.setKeepPositionOnInsert(false); + cur.insertHtml(getHtml()); + cur.setKeepPositionOnInsert(true); + int end = cur.position(); + cur.setPosition(pos); + cur.setPosition(end, QTextCursor::KeepAnchor); +} diff --git a/widget/tool/chataction.h b/widget/tool/chataction.h index 0e6d8ac87..22420c85e 100644 --- a/widget/tool/chataction.h +++ b/widget/tool/chataction.h @@ -18,14 +18,17 @@ #define CHATACTION_H #include -#include "filetransferinstance.h" +#include -class ChatAction +class FileTransferInstance; + +class ChatAction : public QObject { public: ChatAction(const bool &me) : isMe(me) {;} virtual ~ChatAction(){;} virtual QString getHtml() = 0; + virtual void setTextCursor(QTextCursor cursor){(void)cursor;} ///< Call once, and then you MUST let the object update itself protected: QString toHtmlChars(const QString &str); @@ -46,6 +49,7 @@ public: MessageAction(const QString &author, const QString &message, const QString &date, const bool &me); virtual ~MessageAction(){;} virtual QString getHtml(); + virtual void setTextCursor(QTextCursor cursor) final; private: QString content; @@ -53,15 +57,21 @@ private: class FileTransferAction : public ChatAction { + Q_OBJECT public: FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me); virtual ~FileTransferAction(); virtual QString getHtml(); virtual QString wrapMessage(const QString &message); + virtual void setTextCursor(QTextCursor cursor) final; + +private slots: + void updateHtml(); private: FileTransferInstance *w; QString sender, timestamp; + QTextCursor cur; }; #endif // CHATACTION_H From 5aa5e266b2ae07c5cfa15e25eb93106d44d5eff9 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 18:46:52 +0200 Subject: [PATCH 05/26] Free some RAM when a file transfer ends --- filetransferinstance.h | 6 ++++-- widget/chatareawidget.cpp | 1 - widget/chatareawidget.h | 1 - widget/tool/chataction.cpp | 12 +++++++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/filetransferinstance.h b/filetransferinstance.h index e17f03327..39e3bf380 100644 --- a/filetransferinstance.h +++ b/filetransferinstance.h @@ -32,11 +32,15 @@ struct ToxFile; class FileTransferInstance : public QObject { Q_OBJECT +public: + enum TransfState {tsPending, tsProcessing, tsPaused, tsFinished, tsCanceled}; + public: explicit FileTransferInstance(ToxFile File); QString getHtmlImage(); uint getId(){return id;} void setTextCursor(QTextCursor cursor); + TransfState getState() {return state;} public slots: void onFileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection Direction); @@ -63,8 +67,6 @@ private: QString wrapIntoForm(const QString &content, const QString &type, const QString &imgAstr, const QString &imgBstr); private: - enum TransfState {tsPending, tsProcessing, tsPaused, tsFinished, tsCanceled}; - static uint Idconter; uint id; diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index cb9037e92..bd261f3bf 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -19,7 +19,6 @@ #include #include #include -#include ChatAreaWidget::ChatAreaWidget(QWidget *parent) : QTextEdit(parent) diff --git a/widget/chatareawidget.h b/widget/chatareawidget.h index a5e531859..cf9026877 100644 --- a/widget/chatareawidget.h +++ b/widget/chatareawidget.h @@ -19,7 +19,6 @@ #include #include -#include class ChatAction; diff --git a/widget/tool/chataction.cpp b/widget/tool/chataction.cpp index 5ac71ca9a..f362e2b81 100644 --- a/widget/tool/chataction.cpp +++ b/widget/tool/chataction.cpp @@ -18,7 +18,6 @@ #include "smileypack.h" #include #include -#include #include "filetransferinstance.h" QString ChatAction::toHtmlChars(const QString &str) @@ -154,4 +153,15 @@ void FileTransferAction::updateHtml() int end = cur.position(); cur.setPosition(pos); cur.setPosition(end, QTextCursor::KeepAnchor); + + // Free our ressources if we'll never need to update again + if (w->getState() == FileTransferInstance::TransfState::tsCanceled + || w->getState() == FileTransferInstance::TransfState::tsFinished) + { + sender.clear(); + sender.squeeze(); + timestamp.clear(); + timestamp.squeeze(); + cur = QTextCursor(); + } } From 1b4afcb93715e1ec3eb79b16beaaabd3120a39a5 Mon Sep 17 00:00:00 2001 From: krepa098 Date: Wed, 10 Sep 2014 16:03:53 +0200 Subject: [PATCH 06/26] detect URLs and show them as hyperlink --- widget/chatareawidget.cpp | 16 ++++++++++++++-- widget/chatareawidget.h | 7 +++++-- widget/tool/chataction.cpp | 20 +++++++++++++++++++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index bd261f3bf..a6762c673 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -19,14 +19,21 @@ #include #include #include +#include ChatAreaWidget::ChatAreaWidget(QWidget *parent) : - QTextEdit(parent) + QTextBrowser(parent) { setReadOnly(true); viewport()->setCursor(Qt::ArrowCursor); setContextMenuPolicy(Qt::CustomContextMenu); setUndoRedoEnabled(false); + + setOpenExternalLinks(false); + setOpenLinks(false); + setAcceptRichText(false); + + connect(this, &ChatAreaWidget::anchorClicked, this, &ChatAreaWidget::onAnchorClicked); } ChatAreaWidget::~ChatAreaWidget() @@ -67,7 +74,12 @@ void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event) } } -void ChatAreaWidget::insertMessage(ChatAction* msgAction) +void ChatAreaWidget::onAnchorClicked(const QUrl &url) +{ + QDesktopServices::openUrl(url); +} + +void ChatAreaWidget::insertMessage(ChatAction *msgAction) { if (msgAction == nullptr) return; diff --git a/widget/chatareawidget.h b/widget/chatareawidget.h index cf9026877..e42f576f1 100644 --- a/widget/chatareawidget.h +++ b/widget/chatareawidget.h @@ -17,12 +17,12 @@ #ifndef CHATAREAWIDGET_H #define CHATAREAWIDGET_H -#include +#include #include class ChatAction; -class ChatAreaWidget : public QTextEdit +class ChatAreaWidget : public QTextBrowser { Q_OBJECT public: @@ -36,6 +36,9 @@ signals: protected: void mouseReleaseEvent(QMouseEvent * event); +public slots: + void onAnchorClicked(const QUrl& url); + private: QList messages; bool lockSliderToBottom; diff --git a/widget/tool/chataction.cpp b/widget/tool/chataction.cpp index f362e2b81..cb59290ae 100644 --- a/widget/tool/chataction.cpp +++ b/widget/tool/chataction.cpp @@ -22,7 +22,7 @@ QString ChatAction::toHtmlChars(const QString &str) { - static QList> replaceList = {{"&","&"}, {" "," "}, {">",">"}, {"<","<"}}; + static QList> replaceList = {{"&","&"}, {">",">"}, {"<","<"}}; QString res = str; for (auto &it : replaceList) @@ -71,6 +71,24 @@ MessageAction::MessageAction(const QString &author, const QString &message, cons { QString message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message)); + // detect urls + QRegExp exp("(www\\.|http[s]?:\\/\\/|ftp:\\/\\/)\\S+"); + int offset = 0; + while ((offset = exp.indexIn(message_, offset)) != -1) + { + QString url = exp.cap(); + + // add scheme if not specified + if (exp.cap(1) == "www.") + url.prepend("http://"); + + QString htmledUrl = QString("%1").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) From c3e55489b03af6ce1f73a1d5fe6a1d111bb276e0 Mon Sep 17 00:00:00 2001 From: krepa098 Date: Wed, 10 Sep 2014 16:05:33 +0200 Subject: [PATCH 07/26] don't accept RichText as input --- widget/tool/chattextedit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/widget/tool/chattextedit.cpp b/widget/tool/chattextedit.cpp index 8f727cfbb..9b51b8fba 100644 --- a/widget/tool/chattextedit.cpp +++ b/widget/tool/chattextedit.cpp @@ -21,6 +21,7 @@ ChatTextEdit::ChatTextEdit(QWidget *parent) : QTextEdit(parent) { setPlaceholderText("Type your message here..."); + setAcceptRichText(false); } void ChatTextEdit::keyPressEvent(QKeyEvent * event) From ff9e0bbf934ce2d26a86dc6e46df80f0fe631a45 Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 11 Sep 2014 01:28:05 +0700 Subject: [PATCH 08/26] autoscroll fix --- widget/chatareawidget.cpp | 16 ++++++++++++++++ widget/chatareawidget.h | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index a6762c673..44d2f6505 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -34,6 +34,7 @@ ChatAreaWidget::ChatAreaWidget(QWidget *parent) : setAcceptRichText(false); connect(this, &ChatAreaWidget::anchorClicked, this, &ChatAreaWidget::onAnchorClicked); + connect(verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(onSliderRangeChanged())); } ChatAreaWidget::~ChatAreaWidget() @@ -84,6 +85,8 @@ void ChatAreaWidget::insertMessage(ChatAction *msgAction) if (msgAction == nullptr) return; + checkSlider(); + moveCursor(QTextCursor::End); moveCursor(QTextCursor::PreviousCell); QTextCursor cur = textCursor(); @@ -94,3 +97,16 @@ void ChatAreaWidget::insertMessage(ChatAction *msgAction) messages.append(msgAction); } + +void ChatAreaWidget::onSliderRangeChanged() +{ + QScrollBar* scroll = verticalScrollBar(); + if (lockSliderToBottom) + scroll->setValue(scroll->maximum()); +} + +void ChatAreaWidget::checkSlider() +{ + QScrollBar* scroll = verticalScrollBar(); + lockSliderToBottom = scroll && scroll->value() == scroll->maximum(); +} diff --git a/widget/chatareawidget.h b/widget/chatareawidget.h index e42f576f1..ebc76fa06 100644 --- a/widget/chatareawidget.h +++ b/widget/chatareawidget.h @@ -36,10 +36,13 @@ signals: protected: void mouseReleaseEvent(QMouseEvent * event); -public slots: +private slots: void onAnchorClicked(const QUrl& url); + void onSliderRangeChanged(); private: + void checkSlider(); + QList messages; bool lockSliderToBottom; int sliderPosition; From 096874d5f4e87b16daadbeceefda6e77c441955e Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 11 Sep 2014 02:22:00 +0700 Subject: [PATCH 09/26] HTML table chat representation replaced by QTextTable one --- widget/chatareawidget.cpp | 29 +++++++++++-- widget/chatareawidget.h | 3 ++ widget/tool/chataction.cpp | 86 ++++++++++++++++---------------------- widget/tool/chataction.h | 23 +++++----- 4 files changed, 73 insertions(+), 68 deletions(-) diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index 44d2f6505..1558ed520 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -33,6 +33,22 @@ ChatAreaWidget::ChatAreaWidget(QWidget *parent) : setOpenLinks(false); setAcceptRichText(false); + chatTextTable = textCursor().insertTable(1,3); + + QTextTableFormat tableFormat; + tableFormat.setColumnWidthConstraints({QTextLength(QTextLength::VariableLength,0), + QTextLength(QTextLength::PercentageLength,100), + QTextLength(QTextLength::VariableLength,0)}); + tableFormat.setBorderStyle(QTextFrameFormat::BorderStyle_None); + chatTextTable->setFormat(tableFormat); + chatTextTable->format().setCellSpacing(2); + chatTextTable->format().setWidth(QTextLength(QTextLength::PercentageLength,100)); + + 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())); } @@ -87,12 +103,17 @@ void ChatAreaWidget::insertMessage(ChatAction *msgAction) checkSlider(); - moveCursor(QTextCursor::End); - moveCursor(QTextCursor::PreviousCell); - QTextCursor cur = textCursor(); + int row = chatTextTable->rows() - 1; + chatTextTable->cellAt(row,0).firstCursorPosition().setBlockFormat(nameFormat); + chatTextTable->cellAt(row,2).firstCursorPosition().setBlockFormat(dateFormat); + QTextCursor cur = chatTextTable->cellAt(row,1).firstCursorPosition(); cur.clearSelection(); cur.setKeepPositionOnInsert(true); - insertHtml(msgAction->getHtml()); + chatTextTable->cellAt(row,0).firstCursorPosition().insertHtml(msgAction->getName()); + chatTextTable->cellAt(row,1).firstCursorPosition().insertHtml(msgAction->getMessage()); + chatTextTable->cellAt(row,2).firstCursorPosition().insertHtml(msgAction->getDate()); + chatTextTable->appendRows(1); + msgAction->setTextCursor(cur); messages.append(msgAction); diff --git a/widget/chatareawidget.h b/widget/chatareawidget.h index ebc76fa06..ffcfd0d5b 100644 --- a/widget/chatareawidget.h +++ b/widget/chatareawidget.h @@ -19,6 +19,7 @@ #include #include +#include class ChatAction; @@ -46,6 +47,8 @@ private: QList messages; bool lockSliderToBottom; int sliderPosition; + QTextTable *chatTextTable; + QTextBlockFormat nameFormat, dateFormat; }; #endif // CHATAREAWIDGET_H diff --git a/widget/tool/chataction.cpp b/widget/tool/chataction.cpp index cb59290ae..92b8a18b3 100644 --- a/widget/tool/chataction.cpp +++ b/widget/tool/chataction.cpp @@ -40,34 +40,42 @@ QString ChatAction::QImage2base64(const QImage &img) return ba.toBase64(); } -QString ChatAction::wrapName(const QString &name) +QString ChatAction::getName() { if (isMe) - return QString("
" + name + "
\n"); + return QString("
" + name + "
"); else - return QString("
" + name + "
\n"); + return QString("
" + name + "
"); } -QString ChatAction::wrapDate(const QString &date) +QString ChatAction::getDate() { - QString res = "
" + date + "
\n"; - return res; -} - -QString ChatAction::wrapMessage(const QString &message) -{ - QString res = "
" + message + "
\n"; - return res; -} - -QString ChatAction::wrapWholeLine(const QString &line) -{ - QString res = "\n" + line + "\n"; + QString res = "
" + date + "
"; return res; } MessageAction::MessageAction(const QString &author, const QString &message, const QString &date, const bool &me) : - ChatAction(me) + ChatAction(me, author, date), + message(message) +{ +} + +void MessageAction::setTextCursor(QTextCursor cursor) +{ + // When this function is called, we're supposed to only update ourselve when needed + // Nobody should ask us to do anything with our content, we're on our own + // Except we never udpate on our own, so we can safely free our resources + + (void) cursor; + message.clear(); + message.squeeze(); + name.clear(); + name.squeeze(); + date.clear(); + date.squeeze(); +} + +QString MessageAction::getMessage() { QString message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message)); @@ -100,29 +108,11 @@ MessageAction::MessageAction(const QString &author, const QString &message, cons } message_ = message_.left(message_.length()-4); - content = wrapWholeLine(wrapName(author) + wrapMessage(message_) + wrapDate(date)); -} - -void MessageAction::setTextCursor(QTextCursor cursor) -{ - // When this function is called, we're supposed to only update ourselve when needed - // Nobody should ask us to do anything with our content, we're on our own - // Except we never udpate on our own, so we can safely free our resources - - (void) cursor; - content.clear(); - content.squeeze(); -} - -QString MessageAction::getHtml() -{ - return content; + return QString("
" + message_ + "
"); } FileTransferAction::FileTransferAction(FileTransferInstance *widget, const QString &author, const QString &date, const bool &me) : - ChatAction(me), - sender(author), - timestamp(date) + ChatAction(me, author, date) { w = widget; @@ -133,22 +123,16 @@ FileTransferAction::~FileTransferAction() { } -QString FileTransferAction::getHtml() +QString FileTransferAction::getMessage() { QString widgetHtml; if (w != nullptr) widgetHtml = w->getHtmlImage(); else widgetHtml = "
EMPTY CONTENT
"; - QString res = wrapWholeLine(wrapName(sender) + wrapMessage(widgetHtml) + wrapDate(timestamp)); - return res; + return widgetHtml; } -QString FileTransferAction::wrapMessage(const QString &message) -{ - QString res = "" + message + "\n"; - return res; -} void FileTransferAction::setTextCursor(QTextCursor cursor) { cur = cursor; @@ -166,7 +150,7 @@ void FileTransferAction::updateHtml() int pos = cur.selectionStart(); cur.removeSelectedText(); cur.setKeepPositionOnInsert(false); - cur.insertHtml(getHtml()); + cur.insertHtml(getMessage()); cur.setKeepPositionOnInsert(true); int end = cur.position(); cur.setPosition(pos); @@ -176,10 +160,10 @@ void FileTransferAction::updateHtml() if (w->getState() == FileTransferInstance::TransfState::tsCanceled || w->getState() == FileTransferInstance::TransfState::tsFinished) { - sender.clear(); - sender.squeeze(); - timestamp.clear(); - timestamp.squeeze(); + name.clear(); + name.squeeze(); + date.clear(); + date.squeeze(); cur = QTextCursor(); } } diff --git a/widget/tool/chataction.h b/widget/tool/chataction.h index 22420c85e..a324a41c6 100644 --- a/widget/tool/chataction.h +++ b/widget/tool/chataction.h @@ -25,22 +25,21 @@ class FileTransferInstance; class ChatAction : public QObject { public: - ChatAction(const bool &me) : isMe(me) {;} + ChatAction(const bool &me, const QString &author, const QString &date) : isMe(me), name(author), date(date) {;} virtual ~ChatAction(){;} - virtual QString getHtml() = 0; virtual void setTextCursor(QTextCursor cursor){(void)cursor;} ///< Call once, and then you MUST let the object update itself + virtual QString getName(); + virtual QString getMessage() = 0; + virtual QString getDate(); + 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: +protected: bool isMe; + QString name, date; }; class MessageAction : public ChatAction @@ -48,11 +47,11 @@ class MessageAction : public ChatAction public: MessageAction(const QString &author, const QString &message, const QString &date, const bool &me); virtual ~MessageAction(){;} - virtual QString getHtml(); + virtual QString getMessage(); virtual void setTextCursor(QTextCursor cursor) final; private: - QString content; + QString message; }; class FileTransferAction : public ChatAction @@ -61,8 +60,7 @@ 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); + virtual QString getMessage(); virtual void setTextCursor(QTextCursor cursor) final; private slots: @@ -70,7 +68,6 @@ private slots: private: FileTransferInstance *w; - QString sender, timestamp; QTextCursor cur; }; From db68afe8ddd865cc96a70b2bad0895d0c0ca8c34 Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 11 Sep 2014 02:25:36 +0700 Subject: [PATCH 10/26] sometimes names are just incredible --- widget/tool/chataction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/tool/chataction.cpp b/widget/tool/chataction.cpp index 92b8a18b3..0847abdda 100644 --- a/widget/tool/chataction.cpp +++ b/widget/tool/chataction.cpp @@ -43,9 +43,9 @@ QString ChatAction::QImage2base64(const QImage &img) QString ChatAction::getName() { if (isMe) - return QString("
" + name + "
"); + return QString("
" + toHtmlChars(name) + "
"); else - return QString("
" + name + "
"); + return QString("
" + toHtmlChars(name) + "
"); } QString ChatAction::getDate() From 87eb5704ec288fee696d73b0131c6a886c3d0191 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 21:34:24 +0200 Subject: [PATCH 11/26] Use homemade endianess conversion for DHT Should help with #237 --- core.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core.cpp b/core.cpp index de5dc66ee..2fc55b45a 100644 --- a/core.cpp +++ b/core.cpp @@ -780,8 +780,9 @@ void Core::bootstrapDht() while (i < (2 - (n>3))) { const Settings::DhtServer& dhtServer = dhtServerList[j % listSize]; + quint16 bitswappedPort = quint16(0|((dhtServer.port&0xff)<<8)|((dhtServer.port&0xff00)>>8)); if (tox_bootstrap_from_address(tox, dhtServer.address.toLatin1().data(), - qToBigEndian(dhtServer.port), CUserId(dhtServer.userId).data()) == 1) + qToBigEndian(bitswappedPort), CUserId(dhtServer.userId).data()) == 1) qDebug() << QString("Core: Bootstraping from ")+dhtServer.name+QString(", addr ")+dhtServer.address.toLatin1().data() +QString(", port ")+QString().setNum(dhtServer.port); else From cb9d626155daf14fb3a4f724ac88596ba1d5d3c4 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 21:43:49 +0200 Subject: [PATCH 12/26] Fix #237, don't use ports in big endian --- core.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core.cpp b/core.cpp index 2fc55b45a..2daf1241f 100644 --- a/core.cpp +++ b/core.cpp @@ -780,9 +780,8 @@ void Core::bootstrapDht() while (i < (2 - (n>3))) { const Settings::DhtServer& dhtServer = dhtServerList[j % listSize]; - quint16 bitswappedPort = quint16(0|((dhtServer.port&0xff)<<8)|((dhtServer.port&0xff00)>>8)); if (tox_bootstrap_from_address(tox, dhtServer.address.toLatin1().data(), - qToBigEndian(bitswappedPort), CUserId(dhtServer.userId).data()) == 1) + dhtServer.port, CUserId(dhtServer.userId).data()) == 1) qDebug() << QString("Core: Bootstraping from ")+dhtServer.name+QString(", addr ")+dhtServer.address.toLatin1().data() +QString(", port ")+QString().setNum(dhtServer.port); else From f16ae3a223a63a460b1ef7e03b4bd1be2d81ca7f Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 21:50:09 +0200 Subject: [PATCH 13/26] Remove unnecessary header --- core.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core.cpp b/core.cpp index 2daf1241f..fe32ff255 100644 --- a/core.cpp +++ b/core.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include From c564367354632dee990c746509d6470179fcf4a7 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 22:23:49 +0200 Subject: [PATCH 14/26] Fix #234 --- widget/form/settingsform.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/widget/form/settingsform.cpp b/widget/form/settingsform.cpp index 193fac4a1..8bd2f1060 100644 --- a/widget/form/settingsform.cpp +++ b/widget/form/settingsform.cpp @@ -31,6 +31,7 @@ SettingsForm::SettingsForm() QFont bold, small; bold.setBold(true); small.setPixelSize(13); + small.setKerning(false); headLabel.setText(tr("User Settings","\"Headline\" of the window")); headLabel.setFont(bold); @@ -42,7 +43,7 @@ SettingsForm::SettingsForm() id.setReadOnly(true); id.setFrameStyle(QFrame::NoFrame); id.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - id.setFixedHeight(id.document()->size().height()); + id.setFixedHeight(id.document()->size().height()*2); videoTest.setText(tr("Test video","Text on a button to test the video/webcam")); enableIPv6.setText(tr("Enable IPv6 (recommended)","Text on a checkbox to enable IPv6")); @@ -90,7 +91,9 @@ SettingsForm::~SettingsForm() void SettingsForm::setFriendAddress(const QString& friendAddress) { - id.setText(friendAddress); + QString txt{friendAddress}; + txt.insert(38,'\n'); + id.setText(txt); } void SettingsForm::show(Ui::MainWindow &ui) From 960fbfff486ee9efbc8a127b276efe0a4debd373 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 22:27:56 +0200 Subject: [PATCH 15/26] Remove \n when copying own tox ID --- widget/form/settingsform.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/widget/form/settingsform.cpp b/widget/form/settingsform.cpp index 8bd2f1060..5417b62d7 100644 --- a/widget/form/settingsform.cpp +++ b/widget/form/settingsform.cpp @@ -118,8 +118,10 @@ void SettingsForm::onEnableIPv6Updated() void SettingsForm::copyIdClicked() { - id.selectAll();; - QApplication::clipboard()->setText(id.toPlainText()); + id.selectAll(); + QString txt = id.toPlainText(); + txt.replace('\n',""); + QApplication::clipboard()->setText(txt); } void SettingsForm::onUseTranslationUpdated() From c76f6a7e73cc38c6319028271e317b9c11db15b2 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 22:53:02 +0200 Subject: [PATCH 16/26] Fix #275 --- widget/form/chatform.cpp | 5 +++++ widget/form/chatform.h | 1 + widget/widget.cpp | 1 + 3 files changed, 7 insertions(+) diff --git a/widget/form/chatform.cpp b/widget/form/chatform.cpp index d52fd9d53..05a986048 100644 --- a/widget/form/chatform.cpp +++ b/widget/form/chatform.cpp @@ -443,3 +443,8 @@ void ChatForm::onFileTansBtnClicked(QString widgetName, QString buttonName) else qDebug() << "no filetransferwidget: " << id; } + +void ChatForm::focusInput() +{ + msgEdit->setFocus(); +} diff --git a/widget/form/chatform.h b/widget/form/chatform.h index 9407f1c11..8fe92b2ce 100644 --- a/widget/form/chatform.h +++ b/widget/form/chatform.h @@ -55,6 +55,7 @@ public slots: void onAvPeerTimeout(int FriendId, int CallId); void onAvMediaChange(int FriendId, int CallId, bool video); void onMicMuteToggle(); + void focusInput(); private slots: void onSendTriggered(); diff --git a/widget/widget.cpp b/widget/widget.cpp index d08ea0fe1..dbdc65af4 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -411,6 +411,7 @@ void Widget::addFriend(int friendId, const QString &userId) connect(newfriend->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); connect(newfriend->widget, SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); connect(newfriend->widget, SIGNAL(copyFriendIdToClipboard(int)), this, SLOT(copyFriendIdToClipboard(int))); + connect(newfriend->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newfriend->chatForm, SLOT(focusInput())); connect(newfriend->chatForm, SIGNAL(sendMessage(int,QString)), core, SLOT(sendMessage(int,QString))); connect(newfriend->chatForm, SIGNAL(sendFile(int32_t, QString, QString, long long)), core, SLOT(sendFile(int32_t, QString, QString, long long))); connect(newfriend->chatForm, SIGNAL(answerCall(int)), core, SLOT(answerCall(int))); From 0ae0d01a6129d15c2d17bade5d20835b5b6733e7 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 10 Sep 2014 23:00:45 +0200 Subject: [PATCH 17/26] Generalize #275 to groupchats --- widget/form/chatform.cpp | 5 ----- widget/form/chatform.h | 1 - widget/form/genericchatform.cpp | 5 +++++ widget/form/genericchatform.h | 1 + widget/widget.cpp | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/widget/form/chatform.cpp b/widget/form/chatform.cpp index 05a986048..d52fd9d53 100644 --- a/widget/form/chatform.cpp +++ b/widget/form/chatform.cpp @@ -443,8 +443,3 @@ void ChatForm::onFileTansBtnClicked(QString widgetName, QString buttonName) else qDebug() << "no filetransferwidget: " << id; } - -void ChatForm::focusInput() -{ - msgEdit->setFocus(); -} diff --git a/widget/form/chatform.h b/widget/form/chatform.h index 8fe92b2ce..9407f1c11 100644 --- a/widget/form/chatform.h +++ b/widget/form/chatform.h @@ -55,7 +55,6 @@ public slots: void onAvPeerTimeout(int FriendId, int CallId); void onAvMediaChange(int FriendId, int CallId, bool video); void onMicMuteToggle(); - void focusInput(); private slots: void onSendTriggered(); diff --git a/widget/form/genericchatform.cpp b/widget/form/genericchatform.cpp index dd3b793ed..7c44f9ba5 100644 --- a/widget/form/genericchatform.cpp +++ b/widget/form/genericchatform.cpp @@ -201,3 +201,8 @@ void GenericChatForm::onEmoteInsertRequested(QString str) msgEdit->setFocus(); // refocus so that we can continue typing } + +void GenericChatForm::focusInput() +{ + msgEdit->setFocus(); +} diff --git a/widget/form/genericchatform.h b/widget/form/genericchatform.h index d8b20281d..9c0e67763 100644 --- a/widget/form/genericchatform.h +++ b/widget/form/genericchatform.h @@ -51,6 +51,7 @@ signals: void sendMessage(int, QString); public slots: + void focusInput(); protected slots: void onChatContextMenuRequested(QPoint pos); diff --git a/widget/widget.cpp b/widget/widget.cpp index dbdc65af4..0f7147f0d 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -663,6 +663,7 @@ Group *Widget::createGroup(int groupId) connect(newgroup->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); connect(newgroup->widget, SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); + connect(newgroup->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->chatForm, SLOT(focusInput())); connect(newgroup->chatForm, SIGNAL(sendMessage(int,QString)), core, SLOT(sendGroupMessage(int,QString))); return newgroup; } From b6848772fc6710e723e57432f09328b612b2c752 Mon Sep 17 00:00:00 2001 From: krepa098 Date: Wed, 10 Sep 2014 21:53:18 +0200 Subject: [PATCH 18/26] converted notification.wav to notification.pcm (raw audio) fixes #256 --- audio/notification.pcm | Bin 0 -> 33924 bytes audio/notification.wav | Bin 67992 -> 0 bytes res.qrc | 2 +- widget/widget.cpp | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 audio/notification.pcm delete mode 100644 audio/notification.wav diff --git a/audio/notification.pcm b/audio/notification.pcm new file mode 100644 index 0000000000000000000000000000000000000000..d57ac783d2f4bf6002bfff4275816c8c4519e643 GIT binary patch literal 33924 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 I{(sH?4=v-J-~a#s diff --git a/res.qrc b/res.qrc index 508fbef28..6104327f8 100644 --- a/res.qrc +++ b/res.qrc @@ -6,7 +6,6 @@ res/DejaVuSans.ttf - audio/notification.wav img/icon.png img/contact.png img/contact_dark.png @@ -134,5 +133,6 @@ ui/fileTransferInstance/emptyLRedFileButton.png ui/fileTransferInstance/emptyRGreenFileButton.png ui/fileTransferInstance/emptyRRedFileButton.png + audio/notification.pcm diff --git a/widget/widget.cpp b/widget/widget.cpp index 0f7147f0d..7be90b784 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -533,7 +533,7 @@ void Widget::newMessageAlert() { QApplication::alert(this); - static QFile sndFile(":audio/notification.wav"); + static QFile sndFile(":audio/notification.pcm"); static QByteArray sndData; if (sndData.isEmpty()) { @@ -544,7 +544,7 @@ void Widget::newMessageAlert() ALuint buffer; alGenBuffers(1, &buffer); - alBufferData(buffer, AL_FORMAT_STEREO16, sndData.data(), sndData.size(), 44100); + alBufferData(buffer, AL_FORMAT_MONO16, sndData.data(), sndData.size(), 44100); alSourcei(core->alMainSource, AL_BUFFER, buffer); alSourcePlay(core->alMainSource); } From f148100fe3fa8d08d34765af176e5c8101f62328 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 10 Sep 2014 17:36:34 -0500 Subject: [PATCH 19/26] simplify compilation --- INSTALL.md | 6 +++--- bootstrap.sh | 2 +- simple_make.sh | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100755 simple_make.sh diff --git a/INSTALL.md b/INSTALL.md index f53adbeb4..b5b2d5f11 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -131,17 +131,17 @@ First of all install the dependencies of Tox Core. Debian: ```bash -sudo apt-get install libtool autotools-dev automake checkinstall check yasm libopus-dev libvpx-dev +sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev ``` Ubuntu: ```bash -sudo apt-get install libtool autotools-dev automake checkinstall check yasm libopus-dev libvpx-dev +sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev ``` Arch Linux: (Arch Linux provides the package "tox-git" in AUR) ```bash -sudo pacman -S --needed yasm opus vpx +sudo pacman -S --needed opus vpx ``` Fedora: diff --git a/bootstrap.sh b/bootstrap.sh index e2b6c08c4..3d9d56003 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -21,7 +21,7 @@ TOX_CORE_DIR=libtoxcore-latest # the default value is 'false' and will be set to 'true' # if this script gets the parameter -t or --tox TOX_ONLY=false -GLOBAL=false +GLOBAL=true KEEP=false if [ -z "$BASE_DIR" ]; then diff --git a/simple_make.sh b/simple_make.sh new file mode 100755 index 000000000..cf2b0234c --- /dev/null +++ b/simple_make.sh @@ -0,0 +1,17 @@ +#! /bin/bash + +if which apt-get; then + sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev \ + libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev +elif which pacman; then + sudo pacman -S --needed base-devel qt5 opencv openal opus vpx +elif which yum; then + yum groupinstall "Development Tools" + yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel +else + echo "Unknown package manager, attempting to compile anyways" +fi + +./bootstrap.sh +qmake +make From 26e25ffe3a7e9ee530d4bf5222a30c7f1259f52c Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 10 Sep 2014 17:41:46 -0500 Subject: [PATCH 20/26] change bootstrap.sh --- bootstrap.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 3d9d56003..60bd2eeb4 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -49,8 +49,8 @@ while [ $# -ge 1 ] ; do if [ ${1} = "-t" -o ${1} = "--tox" ] ; then TOX_ONLY=true shift - elif [ ${1} = "-g" -o ${1} = "--global" ] ; then - GLOBAL=true + elif [ ${1} = "-l" -o ${1} = "--local" ] ; then + GLOBAL=false shift elif [ ${1} = "-k" -o ${1} = "--keep" ]; then KEEP=true @@ -71,8 +71,7 @@ while [ $# -ge 1 ] ; do echo " -h|--help : displays this help" echo " -t|--tox : only install/update libtoxcore" echo " requires an already installed libsodium" - echo " -g|--global: installs libtox* and libsodium globally" - echo " (also disables local configure prefixes)" + echo " -l|--local: installs libtox* and libsodium in the current directory" echo " -k|--keep : does not delete the build directories afterwards" echo "" echo "example usages:" From 9e97011a3950789e76730fe1c9ec87360ea3c050 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 10 Sep 2014 17:44:12 -0500 Subject: [PATCH 21/26] sigh --- bootstrap.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bootstrap.sh b/bootstrap.sh index 60bd2eeb4..9c67a7af9 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -71,7 +71,8 @@ while [ $# -ge 1 ] ; do echo " -h|--help : displays this help" echo " -t|--tox : only install/update libtoxcore" echo " requires an already installed libsodium" - echo " -l|--local: installs libtox* and libsodium in the current directory" + echo " -l|--local : installs libtox* and libsodium in the current directory," + echo " as opposed to the system directories" echo " -k|--keep : does not delete the build directories afterwards" echo "" echo "example usages:" From e91846e821fd4a29b8e71e810ee35d4e5b06a1cb Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 11 Sep 2014 13:40:27 +0200 Subject: [PATCH 22/26] Fix #205 --- core.cpp | 8 ++++++++ core.h | 1 + widget/form/addfriendform.cpp | 8 +++++++- widget/widget.cpp | 3 ++- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core.cpp b/core.cpp index fe32ff255..54bb1520a 100644 --- a/core.cpp +++ b/core.cpp @@ -701,6 +701,14 @@ void Core::setUsername(const QString& username) } } +QString Core::getSelfId() +{ + uint8_t friendAddress[TOX_FRIEND_ADDRESS_SIZE]; + tox_get_address(tox, friendAddress); + + return CFriendAddress::toString(friendAddress); +} + QString Core::getStatusMessage() { int size = tox_get_self_status_message_size(tox); diff --git a/core.h b/core.h index e22bd183d..af9fab8a3 100644 --- a/core.h +++ b/core.h @@ -127,6 +127,7 @@ public: QString getUsername(); QString getStatusMessage(); + QString getSelfId(); void increaseVideoBusyness(); void decreaseVideoBusyness(); diff --git a/widget/form/addfriendform.cpp b/widget/form/addfriendform.cpp index 9d09e0682..89bb0935d 100644 --- a/widget/form/addfriendform.cpp +++ b/widget/form/addfriendform.cpp @@ -19,6 +19,8 @@ #include #include #include +#include "widget/widget.h" +#include "core.h" #define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE @@ -74,6 +76,7 @@ bool AddFriendForm::isToxId(const QString &value) const void AddFriendForm::showWarning(const QString &message) const { QMessageBox warning(main); + warning.setWindowTitle("Tox"); warning.setText(message); warning.setIcon(QMessageBox::Warning); warning.exec(); @@ -92,7 +95,10 @@ void AddFriendForm::onSendTriggered() if (id.isEmpty()) { showWarning(tr("Please fill in a valid Tox ID","Tox ID of the friend you're sending a friend request to")); } else if (isToxId(id)) { - emit friendRequested(id, getMessage()); + if (id.toUpper() == Widget::getInstance()->getCore()->getSelfId().toUpper()) + showWarning(tr("You can't add yourself as a friend !","When trying to add your own Tox ID as friend")); + else + emit friendRequested(id, getMessage()); this->toxId.setText(""); this->message.setText(""); } else { diff --git a/widget/widget.cpp b/widget/widget.cpp index 7be90b784..89237158e 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -404,7 +404,8 @@ void Widget::setStatusMessage(const QString &statusMessage) void Widget::addFriend(int friendId, const QString &userId) { - qDebug() << "Adding friend with id "+userId; + + qDebug() << "Widget: Adding friend with id "+userId; Friend* newfriend = FriendList::addFriend(friendId, userId); QLayout* layout = contactListWidget->getFriendLayout(Status::Offline); layout->addWidget(newfriend->widget); From a7c75e63a562466a61539f2bfc15d9e6f455d4da Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 11 Sep 2014 15:44:34 +0200 Subject: [PATCH 23/26] Clean code, improve compilation speed --- cdata.cpp | 6 +++ cdata.h | 7 ++- core.cpp | 11 +++- core.h | 94 +++------------------------------ coreav.cpp | 14 ++--- coreav.h | 29 ++++++++++ coredefines.h | 15 ++++++ corestructs.cpp | 19 +++++++ corestructs.h | 54 +++++++++++++++++++ cstring.cpp | 1 + cstring.h | 3 +- filetransferinstance.cpp | 25 ++++----- filetransferinstance.h | 10 +--- friend.cpp | 1 + friend.h | 4 +- friendlist.cpp | 2 +- friendlist.h | 7 ++- group.cpp | 15 +++--- group.h | 6 +-- grouplist.cpp | 2 +- grouplist.h | 9 ++-- main.cpp | 45 +--------------- qtox.pro | 8 ++- settings.cpp | 2 +- settings.h | 3 +- smileypack.cpp | 10 +++- style.h | 2 +- widget/camera.cpp | 7 ++- widget/camera.h | 1 + widget/chatareawidget.cpp | 4 +- widget/chatareawidget.h | 2 +- widget/croppinglabel.cpp | 1 + widget/croppinglabel.h | 3 +- widget/form/addfriendform.cpp | 4 +- widget/form/addfriendform.h | 8 +-- widget/form/chatform.cpp | 32 ++++++----- widget/form/chatform.h | 4 +- widget/form/filesform.cpp | 5 ++ widget/form/filesform.h | 11 ++-- widget/form/genericchatform.cpp | 2 + widget/form/genericchatform.h | 17 +++--- widget/form/groupchatform.cpp | 4 +- widget/form/groupchatform.h | 3 +- widget/form/settingsform.cpp | 1 + widget/form/settingsform.h | 8 +-- widget/friendlistwidget.cpp | 1 + widget/friendlistwidget.h | 7 ++- widget/friendwidget.cpp | 5 +- widget/friendwidget.h | 3 -- widget/groupwidget.h | 1 - widget/netcamview.cpp | 7 ++- widget/netcamview.h | 7 ++- widget/selfcamview.cpp | 23 ++++---- widget/selfcamview.h | 11 ++-- widget/widget.cpp | 11 ++-- widget/widget.h | 14 ++--- 56 files changed, 323 insertions(+), 288 deletions(-) create mode 100644 coreav.h create mode 100644 coredefines.h create mode 100644 corestructs.cpp create mode 100644 corestructs.h diff --git a/cdata.cpp b/cdata.cpp index 3ab947a51..65f81d842 100644 --- a/cdata.cpp +++ b/cdata.cpp @@ -15,6 +15,8 @@ */ #include "cdata.h" +#include +#include // CData @@ -54,6 +56,8 @@ uint16_t CData::fromString(const QString& data, uint8_t* cData) // CUserId +const uint16_t CUserId::SIZE{TOX_CLIENT_ID_SIZE}; + CUserId::CUserId(const QString &userId) : CData(userId, SIZE) { @@ -68,6 +72,8 @@ QString CUserId::toString(const uint8_t* cUserId) // CFriendAddress +const uint16_t CFriendAddress::SIZE{TOX_FRIEND_ADDRESS_SIZE}; + CFriendAddress::CFriendAddress(const QString &friendAddress) : CData(friendAddress, SIZE) { diff --git a/cdata.h b/cdata.h index cd9558667..6433e9bac 100644 --- a/cdata.h +++ b/cdata.h @@ -18,9 +18,8 @@ #define CDATA_H #include -#include -#include "tox/tox.h" +class QString; class CData { public: @@ -48,7 +47,7 @@ public: static QString toString(const uint8_t *cUserId); private: - static const uint16_t SIZE = TOX_CLIENT_ID_SIZE; + static const uint16_t SIZE; }; @@ -60,7 +59,7 @@ public: static QString toString(const uint8_t* cFriendAddress); private: - static const uint16_t SIZE = TOX_FRIEND_ADDRESS_SIZE; + static const uint16_t SIZE; }; diff --git a/core.cpp b/core.cpp index 54bb1520a..607da59c6 100644 --- a/core.cpp +++ b/core.cpp @@ -20,6 +20,8 @@ #include "settings.h" #include "widget/widget.h" +#include + #include #include @@ -29,7 +31,9 @@ #include #include #include -#include +#include +#include +#include const QString Core::CONFIG_FILE_NAME = "data"; QList Core::fileSendQueue; @@ -111,6 +115,11 @@ Core::~Core() alcCaptureCloseDevice(alInDev); } +Core* Core::getInstance() +{ + return Widget::getInstance()->getCore(); +} + void Core::start() { // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options. diff --git a/core.h b/core.h index af9fab8a3..6ec505468 100644 --- a/core.h +++ b/core.h @@ -17,102 +17,24 @@ #ifndef CORE_HPP #define CORE_HPP -#include -#include - -#if defined(__APPLE__) && defined(__MACH__) - #include - #include -#else - #include - #include -#endif - #include -#include #include -#include -#include -#include -#include -#include -#define TOXAV_MAX_CALLS 16 -#define GROUPCHAT_MAX_SIZE 32 -#define TOX_SAVE_INTERVAL 30*1000 -#define TOX_FILE_INTERVAL 0 -#define TOX_BOOTSTRAP_INTERVAL 5*1000 -#define TOXAV_RINGING_TIME 15 - -// TODO: Put that in the settings -#define TOXAV_MAX_VIDEO_WIDTH 1600 -#define TOXAV_MAX_VIDEO_HEIGHT 1200 +#include "corestructs.h" +#include "coreav.h" +#include "coredefines.h" +template class QList; class Camera; - -enum class Status : int {Online = 0, Away, Busy, Offline}; - -struct DhtServer -{ - QString name; - QString userId; - QString address; - int port; -}; - -struct ToxFile -{ - enum FileStatus - { - STOPPED, - PAUSED, - TRANSMITTING - }; - - enum FileDirection : bool - { - SENDING, - RECEIVING - }; - - ToxFile()=default; - ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction) - : fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)}, - bytesSent{0}, filesize{0}, status{STOPPED}, direction{Direction}, sendTimer{nullptr} {} - ~ToxFile(){} - void setFilePath(QString path) {filePath=path; file->setFileName(path);} - bool open(bool write) {return write?file->open(QIODevice::ReadWrite):file->open(QIODevice::ReadOnly);} - - int fileNum; - int friendId; - QByteArray fileName; - QString filePath; - QFile* file; - long long bytesSent; - long long filesize; - FileStatus status; - FileDirection direction; - QTimer* sendTimer; -}; - -struct ToxCall -{ -public: - ToxAvCSettings codecSettings; - QTimer *sendAudioTimer, *sendVideoTimer; - int callId; - int friendId; - bool videoEnabled; - bool active; - bool muteMic; - ALuint alSource; -}; +class QTimer; +class QString; class Core : public QObject { Q_OBJECT public: explicit Core(Camera* cam, QThread* coreThread); + static Core* getInstance(); ///< Returns the global widget's Core instance ~Core(); int getGroupNumberPeers(int groupId) const; @@ -298,7 +220,7 @@ private: QList dhtServerList; int dhtServerId; static QList fileSendQueue, fileRecvQueue; - static ToxCall calls[TOXAV_MAX_CALLS]; + static ToxCall calls[]; static const QString CONFIG_FILE_NAME; static const int videobufsize; diff --git a/coreav.cpp b/coreav.cpp index 2a3ce7a67..9244fe433 100644 --- a/coreav.cpp +++ b/coreav.cpp @@ -15,7 +15,9 @@ */ #include "core.h" -#include "widget/widget.h" +#include "widget/camera.h" +#include +#include ToxCall Core::calls[TOXAV_MAX_CALLS]; const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4}; @@ -55,7 +57,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled if (calls[callId].videoEnabled) { calls[callId].sendVideoTimer->start(); - Widget::getInstance()->getCamera()->suscribe(); + Camera::getInstance()->suscribe(); } } @@ -71,12 +73,12 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core) { calls[callId].videoEnabled = false; calls[callId].sendVideoTimer->stop(); - Widget::getInstance()->getCamera()->unsuscribe(); + Camera::getInstance()->unsuscribe(); emit ((Core*)core)->avMediaChange(friendId, callId, false); } else { - Widget::getInstance()->getCamera()->suscribe(); + Camera::getInstance()->suscribe(); calls[callId].videoEnabled = true; calls[callId].sendVideoTimer->start(); emit ((Core*)core)->avMediaChange(friendId, callId, true); @@ -159,7 +161,7 @@ void Core::cleanupCall(int callId) calls[callId].sendAudioTimer->stop(); calls[callId].sendVideoTimer->stop(); if (calls[callId].videoEnabled) - Widget::getInstance()->getCamera()->unsuscribe(); + Camera::getInstance()->unsuscribe(); alcCaptureStop(alInDev); } @@ -224,7 +226,7 @@ void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img, void *user_da if (videoBusyness >= 1) qWarning() << "Core: playCallVideo: Busy, dropping current frame"; else - emit Widget::getInstance()->getCore()->videoFrameReceived(img); + emit Core::getInstance()->videoFrameReceived(img); vpx_img_free(img); } diff --git a/coreav.h b/coreav.h new file mode 100644 index 000000000..ea563d6ce --- /dev/null +++ b/coreav.h @@ -0,0 +1,29 @@ +#ifndef COREAV_H +#define COREAV_H + +#include + +#if defined(__APPLE__) && defined(__MACH__) + #include + #include +#else + #include + #include +#endif + +class QTimer; + +struct ToxCall +{ +public: + ToxAvCSettings codecSettings; + QTimer *sendAudioTimer, *sendVideoTimer; + int callId; + int friendId; + bool videoEnabled; + bool active; + bool muteMic; + ALuint alSource; +}; + +#endif // COREAV_H diff --git a/coredefines.h b/coredefines.h new file mode 100644 index 000000000..cb6fcffed --- /dev/null +++ b/coredefines.h @@ -0,0 +1,15 @@ +#ifndef COREDEFINES_H +#define COREDEFINES_H + +#define TOXAV_MAX_CALLS 16 +#define GROUPCHAT_MAX_SIZE 32 +#define TOX_SAVE_INTERVAL 30*1000 +#define TOX_FILE_INTERVAL 0 +#define TOX_BOOTSTRAP_INTERVAL 5*1000 +#define TOXAV_RINGING_TIME 15 + +// TODO: Put that in the settings +#define TOXAV_MAX_VIDEO_WIDTH 1600 +#define TOXAV_MAX_VIDEO_HEIGHT 1200 + +#endif // COREDEFINES_H diff --git a/corestructs.cpp b/corestructs.cpp new file mode 100644 index 000000000..e7c8245b0 --- /dev/null +++ b/corestructs.cpp @@ -0,0 +1,19 @@ +#include "corestructs.h" +#include + +ToxFile::ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction) + : fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)}, + bytesSent{0}, filesize{0}, status{STOPPED}, direction{Direction}, sendTimer{nullptr} +{ +} + +void ToxFile::setFilePath(QString path) +{ + filePath=path; + file->setFileName(path); +} + +bool ToxFile::open(bool write) +{ + return write ? file->open(QIODevice::ReadWrite) : file->open(QIODevice::ReadOnly); +} diff --git a/corestructs.h b/corestructs.h new file mode 100644 index 000000000..ebaab4534 --- /dev/null +++ b/corestructs.h @@ -0,0 +1,54 @@ +#ifndef CORESTRUCTS_H +#define CORESTRUCTS_H + +// Some headers use Core structs but don't need to include all of core.h +// They should include this file directly instead to reduce compilation times + +#include +class QFile; +class QTimer; + +enum class Status : int {Online = 0, Away, Busy, Offline}; + +struct DhtServer +{ + QString name; + QString userId; + QString address; + int port; +}; + +struct ToxFile +{ + enum FileStatus + { + STOPPED, + PAUSED, + TRANSMITTING + }; + + enum FileDirection : bool + { + SENDING, + RECEIVING + }; + + ToxFile()=default; + ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction); + ~ToxFile(){} + void setFilePath(QString path); + bool open(bool write); + + int fileNum; + int friendId; + QByteArray fileName; + QString filePath; + QFile* file; + long long bytesSent; + long long filesize; + FileStatus status; + FileDirection direction; + QTimer* sendTimer; +}; + +#endif // CORESTRUCTS_H diff --git a/cstring.cpp b/cstring.cpp index 7bcd29cb3..6d7815e9c 100644 --- a/cstring.cpp +++ b/cstring.cpp @@ -15,6 +15,7 @@ */ #include "cstring.h" +#include CString::CString(const QString& string) { diff --git a/cstring.h b/cstring.h index 8d6e7efd3..a75ed6468 100644 --- a/cstring.h +++ b/cstring.h @@ -18,7 +18,8 @@ #define CSTRING_H #include -#include + +class QString; class CString { diff --git a/filetransferinstance.cpp b/filetransferinstance.cpp index e927545c6..094df87a9 100644 --- a/filetransferinstance.cpp +++ b/filetransferinstance.cpp @@ -15,15 +15,12 @@ */ #include "filetransferinstance.h" -#include "widget/widget.h" #include "core.h" -#include "math.h" -#include "style.h" +#include #include -#include -#include #include #include +#include uint FileTransferInstance::Idconter = 0; @@ -93,7 +90,7 @@ void FileTransferInstance::onFileTransferCancelled(int FriendId, int FileNum, To { if (FileNum != fileNum || FriendId != friendId || Direction != direction) return; - disconnect(Widget::getInstance()->getCore(),0,this,0); + disconnect(Core::getInstance(),0,this,0); state = tsCanceled; emit stateUpdated(); @@ -103,7 +100,7 @@ void FileTransferInstance::onFileTransferFinished(ToxFile File) { if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction) return; - disconnect(Widget::getInstance()->getCore(),0,this,0); + disconnect(Core::getInstance(),0,this,0); if (File.direction == ToxFile::RECEIVING) { @@ -126,14 +123,14 @@ void FileTransferInstance::onFileTransferFinished(ToxFile File) void FileTransferInstance::cancelTransfer() { - Widget::getInstance()->getCore()->cancelFileSend(friendId, fileNum); + Core::getInstance()->cancelFileSend(friendId, fileNum); state = tsCanceled; emit stateUpdated(); } void FileTransferInstance::rejectRecvRequest() { - Widget::getInstance()->getCore()->rejectFileRecvRequest(friendId, fileNum); + Core::getInstance()->rejectFileRecvRequest(friendId, fileNum); onFileTransferCancelled(friendId, fileNum, direction); state = tsCanceled; emit stateUpdated(); @@ -159,7 +156,7 @@ 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)); + path = QFileDialog::getSaveFileName(0, tr("Save a file","Title of the file saving dialog"), QDir::current().filePath(filename)); if (path.isEmpty()) return; else @@ -170,13 +167,13 @@ void FileTransferInstance::acceptRecvRequest() 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")); + 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; - Widget::getInstance()->getCore()->acceptFileRecvRequest(friendId, fileNum, path); + Core::getInstance()->acceptFileRecvRequest(friendId, fileNum, path); state = tsProcessing; emit stateUpdated(); @@ -184,7 +181,7 @@ void FileTransferInstance::acceptRecvRequest() void FileTransferInstance::pauseResumeRecv() { - Widget::getInstance()->getCore()->pauseResumeFileRecv(friendId, fileNum); + Core::getInstance()->pauseResumeFileRecv(friendId, fileNum); if (state == tsProcessing) state = tsPaused; else state = tsProcessing; @@ -193,7 +190,7 @@ void FileTransferInstance::pauseResumeRecv() void FileTransferInstance::pauseResumeSend() { - Widget::getInstance()->getCore()->pauseResumeFileSend(friendId, fileNum); + Core::getInstance()->pauseResumeFileSend(friendId, fileNum); if (state == tsProcessing) state = tsPaused; else state = tsProcessing; diff --git a/filetransferinstance.h b/filetransferinstance.h index 39e3bf380..6e48d28cf 100644 --- a/filetransferinstance.h +++ b/filetransferinstance.h @@ -17,15 +17,10 @@ #define FILETRANSFERINSTANCE_H #include -#include -#include -#include -#include -#include #include -#include +#include -#include "core.h" +#include "corestructs.h" struct ToxFile; @@ -39,7 +34,6 @@ public: explicit FileTransferInstance(ToxFile File); QString getHtmlImage(); uint getId(){return id;} - void setTextCursor(QTextCursor cursor); TransfState getState() {return state;} public slots: diff --git a/friend.cpp b/friend.cpp index ec8dd3860..6676cea81 100644 --- a/friend.cpp +++ b/friend.cpp @@ -17,6 +17,7 @@ #include "friend.h" #include "friendlist.h" #include "widget/friendwidget.h" +#include "widget/form/chatform.h" Friend::Friend(int FriendId, QString UserId) : friendId(FriendId), userId(UserId) diff --git a/friend.h b/friend.h index 8a3a2427a..390235657 100644 --- a/friend.h +++ b/friend.h @@ -18,9 +18,10 @@ #define FRIEND_H #include -#include "widget/form/chatform.h" +#include "corestructs.h" struct FriendWidget; +class ChatForm; struct Friend { @@ -38,7 +39,6 @@ public: ChatForm* chatForm; int hasNewEvents; Status friendStatus; - QPixmap avatar; }; #endif // FRIEND_H diff --git a/friendlist.cpp b/friendlist.cpp index 6547c408c..1c16c77d4 100644 --- a/friendlist.cpp +++ b/friendlist.cpp @@ -21,7 +21,7 @@ QList FriendList::friendList; -Friend* FriendList::addFriend(int friendId, QString userId) +Friend* FriendList::addFriend(int friendId, const QString& userId) { for (Friend* f : friendList) if (f->friendId == friendId) diff --git a/friendlist.h b/friendlist.h index b0f1279b7..bde30526d 100644 --- a/friendlist.h +++ b/friendlist.h @@ -17,16 +17,15 @@ #ifndef FRIENDLIST_H #define FRIENDLIST_H -#include -#include - +template class QList; struct Friend; +class QString; class FriendList { public: FriendList(); - static Friend* addFriend(int friendId, QString userId); + static Friend* addFriend(int friendId, const QString& userId); static Friend* findFriend(int friendId); static void removeFriend(int friendId); diff --git a/group.cpp b/group.cpp index 88e8a13a7..4898f525a 100644 --- a/group.cpp +++ b/group.cpp @@ -19,18 +19,18 @@ #include "widget/form/groupchatform.h" #include "friendlist.h" #include "friend.h" -#include "widget/widget.h" #include "core.h" #include +#include Group::Group(int GroupId, QString Name) - : groupId(GroupId), nPeers{0}, hasPeerInfo{false} + : groupId(GroupId), nPeers{0}, hasPeerInfo{false}, peerInfoTimer{new QTimer} { widget = new GroupWidget(groupId, Name); chatForm = new GroupChatForm(this); - connect(&peerInfoTimer, SIGNAL(timeout()), this, SLOT(queryPeerInfo())); - peerInfoTimer.setInterval(500); - peerInfoTimer.setSingleShot(false); + connect(peerInfoTimer, SIGNAL(timeout()), this, SLOT(queryPeerInfo())); + peerInfoTimer->setInterval(500); + peerInfoTimer->setSingleShot(false); //peerInfoTimer.start(); //in groupchats, we only notify on messages containing your name @@ -42,11 +42,12 @@ Group::~Group() { delete chatForm; delete widget; + delete peerInfoTimer; } void Group::queryPeerInfo() { - const Core* core = Widget::getInstance()->getCore(); + const Core* core = Core::getInstance(); int nPeersResult = core->getGroupNumberPeers(groupId); if (nPeersResult == -1) { @@ -86,7 +87,7 @@ void Group::queryPeerInfo() { qDebug() << "Group::queryPeerInfo: Successfully loaded names"; hasPeerInfo = true; - peerInfoTimer.stop(); + peerInfoTimer->stop(); } } diff --git a/group.h b/group.h index aa996978e..fed4b11ee 100644 --- a/group.h +++ b/group.h @@ -17,15 +17,15 @@ #ifndef GROUP_H #define GROUP_H -#include #include -#include +#include #define RETRY_PEER_INFO_INTERVAL 500 struct Friend; class GroupWidget; class GroupChatForm; +class QTimer; class Group : public QObject { @@ -47,7 +47,7 @@ public: GroupWidget* widget; GroupChatForm* chatForm; bool hasPeerInfo; - QTimer peerInfoTimer; + QTimer* peerInfoTimer; int hasNewMessages, userWasMentioned; }; diff --git a/grouplist.cpp b/grouplist.cpp index 767faa88f..851ae5b37 100644 --- a/grouplist.cpp +++ b/grouplist.cpp @@ -19,7 +19,7 @@ QList GroupList::groupList; -Group* GroupList::addGroup(int groupId, QString name) +Group* GroupList::addGroup(int groupId, const QString& name) { Group* newGroup = new Group(groupId, name); groupList.append(newGroup); diff --git a/grouplist.h b/grouplist.h index 313ec9b0d..a2bd5a887 100644 --- a/grouplist.h +++ b/grouplist.h @@ -17,17 +17,16 @@ #ifndef GROUPLIST_H #define GROUPLIST_H -#include -#include -#include - +template +class QList; class Group; +class QString; class GroupList { public: GroupList(); - static Group* addGroup(int groupId, QString name); + static Group* addGroup(int groupId, const QString& name); static Group* findGroup(int groupId); static void removeGroup(int groupId); diff --git a/main.cpp b/main.cpp index d2d34b138..1cdac827e 100644 --- a/main.cpp +++ b/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include int main(int argc, char *argv[]) { @@ -31,7 +32,7 @@ int main(int argc, char *argv[]) if (Settings::getInstance().getUseTranslations()) { QString locale = QLocale::system().name().section('_', 0, 0); - if (translator.load(locale,":translations/")) + if (locale=="en" || translator.load(locale,":translations/")) qDebug() << "Loaded translation "+locale; else qDebug() << "Error loading translation "+locale; @@ -50,45 +51,3 @@ int main(int argc, char *argv[]) return errorcode; } - -/** TODO - * ">using a dedicated tool to maintain a TODO list" edition - * - * QRC FILES DO YOU EVEN INTO THEM ? Fix it soon for packaging and Urras. - * Most cameras use YUYV, implement YUYV -> YUV240 - * Sending large files (~380MB) "restarts" after ~10MB. Goes back to 0%, consumes twice as much ram (reloads the file?) - * => Don't load the whole file at once, load small chunks (25MB?) when needed, then free them and load the next - * Don't do anything if a friend is disconnected, don't print to the chat - * Changing online/away/busy/offline by clicking the bubble - * /me action messages - * Popup windows for adding friends and changing settings - * And logging of the chat - * Show the picture's size between name and size after transfer completion if it's a pic - * Adjust all status icons to match the mockup, including scooting the friendslist ones to the left and making the user one the same size - * Sidepanel (friendlist) should be resizeable - * An extra side panel for groupchats, like Venom does (?) - * - * In the file transfer widget: - * >There is more padding on the left side compared to the right. - * >Maybe put the file size should be in the same row as the name. - * >Right-align the ETA. - * - */ - -/** NAMES : -Botox -Ricin -Anthrax -Sarin -Cyanide -Polonium -Mercury -Arsenic -qTox -plague -Britney -Nightshade -Belladonna -toxer -GoyIM - */ diff --git a/qtox.pro b/qtox.pro index 607d25791..7b52d779c 100644 --- a/qtox.pro +++ b/qtox.pro @@ -107,7 +107,10 @@ HEADERS += widget/form/addfriendform.h \ widget/form/genericchatform.h \ widget/tool/chataction.h \ widget/chatareawidget.h \ - filetransferinstance.h + filetransferinstance.h \ + corestructs.h \ + coredefines.h \ + coreav.h SOURCES += \ widget/form/addfriendform.cpp \ @@ -143,4 +146,5 @@ SOURCES += \ widget/form/genericchatform.cpp \ widget/tool/chataction.cpp \ widget/chatareawidget.cpp \ - filetransferinstance.cpp + filetransferinstance.cpp \ + corestructs.cpp diff --git a/settings.cpp b/settings.cpp index 01c6bed2c..6c039c6d2 100644 --- a/settings.cpp +++ b/settings.cpp @@ -16,8 +16,8 @@ #include "settings.h" #include "smileypack.h" -#include "widget/widget.h" +#include #include #include #include diff --git a/settings.h b/settings.h index 8ea767615..88811deb7 100644 --- a/settings.h +++ b/settings.h @@ -18,8 +18,7 @@ #define SETTINGS_HPP #include -#include -#include +#include class Settings : public QObject { diff --git a/smileypack.cpp b/smileypack.cpp index cca56583c..5af07194f 100644 --- a/smileypack.cpp +++ b/smileypack.cpp @@ -19,8 +19,14 @@ #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include SmileyPack::SmileyPack() { diff --git a/style.h b/style.h index 77a09ce25..4bd42d77f 100644 --- a/style.h +++ b/style.h @@ -17,7 +17,7 @@ #ifndef STYLE_H #define STYLE_H -#include +class QString; class Style { diff --git a/widget/camera.cpp b/widget/camera.cpp index e4a6010c7..dab371005 100644 --- a/widget/camera.cpp +++ b/widget/camera.cpp @@ -15,7 +15,7 @@ */ #include "camera.h" -#include +#include "widget.h" using namespace cv; @@ -114,3 +114,8 @@ vpx_image Camera::getLastVPXImage() } return img; } + +Camera* Camera::getInstance() +{ + return Widget::getInstance()->getCamera(); +} diff --git a/widget/camera.h b/widget/camera.h index e717c5c1a..e48307d41 100644 --- a/widget/camera.h +++ b/widget/camera.h @@ -31,6 +31,7 @@ class Camera { public: Camera(); + static Camera* getInstance(); ///< Returns the global widget's Camera instance void suscribe(); ///< Call this once before trying to get frames void unsuscribe(); ///< Call this once when you don't need frames anymore cv::Mat getLastFrame(); ///< Get the last captured frame diff --git a/widget/chatareawidget.cpp b/widget/chatareawidget.cpp index 1558ed520..203633386 100644 --- a/widget/chatareawidget.cpp +++ b/widget/chatareawidget.cpp @@ -16,10 +16,10 @@ #include "chatareawidget.h" #include "widget/tool/chataction.h" -#include -#include #include #include +#include +#include ChatAreaWidget::ChatAreaWidget(QWidget *parent) : QTextBrowser(parent) diff --git a/widget/chatareawidget.h b/widget/chatareawidget.h index ffcfd0d5b..c30b12c61 100644 --- a/widget/chatareawidget.h +++ b/widget/chatareawidget.h @@ -19,9 +19,9 @@ #include #include -#include class ChatAction; +class QTextTable; class ChatAreaWidget : public QTextBrowser { diff --git a/widget/croppinglabel.cpp b/widget/croppinglabel.cpp index 47b324d1e..d9022402a 100644 --- a/widget/croppinglabel.cpp +++ b/widget/croppinglabel.cpp @@ -16,6 +16,7 @@ #include "croppinglabel.h" #include +#include CroppingLabel::CroppingLabel(QWidget* parent) : QLabel(parent) diff --git a/widget/croppinglabel.h b/widget/croppinglabel.h index aeca082b1..678829cbe 100644 --- a/widget/croppinglabel.h +++ b/widget/croppinglabel.h @@ -18,7 +18,8 @@ #define CROPPINGLABEL_H #include -#include + +class QLineEdit; class CroppingLabel : public QLabel { diff --git a/widget/form/addfriendform.cpp b/widget/form/addfriendform.cpp index 89bb0935d..3a44ae672 100644 --- a/widget/form/addfriendform.cpp +++ b/widget/form/addfriendform.cpp @@ -19,7 +19,7 @@ #include #include #include -#include "widget/widget.h" +#include "ui_mainwindow.h" #include "core.h" #define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE @@ -95,7 +95,7 @@ void AddFriendForm::onSendTriggered() if (id.isEmpty()) { showWarning(tr("Please fill in a valid Tox ID","Tox ID of the friend you're sending a friend request to")); } else if (isToxId(id)) { - if (id.toUpper() == Widget::getInstance()->getCore()->getSelfId().toUpper()) + if (id.toUpper() == Core::getInstance()->getSelfId().toUpper()) showWarning(tr("You can't add yourself as a friend !","When trying to add your own Tox ID as friend")); else emit friendRequested(id, getMessage()); diff --git a/widget/form/addfriendform.h b/widget/form/addfriendform.h index 0edcf2214..6fd9d736c 100644 --- a/widget/form/addfriendform.h +++ b/widget/form/addfriendform.h @@ -17,8 +17,6 @@ #ifndef ADDFRIENDFORM_H #define ADDFRIENDFORM_H -#include "ui_mainwindow.h" - #include #include #include @@ -26,6 +24,8 @@ #include #include +namespace Ui {class MainWindow;} + class AddFriendForm : public QObject { Q_OBJECT @@ -42,8 +42,8 @@ signals: void friendRequested(const QString& friendAddress, const QString& message); private slots: - void onSendTriggered(); - void handleDnsLookup(); + void onSendTriggered(); + void handleDnsLookup(); private: QLabel headLabel, toxIdLabel, messageLabel; diff --git a/widget/form/chatform.cpp b/widget/form/chatform.cpp index d52fd9d53..96b57b865 100644 --- a/widget/form/chatform.cpp +++ b/widget/form/chatform.cpp @@ -14,15 +14,21 @@ See the COPYING file for more details. */ +#include +#include +#include +#include +#include #include "chatform.h" #include "friend.h" #include "widget/friendwidget.h" #include "filetransferinstance.h" -#include "widget/widget.h" #include "widget/tool/chataction.h" -#include -#include -#include +#include "widget/netcamview.h" +#include "widget/chatareawidget.h" +#include "widget/tool/chattextedit.h" +#include "core.h" +#include "widget/widget.h" ChatForm::ChatForm(Friend* chatFriend) : f(chatFriend) @@ -36,15 +42,15 @@ ChatForm::ChatForm(Friend* chatFriend) headTextLayout->addWidget(statusMessageLabel); headTextLayout->addStretch(); - connect(Widget::getInstance()->getCore(), &Core::fileSendStarted, this, &ChatForm::startFileSend); - connect(Widget::getInstance()->getCore(), &Core::videoFrameReceived, netcam, &NetCamView::updateDisplay); + connect(Core::getInstance(), &Core::fileSendStarted, this, &ChatForm::startFileSend); + connect(Core::getInstance(), &Core::videoFrameReceived, netcam, &NetCamView::updateDisplay); connect(sendButton, &QPushButton::clicked, this, &ChatForm::onSendTriggered); connect(fileButton, &QPushButton::clicked, this, &ChatForm::onAttachClicked); connect(callButton, &QPushButton::clicked, this, &ChatForm::onCallTriggered); connect(videoButton, &QPushButton::clicked, this, &ChatForm::onVideoCallTriggered); connect(msgEdit, &ChatTextEdit::enterPressed, this, &ChatForm::onSendTriggered); connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); - connect(chatWidget, SIGNAL(onFileTranfertInterract(QString,QString)), this, SLOT(onFileTansBtnClicked(QString,QString))); + connect(chatWidget, &ChatAreaWidget::onFileTranfertInterract, this, &ChatForm::onFileTansBtnClicked); } ChatForm::~ChatForm() @@ -99,9 +105,9 @@ void ChatForm::startFileSend(ToxFile file) FileTransferInstance* fileTrans = new FileTransferInstance(file); ftransWidgets.insert(fileTrans->getId(), fileTrans); - connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo); - connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled); - connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished); + connect(Core::getInstance(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo); + connect(Core::getInstance(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled); + connect(Core::getInstance(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished); QString name = Widget::getInstance()->getUsername(); if (name == previousName) @@ -119,9 +125,9 @@ void ChatForm::onFileRecvRequest(ToxFile file) FileTransferInstance* fileTrans = new FileTransferInstance(file); ftransWidgets.insert(fileTrans->getId(), fileTrans); - connect(Widget::getInstance()->getCore(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo); - connect(Widget::getInstance()->getCore(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled); - connect(Widget::getInstance()->getCore(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished); + connect(Core::getInstance(), &Core::fileTransferInfo, fileTrans, &FileTransferInstance::onFileTransferInfo); + connect(Core::getInstance(), &Core::fileTransferCancelled, fileTrans, &FileTransferInstance::onFileTransferCancelled); + connect(Core::getInstance(), &Core::fileTransferFinished, fileTrans, &FileTransferInstance::onFileTransferFinished); Widget* w = Widget::getInstance(); if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow()) diff --git a/widget/form/chatform.h b/widget/form/chatform.h index 9407f1c11..93bc2d6b4 100644 --- a/widget/form/chatform.h +++ b/widget/form/chatform.h @@ -18,11 +18,11 @@ #define CHATFORM_H #include "genericchatform.h" -#include "core.h" -#include "widget/netcamview.h" +#include "corestructs.h" struct Friend; class FileTransferInstance; +class NetCamView; class ChatForm : public GenericChatForm { diff --git a/widget/form/filesform.cpp b/widget/form/filesform.cpp index 6d69c1f5a..8aaa314b2 100644 --- a/widget/form/filesform.cpp +++ b/widget/form/filesform.cpp @@ -15,6 +15,11 @@ */ #include "filesform.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include FilesForm::FilesForm() : QObject() diff --git a/widget/form/filesform.h b/widget/form/filesform.h index 0f1a2b1d8..c3efdaacf 100644 --- a/widget/form/filesform.h +++ b/widget/form/filesform.h @@ -17,17 +17,14 @@ #ifndef FILESFORM_H #define FILESFORM_H -#include "ui_mainwindow.h" - -#include +#include #include #include -#include #include #include -#include -#include -#include + +namespace Ui {class MainWindow;} +class QListWidget; class FilesForm : public QObject { diff --git a/widget/form/genericchatform.cpp b/widget/form/genericchatform.cpp index 7c44f9ba5..f5681f1c6 100644 --- a/widget/form/genericchatform.cpp +++ b/widget/form/genericchatform.cpp @@ -23,6 +23,8 @@ #include "widget/widget.h" #include "settings.h" #include "widget/tool/chataction.h" +#include "widget/chatareawidget.h" +#include "widget/tool/chattextedit.h" GenericChatForm::GenericChatForm(QObject *parent) : QObject(parent) diff --git a/widget/form/genericchatform.h b/widget/form/genericchatform.h index 9c0e67763..95940cda5 100644 --- a/widget/form/genericchatform.h +++ b/widget/form/genericchatform.h @@ -19,19 +19,18 @@ #include #include -#include -#include -#include -#include -#include - -#include "widget/croppinglabel.h" -#include "widget/chatareawidget.h" -#include "widget/tool/chattextedit.h" +#include // Spacing in px inserted when the author of the last message changes #define AUTHOR_CHANGE_SPACING 5 +class QLabel; +class QVBoxLayout; +class QPushButton; +class CroppingLabel; +class ChatTextEdit; +class ChatAreaWidget; + namespace Ui { class MainWindow; } diff --git a/widget/form/groupchatform.cpp b/widget/form/groupchatform.cpp index ca355e3db..907787fd3 100644 --- a/widget/form/groupchatform.cpp +++ b/widget/form/groupchatform.cpp @@ -17,7 +17,9 @@ #include "groupchatform.h" #include "group.h" #include "widget/groupwidget.h" -#include "widget/widget.h" +#include "widget/tool/chattextedit.h" +#include "widget/croppinglabel.h" +#include GroupChatForm::GroupChatForm(Group* chatGroup) : group(chatGroup) diff --git a/widget/form/groupchatform.h b/widget/form/groupchatform.h index 9b4766180..cdf20a113 100644 --- a/widget/form/groupchatform.h +++ b/widget/form/groupchatform.h @@ -18,9 +18,8 @@ #define GROUPCHATFORM_H #include "genericchatform.h" -#include "widget/tool/chattextedit.h" -#include "ui_mainwindow.h" +namespace Ui {class MainWindow;} class Group; class GroupChatForm : public GenericChatForm diff --git a/widget/form/settingsform.cpp b/widget/form/settingsform.cpp index 5417b62d7..3d4a213da 100644 --- a/widget/form/settingsform.cpp +++ b/widget/form/settingsform.cpp @@ -18,6 +18,7 @@ #include "widget/widget.h" #include "settings.h" #include "smileypack.h" +#include "ui_mainwindow.h" #include #include #include diff --git a/widget/form/settingsform.h b/widget/form/settingsform.h index 81851e8a9..a081d01fb 100644 --- a/widget/form/settingsform.h +++ b/widget/form/settingsform.h @@ -20,17 +20,17 @@ #include #include #include -#include #include -#include #include #include #include #include -#include "ui_mainwindow.h" -#include "widget/selfcamview.h" + #include "widget/croppinglabel.h" +namespace Ui {class MainWindow;} +class QString; + class SettingsForm : public QObject { Q_OBJECT diff --git a/widget/friendlistwidget.cpp b/widget/friendlistwidget.cpp index aced0f297..176850e82 100644 --- a/widget/friendlistwidget.cpp +++ b/widget/friendlistwidget.cpp @@ -15,6 +15,7 @@ */ #include "friendlistwidget.h" #include +#include FriendListWidget::FriendListWidget(QWidget *parent) : QWidget(parent) diff --git a/widget/friendlistwidget.h b/widget/friendlistwidget.h index 02baa3f94..dae6b1c61 100644 --- a/widget/friendlistwidget.h +++ b/widget/friendlistwidget.h @@ -18,8 +18,11 @@ #define FRIENDLISTWIDGET_H #include -#include -#include "core.h" +#include +#include "corestructs.h" + +class QLayout; +class QGridLayout; class FriendListWidget : public QWidget { diff --git a/widget/friendwidget.cpp b/widget/friendwidget.cpp index 97c7b9c77..e1744002d 100644 --- a/widget/friendwidget.cpp +++ b/widget/friendwidget.cpp @@ -18,9 +18,10 @@ #include "group.h" #include "grouplist.h" #include "groupwidget.h" -#include "widget.h" #include "friendlist.h" #include "friend.h" +#include "core.h" +#include "widget/form/chatform.h" #include #include @@ -112,7 +113,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) else if (groupActions.contains(selectedItem)) { Group* group = groupActions[selectedItem]; - Widget::getInstance()->getCore()->groupInviteFriend(friendId, group->groupId); + Core::getInstance()->groupInviteFriend(friendId, group->groupId); } } } diff --git a/widget/friendwidget.h b/widget/friendwidget.h index 257d48a24..da158e68d 100644 --- a/widget/friendwidget.h +++ b/widget/friendwidget.h @@ -17,10 +17,7 @@ #ifndef FRIENDWIDGET_H #define FRIENDWIDGET_H -#include #include -#include -#include #include "genericchatroomwidget.h" #include "croppinglabel.h" diff --git a/widget/groupwidget.h b/widget/groupwidget.h index c32db80a6..54d1ee533 100644 --- a/widget/groupwidget.h +++ b/widget/groupwidget.h @@ -17,7 +17,6 @@ #ifndef GROUPWIDGET_H #define GROUPWIDGET_H -#include #include #include "genericchatroomwidget.h" diff --git a/widget/netcamview.cpp b/widget/netcamview.cpp index cd9e78fda..4268ea8dd 100644 --- a/widget/netcamview.cpp +++ b/widget/netcamview.cpp @@ -16,9 +16,8 @@ #include "netcamview.h" #include "core.h" -#include "widget.h" -#include -#include +#include +#include static inline void fromYCbCrToRGB( uint8_t Y, uint8_t Cb, uint8_t Cr, @@ -70,7 +69,7 @@ void NetCamView::updateDisplay(vpx_image* frame) if (!frame->w || !frame->h) return; - Core* core = Widget::getInstance()->getCore(); + Core* core = Core::getInstance(); core->increaseVideoBusyness(); diff --git a/widget/netcamview.h b/widget/netcamview.h index 7552dd267..0aba0edd9 100644 --- a/widget/netcamview.h +++ b/widget/netcamview.h @@ -18,14 +18,13 @@ #define NETCAMVIEW_H #include -#include -#include -#include -#include class QCloseEvent; class QShowEvent; class QPainter; +class QLabel; +class QHBoxLayout; +class vpx_image; class NetCamView : public QWidget { diff --git a/widget/selfcamview.cpp b/widget/selfcamview.cpp index 2d46e99bb..6436e24d9 100644 --- a/widget/selfcamview.cpp +++ b/widget/selfcamview.cpp @@ -15,46 +15,45 @@ */ #include "selfcamview.h" +#include "camera.h" #include #include - -#include "widget.h" +#include +#include +#include +#include using namespace cv; SelfCamView::SelfCamView(Camera* Cam, QWidget* parent) : QWidget(parent), displayLabel{new QLabel}, - mainLayout{new QHBoxLayout()}, cam(Cam) + mainLayout{new QHBoxLayout()}, cam(Cam), updateDisplayTimer{new QTimer} { setLayout(mainLayout); setWindowTitle(SelfCamView::tr("Tox video test","Title of the window to test the video/webcam")); setMinimumSize(320,240); - updateDisplayTimer.setInterval(5); - updateDisplayTimer.setSingleShot(false); + updateDisplayTimer->setInterval(5); + updateDisplayTimer->setSingleShot(false); displayLabel->setScaledContents(true); mainLayout->addWidget(displayLabel); - connect(&updateDisplayTimer, SIGNAL(timeout()), this, SLOT(updateDisplay())); -} - -SelfCamView::~SelfCamView() -{ + connect(updateDisplayTimer, SIGNAL(timeout()), this, SLOT(updateDisplay())); } void SelfCamView::closeEvent(QCloseEvent* event) { cam->unsuscribe(); - updateDisplayTimer.stop(); + updateDisplayTimer->stop(); event->accept(); } void SelfCamView::showEvent(QShowEvent* event) { cam->suscribe(); - updateDisplayTimer.start(); + updateDisplayTimer->start(); event->accept(); } diff --git a/widget/selfcamview.h b/widget/selfcamview.h index 328168299..a8cc29679 100644 --- a/widget/selfcamview.h +++ b/widget/selfcamview.h @@ -18,14 +18,14 @@ #define SELFCAMVIEW_H #include -#include -#include -#include -#include "camera.h" class QCloseEvent; class QShowEvent; class QPainter; +class Camera; +class QLabel; +class QHBoxLayout; +class QTimer; class SelfCamView : public QWidget { @@ -33,7 +33,6 @@ class SelfCamView : public QWidget public: SelfCamView(Camera* Cam, QWidget *parent=0); - ~SelfCamView(); private slots: void updateDisplay(); @@ -47,7 +46,7 @@ private: QLabel *displayLabel; QHBoxLayout* mainLayout; Camera* cam; - QTimer updateDisplayTimer; + QTimer* updateDisplayTimer; }; #endif // SELFCAMVIEW_H diff --git a/widget/widget.cpp b/widget/widget.cpp index 89237158e..b12dcce83 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -16,6 +16,7 @@ #include "widget.h" #include "ui_mainwindow.h" +#include "core.h" #include "settings.h" #include "friend.h" #include "friendlist.h" @@ -26,17 +27,19 @@ #include "widget/groupwidget.h" #include "widget/form/groupchatform.h" #include "style.h" +#include "selfcamview.h" +#include "widget/friendlistwidget.h" +#include "camera.h" +#include "widget/form/chatform.h" #include #include -#include #include #include #include #include -#include -#include -#include #include +#include +#include Widget *Widget::instance{nullptr}; diff --git a/widget/widget.h b/widget/widget.h index 4559862f4..1fbecb412 100644 --- a/widget/widget.h +++ b/widget/widget.h @@ -17,17 +17,11 @@ #ifndef WIDGET_H #define WIDGET_H -#include #include -#include -#include -#include -#include "core.h" #include "widget/form/addfriendform.h" #include "widget/form/settingsform.h" #include "widget/form/filesform.h" -#include "camera.h" -#include "friendlistwidget.h" +#include "corestructs.h" #define PIXELS_TO_ACT 7 @@ -38,6 +32,12 @@ class MainWindow; class GenericChatroomWidget; class Group; struct Friend; +class QSplitter; +class SelfCamView; +class QMenu; +class Core; +class Camera; +class FriendListWidget; class Widget : public QMainWindow { From fd2419669c0bb001255d4671cc44890ae0e24ad8 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 11 Sep 2014 16:08:14 +0200 Subject: [PATCH 24/26] README: Update screenshot --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6bc803bb5..d6358283e 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,7 @@ This client runs on Windows, Linux and Mac natively.

Screenshots

Note: The screenshots may not always be up to date, but they should give a good idea of the general look and features
- - +/ ##Documentation: From 6440ef8310e4ae24e3071cf559b9470183a15a8d Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 11 Sep 2014 18:05:02 +0200 Subject: [PATCH 25/26] Make group name translatable --- widget/widget.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/widget/widget.cpp b/widget/widget.cpp index b12dcce83..abe14460a 100644 --- a/widget/widget.cpp +++ b/widget/widget.cpp @@ -625,7 +625,12 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha TOX_CHAT_CHANGE change = static_cast(Change); if (change == TOX_CHAT_CHANGE_PEER_ADD) - g->addPeer(peernumber,""); + { + QString name = core->getGroupPeerName(groupnumber, peernumber); + if (name.isEmpty()) + name = tr("", "Placeholder when we don't know someone's name in a group chat"); + g->addPeer(peernumber,name); + } else if (change == TOX_CHAT_CHANGE_PEER_DEL) g->removePeer(peernumber); else if (change == TOX_CHAT_CHANGE_PEER_NAME) From 6c5cd7c01061f2899fde1404e41774d35219ca33 Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 11 Sep 2014 20:36:57 +0700 Subject: [PATCH 26/26] filetransfer: corrected pause/resume behaviour --- core.cpp | 8 ++ core.h | 1 + filetransferinstance.cpp | 70 ++++++++++++++++-- filetransferinstance.h | 4 + res.qrc | 1 + .../pauseGreyFileButton.png | Bin 0 -> 244 bytes ui/fileTransferInstance/resumeFileButton.png | Bin 244 -> 403 bytes widget/form/chatform.cpp | 6 ++ 8 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 ui/fileTransferInstance/pauseGreyFileButton.png diff --git a/core.cpp b/core.cpp index 607da59c6..876301a63 100644 --- a/core.cpp +++ b/core.cpp @@ -393,6 +393,14 @@ void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0); removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum); } + else if (receive_send == 0 && control_type == TOX_FILECONTROL_ACCEPT) + { + emit static_cast(core)->fileTransferRemotePausedUnpaused(*file, false); + } + else if ((receive_send == 0 || receive_send == 1) && control_type == TOX_FILECONTROL_PAUSE) + { + emit static_cast(core)->fileTransferRemotePausedUnpaused(*file, true); + } else { qDebug() << QString("Core: File control callback, receive_send=%1, control_type=%2") diff --git a/core.h b/core.h index 6ec505468..96ab86d2d 100644 --- a/core.h +++ b/core.h @@ -146,6 +146,7 @@ signals: void fileDownloadFinished(const QString& path); void fileTransferPaused(int FriendId, int FileNum, ToxFile::FileDirection direction); void fileTransferInfo(int FriendId, int FileNum, int64_t Filesize, int64_t BytesSent, ToxFile::FileDirection direction); + void fileTransferRemotePausedUnpaused(ToxFile file, bool paused); void avInvite(int friendId, int callIndex, bool video); void avStart(int friendId, int callIndex, bool video); diff --git a/filetransferinstance.cpp b/filetransferinstance.cpp index 094df87a9..9bcc7b6e1 100644 --- a/filetransferinstance.cpp +++ b/filetransferinstance.cpp @@ -30,6 +30,7 @@ FileTransferInstance::FileTransferInstance(ToxFile File) { id = Idconter++; state = tsPending; + remotePaused = false; filename = File.fileName; size = getHumanReadableSize(File.filesize); @@ -61,7 +62,7 @@ void FileTransferInstance::onFileTransferInfo(int FriendId, int FileNum, int64_t if (FileNum != fileNum || FriendId != friendId || Direction != direction) return; - state = tsProcessing; +// state = tsProcessing; QDateTime newtime = QDateTime::currentDateTime(); int timediff = lastUpdate.secsTo(newtime); if (timediff <= 0) @@ -121,6 +122,37 @@ void FileTransferInstance::onFileTransferFinished(ToxFile File) emit stateUpdated(); } +void FileTransferInstance::onFileTransferAccepted(ToxFile File) +{ + if (File.fileNum != fileNum || File.friendId != friendId || File.direction != direction) + return; + + remotePaused = false; + state = tsProcessing; + + 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); @@ -181,19 +213,33 @@ void FileTransferInstance::acceptRecvRequest() void FileTransferInstance::pauseResumeRecv() { + if (!(state == tsProcessing || state == tsPaused)) + return; + + if (remotePaused) + return; + Core::getInstance()->pauseResumeFileRecv(friendId, fileNum); - if (state == tsProcessing) - state = tsPaused; - else state = tsProcessing; +// if (state == tsProcessing) +// state = tsPaused; +// else state = tsProcessing; + emit stateUpdated(); } void FileTransferInstance::pauseResumeSend() { + if (!(state == tsProcessing || state == tsPaused)) + return; + + if (remotePaused) + return; + Core::getInstance()->pauseResumeFileSend(friendId, fileNum); - if (state == tsProcessing) - state = tsPaused; - else state = tsProcessing; +// if (state == tsProcessing) +// state = tsPaused; +// else state = tsProcessing; + emit stateUpdated(); } @@ -220,7 +266,15 @@ QString FileTransferInstance::getHtmlImage() else if (state == tsPaused) rightBtnImg = QImage(":/ui/fileTransferInstance/resumeFileButton.png"); else - rightBtnImg = QImage(":/ui/fileTransferInstance/acceptFileButton.png"); + { + 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 == tsCanceled) diff --git a/filetransferinstance.h b/filetransferinstance.h index 6e48d28cf..b81b03102 100644 --- a/filetransferinstance.h +++ b/filetransferinstance.h @@ -40,6 +40,9 @@ 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 pressFromHtml(QString); signals: @@ -65,6 +68,7 @@ private: uint id; TransfState state; + bool remotePaused; QImage pic; QString filename, size, speed, eta; QDateTime lastUpdate; diff --git a/res.qrc b/res.qrc index 6104327f8..cbf038a58 100644 --- a/res.qrc +++ b/res.qrc @@ -127,6 +127,7 @@ ui/volButton/volButton.css ui/fileTransferInstance/acceptFileButton.png ui/fileTransferInstance/pauseFileButton.png + ui/fileTransferInstance/pauseGreyFileButton.png ui/fileTransferInstance/resumeFileButton.png ui/fileTransferInstance/stopFileButton.png ui/fileTransferInstance/emptyLGreenFileButton.png diff --git a/ui/fileTransferInstance/pauseGreyFileButton.png b/ui/fileTransferInstance/pauseGreyFileButton.png new file mode 100644 index 0000000000000000000000000000000000000000..4bf1efd12a9b8c85874542b7633c3f62ecc6d303 GIT binary patch literal 244 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoO!2%@hbniq1DW)WEcNYeRRlUkaKptm-M`SSr z1K$x4W}K?cCk+&2FY)wsWxvPC&Zof{WwFf|DAeld;uvCa`swA}oDBvdtq<*!98SvJ zJe-&ld+ZwP9EGLTOUhdoOmLDG3=zqbV$Cn_>bLP*G;OK7ndBo+mC19a$lILOQFlFb z>{Id@gNi@Xbfz8n;KReVSG~7#>tFfF!TO9H)mz*VLp{btdDiA_KUF?hQAxvXu0h0re85#xv005LE6wm+w00v@9M??Ss00000`9r&ZkuM;B3k)SWfE|+r z0003XNklv;<5HbO3a<$#q=Ugy z+?FKENOqPmrzk_TS`tHlok<6Sg_sQkBLgGtlCTJ;C_{{TGEtUMoOA@)1Q`_hR2X*?|;3;tCoe9hLdc`pMQTCPQ5(Fu;bAthId37PP8SI z7*3QWCte<5*!OfN!@HkvC`uf7Em?DK>1a`p3~tfGlI^s%2<1FxqEM{QfI|9OtQ?>b|fr9KM zp1!W^_c+=4G&rLywiyG3T0LDHLo7}|y}X;V!9b+-p?#9WNx7Sc6LVsZU1Oc2u(Wzf zdCP(cPSS!QB6(7*`Q=^xHhznyEp<1OeB`MzIcCljd7INZ>aK^5eM(+qQ1NG)&a?v` ze0bRQs`plI{VP8?Sf8<@x{Kvd>x%xmCoRX{KRx1k(nVX9H(4!e;j-N!mF3getUsername(); if (name == previousName) @@ -128,6 +131,9 @@ void ChatForm::onFileRecvRequest(ToxFile file) 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))); Widget* w = Widget::getInstance(); if (!w->isFriendWidgetCurActiveWidget(f)|| w->getIsWindowMinimized() || !w->isActiveWindow())