mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Show context menu anywhere in MessageLayout when applicable (#3566)
Co-authored-by: James Upjohn <jammehcow@jammehcow.co.nz> Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
fb9c3ad42b
commit
5978ed8b1f
|
@ -53,6 +53,7 @@
|
||||||
- Minor: Removed timestamp from AutoMod messages. (#3503)
|
- Minor: Removed timestamp from AutoMod messages. (#3503)
|
||||||
- Minor: Added ability to copy message ID with `Shift + Right Click`. (#3481)
|
- Minor: Added ability to copy message ID with `Shift + Right Click`. (#3481)
|
||||||
- Minor: Colorize the entire split header when focused. (#3379)
|
- Minor: Colorize the entire split header when focused. (#3379)
|
||||||
|
- Minor: Show right click context menu anywhere within a message's line. (#3566)
|
||||||
- Minor: Make Tab Layout setting only accept predefined values (#3564)
|
- Minor: Make Tab Layout setting only accept predefined values (#3564)
|
||||||
- Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362)
|
- Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362)
|
||||||
- Bugfix: Fixed colored usernames sometimes not working. (#3170)
|
- Bugfix: Fixed colored usernames sometimes not working. (#3170)
|
||||||
|
|
|
@ -1762,11 +1762,6 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hoverLayoutElement == nullptr)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle the click
|
// handle the click
|
||||||
this->handleMouseClick(event, hoverLayoutElement, layout);
|
this->handleMouseClick(event, hoverLayoutElement, layout);
|
||||||
|
|
||||||
|
@ -1790,7 +1785,12 @@ void ChannelView::handleMouseClick(QMouseEvent *event,
|
||||||
this->queueLayout();
|
this->queueLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &link = hoveredElement->getLink();
|
if (hoveredElement == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &link = hoveredElement->getLink();
|
||||||
if (!getSettings()->linksDoubleClickOnly)
|
if (!getSettings()->linksDoubleClickOnly)
|
||||||
{
|
{
|
||||||
this->handleLinkClick(event, link, layout.get());
|
this->handleLinkClick(event, link, layout.get());
|
||||||
|
@ -1812,28 +1812,39 @@ void ChannelView::handleMouseClick(QMouseEvent *event,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto &link = hoveredElement->getLink();
|
if (hoveredElement != nullptr)
|
||||||
|
{
|
||||||
|
const auto &link = hoveredElement->getLink();
|
||||||
|
|
||||||
if (link.type == Link::UserInfo)
|
if (link.type == Link::UserInfo)
|
||||||
{
|
{
|
||||||
const bool commaMention = getSettings()->mentionUsersWithComma;
|
const bool commaMention =
|
||||||
|
getSettings()->mentionUsersWithComma;
|
||||||
const bool isFirstWord =
|
const bool isFirstWord =
|
||||||
split && split->getInput().isEditFirstWord();
|
split && split->getInput().isEditFirstWord();
|
||||||
auto userMention =
|
auto userMention = formatUserMention(
|
||||||
formatUserMention(link.value, isFirstWord, commaMention);
|
link.value, isFirstWord, commaMention);
|
||||||
insertText("@" + userMention + " ");
|
insertText("@" + userMention + " ");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (link.type == Link::UserWhisper)
|
|
||||||
|
if (link.type == Link::UserWhisper)
|
||||||
{
|
{
|
||||||
insertText("/w " + link.value + " ");
|
insertText("/w " + link.value + " ");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
|
||||||
this->addContextMenuItems(hoveredElement, layout, event);
|
this->addContextMenuItems(hoveredElement, layout, event);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Qt::MiddleButton: {
|
case Qt::MiddleButton: {
|
||||||
auto &link = hoveredElement->getLink();
|
if (hoveredElement == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &link = hoveredElement->getLink();
|
||||||
if (!getSettings()->linksDoubleClickOnly)
|
if (!getSettings()->linksDoubleClickOnly)
|
||||||
{
|
{
|
||||||
this->handleLinkClick(event, link, layout.get());
|
this->handleLinkClick(event, link, layout.get());
|
||||||
|
@ -1848,9 +1859,6 @@ void ChannelView::addContextMenuItems(
|
||||||
const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout,
|
const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout,
|
||||||
QMouseEvent *event)
|
QMouseEvent *event)
|
||||||
{
|
{
|
||||||
const auto &creator = hoveredElement->getCreator();
|
|
||||||
auto creatorFlags = creator.getFlags();
|
|
||||||
|
|
||||||
static QMenu *previousMenu = nullptr;
|
static QMenu *previousMenu = nullptr;
|
||||||
if (previousMenu != nullptr)
|
if (previousMenu != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -1861,12 +1869,45 @@ void ChannelView::addContextMenuItems(
|
||||||
auto menu = new QMenu;
|
auto menu = new QMenu;
|
||||||
previousMenu = menu;
|
previousMenu = menu;
|
||||||
|
|
||||||
|
// Add image options if the element clicked contains an image (e.g. a badge or an emote)
|
||||||
|
this->addImageContextMenuItems(hoveredElement, layout, event, *menu);
|
||||||
|
|
||||||
|
// Add link options if the element clicked contains a link
|
||||||
|
this->addLinkContextMenuItems(hoveredElement, layout, event, *menu);
|
||||||
|
|
||||||
|
// Add message options
|
||||||
|
this->addMessageContextMenuItems(hoveredElement, layout, event, *menu);
|
||||||
|
|
||||||
|
// Add Twitch-specific link options if the element clicked contains a link detected as a Twitch username
|
||||||
|
this->addTwitchLinkContextMenuItems(hoveredElement, layout, event, *menu);
|
||||||
|
|
||||||
|
// Add hidden options (e.g. copy message ID) if the user held down Shift
|
||||||
|
this->addHiddenContextMenuItems(hoveredElement, layout, event, *menu);
|
||||||
|
|
||||||
|
menu->popup(QCursor::pos());
|
||||||
|
menu->raise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelView::addImageContextMenuItems(
|
||||||
|
const MessageLayoutElement *hoveredElement, MessageLayoutPtr /*layout*/,
|
||||||
|
QMouseEvent * /*event*/, QMenu &menu)
|
||||||
|
{
|
||||||
|
if (hoveredElement == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &creator = hoveredElement->getCreator();
|
||||||
|
auto creatorFlags = creator.getFlags();
|
||||||
|
|
||||||
// Badge actions
|
// Badge actions
|
||||||
if (creatorFlags.hasAny({MessageElementFlag::Badges}))
|
if (creatorFlags.hasAny({MessageElementFlag::Badges}))
|
||||||
{
|
{
|
||||||
if (auto badgeElement = dynamic_cast<const BadgeElement *>(&creator))
|
if (auto badgeElement = dynamic_cast<const BadgeElement *>(&creator))
|
||||||
|
{
|
||||||
addEmoteContextMenuItems(*badgeElement->getEmote(), creatorFlags,
|
addEmoteContextMenuItems(*badgeElement->getEmote(), creatorFlags,
|
||||||
*menu);
|
menu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emote actions
|
// Emote actions
|
||||||
|
@ -1874,48 +1915,68 @@ void ChannelView::addContextMenuItems(
|
||||||
{MessageElementFlag::EmoteImages, MessageElementFlag::EmojiImage}))
|
{MessageElementFlag::EmoteImages, MessageElementFlag::EmojiImage}))
|
||||||
{
|
{
|
||||||
if (auto emoteElement = dynamic_cast<const EmoteElement *>(&creator))
|
if (auto emoteElement = dynamic_cast<const EmoteElement *>(&creator))
|
||||||
|
{
|
||||||
addEmoteContextMenuItems(*emoteElement->getEmote(), creatorFlags,
|
addEmoteContextMenuItems(*emoteElement->getEmote(), creatorFlags,
|
||||||
*menu);
|
menu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add seperator
|
// add seperator
|
||||||
if (!menu->actions().empty())
|
if (!menu.actions().empty())
|
||||||
{
|
{
|
||||||
menu->addSeparator();
|
menu.addSeparator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelView::addLinkContextMenuItems(
|
||||||
|
const MessageLayoutElement *hoveredElement, MessageLayoutPtr /*layout*/,
|
||||||
|
QMouseEvent * /*event*/, QMenu &menu)
|
||||||
|
{
|
||||||
|
if (hoveredElement == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &link = hoveredElement->getLink();
|
||||||
|
|
||||||
|
if (link.type != Link::Url)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link copy
|
// Link copy
|
||||||
if (hoveredElement->getLink().type == Link::Url)
|
QString url = link.value;
|
||||||
{
|
|
||||||
QString url = hoveredElement->getLink().value;
|
|
||||||
|
|
||||||
// open link
|
// open link
|
||||||
menu->addAction("Open link", [url] {
|
menu.addAction("Open link", [url] {
|
||||||
QDesktopServices::openUrl(QUrl(url));
|
QDesktopServices::openUrl(QUrl(url));
|
||||||
});
|
});
|
||||||
// open link default
|
// open link default
|
||||||
if (supportsIncognitoLinks())
|
if (supportsIncognitoLinks())
|
||||||
{
|
{
|
||||||
menu->addAction("Open link incognito", [url] {
|
menu.addAction("Open link incognito", [url] {
|
||||||
openLinkIncognito(url);
|
openLinkIncognito(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
menu->addAction("Copy link", [url] {
|
menu.addAction("Copy link", [url] {
|
||||||
crossPlatformCopy(url);
|
crossPlatformCopy(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
menu->addSeparator();
|
menu.addSeparator();
|
||||||
}
|
}
|
||||||
|
void ChannelView::addMessageContextMenuItems(
|
||||||
|
const MessageLayoutElement * /*hoveredElement*/, MessageLayoutPtr layout,
|
||||||
|
QMouseEvent * /*event*/, QMenu &menu)
|
||||||
|
{
|
||||||
// Copy actions
|
// Copy actions
|
||||||
if (!this->selection_.isEmpty())
|
if (!this->selection_.isEmpty())
|
||||||
{
|
{
|
||||||
menu->addAction("Copy selection", [this] {
|
menu.addAction("Copy selection", [this] {
|
||||||
crossPlatformCopy(this->getSelectedText());
|
crossPlatformCopy(this->getSelectedText());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->addAction("Copy message", [layout] {
|
menu.addAction("Copy message", [layout] {
|
||||||
QString copyString;
|
QString copyString;
|
||||||
layout->addSelectionText(copyString, 0, INT_MAX,
|
layout->addSelectionText(copyString, 0, INT_MAX,
|
||||||
CopyMode::OnlyTextAndEmotes);
|
CopyMode::OnlyTextAndEmotes);
|
||||||
|
@ -1923,16 +1984,30 @@ void ChannelView::addContextMenuItems(
|
||||||
crossPlatformCopy(copyString);
|
crossPlatformCopy(copyString);
|
||||||
});
|
});
|
||||||
|
|
||||||
menu->addAction("Copy full message", [layout] {
|
menu.addAction("Copy full message", [layout] {
|
||||||
QString copyString;
|
QString copyString;
|
||||||
layout->addSelectionText(copyString);
|
layout->addSelectionText(copyString);
|
||||||
|
|
||||||
crossPlatformCopy(copyString);
|
crossPlatformCopy(copyString);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If is a link to a Twitch user/stream
|
void ChannelView::addTwitchLinkContextMenuItems(
|
||||||
if (hoveredElement->getLink().type == Link::Url)
|
const MessageLayoutElement *hoveredElement, MessageLayoutPtr /*layout*/,
|
||||||
|
QMouseEvent * /*event*/, QMenu &menu)
|
||||||
|
{
|
||||||
|
if (hoveredElement == nullptr)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &link = hoveredElement->getLink();
|
||||||
|
|
||||||
|
if (link.type != Link::Url)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static QRegularExpression twitchChannelRegex(
|
static QRegularExpression twitchChannelRegex(
|
||||||
R"(^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/(?:popout\/)?(?<username>[a-z0-9_]{3,}))",
|
R"(^(?:https?:\/\/)?(?:www\.|go\.)?twitch\.tv\/(?:popout\/)?(?<username>[a-z0-9_]{3,}))",
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
@ -1956,49 +2031,55 @@ void ChannelView::addContextMenuItems(
|
||||||
"wallet", //
|
"wallet", //
|
||||||
};
|
};
|
||||||
|
|
||||||
auto twitchMatch =
|
auto twitchMatch = twitchChannelRegex.match(link.value);
|
||||||
twitchChannelRegex.match(hoveredElement->getLink().value);
|
|
||||||
auto twitchUsername = twitchMatch.captured("username");
|
auto twitchUsername = twitchMatch.captured("username");
|
||||||
if (!twitchUsername.isEmpty() &&
|
if (!twitchUsername.isEmpty() && !ignoredUsernames.contains(twitchUsername))
|
||||||
!ignoredUsernames.contains(twitchUsername))
|
|
||||||
{
|
{
|
||||||
menu->addSeparator();
|
menu.addSeparator();
|
||||||
menu->addAction("Open in new split", [twitchUsername, this] {
|
menu.addAction("Open in new split", [twitchUsername, this] {
|
||||||
this->openChannelIn.invoke(twitchUsername,
|
this->openChannelIn.invoke(twitchUsername,
|
||||||
FromTwitchLinkOpenChannelIn::Split);
|
FromTwitchLinkOpenChannelIn::Split);
|
||||||
});
|
});
|
||||||
menu->addAction("Open in new tab", [twitchUsername, this] {
|
menu.addAction("Open in new tab", [twitchUsername, this] {
|
||||||
this->openChannelIn.invoke(twitchUsername,
|
this->openChannelIn.invoke(twitchUsername,
|
||||||
FromTwitchLinkOpenChannelIn::Tab);
|
FromTwitchLinkOpenChannelIn::Tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
menu->addSeparator();
|
menu.addSeparator();
|
||||||
menu->addAction("Open player in browser", [twitchUsername, this] {
|
menu.addAction("Open player in browser", [twitchUsername, this] {
|
||||||
this->openChannelIn.invoke(
|
this->openChannelIn.invoke(
|
||||||
twitchUsername, FromTwitchLinkOpenChannelIn::BrowserPlayer);
|
twitchUsername, FromTwitchLinkOpenChannelIn::BrowserPlayer);
|
||||||
});
|
});
|
||||||
menu->addAction("Open in streamlink", [twitchUsername, this] {
|
menu.addAction("Open in streamlink", [twitchUsername, this] {
|
||||||
this->openChannelIn.invoke(
|
this->openChannelIn.invoke(twitchUsername,
|
||||||
twitchUsername, FromTwitchLinkOpenChannelIn::Streamlink);
|
FromTwitchLinkOpenChannelIn::Streamlink);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelView::addHiddenContextMenuItems(
|
||||||
|
const MessageLayoutElement * /*hoveredElement*/, MessageLayoutPtr layout,
|
||||||
|
QMouseEvent *event, QMenu &menu)
|
||||||
|
{
|
||||||
|
if (!layout)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event->modifiers() == Qt::ShiftModifier &&
|
if (event->modifiers() != Qt::ShiftModifier)
|
||||||
!layout->getMessage()->id.isEmpty())
|
|
||||||
{
|
{
|
||||||
menu->addAction("Copy message ID",
|
// NOTE: We currently require the modifier to be ONLY shift - we might want to check if shift is among the modifiers instead
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!layout->getMessage()->id.isEmpty())
|
||||||
|
{
|
||||||
|
menu.addAction("Copy message ID",
|
||||||
[messageID = layout->getMessage()->id] {
|
[messageID = layout->getMessage()->id] {
|
||||||
crossPlatformCopy(messageID);
|
crossPlatformCopy(messageID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
|
||||||
menu->raise();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChannelView::mouseDoubleClickEvent(QMouseEvent *event)
|
void ChannelView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
std::shared_ptr<MessageLayout> layout;
|
std::shared_ptr<MessageLayout> layout;
|
||||||
|
|
|
@ -157,10 +157,25 @@ private:
|
||||||
const QPoint &relativePos, int &wordStart, int &wordEnd);
|
const QPoint &relativePos, int &wordStart, int &wordEnd);
|
||||||
|
|
||||||
void handleMouseClick(QMouseEvent *event,
|
void handleMouseClick(QMouseEvent *event,
|
||||||
const MessageLayoutElement *hoverLayoutElement,
|
const MessageLayoutElement *hoveredElement,
|
||||||
MessageLayoutPtr layout);
|
MessageLayoutPtr layout);
|
||||||
void addContextMenuItems(const MessageLayoutElement *hoveredElement,
|
void addContextMenuItems(const MessageLayoutElement *hoveredElement,
|
||||||
MessageLayoutPtr layout, QMouseEvent *event);
|
MessageLayoutPtr layout, QMouseEvent *event);
|
||||||
|
void addImageContextMenuItems(const MessageLayoutElement *hoveredElement,
|
||||||
|
MessageLayoutPtr layout, QMouseEvent *event,
|
||||||
|
QMenu &menu);
|
||||||
|
void addLinkContextMenuItems(const MessageLayoutElement *hoveredElement,
|
||||||
|
MessageLayoutPtr layout, QMouseEvent *event,
|
||||||
|
QMenu &menu);
|
||||||
|
void addMessageContextMenuItems(const MessageLayoutElement *hoveredElement,
|
||||||
|
MessageLayoutPtr layout, QMouseEvent *event,
|
||||||
|
QMenu &menu);
|
||||||
|
void addTwitchLinkContextMenuItems(
|
||||||
|
const MessageLayoutElement *hoveredElement, MessageLayoutPtr layout,
|
||||||
|
QMouseEvent *event, QMenu &menu);
|
||||||
|
void addHiddenContextMenuItems(const MessageLayoutElement *hoveredElement,
|
||||||
|
MessageLayoutPtr layout, QMouseEvent *event,
|
||||||
|
QMenu &menu);
|
||||||
int getLayoutWidth() const;
|
int getLayoutWidth() const;
|
||||||
void updatePauses();
|
void updatePauses();
|
||||||
void unpaused();
|
void unpaused();
|
||||||
|
|
Loading…
Reference in a new issue