Renga/ui/TalkView.cpp

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);
}