1 // Copyright (C) 2007 Mark Pustjens <pustjens@dds.nl>
2 // Copyright (C) 2010-2015 Petr Pavlu <setup@dagobah.cz>
4 // This file is part of CenterIM.
6 // CenterIM is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // CenterIM is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
19 #include "Conversations.h"
24 Conversations
*Conversations::my_instance_
= nullptr;
26 Conversations
*Conversations::instance()
31 void Conversations::onScreenResized()
33 CppConsUI::Rect r
= CENTERIM
->getScreenArea(CenterIM::CHAT_AREA
);
40 void Conversations::focusActiveConversation()
42 activateConversation(active_
);
45 void Conversations::focusConversation(int i
)
49 if (conversations_
.empty())
52 // The external conversation #1 is the internal conversation #0.
55 int s
= static_cast<int>(conversations_
.size());
57 activateConversation(i
);
59 // If there is less than i active conversations then active the last one.
60 activateConversation(s
- 1);
64 void Conversations::focusPrevConversation()
66 int i
= prevActiveConversation(active_
);
68 activateConversation(i
);
71 void Conversations::focusNextConversation()
73 int i
= nextActiveConversation(active_
);
75 activateConversation(i
);
78 void Conversations::setExpandedConversations(bool expanded
)
81 // Make small room on the left and right.
82 left_spacer_
->setWidth(1);
83 right_spacer_
->setWidth(1);
86 left_spacer_
->setWidth(0);
87 right_spacer_
->setWidth(0);
90 // Trigger the Conversation::show() method to change the scrollbar setting of
91 // the active conversation.
92 activateConversation(active_
);
95 Conversations::Conversations()
96 : Window(0, 0, 80, 1, TYPE_NON_FOCUSABLE
, false), active_(-1)
98 setColorScheme(CenterIM::SCHEME_CONVERSATION
);
100 outer_list_
= new CppConsUI::HorizontalListBox(AUTOSIZE
, 1);
101 addWidget(*outer_list_
, 0, 0);
103 left_spacer_
= new CppConsUI::Spacer(0, 1);
104 right_spacer_
= new CppConsUI::Spacer(0, 1);
105 conv_list_
= new CppConsUI::HorizontalListBox(AUTOSIZE
, 1);
107 outer_list_
->appendWidget(*left_spacer_
);
108 outer_list_
->appendWidget(*conv_list_
);
109 outer_list_
->appendWidget(*right_spacer_
);
112 purple_prefs_add_none(CONF_PREFIX
"/chat");
113 purple_prefs_add_int(CONF_PREFIX
"/chat/partitioning", 80);
114 purple_prefs_add_int(CONF_PREFIX
"/chat/roomlist_partitioning", 80);
115 purple_prefs_add_bool(CONF_PREFIX
"/chat/beep_on_msg", false);
117 // send_typing caching.
118 send_typing_
= purple_prefs_get_bool("/purple/conversations/im/send_typing");
119 purple_prefs_connect_callback(this, "/purple/conversations/im/send_typing",
120 send_typing_pref_change_
, this);
122 std::memset(¢erim_conv_ui_ops_
, 0, sizeof(centerim_conv_ui_ops_
));
123 centerim_conv_ui_ops_
.create_conversation
= create_conversation_
;
124 centerim_conv_ui_ops_
.destroy_conversation
= destroy_conversation_
;
125 // centerim_conv_ui_ops_.write_chat = ;
126 // centerim_conv_ui_ops_.write_im = ;
127 centerim_conv_ui_ops_
.write_conv
= write_conv_
;
128 centerim_conv_ui_ops_
.chat_add_users
= chat_add_users_
;
129 centerim_conv_ui_ops_
.chat_rename_user
= chat_rename_user_
;
130 centerim_conv_ui_ops_
.chat_remove_users
= chat_remove_users_
;
131 centerim_conv_ui_ops_
.chat_update_user
= chat_update_user_
;
133 centerim_conv_ui_ops_
.present
= present_
;
134 // centerim_conv_ui_ops_.has_focus = ;
135 // centerim_conv_ui_ops_.custom_smiley_add = ;
136 // centerim_conv_ui_ops_.custom_smiley_write = ;
137 // centerim_conv_ui_ops_.custom_smiley_close = ;
138 // centerim_conv_ui_ops_.send_confirm = ;
140 // Setup callbacks for conversations.
141 purple_conversations_set_ui_ops(¢erim_conv_ui_ops_
);
143 void *handle
= purple_conversations_get_handle();
144 purple_signal_connect(
145 handle
, "buddy-typing", this, PURPLE_CALLBACK(buddy_typing_
), this);
146 purple_signal_connect(
147 handle
, "buddy-typing-stopped", this, PURPLE_CALLBACK(buddy_typing_
), this);
149 // Setup callbacks for connections in relation to conversations.
150 void *connections_handle
= purple_connections_get_handle();
151 purple_signal_connect(connections_handle
, "signed-on", this,
152 PURPLE_CALLBACK(account_signed_on_
), this);
157 Conversations::~Conversations()
159 // Close all opened conversations.
160 while (!conversations_
.empty())
161 purple_conversation_destroy(
162 conversations_
.front().conv
->getPurpleConversation());
164 purple_conversations_set_ui_ops(nullptr);
165 purple_prefs_disconnect_by_handle(this);
166 purple_signals_disconnect_by_handle(this);
169 void Conversations::init()
171 g_assert(my_instance_
== nullptr);
173 my_instance_
= new Conversations
;
174 my_instance_
->show();
177 void Conversations::finalize()
179 g_assert(my_instance_
!= nullptr);
182 my_instance_
= nullptr;
185 int Conversations::findConversation(PurpleConversation
*conv
)
187 for (int i
= 0; i
< static_cast<int>(conversations_
.size()); ++i
)
188 if (conversations_
[i
].conv
->getPurpleConversation() == conv
)
194 int Conversations::prevActiveConversation(int current
)
196 g_assert(current
< static_cast<int>(conversations_
.size()));
198 if (conversations_
.empty()) {
204 // Return the last conversation.
205 return conversations_
.size() - 1;
211 int Conversations::nextActiveConversation(int current
)
213 g_assert(current
< static_cast<int>(conversations_
.size()));
215 if (conversations_
.empty()) {
220 if (current
== static_cast<int>(conversations_
.size() - 1)) {
221 // Return the first conversation.
228 void Conversations::activateConversation(int i
)
231 g_assert(i
< static_cast<int>(conversations_
.size()));
235 conversations_
[active_
].conv
->show();
240 // Show a new active conversation.
241 conversations_
[i
].label
->setVisibility(true);
242 conversations_
[i
].label
->setColorScheme(
243 CenterIM::SCHEME_CONVERSATION_ACTIVE
);
244 conversations_
[i
].conv
->show();
247 // Hide old active conversation if there is any.
249 conversations_
[active_
].label
->setColorScheme(0);
250 conversations_
[active_
].conv
->hide();
256 void Conversations::updateLabel(int i
)
259 g_assert(i
< static_cast<int>(conversations_
.size()));
261 char *name
= g_strdup_printf(
262 " %d|%s%c", i
+ 1, purple_conversation_get_title(
263 conversations_
[i
].conv
->getPurpleConversation()),
264 conversations_
[i
].typing_status
);
265 conversations_
[i
].label
->setText(name
);
269 void Conversations::updateLabels()
271 // Note: This can be a little slow if there are too many open conversations.
272 for (int i
= 0; i
< static_cast<int>(conversations_
.size()); ++i
)
276 void Conversations::create_conversation(PurpleConversation
*conv
)
278 g_return_if_fail(conv
!= nullptr);
279 g_return_if_fail(findConversation(conv
) == -1);
281 PurpleConversationType type
= purple_conversation_get_type(conv
);
282 if (type
!= PURPLE_CONV_TYPE_IM
&& type
!= PURPLE_CONV_TYPE_CHAT
) {
283 purple_conversation_destroy(conv
);
284 LOG
->error(_("Unhandled conversation type '%d'."), type
);
288 auto conversation
= new Conversation(conv
);
290 conv
->ui_data
= static_cast<void *>(conversation
);
293 c
.conv
= conversation
;
294 c
.label
= new CppConsUI::Label(AUTOSIZE
, 1);
295 c
.typing_status
= ' ';
296 conv_list_
->appendWidget(*c
.label
);
297 conversations_
.push_back(c
);
300 // Show the first conversation if there is not any already.
302 activateConversation(conversations_
.size() - 1);
305 void Conversations::destroy_conversation(PurpleConversation
*conv
)
307 g_return_if_fail(conv
!= nullptr);
309 int i
= findConversation(conv
);
311 // Destroying unhandled conversation type.
316 if (conversations_
.size() == 1) {
317 // The last conversation is closed.
321 if (active_
== static_cast<int>(conversations_
.size() - 1))
322 focusPrevConversation();
324 focusNextConversation();
328 delete conversations_
[i
].conv
;
329 conv_list_
->removeWidget(*conversations_
[i
].label
);
330 conversations_
.erase(conversations_
.begin() + i
);
333 // Fix up the number of the active conversation.
340 void Conversations::write_conv(PurpleConversation
*conv
, const char *name
,
341 const char *alias
, const char *message
, PurpleMessageFlags flags
,
344 g_return_if_fail(conv
!= nullptr);
346 int i
= findConversation(conv
);
348 // Message to unhandled conversation type.
353 conversations_
[i
].label
->setColorScheme(CenterIM::SCHEME_CONVERSATION_NEW
);
355 // Delegate it to Conversation object.
356 conversations_
[i
].conv
->write(name
, alias
, message
, flags
, mtime
);
359 ConversationRoomList
*Conversations::getRoomList(PurpleConversation
*conv
)
361 if (conv
!= nullptr) {
362 Conversation
*conversation
= static_cast<Conversation
*>(conv
->ui_data
);
363 if (conversation
!= nullptr)
364 return conversation
->getRoomList();
370 void Conversations::chat_add_users(
371 PurpleConversation
*conv
, GList
*cbuddies
, gboolean new_arrivals
)
373 ConversationRoomList
*room_list
= getRoomList(conv
);
374 if (room_list
!= nullptr)
375 room_list
->add_users(cbuddies
, new_arrivals
);
378 void Conversations::chat_rename_user(PurpleConversation
*conv
,
379 const char *old_name
, const char *new_name
, const char *new_alias
)
381 ConversationRoomList
*room_list
= getRoomList(conv
);
382 if (room_list
!= nullptr)
383 room_list
->rename_user(old_name
, new_name
, new_alias
);
386 void Conversations::chat_remove_users(PurpleConversation
*conv
, GList
*users
)
388 ConversationRoomList
*room_list
= getRoomList(conv
);
389 if (room_list
!= nullptr)
390 room_list
->remove_users(users
);
393 void Conversations::chat_update_user(PurpleConversation
*conv
, const char *user
)
395 ConversationRoomList
*room_list
= getRoomList(conv
);
396 if (room_list
!= nullptr)
397 room_list
->update_user(user
);
400 void Conversations::present(PurpleConversation
*conv
)
402 g_return_if_fail(conv
);
404 int i
= findConversation(conv
);
406 // Unhandled conversation type.
410 activateConversation(i
);
413 void Conversations::buddy_typing(PurpleAccount
*account
, const char *who
)
415 PurpleConversation
*conv
=
416 purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, who
, account
);
420 int i
= findConversation(conv
);
422 // This should probably never happen.
426 PurpleConvIm
*im
= PURPLE_CONV_IM(conv
);
427 g_assert(im
!= nullptr);
428 if (purple_conv_im_get_typing_state(im
) == PURPLE_TYPING
)
429 conversations_
[i
].typing_status
= '*';
431 conversations_
[i
].typing_status
= ' ';
436 void Conversations::account_signed_on(PurpleConnection
*gc
)
438 for (ConvChild
&conv_child
: conversations_
) {
439 PurpleConversation
*purple_conv
= conv_child
.conv
->getPurpleConversation();
441 // Only process chats for this connection.
442 if (purple_conversation_get_type(purple_conv
) != PURPLE_CONV_TYPE_CHAT
||
443 purple_conversation_get_gc(purple_conv
) != gc
)
446 // TODO Add and consult the "want-to-rejoin" configuration parameter?
448 // Look up the chat from the buddy list.
449 PurpleChat
*purple_chat
=
450 purple_blist_find_chat(purple_conversation_get_account(purple_conv
),
451 purple_conversation_get_name(purple_conv
));
453 GHashTable
*components
= NULL
;
455 if (purple_chat
!= nullptr)
456 components
= purple_chat_get_components(purple_chat
);
458 // Use defaults if the chat cannot be found.
459 PurplePluginProtocolInfo
*info
=
460 PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc
));
462 info
->chat_info_defaults(gc
, purple_conversation_get_name(purple_conv
));
465 serv_join_chat(gc
, components
);
469 void Conversations::send_typing_pref_change(
470 const char *name
, PurplePrefType
/*type*/, gconstpointer
/*val*/)
472 g_assert(std::strcmp(name
, "/purple/conversations/im/send_typing") == 0);
473 send_typing_
= purple_prefs_get_bool(name
);
476 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: