Add "system buffer" per protocol

Now, per each account, there is a read-only chat view associated with
it, accessible through its item in the conversations list. This can be
used to place system messages, MOTDs, insignificant errors, etc.

Protocols can send text to this buffer by specifying no "chat_id" in
an IM_MESSAGE_RECEIVED message.
This commit is contained in:
Jaidyn Ann 2021-08-12 15:43:52 -05:00
parent d16f397fe6
commit 057e7fba9b
13 changed files with 168 additions and 115 deletions

View File

@ -70,7 +70,13 @@ enum im_what_code {
IM_MESSAGE_SENT = 21, IM_MESSAGE_SENT = 21,
/*! Chat message received →App /*! Chat message received →App
Requires: String "chat_id", String "user_id", String "body" */ To send a normal chat message, specify chat_id and user_id if
user_id is ommitted, the message is treated as a "system message"
printed in chat.
If chat_id is ommitted, the message is sent to the protocol's system
buffer, rather than a specific conversation.
Requires: String "body"
Allows: String "chat_id", String "user_id", String "user_name" */
IM_MESSAGE_RECEIVED = 22, IM_MESSAGE_RECEIVED = 22,
/*! Logs received →App /*! Logs received →App

View File

@ -317,7 +317,6 @@ Conversation::SetNotifyIconBitmap(BBitmap* icon)
{ {
if (icon != NULL) { if (icon != NULL) {
fIcon = icon; fIcon = icon;
GetView()->UpdateIcon();
NotifyPointer(PTR_ROOM_BITMAP, (void*)icon); NotifyPointer(PTR_ROOM_BITMAP, (void*)icon);
return true; return true;
} }

View File

@ -17,8 +17,13 @@
#include "Account.h" #include "Account.h"
#include "AppMessages.h" #include "AppMessages.h"
#include "ChatProtocolMessages.h"
#include "Conversation.h" #include "Conversation.h"
#include "ConversationAccountItem.h" #include "ConversationAccountItem.h"
#include "ConversationView.h"
#include "MainWindow.h"
#include "NotifyMessage.h"
#include "TheApp.h"
ProtocolLooper::ProtocolLooper(ChatProtocol* protocol, int64 instance) ProtocolLooper::ProtocolLooper(ChatProtocol* protocol, int64 instance)
@ -36,8 +41,10 @@ ProtocolLooper::ProtocolLooper(ChatProtocol* protocol, int64 instance)
BString name(protocol->FriendlySignature()); BString name(protocol->FriendlySignature());
name << " - " << account->Name(); name << " - " << account->Name();
SetName(name.String()); SetName(name.String());
_InitChatView();
Run(); Run();
} }
@ -65,6 +72,22 @@ ProtocolLooper::MessageReceived(BMessage* msg)
} }
ConversationView*
ProtocolLooper::GetView()
{
return fSystemChatView;
}
void
ProtocolLooper::ShowView()
{
MainWindow* win = ((TheApp*)be_app)->GetMainWindow();
win->SetConversation(NULL);
win->SetConversationView(fSystemChatView);
}
ChatProtocol* ChatProtocol*
ProtocolLooper::Protocol() ProtocolLooper::Protocol()
{ {
@ -213,7 +236,7 @@ ProtocolLooper::GetListItem()
{ {
if (fListItem == NULL) if (fListItem == NULL)
fListItem = new ConversationAccountItem(fProtocol->GetName(), fListItem = new ConversationAccountItem(fProtocol->GetName(),
fInstance); fInstance, this);
return fListItem; return fListItem;
} }
@ -228,3 +251,26 @@ ProtocolLooper::LoadCommands()
fCommands.AddItem(cmd->GetName(), cmd); fCommands.AddItem(cmd->GetName(), cmd);
} }
} }
void
ProtocolLooper::_InitChatView()
{
fSystemChatView = new ConversationView();
BMessage clear(kClearText);
fSystemChatView->MessageReceived(&clear);
BMessage init(IM_MESSAGE);
init.AddInt32("im_what", IM_MESSAGE_RECEIVED);
init.AddString("user_name", "Cardie");
init.AddString("body", B_TRANSLATE("I'm rearing to go!"));
fSystemChatView->MessageReceived(&init);
fSystemChatView->ObserveString(STR_ROOM_NAME, fProtocol->GetName());
fSystemChatView->ObserveString(STR_ROOM_SUBJECT, "System buffer");
BBitmap* icon = fProtocol->Icon();
if (icon != NULL)
fSystemChatView->ObservePointer(PTR_ROOM_BITMAP, (void*)icon);
}

View File

@ -19,6 +19,7 @@
class Contact; class Contact;
class Conversation; class Conversation;
class ConversationAccountItem; class ConversationAccountItem;
class ConversationView;
class User; class User;
@ -34,6 +35,10 @@ public:
void MessageReceived(BMessage* msg); void MessageReceived(BMessage* msg);
ConversationView*
GetView();
void ShowView();
ChatProtocol* Protocol(); ChatProtocol* Protocol();
ChatMap Conversations() const; ChatMap Conversations() const;
@ -64,6 +69,8 @@ public:
void LoadCommands(); void LoadCommands();
private: private:
void _InitChatView();
ChatProtocol* fProtocol; ChatProtocol* fProtocol;
int64 fInstance; int64 fInstance;
@ -74,6 +81,8 @@ private:
UserMap fUserMap; UserMap fUserMap;
CommandMap fCommands; CommandMap fCommands;
ConversationView*
fSystemChatView;
ConversationAccountItem* ConversationAccountItem*
fListItem; fListItem;
}; };

View File

@ -32,6 +32,7 @@
#include "Cardie.h" #include "Cardie.h"
#include "ChatProtocol.h" #include "ChatProtocol.h"
#include "ConversationInfoWindow.h" #include "ConversationInfoWindow.h"
#include "ConversationView.h"
#include "ChatProtocolMessages.h" #include "ChatProtocolMessages.h"
#include "Flags.h" #include "Flags.h"
#include "ImageCache.h" #include "ImageCache.h"
@ -461,8 +462,14 @@ Server::ImMessage(BMessage* msg)
} }
break; break;
} }
case IM_MESSAGE_SENT:
case IM_MESSAGE_RECEIVED: case IM_MESSAGE_RECEIVED:
if (msg->HasString("chat_id") == false) {
ProtocolLooper* looper = _LooperFromMessage(msg);
if (looper != NULL)
looper->GetView()->MessageReceived(msg);
return B_SKIP_MESSAGE;
}
case IM_MESSAGE_SENT:
case IM_ROOM_JOINED: case IM_ROOM_JOINED:
case IM_ROOM_CREATED: case IM_ROOM_CREATED:
case IM_ROOM_METADATA: case IM_ROOM_METADATA:
@ -992,7 +999,7 @@ Server::_EnsureConversation(BMessage* message)
if (!message || (looper = _LooperFromMessage(message)) == NULL) if (!message || (looper = _LooperFromMessage(message)) == NULL)
return NULL; return NULL;
BString chat_id = message->FindString("chat_id"); BString chat_id = message->GetString("chat_id", "");
Conversation* item = NULL; Conversation* item = NULL;
if (chat_id.IsEmpty() == false) { if (chat_id.IsEmpty() == false) {

View File

@ -5,11 +5,15 @@
#include "ConversationAccountItem.h" #include "ConversationAccountItem.h"
#include "ProtocolLooper.h"
ConversationAccountItem::ConversationAccountItem(const char* name, int64 instance)
ConversationAccountItem::ConversationAccountItem(const char* name,
int64 instance, ProtocolLooper* looper)
: :
BStringItem(name), BStringItem(name),
fInstance(instance) fInstance(instance),
fProtocolLooper(looper)
{ {
} }
@ -21,3 +25,8 @@ ConversationAccountItem::GetInstance()
} }
ProtocolLooper*
ConversationAccountItem::GetLooper()
{
return fProtocolLooper;
}

View File

@ -8,16 +8,20 @@
#include <StringItem.h> #include <StringItem.h>
class Conversation; class Conversation;
class ProtocolLooper;
class ConversationAccountItem : public BStringItem { class ConversationAccountItem : public BStringItem {
public: public:
ConversationAccountItem(const char* name, int64 instance); ConversationAccountItem(const char* name, int64 instance,
ProtocolLooper* looper);
int64 GetInstance(); int64 GetInstance();
ProtocolLooper* GetLooper();
private: private:
int64 fInstance; int64 fInstance;
ProtocolLooper* fProtocolLooper;
}; };

View File

@ -75,16 +75,22 @@ ConversationListView::MessageReceived(BMessage* msg)
switch (msg->what) { switch (msg->what) {
case kOpenSelectedChat: case kOpenSelectedChat:
{ {
ConversationItem* item; ConversationItem* citem;
ConversationAccountItem* caitem;
int32 selIndex = CurrentSelection(); int32 selIndex = CurrentSelection();
if (selIndex >= 0 if (selIndex >= 0
&& (item = (ConversationItem*)ItemAt(selIndex)) != NULL && (citem = (ConversationItem*)ItemAt(selIndex)) != NULL
&& item->OutlineLevel() == 1) && citem->OutlineLevel() == 1)
item->GetConversation()->ShowView(false, true); citem->GetConversation()->ShowView(false, true);
else if (selIndex >= 0
&& (caitem = (ConversationAccountItem*)ItemAt(selIndex))
!= NULL
&& caitem->OutlineLevel() == 0)
caitem->GetLooper()->ShowView();
break; break;
} }
default: default:
BListView::MessageReceived(msg); BListView::MessageReceived(msg);
} }
@ -95,18 +101,10 @@ void
ConversationListView::MouseDown(BPoint where) ConversationListView::MouseDown(BPoint where)
{ {
int32 selection = CurrentSelection(); int32 selection = CurrentSelection();
BOutlineListView::MouseDown(where); BOutlineListView::MouseDown(where);
int32 newSel = CurrentSelection(); int32 newSel = CurrentSelection();
// Don't allow selecting an AccountItem // Don't allow deselecting anything
if (newSel >= 0 && ItemAt(newSel)->OutlineLevel() == 0) {
Select(selection);
return;
}
// Don't allow deselecting a room
if (newSel < 0 && selection >= 0) if (newSel < 0 && selection >= 0)
Select(selection); Select(selection);
@ -158,49 +156,6 @@ ConversationListView::SortConversation(Conversation* chat)
} }
int32
ConversationListView::CountConversations()
{
int32 count = 0;
for (int32 i = 0; i < CountItems(); i++)
if (ItemAt(i)->OutlineLevel() == 1)
count++;
return count;
}
int32
ConversationListView::ConversationIndexOf(Conversation* chat)
{
ConversationItem* item = chat->GetListItem();
int32 index = IndexOf(item);
int32 chatIndex = index;
if (item == NULL || index < 0)
return -1;
for (int i = 0; i < index; i++)
if (ItemAt(i)->OutlineLevel() == 0) // If AccountItem
chatIndex--;
return chatIndex;
}
void
ConversationListView::SelectConversation(int32 index)
{
for (int32 i = 0, cindex = -1; i < CountItems(); i++) {
if (ItemAt(i)->OutlineLevel() == 1) // If ConversationItem
cindex++;
if (cindex == index) {
Select(i);
break;
}
}
}
BPopUpMenu* BPopUpMenu*
ConversationListView::_ConversationPopUp() ConversationListView::_ConversationPopUp()
{ {

View File

@ -24,10 +24,6 @@ public:
void RemoveConversation(Conversation* chat); void RemoveConversation(Conversation* chat);
void SortConversation(Conversation* chat); void SortConversation(Conversation* chat);
int32 CountConversations();
int32 ConversationIndexOf(Conversation* chat);
void SelectConversation(int32 index);
private: private:
BPopUpMenu* _ConversationPopUp(); BPopUpMenu* _ConversationPopUp();
BPopUpMenu* _BlankPopUp(); BPopUpMenu* _BlankPopUp();

View File

@ -59,7 +59,7 @@ ConversationView::AttachedToWindow()
{ {
while (fMessageQueue.IsEmpty() == false) { while (fMessageQueue.IsEmpty() == false) {
BMessage* msg = fMessageQueue.RemoveItemAt(0); BMessage* msg = fMessageQueue.RemoveItemAt(0);
ImMessage(msg); MessageReceived(msg);
} }
if (fConversation != NULL) { if (fConversation != NULL) {
if (fNameTextView->Text() != fConversation->GetName()) if (fNameTextView->Text() != fConversation->GetName())
@ -95,6 +95,9 @@ ConversationView::MessageReceived(BMessage* message)
fSendView->ScrollToOffset(0); fSendView->ScrollToOffset(0);
break; break;
} }
case kClearText:
_AppendOrEnqueueMessage(message);
break;
case IM_MESSAGE: case IM_MESSAGE:
ImMessage(message); ImMessage(message);
break; break;
@ -243,14 +246,6 @@ ConversationView::SetConversation(Conversation* chat)
} }
void
ConversationView::UpdateIcon()
{
if (fConversation != NULL && fConversation->IconBitmap() != NULL)
fIcon->SetBitmap(fConversation->IconBitmap());
}
void void
ConversationView::UpdateUserList(UserMap users) ConversationView::UpdateUserList(UserMap users)
{ {
@ -299,6 +294,21 @@ ConversationView::ObserveString(int32 what, BString str)
} }
void
ConversationView::ObservePointer(int32 what, void* ptr)
{
switch (what)
{
case PTR_ROOM_BITMAP:
{
if (ptr != NULL)
fIcon->SetBitmap((BBitmap*)ptr);
break;
}
}
}
void void
ConversationView::GetWeights(float* horizChat, float* horizList, ConversationView::GetWeights(float* horizChat, float* horizList,
float* vertChat, float* vertSend) float* vertChat, float* vertSend)
@ -401,6 +411,13 @@ ConversationView::_AppendOrEnqueueMessage(BMessage* msg)
void void
ConversationView::_AppendMessage(BMessage* msg) ConversationView::_AppendMessage(BMessage* msg)
{ {
// If ordered to clear buffer… well, I guess we can't refuse
if (msg->what == kClearText) {
fReceiveView->SetText("");
return;
}
// … else we're jamming a message into this view no matter what it takes!
BStringList user_ids, user_names, bodies; BStringList user_ids, user_names, bodies;
if (msg->FindStrings("body", &bodies) != B_OK) if (msg->FindStrings("body", &bodies) != B_OK)
return; return;

View File

@ -24,6 +24,9 @@ class User;
class UserListView; class UserListView;
const uint32 kClearText = 'CVct';
class ConversationView : public BGroupView, public Observer, public Notifier { class ConversationView : public BGroupView, public Observer, public Notifier {
public: public:
ConversationView(Conversation* chat = NULL); ConversationView(Conversation* chat = NULL);
@ -36,12 +39,11 @@ public:
Conversation* GetConversation(); Conversation* GetConversation();
void SetConversation(Conversation* chat); void SetConversation(Conversation* chat);
void UpdateIcon();
void UpdateUserList(UserMap users); void UpdateUserList(UserMap users);
void InvalidateUserList(); void InvalidateUserList();
void ObserveString(int32 what, BString str); void ObserveString(int32 what, BString str);
void ObservePointer(int32 what, void* ptr);
void GetWeights(float* horizChat, float* horizList, void GetWeights(float* horizChat, float* horizList,
float* vertChat, float* vertSend); float* vertChat, float* vertSend);

View File

@ -210,23 +210,17 @@ MainWindow::MessageReceived(BMessage* message)
} }
case APP_MOVE_UP: case APP_MOVE_UP:
{ {
if (fConversation == NULL) int32 index = fListView->CurrentSelection();
break;
int32 index = fListView->ConversationIndexOf(fConversation);
if (index > 0) if (index > 0)
fListView->SelectConversation(index - 1); fListView->Select(index - 1);
break; break;
} }
case APP_MOVE_DOWN: case APP_MOVE_DOWN:
{ {
if (fConversation == NULL) int32 index = fListView->CurrentSelection();
break; int32 count = fListView->CountItems();
int32 index = fListView->ConversationIndexOf(fConversation);
int32 count = fListView->CountConversations();
if (index < (count - 1)) if (index < (count - 1))
fListView->SelectConversation(index + 1); fListView->Select(index + 1);
break; break;
} }
case APP_REPLICANT_STATUS_SET: case APP_REPLICANT_STATUS_SET:
@ -346,6 +340,24 @@ MainWindow::WorkspaceActivated(int32 workspace, bool active)
void void
MainWindow::SetConversation(Conversation* chat) MainWindow::SetConversation(Conversation* chat)
{
fConversation = chat;
if (chat != NULL) {
SetConversationView(chat->GetView());
BString title(chat->GetName());
title << "" << APP_NAME;
SetTitle(title.String());
}
else {
SetConversationView(fBackupChatView);
SetTitle(APP_NAME);
}
}
void
MainWindow::SetConversationView(ConversationView* chatView)
{ {
// Save split weights // Save split weights
float weightChat = fRightView->ItemWeight((int32)0); float weightChat = fRightView->ItemWeight((int32)0);
@ -354,17 +366,7 @@ MainWindow::SetConversation(Conversation* chat)
fChatView->GetWeights(&horizChat, &horizList, &vertChat, &vertSend); fChatView->GetWeights(&horizChat, &horizList, &vertChat, &vertSend);
fRightView->RemoveChild(fRightView->FindView("chatView")); fRightView->RemoveChild(fRightView->FindView("chatView"));
fChatView = chatView;
if (chat != NULL) {
fChatView = chat->GetView();
fConversation = chat;
BString title(chat->GetName());
title << "" << APP_NAME;
SetTitle(title.String());
}
else
SetTitle(APP_NAME);
fRightView->AddChild(fChatView, 9); fRightView->AddChild(fChatView, 9);
@ -418,18 +420,16 @@ MainWindow::SetConversation(Conversation* chat)
void void
MainWindow::RemoveConversation(Conversation* chat) MainWindow::RemoveConversation(Conversation* chat)
{ {
int32 index = fListView->ConversationIndexOf(chat); SetConversation(NULL);
int32 index = fListView->IndexOf(chat->GetListItem());
if (index > 0) if (index > 0)
index--; index--;
fListView->RemoveConversation(chat); fListView->RemoveConversation(chat);
if (fListView->CountConversations() == 0) { if (fListView->CountItems() > 0)
fChatView = new ConversationView(); fListView->Select(index);
SetConversation(NULL);
}
else
fListView->SelectConversation(index);
_ToggleMenuItems(); _ToggleMenuItems();
} }
@ -451,7 +451,8 @@ MainWindow::_InitInterface()
// Right-side of window, Chat + Textbox // Right-side of window, Chat + Textbox
fRightView = new BSplitView(B_VERTICAL, 0); fRightView = new BSplitView(B_VERTICAL, 0);
fChatView = new ConversationView(); fBackupChatView = new ConversationView();
fChatView = fBackupChatView;
// Load weights from settings // Load weights from settings
float horizChat, horizList, vertChat, vertSend; float horizChat, horizList, vertChat, vertSend;
@ -587,7 +588,7 @@ MainWindow::_ToggleMenuItems()
BMenuItem* windowMenuItem = fMenuBar->FindItem(B_TRANSLATE("Window")); BMenuItem* windowMenuItem = fMenuBar->FindItem(B_TRANSLATE("Window"));
BMenu* windowMenu = windowMenuItem->Submenu(); BMenu* windowMenu = windowMenuItem->Submenu();
enabled = (fListView->CountConversations() > 0); enabled = (fListView->CountItems() > 0);
for (int i = 0; i < windowMenu->CountItems(); i++) for (int i = 0; i < windowMenu->CountItems(); i++)
windowMenu->ItemAt(i)->SetEnabled(enabled); windowMenu->ItemAt(i)->SetEnabled(enabled);
@ -611,8 +612,8 @@ MainWindow::_EnsureConversationItem(BMessage* msg)
_ToggleMenuItems(); _ToggleMenuItems();
} }
if (fListView->CountConversations() == 1) if (fListView->CountItems() == 1)
fListView->SelectConversation(0); fListView->Select(0);
return item; return item;
} }
return NULL; return NULL;

View File

@ -41,6 +41,7 @@ public:
bool active); bool active);
void SetConversation(Conversation* chat); void SetConversation(Conversation* chat);
void SetConversationView(ConversationView* chatView);
void RemoveConversation(Conversation* chat); void RemoveConversation(Conversation* chat);
void SortConversation(Conversation* chat); void SortConversation(Conversation* chat);
@ -75,8 +76,9 @@ private:
// Right panel, chat // Right panel, chat
BSplitView* fRightView; BSplitView* fRightView;
ConversationView* fChatView;
Conversation* fConversation; Conversation* fConversation;
ConversationView* fChatView;
ConversationView* fBackupChatView;
}; };