| 4348 if (is_me && color) |
4348 if (is_me && color) |
| 4349 gdk_color_free(color); |
4349 gdk_color_free(color); |
| 4350 g_free(alias_key); |
4350 g_free(alias_key); |
| 4351 } |
4351 } |
| 4352 |
4352 |
| 4353 #if 0 |
|
| 4354 /** |
4353 /** |
| 4355 * @param most_matched Used internally by this function. |
4354 * @param most_matched Used internally by this function. |
| 4356 * @param entered The partial string that the user types before hitting the |
4355 * @param entered The partial string that the user types before hitting the |
| 4357 * tab key. |
4356 * tab key. |
| 4358 * @param entered_bytes The length of entered. |
4357 * @param entered_chars The length of entered. |
| 4359 * @param partial This is a return variable. This will be set to a string |
4358 * @param partial This is a return variable. This will be set to a string |
| 4360 * containing the largest common string between all matches. This will |
4359 * containing the largest common string between all matches. This will |
| 4361 * be inserted into the input box at the start of the word that the |
4360 * be inserted into the input box at the start of the word that the |
| 4362 * user is tab completing. For example, if a chat room contains |
4361 * user is tab completing. For example, if a chat room contains |
| 4363 * "AlfFan" and "AlfHater" and the user types "a<TAB>" then this will |
4362 * "AlfFan" and "AlfHater" and the user types "a<TAB>" then this will |
| 4364 * contain "Alf" |
4363 * contain "Alf" |
| 4365 * @param nick_partial Used internally by this function. Shoudl be a |
|
| 4366 * temporary buffer that is entered_bytes+1 bytes long. |
|
| 4367 * @param matches This is a return variable. If the given name is a potential |
4364 * @param matches This is a return variable. If the given name is a potential |
| 4368 * match for the entered string, then add a copy of the name to this |
4365 * match for the entered string, then add a copy of the name to this |
| 4369 * list. The caller is responsible for g_free'ing the data in this |
4366 * list. The caller is responsible for g_free'ing the data in this |
| 4370 * list. |
4367 * list. |
| 4371 * @param name The buddy name or alias or slash command name that we're |
4368 * @param name The buddy name or alias or slash command name that we're |
| 4372 * checking for a match. |
4369 * checking for a match. |
| 4373 */ |
4370 */ |
| 4374 static void |
4371 static void |
| 4375 tab_complete_process_item(int *most_matched, const char *entered, gsize entered_bytes, char **partial, char *nick_partial, |
4372 tab_complete_process_item(int *most_matched, const char *entered, gsize entered_chars, char **partial, |
| 4376 GList **matches, const char *name) |
4373 GList **matches, const char *name) |
| 4377 { |
4374 { |
| 4378 memcpy(nick_partial, name, entered_bytes); |
4375 char *nick_partial; |
| 4379 if (purple_utf8_strcasecmp(nick_partial, entered)) |
4376 gsize name_len = g_utf8_strlen(name, -1); |
| |
4377 |
| |
4378 if ((glong)entered_chars > name_len) |
| 4380 return; |
4379 return; |
| |
4380 |
| |
4381 nick_partial = g_utf8_substring(name, 0, entered_chars); |
| |
4382 if (purple_utf8_strcasecmp(nick_partial, entered)) { |
| |
4383 g_free(nick_partial); |
| |
4384 return; |
| |
4385 } |
| |
4386 g_free(nick_partial); |
| 4381 |
4387 |
| 4382 /* if we're here, it's a possible completion */ |
4388 /* if we're here, it's a possible completion */ |
| 4383 |
4389 |
| 4384 if (*most_matched == -1) { |
4390 if (*most_matched == -1) { |
| 4385 /* |
4391 /* |
| 4386 * this will only get called once, since from now |
4392 * this will only get called once, since from now |
| 4387 * on *most_matched is >= 0 |
4393 * on *most_matched is >= 0 |
| 4388 */ |
4394 */ |
| 4389 *most_matched = strlen(name); |
4395 *most_matched = name_len; |
| 4390 *partial = g_strdup(name); |
4396 *partial = g_strdup(name); |
| 4391 } |
4397 } |
| 4392 else if (*most_matched) { |
4398 else if (*most_matched) { |
| 4393 char *tmp = g_strdup(name); |
4399 char *tmp = g_strdup(name); |
| 4394 |
4400 |
| 4395 while (purple_utf8_strcasecmp(tmp, *partial)) { |
4401 while (purple_utf8_strcasecmp(tmp, *partial)) { |
| 4396 (*partial)[*most_matched] = '\0'; |
4402 *(g_utf8_offset_to_pointer(*partial, *most_matched)) = '\0'; |
| 4397 if (*most_matched < (goffset)strlen(tmp)) |
4403 if (*most_matched < (goffset)g_utf8_strlen(tmp, -1)) |
| 4398 tmp[*most_matched] = '\0'; |
4404 *(g_utf8_offset_to_pointer(tmp, *most_matched)) = '\0'; |
| 4399 (*most_matched)--; |
4405 (*most_matched)--; |
| 4400 } |
4406 } |
| 4401 (*most_matched)++; |
4407 (*most_matched)++; |
| 4402 |
4408 |
| 4403 g_free(tmp); |
4409 g_free(tmp); |
| 4404 } |
4410 } |
| 4405 |
4411 |
| 4406 *matches = g_list_insert_sorted(*matches, g_strdup(name), |
4412 *matches = g_list_insert_sorted(*matches, g_strdup(name), |
| 4407 (GCompareFunc)purple_utf8_strcasecmp); |
4413 (GCompareFunc)purple_utf8_strcasecmp); |
| 4408 } |
4414 } |
| 4409 #endif |
4415 |
| |
4416 static gboolean |
| |
4417 is_first_container(WebKitDOMNode *container) |
| |
4418 { |
| |
4419 gchar *name; |
| |
4420 WebKitDOMNode *parent; |
| |
4421 |
| |
4422 while (container) { |
| |
4423 parent = webkit_dom_node_get_parent_node(container); |
| |
4424 if (parent) { |
| |
4425 name = webkit_dom_node_get_node_name(parent); |
| |
4426 |
| |
4427 if (!strcmp(name, "BODY")) { |
| |
4428 g_free(name); |
| |
4429 |
| |
4430 if (webkit_dom_node_get_previous_sibling(container) == NULL) |
| |
4431 return TRUE; |
| |
4432 else |
| |
4433 return FALSE; |
| |
4434 } |
| |
4435 g_free(name); |
| |
4436 } |
| |
4437 else |
| |
4438 break; |
| |
4439 |
| |
4440 container = parent; |
| |
4441 } |
| |
4442 |
| |
4443 return FALSE; |
| |
4444 } |
| 4410 |
4445 |
| 4411 static gboolean |
4446 static gboolean |
| 4412 tab_complete(PurpleConversation *conv) |
4447 tab_complete(PurpleConversation *conv) |
| 4413 { |
4448 { |
| 4414 #if 0 |
|
| 4415 /* TODO WebKit */ |
|
| 4416 PidginConversation *gtkconv; |
4449 PidginConversation *gtkconv; |
| 4417 GtkTextIter cursor, word_start, start_buffer; |
4450 WebKitDOMNode *container; |
| 4418 int start; |
4451 glong caret, word_start, content_len; |
| 4419 int most_matched = -1; |
4452 int most_matched = -1, colon = 0; |
| |
4453 char *ch, *ch2 = NULL; |
| 4420 char *entered, *partial = NULL; |
4454 char *entered, *partial = NULL; |
| 4421 char *text; |
4455 char *content, *sub1, *sub2, *modified; |
| 4422 char *nick_partial; |
|
| 4423 const char *prefix; |
4456 const char *prefix; |
| 4424 GList *matches = NULL; |
4457 GList *matches = NULL; |
| 4425 gboolean command = FALSE; |
4458 gboolean command = FALSE; |
| 4426 gsize entered_bytes = 0; |
4459 gsize entered_chars = 0; |
| 4427 |
4460 |
| 4428 gtkconv = PIDGIN_CONVERSATION(conv); |
4461 gtkconv = PIDGIN_CONVERSATION(conv); |
| 4429 |
4462 gtk_webview_get_caret(GTK_WEBVIEW(gtkconv->entry), &container, &caret); |
| 4430 gtk_text_buffer_get_start_iter(gtkconv->entry_buffer, &start_buffer); |
|
| 4431 gtk_text_buffer_get_iter_at_mark(gtkconv->entry_buffer, &cursor, |
|
| 4432 gtk_text_buffer_get_insert(gtkconv->entry_buffer)); |
|
| 4433 |
|
| 4434 word_start = cursor; |
|
| 4435 |
4463 |
| 4436 /* if there's nothing there just return */ |
4464 /* if there's nothing there just return */ |
| 4437 if (!gtk_text_iter_compare(&cursor, &start_buffer)) |
4465 if (caret <= 0) |
| 4438 return PURPLE_IS_CHAT_CONVERSATION(conv); |
4466 return PURPLE_IS_CHAT_CONVERSATION(conv); |
| 4439 |
4467 |
| 4440 text = gtk_text_buffer_get_text(gtkconv->entry_buffer, &start_buffer, |
4468 content = webkit_dom_node_get_node_value(container); |
| 4441 &cursor, FALSE); |
4469 content_len = g_utf8_strlen(content, -1); |
| 4442 |
4470 |
| 4443 /* if we're at the end of ": " we need to move back 2 spaces */ |
4471 /* if we're at the end of ":" or ": " we need to move back 1 or 2 spaces */ |
| 4444 start = strlen(text) - 1; |
4472 if (caret >= 2) { |
| 4445 |
4473 ch = g_utf8_offset_to_pointer(content, caret - 2); |
| 4446 if (start >= 1 && !strncmp(&text[start-1], ": ", 2)) { |
4474 ch2 = g_utf8_find_next_char(ch, NULL); |
| 4447 gtk_text_iter_backward_chars(&word_start, 2); |
4475 } |
| 4448 } |
4476 |
| 4449 |
4477 if (caret >= 2 && *ch == ':' && (*ch2 == ' ' || g_utf8_get_char(ch2) == 0xA0)) |
| 4450 /* find the start of the word that we're tabbing. |
4478 colon = 2; |
| 4451 * Using gtk_text_iter_backward_word_start won't work, because a nick can contain |
4479 else if (caret >= 1 && content[caret - 1] == ':') |
| 4452 * characters (e.g. '.', '/' etc.) that Pango may think are word separators. */ |
4480 colon = 1; |
| 4453 while (gtk_text_iter_backward_char(&word_start)) { |
4481 |
| 4454 if (gtk_text_iter_get_char(&word_start) == ' ') { |
4482 caret -= colon; |
| 4455 /* Reached the whitespace before the start of the word. Move forward once */ |
4483 word_start = caret; |
| 4456 gtk_text_iter_forward_char(&word_start); |
4484 |
| |
4485 /* find the start of the word that we're tabbing. */ |
| |
4486 ch = g_utf8_offset_to_pointer(content, caret); |
| |
4487 while ((ch = g_utf8_find_prev_char(content, ch))) { |
| |
4488 if (*ch != ' ' && g_utf8_get_char(ch) != 0xA0) |
| |
4489 --word_start; |
| |
4490 else |
| 4457 break; |
4491 break; |
| 4458 } |
|
| 4459 } |
4492 } |
| 4460 |
4493 |
| 4461 prefix = pidgin_get_cmd_prefix(); |
4494 prefix = pidgin_get_cmd_prefix(); |
| 4462 if (gtk_text_iter_get_offset(&word_start) == 0 && |
4495 if (word_start == 0 && |
| 4463 (strlen(text) >= strlen(prefix)) && !strncmp(text, prefix, strlen(prefix))) { |
4496 ((gsize)caret >= strlen(prefix)) && !strncmp(content, prefix, strlen(prefix))) { |
| 4464 command = TRUE; |
4497 command = TRUE; |
| 4465 gtk_text_iter_forward_chars(&word_start, strlen(prefix)); |
4498 word_start += strlen(prefix); |
| 4466 } |
4499 } |
| 4467 |
4500 |
| 4468 g_free(text); |
4501 entered = g_utf8_substring(content, word_start, caret); |
| 4469 |
4502 entered_chars = g_utf8_strlen(entered, -1); |
| 4470 entered = gtk_text_buffer_get_text(gtkconv->entry_buffer, &word_start, |
4503 |
| 4471 &cursor, FALSE); |
4504 if (!entered_chars) { |
| 4472 entered_bytes = strlen(entered); |
4505 g_free(content); |
| 4473 |
|
| 4474 if (!g_utf8_strlen(entered, -1)) { |
|
| 4475 g_free(entered); |
4506 g_free(entered); |
| 4476 return PURPLE_IS_CHAT_CONVERSATION(conv); |
4507 return PURPLE_IS_CHAT_CONVERSATION(conv); |
| 4477 } |
4508 } |
| 4478 |
|
| 4479 nick_partial = g_malloc0(entered_bytes + 1); |
|
| 4480 |
4509 |
| 4481 if (command) { |
4510 if (command) { |
| 4482 GList *list = purple_cmd_list(conv); |
4511 GList *list = purple_cmd_list(conv); |
| 4483 GList *l; |
4512 GList *l; |
| 4484 |
4513 |
| 4485 /* Commands */ |
4514 /* Commands */ |
| 4486 for (l = list; l != NULL; l = l->next) { |
4515 for (l = list; l != NULL; l = l->next) { |
| 4487 tab_complete_process_item(&most_matched, entered, entered_bytes, &partial, nick_partial, |
4516 tab_complete_process_item(&most_matched, entered, entered_chars, &partial, |
| 4488 &matches, l->data); |
4517 &matches, l->data); |
| 4489 } |
4518 } |
| 4490 g_list_free(list); |
4519 g_list_free(list); |
| 4491 } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) { |
4520 } else if (PURPLE_IS_CHAT_CONVERSATION(conv)) { |
| 4492 PurpleChatConversation *chat = PURPLE_CONV_CHAT(conv); |
4521 GList *l = purple_chat_conversation_get_users(PURPLE_CHAT_CONVERSATION(conv)); |
| 4493 GList *l = purple_chat_conversation_get_users(chat); |
|
| 4494 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv)->u.chat->list)); |
4522 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv)->u.chat->list)); |
| 4495 GtkTreeIter iter; |
4523 GtkTreeIter iter; |
| 4496 int f; |
4524 int f; |
| 4497 |
4525 |
| 4498 /* Users */ |
4526 /* Users */ |
| 4499 for (; l != NULL; l = l->next) { |
4527 for (; l != NULL; l = l->next) { |
| 4500 tab_complete_process_item(&most_matched, entered, entered_bytes, &partial, nick_partial, |
4528 tab_complete_process_item(&most_matched, entered, entered_chars, &partial, |
| 4501 &matches, purple_chat_user_get_name((PurpleChatUser *)l->data)); |
4529 &matches, purple_chat_user_get_name((PurpleChatUser *)l->data)); |
| 4502 } |
4530 } |
| 4503 |
|
| 4504 |
4531 |
| 4505 /* Aliases */ |
4532 /* Aliases */ |
| 4506 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
4533 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) |
| 4507 { |
4534 { |
| 4508 do { |
4535 do { |
| 4513 CHAT_USERS_NAME_COLUMN, &name, |
4540 CHAT_USERS_NAME_COLUMN, &name, |
| 4514 CHAT_USERS_ALIAS_COLUMN, &alias, |
4541 CHAT_USERS_ALIAS_COLUMN, &alias, |
| 4515 -1); |
4542 -1); |
| 4516 |
4543 |
| 4517 if (name && alias && strcmp(name, alias)) |
4544 if (name && alias && strcmp(name, alias)) |
| 4518 tab_complete_process_item(&most_matched, entered, entered_bytes, &partial, nick_partial, |
4545 tab_complete_process_item(&most_matched, entered, entered_chars, &partial, |
| 4519 &matches, alias); |
4546 &matches, alias); |
| 4520 g_free(name); |
4547 g_free(name); |
| 4521 g_free(alias); |
4548 g_free(alias); |
| 4522 |
4549 |
| 4523 f = gtk_tree_model_iter_next(model, &iter); |
4550 f = gtk_tree_model_iter_next(model, &iter); |
| 4524 } while (f != 0); |
4551 } while (f != 0); |
| 4525 } |
4552 } |
| 4526 } else { |
4553 } else { |
| 4527 g_free(nick_partial); |
4554 g_free(content); |
| 4528 g_free(entered); |
4555 g_free(entered); |
| 4529 return FALSE; |
4556 return FALSE; |
| 4530 } |
4557 } |
| 4531 |
|
| 4532 g_free(nick_partial); |
|
| 4533 |
|
| 4534 /* we're only here if we're doing new style */ |
|
| 4535 |
4558 |
| 4536 /* if there weren't any matches, return */ |
4559 /* if there weren't any matches, return */ |
| 4537 if (!matches) { |
4560 if (!matches) { |
| 4538 /* if matches isn't set partials won't be either */ |
4561 /* if matches isn't set partials won't be either */ |
| |
4562 g_free(content); |
| 4539 g_free(entered); |
4563 g_free(entered); |
| 4540 return PURPLE_IS_CHAT_CONVERSATION(conv); |
4564 return PURPLE_IS_CHAT_CONVERSATION(conv); |
| 4541 } |
4565 } |
| 4542 |
4566 |
| 4543 gtk_text_buffer_delete(gtkconv->entry_buffer, &word_start, &cursor); |
4567 sub1 = g_utf8_substring(content, 0, word_start); |
| |
4568 sub2 = g_utf8_substring(content, caret, content_len); |
| 4544 |
4569 |
| 4545 if (!matches->next) { |
4570 if (!matches->next) { |
| 4546 /* there was only one match. fill it in. */ |
4571 /* there was only one match. fill it in. */ |
| 4547 gtk_text_buffer_get_start_iter(gtkconv->entry_buffer, &start_buffer); |
4572 |
| 4548 gtk_text_buffer_get_iter_at_mark(gtkconv->entry_buffer, &cursor, |
4573 if (!colon && !word_start && is_first_container(container)) { |
| 4549 gtk_text_buffer_get_insert(gtkconv->entry_buffer)); |
4574 char *tmp = NULL; |
| 4550 |
4575 if (caret < content_len) { |
| 4551 if (!gtk_text_iter_compare(&cursor, &start_buffer)) { |
4576 tmp = g_strdup_printf("%s: ", (char *)matches->data); |
| 4552 char *tmp = g_strdup_printf("%s: ", (char *)matches->data); |
4577 } else { |
| 4553 gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, tmp, -1); |
4578 char utf[6] = {0}; |
| |
4579 g_unichar_to_utf8(0xA0, utf); |
| |
4580 tmp = g_strdup_printf("%s:%s", (char *)matches->data, utf); |
| |
4581 } |
| |
4582 |
| |
4583 modified = g_strdup_printf("%s%s", tmp, sub2); |
| |
4584 webkit_dom_node_set_node_value(container, modified, NULL); |
| |
4585 gtk_webview_set_caret(GTK_WEBVIEW(gtkconv->entry), container, |
| |
4586 g_utf8_strlen(tmp, -1)); |
| 4554 g_free(tmp); |
4587 g_free(tmp); |
| 4555 } |
4588 g_free(modified); |
| 4556 else |
4589 } |
| 4557 gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, |
4590 else { |
| 4558 matches->data, -1); |
4591 modified = g_strdup_printf("%s%s%s", sub1, (char *)matches->data, sub2); |
| |
4592 webkit_dom_node_set_node_value(container, modified, NULL); |
| |
4593 gtk_webview_set_caret(GTK_WEBVIEW(gtkconv->entry), container, |
| |
4594 word_start + g_utf8_strlen(matches->data, -1) + colon); |
| |
4595 g_free(modified); |
| |
4596 } |
| 4559 |
4597 |
| 4560 g_free(matches->data); |
4598 g_free(matches->data); |
| 4561 g_list_free(matches); |
4599 g_list_free(matches); |
| 4562 } |
4600 } |
| 4563 else { |
4601 else { |