mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
435 lines
14 KiB
C++
435 lines
14 KiB
C++
/*
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
qTox is libre software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
qTox is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "friendwidget.h"
|
|
|
|
#include "circlewidget.h"
|
|
#include "contentdialog.h"
|
|
#include "friendlistwidget.h"
|
|
#include "groupwidget.h"
|
|
#include "maskablepixmapwidget.h"
|
|
|
|
#include "src/core/core.h"
|
|
#include "src/model/friend.h"
|
|
#include "src/friendlist.h"
|
|
#include "src/model/group.h"
|
|
#include "src/grouplist.h"
|
|
#include "src/persistence/settings.h"
|
|
#include "src/widget/about/aboutuser.h"
|
|
#include "src/widget/form/chatform.h"
|
|
#include "src/widget/style.h"
|
|
#include "src/widget/tool/croppinglabel.h"
|
|
#include "src/widget/widget.h"
|
|
|
|
#include <QApplication>
|
|
#include <QBitmap>
|
|
#include <QCollator>
|
|
#include <QContextMenuEvent>
|
|
#include <QDebug>
|
|
#include <QDrag>
|
|
#include <QFileDialog>
|
|
#include <QInputDialog>
|
|
#include <QMenu>
|
|
#include <QMimeData>
|
|
|
|
#include <cassert>
|
|
|
|
/**
|
|
* @class FriendWidget
|
|
*
|
|
* Widget, which displays brief information about friend.
|
|
* For example, used on friend list.
|
|
* When you click should open the chat with friend. Widget has a context menu.
|
|
*/
|
|
|
|
FriendWidget::FriendWidget(const Friend* f, bool compact)
|
|
: GenericChatroomWidget(compact)
|
|
, frnd{f}
|
|
, isDefaultAvatar{true}
|
|
{
|
|
avatar->setPixmap(QPixmap(":/img/contact.svg"));
|
|
statusPic.setPixmap(QPixmap(":/img/status/dot_offline.svg"));
|
|
statusPic.setMargin(3);
|
|
nameLabel->setText(f->getDisplayedName());
|
|
nameLabel->setTextFormat(Qt::PlainText);
|
|
connect(nameLabel, &CroppingLabel::editFinished, this, &FriendWidget::setAlias);
|
|
statusMessageLabel->setTextFormat(Qt::PlainText);
|
|
}
|
|
|
|
/**
|
|
* @brief FriendWidget::contextMenuEvent
|
|
* @param event Describe a context menu event
|
|
*
|
|
* Default context menu event handler.
|
|
* Redirect all event information to the signal.
|
|
*/
|
|
void FriendWidget::contextMenuEvent(QContextMenuEvent* event)
|
|
{
|
|
emit contextMenuCalled(event);
|
|
}
|
|
|
|
/**
|
|
* @brief FriendWidget::onContextMenuCalled
|
|
* @param event Redirected from native contextMenuEvent
|
|
*
|
|
* Context menu handler. Always should be called to FriendWidget from FriendList
|
|
*/
|
|
void FriendWidget::onContextMenuCalled(QContextMenuEvent* event)
|
|
{
|
|
if (!active) {
|
|
setBackgroundRole(QPalette::Highlight);
|
|
}
|
|
|
|
installEventFilter(this); // Disable leave event.
|
|
|
|
QMenu menu;
|
|
QAction* openChatWindow = nullptr;
|
|
QAction* removeChatWindow = nullptr;
|
|
|
|
const uint32_t friendId = frnd->getId();
|
|
const ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId);
|
|
|
|
if (!contentDialog || contentDialog->chatroomWidgetCount() > 1) {
|
|
openChatWindow = menu.addAction(tr("Open chat in new window"));
|
|
}
|
|
|
|
if (contentDialog && contentDialog->hasFriendWidget(friendId, this)) {
|
|
removeChatWindow = menu.addAction(tr("Remove chat from this window"));
|
|
}
|
|
|
|
menu.addSeparator();
|
|
QMenu* inviteMenu = menu.addMenu(tr("Invite to group",
|
|
"Menu to invite a friend to a groupchat"));
|
|
inviteMenu->setEnabled(frnd->getStatus() != Status::Offline);
|
|
const QAction* newGroupAction = inviteMenu->addAction(tr("To new group"));
|
|
inviteMenu->addSeparator();
|
|
QMap<const QAction*, const Group*> groupActions;
|
|
|
|
for (const Group* group : GroupList::getAllGroups()) {
|
|
const int maxNameLen = 30;
|
|
QString name = group->getName();
|
|
if (name.length() > maxNameLen) {
|
|
name = name.left(maxNameLen).trimmed() + "..";
|
|
}
|
|
const QAction* groupAction = inviteMenu->addAction(tr("Invite to group '%1'").arg(name));
|
|
groupActions[groupAction] = group;
|
|
}
|
|
|
|
const Settings& s = Settings::getInstance();
|
|
const int circleId = s.getFriendCircleID(frnd->getPublicKey());
|
|
CircleWidget* circleWidget = CircleWidget::getFromID(circleId);
|
|
QWidget* w = circleWidget ? circleWidget : static_cast<QWidget*>(this);
|
|
FriendListWidget* friendList = qobject_cast<FriendListWidget*>(w->parentWidget());
|
|
|
|
QMenu* circleMenu = menu.addMenu(tr("Move to circle...",
|
|
"Menu to move a friend into a different circle"));
|
|
|
|
const QAction* newCircleAction = circleMenu->addAction(tr("To new circle"));
|
|
|
|
QAction* removeCircleAction = nullptr;
|
|
if (circleId != -1) {
|
|
const QString circleName = s.getCircleName(circleId);
|
|
removeCircleAction = circleMenu->addAction(tr("Remove from circle '%1'").arg(circleName));
|
|
}
|
|
|
|
circleMenu->addSeparator();
|
|
|
|
QList<QAction*> circleActionList;
|
|
QMap<QAction*, int> circleActions;
|
|
|
|
for (int i = 0; i < s.getCircleCount(); ++i) {
|
|
if (i == circleId) {
|
|
continue;
|
|
}
|
|
|
|
const QString name = s.getCircleName(i);
|
|
QAction* action = new QAction(tr("Move to circle \"%1\"").arg(name), circleMenu);
|
|
circleActionList.push_back(action);
|
|
circleActions[circleActionList.back()] = i;
|
|
}
|
|
|
|
std::sort(circleActionList.begin(), circleActionList.end(),
|
|
[](const QAction* lhs, const QAction* rhs) -> bool {
|
|
QCollator collator;
|
|
collator.setNumericMode(true);
|
|
return collator.compare(lhs->text(), rhs->text()) < 0;
|
|
});
|
|
|
|
circleMenu->addActions(circleActionList);
|
|
|
|
const QAction* setAlias = menu.addAction(tr("Set alias..."));
|
|
|
|
menu.addSeparator();
|
|
QAction* autoAccept = menu.addAction(tr("Auto accept files from this friend",
|
|
"context menu entry"));
|
|
const ToxPk id = frnd->getPublicKey();
|
|
const QString dir = s.getAutoAcceptDir(id);
|
|
autoAccept->setCheckable(true);
|
|
autoAccept->setChecked(!dir.isEmpty());
|
|
menu.addSeparator();
|
|
|
|
QAction* removeFriendAction = nullptr;
|
|
|
|
if (!contentDialog || !contentDialog->hasFriendWidget(friendId, this)) {
|
|
removeFriendAction = menu.addAction(tr("Remove friend",
|
|
"Menu to remove the friend from our friendlist"));
|
|
}
|
|
|
|
menu.addSeparator();
|
|
const QAction* aboutWindow = menu.addAction(tr("Show details"));
|
|
|
|
const QPoint pos = event->globalPos();
|
|
QAction* selectedItem = menu.exec(pos);
|
|
|
|
removeEventFilter(this);
|
|
|
|
if (!active) {
|
|
setBackgroundRole(QPalette::Window);
|
|
}
|
|
|
|
if (!selectedItem) {
|
|
return;
|
|
}
|
|
|
|
if (selectedItem == setAlias) {
|
|
nameLabel->editBegin();
|
|
} else if (selectedItem == removeFriendAction) {
|
|
emit removeFriend(friendId);
|
|
} else if (selectedItem == openChatWindow) {
|
|
emit newWindowOpened(this);
|
|
} else if (selectedItem == removeChatWindow) {
|
|
ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId);
|
|
contentDialog->removeFriend(friendId);
|
|
} else if (selectedItem == autoAccept) {
|
|
if (!autoAccept->isChecked()) {
|
|
qDebug() << "not checked";
|
|
autoAccept->setChecked(false);
|
|
Settings::getInstance().setAutoAcceptDir(id, "");
|
|
} else if (autoAccept->isChecked()) {
|
|
const QString dir = QFileDialog::getExistingDirectory(
|
|
Q_NULLPTR, tr("Choose an auto accept directory", "popup title"), dir);
|
|
|
|
autoAccept->setChecked(true);
|
|
qDebug() << "Setting auto accept dir for" << friendId << "to" << dir;
|
|
Settings::getInstance().setAutoAcceptDir(id, dir);
|
|
}
|
|
} else if (selectedItem == aboutWindow) {
|
|
const Friend* f = FriendList::findFriend(friendId);
|
|
AboutUser* aboutUser = new AboutUser(f, Widget::getInstance());
|
|
aboutUser->show();
|
|
} else if (selectedItem == newGroupAction) {
|
|
const int groupId = Core::getInstance()->createGroup();
|
|
Core::getInstance()->groupInviteFriend(friendId, groupId);
|
|
} else if (selectedItem == newCircleAction) {
|
|
if (circleWidget != nullptr) {
|
|
circleWidget->updateStatus();
|
|
}
|
|
|
|
if (friendList != nullptr) {
|
|
friendList->addCircleWidget(this);
|
|
} else {
|
|
Settings::getInstance().setFriendCircleID(id, Settings::getInstance().addCircle());
|
|
}
|
|
} else if (groupActions.contains(selectedItem)) {
|
|
const Group* group = groupActions[selectedItem];
|
|
Core::getInstance()->groupInviteFriend(friendId, group->getId());
|
|
} else if (removeCircleAction != nullptr && selectedItem == removeCircleAction) {
|
|
if (friendList) {
|
|
friendList->moveWidget(this, frnd->getStatus(), true);
|
|
} else {
|
|
Settings::getInstance().setFriendCircleID(id, -1);
|
|
}
|
|
|
|
if (circleWidget) {
|
|
circleWidget->updateStatus();
|
|
Widget::getInstance()->searchCircle(circleWidget);
|
|
}
|
|
} else if (circleActions.contains(selectedItem)) {
|
|
CircleWidget* circle = CircleWidget::getFromID(circleActions[selectedItem]);
|
|
|
|
if (circle) {
|
|
circle->addFriendWidget(this, frnd->getStatus());
|
|
circle->setExpanded(true);
|
|
Widget::getInstance()->searchCircle(circle);
|
|
Settings::getInstance().savePersonal();
|
|
} else {
|
|
Settings::getInstance().setFriendCircleID(id, circleActions[selectedItem]);
|
|
}
|
|
|
|
if (circleWidget) {
|
|
circleWidget->updateStatus();
|
|
Widget::getInstance()->searchCircle(circleWidget);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FriendWidget::setAsActiveChatroom()
|
|
{
|
|
setActive(true);
|
|
|
|
if (isDefaultAvatar) {
|
|
avatar->setPixmap(QPixmap(":img/contact_dark.svg"));
|
|
}
|
|
}
|
|
|
|
void FriendWidget::setAsInactiveChatroom()
|
|
{
|
|
setActive(false);
|
|
|
|
if (isDefaultAvatar) {
|
|
avatar->setPixmap(QPixmap(":img/contact.svg"));
|
|
}
|
|
}
|
|
|
|
void FriendWidget::updateStatusLight()
|
|
{
|
|
static const QString statuses[] = {
|
|
":img/status/dot_online.svg",
|
|
":img/status/dot_online_notification.svg",
|
|
":img/status/dot_away.svg",
|
|
":img/status/dot_away_notification.svg",
|
|
":img/status/dot_busy.svg",
|
|
":img/status/dot_busy_notification.svg",
|
|
":img/status/dot_offline.svg",
|
|
":img/status/dot_offline_notification.svg",
|
|
};
|
|
|
|
const bool event = frnd->getEventFlag();
|
|
const int index = static_cast<int>(frnd->getStatus()) * 2 + event;
|
|
statusPic.setPixmap(QPixmap(statuses[index]));
|
|
|
|
if (event) {
|
|
const Settings& s = Settings::getInstance();
|
|
const uint32_t circleId = s.getFriendCircleID(frnd->getPublicKey());
|
|
CircleWidget* circleWidget = CircleWidget::getFromID(circleId);
|
|
if (circleWidget) {
|
|
circleWidget->setExpanded(true);
|
|
}
|
|
|
|
Widget::getInstance()->updateFriendActivity(frnd);
|
|
}
|
|
|
|
statusPic.setMargin(event ? 0 : 3);
|
|
}
|
|
|
|
QString FriendWidget::getStatusString() const
|
|
{
|
|
const int status = static_cast<int>(frnd->getStatus());
|
|
const bool event = frnd->getEventFlag();
|
|
|
|
static const QVector<QString> names = {
|
|
tr("Online"),
|
|
tr("Away"),
|
|
tr("Busy"),
|
|
tr("Offline"),
|
|
};
|
|
|
|
return event ? tr("New message") : names.value(status);
|
|
}
|
|
|
|
const Friend* FriendWidget::getFriend() const
|
|
{
|
|
return frnd;
|
|
}
|
|
|
|
void FriendWidget::search(const QString& searchString, bool hide)
|
|
{
|
|
searchName(searchString, hide);
|
|
const Settings& s = Settings::getInstance();
|
|
const uint32_t circleId = s.getFriendCircleID(frnd->getPublicKey());
|
|
CircleWidget* circleWidget = CircleWidget::getFromID(circleId);
|
|
if (circleWidget) {
|
|
circleWidget->search(searchString);
|
|
}
|
|
}
|
|
|
|
void FriendWidget::setChatForm(ContentLayout* contentLayout)
|
|
{
|
|
ChatForm* form = frnd->getChatForm();
|
|
if (form) {
|
|
form->show(contentLayout);
|
|
}
|
|
}
|
|
|
|
void FriendWidget::resetEventFlags()
|
|
{
|
|
// Hack to avoid edit const Friend. TODO: Repalce on emit
|
|
Friend* f = FriendList::findFriend(frnd->getId());
|
|
f->setEventFlag(false);
|
|
}
|
|
|
|
void FriendWidget::onAvatarChange(int friendId, const QPixmap& pic)
|
|
{
|
|
if (friendId != frnd->getId()) {
|
|
return;
|
|
}
|
|
|
|
isDefaultAvatar = false;
|
|
avatar->setPixmap(pic);
|
|
}
|
|
|
|
void FriendWidget::onAvatarRemoved(int friendId)
|
|
{
|
|
if (friendId != frnd->getId()) {
|
|
return;
|
|
}
|
|
|
|
isDefaultAvatar = true;
|
|
|
|
const QString path = QString(":/img/contact%1.svg").arg(isActive() ? "" : "_dark");
|
|
avatar->setPixmap(QPixmap(path));
|
|
}
|
|
|
|
void FriendWidget::mousePressEvent(QMouseEvent* ev)
|
|
{
|
|
if (ev->button() == Qt::LeftButton) {
|
|
dragStartPos = ev->pos();
|
|
}
|
|
|
|
GenericChatroomWidget::mousePressEvent(ev);
|
|
}
|
|
|
|
void FriendWidget::mouseMoveEvent(QMouseEvent* ev)
|
|
{
|
|
if (!(ev->buttons() & Qt::LeftButton)) {
|
|
return;
|
|
}
|
|
|
|
const int distance = (dragStartPos - ev->pos()).manhattanLength();
|
|
if (distance > QApplication::startDragDistance()) {
|
|
QMimeData* mdata = new QMimeData;
|
|
mdata->setText(getFriend()->getPublicKey().toString());
|
|
|
|
QDrag* drag = new QDrag(this);
|
|
drag->setMimeData(mdata);
|
|
drag->setPixmap(avatar->getPixmap());
|
|
drag->exec(Qt::CopyAction | Qt::MoveAction);
|
|
}
|
|
}
|
|
|
|
void FriendWidget::setAlias(const QString& _alias)
|
|
{
|
|
QString alias = _alias.left(tox_max_name_length());
|
|
// Hack to avoid edit const Friend. TODO: Repalce on emit
|
|
Friend* f = FriendList::findFriend(frnd->getId());
|
|
f->setAlias(alias);
|
|
|
|
Settings& s = Settings::getInstance();
|
|
s.setFriendAlias(frnd->getPublicKey(), alias);
|
|
s.savePersonal();
|
|
}
|