qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
addfriendform.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2014-2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "addfriendform.h"
21 #include "src/core/core.h"
22 #include "src/nexus.h"
25 #include "src/widget/gui.h"
27 #include "src/widget/style.h"
28 #include "src/widget/translator.h"
29 #include <QApplication>
30 #include <QClipboard>
31 #include <QErrorMessage>
32 #include <QFileDialog>
33 #include <QFont>
34 #include <QMessageBox>
35 #include <QRegularExpression>
36 #include <QScrollArea>
37 #include <QSignalMapper>
38 #include <QTabWidget>
39 #include <QWindow>
40 
41 namespace
42 {
43  QString getToxId(const QString& id)
44  {
45  const QString toxUriPrefix{"tox:"};
46  QString strippedId = id.trimmed();
47  if (strippedId.startsWith(toxUriPrefix)) {
48  strippedId.remove(0, toxUriPrefix.length());
49  }
50  return strippedId;
51  }
52 
53  bool checkIsValidId(const QString& id)
54  {
55  return ToxId::isToxId(id);
56  }
57 }
58 
65  : ownId{_ownId}
66 {
67  tabWidget = new QTabWidget();
68  main = new QWidget(tabWidget);
69  head = new QWidget();
70  QFont bold;
71  bold.setBold(true);
72  headLabel.setFont(bold);
73  toxIdLabel.setTextFormat(Qt::RichText);
74 
75  main->setLayout(&layout);
76  layout.addWidget(&toxIdLabel);
77  layout.addWidget(&toxId);
78  layout.addWidget(&messageLabel);
79  layout.addWidget(&message);
80  layout.addWidget(&sendButton);
81  tabWidget->addTab(main, QString());
82 
83  importContacts = new QWidget(tabWidget);
84  importContacts->setLayout(&importContactsLayout);
85  importFileLine.addWidget(&importFileLabel);
86  importFileLine.addStretch();
87  importFileLine.addWidget(&importFileButton);
88  importContactsLayout.addLayout(&importFileLine);
89  importContactsLayout.addWidget(&importMessageLabel);
90  importContactsLayout.addWidget(&importMessage);
91  importContactsLayout.addWidget(&importSendButton);
92  tabWidget->addTab(importContacts, QString());
93 
94  QScrollArea* scrollArea = new QScrollArea(tabWidget);
95  QWidget* requestWidget = new QWidget(tabWidget);
96  scrollArea->setWidget(requestWidget);
97  scrollArea->setWidgetResizable(true);
98  requestsLayout = new QVBoxLayout(requestWidget);
99  requestsLayout->addStretch(1);
100  tabWidget->addTab(scrollArea, QString());
101 
102  head->setLayout(&headLayout);
103  headLayout.addWidget(&headLabel);
104 
105  connect(&toxId, &QLineEdit::returnPressed, this, &AddFriendForm::onSendTriggered);
106  connect(&toxId, &QLineEdit::textChanged, this, &AddFriendForm::onIdChanged);
107  connect(tabWidget, &QTabWidget::currentChanged, this, &AddFriendForm::onCurrentChanged);
108  connect(&sendButton, &QPushButton::clicked, this, &AddFriendForm::onSendTriggered);
109  connect(&importSendButton, &QPushButton::clicked, this, &AddFriendForm::onImportSendClicked);
110  connect(&importFileButton, &QPushButton::clicked, this, &AddFriendForm::onImportOpenClicked);
112 
113  // accessibility stuff
114  toxIdLabel.setAccessibleDescription(
115  tr("Tox ID, 76 hexadecimal characters"));
116  toxId.setAccessibleDescription(tr("Type in Tox ID of your friend"));
117  messageLabel.setAccessibleDescription(tr("Friend request message"));
118  message.setAccessibleDescription(tr(
119  "Type message to send with the friend request or leave empty to send a default message"));
120  message.setTabChangesFocus(true);
121 
122  retranslateUi();
124 
125  const int size = Settings::getInstance().getFriendRequestSize();
126  for (int i = 0; i < size; ++i) {
128  addFriendRequestWidget(request.address, request.message);
129  }
130 }
131 
133 {
135  head->deleteLater();
136  tabWidget->deleteLater();
137 }
138 
140 {
141  if (head->isVisible()) {
142  head->window()->windowHandle()->alert(0);
143  return true;
144  }
145 
146  return false;
147 }
148 
149 void AddFriendForm::show(ContentLayout* contentLayout)
150 {
151  contentLayout->mainContent->layout()->addWidget(tabWidget);
152  contentLayout->mainHead->layout()->addWidget(head);
153  tabWidget->show();
154  head->show();
156  toxId.setFocus();
157 
158  // Fix #3421
159  // Needed to update tab after opening window
160  const int index = tabWidget->currentIndex();
161  onCurrentChanged(index);
162 }
163 
165 {
166  const QString msg = message.toPlainText();
167  return !msg.isEmpty() ? msg : message.placeholderText();
168 }
169 
171 {
172  const QString msg = importMessage.toPlainText();
173  return msg.isEmpty() ? importMessage.placeholderText() : msg;
174 }
175 
177 {
178  tabWidget->setCurrentIndex(mode);
179 }
180 
181 bool AddFriendForm::addFriendRequest(const QString& friendAddress, const QString& message)
182 {
183  if (Settings::getInstance().addFriendRequest(friendAddress, message)) {
184  addFriendRequestWidget(friendAddress, message);
185  if (isShown()) {
186  onCurrentChanged(tabWidget->currentIndex());
187  }
188 
189  return true;
190  }
191  return false;
192 }
193 
194 void AddFriendForm::onUsernameSet(const QString& username)
195 {
196  lastUsername = username;
197  retranslateUi();
198 }
199 
200 void AddFriendForm::addFriend(const QString& idText)
201 {
202  ToxId friendId(idText);
203 
204  if (!friendId.isValid()) {
205  GUI::showWarning(tr("Couldn't add friend"),
206  tr("%1 Tox ID is invalid", "Tox address error").arg(idText));
207  return;
208  }
209 
210  deleteFriendRequest(friendId);
211  if (friendId == ownId) {
212  GUI::showWarning(tr("Couldn't add friend"),
213  //: When trying to add your own Tox ID as friend
214  tr("You can't add yourself as a friend!"));
215  } else {
216  emit friendRequested(friendId, getMessage());
217  }
218 }
219 
221 {
222  const QString id = getToxId(toxId.text());
223  addFriend(id);
224 
225  this->toxId.clear();
226  this->message.clear();
227 }
228 
230 {
231  for (const QString& id : contactsToImport) {
232  addFriend(id);
233  }
234 
235  contactsToImport.clear();
236  importMessage.clear();
237  retranslateUi(); // Update the importFileLabel
238 }
239 
241 {
242  const QString path = QFileDialog::getOpenFileName(Q_NULLPTR, tr("Open contact list"));
243  if (path.isEmpty()) {
244  return;
245  }
246 
247  QFile contactFile(path);
248  if (!contactFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
249  GUI::showWarning(tr("Couldn't open file"),
250  //: Error message when trying to open a contact list file to import
251  tr("Couldn't open the contact file"));
252  return;
253  }
254 
255  contactsToImport = QString::fromUtf8(contactFile.readAll()).split('\n');
256  qDebug() << "Import list:";
257  for (auto it = contactsToImport.begin(); it != contactsToImport.end();) {
258  const QString id = getToxId(*it);
259  if (checkIsValidId(id)) {
260  *it = id;
261  qDebug() << *it;
262  ++it;
263  } else {
264  if (!id.isEmpty()) {
265  qDebug() << "Invalid ID:" << *it;
266  }
267  it = contactsToImport.erase(it);
268  }
269  }
270 
271  if (contactsToImport.isEmpty()) {
272  GUI::showWarning(tr("Invalid file"),
273  tr("We couldn't find any contacts to import in this file!"));
274  }
275 
276  retranslateUi(); // Update the importFileLabel to show how many contacts we have
277 }
278 
279 void AddFriendForm::onIdChanged(const QString& id)
280 {
281  const QString strippedId = getToxId(id);
282 
283  const bool isValidId = checkIsValidId(strippedId);
284  const bool isValidOrEmpty = strippedId.isEmpty() || isValidId;
285 
286  //: Tox ID of the person you're sending a friend request to
287  const QString toxIdText(tr("Tox ID"));
288  //: Tox ID format description
289  const QString toxIdComment(tr("76 hexadecimal characters"));
290 
291  const QString labelText =
292  isValidId ? QStringLiteral("%1 (%2)") : QStringLiteral("%1 <font color='red'>(%2)</font>");
293  toxIdLabel.setText(labelText.arg(toxIdText, toxIdComment));
294  toxId.setStyleSheet(isValidOrEmpty ? QStringLiteral("")
295  : Style::getStylesheet("addFriendForm/toxId.css"));
296  toxId.setToolTip(isValidOrEmpty ? QStringLiteral("") : tr("Invalid Tox ID format"));
297 
298  sendButton.setEnabled(isValidId);
299 }
300 
302 {
303  const QClipboard* clipboard = QApplication::clipboard();
304  const QString trimmedId = clipboard->text().trimmed();
305  const QString strippedId = getToxId(trimmedId);
306  const bool isSelf = ToxId::isToxId(strippedId) && ToxId(strippedId) != ownId;
307  if (!strippedId.isEmpty() && ToxId::isToxId(strippedId) && isSelf) {
308  toxId.setText(trimmedId);
309  }
310 }
311 
313 {
314  const int size = Settings::getInstance().getFriendRequestSize();
315  for (int i = 0; i < size; ++i) {
317  if (toxId.getPublicKey() == ToxPk(request.address)) {
319  return;
320  }
321  }
322 }
323 
325 {
326  QPushButton* acceptButton = static_cast<QPushButton*>(sender());
327  QWidget* friendWidget = acceptButton->parentWidget();
328  const int index = requestsLayout->indexOf(friendWidget);
329  removeFriendRequestWidget(friendWidget);
330  const int indexFromEnd = requestsLayout->count() - index - 1;
331  const Settings::Request request = Settings::getInstance().getFriendRequest(indexFromEnd);
332  emit friendRequestAccepted(ToxPk{request.address});
335 }
336 
338 {
339  QPushButton* rejectButton = static_cast<QPushButton*>(sender());
340  QWidget* friendWidget = rejectButton->parentWidget();
341  const int index = requestsLayout->indexOf(friendWidget);
342  removeFriendRequestWidget(friendWidget);
343  const int indexFromEnd = requestsLayout->count() - index - 1;
346 }
347 
349 {
350  if (index == FriendRequest && Settings::getInstance().getUnreadFriendRequests() != 0) {
353  emit friendRequestsSeen();
354  }
355 }
356 
358 {
359  headLabel.setText(tr("Add Friends"));
360  //: The message you send in friend requests
361  static const QString messageLabelText = tr("Message");
362  messageLabel.setText(messageLabelText);
363  importMessageLabel.setText(messageLabelText);
364  //: Button to choose a file with a list of contacts to import
365  importFileButton.setText(tr("Open"));
366  importSendButton.setText(tr("Send friend requests"));
367  sendButton.setText(tr("Send friend request"));
368  //: Default message in friend requests if the field is left blank. Write something appropriate!
369  message.setPlaceholderText(tr("%1 here! Tox me maybe?").arg(lastUsername));
370  importMessage.setPlaceholderText(message.placeholderText());
371 
372  importFileLabel.setText(
373  contactsToImport.isEmpty()
374  ? tr("Import a list of contacts, one Tox ID per line")
375  //: Shows the number of contacts we're about to import from a file (at least one)
376  : tr("Ready to import %n contact(s), click send to confirm", "", contactsToImport.size()));
377 
378  onIdChanged(toxId.text());
379 
380  tabWidget->setTabText(AddFriend, tr("Add a friend"));
381  tabWidget->setTabText(ImportContacts, tr("Import contacts"));
382  tabWidget->setTabText(FriendRequest, tr("Friend requests"));
383 
384  for (QPushButton* acceptButton : acceptButtons) {
385  retranslateAcceptButton(acceptButton);
386  }
387 
388  for (QPushButton* rejectButton : rejectButtons) {
389  retranslateRejectButton(rejectButton);
390  }
391 }
392 
393 void AddFriendForm::addFriendRequestWidget(const QString& friendAddress, const QString& message)
394 {
395  QWidget* friendWidget = new QWidget(tabWidget);
396  QHBoxLayout* friendLayout = new QHBoxLayout(friendWidget);
397  QVBoxLayout* horLayout = new QVBoxLayout();
398  horLayout->setMargin(0);
399  friendLayout->addLayout(horLayout);
400 
401  CroppingLabel* friendLabel = new CroppingLabel(friendWidget);
402  friendLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
403  friendLabel->setText("<b>" + friendAddress + "</b>");
404  horLayout->addWidget(friendLabel);
405 
406  QLabel* messageLabel = new QLabel(message);
407  // allow to select text, but treat links as plaintext to prevent phishing
408  messageLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
409  messageLabel->setTextFormat(Qt::PlainText);
410  messageLabel->setWordWrap(true);
411  horLayout->addWidget(messageLabel, 1);
412 
413  QPushButton* acceptButton = new QPushButton(friendWidget);
414  acceptButtons.append(acceptButton);
415  connect(acceptButton, &QPushButton::released, this, &AddFriendForm::onFriendRequestAccepted);
416  friendLayout->addWidget(acceptButton);
417  retranslateAcceptButton(acceptButton);
418 
419  QPushButton* rejectButton = new QPushButton(friendWidget);
420  rejectButtons.append(rejectButton);
421  connect(rejectButton, &QPushButton::released, this, &AddFriendForm::onFriendRequestRejected);
422  friendLayout->addWidget(rejectButton);
423  retranslateRejectButton(rejectButton);
424 
425  requestsLayout->insertWidget(0, friendWidget);
426 }
427 
428 void AddFriendForm::removeFriendRequestWidget(QWidget* friendWidget)
429 {
430  int index = requestsLayout->indexOf(friendWidget);
431  requestsLayout->removeWidget(friendWidget);
432  acceptButtons.removeAt(index);
433  rejectButtons.removeAt(index);
434  friendWidget->deleteLater();
435 }
436 
437 void AddFriendForm::retranslateAcceptButton(QPushButton* acceptButton)
438 {
439  acceptButton->setText(tr("Accept"));
440 }
441 
442 void AddFriendForm::retranslateRejectButton(QPushButton* rejectButton)
443 {
444  rejectButton->setText(tr("Reject"));
445 }
style.h
AddFriendForm::importMessage
QTextEdit importMessage
Definition: addfriendform.h:100
AddFriendForm::onSendTriggered
void onSendTriggered()
Definition: addfriendform.cpp:220
AddFriendForm::onIdChanged
void onIdChanged(const QString &id)
Definition: addfriendform.cpp:279
settings.h
AddFriendForm::show
void show(ContentLayout *contentLayout)
Definition: addfriendform.cpp:149
AddFriendForm::AddFriend
@ AddFriend
Definition: addfriendform.h:43
AddFriendForm::onImportSendClicked
void onImportSendClicked()
Definition: addfriendform.cpp:229
AddFriendForm::importMessageLabel
QLabel importMessageLabel
Definition: addfriendform.h:93
CroppingLabel
Definition: croppinglabel.h:26
ToxId::isValid
bool isValid() const
Check it it's a valid Tox ID by verifying the checksum.
Definition: toxid.cpp:239
AddFriendForm::contactsToImport
QList< QString > contactsToImport
Definition: addfriendform.h:113
AddFriendForm::AddFriendForm
AddFriendForm(ToxId _ownId)
Definition: addfriendform.cpp:64
AddFriendForm::onImportOpenClicked
void onImportOpenClicked()
Definition: addfriendform.cpp:240
Settings::removeFriendRequest
void removeFriendRequest(int index)
Definition: settings.cpp:2118
CroppingLabel::setText
void setText(const QString &text)
Definition: croppinglabel.cpp:81
AddFriendForm::isShown
bool isShown() const
Definition: addfriendform.cpp:139
AddFriendForm::setIdFromClipboard
void setIdFromClipboard()
Definition: addfriendform.cpp:301
Translator::unregister
static void unregister(void *owner)
Unregisters all handlers of an owner.
Definition: translator.cpp:103
Settings::Request::message
QString message
Definition: settings.h:165
AddFriendForm::friendRequestAccepted
void friendRequestAccepted(const ToxPk &friendAddress)
AddFriendForm::deleteFriendRequest
void deleteFriendRequest(const ToxId &toxId)
Definition: addfriendform.cpp:312
AddFriendForm::retranslateRejectButton
void retranslateRejectButton(QPushButton *rejectButton)
Definition: addfriendform.cpp:442
AddFriendForm::friendRequestsSeen
void friendRequestsSeen()
AddFriendForm::onFriendRequestAccepted
void onFriendRequestAccepted()
Definition: addfriendform.cpp:324
AddFriendForm::message
QTextEdit message
Definition: addfriendform.h:99
AddFriendForm::toxId
QLineEdit toxId
Definition: addfriendform.h:98
AddFriendForm::friendRequested
void friendRequested(const ToxId &friendAddress, const QString &message)
AddFriendForm::lastUsername
QString lastUsername
Cached username so we can retranslate the invite message.
Definition: addfriendform.h:108
AddFriendForm::tabWidget
QTabWidget * tabWidget
Definition: addfriendform.h:109
AddFriendForm::onUsernameSet
void onUsernameSet(const QString &userName)
Definition: addfriendform.cpp:194
AddFriendForm::sendButton
QPushButton sendButton
Definition: addfriendform.h:95
croppinglabel.h
ToxId::isToxId
static bool isToxId(const QString &id)
Check, that id is probably a valid Tox ID.
Definition: toxid.cpp:230
ContentLayout::mainHead
QWidget * mainHead
Definition: contentlayout.h:37
HistMessageContentType::message
@ message
AddFriendForm::Mode
Mode
Definition: addfriendform.h:41
AddFriendForm::ownId
ToxId ownId
Definition: addfriendform.h:115
ToxPk
This class represents a Tox Public Key, which is a part of Tox ID.
Definition: toxpk.h:26
AddFriendForm::acceptButtons
QList< QPushButton * > acceptButtons
Definition: addfriendform.h:111
AddFriendForm::getImportMessage
QString getImportMessage() const
Definition: addfriendform.cpp:170
AddFriendForm::onFriendRequestRejected
void onFriendRequestRejected()
Definition: addfriendform.cpp:337
AddFriendForm::addFriendRequest
bool addFriendRequest(const QString &friendAddress, const QString &message)
Definition: addfriendform.cpp:181
Settings::Request
Definition: settings.h:162
AddFriendForm::retranslateAcceptButton
void retranslateAcceptButton(QPushButton *acceptButton)
Definition: addfriendform.cpp:437
AddFriendForm::onCurrentChanged
void onCurrentChanged(int index)
Definition: addfriendform.cpp:348
AddFriendForm::removeFriendRequestWidget
void removeFriendRequestWidget(QWidget *friendWidget)
Definition: addfriendform.cpp:428
Core::usernameSet
void usernameSet(const QString &username)
Translator::registerHandler
static void registerHandler(const std::function< void()> &, void *owner)
Register a function to be called when the UI needs to be retranslated.
Definition: translator.cpp:93
ToxId
This class represents a Tox ID.
Definition: toxid.h:29
AddFriendForm::importFileButton
QPushButton importFileButton
Definition: addfriendform.h:96
AddFriendForm::messageLabel
QLabel messageLabel
Definition: addfriendform.h:91
Settings::getInstance
static Settings & getInstance()
Returns the singleton instance.
Definition: settings.cpp:88
AddFriendForm::getMessage
QString getMessage() const
Definition: addfriendform.cpp:164
AddFriendForm::importSendButton
QPushButton importSendButton
Definition: addfriendform.h:97
contentlayout.h
Settings::clearUnreadFriendRequests
void clearUnreadFriendRequests()
Definition: settings.cpp:2110
GUI::showWarning
static void showWarning(const QString &title, const QString &msg)
Show a warning to the user.
Definition: gui.cpp:130
Settings::getFriendRequestSize
int getFriendRequestSize() const
Definition: settings.cpp:2104
Style::getStylesheet
static const QString getStylesheet(const QString &filename, const QFont &baseFont=QFont())
Definition: style.cpp:165
core.h
Settings::savePersonal
void savePersonal()
Asynchronous, saves the current profile.
Definition: settings.cpp:717
ContentLayout::mainContent
QWidget * mainContent
Definition: contentlayout.h:36
ContentLayout
Definition: contentlayout.h:25
AddFriendForm::importFileLabel
QLabel importFileLabel
Definition: addfriendform.h:92
AddFriendForm::addFriend
void addFriend(const QString &idText)
Definition: addfriendform.cpp:200
AddFriendForm::FriendRequest
@ FriendRequest
Definition: addfriendform.h:45
gui.h
AddFriendForm::addFriendRequestWidget
void addFriendRequestWidget(const QString &friendAddress, const QString &message)
Definition: addfriendform.cpp:393
AddFriendForm::setMode
void setMode(Mode mode)
Definition: addfriendform.cpp:176
Settings::Request::address
QString address
Definition: settings.h:164
AddFriendForm::head
QWidget * head
Definition: addfriendform.h:105
translator.h
Nexus::getCore
static Core * getCore()
Get core instance.
Definition: nexus.cpp:277
Settings::getFriendRequest
Request getFriendRequest(int index) const
Definition: settings.cpp:2098
AddFriendForm::headLabel
QLabel headLabel
Definition: addfriendform.h:89
AddFriendForm::rejectButtons
QList< QPushButton * > rejectButtons
Definition: addfriendform.h:112
AddFriendForm::requestsLayout
QVBoxLayout * requestsLayout
Definition: addfriendform.h:110
nexus.h
AddFriendForm::ImportContacts
@ ImportContacts
Definition: addfriendform.h:44
AddFriendForm::~AddFriendForm
~AddFriendForm()
Definition: addfriendform.cpp:132
AddFriendForm::toxIdLabel
QLabel toxIdLabel
Definition: addfriendform.h:90
main
int main(int argc, char *argv[])
Definition: main.cpp:190
addfriendform.h
AddFriendForm::retranslateUi
void retranslateUi()
Definition: addfriendform.cpp:357