579 lines
14 KiB
C++
579 lines
14 KiB
C++
//////////////////////////////////////////////////
|
|
// Blabber [TalkView.cpp]
|
|
//////////////////////////////////////////////////
|
|
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
#include <malloc.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <Box.h>
|
|
#include <be_apps/NetPositive/NetPositive.h>
|
|
#include <FindDirectory.h>
|
|
#include <GridView.h>
|
|
#include <GroupLayout.h>
|
|
#include <GroupView.h>
|
|
#include <LayoutBuilder.h>
|
|
#include <Roster.h>
|
|
#include <storage/Path.h>
|
|
#include <SplitView.h>
|
|
|
|
#include "support/AppLocation.h"
|
|
|
|
#include "jabber/BlabberSettings.h"
|
|
#include "jabber/CommandMessage.h"
|
|
#include "jabber/GenericFunctions.h"
|
|
#include "jabber/JabberSpeak.h"
|
|
#include "jabber/MessageRepeater.h"
|
|
#include "jabber/Messages.h"
|
|
#include "jabber/PreferencesWindow.h"
|
|
#include "jabber/TalkListItem.h"
|
|
#include "jabber/TalkManager.h"
|
|
|
|
#include "ui/PeopleListItem.h"
|
|
#include "ui/RotateChatFilter.h"
|
|
|
|
#include "TalkView.h"
|
|
|
|
#include "gloox/rostermanager.h"
|
|
|
|
#define NOTIFICATION_CHAR "√"
|
|
|
|
|
|
TalkView::TalkView(const gloox::JID *user, string group_room,
|
|
string group_username, gloox::MessageSession* session)
|
|
: BGroupView("<talk window>", B_VERTICAL)
|
|
, _session(session)
|
|
{
|
|
_am_logging = false;
|
|
_log = NULL;
|
|
_chat_index = -1;
|
|
|
|
UserID* uid = NULL;
|
|
if (user) {
|
|
uid = JRoster::Instance()->FindUser(*user);
|
|
}
|
|
_group_room = group_room;
|
|
_group_username = group_username;
|
|
|
|
if (!IsGroupChat() && uid) {
|
|
_current_status = uid->OnlineStatus();
|
|
}
|
|
|
|
// FILE MENU
|
|
// status bar
|
|
_status_view = new StatusView();
|
|
_status_view->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
|
|
|
|
_chat = new ChatTextView("chat", B_WILL_DRAW | B_FRAME_EVENTS);
|
|
_chat_scroller = new BScrollView("chat_scroller", _chat, B_WILL_DRAW, false, true);
|
|
_chat->TargetedByScrollView(_chat_scroller);
|
|
_chat->SetWordWrap(true);
|
|
_chat->SetStylable(true);
|
|
_chat->MakeEditable(false);
|
|
|
|
BGridView* _sending = new BGridView("communicate");
|
|
|
|
// message control
|
|
rgb_color text_color = ui_color(B_PANEL_TEXT_COLOR);
|
|
BFont text_font(be_plain_font);
|
|
|
|
_message_input = new BTextView("message", &text_font, &text_color, B_WILL_DRAW);
|
|
_message_scroller = new BScrollView("message_scroller", _message_input, B_WILL_DRAW, false, false);
|
|
_message_input->TargetedByScrollView(_message_scroller);
|
|
_message_input->SetWordWrap(true);
|
|
|
|
// editing filter for messaging
|
|
_message_input->AddFilter(new EditingFilter(_message_input, this));
|
|
|
|
// send button
|
|
_send_message = new BButton("send", "\xe2\x96\xb6", new BMessage(JAB_CHAT_SENT));
|
|
_send_message->MakeDefault(true);
|
|
_send_message->SetFlat(true);
|
|
_send_message->SetExplicitSize(BSize(_send_message->StringWidth("WWWW"), B_SIZE_UNSET));
|
|
|
|
// add alt-enter note
|
|
|
|
BLayoutBuilder::Grid<>(_sending)
|
|
.Add(_message_scroller, 0, 0)
|
|
.Add(_send_message, 1, 0)
|
|
.End();
|
|
|
|
// handle splits
|
|
BGroupView* _split_talk = new BGroupView(B_VERTICAL);
|
|
_split_talk->AddChild(_chat_scroller);
|
|
_split_talk->AddChild(_sending);
|
|
|
|
_people = new BListView(NULL, B_SINGLE_SELECTION_LIST);
|
|
_people->SetExplicitMinSize(BSize(StringWidth("Firstname M. Lastname"), B_SIZE_UNSET));
|
|
_scrolled_people_pane = new BScrollView(NULL, _people, 0, false, true, B_PLAIN_BORDER);
|
|
|
|
BSplitView* _split_group_people = new BSplitView(B_HORIZONTAL);
|
|
_split_group_people->AddChild(_split_talk);
|
|
_split_group_people->AddChild(_scrolled_people_pane);
|
|
_split_group_people->SetItemWeight(0, 5, false);
|
|
_split_group_people->SetItemWeight(1, 1, false);
|
|
_split_group_people->SetSpacing(0);
|
|
|
|
if (!IsGroupChat()) {
|
|
_split_group_people->SetItemCollapsed(1, true);
|
|
_split_group_people->SetSplitterSize(0);
|
|
}
|
|
|
|
// add GUI components to BView
|
|
BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
|
|
SetLayout(layout);
|
|
layout->SetSpacing(0);
|
|
|
|
AddChild(_split_group_people);
|
|
AddChild(_status_view);
|
|
|
|
_message_input->MakeFocus(true);
|
|
|
|
// generate window title
|
|
char buffer[1024];
|
|
string user_representation;
|
|
|
|
if (!IsGroupChat()) {
|
|
// identify the user
|
|
sprintf(buffer, "your identity is %s", _group_username.c_str());
|
|
_status_view->SetMessage(buffer);
|
|
} else if (!uid || uid->UserType() == UserID::JABBER) {
|
|
// identify the user
|
|
sprintf(buffer, "your identity is %s", JabberSpeak::Instance()->CurrentLogin().c_str());
|
|
_status_view->SetMessage(buffer);
|
|
} else {
|
|
user_representation = uid->FriendlyName();
|
|
|
|
if (user_representation.empty()) {
|
|
user_representation = uid->JabberUsername();
|
|
}
|
|
|
|
}
|
|
|
|
if (!IsGroupChat() && user_representation.empty()) {
|
|
if (uid)
|
|
user_representation = uid->FriendlyName();
|
|
else
|
|
user_representation = _session->target().bare();
|
|
}
|
|
|
|
// put Session started message
|
|
// construct timestamp
|
|
string message;
|
|
message.resize(128);
|
|
time_t now = time(NULL);
|
|
struct tm *time_struct = localtime(&now);
|
|
strftime(&message[0], message.size()-1, "Session started %e %b %y [%R:%S]", time_struct);
|
|
AddToTalk("", message.c_str(), OTHER);
|
|
|
|
TalkManager::Instance()->StartWatchingAll(this);
|
|
}
|
|
|
|
|
|
TalkView::~TalkView() {
|
|
string message;
|
|
message.resize(128);
|
|
time_t now = time(NULL);
|
|
struct tm *time_struct = localtime(&now);
|
|
strftime(&message[0], message.size()-1, "Session finished %e %b %y [%R:%S]\n---", time_struct);
|
|
AddToTalk("", message.c_str(), OTHER);
|
|
|
|
// close log cleanly if it's open
|
|
if (_log) {
|
|
fclose(_log);
|
|
}
|
|
|
|
if (IsGroupChat()) {
|
|
JabberSpeak::Instance()->SendGroupUnvitation(_group_room, _group_username);
|
|
}
|
|
|
|
TalkManager::Instance()->RemoveWindow(this);
|
|
}
|
|
|
|
|
|
void TalkView::AttachedToWindow()
|
|
{
|
|
_send_message->SetTarget(this);
|
|
}
|
|
|
|
|
|
void TalkView::FrameResized(float width, float height)
|
|
{
|
|
BView::FrameResized(width, height);
|
|
|
|
BRect chat_rect = _chat->Frame();
|
|
BRect message_rect = _message_input->Frame();
|
|
|
|
chat_rect.OffsetTo(B_ORIGIN);
|
|
message_rect.OffsetTo(B_ORIGIN);
|
|
|
|
chat_rect.InsetBy(2.0, 2.0);
|
|
message_rect.InsetBy(2.0, 2.0);
|
|
|
|
_chat->SetTextRect(chat_rect);
|
|
_message_input->SetTextRect(message_rect);
|
|
|
|
_chat->Invalidate();
|
|
_chat_scroller->Invalidate();
|
|
}
|
|
|
|
|
|
void TalkView::MessageReceived(BMessage *msg) {
|
|
switch(msg->what) {
|
|
case JAB_CLOSE_TALKS:
|
|
{
|
|
RemoveSelf();
|
|
delete this;
|
|
break;
|
|
}
|
|
|
|
case B_OBSERVER_NOTICE_CHANGE:
|
|
{
|
|
// only for groupchat
|
|
if (!IsGroupChat()) {
|
|
break;
|
|
}
|
|
|
|
switch(msg->FindInt32("be:observe_orig_what"))
|
|
{
|
|
case JAB_GROUP_CHATTER_ONLINE:
|
|
{
|
|
if (GetGroupRoom() == msg->FindString("room")) {
|
|
AddGroupChatter(msg->FindString("username"),
|
|
(gloox::MUCRoomAffiliation)msg->FindInt32("affiliation"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case JAB_GROUP_CHATTER_OFFLINE: {
|
|
RemoveGroupChatter(msg->FindString("username"));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case BLAB_UPDATE_ROSTER: {
|
|
// doesn't apply to groupchat
|
|
if (!IsGroupChat()) {
|
|
break;
|
|
}
|
|
|
|
// get new status
|
|
JRoster::Instance()->Lock();
|
|
UserID* user = JRoster::Instance()->FindUser(_session->target());
|
|
if (!user) {
|
|
JRoster::Instance()->Unlock();
|
|
break;
|
|
}
|
|
UserID::online_status new_status = user->OnlineStatus();
|
|
|
|
// if we have one, check their presence
|
|
if (_current_status != new_status) {
|
|
char buffer[2048];
|
|
|
|
if (_current_status == UserID::ONLINE && new_status == UserID::OFFLINE) {
|
|
sprintf(buffer, "This user is now offline.");
|
|
AddToTalk("", buffer, OTHER);
|
|
} else if (_current_status == UserID::OFFLINE && new_status == UserID::ONLINE) {
|
|
sprintf(buffer, "This user is now online.");
|
|
AddToTalk("", buffer, OTHER);
|
|
}
|
|
}
|
|
|
|
_current_status = new_status;
|
|
|
|
JRoster::Instance()->Unlock();
|
|
|
|
break;
|
|
}
|
|
|
|
case JAB_CHAT_SENT: {
|
|
|
|
string chat_message = _message_input->Text();
|
|
|
|
// eliminate empty messages
|
|
if (chat_message.empty()) {
|
|
break;
|
|
}
|
|
|
|
if (!CommandMessage::IsCommand(chat_message) || CommandMessage::IsLegalCommand(chat_message)) {
|
|
_session->send(chat_message);
|
|
}
|
|
|
|
// user part
|
|
AddToTalk(OurRepresentation().c_str(), chat_message, LOCAL);
|
|
|
|
// GUI
|
|
_message_input->SetText("");
|
|
_message_input->MakeFocus(true);
|
|
|
|
break;
|
|
}
|
|
|
|
case JAB_CLOSE_CHAT: {
|
|
RemoveSelf();
|
|
delete this;
|
|
break;
|
|
}
|
|
|
|
case JAB_FOCUS_BUDDY: {
|
|
BlabberMainWindow::Instance()->Activate();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
string TalkView::OurRepresentation() {
|
|
// use friendly name if you have it
|
|
string user = JabberSpeak::Instance()->CurrentRealName();
|
|
|
|
// and if not :)
|
|
if (user.empty()) {
|
|
user = JabberSpeak::Instance()->CurrentLogin();
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
|
|
bool TalkView::AddChatCommand(string command) {
|
|
rgb_color message_color = ui_color(B_PANEL_TEXT_COLOR);
|
|
BFont thin(be_plain_font);
|
|
text_run tr_font = {0, thin, message_color};
|
|
text_run_array tra_font = {1, {tr_font}};
|
|
|
|
_chat->Insert(_chat->TextLength(), command.c_str(), command.size(), &tra_font);
|
|
_chat->Insert(_chat->TextLength(), "\n", 2, &tra_font);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool TalkView::AddChatMessage(string username, string message, user_type type) {
|
|
|
|
BFont thick(be_bold_font);
|
|
|
|
// some colors to play with
|
|
rgb_color blue = {0, 0, 255, 255};
|
|
rgb_color red = {255, 0, 0, 255};
|
|
rgb_color message_color = ui_color(B_PANEL_TEXT_COLOR);
|
|
|
|
// some runs to play with
|
|
text_run tr_thick_blue = {0, thick, blue};
|
|
text_run tr_thick_red = {0, thick, red};
|
|
text_run tr_thick_message = {0, thick, message_color};
|
|
|
|
// some run array to play with (simple)
|
|
text_run_array tra_thick_blue = {1, {tr_thick_blue}};
|
|
text_run_array tra_thick_red = {1, {tr_thick_red}};
|
|
text_run_array tra_thick_black = {1, {tr_thick_message}};
|
|
|
|
if (type == MAIN_RECIPIENT) {
|
|
_chat->Insert(_chat->TextLength(), username.c_str(), username.size(), &tra_thick_blue);
|
|
_chat->Insert(_chat->TextLength(), ": ", 2, &tra_thick_black);
|
|
|
|
} else if (type == LOCAL || (IsGroupChat() && GetGroupUsername() == username)) {
|
|
_chat->Insert(_chat->TextLength(), username.c_str(), username.size(), &tra_thick_red);
|
|
_chat->Insert(_chat->TextLength(), ": ", 2, &tra_thick_black);
|
|
}
|
|
_chat->Insert(_chat->TextLength(), message.c_str(), message.size(), &tra_thick_black);
|
|
_chat->Insert(_chat->TextLength(), "\n", 1, &tra_thick_black);
|
|
return true;
|
|
}
|
|
|
|
|
|
void TalkView::AddToTalk(string username, string message, user_type type) {
|
|
// transform local identity
|
|
if (IsGroupChat() && type == LOCAL) {
|
|
username = _group_username;
|
|
}
|
|
|
|
// history
|
|
if (type == LOCAL) {
|
|
// reset chat history index
|
|
_chat_index = -1;
|
|
|
|
// add latest
|
|
_chat_history.push_front(message);
|
|
|
|
// prune end
|
|
if (_chat_history.size() > 50) {
|
|
_chat_history.pop_back();
|
|
}
|
|
}
|
|
|
|
// ignore empty messages
|
|
if (message.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (CommandMessage::IsCommand(message)) {
|
|
AddChatCommand(message);
|
|
} else {
|
|
AddChatMessage(username, message, type);
|
|
}
|
|
|
|
_chat->ScrollTo(0.0, _chat->Bounds().bottom);
|
|
}
|
|
|
|
|
|
void TalkView::NewMessage(string new_message) {
|
|
if (IsGroupChat()) {
|
|
return; // GCHAT
|
|
} else {
|
|
gloox::RosterManager* rm = JabberSpeak::Instance()->GlooxClient()->rosterManager();
|
|
gloox::RosterItem* item = rm->getRosterItem(_session->target());
|
|
if (item && !item->name().empty()) {
|
|
AddToTalk(item->name().c_str(), new_message, MAIN_RECIPIENT);
|
|
} else {
|
|
AddToTalk(_session->target().bare().c_str(), new_message, MAIN_RECIPIENT);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TalkView::NewMessage(string username, string new_message) {
|
|
AddToTalk(username.c_str(), new_message, MAIN_RECIPIENT);
|
|
}
|
|
|
|
|
|
const gloox::JID& TalkView::GetUserID() {
|
|
if (_session == NULL)
|
|
debugger("Getting user ID not possible for group chat");
|
|
|
|
return _session->target();
|
|
}
|
|
|
|
|
|
string TalkView::GetGroupRoom() {
|
|
return _group_room;
|
|
}
|
|
|
|
|
|
string TalkView::GetGroupUsername() {
|
|
return _group_username;
|
|
}
|
|
|
|
|
|
bool TalkView::NewlinesAllowed() {
|
|
return false;
|
|
}
|
|
|
|
|
|
static int compareStrings(const char* a, const char* b)
|
|
{
|
|
// FIXME use ICU locale aware comparison instead
|
|
int icompare = strcasecmp(a, b);
|
|
if (icompare != 0)
|
|
return icompare;
|
|
|
|
// In case the names are case-insensitive-equal, still sort them in a
|
|
// predictible way
|
|
return strcmp(a, b);
|
|
}
|
|
|
|
|
|
void TalkView::AddGroupChatter(string user, gloox::MUCRoomAffiliation affiliation) {
|
|
int i;
|
|
|
|
// create a new entry
|
|
PeopleListItem *people_item = new PeopleListItem(user, affiliation);
|
|
|
|
// exception
|
|
if (_people->CountItems() == 0) {
|
|
// add the new user
|
|
_people->AddItem(people_item);
|
|
|
|
return;
|
|
}
|
|
|
|
// add it to the list
|
|
// FIXME we should binary search for the correct position
|
|
for (i=0; i < _people->CountItems(); ++i) {
|
|
PeopleListItem *iterating_item = dynamic_cast<PeopleListItem *>(_people->ItemAt(i));
|
|
|
|
int compare = compareStrings(iterating_item->User().c_str(), user.c_str());
|
|
|
|
if (compare == 0) {
|
|
// Update existing user
|
|
// FIXME affiliation might have changed, refresh it
|
|
_people->InvalidateItem(i);
|
|
} else if (compare > 0) {
|
|
// add the new user in the middle
|
|
_people->AddItem(people_item, i);
|
|
} else if (i == (_people->CountItems() - 1)) {
|
|
// add the new user at the end
|
|
_people->AddItem(people_item);
|
|
} else {
|
|
// continue searching for the correct place
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void TalkView::RemoveGroupChatter(string username) {
|
|
// remove user
|
|
for (int i=0; i < _people->CountItems(); ++i) {
|
|
if (dynamic_cast<PeopleListItem *>(_people->ItemAt(i))->User() == username) {
|
|
_people->RemoveItem(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TalkView::RevealPreviousHistory() {
|
|
// boundary
|
|
if (_chat_index == 49 || _chat_index == ((int)_chat_history.size() - 1)) {
|
|
return;
|
|
}
|
|
|
|
if (_chat_index == -1) {
|
|
_chat_buffer = _message_input->Text();
|
|
}
|
|
|
|
// go back
|
|
++_chat_index;
|
|
|
|
// update text
|
|
_message_input->SetText(_chat_history[_chat_index].c_str());
|
|
}
|
|
|
|
|
|
void TalkView::RevealNextHistory() {
|
|
// boundary
|
|
if (_chat_index == -1) {
|
|
return;
|
|
}
|
|
|
|
// go forward
|
|
--_chat_index;
|
|
|
|
// last buffer
|
|
if (_chat_index == -1) {
|
|
_message_input->SetText(_chat_buffer.c_str());
|
|
} else {
|
|
// update text
|
|
_message_input->SetText(_chat_history[_chat_index].c_str());
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
TalkView::IsGroupChat()
|
|
{
|
|
return !_group_room.empty();
|
|
}
|
|
|
|
|
|
void
|
|
TalkView::SetStatus(std::string message)
|
|
{
|
|
_status_view->SetMessage(message);
|
|
}
|