diff --git a/res/icons/icon_float.ico b/res/icons/icon_float.ico new file mode 100644 index 0000000..34381dc Binary files /dev/null and b/res/icons/icon_float.ico differ diff --git a/src/discord/ChatView.cpp b/src/discord/ChatView.cpp new file mode 100644 index 0000000..a3732d4 --- /dev/null +++ b/src/discord/ChatView.cpp @@ -0,0 +1,111 @@ +#include "ChatView.hpp" +#include "DiscordInstance.hpp" +#include "Frontend.hpp" + +Guild* ChatView::GetCurrentGuild() +{ + return GetDiscordInstance()->GetGuild(m_guildID); +} + +Channel* ChatView::GetCurrentChannel() +{ + auto guild = GetCurrentGuild(); + if (!guild) + return nullptr; + + return guild->GetChannel(m_channelID); +} + +void ChatView::OnSelectChannel(Snowflake sf, bool bSendSubscriptionUpdate) +{ + if (m_channelID == sf) + return; + + // check if there are any channels and select the first (for now) + Guild* pGuild = GetDiscordInstance()->GetGuild(m_guildID); + if (!pGuild) return; + + Channel* pChan = pGuild->GetChannel(sf); + + if (!pChan) + { + if (sf != 0) + return; + } + else if (pChan->m_channelType == Channel::CATEGORY) + return; + + // Check if we have permission to view the channel. + auto& denyList = GetDiscordInstance()->m_channelDenyList; + + if (denyList.find(sf) != denyList.end() || + (pChan && !pChan->HasPermission(PERM_VIEW_CHANNEL))) + { + GetFrontend()->OnCantViewChannel(pChan->m_name); + return; + } + + GetDiscordInstance()->m_channelHistory.AddToHistory(m_channelID); + + m_channelID = sf; + pGuild->m_currentChannel = sf; + + GetFrontend()->UpdateSelectedChannel(m_viewID); + + if (!GetCurrentChannel() || !GetCurrentGuild()) + return; + + // send an update subscriptions message + if (bSendSubscriptionUpdate) + GetDiscordInstance()->UpdateSubscriptions(); +} + +void ChatView::OnSelectGuild(Snowflake sf, Snowflake chan) +{ + if (m_guildID == sf) + { + if (chan) + OnSelectChannel(chan); + + return; + } + + GetDiscordInstance()->m_channelHistory.AddToHistory(m_channelID); + + // select the guild + m_guildID = sf; + + // check if there are any channels and select the first (for now) + Guild* pGuild = GetDiscordInstance()->GetGuild(sf); + if (!pGuild) + return; + + GetFrontend()->UpdateSelectedGuild(m_viewID); + + if (pGuild->m_bChannelsLoaded && pGuild->m_channels.size()) + { + // Determine the first channel we should load. + // TODO: Note, unfortunately this isn't really the right order, but it works for now. + if (!chan && pGuild->m_currentChannel == 0) + { + for (auto& ch : pGuild->m_channels) + { + if (ch.HasPermission(PERM_VIEW_CHANNEL) && ch.m_channelType != Channel::CATEGORY) { + pGuild->m_currentChannel = ch.m_snowflake; + break; + } + } + } + + if (!chan) + chan = pGuild->m_currentChannel; + + OnSelectChannel(chan, false); + } + else + { + OnSelectChannel(0); + } + + GetDiscordInstance()->UpdateSubscriptions(); +} diff --git a/src/discord/ChatView.hpp b/src/discord/ChatView.hpp new file mode 100644 index 0000000..98236ab --- /dev/null +++ b/src/discord/ChatView.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include "Snowflake.hpp" + +struct Guild; +struct Channel; + +class ChatView +{ +public: + int GetID() const { return m_viewID; } + void SetID(int i) { m_viewID = i; } + + Snowflake GetCurrentGuildID() const { return m_guildID; } + Snowflake GetCurrentChannelID() const { return m_channelID; } + + Guild* GetCurrentGuild(); + Channel* GetCurrentChannel(); + + void SetCurrentGuildID(Snowflake sf) { m_guildID = sf; } + void SetCurrentChannelID(Snowflake sf) { m_channelID = sf; } + + void OnSelectChannel(Snowflake sf, bool bSendSubscriptionUpdate = true); + void OnSelectGuild(Snowflake sf, Snowflake chan = 0); + +private: + Snowflake m_guildID = 0; + Snowflake m_channelID = 0; + + int m_viewID = 0; +}; + +typedef std::shared_ptr ChatViewPtr; diff --git a/src/discord/DiscordInstance.cpp b/src/discord/DiscordInstance.cpp index f54d36f..7b317d1 100644 --- a/src/discord/DiscordInstance.cpp +++ b/src/discord/DiscordInstance.cpp @@ -91,49 +91,10 @@ void DiscordInstance::OnFetchedChannels(Guild* pGld, const std::string& content) pGld->m_bChannelsLoaded = true; pGld->m_currentChannel = chan; - GetFrontend()->UpdateSelectedGuild(); -} - -void DiscordInstance::OnSelectChannel(Snowflake sf, bool bSendSubscriptionUpdate) -{ - if (m_CurrentChannel == sf) - return; - - // check if there are any channels and select the first (for now) - Guild* pGuild = GetGuild(m_CurrentGuild); - if (!pGuild) return; - - Channel* pChan = pGuild->GetChannel(sf); - - if (!pChan) - { - if (sf != 0) - return; - } - else if (pChan->m_channelType == Channel::CATEGORY) - return; - - // Check if we have permission to view the channel. - if (m_channelDenyList.find(sf) != m_channelDenyList.end() || - (pChan && !pChan->HasPermission(PERM_VIEW_CHANNEL))) + for (auto& view : m_chatViews) { - GetFrontend()->OnCantViewChannel(pChan->m_name); - return; - } - - m_channelHistory.AddToHistory(m_CurrentChannel); - m_CurrentChannel = sf; - - pGuild->m_currentChannel = m_CurrentChannel; - - GetFrontend()->UpdateSelectedChannel(); - - if (!GetCurrentChannel() || !GetCurrentGuild()) - return; - - // send an update subscriptions message - if (bSendSubscriptionUpdate) { - UpdateSubscriptions(m_CurrentGuild, sf, false, false, false); + if (view->GetCurrentGuildID() == pGld->m_snowflake) + GetFrontend()->UpdateSelectedGuild(view->GetID()); } } @@ -197,6 +158,9 @@ void DiscordInstance::RequestPinnedMessages(Snowflake channel) void DiscordInstance::RequestGuildMembers(Snowflake guild, std::set members, bool bLoadPresences) { + if (!guild) + return; + Json data; Json guildIdArray, userIdsArray; guildIdArray.push_back(guild); @@ -210,7 +174,9 @@ void DiscordInstance::RequestGuildMembers(Snowflake guild, std::set m if (userIdsArray.empty()) return; - data["guild_id"] = guildIdArray; + if (!guildIdArray.empty()) + data["guild_id"] = guildIdArray; + data["user_ids"] = userIdsArray; data["presences"] = bLoadPresences; data["limit"] = nullptr; @@ -304,11 +270,33 @@ std::string DiscordInstance::LookupUserNameGlobally(Snowflake sf, Snowflake gld) #define MATCH_DMS (1 << 2) #define MATCH_GUILDS (1 << 3) +bool DiscordInstance::IsGuildOpened(Snowflake sf) const +{ + for (auto& view : m_chatViews) + { + if (view->GetCurrentGuildID() == sf) + return true; + } + + return false; +} + +bool DiscordInstance::IsChannelOpened(Snowflake sf) const +{ + for (auto& view : m_chatViews) + { + if (view->GetCurrentChannelID() == sf) + return true; + } + + return false; +} + void DiscordInstance::SearchSubGuild(std::vector& matches, Guild* pGuild, int matchFlags, const char* queryPtr) { for (auto& chan : pGuild->m_channels) { - if (chan.m_snowflake == m_CurrentChannel) + if (IsChannelOpened(chan.m_snowflake)) continue; if (chan.IsText()) { @@ -388,7 +376,7 @@ std::vector DiscordInstance::Search(const std::string& query) { for (auto& gld : m_guilds) { - if (gld.m_snowflake == m_CurrentGuild) + if (IsGuildOpened(gld.m_snowflake)) continue; float fzc = CompareFuzzy(gld.m_name, queryPtr); @@ -405,55 +393,6 @@ std::vector DiscordInstance::Search(const std::string& query) return matches; } -void DiscordInstance::OnSelectGuild(Snowflake sf, Snowflake chan) -{ - if (m_CurrentGuild == sf) - { - if (chan) - OnSelectChannel(chan); - - return; - } - - m_channelHistory.AddToHistory(m_CurrentChannel); - - // select the guild - m_CurrentGuild = sf; - - // check if there are any channels and select the first (for now) - Guild* pGuild = GetGuild(sf); - if (!pGuild) return; - - GetFrontend()->UpdateSelectedGuild(); - - if (pGuild->m_bChannelsLoaded && pGuild->m_channels.size()) - { - // Determine the first channel we should load. - // TODO: Note, unfortunately this isn't really the right order, but it works for now. - if (!chan && pGuild->m_currentChannel == 0) - { - for (auto& ch : pGuild->m_channels) - { - if (ch.HasPermission(PERM_VIEW_CHANNEL) && ch.m_channelType != Channel::CATEGORY) { - pGuild->m_currentChannel = ch.m_snowflake; - break; - } - } - } - - if (!chan) - chan = pGuild->m_currentChannel; - - OnSelectChannel(chan); - } - else - { - OnSelectChannel(0); - } - - UpdateSubscriptions(sf, chan, true, true, true); -} - void OnUpdateAvatar(const std::string& resid); void DiscordInstance::HandleRequest(NetRequest* pRequest) @@ -510,8 +449,11 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) str = "Sorry, you're not allowed to view the channel " + name + "."; - if (m_CurrentChannel == pRequest->key) - OnSelectChannel(0); + for (auto& view : m_chatViews) + { + if (view->GetCurrentChannelID() == pRequest->key) + view->OnSelectChannel(0); + } m_channelDenyList.insert(pRequest->key); @@ -526,7 +468,7 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) // Can't create a message here, buckaroo Snowflake nonce = GetIntFromString(pRequest->additional_data); - GetFrontend()->OnFailedToSendMessage(m_CurrentChannel, nonce); + GetFrontend()->OnFailedToSendMessage(pRequest->key, nonce); Message msg; msg.m_type = MessageType::DEFAULT; @@ -539,7 +481,7 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) msg.SetTime(time(NULL)); // *1 - I checked, the official Discord client also does that :) - GetFrontend()->OnAddMessage(m_CurrentChannel, msg); + GetFrontend()->OnAddMessage(pRequest->key, msg); break; } @@ -712,8 +654,7 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) for (auto& elem : j) ParseAndAddGuild(elem); - m_CurrentChannel = 0; - + GetMainView()->SetCurrentChannelID(0); GetFrontend()->RepaintGuildList(); // select the first one, if possible @@ -721,7 +662,7 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) if (m_guilds.size() > 0) guildsf = m_guilds.front().m_snowflake; - OnSelectGuild(guildsf); + GetMainView()->OnSelectGuild(guildsf); break; } case GUILD: @@ -788,9 +729,9 @@ void DiscordInstance::HandleRequest(NetRequest* pRequest) #endif } -void DiscordInstance::OnFetchedMessages(Snowflake gap, ScrollDir::eScrollDir sd) +void DiscordInstance::OnFetchedMessages(Snowflake channelId, Snowflake gap, ScrollDir::eScrollDir sd) { - GetFrontend()->RefreshMessages(sd, gap); + GetFrontend()->RefreshMessages(channelId, sd, gap); } void DiscordInstance::GatewayClosed(int errorCode) @@ -1251,18 +1192,21 @@ void DiscordInstance::SendHeartbeat() GetWebsocketClient()->SendMsg(m_gatewayConnId, j.dump()); } -bool DiscordInstance::EditMessageInCurrentChannel(const std::string& msg_, Snowflake msgId) +bool DiscordInstance::EditMessage(Snowflake channelID, const std::string& msg_, Snowflake msgId) { - if (!GetCurrentChannel() || !GetCurrentGuild()) + Channel* pChan = GetChannelGlobally(channelID); + if (!pChan) return false; - std::string msg = ResolveMentions(msg_, m_CurrentGuild, m_CurrentChannel); + Guild* pGuild = GetGuild(pChan->m_parentGuild); + if (!pGuild) + return false; - Channel* pChan = GetCurrentChannel(); - if (!pChan->HasPermission(PERM_SEND_MESSAGES)) return false; - + + std::string msg = ResolveMentions(msg_, pGuild->m_snowflake, pChan->m_snowflake); + MessagePtr pMsg = GetMessageCache()->GetLoadedMessage(pChan->m_snowflake, msgId); if (!pMsg) return false; @@ -1295,19 +1239,22 @@ bool DiscordInstance::EditMessageInCurrentChannel(const std::string& msg_, Snowf return true; } -bool DiscordInstance::SendMessageToCurrentChannel(const std::string& msg_, Snowflake& tempSf, Snowflake replyTo, bool mentionReplied) +bool DiscordInstance::SendAMessage(Snowflake channelID, const std::string& msg_, Snowflake& tempSf, Snowflake replyTo, bool mentionReplied) { - if (!GetCurrentChannel() || !GetCurrentGuild()) + Channel* pChan = GetChannelGlobally(channelID); + if (!pChan) return false; - std::string msg = ResolveMentions(msg_, m_CurrentGuild, m_CurrentChannel); - - Channel* pChan = GetCurrentChannel(); - tempSf = CreateTemporarySnowflake(); + Guild* pGuild = GetGuild(pChan->m_parentGuild); + if (!pGuild) + return false; if (!pChan->HasPermission(PERM_SEND_MESSAGES)) return false; - + + std::string msg = ResolveMentions(msg_, pGuild->m_snowflake, pChan->m_snowflake); + tempSf = CreateTemporarySnowflake(); + Json j; j["content"] = msg; j["flags"] = 0; @@ -1318,10 +1265,10 @@ bool DiscordInstance::SendMessageToCurrentChannel(const std::string& msg_, Snowf if (replyTo) { Json mr; - if (m_CurrentGuild) - mr["guild_id"] = m_CurrentGuild; + if (pGuild != &m_dmGuild) + mr["guild_id"] = pGuild->m_snowflake; - mr["channel_id"] = m_CurrentChannel; + mr["channel_id"] = pChan->m_snowflake; mr["message_id"] = replyTo; j["message_reference"] = mr; } @@ -1357,13 +1304,8 @@ bool DiscordInstance::SendMessageToCurrentChannel(const std::string& msg_, Snowf return true; } -void DiscordInstance::Typing() +void DiscordInstance::Typing(Snowflake channelID) { - if (!GetCurrentChannel() || !GetCurrentGuild()) - return; - - Channel* pChan = GetCurrentChannel(); - if (m_lastTypingSent + TYPING_INTERVAL >= GetTimeMs()) return; @@ -1372,7 +1314,7 @@ void DiscordInstance::Typing() GetHTTPClient()->PerformRequest( true, NetRequest::POST, - GetDiscordAPI() + "channels/" + std::to_string(pChan->m_snowflake) + "/typing", + GetDiscordAPI() + "channels/" + std::to_string(channelID) + "/typing", 0, DiscordRequest::TYPING, "", @@ -1487,43 +1429,47 @@ void DiscordInstance::RequestDeleteMessage(Snowflake chan, Snowflake msg) ); } -void DiscordInstance::UpdateSubscriptions(Snowflake guildId, Snowflake channelId, bool typing, bool activities, bool threads, int rangeMembers) +void DiscordInstance::UpdateSubscriptions(bool typing, bool activities, bool threads, int rangeMembers) { Json j, data; - if (guildId == 0) - { - // TODO - Subscriptions for DMs and groups. - j["op"] = GatewayOp::SUBSCRIBE_DM; + j["op"] = GatewayOp::UPDATE_SUBSCRIPTIONS; - data["channel_id"] = channelId; - } - else + std::map guildSubscriptions; + + for (auto& view : m_chatViews) { - j["op"] = GatewayOp::SUBSCRIBE_GUILD; + Snowflake guildID = view->GetCurrentGuildID(); + Snowflake channelID = view->GetCurrentChannelID(); + + if (guildID == 0) + continue; - Json subs, guild, channels, rangeParent, rangeChildren[1]; + auto f = guildSubscriptions.find(guildID); + if (f != guildSubscriptions.end()) + continue; // already added - // Amount of users loaded - int arr[2] = { 0, rangeMembers }; - rangeChildren[0] = arr; - rangeParent = rangeChildren; + guildSubscriptions[guildID] = channelID; + } - if (channelId != 0) - channels[std::to_string(channelId)] = rangeParent; + if (guildSubscriptions.empty()) + return; - data["guild_id"] = std::to_string(guildId); + Json subscriptions; + Json array1, array2; + array1.push_back(0); + array1.push_back(99); + array2.push_back(array1); - if (typing) - data["typing"] = true; - if (activities) - data["activities"] = true; - if (threads) - data["threads"] = true; + for (auto& kvp : guildSubscriptions) + { + auto guildID = std::to_string(kvp.first), channelID = std::to_string(kvp.second); - data["channels"] = channels; + subscriptions[guildID]["channels"][channelID] = array2; + subscriptions[guildID]["typing"] = true; } + data["subscriptions"] = subscriptions; j["d"] = data; DbgPrintF("Would be: %s", j.dump().c_str()); @@ -1548,15 +1494,28 @@ void DiscordInstance::RequestLeaveGuild(Snowflake guild) void DiscordInstance::JumpToMessage(Snowflake guild, Snowflake channel, Snowflake message) { - // jump there! - if (m_CurrentGuild != guild) { - OnSelectGuild(guild); - } - if (m_CurrentChannel != channel) { - OnSelectChannel(channel); + // check if any views already have the guild opened + ChatViewPtr viewPtr = nullptr; + + for (auto& view : m_chatViews) + { + if (view->GetCurrentGuildID() == guild) + { + if (!viewPtr || view->GetCurrentChannelID() == channel) + viewPtr = view; + } } + + // if we couldn't find the best view for the job, select the main one + if (!viewPtr) + viewPtr = GetMainView(); + + // just jump there + viewPtr->OnSelectGuild(guild); + viewPtr->OnSelectChannel(channel); + if (message) - GetFrontend()->JumpToMessage(message); + GetFrontend()->JumpToMessage(viewPtr->GetID(), message); } void DiscordInstance::LaunchURL(const std::string& url) @@ -1625,12 +1584,15 @@ void DiscordInstance::ClearData() m_relationships.clear(); m_mySnowflake = 0; - m_CurrentGuild = 0; - m_CurrentChannel = 0; m_gatewayConnId = -1; m_heartbeatSequenceId = -1; m_ackVersion = 0; m_nextAttachmentID = 1; + + for (auto& view : m_chatViews) { + view->SetCurrentGuildID(0); + view->SetCurrentChannelID(0); + } } std::string DiscordInstance::ResolveTimestamp(const std::string& timestampCode) @@ -1670,9 +1632,6 @@ std::string DiscordInstance::ResolveTimestamp(const std::string& timestampCode) void DiscordInstance::ResolveLinks(FormattedText* message, std::vector& interactables, Snowflake guildID) { - if (guildID == 0) - guildID = GetCurrentGuildID(); - auto& words = message->GetWords(); for (size_t i = 0; i < words.size(); i++) { @@ -1746,6 +1705,30 @@ void DiscordInstance::ResolveLinks(FormattedText* message, std::vectorGetID() == viewId) + viewId++; + } + + view->SetID(viewId); + m_chatViews.push_back(view); + +} + +void DiscordInstance::UnregisterView(ChatViewPtr view) +{ + auto iter = std::find(m_chatViews.begin(), m_chatViews.end(), view); + if (iter != m_chatViews.end()) + m_chatViews.erase(iter); + + assert(m_chatViews.size() != 0); +} + void DiscordInstance::SetActivityStatus(eActiveStatus status, bool bRequestServer) { DbgPrintF("Setting activity status to %d", status); @@ -2465,24 +2448,27 @@ void DiscordInstance::HandleREADY(Json& j) // select the first guild, if possible Snowflake guildsf = 0; + ChatViewPtr mainView = GetMainView(); if (firstReadyOnThisUser) { - m_CurrentChannel = 0; + GetMainView()->OnSelectChannel(0); auto list = m_guildItemList.GetItems(); if (list->size() > 0) guildsf = list->front()->GetID(); } else { - guildsf = m_CurrentGuild; + guildsf = GetMainView()->GetCurrentGuildID(); } GetFrontend()->RepaintGuildList(); - OnSelectGuild(guildsf); + GetMainView()->OnSelectGuild(guildsf); // Doing this because the contents of all channels might be outdated. GetMessageCache()->ClearAllChannels(); - GetFrontend()->UpdateSelectedChannel(); + + for (auto& view : m_chatViews) + GetFrontend()->UpdateSelectedChannel(view->GetID()); } void DiscordInstance::HandleMessageInsertOrUpdate(Json& j, bool bIsUpdate) @@ -2525,13 +2511,13 @@ void DiscordInstance::HandleMessageInsertOrUpdate(Json& j, bool bIsUpdate) suppRoles = pSettings->m_bSuppressRoles; } - if (msg.CheckWasMentioned(m_mySnowflake, guildId, suppEveryone, suppRoles) && m_CurrentChannel != channelId) + if (msg.CheckWasMentioned(m_mySnowflake, guildId, suppEveryone, suppRoles) && !IsChannelOpened(channelId)) pChan->m_mentionCount++; } bool updateAck = false; - if (m_CurrentChannel == channelId && pChan->m_lastViewedMsg == oldSentMsg) + if (IsChannelOpened(channelId) && pChan->m_lastViewedMsg == oldSentMsg) pChan->m_lastViewedMsg = pChan->m_lastSentMsg; else updateAck = true; @@ -2567,10 +2553,14 @@ void DiscordInstance::HandleMESSAGE_DELETE(Json& j) GetMessageCache()->DeleteMessage(channelId, messageId); - if (m_CurrentGuild != guildId || m_CurrentChannel != channelId) - return; - - GetFrontend()->OnDeleteMessage(messageId); + for (auto& view : m_chatViews) + { + if (view->GetCurrentGuildID() == guildId && + view->GetCurrentChannelID() == channelId) + { + GetFrontend()->OnDeleteMessage(view->GetID(), messageId); + } + } } void DiscordInstance::HandleMESSAGE_ACK(nlohmann::json& j) @@ -2648,12 +2638,23 @@ void DiscordInstance::HandleGUILD_DELETE(Json& j) m_guildItemList.EraseGuild(sf); GetFrontend()->RepaintGuildList(); - if (m_CurrentGuild == sf) + for (auto& view : m_chatViews) { - Snowflake sf = 0; - if (!m_guilds.empty()) - sf = m_guilds.begin()->m_snowflake; - OnSelectGuild(sf); + if (view->GetCurrentGuildID() != sf) + continue; + + if (view == GetMainView()) + { + Snowflake sf = 0; + if (!m_guilds.empty()) + sf = m_guilds.begin()->m_snowflake; + + view->OnSelectGuild(sf); + } + else + { + GetFrontend()->CloseView(view->GetID()); + } } break; } @@ -2677,8 +2678,11 @@ void DiscordInstance::HandleCHANNEL_CREATE(Json& j) pGuild->m_channels.push_back(chn); pGuild->m_channels.sort(); - if (m_CurrentGuild == guildId) - GetFrontend()->UpdateChannelList(); + for (auto& view : m_chatViews) + { + if (view->GetCurrentGuildID() == guildId) + GetFrontend()->UpdateChannelList(view->GetID()); + } } void DiscordInstance::HandleCHANNEL_UPDATE(Json& j) @@ -2709,8 +2713,12 @@ void DiscordInstance::HandleCHANNEL_UPDATE(Json& j) pGuild->m_channels.sort(); } - if (modifiedOrder || oldPerms != pChan->ComputePermissionOverwrites(m_mySnowflake, pGuild->ComputeBasePermissions(m_mySnowflake))) { - GetFrontend()->UpdateChannelList(); + if (modifiedOrder || oldPerms != pChan->ComputePermissionOverwrites(m_mySnowflake, pGuild->ComputeBasePermissions(m_mySnowflake))) + { + for (auto& view : m_chatViews) { + if (view->GetCurrentGuildID() == guildId) + GetFrontend()->UpdateChannelList(view->GetID()); + } } } @@ -2734,11 +2742,20 @@ void DiscordInstance::HandleCHANNEL_DELETE(Json& j) } } - if (m_CurrentChannel == channelId) - OnSelectChannel(0); + for (auto& view : m_chatViews) + { + if (view->GetCurrentChannelID() == channelId) + { + // if a DM, close immediately, otherwise select no channel + if (view->GetCurrentGuildID() == 0) + GetFrontend()->CloseView(view->GetID()); + else + view->OnSelectChannel(0); + } - if (m_CurrentGuild == guildId) - GetFrontend()->UpdateChannelList(); + if (view->GetCurrentGuildID() == guildId) + GetFrontend()->UpdateChannelList(view->GetID()); + } } static Snowflake GetGroupId(std::string idStr) @@ -2806,7 +2823,8 @@ void DiscordInstance::HandleGUILD_MEMBER_LIST_UPDATE(Json& j) pMember->m_groupId = currentGroup; } - GetFrontend()->UpdateMemberList(); + if (GetMainView()->GetCurrentGuildID() == guildId) + GetFrontend()->UpdateMemberList(GetMainView()->GetID()); } Snowflake DiscordInstance::ParseGuildMember(Snowflake guild, nlohmann::json& memb, Snowflake userID) @@ -2947,8 +2965,11 @@ void DiscordInstance::HandleGUILD_MEMBERS_CHUNK(nlohmann::json& j) } } - if (m_CurrentGuild == guildId) - GetFrontend()->RefreshMembers(memsToRefresh); + for (auto& view : m_chatViews) + { + if (view->GetCurrentGuildID() == guildId) + GetFrontend()->RefreshMembers(view->GetID(), memsToRefresh); + } } void DiscordInstance::HandleTYPING_START(nlohmann::json& j) @@ -3073,7 +3094,11 @@ void DiscordInstance::HandleGuildMemberListUpdate_Update(Snowflake guild, nlohma pGld->m_members[index] = sf; std::set updates{ sf }; - GetFrontend()->RefreshMembers(updates); + + for (auto& view : m_chatViews) { + if (view->GetCurrentGuildID() == pGld->m_snowflake) + GetFrontend()->RefreshMembers(view->GetID(), updates); + } } void DiscordInstance::OnUploadAttachmentFirst(NetRequest* pReq) @@ -3121,7 +3146,16 @@ void DiscordInstance::OnUploadAttachmentFirst(NetRequest* pReq) up.m_data.size() ); - GetFrontend()->OnStartProgress(pReq->key, up.m_name, true); + int viewID = 0; + for (auto& view : m_chatViews) + { + if (view->GetCurrentChannelID() == up.m_channelSF) { + viewID = view->GetID(); + break; + } + } + + GetFrontend()->OnStartProgress(viewID, pReq->key, up.m_name, true); } } @@ -3185,7 +3219,8 @@ void DiscordInstance::OnUploadAttachmentSecond(NetRequest* pReq) ups.erase(iter); } -bool DiscordInstance::SendMessageAndAttachmentToCurrentChannel( +bool DiscordInstance::SendMessageAndAttachment( + Snowflake channelID, const std::string& msg_, Snowflake& tempSf, uint8_t* attData, @@ -3193,17 +3228,21 @@ bool DiscordInstance::SendMessageAndAttachmentToCurrentChannel( const std::string& attName, bool isSpoiler) { - if (!GetCurrentChannel() || !GetCurrentGuild()) + Channel* pChan = GetChannelGlobally(channelID); + if (!pChan) return false; - std::string msg = ResolveMentions(msg_, m_CurrentGuild, m_CurrentChannel); - - Channel* pChan = GetCurrentChannel(); - tempSf = CreateTemporarySnowflake(); + Guild* pGuild = GetGuild(pChan->m_parentGuild); + if (!pGuild) + return false; if (!pChan->HasPermission(PERM_SEND_MESSAGES) || !pChan->HasPermission(PERM_ATTACH_FILES)) return false; - + + std::string msg = ResolveMentions(msg_, pGuild->m_snowflake, pChan->m_snowflake); + + tempSf = CreateTemporarySnowflake(); + std::string newAttName = (isSpoiler ? "SPOILER_" : "") + attName; Json file; @@ -3218,12 +3257,12 @@ bool DiscordInstance::SendMessageAndAttachmentToCurrentChannel( Json j; j["files"] = files; - m_pendingUploads[m_nextAttachmentID] = PendingUpload(newAttName, attData, attSize, msg, tempSf, m_CurrentChannel); + m_pendingUploads[m_nextAttachmentID] = PendingUpload(newAttName, attData, attSize, msg, tempSf, pChan->m_snowflake); GetHTTPClient()->PerformRequest( true, NetRequest::POST_JSON, - GetDiscordAPI() + "channels/" + std::to_string(m_CurrentChannel) + "/attachments", + GetDiscordAPI() + "channels/" + std::to_string(pChan->m_snowflake) + "/attachments", DiscordRequest::UPLOAD_ATTACHMENT, m_nextAttachmentID, j.dump(), diff --git a/src/discord/DiscordInstance.hpp b/src/discord/DiscordInstance.hpp index c2901a1..27e92cb 100644 --- a/src/discord/DiscordInstance.hpp +++ b/src/discord/DiscordInstance.hpp @@ -20,6 +20,7 @@ #include "UserGuildSettings.hpp" #include "GuildListItem.hpp" #include "FormattedText.hpp" +#include "ChatView.hpp" struct NetRequest; @@ -162,8 +163,6 @@ class DiscordInstance public: Snowflake m_mySnowflake = 0; - Snowflake m_CurrentGuild = 0; - Snowflake m_CurrentChannel = 0; // Guild DB std::list m_guilds; @@ -214,6 +213,9 @@ class DiscordInstance // List of guilds and guild folders. GuildItemList m_guildItemList; + // List of registered views. + std::vector m_chatViews; + public: Profile* GetProfile() { return GetProfileCache()->LookupProfile(m_mySnowflake, "", "", "", false); @@ -326,23 +328,9 @@ class DiscordInstance } } - Guild* GetCurrentGuild() - { - return GetGuild(m_CurrentGuild); - } - - Snowflake GetCurrentGuildID() const { - return m_CurrentGuild; - } - Channel* GetChannel(Snowflake sf) { Channel* pChan; - Guild* pGuild = GetCurrentGuild(); - if (pGuild) { - pChan = pGuild->GetChannel(sf); - if (pChan) return pChan; - } for (auto& gld : m_guilds) { pChan = gld.GetChannel(sf); @@ -353,15 +341,6 @@ class DiscordInstance return m_dmGuild.GetChannel(sf); } - Channel* GetCurrentChannel() - { - return GetChannel(m_CurrentChannel); - } - - Snowflake GetCurrentChannelID() const { - return m_CurrentChannel; - } - Channel* GetChannelGlobally(Snowflake sf) { return GetChannel(sf); } @@ -383,12 +362,6 @@ class DiscordInstance // Search for channels using the quick switcher query format. std::vector Search(const std::string& query); - // Select a guild. - void OnSelectGuild(Snowflake sf, Snowflake chan = 0); - - // Select a channel in the current guild. - void OnSelectChannel(Snowflake sf, bool bSendSubscriptionUpdate = true); - // Fetch messages in specified channel. void RequestMessages(Snowflake sf, ScrollDir::eScrollDir dir = ScrollDir::BEFORE, Snowflake source = 0, Snowflake gapper = 0); @@ -402,7 +375,7 @@ class DiscordInstance void RequestPinnedMessages(Snowflake channel); // Send a refresh to the message list. - void OnFetchedMessages(Snowflake gap = 0, ScrollDir::eScrollDir dir = ScrollDir::BEFORE); + void OnFetchedMessages(Snowflake channelId, Snowflake gap = 0, ScrollDir::eScrollDir dir = ScrollDir::BEFORE); // Handle the case where a channel list was fetched from a guild. void OnFetchedChannels(Guild* pGld, const std::string& content); @@ -419,14 +392,15 @@ class DiscordInstance // Transform snowflake mentions into user, channel, or emoji mentions. std::string ReverseMentions(const std::string& message, Snowflake guild, bool ttsMode = false); - // Send a message to the current channel. - bool SendMessageToCurrentChannel(const std::string& msg, Snowflake& tempSf, Snowflake reply = 0, bool mentionReplied = true); + // Send a message to a specified channel. + // N.B. would call it SendMessage but it conflicts with Windows' SendMessage. + bool SendAMessage(Snowflake channelID, const std::string& msg, Snowflake& tempSf, Snowflake reply = 0, bool mentionReplied = true); - // Send a message with an attachment to the current channel. - bool SendMessageAndAttachmentToCurrentChannel(const std::string& msg, Snowflake& tempSf, uint8_t* pAttData, size_t szAtt, const std::string& attName, bool isSpoiler = false); + // Send a message with an attachment to a specified channel. + bool SendMessageAndAttachment(Snowflake channelID, const std::string& msg, Snowflake& tempSf, uint8_t* pAttData, size_t szAtt, const std::string& attName, bool isSpoiler = false); - // Edit a message in the current channel. - bool EditMessageInCurrentChannel(const std::string& msg, Snowflake msgId); + // Edit a message in a specified channel. + bool EditMessage(Snowflake channelID, const std::string& msg, Snowflake msgId); // Set current activity status. void SetActivityStatus(eActiveStatus status, bool bRequestServer = true); @@ -435,7 +409,7 @@ class DiscordInstance void CloseGatewaySession(); // Inform the Discord backend that we are typing. - void Typing(); + void Typing(Snowflake channelID); // Inform the Discord backend that we have acknowledged messages up to but not including "message". // Used by the "mark unread" feature. @@ -454,7 +428,7 @@ class DiscordInstance void RequestLeaveGuild(Snowflake guild); // Update channels that we are subscribed to. - void UpdateSubscriptions(Snowflake guild, Snowflake channel, bool typing, bool activities, bool threads, int rangeMembers = 99); + void UpdateSubscriptions(bool typing = true, bool activities = true, bool threads = true, int rangeMembers = 99); // Request a jump to a message. void JumpToMessage(Snowflake guild, Snowflake channel, Snowflake message); @@ -473,7 +447,24 @@ class DiscordInstance void ClearData(); // Resolves links automatically in a formatted message. - void ResolveLinks(FormattedText* message, std::vector& interactables, Snowflake guildID = 0); + void ResolveLinks(FormattedText* message, std::vector& interactables, Snowflake guildID); + + // Register a chat view. + void RegisterView(ChatViewPtr view); + + // Unregister a chat view. + void UnregisterView(ChatViewPtr view); + + // Gets a pointer to the main (first) view. + ChatViewPtr GetMainView() const { + return m_chatViews[0]; + } + + // Checks if a guild is opened in a view. + bool IsGuildOpened(Snowflake sf) const; + + // Checks if a channel is opened in a view. + bool IsChannelOpened(Snowflake sf) const; public: DiscordInstance(std::string token) : m_token(token), m_notificationManager(this) { diff --git a/src/discord/FormattedText.cpp b/src/discord/FormattedText.cpp index 5cefb2b..8b0e797 100644 --- a/src/discord/FormattedText.cpp +++ b/src/discord/FormattedText.cpp @@ -299,7 +299,7 @@ void FormattedText::Tokenize(const std::string& newmsg, const std::string& oldms { switch (state) { case 0: - if (!isalnum(msg[i]) && msg[i] != ' ' && msg[i] != ']') + if ((msg[i] < 0 || !isalnum(msg[i])) && msg[i] != ' ' && msg[i] != ']') isok = false; if (msg[i] == ']') diff --git a/src/discord/Frontend.hpp b/src/discord/Frontend.hpp index e074487..54dd15b 100644 --- a/src/discord/Frontend.hpp +++ b/src/discord/Frontend.hpp @@ -25,7 +25,7 @@ class Frontend virtual void OnConnected() = 0; virtual void OnAddMessage(Snowflake channelID, const Message& msg) = 0; virtual void OnUpdateMessage(Snowflake channelID, const Message& msg) = 0; - virtual void OnDeleteMessage(Snowflake messageInCurrentChannel) = 0; + virtual void OnDeleteMessage(int viewID, Snowflake messageInCurrentChannel) = 0; virtual void OnStartTyping(Snowflake userID, Snowflake guildID, Snowflake channelID, time_t startTime) = 0; virtual void OnAttachmentDownloaded(bool bIsProfilePicture, const uint8_t* pData, size_t nSize, const std::string& additData) = 0; virtual void OnAttachmentFailed(bool bIsProfilePicture, const std::string& additData) = 0; @@ -35,7 +35,7 @@ class Frontend virtual void OnFailedToSendMessage(Snowflake channel, Snowflake message) = 0; virtual void OnFailedToUploadFile(const std::string& file, int error) = 0; virtual void OnFailedToCheckForUpdates(int result, const std::string& response) = 0; - virtual void OnStartProgress(Snowflake key, const std::string& fileName, bool isUploading) = 0; + virtual void OnStartProgress(int viewID, Snowflake key, const std::string& fileName, bool isUploading) = 0; virtual bool OnUpdateProgress(Snowflake key, size_t offset, size_t length) = 0; virtual void OnStopProgress(Snowflake key) = 0; virtual void OnNotification() = 0; @@ -48,10 +48,10 @@ class Frontend virtual void OnProtobufError(Protobuf::ErrorCode code) = 0; // Update requests - virtual void UpdateSelectedGuild() = 0; - virtual void UpdateSelectedChannel() = 0; - virtual void UpdateChannelList() = 0; - virtual void UpdateMemberList() = 0; + virtual void UpdateSelectedGuild(int viewID) = 0; + virtual void UpdateSelectedChannel(int viewID) = 0; + virtual void UpdateChannelList(int viewID) = 0; + virtual void UpdateMemberList(int viewID) = 0; virtual void UpdateChannelAcknowledge(Snowflake channelID, Snowflake messageID) = 0; virtual void UpdateProfileAvatar(Snowflake userID, const std::string& resid) = 0; virtual void UpdateProfilePopout(Snowflake userID) = 0; // <-- Updates if userID is the ID of the profile currently open @@ -60,11 +60,12 @@ class Frontend virtual void RepaintGuildList() = 0; virtual void RepaintProfile() = 0; virtual void RepaintProfileWithUserID(Snowflake id) = 0; - virtual void RefreshMessages(ScrollDir::eScrollDir sd, Snowflake gapCulprit) = 0; - virtual void RefreshMembers(const std::set& members) = 0; + virtual void RefreshMessages(Snowflake channelId, ScrollDir::eScrollDir sd, Snowflake gapCulprit) = 0; + virtual void RefreshMembers(int viewID, const std::set& members) = 0; + virtual void CloseView(int viewID) = 0; // Interactive requests - virtual void JumpToMessage(Snowflake messageInCurrentChannel) = 0; + virtual void JumpToMessage(int viewID, Snowflake messageInCurrentChannel) = 0; virtual void LaunchURL(const std::string& url) = 0; // Called by WebSocketClient, dispatches to relevant places including DiscordInstance diff --git a/src/discord/MessageCache.cpp b/src/discord/MessageCache.cpp index d7540ca..a5fbda7 100644 --- a/src/discord/MessageCache.cpp +++ b/src/discord/MessageCache.cpp @@ -16,6 +16,7 @@ void MessageCache::GetLoadedMessages(Snowflake channel, Snowflake guild, std::li { MessageChunkList& lst = m_mapMessages[channel]; lst.m_guild = guild; + lst.m_channel = channel; for (auto& msg : lst.m_messages) out.push_back(msg.second); @@ -24,6 +25,8 @@ void MessageCache::GetLoadedMessages(Snowflake channel, Snowflake guild, std::li void MessageCache::ProcessRequest(Snowflake channel, ScrollDir::eScrollDir sd, Snowflake anchor, nlohmann::json& j, const std::string& channelName) { MessageChunkList& lst = m_mapMessages[channel]; + lst.m_channel = channel; + lst.ProcessRequest(sd, anchor, j, channelName); } @@ -155,7 +158,7 @@ void MessageChunkList::ProcessRequest(ScrollDir::eScrollDir sd, Snowflake gap, j m_messages[msg->m_snowflake] = std::move(msg); } - GetDiscordInstance()->OnFetchedMessages(gap, sd); + GetDiscordInstance()->OnFetchedMessages(m_channel, gap, sd); } void MessageChunkList::AddMessage(const Message& msg) diff --git a/src/discord/MessageCache.hpp b/src/discord/MessageCache.hpp index 27c4788..264e734 100644 --- a/src/discord/MessageCache.hpp +++ b/src/discord/MessageCache.hpp @@ -14,6 +14,7 @@ struct MessageChunkList bool m_lastMessagesLoaded = false; Snowflake m_guild = 0; + Snowflake m_channel = 0; MessageChunkList(); void ProcessRequest(ScrollDir::eScrollDir sd, Snowflake anchor, nlohmann::json& j, const std::string& channelName); diff --git a/src/discord/NotificationManager.cpp b/src/discord/NotificationManager.cpp index 67561e6..d76087d 100644 --- a/src/discord/NotificationManager.cpp +++ b/src/discord/NotificationManager.cpp @@ -61,7 +61,7 @@ bool NotificationManager::IsNotificationWorthy(Snowflake guildID, Snowflake chan } // If we are focused on this very channel and the window is not minimized, return - if (channelID == m_pDiscord->GetCurrentChannelID() && !GetFrontend()->IsWindowMinimized()) + if (m_pDiscord->IsChannelOpened(channelID) && !GetFrontend()->IsWindowMinimized()) return false; auto pSettings = m_pDiscord->m_userGuildSettings.GetSettings(guildID); diff --git a/src/resource.h b/src/resource.h index 0c166d3..61f8fb1 100644 --- a/src/resource.h +++ b/src/resource.h @@ -99,7 +99,7 @@ #define IDI_NOTIFICATION 99 #define IDI_NOTIFICATION_2K 100 #define IDI_FOLDER 101 -#define IDC_CLICKER 102 +#define IDI_FLOAT 102 #define IDB_TARGET 200 #define IDB_CHANNEL 201 #define IDB_CATEGORY 202 @@ -120,6 +120,7 @@ #define IDB_EMPTY_4BPP 217 #define IDC_ZOOMIN 350 #define IDC_ZOOMOUT 351 +#define IDC_CLICKER 352 #define IDD_DIALOG_ABOUT 401 #define IDD_DIALOG_OPTIONS 402 #define IDD_DIALOG_MY_ACCOUNT 403 diff --git a/src/resource.rc b/src/resource.rc index f82a2d1..4c92024 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -415,6 +415,8 @@ IDI_NOTIFICATION_2K ICON "../res/icons/icon_notification_ IDI_FOLDER ICON "../res/icons/icon_folder.ico" +IDI_FLOAT ICON "../res/icons/icon_float.ico" + ///////////////////////////////////////////////////////////////////////////// // diff --git a/src/windows/AboutDialog.cpp b/src/windows/AboutDialog.cpp index beaac38..bdae086 100644 --- a/src/windows/AboutDialog.cpp +++ b/src/windows/AboutDialog.cpp @@ -34,5 +34,5 @@ static LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l void About() { - DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_ABOUT)), g_Hwnd, (DLGPROC)DialogProc); + DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_ABOUT)), GetMainHWND(), (DLGPROC)DialogProc); } diff --git a/src/windows/ChannelView.cpp b/src/windows/ChannelView.cpp index be8cf87..5e7f33c 100644 --- a/src/windows/ChannelView.cpp +++ b/src/windows/ChannelView.cpp @@ -420,10 +420,11 @@ void ChannelView::RemoveCategoryIfNeeded(const Channel& ch) mem.m_hItem = NULL; } -ChannelView* ChannelView::Create(HWND hwnd, LPRECT rect) +ChannelView* ChannelView::Create(ChatWindow* parent, LPRECT rect) { ChannelView* view = new ChannelView; - view->m_hwndParent = hwnd; + view->m_pParent = parent; + view->m_hwndParent = parent->GetHWND(); view->m_hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, @@ -434,7 +435,7 @@ ChannelView* ChannelView::Create(HWND hwnd, LPRECT rect) rect->top, rect->right - rect->left, rect->bottom - rect->top, - hwnd, + parent->GetHWND(), (HMENU)(CID_CHANNELVIEW), g_hInstance, view @@ -555,7 +556,7 @@ bool ChannelView::OnNotifyTree(LRESULT& out, WPARAM wParam, LPARAM lParam) if (sf) { m_currentChannel = sf; - GetDiscordInstance()->OnSelectChannel(sf); + m_pParent->GetChatView()->OnSelectChannel(sf); } break; @@ -614,7 +615,7 @@ bool ChannelView::OnNotifyList(LRESULT& out, WPARAM wParam, LPARAM lParam) return false; ChannelMember* pMember = &m_channels[itemID]; - GetDiscordInstance()->OnSelectChannel(pMember->m_snowflake); + m_pParent->GetChatView()->OnSelectChannel(pMember->m_snowflake); } break; } diff --git a/src/windows/ChannelView.hpp b/src/windows/ChannelView.hpp index 03ff846..1076c27 100644 --- a/src/windows/ChannelView.hpp +++ b/src/windows/ChannelView.hpp @@ -22,6 +22,7 @@ class ChannelView : public IChannelView int m_childCount; }; + ChatWindow* m_pParent; HWND m_hwndParent; std::vector m_channels; Snowflake m_currentChannel = 0; @@ -80,7 +81,7 @@ class ChannelView : public IChannelView public: static WNDCLASS g_ChannelViewClass; - static ChannelView* Create(HWND hwnd, LPRECT rect); + static ChannelView* Create(ChatWindow* parent, LPRECT rect); static void InitializeClass(); static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK ListWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/src/windows/ChannelViewOld.cpp b/src/windows/ChannelViewOld.cpp index 2441774..030b44e 100644 --- a/src/windows/ChannelViewOld.cpp +++ b/src/windows/ChannelViewOld.cpp @@ -2,10 +2,11 @@ WNDCLASS ChannelViewOld::g_ChannelViewLegacyClass; -ChannelViewOld* ChannelViewOld::Create(HWND hwnd, LPRECT rect) +ChannelViewOld* ChannelViewOld::Create(ChatWindow* parent, LPRECT rect) { ChannelViewOld* view = new ChannelViewOld; - view->m_hwndParent = hwnd; + view->m_pParent = parent; + view->m_hwndParent = parent->GetHWND(); view->m_hwnd = CreateWindowEx( 0, @@ -16,7 +17,7 @@ ChannelViewOld* ChannelViewOld::Create(HWND hwnd, LPRECT rect) rect->top, rect->right - rect->left, rect->bottom - rect->top, - hwnd, + view->m_hwndParent, (HMENU)(CID_CHANNELVIEW), g_hInstance, view @@ -248,7 +249,7 @@ LRESULT ChannelViewOld::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar Snowflake sf = member.m_id; pView->m_currentChannel = sf; - GetDiscordInstance()->OnSelectChannel(sf); + pView->m_pParent->GetChatView()->OnSelectChannel(sf); break; } } diff --git a/src/windows/ChannelViewOld.hpp b/src/windows/ChannelViewOld.hpp index 03228b6..c031c36 100644 --- a/src/windows/ChannelViewOld.hpp +++ b/src/windows/ChannelViewOld.hpp @@ -66,7 +66,7 @@ class ChannelViewOld : public IChannelView }; public: - static ChannelViewOld* Create(HWND hwnd, LPRECT rect); + static ChannelViewOld* Create(ChatWindow* parent, LPRECT rect); static void InitializeClass(); public: @@ -82,6 +82,7 @@ class ChannelViewOld : public IChannelView HWND GetTreeHWND() override { return m_listHwnd; } private: + ChatWindow* m_pParent = nullptr; HWND m_hwndParent = NULL; HWND m_listHwnd = NULL; Snowflake m_currentChannel = 0; diff --git a/src/windows/ChatWindow.cpp b/src/windows/ChatWindow.cpp new file mode 100644 index 0000000..ba0b556 --- /dev/null +++ b/src/windows/ChatWindow.cpp @@ -0,0 +1,161 @@ +#include "ChatWindow.hpp" +#include "ImageLoader.hpp" +#include "TextManager.hpp" +#include "UploadDialog.hpp" +#include "WinUtils.hpp" +#include "../discord/DiscordInstance.hpp" + +ChatWindow::ChatWindow() +{ + m_chatView = std::make_shared(); + + if (GetDiscordInstance()) + GetDiscordInstance()->RegisterView(m_chatView); +} + +ChatWindow::~ChatWindow() +{ + if (GetDiscordInstance()) + GetDiscordInstance()->UnregisterView(m_chatView); +} + +ChatViewPtr ChatWindow::GetChatView() const +{ + return m_chatView; +} + +Snowflake ChatWindow::GetCurrentGuildID() const +{ + return m_chatView->GetCurrentGuildID(); +} + +Snowflake ChatWindow::GetCurrentChannelID() const +{ + return m_chatView->GetCurrentChannelID(); +} + +Guild* ChatWindow::GetCurrentGuild() +{ + return m_chatView->GetCurrentGuild(); +} + +Channel* ChatWindow::GetCurrentChannel() +{ + return m_chatView->GetCurrentChannel(); +} + +void ChatWindow::SetCurrentGuildID(Snowflake sf) +{ + m_chatView->SetCurrentGuildID(sf); +} + +void ChatWindow::SetCurrentChannelID(Snowflake sf) +{ + m_chatView->SetCurrentChannelID(sf); +} + +void ChatWindow::ShowPasteFileDialog(HWND editHwnd) +{ + HWND hWnd = GetHWND(); + if (!OpenClipboard(hWnd)) { + DbgPrintW("Error opening clipboard: %d", GetLastError()); + return; + } + + HBITMAP hbm = NULL; + HDC hdc = NULL; + BYTE* pBytes = nullptr; + LPCTSTR fileName = nullptr; + std::vector data; + bool isClipboardClosed = false; + bool hadToUseLegacyBitmap = false; + int width = 0, height = 0, stride = 0, bpp = 0; + Channel* pChan = nullptr; + + HANDLE hbmi = GetClipboardData(CF_DIB); + if (hbmi) + { + DbgPrintW("Using CF_DIB to fetch image from clipboard"); + PBITMAPINFO bmi = (PBITMAPINFO) GlobalLock(hbmi); + + // WHAT THE HELL: Windows seems to add 12 extra bytes after the header + // representing the colors: [RED] [GREEN] [BLUE]. I don't know why we + // have to do this or how to un-hardcode it. Yes, in this case, the + // biClrUsed member is zero. + // Does it happen to include only part of the BITMAPV4HEADER??? + const int HACK_OFFSET = 12; + + width = bmi->bmiHeader.biWidth; + height = bmi->bmiHeader.biHeight; + bpp = bmi->bmiHeader.biBitCount; + stride = ((((width * bpp) + 31) & ~31) >> 3); + + int sz = stride * height; + pBytes = new BYTE[sz]; + memcpy(pBytes, (char*) bmi + bmi->bmiHeader.biSize + HACK_OFFSET, sz); + + GlobalUnlock(hbmi); + } + else + { + // try legacy CF_BITMAP + DbgPrintW("Using legacy CF_BITMAP"); + HBITMAP hbm = (HBITMAP) GetClipboardData(CF_BITMAP); + hadToUseLegacyBitmap = true; + + if (!hbm) + { + CloseClipboard(); + + // No bitmap, forward to the edit control if selected + HWND hFocus = GetFocus(); + if (hFocus == editHwnd) + SendMessage(hFocus, WM_PASTE, 0, 0); + + return; + } + + HDC hdc = GetDC(hWnd); + bool res = GetDataFromBitmap(hdc, hbm, pBytes, width, height, bpp); + ReleaseDC(hWnd, hdc); + + if (!res) + goto _fail; + + stride = ((((width * bpp) + 31) & ~31) >> 3); + } + + pChan = GetCurrentChannel(); + if (!pChan) { + DbgPrintW("No channel!"); + goto _fail; + } + + if (!pChan->HasPermission(PERM_SEND_MESSAGES) || + !pChan->HasPermission(PERM_ATTACH_FILES)) { + DbgPrintW("Can't attach files here!"); + goto _fail; + } + + // TODO: Don't always force alpha when pasting from CF_DIB or CF_BITMAP. + // I know this sucks, but I've tried a lot of things... + + // have to force alpha always + // have to flip vertically if !hadToUseLegacyBitmap + if (!ImageLoader::ConvertToPNG(&data, pBytes, width, height, stride, bpp, true, !hadToUseLegacyBitmap)) { + DbgPrintW("Cannot convert to PNG!"); + goto _fail; + } + + CloseClipboard(); isClipboardClosed = true; + + fileName = TmGetTString(IDS_UNKNOWN_FILE_NAME); + UploadDialogShowWithFileData(GetHWND(), GetCurrentChannelID(), data.data(), data.size(), fileName); + +_fail: + data.clear(); + if (pBytes) delete[] pBytes; + if (hdc) ReleaseDC(hWnd, hdc); + if (hbm) DeleteObject(hbm); // should I do this? + if (!isClipboardClosed) CloseClipboard(); +} diff --git a/src/windows/ChatWindow.hpp b/src/windows/ChatWindow.hpp new file mode 100644 index 0000000..8a99512 --- /dev/null +++ b/src/windows/ChatWindow.hpp @@ -0,0 +1,36 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "../discord/ChatView.hpp" + +class ChatWindow +{ +public: + ChatWindow(); + virtual ~ChatWindow(); + + virtual HWND GetHWND() const = 0; + virtual void Close() = 0; + + ChatViewPtr GetChatView() const; + + Snowflake GetCurrentGuildID() const; + Snowflake GetCurrentChannelID() const; + + Guild* GetCurrentGuild(); + Channel* GetCurrentChannel(); + + virtual void SetCurrentGuildID(Snowflake sf); + virtual void SetCurrentChannelID(Snowflake sf); + virtual bool IsChannelListVisible() const { return false; } + virtual int GetGuildListerWidth() const { return 0; } + virtual void OnTyping(Snowflake guildID, Snowflake channelID, Snowflake userID, time_t timeStamp) = 0; + virtual void OnStopTyping(Snowflake channelID, Snowflake userID) = 0; + + void ShowPasteFileDialog(HWND editHwnd); + +private: + ChatViewPtr m_chatView; +}; diff --git a/src/windows/Frontend_Win32.cpp b/src/windows/Frontend_Win32.cpp index 75d1367..106b2f6 100644 --- a/src/windows/Frontend_Win32.cpp +++ b/src/windows/Frontend_Win32.cpp @@ -12,27 +12,27 @@ void Frontend_Win32::OnLoginAgain() { - SendMessage(g_Hwnd, WM_LOGINAGAIN, 0, 0); + SendMessage(GetMainHWND(), WM_LOGINAGAIN, 0, 0); } void Frontend_Win32::OnLoggedOut() { - SendMessage(g_Hwnd, WM_LOGGEDOUT, 0, 0); + SendMessage(GetMainHWND(), WM_LOGGEDOUT, 0, 0); } void Frontend_Win32::OnSessionClosed(int errorCode) { - SendMessage(g_Hwnd, WM_SESSIONCLOSED, (WPARAM) errorCode, 0); + SendMessage(GetMainHWND(), WM_SESSIONCLOSED, (WPARAM) errorCode, 0); } void Frontend_Win32::OnConnecting() { - SendMessage(g_Hwnd, WM_CONNECTING, 0, 0); + SendMessage(GetMainHWND(), WM_CONNECTING, 0, 0); } void Frontend_Win32::OnConnected() { - SendMessage(g_Hwnd, WM_CONNECTED, 0, 0); + SendMessage(GetMainHWND(), WM_CONNECTED, 0, 0); } void Frontend_Win32::OnAddMessage(Snowflake channelID, const Message& msg) @@ -40,7 +40,7 @@ void Frontend_Win32::OnAddMessage(Snowflake channelID, const Message& msg) AddMessageParams parms; parms.channel = channelID; parms.msg = msg; - SendMessage(g_Hwnd, WM_ADDMESSAGE, 0, (LPARAM)&parms); + SendMessage(GetMainHWND(), WM_ADDMESSAGE, 0, (LPARAM)&parms); } void Frontend_Win32::OnUpdateMessage(Snowflake channelID, const Message& msg) @@ -48,12 +48,12 @@ void Frontend_Win32::OnUpdateMessage(Snowflake channelID, const Message& msg) AddMessageParams parms; parms.channel = channelID; parms.msg = msg; - SendMessage(g_Hwnd, WM_UPDATEMESSAGE, 0, (LPARAM)&parms); + SendMessage(GetMainHWND(), WM_UPDATEMESSAGE, 0, (LPARAM)&parms); } -void Frontend_Win32::OnDeleteMessage(Snowflake messageInCurrentChannel) +void Frontend_Win32::OnDeleteMessage(int viewID, Snowflake messageInCurrentChannel) { - SendMessage(g_Hwnd, WM_DELETEMESSAGE, 0, (LPARAM)&messageInCurrentChannel); + SendMessage(GetMainHWND(), WM_DELETEMESSAGE, viewID, (LPARAM)&messageInCurrentChannel); } void Frontend_Win32::OnStartTyping(Snowflake userID, Snowflake guildID, Snowflake channelID, time_t startTime) @@ -63,7 +63,7 @@ void Frontend_Win32::OnStartTyping(Snowflake userID, Snowflake guildID, Snowflak parms.m_guild = guildID; parms.m_channel = channelID; parms.m_timestamp = startTime; - SendMessage(g_Hwnd, WM_STARTTYPING, 0, (LPARAM)&parms); + SendMessage(GetMainHWND(), WM_STARTTYPING, 0, (LPARAM)&parms); } extern int g_latestSSLError; // HACK -- defined by the NetworkerThread. Used to debug an issue. @@ -81,7 +81,7 @@ void Frontend_Win32::OnGenericError(const std::string& message) } LPCTSTR pMsgBoxText = ConvertCppStringToTString(newMsg); - MessageBox(g_Hwnd, pMsgBoxText, TmGetTString(IDS_ERROR), MB_OK | MB_ICONERROR); + MessageBox(GetMainHWND(), pMsgBoxText, TmGetTString(IDS_ERROR), MB_OK | MB_ICONERROR); free((void*)pMsgBoxText); pMsgBoxText = NULL; } @@ -91,7 +91,7 @@ void Frontend_Win32::OnJsonException(const std::string& message) std::string err(TmGetString(IDS_JSON_EXCEPTION)); err += message; LPCTSTR pMsgBoxText = ConvertCppStringToTString(err); - MessageBox(g_Hwnd, pMsgBoxText, TmGetTString(IDS_ERROR), MB_OK | MB_ICONERROR); + MessageBox(GetMainHWND(), pMsgBoxText, TmGetTString(IDS_ERROR), MB_OK | MB_ICONERROR); free((void*)pMsgBoxText); pMsgBoxText = NULL; } @@ -104,7 +104,7 @@ void Frontend_Win32::OnCantViewChannel(const std::string& channelName) free(chanName); MessageBox( - g_Hwnd, + GetMainHWND(), buff, TmGetTString(IDS_PROGRAM_NAME), MB_OK | MB_ICONERROR @@ -113,7 +113,7 @@ void Frontend_Win32::OnCantViewChannel(const std::string& channelName) void Frontend_Win32::OnGatewayConnectFailure() { - MessageBox(g_Hwnd, TmGetTString(IDS_CONNECT_FAILURE), TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); + MessageBox(GetMainHWND(), TmGetTString(IDS_CONNECT_FAILURE), TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); RequestQuit(); } @@ -122,12 +122,12 @@ void Frontend_Win32::OnProtobufError(Protobuf::ErrorCode code) char buff[512]; buff[511] = 0; snprintf(buff, sizeof buff - 1, TmGetString(IDS_PROTOBUF_ERROR).c_str(), code); - MessageBoxA(g_Hwnd, buff, TmGetString(IDS_ERROR).c_str(), MB_ICONERROR); + MessageBoxA(GetMainHWND(), buff, TmGetString(IDS_ERROR).c_str(), MB_ICONERROR); } void Frontend_Win32::OnRequestDone(NetRequest* pRequest) { - SendMessage(g_Hwnd, WM_REQUESTDONE, 0, (LPARAM) pRequest); + SendMessage(GetMainHWND(), WM_REQUESTDONE, 0, (LPARAM) pRequest); } void Frontend_Win32::OnLoadedPins(Snowflake channel, const std::string& data) @@ -141,7 +141,7 @@ void Frontend_Win32::OnUpdateAvailable(const std::string& url, const std::string std::string* msg = new std::string[2]; msg[0] = url; msg[1] = version; - PostMessage(g_Hwnd, WM_UPDATEAVAILABLE, 0, (LPARAM) msg); + PostMessage(GetMainHWND(), WM_UPDATEAVAILABLE, 0, (LPARAM) msg); } void Frontend_Win32::OnFailedToSendMessage(Snowflake channel, Snowflake message) @@ -149,7 +149,7 @@ void Frontend_Win32::OnFailedToSendMessage(Snowflake channel, Snowflake message) FailedMessageParams parms; parms.channel = channel; parms.message = message; - SendMessage(g_Hwnd, WM_FAILMESSAGE, 0, (LPARAM)&parms); + SendMessage(GetMainHWND(), WM_FAILMESSAGE, 0, (LPARAM)&parms); } void Frontend_Win32::OnFailedToUploadFile(const std::string& file, int error) @@ -162,7 +162,7 @@ void Frontend_Win32::OnFailedToUploadFile(const std::string& file, int error) LPTSTR tstr = ConvertCppStringToTString(file); WAsnprintf(buff, _countof(buff), TmGetTString(IDS_FAILED_TO_UPLOAD), tstr, error); free(tstr); - MessageBox(g_Hwnd, buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); + MessageBox(GetMainHWND(), buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); } void Frontend_Win32::OnFailedToCheckForUpdates(int result, const std::string& response) @@ -172,12 +172,23 @@ void Frontend_Win32::OnFailedToCheckForUpdates(int result, const std::string& re WAsnprintf(buff, _countof(buff), TmGetTString(IDS_FAILED_UPDATE_CHECK), result, tstr); free(tstr); - MessageBox(g_Hwnd, buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); + MessageBox(GetMainHWND(), buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); } -void Frontend_Win32::OnStartProgress(Snowflake key, const std::string& fileName, bool isUploading) +void Frontend_Win32::OnStartProgress(int viewID, Snowflake key, const std::string& fileName, bool isUploading) { - ProgressDialog::Show(fileName, key, isUploading, g_Hwnd); + HWND hWnd = GetMainHWND(); + + auto& windows = GetMainWindow()->GetSubWindows(); + for (auto& window : windows) + { + if (window->GetChatView()->GetID() == viewID) { + hWnd = window->GetHWND(); + break; + } + } + + ProgressDialog::Show(fileName, key, isUploading, hWnd); } bool Frontend_Win32::OnUpdateProgress(Snowflake key, size_t offset, size_t length) @@ -207,7 +218,7 @@ void Frontend_Win32::OnAttachmentDownloaded(bool bIsProfilePicture, const uint8_ { GetAvatarCache()->LoadedResource(additData); GetAvatarCache()->SetImage(additData, himg, bHasAlpha); - OnUpdateAvatar(additData); + GetMainWindow()->OnUpdateAvatar(additData); // note: stole the resource so that the HImage destructor doesn't delete it. } else @@ -243,27 +254,27 @@ void Frontend_Win32::OnAttachmentFailed(bool bIsProfilePicture, const std::strin GetAvatarCache()->LoadedResource(additData); GetAvatarCache()->SetImage(additData, HIMAGE_ERROR, false); - OnUpdateAvatar(additData); + GetMainWindow()->OnUpdateAvatar(additData); } -void Frontend_Win32::UpdateSelectedGuild() +void Frontend_Win32::UpdateSelectedGuild(int viewID) { - SendMessage(g_Hwnd, WM_UPDATESELECTEDGUILD, 0, 0); + SendMessage(GetMainHWND(), WM_UPDATESELECTEDGUILD, viewID, 0); } -void Frontend_Win32::UpdateSelectedChannel() +void Frontend_Win32::UpdateSelectedChannel(int viewID) { - SendMessage(g_Hwnd, WM_UPDATESELECTEDCHANNEL, 0, 0); + SendMessage(GetMainHWND(), WM_UPDATESELECTEDCHANNEL, viewID, 0); } -void Frontend_Win32::UpdateChannelList() +void Frontend_Win32::UpdateChannelList(int viewID) { - SendMessage(g_Hwnd, WM_UPDATECHANLIST, 0, 0); + SendMessage(GetMainHWND(), WM_UPDATECHANLIST, viewID, 0); } -void Frontend_Win32::UpdateMemberList() +void Frontend_Win32::UpdateMemberList(int viewID) { - SendMessage(g_Hwnd, WM_UPDATEMEMBERLIST, 0, 0); + SendMessage(GetMainHWND(), WM_UPDATEMEMBERLIST, viewID, 0); } void Frontend_Win32::UpdateChannelAcknowledge(Snowflake channelID, Snowflake messageID) @@ -271,7 +282,7 @@ void Frontend_Win32::UpdateChannelAcknowledge(Snowflake channelID, Snowflake mes Snowflake sfs[2]; sfs[0] = channelID; sfs[1] = messageID; - SendMessage(g_Hwnd, WM_UPDATECHANACKS, 0, (LPARAM) sfs); + SendMessage(GetMainHWND(), WM_UPDATECHANACKS, 0, (LPARAM) sfs); } void Frontend_Win32::UpdateProfileAvatar(Snowflake userID, const std::string& resid) @@ -279,7 +290,7 @@ void Frontend_Win32::UpdateProfileAvatar(Snowflake userID, const std::string& re UpdateProfileParams parms; parms.m_user = userID; parms.m_resId = resid; - SendMessage(g_Hwnd, WM_UPDATEPROFILEAVATAR, 0, (LPARAM) &parms); + SendMessage(GetMainHWND(), WM_UPDATEPROFILEAVATAR, 0, (LPARAM) &parms); } void Frontend_Win32::UpdateProfilePopout(Snowflake userID) @@ -290,43 +301,48 @@ void Frontend_Win32::UpdateProfilePopout(Snowflake userID) void Frontend_Win32::UpdateUserData(Snowflake userID) { - SendMessage(g_Hwnd, WM_UPDATEUSER, 0, (LPARAM) &userID); + SendMessage(GetMainHWND(), WM_UPDATEUSER, 0, (LPARAM) &userID); } void Frontend_Win32::UpdateAttachment(Snowflake attID) { - SendMessage(g_Hwnd, WM_UPDATEATTACHMENT, 0, (LPARAM) &attID); + SendMessage(GetMainHWND(), WM_UPDATEATTACHMENT, 0, (LPARAM) &attID); } void Frontend_Win32::RepaintGuildList() { - SendMessage(g_Hwnd, WM_REPAINTGUILDLIST, 0, 0); + SendMessage(GetMainHWND(), WM_REPAINTGUILDLIST, 0, 0); } void Frontend_Win32::RepaintProfile() { - SendMessage(g_Hwnd, WM_REPAINTPROFILE, 0, 0); + SendMessage(GetMainHWND(), WM_REPAINTPROFILE, 0, 0); } void Frontend_Win32::RepaintProfileWithUserID(Snowflake id) { if (GetDiscordInstance()->GetUserID() == id) - SendMessage(g_Hwnd, WM_REPAINTPROFILE, 0, 0); + SendMessage(GetMainHWND(), WM_REPAINTPROFILE, 0, 0); } -void Frontend_Win32::RefreshMessages(ScrollDir::eScrollDir sd, Snowflake gapCulprit) +void Frontend_Win32::RefreshMessages(Snowflake channelId, ScrollDir::eScrollDir sd, Snowflake gapCulprit) { - SendMessage(g_Hwnd, WM_REFRESHMESSAGES, (WPARAM) sd, (LPARAM) &gapCulprit); + RefreshMessagesParams params; + params.m_channelId = channelId; + params.m_scrollDir = sd; + params.m_gapCulprit = gapCulprit; + + SendMessage(GetMainHWND(), WM_REFRESHMESSAGES, 0, (LPARAM) ¶ms); } -void Frontend_Win32::RefreshMembers(const std::set& members) +void Frontend_Win32::RefreshMembers(int viewID, const std::set& members) { - SendMessage(g_Hwnd, WM_REFRESHMEMBERS, 0, (LPARAM) &members); + SendMessage(GetMainHWND(), WM_REFRESHMEMBERS, viewID, (LPARAM) &members); } -void Frontend_Win32::JumpToMessage(Snowflake messageInCurrentChannel) +void Frontend_Win32::JumpToMessage(int viewID, Snowflake messageInCurrentChannel) { - SendMessage(g_Hwnd, WM_SENDTOMESSAGE, 0, (LPARAM) &messageInCurrentChannel); + SendMessage(GetMainHWND(), WM_SENDTOMESSAGE, viewID, (LPARAM) &messageInCurrentChannel); } void Frontend_Win32::OnWebsocketMessage(int gatewayID, const std::string& payload) @@ -336,7 +352,7 @@ void Frontend_Win32::OnWebsocketMessage(int gatewayID, const std::string& payloa pParm->m_payload = payload; // N.B. The main window shall respond the message immediately with ReplyMessage - SendMessage(g_Hwnd, WM_WEBSOCKETMESSAGE, 0, (LPARAM) pParm); + SendMessage(GetMainHWND(), WM_WEBSOCKETMESSAGE, 0, (LPARAM) pParm); } void Frontend_Win32::OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) @@ -375,19 +391,19 @@ void Frontend_Win32::OnWebsocketFail(int gatewayID, int errorCode, const std::st } int flags = (mayRetry ? MB_RETRYCANCEL : MB_OK) | (isTLSError ? MB_ICONWARNING : MB_ICONERROR); - int result = MessageBox(g_Hwnd, buffer, TmGetTString(IDS_PROGRAM_NAME), flags); + int result = MessageBox(GetMainHWND(), buffer, TmGetTString(IDS_PROGRAM_NAME), flags); if (mayRetry) { if (result == IDRETRY) { - SendMessage(g_Hwnd, WM_CONNECTERROR, 0, 0); + SendMessage(GetMainHWND(), WM_CONNECTERROR, 0, 0); } else { - SendMessage(g_Hwnd, WM_DESTROY, 0, 0); + SendMessage(GetMainHWND(), WM_DESTROY, 0, 0); RequestQuit(); } } else { - SendMessage(g_Hwnd, WM_CONNECTERROR2, 0, 0); + SendMessage(GetMainHWND(), WM_CONNECTERROR2, 0, 0); } } @@ -399,22 +415,22 @@ void Frontend_Win32::RequestQuit() void Frontend_Win32::HideWindow() { - ShowWindow(g_Hwnd, SW_HIDE); + ShowWindow(GetMainHWND(), SW_HIDE); } void Frontend_Win32::RestoreWindow() { - ShowWindow(g_Hwnd, SW_RESTORE); + ShowWindow(GetMainHWND(), SW_RESTORE); } void Frontend_Win32::MaximizeWindow() { - ShowWindow(g_Hwnd, SW_MAXIMIZE); + ShowWindow(GetMainHWND(), SW_MAXIMIZE); } bool Frontend_Win32::IsWindowMinimized() { - return IsIconic(g_Hwnd); + return IsIconic(GetMainHWND()); } #ifdef USE_DEBUG_PRINTS @@ -445,7 +461,7 @@ void Frontend_Win32::DebugPrint(const char* fmt, va_list vl) void Frontend_Win32::SetHeartbeatInterval(int timeMs) { - ::SetHeartbeatInterval(timeMs); + GetMainWindow()->SetHeartbeatInterval(timeMs); } void Frontend_Win32::LaunchURL(const std::string& url) @@ -473,6 +489,11 @@ void Frontend_Win32::RegisterChannelIcon(Snowflake sf, const std::string& avatar GetAvatarCache()->AddImagePlace(avatarlnk, eImagePlace::CHANNEL_ICONS, avatarlnk, sf); } +void Frontend_Win32::CloseView(int viewID) +{ + SendMessage(GetMainHWND(), WM_CLOSEVIEW, viewID, 0); +} + std::string Frontend_Win32::GetDirectMessagesText() { return TmGetString(IDS_DIRECT_MESSAGES); diff --git a/src/windows/Frontend_Win32.hpp b/src/windows/Frontend_Win32.hpp index ff7821f..b7a626a 100644 --- a/src/windows/Frontend_Win32.hpp +++ b/src/windows/Frontend_Win32.hpp @@ -15,7 +15,7 @@ class Frontend_Win32 : public Frontend void OnConnected() override; void OnAddMessage(Snowflake channelID, const Message& msg) override; void OnUpdateMessage(Snowflake channelID, const Message& msg) override; - void OnDeleteMessage(Snowflake messageInCurrentChannel) override; + void OnDeleteMessage(int viewID, Snowflake messageInCurrentChannel) override; void OnStartTyping(Snowflake userID, Snowflake guildID, Snowflake channelID, time_t startTime) override; void OnRequestDone(NetRequest* pRequest) override; void OnLoadedPins(Snowflake channel, const std::string& data) override; @@ -23,7 +23,7 @@ class Frontend_Win32 : public Frontend void OnFailedToSendMessage(Snowflake channel, Snowflake message) override; void OnFailedToUploadFile(const std::string& file, int error) override; void OnFailedToCheckForUpdates(int result, const std::string& response) override; - void OnStartProgress(Snowflake key, const std::string& fileName, bool isUploading); + void OnStartProgress(int viewID, Snowflake key, const std::string& fileName, bool isUploading); bool OnUpdateProgress(Snowflake key, size_t offset, size_t length); void OnStopProgress(Snowflake key); void OnNotification(); @@ -34,10 +34,10 @@ class Frontend_Win32 : public Frontend void OnProtobufError(Protobuf::ErrorCode code) override; void OnAttachmentDownloaded(bool bIsProfilePicture, const uint8_t* pData, size_t nSize, const std::string& additData) override; void OnAttachmentFailed(bool bIsProfilePicture, const std::string& additData) override; - void UpdateSelectedGuild() override; - void UpdateSelectedChannel() override; - void UpdateChannelList() override; - void UpdateMemberList() override; + void UpdateSelectedGuild(int viewID) override; + void UpdateSelectedChannel(int viewID) override; + void UpdateChannelList(int viewID) override; + void UpdateMemberList(int viewID) override; void UpdateChannelAcknowledge(Snowflake channelID, Snowflake messageID) override; void UpdateProfileAvatar(Snowflake userID, const std::string& resid) override; void UpdateProfilePopout(Snowflake userID) override; @@ -46,9 +46,9 @@ class Frontend_Win32 : public Frontend void RepaintGuildList() override; void RepaintProfile() override; void RepaintProfileWithUserID(Snowflake id) override; - void RefreshMessages(ScrollDir::eScrollDir sd, Snowflake gapCulprit) override; - void RefreshMembers(const std::set& members) override; - void JumpToMessage(Snowflake messageInCurrentChannel) override; + void RefreshMessages(Snowflake channelId, ScrollDir::eScrollDir sd, Snowflake gapCulprit) override; + void RefreshMembers(int viewID, const std::set& members) override; + void JumpToMessage(int viewID, Snowflake messageInCurrentChannel) override; void OnWebsocketMessage(int gatewayID, const std::string& payload) override; void OnWebsocketClose(int gatewayID, int errorCode, const std::string& message) override; void OnWebsocketFail(int gatewayID, int errorCode, const std::string& message, bool isTLSError, bool mayRetry) override; @@ -58,6 +58,7 @@ class Frontend_Win32 : public Frontend void RegisterAvatar(Snowflake sf, const std::string& avatarlnk) override; void RegisterAttachment(Snowflake sf, const std::string& avatarlnk) override; void RegisterChannelIcon(Snowflake sf, const std::string& avatarlnk) override; + void CloseView(int viewID) override; void RequestQuit() override; void HideWindow() override; void RestoreWindow() override; diff --git a/src/windows/GuildHeader.cpp b/src/windows/GuildHeader.cpp index 627e97c..a2591df 100644 --- a/src/windows/GuildHeader.cpp +++ b/src/windows/GuildHeader.cpp @@ -10,6 +10,7 @@ #define IDTB_PINS (1001) // Show pinned messages #define IDTB_CHANNELS (1002) // Hide channel list #define IDTB_NOTIFS (1003) // Show notifications +#define IDTB_FLOAT (1004) // Float channel out WNDCLASS GuildHeader::g_GuildHeaderClass; @@ -39,12 +40,13 @@ GuildHeader::GuildHeader() m_buttons.push_back(Button(IDTB_MEMBERS, DMIC(IDI_MEMBERS), BUTTON_RIGHT)); m_buttons.push_back(Button(IDTB_PINS, DMIC(IDI_PIN), BUTTON_RIGHT)); m_buttons.push_back(Button(IDTB_NOTIFS, DMIC(IDI_NOTIFICATION), BUTTON_RIGHT)); + m_buttons.push_back(Button(IDTB_FLOAT, DMIC(IDI_FLOAT), BUTTON_RIGHT)); m_buttons.push_back(Button(IDTB_CHANNELS, DMIC(IDI_SHIFT_LEFT), BUTTON_GUILD_RIGHT)); } std::string GuildHeader::GetGuildName() { - Guild* pGuild = GetDiscordInstance()->GetCurrentGuild(); + Guild* pGuild = GetCurrentGuild(); if (!pGuild) return ""; return pGuild->m_name; @@ -57,7 +59,7 @@ std::string GuildHeader::GetSubtitleText() std::string GuildHeader::GetChannelName() { - Channel *pChannel = GetDiscordInstance()->GetCurrentChannel(); + Channel *pChannel = GetCurrentChannel(); if (!pChannel) return ""; return pChannel->m_name; @@ -65,12 +67,26 @@ std::string GuildHeader::GetChannelName() std::string GuildHeader::GetChannelInfo() { - Channel* pChannel = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChannel = GetCurrentChannel(); if (!pChannel) return ""; return pChannel->m_topic; } +Guild* GuildHeader::GetCurrentGuild() +{ + return GetDiscordInstance()->GetGuild(m_guildID); +} + +Channel* GuildHeader::GetCurrentChannel() +{ + auto guild = GetCurrentGuild(); + if (!guild) + return nullptr; + + return guild->GetChannel(m_channelID); +} + void GuildHeader::Update() { InvalidateRect(m_hwnd, NULL, false); @@ -117,8 +133,6 @@ void GuildHeader::LayoutButton(Button& button, RECT& chanNameRect, RECT& guildNa } } -extern bool g_bChannelListVisible; // main.cpp - void GuildHeader::Layout() { RECT rect = {}; @@ -129,7 +143,7 @@ void GuildHeader::Layout() RECT &rrect1 = m_rectLeftFull, &rrect2 = m_rectMidFull, &rrect3 = m_rectRightFull; rrect1 = rect, rrect2 = rect, rrect3 = rect; - rrect1.right = rrect1.left + g_bChannelListVisible ? ScaleByDPI(CHANNEL_VIEW_LIST_WIDTH) : (rect.bottom - rect.top); + rrect1.right = rrect1.left + m_pParent->IsChannelListVisible() ? ScaleByDPI(CHANNEL_VIEW_LIST_WIDTH) : (rect.bottom - rect.top); rrect2.left = rrect1.right; rrect3.left = rrect3.right - ScaleByDPI(MEMBER_LIST_WIDTH); rrect2.right = rrect3.left; @@ -292,9 +306,7 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_NCCREATE: { CREATESTRUCT* strct = (CREATESTRUCT*)lParam; - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)strct->lpCreateParams); - break; } case WM_DESTROY: @@ -369,7 +381,7 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Show the info in a full textbox. LPCTSTR ctstr1 = ConvertCppStringToTString(cinfo); LPCTSTR ctstr2 = ConvertCppStringToTString("Discord Messenger - " + cname); - MessageBox(g_Hwnd, ctstr1, ctstr2, MB_OK | MB_ICONINFORMATION); + MessageBox(GetParent(pThis->m_hwnd), ctstr1, ctstr2, MB_OK | MB_ICONINFORMATION); free((void*)ctstr1); free((void*)ctstr2); } @@ -449,7 +461,7 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // N.B. Interactables currently unused for now. std::vector interactables; - GetDiscordInstance()->ResolveLinks(&pThis->m_channelDescription, interactables, GetDiscordInstance()->GetCurrentGuildID()); + GetDiscordInstance()->ResolveLinks(&pThis->m_channelDescription, interactables, pThis->GetCurrentGuildID()); } LPCTSTR ctstrName = ConvertCppStringToTString(gname); @@ -460,7 +472,8 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA RECT nameRect; int nameHeight; - if (g_bChannelListVisible) + bool channelListVisible = pThis->m_pParent->IsChannelListVisible(); + if (channelListVisible) { nameRect = pThis->m_rectLeft; nameRect.left += ScaleByDPI(8); @@ -498,7 +511,7 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA nameRect = pThis->m_rectMid; // Draw the channel icon. - Channel* pChannel = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChannel = pThis->GetCurrentChannel(); if (pChannel) { HICON icon = pThis->GetIconFromType(pChannel->m_channelType); @@ -558,8 +571,8 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA switch (wParam) { case IDTB_PINS: { - Guild* pGuild = GetDiscordInstance()->GetCurrentGuild(); - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + Guild* pGuild = pThis->GetCurrentGuild(); + Channel* pChan = pThis->GetCurrentChannel(); if (!pGuild || !pChan) break; POINT pt = { @@ -579,17 +592,25 @@ LRESULT CALLBACK GuildHeader::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA NotificationViewer::Show(pt.x, pt.y, true); break; } - case IDTB_MEMBERS: - SendMessage(g_Hwnd, WM_TOGGLEMEMBERS, 0, 0); + case IDTB_MEMBERS: { + SendMessage(GetParent(pThis->m_hwnd), WM_TOGGLEMEMBERS, 0, 0); break; - case IDTB_CHANNELS: - SendMessage(g_Hwnd, WM_TOGGLECHANNELS, 0, 0); - pThis->m_buttons[lParam].m_iconID = DMIC(g_bChannelListVisible ? IDI_SHIFT_LEFT : IDI_SHIFT_RIGHT); - pThis->m_buttons[lParam].m_placement = g_bChannelListVisible ? BUTTON_GUILD_RIGHT : BUTTON_GUILD_LEFT; + } + case IDTB_CHANNELS: { + SendMessage(GetParent(pThis->m_hwnd), WM_TOGGLECHANNELS, 0, 0); + bool channelListVisible = pThis->m_pParent->IsChannelListVisible(); + + pThis->m_buttons[lParam].m_iconID = DMIC(channelListVisible ? IDI_SHIFT_LEFT : IDI_SHIFT_RIGHT); + pThis->m_buttons[lParam].m_placement = channelListVisible ? BUTTON_GUILD_RIGHT : BUTTON_GUILD_LEFT; pThis->Layout(); InvalidateRect(hWnd, &pThis->m_rectLeftFull, FALSE); InvalidateRect(hWnd, &pThis->m_rectMidFull, FALSE); break; + } + case IDTB_FLOAT: { + GetMainWindow()->CreateGuildSubWindow(pThis->m_guildID, pThis->m_channelID); + break; + } } break; } @@ -611,9 +632,10 @@ void GuildHeader::InitializeClass() RegisterClass(&wc); } -GuildHeader* GuildHeader::Create(HWND hwnd, LPRECT pRect) +GuildHeader* GuildHeader::Create(ChatWindow* parent, LPRECT pRect) { GuildHeader* newThis = new GuildHeader; + newThis->m_pParent = parent; int width = pRect->right - pRect->left, height = pRect->bottom - pRect->top; @@ -624,7 +646,7 @@ GuildHeader* GuildHeader::Create(HWND hwnd, LPRECT pRect) WS_CHILD | WS_VISIBLE, pRect->left, pRect->top, width, height, - hwnd, + parent->GetHWND(), (HMENU)CID_GUILDHEADER, g_hInstance, newThis diff --git a/src/windows/GuildHeader.hpp b/src/windows/GuildHeader.hpp index 4cb0411..02770cc 100644 --- a/src/windows/GuildHeader.hpp +++ b/src/windows/GuildHeader.hpp @@ -11,12 +11,23 @@ class GuildHeader { public: HWND m_hwnd = NULL; + ChatWindow* m_pParent = nullptr; + Snowflake m_guildID = 0; + Snowflake m_channelID = 0; std::string GetGuildName();// = "Operating System Development"; std::string GetSubtitleText();// = "Boost Level 3 - 15 Boosts"; std::string GetChannelName();// = "#fictional-channel"; std::string GetChannelInfo();// = "This is an entirely fictional channel that I made up!"; + Guild* GetCurrentGuild(); + Channel* GetCurrentChannel(); + + Snowflake GetCurrentGuildID() { return m_guildID; } + Snowflake GetCurrentChannelID() { return m_channelID; } + void SetGuildID(Snowflake sf) { m_guildID = sf; } + void SetChannelID(Snowflake sf) { m_channelID = sf; } + public: GuildHeader(); ~GuildHeader(); @@ -30,7 +41,7 @@ class GuildHeader static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static void InitializeClass(); - static GuildHeader* Create(HWND hwnd, LPRECT pRect); + static GuildHeader* Create(ChatWindow* parent, LPRECT pRect); private: enum eButtonPlacement { diff --git a/src/windows/GuildLister.cpp b/src/windows/GuildLister.cpp index 1b3f3ec..bce5320 100644 --- a/src/windows/GuildLister.cpp +++ b/src/windows/GuildLister.cpp @@ -16,8 +16,6 @@ WNDCLASS GuildLister::g_GuildListerClass; WNDCLASS GuildLister::g_GuildListerParentClass; GuildLister::GuildLister() {} -extern int g_GuildListerWidth; - enum { GCMD_MORE, GCMD_BAR, @@ -51,7 +49,7 @@ void GuildLister::ProperlyResizeSubWindows() int buttonHeight = ScaleByDPI(24); bool wasScrollBarVisible = m_bIsScrollBarVisible; - m_bIsScrollBarVisible = rect.right - rect.left > g_GuildListerWidth; + m_bIsScrollBarVisible = rect.right - rect.left > m_pParent->GetGuildListerWidth(); if (wasScrollBarVisible != m_bIsScrollBarVisible) { if (wasScrollBarVisible) { @@ -192,7 +190,7 @@ void GuildLister::UpdateSelected() InvalidateRect(m_hwnd, &it->second, FALSE); } - m_selectedGuild = GetDiscordInstance()->GetCurrentGuildID(); + m_selectedGuild = m_pParent->GetCurrentGuildID(); it = m_iconRects.find(m_selectedGuild); if (it != m_iconRects.end()) { InvalidateRect(m_hwnd, &it->second, FALSE); @@ -299,7 +297,7 @@ void GuildLister::RestoreScrollInfo() void GuildLister::ShowGuildChooserMenu() { - DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_GUILD_CHOOSER)), g_Hwnd, &ChooserDlgProc); + DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_GUILD_CHOOSER)), GetParent(m_hwnd), &ChooserDlgProc); } void GuildLister::OnScroll() @@ -425,7 +423,7 @@ void GuildLister::AskLeave(Snowflake guild) TCHAR buff[4096]; WAsnprintf(buff, _countof(buff), TmGetTString(IDS_CONFIRM_LEAVE_GUILD), pGuild->m_name.c_str()); - if (MessageBox(g_Hwnd, buff, TmGetTString(IDS_PROGRAM_NAME), MB_YESNO | MB_ICONQUESTION) == IDYES) + if (MessageBox(GetParent(m_hwnd), buff, TmGetTString(IDS_PROGRAM_NAME), MB_YESNO | MB_ICONQUESTION) == IDYES) { // Leave Server GetDiscordInstance()->RequestLeaveGuild(guild); @@ -467,7 +465,7 @@ LRESULT CALLBACK GuildLister::ParentWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, GetLocalSettings()->SetShowScrollBarOnGuildList( !GetLocalSettings()->ShowScrollBarOnGuildList() ); - SendMessage(g_Hwnd, WM_REPOSITIONEVERYTHING, 0, 0); + SendMessage(GetParent(pThis->m_hwnd), WM_REPOSITIONEVERYTHING, 0, 0); break; } } @@ -481,13 +479,13 @@ void GuildLister::DrawServerIcon(HDC hdc, HBITMAP hicon, int& y, RECT& rect, Sno int height = 0; int pfpSize = GetProfilePictureSize(); int pfpBorderSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF + 12); - bool isCurrent = GetDiscordInstance()->GetCurrentGuildID() == id; + bool isCurrent = m_pParent->GetCurrentGuildID() == id; SetRectEmpty(&m_iconRects[id]); bool isFolderIcon = currentFolder && (id & ~BIT_FOLDER) == currentFolder; - m_selectedGuild = GetDiscordInstance()->GetCurrentGuildID(); + m_selectedGuild = m_pParent->GetCurrentGuildID(); if (hdc && hicon) { HICON hborder = NULL; @@ -1005,11 +1003,11 @@ LRESULT CALLBACK GuildLister::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA else if (GetDiscordInstance()->GetGuild(selected)) { Snowflake sf1 = 0; - if (GetDiscordInstance()->GetCurrentGuild()) - sf1 = GetDiscordInstance()->GetCurrentGuild()->m_snowflake; + if (GetMainWindow()->GetCurrentGuild()) + sf1 = GetMainWindow()->GetCurrentGuild()->m_snowflake; if (sf1 != selected) - GetDiscordInstance()->OnSelectGuild(selected); + GetMainWindow()->GetChatView()->OnSelectGuild(selected); } } @@ -1217,7 +1215,7 @@ INT_PTR GuildLister::ChooserDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM break; Snowflake guild = pData->m_guildIDs[index]; - GetDiscordInstance()->OnSelectGuild(guild); + GetMainWindow()->GetChatView()->OnSelectGuild(guild); EndDialog(hWnd, TRUE); break; } @@ -1248,7 +1246,7 @@ INT_PTR GuildLister::ChooserDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM break; Snowflake guild = pData->m_guildIDs[index]; - GetDiscordInstance()->OnSelectGuild(guild); + GetMainWindow()->GetChatView()->OnSelectGuild(guild); EndDialog(hWnd, TRUE); break; } @@ -1355,9 +1353,10 @@ void GuildLister::InitializeClass() RegisterClass(&wc); } -GuildLister* GuildLister::Create(HWND hwnd, LPRECT pRect) +GuildLister* GuildLister::Create(ChatWindow* parent, LPRECT pRect) { GuildLister* newThis = new GuildLister; + newThis->m_pParent = parent; int width = pRect->right - pRect->left, height = pRect->bottom - pRect->top; @@ -1370,7 +1369,7 @@ GuildLister* GuildLister::Create(HWND hwnd, LPRECT pRect) newThis->m_hwnd = CreateWindowEx( flagsex, T_GUILD_LISTER_PARENT_CLASS, NULL, WS_CHILD | WS_VISIBLE, - pRect->left, pRect->top, width, height, hwnd, (HMENU)CID_GUILDLISTER, g_hInstance, newThis + pRect->left, pRect->top, width, height, parent->GetHWND(), (HMENU)CID_GUILDLISTER, g_hInstance, newThis ); newThis->m_scrollable_hwnd = CreateWindowEx( diff --git a/src/windows/GuildLister.hpp b/src/windows/GuildLister.hpp index 0addccc..0139a62 100644 --- a/src/windows/GuildLister.hpp +++ b/src/windows/GuildLister.hpp @@ -22,6 +22,7 @@ class GuildLister HWND m_tooltip_hwnd = NULL; HWND m_more_btn_hwnd = NULL; HWND m_bar_btn_hwnd = NULL; + ChatWindow* m_pParent = nullptr; bool m_bIsScrollBarVisible = false; SCROLLINFO m_simulatedScrollInfo{}; @@ -69,6 +70,6 @@ class GuildLister static INT_PTR CALLBACK ChooserDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static void InitializeClass(); - static GuildLister* Create(HWND hwnd, LPRECT pRect); + static GuildLister* Create(ChatWindow* parent, LPRECT pRect); }; diff --git a/src/windows/GuildSubWindow.cpp b/src/windows/GuildSubWindow.cpp new file mode 100644 index 0000000..0a3831b --- /dev/null +++ b/src/windows/GuildSubWindow.cpp @@ -0,0 +1,659 @@ +#include "GuildSubWindow.hpp" +#include "Main.hpp" + +#include "MessageList.hpp" +#include "MemberList.hpp" +#include "IChannelView.hpp" +#include "MessageEditor.hpp" +#include "StatusBar.hpp" +#include "ProfilePopout.hpp" +#include "UploadDialog.hpp" + +#include "../discord/LocalSettings.hpp" + +#define GUILD_SUB_WINDOW_CLASS TEXT("DiscordMessengerGuildSubWindowClass") + +WNDCLASS GuildSubWindow::m_wndClass {}; +bool GuildSubWindow::InitializeClass() +{ + WNDCLASS& wc = m_wndClass; + + wc.lpfnWndProc = WndProc; + wc.hInstance = g_hInstance; + wc.lpszClassName = GUILD_SUB_WINDOW_CLASS; + wc.hbrBackground = g_backgroundBrush; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hIcon = g_Icon; + //wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); + + return RegisterClass(&wc) != 0; +} + +GuildSubWindow::GuildSubWindow(Snowflake guildID, Snowflake channelID) +{ + m_requestedGuildID = guildID; + m_requestedChannelID = channelID; + + int width = 800; + if (guildID == 0) { + width -= CHANNEL_VIEW_LIST_WIDTH + 10; + m_bChannelListVisible = false; + } + + m_hwnd = CreateWindow( + GUILD_SUB_WINDOW_CLASS, + TEXT("Discord Messenger (sub-window)"), + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + CW_USEDEFAULT, + ScaleByDPI(width), // TODO: Specify non-hardcoded size + ScaleByDPI(550), + NULL, + NULL, + g_hInstance, + this + ); + + if (!m_hwnd) { + m_bInitFailed = true; + return; + } + + ShowWindow(m_hwnd, SW_SHOWNORMAL); +} + +GuildSubWindow::~GuildSubWindow() +{ + DbgPrintW("GuildSubWindow destructor!"); +} + +void GuildSubWindow::Close() +{ + DestroyWindow(m_hwnd); +} + +void GuildSubWindow::ProperlySizeControls() +{ + RECT rect = {}, rect2 = {}, rcClient = {}, rcSBar = {}; + GetClientRect(m_hwnd, &rect); + int clientWidth = rect.right - rect.left; + rcClient = rect; + + int scaled10 = ScaleByDPI(10); + + rect.left += scaled10; + rect.top += scaled10; + rect.right -= scaled10; + rect.bottom -= scaled10; + + HWND hWndMsg = m_pMessageList->m_hwnd; + HWND hWndTin = m_pMessageEditor->m_hwnd; + HWND hWndStb = m_pStatusBar->m_hwnd; + HWND hWndChv = m_pChannelView ? m_pChannelView->m_hwnd : NULL; + + int statusBarHeight = 0; + GetChildRect(m_hwnd, hWndStb, &rcSBar); + statusBarHeight = rcSBar.bottom - rcSBar.top; + + rect.bottom -= statusBarHeight; + rect2 = rect; + + g_ProfilePictureSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF); + m_ChannelViewListWidth = ScaleByDPI(CHANNEL_VIEW_LIST_WIDTH); + m_BottomBarHeight = ScaleByDPI(BOTTOM_BAR_HEIGHT); + m_BottomInputHeight = ScaleByDPI(BOTTOM_INPUT_HEIGHT); + m_SendButtonWidth = ScaleByDPI(SEND_BUTTON_WIDTH); + m_SendButtonHeight = ScaleByDPI(SEND_BUTTON_HEIGHT); + m_GuildHeaderHeight = 0; //ScaleByDPI(GUILD_HEADER_HEIGHT); + m_MemberListWidth = ScaleByDPI(MEMBER_LIST_WIDTH); + m_MessageEditorHeight = ScaleByDPI(MESSAGE_EDITOR_HEIGHT); + + if (m_pMessageEditor) + m_pMessageEditor->SetJumpPresentHeight(m_BottomBarHeight - m_MessageEditorHeight - scaled10); + + int channelViewListWidth = m_ChannelViewListWidth; + m_ChannelViewListWidth2 = channelViewListWidth; + + // May need to do some adjustments. + const int minFullWidth = ScaleByDPI(700); + if (clientWidth < minFullWidth) + { + int widthOfAll3Things = + clientWidth + - scaled10 * 3 /* Left edge, Right edge, Between GuildLister and the group */ + - scaled10 * 2; /* Between channelview and messageview, between messageview and memberlist */ + + int widthOfAll3ThingsAt800px = ScaleByDPI(648); + + m_ChannelViewListWidth2 = MulDiv(m_ChannelViewListWidth2, widthOfAll3Things, widthOfAll3ThingsAt800px); + m_MemberListWidth = MulDiv(m_MemberListWidth, widthOfAll3Things, widthOfAll3ThingsAt800px); + channelViewListWidth = m_ChannelViewListWidth2; + } + + bool bRepaint = true; + + // Create a message list + rect.bottom -= m_MessageEditorHeight + m_pMessageEditor->ExpandedBy() + scaled10; + rect.top += m_GuildHeaderHeight; + if (m_bChannelListVisible) rect.left += channelViewListWidth + scaled10; + if (m_bMemberListVisible) rect.right -= m_MemberListWidth + scaled10; + MoveWindow(hWndMsg, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + + //rect.left = rect.right + scaled10; + //if (!m_bMemberListVisible) rect.left -= m_MemberListWidth; + //rect.right = rect.left + m_MemberListWidth; + //rect.bottom = rect2.bottom; + //MoveWindow(hWndMel, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + + if (hWndChv) + { + rect.left = scaled10; + rect.right = rect.left + channelViewListWidth; + rect.top += m_GuildHeaderHeight; + MoveWindow(hWndChv, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + } + + rect.left = scaled10; + rect.top = rect.bottom - m_MessageEditorHeight - m_pMessageEditor->ExpandedBy(); + if (m_bChannelListVisible) rect.left += channelViewListWidth + scaled10; + if (m_bMemberListVisible) rect.right -= m_MemberListWidth + scaled10; + int textInputHeight = rect.bottom - rect.top, textInputWidth = rect.right - rect.left; + MoveWindow(hWndTin, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + + // Forward the resize event to the status bar. + MoveWindow(hWndStb, 0, rcClient.bottom - statusBarHeight, rcClient.right - rcClient.left, statusBarHeight, TRUE); + // + // Erase the old rectangle + InvalidateRect(m_hwnd, &rcSBar, TRUE); + + m_pStatusBar->UpdateParts(rcClient.right - rcClient.left); +} + +LRESULT GuildSubWindow::HandleCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case IDA_PASTE: + { + ShowPasteFileDialog(m_pMessageEditor->m_edit_hwnd); + break; + } + } + + return 0; +} + +LRESULT GuildSubWindow::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_SHOWUPLOADDIALOG: + { + Snowflake* sf = (Snowflake*)lParam; + UploadDialogShow2(hWnd, *sf); + delete sf; + break; + } + case WM_CREATE: + { + m_hwnd = hWnd; + + m_bMemberListVisible = false; + m_bChannelListVisible = m_requestedGuildID != 0; + + RECT rect { 0, 0, 10, 10 }; + m_pMessageList = MessageList::Create(this, NULL, &rect); + m_pMessageEditor = MessageEditor::Create(this, &rect); + + if (m_bChannelListVisible) + m_pChannelView = IChannelView::CreateChannelView(this, &rect); + else + m_pChannelView = nullptr; + + m_pStatusBar = StatusBar::Create(this); + + if (!m_pMessageList || + !m_pMessageEditor) + { + DbgPrintW("ERROR: A control failed to be created!"); + m_bInitFailed = true; + break; + } + + if (m_pMessageEditor) + m_pMessageEditor->SetMessageList(m_pMessageList); + + SetCurrentGuildID(m_requestedGuildID); + SetCurrentChannelID(m_requestedChannelID); + + break; + } + case WM_CLOSE: + { + DestroyWindow(hWnd); + break; + } + case WM_COMMAND: + return HandleCommand(hWnd, uMsg, wParam, lParam); + + case WM_DESTROY: + { + SAFE_DELETE(m_pChannelView); + SAFE_DELETE(m_pMessageList); + SAFE_DELETE(m_pMessageEditor); + SAFE_DELETE(m_pStatusBar); + break; + } + case WM_UPDATEEMOJI: + { + Snowflake sf = *(Snowflake*)lParam; + m_pMessageList->OnUpdateEmoji(sf); + break; + } + case WM_UPDATEUSER: + { + Snowflake sf = *(Snowflake*)lParam; + m_pMessageList->OnUpdateAvatar(sf); + + if (m_pChannelView) + m_pChannelView->OnUpdateAvatar(sf); + break; + } + case WM_UPDATEMESSAGELENGTH: + { + m_pStatusBar->UpdateCharacterCounter(int(lParam), MAX_MESSAGE_SIZE); + break; + } + case WM_UPDATESELECTEDGUILD: + { + SetCurrentGuildID(GetCurrentGuildID()); + break; + } + case WM_UPDATESELECTEDCHANNEL: + { + SetCurrentChannelID(GetCurrentChannelID()); + break; + } + case WM_UPDATEMEMBERLIST: + { + //m_pMemberList->SetGuild(GetCurrentGuildID()); + //m_pMemberList->Update(); + break; + } + case WM_UPDATEATTACHMENT: + { + ImagePlace pl = *(ImagePlace*)lParam; + m_pMessageList->OnUpdateEmbed(pl.key); + break; + } + case WM_UPDATECHANACKS: + { + Snowflake* sfs = (Snowflake*)lParam; + + Snowflake channelID = sfs[0]; + Snowflake messageID = sfs[1]; + + Channel* pChan = GetDiscordInstance()->GetChannelGlobally(channelID); + + // Update the icon for the specific guild + //m_pGuildLister->RedrawIconForGuild(pChan->m_parentGuild); + + // Get the channel as is in the current guild; if found, + // update the channel view's ack status + Guild* pGuild = GetDiscordInstance()->GetGuild(GetCurrentGuildID()); + if (!pGuild) break; + + pChan = pGuild->GetChannel(channelID); + if (!pChan) + break; + + if (m_pChannelView) + m_pChannelView->UpdateAcknowledgement(channelID); + + if (m_pMessageList->GetCurrentChannelID() == channelID) + m_pMessageList->SetLastViewedMessage(messageID, true); + break; + } + case WM_ADDMESSAGE: + { + AddMessageParams* pParms = (AddMessageParams*)lParam; + + if (m_pMessageList->GetCurrentChannelID() == pParms->channel) + m_pMessageList->AddMessage(pParms->msg.m_snowflake, GetForegroundWindow() == hWnd); + + OnStopTyping(pParms->channel, pParms->msg.m_author_snowflake); + break; + } + case WM_UPDATEMESSAGE: + { + AddMessageParams* pParms = (AddMessageParams*)lParam; + + if (m_pMessageList->GetCurrentChannelID() == pParms->channel) + m_pMessageList->EditMessage(pParms->msg.m_snowflake); + + break; + } + case WM_DELETEMESSAGE: + { + Snowflake sf = *(Snowflake*)lParam; + m_pMessageList->DeleteMessage(sf); + + if (sf == m_pMessageEditor->ReplyingTo()) + m_pMessageEditor->StopReply(); + + break; + } + case WM_UPDATECHANLIST: + { + // repaint the channel view + if (m_pChannelView) + m_pChannelView->ClearChannels(); + + // get the current guild + Guild* pGuild = GetDiscordInstance()->GetGuild(GetCurrentGuildID()); + if (!pGuild) break; + + if (m_pChannelView) + { + m_pChannelView->SetMode(false); + + // if the channels haven't been fetched yet + if (!pGuild->m_bChannelsLoaded) + { + pGuild->RequestFetchChannels(); + } + else + { + for (auto& ch : pGuild->m_channels) + m_pChannelView->AddChannel(ch); + for (auto& ch : pGuild->m_channels) + m_pChannelView->RemoveCategoryIfNeeded(ch); + + m_pChannelView->CommitChannels(); + } + } + + break; + } + case WM_SIZE: + { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + + RECT r{ 0, 0, width, height }; + AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE); + + // Save the new size + GetLocalSettings()->SetWindowSize(r.right - r.left, r.bottom - r.top); + GetLocalSettings()->SetMaximized(wParam == SIZE_MAXIMIZED); + + ProfilePopout::Dismiss(); + ProperlySizeControls(); + break; + } + case WM_REFRESHMESSAGES: + { + RefreshMessagesParams* params = (RefreshMessagesParams*)lParam; + + if (m_pMessageList->GetCurrentChannelID() == params->m_channelId) + m_pMessageList->RefetchMessages(params->m_gapCulprit, true); + + break; + } + case WM_REPOSITIONEVERYTHING: + { + ProperlySizeControls(); + break; + } + case WM_DRAWITEM: + { + switch (wParam) + { + case CID_STATUSBAR: + m_pStatusBar->DrawItem((LPDRAWITEMSTRUCT)lParam); + break; + } + break; + } + case WM_STARTTYPING: + { + TypingParams params = *((TypingParams*)lParam); + OnTyping(params.m_guild, params.m_channel, params.m_user, params.m_timestamp); + break; + } + case WM_SENDMESSAGE: + { + SendMessageParams parms = *((SendMessageParams*)lParam); + + std::string msg = MakeStringFromEditData(parms.m_rawMessage); + + if (msg.empty()) + return 1; + + // send it! + if (msg.size() >= MAX_MESSAGE_SIZE) + { + MessageBox( + hWnd, + TmGetTString(IDS_MESSAGE_TOO_LONG_MSG), + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONINFORMATION | MB_OK + ); + return 1; + } + + Snowflake sf; + if (parms.m_bEdit) + return GetDiscordInstance()->EditMessage(parms.m_channel, msg, parms.m_replyTo) ? 0 : 1; + + if (!GetDiscordInstance()->SendAMessage(parms.m_channel, msg, sf, parms.m_replyTo, parms.m_bMention)) + return 1; + + SendMessageAuxParams smap; + smap.m_message = msg; + smap.m_snowflake = sf; + SendMessage(hWnd, WM_SENDMESSAGEAUX, 0, (LPARAM)&smap); + return 0; + } + case WM_SENDMESSAGEAUX: + { + SendMessageAuxParams* psmap = (SendMessageAuxParams*) lParam; + + time_t lastTime = m_pMessageList->GetLastSentMessageTime(); + Profile* pf = GetDiscordInstance()->GetProfile(); + MessagePtr m = MakeMessage(); + m->m_snowflake = psmap->m_snowflake; + m->m_author_snowflake = pf->m_snowflake; + m->m_author = pf->m_name; + m->m_avatar = pf->m_avatarlnk; + m->m_message = psmap->m_message; + m->m_type = MessageType::SENDING_MESSAGE; + m->SetTime(time(NULL)); + m->m_dateFull = "Sending..."; + m->m_dateCompact = "Sending..."; + m_pMessageList->AddMessage(m, true); + return 0; + } + case WM_STARTREPLY: + { + Snowflake* sf = (Snowflake*)lParam; + // sf[0] - Message ID + // sf[1] - Author ID + m_pMessageEditor->StartReply(sf[0], sf[1]); + + break; + } + case WM_STARTEDITING: + { + Snowflake* sf = (Snowflake*)lParam; + m_pMessageEditor->StartEdit(*sf); + break; + } + } + + return 0; +} + +LRESULT GuildSubWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + GuildSubWindow* pThis = (GuildSubWindow*) GetWindowLongPtr(hWnd, GWLP_USERDATA); + if (!pThis && uMsg != WM_NCCREATE) + return DefWindowProc(hWnd, uMsg, wParam, lParam); + + if (uMsg == WM_NCCREATE) + { + CREATESTRUCT* strct = (CREATESTRUCT*) lParam; + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)strct->lpCreateParams); + } + + if (pThis) + { + LRESULT lres = pThis->WindowProc(hWnd, uMsg, wParam, lParam); + if (lres != 0) + return lres; + } + + if (uMsg == WM_DESTROY) + { + GetMainWindow()->OnClosedWindow(pThis); + SetWindowLongPtr(hWnd, GWLP_USERDATA, 0); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +void GuildSubWindow::UpdateWindowTitle() +{ + std::string windowTitle; + + Channel* pChannel = GetCurrentChannel(); + if (pChannel) + windowTitle += pChannel->GetTypeSymbol() + pChannel->m_name + " - "; + + Guild* pGuild = GetCurrentGuild(); + if (pGuild && pGuild->m_snowflake != 0) + windowTitle += pGuild->m_name + " - "; + + windowTitle += TmGetString(IDS_PROGRAM_NAME); + + LPTSTR tstr = ConvertCppStringToTString(windowTitle); + SetWindowText(m_hwnd, tstr); + free(tstr); +} + +void GuildSubWindow::OnTyping(Snowflake guildID, Snowflake channelID, Snowflake userID, time_t timeStamp) +{ + if (channelID == GetCurrentChannelID()) + { + if (userID == GetDiscordInstance()->GetUserID()) { + // Ignore typing start from ourselves for the lower bar + return; + } + + TypingInfo& ti = GetMainWindow()->GetTypingInfo(channelID); + TypingUser& tu = ti.m_typingUsers[userID]; + + m_pStatusBar->AddTypingName(userID, timeStamp, tu.m_name); + } +} + +void GuildSubWindow::OnStopTyping(Snowflake channelID, Snowflake userID) +{ + if (channelID == GetCurrentChannelID()) + m_pStatusBar->RemoveTypingName(userID); +} + +void GuildSubWindow::SetCurrentGuildID(Snowflake sf) +{ + if (m_lastGuildID == sf) + return; + + m_lastGuildID = sf; + + ChatWindow::SetCurrentGuildID(sf); + + // repaint the guild lister + //m_pGuildLister->UpdateSelected(); + //m_pGuildHeader->Update(); + + SendMessage(m_hwnd, WM_UPDATECHANLIST, 0, 0); + SendMessage(m_hwnd, WM_UPDATEMEMBERLIST, 0, 0); +} + +void GuildSubWindow::SetCurrentChannelID(Snowflake channID) +{ + if (m_lastChannelID == channID) + return; + + m_lastChannelID = channID; + + Channel* pChan = GetDiscordInstance()->GetChannelGlobally(channID); + if (!pChan) + return; + + Guild* pGuild = GetDiscordInstance()->GetGuild(pChan->m_parentGuild); + if (!pGuild) + return; + + Snowflake guildID = pGuild->m_snowflake; + SetCurrentGuildID(guildID); + + ChatWindow::SetCurrentChannelID(channID); + + m_pMessageEditor->StopReply(); + m_pMessageEditor->StopEdit(); + m_pMessageEditor->StopBrowsingPast(); + m_pMessageEditor->Layout(); + + if (m_pChannelView) + m_pChannelView->OnUpdateSelectedChannel(channID); + + // repaint the message view + m_pMessageList->ClearMessages(); + m_pMessageList->SetGuild(guildID); + m_pMessageList->SetChannel(channID); + + m_pMessageList->UpdateAllowDrop(); + + m_pMessageEditor->SetGuildID(guildID); + m_pMessageEditor->SetChannelID(channID); + + //m_pGuildHeader->SetGuildID(guildID); + //m_pGuildHeader->SetChannelID(channID); + + UpdateWindowTitle(); + + if (IsWindowActive(m_hwnd)) + SetFocus(m_pMessageEditor->m_edit_hwnd); + + if (!m_pMessageList->GetCurrentChannel()) + { + InvalidateRect(m_pMessageList->m_hwnd, NULL, TRUE); + //InvalidateRect(m_pGuildHeader->m_hwnd, NULL, FALSE); + return; + } + + GetDiscordInstance()->HandledChannelSwitch(); + + m_pMessageList->RefetchMessages(m_pMessageList->GetMessageSentTo()); + + InvalidateRect(m_pMessageList->m_hwnd, NULL, MessageList::MayErase()); + //m_pGuildHeader->Update(); + m_pMessageEditor->UpdateTextBox(); + + // Update the message editor's typing indicators + m_pStatusBar->SetChannelID(channID); + m_pStatusBar->ClearTypers(); + + Snowflake myUserID = GetDiscordInstance()->GetUserID(); + TypingInfo& ti = GetMainWindow()->GetTypingInfo(channID); + for (auto& tu : ti.m_typingUsers) { + if (tu.second.m_key == myUserID) + continue; + + m_pStatusBar->AddTypingName(tu.second.m_key, tu.second.m_startTimeMS / 1000ULL, tu.second.m_name); + } +} diff --git a/src/windows/GuildSubWindow.hpp b/src/windows/GuildSubWindow.hpp new file mode 100644 index 0000000..7adfcba --- /dev/null +++ b/src/windows/GuildSubWindow.hpp @@ -0,0 +1,75 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include +#include "WinUtils.hpp" +#include "ChatWindow.hpp" + +class MessageList; +class MessageEditor; +class IMemberList; +class IChannelView; +class StatusBar; + +extern HINSTANCE g_hInstance; + +class GuildSubWindow : public ChatWindow +{ +public: + static bool InitializeClass(); + +public: + GuildSubWindow(Snowflake guildID, Snowflake channelID); + ~GuildSubWindow(); + bool InitFailed() const { return m_bInitFailed; } + + HWND GetHWND() const override { return m_hwnd; } + void SetCurrentGuildID(Snowflake sf) override; + void SetCurrentChannelID(Snowflake sf) override; + + void Close() override; + void ProperlySizeControls(); + void UpdateWindowTitle(); + + void OnTyping(Snowflake guildID, Snowflake channelID, Snowflake userID, time_t timeStamp) override; + void OnStopTyping(Snowflake channelID, Snowflake userID) override; + +private: + LRESULT HandleCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +private: + static WNDCLASS m_wndClass; + +private: + HWND m_hwnd; + bool m_bInitFailed = false; + + MessageList* m_pMessageList = nullptr; + MessageEditor* m_pMessageEditor = nullptr; + IChannelView* m_pChannelView = nullptr; + StatusBar* m_pStatusBar = nullptr; + + bool m_bMemberListVisible = false; + bool m_bChannelListVisible = false; + + Snowflake m_lastGuildID = 0; + Snowflake m_lastChannelID = 0; + Snowflake m_requestedGuildID = 0; + Snowflake m_requestedChannelID = 0; + +protected: + friend class StatusBar; + + // Proportions + int m_ChannelViewListWidth = 0; + int m_ChannelViewListWidth2 = 0; + int m_BottomBarHeight = 0; + int m_BottomInputHeight = 0; + int m_SendButtonWidth = 0; + int m_SendButtonHeight = 0; + int m_GuildHeaderHeight = 0; + int m_MemberListWidth = 0; + int m_MessageEditorHeight = 0; +}; diff --git a/src/windows/IChannelView.cpp b/src/windows/IChannelView.cpp index 6fd0b45..7ebe8db 100644 --- a/src/windows/IChannelView.cpp +++ b/src/windows/IChannelView.cpp @@ -2,12 +2,12 @@ #include "ChannelView.hpp" #include "ChannelViewOld.hpp" -IChannelView* IChannelView::CreateChannelView(HWND hwnd, LPRECT rect) +IChannelView* IChannelView::CreateChannelView(ChatWindow* parent, LPRECT rect) { - IChannelView* pChView = ChannelView::Create(hwnd, rect); + IChannelView* pChView = ChannelView::Create(parent, rect); if (!pChView) - pChView = ChannelViewOld::Create(hwnd, rect); + pChView = ChannelViewOld::Create(parent, rect); return pChView; } diff --git a/src/windows/IChannelView.hpp b/src/windows/IChannelView.hpp index cbf1ea4..c2b1d44 100644 --- a/src/windows/IChannelView.hpp +++ b/src/windows/IChannelView.hpp @@ -8,7 +8,7 @@ class IChannelView HWND m_hwnd = NULL; public: - static IChannelView* CreateChannelView(HWND hwnd, LPRECT rect); + static IChannelView* CreateChannelView(ChatWindow* parent, LPRECT rect); static void InitializeClasses(); public: diff --git a/src/windows/IMemberList.cpp b/src/windows/IMemberList.cpp index ca07af6..053b2f1 100644 --- a/src/windows/IMemberList.cpp +++ b/src/windows/IMemberList.cpp @@ -2,12 +2,12 @@ #include "MemberList.hpp" #include "MemberListOld.hpp" -IMemberList* IMemberList::CreateMemberList(HWND hwnd, LPRECT lprect) +IMemberList* IMemberList::CreateMemberList(ChatWindow* parent, LPRECT lprect) { - IMemberList* pMList = MemberList::Create(hwnd, lprect); + IMemberList* pMList = MemberList::Create(parent, lprect); if (!pMList) - pMList = MemberListOld::Create(hwnd, lprect); + pMList = MemberListOld::Create(parent, lprect); return pMList; } diff --git a/src/windows/IMemberList.hpp b/src/windows/IMemberList.hpp index 20058ae..d20625c 100644 --- a/src/windows/IMemberList.hpp +++ b/src/windows/IMemberList.hpp @@ -8,7 +8,7 @@ class IMemberList HWND m_mainHwnd; public: - static IMemberList* CreateMemberList(HWND hwnd, LPRECT lprect); + static IMemberList* CreateMemberList(ChatWindow* parent, LPRECT lprect); static void InitializeClasses(); virtual void SetGuild(Snowflake sf) = 0; diff --git a/src/windows/ImageLoader.cpp b/src/windows/ImageLoader.cpp index e3a15ba..2caef86 100644 --- a/src/windows/ImageLoader.cpp +++ b/src/windows/ImageLoader.cpp @@ -170,7 +170,7 @@ HImage* ImageLoader::ConvertToBitmap(const uint8_t* pData, size_t size, bool& ou hdr.biCompression = BI_RGB; hdr.biSize = sizeof(BITMAPINFOHEADER); - HDC wndHdc = GetDC(g_Hwnd); + HDC wndHdc = GetDC(GetMainHWND()); HDC hdc = CreateCompatibleDC(wndHdc); for (size_t i = 0; i < loadedImageCount; i++) @@ -390,7 +390,7 @@ HImage* ImageLoader::ConvertToBitmap(const uint8_t* pData, size_t size, bool& ou } DeleteDC(hdc); - ReleaseDC(g_Hwnd, wndHdc); + ReleaseDC(GetMainHWND(), wndHdc); return himg; } diff --git a/src/windows/ImageViewer.cpp b/src/windows/ImageViewer.cpp index eac5835..255bd5a 100644 --- a/src/windows/ImageViewer.cpp +++ b/src/windows/ImageViewer.cpp @@ -524,7 +524,7 @@ void CreateImageViewer(const std::string& proxyURL, const std::string& url, cons int winHeight = rc.bottom - rc.top + EDGE_SIZE; RECT parentRect = { 0, 0, 0, 0 }; - GetWindowRect(g_Hwnd, &parentRect); + GetWindowRect(GetMainHWND(), &parentRect); int scaled5 = ScaleByDPI(5); int scaled10 = ScaleByDPI(10); diff --git a/src/windows/LoadingMessage.cpp b/src/windows/LoadingMessage.cpp index d8c0142..305b359 100644 --- a/src/windows/LoadingMessage.cpp +++ b/src/windows/LoadingMessage.cpp @@ -34,7 +34,7 @@ void LoadingMessage::CreateWnd() rcLoading.left, rcLoading.top, width, height, - g_Hwnd, + GetMainHWND(), NULL, g_hInstance, this diff --git a/src/windows/LogonDialog.cpp b/src/windows/LogonDialog.cpp index 69480a7..8931a03 100644 --- a/src/windows/LogonDialog.cpp +++ b/src/windows/LogonDialog.cpp @@ -72,5 +72,5 @@ static INT_PTR CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l bool LogonDialogShow() { - return DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_LOGON)), g_Hwnd, DialogProc) != IDCANCEL; + return DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_LOGON)), GetMainHWND(), DialogProc) != IDCANCEL; } diff --git a/src/windows/Main.cpp b/src/windows/Main.cpp index 0eda72e..9633d4a 100644 --- a/src/windows/Main.cpp +++ b/src/windows/Main.cpp @@ -31,32 +31,13 @@ #include "../discord/WebsocketClient.hpp" #include "../discord/UpdateChecker.hpp" +#include "MainWindow.hpp" + #include #include // proportions: int g_ProfilePictureSize; -int g_ChannelViewListWidth; -int g_BottomBarHeight; -int g_BottomInputHeight; -int g_SendButtonWidth; -int g_SendButtonHeight; -int g_GuildHeaderHeight; -int g_GuildListerWidth; -int g_MemberListWidth; -int g_MessageEditorHeight; -int g_GuildListerWidth2; -int g_ChannelViewListWidth2; - -StatusBar * g_pStatusBar; -MessageList* g_pMessageList; -ProfileView* g_pProfileView; -GuildHeader* g_pGuildHeader; -GuildLister* g_pGuildLister; -IMemberList* g_pMemberList; -IChannelView* g_pChannelView; -MessageEditor* g_pMessageEditor; -LoadingMessage* g_pLoadingMessage; int GetProfilePictureSize() { @@ -69,13 +50,13 @@ void FindBasePath() pwStr[0] = 0; LPCTSTR p1 = NULL, p2 = NULL; - if (SUCCEEDED(ri::SHGetFolderPath(g_Hwnd, CSIDL_APPDATA, NULL, 0, pwStr))) + if (SUCCEEDED(ri::SHGetFolderPath(GetMainHWND(), CSIDL_APPDATA, NULL, 0, pwStr))) { SetBasePath(MakeStringFromTString(pwStr)); } else { - MessageBox(g_Hwnd, TmGetTString(IDS_NO_APPDATA), TmGetTString(IDS_NON_CRITICAL_ERROR), MB_OK | MB_ICONERROR); + MessageBox(GetMainHWND(), TmGetTString(IDS_NO_APPDATA), TmGetTString(IDS_NON_CRITICAL_ERROR), MB_OK | MB_ICONERROR); SetBasePath("."); } } @@ -114,26 +95,13 @@ void SetupCachePathIfNeeded() if (TryThisBasePath()) return; - MessageBox(g_Hwnd, TmGetTString(IDS_NO_CACHE_DIR), TmGetTString(IDS_NON_CRITICAL_ERROR), MB_OK | MB_ICONERROR); + MessageBox(GetMainHWND(), TmGetTString(IDS_NO_CACHE_DIR), TmGetTString(IDS_NON_CRITICAL_ERROR), MB_OK | MB_ICONERROR); SetBasePath("."); } -DiscordInstance* g_pDiscordInstance; - -DiscordInstance* GetDiscordInstance() -{ - return g_pDiscordInstance; -} - -std::string GetDiscordToken() -{ - return g_pDiscordInstance->GetToken(); -} - HINSTANCE g_hInstance; WNDCLASS g_MainWindowClass; HBRUSH g_backgroundBrush; -HWND g_Hwnd; HICON g_Icon; HICON g_BotIcon; HICON g_SendIcon; @@ -165,8 +133,6 @@ HICON g_folderOpenIcon; HBITMAP g_DefaultProfilePicture; HImage g_defaultImage; -UINT_PTR g_HeartbeatTimer; -int g_HeartbeatTimerInterval; HBITMAP GetDefaultBitmap() { return g_DefaultProfilePicture; @@ -180,514 +146,12 @@ void WantQuit() GetHTTPClient()->PrepareQuit(); } -void OnUpdateAvatar(const std::string& resid) -{ - // depending on where this is, update said control - ImagePlace ip = GetAvatarCache()->GetPlace(resid); - - switch (ip.type) - { - case eImagePlace::AVATARS: - { - if (GetDiscordInstance()->GetUserID() == ip.sf) - SendMessage(g_Hwnd, WM_REPAINTPROFILE, 0, 0); - - if (ProfilePopout::GetUser() == ip.sf) - SendMessage(g_Hwnd, WM_UPDATEPROFILEPOPOUT, 0, 0); - - Snowflake sf = ip.sf; - SendMessage(g_Hwnd, WM_UPDATEUSER, 0, (LPARAM) &sf); - break; - } - case eImagePlace::ICONS: - { - SendMessage(g_Hwnd, WM_REPAINTGUILDLIST, 0, (LPARAM)&ip.sf); - break; - } - case eImagePlace::ATTACHMENTS: - { - SendMessage(g_Hwnd, WM_UPDATEATTACHMENT, 0, (LPARAM) &ip); - break; - } - case eImagePlace::EMOJIS: - { - SendMessage(g_Hwnd, WM_UPDATEEMOJI, 0, (LPARAM) &ip.sf); - break; - } - } -} - -bool g_bMemberListVisible = false; -bool g_bChannelListVisible = false; - -void ProperlySizeControls(HWND hWnd) -{ - RECT rect = {}, rect2 = {}, rcClient = {}, rcSBar = {}; - GetClientRect(hWnd, &rect); - int clientWidth = rect.right - rect.left; - rcClient = rect; - - int scaled10 = ScaleByDPI(10); - - rect.left += scaled10; - rect.top += scaled10; - rect.right -= scaled10; - rect.bottom -= scaled10; - - HWND hWndMsg = g_pMessageList->m_hwnd; - HWND hWndChv = g_pChannelView->m_hwnd; - HWND hWndPfv = g_pProfileView->m_hwnd; - HWND hWndGuh = g_pGuildHeader->m_hwnd; - HWND hWndGul = g_pGuildLister->m_hwnd; - HWND hWndMel = g_pMemberList->m_mainHwnd; - HWND hWndTin = g_pMessageEditor->m_hwnd; - HWND hWndStb = g_pStatusBar->m_hwnd; - - int statusBarHeight = 0; - GetChildRect(hWnd, hWndStb, &rcSBar); - statusBarHeight = rcSBar.bottom - rcSBar.top; - - rect.bottom -= statusBarHeight; - rect2 = rect; - - g_ProfilePictureSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF); - g_ChannelViewListWidth = ScaleByDPI(CHANNEL_VIEW_LIST_WIDTH); - g_BottomBarHeight = ScaleByDPI(BOTTOM_BAR_HEIGHT); - g_BottomInputHeight = ScaleByDPI(BOTTOM_INPUT_HEIGHT); - g_SendButtonWidth = ScaleByDPI(SEND_BUTTON_WIDTH); - g_SendButtonHeight = ScaleByDPI(SEND_BUTTON_HEIGHT); - g_GuildHeaderHeight = ScaleByDPI(GUILD_HEADER_HEIGHT); - g_GuildListerWidth = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF + 12) + GUILD_LISTER_BORDER_SIZE * 4; - g_MemberListWidth = ScaleByDPI(MEMBER_LIST_WIDTH); - g_MessageEditorHeight = ScaleByDPI(MESSAGE_EDITOR_HEIGHT); - - if (g_pMessageEditor) - g_pMessageEditor->SetJumpPresentHeight(g_BottomBarHeight - g_MessageEditorHeight - scaled10); - - int guildListerWidth = g_GuildListerWidth; - int channelViewListWidth = g_ChannelViewListWidth; - if (GetLocalSettings()->ShowScrollBarOnGuildList()) { - int offset = GetSystemMetrics(SM_CXVSCROLL); - guildListerWidth += offset; - channelViewListWidth -= offset; - } - - bool bRepaintGuildHeader = g_GuildListerWidth2 != guildListerWidth; - g_GuildListerWidth2 = guildListerWidth; - g_ChannelViewListWidth2 = channelViewListWidth; - - // May need to do some adjustments. - bool bFullRepaintProfileView = false; - const int minFullWidth = ScaleByDPI(800); - if (clientWidth < minFullWidth) - { - bFullRepaintProfileView = true; - - int widthOfAll3Things = - clientWidth - - scaled10 * 3 /* Left edge, Right edge, Between GuildLister and the group */ - - scaled10 * 2 /* Between channelview and messageview, between messageview and memberlist */ - - g_GuildListerWidth2; /* The guild list itself. So just the channelview, messageview and memberlist summed */ - - int widthOfAll3ThingsAt800px = ScaleByDPI(694); - - g_ChannelViewListWidth2 = MulDiv(g_ChannelViewListWidth2, widthOfAll3Things, widthOfAll3ThingsAt800px); - g_MemberListWidth = MulDiv(g_MemberListWidth, widthOfAll3Things, widthOfAll3ThingsAt800px); - channelViewListWidth = g_ChannelViewListWidth2; - } - - bool bRepaint = true; - - // Create a message list - rect.left += guildListerWidth + scaled10; - rect.bottom -= g_MessageEditorHeight + g_pMessageEditor->ExpandedBy() + scaled10; - rect.top += g_GuildHeaderHeight; - if (g_bChannelListVisible) rect.left += channelViewListWidth + scaled10; - if (g_bMemberListVisible) rect.right -= g_MemberListWidth + scaled10; - MoveWindow(hWndMsg, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); - - rect.left = rect.right + scaled10; - if (!g_bMemberListVisible) rect.left -= g_MemberListWidth; - rect.right = rect.left + g_MemberListWidth; - rect.bottom = rect2.bottom; - MoveWindow(hWndMel, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); - rect = rect2; - - rect.left += guildListerWidth + scaled10; - rect.right = rect.left + channelViewListWidth; - rect.bottom -= g_BottomBarHeight; - rect.top += g_GuildHeaderHeight; - MoveWindow(hWndChv, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); - rect = rect2; - - rect.left += guildListerWidth + scaled10; - rect.top = rect.bottom - g_BottomBarHeight + scaled10; - rect.right = rect.left + channelViewListWidth; - MoveWindow(hWndPfv, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); - rect = rect2; - - rect.left = scaled10 + guildListerWidth + scaled10; - rect.top = rect.bottom - g_MessageEditorHeight - g_pMessageEditor->ExpandedBy(); - if (g_bChannelListVisible) rect.left += channelViewListWidth + scaled10; - if (g_bMemberListVisible) rect.right -= g_MemberListWidth + scaled10; - int textInputHeight = rect.bottom - rect.top, textInputWidth = rect.right - rect.left; - MoveWindow(hWndTin, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); - rect = rect2; - - rect.left += guildListerWidth + scaled10; - rect.bottom = rect.top + g_GuildHeaderHeight - scaled10; - MoveWindow(hWndGuh, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaintGuildHeader); - rect = rect2; - - rect.right = rect.left + guildListerWidth; - MoveWindow(hWndGul, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); - rect = rect2; - - // Forward the resize event to the status bar. - MoveWindow(hWndStb, 0, rcClient.bottom - statusBarHeight, rcClient.right - rcClient.left, statusBarHeight, TRUE); - // Erase the old rectangle - InvalidateRect(hWnd, &rcSBar, TRUE); - - if (bFullRepaintProfileView) - InvalidateRect(hWndPfv, NULL, FALSE); - - g_pStatusBar->UpdateParts(rcClient.right - rcClient.left); -} - bool g_bQuittingEarly = false; -UINT_PTR g_deferredRestartSessionTimer = 0; -void CALLBACK DeferredRestartSession(HWND hWnd, UINT uInt, UINT_PTR uIntPtr, DWORD dWord) -{ - KillTimer(hWnd, g_deferredRestartSessionTimer); - g_deferredRestartSessionTimer = 0; - - if (GetDiscordInstance()->GetGatewayID() < 0) - GetDiscordInstance()->StartGatewaySession(); -} INT_PTR CALLBACK DDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return 0L; } -struct TypingInfo { - std::map m_typingUsers; -}; - -std::map g_typingInfo; - -void OnTyping(Snowflake guildID, Snowflake channelID, Snowflake userID, time_t timeStamp) -{ - TypingInfo& ti = g_typingInfo[channelID]; - - TypingUser tu; - tu.m_startTimeMS = timeStamp * 1000LL; - tu.m_key = userID; - tu.m_name = GetProfileCache()->LookupProfile(userID, "", "", "", false)->GetName(guildID); - ti.m_typingUsers[userID] = tu; - - // Send an update to the message editor - if (channelID == GetDiscordInstance()->GetCurrentChannelID()) - { - if (userID == GetDiscordInstance()->GetUserID()) { - // Ignore typing start from ourselves for the lower bar - return; - } - - g_pStatusBar->AddTypingName(userID, timeStamp, tu.m_name); - } -} - -void OnStopTyping(Snowflake channelID, Snowflake userID) -{ - g_typingInfo[channelID].m_typingUsers[userID].m_startTimeMS = 0; - - if (channelID == GetDiscordInstance()->GetCurrentChannelID()) - g_pStatusBar->RemoveTypingName(userID); -} - -void UpdateMainWindowTitle(HWND hWnd) -{ - std::string windowTitle; - - Channel* pChannel = GetDiscordInstance()->GetCurrentChannel(); - if (pChannel) - windowTitle += pChannel->GetTypeSymbol() + pChannel->m_name + " - "; - - windowTitle += TmGetString(IDS_PROGRAM_NAME); - - LPTSTR tstr = ConvertCppStringToTString(windowTitle); - SetWindowText(hWnd, tstr); - free(tstr); -} - -int OnHTTPError(const std::string& url, const std::string& reasonString, bool isSSL) -{ - LPTSTR urlt = ConvertCppStringToTString(url); - LPTSTR rstt = ConvertCppStringToTString(reasonString); - LPCTSTR title; - static TCHAR buffer[8192]; - - if (isSSL) { - title = TmGetTString(IDS_SSL_ERROR_TITLE); - _tcscpy(buffer, TmGetTString(IDS_SSL_ERROR_1)); - _tcscat(buffer, urlt); - _tcscat(buffer, TEXT("\n\n")); - _tcscat(buffer, rstt); - _tcscat(buffer, TEXT("\n\n")); - _tcscat(buffer, TmGetTString(IDS_SSL_ERROR_2)); - _tcscat(buffer, TEXT("\n\n")); - _tcscat(buffer, TmGetTString(IDS_SSL_ERROR_3)); - } - else { - title = TmGetTString(IDS_CONNECT_ERROR_TITLE); - _tcscpy(buffer, TmGetTString(IDS_CONNECT_ERROR_1)); - _tcscat(buffer, urlt); - _tcscat(buffer, TEXT("\n\n")); - _tcscat(buffer, rstt); - _tcscat(buffer, TEXT("\n\n")); - _tcscat(buffer, TmGetTString(IDS_CONNECT_ERROR_2)); - _tcscat(buffer, TEXT("\n\n")); - _tcscat(buffer, TmGetTString(IDS_CONNECT_ERROR_3)); - } - - free(urlt); - free(rstt); - - size_t l = _tcslen(buffer); - return MessageBox(g_Hwnd, buffer, title, (isSSL ? MB_ABORTRETRYIGNORE : MB_RETRYCANCEL) | MB_ICONWARNING); -} - -void CloseCleanup(HWND hWnd) -{ - KillImageViewer(); - ProfilePopout::Dismiss(); - AutoComplete::DismissAutoCompleteWindowsIfNeeded(hWnd); - g_pLoadingMessage->Hide(); -} - -LRESULT HandleCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (LOWORD(wParam)) - { - case ID_FILE_PREFERENCES: - { - switch (ShowOptionsDialog()) - { - case OPTIONS_RESULT_LOGOUT: { - PostMessage(hWnd, WM_LOGGEDOUT, 100, 0); - GetDiscordInstance()->SetToken(""); - GetDiscordInstance()->ClearData(); - GetLocalSettings()->SetToken(""); - GetLocalSettings()->Save(); - g_pChannelView->ClearChannels(); - g_pMemberList->ClearMembers(); - g_pStatusBar->ClearTypers(); - g_pMessageList->ClearMessages(); - g_pGuildLister->ClearTooltips(); - return TRUE; - } - } - break; - } - case ID_FILE_RECONNECTTODISCORD: - { - if (!GetDiscordInstance()->IsGatewayConnected()) - SendMessage(hWnd, WM_LOGINAGAIN, 0, 0); - - break; - } - case ID_FILE_EXIT: - { - SendMessage(hWnd, WM_CLOSEBYPASSTRAY, 0, 0); - return 0; - } - case ID_HELP_ABOUTDISCORDMESSENGER: - { - About(); - break; - } - case ID_FILE_STOPALLSPEECH: - { - TextToSpeech::StopAll(); - break; - } - case ID_ONLINESTATUSPLACEHOLDER_ONLINE: - { - GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_ONLINE); - break; - } - case ID_ONLINESTATUSPLACEHOLDER_IDLE: - { - GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_IDLE); - break; - } - case ID_ONLINESTATUSPLACEHOLDER_DONOTDISTURB: - { - GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_DND); - break; - } - case ID_ONLINESTATUSPLACEHOLDER_INVISIBLE: - { - GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_OFFLINE); - break; - } - case IDM_LEAVEGUILD: // Server > Leave Server - { - g_pGuildLister->AskLeave(GetDiscordInstance()->GetCurrentGuildID()); - break; - } - // Accelerators - case IDA_SEARCH: - case ID_ACTIONS_SEARCH: - { - // TODO - //GetWebsocketClient()->Close(GetDiscordInstance()->GetGatewayID(), websocketpp::close::status::normal); - DbgPrintW("Search!"); - break; - } - case IDA_QUICKSWITCHER: - case ID_ACTIONS_QUICKSWITCHER: - { - QuickSwitcher::ShowDialog(); - break; - } - case IDA_PASTE: - { - if (!OpenClipboard(hWnd)) { - DbgPrintW("Error opening clipboard: %d", GetLastError()); - break; - } - - HBITMAP hbm = NULL; - HDC hdc = NULL; - BYTE* pBytes = nullptr; - LPCTSTR fileName = nullptr; - std::vector data; - bool isClipboardClosed = false; - bool hadToUseLegacyBitmap = false; - int width = 0, height = 0, stride = 0, bpp = 0; - Channel* pChan = nullptr; - - HANDLE hbmi = GetClipboardData(CF_DIB); - if (hbmi) - { - DbgPrintW("Using CF_DIB to fetch image from clipboard"); - PBITMAPINFO bmi = (PBITMAPINFO) GlobalLock(hbmi); - - // WHAT THE HELL: Windows seems to add 12 extra bytes after the header - // representing the colors: [RED] [GREEN] [BLUE]. I don't know why we - // have to do this or how to un-hardcode it. Yes, in this case, the - // biClrUsed member is zero. - // Does it happen to include only part of the BITMAPV4HEADER??? - const int HACK_OFFSET = 12; - - width = bmi->bmiHeader.biWidth; - height = bmi->bmiHeader.biHeight; - bpp = bmi->bmiHeader.biBitCount; - stride = ((((width * bpp) + 31) & ~31) >> 3); - - int sz = stride * height; - pBytes = new BYTE[sz]; - memcpy(pBytes, (char*) bmi + bmi->bmiHeader.biSize + HACK_OFFSET, sz); - - GlobalUnlock(hbmi); - } - else - { - // try legacy CF_BITMAP - DbgPrintW("Using legacy CF_BITMAP"); - HBITMAP hbm = (HBITMAP) GetClipboardData(CF_BITMAP); - hadToUseLegacyBitmap = true; - - if (!hbm) - { - CloseClipboard(); - - // No bitmap, forward to the edit control if selected - HWND hFocus = GetFocus(); - if (hFocus == g_pMessageEditor->m_edit_hwnd) - SendMessage(hFocus, WM_PASTE, 0, 0); - - return FALSE; - } - - HDC hdc = GetDC(hWnd); - bool res = GetDataFromBitmap(hdc, hbm, pBytes, width, height, bpp); - ReleaseDC(hWnd, hdc); - - if (!res) - goto _fail; - - stride = ((((width * bpp) + 31) & ~31) >> 3); - } - - pChan = GetDiscordInstance()->GetCurrentChannel(); - if (!pChan) { - DbgPrintW("No channel!"); - goto _fail; - } - - if (!pChan->HasPermission(PERM_SEND_MESSAGES) || - !pChan->HasPermission(PERM_ATTACH_FILES)) { - DbgPrintW("Can't attach files here!"); - goto _fail; - } - - // TODO: Don't always force alpha when pasting from CF_DIB or CF_BITMAP. - // I know this sucks, but I've tried a lot of things... - - // have to force alpha always - // have to flip vertically if !hadToUseLegacyBitmap - if (!ImageLoader::ConvertToPNG(&data, pBytes, width, height, stride, bpp, true, !hadToUseLegacyBitmap)) { - DbgPrintW("Cannot convert to PNG!"); - goto _fail; - } - - CloseClipboard(); isClipboardClosed = true; - - fileName = TmGetTString(IDS_UNKNOWN_FILE_NAME); - UploadDialogShowWithFileData(data.data(), data.size(), fileName); - - _fail: - data.clear(); - if (pBytes) delete[] pBytes; - if (hdc) ReleaseDC(hWnd, hdc); - if (hbm) DeleteObject(hbm); // should I do this? - if (!isClipboardClosed) CloseClipboard(); - break; - } - case ID_NOTIFICATION_SHOW: - SendMessage(g_Hwnd, WM_RESTOREAPP, 0, 0); - break; - case ID_NOTIFICATION_EXIT: - SendMessage(g_Hwnd, WM_CLOSEBYPASSTRAY, 0, 0); - break; - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - -int g_tryAgainTimerElapse = 100; -UINT_PTR g_tryAgainTimer = 0; -const UINT_PTR g_tryAgainTimerId = 123456; - -void CALLBACK TryAgainTimer(HWND hWnd, UINT uMsg, UINT_PTR uTimerID, DWORD dwParam) { - if (uTimerID != g_tryAgainTimerId) - return; - - KillTimer(hWnd, g_tryAgainTimer); - g_tryAgainTimer = 0; - GetDiscordInstance()->StartGatewaySession(); -} - -void TryConnectAgainIn(int time) { - g_tryAgainTimer = SetTimer(g_Hwnd, g_tryAgainTimerId, time, TryAgainTimer); -} - -void ResetTryAgainInTime() { - g_tryAgainTimerElapse = 500; -} - const CHAR g_StartupArg[] = "/startup"; bool g_bFromStartup = false; @@ -697,957 +161,8 @@ void CheckIfItsStartup(const LPSTR pCmdLine) g_bFromStartup = strstr(pCmdLine, g_StartupArg); } -void AddOrRemoveAppFromStartup() -{ - HKEY hkey = NULL; - RegCreateKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), &hkey); - - LPCTSTR value = TmGetTString(IDS_PROGRAM_NAME); - - if (GetLocalSettings()->GetOpenOnStartup()) - { - TCHAR tPath[MAX_PATH]; - const DWORD length = GetModuleFileName(NULL, tPath, MAX_PATH); - - const std::string sPath = "\"" + MakeStringFromTString(tPath) + "\" " + std::string(g_StartupArg); - const LPTSTR finalPath = ConvertCppStringToTString(sPath); - - RegSetValueEx(hkey, value, 0, REG_SZ, (BYTE *)finalPath, (DWORD)((_tcslen(finalPath) + 1) * sizeof(TCHAR))); - } - else - { - RegDeleteValue(hkey, value); - } -} - -int g_agerCounter = 0; - -LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - KeepOverridingTheFilter(); - - g_agerCounter++; - if (g_agerCounter >= 50) { - g_agerCounter = 0; - GetAvatarCache()->AgeBitmaps(); - } - - switch (uMsg) - { - case WM_UPDATEEMOJI: - { - Snowflake sf = *(Snowflake*) lParam; - g_pMessageList->OnUpdateEmoji(sf); - g_pGuildHeader->OnUpdateEmoji(sf); - PinList::OnUpdateEmoji(sf); - break; - } - case WM_UPDATEUSER: - { - Snowflake sf = *(Snowflake*) lParam; - g_pMessageList->OnUpdateAvatar(sf); - g_pMemberList->OnUpdateAvatar(sf); - g_pChannelView->OnUpdateAvatar(sf); - PinList::OnUpdateAvatar(sf); - - if (ProfilePopout::GetUser() == sf) - ProfilePopout::Update(); - break; - } - case WM_HTTPERROR: - { - const char** arr = (const char**) lParam; - return OnHTTPError(CutOutURLPath(std::string((const char*) arr[0])), std::string((const char*) arr[1]), (bool)wParam); - } - case WM_FORCERESTART: - { - MessageBox(hWnd, TmGetTString(IDS_RESTART_CONFIG_CHANGE), TmGetTString(IDS_PROGRAM_NAME), MB_OK | MB_ICONINFORMATION); - if (InSendMessage()) - ReplyMessage(0); - SendMessage(hWnd, WM_DESTROY, 0, 0); - return 0; - } - case WM_UPDATEMESSAGELENGTH: - { - g_pStatusBar->UpdateCharacterCounter(int(lParam), MAX_MESSAGE_SIZE); - break; - } - case WM_UPDATEPROFILEAVATAR: - { - UpdateProfileParams parms = *(UpdateProfileParams*) lParam; - GetAvatarCache()->EraseBitmap(parms.m_resId); - break; - } - case WM_UPDATEATTACHMENT: - { - ImagePlace pl = *(ImagePlace*)lParam; - g_pMessageList->OnUpdateEmbed(pl.key); - PinList::OnUpdateEmbed(pl.key); - break; - } - case WM_REPAINTPROFILE: - { - g_pProfileView->Update(); - break; - } - case WM_UPDATEPROFILEPOPOUT: - { - ProfilePopout::Update(); - break; - } - case WM_REPAINTGUILDLIST: - { - g_pGuildLister->Update(); - break; - } - case WM_REFRESHMESSAGES: - { - Snowflake sf = *(Snowflake*)lParam; - g_pMessageList->RefetchMessages(sf, true); - break; - } - case WM_REPAINTMSGLIST: - { - InvalidateRect(g_pMessageList->m_hwnd, NULL, false); - break; - } - case WM_MSGLISTUPDATEMODE: - { - g_pMessageList->UpdateBackgroundBrush(); - InvalidateRect(g_pMessageList->m_hwnd, NULL, false); - break; - } - case WM_RECALCMSGLIST: - { - g_pMessageList->FullRecalcAndRepaint(); - break; - } - case WM_SHOWUPLOADDIALOG: - { - UploadDialogShow2(); - break; - } - case WM_UPDATESELECTEDCHANNEL: - { - g_pMessageEditor->StopReply(); - g_pMessageEditor->Layout(); - - Snowflake guildID = GetDiscordInstance()->GetCurrentGuildID(); - Snowflake channID = GetDiscordInstance()->GetCurrentChannelID(); - - g_pChannelView->OnUpdateSelectedChannel(channID); - - // repaint the message view - g_pMessageList->ClearMessages(); - g_pMessageList->SetGuild(guildID); - g_pMessageList->SetChannel(channID); - - g_pMessageList->UpdateAllowDrop(); - - UpdateMainWindowTitle(hWnd); - - if (IsWindowActive(g_Hwnd)) - SetFocus(g_pMessageEditor->m_edit_hwnd); - - if (!GetDiscordInstance()->GetCurrentChannel()) - { - InvalidateRect(g_pMessageList->m_hwnd, NULL, TRUE); - InvalidateRect(g_pGuildHeader->m_hwnd, NULL, FALSE); - break; - } - - GetDiscordInstance()->HandledChannelSwitch(); - - g_pMessageList->RefetchMessages(g_pMessageList->GetMessageSentTo()); - - InvalidateRect(g_pMessageList->m_hwnd, NULL, MessageList::MayErase()); - g_pGuildHeader->Update(); - g_pMessageEditor->UpdateTextBox(); - - // Update the message editor's typing indicators - g_pStatusBar->ClearTypers(); - - Snowflake myUserID = GetDiscordInstance()->GetUserID(); - TypingInfo& ti = g_typingInfo[channID]; - for (auto& tu : ti.m_typingUsers) { - if (tu.second.m_key == myUserID) - continue; - - g_pStatusBar->AddTypingName(tu.second.m_key, tu.second.m_startTimeMS / 1000ULL, tu.second.m_name); - } - - break; - } - case WM_UPDATEMEMBERLIST: - { - g_pMemberList->SetGuild(GetDiscordInstance()->GetCurrentGuild()->m_snowflake); - g_pMemberList->Update(); - break; - } - case WM_TOGGLEMEMBERS: - { - g_bMemberListVisible ^= 1; - int off = ScaleByDPI(10) + g_MemberListWidth; - - if (g_bMemberListVisible) { - ShowWindow(g_pMemberList->m_mainHwnd, SW_SHOWNORMAL); - UpdateWindow(g_pMemberList->m_mainHwnd); - ResizeWindow(g_pMessageList->m_hwnd, -off, 0, false, false, true); - ResizeWindow(g_pMessageEditor->m_hwnd, -off, 0, false, false, true); - } - else { - ShowWindow(g_pMemberList->m_mainHwnd, SW_HIDE); - UpdateWindow(g_pMemberList->m_mainHwnd); - ResizeWindow(g_pMessageList->m_hwnd, off, 0, false, false, true); - ResizeWindow(g_pMessageEditor->m_hwnd, off, 0, false, false, true); - } - - break; - } - case WM_TOGGLECHANNELS: - { - g_bChannelListVisible ^= 1; - int off = ScaleByDPI(10) + g_ChannelViewListWidth2; - - if (g_bChannelListVisible) { - ShowWindow(g_pChannelView->m_hwnd, SW_SHOWNORMAL); - ShowWindow(g_pProfileView->m_hwnd, SW_SHOWNORMAL); - UpdateWindow(g_pChannelView->m_hwnd); - UpdateWindow(g_pProfileView->m_hwnd); - ResizeWindow(g_pMessageList->m_hwnd, off, 0, true, false, true); - ResizeWindow(g_pMessageEditor->m_hwnd, off, 0, true, false, true); - } - else { - ShowWindow(g_pChannelView->m_hwnd, SW_HIDE); - ShowWindow(g_pProfileView->m_hwnd, SW_HIDE); - UpdateWindow(g_pChannelView->m_hwnd); - UpdateWindow(g_pProfileView->m_hwnd); - ResizeWindow(g_pMessageList->m_hwnd, -off, 0, true, false, true); - ResizeWindow(g_pMessageEditor->m_hwnd, -off, 0, true, false, true); - } - - break; - } - case WM_UPDATESELECTEDGUILD: - { - // repaint the guild lister - g_pGuildLister->UpdateSelected(); - g_pGuildHeader->Update(); - - SendMessage(hWnd, WM_UPDATECHANLIST, 0, 0); - SendMessage(hWnd, WM_UPDATEMEMBERLIST, 0, 0); - break; - } - case WM_UPDATECHANACKS: - { - Snowflake* sfs = (Snowflake*)lParam; - - Snowflake channelID = sfs[0]; - Snowflake messageID = sfs[1]; - auto inst = GetDiscordInstance(); - - Channel* pChan = GetDiscordInstance()->GetChannelGlobally(channelID); - - // Update the icon for the specific guild - g_pGuildLister->RedrawIconForGuild(pChan->m_parentGuild); - - // Get the channel as is in the current guild; if found, - // update the channel view's ack status - Guild* pGuild = inst->GetGuild(inst->m_CurrentGuild); - if (!pGuild) break; - - pChan = pGuild->GetChannel(channelID); - if (!pChan) - break; - - g_pChannelView->UpdateAcknowledgement(channelID); - if (g_pMessageList->GetCurrentChannel() == channelID) - g_pMessageList->SetLastViewedMessage(messageID, true); - break; - } - case WM_UPDATECHANLIST: - { - // repaint the channel view - g_pChannelView->ClearChannels(); - - // get the current guild - auto inst = GetDiscordInstance(); - Guild* pGuild = inst->GetGuild(inst->m_CurrentGuild); - if (!pGuild) break; - - g_pChannelView->SetMode(pGuild->m_snowflake == 0); - - // if the channels haven't been fetched yet - if (!pGuild->m_bChannelsLoaded) - { - pGuild->RequestFetchChannels(); - } - else - { - for (auto& ch : pGuild->m_channels) - g_pChannelView->AddChannel(ch); - for (auto& ch : pGuild->m_channels) - g_pChannelView->RemoveCategoryIfNeeded(ch); - - g_pChannelView->CommitChannels(); - } - - break; - } - // wParam = request code, lParam = Request* - case WM_REQUESTDONE: - { - NetRequest cloneReq = * (NetRequest*) lParam; - - // Round-trip time should probably be low to avoid stalling. - if (InSendMessage()) - ReplyMessage(0); - - GetDiscordInstance()->HandleRequest(&cloneReq); - break; - } - case WM_ADDMESSAGE: - { - AddMessageParams* pParms = (AddMessageParams*)lParam; - - GetMessageCache()->AddMessage(pParms->channel, pParms->msg); - - if (g_pMessageList->GetCurrentChannel() == pParms->channel) - { - g_pMessageList->AddMessage(pParms->msg.m_snowflake, GetForegroundWindow() == hWnd); - OnStopTyping(pParms->channel, pParms->msg.m_author_snowflake); - } - - Channel* pChan = GetDiscordInstance()->GetChannel(pParms->channel); - if (!pChan) - break; - - if (pChan->IsDM()) { - if (GetDiscordInstance()->ResortChannels(pChan->m_parentGuild)) - SendMessage(g_Hwnd, WM_UPDATECHANLIST, 0, 0); - } - - break; - } - case WM_UPDATEMESSAGE: - { - AddMessageParams* pParms = (AddMessageParams*)lParam; - - GetMessageCache()->EditMessage(pParms->channel, pParms->msg); - - if (g_pMessageList->GetCurrentChannel() == pParms->channel) - g_pMessageList->EditMessage(pParms->msg.m_snowflake); - - break; - } - case WM_DELETEMESSAGE: - { - Snowflake sf = *(Snowflake*)lParam; - g_pMessageList->DeleteMessage(sf); - - if (sf == g_pMessageEditor->ReplyingTo()) - g_pMessageEditor->StopReply(); - - break; - } - case WM_WEBSOCKETMESSAGE: - { - if (InSendMessage()) - ReplyMessage(0); - - WebsocketMessageParams* pParm = (WebsocketMessageParams*) lParam; - DbgPrintW("RECEIVED MESSAGE: %s\n", pParm->m_payload.c_str()); - - if (GetDiscordInstance()->GetGatewayID() == pParm->m_gatewayId) - GetDiscordInstance()->HandleGatewayMessage(pParm->m_payload); - - if (GetQRCodeDialog()->GetGatewayID() == pParm->m_gatewayId) - GetQRCodeDialog()->HandleGatewayMessage(pParm->m_payload); - - delete pParm; - break; - } - case WM_REFRESHMEMBERS: - { - auto* memsToUpdate = (std::set*)lParam; - - g_pMessageList->UpdateMembers(*memsToUpdate); - g_pMemberList ->UpdateMembers(*memsToUpdate); - g_pMessageEditor->OnLoadedMemberChunk(); - - break; - } - case WM_CREATE: - { - g_bMemberListVisible = true; - g_bChannelListVisible = true; - ForgetSystemDPI(); - g_ProfilePictureSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF); - - try { - GetWebsocketClient()->Init(); - } - catch (websocketpp::exception ex) { - Terminate("hey, websocketpp excepted: %s | %d | %s", ex.what(), ex.code(), ex.m_msg.c_str()); - } - catch (std::exception ex) { - Terminate("hey, websocketpp excepted (generic std::exception): %s", ex.what()); - } - catch (...) { - MessageBox(hWnd, TmGetTString(IDS_CANNOT_INIT_WS), TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); - g_bQuittingEarly = true; - break; - } - - if (GetLocalSettings()->IsFirstStart()) - { - MessageBox( - hWnd, - TmGetTString(IDS_WELCOME_MSG), - TmGetTString(IDS_PROGRAM_NAME), - MB_ICONINFORMATION | MB_OK - ); - } - - if (GetLocalSettings()->GetToken().empty()) - { - if (!LogonDialogShow()) - { - g_bQuittingEarly = true; - break; - } - } - - if (GetLocalSettings()->AskToCheckUpdates()) - { - bool check = MessageBox( - hWnd, - TmGetTString(IDS_CHECK_UPDATES), - TmGetTString(IDS_PROGRAM_NAME), - MB_ICONQUESTION | MB_YESNO - ) == IDYES; - - GetLocalSettings()->SetCheckUpdates(check); - } - - if (GetLocalSettings()->CheckUpdates()) - { - UpdateChecker::StartCheckingForUpdates(); - } - - if (g_SendIcon) DeleteObject(g_SendIcon); - if (g_JumpIcon) DeleteObject(g_JumpIcon); - if (g_CancelIcon) DeleteObject(g_CancelIcon); - if (g_UploadIcon) DeleteObject(g_UploadIcon); - if (g_DownloadIcon) DeleteObject(g_DownloadIcon); - - g_pDiscordInstance = new DiscordInstance(GetLocalSettings()->GetToken()); - - bool x = NT31SimplifiedInterface(); - g_SendIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_SEND)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); - g_JumpIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_JUMP)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); - g_CancelIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_CANCEL)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); - g_UploadIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_UPLOAD)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); - - g_BotIcon = (HICON) ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_BOT)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); - g_DownloadIcon = (HICON) ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_DOWNLOAD)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); - - MessageList::InitializeClass(); - ProfileView::InitializeClass(); - GuildHeader::InitializeClass(); - GuildLister::InitializeClass(); - AutoComplete::InitializeClass(); - IMemberList::InitializeClasses(); - IChannelView::InitializeClasses(); - MessageEditor::InitializeClass(); - LoadingMessage::InitializeClass(); - - RECT rect = {}, rect2 = {}; - GetClientRect(hWnd, &rect); - - rect2 = rect; - rect.left += 10; - rect.top += 10; - rect.right -= 10; - rect.bottom -= 10; - - RECT rcLoading = { 0, 0, 300, 200 }; - POINT pt{ 0, 0 }; - OffsetRect(&rcLoading, rect2.right / 2 - rcLoading.right / 2, 0);// rect2.bottom / 2 - rcLoading.bottom / 2); - ClientToScreen(hWnd, &pt); - OffsetRect(&rcLoading, pt.x, pt.y); - - g_pStatusBar = StatusBar::Create(hWnd); - g_pMessageList = MessageList::Create(hWnd, &rect); - g_pProfileView = ProfileView::Create(hWnd, &rect, CID_PROFILEVIEW, true); - g_pGuildHeader = GuildHeader::Create(hWnd, &rect); - g_pGuildLister = GuildLister::Create(hWnd, &rect); - g_pMemberList = IMemberList::CreateMemberList(hWnd, &rect); - g_pChannelView = IChannelView::CreateChannelView(hWnd, &rect); - g_pMessageEditor = MessageEditor::Create(hWnd, &rect); - g_pLoadingMessage = LoadingMessage::Create(hWnd, &rcLoading); - - SendMessage(hWnd, WM_LOGINAGAIN, 0, 0); - PostMessage(hWnd, WM_POSTINIT, 0, 0); - break; - } - case WM_POSTINIT: - { - GetShellNotification()->Initialize(); - break; - } - case WM_CONNECTERROR: - // try it again - EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_RECONNECTTODISCORD, MF_ENABLED); - DbgPrintW("Trying to connect to websocket again in %d ms", g_tryAgainTimerElapse); - TryConnectAgainIn(g_tryAgainTimerElapse); - g_tryAgainTimerElapse = g_tryAgainTimerElapse * 115 / 100; - if (g_tryAgainTimerElapse > 10000) - g_tryAgainTimerElapse = 10000; - break; - case WM_CONNECTERROR2: - case WM_CONNECTED: - ResetTryAgainInTime(); - if (g_tryAgainTimer) { - KillTimer(hWnd, g_tryAgainTimer); - g_tryAgainTimer = 0; - } - g_pLoadingMessage->Hide(); - - if (g_bFromStartup && GetLocalSettings()->GetStartMinimized()) { - GetFrontend()->HideWindow(); - } - - EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_RECONNECTTODISCORD, MF_GRAYED); - break; - case WM_CONNECTING: { - if (!g_bFromStartup || !GetLocalSettings()->GetStartMinimized()) - g_pLoadingMessage->Show(); - break; - } - case WM_LOGINAGAIN: - { - if (GetDiscordInstance()->HasGatewayURL()) { - GetDiscordInstance()->StartGatewaySession(); - } - else { - GetHTTPClient()->PerformRequest( - false, - NetRequest::GET, - GetDiscordAPI() + "gateway", - DiscordRequest::GATEWAY, - 0, "", GetDiscordToken() - ); - } - - break; - } - - case WM_CLOSEBYPASSTRAY: - AddOrRemoveAppFromStartup(); - CloseCleanup(hWnd); - DestroyWindow(hWnd); - break; - - case WM_SETBROWSINGPAST: - if (wParam) - g_pMessageEditor->StartBrowsingPast(); - else - g_pMessageEditor->StopBrowsingPast(); - break; - - case WM_CLOSE: - CloseCleanup(hWnd); - - if (GetLocalSettings()->GetMinimizeToNotif() && LOBYTE(GetVersion()) >= 4) - { - GetFrontend()->HideWindow(); - return 1; - } - break; - - case WM_SIZE: { - int width = LOWORD(lParam); - int height = HIWORD(lParam); - - RECT r{ 0, 0, width, height }; - AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE); - - // Save the new size - GetLocalSettings()->SetWindowSize(r.right - r.left, r.bottom - r.top); - GetLocalSettings()->SetMaximized(wParam == SIZE_MAXIMIZED); - - ProfilePopout::Dismiss(); - ProperlySizeControls(hWnd); - break; - } - case WM_REPOSITIONEVERYTHING: - { - ProperlySizeControls(hWnd); - break; - } - case WM_MOVE: { - ProfilePopout::Dismiss(); - AutoComplete::DismissAutoCompleteWindowsIfNeeded(hWnd); - break; - } - case WM_GETMINMAXINFO: { - PMINMAXINFO pMinMaxInfo = (PMINMAXINFO) lParam; - // these are the minimums as of now! - pMinMaxInfo->ptMinTrackSize.x = ScaleByDPI(640); - pMinMaxInfo->ptMinTrackSize.y = ScaleByDPI(400); - break; - } - case WM_COMMAND: - return HandleCommand(hWnd, uMsg, wParam, lParam); - - case WM_KILLFOCUS: - GetLocalSettings()->Save(); - break; - - case WM_DESTROY: - { - if (GetDiscordInstance()) - GetDiscordInstance()->CloseGatewaySession(); - if (GetAvatarCache()) - GetAvatarCache()->WipeBitmaps(); - - SAFE_DELETE(g_pChannelView); - SAFE_DELETE(g_pGuildHeader); - SAFE_DELETE(g_pGuildLister); - SAFE_DELETE(g_pMessageList); - SAFE_DELETE(g_pProfileView); - - SAFE_DELETE(g_pStatusBar); - SAFE_DELETE(g_pMemberList); - SAFE_DELETE(g_pMessageEditor); - SAFE_DELETE(g_pLoadingMessage); - - GetShellNotification()->Deinitialize(); - - PostQuitMessage(0); - break; - } - case WM_DRAWITEM: - { - switch (wParam) - { - case CID_STATUSBAR: - g_pStatusBar->DrawItem((LPDRAWITEMSTRUCT)lParam); - break; - } - break; - } - case WM_RECREATEMEMBERLIST: - { - RECT rc{}; - GetChildRect(hWnd, g_pMemberList->m_mainHwnd, &rc); - SAFE_DELETE(g_pMemberList); - g_pMemberList = IMemberList::CreateMemberList(hWnd, &rc); - WindowProc(hWnd, WM_UPDATEMEMBERLIST, 0, 0); - break; - } - case WM_INITMENUPOPUP: - { - HMENU Menu = (HMENU)wParam; - int firstMenuItem = GetMenuItemID(Menu, 0); - - switch (firstMenuItem) - { - case ID_ONLINESTATUSPLACEHOLDER_ONLINE: - { - Profile* pf = GetDiscordInstance()->GetProfile(); - if (!pf) - break; - - // It's the account status menu - CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_ONLINE, pf->m_activeStatus == STATUS_ONLINE ? MF_CHECKED : 0); - CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_IDLE, pf->m_activeStatus == STATUS_IDLE ? MF_CHECKED : 0); - CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_DONOTDISTURB, pf->m_activeStatus == STATUS_DND ? MF_CHECKED : 0); - CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_INVISIBLE, pf->m_activeStatus == STATUS_OFFLINE ? MF_CHECKED : 0); - break; - } - case IDM_INVITEPEOPLE: - { - Profile* pf = GetDiscordInstance()->GetProfile(); - Guild* pGuild = GetDiscordInstance()->GetCurrentGuild(); - if (!pGuild || !pf) - break; - - EnableMenuItem(Menu, IDM_LEAVEGUILD, pGuild->m_ownerId == pf->m_snowflake ? MF_GRAYED : MF_ENABLED); - break; - } - } - - break; - } - case WM_SENDTOMESSAGE: - { - Snowflake sf = *((Snowflake*)lParam); - g_pMessageList->SendToMessage(sf); - break; - } - case WM_SENDMESSAGE: - { - SendMessageParams parms = *((SendMessageParams*)lParam); - - std::string msg = MakeStringFromEditData(parms.m_rawMessage); - - if (msg.empty()) - return 1; - - // send it! - if (msg.size() >= MAX_MESSAGE_SIZE) - { - MessageBox( - hWnd, - TmGetTString(IDS_MESSAGE_TOO_LONG_MSG), - TmGetTString(IDS_PROGRAM_NAME), - MB_ICONINFORMATION | MB_OK - ); - return 1; - } - - Snowflake sf; - if (parms.m_bEdit) - return GetDiscordInstance()->EditMessageInCurrentChannel(msg, parms.m_replyTo) ? 0 : 1; - - if (!GetDiscordInstance()->SendMessageToCurrentChannel(msg, sf, parms.m_replyTo, parms.m_bMention)) - return 1; - - SendMessageAuxParams smap; - smap.m_message = msg; - smap.m_snowflake = sf; - SendMessage(hWnd, WM_SENDMESSAGEAUX, 0, (LPARAM)&smap); - return 0; - } - case WM_SENDMESSAGEAUX: - { - SendMessageAuxParams* psmap = (SendMessageAuxParams*) lParam; - - time_t lastTime = g_pMessageList->GetLastSentMessageTime(); - Profile* pf = GetDiscordInstance()->GetProfile(); - MessagePtr m = MakeMessage(); - m->m_snowflake = psmap->m_snowflake; - m->m_author_snowflake = pf->m_snowflake; - m->m_author = pf->m_name; - m->m_avatar = pf->m_avatarlnk; - m->m_message = psmap->m_message; - m->m_type = MessageType::SENDING_MESSAGE; - m->SetTime(time(NULL)); - m->m_dateFull = "Sending..."; - m->m_dateCompact = "Sending..."; - g_pMessageList->AddMessage(m, true); - return 0; - } - case WM_FAILMESSAGE: - { - FailedMessageParams parms = *((FailedMessageParams*)lParam); - if (g_pMessageList->GetCurrentChannel() != parms.channel) - return 0; - - g_pMessageList->OnFailedToSendMessage(parms.message); - return 0; - } - case WM_STARTTYPING: - { - TypingParams params = *((TypingParams*) lParam); - OnTyping(params.m_guild, params.m_channel, params.m_user, params.m_timestamp); - break; - } - case WM_SESSIONCLOSED: - { - TCHAR buf[512]; - WAsnprintf(buf, _countof(buf), TmGetTString(IDS_SESSION_ERROR), (int)wParam); - buf[_countof(buf) - 1] = 0; - - MessageBox( - hWnd, - buf, - TmGetTString(IDS_PROGRAM_NAME), - MB_ICONERROR | MB_OK - ); - - EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_RECONNECTTODISCORD, MF_ENABLED); - break; - } - case WM_SHOWPROFILEPOPOUT: - { - auto* pParams = (ShowProfilePopoutParams*) lParam; - ProfilePopout::DeferredShow(*pParams); - delete pParams; - break; - } - case WM_LOGGEDOUT: - { - if (wParam != 100) - { - MessageBox( - hWnd, - TmGetTString(IDS_NOTLOGGEDIN), - TmGetTString(IDS_PROGRAM_NAME), - MB_ICONERROR | MB_OK - ); - } - - if (!LogonDialogShow()) - PostQuitMessage(0); - else - { - case WM_LOGGEDOUT2: - // try again with the new token - GetHTTPClient()->StopAllRequests(); - GetAvatarCache()->WipeBitmaps(); - GetAvatarCache()->ClearProcessingRequests(); - GetProfileCache()->ClearAll(); - if (g_pDiscordInstance) delete g_pDiscordInstance; - g_pDiscordInstance = new DiscordInstance(GetLocalSettings()->GetToken()); - g_pChannelView->ClearChannels(); - g_pMemberList->ClearMembers(); - g_pGuildLister->Update(); - g_pGuildHeader->Update(); - g_pProfileView->Update(); - GetDiscordInstance()->GatewayClosed(CloseCode::LOG_ON_AGAIN); - } - break; - } - case WM_STARTREPLY: - { - Snowflake* sf = (Snowflake*)lParam; - // sf[0] - Message ID - // sf[1] - Author ID - g_pMessageEditor->StartReply(sf[0], sf[1]); - - break; - } - case WM_STARTEDITING: - { - Snowflake* sf = (Snowflake*)lParam; - g_pMessageEditor->StartEdit(*sf); - break; - } - case WM_KEYUP: { - -#ifdef _DEBUG - if (wParam == VK_F6) { - SendMessage(hWnd, WM_LOGGEDOUT2, 0, 0); - } - if (wParam == VK_F7) { - SendMessage(hWnd, WM_LOGGEDOUT, 0, 0); - } - if (wParam == VK_F8) { - GetQRCodeDialog()->Show(); - } - if (wParam == VK_F9) { - ProgressDialog::Show("Test!", 1234, true, g_Hwnd); - } - if (wParam == VK_F10) { - ProgressDialog::Show("Test!", 1234, false, g_Hwnd); - } - if (wParam == VK_F11) { - Message msg; - msg.m_author = "Test Author"; - msg.m_message = "Test message!!"; - GetNotificationManager()->OnMessageCreate(0, 1, msg); - } -#endif - - break; - } - case WM_ACTIVATE: - { - if (wParam == WA_INACTIVE && (HWND) lParam != ProfilePopout::GetHWND()) - ProfilePopout::Dismiss(); - - if (wParam == WA_INACTIVE) - AutoComplete::DismissAutoCompleteWindowsIfNeeded((HWND) lParam); - - break; - } - case WM_IMAGESAVED: - { - LPCTSTR file = (LPCTSTR) lParam; - size_t sl = _tcslen(file); - bool isExe = false; - - if (sl > 4 && ( - _tcscmp(file + sl - 4, TEXT(".exe")) == 0 || - _tcscmp(file + sl - 4, TEXT(".scr")) == 0 || - _tcscmp(file + sl - 4, TEXT(".lnk")) == 0 || - _tcscmp(file + sl - 4, TEXT(".zip")) == 0 || - _tcscmp(file + sl - 4, TEXT(".rar")) == 0 || - _tcscmp(file + sl - 4, TEXT(".7z")) == 0)) { - isExe = true; - } - - TCHAR buff[4096]; - WAsnprintf( - buff, - _countof(buff), - isExe ? TmGetTString(IDS_SAVED_STRING_EXE) : TmGetTString(IDS_SAVED_UPDATE), - file - ); - buff[_countof(buff) - 1] = 0; - - // TODO: Probably should automatically extract and install it or something - int res = MessageBox( - hWnd, - buff, - TmGetTString(IDS_PROGRAM_NAME), - MB_ICONINFORMATION | MB_YESNO - ); - - if (res == IDYES) { - ShellExecute(g_Hwnd, TEXT("open"), file, NULL, NULL, SW_SHOWNORMAL); - } - - break; - } - case WM_UPDATEAVAILABLE: - { - std::string* msg = (std::string*) lParam; - auto& url = msg[0]; - auto& version = msg[1]; - - TCHAR buff[2048]; - LPTSTR tstr1 = ConvertCppStringToTString(std::string(GetAppVersionString())); - LPTSTR tstr2 = ConvertCppStringToTString(version); - WAsnprintf(buff, _countof(buff), TmGetTString(IDS_NEW_VERSION_AVAILABLE), tstr1, tstr2); - free(tstr1); - free(tstr2); - - if (MessageBox(g_Hwnd, buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONINFORMATION | MB_YESNO) == IDYES) - { - size_t idx = 0, idxsave = 0; - for (; idx != url.size(); idx++) { - if (url[idx] == '/') - idxsave = idx + 1; - } - - DownloadFileDialog(g_Hwnd, url, url.substr(idxsave)); - } - else - { - GetLocalSettings()->StopUpdateCheckTemporarily(); - } - - delete[] msg; - break; - } - case WM_NOTIFMANAGERCALLBACK: - { - GetShellNotification()->Callback(wParam, lParam); - break; - } - case WM_RESTOREAPP: - if (GetLocalSettings()->GetMaximized()) - GetFrontend()->MaximizeWindow(); - else - GetFrontend()->RestoreWindow(); - break; - } - - return DefWindowProc(hWnd, uMsg, wParam, lParam); +bool IsFromStartup() { + return g_bFromStartup; } HFONT @@ -1865,6 +380,7 @@ static bool ForceSingleInstance(LPCTSTR pClassName) int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nShowCmd) { + MSG msg = { }; g_hInstance = hInstance; PrepareCutDownFlags(pCmdLine); @@ -1921,22 +437,6 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLin SetUserScale(GetLocalSettings()->GetUserScale()); - int wndWidth = 0, wndHeight = 0; - bool startMaximized = false; - GetLocalSettings()->GetWindowSize(wndWidth, wndHeight); - startMaximized = GetLocalSettings()->GetStartMaximized() || GetLocalSettings()->GetMaximized(); - - // Initialize the window class. - WNDCLASS& wc = g_MainWindowClass; - - wc.lpfnWndProc = WindowProc; - wc.hInstance = hInstance; - wc.lpszClassName = pClassName; - wc.hbrBackground = g_backgroundBrush; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hIcon = g_Icon = LoadIcon(hInstance, MAKEINTRESOURCE(DMIC(IDI_ICON))); - wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); - // NOTE: Despite that we pass LR_SHARED, if this "isn't a standard size" (whatever Microsoft means), we must still delete it!! g_DefaultProfilePicture = (HBITMAP)ri::LoadImage(hInstance, MAKEINTRESOURCE(IDB_DEFAULT), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_SHARED); g_ProfileBorderIcon = (HICON) ri::LoadImage(hInstance, MAKEINTRESOURCE(DMIC(IDI_PROFILE_BORDER)), IMAGE_ICON, 0, 0, LR_CREATEDIBSECTION | LR_SHARED); @@ -1958,18 +458,32 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLin InitializeStatusIcons(); - // Register the class. - if (!RegisterClass(&wc)) - return 1; - if (!RegisterImageViewerClass()) return 1; HACCEL hAccTable = LoadAccelerators(g_hInstance, MAKEINTRESOURCE(IDR_MAIN_ACCELS)); - // Initialize the worker thread manager + // Initialize the HTTP client and websocket client GetHTTPClient()->Init(); + try + { + GetWebsocketClient()->Init(); + } + catch (websocketpp::exception ex) + { + Terminate("hey, websocketpp excepted: %s | %d | %s", ex.what(), ex.code(), ex.m_msg.c_str()); + } + catch (std::exception ex) + { + Terminate("hey, websocketpp excepted (generic std::exception): %s", ex.what()); + } + catch (...) + { + MessageBox(NULL, TmGetTString(IDS_CANNOT_INIT_WS), TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); + return 1; + } + // Initialize DPI ForgetSystemDPI(); g_ProfilePictureSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF); @@ -1977,41 +491,13 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLin // Create some fonts. InitializeFonts(); - int flags = WS_OVERLAPPEDWINDOW; - if (startMaximized) { - flags |= WS_MAXIMIZE; - } - - g_Hwnd = CreateWindow( - /* class */ TEXT("DiscordMessengerClass"), - /* title */ TmGetTString(IDS_PROGRAM_NAME), - /* style */ flags, - /* x pos */ CW_USEDEFAULT, - /* y pos */ CW_USEDEFAULT, - /* x siz */ wndWidth, - /* y siz */ wndHeight, - /* parent */ NULL, - /* menu */ NULL, - /* instance */ hInstance, - /* lpparam */ NULL - ); - - if (!g_Hwnd) return 1; - - MSG msg = { }; - - if (g_bQuittingEarly) { - DestroyWindow(g_Hwnd); - g_Hwnd = NULL; - } - else { - TextToSpeech::Initialize(); + TextToSpeech::Initialize(); + // Create the main window; + MainWindow mainWindow(TEXT("DiscordMessengerClass"), nShowCmd); + if (!mainWindow.InitFailed()) + { // Run the message loop. - - if (!g_bFromStartup || !GetLocalSettings()->GetStartMinimized()) - ShowWindow (g_Hwnd, startMaximized ? SW_SHOWMAXIMIZED : nShowCmd); - while (GetMessage(&msg, NULL, 0, 0) > 0) { // @@ -2030,46 +516,38 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLin // If the focus isn't sent to a child of the profile popout, dismiss the latter if (!IsChildOf(msg.hwnd, ProfilePopout::GetHWND())) ProfilePopout::Dismiss(); - + // fallthrough - + case WM_WINDOWPOSCHANGED: case WM_MOVE: case WM_SIZE: AutoComplete::DismissAutoCompleteWindowsIfNeeded(msg.hwnd); break; - + case WM_CHAR: - - if (msg.hwnd == g_pChannelView->GetListHWND() || - msg.hwnd == g_pChannelView->GetTreeHWND() || - msg.hwnd == g_pMemberList->GetListHWND() || - msg.hwnd == g_pMemberList->m_mainHwnd || - msg.hwnd == g_pGuildLister->m_hwnd || - msg.hwnd == g_pMessageList->m_hwnd) + if (mainWindow.IsPartOfMainWindow(msg.hwnd)) { - msg.hwnd = g_pMessageEditor->m_edit_hwnd; + msg.hwnd = mainWindow.GetMessageEditor()->m_edit_hwnd; SetFocus(msg.hwnd); } break; } - + if (IsWindow(ProfilePopout::GetHWND()) && IsDialogMessage(ProfilePopout::GetHWND(), &msg)) continue; - + if (hAccTable && - TranslateAccelerator(g_Hwnd, hAccTable, &msg)) + TranslateAccelerator(mainWindow.GetHWND(), hAccTable, &msg)) continue; - + TranslateMessage(&msg); DispatchMessage(&msg); } - - TextToSpeech::Deinitialize(); } - - g_Hwnd = NULL; - UnregisterClass(wc.lpszClassName, hInstance); + +quitEarly: + TextToSpeech::Deinitialize(); GetLocalSettings()->Save(); GetWebsocketClient()->Kill(); GetHTTPClient()->Kill(); @@ -2078,27 +556,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLin return (int)msg.wParam; } -void CALLBACK OnHeartbeatTimer(HWND hWnd, UINT uInt, UINT_PTR uIntPtr, DWORD dWord) -{ - // Don't care about the parameters, tell discord instance to send a heartbeat - GetDiscordInstance()->SendHeartbeat(); -} - void SetHeartbeatInterval(int timeMs) { - if (g_HeartbeatTimerInterval == timeMs) - return; - - assert(timeMs > 100 || timeMs == 0); - - if (g_HeartbeatTimer != 0) - { - KillTimer(g_Hwnd, g_HeartbeatTimer); - g_HeartbeatTimer = 0; - } - - if (timeMs != 0) - { - g_HeartbeatTimer = SetTimer(g_Hwnd, 0, timeMs, OnHeartbeatTimer); - } + GetMainWindow()->SetHeartbeatInterval(timeMs); } diff --git a/src/windows/Main.hpp b/src/windows/Main.hpp index e3a8d63..a3c3db0 100644 --- a/src/windows/Main.hpp +++ b/src/windows/Main.hpp @@ -42,6 +42,7 @@ #include "AvatarCache.hpp" #include "NetworkerThread.hpp" #include "TextInterface_Win32.hpp" +#include "MainWindow.hpp" #include "../discord/DiscordAPI.hpp" #include "../discord/SettingsManager.hpp" @@ -52,9 +53,7 @@ #define MAX_MESSAGE_SIZE 2000 // 4000 with nitro extern HINSTANCE g_hInstance; -extern WNDCLASS g_MainWindowClass; extern HBRUSH g_backgroundBrush; -extern HWND g_Hwnd; extern HICON g_Icon; extern HICON g_BotIcon; extern HICON g_SendIcon; @@ -63,6 +62,8 @@ extern HICON g_CancelIcon; extern HICON g_UploadIcon; extern HICON g_DownloadIcon; +extern int g_ProfilePictureSize; + extern HFONT g_MessageTextFont, g_AuthorTextFont, @@ -123,19 +124,20 @@ struct WebsocketMessageParams struct TypingParams { - Snowflake m_user; - Snowflake m_guild; - Snowflake m_channel; - time_t m_timestamp; + Snowflake m_user = 0; + Snowflake m_guild = 0; + Snowflake m_channel = 0; + time_t m_timestamp = 0; }; struct SendMessageParams { - LPTSTR m_rawMessage; - Snowflake m_replyTo; - bool m_bEdit; - bool m_bReply; - bool m_bMention; + LPTSTR m_rawMessage = NULL; + Snowflake m_channel = 0; + Snowflake m_replyTo = 0; + bool m_bEdit = false; + bool m_bReply = false; + bool m_bMention = false; }; struct SendMessageAuxParams @@ -144,12 +146,16 @@ struct SendMessageAuxParams Snowflake m_snowflake; }; +struct RefreshMessagesParams +{ + Snowflake m_channelId; + ScrollDir::eScrollDir m_scrollDir; + Snowflake m_gapCulprit; +}; + std::string FormatDiscrim(int discrim); std::string GetStringFromHResult(HRESULT hr); -std::string GetDiscordToken(); -void OnUpdateAvatar(const std::string& resid); -DiscordInstance* GetDiscordInstance(); void WantQuit(); -void SetHeartbeatInterval(int timeMs); int GetProfilePictureSize(); HBITMAP GetDefaultBitmap(); +bool IsFromStartup(); diff --git a/src/windows/MainWindow.cpp b/src/windows/MainWindow.cpp new file mode 100644 index 0000000..eb999ad --- /dev/null +++ b/src/windows/MainWindow.cpp @@ -0,0 +1,1738 @@ +#include "MainWindow.hpp" +#include + +// Controls +#include "StatusBar.hpp" +#include "MessageList.hpp" +#include "GuildLister.hpp" +#include "GuildHeader.hpp" +#include "ProfileView.hpp" +#include "IMemberList.hpp" +#include "IChannelView.hpp" +#include "MessageEditor.hpp" +#include "LoadingMessage.hpp" + +// Other Stuff +#include "Main.hpp" +#include "CrashDebugger.hpp" +#include "PinList.hpp" +#include "ProfilePopout.hpp" +#include "UploadDialog.hpp" +#include "ShellNotification.hpp" +#include "QRCodeDialog.hpp" +#include "LogonDialog.hpp" +#include "ImageViewer.hpp" +#include "OptionsDialog.hpp" +#include "AboutDialog.hpp" +#include "TextToSpeech.hpp" +#include "QuickSwitcher.hpp" +#include "GuildSubWindow.hpp" + +#include "../discord/WebsocketClient.hpp" +#include "../discord/LocalSettings.hpp" +#include "../discord/UpdateChecker.hpp" + +DiscordInstance* g_pDiscordInstance; +MainWindow* g_pMainWindow; + +DiscordInstance* GetDiscordInstance() { + return g_pDiscordInstance; +} +std::string GetDiscordToken() { + return g_pDiscordInstance->GetToken(); +} +MainWindow* GetMainWindow() { + return g_pMainWindow; +} +HWND GetMainHWND() { + if (!g_pMainWindow) + return NULL; + return g_pMainWindow->GetHWND(); +} + +const CHAR g_StartupArg[] = "/startup"; + +MainWindow::MainWindow(LPCTSTR pClassName, int nShowCmd) +{ + if (g_pMainWindow) { + DbgPrintW("There can be only one."); + m_bInitFailed = true; + return; + } + + g_pMainWindow = this; + + g_pDiscordInstance = new DiscordInstance(GetLocalSettings()->GetToken()); + + // since we're the main window, g_pDiscordInstance didn't exist yet, therefore + // register us manually + g_pDiscordInstance->RegisterView(GetChatView()); + + assert(GetChatView()->GetID() == 0); + + // Initialize the window class. + WNDCLASS& wc = m_wndClass; + + wc.lpfnWndProc = WndProc; + wc.hInstance = g_hInstance; + wc.lpszClassName = pClassName; + wc.hbrBackground = g_backgroundBrush; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hIcon = g_Icon = LoadIcon(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_ICON))); + wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); + + if (!RegisterClass(&wc)) { + DbgPrintW("MainWindow RegisterClass failed!"); + m_bInitFailed = true; + return; + } + + if (!GuildSubWindow::InitializeClass()) { + DbgPrintW("GuildSubWindow RegisterClass failed!"); + return; + } + + // Get the window creation parameters. + int wndWidth = 0, wndHeight = 0; + bool startMaximized = false; + GetLocalSettings()->GetWindowSize(wndWidth, wndHeight); + startMaximized = GetLocalSettings()->GetStartMaximized() || GetLocalSettings()->GetMaximized(); + + int flags = WS_OVERLAPPEDWINDOW; + if (startMaximized) { + flags |= WS_MAXIMIZE; + } + + m_hwnd = CreateWindow( + /* class */ pClassName, + /* title */ TmGetTString(IDS_PROGRAM_NAME), + /* style */ flags, + /* x pos */ CW_USEDEFAULT, + /* y pos */ CW_USEDEFAULT, + /* x siz */ wndWidth, + /* y siz */ wndHeight, + /* parent */ NULL, + /* menu */ NULL, + /* instance */ g_hInstance, + /* lpparam */ NULL + ); + if (!m_hwnd) { + DbgPrintW("Main window failed to create!"); + m_bInitFailed = true; + return; + } + + // Show the main window if needed. + if (!IsFromStartup() || !GetLocalSettings()->GetStartMinimized()) + ShowWindow(m_hwnd, startMaximized ? SW_SHOWMAXIMIZED : nShowCmd); +} + +MainWindow::~MainWindow() +{ + if (g_pMainWindow == this) { + g_pMainWindow = nullptr; + } + + SAFE_DELETE(g_pDiscordInstance); + SAFE_DELETE(m_pStatusBar); + SAFE_DELETE(m_pMessageList); + SAFE_DELETE(m_pProfileView); + SAFE_DELETE(m_pGuildHeader); + SAFE_DELETE(m_pGuildLister); + SAFE_DELETE(m_pMemberList); + SAFE_DELETE(m_pChannelView); + SAFE_DELETE(m_pMessageEditor); + SAFE_DELETE(m_pLoadingMessage); + + if (m_HeartbeatTimer != 0) KillTimer(m_hwnd, m_HeartbeatTimer); + if (m_TryAgainTimer != 0) KillTimer(m_hwnd, m_TryAgainTimer); +} + +bool MainWindow::IsPartOfMainWindow(HWND hWnd) +{ + return hWnd == m_pChannelView->GetListHWND() || + hWnd == m_pChannelView->GetTreeHWND() || + hWnd == m_pMemberList->GetListHWND() || + hWnd == m_pMemberList->m_mainHwnd || + hWnd == m_pGuildLister->m_hwnd || + hWnd == m_pMessageList->m_hwnd; +} + +bool HasViewIDSpecifier(UINT uMsg) +{ + switch (uMsg) + { + case WM_UPDATESELECTEDGUILD: + case WM_UPDATESELECTEDCHANNEL: + case WM_UPDATECHANLIST: + case WM_UPDATEMEMBERLIST: + case WM_REFRESHMEMBERS: + case WM_SENDTOMESSAGE: + case WM_DELETEMESSAGE: + case WM_CLOSEVIEW: + return true; + } + + return false; +} + +bool ShouldMirrorEventToSubViews(UINT uMsg) +{ + switch (uMsg) + { + case WM_UPDATEUSER: + case WM_UPDATEEMOJI: + case WM_UPDATECHANACKS: + case WM_UPDATEATTACHMENT: + case WM_REPAINTGUILDLIST: + case WM_REPAINTPROFILE: + case WM_REFRESHMESSAGES: + case WM_REPAINTMSGLIST: + case WM_RECALCMSGLIST: + case WM_MSGLISTUPDATEMODE: + case WM_ADDMESSAGE: + case WM_UPDATEMESSAGE: + case WM_DELETEMESSAGE: + case WM_STARTTYPING: + return true; + } + + return false; +} + +void MainWindow::UpdateMemberListVisibility() +{ + int off = ScaleByDPI(10) + m_MemberListWidth; + + if (m_bMemberListVisible) { + ShowWindow(m_pMemberList->m_mainHwnd, SW_SHOWNORMAL); + UpdateWindow(m_pMemberList->m_mainHwnd); + ResizeWindow(m_pMessageList->m_hwnd, -off, 0, false, false, true); + ResizeWindow(m_pMessageEditor->m_hwnd, -off, 0, false, false, true); + } + else { + ShowWindow(m_pMemberList->m_mainHwnd, SW_HIDE); + UpdateWindow(m_pMemberList->m_mainHwnd); + ResizeWindow(m_pMessageList->m_hwnd, off, 0, false, false, true); + ResizeWindow(m_pMessageEditor->m_hwnd, off, 0, false, false, true); + } +} + +LRESULT MainWindow::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + KeepOverridingTheFilter(); + + m_agerCounter++; + if (m_agerCounter >= 50) { + m_agerCounter = 0; + GetAvatarCache()->AgeBitmaps(); + } + + // Main view always has view ID of 0 + if (HasViewIDSpecifier(uMsg) && wParam != 0) + { + for (auto& window : m_subWindows) { + if (window->GetChatView()->GetID() == (int) wParam) + return SendMessage(window->GetHWND(), uMsg, wParam, lParam); + } + } + + switch (uMsg) + { + case WM_UPDATEEMOJI: + { + Snowflake sf = *(Snowflake*)lParam; + m_pMessageList->OnUpdateEmoji(sf); + m_pGuildHeader->OnUpdateEmoji(sf); + PinList::OnUpdateEmoji(sf); + break; + } + case WM_UPDATEUSER: + { + Snowflake sf = *(Snowflake*)lParam; + m_pMessageList->OnUpdateAvatar(sf); + m_pMemberList->OnUpdateAvatar(sf); + m_pChannelView->OnUpdateAvatar(sf); + PinList::OnUpdateAvatar(sf); + + if (ProfilePopout::GetUser() == sf) + ProfilePopout::Update(); + break; + } + case WM_HTTPERROR: + { + const char** arr = (const char**)lParam; + return OnHTTPError(CutOutURLPath(std::string((const char*)arr[0])), std::string((const char*)arr[1]), (bool)wParam); + } + case WM_FORCERESTART: + { + MessageBox(hWnd, TmGetTString(IDS_RESTART_CONFIG_CHANGE), TmGetTString(IDS_PROGRAM_NAME), MB_OK | MB_ICONINFORMATION); + if (InSendMessage()) + ReplyMessage(0); + SendMessage(hWnd, WM_DESTROY, 0, 0); + return 0; + } + case WM_UPDATEMESSAGELENGTH: + { + m_pStatusBar->UpdateCharacterCounter(int(lParam), MAX_MESSAGE_SIZE); + break; + } + case WM_UPDATEPROFILEAVATAR: + { + UpdateProfileParams parms = *(UpdateProfileParams*) lParam; + GetAvatarCache()->EraseBitmap(parms.m_resId); + break; + } + case WM_UPDATEATTACHMENT: + { + ImagePlace pl = *(ImagePlace*)lParam; + m_pMessageList->OnUpdateEmbed(pl.key); + PinList::OnUpdateEmbed(pl.key); + break; + } + case WM_REPAINTPROFILE: + { + m_pProfileView->Update(); + break; + } + case WM_UPDATEPROFILEPOPOUT: + { + ProfilePopout::Update(); + break; + } + case WM_REPAINTGUILDLIST: + { + m_pGuildLister->Update(); + break; + } + case WM_REFRESHMESSAGES: + { + RefreshMessagesParams* params = (RefreshMessagesParams*)lParam; + + if (m_pMessageList->GetCurrentChannelID() == params->m_channelId) + m_pMessageList->RefetchMessages(params->m_gapCulprit, true); + + break; + } + case WM_REPAINTMSGLIST: + { + InvalidateRect(m_pMessageList->m_hwnd, NULL, false); + break; + } + case WM_MSGLISTUPDATEMODE: + { + m_pMessageList->UpdateBackgroundBrush(); + InvalidateRect(m_pMessageList->m_hwnd, NULL, false); + break; + } + case WM_RECALCMSGLIST: + { + m_pMessageList->FullRecalcAndRepaint(); + break; + } + case WM_SHOWUPLOADDIALOG: + { + Snowflake* sf = (Snowflake*) lParam; + UploadDialogShow2(hWnd, *sf); + delete sf; + break; + } + case WM_TOGGLEMEMBERS: + { + m_bMemberListVisible ^= 1; + UpdateMemberListVisibility(); + break; + } + case WM_TOGGLECHANNELS: + { + m_bChannelListVisible ^= 1; + int off = ScaleByDPI(10) + m_ChannelViewListWidth2; + + if (m_bChannelListVisible) { + ShowWindow(m_pChannelView->m_hwnd, SW_SHOWNORMAL); + ShowWindow(m_pProfileView->m_hwnd, SW_SHOWNORMAL); + UpdateWindow(m_pChannelView->m_hwnd); + UpdateWindow(m_pProfileView->m_hwnd); + ResizeWindow(m_pMessageList->m_hwnd, off, 0, true, false, true); + ResizeWindow(m_pMessageEditor->m_hwnd, off, 0, true, false, true); + } + else { + ShowWindow(m_pChannelView->m_hwnd, SW_HIDE); + ShowWindow(m_pProfileView->m_hwnd, SW_HIDE); + UpdateWindow(m_pChannelView->m_hwnd); + UpdateWindow(m_pProfileView->m_hwnd); + ResizeWindow(m_pMessageList->m_hwnd, -off, 0, true, false, true); + ResizeWindow(m_pMessageEditor->m_hwnd, -off, 0, true, false, true); + } + + break; + } + case WM_UPDATESELECTEDGUILD: + { + SetCurrentGuildID(GetCurrentGuildID()); + break; + } + case WM_UPDATESELECTEDCHANNEL: + { + SetCurrentChannelID(GetCurrentChannelID()); + break; + } + case WM_UPDATECHANACKS: + { + Snowflake* sfs = (Snowflake*)lParam; + + Snowflake channelID = sfs[0]; + Snowflake messageID = sfs[1]; + + Channel* pChan = GetDiscordInstance()->GetChannelGlobally(channelID); + + // Update the icon for the specific guild + m_pGuildLister->RedrawIconForGuild(pChan->m_parentGuild); + + // Get the channel as is in the current guild; if found, + // update the channel view's ack status + Guild* pGuild = GetDiscordInstance()->GetGuild(GetCurrentGuildID()); + if (!pGuild) break; + + pChan = pGuild->GetChannel(channelID); + if (!pChan) + break; + + m_pChannelView->UpdateAcknowledgement(channelID); + if (m_pMessageList->GetCurrentChannelID() == channelID) + m_pMessageList->SetLastViewedMessage(messageID, true); + break; + } + case WM_UPDATEMEMBERLIST: + { + m_pMemberList->SetGuild(GetCurrentGuildID()); + m_pMemberList->Update(); + break; + } + case WM_UPDATECHANLIST: + { + // repaint the channel view + m_pChannelView->ClearChannels(); + + // get the current guild + Guild* pGuild = GetDiscordInstance()->GetGuild(GetCurrentGuildID()); + if (!pGuild) break; + + m_pChannelView->SetMode(pGuild->m_snowflake == 0); + + // if the channels haven't been fetched yet + if (!pGuild->m_bChannelsLoaded) + { + pGuild->RequestFetchChannels(); + } + else + { + for (auto& ch : pGuild->m_channels) + m_pChannelView->AddChannel(ch); + for (auto& ch : pGuild->m_channels) + m_pChannelView->RemoveCategoryIfNeeded(ch); + + m_pChannelView->CommitChannels(); + } + + break; + } + // wParam = request code, lParam = Request* + case WM_REQUESTDONE: + { + NetRequest cloneReq = * (NetRequest*) lParam; + + // Round-trip time should probably be low to avoid stalling. + if (InSendMessage()) + ReplyMessage(0); + + GetDiscordInstance()->HandleRequest(&cloneReq); + break; + } + case WM_ADDMESSAGE: + { + AddMessageParams* pParms = (AddMessageParams*)lParam; + + GetMessageCache()->AddMessage(pParms->channel, pParms->msg); + + Channel* pChan = GetDiscordInstance()->GetChannel(pParms->channel); + if (!pChan) + break; + + if (pChan->IsDM()) { + if (GetDiscordInstance()->ResortChannels(pChan->m_parentGuild)) + SendMessage(m_hwnd, WM_UPDATECHANLIST, 0, 0); + } + + if (m_pMessageList->GetCurrentChannelID() == pParms->channel) + m_pMessageList->AddMessage(pParms->msg.m_snowflake, GetForegroundWindow() == hWnd); + + OnStopTyping(pParms->channel, pParms->msg.m_author_snowflake); + break; + } + case WM_UPDATEMESSAGE: + { + AddMessageParams* pParms = (AddMessageParams*)lParam; + + GetMessageCache()->EditMessage(pParms->channel, pParms->msg); + + if (m_pMessageList->GetCurrentChannelID() == pParms->channel) + m_pMessageList->EditMessage(pParms->msg.m_snowflake); + + break; + } + case WM_DELETEMESSAGE: + { + Snowflake sf = *(Snowflake*)lParam; + m_pMessageList->DeleteMessage(sf); + + if (sf == m_pMessageEditor->ReplyingTo()) + m_pMessageEditor->StopReply(); + + break; + } + case WM_WEBSOCKETMESSAGE: + { + if (InSendMessage()) + ReplyMessage(0); + + WebsocketMessageParams* pParm = (WebsocketMessageParams*) lParam; + DbgPrintW("RECEIVED MESSAGE: %s\n", pParm->m_payload.c_str()); + + if (GetDiscordInstance()->GetGatewayID() == pParm->m_gatewayId) + GetDiscordInstance()->HandleGatewayMessage(pParm->m_payload); + + if (GetQRCodeDialog()->GetGatewayID() == pParm->m_gatewayId) + GetQRCodeDialog()->HandleGatewayMessage(pParm->m_payload); + + delete pParm; + break; + } + case WM_REFRESHMEMBERS: + { + auto* memsToUpdate = (std::set*)lParam; + + m_pMessageList->UpdateMembers(*memsToUpdate); + m_pMemberList ->UpdateMembers(*memsToUpdate); + m_pMessageEditor->OnLoadedMemberChunk(); + + break; + } + case WM_CREATE: + { + m_hwnd = hWnd; + + m_bMemberListVisible = true; + m_bChannelListVisible = true; + ForgetSystemDPI(); + + g_ProfilePictureSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF); + + if (GetLocalSettings()->GetToken().empty()) + { + if (!LogonDialogShow()) + { + PostQuitMessage(0); + break; + } + } + + if (GetLocalSettings()->AskToCheckUpdates()) + { + bool check = MessageBox( + hWnd, + TmGetTString(IDS_CHECK_UPDATES), + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONQUESTION | MB_YESNO + ) == IDYES; + + GetLocalSettings()->SetCheckUpdates(check); + } + + if (GetLocalSettings()->CheckUpdates()) + { + UpdateChecker::StartCheckingForUpdates(); + } + + if (GetLocalSettings()->IsFirstStart()) + { + MessageBox( + m_hwnd, + TmGetTString(IDS_WELCOME_MSG), + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONINFORMATION | MB_OK + ); + } + + if (g_SendIcon) DeleteObject(g_SendIcon); + if (g_JumpIcon) DeleteObject(g_JumpIcon); + if (g_CancelIcon) DeleteObject(g_CancelIcon); + if (g_UploadIcon) DeleteObject(g_UploadIcon); + if (g_DownloadIcon) DeleteObject(g_DownloadIcon); + + bool x = NT31SimplifiedInterface(); + g_SendIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_SEND)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + g_JumpIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_JUMP)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + g_CancelIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_CANCEL)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + g_UploadIcon = x ? NULL : (HICON)ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_UPLOAD)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + + g_BotIcon = (HICON) ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_BOT)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + g_DownloadIcon = (HICON) ri::LoadImage(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_DOWNLOAD)), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); + + MessageList::InitializeClass(); + ProfileView::InitializeClass(); + GuildHeader::InitializeClass(); + GuildLister::InitializeClass(); + AutoComplete::InitializeClass(); + IMemberList::InitializeClasses(); + IChannelView::InitializeClasses(); + MessageEditor::InitializeClass(); + LoadingMessage::InitializeClass(); + + RECT rect = {}, rect2 = {}; + GetClientRect(m_hwnd, &rect); + + rect2 = rect; + rect.left += 10; + rect.top += 10; + rect.right -= 10; + rect.bottom -= 10; + + RECT rcLoading = { 0, 0, 300, 200 }; + POINT pt{ 0, 0 }; + OffsetRect(&rcLoading, rect2.right / 2 - rcLoading.right / 2, 0);// rect2.bottom / 2 - rcLoading.bottom / 2); + ClientToScreen(m_hwnd, &pt); + OffsetRect(&rcLoading, pt.x, pt.y); + + m_pStatusBar = StatusBar::Create(this); + m_pMessageList = MessageList::Create(this, NULL, &rect); + m_pProfileView = ProfileView::Create(m_hwnd, &rect, CID_PROFILEVIEW, true); + m_pGuildHeader = GuildHeader::Create(this, &rect); + m_pGuildLister = GuildLister::Create(this, &rect); + m_pMemberList = IMemberList::CreateMemberList(this, &rect); + m_pChannelView = IChannelView::CreateChannelView(this, &rect); + m_pMessageEditor = MessageEditor::Create(this, &rect); + m_pLoadingMessage = LoadingMessage::Create(m_hwnd, &rcLoading); + + if (!m_pStatusBar || + !m_pMemberList || + !m_pMessageList || + !m_pProfileView || + !m_pGuildLister || + !m_pGuildHeader || + !m_pChannelView || + !m_pMessageEditor || + !m_pLoadingMessage) + { + DbgPrintW("ERROR: A control failed to be created!"); + m_bInitFailed = true; + break; + } + + if (m_pMessageEditor) + m_pMessageEditor->SetMessageList(m_pMessageList); + + SendMessage(hWnd, WM_LOGINAGAIN, 0, 0); + PostMessage(hWnd, WM_POSTINIT, 0, 0); + break; + } + case WM_POSTINIT: + { + GetShellNotification()->Initialize(); + break; + } + case WM_CONNECTERROR: + // try it again + EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_RECONNECTTODISCORD, MF_ENABLED); + DbgPrintW("Trying to connect to websocket again in %d ms", m_TryAgainTimerInterval); + + TryConnectAgainIn(m_TryAgainTimerInterval); + m_TryAgainTimerInterval = m_TryAgainTimerInterval * 115 / 100; + if (m_TryAgainTimerInterval > 10000) + m_TryAgainTimerInterval = 10000; + + break; + + case WM_CONNECTERROR2: + case WM_CONNECTED: + { + ResetTryAgainInTime(); + if (m_TryAgainTimer) + { + KillTimer(hWnd, m_TryAgainTimer); + m_TryAgainTimer = 0; + } + + m_pLoadingMessage->Hide(); + + if (IsFromStartup() && GetLocalSettings()->GetStartMinimized()) + { + GetFrontend()->HideWindow(); + } + + EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_RECONNECTTODISCORD, MF_GRAYED); + break; + } + + case WM_CONNECTING: + { + if (!IsFromStartup() || !GetLocalSettings()->GetStartMinimized()) + m_pLoadingMessage->Show(); + + break; + } + + case WM_LOGINAGAIN: + { + if (GetDiscordInstance()->HasGatewayURL()) { + GetDiscordInstance()->StartGatewaySession(); + } + else { + GetHTTPClient()->PerformRequest( + false, + NetRequest::GET, + GetDiscordAPI() + "gateway", + DiscordRequest::GATEWAY, + 0, "", GetDiscordToken() + ); + } + + break; + } + + case WM_CLOSEBYPASSTRAY: + AddOrRemoveAppFromStartup(); + CloseCleanup(); + DestroyWindow(hWnd); + break; + + case WM_SETBROWSINGPAST: + if (wParam) + m_pMessageEditor->StartBrowsingPast(); + else + m_pMessageEditor->StopBrowsingPast(); + break; + + case WM_CLOSE: + CloseCleanup(); + + if (GetLocalSettings()->GetMinimizeToNotif() && LOBYTE(GetVersion()) >= 4) + { + GetFrontend()->HideWindow(); + return 1; + } + break; + + case WM_SIZE: { + int width = LOWORD(lParam); + int height = HIWORD(lParam); + + RECT r{ 0, 0, width, height }; + AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE); + + // Save the new size + GetLocalSettings()->SetWindowSize(r.right - r.left, r.bottom - r.top); + GetLocalSettings()->SetMaximized(wParam == SIZE_MAXIMIZED); + + ProfilePopout::Dismiss(); + ProperlySizeControls(); + break; + } + case WM_REPOSITIONEVERYTHING: + { + ProperlySizeControls(); + break; + } + case WM_MOVE: { + ProfilePopout::Dismiss(); + AutoComplete::DismissAutoCompleteWindowsIfNeeded(hWnd); + break; + } + case WM_GETMINMAXINFO: { + PMINMAXINFO pMinMaxInfo = (PMINMAXINFO) lParam; + // these are the minimums as of now! + pMinMaxInfo->ptMinTrackSize.x = ScaleByDPI(640); + pMinMaxInfo->ptMinTrackSize.y = ScaleByDPI(400); + break; + } + case WM_COMMAND: + return HandleCommand(hWnd, uMsg, wParam, lParam); + + case WM_KILLFOCUS: + GetLocalSettings()->Save(); + break; + + case WM_DESTROY: + { + if (GetDiscordInstance()) + GetDiscordInstance()->CloseGatewaySession(); + if (GetAvatarCache()) + GetAvatarCache()->WipeBitmaps(); + + SAFE_DELETE(m_pChannelView); + SAFE_DELETE(m_pGuildHeader); + SAFE_DELETE(m_pGuildLister); + SAFE_DELETE(m_pMessageList); + SAFE_DELETE(m_pProfileView); + + SAFE_DELETE(m_pStatusBar); + SAFE_DELETE(m_pMemberList); + SAFE_DELETE(m_pMessageEditor); + SAFE_DELETE(m_pLoadingMessage); + + GetShellNotification()->Deinitialize(); + + PostQuitMessage(0); + break; + } + case WM_DRAWITEM: + { + switch (wParam) + { + case CID_STATUSBAR: + m_pStatusBar->DrawItem((LPDRAWITEMSTRUCT)lParam); + break; + } + break; + } + case WM_RECREATEMEMBERLIST: + { + RECT rc{}; + GetChildRect(hWnd, m_pMemberList->m_mainHwnd, &rc); + SAFE_DELETE(m_pMemberList); + + m_pMemberList = IMemberList::CreateMemberList(this, &rc); + + WindowProc(hWnd, WM_UPDATEMEMBERLIST, 0, 0); + break; + } + case WM_INITMENUPOPUP: + { + HMENU Menu = (HMENU)wParam; + int firstMenuItem = GetMenuItemID(Menu, 0); + + switch (firstMenuItem) + { + case ID_ONLINESTATUSPLACEHOLDER_ONLINE: + { + Profile* pf = GetDiscordInstance()->GetProfile(); + if (!pf) + break; + + // It's the account status menu + CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_ONLINE, pf->m_activeStatus == STATUS_ONLINE ? MF_CHECKED : 0); + CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_IDLE, pf->m_activeStatus == STATUS_IDLE ? MF_CHECKED : 0); + CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_DONOTDISTURB, pf->m_activeStatus == STATUS_DND ? MF_CHECKED : 0); + CheckMenuItem(Menu, ID_ONLINESTATUSPLACEHOLDER_INVISIBLE, pf->m_activeStatus == STATUS_OFFLINE ? MF_CHECKED : 0); + break; + } + case IDM_INVITEPEOPLE: + { + Profile* pf = GetDiscordInstance()->GetProfile(); + Guild* pGuild = GetCurrentGuild(); + if (!pGuild || !pf) + break; + + EnableMenuItem(Menu, IDM_LEAVEGUILD, pGuild->m_ownerId == pf->m_snowflake ? MF_GRAYED : MF_ENABLED); + break; + } + } + + break; + } + case WM_SENDTOMESSAGE: + { + Snowflake sf = *((Snowflake*)lParam); + m_pMessageList->SendToMessage(sf); + break; + } + case WM_SENDMESSAGE: + { + SendMessageParams parms = *((SendMessageParams*)lParam); + + std::string msg = MakeStringFromEditData(parms.m_rawMessage); + + if (msg.empty()) + return 1; + + // send it! + if (msg.size() >= MAX_MESSAGE_SIZE) + { + MessageBox( + hWnd, + TmGetTString(IDS_MESSAGE_TOO_LONG_MSG), + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONINFORMATION | MB_OK + ); + return 1; + } + + Snowflake sf; + if (parms.m_bEdit) + return GetDiscordInstance()->EditMessage(parms.m_channel, msg, parms.m_replyTo) ? 0 : 1; + + if (!GetDiscordInstance()->SendAMessage(parms.m_channel, msg, sf, parms.m_replyTo, parms.m_bMention)) + return 1; + + SendMessageAuxParams smap; + smap.m_message = msg; + smap.m_snowflake = sf; + SendMessage(hWnd, WM_SENDMESSAGEAUX, 0, (LPARAM)&smap); + return 0; + } + case WM_SENDMESSAGEAUX: + { + SendMessageAuxParams* psmap = (SendMessageAuxParams*) lParam; + + time_t lastTime = m_pMessageList->GetLastSentMessageTime(); + Profile* pf = GetDiscordInstance()->GetProfile(); + MessagePtr m = MakeMessage(); + m->m_snowflake = psmap->m_snowflake; + m->m_author_snowflake = pf->m_snowflake; + m->m_author = pf->m_name; + m->m_avatar = pf->m_avatarlnk; + m->m_message = psmap->m_message; + m->m_type = MessageType::SENDING_MESSAGE; + m->SetTime(time(NULL)); + m->m_dateFull = "Sending..."; + m->m_dateCompact = "Sending..."; + m_pMessageList->AddMessage(m, true); + return 0; + } + case WM_FAILMESSAGE: + { + FailedMessageParams parms = *((FailedMessageParams*)lParam); + if (m_pMessageList->GetCurrentChannelID() != parms.channel) + return 0; + + m_pMessageList->OnFailedToSendMessage(parms.message); + return 0; + } + case WM_STARTTYPING: + { + TypingParams params = *((TypingParams*) lParam); + OnTyping(params.m_guild, params.m_channel, params.m_user, params.m_timestamp); + break; + } + case WM_SESSIONCLOSED: + { + TCHAR buf[512]; + WAsnprintf(buf, _countof(buf), TmGetTString(IDS_SESSION_ERROR), (int)wParam); + buf[_countof(buf) - 1] = 0; + + MessageBox( + hWnd, + buf, + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONERROR | MB_OK + ); + + EnableMenuItem(GetSubMenu(GetMenu(hWnd), 0), ID_FILE_RECONNECTTODISCORD, MF_ENABLED); + break; + } + case WM_SHOWPROFILEPOPOUT: + { + auto* pParams = (ShowProfilePopoutParams*) lParam; + ProfilePopout::DeferredShow(*pParams); + delete pParams; + break; + } + case WM_LOGGEDOUT: + { + if (wParam != 100) + { + MessageBox( + hWnd, + TmGetTString(IDS_NOTLOGGEDIN), + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONERROR | MB_OK + ); + } + + if (!LogonDialogShow()) { + PostQuitMessage(0); + break; + } + + // fallthrough + + case WM_LOGGEDOUT2: + // try again with the new token + GetHTTPClient()->StopAllRequests(); + GetAvatarCache()->WipeBitmaps(); + GetAvatarCache()->ClearProcessingRequests(); + GetProfileCache()->ClearAll(); + + if (g_pDiscordInstance) delete g_pDiscordInstance; + g_pDiscordInstance = new DiscordInstance(GetLocalSettings()->GetToken()); + + m_pChannelView->ClearChannels(); + m_pMemberList->ClearMembers(); + m_pGuildLister->Update(); + m_pGuildHeader->Update(); + m_pProfileView->Update(); + GetDiscordInstance()->GatewayClosed(CloseCode::LOG_ON_AGAIN); + break; + } + case WM_STARTREPLY: + { + Snowflake* sf = (Snowflake*)lParam; + // sf[0] - Message ID + // sf[1] - Author ID + m_pMessageEditor->StartReply(sf[0], sf[1]); + + break; + } + case WM_STARTEDITING: + { + Snowflake* sf = (Snowflake*)lParam; + m_pMessageEditor->StartEdit(*sf); + break; + } + case WM_KEYUP: { + +#ifdef _DEBUG + if (wParam == VK_F6) { + SendMessage(hWnd, WM_LOGGEDOUT2, 0, 0); + } + if (wParam == VK_F7) { + SendMessage(hWnd, WM_LOGGEDOUT, 0, 0); + } + if (wParam == VK_F8) { + GetQRCodeDialog()->Show(); + } + if (wParam == VK_F11) { + Message msg; + msg.m_author = "Test Author"; + msg.m_message = "Test message!!"; + GetNotificationManager()->OnMessageCreate(0, 1, msg); + } +#endif + + break; + } + case WM_ACTIVATE: + { + if (wParam == WA_INACTIVE && (HWND) lParam != ProfilePopout::GetHWND()) + ProfilePopout::Dismiss(); + + if (wParam == WA_INACTIVE) + AutoComplete::DismissAutoCompleteWindowsIfNeeded((HWND) lParam); + + break; + } + case WM_IMAGESAVED: + { + LPCTSTR file = (LPCTSTR) lParam; + size_t sl = _tcslen(file); + bool isExe = false; + + if (sl > 4 && ( + _tcscmp(file + sl - 4, TEXT(".exe")) == 0 || + _tcscmp(file + sl - 4, TEXT(".scr")) == 0 || + _tcscmp(file + sl - 4, TEXT(".lnk")) == 0 || + _tcscmp(file + sl - 4, TEXT(".zip")) == 0 || + _tcscmp(file + sl - 4, TEXT(".rar")) == 0 || + _tcscmp(file + sl - 4, TEXT(".7z")) == 0)) { + isExe = true; + } + + TCHAR buff[4096]; + WAsnprintf( + buff, + _countof(buff), + isExe ? TmGetTString(IDS_SAVED_STRING_EXE) : TmGetTString(IDS_SAVED_UPDATE), + file + ); + buff[_countof(buff) - 1] = 0; + + // TODO: Probably should automatically extract and install it or something + int res = MessageBox( + hWnd, + buff, + TmGetTString(IDS_PROGRAM_NAME), + MB_ICONINFORMATION | MB_YESNO + ); + + if (res == IDYES) { + ShellExecute(m_hwnd, TEXT("open"), file, NULL, NULL, SW_SHOWNORMAL); + } + + break; + } + case WM_UPDATEAVAILABLE: + { + std::string* msg = (std::string*) lParam; + auto& url = msg[0]; + auto& version = msg[1]; + + TCHAR buff[2048]; + LPTSTR tstr1 = ConvertCppStringToTString(std::string(GetAppVersionString())); + LPTSTR tstr2 = ConvertCppStringToTString(version); + WAsnprintf(buff, _countof(buff), TmGetTString(IDS_NEW_VERSION_AVAILABLE), tstr1, tstr2); + free(tstr1); + free(tstr2); + + if (MessageBox(m_hwnd, buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONINFORMATION | MB_YESNO) == IDYES) + { + size_t idx = 0, idxsave = 0; + for (; idx != url.size(); idx++) { + if (url[idx] == '/') + idxsave = idx + 1; + } + + DownloadFileDialog(m_hwnd, url, url.substr(idxsave)); + } + else + { + GetLocalSettings()->StopUpdateCheckTemporarily(); + } + + delete[] msg; + break; + } + case WM_NOTIFMANAGERCALLBACK: + { + GetShellNotification()->Callback(wParam, lParam); + break; + } + case WM_RESTOREAPP: + if (GetLocalSettings()->GetMaximized()) + GetFrontend()->MaximizeWindow(); + else + GetFrontend()->RestoreWindow(); + break; + } + + if (ShouldMirrorEventToSubViews(uMsg)) + { + for (auto& window : m_subWindows) + SendMessage(window->GetHWND(), uMsg, wParam, lParam); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +LRESULT MainWindow::HandleCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (LOWORD(wParam)) + { + case ID_FILE_PREFERENCES: + { + switch (ShowOptionsDialog()) + { + case OPTIONS_RESULT_LOGOUT: { + PostMessage(hWnd, WM_LOGGEDOUT, 100, 0); + GetDiscordInstance()->SetToken(""); + GetDiscordInstance()->ClearData(); + GetLocalSettings()->SetToken(""); + GetLocalSettings()->Save(); + m_pChannelView->ClearChannels(); + m_pMemberList->ClearMembers(); + m_pStatusBar->ClearTypers(); + m_pMessageList->ClearMessages(); + m_pGuildLister->ClearTooltips(); + return TRUE; + } + } + break; + } + case ID_FILE_RECONNECTTODISCORD: + { + if (!GetDiscordInstance()->IsGatewayConnected()) + SendMessage(hWnd, WM_LOGINAGAIN, 0, 0); + + break; + } + case ID_FILE_EXIT: + { + SendMessage(hWnd, WM_CLOSEBYPASSTRAY, 0, 0); + return 0; + } + case ID_HELP_ABOUTDISCORDMESSENGER: + { + About(); + break; + } + case ID_FILE_STOPALLSPEECH: + { + TextToSpeech::StopAll(); + break; + } + case ID_ONLINESTATUSPLACEHOLDER_ONLINE: + { + GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_ONLINE); + break; + } + case ID_ONLINESTATUSPLACEHOLDER_IDLE: + { + GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_IDLE); + break; + } + case ID_ONLINESTATUSPLACEHOLDER_DONOTDISTURB: + { + GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_DND); + break; + } + case ID_ONLINESTATUSPLACEHOLDER_INVISIBLE: + { + GetDiscordInstance()->SetActivityStatus(eActiveStatus::STATUS_OFFLINE); + break; + } + case IDM_LEAVEGUILD: // Server > Leave Server + { + m_pGuildLister->AskLeave(GetCurrentGuildID()); + break; + } + // Accelerators + case IDA_SEARCH: + case ID_ACTIONS_SEARCH: + { + // TODO + //GetWebsocketClient()->Close(GetDiscordInstance()->GetGatewayID(), websocketpp::close::status::normal); + DbgPrintW("Search!"); + break; + } + case IDA_QUICKSWITCHER: + case ID_ACTIONS_QUICKSWITCHER: + { + QuickSwitcher::ShowDialog(); + break; + } + case IDA_PASTE: + { + HWND focus = GetFocus(); + + if (IsChild(m_hwnd, focus)) + { + ShowPasteFileDialog(m_pMessageEditor->m_edit_hwnd); + } + else + { + // maybe it's pasted into one of the children + for (auto& window : m_subWindows) + { + if (IsChild(window->GetHWND(), focus)) + SendMessage(window->GetHWND(), WM_COMMAND, wParam, lParam); + } + } + break; + } + + case ID_NOTIFICATION_SHOW: + SendMessage(m_hwnd, WM_RESTOREAPP, 0, 0); + break; + + case ID_NOTIFICATION_EXIT: + SendMessage(m_hwnd, WM_CLOSEBYPASSTRAY, 0, 0); + break; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + + +void MainWindow::SetHeartbeatInterval(int timeMs) +{ + if (m_HeartbeatTimerInterval == timeMs) + return; + + assert(timeMs > 100 || timeMs == 0); + + if (m_HeartbeatTimer != 0) + { + KillTimer(m_hwnd, m_HeartbeatTimer); + m_HeartbeatTimer = 0; + } + + if (timeMs != 0) + { + m_HeartbeatTimer = SetTimer(m_hwnd, 0, timeMs, OnHeartbeatTimer); + } +} + +void MainWindow::OnHeartbeatTimer(HWND hWnd, UINT uInt, UINT_PTR uIntPtr, DWORD dWord) +{ + // Don't care about the parameters, tell discord instance to send a heartbeat + GetDiscordInstance()->SendHeartbeat(); +} + +void MainWindow::OnTyping(Snowflake guildID, Snowflake channelID, Snowflake userID, time_t timeStamp) +{ + TypingInfo& ti = m_typingInfo[channelID]; + + TypingUser tu; + tu.m_startTimeMS = timeStamp * 1000LL; + tu.m_key = userID; + tu.m_name = GetProfileCache()->LookupProfile(userID, "", "", "", false)->GetName(guildID); + ti.m_typingUsers[userID] = tu; + + // Send an update to the message editor + if (channelID == GetCurrentChannelID()) + { + if (userID == GetDiscordInstance()->GetUserID()) { + // Ignore typing start from ourselves for the lower bar + return; + } + + m_pStatusBar->AddTypingName(userID, timeStamp, tu.m_name); + } +} + +void MainWindow::OnStopTyping(Snowflake channelID, Snowflake userID) +{ + m_typingInfo[channelID].m_typingUsers[userID].m_startTimeMS = 0; + + if (channelID == GetCurrentChannelID()) + m_pStatusBar->RemoveTypingName(userID); +} + +void MainWindow::CreateGuildSubWindow(Snowflake guildId, Snowflake channelId) +{ + auto window = std::make_shared(guildId, channelId); + if (window->InitFailed()) { + DbgPrintW("Failed to create guild sub window!"); + return; + } + + m_subWindows.push_back(window); +} + +void MainWindow::CloseSubWindowByViewID(int viewID) +{ + for (auto& window : m_subWindows) + { + if (window->GetChatView()->GetID() == viewID) + window->Close(); + } +} + +void MainWindow::Close() +{ + DbgPrintW("To close the main window, you have to press the X"); +} + +void MainWindow::OnClosedWindow(ChatWindow* ptr) +{ + for (auto iter = m_subWindows.begin(); + iter != m_subWindows.end(); + ++iter) + { + if (iter->get() == ptr) + { + m_subWindows.erase(iter); + return; + } + } +} + +void MainWindow::MirrorMessageToSubViewByChannelID(Snowflake channelId, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + for (auto& window : m_subWindows) + { + if (window->GetCurrentChannelID() == channelId) + { + SendMessage(window->GetHWND(), uMsg, wParam, lParam); + return; + } + } +} + +TypingInfo& MainWindow::GetTypingInfo(Snowflake sf) +{ + return m_typingInfo[sf]; +} + +void MainWindow::SetCurrentGuildID(Snowflake sf) +{ + if (m_lastGuildID == sf) + return; + + bool wasVisible = m_bMemberListVisible; + if (m_lastGuildID == 0 && sf != 0) + { + m_bMemberListVisible = m_bMemberListVisibleBackup; + } + else if (m_lastGuildID != 0 && sf == 0) + { + m_bMemberListVisibleBackup = m_bMemberListVisible; + m_bMemberListVisible = false; + } + + m_lastGuildID = sf; + + ChatWindow::SetCurrentGuildID(sf); + + // repaint the guild lister + m_pGuildLister->UpdateSelected(); + m_pGuildHeader->Update(); + + if (m_bMemberListVisible != wasVisible) + UpdateMemberListVisibility(); + + SendMessage(m_hwnd, WM_UPDATECHANLIST, 0, 0); + SendMessage(m_hwnd, WM_UPDATEMEMBERLIST, 0, 0); +} + +void MainWindow::SetCurrentChannelID(Snowflake channID) +{ + if (m_lastChannelID == channID) + return; + + m_lastChannelID = channID; + + Channel* pChan = GetDiscordInstance()->GetChannelGlobally(channID); + if (!pChan) + return; + + Guild* pGuild = GetDiscordInstance()->GetGuild(pChan->m_parentGuild); + if (!pGuild) + return; + + Snowflake guildID = pGuild->m_snowflake; + SetCurrentGuildID(guildID); + + ChatWindow::SetCurrentChannelID(channID); + + m_pMessageEditor->StopReply(); + m_pMessageEditor->StopEdit(); + m_pMessageEditor->StopBrowsingPast(); + m_pMessageEditor->Layout(); + + m_pChannelView->OnUpdateSelectedChannel(channID); + + // repaint the message view + m_pMessageList->ClearMessages(); + m_pMessageList->SetGuild(guildID); + m_pMessageList->SetChannel(channID); + + m_pMessageList->UpdateAllowDrop(); + + m_pMessageEditor->SetGuildID(guildID); + m_pMessageEditor->SetChannelID(channID); + + m_pGuildHeader->SetGuildID(guildID); + m_pGuildHeader->SetChannelID(channID); + + UpdateMainWindowTitle(); + + if (IsWindowActive(m_hwnd)) + SetFocus(m_pMessageEditor->m_edit_hwnd); + + if (!m_pMessageList->GetCurrentChannel()) + { + InvalidateRect(m_pMessageList->m_hwnd, NULL, TRUE); + InvalidateRect(m_pGuildHeader->m_hwnd, NULL, FALSE); + return; + } + + GetDiscordInstance()->HandledChannelSwitch(); + + m_pMessageList->RefetchMessages(m_pMessageList->GetMessageSentTo()); + + InvalidateRect(m_pMessageList->m_hwnd, NULL, MessageList::MayErase()); + m_pGuildHeader->Update(); + m_pMessageEditor->UpdateTextBox(); + + m_pStatusBar->SetChannelID(channID); + + // Update the message editor's typing indicators + m_pStatusBar->ClearTypers(); + + Snowflake myUserID = GetDiscordInstance()->GetUserID(); + TypingInfo& ti = m_typingInfo[channID]; + for (auto& tu : ti.m_typingUsers) { + if (tu.second.m_key == myUserID) + continue; + + m_pStatusBar->AddTypingName(tu.second.m_key, tu.second.m_startTimeMS / 1000ULL, tu.second.m_name); + } +} + +LRESULT MainWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + return GetMainWindow()->WindowProc(hWnd, uMsg, wParam, lParam); +} + +void MainWindow::TryAgainTimer(HWND hWnd, UINT uMsg, UINT_PTR uTimerID, DWORD dwParam) +{ + if (uTimerID != m_TryAgainTimerId) + return; + + KillTimer(hWnd, m_TryAgainTimer); + m_TryAgainTimer = 0; + + GetDiscordInstance()->StartGatewaySession(); +} + +void CALLBACK MainWindow::TryAgainTimerStatic(HWND hWnd, UINT uMsg, UINT_PTR uTimerID, DWORD dwParam) +{ + return GetMainWindow()->TryAgainTimer(hWnd, uMsg, uTimerID, dwParam); +} + +void MainWindow::TryConnectAgainIn(int time) +{ + m_TryAgainTimer = SetTimer(m_hwnd, m_TryAgainTimerId, time, TryAgainTimerStatic); +} + +void MainWindow::ResetTryAgainInTime() +{ + m_TryAgainTimerInterval = 500; +} + +void MainWindow::UpdateMainWindowTitle() +{ + std::string windowTitle; + + Channel* pChannel = m_pMessageList->GetCurrentChannel(); + if (pChannel) + windowTitle += pChannel->GetTypeSymbol() + pChannel->m_name + " - "; + + windowTitle += TmGetString(IDS_PROGRAM_NAME); + + LPTSTR tstr = ConvertCppStringToTString(windowTitle); + SetWindowText(m_hwnd, tstr); + free(tstr); +} + +void MainWindow::ProperlySizeControls() +{ + RECT rect = {}, rect2 = {}, rcClient = {}, rcSBar = {}; + GetClientRect(m_hwnd, &rect); + int clientWidth = rect.right - rect.left; + rcClient = rect; + + int scaled10 = ScaleByDPI(10); + + rect.left += scaled10; + rect.top += scaled10; + rect.right -= scaled10; + rect.bottom -= scaled10; + + HWND hWndMsg = m_pMessageList->m_hwnd; + HWND hWndChv = m_pChannelView->m_hwnd; + HWND hWndPfv = m_pProfileView->m_hwnd; + HWND hWndGuh = m_pGuildHeader->m_hwnd; + HWND hWndGul = m_pGuildLister->m_hwnd; + HWND hWndMel = m_pMemberList->m_mainHwnd; + HWND hWndTin = m_pMessageEditor->m_hwnd; + HWND hWndStb = m_pStatusBar->m_hwnd; + + int statusBarHeight = 0; + GetChildRect(m_hwnd, hWndStb, &rcSBar); + statusBarHeight = rcSBar.bottom - rcSBar.top; + + rect.bottom -= statusBarHeight; + rect2 = rect; + + g_ProfilePictureSize = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF); + m_ChannelViewListWidth = ScaleByDPI(CHANNEL_VIEW_LIST_WIDTH); + m_BottomBarHeight = ScaleByDPI(BOTTOM_BAR_HEIGHT); + m_BottomInputHeight = ScaleByDPI(BOTTOM_INPUT_HEIGHT); + m_SendButtonWidth = ScaleByDPI(SEND_BUTTON_WIDTH); + m_SendButtonHeight = ScaleByDPI(SEND_BUTTON_HEIGHT); + m_GuildHeaderHeight = ScaleByDPI(GUILD_HEADER_HEIGHT); + m_GuildListerWidth = ScaleByDPI(PROFILE_PICTURE_SIZE_DEF + 12) + GUILD_LISTER_BORDER_SIZE * 4; + m_MemberListWidth = ScaleByDPI(MEMBER_LIST_WIDTH); + m_MessageEditorHeight = ScaleByDPI(MESSAGE_EDITOR_HEIGHT); + + if (m_pMessageEditor) + m_pMessageEditor->SetJumpPresentHeight(m_BottomBarHeight - m_MessageEditorHeight - scaled10); + + int guildListerWidth = m_GuildListerWidth; + int channelViewListWidth = m_ChannelViewListWidth; + if (GetLocalSettings()->ShowScrollBarOnGuildList()) { + int offset = GetSystemMetrics(SM_CXVSCROLL); + guildListerWidth += offset; + channelViewListWidth -= offset; + } + + bool bRepaintGuildHeader = m_GuildListerWidth2 != guildListerWidth; + m_GuildListerWidth2 = guildListerWidth; + m_ChannelViewListWidth2 = channelViewListWidth; + + // May need to do some adjustments. + bool bFullRepaintProfileView = false; + const int minFullWidth = ScaleByDPI(800); + if (clientWidth < minFullWidth) + { + bFullRepaintProfileView = true; + + int widthOfAll3Things = + clientWidth + - scaled10 * 3 /* Left edge, Right edge, Between GuildLister and the group */ + - scaled10 * 2 /* Between channelview and messageview, between messageview and memberlist */ + - m_GuildListerWidth2; /* The guild list itself. So just the channelview, messageview and memberlist summed */ + + int widthOfAll3ThingsAt800px = ScaleByDPI(694); + + m_ChannelViewListWidth2 = MulDiv(m_ChannelViewListWidth2, widthOfAll3Things, widthOfAll3ThingsAt800px); + m_MemberListWidth = MulDiv(m_MemberListWidth, widthOfAll3Things, widthOfAll3ThingsAt800px); + channelViewListWidth = m_ChannelViewListWidth2; + } + + bool bRepaint = true; + + // Create a message list + rect.left += guildListerWidth + scaled10; + rect.bottom -= m_MessageEditorHeight + m_pMessageEditor->ExpandedBy() + scaled10; + rect.top += m_GuildHeaderHeight; + if (m_bChannelListVisible) rect.left += channelViewListWidth + scaled10; + if (m_bMemberListVisible) rect.right -= m_MemberListWidth + scaled10; + MoveWindow(hWndMsg, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + + rect.left = rect.right + scaled10; + if (!m_bMemberListVisible) rect.left -= m_MemberListWidth; + rect.right = rect.left + m_MemberListWidth; + rect.bottom = rect2.bottom; + MoveWindow(hWndMel, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + + rect.left += guildListerWidth + scaled10; + rect.right = rect.left + channelViewListWidth; + rect.bottom -= m_BottomBarHeight; + rect.top += m_GuildHeaderHeight; + MoveWindow(hWndChv, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + + rect.left += guildListerWidth + scaled10; + rect.top = rect.bottom - m_BottomBarHeight + scaled10; + rect.right = rect.left + channelViewListWidth; + MoveWindow(hWndPfv, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); + rect = rect2; + + rect.left = scaled10 + guildListerWidth + scaled10; + rect.top = rect.bottom - m_MessageEditorHeight - m_pMessageEditor->ExpandedBy(); + if (m_bChannelListVisible) rect.left += channelViewListWidth + scaled10; + if (m_bMemberListVisible) rect.right -= m_MemberListWidth + scaled10; + int textInputHeight = rect.bottom - rect.top, textInputWidth = rect.right - rect.left; + MoveWindow(hWndTin, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + + rect.left += guildListerWidth + scaled10; + rect.bottom = rect.top + m_GuildHeaderHeight - scaled10; + MoveWindow(hWndGuh, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaintGuildHeader); + rect = rect2; + + rect.right = rect.left + guildListerWidth; + MoveWindow(hWndGul, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, bRepaint); + rect = rect2; + + // Forward the resize event to the status bar. + MoveWindow(hWndStb, 0, rcClient.bottom - statusBarHeight, rcClient.right - rcClient.left, statusBarHeight, TRUE); + // Erase the old rectangle + InvalidateRect(m_hwnd, &rcSBar, TRUE); + + if (bFullRepaintProfileView) + InvalidateRect(hWndPfv, NULL, FALSE); + + m_pStatusBar->UpdateParts(rcClient.right - rcClient.left); +} + +void MainWindow::AddOrRemoveAppFromStartup() +{ + HKEY hkey = NULL; + RegCreateKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), &hkey); + + LPCTSTR value = TmGetTString(IDS_PROGRAM_NAME); + + if (GetLocalSettings()->GetOpenOnStartup()) + { + TCHAR tPath[MAX_PATH]; + const DWORD length = GetModuleFileName(NULL, tPath, MAX_PATH); + + const std::string sPath = "\"" + MakeStringFromTString(tPath) + "\" " + std::string(g_StartupArg); + const LPTSTR finalPath = ConvertCppStringToTString(sPath); + + RegSetValueEx(hkey, value, 0, REG_SZ, (BYTE *)finalPath, (DWORD)((_tcslen(finalPath) + 1) * sizeof(TCHAR))); + } + else + { + RegDeleteValue(hkey, value); + } +} + +void MainWindow::CloseCleanup() +{ + KillImageViewer(); + ProfilePopout::Dismiss(); + AutoComplete::DismissAutoCompleteWindowsIfNeeded(m_hwnd); + m_pLoadingMessage->Hide(); + + while (!m_subWindows.empty()) + m_subWindows[0]->Close(); +} + +void MainWindow::OnUpdateAvatar(const std::string& resid) +{ + // depending on where this is, update said control + ImagePlace ip = GetAvatarCache()->GetPlace(resid); + + switch (ip.type) + { + case eImagePlace::AVATARS: + { + if (GetDiscordInstance()->GetUserID() == ip.sf) + SendMessage(m_hwnd, WM_REPAINTPROFILE, 0, 0); + + if (ProfilePopout::GetUser() == ip.sf) + SendMessage(m_hwnd, WM_UPDATEPROFILEPOPOUT, 0, 0); + + Snowflake sf = ip.sf; + SendMessage(m_hwnd, WM_UPDATEUSER, 0, (LPARAM) &sf); + break; + } + case eImagePlace::ICONS: + { + SendMessage(m_hwnd, WM_REPAINTGUILDLIST, 0, (LPARAM)&ip.sf); + break; + } + case eImagePlace::ATTACHMENTS: + { + SendMessage(m_hwnd, WM_UPDATEATTACHMENT, 0, (LPARAM) &ip); + break; + } + case eImagePlace::EMOJIS: + { + SendMessage(m_hwnd, WM_UPDATEEMOJI, 0, (LPARAM) &ip.sf); + break; + } + } +} + +int MainWindow::OnHTTPError(const std::string& url, const std::string& reasonString, bool isSSL) +{ + LPTSTR urlt = ConvertCppStringToTString(url); + LPTSTR rstt = ConvertCppStringToTString(reasonString); + LPCTSTR title; + static TCHAR buffer[8192]; + + if (isSSL) { + title = TmGetTString(IDS_SSL_ERROR_TITLE); + _tcscpy(buffer, TmGetTString(IDS_SSL_ERROR_1)); + _tcscat(buffer, urlt); + _tcscat(buffer, TEXT("\n\n")); + _tcscat(buffer, rstt); + _tcscat(buffer, TEXT("\n\n")); + _tcscat(buffer, TmGetTString(IDS_SSL_ERROR_2)); + _tcscat(buffer, TEXT("\n\n")); + _tcscat(buffer, TmGetTString(IDS_SSL_ERROR_3)); + } + else { + title = TmGetTString(IDS_CONNECT_ERROR_TITLE); + _tcscpy(buffer, TmGetTString(IDS_CONNECT_ERROR_1)); + _tcscat(buffer, urlt); + _tcscat(buffer, TEXT("\n\n")); + _tcscat(buffer, rstt); + _tcscat(buffer, TEXT("\n\n")); + _tcscat(buffer, TmGetTString(IDS_CONNECT_ERROR_2)); + _tcscat(buffer, TEXT("\n\n")); + _tcscat(buffer, TmGetTString(IDS_CONNECT_ERROR_3)); + } + + free(urlt); + free(rstt); + + size_t l = _tcslen(buffer); + return MessageBox(m_hwnd, buffer, title, (isSSL ? MB_ABORTRETRYIGNORE : MB_RETRYCANCEL) | MB_ICONWARNING); +} diff --git a/src/windows/MainWindow.hpp b/src/windows/MainWindow.hpp new file mode 100644 index 0000000..8a9f6d2 --- /dev/null +++ b/src/windows/MainWindow.hpp @@ -0,0 +1,151 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "WinUtils.hpp" +#include "StatusBar.hpp" +#include "TextManager.hpp" +#include "ChatWindow.hpp" + +extern HINSTANCE g_hInstance; + +class MessageList; +class GuildLister; +class GuildHeader; +class ProfileView; +class IMemberList; +class IChannelView; +class MessageEditor; +class LoadingMessage; +class DiscordInstance; + +struct Guild; +struct Channel; + +typedef std::shared_ptr ChatWindowPtr; + +struct TypingInfo +{ + std::map m_typingUsers; +}; + +class MainWindow : public ChatWindow +{ +public: + MainWindow(LPCTSTR pClassName, int nShowCmd); + ~MainWindow(); + + // cannot copy the main window. + MainWindow(const MainWindow& mw) = delete; + + HWND GetHWND() const override { + return m_hwnd; + } + + void SetCurrentGuildID(Snowflake sf) override; + void SetCurrentChannelID(Snowflake sf) override; + + bool InitFailed() const { return m_bInitFailed; } + + bool IsPartOfMainWindow(HWND hWnd); + + MessageEditor* GetMessageEditor() { + return m_pMessageEditor; + } + + bool IsChannelListVisible() const override { return m_bChannelListVisible; } + int GetGuildListerWidth() const override { return m_GuildListerWidth; } + bool IsMemberListVisible() const { return m_bMemberListVisible; } + + void SetHeartbeatInterval(int timeMs); + void TryConnectAgainIn(int time); + void ResetTryAgainInTime(); + void UpdateMainWindowTitle(); + void OnTyping(Snowflake guildID, Snowflake channelID, Snowflake userID, time_t timeStamp); + void OnStopTyping(Snowflake channelID, Snowflake userID); + void ProperlySizeControls(); + void AddOrRemoveAppFromStartup(); + void CloseCleanup(); + void OnUpdateAvatar(const std::string& resid); + int OnHTTPError(const std::string& url, const std::string& reasonString, bool isSSL); + void CreateGuildSubWindow(Snowflake guildId, Snowflake channelId); + void CloseSubWindowByViewID(int viewID); + void Close(); + +protected: + friend class GuildSubWindow; + friend class Frontend_Win32; + + TypingInfo& GetTypingInfo(Snowflake sf); + void OnClosedWindow(ChatWindow* ptr); + std::vector& GetSubWindows() { return m_subWindows; } + +private: + void MirrorMessageToSubViewByChannelID(Snowflake channelId, UINT uMsg, WPARAM wParam, LPARAM lParam); + void UpdateMemberListVisibility(); + + LRESULT WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void TryAgainTimer(HWND hWnd, UINT uMsg, UINT_PTR uTimerID, DWORD dwParam); + LRESULT HandleCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + static void CALLBACK OnHeartbeatTimer(HWND hWnd, UINT uInt, UINT_PTR uIntPtr, DWORD dWord); + static void CALLBACK TryAgainTimerStatic(HWND hWnd, UINT uMsg, UINT_PTR uTimerID, DWORD dwParam); + +private: + HWND m_hwnd; + WNDCLASS m_wndClass {}; + + bool m_bInitFailed = false; + + StatusBar* m_pStatusBar = nullptr; + MessageList* m_pMessageList = nullptr; + ProfileView* m_pProfileView = nullptr; + GuildHeader* m_pGuildHeader = nullptr; + GuildLister* m_pGuildLister = nullptr; + IMemberList* m_pMemberList = nullptr; + IChannelView* m_pChannelView = nullptr; + MessageEditor* m_pMessageEditor = nullptr; + LoadingMessage* m_pLoadingMessage = nullptr; + + Snowflake m_lastGuildID = 0; + Snowflake m_lastChannelID = 0; + + int m_agerCounter = 0; + int m_HeartbeatTimerInterval = 0; + int m_TryAgainTimerInterval = 100; + + UINT_PTR m_HeartbeatTimer = 0; + UINT_PTR m_TryAgainTimer = 0; + + const UINT_PTR m_TryAgainTimerId = 123456; + + bool m_bMemberListVisible = false; + bool m_bMemberListVisibleBackup = true; + bool m_bChannelListVisible = false; + + std::map m_typingInfo; + + std::vector m_subWindows; + +protected: + friend class StatusBar; + + // Proportions + int m_ChannelViewListWidth = 0; + int m_ChannelViewListWidth2 = 0; + int m_BottomBarHeight = 0; + int m_BottomInputHeight = 0; + int m_SendButtonWidth = 0; + int m_SendButtonHeight = 0; + int m_GuildHeaderHeight = 0; + int m_GuildListerWidth = 0; + int m_GuildListerWidth2 = 0; + int m_MemberListWidth = 0; + int m_MessageEditorHeight = 0; +}; + +MainWindow* GetMainWindow(); +HWND GetMainHWND(); +DiscordInstance* GetDiscordInstance(); +std::string GetDiscordToken(); diff --git a/src/windows/MemberList.cpp b/src/windows/MemberList.cpp index b17c0ee..0451cf5 100644 --- a/src/windows/MemberList.cpp +++ b/src/windows/MemberList.cpp @@ -475,11 +475,11 @@ LRESULT MemberList::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) return DefWindowProc( hWnd, uMsg, wParam, lParam ); } -MemberList* MemberList::Create(HWND hWnd, LPRECT rect) +MemberList* MemberList::Create(ChatWindow* parent, LPRECT rect) { MemberList* list = new MemberList; - - list->m_hwndParent = hWnd; + list->m_pParent = parent; + list->m_hwndParent = parent->GetHWND(); list->m_mainHwnd = CreateWindowEx( WS_EX_CLIENTEDGE, @@ -490,7 +490,7 @@ MemberList* MemberList::Create(HWND hWnd, LPRECT rect) rect->top, rect->right - rect->left, rect->bottom - rect->top, - hWnd, + parent->GetHWND(), (HMENU)CID_MEMBERLIST, g_hInstance, list diff --git a/src/windows/MemberList.hpp b/src/windows/MemberList.hpp index f458d30..cd84ad3 100644 --- a/src/windows/MemberList.hpp +++ b/src/windows/MemberList.hpp @@ -11,6 +11,7 @@ class MemberList : public IMemberList { private: HWND m_hwndParent; + ChatWindow* m_pParent = nullptr; public: HWND m_listHwnd; @@ -38,7 +39,7 @@ class MemberList : public IMemberList void StopUpdate(); public: - static MemberList* Create(HWND hWnd, LPRECT lpRect); + static MemberList* Create(ChatWindow* parent, LPRECT lpRect); static void InitializeClass(); private: diff --git a/src/windows/MemberListOld.cpp b/src/windows/MemberListOld.cpp index e055e61..d9402cd 100644 --- a/src/windows/MemberListOld.cpp +++ b/src/windows/MemberListOld.cpp @@ -2,11 +2,11 @@ WNDCLASS MemberListOld::g_memberListOldClass; -MemberListOld* MemberListOld::Create(HWND hWnd, LPRECT rect) +MemberListOld* MemberListOld::Create(ChatWindow* parent, LPRECT rect) { MemberListOld* list = new MemberListOld; - - list->m_hwndParent = hWnd; + list->m_pParent = parent; + list->m_hwndParent = parent->GetHWND(); list->m_mainHwnd = CreateWindowEx( 0, @@ -17,7 +17,7 @@ MemberListOld* MemberListOld::Create(HWND hWnd, LPRECT rect) rect->top, rect->right - rect->left, rect->bottom - rect->top, - hWnd, + parent->GetHWND(), (HMENU)CID_MEMBERLIST, g_hInstance, list diff --git a/src/windows/MemberListOld.hpp b/src/windows/MemberListOld.hpp index 5efe89e..8ae531f 100644 --- a/src/windows/MemberListOld.hpp +++ b/src/windows/MemberListOld.hpp @@ -1,6 +1,7 @@ #pragma once #include "IMemberList.hpp" +#include "ChatWindow.hpp" #define T_MEMBER_LIST_CLASS_OLD TEXT("MemberListOld") @@ -19,7 +20,7 @@ class MemberListOld : public IMemberList }; public: - static MemberListOld* Create(HWND hwnd, LPRECT lprect); + static MemberListOld* Create(ChatWindow* parent, LPRECT lprect); static void InitializeClass(); void SetGuild(Snowflake sf) override; @@ -35,6 +36,7 @@ class MemberListOld : public IMemberList HWND m_listHwnd = NULL; HWND m_hwndParent = NULL; + ChatWindow* m_pParent = nullptr; Snowflake m_guild = 0; diff --git a/src/windows/MessageEditor.cpp b/src/windows/MessageEditor.cpp index 5345429..6ad485f 100644 --- a/src/windows/MessageEditor.cpp +++ b/src/windows/MessageEditor.cpp @@ -100,7 +100,8 @@ MessageEditor::~MessageEditor() void MessageEditor::UpdateTextBox() { - bool mayType = GetDiscordInstance()->GetCurrentChannel() && GetDiscordInstance()->GetCurrentChannel()->HasPermission(PERM_SEND_MESSAGES); + auto chan = GetCurrentChannel(); + bool mayType = chan && chan->HasPermission(PERM_SEND_MESSAGES); bool wasDisabled = EnableWindow(m_edit_hwnd, mayType); EnableWindow(m_send_hwnd, mayType); @@ -143,7 +144,7 @@ void MessageEditor::UpdateCommonButtonsShown() bool MessageEditor::IsUploadingAllowed() { - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChan = GetCurrentChannel(); if (!pChan) return true; @@ -155,7 +156,6 @@ void MessageEditor::Expand(int Amount) if (Amount == 0) return; - extern MessageList* g_pMessageList; // main.cpp - This sucks, should ideally have a function for that m_expandedBy += Amount; ShowOrHideReply(m_bReplying); @@ -164,13 +164,13 @@ void MessageEditor::Expand(int Amount) GetChildRect(m_hwnd, m_send_hwnd, &rcSend); RECT rect{}, rectM{}; - GetChildRect(g_Hwnd, g_pMessageList->m_hwnd, &rect); + GetChildRect(GetParent(m_hwnd), m_pMessageList->m_hwnd, &rect); int oldBottom = rect.bottom; rect.bottom -= Amount; - MoveWindow(g_pMessageList->m_hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, FALSE); + MoveWindow(m_pMessageList->m_hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, FALSE); rectM = rect; - GetChildRect(g_Hwnd, m_hwnd, &rect); + GetChildRect(GetParent(m_hwnd), m_hwnd, &rect); rect.top -= Amount; MoveWindow(m_hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, FALSE); @@ -178,7 +178,7 @@ void MessageEditor::Expand(int Amount) RECT gap = rectM; gap.top = std::min(int(rectM.bottom) - 2, oldBottom - 2); gap.bottom = rect.top; - InvalidateRect(g_Hwnd, &gap, TRUE); + InvalidateRect(GetParent(m_hwnd), &gap, TRUE); RECT rcClient{}; GetClientRect(m_hwnd, &rcClient); @@ -208,6 +208,20 @@ bool MessageEditor::MentionRepliedUser() return Button_GetCheck(m_mentionCheck_hwnd) == BST_CHECKED; } +Guild* MessageEditor::GetCurrentGuild() +{ + return GetDiscordInstance()->GetGuild(m_guildID); +} + +Channel* MessageEditor::GetCurrentChannel() +{ + auto guild = GetCurrentGuild(); + if (!guild) + return nullptr; + + return guild->GetChannel(m_channelID); +} + void MessageEditor::TryToSendMessage() { int length = GetWindowTextLength(m_edit_hwnd); @@ -223,7 +237,7 @@ void MessageEditor::TryToSendMessage() if (ContainsAuthenticationToken(data)) { - if (MessageBox(g_Hwnd, TmGetTString(IDS_MAY_CONTAIN_TOKEN), TmGetTString(IDS_HOLD_UP_CONFIRM), MB_YESNO | MB_ICONQUESTION) != IDYES) { + if (MessageBox(GetParent(m_hwnd), TmGetTString(IDS_MAY_CONTAIN_TOKEN), TmGetTString(IDS_HOLD_UP_CONFIRM), MB_YESNO | MB_ICONQUESTION) != IDYES) { delete[] data; return; } @@ -235,9 +249,10 @@ void MessageEditor::TryToSendMessage() parms.m_bEdit = m_bEditing; parms.m_bReply = m_bReplying; parms.m_bMention = MentionRepliedUser(); + parms.m_channel = m_channelID; // send it as a message to the main window - if (!SendMessage(g_Hwnd, WM_SENDMESSAGE, 0, (LPARAM) &parms)) + if (!SendMessage(GetParent(m_hwnd), WM_SENDMESSAGE, 0, (LPARAM) &parms)) { // Message was sent, so clear the box SetWindowText(m_edit_hwnd, TEXT("")); @@ -280,7 +295,7 @@ void MessageEditor::StartReply(Snowflake messageID, Snowflake authorID) std::string userName = ""; if (pf) { - Snowflake guildID = GetDiscordInstance()->GetCurrentGuildID(); + Snowflake guildID = GetCurrentGuildID(); userName = pf->GetName(guildID); COLORREF clr = CLR_NONE; @@ -328,7 +343,7 @@ void MessageEditor::StopReply() void MessageEditor::StartEdit(Snowflake messageID) { - MessagePtr pMsg = GetMessageCache()->GetLoadedMessage(GetDiscordInstance()->GetCurrentChannelID(), messageID); + MessagePtr pMsg = GetMessageCache()->GetLoadedMessage(m_channelID, messageID); if (!pMsg) return; @@ -381,7 +396,7 @@ void MessageEditor::AutoCompleteLookup(const std::string& word, char query, std: { case ':': // EMOJI { - Guild* pGld = GetDiscordInstance()->GetCurrentGuild(); + Guild* pGld = GetCurrentGuild(); if (!pGld) break; @@ -406,7 +421,7 @@ void MessageEditor::AutoCompleteLookup(const std::string& word, char query, std: } case '#': // CHANNELS { - Guild* pGld = GetDiscordInstance()->GetCurrentGuild(); + Guild* pGld = GetCurrentGuild(); if (!pGld) break; if (pGld->m_snowflake == 0) @@ -424,7 +439,7 @@ void MessageEditor::AutoCompleteLookup(const std::string& word, char query, std: } case '@': // USERS { - Guild* pGld = GetDiscordInstance()->GetCurrentGuild(); + Guild* pGld = GetCurrentGuild(); if (!pGld) break; @@ -433,7 +448,7 @@ void MessageEditor::AutoCompleteLookup(const std::string& word, char query, std: if (pGld->m_snowflake == 0) { // can do that in DM channels, but have to do it a bit differently. - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChan = GetCurrentChannel(); if (!pChan) break; @@ -479,7 +494,7 @@ void MessageEditor::AutoCompleteLookup(const std::string& word, char query, std: break; // send a query to Discord if needed - Snowflake guildID = GetDiscordInstance()->GetCurrentGuildID(); + Snowflake guildID = GetCurrentGuildID(); if (guildID == 0) break; @@ -521,7 +536,7 @@ void MessageEditor::OnUpdateText() // \r character in the input buffer. If you need this to be exact, make it go through // the entire text every character, or count the characters. Use for speed. int length = Edit_GetTextLength(m_edit_hwnd); - SendMessage(g_Hwnd, WM_UPDATEMESSAGELENGTH, 0, (LPARAM)length); + SendMessage(GetParent(m_hwnd), WM_UPDATEMESSAGELENGTH, 0, (LPARAM)length); return; */ @@ -537,7 +552,7 @@ void MessageEditor::OnUpdateText() actualLength--; } - SendMessage(g_Hwnd, WM_UPDATEMESSAGELENGTH, 0, (LPARAM)actualLength); + SendMessage(GetParent(m_hwnd), WM_UPDATEMESSAGELENGTH, 0, (LPARAM)actualLength); m_autoComplete.Update(tchr, length); delete[] tchr; @@ -552,6 +567,11 @@ void MessageEditor::OnLoadedMemberChunk() m_autoComplete.Update(); } +void MessageEditor::SetMessageList(MessageList* messageList) +{ + m_pMessageList = messageList; +} + LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { MessageEditor* pThis = (MessageEditor*) GetWindowLongPtr(GetParent(hWnd), GWLP_USERDATA); @@ -579,7 +599,7 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l case WM_UPDATETEXTSIZE: { RECT rect{}; - GetClientRect(g_Hwnd, &rect); + GetClientRect(GetParent(pThis->m_hwnd), &rect); int maxHeight = (rect.bottom - rect.top) / 3; // Now check for expansion. @@ -612,10 +632,11 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l if (pThis->m_autoComplete.HandleCharMessage(uMsg, wParam, lParam)) return 0; - if (!GetDiscordInstance()->GetCurrentChannel()) + Channel* pChan = pThis->GetCurrentChannel(); + if (!pChan) break; - if (!GetDiscordInstance()->GetCurrentChannel()->HasPermission(PERM_SEND_MESSAGES)) + if (!pChan->HasPermission(PERM_SEND_MESSAGES)) break; if (wParam == '\r' && !m_shiftHeld) @@ -627,7 +648,7 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l // Let the edit control modify the text first. LRESULT lres = CallWindowProc(m_editWndProc, hWnd, uMsg, wParam, lParam); pThis->OnUpdateText(); - GetDiscordInstance()->Typing(); + GetDiscordInstance()->Typing(pThis->m_channelID); return lres; } } @@ -637,6 +658,9 @@ LRESULT MessageEditor::EditWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l void MessageEditor::Layout() { + if (!m_hwnd) + return; + // Re-position controls RECT rcClient{}; GetClientRect(m_hwnd, &rcClient); @@ -821,16 +845,16 @@ LRESULT MessageEditor::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPara pThis->StopReply(); break; case CID_MESSAGEUPLOAD: - UploadDialogShow(); + UploadDialogShow(pThis->m_pParent->GetHWND(), pThis->m_channelID); break; case CID_MESSAGEJUMPPRESENT: { Snowflake max = UINT64_MAX; - SendMessage(g_Hwnd, WM_SENDTOMESSAGE, 0, (LPARAM)&max); + SendMessage(GetParent(pThis->m_hwnd), WM_SENDTOMESSAGE, 0, (LPARAM)&max); break; } case CID_MESSAGEREPLYJUMP: { if (pThis->m_replyMessage) - SendMessage(g_Hwnd, WM_SENDTOMESSAGE, 0, (LPARAM) &pThis->m_replyMessage); + SendMessage(GetParent(pThis->m_hwnd), WM_SENDTOMESSAGE, 0, (LPARAM) &pThis->m_replyMessage); break; } } @@ -865,21 +889,22 @@ void MessageEditor::InitializeClass() RegisterClass(&wc); } -MessageEditor* MessageEditor::Create(HWND hwnd, LPRECT pRect) +MessageEditor* MessageEditor::Create(ChatWindow* parent, LPRECT pRect) { MessageEditor* newThis = new MessageEditor; + newThis->m_pParent = parent; int sendButtonWidth = 1, sendButtonHeight = 1; int width = pRect->right - pRect->left, height = pRect->bottom - pRect->top; - newThis->m_parent_hwnd = hwnd; + newThis->m_parent_hwnd = parent->GetHWND(); newThis->m_hwnd = CreateWindowEx( 0, T_MESSAGE_EDITOR_CLASS, NULL, WS_CHILD | WS_VISIBLE, pRect->left, pRect->top, width, height, - hwnd, + parent->GetHWND(), (HMENU)CID_MESSAGEEDITOR, g_hInstance, newThis diff --git a/src/windows/MessageEditor.hpp b/src/windows/MessageEditor.hpp index c4942cc..9b65bf0 100644 --- a/src/windows/MessageEditor.hpp +++ b/src/windows/MessageEditor.hpp @@ -6,11 +6,14 @@ #include "Main.hpp" #include "AutoComplete.hpp" +class MessageList; + class MessageEditor { public: HWND m_hwnd = NULL; HWND m_edit_hwnd = NULL; + ChatWindow* m_pParent = nullptr; private: HWND m_parent_hwnd = NULL; @@ -44,11 +47,15 @@ class MessageEditor COLORREF m_userNameColor = CLR_NONE; bool m_bWasUploadingAllowed = false; + Snowflake m_guildID = 0; + Snowflake m_channelID = 0; + AutoComplete m_autoComplete; bool m_bDidMemberLookUpRecently = false; Snowflake m_previousQueriesActiveOnGuild = 0; uint64_t m_lastRemoteQuery = 0; std::set m_previousQueries; + MessageList* m_pMessageList; static WNDPROC m_editWndProc; static bool m_shiftHeld; @@ -66,6 +73,7 @@ class MessageEditor void StopBrowsingPast(); void Layout(); void OnLoadedMemberChunk(); + void SetMessageList(MessageList* messageList); Snowflake ReplyingTo() const { return m_replyMessage; @@ -77,6 +85,15 @@ class MessageEditor m_jumpPresentHeight = x; } + Snowflake GetCurrentGuildID() const { return m_guildID; } + Snowflake GetCurrentChannelID() const { return m_channelID; } + + void SetGuildID(Snowflake sf) { m_guildID = sf; } + void SetChannelID(Snowflake sf) { m_channelID = sf; } + + Guild* GetCurrentGuild(); + Channel* GetCurrentChannel(); + private: void TryToSendMessage(); void Expand(int Amount); // Positive amount means up, negative means down. @@ -95,6 +112,6 @@ class MessageEditor static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static void InitializeClass(); - static MessageEditor* Create(HWND hwnd, LPRECT pRect); + static MessageEditor* Create(ChatWindow* parent, LPRECT pRect); }; diff --git a/src/windows/MessageList.cpp b/src/windows/MessageList.cpp index 83f560d..eb9949a 100644 --- a/src/windows/MessageList.cpp +++ b/src/windows/MessageList.cpp @@ -664,7 +664,7 @@ void MessageItem::Update(Snowflake guildID) m_pRepliedMessage->SetMessage(m_msg->m_pReferencedMessage->m_message); std::vector interactables; - GetDiscordInstance()->ResolveLinks(m_pRepliedMessage, interactables); + GetDiscordInstance()->ResolveLinks(m_pRepliedMessage, interactables, guildID); } else if (m_pRepliedMessage) { @@ -736,10 +736,7 @@ void MessageList::DeleteMessage(Snowflake sf) } if (iter == m_messages.rend()) - { - DbgPrintW("Message with id %lld not found to be deleted", sf); return; - } // delete it RECT messageRect = iter->m_rect; @@ -1174,6 +1171,20 @@ bool MessageList::MayErase() return GetLocalSettings()->GetMessageStyle() != MS_IMAGE; } +Guild* MessageList::GetCurrentGuild() +{ + return GetDiscordInstance()->GetGuild(m_guildID); +} + +Channel* MessageList::GetCurrentChannel() +{ + auto guild = GetCurrentGuild(); + if (!guild) + return nullptr; + + return guild->GetChannel(m_channelID); +} + void MessageList::HitTestReply(POINT pt, BOOL& hit) { auto msg = FindMessageByPointReplyRect(pt); @@ -1369,7 +1380,7 @@ void MessageList::OpenAttachment(AttachmentItem* pItem) buffer[_countof(buffer) - 1] = 0; free((void*)urlTStr); int result = MessageBox( - g_Hwnd, + GetParent(m_hwnd), buffer, GetTextManager()->GetTString(IDS_DANGEROUS_DOWNLOAD_TITLE), MB_ICONWARNING | MB_YESNO @@ -1419,7 +1430,7 @@ void MessageList::OpenInteractable(InteractableItem* pItem, MessageItem* pMsg) } case InteractableItem::LINK: case InteractableItem::EMBED_LINK: - ConfirmOpenLink(pItem->m_destination); + ConfirmOpenLink(GetParent(m_hwnd), pItem->m_destination); break; case InteractableItem::MENTION: { if (pItem->m_destination.size() < 2) @@ -1450,10 +1461,16 @@ void MessageList::OpenInteractable(InteractableItem* pItem, MessageItem* pMsg) Snowflake oldGuildID = m_guildID, oldChannelID = m_channelID; if (oldGuildID != pNewChan->m_parentGuild) - GetDiscordInstance()->OnSelectGuild(pNewChan->m_parentGuild); + { + if (m_pParent) + m_pParent->GetChatView()->OnSelectGuild(pNewChan->m_parentGuild); + } if (oldChannelID != pNewChan->m_snowflake) - GetDiscordInstance()->OnSelectChannel(pNewChan->m_snowflake); + { + if (m_pParent) + m_pParent->GetChatView()->OnSelectChannel(pNewChan->m_snowflake); + } } } @@ -1907,7 +1924,7 @@ void MessageList::DetermineMessageData( } } -void MessageList::ConfirmOpenLink(const std::string& link) +void MessageList::ConfirmOpenLink(HWND hWnd, const std::string& link) { bool trust = GetLocalSettings()->CheckTrustedDomain(link); @@ -1922,12 +1939,12 @@ void MessageList::ConfirmOpenLink(const std::string& link) if (LOBYTE(GetVersion()) >= 4) { // TODO: This actually works on NT 3.51 at first. But the second time this causes an abort - if (MessageBoxHooked(g_Hwnd, buffer, TmGetTString(IDS_HOLD_UP_CONFIRM), MB_ICONWARNING | MB_OKCANCEL, IDOK, TmGetTString(IDS_EXCITED_YES)) != IDOK) + if (MessageBoxHooked(hWnd, buffer, TmGetTString(IDS_HOLD_UP_CONFIRM), MB_ICONWARNING | MB_OKCANCEL, IDOK, TmGetTString(IDS_EXCITED_YES)) != IDOK) return; } else { - if (MessageBox(g_Hwnd, buffer, TmGetTString(IDS_HOLD_UP_CONFIRM), MB_ICONWARNING | MB_YESNO) != IDYES) + if (MessageBox(hWnd, buffer, TmGetTString(IDS_HOLD_UP_CONFIRM), MB_ICONWARNING | MB_YESNO) != IDYES) return; } } @@ -2175,7 +2192,7 @@ int MessageList::DrawMessageReply(HDC hdc, MessageItem& item, RECT& rc) item.m_pRepliedMessage->SetMessage(item.m_msg->m_pReferencedMessage->m_message); std::vector interactables; - GetDiscordInstance()->ResolveLinks(item.m_pRepliedMessage, interactables); + GetDiscordInstance()->ResolveLinks(item.m_pRepliedMessage, interactables, m_guildID); } DrawingContext dc(hdc); @@ -3155,11 +3172,11 @@ void MessageList::Paint(HDC hdc, RECT& paintRect) if (!m_bManagedByOwner) { if (index >= 100 || hasUnloadedMessagesBelow) { - PostMessage(g_Hwnd, WM_SETBROWSINGPAST, 1, 0); + PostMessage(GetParent(m_hwnd), WM_SETBROWSINGPAST, 1, 0); sent = true; } else if (!sent && index <= 70) { - PostMessage(g_Hwnd, WM_SETBROWSINGPAST, 0, 0); + PostMessage(GetParent(m_hwnd), WM_SETBROWSINGPAST, 0, 0); } } } @@ -3255,7 +3272,7 @@ void MessageList::HandleRightClickMenuCommand(int command) } case ID_DUMMYPOPUP_EDITMESSAGE: { - SendMessage(g_Hwnd, WM_STARTEDITING, 0, (LPARAM) &rightClickedMessage); + SendMessage(GetParent(m_hwnd), WM_STARTEDITING, 0, (LPARAM) &rightClickedMessage); break; } case ID_DUMMYPOPUP_COPYTEXT: @@ -3274,10 +3291,9 @@ void MessageList::HandleRightClickMenuCommand(int command) static char buffer[8192]; snprintf(buffer, sizeof buffer, TmGetString(IDS_CONFIRM_DELETE).c_str(), pMsg->m_msg->m_author.c_str(), pMsg->m_msg->m_dateFull.c_str(), pMsg->m_msg->m_message.c_str()); LPCTSTR xstr = ConvertCppStringToTString(buffer); - if (MessageBox(g_Hwnd, xstr, TmGetTString(IDS_CONFIRM_DELETE_TITLE), MB_YESNO | MB_ICONQUESTION) == IDYES) - { + + if (MessageBox(GetParent(m_hwnd), xstr, TmGetTString(IDS_CONFIRM_DELETE_TITLE), MB_YESNO | MB_ICONQUESTION) == IDYES) GetDiscordInstance()->RequestDeleteMessage(m_channelID, rightClickedMessage); - } free((void*)xstr); break; @@ -3287,12 +3303,12 @@ void MessageList::HandleRightClickMenuCommand(int command) Snowflake sf[2]; sf[0] = pMsg->m_msg->m_snowflake; sf[1] = pMsg->m_msg->m_author_snowflake; - SendMessage(g_Hwnd, WM_STARTREPLY, 0, (LPARAM)sf); + SendMessage(GetParent(m_hwnd), WM_STARTREPLY, 0, (LPARAM)sf); break; } case ID_DUMMYPOPUP_PINMESSAGE: { - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChan = GetCurrentChannel(); if (!pChan) break; @@ -3301,7 +3317,7 @@ void MessageList::HandleRightClickMenuCommand(int command) LPCTSTR xstr = ConvertCppStringToTString(buffer); - if (MessageBox(g_Hwnd, xstr, TmGetTString(IDS_CONFIRM_PIN_TITLE), MB_YESNO | MB_ICONQUESTION) == IDYES) + if (MessageBox(GetParent(m_hwnd), xstr, TmGetTString(IDS_CONFIRM_PIN_TITLE), MB_YESNO | MB_ICONQUESTION) == IDYES) { // TODO } @@ -3377,7 +3393,7 @@ LRESULT CALLBACK MessageList::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (amount != 1) { - MessageBox(g_Hwnd, TmGetTString(IDS_CANT_UPLOAD_SEVERAL), TmGetTString(IDS_PROGRAM_NAME), MB_ICONWARNING | MB_OK); + MessageBox(GetParent(pThis->m_hwnd), TmGetTString(IDS_CANT_UPLOAD_SEVERAL), TmGetTString(IDS_PROGRAM_NAME), MB_ICONWARNING | MB_OK); DragFinish(hDrop); return 0; } @@ -3408,7 +3424,7 @@ LRESULT CALLBACK MessageList::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return 0; } - UploadDialogShowWithFileName(fileName, fileTitle); + UploadDialogShowWithFileName(pThis->m_pParent->GetHWND(), pThis->m_channelID, fileName, fileTitle); DragFinish(hDrop); return 0; } @@ -3594,7 +3610,7 @@ LRESULT CALLBACK MessageList::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA RECT rectParent = {}; GetClientRect(pThis->m_hwnd, &rectParent); RECT rectParent2 = {}; - GetClientRect(g_Hwnd, &rectParent2); + GetClientRect(GetParent(pThis->m_hwnd), &rectParent2); POINT pt; pt.x = xPos; @@ -3628,7 +3644,7 @@ LRESULT CALLBACK MessageList::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // disable the Delete button if we're not the user Profile* ourPf = GetDiscordInstance()->GetProfile(); - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChan = pThis->GetCurrentChannel(); if (!pChan) break; @@ -5008,9 +5024,14 @@ void MessageList::UpdateAllowDrop() DragAcceptFiles(m_hwnd, Accept); } -MessageList* MessageList::Create(HWND hwnd, LPRECT pRect) +MessageList* MessageList::Create(ChatWindow* parent, HWND hwnd, LPRECT pRect) { + if (!hwnd) + hwnd = parent->GetHWND(); + MessageList* newThis = new MessageList; + newThis->m_pParent = parent; + int width = pRect->right - pRect->left, height = pRect->bottom - pRect->top; newThis->m_hwnd = CreateWindowEx( @@ -5018,6 +5039,11 @@ MessageList* MessageList::Create(HWND hwnd, LPRECT pRect) pRect->left, pRect->top, width, height, hwnd, (HMENU)CID_MESSAGELIST, g_hInstance, newThis ); + if (!newThis->m_hwnd) { + delete newThis; + return nullptr; + } + newThis->UpdateBackgroundBrush(); return newThis; diff --git a/src/windows/MessageList.hpp b/src/windows/MessageList.hpp index c9f735f..0617917 100644 --- a/src/windows/MessageList.hpp +++ b/src/windows/MessageList.hpp @@ -2,6 +2,7 @@ #include #include #include "Main.hpp" +#include "ChatWindow.hpp" #include "../discord/FormattedText.hpp" #define T_MESSAGE_LIST_PARENT_CLASS TEXT("MessageListParent") @@ -360,6 +361,8 @@ class MessageList HWND m_hwnd = NULL; private: + ChatWindow* m_pParent = nullptr; + UINT_PTR m_flash_timer = 0; int m_flash_counter = 0; @@ -490,7 +493,7 @@ class MessageList m_bIsTopDown = bNew; } - Snowflake GetCurrentChannel() const { + Snowflake GetCurrentChannelID() const { return m_channelID; } @@ -503,6 +506,9 @@ class MessageList return m_messageSentTo; } + Guild* GetCurrentGuild(); + Channel* GetCurrentChannel(); + void ProperlyResizeSubWindows(); int RecalcMessageSizes(bool update, int& repaintSize, Snowflake addedMessagesBeforeThisID, Snowflake addedMessagesAfterThisID); void FullRecalcAndRepaint(); @@ -524,7 +530,7 @@ class MessageList static WNDCLASS g_MsgListClass; static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static void InitializeClass(); - static MessageList* Create (HWND hwnd, LPRECT pRect); + static MessageList* Create(ChatWindow* parent, HWND hwnd, LPRECT pRect); static bool IsCompact(); static bool MayErase();// checks if InvalidateRect or *Rgn should NEVER be passed true @@ -587,7 +593,7 @@ class MessageList int& icon /* OUT */ ); - static void ConfirmOpenLink(const std::string& link); + static void ConfirmOpenLink(HWND hWnd, const std::string& link); static COLORREF DrawMentionBackground(HDC hdc, RECT& rc, COLORREF chosenBkColor); diff --git a/src/windows/NetworkerThread.cpp b/src/windows/NetworkerThread.cpp index c7d8754..6f0c786 100644 --- a/src/windows/NetworkerThread.cpp +++ b/src/windows/NetworkerThread.cpp @@ -1,6 +1,7 @@ #include "NetworkerThread.hpp" #include "WinUtils.hpp" #include "WindowMessages.hpp" +#include "MainWindow.hpp" #include "../discord/DiscordRequest.hpp" #include "../discord/LocalSettings.hpp" #include "../discord/Frontend.hpp" @@ -24,8 +25,6 @@ void LoadSystemCertsOnWindows(SSL_CTX* ctx) SSL_CTX_set_cert_store(ctx, store); } -extern HWND g_Hwnd; - static NetworkerThread::nmutex g_sslErrorMutex; static bool g_bQuittingFromSSLError; @@ -98,7 +97,7 @@ bool NetworkerThread::ProcessResult(NetRequest& req, const httplib::Result& res) const char* strarr[2]; strarr[0] = req.url.c_str(); strarr[1] = errorstr.c_str(); - int result = (int) SendMessage(g_Hwnd, WM_HTTPERROR, (WPARAM) isSSLError, (LPARAM) strarr); + int result = (int) SendMessage(GetMainHWND(), WM_HTTPERROR, (WPARAM) isSSLError, (LPARAM) strarr); if (result == IDCANCEL || result == IDABORT) { @@ -114,7 +113,7 @@ bool NetworkerThread::ProcessResult(NetRequest& req, const httplib::Result& res) PrepareQuit(); g_bQuittingFromSSLError = true; - SendMessage(g_Hwnd, WM_FORCERESTART, 0, 0); + SendMessage(GetMainHWND(), WM_FORCERESTART, 0, 0); g_sslErrorMutex.unlock(); return false; } @@ -417,7 +416,7 @@ NetworkerThread::NetworkerThread() HRESULT hr = GetLastError(); std::string str = "Could not start NetworkerThread. Discord Messenger will now close.\n\n(" + std::to_string(hr) + ") " + GetStringFromHResult(hr); LPCTSTR ctstr = ConvertCppStringToTString(str); - MessageBox(g_Hwnd, ctstr, TEXT("Discord Messenger - Fatal Error"), MB_ICONERROR | MB_OK); + MessageBox(GetMainHWND(), ctstr, TEXT("Discord Messenger - Fatal Error"), MB_ICONERROR | MB_OK); free((void*)ctstr); exit(1); } diff --git a/src/windows/NotificationViewer.cpp b/src/windows/NotificationViewer.cpp index 927aa53..a41a64e 100644 --- a/src/windows/NotificationViewer.cpp +++ b/src/windows/NotificationViewer.cpp @@ -17,7 +17,7 @@ void NotificationViewer::Show(int x, int y, bool rightJustify) m_appearXY = { x, y }; m_bRightJustify = rightJustify; - DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_NOTIFICATIONS)), g_Hwnd, DlgProc); + DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_NOTIFICATIONS)), GetMainHWND(), DlgProc); } void NotificationViewer::Initialize(HWND hWnd) @@ -48,7 +48,7 @@ void NotificationViewer::Initialize(HWND hWnd) SAFE_DELETE(m_pMessageList); - m_pMessageList = MessageList::Create(hWnd, &rect); + m_pMessageList = MessageList::Create(nullptr, hWnd, &rect); m_pMessageList->SetManagedByOwner(true); m_pMessageList->SetTopDown(true); m_pMessageList->SetGuild(0); diff --git a/src/windows/OptionsDialog.cpp b/src/windows/OptionsDialog.cpp index e38ca8f..99967e3 100644 --- a/src/windows/OptionsDialog.cpp +++ b/src/windows/OptionsDialog.cpp @@ -400,7 +400,7 @@ INT_PTR OptionsHandleCommand(HWND hwndParent, HWND hWnd, int pageNum, UINT uMsg, EnableWindow(GetDlgItem(hWnd, IDC_ACTIVE_IMAGE_BROWSE), enable); EnableWindow(GetDlgItem(hWnd, IDC_COMBO_ALIGNMENT), enable); - SendMessage(g_Hwnd, WM_MSGLISTUPDATEMODE, 0, 0); + SendMessage(GetMainHWND(), WM_MSGLISTUPDATEMODE, 0, 0); break; } case IDC_COMBO_ALIGNMENT: @@ -416,7 +416,7 @@ INT_PTR OptionsHandleCommand(HWND hwndParent, HWND hWnd, int pageNum, UINT uMsg, GetLocalSettings()->SetImageAlignment(align); if (GetLocalSettings()->GetMessageStyle() == MS_IMAGE) - SendMessage(g_Hwnd, WM_MSGLISTUPDATEMODE, 0, 0); + SendMessage(GetMainHWND(), WM_MSGLISTUPDATEMODE, 0, 0); break; } @@ -447,7 +447,7 @@ INT_PTR OptionsHandleCommand(HWND hwndParent, HWND hWnd, int pageNum, UINT uMsg, OPENFILENAME ofn{}; ofn.lStructSize = SIZEOF_OPENFILENAME_NT4; - ofn.hwndOwner = g_Hwnd; + ofn.hwndOwner = GetMainHWND(); ofn.hInstance = g_hInstance; ofn.nMaxFile = MAX_FILE; ofn.lpstrFile = buffer; @@ -463,7 +463,7 @@ INT_PTR OptionsHandleCommand(HWND hwndParent, HWND hWnd, int pageNum, UINT uMsg, std::string fileName = MakeStringFromTString(ofn.lpstrFile); GetLocalSettings()->SetImageBackgroundFileName(fileName); - SendMessage(g_Hwnd, WM_MSGLISTUPDATEMODE, 0, 0); + SendMessage(GetMainHWND(), WM_MSGLISTUPDATEMODE, 0, 0); SetDlgItemText(hWnd, IDC_ACTIVE_IMAGE_EDIT, ofn.lpstrFile); break; } @@ -471,16 +471,16 @@ INT_PTR OptionsHandleCommand(HWND hwndParent, HWND hWnd, int pageNum, UINT uMsg, case IDC_APPEARANCE_COZY: GetSettingsManager()->SetMessageCompact(false); GetSettingsManager()->FlushSettings(); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; case IDC_APPEARANCE_COMPACT: GetSettingsManager()->SetMessageCompact(true); GetSettingsManager()->FlushSettings(); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; case IDC_COMPACT_MEMBER_LIST: GetLocalSettings()->SetCompactMemberList(IsDlgButtonChecked(hWnd, IDC_COMPACT_MEMBER_LIST)); - SendMessage(g_Hwnd, WM_RECREATEMEMBERLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECREATEMEMBERLIST, 0, 0); break; } break; @@ -504,23 +504,23 @@ INT_PTR OptionsHandleCommand(HWND hwndParent, HWND hWnd, int pageNum, UINT uMsg, { case IDC_DISABLE_FORMATTING: GetLocalSettings()->SetDisableFormatting(IsDlgButtonChecked(hWnd, IDC_DISABLE_FORMATTING)); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; case IDC_USE_12HR_TIME: GetLocalSettings()->SetUse12HourTime(IsDlgButtonChecked(hWnd, IDC_USE_12HR_TIME)); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; case IDC_IMAGES_WHEN_UPLOADED: GetLocalSettings()->SetShowAttachmentImages(IsDlgButtonChecked(hWnd, IDC_IMAGES_WHEN_UPLOADED)); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; case IDC_IMAGES_WHEN_EMBEDDED: GetLocalSettings()->SetShowEmbedImages(IsDlgButtonChecked(hWnd, IDC_IMAGES_WHEN_EMBEDDED)); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; case IDC_SHOW_EMBEDS: GetLocalSettings()->SetShowEmbedContent(IsDlgButtonChecked(hWnd, IDC_SHOW_EMBEDS)); - SendMessage(g_Hwnd, WM_RECALCMSGLIST, 0, 0); + SendMessage(GetMainHWND(), WM_RECALCMSGLIST, 0, 0); break; } break; @@ -836,5 +836,5 @@ static INT_PTR CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l int ShowOptionsDialog() { - return (int) DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_OPTIONS)), g_Hwnd, DialogProc); + return (int) DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_OPTIONS)), GetMainHWND(), DialogProc); } diff --git a/src/windows/PinList.cpp b/src/windows/PinList.cpp index 6a57c60..30f92ed 100644 --- a/src/windows/PinList.cpp +++ b/src/windows/PinList.cpp @@ -68,7 +68,7 @@ void PinList::Initialize(HWND hWnd) GetDiscordInstance()->RequestPinnedMessages(m_channel); } - m_pMessageList = MessageList::Create(hWnd, &rect); + m_pMessageList = MessageList::Create(nullptr, hWnd, &rect); m_pMessageList->SetManagedByOwner(true); m_pMessageList->SetTopDown(true); m_pMessageList->SetGuild(m_guild); @@ -176,5 +176,5 @@ void PinList::Show(Snowflake channelID, Snowflake guildID, int x, int y, bool ri m_appearXY = { x, y }; m_bRightJustify = rightJustify; - DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_PINNEDMESSAGES)), g_Hwnd, &DlgProc); + DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_PINNEDMESSAGES)), GetMainHWND(), &DlgProc); } diff --git a/src/windows/ProfilePopout.cpp b/src/windows/ProfilePopout.cpp index e85b137..ec5050a 100644 --- a/src/windows/ProfilePopout.cpp +++ b/src/windows/ProfilePopout.cpp @@ -594,7 +594,7 @@ void ProfilePopout::DeferredShow(const ShowProfilePopoutParams& params) m_user = user; m_guild = guild; m_size = { 10, 10 }; - m_hwnd = CreateDialog(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_PROFILE_POPOUT)), g_Hwnd, &Proc); + m_hwnd = CreateDialog(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_PROFILE_POPOUT)), GetMainHWND(), &Proc); // calculated in WM_CREATE int wndWidth = m_size.cx; @@ -641,7 +641,7 @@ void ProfilePopout::Show(Snowflake user, Snowflake guild, int x, int y, bool bRi // N.B. The parms struct is now owned by the main window. // Don't send immediately (the message gets enqueued instead). It'd be useless to do // it like that as it ends up just calling the deferred version directly - if (!PostMessage(g_Hwnd, WM_SHOWPROFILEPOPOUT, 0, (LPARAM) parms)) + if (!PostMessage(GetMainHWND(), WM_SHOWPROFILEPOPOUT, 0, (LPARAM) parms)) delete parms; } diff --git a/src/windows/ProgressDialog.cpp b/src/windows/ProgressDialog.cpp index c594bb2..47e2a56 100644 --- a/src/windows/ProgressDialog.cpp +++ b/src/windows/ProgressDialog.cpp @@ -128,7 +128,7 @@ INT_PTR ProgressDialog::DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar Animate_Open(GetDlgItem(hWnd, IDC_PROGRESS_ANIMATE), MAKEINTRESOURCE(res)); - CenterWindow(hWnd, g_Hwnd); + CenterWindow(hWnd, GetParent(hWnd)); break; } diff --git a/src/windows/QRCodeDialog.cpp b/src/windows/QRCodeDialog.cpp index 270fde1..0803683 100644 --- a/src/windows/QRCodeDialog.cpp +++ b/src/windows/QRCodeDialog.cpp @@ -3,7 +3,7 @@ #include #include #include "QRCodeDialog.hpp" -#include "Main.hpp" +#include "MainWindow.hpp" #include "../discord/WebsocketClient.hpp" // NOTE - This is unused, probably won't be finished @@ -254,5 +254,5 @@ void QRCodeDialog::CreateRSAKey() void QRCodeDialog::Show() { - DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_QRCODELOGIN)), g_Hwnd, &QRCodeDialog::OnMessage); + DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_QRCODELOGIN)), GetMainHWND(), &QRCodeDialog::OnMessage); } diff --git a/src/windows/QuickSwitcher.cpp b/src/windows/QuickSwitcher.cpp index 97b2c01..f4b11c5 100644 --- a/src/windows/QuickSwitcher.cpp +++ b/src/windows/QuickSwitcher.cpp @@ -48,7 +48,7 @@ static std::vector g_qsItems; void QuickSwitcher::ShowDialog() { g_qsItems.clear(); - DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_QUICK_SWITCHER)), g_Hwnd, &QuickSwitcher::DialogProc); + DialogBox(g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_QUICK_SWITCHER)), GetMainHWND(), &QuickSwitcher::DialogProc); g_qsItems.clear(); } @@ -206,9 +206,9 @@ void QuickSwitcher::SwitchToChannelAtIndex(int idx) QuickSwitchItem& qsi = g_qsItems[idx]; if (qsi.m_match.IsChannel()) - GetDiscordInstance()->OnSelectGuild(qsi.m_guildID, qsi.m_match.Id()); + GetMainWindow()->GetChatView()->OnSelectGuild(qsi.m_guildID, qsi.m_match.Id()); else - GetDiscordInstance()->OnSelectGuild(qsi.m_guildID); + GetMainWindow()->GetChatView()->OnSelectGuild(qsi.m_guildID); } void QuickSwitcher::SwitchToSelectedChannel(HWND hWnd) diff --git a/src/windows/ShellNotification.cpp b/src/windows/ShellNotification.cpp index 7ca94b0..1d6b7e8 100644 --- a/src/windows/ShellNotification.cpp +++ b/src/windows/ShellNotification.cpp @@ -21,7 +21,7 @@ void ShellNotification::Initialize() ZeroMemory(&d, sizeof d); d.cbSize = NOTIFYICONDATA_V2_SIZE; - d.hWnd = g_Hwnd; + d.hWnd = GetMainHWND(); d.uID = NOTIFICATION_ID; d.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP; d.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(DMIC(IDI_ICON))); @@ -43,7 +43,7 @@ void ShellNotification::Deinitialize() ZeroMemory(&d, sizeof d); d.cbSize = NOTIFYICONDATA_V2_SIZE; d.uID = NOTIFICATION_ID; - d.hWnd = g_Hwnd; + d.hWnd = GetMainHWND(); ri::Shell_NotifyIcon(NIM_DELETE, &d); m_bInitialized = false; @@ -63,16 +63,14 @@ void ShellNotification::ShowBalloon(const std::string& titleString, const std::s return; } - if (GetLocalSettings()->FlashOnNotification() && IsIconic(g_Hwnd)) - { - FlashWindow(g_Hwnd, FALSE); - } + if (GetLocalSettings()->FlashOnNotification() && IsIconic(GetMainHWND())) + FlashWindow(GetMainHWND(), FALSE); NOTIFYICONDATA d; ZeroMemory(&d, sizeof d); d.cbSize = NOTIFYICONDATA_V2_SIZE; d.uID = NOTIFICATION_ID; - d.hWnd = g_Hwnd; + d.hWnd = GetMainHWND(); d.uFlags = NIF_INFO | NIF_REALTIME; d.dwInfoFlags = NIIF_USER; d.uTimeout = 5000; @@ -203,8 +201,8 @@ void ShellNotification::ShowContextMenu() } const HMENU popupMenu = GetSubMenu(LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_NOTIFICATION_CONTEXT)), 0); - SetForegroundWindow(g_Hwnd); - TrackPopupMenu(popupMenu, TPM_LEFTBUTTON, cursor.x, cursor.y, 0, g_Hwnd, NULL); + SetForegroundWindow(GetMainHWND()); + TrackPopupMenu(popupMenu, TPM_LEFTBUTTON, cursor.x, cursor.y, 0, GetMainHWND(), NULL); } void ShellNotification::Callback(WPARAM wParam, LPARAM lParam) @@ -237,14 +235,14 @@ void ShellNotification::Callback(WPARAM wParam, LPARAM lParam) // TODO: Don't know how to know which one we clicked DbgPrintW("Acknowledge Notification"); - ShowWindow(g_Hwnd, SW_SHOW); - SetActiveWindow(g_Hwnd); + ShowWindow(GetMainHWND(), SW_SHOW); + SetActiveWindow(GetMainHWND()); break; } case WM_LBUTTONUP: - SendMessage(g_Hwnd, WM_RESTOREAPP, 0, 0); + SendMessage(GetMainHWND(), WM_RESTOREAPP, 0, 0); break; case WM_RBUTTONUP: diff --git a/src/windows/StatusBar.cpp b/src/windows/StatusBar.cpp index 514a49a..8ca35b9 100644 --- a/src/windows/StatusBar.cpp +++ b/src/windows/StatusBar.cpp @@ -1,5 +1,6 @@ #include "StatusBar.hpp" #include "Main.hpp" +#include "GuildSubWindow.hpp" enum { IDP_NOTIFS, @@ -8,16 +9,18 @@ enum { IDP_CNTRLS }; -StatusBar* StatusBar::Create(HWND hParent) +StatusBar* StatusBar::Create(ChatWindow* parent) { StatusBar* pBar = new StatusBar; + pBar->m_pParent = parent; + pBar->m_hwnd = CreateWindowEx( 0, STATUSCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, - hParent, + parent->GetHWND(), (HMENU)CID_STATUSBAR, g_hInstance, NULL @@ -29,7 +32,7 @@ StatusBar* StatusBar::Create(HWND hParent) pBar->m_hwnd = ri::CreateStatusWindowANSI( WS_CHILD | WS_VISIBLE, "", - hParent, + parent->GetHWND(), CID_STATUSBAR ); } @@ -37,6 +40,7 @@ StatusBar* StatusBar::Create(HWND hParent) if (!pBar->m_hwnd) { delete pBar; pBar = nullptr; + return nullptr; } SetWindowFont(pBar->m_hwnd, g_MessageTextFont, TRUE); @@ -271,11 +275,6 @@ void StatusBar::DrawItem(LPDRAWITEMSTRUCT lpDIS) SetBkMode(lpDIS->hDC, mode); } -extern int g_GuildListerWidth; // all in main.cpp -extern int g_ChannelViewListWidth; -extern int g_MemberListWidth; -extern int g_SendButtonWidth; - StatusBar::~StatusBar() { if (m_hwnd) { @@ -293,13 +292,34 @@ void StatusBar::UpdateParts(int width) // Part 2 - Under the send button, character count // Part 3 - Under the member list, some controls, I don't know - int scaled10 = ScaleByDPI(10); - Widths[IDP_CNTRLS] = width; - Widths[IDP_CHRCNT] = width - g_MemberListWidth - scaled10 * 3 / 2; - Widths[IDP_TYPING] = Widths[IDP_CHRCNT] - g_SendButtonWidth - scaled10; - Widths[IDP_NOTIFS] = g_GuildListerWidth + g_ChannelViewListWidth + scaled10 * 3; + // TODO: Get rid of GetMainWindow()? Or maybe just don't create this for sub-windows? - SendMessage(m_hwnd, SB_SETPARTS, _countof(Widths), (LPARAM) Widths); + if (m_pParent == GetMainWindow()) + { + MainWindow* mainWindow = (MainWindow*) m_pParent; + int scaled10 = ScaleByDPI(10); + Widths[IDP_CNTRLS] = width; + Widths[IDP_CHRCNT] = width - mainWindow->m_MemberListWidth - scaled10 * 3 / 2; + Widths[IDP_TYPING] = Widths[IDP_CHRCNT] - mainWindow->m_SendButtonWidth - scaled10; + Widths[IDP_NOTIFS] = mainWindow->m_GuildListerWidth + mainWindow->m_ChannelViewListWidth + scaled10 * 3; + } + else + { + GuildSubWindow* subWindow = dynamic_cast(m_pParent); + if (subWindow) + { + int scaled10 = ScaleByDPI(10); + int meliWidth = subWindow->m_bMemberListVisible ? subWindow->m_MemberListWidth : 0; + int chaviWidth = subWindow->m_bChannelListVisible ? subWindow->m_ChannelViewListWidth + scaled10 : 0; + + Widths[IDP_CNTRLS] = width; + Widths[IDP_CHRCNT] = width - meliWidth - scaled10; + Widths[IDP_TYPING] = Widths[IDP_CHRCNT] - subWindow->m_SendButtonWidth - scaled10; + Widths[IDP_NOTIFS] = chaviWidth + scaled10; + } + } + + SendMessage(m_hwnd, SB_SETPARTS, _countof(Widths), (LPARAM) Widths); SendMessage(m_hwnd, SB_SETTEXT, 1 | SBT_OWNERDRAW, 0); } diff --git a/src/windows/StatusBar.hpp b/src/windows/StatusBar.hpp index 4489da4..6a6e4e2 100644 --- a/src/windows/StatusBar.hpp +++ b/src/windows/StatusBar.hpp @@ -6,6 +6,7 @@ #include #include "../discord/Snowflake.hpp" +#include "ChatWindow.hpp" #define IDT_EXPIRY (1) #define IDT_ANIMATION (2) @@ -23,7 +24,7 @@ struct TypingUser class StatusBar { public: - static StatusBar* Create(HWND hParent); + static StatusBar* Create(ChatWindow* pParent); static void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT_PTR uTimerId, DWORD dwTime); ~StatusBar(); @@ -47,6 +48,9 @@ class StatusBar void ClearTypers(); void UpdateCharacterCounter(int nChars, int nCharsMax); + Snowflake GetCurrentChannelID() { return m_channelID; } + void SetChannelID(Snowflake sf) { m_channelID = sf; } + public: HWND m_hwnd = NULL; @@ -57,4 +61,6 @@ class StatusBar int m_anim_frame_number = 0; RECT m_typing_status_rect; RECT m_typing_animation_rect; + Snowflake m_channelID; + ChatWindow* m_pParent; }; diff --git a/src/windows/TextToSpeech.cpp b/src/windows/TextToSpeech.cpp index e9d6c6c..3a2ab10 100644 --- a/src/windows/TextToSpeech.cpp +++ b/src/windows/TextToSpeech.cpp @@ -24,7 +24,7 @@ void TextToSpeech::Initialize() { std::string xstr = TmGetString(IDS_FAILED_TO_INITIALIZE_SAPI) + "\n\n(" + std::to_string((long)hr) + ") " + GetStringFromHResult(hr); LPCTSTR tstr1 = ConvertCppStringToTString(xstr); - MessageBox(g_Hwnd, tstr1, TmGetTString(IDS_FAILED_TO_INITIALIZE_SAPI_TITLE), MB_OK | MB_ICONERROR); + MessageBox(GetMainHWND(), tstr1, TmGetTString(IDS_FAILED_TO_INITIALIZE_SAPI_TITLE), MB_OK | MB_ICONERROR); free((void*)tstr1); // TODO: Let the user know that there is no text to speech. return; @@ -52,7 +52,7 @@ void TextToSpeech::Speak(const std::string& str) { std::string xstr = TmGetString(IDS_FAILED_TO_SPEAK_MESSAGE) + " \"" + str + "\":\n\n(" + std::to_string((long)hr) + ") " + GetStringFromHResult(hr); LPCTSTR tstr1 = ConvertCppStringToTString(xstr); - MessageBox(g_Hwnd, tstr1, TmGetTString(IDS_PROGRAM_NAME), MB_OK | MB_ICONERROR); + MessageBox(GetMainHWND(), tstr1, TmGetTString(IDS_PROGRAM_NAME), MB_OK | MB_ICONERROR); free((void*)tstr1); } free((void*)tstr); diff --git a/src/windows/UploadDialog.cpp b/src/windows/UploadDialog.cpp index 1cd23a8..ca71ee7 100644 --- a/src/windows/UploadDialog.cpp +++ b/src/windows/UploadDialog.cpp @@ -9,6 +9,7 @@ using Json = nlohmann::json; struct UploadDialogData { + Snowflake m_channelID = 0; LPCTSTR m_lpstrFile = nullptr; LPCTSTR m_lpstrFileTitle = nullptr; SHFILEINFO m_sfi{}; @@ -25,6 +26,11 @@ struct UploadDialogData if (m_pFileData) delete[] m_pFileData; } + + Channel* GetChannel() + { + return GetDiscordInstance()->GetChannelGlobally(m_channelID); + } }; static int g_UploadId = 1; @@ -139,7 +145,10 @@ INT_PTR CALLBACK UploadDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP { case WM_INITDIALOG: { - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam); + pData = (UploadDialogData*)lParam; + + Channel* pChan = pData->GetChannel(); if (!pChan) { EndDialog(hWnd, IDCANCEL); return TRUE; @@ -150,8 +159,6 @@ INT_PTR CALLBACK UploadDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP return TRUE; } - SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)lParam); - pData = (UploadDialogData*)lParam; pData->m_comment[0] = 0; if (pData->m_lpstrFile) @@ -194,7 +201,7 @@ INT_PTR CALLBACK UploadDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP Static_SetIcon(GetDlgItem(hWnd, IDC_FILE_ICON), pData->m_sfi.hIcon); - CenterWindow(hWnd, g_Hwnd); + CenterWindow(hWnd, GetParent(hWnd)); SetFocus(GetDlgItem(hWnd, IDC_ATTACH_COMMENT)); break; @@ -216,12 +223,12 @@ INT_PTR CALLBACK UploadDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP return FALSE; } -static void UploadDialogShowData(UploadDialogData* data) +static void UploadDialogShowData(HWND hWnd, UploadDialogData* data) { if (DialogBoxParam( g_hInstance, MAKEINTRESOURCE(DMDI(IDD_DIALOG_UPLOADFILES)), - g_Hwnd, + hWnd, UploadDialogProc, (LPARAM) data ) == IDCANCEL) @@ -233,7 +240,8 @@ static void UploadDialogShowData(UploadDialogData* data) Snowflake sf; std::string content = MakeStringFromTString(data->m_comment); - if (GetDiscordInstance()->SendMessageAndAttachmentToCurrentChannel( + if (GetDiscordInstance()->SendMessageAndAttachment( + data->m_channelID, content, sf, data->m_pFileData, @@ -248,20 +256,20 @@ static void UploadDialogShowData(UploadDialogData* data) SendMessageAuxParams smap; smap.m_message = content; smap.m_snowflake = sf; - SendMessage(g_Hwnd, WM_SENDMESSAGEAUX, 0, (LPARAM)&smap); + SendMessage(hWnd, WM_SENDMESSAGEAUX, 0, (LPARAM)&smap); } else { // TODO: Placeholder, replace - MessageBox(g_Hwnd, TmGetTString(IDS_CANNOT_UPLOAD_ATTACHMENT), TmGetTString(IDS_PROGRAM_NAME), MB_OK); + MessageBox(hWnd, TmGetTString(IDS_CANNOT_UPLOAD_ATTACHMENT), TmGetTString(IDS_PROGRAM_NAME), MB_OK); } delete data; } -bool UploadDialogCheckHasUploadRights() +bool UploadDialogCheckHasUploadRights(Snowflake channelID) { - Channel* pChan = GetDiscordInstance()->GetCurrentChannel(); + Channel* pChan = GetDiscordInstance()->GetChannelGlobally(channelID); if (!pChan) return false; @@ -272,35 +280,37 @@ bool UploadDialogCheckHasUploadRights() return true; } -void UploadDialogShowWithFileName(LPCTSTR lpstrFileName, LPCTSTR lpstrFileTitle) +void UploadDialogShowWithFileName(HWND hWnd, Snowflake channelID, LPCTSTR lpstrFileName, LPCTSTR lpstrFileTitle) { - if (!UploadDialogCheckHasUploadRights()) + if (!UploadDialogCheckHasUploadRights(channelID)) return; UploadDialogData* data = new UploadDialogData; + data->m_channelID = channelID; data->m_lpstrFile = lpstrFileName; data->m_lpstrFileTitle = lpstrFileTitle; - UploadDialogShowData(data); + UploadDialogShowData(hWnd, data); } -void UploadDialogShowWithFileData(uint8_t* fileData, size_t fileSize, LPCTSTR lpstrFileTitle) +void UploadDialogShowWithFileData(HWND hWnd, Snowflake channelID, uint8_t* fileData, size_t fileSize, LPCTSTR lpstrFileTitle) { - if (!UploadDialogCheckHasUploadRights()) + if (!UploadDialogCheckHasUploadRights(channelID)) return; UploadDialogData* data = new UploadDialogData; + data->m_channelID = channelID; data->m_lpstrFileTitle = lpstrFileTitle; data->m_fileSize = (DWORD) fileSize; data->m_pFileData = new uint8_t[fileSize]; memcpy(data->m_pFileData, fileData, fileSize); - UploadDialogShowData(data); + UploadDialogShowData(hWnd, data); } -void UploadDialogShow2() +void UploadDialogShow2(HWND hWnd, Snowflake channelID) { - if (!UploadDialogCheckHasUploadRights()) + if (!UploadDialogCheckHasUploadRights(channelID)) return; const int MAX_FILE = 4096; @@ -310,7 +320,7 @@ void UploadDialogShow2() OPENFILENAME ofn{}; ofn.lStructSize = SIZEOF_OPENFILENAME_NT4; - ofn.hwndOwner = g_Hwnd; + ofn.hwndOwner = GetMainHWND(); ofn.hInstance = g_hInstance; ofn.nMaxFile = MAX_FILE; ofn.lpstrFile = buffer; @@ -326,15 +336,19 @@ void UploadDialogShow2() } UploadDialogShowWithFileName( + hWnd, + channelID, ofn.lpstrFile, ofn.lpstrFileTitle ); } -void UploadDialogShow() +void UploadDialogShow(HWND hWnd, Snowflake channelID) { - if (!UploadDialogCheckHasUploadRights()) + if (!UploadDialogCheckHasUploadRights(channelID)) return; - PostMessage(g_Hwnd, WM_SHOWUPLOADDIALOG, 0, 0); + Snowflake* sf = new Snowflake; + *sf = channelID; + PostMessage(hWnd, WM_SHOWUPLOADDIALOG, 0, (LPARAM) sf); } diff --git a/src/windows/UploadDialog.hpp b/src/windows/UploadDialog.hpp index 9495b2b..fd340cf 100644 --- a/src/windows/UploadDialog.hpp +++ b/src/windows/UploadDialog.hpp @@ -2,8 +2,9 @@ #include #include +#include "../discord/Snowflake.hpp" -void UploadDialogShow2(); -void UploadDialogShow(); -void UploadDialogShowWithFileName(LPCTSTR fileName, LPCTSTR fileTitle); -void UploadDialogShowWithFileData(uint8_t* fileData, size_t fileSize, LPCTSTR fileTitle); +void UploadDialogShow2(HWND hWnd, Snowflake channelID); +void UploadDialogShow(HWND hWnd, Snowflake channelID); +void UploadDialogShowWithFileName(HWND hWnd, Snowflake channelID, LPCTSTR fileName, LPCTSTR fileTitle); +void UploadDialogShowWithFileData(HWND hWnd, Snowflake channelID, uint8_t* fileData, size_t fileSize, LPCTSTR fileTitle); diff --git a/src/windows/WinUtils.cpp b/src/windows/WinUtils.cpp index a47df79..bf0b85c 100644 --- a/src/windows/WinUtils.cpp +++ b/src/windows/WinUtils.cpp @@ -19,6 +19,7 @@ #include "ImageViewer.hpp" #include "TextManager.hpp" #include "ProgressDialog.hpp" +#include "MainWindow.hpp" #ifndef OLD_WINDOWS #include @@ -28,7 +29,6 @@ constexpr int DEFAULT_DPI = 96; constexpr int NORMAL_SCALE = 1000; -extern HWND g_Hwnd; // main.hpp extern HINSTANCE g_hInstance; // main.hpp void InitializeCOM() @@ -38,7 +38,7 @@ void InitializeCOM() int GetSystemDpiU() { - HWND hwnd = g_Hwnd; + HWND hwnd = GetMainHWND(); if (!hwnd) hwnd = GetDesktopWindow(); HDC hdc = GetDC(hwnd); int dpi = GetDeviceCaps(hdc, LOGPIXELSX); @@ -198,7 +198,7 @@ std::string GetStringFromHResult(HRESULT hr) void CopyStringToClipboard(const std::string& str) { - if (OpenClipboard(g_Hwnd)) + if (OpenClipboard(GetMainHWND())) { EmptyClipboard(); @@ -782,7 +782,7 @@ COLORREF GetNameColor(Profile* pf, Snowflake guild) void LaunchURL(const std::string& link) { LPTSTR tstr = ConvertCppStringToTString(link); - INT_PTR res = (INT_PTR) ShellExecute(g_Hwnd, TEXT("open"), tstr, NULL, NULL, SW_SHOWNORMAL); + INT_PTR res = (INT_PTR) ShellExecute(GetMainHWND(), TEXT("open"), tstr, NULL, NULL, SW_SHOWNORMAL); if (res > 32) { free(tstr); @@ -817,7 +817,7 @@ void LaunchURL(const std::string& link) WAsnprintf(buff, _countof(buff), TmGetTString(IDS_CANT_LAUNCH_URL_ERR), tstr, res, res); free(tstr); - MessageBox(g_Hwnd, buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); + MessageBox(GetMainHWND(), buff, TmGetTString(IDS_PROGRAM_NAME), MB_ICONERROR | MB_OK); } bool IsChildOf(HWND child, HWND of) @@ -1523,7 +1523,7 @@ bool IsIconMostlyBlack(HICON hic) uint32_t* bits = new uint32_t[bm.bmWidth * bm.bmHeight]; - HDC hdc = GetDC(g_Hwnd); + HDC hdc = GetDC(GetMainHWND()); if (GetDIBits(hdc, ii.hbmColor, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS)) { // check! @@ -1542,7 +1542,7 @@ bool IsIconMostlyBlack(HICON hic) } } - ReleaseDC(g_Hwnd, hdc); + ReleaseDC(GetMainHWND(), hdc); if (ii.hbmColor) DeleteBitmap(ii.hbmColor); if (ii.hbmMask) DeleteBitmap(ii.hbmMask); delete[] bits; diff --git a/src/windows/WindowMessages.hpp b/src/windows/WindowMessages.hpp index 947f045..9f3687b 100644 --- a/src/windows/WindowMessages.hpp +++ b/src/windows/WindowMessages.hpp @@ -68,6 +68,7 @@ enum eWmUserMsgs WM_CLOSEBYPASSTRAY, WM_SETBROWSINGPAST, WM_UPDATEAVAILABLE, // wparam=string*, lparam=string* + WM_CLOSEVIEW, // wparam = viewID WM_UPDATETEXTSIZE = WM_APP, // used by the MessageEditor WM_RESTOREAPP, diff --git a/vs/DiscordMessenger.vcxproj b/vs/DiscordMessenger.vcxproj index 01811b2..e1d95d7 100644 --- a/vs/DiscordMessenger.vcxproj +++ b/vs/DiscordMessenger.vcxproj @@ -572,8 +572,6 @@ - - @@ -586,6 +584,7 @@ + @@ -623,11 +622,13 @@ + + @@ -636,6 +637,7 @@ + @@ -670,6 +672,7 @@ + @@ -696,10 +699,12 @@ + + @@ -708,6 +713,7 @@ + diff --git a/vs/DiscordMessenger.vcxproj.filters b/vs/DiscordMessenger.vcxproj.filters index 111aed2..f75879c 100644 --- a/vs/DiscordMessenger.vcxproj.filters +++ b/vs/DiscordMessenger.vcxproj.filters @@ -671,6 +671,18 @@ Header Files\Windows + + Header Files\Windows + + + Header Files\Windows + + + Header Files\Discord + + + Header Files\Windows + @@ -865,5 +877,17 @@ Source Files\Windows + + Source Files\Windows + + + Source Files\Windows + + + Source Files\Discord + + + Source Files\Windows + \ No newline at end of file