2018-06-26 14:09:39 +02:00
# include "ChannelView.hpp"
2019-09-25 23:47:13 +02:00
# include <QClipboard>
2021-05-09 18:44:57 +02:00
# include <QDate>
2019-09-25 23:47:13 +02:00
# include <QDebug>
# include <QDesktopServices>
# include <QGraphicsBlurEffect>
# include <QMessageBox>
# include <QPainter>
2020-04-18 11:09:22 +02:00
# include <QScreen>
2019-09-25 23:47:13 +02:00
# include <algorithm>
# include <chrono>
# include <cmath>
# include <functional>
# include <memory>
2018-06-26 14:09:39 +02:00
# include "Application.hpp"
2018-08-11 22:23:06 +02:00
# include "common/Common.hpp"
2020-11-21 16:20:10 +01:00
# include "common/QLogging.hpp"
2019-01-20 16:07:31 +01:00
# include "controllers/accounts/AccountController.hpp"
2020-04-11 12:13:38 +02:00
# include "controllers/commands/CommandController.hpp"
2018-06-26 17:20:03 +02:00
# include "debug/Benchmark.hpp"
2018-08-11 22:23:06 +02:00
# include "messages/Emote.hpp"
2018-06-26 14:09:39 +02:00
# include "messages/LimitedQueueSnapshot.hpp"
# include "messages/Message.hpp"
2021-05-09 18:44:57 +02:00
# include "messages/MessageBuilder.hpp"
2018-08-11 22:23:06 +02:00
# include "messages/MessageElement.hpp"
2018-06-26 17:20:03 +02:00
# include "messages/layouts/MessageLayout.hpp"
2018-08-11 22:23:06 +02:00
# include "messages/layouts/MessageLayoutElement.hpp"
2020-11-01 14:33:01 +01:00
# include "providers/LinkResolver.hpp"
2018-09-21 22:46:00 +02:00
# include "providers/twitch/TwitchChannel.hpp"
2019-09-15 13:02:02 +02:00
# include "providers/twitch/TwitchIrcServer.hpp"
2020-04-18 11:09:22 +02:00
# include "singletons/Resources.hpp"
2018-06-28 19:46:45 +02:00
# include "singletons/Settings.hpp"
2018-06-28 20:03:04 +02:00
# include "singletons/Theme.hpp"
2019-06-11 20:16:56 +02:00
# include "singletons/TooltipPreviewImage.hpp"
2019-06-11 22:54:20 +02:00
# include "singletons/WindowManager.hpp"
2020-01-24 21:36:51 +01:00
# include "util/Clipboard.hpp"
2018-06-26 14:09:39 +02:00
# include "util/DistanceBetweenPoints.hpp"
2021-07-24 12:01:50 +02:00
# include "util/Helpers.hpp"
2018-10-16 16:07:59 +02:00
# include "util/IncognitoBrowser.hpp"
2020-08-22 15:27:42 +02:00
# include "util/StreamerMode.hpp"
2020-05-10 12:45:19 +02:00
# include "util/Twitch.hpp"
2018-08-11 22:23:06 +02:00
# include "widgets/Scrollbar.hpp"
2018-06-26 14:09:39 +02:00
# include "widgets/TooltipWidget.hpp"
2020-11-15 14:40:34 +01:00
# include "widgets/Window.hpp"
2020-10-04 17:36:38 +02:00
# include "widgets/dialogs/SettingsDialog.hpp"
2018-06-26 15:11:45 +02:00
# include "widgets/dialogs/UserInfoPopup.hpp"
2018-08-11 22:23:06 +02:00
# include "widgets/helper/EffectLabel.hpp"
2021-05-23 15:51:53 +02:00
# include "widgets/helper/SearchPopup.hpp"
2018-06-26 17:20:03 +02:00
# include "widgets/splits/Split.hpp"
2021-07-24 12:01:50 +02:00
# include "widgets/splits/SplitInput.hpp"
2017-01-11 01:08:20 +01:00
2018-05-31 12:59:43 +02:00
# define DRAW_WIDTH (this->width())
2018-05-17 12:16:13 +02:00
# define SELECTION_RESUME_SCROLLING_MSG_THRESHOLD 3
2018-06-13 13:27:10 +02:00
# define CHAT_HOVER_PAUSE_DURATION 1000
2018-01-05 10:41:21 +01:00
2017-01-18 21:30:23 +01:00
namespace chatterino {
2018-08-02 14:23:27 +02:00
namespace {
2018-08-15 22:46:20 +02:00
void addEmoteContextMenuItems ( const Emote & emote ,
MessageElementFlags creatorFlags , QMenu & menu )
{
auto openAction = menu . addAction ( " Open " ) ;
auto openMenu = new QMenu ;
openAction - > setMenu ( openMenu ) ;
2018-08-02 14:23:27 +02:00
2018-08-15 22:46:20 +02:00
auto copyAction = menu . addAction ( " Copy " ) ;
auto copyMenu = new QMenu ;
copyAction - > setMenu ( copyMenu ) ;
2018-08-02 14:23:27 +02:00
2018-08-15 22:46:20 +02:00
// see if the QMenu actually gets destroyed
QObject : : connect ( openMenu , & QMenu : : destroyed , [ ] {
QMessageBox ( QMessageBox : : Information , " xD " , " the menu got deleted " )
. exec ( ) ;
} ) ;
2018-08-02 14:23:27 +02:00
2018-08-15 22:46:20 +02:00
// Add copy and open links for 1x, 2x, 3x
auto addImageLink = [ & ] ( const ImagePtr & image , char scale ) {
2018-10-21 13:43:02 +02:00
if ( ! image - > isEmpty ( ) )
{
2020-11-08 12:02:19 +01:00
copyMenu - > addAction ( QString ( scale ) + " x link " ,
[ url = image - > url ( ) ] {
crossPlatformCopy ( url . string ) ;
} ) ;
2018-08-15 22:46:20 +02:00
openMenu - > addAction (
QString ( scale ) + " x link " , [ url = image - > url ( ) ] {
QDesktopServices : : openUrl ( QUrl ( url . string ) ) ;
} ) ;
}
} ;
2018-08-02 14:23:27 +02:00
2018-08-15 22:46:20 +02:00
addImageLink ( emote . images . getImage1 ( ) , ' 1 ' ) ;
addImageLink ( emote . images . getImage2 ( ) , ' 2 ' ) ;
addImageLink ( emote . images . getImage3 ( ) , ' 3 ' ) ;
2018-08-02 14:23:27 +02:00
2018-08-15 22:46:20 +02:00
// Copy and open emote page link
auto addPageLink = [ & ] ( const QString & name ) {
copyMenu - > addSeparator ( ) ;
openMenu - > addSeparator ( ) ;
2018-08-02 14:23:27 +02:00
2020-11-08 12:02:19 +01:00
copyMenu - > addAction ( " Copy " + name + " emote link " ,
[ url = emote . homePage ] {
crossPlatformCopy ( url . string ) ;
} ) ;
openMenu - > addAction ( " Open " + name + " emote link " ,
[ url = emote . homePage ] {
QDesktopServices : : openUrl ( QUrl ( url . string ) ) ;
} ) ;
2018-08-15 22:46:20 +02:00
} ;
2018-08-02 14:23:27 +02:00
2021-08-07 13:13:05 +02:00
if ( creatorFlags . has ( MessageElementFlag : : BttvEmote ) )
2018-10-21 13:43:02 +02:00
{
2018-08-15 22:46:20 +02:00
addPageLink ( " BTTV " ) ;
2018-10-21 13:43:02 +02:00
}
else if ( creatorFlags . has ( MessageElementFlag : : FfzEmote ) )
{
2018-08-15 22:46:20 +02:00
addPageLink ( " FFZ " ) ;
2018-08-02 14:23:27 +02:00
}
}
} // namespace
2017-01-18 21:30:23 +01:00
2017-12-17 02:18:13 +01:00
ChannelView : : ChannelView ( BaseWidget * parent )
2017-09-16 00:05:06 +02:00
: BaseWidget ( parent )
2018-08-11 22:23:06 +02:00
, scrollBar_ ( new Scrollbar ( this ) )
2017-01-01 02:30:42 +01:00
{
2017-06-11 11:36:42 +02:00
this - > setMouseTracking ( true ) ;
2017-01-22 12:46:35 +01:00
2018-08-08 20:06:20 +02:00
this - > initializeLayout ( ) ;
this - > initializeScrollbar ( ) ;
this - > initializeSignals ( ) ;
2018-05-17 17:27:20 +02:00
2020-04-18 11:09:22 +02:00
this - > cursors_ . neutral = QCursor ( getResources ( ) . scrolling . neutralScroll ) ;
this - > cursors_ . up = QCursor ( getResources ( ) . scrolling . upScroll ) ;
this - > cursors_ . down = QCursor ( getResources ( ) . scrolling . downScroll ) ;
2018-11-03 21:26:57 +01:00
this - > pauseTimer_ . setSingleShot ( true ) ;
QObject : : connect ( & this - > pauseTimer_ , & QTimer : : timeout , this , [ this ] {
/// remove elements that are finite
for ( auto it = this - > pauses_ . begin ( ) ; it ! = this - > pauses_ . end ( ) ; )
it = it - > second ? this - > pauses_ . erase ( it ) : + + it ;
2019-09-25 23:47:13 +02:00
this - > updatePauses ( ) ;
2017-06-06 17:18:23 +02:00
} ) ;
2017-09-17 02:13:57 +02:00
2020-09-26 01:19:47 +02:00
auto shortcut = new QShortcut ( QKeySequence : : StandardKey : : Copy , this ) ;
2020-11-08 12:02:19 +01:00
QObject : : connect ( shortcut , & QShortcut : : activated , [ this ] {
crossPlatformCopy ( this - > getSelectedText ( ) ) ;
} ) ;
2018-10-02 12:56:10 +02:00
this - > clickTimer_ = new QTimer ( this ) ;
this - > clickTimer_ - > setSingleShot ( true ) ;
this - > clickTimer_ - > setInterval ( 500 ) ;
2019-09-08 12:29:02 +02:00
2020-04-18 11:09:22 +02:00
this - > scrollTimer_ . setInterval ( 20 ) ;
QObject : : connect ( & this - > scrollTimer_ , & QTimer : : timeout , this ,
& ChannelView : : scrollUpdateRequested ) ;
2019-09-08 12:29:02 +02:00
this - > setFocusPolicy ( Qt : : FocusPolicy : : StrongFocus ) ;
2018-08-08 20:06:20 +02:00
}
2018-06-13 03:58:52 +02:00
2018-08-08 20:06:20 +02:00
void ChannelView : : initializeLayout ( )
{
2018-08-08 15:35:54 +02:00
this - > goToBottom_ = new EffectLabel ( this , 0 ) ;
2018-08-06 21:17:03 +02:00
this - > goToBottom_ - > setStyleSheet (
" background-color: rgba(0,0,0,0.66); color: #FFF; " ) ;
2018-06-13 13:27:10 +02:00
this - > goToBottom_ - > getLabel ( ) . setText ( " More messages below " ) ;
this - > goToBottom_ - > setVisible ( false ) ;
2017-09-21 17:34:41 +02:00
2018-10-21 16:13:26 +02:00
QObject : : connect ( this - > goToBottom_ , & EffectLabel : : leftClicked , this , [ = ] {
2018-04-27 22:11:19 +02:00
QTimer : : singleShot ( 180 , [ = ] {
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > scrollToBottom (
2018-08-12 12:56:28 +02:00
getSettings ( ) - > enableSmoothScrollingNewMessages . getValue ( ) ) ;
2018-01-05 03:14:46 +01:00
} ) ;
} ) ;
2018-08-08 20:06:20 +02:00
}
2017-10-11 10:34:04 +02:00
2018-08-08 20:06:20 +02:00
void ChannelView : : initializeScrollbar ( )
{
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > getCurrentValueChanged ( ) . connect ( [ this ] {
2018-11-03 21:26:57 +01:00
this - > performLayout ( true ) ;
2018-08-08 20:06:20 +02:00
this - > queueUpdate ( ) ;
2018-04-06 18:27:49 +02:00
} ) ;
2018-05-23 13:54:42 +02:00
}
2017-01-22 12:46:35 +01:00
2018-08-08 20:06:20 +02:00
void ChannelView : : initializeSignals ( )
2017-01-22 12:46:35 +01:00
{
2018-08-08 20:06:20 +02:00
this - > connections_ . push_back (
getApp ( ) - > windows - > wordFlagsChanged . connect ( [ this ] {
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2018-08-08 20:06:20 +02:00
this - > update ( ) ;
} ) ) ;
2018-08-12 12:56:28 +02:00
getSettings ( ) - > showLastMessageIndicator . connect (
2020-11-08 12:02:19 +01:00
[ this ] ( auto , auto ) {
this - > update ( ) ;
} ,
this - > connections_ ) ;
2018-08-08 20:06:20 +02:00
2020-11-08 12:02:19 +01:00
connections_ . push_back ( getApp ( ) - > windows - > gifRepaintRequested . connect ( [ & ] {
this - > queueUpdate ( ) ;
} ) ) ;
2018-08-08 20:06:20 +02:00
connections_ . push_back (
2018-11-14 17:26:08 +01:00
getApp ( ) - > windows - > layoutRequested . connect ( [ & ] ( Channel * channel ) {
if ( this - > isVisible ( ) & &
( channel = = nullptr | | this - > channel_ . get ( ) = = channel ) )
{
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2018-11-14 17:26:08 +01:00
}
2018-08-08 20:06:20 +02:00
} ) ) ;
2020-11-08 12:02:19 +01:00
connections_ . push_back ( getApp ( ) - > fonts - > fontChanged . connect ( [ this ] {
this - > queueLayout ( ) ;
} ) ) ;
2018-11-03 21:26:57 +01:00
}
2018-12-02 18:37:51 +01:00
bool ChannelView : : pausable ( ) const
{
return pausable_ ;
}
void ChannelView : : setPausable ( bool value )
{
this - > pausable_ = value ;
}
2018-11-03 21:26:57 +01:00
bool ChannelView : : paused ( ) const
{
/// No elements in the map -> not paused
2018-12-02 18:37:51 +01:00
return this - > pausable ( ) & & ! this - > pauses_ . empty ( ) ;
2018-11-03 21:26:57 +01:00
}
void ChannelView : : pause ( PauseReason reason , boost : : optional < uint > msecs )
{
if ( msecs )
{
/// Msecs has a value
auto timePoint =
SteadyClock : : now ( ) + std : : chrono : : milliseconds ( msecs . get ( ) ) ;
auto it = this - > pauses_ . find ( reason ) ;
if ( it = = this - > pauses_ . end ( ) )
{
/// No value found so we insert a new one.
this - > pauses_ [ reason ] = timePoint ;
}
else
{
/// If the new time point is newer then we override.
if ( it - > second & & it - > second . get ( ) < timePoint )
it - > second = timePoint ;
}
}
else
{
/// Msecs is none -> pause is infinite.
/// We just override the value.
this - > pauses_ [ reason ] = boost : : none ;
}
2019-09-25 23:47:13 +02:00
this - > updatePauses ( ) ;
2018-11-03 21:26:57 +01:00
}
void ChannelView : : unpause ( PauseReason reason )
{
/// Remove the value from the map
this - > pauses_ . erase ( reason ) ;
2019-09-25 23:47:13 +02:00
this - > updatePauses ( ) ;
2018-11-03 21:26:57 +01:00
}
2019-09-25 23:47:13 +02:00
void ChannelView : : updatePauses ( )
2018-11-03 21:26:57 +01:00
{
using namespace std : : chrono ;
if ( this - > pauses_ . empty ( ) )
{
2019-09-25 23:47:13 +02:00
this - > unpaused ( ) ;
2018-11-03 21:26:57 +01:00
/// No pauses so we can stop the timer
2018-12-04 08:56:07 +01:00
this - > pauseEnd_ = boost : : none ;
2018-11-03 21:26:57 +01:00
this - > pauseTimer_ . stop ( ) ;
2018-12-04 08:56:07 +01:00
this - > scrollBar_ - > offset ( this - > pauseScrollOffset_ ) ;
this - > pauseScrollOffset_ = 0 ;
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
}
else if ( std : : any_of ( this - > pauses_ . begin ( ) , this - > pauses_ . end ( ) ,
2020-11-08 12:02:19 +01:00
[ ] ( auto & & value ) {
return ! value . second ;
} ) )
2018-11-03 21:26:57 +01:00
{
/// Some of the pauses are infinite
2018-12-04 08:56:07 +01:00
this - > pauseEnd_ = boost : : none ;
2018-11-03 21:26:57 +01:00
this - > pauseTimer_ . stop ( ) ;
}
else
{
/// Get the maximum pause
2018-12-04 21:07:55 +01:00
auto pauseEnd =
2020-11-08 12:02:19 +01:00
std : : max_element ( this - > pauses_ . begin ( ) , this - > pauses_ . end ( ) ,
[ ] ( auto & & a , auto & & b ) {
return a . second > b . second ;
} )
2018-12-04 21:07:55 +01:00
- > second . get ( ) ;
2018-11-03 21:26:57 +01:00
2018-12-04 21:07:55 +01:00
if ( pauseEnd ! = this - > pauseEnd_ )
2018-11-03 21:26:57 +01:00
{
/// Start the timer
2018-12-04 21:07:55 +01:00
this - > pauseEnd_ = pauseEnd ;
2018-11-03 21:26:57 +01:00
this - > pauseTimer_ . start (
2018-12-04 21:07:55 +01:00
duration_cast < milliseconds > ( pauseEnd - SteadyClock : : now ( ) ) ) ;
2018-11-03 21:26:57 +01:00
}
}
2017-01-01 02:30:42 +01:00
}
2017-01-03 21:19:33 +01:00
2019-09-25 23:47:13 +02:00
void ChannelView : : unpaused ( )
{
/// Move selection
this - > selection_ . selectionMin . messageIndex - = this - > pauseSelectionOffset_ ;
this - > selection_ . selectionMax . messageIndex - = this - > pauseSelectionOffset_ ;
this - > selection_ . start . messageIndex - = this - > pauseSelectionOffset_ ;
this - > selection_ . end . messageIndex - = this - > pauseSelectionOffset_ ;
this - > pauseSelectionOffset_ = 0 ;
}
2018-07-06 17:11:37 +02:00
void ChannelView : : themeChangedEvent ( )
2018-01-24 20:35:26 +01:00
{
2018-07-06 17:11:37 +02:00
BaseWidget : : themeChangedEvent ( ) ;
2018-01-24 20:35:26 +01:00
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2018-01-24 20:35:26 +01:00
}
2018-12-02 18:26:21 +01:00
void ChannelView : : scaleChangedEvent ( float scale )
{
BaseWidget : : scaleChangedEvent ( scale ) ;
if ( this - > goToBottom_ )
{
2019-04-27 14:42:51 +02:00
auto factor = this - > qtFontScale ( ) ;
# ifdef Q_OS_MACOS
2020-04-19 21:05:40 +02:00
factor = scale * 80.f /
std : : max < float > (
0.01 , this - > logicalDpiX ( ) * this - > devicePixelRatioF ( ) ) ;
2019-04-27 14:42:51 +02:00
# endif
2018-12-02 18:26:21 +01:00
this - > goToBottom_ - > getLabel ( ) . setFont (
2019-04-27 14:42:51 +02:00
getFonts ( ) - > getFont ( FontStyle : : UiMedium , factor ) ) ;
2018-12-02 18:26:21 +01:00
}
}
2017-10-11 10:34:04 +02:00
void ChannelView : : queueUpdate ( )
2017-01-03 21:19:33 +01:00
{
2018-04-06 23:31:34 +02:00
// if (this->updateTimer.isActive()) {
// this->updateQueued = true;
// return;
// }
2017-10-11 10:34:04 +02:00
2018-01-19 23:41:02 +01:00
// this->repaint();
2019-06-04 19:59:08 +02:00
2018-01-19 23:41:02 +01:00
this - > update ( ) ;
2017-10-11 10:34:04 +02:00
2018-04-06 23:31:34 +02:00
// this->updateTimer.start();
2017-10-11 10:34:04 +02:00
}
2018-11-03 21:26:57 +01:00
void ChannelView : : queueLayout ( )
2017-10-11 10:34:04 +02:00
{
2018-04-10 02:07:25 +02:00
// if (!this->layoutCooldown->isActive()) {
2018-11-03 21:26:57 +01:00
this - > performLayout ( ) ;
2018-04-06 18:27:49 +02:00
2018-04-10 02:07:25 +02:00
// this->layoutCooldown->start();
// } else {
// this->layoutQueued = true;
// }
2017-10-11 10:34:04 +02:00
}
2018-11-03 21:26:57 +01:00
void ChannelView : : performLayout ( bool causedByScrollbar )
2017-10-11 10:34:04 +02:00
{
2018-11-03 21:26:57 +01:00
// BenchmarkGuard benchmark("layout");
2017-02-03 19:31:51 +01:00
2018-11-03 21:26:57 +01:00
/// Get messages and check if there are at least 1
auto messages = this - > getMessagesSnapshot ( ) ;
2017-02-03 19:31:51 +01:00
2018-08-06 21:17:03 +02:00
this - > showingLatestMessages_ =
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > isAtBottom ( ) | | ! this - > scrollBar_ - > isVisible ( ) ;
2017-06-06 17:18:23 +02:00
2018-11-03 21:26:57 +01:00
/// Layout visible messages
this - > layoutVisibleMessages ( messages ) ;
/// Update scrollbar
this - > updateScrollbar ( messages , causedByScrollbar ) ;
2018-11-05 13:38:30 +01:00
this - > goToBottom_ - > setVisible ( this - > enableScrollingToBottom_ & &
this - > scrollBar_ - > isVisible ( ) & &
! this - > scrollBar_ - > isAtBottom ( ) ) ;
2018-11-03 21:26:57 +01:00
}
2017-02-03 19:31:51 +01:00
2018-11-03 21:26:57 +01:00
void ChannelView : : layoutVisibleMessages (
LimitedQueueSnapshot < MessageLayoutPtr > & messages )
{
const auto start = size_t ( this - > scrollBar_ - > getCurrentValue ( ) ) ;
const auto layoutWidth = this - > getLayoutWidth ( ) ;
const auto flags = this - > getFlags ( ) ;
auto redrawRequired = false ;
2018-01-17 16:52:51 +01:00
2018-11-03 21:26:57 +01:00
if ( messages . size ( ) > start )
2018-10-21 13:43:02 +02:00
{
2018-11-03 21:26:57 +01:00
auto y = int ( - ( messages [ start ] - > getHeight ( ) *
( fmod ( this - > scrollBar_ - > getCurrentValue ( ) , 1 ) ) ) ) ;
2017-02-06 11:38:26 +01:00
2018-12-02 19:20:14 +01:00
for ( auto i = start ; i < messages . size ( ) & & y < = this - > height ( ) ; i + + )
2018-10-21 13:43:02 +02:00
{
2018-11-03 21:26:57 +01:00
auto message = messages [ i ] ;
2017-01-16 03:15:07 +01:00
2018-08-06 21:17:03 +02:00
redrawRequired | =
2018-11-21 21:37:41 +01:00
message - > layout ( layoutWidth , this - > scale ( ) , flags ) ;
2017-02-02 20:35:12 +01:00
2017-02-06 18:31:25 +01:00
y + = message - > getHeight ( ) ;
2017-02-03 19:31:51 +01:00
}
}
2017-01-16 03:15:07 +01:00
2018-11-03 21:26:57 +01:00
if ( redrawRequired )
this - > queueUpdate ( ) ;
}
void ChannelView : : updateScrollbar (
LimitedQueueSnapshot < MessageLayoutPtr > & messages , bool causedByScrollbar )
{
if ( messages . size ( ) = = 0 )
{
this - > scrollBar_ - > setVisible ( false ) ;
return ;
}
2017-01-26 04:26:40 +01:00
2018-11-03 21:26:57 +01:00
/// Layout the messages at the bottom
auto h = this - > height ( ) - 8 ;
auto flags = this - > getFlags ( ) ;
auto layoutWidth = this - > getLayoutWidth ( ) ;
auto showScrollbar = false ;
// convert i to int since it checks >= 0
for ( auto i = int ( messages . size ( ) ) - 1 ; i > = 0 ; i - - )
2018-10-21 13:43:02 +02:00
{
2018-11-03 21:26:57 +01:00
auto * message = messages [ i ] . get ( ) ;
2017-01-16 03:15:07 +01:00
2018-11-21 21:37:41 +01:00
message - > layout ( layoutWidth , this - > scale ( ) , flags ) ;
2017-01-26 04:26:40 +01:00
2017-01-26 07:10:46 +01:00
h - = message - > getHeight ( ) ;
2018-11-03 21:26:57 +01:00
if ( h < 0 ) // break condition
2018-10-21 13:43:02 +02:00
{
2020-04-19 21:05:40 +02:00
this - > scrollBar_ - > setLargeChange (
( messages . size ( ) - i ) +
qreal ( h ) / std : : max < int > ( 1 , message - > getHeight ( ) ) ) ;
2017-01-26 07:10:46 +01:00
showScrollbar = true ;
2017-02-01 16:28:28 +01:00
break ;
2017-01-26 07:10:46 +01:00
}
2017-01-26 04:26:40 +01:00
}
2018-11-03 21:26:57 +01:00
/// Update scrollbar values
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > setVisible ( showScrollbar ) ;
2017-01-26 07:10:46 +01:00
2018-10-21 13:43:02 +02:00
if ( ! showScrollbar & & ! causedByScrollbar )
{
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > setDesiredValue ( 0 ) ;
2017-02-07 00:03:15 +01:00
}
2018-11-03 21:26:57 +01:00
this - > scrollBar_ - > setMaximum ( messages . size ( ) ) ;
2017-01-26 07:10:46 +01:00
2018-08-06 21:17:03 +02:00
// If we were showing the latest messages and the scrollbar now wants to be
// rendered, scroll to bottom
if ( this - > enableScrollingToBottom_ & & this - > showingLatestMessages_ & &
2018-10-21 13:43:02 +02:00
showScrollbar )
{
2018-11-03 21:26:57 +01:00
this - > scrollBar_ - > scrollToBottom (
// this->messageWasAdded &&
getSettings ( ) - > enableSmoothScrollingNewMessages . getValue ( ) ) ;
2018-06-13 13:27:10 +02:00
this - > messageWasAdded_ = false ;
2017-06-06 17:18:23 +02:00
}
2017-01-26 04:26:40 +01:00
}
2017-09-16 00:05:06 +02:00
void ChannelView : : clearMessages ( )
{
// Clear all stored messages in this chat widget
2018-11-14 17:26:08 +01:00
this - > messages_ . clear ( ) ;
2018-09-02 02:02:12 +02:00
this - > scrollBar_ - > clearHighlights ( ) ;
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2020-07-18 14:44:10 +02:00
this - > lastMessageHasAlternateBackground_ = false ;
this - > lastMessageHasAlternateBackgroundReverse_ = true ;
2017-09-16 00:05:06 +02:00
}
2018-01-06 03:48:56 +01:00
Scrollbar & ChannelView : : getScrollBar ( )
2017-04-12 17:46:44 +02:00
{
2018-08-11 22:23:06 +02:00
return * this - > scrollBar_ ;
2017-04-12 17:46:44 +02:00
}
2017-09-16 00:05:06 +02:00
QString ChannelView : : getSelectedText ( )
2017-09-12 19:06:16 +02:00
{
2018-01-16 02:39:31 +01:00
QString result = " " ;
2017-09-12 19:06:16 +02:00
2018-08-06 21:17:03 +02:00
LimitedQueueSnapshot < MessageLayoutPtr > messagesSnapshot =
this - > getMessagesSnapshot ( ) ;
2017-09-12 19:06:16 +02:00
2018-06-13 13:27:10 +02:00
Selection _selection = this - > selection_ ;
2017-09-12 19:06:16 +02:00
2018-10-21 13:43:02 +02:00
if ( _selection . isEmpty ( ) )
{
2018-01-16 02:39:31 +01:00
return result ;
}
2017-09-12 19:06:16 +02:00
2018-05-06 14:57:57 +02:00
for ( int msg = _selection . selectionMin . messageIndex ;
2018-10-21 13:43:02 +02:00
msg < = _selection . selectionMax . messageIndex ; msg + + )
{
2018-01-16 02:39:31 +01:00
MessageLayoutPtr layout = messagesSnapshot [ msg ] ;
2018-08-06 21:17:03 +02:00
int from = msg = = _selection . selectionMin . messageIndex
? _selection . selectionMin . charIndex
: 0 ;
int to = msg = = _selection . selectionMax . messageIndex
? _selection . selectionMax . charIndex
: layout - > getLastCharacterIndex ( ) + 1 ;
2018-04-10 15:48:56 +02:00
2018-01-16 02:39:31 +01:00
layout - > addSelectionText ( result , from , to ) ;
}
2017-09-12 19:06:16 +02:00
2018-01-16 02:39:31 +01:00
return result ;
2017-09-12 19:06:16 +02:00
}
2017-09-21 02:20:02 +02:00
bool ChannelView : : hasSelection ( )
{
2018-06-13 13:27:10 +02:00
return ! this - > selection_ . isEmpty ( ) ;
2017-09-21 02:20:02 +02:00
}
void ChannelView : : clearSelection ( )
{
2018-06-13 13:27:10 +02:00
this - > selection_ = Selection ( ) ;
2018-11-03 21:26:57 +01:00
queueLayout ( ) ;
2017-09-21 02:20:02 +02:00
}
2017-12-28 00:48:21 +01:00
void ChannelView : : setEnableScrollingToBottom ( bool value )
{
2018-06-13 13:27:10 +02:00
this - > enableScrollingToBottom_ = value ;
2017-12-28 00:48:21 +01:00
}
bool ChannelView : : getEnableScrollingToBottom ( ) const
{
2018-06-13 13:27:10 +02:00
return this - > enableScrollingToBottom_ ;
2017-12-28 00:48:21 +01:00
}
2018-08-07 07:55:31 +02:00
void ChannelView : : setOverrideFlags ( boost : : optional < MessageElementFlags > value )
2018-01-27 21:13:22 +01:00
{
2021-04-10 14:34:40 +02:00
this - > overrideFlags_ = std : : move ( value ) ;
2018-01-27 21:13:22 +01:00
}
2018-08-07 07:55:31 +02:00
const boost : : optional < MessageElementFlags > & ChannelView : : getOverrideFlags ( )
2018-08-06 21:17:03 +02:00
const
2018-01-27 21:13:22 +01:00
{
2018-06-13 13:27:10 +02:00
return this - > overrideFlags_ ;
2018-01-27 21:13:22 +01:00
}
2018-06-28 19:38:57 +02:00
LimitedQueueSnapshot < MessageLayoutPtr > ChannelView : : getMessagesSnapshot ( )
2017-09-16 00:05:06 +02:00
{
2018-11-03 21:26:57 +01:00
if ( ! this - > paused ( ) /*|| this->scrollBar_->isVisible()*/ )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > snapshot_ = this - > messages_ . getSnapshot ( ) ;
2018-06-13 13:27:10 +02:00
}
2018-01-05 11:22:51 +01:00
2018-06-13 13:27:10 +02:00
return this - > snapshot_ ;
2017-09-16 00:05:06 +02:00
}
2018-11-14 17:26:08 +01:00
ChannelPtr ChannelView : : channel ( )
{
return this - > channel_ ;
}
2020-10-18 15:16:56 +02:00
bool ChannelView : : showScrollbarHighlights ( ) const
{
return this - > channel_ - > getType ( ) ! = Channel : : Type : : TwitchMentions ;
}
void ChannelView : : setChannel ( ChannelPtr underlyingChannel )
2017-09-16 00:05:06 +02:00
{
2018-11-14 17:26:08 +01:00
/// Clear connections from the last channel
this - > channelConnections_ . clear ( ) ;
2018-04-20 19:54:45 +02:00
2018-09-26 21:16:40 +02:00
this - > clearMessages ( ) ;
2019-09-16 11:54:34 +02:00
this - > scrollBar_ - > clearHighlights ( ) ;
2017-09-16 00:05:06 +02:00
2020-10-18 15:16:56 +02:00
/// make copy of channel and expose
this - > channel_ = std : : make_unique < Channel > ( underlyingChannel - > getName ( ) ,
underlyingChannel - > getType ( ) ) ;
//
// Proxy channel connections
// Use a proxy channel to keep filtered messages past the time they are removed from their origin channel
//
this - > channelConnections_ . push_back (
underlyingChannel - > messageAppended . connect (
[ this ] ( MessagePtr & message ,
boost : : optional < MessageFlags > overridingFlags ) {
if ( this - > shouldIncludeMessage ( message ) )
{
2021-05-09 18:44:57 +02:00
if ( this - > channel_ - > lastDate_ ! = QDate : : currentDate ( ) )
{
this - > channel_ - > lastDate_ = QDate : : currentDate ( ) ;
2021-05-16 00:34:42 +02:00
auto msg = makeSystemMessage (
QLocale ( ) . toString ( QDate : : currentDate ( ) ,
QLocale : : LongFormat ) ,
QTime ( 0 , 0 ) ) ;
2021-05-09 18:44:57 +02:00
this - > channel_ - > addMessage ( msg ) ;
}
2020-10-18 15:16:56 +02:00
// When the message was received in the underlyingChannel,
// logging will be handled. Prevent duplications.
if ( overridingFlags )
{
overridingFlags . get ( ) . set ( MessageFlag : : DoNotLog ) ;
}
else
{
2020-11-17 18:47:07 +01:00
overridingFlags = MessageFlags ( message - > flags ) ;
2020-10-21 16:27:24 +02:00
overridingFlags . get ( ) . set ( MessageFlag : : DoNotLog ) ;
2020-10-18 15:16:56 +02:00
}
this - > channel_ - > addMessage ( message , overridingFlags ) ;
}
} ) ) ;
this - > channelConnections_ . push_back (
underlyingChannel - > messagesAddedAtStart . connect (
[ this ] ( std : : vector < MessagePtr > & messages ) {
std : : vector < MessagePtr > filtered ;
2020-11-08 12:02:19 +01:00
std : : copy_if ( messages . begin ( ) , messages . end ( ) ,
std : : back_inserter ( filtered ) ,
[ this ] ( MessagePtr msg ) {
return this - > shouldIncludeMessage ( msg ) ;
} ) ;
2020-10-18 15:16:56 +02:00
if ( ! filtered . empty ( ) )
this - > channel_ - > addMessagesAtStart ( filtered ) ;
} ) ) ;
this - > channelConnections_ . push_back (
underlyingChannel - > messageReplaced . connect (
[ this ] ( size_t index , MessagePtr replacement ) {
if ( this - > shouldIncludeMessage ( replacement ) )
this - > channel_ - > replaceMessage ( index , replacement ) ;
} ) ) ;
//
// Standard channel connections
//
2017-09-16 00:05:06 +02:00
// on new message
2020-10-18 15:16:56 +02:00
this - > channelConnections_ . push_back ( this - > channel_ - > messageAppended . connect (
2018-10-05 23:33:01 +02:00
[ this ] ( MessagePtr & message ,
boost : : optional < MessageFlags > overridingFlags ) {
2021-04-10 14:34:40 +02:00
this - > messageAppended ( message , std : : move ( overridingFlags ) ) ;
2018-06-13 03:58:52 +02:00
} ) ) ;
2018-01-01 22:29:21 +01:00
2020-10-18 15:16:56 +02:00
this - > channelConnections_ . push_back (
this - > channel_ - > messagesAddedAtStart . connect (
[ this ] ( std : : vector < MessagePtr > & messages ) {
this - > messageAddedAtStart ( messages ) ;
} ) ) ;
2017-09-16 00:05:06 +02:00
// on message removed
2018-06-13 03:58:52 +02:00
this - > channelConnections_ . push_back (
2020-10-18 15:16:56 +02:00
this - > channel_ - > messageRemovedFromStart . connect (
[ this ] ( MessagePtr & message ) {
this - > messageRemoveFromStart ( message ) ;
} ) ) ;
2017-09-16 00:05:06 +02:00
2018-01-05 23:14:55 +01:00
// on message replaced
2020-10-18 15:16:56 +02:00
this - > channelConnections_ . push_back ( this - > channel_ - > messageReplaced . connect (
2018-08-06 21:17:03 +02:00
[ this ] ( size_t index , MessagePtr replacement ) {
2018-11-03 21:26:57 +01:00
this - > messageReplaced ( index , replacement ) ;
2018-06-13 03:58:52 +02:00
} ) ) ;
2018-01-05 23:14:55 +01:00
2020-10-18 15:16:56 +02:00
auto snapshot = underlyingChannel - > getMessageSnapshot ( ) ;
2017-09-16 00:05:06 +02:00
2018-11-03 21:26:57 +01:00
for ( size_t i = 0 ; i < snapshot . size ( ) ; i + + )
2018-10-21 13:43:02 +02:00
{
2018-01-11 20:16:25 +01:00
MessageLayoutPtr deleted ;
2017-09-16 00:05:06 +02:00
2019-09-16 11:54:34 +02:00
auto messageLayout = new MessageLayout ( snapshot [ i ] ) ;
2017-09-16 00:05:06 +02:00
2018-10-21 13:43:02 +02:00
if ( this - > lastMessageHasAlternateBackground_ )
{
2019-09-16 11:54:34 +02:00
messageLayout - > flags . set ( MessageLayoutFlag : : AlternateBackground ) ;
2018-05-06 14:57:57 +02:00
}
2018-08-06 21:17:03 +02:00
this - > lastMessageHasAlternateBackground_ =
! this - > lastMessageHasAlternateBackground_ ;
2018-05-06 14:57:57 +02:00
2020-10-18 15:16:56 +02:00
if ( underlyingChannel - > shouldIgnoreHighlights ( ) )
2019-09-21 22:19:03 +02:00
{
2019-09-22 10:42:22 +02:00
messageLayout - > flags . set ( MessageLayoutFlag : : IgnoreHighlights ) ;
2019-09-21 22:19:03 +02:00
}
2019-09-16 11:54:34 +02:00
this - > messages_ . pushBack ( MessageLayoutPtr ( messageLayout ) , deleted ) ;
2020-10-18 15:16:56 +02:00
if ( this - > showScrollbarHighlights ( ) )
{
this - > scrollBar_ - > addHighlight (
snapshot [ i ] - > getScrollBarHighlight ( ) ) ;
}
2017-09-16 00:05:06 +02:00
}
2020-10-18 15:16:56 +02:00
this - > underlyingChannel_ = underlyingChannel ;
2017-09-16 00:05:06 +02:00
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2018-01-07 00:05:32 +01:00
this - > queueUpdate ( ) ;
2018-08-17 21:19:15 +02:00
// Notifications
2020-10-18 15:16:56 +02:00
if ( auto tc = dynamic_cast < TwitchChannel * > ( underlyingChannel . get ( ) ) )
2018-10-21 13:43:02 +02:00
{
2018-11-10 14:30:19 +01:00
this - > connections_ . push_back ( tc - > liveStatusChanged . connect ( [ this ] ( ) {
2020-11-08 12:02:19 +01:00
this - > liveStatusChanged . invoke ( ) ;
2018-11-10 14:30:19 +01:00
} ) ) ;
2018-08-17 21:19:15 +02:00
}
2017-09-16 00:05:06 +02:00
}
2020-10-18 15:16:56 +02:00
void ChannelView : : setFilters ( const QList < QUuid > & ids )
{
this - > channelFilters_ = std : : make_shared < FilterSet > ( ids ) ;
}
const QList < QUuid > ChannelView : : getFilterIds ( ) const
{
if ( ! this - > channelFilters_ )
{
return QList < QUuid > ( ) ;
}
return this - > channelFilters_ - > filterIds ( ) ;
}
FilterSetPtr ChannelView : : getFilterSet ( ) const
{
return this - > channelFilters_ ;
}
bool ChannelView : : shouldIncludeMessage ( const MessagePtr & m ) const
{
if ( this - > channelFilters_ )
{
if ( getSettings ( ) - > excludeUserMessagesFromFilter & &
getApp ( ) - > accounts - > twitch . getCurrent ( ) - > getUserName ( ) . compare (
m - > loginName , Qt : : CaseInsensitive ) = = 0 )
return true ;
2021-08-03 09:39:27 +02:00
return this - > channelFilters_ - > filter ( m , this - > channel_ ) ;
2020-10-18 15:16:56 +02:00
}
return true ;
}
2020-06-21 14:15:14 +02:00
ChannelPtr ChannelView : : sourceChannel ( ) const
{
return this - > sourceChannel_ ;
}
void ChannelView : : setSourceChannel ( ChannelPtr sourceChannel )
{
2021-04-10 14:34:40 +02:00
this - > sourceChannel_ = std : : move ( sourceChannel ) ;
2020-06-21 14:15:14 +02:00
}
bool ChannelView : : hasSourceChannel ( ) const
{
return this - > sourceChannel_ ! = nullptr ;
}
2018-11-03 21:26:57 +01:00
void ChannelView : : messageAppended ( MessagePtr & message ,
boost : : optional < MessageFlags > overridingFlags )
2017-09-16 00:05:06 +02:00
{
2018-11-03 21:26:57 +01:00
MessageLayoutPtr deleted ;
auto * messageFlags = & message - > flags ;
if ( overridingFlags )
{
messageFlags = overridingFlags . get_ptr ( ) ;
}
auto messageRef = new MessageLayout ( message ) ;
if ( this - > lastMessageHasAlternateBackground_ )
{
messageRef - > flags . set ( MessageLayoutFlag : : AlternateBackground ) ;
}
if ( this - > channel_ - > shouldIgnoreHighlights ( ) )
{
messageRef - > flags . set ( MessageLayoutFlag : : IgnoreHighlights ) ;
}
this - > lastMessageHasAlternateBackground_ =
! this - > lastMessageHasAlternateBackground_ ;
2021-04-24 17:15:15 +02:00
if ( ! this - > scrollBar_ - > isAtBottom ( ) & &
this - > scrollBar_ - > getCurrentValueAnimation ( ) . state ( ) = =
QPropertyAnimation : : Running )
{
QEventLoop loop ;
connect ( & this - > scrollBar_ - > getCurrentValueAnimation ( ) ,
& QAbstractAnimation : : stateChanged , & loop , & QEventLoop : : quit ) ;
loop . exec ( ) ;
}
2018-11-14 17:26:08 +01:00
if ( this - > messages_ . pushBack ( MessageLayoutPtr ( messageRef ) , deleted ) )
2018-11-03 21:26:57 +01:00
{
2018-12-04 08:56:07 +01:00
if ( this - > paused ( ) )
2018-11-03 21:26:57 +01:00
{
2018-12-04 08:56:07 +01:00
if ( ! this - > scrollBar_ - > isAtBottom ( ) )
this - > pauseScrollOffset_ - - ;
2018-11-03 21:26:57 +01:00
}
else
{
2018-12-04 08:56:07 +01:00
if ( this - > scrollBar_ - > isAtBottom ( ) )
this - > scrollBar_ - > scrollToBottom ( ) ;
else
this - > scrollBar_ - > offset ( - 1 ) ;
2018-11-03 21:26:57 +01:00
}
}
if ( ! messageFlags - > has ( MessageFlag : : DoNotTriggerNotification ) )
{
2020-01-26 10:08:25 +01:00
if ( messageFlags - > has ( MessageFlag : : Highlighted ) & &
2020-10-24 14:33:15 +02:00
messageFlags - > has ( MessageFlag : : ShowInMentions ) & &
2021-01-16 14:27:19 +01:00
! messageFlags - > has ( MessageFlag : : Subscription ) & &
( getSettings ( ) - > highlightMentions | |
this - > channel_ - > getType ( ) ! = Channel : : Type : : TwitchMentions ) )
2018-11-03 21:26:57 +01:00
{
this - > tabHighlightRequested . invoke ( HighlightState : : Highlighted ) ;
}
else
{
this - > tabHighlightRequested . invoke ( HighlightState : : NewMessage ) ;
}
}
2020-10-18 15:16:56 +02:00
if ( this - > showScrollbarHighlights ( ) )
2018-11-03 21:26:57 +01:00
{
this - > scrollBar_ - > addHighlight ( message - > getScrollBarHighlight ( ) ) ;
}
this - > messageWasAdded_ = true ;
this - > queueLayout ( ) ;
2017-09-16 00:05:06 +02:00
}
2018-11-03 21:26:57 +01:00
void ChannelView : : messageAddedAtStart ( std : : vector < MessagePtr > & messages )
2018-01-05 11:22:51 +01:00
{
2018-11-03 21:26:57 +01:00
std : : vector < MessageLayoutPtr > messageRefs ;
messageRefs . resize ( messages . size ( ) ) ;
2018-11-03 21:40:48 +01:00
/// Create message layouts
2018-11-03 21:26:57 +01:00
for ( size_t i = 0 ; i < messages . size ( ) ; i + + )
{
2020-10-18 15:16:56 +02:00
auto message = messages . at ( i ) ;
auto layout = new MessageLayout ( message ) ;
2018-11-03 21:40:48 +01:00
// alternate color
if ( ! this - > lastMessageHasAlternateBackgroundReverse_ )
layout - > flags . set ( MessageLayoutFlag : : AlternateBackground ) ;
this - > lastMessageHasAlternateBackgroundReverse_ =
! this - > lastMessageHasAlternateBackgroundReverse_ ;
messageRefs . at ( i ) = MessageLayoutPtr ( layout ) ;
2018-11-03 21:26:57 +01:00
}
2018-01-05 11:22:51 +01:00
2018-11-03 21:40:48 +01:00
/// Add the messages at the start
2018-11-14 17:26:08 +01:00
if ( this - > messages_ . pushFront ( messageRefs ) . size ( ) > 0 )
2018-10-21 13:43:02 +02:00
{
2018-11-03 21:40:48 +01:00
if ( this - > scrollBar_ - > isAtBottom ( ) )
this - > scrollBar_ - > scrollToBottom ( ) ;
else
this - > scrollBar_ - > offset ( qreal ( messages . size ( ) ) ) ;
2018-11-03 21:26:57 +01:00
}
2020-10-18 15:16:56 +02:00
if ( this - > showScrollbarHighlights ( ) )
2018-11-03 21:26:57 +01:00
{
2020-10-18 15:16:56 +02:00
std : : vector < ScrollbarHighlight > highlights ;
highlights . reserve ( messages . size ( ) ) ;
for ( const auto & message : messages )
{
highlights . push_back ( message - > getScrollBarHighlight ( ) ) ;
}
2018-11-03 21:26:57 +01:00
2020-10-18 15:16:56 +02:00
this - > scrollBar_ - > addHighlightsAtStart ( highlights ) ;
}
2018-11-03 21:26:57 +01:00
this - > messageWasAdded_ = true ;
this - > queueLayout ( ) ;
}
void ChannelView : : messageRemoveFromStart ( MessagePtr & message )
{
2019-09-16 10:55:54 +02:00
if ( this - > paused ( ) )
{
this - > pauseSelectionOffset_ + = 1 ;
}
else
{
this - > selection_ . selectionMin . messageIndex - - ;
this - > selection_ . selectionMax . messageIndex - - ;
this - > selection_ . start . messageIndex - - ;
this - > selection_ . end . messageIndex - - ;
}
2018-06-13 13:27:10 +02:00
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
}
void ChannelView : : messageReplaced ( size_t index , MessagePtr & replacement )
{
2018-12-04 08:56:07 +01:00
if ( index > = this - > messages_ . getSnapshot ( ) . size ( ) )
2018-11-03 21:26:57 +01:00
{
return ;
}
MessageLayoutPtr newItem ( new MessageLayout ( replacement ) ) ;
2018-11-14 17:26:08 +01:00
auto snapshot = this - > messages_ . getSnapshot ( ) ;
2018-11-03 21:26:57 +01:00
if ( index > = snapshot . size ( ) )
{
2020-11-21 16:20:10 +01:00
qCDebug ( chatterinoWidget )
< < " Tried to replace out of bounds message. Index: " < < index
< < " . Length: " < < snapshot . size ( ) ;
2018-11-03 21:26:57 +01:00
return ;
2018-06-13 13:27:10 +02:00
}
2018-11-03 21:26:57 +01:00
const auto & message = snapshot [ index ] ;
if ( message - > flags . has ( MessageLayoutFlag : : AlternateBackground ) )
{
newItem - > flags . set ( MessageLayoutFlag : : AlternateBackground ) ;
}
this - > scrollBar_ - > replaceHighlight ( index ,
replacement - > getScrollBarHighlight ( ) ) ;
2018-11-14 17:26:08 +01:00
this - > messages_ . replaceItem ( message , newItem ) ;
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
}
2018-01-23 22:48:33 +01:00
void ChannelView : : updateLastReadMessage ( )
{
auto _snapshot = this - > getMessagesSnapshot ( ) ;
2018-11-03 21:26:57 +01:00
if ( _snapshot . size ( ) > 0 )
2018-10-21 13:43:02 +02:00
{
2018-11-03 21:26:57 +01:00
this - > lastReadMessage_ = _snapshot [ _snapshot . size ( ) - 1 ] ;
2018-01-23 22:48:33 +01:00
}
this - > update ( ) ;
}
2017-09-16 00:05:06 +02:00
void ChannelView : : resizeEvent ( QResizeEvent * )
2017-04-12 17:46:44 +02:00
{
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > setGeometry ( this - > width ( ) - this - > scrollBar_ - > width ( ) , 0 ,
this - > scrollBar_ - > width ( ) , this - > height ( ) ) ;
2017-01-16 03:15:07 +01:00
2018-12-02 18:26:21 +01:00
this - > goToBottom_ - > setGeometry ( 0 , this - > height ( ) - int ( this - > scale ( ) * 26 ) ,
this - > width ( ) , int ( this - > scale ( ) * 26 ) ) ;
2017-09-21 17:34:41 +02:00
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > raise ( ) ;
2017-09-21 17:34:41 +02:00
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2017-02-07 00:03:15 +01:00
2017-06-06 17:18:23 +02:00
this - > update ( ) ;
2017-01-03 21:19:33 +01:00
}
2017-01-05 16:07:20 +01:00
2018-08-06 21:17:03 +02:00
void ChannelView : : setSelection ( const SelectionItem & start ,
const SelectionItem & end )
2017-08-18 15:12:07 +02:00
{
// selections
2018-10-21 13:43:02 +02:00
if ( ! this - > selecting_ & & start ! = end )
{
2018-11-03 21:26:57 +01:00
// this->messagesAddedSinceSelectionPause_ = 0;
2018-05-17 12:16:13 +02:00
2018-06-13 13:27:10 +02:00
this - > selecting_ = true ;
2018-11-03 21:26:57 +01:00
// this->pausedBySelection_ = true;
2018-05-17 12:16:13 +02:00
}
2018-06-13 13:27:10 +02:00
this - > selection_ = Selection ( start , end ) ;
2017-08-18 15:12:07 +02:00
2018-04-03 02:55:32 +02:00
this - > selectionChanged . invoke ( ) ;
2017-08-18 15:12:07 +02:00
}
2018-08-07 07:55:31 +02:00
MessageElementFlags ChannelView : : getFlags ( ) const
2018-01-17 16:52:51 +01:00
{
2018-04-27 22:11:19 +02:00
auto app = getApp ( ) ;
2018-10-21 13:43:02 +02:00
if ( this - > overrideFlags_ )
{
2018-06-13 13:27:10 +02:00
return this - > overrideFlags_ . get ( ) ;
2018-01-27 21:13:22 +01:00
}
2018-08-07 07:55:31 +02:00
MessageElementFlags flags = app - > windows - > getWordFlags ( ) ;
2018-01-17 16:52:51 +01:00
Split * split = dynamic_cast < Split * > ( this - > parentWidget ( ) ) ;
2021-05-23 15:51:53 +02:00
if ( split = = nullptr )
{
SearchPopup * searchPopup =
dynamic_cast < SearchPopup * > ( this - > parentWidget ( ) ) ;
if ( searchPopup ! = nullptr )
{
split = dynamic_cast < Split * > ( searchPopup - > parentWidget ( ) ) ;
}
}
2018-10-21 13:43:02 +02:00
if ( split ! = nullptr )
{
if ( split - > getModerationMode ( ) )
{
2018-08-07 07:55:31 +02:00
flags . set ( MessageElementFlag : : ModeratorTools ) ;
2018-01-17 16:52:51 +01:00
}
2021-05-09 16:17:04 +02:00
if ( this - > underlyingChannel_ = = app - > twitch . server - > mentionsChannel | |
this - > underlyingChannel_ = = app - > twitch . server - > liveChannel )
2018-10-21 13:43:02 +02:00
{
2018-08-07 07:55:31 +02:00
flags . set ( MessageElementFlag : : ChannelName ) ;
2020-08-08 15:37:22 +02:00
flags . unset ( MessageElementFlag : : ChannelPointReward ) ;
2018-01-23 23:28:06 +01:00
}
2018-01-17 16:52:51 +01:00
}
2021-05-22 15:11:24 +02:00
if ( this - > sourceChannel_ = = app - > twitch . server - > mentionsChannel )
flags . set ( MessageElementFlag : : ChannelName ) ;
2018-01-17 16:52:51 +01:00
return flags ;
}
2017-09-16 00:05:06 +02:00
void ChannelView : : paintEvent ( QPaintEvent * /*event*/ )
2017-01-05 16:07:20 +01:00
{
2018-08-09 18:39:46 +02:00
// BenchmarkGuard benchmark("paint");
2017-02-07 00:03:15 +01:00
2018-01-11 20:16:25 +01:00
QPainter painter ( this ) ;
2017-02-07 00:03:15 +01:00
2018-07-06 17:11:37 +02:00
painter . fillRect ( rect ( ) , this - > theme - > splits . background ) ;
2017-08-18 15:12:07 +02:00
// draw messages
2018-01-11 20:16:25 +01:00
this - > drawMessages ( painter ) ;
2018-11-03 21:26:57 +01:00
// draw paused sign
if ( this - > paused ( ) )
{
2020-05-10 12:55:13 +02:00
auto a = this - > scale ( ) * 20 ;
auto brush = QBrush ( QColor ( 127 , 127 , 127 , 255 ) ) ;
painter . fillRect ( QRectF ( 5 , a / 4 , a / 4 , a ) , brush ) ;
painter . fillRect ( QRectF ( 15 , a / 4 , a / 4 , a ) , brush ) ;
2018-11-03 21:26:57 +01:00
}
2017-08-18 15:12:07 +02:00
}
2018-08-06 21:17:03 +02:00
// if overlays is false then it draws the message, if true then it draws things
// such as the grey overlay when a message is disabled
2018-01-11 20:16:25 +01:00
void ChannelView : : drawMessages ( QPainter & painter )
2017-08-18 15:12:07 +02:00
{
2017-12-22 15:13:42 +01:00
auto messagesSnapshot = this - > getMessagesSnapshot ( ) ;
2017-01-11 01:08:20 +01:00
2018-08-11 22:23:06 +02:00
size_t start = size_t ( this - > scrollBar_ - > getCurrentValue ( ) ) ;
2017-01-11 01:08:20 +01:00
2018-11-03 21:26:57 +01:00
if ( start > = messagesSnapshot . size ( ) )
2018-10-21 13:43:02 +02:00
{
2017-01-18 04:33:30 +01:00
return ;
}
2018-05-25 12:45:18 +02:00
int y = int ( - ( messagesSnapshot [ start ] . get ( ) - > getHeight ( ) *
2018-08-11 22:23:06 +02:00
( fmod ( this - > scrollBar_ - > getCurrentValue ( ) , 1 ) ) ) ) ;
2017-01-18 04:33:30 +01:00
2018-06-28 19:38:57 +02:00
MessageLayout * end = nullptr ;
2018-01-23 22:48:33 +01:00
bool windowFocused = this - > window ( ) = = QApplication : : activeWindow ( ) ;
2017-12-26 18:24:02 +01:00
2020-08-08 15:37:22 +02:00
auto app = getApp ( ) ;
2020-10-18 15:16:56 +02:00
bool isMentions =
this - > underlyingChannel_ = = app - > twitch . server - > mentionsChannel ;
2020-08-08 15:37:22 +02:00
2018-11-03 21:26:57 +01:00
for ( size_t i = start ; i < messagesSnapshot . size ( ) ; + + i )
2018-10-21 13:43:02 +02:00
{
2018-06-28 19:38:57 +02:00
MessageLayout * layout = messagesSnapshot [ i ] . get ( ) ;
2017-02-07 00:03:15 +01:00
2018-01-23 22:48:33 +01:00
bool isLastMessage = false ;
2018-10-21 13:43:02 +02:00
if ( getSettings ( ) - > showLastMessageIndicator )
{
2018-06-13 13:27:10 +02:00
isLastMessage = this - > lastReadMessage_ . get ( ) = = layout ;
2018-01-23 22:48:33 +01:00
}
2018-08-06 21:17:03 +02:00
layout - > paint ( painter , DRAW_WIDTH , y , i , this - > selection_ ,
2020-08-08 15:37:22 +02:00
isLastMessage , windowFocused , isMentions ) ;
2017-02-03 19:31:51 +01:00
2018-01-11 20:16:25 +01:00
y + = layout - > getHeight ( ) ;
2017-01-20 06:10:28 +01:00
2018-01-11 20:16:25 +01:00
end = layout ;
2018-10-21 13:43:02 +02:00
if ( y > this - > height ( ) )
{
2017-01-20 06:10:28 +01:00
break ;
}
2017-01-11 01:08:20 +01:00
}
2017-12-26 18:24:02 +01:00
2018-10-21 13:43:02 +02:00
if ( end = = nullptr )
{
2017-12-26 18:24:02 +01:00
return ;
}
// remove messages that are on screen
// the messages that are left at the end get their buffers reset
2018-11-03 21:26:57 +01:00
for ( size_t i = start ; i < messagesSnapshot . size ( ) ; + + i )
2018-10-21 13:43:02 +02:00
{
2018-06-13 13:27:10 +02:00
auto it = this - > messagesOnScreen_ . find ( messagesSnapshot [ i ] ) ;
2018-10-21 13:43:02 +02:00
if ( it ! = this - > messagesOnScreen_ . end ( ) )
{
2018-06-13 13:27:10 +02:00
this - > messagesOnScreen_ . erase ( it ) ;
2017-12-26 18:24:02 +01:00
}
}
// delete the message buffers that aren't on screen
2018-10-21 13:43:02 +02:00
for ( const std : : shared_ptr < MessageLayout > & item : this - > messagesOnScreen_ )
{
2018-01-11 20:16:25 +01:00
item - > deleteBuffer ( ) ;
2017-12-26 18:24:02 +01:00
}
2018-06-13 13:27:10 +02:00
this - > messagesOnScreen_ . clear ( ) ;
2017-12-26 18:24:02 +01:00
// add all messages on screen to the map
2018-11-03 21:26:57 +01:00
for ( size_t i = start ; i < messagesSnapshot . size ( ) ; + + i )
2018-10-21 13:43:02 +02:00
{
2018-06-28 19:38:57 +02:00
std : : shared_ptr < MessageLayout > layout = messagesSnapshot [ i ] ;
2017-12-26 18:24:02 +01:00
2018-06-13 13:27:10 +02:00
this - > messagesOnScreen_ . insert ( layout ) ;
2017-12-26 18:24:02 +01:00
2018-10-21 13:43:02 +02:00
if ( layout . get ( ) = = end )
{
2017-12-26 18:24:02 +01:00
break ;
}
}
2017-08-18 15:12:07 +02:00
}
2017-02-07 00:03:15 +01:00
2017-09-16 00:05:06 +02:00
void ChannelView : : wheelEvent ( QWheelEvent * event )
2017-01-26 04:26:40 +01:00
{
2021-04-26 21:38:16 +02:00
if ( ! event - > angleDelta ( ) . y ( ) )
{
2018-06-19 18:55:45 +02:00
return ;
2021-04-26 21:38:16 +02:00
}
2018-05-17 17:27:20 +02:00
2018-10-21 13:43:02 +02:00
if ( event - > modifiers ( ) & Qt : : ControlModifier )
{
2018-06-11 15:04:54 +02:00
event - > ignore ( ) ;
return ;
}
2018-10-21 13:43:02 +02:00
if ( this - > scrollBar_ - > isVisible ( ) )
{
2018-08-12 12:56:28 +02:00
float mouseMultiplier = getSettings ( ) - > mouseScrollMultiplier ;
2017-04-14 17:47:28 +02:00
2018-08-11 22:23:06 +02:00
qreal desired = this - > scrollBar_ - > getDesiredValue ( ) ;
2021-04-26 21:38:16 +02:00
qreal delta = event - > angleDelta ( ) . y ( ) * qreal ( 1.5 ) * mouseMultiplier ;
2018-01-05 02:55:24 +01:00
auto snapshot = this - > getMessagesSnapshot ( ) ;
2018-11-03 21:26:57 +01:00
int snapshotLength = int ( snapshot . size ( ) ) ;
2018-07-07 11:41:01 +02:00
int i = std : : min < int > ( int ( desired ) , snapshotLength ) ;
2018-01-05 02:55:24 +01:00
2018-10-21 13:43:02 +02:00
if ( delta > 0 )
{
2018-06-13 13:27:10 +02:00
qreal scrollFactor = fmod ( desired , 1 ) ;
2020-04-19 21:05:40 +02:00
qreal currentScrollLeft = std : : max < qreal > (
0.01 , int ( scrollFactor * snapshot [ i ] - > getHeight ( ) ) ) ;
2018-01-05 02:55:24 +01:00
2018-10-21 13:43:02 +02:00
for ( ; i > = 0 ; i - - )
{
if ( delta < currentScrollLeft )
{
2018-01-05 02:55:24 +01:00
desired - = scrollFactor * ( delta / currentScrollLeft ) ;
break ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-01-05 02:55:24 +01:00
delta - = currentScrollLeft ;
desired - = scrollFactor ;
}
2018-10-21 13:43:02 +02:00
if ( i = = 0 )
{
2018-01-05 02:55:24 +01:00
desired = 0 ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
snapshot [ i - 1 ] - > layout ( this - > getLayoutWidth ( ) ,
2018-11-21 21:37:41 +01:00
this - > scale ( ) , this - > getFlags ( ) ) ;
2018-01-05 02:55:24 +01:00
scrollFactor = 1 ;
currentScrollLeft = snapshot [ i - 1 ] - > getHeight ( ) ;
}
}
2018-10-21 13:43:02 +02:00
}
else
{
2018-01-05 02:55:24 +01:00
delta = - delta ;
2018-06-13 13:27:10 +02:00
qreal scrollFactor = 1 - fmod ( desired , 1 ) ;
2020-04-19 21:05:40 +02:00
qreal currentScrollLeft = std : : max < qreal > (
0.01 , int ( scrollFactor * snapshot [ i ] - > getHeight ( ) ) ) ;
2018-01-05 02:55:24 +01:00
2018-10-21 13:43:02 +02:00
for ( ; i < snapshotLength ; i + + )
{
if ( delta < currentScrollLeft )
{
2018-08-06 21:17:03 +02:00
desired + =
scrollFactor * ( qreal ( delta ) / currentScrollLeft ) ;
2018-01-05 02:55:24 +01:00
break ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-01-05 02:55:24 +01:00
delta - = currentScrollLeft ;
desired + = scrollFactor ;
}
2018-10-21 13:43:02 +02:00
if ( i = = snapshotLength - 1 )
{
2018-11-03 21:26:57 +01:00
desired = snapshot . size ( ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-06 21:17:03 +02:00
snapshot [ i + 1 ] - > layout ( this - > getLayoutWidth ( ) ,
2018-11-21 21:37:41 +01:00
this - > scale ( ) , this - > getFlags ( ) ) ;
2018-01-05 02:55:24 +01:00
scrollFactor = 1 ;
currentScrollLeft = snapshot [ i + 1 ] - > getHeight ( ) ;
}
}
}
2018-08-11 22:23:06 +02:00
this - > scrollBar_ - > setDesiredValue ( desired , true ) ;
2017-02-06 18:31:25 +01:00
}
2017-01-26 04:26:40 +01:00
}
2017-02-17 23:51:35 +01:00
2018-01-05 11:22:51 +01:00
void ChannelView : : enterEvent ( QEvent * )
{
}
void ChannelView : : leaveEvent ( QEvent * )
{
2018-11-03 22:02:52 +01:00
this - > unpause ( PauseReason : : Mouse ) ;
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2018-01-05 11:22:51 +01:00
}
2017-09-16 00:05:06 +02:00
void ChannelView : : mouseMoveEvent ( QMouseEvent * event )
2017-02-17 23:51:35 +01:00
{
2018-11-03 21:26:57 +01:00
/// Pause on hover
2019-09-16 11:36:19 +02:00
if ( float pauseTime = getSettings ( ) - > pauseOnHoverDuration ;
pauseTime > 0.001f )
2018-10-21 13:43:02 +02:00
{
2019-09-16 11:36:19 +02:00
this - > pause ( PauseReason : : Mouse , uint ( pauseTime * 1000.f ) ) ;
}
else if ( pauseTime < - 0.5f )
{
this - > pause ( PauseReason : : Mouse ) ;
2018-01-05 11:22:51 +01:00
}
2019-10-07 22:42:34 +02:00
auto tooltipWidget = TooltipWidget : : instance ( ) ;
2018-06-28 19:38:57 +02:00
std : : shared_ptr < MessageLayout > layout ;
2017-02-17 23:51:35 +01:00
QPoint relativePos ;
2017-08-18 15:12:07 +02:00
int messageIndex ;
2017-02-17 23:51:35 +01:00
2017-12-19 00:09:38 +01:00
// no message under cursor
2018-10-21 13:43:02 +02:00
if ( ! tryGetMessageAt ( event - > pos ( ) , layout , relativePos , messageIndex ) )
{
2017-12-19 00:09:38 +01:00
this - > setCursor ( Qt : : ArrowCursor ) ;
2017-12-23 22:17:38 +01:00
tooltipWidget - > hide ( ) ;
2017-12-19 00:09:38 +01:00
return ;
}
2020-04-18 11:09:22 +02:00
if ( this - > isScrolling_ )
{
this - > currentMousePosition_ = event - > screenPos ( ) ;
}
2017-12-19 00:09:38 +01:00
// is selecting
2020-04-18 11:09:22 +02:00
if ( this - > isLeftMouseDown_ )
2018-10-21 13:43:02 +02:00
{
2018-11-03 21:26:57 +01:00
// this->pause(PauseReason::Selecting, 300);
2018-01-11 20:16:25 +01:00
int index = layout - > getSelectionIndex ( relativePos ) ;
2017-08-18 15:12:07 +02:00
2018-08-06 21:17:03 +02:00
this - > setSelection ( this - > selection_ . start ,
SelectionItem ( messageIndex , index ) ) ;
2017-08-18 15:12:07 +02:00
2018-01-05 11:22:51 +01:00
this - > queueUpdate ( ) ;
}
// message under cursor is collapsed
2018-10-21 13:43:02 +02:00
if ( layout - > flags . has ( MessageLayoutFlag : : Collapsed ) )
{
2018-01-05 11:22:51 +01:00
this - > setCursor ( Qt : : PointingHandCursor ) ;
tooltipWidget - > hide ( ) ;
return ;
2017-08-18 15:12:07 +02:00
}
2017-04-24 23:00:26 +02:00
2017-12-19 00:09:38 +01:00
// check if word underneath cursor
2018-08-06 21:17:03 +02:00
const MessageLayoutElement * hoverLayoutElement =
layout - > getElementAt ( relativePos ) ;
2018-01-11 20:16:25 +01:00
2018-10-21 13:43:02 +02:00
if ( hoverLayoutElement = = nullptr )
{
2017-12-19 00:09:38 +01:00
this - > setCursor ( Qt : : ArrowCursor ) ;
2017-12-23 22:17:38 +01:00
tooltipWidget - > hide ( ) ;
2017-02-17 23:51:35 +01:00
return ;
}
2018-10-06 13:43:21 +02:00
2018-10-21 13:43:02 +02:00
if ( this - > isDoubleClick_ )
{
2018-10-06 13:43:21 +02:00
int wordStart ;
int wordEnd ;
this - > getWordBounds ( layout . get ( ) , hoverLayoutElement , relativePos ,
wordStart , wordEnd ) ;
SelectionItem newStart ( messageIndex , wordStart ) ;
SelectionItem newEnd ( messageIndex , wordEnd ) ;
// Selection changed in same message
2018-11-14 17:26:08 +01:00
if ( messageIndex = = this - > doubleClickSelection_ . origMessageIndex )
2018-10-21 13:43:02 +02:00
{
2018-10-06 13:43:21 +02:00
// Selecting to the left
if ( wordStart < this - > selection_ . start . charIndex & &
2018-11-14 17:26:08 +01:00
! this - > doubleClickSelection_ . selectingRight )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft = true ;
2018-10-06 14:56:15 +02:00
// Ensure that the original word stays selected(Edge case)
2018-11-14 17:26:08 +01:00
if ( wordStart > this - > doubleClickSelection_ . originalEnd )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > setSelection (
this - > doubleClickSelection_ . origStartItem , newEnd ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-10-06 14:38:17 +02:00
this - > setSelection ( newStart , this - > selection_ . end ) ;
}
2018-10-06 13:43:21 +02:00
// Selecting to the right
2018-10-21 13:43:02 +02:00
}
else if ( wordEnd > this - > selection_ . end . charIndex & &
2018-11-14 17:26:08 +01:00
! this - > doubleClickSelection_ . selectingLeft )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingRight = true ;
2018-10-06 14:56:15 +02:00
// Ensure that the original word stays selected(Edge case)
2018-11-14 17:26:08 +01:00
if ( wordEnd < this - > doubleClickSelection_ . originalStart )
2018-10-21 13:43:02 +02:00
{
2018-10-06 14:38:17 +02:00
this - > setSelection ( newStart ,
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . origEndItem ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-10-06 14:38:17 +02:00
this - > setSelection ( this - > selection_ . start , newEnd ) ;
}
2018-10-06 13:43:21 +02:00
}
// Swapping from selecting left to selecting right
if ( wordStart > this - > selection_ . start . charIndex & &
2018-11-14 17:26:08 +01:00
! this - > doubleClickSelection_ . selectingRight )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
if ( wordStart > this - > doubleClickSelection_ . originalEnd )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft = false ;
this - > doubleClickSelection_ . selectingRight = true ;
this - > setSelection (
this - > doubleClickSelection_ . origStartItem , newEnd ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-10-06 13:43:21 +02:00
this - > setSelection ( newStart , this - > selection_ . end ) ;
}
// Swapping from selecting right to selecting left
2018-10-21 13:43:02 +02:00
}
else if ( wordEnd < this - > selection_ . end . charIndex & &
2018-11-14 17:26:08 +01:00
! this - > doubleClickSelection_ . selectingLeft )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
if ( wordEnd < this - > doubleClickSelection_ . originalStart )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft = true ;
this - > doubleClickSelection_ . selectingRight = false ;
2018-10-06 13:43:21 +02:00
this - > setSelection ( newStart ,
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . origEndItem ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-10-06 13:43:21 +02:00
this - > setSelection ( this - > selection_ . start , newEnd ) ;
}
}
// Selection changed in a different message
2018-10-21 13:43:02 +02:00
}
else
{
2018-10-06 13:43:21 +02:00
// Message over the original
2018-10-21 13:43:02 +02:00
if ( messageIndex < this - > selection_ . start . messageIndex )
{
2018-10-06 13:43:21 +02:00
// Swapping from left to right selecting
2018-11-14 17:26:08 +01:00
if ( ! this - > doubleClickSelection_ . selectingLeft )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft = true ;
this - > doubleClickSelection_ . selectingRight = false ;
2018-10-06 13:43:21 +02:00
}
if ( wordStart < this - > selection_ . start . charIndex & &
2018-11-14 17:26:08 +01:00
! this - > doubleClickSelection_ . selectingRight )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft = true ;
2018-10-06 13:43:21 +02:00
}
2018-11-14 17:26:08 +01:00
this - > setSelection ( newStart ,
this - > doubleClickSelection_ . origEndItem ) ;
2018-10-06 13:43:21 +02:00
// Message under the original
2018-10-21 13:43:02 +02:00
}
else if ( messageIndex > this - > selection_ . end . messageIndex )
{
2018-10-06 13:43:21 +02:00
// Swapping from right to left selecting
2018-11-14 17:26:08 +01:00
if ( ! this - > doubleClickSelection_ . selectingRight )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft = false ;
this - > doubleClickSelection_ . selectingRight = true ;
2018-10-06 13:43:21 +02:00
}
if ( wordEnd > this - > selection_ . end . charIndex & &
2018-11-14 17:26:08 +01:00
! this - > doubleClickSelection_ . selectingLeft )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingRight = true ;
2018-10-06 13:43:21 +02:00
}
2018-11-14 17:26:08 +01:00
this - > setSelection ( this - > doubleClickSelection_ . origStartItem ,
newEnd ) ;
2018-10-06 13:43:21 +02:00
// Selection changed in non original message
2018-10-21 13:43:02 +02:00
}
else
{
2018-11-14 17:26:08 +01:00
if ( this - > doubleClickSelection_ . selectingLeft )
2018-10-21 13:43:02 +02:00
{
2018-10-06 13:43:21 +02:00
this - > setSelection ( newStart , this - > selection_ . end ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-10-06 13:43:21 +02:00
this - > setSelection ( this - > selection_ . start , newEnd ) ;
}
}
}
// Reset direction of selection
2018-11-14 17:26:08 +01:00
if ( wordStart = = this - > doubleClickSelection_ . originalStart & &
wordEnd = = this - > doubleClickSelection_ . originalEnd )
2018-10-21 13:43:02 +02:00
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft =
this - > doubleClickSelection_ . selectingRight = false ;
2018-10-06 13:43:21 +02:00
}
}
2020-11-01 14:33:01 +01:00
auto element = & hoverLayoutElement - > getCreator ( ) ;
2018-08-25 20:21:18 +02:00
bool isLinkValid = hoverLayoutElement - > getLink ( ) . isValid ( ) ;
2020-11-01 14:33:01 +01:00
auto emoteElement = dynamic_cast < const EmoteElement * > ( element ) ;
2017-12-19 03:36:05 +01:00
2020-11-01 14:33:01 +01:00
if ( element - > getTooltip ( ) . isEmpty ( ) | |
( isLinkValid & & emoteElement = = nullptr & &
! getSettings ( ) - > linkInfoTooltip ) )
2018-10-21 13:43:02 +02:00
{
2018-08-25 19:49:50 +02:00
tooltipWidget - > hide ( ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2019-10-07 22:42:34 +02:00
auto & tooltipPreviewImage = TooltipPreviewImage : : instance ( ) ;
2020-05-10 12:11:10 +02:00
tooltipPreviewImage . setImageScale ( 0 , 0 ) ;
2020-11-01 14:33:01 +01:00
auto badgeElement = dynamic_cast < const BadgeElement * > ( element ) ;
2019-06-11 22:54:20 +02:00
2019-08-21 01:52:01 +02:00
if ( ( badgeElement | | emoteElement ) & &
getSettings ( ) - > emotesTooltipPreview . getValue ( ) )
2019-06-11 22:54:20 +02:00
{
if ( event - > modifiers ( ) = = Qt : : ShiftModifier | |
getSettings ( ) - > emotesTooltipPreview . getValue ( ) = = 1 )
{
2019-08-21 01:52:01 +02:00
if ( emoteElement )
{
tooltipPreviewImage . setImage (
emoteElement - > getEmote ( ) - > images . getImage ( 3.0 ) ) ;
}
else if ( badgeElement )
{
tooltipPreviewImage . setImage (
badgeElement - > getEmote ( ) - > images . getImage ( 3.0 ) ) ;
}
2019-06-11 22:54:20 +02:00
}
else
{
tooltipPreviewImage . setImage ( nullptr ) ;
}
}
else
{
2020-11-01 14:33:01 +01:00
if ( element - > getTooltip ( ) = = " No link info loaded " )
{
std : : weak_ptr < MessageLayout > weakLayout = layout ;
LinkResolver : : getLinkInfo (
element - > getLink ( ) . value , nullptr ,
[ weakLayout , element ] ( QString tooltipText ,
Link originalLink ,
ImagePtr thumbnail ) {
auto shared = weakLayout . lock ( ) ;
if ( ! shared )
return ;
element - > setTooltip ( tooltipText ) ;
element - > setThumbnail ( thumbnail ) ;
} ) ;
}
2020-05-10 12:11:10 +02:00
auto thumbnailSize = getSettings ( ) - > thumbnailSize ;
2020-10-11 13:52:14 +02:00
if ( ! thumbnailSize )
2020-05-10 12:11:10 +02:00
{
tooltipPreviewImage . setImage ( nullptr ) ;
}
else
{
2020-12-05 15:03:13 +01:00
const auto shouldHideThumbnail =
2020-10-11 13:52:14 +02:00
isInStreamerMode ( ) & &
getSettings ( ) - > streamerModeHideLinkThumbnails & &
2020-12-05 15:03:13 +01:00
element - > getThumbnail ( ) ! = nullptr & &
! element - > getThumbnail ( ) - > url ( ) . string . isEmpty ( ) ;
2020-10-11 13:52:14 +02:00
auto thumb =
2020-12-05 15:03:13 +01:00
shouldHideThumbnail
? Image : : fromPixmap ( getResources ( ) . streamerMode )
: element - > getThumbnail ( ) ;
2020-10-11 13:52:14 +02:00
tooltipPreviewImage . setImage ( std : : move ( thumb ) ) ;
2020-05-10 12:11:10 +02:00
if ( element - > getThumbnailType ( ) = =
MessageElement : : ThumbnailType : : Link_Thumbnail )
{
tooltipPreviewImage . setImageScale ( thumbnailSize ,
thumbnailSize ) ;
}
}
2019-05-17 15:26:07 +02:00
}
2018-01-22 15:06:36 +01:00
tooltipWidget - > moveTo ( this , event - > globalPos ( ) ) ;
2018-08-25 20:21:18 +02:00
tooltipWidget - > setWordWrap ( isLinkValid ) ;
2020-11-01 14:33:01 +01:00
tooltipWidget - > setText ( element - > getTooltip ( ) ) ;
2019-06-04 20:22:21 +02:00
tooltipWidget - > adjustSize ( ) ;
2021-01-23 16:49:02 +01:00
tooltipWidget - > setWindowFlag ( Qt : : WindowStaysOnTopHint , true ) ;
2018-01-17 03:26:32 +01:00
tooltipWidget - > show ( ) ;
2018-05-24 10:03:07 +02:00
tooltipWidget - > raise ( ) ;
2018-01-17 03:26:32 +01:00
}
2017-12-19 03:36:05 +01:00
2017-12-19 00:09:38 +01:00
// check if word has a link
2018-10-21 13:43:02 +02:00
if ( isLinkValid )
{
2017-12-19 00:09:38 +01:00
this - > setCursor ( Qt : : PointingHandCursor ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2017-12-19 00:09:38 +01:00
this - > setCursor ( Qt : : ArrowCursor ) ;
2017-02-17 23:51:35 +01:00
}
}
2017-09-16 00:05:06 +02:00
void ChannelView : : mousePressEvent ( QMouseEvent * event )
2017-04-12 17:46:44 +02:00
{
2018-06-04 21:44:03 +02:00
this - > mouseDown . invoke ( event ) ;
2018-06-28 19:38:57 +02:00
std : : shared_ptr < MessageLayout > layout ;
2017-08-18 15:12:07 +02:00
QPoint relativePos ;
int messageIndex ;
2018-10-21 13:43:02 +02:00
if ( ! tryGetMessageAt ( event - > pos ( ) , layout , relativePos , messageIndex ) )
{
2017-08-18 15:12:07 +02:00
setCursor ( Qt : : ArrowCursor ) ;
2017-12-22 15:13:42 +01:00
auto messagesSnapshot = this - > getMessagesSnapshot ( ) ;
2018-11-03 21:26:57 +01:00
if ( messagesSnapshot . size ( ) = = 0 )
2018-10-21 13:43:02 +02:00
{
2017-11-04 13:17:35 +01:00
return ;
}
// Start selection at the last message at its last index
2018-10-21 13:43:02 +02:00
if ( event - > button ( ) = = Qt : : LeftButton )
{
2018-11-03 21:26:57 +01:00
auto lastMessageIndex = messagesSnapshot . size ( ) - 1 ;
2018-06-04 14:39:26 +02:00
auto lastMessage = messagesSnapshot [ lastMessageIndex ] ;
auto lastCharacterIndex = lastMessage - > getLastCharacterIndex ( ) ;
2017-11-04 13:17:35 +01:00
2018-06-04 14:39:26 +02:00
SelectionItem selectionItem ( lastMessageIndex , lastCharacterIndex ) ;
this - > setSelection ( selectionItem , selectionItem ) ;
}
2018-06-05 15:01:40 +02:00
return ;
2017-08-18 15:12:07 +02:00
}
2017-12-19 00:09:38 +01:00
// check if message is collapsed
2018-10-21 13:43:02 +02:00
switch ( event - > button ( ) )
{
2019-09-26 00:51:05 +02:00
case Qt : : LeftButton : {
2020-04-18 11:09:22 +02:00
if ( this - > isScrolling_ )
this - > disableScrolling ( ) ;
this - > lastLeftPressPosition_ = event - > screenPos ( ) ;
this - > isLeftMouseDown_ = true ;
2018-06-05 14:24:01 +02:00
2018-10-21 13:43:02 +02:00
if ( layout - > flags . has ( MessageLayoutFlag : : Collapsed ) )
2018-06-05 14:24:01 +02:00
return ;
2018-10-21 13:43:02 +02:00
if ( getSettings ( ) - > linksDoubleClickOnly . getValue ( ) )
{
2018-11-03 21:26:57 +01:00
this - > pause ( PauseReason : : DoubleClick , 200 ) ;
2018-06-04 14:39:26 +02:00
}
2018-01-17 01:19:42 +01:00
2018-06-04 14:39:26 +02:00
int index = layout - > getSelectionIndex ( relativePos ) ;
auto selectionItem = SelectionItem ( messageIndex , index ) ;
this - > setSelection ( selectionItem , selectionItem ) ;
2018-10-21 13:43:02 +02:00
}
break ;
2018-05-17 12:16:13 +02:00
2019-09-26 00:51:05 +02:00
case Qt : : RightButton : {
2020-04-18 11:09:22 +02:00
if ( this - > isScrolling_ )
this - > disableScrolling ( ) ;
2018-06-13 13:27:10 +02:00
this - > lastRightPressPosition_ = event - > screenPos ( ) ;
this - > isRightMouseDown_ = true ;
2018-10-21 13:43:02 +02:00
}
break ;
2018-05-17 12:16:13 +02:00
2020-04-18 11:09:22 +02:00
case Qt : : MiddleButton : {
2020-05-02 13:19:58 +02:00
const MessageLayoutElement * hoverLayoutElement =
layout - > getElementAt ( relativePos ) ;
if ( hoverLayoutElement ! = nullptr & &
hoverLayoutElement - > getLink ( ) . isUrl ( ) & &
this - > isScrolling_ = = false )
{
break ;
}
2020-04-18 11:09:22 +02:00
else
2020-05-02 13:19:58 +02:00
{
if ( this - > isScrolling_ )
this - > disableScrolling ( ) ;
2020-05-10 12:45:19 +02:00
else if ( hoverLayoutElement ! = nullptr & &
hoverLayoutElement - > getFlags ( ) . has (
MessageElementFlag : : Username ) )
break ;
2021-07-04 12:18:49 +02:00
else if ( this - > scrollBar_ - > isVisible ( ) )
2020-05-02 13:19:58 +02:00
this - > enableScrolling ( event - > screenPos ( ) ) ;
}
2020-04-18 11:09:22 +02:00
}
break ;
2018-06-04 14:39:26 +02:00
default : ;
2018-01-05 11:22:51 +01:00
}
2018-06-04 14:39:26 +02:00
this - > update ( ) ;
}
2017-04-12 17:46:44 +02:00
2018-06-04 14:39:26 +02:00
void ChannelView : : mouseReleaseEvent ( QMouseEvent * event )
{
2020-05-02 13:19:58 +02:00
// find message
this - > queueLayout ( ) ;
std : : shared_ptr < MessageLayout > layout ;
QPoint relativePos ;
int messageIndex ;
2020-05-10 12:45:19 +02:00
bool foundElement =
2020-05-02 13:19:58 +02:00
tryGetMessageAt ( event - > pos ( ) , layout , relativePos , messageIndex ) ;
2018-06-04 14:39:26 +02:00
// check if mouse was pressed
2018-10-21 13:43:02 +02:00
if ( event - > button ( ) = = Qt : : LeftButton )
{
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . selectingLeft =
this - > doubleClickSelection_ . selectingRight = false ;
2018-10-21 13:43:02 +02:00
if ( this - > isDoubleClick_ )
{
2018-10-06 16:19:58 +02:00
this - > isDoubleClick_ = false ;
2018-10-06 14:56:15 +02:00
// Was actually not a wanted triple-click
if ( fabsf ( distanceBetweenPoints ( this - > lastDClickPosition_ ,
2018-10-21 13:43:02 +02:00
event - > screenPos ( ) ) ) > 10.f )
{
2018-10-06 17:15:38 +02:00
this - > clickTimer_ - > stop ( ) ;
return ;
}
2018-10-21 13:43:02 +02:00
}
2020-04-18 11:09:22 +02:00
else if ( this - > isLeftMouseDown_ )
2018-10-21 13:43:02 +02:00
{
2020-04-18 11:09:22 +02:00
this - > isLeftMouseDown_ = false ;
2018-10-06 17:15:38 +02:00
2020-04-18 11:09:22 +02:00
if ( fabsf ( distanceBetweenPoints ( this - > lastLeftPressPosition_ ,
2018-10-21 13:43:02 +02:00
event - > screenPos ( ) ) ) > 15.f )
{
2018-10-06 14:56:15 +02:00
return ;
}
2018-10-21 13:43:02 +02:00
}
else
{
2018-06-04 14:39:26 +02:00
return ;
}
2018-10-21 13:43:02 +02:00
}
else if ( event - > button ( ) = = Qt : : RightButton )
{
if ( this - > isRightMouseDown_ )
{
2018-06-13 13:27:10 +02:00
this - > isRightMouseDown_ = false ;
2017-04-12 17:46:44 +02:00
2018-08-06 21:17:03 +02:00
if ( fabsf ( distanceBetweenPoints ( this - > lastRightPressPosition_ ,
2018-10-21 13:43:02 +02:00
event - > screenPos ( ) ) ) > 15.f )
{
2018-06-04 14:39:26 +02:00
return ;
}
2018-10-21 13:43:02 +02:00
}
else
{
2018-06-04 14:39:26 +02:00
return ;
}
2018-10-21 13:43:02 +02:00
}
2020-04-18 11:09:22 +02:00
else if ( event - > button ( ) = = Qt : : MiddleButton )
{
2021-07-04 12:18:49 +02:00
if ( this - > isScrolling_ & & this - > scrollBar_ - > isVisible ( ) )
2020-05-02 13:19:58 +02:00
{
if ( event - > screenPos ( ) = = this - > lastMiddlePressPosition_ )
this - > enableScrolling ( event - > screenPos ( ) ) ;
else
this - > disableScrolling ( ) ;
return ;
}
2020-05-10 12:45:19 +02:00
else if ( foundElement )
2020-05-02 13:19:58 +02:00
{
const MessageLayoutElement * hoverLayoutElement =
layout - > getElementAt ( relativePos ) ;
2020-05-10 12:45:19 +02:00
if ( hoverLayoutElement = = nullptr )
{
return ;
}
else if ( hoverLayoutElement - > getFlags ( ) . has (
MessageElementFlag : : Username ) )
{
openTwitchUsercard ( this - > channel_ - > getName ( ) ,
hoverLayoutElement - > getLink ( ) . value ) ;
return ;
}
else if ( hoverLayoutElement - > getLink ( ) . isUrl ( ) = = false )
2020-05-02 13:19:58 +02:00
{
return ;
}
}
2020-04-18 11:09:22 +02:00
}
2018-10-21 13:43:02 +02:00
else
{
2018-06-04 14:39:26 +02:00
// not left or right button
2017-04-12 17:46:44 +02:00
return ;
}
2018-06-04 14:39:26 +02:00
// no message found
2020-05-10 12:45:19 +02:00
if ( ! foundElement )
2018-10-21 13:43:02 +02:00
{
2017-04-12 17:46:44 +02:00
// No message at clicked position
return ;
}
2017-12-19 00:09:38 +01:00
// message under cursor is collapsed
2018-10-21 13:43:02 +02:00
if ( layout - > flags . has ( MessageLayoutFlag : : Collapsed ) )
{
2018-08-07 07:55:31 +02:00
layout - > flags . set ( MessageLayoutFlag : : Expanded ) ;
layout - > flags . set ( MessageLayoutFlag : : RequiresLayout ) ;
2018-04-18 09:12:29 +02:00
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2017-12-19 00:09:38 +01:00
return ;
}
2018-08-06 21:17:03 +02:00
const MessageLayoutElement * hoverLayoutElement =
layout - > getElementAt ( relativePos ) ;
2018-10-02 14:13:49 +02:00
// Triple-clicking a message selects the whole message
2018-10-21 13:43:02 +02:00
if ( this - > clickTimer_ - > isActive ( ) & & this - > selecting_ )
{
2018-10-06 17:15:38 +02:00
if ( fabsf ( distanceBetweenPoints ( this - > lastDClickPosition_ ,
2018-10-21 13:43:02 +02:00
event - > screenPos ( ) ) ) < 10.f )
{
2018-10-06 17:15:38 +02:00
this - > selectWholeMessage ( layout . get ( ) , messageIndex ) ;
}
2018-10-02 13:41:34 +02:00
}
2018-10-21 13:43:02 +02:00
if ( hoverLayoutElement = = nullptr )
{
2017-04-24 23:00:26 +02:00
return ;
}
2018-06-04 14:39:26 +02:00
// handle the click
2020-07-05 13:52:24 +02:00
this - > handleMouseClick ( event , hoverLayoutElement , layout ) ;
2020-09-26 00:58:29 +02:00
this - > update ( ) ;
2018-06-04 14:39:26 +02:00
}
2018-05-16 03:55:56 +02:00
2018-08-06 21:17:03 +02:00
void ChannelView : : handleMouseClick ( QMouseEvent * event ,
const MessageLayoutElement * hoveredElement ,
2020-07-05 13:52:24 +02:00
MessageLayoutPtr layout )
2018-06-04 14:39:26 +02:00
{
2018-10-21 13:43:02 +02:00
switch ( event - > button ( ) )
{
2019-09-26 00:51:05 +02:00
case Qt : : LeftButton : {
2018-10-21 13:43:02 +02:00
if ( this - > selecting_ )
{
2018-06-13 13:27:10 +02:00
// this->pausedBySelection = false;
this - > selecting_ = false ;
// this->pauseTimeout.stop();
// this->pausedTemporarily = false;
2018-05-16 03:55:56 +02:00
2018-11-03 21:26:57 +01:00
this - > queueLayout ( ) ;
2018-05-16 03:55:56 +02:00
}
2018-06-04 14:39:26 +02:00
auto & link = hoveredElement - > getLink ( ) ;
2018-10-21 13:43:02 +02:00
if ( ! getSettings ( ) - > linksDoubleClickOnly )
{
2020-07-05 13:52:24 +02:00
this - > handleLinkClick ( event , link , layout . get ( ) ) ;
2018-08-25 10:08:10 +02:00
}
2018-06-04 14:39:26 +02:00
2018-08-25 10:08:10 +02:00
// Invoke to signal from EmotePopup.
2018-10-21 13:43:02 +02:00
if ( link . type = = Link : : InsertText )
{
2018-06-04 14:39:26 +02:00
this - > linkClicked . invoke ( link ) ;
2018-05-16 03:55:56 +02:00
}
2018-10-21 13:43:02 +02:00
}
break ;
2019-09-26 00:51:05 +02:00
case Qt : : RightButton : {
2021-07-24 12:01:50 +02:00
auto split = dynamic_cast < Split * > ( this - > parentWidget ( ) ) ;
2018-08-26 10:42:00 +02:00
auto insertText = [ = ] ( QString text ) {
2021-07-24 12:01:50 +02:00
if ( split )
2018-10-21 13:43:02 +02:00
{
2018-08-26 10:42:00 +02:00
split - > insertTextToInput ( text ) ;
}
} ;
2018-07-10 18:27:42 +02:00
auto & link = hoveredElement - > getLink ( ) ;
2018-10-21 13:43:02 +02:00
if ( link . type = = Link : : UserInfo )
{
2019-10-03 15:30:51 +02:00
const bool commaMention = getSettings ( ) - > mentionUsersWithComma ;
2021-07-24 12:01:50 +02:00
const bool isFirstWord =
split & & split - > getInput ( ) . isEditFirstWord ( ) ;
auto userMention =
formatUserMention ( link . value , isFirstWord , commaMention ) ;
insertText ( " @ " + userMention + " " ) ;
2018-10-21 13:43:02 +02:00
}
else if ( link . type = = Link : : UserWhisper )
{
2018-08-26 10:42:00 +02:00
insertText ( " /w " + link . value + " " ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-07-10 18:27:42 +02:00
this - > addContextMenuItems ( hoveredElement , layout ) ;
}
2018-10-21 13:43:02 +02:00
}
break ;
2020-05-02 13:19:58 +02:00
case Qt : : MiddleButton : {
auto & link = hoveredElement - > getLink ( ) ;
if ( ! getSettings ( ) - > linksDoubleClickOnly )
{
2020-07-05 13:52:24 +02:00
this - > handleLinkClick ( event , link , layout . get ( ) ) ;
2020-05-02 13:19:58 +02:00
}
}
break ;
2018-06-04 14:39:26 +02:00
default : ;
}
}
2018-05-16 03:55:56 +02:00
2018-08-06 21:17:03 +02:00
void ChannelView : : addContextMenuItems (
2020-07-05 13:52:24 +02:00
const MessageLayoutElement * hoveredElement , MessageLayoutPtr layout )
2018-06-04 14:39:26 +02:00
{
const auto & creator = hoveredElement - > getCreator ( ) ;
auto creatorFlags = creator . getFlags ( ) ;
2018-05-16 03:55:56 +02:00
2020-07-18 17:51:31 +02:00
static QMenu * previousMenu = nullptr ;
if ( previousMenu ! = nullptr )
{
previousMenu - > deleteLater ( ) ;
previousMenu = nullptr ;
}
2020-07-05 13:52:24 +02:00
auto menu = new QMenu ;
2020-07-18 17:51:31 +02:00
previousMenu = menu ;
2018-05-23 21:16:34 +02:00
2021-04-25 15:16:32 +02:00
if ( creatorFlags . hasAny ( { MessageElementFlag : : Badges } ) )
{
auto badgeElement = dynamic_cast < const BadgeElement * > ( & creator ) ;
addEmoteContextMenuItems ( * badgeElement - > getEmote ( ) , creatorFlags ,
* menu ) ;
}
2018-06-04 14:39:26 +02:00
// Emote actions
2018-10-21 13:43:02 +02:00
if ( creatorFlags . hasAny (
{ MessageElementFlag : : EmoteImages , MessageElementFlag : : EmojiImage } ) )
{
2018-08-02 14:23:27 +02:00
const auto emoteElement = dynamic_cast < const EmoteElement * > ( & creator ) ;
2018-08-06 21:17:03 +02:00
if ( emoteElement )
addEmoteContextMenuItems ( * emoteElement - > getEmote ( ) , creatorFlags ,
* menu ) ;
2018-06-04 14:39:26 +02:00
}
// add seperator
2018-10-21 13:43:02 +02:00
if ( ! menu - > actions ( ) . empty ( ) )
{
2018-06-04 14:39:26 +02:00
menu - > addSeparator ( ) ;
}
// Link copy
2018-10-21 13:43:02 +02:00
if ( hoveredElement - > getLink ( ) . type = = Link : : Url )
{
2018-06-04 14:39:26 +02:00
QString url = hoveredElement - > getLink ( ) . value ;
2018-05-23 21:16:34 +02:00
2018-10-16 16:34:09 +02:00
// open link
2020-11-08 12:02:19 +01:00
menu - > addAction ( " Open link " , [ url ] {
QDesktopServices : : openUrl ( QUrl ( url ) ) ;
} ) ;
2018-10-16 16:34:09 +02:00
// open link default
2018-10-21 13:43:02 +02:00
if ( supportsIncognitoLinks ( ) )
{
2020-11-08 12:02:19 +01:00
menu - > addAction ( " Open link incognito " , [ url ] {
openLinkIncognito ( url ) ;
} ) ;
2018-10-16 16:07:59 +02:00
}
2020-11-08 12:02:19 +01:00
menu - > addAction ( " Copy link " , [ url ] {
crossPlatformCopy ( url ) ;
} ) ;
2018-06-04 14:39:26 +02:00
menu - > addSeparator ( ) ;
}
// Copy actions
2018-10-21 13:43:02 +02:00
if ( ! this - > selection_ . isEmpty ( ) )
{
2020-11-08 12:02:19 +01:00
menu - > addAction ( " Copy selection " , [ this ] {
crossPlatformCopy ( this - > getSelectedText ( ) ) ;
} ) ;
2018-06-04 14:39:26 +02:00
}
2018-05-23 20:34:37 +02:00
2018-06-05 14:24:54 +02:00
menu - > addAction ( " Copy message " , [ layout ] {
QString copyString ;
2018-08-16 00:16:33 +02:00
layout - > addSelectionText ( copyString , 0 , INT_MAX ,
CopyMode : : OnlyTextAndEmotes ) ;
2018-06-05 14:24:54 +02:00
2020-01-24 21:36:51 +01:00
crossPlatformCopy ( copyString ) ;
2018-06-05 14:24:54 +02:00
} ) ;
2018-08-16 00:16:33 +02:00
menu - > addAction ( " Copy full message " , [ layout ] {
QString copyString ;
layout - > addSelectionText ( copyString ) ;
2018-05-23 20:34:37 +02:00
2020-01-24 21:36:51 +01:00
crossPlatformCopy ( copyString ) ;
2018-08-16 00:16:33 +02:00
} ) ;
2018-05-16 03:55:56 +02:00
2021-10-17 15:06:58 +02:00
// If is a link to a Twitch user/stream
2018-10-21 13:43:02 +02:00
if ( hoveredElement - > getLink ( ) . type = = Link : : Url )
{
2018-09-21 20:35:14 +02:00
static QRegularExpression twitchChannelRegex (
2021-11-06 14:43:03 +01:00
R " (^(?:https?: \ / \ /)?(?:www \ .|go \ .)?twitch \ .tv \ /(?:popout \ /)?(?<username>[a-z0-9_]{3,})) " ,
2018-09-21 20:35:14 +02:00
QRegularExpression : : CaseInsensitiveOption ) ;
2018-09-21 22:46:00 +02:00
static QSet < QString > ignoredUsernames {
2018-10-16 16:07:59 +02:00
" videos " , " settings " , " directory " , " jobs " , " friends " ,
2021-10-31 12:16:07 +01:00
" inventory " , " payments " , " subscriptions " , " messages " , " drops " ,
2018-09-21 22:46:00 +02:00
} ;
2018-09-21 20:35:14 +02:00
2018-09-21 22:46:00 +02:00
auto twitchMatch =
twitchChannelRegex . match ( hoveredElement - > getLink ( ) . value ) ;
2018-09-21 20:35:14 +02:00
auto twitchUsername = twitchMatch . captured ( " username " ) ;
if ( ! twitchUsername . isEmpty ( ) & &
2018-10-21 13:43:02 +02:00
! ignoredUsernames . contains ( twitchUsername ) )
{
2018-09-21 20:35:14 +02:00
menu - > addSeparator ( ) ;
2018-09-21 22:46:00 +02:00
menu - > addAction ( " Open in new split " , [ twitchUsername , this ] {
2021-07-11 13:54:19 +02:00
this - > openChannelIn . invoke ( twitchUsername ,
FromTwitchLinkOpenChannelIn : : Split ) ;
} ) ;
menu - > addAction ( " Open in new tab " , [ twitchUsername , this ] {
this - > openChannelIn . invoke ( twitchUsername ,
FromTwitchLinkOpenChannelIn : : Tab ) ;
} ) ;
menu - > addSeparator ( ) ;
menu - > addAction ( " Open player in browser " , [ twitchUsername , this ] {
this - > openChannelIn . invoke (
twitchUsername , FromTwitchLinkOpenChannelIn : : BrowserPlayer ) ;
} ) ;
menu - > addAction ( " Open in streamlink " , [ twitchUsername , this ] {
this - > openChannelIn . invoke (
twitchUsername , FromTwitchLinkOpenChannelIn : : Streamlink ) ;
2018-09-21 20:35:14 +02:00
} ) ;
}
}
2018-07-13 11:56:46 +02:00
menu - > popup ( QCursor : : pos ( ) ) ;
2018-06-09 18:59:08 +02:00
menu - > raise ( ) ;
2018-01-24 21:44:31 +01:00
2018-06-04 14:39:26 +02:00
return ;
2018-01-24 21:44:31 +01:00
}
void ChannelView : : mouseDoubleClickEvent ( QMouseEvent * event )
{
2018-09-30 18:18:30 +02:00
std : : shared_ptr < MessageLayout > layout ;
QPoint relativePos ;
int messageIndex ;
2018-01-24 21:44:31 +01:00
2018-10-21 13:43:02 +02:00
if ( ! tryGetMessageAt ( event - > pos ( ) , layout , relativePos , messageIndex ) )
{
2018-09-30 18:18:30 +02:00
return ;
}
2018-01-24 21:44:31 +01:00
2018-09-30 18:18:30 +02:00
// message under cursor is collapsed
2018-10-21 13:43:02 +02:00
if ( layout - > flags . has ( MessageLayoutFlag : : Collapsed ) )
{
2018-09-30 18:18:30 +02:00
return ;
}
2017-04-12 17:46:44 +02:00
2018-09-30 18:18:30 +02:00
const MessageLayoutElement * hoverLayoutElement =
layout - > getElementAt ( relativePos ) ;
2018-10-06 14:56:15 +02:00
this - > lastDClickPosition_ = event - > screenPos ( ) ;
2018-01-24 21:44:31 +01:00
2018-10-21 13:43:02 +02:00
if ( hoverLayoutElement = = nullptr )
{
2018-10-02 13:41:34 +02:00
// Possibility for triple click which doesn't have to be over an
// existing layout element
this - > clickTimer_ - > start ( ) ;
2018-09-30 18:18:30 +02:00
return ;
}
2018-11-14 17:26:08 +01:00
2020-04-18 11:09:22 +02:00
if ( ! this - > isLeftMouseDown_ )
2018-10-21 13:43:02 +02:00
{
2018-10-06 13:43:21 +02:00
this - > isDoubleClick_ = true ;
int wordStart ;
int wordEnd ;
this - > getWordBounds ( layout . get ( ) , hoverLayoutElement , relativePos ,
wordStart , wordEnd ) ;
2018-09-30 18:18:30 +02:00
2018-10-02 12:56:10 +02:00
this - > clickTimer_ - > start ( ) ;
2018-09-30 18:18:30 +02:00
SelectionItem wordMin ( messageIndex , wordStart ) ;
SelectionItem wordMax ( messageIndex , wordEnd ) ;
2018-10-06 13:43:21 +02:00
2018-11-14 17:26:08 +01:00
this - > doubleClickSelection_ . originalStart = wordStart ;
this - > doubleClickSelection_ . originalEnd = wordEnd ;
this - > doubleClickSelection_ . origMessageIndex = messageIndex ;
this - > doubleClickSelection_ . origStartItem = wordMin ;
this - > doubleClickSelection_ . origEndItem = wordMax ;
2018-10-06 13:43:21 +02:00
2018-09-30 18:18:30 +02:00
this - > setSelection ( wordMin , wordMax ) ;
}
2017-04-12 17:46:44 +02:00
2018-10-21 13:43:02 +02:00
if ( getSettings ( ) - > linksDoubleClickOnly )
{
2018-01-24 21:44:31 +01:00
auto & link = hoverLayoutElement - > getLink ( ) ;
this - > handleLinkClick ( event , link , layout . get ( ) ) ;
}
}
2018-04-18 09:12:29 +02:00
void ChannelView : : hideEvent ( QHideEvent * )
{
2018-10-21 13:43:02 +02:00
for ( auto & layout : this - > messagesOnScreen_ )
{
2018-04-18 09:12:29 +02:00
layout - > deleteBuffer ( ) ;
}
2018-06-13 13:27:10 +02:00
this - > messagesOnScreen_ . clear ( ) ;
2018-04-18 09:12:29 +02:00
}
2018-10-06 12:13:14 +02:00
void ChannelView : : showUserInfoPopup ( const QString & userName )
{
2020-11-15 14:40:34 +01:00
QWidget * userCardParent = this ;
# ifdef Q_OS_MACOS
// Order of closing/opening/killing widgets when the "Automatically close user info popups" setting is enabled is special on macOS, so user info popups should always use the main window as its parent
userCardParent =
static_cast < QWidget * > ( & ( getApp ( ) - > windows - > getMainWindow ( ) ) ) ;
# endif
2020-10-31 16:42:48 +01:00
auto * userPopup =
2020-11-15 14:40:34 +01:00
new UserInfoPopup ( getSettings ( ) - > autoCloseUserPopup , userCardParent ) ;
2020-10-18 15:16:56 +02:00
userPopup - > setData ( userName , this - > hasSourceChannel ( )
? this - > sourceChannel_
: this - > underlyingChannel_ ) ;
2018-11-21 21:37:41 +01:00
QPoint offset ( int ( 150 * this - > scale ( ) ) , int ( 70 * this - > scale ( ) ) ) ;
2018-10-06 12:13:14 +02:00
userPopup - > move ( QCursor : : pos ( ) - offset ) ;
userPopup - > show ( ) ;
}
2018-08-06 21:17:03 +02:00
void ChannelView : : handleLinkClick ( QMouseEvent * event , const Link & link ,
MessageLayout * layout )
2018-01-24 21:44:31 +01:00
{
2020-05-02 13:19:58 +02:00
if ( event - > button ( ) ! = Qt : : LeftButton & &
event - > button ( ) ! = Qt : : MiddleButton )
2018-10-21 13:43:02 +02:00
{
2018-05-23 21:16:34 +02:00
return ;
}
2018-10-21 13:43:02 +02:00
switch ( link . type )
{
2018-08-26 10:42:00 +02:00
case Link : : UserWhisper :
2019-09-26 00:51:05 +02:00
case Link : : UserInfo : {
2018-01-28 03:52:52 +01:00
auto user = link . value ;
2018-10-06 12:13:14 +02:00
this - > showUserInfoPopup ( user ) ;
2018-10-21 13:43:02 +02:00
}
break ;
2018-06-06 15:54:14 +02:00
2019-09-26 00:51:05 +02:00
case Link : : Url : {
2019-09-08 14:59:51 +02:00
if ( getSettings ( ) - > openLinksIncognito & & supportsIncognitoLinks ( ) )
openLinkIncognito ( link . value ) ;
else
QDesktopServices : : openUrl ( QUrl ( link . value ) ) ;
2018-10-21 13:43:02 +02:00
}
break ;
2018-06-06 15:54:14 +02:00
2019-09-26 00:51:05 +02:00
case Link : : UserAction : {
2018-01-28 03:52:52 +01:00
QString value = link . value ;
2019-05-01 16:08:45 +02:00
2021-05-23 15:51:53 +02:00
ChannelPtr channel = this - > underlyingChannel_ ;
SearchPopup * searchPopup =
dynamic_cast < SearchPopup * > ( this - > parentWidget ( ) ) ;
if ( searchPopup ! = nullptr )
{
Split * split =
dynamic_cast < Split * > ( searchPopup - > parentWidget ( ) ) ;
if ( split ! = nullptr )
{
channel = split - > getChannel ( ) ;
}
}
2021-09-11 14:35:26 +02:00
value = getApp ( ) - > commands - > execCustomCommand (
QStringList ( ) , Command { " (modaction) " , value } , true , channel ,
{
{ " user.name " , layout - > getMessage ( ) - > loginName } ,
{ " msg.id " , layout - > getMessage ( ) - > id } ,
{ " msg.text " , layout - > getMessage ( ) - > messageText } ,
// old placeholders
{ " user " , layout - > getMessage ( ) - > loginName } ,
{ " msg-id " , layout - > getMessage ( ) - > id } ,
{ " message " , layout - > getMessage ( ) - > messageText } ,
// new version of this is inside execCustomCommand
{ " channel " , this - > channel ( ) - > getName ( ) } ,
} ) ;
2019-05-01 16:08:45 +02:00
2021-05-23 15:51:53 +02:00
value = getApp ( ) - > commands - > execCommand ( value , channel , false ) ;
2021-09-11 14:35:26 +02:00
2021-05-23 15:51:53 +02:00
channel - > sendMessage ( value ) ;
2018-10-21 13:43:02 +02:00
}
break ;
2018-06-06 15:54:14 +02:00
2019-09-26 00:51:05 +02:00
case Link : : AutoModAllow : {
2021-05-14 13:14:43 +02:00
getApp ( ) - > accounts - > twitch . getCurrent ( ) - > autoModAllow (
link . value , this - > channel ( ) ) ;
2019-01-20 14:45:59 +01:00
}
2019-01-21 18:33:57 +01:00
break ;
2019-09-26 00:51:05 +02:00
case Link : : AutoModDeny : {
2021-05-14 13:14:43 +02:00
getApp ( ) - > accounts - > twitch . getCurrent ( ) - > autoModDeny (
link . value , this - > channel ( ) ) ;
2019-01-20 14:45:59 +01:00
}
2020-10-04 17:36:38 +02:00
break ;
case Link : : OpenAccountsPage : {
2020-10-31 16:42:48 +01:00
SettingsDialog : : showDialog ( this ,
SettingsDialogPreference : : Accounts ) ;
2020-10-04 17:36:38 +02:00
}
break ;
2020-11-28 17:45:20 +01:00
case Link : : JumpToChannel : {
// Get all currently open pages
QList < SplitContainer * > openPages ;
auto & nb = getApp ( ) - > windows - > getMainWindow ( ) . getNotebook ( ) ;
for ( int i = 0 ; i < nb . getPageCount ( ) ; + + i )
{
openPages . push_back (
static_cast < SplitContainer * > ( nb . getPageAt ( i ) ) ) ;
}
for ( auto * page : openPages )
{
auto splits = page - > getSplits ( ) ;
// Search for channel matching link in page/split container
// TODO(zneix): Consider opening a channel if it's closed (?)
auto it = std : : find_if (
splits . begin ( ) , splits . end ( ) , [ link ] ( Split * split ) {
return split - > getChannel ( ) - > getName ( ) = = link . value ;
} ) ;
if ( it ! = splits . end ( ) )
{
// Select SplitContainer and Split itself where mention message was sent
// TODO(zneix): Try exploring ways of scrolling to a certain message as well
nb . select ( page ) ;
Split * split = * it ;
page - > setSelected ( split ) ;
break ;
}
}
}
break ;
2021-01-17 14:47:34 +01:00
case Link : : CopyToClipboard : {
crossPlatformCopy ( link . value ) ;
}
break ;
2020-12-12 14:19:51 +01:00
case Link : : Reconnect : {
this - > underlyingChannel_ . get ( ) - > reconnect ( ) ;
}
break ;
2019-01-20 14:45:59 +01:00
2018-06-06 15:54:14 +02:00
default : ;
2017-04-24 23:00:26 +02:00
}
2017-04-12 17:46:44 +02:00
}
2018-08-06 21:17:03 +02:00
bool ChannelView : : tryGetMessageAt ( QPoint p ,
std : : shared_ptr < MessageLayout > & _message ,
2017-09-16 00:05:06 +02:00
QPoint & relativePos , int & index )
2017-02-17 23:51:35 +01:00
{
2017-12-22 15:13:42 +01:00
auto messagesSnapshot = this - > getMessagesSnapshot ( ) ;
2017-02-17 23:51:35 +01:00
2018-08-11 22:23:06 +02:00
size_t start = this - > scrollBar_ - > getCurrentValue ( ) ;
2017-02-17 23:51:35 +01:00
2018-11-03 21:26:57 +01:00
if ( start > = messagesSnapshot . size ( ) )
2018-10-21 13:43:02 +02:00
{
2017-02-17 23:51:35 +01:00
return false ;
}
2018-08-06 21:17:03 +02:00
int y = - ( messagesSnapshot [ start ] - > getHeight ( ) *
2018-08-11 22:23:06 +02:00
( fmod ( this - > scrollBar_ - > getCurrentValue ( ) , 1 ) ) ) ;
2017-02-17 23:51:35 +01:00
2018-11-03 21:26:57 +01:00
for ( size_t i = start ; i < messagesSnapshot . size ( ) ; + + i )
2018-10-21 13:43:02 +02:00
{
2017-12-22 15:13:42 +01:00
auto message = messagesSnapshot [ i ] ;
2017-02-17 23:51:35 +01:00
2018-10-21 13:43:02 +02:00
if ( p . y ( ) < y + message - > getHeight ( ) )
{
2017-04-24 23:00:26 +02:00
relativePos = QPoint ( p . x ( ) , p . y ( ) - y ) ;
2017-02-17 23:51:35 +01:00
_message = message ;
2017-08-18 15:12:07 +02:00
index = i ;
2017-02-17 23:51:35 +01:00
return true ;
}
2017-04-24 23:00:26 +02:00
y + = message - > getHeight ( ) ;
2017-02-17 23:51:35 +01:00
}
return false ;
}
2017-06-06 17:18:23 +02:00
2018-06-13 03:58:52 +02:00
int ChannelView : : getLayoutWidth ( ) const
{
2018-08-11 22:23:06 +02:00
if ( this - > scrollBar_ - > isVisible ( ) )
2018-12-02 17:49:15 +01:00
return int ( this - > width ( ) - scrollbarPadding * this - > scale ( ) ) ;
2018-06-13 03:58:52 +02:00
return this - > width ( ) ;
}
2018-10-02 13:41:34 +02:00
void ChannelView : : selectWholeMessage ( MessageLayout * layout , int & messageIndex )
{
SelectionItem msgStart ( messageIndex ,
layout - > getFirstMessageCharacterIndex ( ) ) ;
SelectionItem msgEnd ( messageIndex , layout - > getLastCharacterIndex ( ) ) ;
this - > setSelection ( msgStart , msgEnd ) ;
}
2018-10-06 13:43:21 +02:00
void ChannelView : : getWordBounds ( MessageLayout * layout ,
const MessageLayoutElement * element ,
const QPoint & relativePos , int & wordStart ,
int & wordEnd )
{
const int mouseInWordIndex = element - > getMouseOverIndex ( relativePos ) ;
wordStart = layout - > getSelectionIndex ( relativePos ) - mouseInWordIndex ;
const int selectionLength = element - > getSelectionIndexCount ( ) ;
const int length =
element - > hasTrailingSpace ( ) ? selectionLength - 1 : selectionLength ;
wordEnd = wordStart + length ;
}
2020-04-18 11:09:22 +02:00
void ChannelView : : enableScrolling ( const QPointF & scrollStart )
{
this - > isScrolling_ = true ;
this - > lastMiddlePressPosition_ = scrollStart ;
// The line below prevents a sudden jerk at the beginning
this - > currentMousePosition_ = scrollStart ;
this - > scrollTimer_ . start ( ) ;
if ( ! QGuiApplication : : overrideCursor ( ) )
QGuiApplication : : setOverrideCursor ( this - > cursors_ . neutral ) ;
}
void ChannelView : : disableScrolling ( )
{
this - > isScrolling_ = false ;
this - > scrollTimer_ . stop ( ) ;
QGuiApplication : : restoreOverrideCursor ( ) ;
}
void ChannelView : : scrollUpdateRequested ( )
{
2020-09-26 15:47:20 +02:00
const qreal dpi = this - > devicePixelRatioF ( ) ;
2020-04-18 11:09:22 +02:00
const qreal delta = dpi * ( this - > currentMousePosition_ . y ( ) -
this - > lastMiddlePressPosition_ . y ( ) ) ;
const int cursorHeight = this - > cursors_ . neutral . pixmap ( ) . height ( ) ;
if ( fabs ( delta ) < = cursorHeight * dpi )
{
/*
* If within an area close to the initial position , don ' t do any
* scrolling at all .
*/
QGuiApplication : : changeOverrideCursor ( this - > cursors_ . neutral ) ;
return ;
}
qreal offset ;
if ( delta > 0 )
{
QGuiApplication : : changeOverrideCursor ( this - > cursors_ . down ) ;
offset = delta - cursorHeight ;
}
else
{
QGuiApplication : : changeOverrideCursor ( this - > cursors_ . up ) ;
offset = delta + cursorHeight ;
}
// "Good" feeling multiplier found by trial-and-error
const qreal multiplier = qreal ( 0.02 ) ;
this - > scrollBar_ - > offset ( multiplier * offset ) ;
}
2017-04-14 17:52:22 +02:00
} // namespace chatterino