Mercurial > pidgin
changeset 18052:627f9d40ca1b
propagate from branch 'im.pidgin.pidgin' (head 5f583403bf6a3522123918b975ce234ffd86f3b4)
to branch 'im.pidgin.pidgin.2.1.0' (head a3f0a9ddc360d16c2a0bb1dbde9cccec230882e9)
author | Luke Schierer <lschiere@pidgin.im> |
---|---|
date | Wed, 06 Jun 2007 12:20:11 +0000 |
parents | 6d0bc0b23440 (current diff) c57c2b245c89 (diff) |
children | 891f22c7e884 |
files | libpurple/conversation.c |
diffstat | 90 files changed, 3192 insertions(+), 836 deletions(-) [+] |
line wrap: on
line diff
--- a/COPYRIGHT Wed Jun 06 07:51:14 2007 +0000 +++ b/COPYRIGHT Wed Jun 06 12:20:11 2007 +0000 @@ -52,6 +52,7 @@ Jeremy Brooks Jonathan Brossard Philip Brown +Norbert Buchmuller Sean Burke Thomas Butter Trevor Caira @@ -146,6 +147,7 @@ Charlie Gordon Ryan C. Gordon Miah Gregory +David Grohmann Christian Hammond Erick Hamness Fred Hampton
--- a/ChangeLog Wed Jun 06 07:51:14 2007 +0000 +++ b/ChangeLog Wed Jun 06 12:20:11 2007 +0000 @@ -1,5 +1,23 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.1.0 (??/??/????): + libpurple: + * Core changes to allow UIs to use second-granularity for scheduling. + Pidgin and Finch, which use the glib event loop, were changed to use + g_timeout_add_seconds() on glib >= 2.14 when possible. This allows + glib to better group our longer timers to increase power efficiency. + (Arjan van de Ven with Intel Corporation) + * No longer linkifies screennames containing @ signs in join/part + notifications in chats + * With the HTML logger, images in conversations are now saved. + NOTE: Saved images are not yet displayed when loading logs. + + Pidgin: + * Ensure only one copy of Pidgin is running with a given configuration + directory. The net effect of this is that trying to start Pidgin a + second time will raise the buddy list. (Gabriel Schulhof) + * Undo capability in the conversation window + version 2.0.2 (??/??/????): Pidgin: * Added a custom conversation font option to preferences
--- a/ChangeLog.API Wed Jun 06 07:51:14 2007 +0000 +++ b/ChangeLog.API Wed Jun 06 12:20:11 2007 +0000 @@ -1,5 +1,45 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.1.0 (??/??/????): + Added: + * purple-remote: added getstatus command + * OPT_PROTO_SLASH_COMMANDS_NATIVE protocol option to indicate that + slash commands are "native" to the protocol + * PURPLE_MESSAGE_NO_LINKIFY message flag to indicate that the message + should not be auto-linkified + * PurpleEventLoopUiOps.timeout_add_seconds + UIs can now use better scheduling for whole-second timers. For + example, clients based on the glib event loop can now use + g_timeout_add_seconds(). + * pidgin_create_window() + * purple_core_ensure_single_instance() + This is for UIs to use to ensure only one copy is running. + * purple_dbus_is_owner() + * purple_image_data_calculate_filename() + * purple_timeout_add_seconds() + Callers should prefer this to purple_timeout_add() for timers + longer than 1 second away. Be aware of the rounding, though. + * purple_timeout_add_seconds() + Callers should prefer this to purple_timeout_add() for timers + longer than 1 second away. Be aware of the rounding, though. + * purple_conversation_get_extended_menu + * purple_conversation_do_command + * pidgin_retrieve_user_info, shows immediate feedback when getting + information about a user. + * gtk_imhtml_setup_entry + * purple_xfer_get_remote_user + * purple_blist_node_get_type + + Changed: + * pidgin_separator returns the separator added to the menu. + * pidgin_append_menu_action returns the menuitem added to the menu. + + Signals - Added: (See the Doxygen docs for details on all signals.) + * "conversation-extended-menu" + + Finch - Added: + * finch_retrieve_user_info + version 2.0.0 (5/3/2007): Please note all functions, defines, and data structures have been re-namespaced to match the new names of Pidgin, Finch, and libpurple.
--- a/configure.ac Wed Jun 06 07:51:14 2007 +0000 +++ b/configure.ac Wed Jun 06 12:20:11 2007 +0000 @@ -43,10 +43,10 @@ # # Make sure to update finch/libgnt/configure.ac with libgnt version changes. # -m4_define([purple_lt_current], [0]) +m4_define([purple_lt_current], [1]) m4_define([purple_major_version], [2]) -m4_define([purple_minor_version], [0]) -m4_define([purple_micro_version], [2]) +m4_define([purple_minor_version], [1]) +m4_define([purple_micro_version], [0]) m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) @@ -55,7 +55,7 @@ m4_define([gnt_lt_current], [0]) m4_define([gnt_major_version], [1]) m4_define([gnt_minor_version], [0]) -m4_define([gnt_micro_version], [2]) +m4_define([gnt_micro_version], [1]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/doc/conversation-signals.dox Wed Jun 06 07:51:14 2007 +0000 +++ b/doc/conversation-signals.dox Wed Jun 06 12:20:11 2007 +0000 @@ -29,6 +29,7 @@ @signal chat-joined @signal chat-left @signal chat-topic-changed + @signal conversation-extended-menu @endsignals @signaldef writing-im-msg @@ -417,5 +418,15 @@ @param topic The new topic. @endsignaldef + @signaldef conversation-extended-menu + @signalproto +void (*conversation_extended_menu)(PurpleConversation *conv, GList **list); + @endsignalproto + @signaldesc + Emitted when the UI requests a list of plugin actions for a + conversation. + @param conv The conversation. + @param list A pointer to the list of actions. + @endsignaldef */ // vim: syntax=c tw=75 et
--- a/finch/finch.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/finch.c Wed Jun 06 12:20:11 2007 +0000 @@ -156,11 +156,15 @@ gnt_input_add, g_source_remove, NULL, /* input_get_error */ +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else + NULL, +#endif /* padding */ NULL, NULL, - NULL, NULL };
--- a/finch/gntaccount.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/gntaccount.c Wed Jun 06 12:20:11 2007 +0000 @@ -31,6 +31,7 @@ #include <gntlabel.h> #include <gntline.h> #include <gnttree.h> +#include <gntwindow.h> #include <account.h> #include <accountopt.h> @@ -40,6 +41,7 @@ #include <request.h> #include "gntaccount.h" +#include "gntblist.h" #include "finch.h" #include <string.h> @@ -280,7 +282,11 @@ if (dialog->account) { - s = strrchr(username, purple_account_user_split_get_separator(split)); + if(purple_account_user_split_get_reverse(split)) + s = strrchr(username, purple_account_user_split_get_separator(split)); + else + s = strchr(username, purple_account_user_split_get_separator(split)); + if (s != NULL) { *s = '\0'; @@ -743,12 +749,18 @@ finch_accounts_get_handle(), PURPLE_CALLBACK(account_abled_cb), GINT_TO_POINTER(TRUE)); - for (iter = purple_accounts_get_all(); iter; iter = iter->next) { - if (purple_account_get_enabled(iter->data, FINCH_UI)) - break; + iter = purple_accounts_get_all(); + if (iter) { + for (; iter; iter = iter->next) { + if (purple_account_get_enabled(iter->data, FINCH_UI)) + break; + } + if (!iter) + finch_accounts_show_all(); + } else { + edit_account(NULL); + finch_accounts_show_all(); } - if (!iter) - finch_accounts_show_all(); } void finch_accounts_uninit() @@ -865,25 +877,25 @@ } auth_and_add; static void -authorize_and_add_cb(auth_and_add *aa) +free_auth_and_add(auth_and_add *aa) { - aa->auth_cb(aa->data); - purple_blist_request_add_buddy(aa->account, aa->username, - NULL, aa->alias); - g_free(aa->username); g_free(aa->alias); g_free(aa); } static void +authorize_and_add_cb(auth_and_add *aa) +{ + aa->auth_cb(aa->data); + purple_blist_request_add_buddy(aa->account, aa->username, + NULL, aa->alias); +} + +static void deny_no_add_cb(auth_and_add *aa) { aa->deny_cb(aa->data); - - g_free(aa->username); - g_free(aa->alias); - g_free(aa); } static void * @@ -912,19 +924,47 @@ (message != NULL ? ": " : "."), (message != NULL ? message : "")); if (!on_list) { + GntWidget *widget; + GList *iter; auth_and_add *aa = g_new(auth_and_add, 1); + aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb; aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb; aa->data = user_data; aa->username = g_strdup(remote_user); aa->alias = g_strdup(alias); aa->account = account; - uihandle = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL, + + uihandle = gnt_vwindow_new(FALSE); + gnt_box_set_title(GNT_BOX(uihandle), _("Authorize buddy?")); + gnt_box_set_pad(GNT_BOX(uihandle), 0); + + widget = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL, PURPLE_DEFAULT_ACTION_NONE, account, remote_user, NULL, aa, 2, _("Authorize"), authorize_and_add_cb, _("Deny"), deny_no_add_cb); + gnt_screen_release(widget); + gnt_box_set_toplevel(GNT_BOX(widget), FALSE); + gnt_box_add_widget(GNT_BOX(uihandle), widget); + + gnt_box_add_widget(GNT_BOX(uihandle), gnt_hline_new()); + + widget = finch_retrieve_user_info(account->gc, remote_user); + for (iter = GNT_BOX(widget)->list; iter; iter = iter->next) { + if (GNT_IS_BUTTON(iter->data)) { + gnt_widget_destroy(iter->data); + gnt_box_remove(GNT_BOX(widget), iter->data); + break; + } + } + gnt_box_set_toplevel(GNT_BOX(widget), FALSE); + gnt_screen_release(widget); + gnt_box_add_widget(GNT_BOX(uihandle), widget); + gnt_widget_show(uihandle); + + g_signal_connect_swapped(G_OBJECT(uihandle), "destroy", G_CALLBACK(free_auth_and_add), aa); } else { uihandle = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL, PURPLE_DEFAULT_ACTION_NONE,
--- a/finch/gntblist.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/gntblist.c Wed Jun 06 12:20:11 2007 +0000 @@ -104,6 +104,7 @@ static void add_group(PurpleGroup *group, FinchBlist *ggblist); static void add_chat(PurpleChat *chat, FinchBlist *ggblist); static void add_node(PurpleBlistNode *node, FinchBlist *ggblist); +static void node_update(PurpleBuddyList *list, PurpleBlistNode *node); static void draw_tooltip(FinchBlist *ggblist); static gboolean remove_typing_cb(gpointer null); static void remove_peripherals(FinchBlist *ggblist); @@ -189,6 +190,8 @@ if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) || contact->currentsize < 1) node_remove(list, (PurpleBlistNode*)contact); + else + node_update(list, (PurpleBlistNode*)contact); } else if (!PURPLE_BLIST_NODE_IS_GROUP(node)) { PurpleGroup *group = (PurpleGroup*)node->parent; if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)) || @@ -215,6 +218,9 @@ if (list->ui_data == NULL) return; /* XXX: this is probably the place to auto-join chats */ + if (ggblist->window == NULL) + return; + if (node->ui_data != NULL) { gnt_tree_change_text(GNT_TREE(ggblist->tree), node, 0, get_display_name(node)); @@ -824,17 +830,22 @@ PURPLE_CALLBACK(finch_add_group), group); } +gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name) +{ + PurpleNotifyUserInfo *info = purple_notify_user_info_new(); + gpointer uihandle; + purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); + uihandle = purple_notify_userinfo(conn, name, info, NULL, NULL); + purple_notify_user_info_destroy(info); + + serv_get_info(conn, name); + return uihandle; +} + static void finch_blist_get_buddy_info_cb(PurpleBuddy *buddy, PurpleBlistNode *selected) { - /* Add a userinfo with a "Retrieving information", which will later be updated - * when the server finally returns the information. */ - PurpleNotifyUserInfo *info = purple_notify_user_info_new(); - purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); - purple_notify_userinfo(buddy->account->gc, purple_buddy_get_name(buddy), info, NULL, NULL); - purple_notify_user_info_destroy(info); - - serv_get_info(buddy->account->gc, purple_buddy_get_name(buddy)); + finch_retrieve_user_info(buddy->account->gc, purple_buddy_get_name(buddy)); } static void
--- a/finch/gntblist.h Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/gntblist.h Wed Jun 06 12:20:11 2007 +0000 @@ -90,6 +90,16 @@ */ void finch_blist_set_size(int width, int height); +/** + * Get information about a user. Show immediate feedback. + * + * @param conn The connection to get information fro + * @param name The user to get information about. + * + * @return Returns the ui-handle for the userinfo notification. + */ +gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name); + /*@}*/ #endif
--- a/finch/gntconv.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/gntconv.c Wed Jun 06 12:20:11 2007 +0000 @@ -64,7 +64,7 @@ send_typing_notification(GntWidget *w, FinchConv *ggconv) { const char *text = gnt_entry_get_text(GNT_ENTRY(ggconv->entry)); - gboolean empty = (!text || !*text); + gboolean empty = (!text || !*text || (*text == '/')); if (purple_prefs_get_bool("/finch/conversations/notify_typing")) { PurpleConversation *conv = ggconv->active_conv; PurpleConvIm *im = PURPLE_CONV_IM(conv); @@ -313,12 +313,7 @@ get_info_cb(GntMenuItem *item, gpointer ggconv) { FinchConv *ggc = ggconv; - PurpleNotifyUserInfo *info = purple_notify_user_info_new(); - purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); - purple_notify_userinfo(ggc->active_conv->account->gc, purple_conversation_get_name(ggc->active_conv), info, NULL, NULL); - purple_notify_user_info_destroy(info); - - serv_get_info(purple_conversation_get_gc(ggc->active_conv), + finch_retrieve_user_info(purple_conversation_get_gc(ggc->active_conv), purple_conversation_get_name(ggc->active_conv)); }
--- a/finch/gntnotify.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/gntnotify.c Wed Jun 06 12:20:11 2007 +0000 @@ -69,6 +69,7 @@ gnt_box_set_title(GNT_BOX(window), title); gnt_box_set_fill(GNT_BOX(window), FALSE); gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID); + gnt_box_set_pad(GNT_BOX(window), 0); if (primary) gnt_box_add_widget(GNT_BOX(window), @@ -168,7 +169,7 @@ gnt_label_new_with_format(_("You have mail!"), GNT_TEXT_FLAG_BOLD)); emaildialog.tree = tree = gnt_tree_new_with_columns(3); - gnt_tree_set_column_titles(GNT_TREE(tree), _("Account"), _("From"), _("Subject")); + gnt_tree_set_column_titles(GNT_TREE(tree), _("Account"), _("Sender"), _("Subject")); gnt_tree_set_show_title(GNT_TREE(tree), TRUE); gnt_tree_set_col_width(GNT_TREE(tree), 0, 15); gnt_tree_set_col_width(GNT_TREE(tree), 1, 25); @@ -268,11 +269,11 @@ char *strip = purple_markup_strip_html(info); int tvw, tvh, width, height, ntvw, ntvh; + while (GNT_WIDGET(ui_handle)->parent) + ui_handle = GNT_WIDGET(ui_handle)->parent; gnt_widget_get_size(GNT_WIDGET(ui_handle), &width, &height); gnt_widget_get_size(GNT_WIDGET(msg), &tvw, &tvh); - /* Ideally, I would replace the information in "info". But replacing tagged text is a - * bit nasty right now. So clear the view and add the new stuff instead. */ gnt_text_view_clear(msg); gnt_text_view_append_text_with_flags(msg, strip, GNT_TEXT_FLAG_NORMAL); gnt_text_view_scroll(msg, 0); @@ -280,7 +281,7 @@ ntvw += 3; ntvh++; - gnt_screen_resize_widget(GNT_WIDGET(ui_handle), width + (ntvw - tvw), height + (ntvh - tvh)); + gnt_screen_resize_widget(GNT_WIDGET(ui_handle), width + MAX(0, ntvw - tvw), height + MAX(0, ntvh - tvh)); g_free(strip); g_free(key); } else {
--- a/finch/libgnt/gntbox.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/libgnt/gntbox.c Wed Jun 06 12:20:11 2007 +0000 @@ -293,6 +293,10 @@ { find_next_focus(box); } + else if (strcmp(text, GNT_KEY_BACK_TAB) == 0) + { + find_prev_focus(box); + } } else if (text[0] == '\t') {
--- a/finch/libgnt/gntkeys.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/libgnt/gntkeys.c Wed Jun 06 12:20:11 2007 +0000 @@ -50,6 +50,7 @@ INSERT_KEY("pagedown", GNT_KEY_PGDOWN); INSERT_KEY("insert", GNT_KEY_INS); INSERT_KEY("delete", GNT_KEY_DEL); + INSERT_KEY("back_tab", GNT_KEY_BACK_TAB); INSERT_KEY("left", GNT_KEY_LEFT); INSERT_KEY("right", GNT_KEY_RIGHT);
--- a/finch/libgnt/gntkeys.h Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/libgnt/gntkeys.h Wed Jun 06 12:20:11 2007 +0000 @@ -39,6 +39,7 @@ #define GNT_KEY_BACKSPACE SAFE(key_backspace) #define GNT_KEY_DEL SAFE(key_dc) #define GNT_KEY_INS SAFE(key_ic) +#define GNT_KEY_BACK_TAB SAFE(back_tab) #define GNT_KEY_CTRL_A "\001" #define GNT_KEY_CTRL_B "\002"
--- a/finch/libgnt/gntmain.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/libgnt/gntmain.c Wed Jun 06 12:20:11 2007 +0000 @@ -1,5 +1,5 @@ #define _GNU_SOURCE -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__unix__) #define _XOPEN_SOURCE_EXTENDED #endif @@ -359,8 +359,7 @@ switch (sig) { #ifdef SIGWINCH case SIGWINCH: - werase(stdscr); - wrefresh(stdscr); + erase(); g_idle_add(refresh_screen, NULL); org_winch_handler(sig); signal(SIGWINCH, sighandler);
--- a/finch/libgnt/gntwm.c Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/libgnt/gntwm.c Wed Jun 06 12:20:11 2007 +0000 @@ -1,5 +1,5 @@ #define _GNU_SOURCE -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__unix__) #define _XOPEN_SOURCE_EXTENDED #endif @@ -980,12 +980,11 @@ GntWM *wm = GNT_WM(bindable); endwin(); - refresh(); - curs_set(0); /* endwin resets the cursor to normal */ g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, NULL); update_screen(wm); draw_taskbar(wm, TRUE); + curs_set(0); /* endwin resets the cursor to normal */ return FALSE; }
--- a/finch/libgnt/wms/Makefile.am Wed Jun 06 07:51:14 2007 +0000 +++ b/finch/libgnt/wms/Makefile.am Wed Jun 06 12:20:11 2007 +0000 @@ -1,9 +1,16 @@ s_la_LDFLAGS = -module -avoid-version +irssi_la_LDFLAGS = -module -avoid-version plugin_LTLIBRARIES = \ - s.la + s.la \ + irssi.la + +plugindir = $(libdir)/gnt -plugindir = $(libdir)/finch +irssi_la_SOURCES = irssi.c +irssi_la_LIBADD = \ + $(GLIB_LIBS) \ + $(top_builddir)/finch/libgnt/libgnt.la s_la_SOURCES = s.c s_la_LIBADD = \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/wms/irssi.c Wed Jun 06 12:20:11 2007 +0000 @@ -0,0 +1,161 @@ +/** + * 1. Buddylist and conversation windows are borderless and full height of the screen. + * 2. Conversation windows will have (full-screen-width - buddylist-width) width + * - It's possible to auto-resize the conversation windows when the buddylist + * is closed/opened/resized to keep this always true. But resizing the textview + * in conversation window is rather costly, especially when there's a lot of text + * in the scrollback. So it's not done yet. + * 3. All the other windows are always centered. + */ +#include <string.h> +#include <sys/types.h> + +#include "gnt.h" +#include "gntbox.h" +#include "gntmenu.h" +#include "gntstyle.h" +#include "gntwm.h" +#include "gntwindow.h" +#include "gntlabel.h" + +#define TYPE_IRSSI (irssi_get_gtype()) + +typedef struct _Irssi +{ + GntWM inherit; +} Irssi; + +typedef struct _IrssiClass +{ + GntWMClass inherit; +} IrssiClass; + +GType irssi_get_gtype(void); +void gntwm_init(GntWM **wm); + +static void (*org_new_window)(GntWM *wm, GntWidget *win); + +/* This is changed whenever the buddylist is opened/closed or resized. */ +static int buddylistwidth; + +static gboolean +is_budddylist(GntWidget *win) +{ + const char *name = gnt_widget_get_name(win); + if (name && strcmp(name, "buddylist") == 0) + return TRUE; + return FALSE; +} + +static void +remove_border_set_position_size(GntWM *wm, GntWidget *win, int x, int y, int w, int h) +{ + gnt_box_set_toplevel(GNT_BOX(win), FALSE); + GNT_WIDGET_SET_FLAGS(win, GNT_WIDGET_CAN_TAKE_FOCUS); + + gnt_widget_set_position(win, x, y); + mvwin(win->window, y, x); + gnt_widget_set_size(win, (w < 0) ? -1 : w + 2, h + 2); +} + +static void +irssi_new_window(GntWM *wm, GntWidget *win) +{ + const char *name; + + name = gnt_widget_get_name(win); + if (!name || strcmp(name, "conversation-window")) { + if (!GNT_IS_MENU(win) && !GNT_WIDGET_IS_FLAG_SET(win, GNT_WIDGET_TRANSIENT)) { + if ((!name || strcmp(name, "buddylist"))) { + int w, h, x, y; + gnt_widget_get_size(win, &w, &h); + x = (getmaxx(stdscr) - w) / 2; + y = (getmaxy(stdscr) - h) / 2; + gnt_widget_set_position(win, x, y); + mvwin(win->window, y, x); + } else { + remove_border_set_position_size(wm, win, 0, 0, -1, getmaxy(stdscr) - 1); + gnt_widget_get_size(win, &buddylistwidth, NULL); + mvwvline(stdscr, 0, buddylistwidth, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), getmaxy(stdscr) - 1); + buddylistwidth++; + } + } + org_new_window(wm, win); + return; + } + + /* The window we have here is a conversation window. */ + remove_border_set_position_size(wm, win, buddylistwidth, 0, + getmaxx(stdscr) - buddylistwidth, getmaxy(stdscr) - 1); + org_new_window(wm, win); +} + +static void +irssi_window_resized(GntWM *wm, GntNode *node) +{ + if (!is_budddylist(node->me)) + return; + + if (buddylistwidth) + mvwvline(stdscr, 0, buddylistwidth - 1, + ' ' | COLOR_PAIR(GNT_COLOR_NORMAL), getmaxy(stdscr) - 1); + gnt_widget_get_size(node->me, &buddylistwidth, NULL); + mvwvline(stdscr, 0, buddylistwidth, ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), getmaxy(stdscr) - 1); + buddylistwidth++; +} + +static gboolean +irssi_close_window(GntWM *wm, GntWidget *win) +{ + if (is_budddylist(win)) + buddylistwidth = 0; + return FALSE; +} + +static void +irssi_class_init(IrssiClass *klass) +{ + GntWMClass *pclass = GNT_WM_CLASS(klass); + + org_new_window = pclass->new_window; + + pclass->new_window = irssi_new_window; + pclass->window_resized = irssi_window_resized; + pclass->close_window = irssi_close_window; + + gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass)); + GNTDEBUG; +} + +void gntwm_init(GntWM **wm) +{ + buddylistwidth = 0; + *wm = g_object_new(TYPE_IRSSI, NULL); +} + +GType irssi_get_gtype(void) +{ + static GType type = 0; + + if(type == 0) { + static const GTypeInfo info = { + sizeof(IrssiClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc)irssi_class_init, + NULL, + NULL, /* class_data */ + sizeof(Irssi), + 0, /* n_preallocs */ + NULL, /* instance_init */ + NULL + }; + + type = g_type_register_static(GNT_TYPE_WM, + "GntIrssi", + &info, 0); + } + + return type; +} +
--- a/libpurple/Makefile.am Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/Makefile.am Wed Jun 06 12:20:11 2007 +0000 @@ -151,7 +151,7 @@ dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \ - connection.h conversation.h core.h log.h notify.h prefs.h roomlist.h \ + connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \ savedstatuses.h status.h server.h util.h xmlnode.h purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders))
--- a/libpurple/account.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/account.c Wed Jun 06 12:20:11 2007 +0000 @@ -417,7 +417,7 @@ schedule_accounts_save() { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/accountopt.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/accountopt.c Wed Jun 06 12:20:11 2007 +0000 @@ -308,6 +308,7 @@ split->text = g_strdup(text); split->field_sep = sep; split->default_value = g_strdup(default_value); + split->reverse = TRUE; return split; } @@ -345,3 +346,19 @@ return split->field_sep; } + +gboolean +purple_account_user_split_get_reverse(const PurpleAccountUserSplit *split) +{ + g_return_val_if_fail(split != NULL, FALSE); + + return split->reverse; +} + +void +purple_account_user_split_set_reverse(PurpleAccountUserSplit *split, gboolean reverse) +{ + g_return_if_fail(split != NULL); + + split->reverse = reverse; +}
--- a/libpurple/accountopt.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/accountopt.h Wed Jun 06 12:20:11 2007 +0000 @@ -64,6 +64,9 @@ char *text; /**< The text that will appear to the user. */ char *default_value; /**< The default value. */ char field_sep; /**< The field separator. */ + gboolean reverse; /**< TRUE if the separator should be found + starting a the end of the string, FALSE + otherwise */ } PurpleAccountUserSplit; @@ -353,6 +356,23 @@ */ char purple_account_user_split_get_separator(const PurpleAccountUserSplit *split); +/** + * Returns the 'reverse' value for an account split. + * + * @param split The account username split. + * + * @return The 'reverse' value. + */ +gboolean purple_account_user_split_get_reverse(const PurpleAccountUserSplit *split); + +/** + * Sets the 'reverse' value for an account split. + * + * @param split The account username split. + * @param reverse The 'reverse' value + */ +void purple_account_user_split_set_reverse(PurpleAccountUserSplit *split, gboolean reverse); + /*@}*/ #ifdef __cplusplus
--- a/libpurple/blist.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/blist.c Wed Jun 06 12:20:11 2007 +0000 @@ -365,7 +365,7 @@ purple_blist_schedule_save() { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); } @@ -2498,6 +2498,13 @@ return node->flags; } +PurpleBlistNodeType +purple_blist_node_get_type(PurpleBlistNode *node) +{ + g_return_val_if_fail(node != NULL, PURPLE_BLIST_OTHER_NODE); + return node->type; +} + void purple_blist_node_set_bool(PurpleBlistNode* node, const char *key, gboolean data) {
--- a/libpurple/blist.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/blist.h Wed Jun 06 12:20:11 2007 +0000 @@ -864,6 +864,15 @@ */ PurpleBlistNodeFlags purple_blist_node_get_flags(PurpleBlistNode *node); +/** + * Get the type of a given node. + * + * @param node The node. + * + * @return The type of the node. + */ +PurpleBlistNodeType purple_blist_node_get_type(PurpleBlistNode *node); + /*@}*/ /**
--- a/libpurple/buddyicon.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/buddyicon.c Wed Jun 06 12:20:11 2007 +0000 @@ -24,7 +24,6 @@ */ #include "internal.h" #include "buddyicon.h" -#include "cipher.h" #include "conversation.h" #include "dbus-maybe.h" #include "debug.h" @@ -93,33 +92,6 @@ } } -static char * -purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len) -{ - PurpleCipherContext *context; - gchar digest[41]; - - context = purple_cipher_context_new_by_name("sha1", NULL); - if (context == NULL) - { - purple_debug_error("buddyicon", "Could not find sha1 cipher\n"); - g_return_val_if_reached(NULL); - } - - /* Hash the icon data */ - purple_cipher_context_append(context, icon_data, icon_len); - if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL)) - { - purple_debug_error("buddyicon", "Failed to get SHA-1 digest.\n"); - g_return_val_if_reached(NULL); - } - purple_cipher_context_destroy(context); - - /* Return the filename */ - return g_strdup_printf("%s.%s", digest, - purple_util_get_image_extension(icon_data, icon_len)); -} - static void purple_buddy_icon_data_cache(PurpleStoredImage *img) { @@ -238,7 +210,7 @@ if (filename == NULL) { - file = purple_buddy_icon_data_calculate_filename(icon_data, icon_len); + file = purple_util_get_image_filename(icon_data, icon_len); if (file == NULL) { g_free(icon_data); @@ -966,7 +938,7 @@ g_free(path); - new_filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len); + new_filename = purple_util_get_image_filename(icon_data, icon_len); if (new_filename == NULL) { purple_debug_error("buddyicon",
--- a/libpurple/buddyicon.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/buddyicon.h Wed Jun 06 12:20:11 2007 +0000 @@ -31,6 +31,7 @@ #include "blist.h" #include "imgstore.h" #include "prpl.h" +#include "util.h" #ifdef __cplusplus extern "C" {
--- a/libpurple/connection.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/connection.c Wed Jun 06 12:20:11 2007 +0000 @@ -72,7 +72,7 @@ if (on && !gc->keepalive) { purple_debug_info("connection", "Activating keepalive.\n"); - gc->keepalive = purple_timeout_add(30000, send_keepalive, gc); + gc->keepalive = purple_timeout_add_seconds(30, send_keepalive, gc); } else if (!on && gc->keepalive > 0) {
--- a/libpurple/conversation.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/conversation.c Wed Jun 06 12:20:11 2007 +0000 @@ -21,6 +21,7 @@ */ #include "internal.h" #include "blist.h" +#include "cmds.h" #include "conversation.h" #include "dbus-maybe.h" #include "debug.h" @@ -108,8 +109,12 @@ type = purple_conversation_get_type(conv); - /* Always linkfy the text for display */ - displayed = purple_markup_linkify(message); + /* Always linkfy the text for display, unless we're + * explicitly asked to do otheriwse*/ + if(msgflags & PURPLE_MESSAGE_NO_LINKIFY) + displayed = g_strdup(message); + else + displayed = purple_markup_linkify(message); if ((conv->features & PURPLE_CONNECTION_HTML) && !(msgflags & PURPLE_MESSAGE_RAW)) @@ -1010,7 +1015,7 @@ conv = purple_conv_im_get_conversation(im); name = purple_conversation_get_name(conv); - im->typing_timeout = purple_timeout_add(timeout * 1000, reset_typing_cb, conv); + im->typing_timeout = purple_timeout_add_seconds(timeout, reset_typing_cb, conv); } void @@ -1578,7 +1583,9 @@ } g_free(escaped); - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + purple_conversation_write(conv, NULL, tmp, + PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY, + time(NULL)); g_free(tmp); } @@ -1704,7 +1711,9 @@ g_free(escaped2); } - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + purple_conversation_write(conv, NULL, tmp, + PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY, + time(NULL)); } } @@ -1781,7 +1790,9 @@ } g_free(escaped); - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + purple_conversation_write(conv, NULL, tmp, + PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY, + time(NULL)); g_free(tmp); } @@ -1993,6 +2004,29 @@ return cb->name; } +GList * +purple_conversation_get_extended_menu(PurpleConversation *conv) +{ + GList *menu = NULL; + + g_return_val_if_fail(conv != NULL, NULL); + + purple_signal_emit(purple_conversations_get_handle(), + "conversation-extended-menu", conv, &menu); + return menu; +} + +gboolean +purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline, + const gchar *markup, gchar **error) +{ + char *mark = (markup && *markup) ? NULL : g_markup_escape_text(cmdline, -1), *err = NULL; + PurpleCmdStatus status = purple_cmd_do_command(conv, cmdline, mark ? mark : markup, error ? error : &err); + g_free(mark); + g_free(err); + return (status == PURPLE_CMD_STATUS_OK); +} + void * purple_conversations_get_handle(void) { @@ -2256,6 +2290,12 @@ PURPLE_SUBTYPE_CONVERSATION), purple_value_new(PURPLE_TYPE_STRING), purple_value_new(PURPLE_TYPE_STRING)); + + purple_signal_register(handle, "conversation-extended-menu", + purple_marshal_VOID__POINTER_POINTER, NULL, 2, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CONVERSATION), + purple_value_new(PURPLE_TYPE_BOXED, "GList **")); } void
--- a/libpurple/conversation.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/conversation.h Wed Jun 06 12:20:11 2007 +0000 @@ -116,7 +116,9 @@ PURPLE_MESSAGE_RAW = 0x0800, /**< "Raw" message - don't apply formatting */ PURPLE_MESSAGE_IMAGES = 0x1000, /**< Message contains images */ - PURPLE_MESSAGE_NOTIFY = 0x2000 /**< Message is a notification */ + PURPLE_MESSAGE_NOTIFY = 0x2000, /**< Message is a notification */ + PURPLE_MESSAGE_NO_LINKIFY = 0x4000 /**< Message should not be auto- + linkified */ } PurpleMessageFlags; @@ -1190,6 +1192,30 @@ */ void purple_conv_chat_cb_destroy(PurpleConvChatBuddy *cb); +/** + * Retrieves the extended menu items for the conversation. + * + * @param conv The conversation. + * + * @return A list of PurpleMenuAction items, harvested by the + * chat-extended-menu signal. The list and the menuaction + * items should be freed by the caller. + */ +GList * purple_conversation_get_extended_menu(PurpleConversation *conv); + +/** + * Perform a command in a conversation. Similar to @see purple_cmd_do_command + * + * @param conv The conversation. + * @param cmdline The entire command including the arguments. + * @param markup @c NULL, or the formatted command line. + * @param error If the command failed errormsg is filled in with the appropriate error + * message, if not @c NULL. It must be freed by the caller with g_free(). + * + * @return @c TRUE if the command was executed successfully, @c FALSE otherwise. + */ +gboolean purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline, const gchar *markup, gchar **error); + /*@}*/ /**************************************************************************/
--- a/libpurple/core.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/core.c Wed Jun 06 12:20:11 2007 +0000 @@ -48,7 +48,11 @@ #include "util.h" #ifdef HAVE_DBUS +# define DBUS_API_SUBJECT_TO_CHANGE +# include <dbus/dbus.h> +# include "dbus-purple.h" # include "dbus-server.h" +# include "dbus-bindings.h" #endif struct PurpleCore @@ -276,6 +280,91 @@ return _ops; } +#ifdef HAVE_DBUS +static char *purple_dbus_owner_user_dir(void) +{ + DBusMessage *msg = NULL, *reply = NULL; + DBusConnection *dbus_connection = NULL; + DBusError dbus_error; + char *remote_user_dir = NULL; + + if ((dbus_connection = purple_dbus_get_connection()) == NULL) + return NULL; + + if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleUserDir")) == NULL) + return NULL; + + dbus_error_init(&dbus_error); + reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error); + dbus_message_unref(msg); + dbus_error_free(&dbus_error); + + if (reply) + { + dbus_error_init(&dbus_error); + dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_STRING, &remote_user_dir, DBUS_TYPE_INVALID); + remote_user_dir = g_strdup(remote_user_dir); + dbus_error_free(&dbus_error); + dbus_message_unref(reply); + } + + return remote_user_dir; +} + +static void purple_dbus_owner_show_buddy_list(void) +{ + DBusError dbus_error; + DBusMessage *msg = NULL, *reply = NULL; + DBusConnection *dbus_connection = NULL; + + if ((dbus_connection = purple_dbus_get_connection()) == NULL) + return; + + if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleBlistShow")) == NULL) + return; + + dbus_error_init(&dbus_error); + if ((reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error)) != NULL) + { + dbus_message_unref(msg); + } + dbus_error_free(&dbus_error); +} +#endif /* HAVE_DBUS */ + +gboolean +purple_core_ensure_single_instance() +{ + gboolean is_single_instance = TRUE; +#ifdef HAVE_DBUS + /* in the future, other mechanisms might have already set this to FALSE */ + if (is_single_instance) + { + if (!purple_dbus_is_owner()) + { + const char *user_dir = purple_user_dir(); + char *dbus_owner_user_dir = purple_dbus_owner_user_dir(); + + if (NULL == user_dir && NULL != dbus_owner_user_dir) + is_single_instance = TRUE; + else if (NULL != user_dir && NULL == dbus_owner_user_dir) + is_single_instance = TRUE; + else if (NULL == user_dir && NULL == dbus_owner_user_dir) + is_single_instance = FALSE; + else + is_single_instance = strcmp(dbus_owner_user_dir, user_dir); + + if (!is_single_instance) + purple_dbus_owner_show_buddy_list(); + + g_free(dbus_owner_user_dir); + } + } +#endif /* HAVE_DBUS */ + + return is_single_instance; +} + static gboolean move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative) {
--- a/libpurple/core.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/core.h Wed Jun 06 12:20:11 2007 +0000 @@ -121,6 +121,16 @@ */ gboolean purple_core_migrate(void); +/** + * Ensures that only one instance is running. + * + * @return A boolean such that @c TRUE indicates that this is the first instance, + * whereas @c FALSE indicates that there is another instance running. + * + * @since 2.1.0 + */ +gboolean purple_core_ensure_single_instance(void); + #ifdef __cplusplus } #endif
--- a/libpurple/dbus-server.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/dbus-server.c Wed Jun 06 12:20:11 2007 +0000 @@ -65,6 +65,12 @@ static GHashTable *map_id_type; static gchar *init_error; +static int dbus_request_name_reply = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + +gboolean purple_dbus_is_owner(void) +{ + return(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER == dbus_request_name_reply); +} /** * This function initializes the pointer-id traslation system. It @@ -592,6 +598,7 @@ return; } + dbus_request_name_reply = result = dbus_bus_request_name(purple_dbus_connection, DBUS_SERVICE_PURPLE, 0, &error);
--- a/libpurple/dbus-server.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/dbus-server.h Wed Jun 06 12:20:11 2007 +0000 @@ -169,6 +169,13 @@ void *purple_dbus_get_handle(void); /** + * Determines whether this instance owns the DBus service name + * + * @since 2.1.0 + */ +gboolean purple_dbus_is_owner(void); + +/** * Starts Purple's D-BUS server. It is responsible for handling DBUS * requests from other applications. */
--- a/libpurple/eventloop.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/eventloop.c Wed Jun 06 12:20:11 2007 +0000 @@ -35,6 +35,17 @@ return ops->timeout_add(interval, function, data); } +guint +purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) +{ + PurpleEventLoopUiOps *ops = purple_eventloop_get_ui_ops(); + + if (ops->timeout_add_seconds) + return ops->timeout_add_seconds(interval, function, data); + else + return ops->timeout_add(1000 * interval, function, data); +} + gboolean purple_timeout_remove(guint tag) {
--- a/libpurple/eventloop.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/eventloop.h Wed Jun 06 12:20:11 2007 +0000 @@ -48,7 +48,7 @@ struct _PurpleEventLoopUiOps { /** - * Creates a callback timer. + * Creates a callback timer with an interval measured in milliseconds. * @see g_timeout_add, purple_timeout_add **/ guint (*timeout_add)(guint interval, GSourceFunc function, gpointer data); @@ -81,7 +81,20 @@ */ int (*input_get_error)(int fd, int *error); - void (*_purple_reserved1)(void); + /** + * Creates a callback timer with an interval measured in seconds. + * + * This allows UIs to group timers for better power efficiency. For + * this reason, @a interval may be rounded by up to a second. + * + * Implementation of this UI op is optional. If it's not implemented, + * calls to purple_timeout_add_seconds() will be serviced by the + * timeout_add UI op. + * + * @see g_timeout_add_seconds, purple_timeout_add_seconds() + **/ + guint (*timeout_add_seconds)(guint interval, GSourceFunc function, gpointer data); + void (*_purple_reserved2)(void); void (*_purple_reserved3)(void); void (*_purple_reserved4)(void); @@ -93,10 +106,15 @@ /*@{*/ /** * Creates a callback timer. + * * The timer will repeat until the function returns @c FALSE. The * first call will be at the end of the first interval. + * + * If the timer is in a multiple of seconds, use purple_timeout_add_seconds() + * instead as it allows UIs to group timers for power efficiency. + * * @param interval The time between calls of the function, in - * milliseconds. + * milliseconds. * @param function The function to call. * @param data data to pass to @a function. * @return A handle to the timer which can be passed to @@ -105,6 +123,24 @@ guint purple_timeout_add(guint interval, GSourceFunc function, gpointer data); /** + * Creates a callback timer. + * + * The timer will repeat until the function returns @c FALSE. The + * first call will be at the end of the first interval. + * + * This function allows UIs to group timers for better power efficiency. For + * this reason, @a interval may be rounded by up to a second. + * + * @param interval The time between calls of the function, in + * seconds. + * @param function The function to call. + * @param data data to pass to @a function. + * @return A handle to the timer which can be passed to + * purple_timeout_remove to remove the timer. + */ +guint purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data); + +/** * Removes a timeout handler. * * @param handle The handle, as returned by purple_timeout_add.
--- a/libpurple/example/nullclient.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/example/nullclient.c Wed Jun 06 12:20:11 2007 +0000 @@ -108,11 +108,15 @@ glib_input_add, g_source_remove, NULL, +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else + NULL, +#endif /* padding */ NULL, NULL, - NULL, NULL }; /*** End of the eventloop functions. ***/
--- a/libpurple/ft.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/ft.c Wed Jun 06 12:20:11 2007 +0000 @@ -23,6 +23,7 @@ * */ #include "internal.h" +#include "dbus-maybe.h" #include "ft.h" #include "network.h" #include "notify.h" @@ -56,6 +57,7 @@ g_return_val_if_fail(who != NULL, NULL); xfer = g_new0(PurpleXfer, 1); + PURPLE_DBUS_REGISTER_POINTER(xfer, PurpleXfer); xfer->ref = 1; xfer->type = type; @@ -97,6 +99,7 @@ g_free(xfer->remote_ip); g_free(xfer->local_filename); + PURPLE_DBUS_UNREGISTER_POINTER(xfer); g_free(xfer); xfers = g_list_remove(xfers, xfer); } @@ -551,6 +554,13 @@ return xfer->account; } +const char * +purple_xfer_get_remote_user(const PurpleXfer *xfer) +{ + g_return_val_if_fail(xfer != NULL, NULL); + return xfer->who; +} + PurpleXferStatusType purple_xfer_get_status(const PurpleXfer *xfer) {
--- a/libpurple/ft.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/ft.h Wed Jun 06 12:20:11 2007 +0000 @@ -237,6 +237,15 @@ PurpleAccount *purple_xfer_get_account(const PurpleXfer *xfer); /** + * Returns the name of the remote user. + * + * @param xfer The file transfer. + * + * @return The name of the remote user. + */ +const char *purple_xfer_get_remote_user(const PurpleXfer *xfer); + +/** * Returns the status of the xfer. * * @param xfer The file transfer.
--- a/libpurple/idle.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/idle.c Wed Jun 06 12:20:11 2007 +0000 @@ -224,7 +224,11 @@ if (time_until_next_idle_event == 0) idle_timer = 0; else - idle_timer = purple_timeout_add(1000 * (time_until_next_idle_event + 1), check_idleness_timer, NULL); + { + /* +1 for the boundary, + * +1 more for g_timeout_add_seconds rounding. */ + idle_timer = purple_timeout_add_seconds(time_until_next_idle_event + 2, check_idleness_timer, NULL); + } return FALSE; } @@ -303,8 +307,10 @@ void purple_idle_init() { - /* Add the timer to check if we're idle */ - idle_timer = purple_timeout_add(1000 * (IDLEMARK + 1), check_idleness_timer, NULL); + /* Add the timer to check if we're idle. + * IDLEMARK + 1 as the boundary, + * +1 more for g_timeout_add_seconds rounding. */ + idle_timer = purple_timeout_add_seconds((IDLEMARK + 2), check_idleness_timer, NULL); purple_signal_connect(purple_conversations_get_handle(), "sent-im-msg", purple_idle_get_handle(),
--- a/libpurple/imgstore.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/imgstore.c Wed Jun 06 12:20:11 2007 +0000 @@ -25,6 +25,7 @@ */ #include <glib.h> +#include "dbus-maybe.h" #include "debug.h" #include "imgstore.h" #include "util.h" @@ -56,6 +57,7 @@ g_return_val_if_fail(size > 0, 0); img = g_new(PurpleStoredImage, 1); + PURPLE_DBUS_REGISTER_POINTER(img, PurpleStoredImage); img->data = data; img->size = size; img->filename = g_strdup(filename); @@ -159,6 +161,7 @@ g_free(img->data); g_free(img->filename); + PURPLE_DBUS_UNREGISTER_POINTER(img); g_free(img); img = NULL; }
--- a/libpurple/log.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/log.c Wed Jun 06 12:20:11 2007 +0000 @@ -32,6 +32,7 @@ #include "prefs.h" #include "util.h" #include "stringref.h" +#include "imgstore.h" static GSList *loggers = NULL; @@ -690,6 +691,109 @@ return g_strdup(purple_time_format(&tm)); } +/* NOTE: This can return msg (which you may or may not want to g_free()) + * NOTE: or a newly allocated string which you MUST g_free(). */ +static char * +convert_image_tags(const PurpleLog *log, const char *msg) +{ + const char *tmp; + const char *start; + const char *end; + GData *attributes; + GString *newmsg = NULL; + + tmp = msg; + + newmsg = g_string_new(""); + + while (purple_markup_find_tag("img", tmp, &start, &end, &attributes)) { + int imgid = 0; + char *idstr = NULL; + + if (newmsg == NULL) + newmsg = g_string_new(""); + + /* copy any text before the img tag */ + if (tmp < start) + g_string_append_len(newmsg, tmp, start - tmp); + + idstr = g_datalist_get_data(&attributes, "id"); + + imgid = atoi(idstr); + if (imgid != 0) + { + FILE *image_file; + char *dir; + PurpleStoredImage *image; + gconstpointer image_data; + char *new_filename = NULL; + char *path = NULL; + size_t image_byte_count; + + image = purple_imgstore_find_by_id(imgid); + if (image == NULL) + { + /* This should never happen. */ + g_string_free(newmsg, TRUE); + g_return_val_if_reached((char *)msg); + } + + image_data = purple_imgstore_get_data(image); + image_byte_count = purple_imgstore_get_size(image); + dir = purple_log_get_log_dir(log->type, log->name, log->account); + new_filename = purple_util_get_image_filename(image_data, image_byte_count); + + path = g_build_filename(dir, new_filename, NULL); + + /* Only save unique files. */ + if (!g_file_test(path, G_FILE_TEST_EXISTS)) + { + if ((image_file = g_fopen(path, "wb")) != NULL) + { + if (!fwrite(image_data, image_byte_count, 1, image_file)) + { + purple_debug_error("log", "Error writing %s: %s\n", + path, strerror(errno)); + fclose(image_file); + + /* Attempt to not leave half-written files around. */ + unlink(path); + } + else + { + purple_debug_info("log", "Wrote image file: %s\n", path); + fclose(image_file); + } + } + else + { + purple_debug_error("log", "Unable to create file %s: %s\n", + path, strerror(errno)); + } + } + + /* Write the new image tag */ + g_string_append_printf(newmsg, "<IMG SRC=\"%s\">", new_filename); + g_free(new_filename); + g_free(path); + } + + /* Continue from the end of the tag */ + tmp = end + 1; + } + + if (newmsg == NULL) + { + /* No images were found to change. */ + return (char *)msg; + } + + /* Append any remaining message data */ + g_string_append(newmsg, tmp); + + return g_string_free(newmsg, FALSE); +} + void purple_log_common_writer(PurpleLog *log, const char *ext) { PurpleLogCommonLoggerData *data = log->logger_data; @@ -1191,6 +1295,7 @@ const char *from, time_t time, const char *message) { char *msg_fixed; + char *image_corrected_msg; char *date; char *header; PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account)); @@ -1231,7 +1336,14 @@ if(!data->file) return 0; - purple_markup_html_to_xhtml(message, &msg_fixed, NULL); + image_corrected_msg = convert_image_tags(log, message); + purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL); + + /* Yes, this breaks encapsulation. But it's a static function and + * this saves a needless strdup(). */ + if (image_corrected_msg != message) + g_free(image_corrected_msg); + date = log_get_timestamp(log, time); if(log->type == PURPLE_LOG_SYSTEM){
--- a/libpurple/plugins/perl/common/Account.xs Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/plugins/perl/common/Account.xs Wed Jun 06 12:20:11 2007 +0000 @@ -215,6 +215,7 @@ t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(list), i, 0), t_sl)); } purple_account_add_buddies(account, t_GL); + g_list_free(t_GL); void purple_account_add_buddy(account, buddy) @@ -252,6 +253,8 @@ t_GL2 = g_list_append(t_GL2, SvPV(*av_fetch((AV *)SvRV(B), i, 0), t_sl)); } purple_account_remove_buddies(account, t_GL1, t_GL2); + g_list_free(t_GL1); + g_list_free(t_GL2); void purple_account_remove_buddy(account, buddy, group)
--- a/libpurple/plugins/perl/common/BuddyList.xs Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/plugins/perl/common/BuddyList.xs Wed Jun 06 12:20:11 2007 +0000 @@ -112,6 +112,10 @@ Purple::BuddyList::Group group Purple::Account account +const char * +purple_group_get_name(group) + Purple::BuddyList::Group group + MODULE = Purple::BuddyList PACKAGE = Purple::BuddyList PREFIX = purple_blist_ PROTOTYPES: ENABLE @@ -248,6 +252,9 @@ Purple::Handle purple_blist_get_handle() +Purple::BuddyList::Node +purple_blist_get_root() + void purple_blist_init() @@ -263,7 +270,7 @@ PREINIT: GList *l; PPCODE: - for (l = purple_blist_node_get_extended_menu(node); l != NULL; l = l->next) { + for (l = purple_blist_node_get_extended_menu(node); l != NULL; l = g_list_delete_link(l, l)) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Menu::Action"))); } @@ -308,6 +315,15 @@ purple_blist_node_get_flags(node) Purple::BuddyList::Node node +Purple::BuddyList::NodeType +purple_blist_node_get_type(node) + Purple::BuddyList::Node node + +Purple::BuddyList::Node +purple_blist_node_next(node, offline) + Purple::BuddyList::Node node + gboolean offline + MODULE = Purple::BuddyList PACKAGE = Purple::BuddyList::Chat PREFIX = purple_chat_ PROTOTYPES: ENABLE
--- a/libpurple/plugins/perl/common/Conversation.xs Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/plugins/perl/common/Conversation.xs Wed Jun 06 12:20:11 2007 +0000 @@ -218,6 +218,21 @@ Purple::Conversation conv Purple::Account account +void +purple_conversation_write(conv, who, message, flags, mtime) + Purple::Conversation conv + const char *who + const char *message + Purple::MessageFlags flags + time_t mtime + +gboolean +purple_conversation_do_command(conv, cmdline, markup, error) + Purple::Conversation conv + const char *cmdline + const char *markup + char **error + MODULE = Purple::Conversation PACKAGE = Purple::Conversation::IM PREFIX = purple_conv_im_ PROTOTYPES: ENABLE
--- a/libpurple/plugins/perl/common/Prefs.xs Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/plugins/perl/common/Prefs.xs Wed Jun 06 12:20:11 2007 +0000 @@ -94,8 +94,9 @@ PREINIT: GList *l; PPCODE: - for (l = purple_prefs_get_string_list(name); l != NULL; l = l->next) { - XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::PrefValue"))); + for (l = purple_prefs_get_string_list(name); l != NULL; l = g_list_delete_link(l, l)) { + XPUSHs(sv_2mortal(newSVpv(l->data, 0))); + g_free(l->data); } Purple::PrefType
--- a/libpurple/plugins/perl/common/module.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/plugins/perl/common/module.h Wed Jun 06 12:20:11 2007 +0000 @@ -69,6 +69,7 @@ /* blist.h */ typedef PurpleBlistNode * Purple__BuddyList__Node; typedef PurpleBlistNodeFlags Purple__BuddyList__NodeFlags; +typedef PurpleBlistNodeType Purple__BuddyList__NodeType; typedef PurpleBlistUiOps * Purple__BuddyList__UiOps; typedef PurpleBuddyList * Purple__BuddyList; typedef PurpleBuddy * Purple__BuddyList__Buddy;
--- a/libpurple/plugins/perl/common/typemap Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/plugins/perl/common/typemap Wed Jun 06 12:20:11 2007 +0000 @@ -52,6 +52,7 @@ Purple::BuddyList::Group T_PurpleObj Purple::BuddyList::Node T_PurpleObj Purple::BuddyList::NodeFlags T_IV +Purple::BuddyList::NodeType T_IV Purple::BuddyList::UiOps T_PurpleObj Purple::Cipher T_PurpleObj
--- a/libpurple/pounce.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/pounce.c Wed Jun 06 12:20:11 2007 +0000 @@ -273,7 +273,7 @@ schedule_pounces_save(void) { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/prefs.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/prefs.c Wed Jun 06 12:20:11 2007 +0000 @@ -226,7 +226,7 @@ schedule_prefs_save(void) { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/protocols/irc/irc.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Wed Jun 06 12:20:11 2007 +0000 @@ -814,7 +814,8 @@ static PurplePluginProtocolInfo prpl_info = { - OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL, + OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL | + OPT_PROTO_SLASH_COMMANDS_NATIVE, NULL, /* user_splits */ NULL, /* protocol_options */ NO_BUDDY_ICONS, /* icon_spec */
--- a/libpurple/protocols/jabber/libxmpp.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Jun 06 12:20:11 2007 +0000 @@ -43,9 +43,11 @@ { #ifdef HAVE_CYRUS_SASL OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | - OPT_PROTO_MAIL_CHECK | OPT_PROTO_PASSWORD_OPTIONAL, + OPT_PROTO_MAIL_CHECK | OPT_PROTO_PASSWORD_OPTIONAL | + OPT_PROTO_SLASH_COMMANDS_NATIVE, #else - OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK, + OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK | + OPT_PROTO_SLASH_COMMANDS_NATIVE, #endif NULL, /* user_splits */ NULL, /* protocol_options */ @@ -193,9 +195,11 @@ /* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */ split = purple_account_user_split_new(_("Domain"), NULL, '@'); + purple_account_user_split_set_reverse(split, FALSE); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); split = purple_account_user_split_new(_("Resource"), "Home", '/'); + purple_account_user_split_set_reverse(split, FALSE); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
--- a/libpurple/protocols/silc/silc.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/protocols/silc/silc.c Wed Jun 06 12:20:11 2007 +0000 @@ -1724,10 +1724,11 @@ { #ifdef HAVE_SILCMIME_H OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | - OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE, + OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE | + OPT_PROTO_SLASH_COMMANDS_NATIVE, #else OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | - OPT_PROTO_PASSWORD_OPTIONAL, + OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_SLASH_COMMANDS_NATIVE, #endif NULL, /* user_splits */ NULL, /* protocol_options */
--- a/libpurple/prpl.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/prpl.h Wed Jun 06 12:20:11 2007 +0000 @@ -158,6 +158,12 @@ */ OPT_PROTO_REGISTER_NOSCREENNAME = 0x00000200, + /** + * Indicates that slash commands are native to this protocol. + * Used as a hint that unknown commands should not be sent as messages. + */ + OPT_PROTO_SLASH_COMMANDS_NATIVE = 0x00000400, + } PurpleProtocolOptions; /**
--- a/libpurple/purple-remote Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/purple-remote Wed Jun 06 12:20:11 2007 +0000 @@ -157,6 +157,12 @@ return None + elif command == "getstatus": + current = purple.PurpleSavedstatusGetCurrent() + status_type = purple.PurpleSavedstatusGetType(current) + status_id = purple.PurplePrimitiveGetIdFromType(status_type) + return status_id + elif command == "getinfo": account = findaccount(accountname, protocol) connection = cpurple.PurpleAccountGetConnection(account)
--- a/libpurple/savedstatuses.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/savedstatuses.c Wed Jun 06 12:20:11 2007 +0000 @@ -357,7 +357,7 @@ schedule_save(void) { if (save_timer == 0) - save_timer = purple_timeout_add(5000, save_cb, NULL); + save_timer = purple_timeout_add_seconds(5, save_cb, NULL); }
--- a/libpurple/server.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/server.c Wed Jun 06 12:20:11 2007 +0000 @@ -92,7 +92,7 @@ /* because we're modifying or creating a lar, schedule the * function to expire them as the pref dictates */ - purple_timeout_add((SECS_BEFORE_RESENDING_AUTORESPONSE + 1) * 1000, expire_last_auto_responses, NULL); + purple_timeout_add_seconds((SECS_BEFORE_RESENDING_AUTORESPONSE + 1), expire_last_auto_responses, NULL); tmp = last_auto_responses; @@ -233,8 +233,9 @@ char *tmp = g_strdup_printf(_("%s is now known as %s.\n"), who, alias); - purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, - time(NULL)); + purple_conversation_write(conv, NULL, tmp, + PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY, + time(NULL)); g_free(tmp); }
--- a/libpurple/util.c Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/util.c Wed Jun 06 12:20:11 2007 +0000 @@ -22,6 +22,7 @@ */ #include "internal.h" +#include "cipher.h" #include "conversation.h" #include "core.h" #include "debug.h" @@ -156,6 +157,31 @@ return data; } +gchar * +purple_base16_encode_chunked(const guchar *data, gsize len) +{ + int i; + gchar *ascii = NULL; + + g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(len > 0, NULL); + + /* For each byte of input, we need 2 bytes for the hex representation + * and 1 for the colon. + * The final colon will be replaced by a terminating NULL + */ + ascii = g_malloc(len * 3 + 1); + + for (i = 0; i < len; i++) + g_snprintf(&ascii[i * 3], 4, "%02hhx:", data[i]); + + /* Replace the final colon with NULL */ + ascii[len * 3 - 1] = 0; + + return ascii; +} + + /************************************************************************** * Base64 Functions **************************************************************************/ @@ -1397,6 +1423,40 @@ plain = g_string_append_c(plain, '\n'); continue; } + if(!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) { + const char *p = c; + GString *src = NULL; + struct purple_parse_tag *pt; + while(*p && *p != '>') { + if(!g_ascii_strncasecmp(p, "src=", strlen("src="))) { + const char *q = p + strlen("src="); + src = g_string_new(""); + if(*q == '\'' || *q == '\"') + q++; + while(*q && *q != '\"' && *q != '\'' && *q != ' ') { + src = g_string_append_c(src, *q); + q++; + } + p = q; + } + p++; + } + if ((c = strchr(c, '>')) != NULL) + c++; + else + c = p; + pt = g_new0(struct purple_parse_tag, 1); + pt->src_tag = "img"; + pt->dest_tag = "img"; + tags = g_list_prepend(tags, pt); + if(xhtml && src && src->len) + g_string_append_printf(xhtml, "<img src='%s' alt=''>", g_strstrip(src->str)); + else + pt->ignore = TRUE; + if (src) + g_string_free(src, TRUE); + continue; + } if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>"))) { struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); pt->src_tag = *(c+2) == '>' ? "b" : "bold"; @@ -1538,10 +1598,7 @@ pt->dest_tag = "span"; tags = g_list_prepend(tags, pt); if(style->len) - { - if(xhtml) - g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str)); - } + g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str)); else pt->ignore = TRUE; g_string_free(style, TRUE); @@ -2670,6 +2727,33 @@ return "icon"; } +char * +purple_util_get_image_filename(gconstpointer image_data, size_t image_len) +{ + PurpleCipherContext *context; + gchar digest[41]; + + context = purple_cipher_context_new_by_name("sha1", NULL); + if (context == NULL) + { + purple_debug_error("util", "Could not find sha1 cipher\n"); + g_return_val_if_reached(NULL); + } + + /* Hash the image data */ + purple_cipher_context_append(context, image_data, image_len); + if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL)) + { + purple_debug_error("util", "Failed to get SHA-1 digest.\n"); + g_return_val_if_reached(NULL); + } + purple_cipher_context_destroy(context); + + /* Return the filename */ + return g_strdup_printf("%s.%s", digest, + purple_util_get_image_extension(image_data, image_len)); +} + gboolean purple_program_is_valid(const char *program) {
--- a/libpurple/util.h Wed Jun 06 07:51:14 2007 +0000 +++ b/libpurple/util.h Wed Jun 06 12:20:11 2007 +0000 @@ -32,6 +32,7 @@ #include "account.h" #include "xmlnode.h" +#include "notify.h" #ifdef __cplusplus extern "C" { @@ -118,6 +119,21 @@ */ guchar *purple_base16_decode(const char *str, gsize *ret_len); +/** + * Converts a chunk of binary data to a chunked base-16 representation + * (handy for key fingerprints) + * + * Example output: 01:23:45:67:89:AB:CD:EF + * + * @param data The data to convert. + * @param len The length of the data. + * + * @return The base-16 string in the ASCII chunked encoding. Must be + * g_free'd when no longer needed. + */ +gchar *purple_base16_encode_chunked(const guchar *data, gsize len); + + /*@}*/ /**************************************************************************/ @@ -608,6 +624,12 @@ const char * purple_util_get_image_extension(gconstpointer data, size_t len); +/** + * Returns a SHA-1 hash string of the data passed in with the correct file + * extention appended. + */ +char *purple_util_get_image_filename(gconstpointer image_data, size_t image_len); + /*@}*/
--- a/pidgin/Makefile.am Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/Makefile.am Wed Jun 06 12:20:11 2007 +0000 @@ -109,6 +109,8 @@ gtksession.c \ gtksound.c \ gtksourceiter.c \ + gtksourceundomanager.c \ + gtksourceview-marshal.c \ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \ @@ -156,6 +158,8 @@ gtksession.h \ gtksound.h \ gtksourceiter.h \ + gtksourceundomanager.h \ + gtksourceview-marshal.h \ gtkstatusbox.h \ pidginstock.h \ gtkthemes.h \
--- a/pidgin/Makefile.mingw Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/Makefile.mingw Wed Jun 06 12:20:11 2007 +0000 @@ -85,6 +85,7 @@ gtkscrollbook.c \ gtksound.c \ gtksourceiter.c \ + gtksourceundomanager.c \ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \
--- a/pidgin/gtkaccount.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkaccount.c Wed Jun 06 12:20:11 2007 +0000 @@ -480,11 +480,15 @@ GtkWidget *entry = l->data; PurpleAccountUserSplit *split = l2->data; - const char *value = NULL, *protocol = NULL; + const char *value = NULL; char *c; if (dialog->account != NULL) { - c = strrchr(username, + if(purple_account_user_split_get_reverse(split)) + c = strrchr(username, + purple_account_user_split_get_separator(split)); + else + c = strchr(username, purple_account_user_split_get_separator(split)); if (c != NULL) { @@ -500,9 +504,8 @@ /* Google Talk default domain hackery! */ menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu)); item = gtk_menu_get_active(GTK_MENU(menu)); - protocol = g_object_get_data(G_OBJECT(item), "protocol"); - if (value == NULL && protocol != NULL && !strcmp(protocol, "prpl-fake") && - !strcmp(purple_account_user_split_get_text(split), _("Domain"))) + if (value == NULL && g_object_get_data(G_OBJECT(item), "fake") && + !strcmp(purple_account_user_split_get_text(split), _("Domain"))) value = "gmail.com"; if (value != NULL) @@ -703,7 +706,7 @@ GList *l; char buf[1024]; char *title; - const char *str_value, *protocol; + const char *str_value; gboolean bool_value; if (dialog->protocol_frame != NULL) { @@ -829,8 +832,7 @@ /* Google Talk default domain hackery! */ menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu)); item = gtk_menu_get_active(GTK_MENU(menu)); - protocol = g_object_get_data(G_OBJECT(item), "protocol"); - if (str_value == NULL && protocol != NULL && !strcmp(protocol, "prpl-fake") && + if (str_value == NULL && g_object_get_data(G_OBJECT(item), "fake") && !strcmp(_("Connect server"), purple_account_option_get_text(option))) str_value = "talk.google.com"; @@ -1467,18 +1469,8 @@ if ((dialog->plugin = purple_find_prpl(dialog->protocol_id)) != NULL) dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin); - - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win), "account"); - - if (type == PIDGIN_ADD_ACCOUNT_DIALOG) - gtk_window_set_title(GTK_WINDOW(win), _("Add Account")); - else - gtk_window_set_title(GTK_WINDOW(win), _("Modify Account")); - - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); + dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"), + PIDGIN_HIG_BORDER, "account", FALSE); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(account_win_destroy_cb), dialog); @@ -2322,7 +2314,6 @@ GtkWidget *button; int width, height; - if (accounts_window != NULL) { gtk_window_present(GTK_WINDOW(accounts_window->window)); return; @@ -2333,11 +2324,8 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height"); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = win = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); - gtk_window_set_role(GTK_WINDOW(win), "accounts"); - gtk_window_set_title(GTK_WINDOW(win), _("Accounts")); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(accedit_win_destroy_cb), accounts_window);
--- a/pidgin/gtkblist.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkblist.c Wed Jun 06 12:20:11 2007 +0000 @@ -272,12 +272,7 @@ static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b) { - PurpleNotifyUserInfo *info = purple_notify_user_info_new(); - purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); - purple_notify_userinfo(b->account->gc, purple_buddy_get_name(b), info, NULL, NULL); - purple_notify_user_info_destroy(info); - - serv_get_info(b->account->gc, b->name); + pidgin_retrieve_user_info(b->account->gc, purple_buddy_get_name(b)); } static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b) @@ -1140,7 +1135,8 @@ } static gboolean -gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data) { +gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data) +{ PurpleBlistNode *node; GValue val; GtkTreeIter iter; @@ -1167,7 +1163,7 @@ return FALSE; } if(buddy) - serv_get_info(buddy->account->gc, buddy->name); + pidgin_retrieve_user_info(buddy->account->gc, buddy->name); } else if (event->keyval == GDK_F2) { gtk_blist_menu_alias_cb(tv, node); } @@ -1421,7 +1417,7 @@ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); if (prpl && prpl_info->get_info) - serv_get_info(b->account->gc, b->name); + pidgin_retrieve_user_info(b->account->gc, b->name); handled = TRUE; } @@ -3835,15 +3831,17 @@ static gboolean gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist) { - GtkWidget *imhtml; + GtkWidget *widget; if (!gtkblist) return FALSE; - imhtml = gtk_window_get_focus(GTK_WINDOW(gtkblist->window)); - - if (GTK_IS_IMHTML(imhtml) && gtk_bindings_activate(GTK_OBJECT(imhtml), event->keyval, event->state)) - return TRUE; + widget = gtk_window_get_focus(GTK_WINDOW(gtkblist->window)); + + if (GTK_IS_IMHTML(widget) || GTK_IS_ENTRY(widget)) { + if (gtk_bindings_activate(GTK_OBJECT(widget), event->keyval, event->state)) + return TRUE; + } return FALSE; } @@ -4206,7 +4204,7 @@ {"application/x-im-contact", 0, DRAG_BUDDY}, {"text/x-vcard", 0, DRAG_VCARD }}; if (gtkblist && gtkblist->window) { - purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible")); + purple_blist_set_visible(TRUE); return; } @@ -4215,9 +4213,7 @@ gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32); gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000); - gtkblist->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(gtkblist->window), "buddy_list"); - gtk_window_set_title(GTK_WINDOW(gtkblist->window), _("Buddy List")); + gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE); g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event", G_CALLBACK(blist_focus_cb), gtkblist); GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
--- a/pidgin/gtkconv.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkconv.c Wed Jun 06 12:20:11 2007 +0000 @@ -271,65 +271,7 @@ default_formatize(PidginConversation *c) { PurpleConversation *conv = c->active_conv; - - if (conv->features & PURPLE_CONNECTION_HTML) - { - char color[8]; - GdkColor fg_color, bg_color; - - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != GTK_IMHTML(c->entry)->edit.bold) - gtk_imhtml_toggle_bold(GTK_IMHTML(c->entry)); - - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != GTK_IMHTML(c->entry)->edit.italic) - gtk_imhtml_toggle_italic(GTK_IMHTML(c->entry)); - - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != GTK_IMHTML(c->entry)->edit.underline) - gtk_imhtml_toggle_underline(GTK_IMHTML(c->entry)); - - gtk_imhtml_toggle_fontface(GTK_IMHTML(c->entry), - purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face")); - - if (!(conv->features & PURPLE_CONNECTION_NO_FONTSIZE)) - { - int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size"); - - /* 3 is the default. */ - if (size != 3) - gtk_imhtml_font_set_size(GTK_IMHTML(c->entry), size); - } - - if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0) - { - gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), - &fg_color); - g_snprintf(color, sizeof(color), "#%02x%02x%02x", - fg_color.red / 256, - fg_color.green / 256, - fg_color.blue / 256); - } else - strcpy(color, ""); - - gtk_imhtml_toggle_forecolor(GTK_IMHTML(c->entry), color); - - if(!(conv->features & PURPLE_CONNECTION_NO_BGCOLOR) && - strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0) - { - gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), - &bg_color); - g_snprintf(color, sizeof(color), "#%02x%02x%02x", - bg_color.red / 256, - bg_color.green / 256, - bg_color.blue / 256); - } else - strcpy(color, ""); - - gtk_imhtml_toggle_background(GTK_IMHTML(c->entry), color); - - if (conv->features & PURPLE_CONNECTION_FORMATTING_WBFO) - gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), TRUE); - else - gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), FALSE); - } + gtk_imhtml_setup_entry(GTK_IMHTML(c->entry), conv->features); } static void @@ -477,6 +419,7 @@ char *cmd; const char *prefix; GtkTextIter start; + gboolean retval = FALSE; gtkconv = PIDGIN_CONVERSATION(conv); prefix = pidgin_get_cmd_prefix(); @@ -500,24 +443,50 @@ gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &end); markup = gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv->entry), &start, &end); status = purple_cmd_do_command(conv, cmdline, markup, &error); - g_free(cmd); g_free(markup); switch (status) { case PURPLE_CMD_STATUS_OK: - return TRUE; + retval = TRUE; + break; case PURPLE_CMD_STATUS_NOT_FOUND: - return FALSE; + { + PurplePluginProtocolInfo *prpl_info = NULL; + PurpleConnection *gc; + + if ((gc = purple_conversation_get_gc(conv))) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); + + if ((prpl_info != NULL) && (prpl_info->options & OPT_PROTO_SLASH_COMMANDS_NATIVE)) { + char *firstspace; + char *slash; + + firstspace = strchr(cmdline, ' '); + if (firstspace != NULL) { + slash = strrchr(firstspace, '/'); + } else { + slash = strchr(cmdline, '/'); + } + + if (slash == NULL) { + purple_conversation_write(conv, "", _("Unknown command."), PURPLE_MESSAGE_NO_LOG, time(NULL)); + retval = TRUE; + } + } + break; + } case PURPLE_CMD_STATUS_WRONG_ARGS: purple_conversation_write(conv, "", _("Syntax Error: You typed the wrong number of arguments " "to that command."), PURPLE_MESSAGE_NO_LOG, time(NULL)); - return TRUE; + retval = TRUE; + break; case PURPLE_CMD_STATUS_FAILED: purple_conversation_write(conv, "", error ? error : _("Your command failed for an unknown reason."), PURPLE_MESSAGE_NO_LOG, time(NULL)); g_free(error); - return TRUE; + retval = TRUE; + break; case PURPLE_CMD_STATUS_WRONG_TYPE: if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) purple_conversation_write(conv, "", _("That command only works in chats, not IMs."), @@ -525,16 +494,18 @@ else purple_conversation_write(conv, "", _("That command only works in IMs, not chats."), PURPLE_MESSAGE_NO_LOG, time(NULL)); - return TRUE; + retval = TRUE; + break; case PURPLE_CMD_STATUS_WRONG_PRPL: purple_conversation_write(conv, "", _("That command doesn't work on this protocol."), PURPLE_MESSAGE_NO_LOG, time(NULL)); - return TRUE; + retval = TRUE; + break; } } g_free(cmd); - return FALSE; + return retval; } static void @@ -666,7 +637,7 @@ purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who); } else - prpl_info->get_info(gc, who); + pidgin_retrieve_user_info(gc, who); } } @@ -677,14 +648,8 @@ PurpleConversation *conv = gtkconv->active_conv; if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { - PurpleNotifyUserInfo *info = purple_notify_user_info_new(); - purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); - purple_notify_userinfo(conv->account->gc, purple_conversation_get_name(conv), info, NULL, NULL); - purple_notify_user_info_destroy(info); - - serv_get_info(purple_conversation_get_gc(conv), + pidgin_retrieve_user_info(purple_conversation_get_gc(conv), purple_conversation_get_name(conv)); - gtk_widget_grab_focus(gtkconv->entry); } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { /* Get info of the person currently selected in the GtkTreeView */ @@ -2228,34 +2193,18 @@ static GList *get_prpl_icon_list(PurpleAccount *account) { GList *l = NULL; - GdkPixbuf *pixbuf; - PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account))); - const char *prpl = prpl_info->list_icon(account, NULL); - char *filename, *path; - l = g_hash_table_lookup(prpl_lists, prpl); + PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account)); + PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + const char *prplname = prpl_info->list_icon(account, NULL); + l = g_hash_table_lookup(prpl_lists, prplname); if (l) return l; - filename = g_strdup_printf("%s.png", prpl); - - path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", filename, NULL); - pixbuf = gdk_pixbuf_new_from_file(path, NULL); - if (pixbuf) - l = g_list_append(l, pixbuf); - g_free(path); - - path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", filename, NULL); - pixbuf = gdk_pixbuf_new_from_file(path, NULL); - if (pixbuf) - l = g_list_append(l, pixbuf); - g_free(path); - - path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "48", filename, NULL); - pixbuf = gdk_pixbuf_new_from_file(path, NULL); - if (pixbuf) - l = g_list_append(l, pixbuf); - g_free(path); - - g_hash_table_insert(prpl_lists, g_strdup(prpl), l); + + l = g_list_prepend(l, pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE)); + l = g_list_prepend(l, pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM)); + l = g_list_prepend(l, pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL)); + + g_hash_table_insert(prpl_lists, g_strdup(prplname), l); return l; } @@ -2942,10 +2891,65 @@ gtk_widget_show_all(menu); } +static void +remove_from_list(GtkWidget *widget, PidginWindow *win) +{ + GList *list = g_object_get_data(G_OBJECT(win->window), "plugin-actions"); + list = g_list_remove(list, widget); + g_object_set_data(G_OBJECT(win->window), "plugin-actions", list); +} + +static void +regenerate_plugins_items(PidginWindow *win) +{ + GList *action_items; + GtkWidget *menu; + GList *list; + PidginConversation *gtkconv; + PurpleConversation *conv; + GtkWidget *item; + + if (win->window == NULL || win == hidden_convwin) + return; + + gtkconv = pidgin_conv_window_get_active_gtkconv(win); + if (gtkconv == NULL) + return; + + conv = gtkconv->active_conv; + action_items = g_object_get_data(G_OBJECT(win->window), "plugin-actions"); + + /* Remove the old menuitems */ + while (action_items) { + g_signal_handlers_disconnect_by_func(G_OBJECT(action_items->data), + G_CALLBACK(remove_from_list), win); + gtk_widget_destroy(action_items->data); + action_items = g_list_delete_link(action_items, action_items); + } + + menu = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Options")); + + list = purple_conversation_get_extended_menu(conv); + if (list) { + action_items = g_list_prepend(NULL, (item = pidgin_separator(menu))); + g_signal_connect(G_OBJECT(item), "destroy", G_CALLBACK(remove_from_list), win); + } + + for(; list; list = g_list_delete_link(list, list)) { + PurpleMenuAction *act = (PurpleMenuAction *) list->data; + item = pidgin_append_menu_action(menu, act, conv); + action_items = g_list_prepend(action_items, item); + gtk_widget_show_all(item); + g_signal_connect(G_OBJECT(item), "destroy", G_CALLBACK(remove_from_list), win); + } + g_object_set_data(G_OBJECT(win->window), "plugin-actions", action_items); +} + static void menubar_activated(GtkWidget *item, gpointer data) { PidginWindow *win = data; regenerate_options_items(win); + regenerate_plugins_items(win); /* The following are to make sure the 'More' submenu is not regenerated every time * the focus shifts from 'Conversations' to some other menu and back. */ @@ -4145,45 +4149,17 @@ } } -static GtkWidget * -setup_chat_pane(PidginConversation *gtkconv) -{ - PurplePluginProtocolInfo *prpl_info; +static void +setup_chat_topic(PidginConversation *gtkconv, GtkWidget *vbox) +{ PurpleConversation *conv = gtkconv->active_conv; - PidginChatPane *gtkchat; - PurpleConnection *gc; - GtkWidget *vpaned, *hpaned; - GtkWidget *vbox, *hbox, *frame; - GtkWidget *imhtml_sw; - GtkPolicyType imhtml_sw_hscroll; - GtkWidget *lbox; - GtkWidget *label; - GtkWidget *list; - GtkWidget *sw; - GtkListStore *ls; - GtkCellRenderer *rend; - GtkTreeViewColumn *col; - void *blist_handle = purple_blist_get_handle(); - GList *focus_chain = NULL; - int ul_width; - - gtkchat = gtkconv->u.chat; - gc = purple_conversation_get_gc(conv); - g_return_val_if_fail(gc != NULL, NULL); - g_return_val_if_fail(gc->prpl != NULL, NULL); - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); - - /* Setup the outer pane. */ - vpaned = gtk_vpaned_new(); - gtk_widget_show(vpaned); - - /* Setup the top part of the pane. */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_paned_pack1(GTK_PANED(vpaned), vbox, TRUE, TRUE); - gtk_widget_show(vbox); - + PurpleConnection *gc = purple_conversation_get_gc(conv); + PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); if (prpl_info->options & OPT_PROTO_CHAT_TOPIC) { + GtkWidget *hbox, *label; + PidginChatPane *gtkchat = gtkconv->u.chat; + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_widget_show(hbox); @@ -4204,35 +4180,19 @@ gtk_box_pack_start(GTK_BOX(hbox), gtkchat->topic_text, TRUE, TRUE, 0); gtk_widget_show(gtkchat->topic_text); } - - /* Setup the horizontal pane. */ - hpaned = gtk_hpaned_new(); - gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); - gtk_widget_show(hpaned); - - /* Setup gtkihmtml. */ - frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); - gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), TRUE); - gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE); - gtk_widget_show(frame); - gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw), - &imhtml_sw_hscroll, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw), - imhtml_sw_hscroll, GTK_POLICY_ALWAYS); - - gtk_widget_set_size_request(gtkconv->imhtml, - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width"), - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height")); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate", - G_CALLBACK(size_allocate_cb), gtkconv); - - g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", - G_CALLBACK(entry_stop_rclick_cb), NULL); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", - G_CALLBACK(refocus_entry_cb), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", - G_CALLBACK(refocus_entry_cb), gtkconv); +} + +static void +setup_chat_userlist(PidginConversation *gtkconv, GtkWidget *hpaned) +{ + PidginChatPane *gtkchat = gtkconv->u.chat; + GtkWidget *lbox, *sw, *list; + GtkListStore *ls; + GtkCellRenderer *rend; + GtkTreeViewColumn *col; + int ul_width; + void *blist_handle = purple_blist_get_handle(); + PurpleConversation *conv = gtkconv->active_conv; /* Build the right pane. */ lbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -4280,9 +4240,7 @@ G_CALLBACK(gtkconv_chat_popup_menu_cb), gtkconv); g_signal_connect(G_OBJECT(lbox), "size-allocate", G_CALLBACK(lbox_size_allocate_cb), gtkconv); - rend = gtk_cell_renderer_text_new(); - g_object_set(rend, "foreground-set", TRUE, "weight-set", TRUE, @@ -4313,10 +4271,72 @@ gtkchat->list = list; gtk_container_add(GTK_CONTAINER(sw), list); +} + +static GtkWidget * +setup_common_pane(PidginConversation *gtkconv) +{ + GtkWidget *paned, *vbox, *frame, *imhtml_sw; + PurpleConversation *conv = gtkconv->active_conv; + gboolean chat = (conv->type == PURPLE_CONV_TYPE_CHAT); + GtkPolicyType imhtml_sw_hscroll; + + paned = gtk_vpaned_new(); + gtk_widget_show(paned); + + /* Setup the top part of the pane */ + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE); + gtk_widget_show(vbox); + + /* Setup the gtkimhtml widget */ + frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); + if (chat) { + GtkWidget *hpaned; + + /* Add the topic */ + setup_chat_topic(gtkconv, vbox); + + /* Add the gtkimhtml frame */ + hpaned = gtk_hpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); + gtk_widget_show(hpaned); + gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE); + + /* Now add the userlist */ + setup_chat_userlist(gtkconv, hpaned); + } else { + gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); + } + gtk_widget_show(frame); + + gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); + gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE); + + gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw), + &imhtml_sw_hscroll, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw), + imhtml_sw_hscroll, GTK_POLICY_ALWAYS); + + gtk_widget_set_size_request(gtkconv->imhtml, + chat ? purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width") : + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width"), + chat ? purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height") : + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height")); + + g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate", + G_CALLBACK(size_allocate_cb), gtkconv); + + g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", + G_CALLBACK(entry_stop_rclick_cb), NULL); + g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", + G_CALLBACK(refocus_entry_cb), gtkconv); + g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", + G_CALLBACK(refocus_entry_cb), gtkconv); /* Setup the bottom half of the conversation window */ vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_paned_pack2(GTK_PANED(vpaned), vbox, FALSE, TRUE); + gtk_paned_pack2(GTK_PANED(paned), vbox, FALSE, TRUE); gtk_widget_show(vbox); gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -4332,20 +4352,15 @@ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); gtk_widget_show(frame); - g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup", - G_CALLBACK(entry_popup_menu_cb), gtkconv); - gtk_widget_set_name(gtkconv->entry, "pidgin_conv_entry"); gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->entry), - purple_account_get_protocol_name(conv->account)); + purple_account_get_protocol_name(conv->account)); gtk_widget_set_size_request(gtkconv->lower_hbox, -1, - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height")); - gtkconv->entry_buffer = - gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); - g_object_set_data(G_OBJECT(gtkconv->entry_buffer), "user_data", gtkconv); - g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed", - G_CALLBACK(resize_imhtml_cb), gtkconv); - + chat ? purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height") : + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height")); + + g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup", + G_CALLBACK(entry_popup_menu_cb), gtkconv); g_signal_connect(G_OBJECT(gtkconv->entry), "key_press_event", G_CALLBACK(entry_key_press_cb), gtkconv); g_signal_connect_after(G_OBJECT(gtkconv->entry), "message_send", @@ -4355,129 +4370,28 @@ g_signal_connect(G_OBJECT(gtkconv->lower_hbox), "size-allocate", G_CALLBACK(size_allocate_cb), gtkconv); - default_formatize(gtkconv); - - /* - * Focus for chat windows should be as follows: - * Tab title -> chat topic -> conversation scrollback -> user list -> - * user list buttons -> entry -> buttons at bottom - */ - focus_chain = g_list_prepend(focus_chain, gtkconv->entry); - gtk_container_set_focus_chain(GTK_CONTAINER(vbox), focus_chain); - - return vpaned; -} - -static GtkWidget * -setup_im_pane(PidginConversation *gtkconv) -{ - PurpleConversation *conv = gtkconv->active_conv; - GtkWidget *frame; - GtkWidget *imhtml_sw; - GtkPolicyType imhtml_sw_hscroll; - GtkWidget *paned; - GtkWidget *vbox; - GtkWidget *vbox2; - GList *focus_chain = NULL; - - /* Setup the outer pane */ - paned = gtk_vpaned_new(); - gtk_widget_show(paned); - - /* Setup the top part of the pane */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE); - gtk_widget_show(vbox); - - /* Setup the gtkimhtml widget */ - frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); - gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE); - gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - gtk_widget_show(frame); - gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw), - &imhtml_sw_hscroll, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw), - imhtml_sw_hscroll, GTK_POLICY_ALWAYS); - - gtk_widget_set_size_request(gtkconv->imhtml, - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width"), - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height")); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate", - G_CALLBACK(size_allocate_cb), gtkconv); - - g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", - G_CALLBACK(entry_stop_rclick_cb), NULL); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", - G_CALLBACK(refocus_entry_cb), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", - G_CALLBACK(refocus_entry_cb), gtkconv); - - /* Setup the bottom half of the conversation window */ - vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_paned_pack2(GTK_PANED(paned), vbox2, FALSE, TRUE); - gtk_widget_show(vbox2); - - gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_box_pack_start(GTK_BOX(vbox2), gtkconv->lower_hbox, TRUE, TRUE, 0); - gtk_widget_show(gtkconv->lower_hbox); - - vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_box_pack_end(GTK_BOX(gtkconv->lower_hbox), vbox2, TRUE, TRUE, 0); - gtk_widget_show(vbox2); - - /* Setup the toolbar, entry widget and all signals */ - frame = pidgin_create_imhtml(TRUE, >kconv->entry, >kconv->toolbar, NULL); - gtk_box_pack_start(GTK_BOX(vbox2), frame, TRUE, TRUE, 0); - gtk_widget_show(frame); - - g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup", - G_CALLBACK(entry_popup_menu_cb), gtkconv); - - gtk_widget_set_name(gtkconv->entry, "pidgin_conv_entry"); - gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->entry), - purple_account_get_protocol_name(conv->account)); - gtk_widget_set_size_request(gtkconv->lower_hbox, -1, - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height")); gtkconv->entry_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry)); g_object_set_data(G_OBJECT(gtkconv->entry_buffer), "user_data", gtkconv); - g_signal_connect(G_OBJECT(gtkconv->entry), "key_press_event", - G_CALLBACK(entry_key_press_cb), gtkconv); - g_signal_connect_after(G_OBJECT(gtkconv->entry), "message_send", - G_CALLBACK(send_cb), gtkconv); - g_signal_connect_after(G_OBJECT(gtkconv->entry), "button_press_event", - G_CALLBACK(entry_stop_rclick_cb), NULL); - g_signal_connect(G_OBJECT(gtkconv->lower_hbox), "size-allocate", - G_CALLBACK(size_allocate_cb), gtkconv); - - g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text", - G_CALLBACK(insert_text_cb), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range", - G_CALLBACK(delete_text_cb), gtkconv); + if (!chat) { + /* For sending typing notifications for IMs */ + g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text", + G_CALLBACK(insert_text_cb), gtkconv); + g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range", + G_CALLBACK(delete_text_cb), gtkconv); + gtkconv->u.im->typing_timer = 0; + gtkconv->u.im->animate = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons"); + gtkconv->u.im->show_icon = TRUE; + } + g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed", G_CALLBACK(resize_imhtml_cb), gtkconv); - /* had to move this after the imtoolbar is attached so that the - * signals get fired to toggle the buttons on the toolbar as well. - */ default_formatize(gtkconv); - g_signal_connect_after(G_OBJECT(gtkconv->entry), "format_function_clear", - G_CALLBACK(clear_formatting_cb), gtkconv); - - gtkconv->u.im->animate = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons"); - gtkconv->u.im->show_icon = TRUE; - - /* - * Focus for IM windows should be as follows: - * Tab title -> conversation scrollback -> entry - */ - focus_chain = g_list_prepend(focus_chain, gtkconv->entry); - gtk_container_set_focus_chain(GTK_CONTAINER(vbox2), focus_chain); - - gtkconv->u.im->typing_timer = 0; + G_CALLBACK(clear_formatting_cb), gtkconv); + return paned; } @@ -4673,12 +4587,10 @@ if (conv_type == PURPLE_CONV_TYPE_IM) { gtkconv->u.im = g_malloc0(sizeof(PidginImPane)); - - pane = setup_im_pane(gtkconv); } else if (conv_type == PURPLE_CONV_TYPE_CHAT) { gtkconv->u.chat = g_malloc0(sizeof(PidginChatPane)); - pane = setup_chat_pane(gtkconv); - } + } + pane = setup_common_pane(gtkconv); gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->imhtml), gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv->imhtml)) | GTK_IMHTML_IMAGE); @@ -5076,7 +4988,11 @@ g_return_if_fail(gc != NULL); /* Make sure URLs are clickable */ - displaying = purple_markup_linkify(message); + if(flags & PURPLE_MESSAGE_NO_LINKIFY) + displaying = g_strdup(message); + else + displaying = purple_markup_linkify(message); + plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1( pidgin_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ? "displaying-im-msg" : "displaying-chat-msg"), @@ -7983,6 +7899,7 @@ generate_send_to_items(win); regenerate_options_items(win); + regenerate_plugins_items(win); pidgin_conv_switch_active_conversation(conv); @@ -8053,6 +7970,12 @@ prpl_lists = g_hash_table_new(g_str_hash, g_str_equal); } +static void +plugin_changed_cb(PurplePlugin *p, gpointer data) +{ + regenerate_plugins_items(data); +} + PidginWindow * pidgin_conv_window_new() { @@ -8066,10 +7989,7 @@ window_list = g_list_append(window_list, win); /* Create the window. */ - win->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win->window), "conversation"); - gtk_window_set_resizable(GTK_WINDOW(win->window), TRUE); - gtk_container_set_border_width(GTK_CONTAINER(win->window), 0); + win->window = pidgin_create_window(NULL, 0, "conversation", TRUE); GTK_WINDOW(win->window)->allow_shrink = TRUE; if (available_list == NULL) { @@ -8127,6 +8047,13 @@ gtk_widget_show(testidea); + /* Update the plugin actions when plugins are (un)loaded */ + purple_signal_connect(purple_plugins_get_handle(), "plugin-load", + win, PURPLE_CALLBACK(plugin_changed_cb), win); + purple_signal_connect(purple_plugins_get_handle(), "plugin-unload", + win, PURPLE_CALLBACK(plugin_changed_cb), win); + + #ifdef _WIN32 g_signal_connect(G_OBJECT(win->window), "show", G_CALLBACK(winpidgin_ensure_onscreen), win->window); @@ -8166,6 +8093,7 @@ g_object_unref(G_OBJECT(win->menu.item_factory)); purple_notify_close_with_handle(win); + purple_signals_disconnect_by_handle(win); g_free(win); }
--- a/pidgin/gtkdialogs.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkdialogs.c Wed Jun 06 12:20:11 2007 +0000 @@ -823,7 +823,7 @@ found = pidgin_dialogs_ee(username); if (!found && username != NULL && *username != '\0' && account != NULL) - serv_get_info(purple_account_get_connection(account), username); + pidgin_retrieve_user_info(purple_account_get_connection(account), username); g_free(username); }
--- a/pidgin/gtkeventloop.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkeventloop.c Wed Jun 06 12:20:11 2007 +0000 @@ -120,7 +120,11 @@ pidgin_input_add, g_source_remove, NULL, /* input_get_error */ +#if GLIB_CHECK_VERSION(2,14,0) + g_timeout_add_seconds, +#else NULL, +#endif NULL, NULL, NULL
--- a/pidgin/gtkft.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkft.c Wed Jun 06 12:20:11 2007 +0000 @@ -758,10 +758,7 @@ purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished"); /* Create the window. */ - dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(window), "file transfer"); - gtk_window_set_title(GTK_WINDOW(window), _("File Transfers")); - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); + dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/gtkimhtml.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkimhtml.c Wed Jun 06 12:20:11 2007 +0000 @@ -31,6 +31,8 @@ #include "util.h" #include "gtkimhtml.h" #include "gtksourceiter.h" +#include "gtksourceundomanager.h" +#include "gtksourceview-marshal.h" #include <gtk/gtk.h> #include <glib/gerror.h> #include <gdk/gdkkeysyms.h> @@ -136,6 +138,8 @@ CLEAR_FORMAT, UPDATE_FORMAT, MESSAGE_SEND, + UNDO, + REDO, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0 }; @@ -1150,6 +1154,23 @@ return FALSE; } +static void +gtk_imhtml_undo(GtkIMHtml *imhtml) { + g_return_if_fail(GTK_IS_IMHTML(imhtml)); + g_return_if_fail(imhtml->editable); + + gtk_source_undo_manager_undo(imhtml->undo_manager); +} + +static void +gtk_imhtml_redo(GtkIMHtml *imhtml) { + g_return_if_fail(GTK_IS_IMHTML(imhtml)); + g_return_if_fail(imhtml->editable); + + gtk_source_undo_manager_redo(imhtml->undo_manager); + +} + static gboolean imhtml_message_send(GtkIMHtml *imhtml) { return FALSE; @@ -1228,6 +1249,7 @@ g_queue_free(imhtml->animations); g_free(imhtml->protocol_name); g_free(imhtml->search_string); + g_object_unref(imhtml->undo_manager); G_OBJECT_CLASS(parent_class)->finalize (object); if (clipboard_selection) gtk_clipboard_set_with_owner(clipboard_selection, @@ -1297,10 +1319,32 @@ NULL, 0, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals [UNDO] = g_signal_new ("undo", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkIMHtmlClass, undo), + NULL, + NULL, + gtksourceview_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + signals [REDO] = g_signal_new ("redo", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkIMHtmlClass, redo), + NULL, + NULL, + gtksourceview_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + klass->toggle_format = imhtml_toggle_format; klass->message_send = imhtml_message_send; klass->clear_format = imhtml_clear_formatting; + klass->undo = gtk_imhtml_undo; + klass->redo = gtk_imhtml_redo; gobject_class->finalize = gtk_imhtml_finalize; widget_class->drag_motion = gtk_text_view_drag_motion; @@ -1325,12 +1369,17 @@ gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0); gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0); gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0); + gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK, "undo", 0); + gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0); + gtk_binding_entry_add_signal (binding_set, GDK_F14, 0, "undo", 0); + } static void gtk_imhtml_init (GtkIMHtml *imhtml) { GtkTextIter iter; imhtml->text_buffer = gtk_text_buffer_new(NULL); + imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer); gtk_text_buffer_get_end_iter (imhtml->text_buffer, &iter); gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR); @@ -4883,3 +4932,70 @@ g_return_if_fail(imhtml != NULL); imhtml->funcs = f; } + +void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags) +{ + if (flags & PURPLE_CONNECTION_HTML) { + char color[8]; + GdkColor fg_color, bg_color; + + gtk_imhtml_set_format_functions(imhtml, GTK_IMHTML_ALL); + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != imhtml->edit.bold) + gtk_imhtml_toggle_bold(imhtml); + + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != imhtml->edit.italic) + gtk_imhtml_toggle_italic(imhtml); + + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != imhtml->edit.underline) + gtk_imhtml_toggle_underline(imhtml); + + gtk_imhtml_toggle_fontface(imhtml, + purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face")); + + if (!(flags & PURPLE_CONNECTION_NO_FONTSIZE)) + { + int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size"); + + /* 3 is the default. */ + if (size != 3) + gtk_imhtml_font_set_size(imhtml, size); + } + + if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0) + { + gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), + &fg_color); + g_snprintf(color, sizeof(color), "#%02x%02x%02x", + fg_color.red / 256, + fg_color.green / 256, + fg_color.blue / 256); + } else + strcpy(color, ""); + + gtk_imhtml_toggle_forecolor(imhtml, color); + + if(!(flags & PURPLE_CONNECTION_NO_BGCOLOR) && + strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0) + { + gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), + &bg_color); + g_snprintf(color, sizeof(color), "#%02x%02x%02x", + bg_color.red / 256, + bg_color.green / 256, + bg_color.blue / 256); + } else + strcpy(color, ""); + + gtk_imhtml_toggle_background(imhtml, color); + + if (flags & PURPLE_CONNECTION_FORMATTING_WBFO) + gtk_imhtml_set_whole_buffer_formatting_only(imhtml, TRUE); + else + gtk_imhtml_set_whole_buffer_formatting_only(imhtml, FALSE); + } else { + imhtml_clear_formatting(imhtml); + gtk_imhtml_set_format_functions(imhtml, 0); + } +} + +
--- a/pidgin/gtkimhtml.h Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkimhtml.h Wed Jun 06 12:20:11 2007 +0000 @@ -27,6 +27,9 @@ #include <gtk/gtktextview.h> #include <gtk/gtktooltips.h> #include <gtk/gtkimage.h> +#include "gtksourceundomanager.h" + +#include "connection.h" #ifdef __cplusplus extern "C" { @@ -126,6 +129,7 @@ GSList *im_images; GtkIMHtmlFuncs *funcs; + GtkSourceUndoManager *undo_manager; }; struct _GtkIMHtmlClass { @@ -137,6 +141,8 @@ void (*clear_format)(GtkIMHtml *); void (*update_format)(GtkIMHtml *); gboolean (*message_send)(GtkIMHtml *); + void (*undo)(GtkIMHtml *); + void (*redo)(GtkIMHtml *); }; struct _GtkIMHtmlFontDetail { @@ -786,6 +792,14 @@ */ char *gtk_imhtml_get_text(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *stop); +/** + * Setup formatting for an imhtml depending on the flags specified. + * + * @param imhtml The GTK+ IM/HTML. + * @param flags The connection flag which describes the allowed types of formatting. + */ +void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags); + /*@}*/ #ifdef __cplusplus
--- a/pidgin/gtkimhtmltoolbar.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Wed Jun 06 12:20:11 2007 +0000 @@ -792,7 +792,8 @@ g_object_unref(object); } -static void update_buttons(GtkIMHtmlToolbar *toolbar) { +static void update_buttons(GtkIMHtmlToolbar *toolbar) +{ gboolean bold, italic, underline; char *tmp; char *tmp2; @@ -852,6 +853,66 @@ update_buttons(toolbar); } + +/* This comes from gtkmenutoolbutton.c from gtk+ + * Copyright (C) 2003 Ricardo Fernandez Pascual + * Copyright (C) 2004 Paolo Borelli + */ +static void +menu_position_func (GtkMenu *menu, + int *x, + int *y, + gboolean *push_in, + gpointer data) +{ + GtkRequisition menu_req; + GtkTextDirection direction; + GdkRectangle monitor; + gint monitor_num; + GdkScreen *screen; + GtkWidget *widget = GTK_WIDGET(data); + + gtk_widget_size_request (GTK_WIDGET (menu), &menu_req); + + direction = gtk_widget_get_direction (widget); + + screen = gtk_widget_get_screen (GTK_WIDGET (menu)); + monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window); + if (monitor_num < 0) + monitor_num = 0; + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + gdk_window_get_origin (widget->window, x, y); + *x += widget->allocation.x; + *y += widget->allocation.y; + + if (direction == GTK_TEXT_DIR_LTR) + *x += MAX (widget->allocation.width - menu_req.width, 0); + else if (menu_req.width > widget->allocation.width) + *x -= menu_req.width - widget->allocation.width; + + if ((*y + widget->allocation.height + menu_req.height) <= monitor.y + monitor.height) + *y += widget->allocation.height; + else if ((*y - menu_req.height) >= monitor.y) + *y -= menu_req.height; + else if (monitor.y + monitor.height - (*y + widget->allocation.height) > *y) + *y += widget->allocation.height; + else + *y -= menu_req.height; + *push_in = FALSE; +} + +static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu) +{ + gtk_widget_show_all(GTK_WIDGET(menu)); + gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time()); +} + +static void pidgin_menu_deactivate(GtkWidget *menu, GtkToggleButton *button) +{ + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE); +} + enum { LAST_SIGNAL }; @@ -899,12 +960,122 @@ gobject_class->finalize = gtk_imhtmltoolbar_finalize; } +static void gtk_imhtmltoolbar_create_old_buttons(GtkIMHtmlToolbar *toolbar) +{ + GtkWidget *button; + /* Bold */ + button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_BOLD); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(do_bold), toolbar); + toolbar->bold = button; + + + /* Italic */ + button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_ITALIC); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(do_italic), toolbar); + toolbar->italic = button; + + /* Underline */ + button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_UNDERLINE); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(do_underline), toolbar); + toolbar->underline = button; + + /* Increase font size */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_LARGER); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(do_big), toolbar); + toolbar->larger_size = button; + + /* Decrease font size */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(do_small), toolbar); + toolbar->smaller_size = button; + + /* Font Face */ + + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_FONT_FACE); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(toggle_font), toolbar); + toolbar->font = button; + + /* Foreground Color */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_FGCOLOR); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(toggle_fg_color), toolbar); + toolbar->fgcolor = button; + + /* Background Color */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_BGCOLOR); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(toggle_bg_color), toolbar); + toolbar->bgcolor = button; + + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT_LINK); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(insert_link_cb), toolbar); + toolbar->link = button; + + /* Insert IM Image */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(insert_image_cb), toolbar); + toolbar->image = button; + + /* Insert Smiley */ + button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(insert_smiley_cb), toolbar); + toolbar->smiley = button; +} + +static void +button_sensitiveness_changed(GtkWidget *button, gpointer dontcare, GtkWidget *item) +{ + gtk_widget_set_sensitive(item, GTK_WIDGET_IS_SENSITIVE(button)); +} + +static void +update_menuitem(GtkToggleButton *button, GtkCheckMenuItem *item) +{ + g_signal_handlers_block_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button); + gtk_check_menu_item_set_active(item, gtk_toggle_button_get_active(button)); + g_signal_handlers_unblock_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button); +} + static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar) { GtkWidget *hbox = GTK_WIDGET(toolbar); + GtkWidget *bbox; + GtkWidget *image; + GtkWidget *label; + GtkWidget *insert_button; + GtkWidget *font_button; + GtkWidget *font_menu; + GtkWidget *insert_menu; GtkWidget *button; GtkWidget *sep; - GtkSizeGroup *sg; + int i; + struct { + const char *label; + GtkWidget **button; + } buttons[] = { + {_("_Bold"), &toolbar->bold}, + {_("_Italic"), &toolbar->italic}, + {_("_Underline"), &toolbar->underline}, + {_("_Larger"), &toolbar->larger_size}, +#if 0 + {_("_Normal"), &toolbar->normal_size}, +#endif + {_("_Smaller"), &toolbar->smaller_size}, + {_("_Font face"), &toolbar->font}, + {_("_Foreground color"), &toolbar->fgcolor}, + {_("_Background color"), &toolbar->bgcolor}, + {NULL, NULL} + }; + toolbar->imhtml = NULL; toolbar->font_dialog = NULL; @@ -917,164 +1088,93 @@ toolbar->tooltips = gtk_tooltips_new(); gtk_box_set_spacing(GTK_BOX(toolbar), 3); - sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH); + + gtk_imhtmltoolbar_create_old_buttons(toolbar); - /* Bold */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_BOLD); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, _("Bold"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_bold), toolbar); - - toolbar->bold = button; + /* Fonts */ + font_button = gtk_toggle_button_new(); + gtk_button_set_relief(GTK_BUTTON(font_button), GTK_RELIEF_NONE); + bbox = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(font_button), bbox); + image = gtk_image_new_from_stock(GTK_STOCK_BOLD, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); + gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); + label = gtk_label_new_with_mnemonic(_("_Font")); + gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), font_button, FALSE, FALSE, 0); + gtk_widget_show_all(font_button); - /* Italic */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_ITALIC); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, _("Italic"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_italic), toolbar); - - toolbar->italic = button; + font_menu = gtk_menu_new(); - /* Underline */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_UNDERLINE); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, _("Underline"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_underline), toolbar); - - toolbar->underline = button; + + for (i = 0; buttons[i].label; i++) { + GtkWidget *old = *buttons[i].button; + GtkWidget *menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label); + g_signal_connect_swapped(G_OBJECT(menuitem), "activate", + G_CALLBACK(gtk_button_clicked), old); + g_signal_connect_after(G_OBJECT(old), "toggled", + G_CALLBACK(update_menuitem), menuitem); + gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem); + g_signal_connect(G_OBJECT(old), "notify::sensitive", + G_CALLBACK(button_sensitiveness_changed), menuitem); + } + + g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu); + g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button); /* Sep */ sep = gtk_vseparator_new(); gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0); - - /* Increase font size */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_LARGER); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, - _("Larger font size"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_big), toolbar); - - toolbar->larger_size = button; - - /* Decrease font size */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, - _("Smaller font size"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_small), toolbar); - - toolbar->smaller_size = button; - - /* Sep */ - sep = gtk_vseparator_new(); - gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0); - - /* Font Face */ - - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_FONT_FACE); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, - _("Font face"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(toggle_font), toolbar); - - toolbar->font = button; - - /* Foreground Color */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_FGCOLOR); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, - _("Foreground font color"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(toggle_fg_color), toolbar); - - toolbar->fgcolor = button; - - /* Background Color */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_BGCOLOR); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, - _("Background color"), NULL); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(toggle_bg_color), toolbar); - - toolbar->bgcolor = button; - - /* Sep */ - sep = gtk_vseparator_new(); - gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0); + gtk_widget_show_all(sep); /* Reset Formatting */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_CLEAR); - gtk_size_group_add_widget(sg, button); + button = gtk_toggle_button_new(); + gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + bbox = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(button), bbox); + image = gtk_image_new_from_stock(PIDGIN_STOCK_CLEAR, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); + gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); + label = gtk_label_new_with_mnemonic(_("_Reset font")); + gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, - _("Reset formatting"), NULL); - + gtk_widget_show_all(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(clear_formatting_cb), toolbar); - toolbar->clear = button; /* Sep */ sep = gtk_vseparator_new(); gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0); + gtk_widget_show_all(sep); - /* Insert Link */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT_LINK); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, _("Insert link"), NULL); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(insert_link_cb), toolbar); + /* Insert */ + insert_button = gtk_toggle_button_new(); + gtk_button_set_relief(GTK_BUTTON(insert_button), GTK_RELIEF_NONE); + bbox = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(insert_button), bbox); + image = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); + gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); + label = gtk_label_new_with_mnemonic(_("_Insert")); + gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), insert_button, FALSE, FALSE, 0); + gtk_widget_show_all(insert_button); - toolbar->link = button; - - /* Insert IM Image */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, _("Insert image"), NULL); + insert_menu = gtk_menu_new(); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(insert_image_cb), toolbar); + button = gtk_menu_item_new_with_mnemonic(_("_Smiley")); + g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smiley); + gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), button); - toolbar->image = button; + button = gtk_menu_item_new_with_mnemonic(_("_Image")); + g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->image); + gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), button); - /* Insert Smiley */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY); - gtk_size_group_add_widget(sg, button); - gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); - gtk_tooltips_set_tip(toolbar->tooltips, button, _("Insert smiley"), NULL); + button = gtk_menu_item_new_with_mnemonic(_("_Link")); + g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->link); + gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), button); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(insert_smiley_cb), toolbar); - - toolbar->smiley = button; - + g_signal_connect(G_OBJECT(insert_button), "clicked", G_CALLBACK(pidgin_menu_clicked), insert_menu); + g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button); toolbar->sml = NULL; - gtk_widget_show_all(hbox); } GtkWidget *gtk_imhtmltoolbar_new() @@ -1138,3 +1238,19 @@ g_free(toolbar->sml); toolbar->sml = g_strdup(proto_id); } + g_signal_connect(G_OBJECT(imhtml), "format_function_update", G_CALLBACK(update_format_cb), toolbar); + g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml)->text_buffer), "mark-set", G_CALLBACK(mark_set_cb), toolbar); + + buttons = gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)); + update_buttons_cb(GTK_IMHTML(imhtml), buttons, toolbar); + + gtk_imhtml_get_current_format(GTK_IMHTML(imhtml), &bold, &italic, &underline); + + update_buttons(toolbar); +} + +void gtk_imhtmltoolbar_associate_smileys(GtkIMHtmlToolbar *toolbar, const char *proto_id) +{ + g_free(toolbar->sml); + toolbar->sml = g_strdup(proto_id); +}
--- a/pidgin/gtkmain.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkmain.c Wed Jun 06 12:20:11 2007 +0000 @@ -733,6 +733,15 @@ abort(); } + if (!purple_core_ensure_single_instance()) { + purple_core_quit(); +#ifdef HAVE_SIGNAL_H + g_free(segfault_message); +#endif + return 0; + } + + /* TODO: Move blist loading into purple_blist_init() */ purple_set_blist(purple_blist_new()); purple_blist_load();
--- a/pidgin/gtknotify.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtknotify.c Wed Jun 06 12:20:11 2007 +0000 @@ -577,10 +577,8 @@ char label_text[2048]; char *linked_text, *primary_esc, *secondary_esc; - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), title); + window = pidgin_create_window(title, PIDGIN_HIG_BORDER, NULL, TRUE); gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(formatted_close_cb), NULL); @@ -712,10 +710,8 @@ data->results = results; /* Create the window */ - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), (title ? title :_("Search Results"))); + window = pidgin_create_window(title ? title :_("Search Results"), PIDGIN_HIG_BORDER, NULL, TRUE); gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); g_signal_connect_swapped(G_OBJECT(window), "delete_event", G_CALLBACK(searchresults_close_cb), data);
--- a/pidgin/gtkpounce.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkpounce.c Wed Jun 06 12:20:11 2007 +0000 @@ -38,6 +38,7 @@ #include "gtkblist.h" #include "gtkdialogs.h" +#include "gtkimhtml.h" #include "gtkpounce.h" #include "pidginstock.h" #include "gtkutils.h" @@ -241,7 +242,8 @@ save_pounce_cb(GtkWidget *w, PidginPounceDialog *dialog) { const char *name; - const char *message, *command, *sound, *reason; + const char *command, *sound, *reason; + char *message; PurplePounceEvent events = PURPLE_POUNCE_NONE; PurplePounceOption options = PURPLE_POUNCE_OPTION_NONE; @@ -290,13 +292,16 @@ events |= PURPLE_POUNCE_MESSAGE_RECEIVED; /* Data fields */ - message = gtk_entry_get_text(GTK_ENTRY(dialog->send_msg_entry)); + message = gtk_imhtml_get_markup(GTK_IMHTML(dialog->send_msg_entry)); command = gtk_entry_get_text(GTK_ENTRY(dialog->exec_cmd_entry)); sound = gtk_entry_get_text(GTK_ENTRY(dialog->play_sound_entry)); reason = gtk_entry_get_text(GTK_ENTRY(dialog->popup_entry)); if (*reason == '\0') reason = NULL; - if (*message == '\0') message = NULL; + if (*message == '\0') { + g_free(message); + message = NULL; + } if (*command == '\0') command = NULL; if (*sound == '\0') sound = NULL; @@ -349,6 +354,7 @@ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->save_pounce))); update_pounces(); + g_free(message); delete_win_cb(NULL, NULL, dialog); } @@ -446,6 +452,14 @@ {"application/x-im-contact", 0, 1} }; +static void +reset_send_msg_entry(PidginPounceDialog *dialog, GtkWidget *dontcare) +{ + PurpleAccount *account = pidgin_account_option_menu_get_selected(dialog->account_menu); + gtk_imhtml_setup_entry(GTK_IMHTML(dialog->send_msg_entry), + (account && account->gc) ? account->gc->flags : PURPLE_CONNECTION_HTML); +} + void pidgin_pounce_editor_show(PurpleAccount *account, const char *name, PurplePounce *cur_pounce) @@ -462,6 +476,7 @@ GtkSizeGroup *sg; GPtrArray *sound_widgets; GPtrArray *exec_widgets; + GtkWidget *send_msg_imhtml; g_return_if_fail((cur_pounce != NULL) || (account != NULL) || @@ -498,15 +513,9 @@ sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); /* Create the window. */ - dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = window = pidgin_create_window((cur_pounce == NULL ? _("New Buddy Pounce") : _("Edit Buddy Pounce")), + PIDGIN_HIG_BORDER, "buddy_pounce", FALSE) ; gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_role(GTK_WINDOW(window), "buddy_pounce"); - gtk_window_set_resizable(GTK_WINDOW(window), FALSE); - gtk_window_set_title(GTK_WINDOW(window), - (cur_pounce == NULL - ? _("New Buddy Pounce") : _("Edit Buddy Pounce"))); - - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog); @@ -653,7 +662,8 @@ dialog->play_sound = gtk_check_button_new_with_mnemonic(_("P_lay a sound")); - dialog->send_msg_entry = gtk_entry_new(); + send_msg_imhtml = pidgin_create_imhtml(TRUE, &dialog->send_msg_entry, NULL, NULL); + reset_send_msg_entry(dialog, NULL); dialog->exec_cmd_entry = gtk_entry_new(); dialog->popup_entry = gtk_entry_new(); dialog->exec_cmd_browse = gtk_button_new_with_mnemonic(_("Brows_e...")); @@ -661,7 +671,7 @@ dialog->play_sound_browse = gtk_button_new_with_mnemonic(_("Br_owse...")); dialog->play_sound_test = gtk_button_new_with_mnemonic(_("Pre_view")); - gtk_widget_set_sensitive(dialog->send_msg_entry, FALSE); + gtk_widget_set_sensitive(send_msg_imhtml, FALSE); gtk_widget_set_sensitive(dialog->exec_cmd_entry, FALSE); gtk_widget_set_sensitive(dialog->popup_entry, FALSE); gtk_widget_set_sensitive(dialog->exec_cmd_browse, FALSE); @@ -673,8 +683,6 @@ gtk_size_group_add_widget(sg, dialog->open_win); gtk_size_group_add_widget(sg, dialog->popup); gtk_size_group_add_widget(sg, dialog->popup_entry); - gtk_size_group_add_widget(sg, dialog->send_msg); - gtk_size_group_add_widget(sg, dialog->send_msg_entry); gtk_size_group_add_widget(sg, dialog->exec_cmd); gtk_size_group_add_widget(sg, dialog->exec_cmd_entry); gtk_size_group_add_widget(sg, dialog->exec_cmd_browse); @@ -689,23 +697,23 @@ GTK_FILL, 0, 0, 0); gtk_table_attach(GTK_TABLE(table), dialog->popup_entry, 1, 4, 1, 2, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->send_msg, 0, 1, 2, 3, + gtk_table_attach(GTK_TABLE(table), dialog->send_msg, 0, 4, 2, 3, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->send_msg_entry, 1, 4, 2, 3, + gtk_table_attach(GTK_TABLE(table), send_msg_imhtml, 0, 4, 3, 4, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd, 0, 1, 3, 4, + gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd, 0, 1, 4, 5, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_entry, 1, 2, 3, 4, + gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_entry, 1, 2, 4, 5, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_browse, 2, 3, 3, 4, + gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_browse, 2, 3, 4, 5, GTK_FILL | GTK_EXPAND, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->play_sound, 0, 1, 4, 5, + gtk_table_attach(GTK_TABLE(table), dialog->play_sound, 0, 1, 5, 6, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->play_sound_entry, 1, 2, 4, 5, + gtk_table_attach(GTK_TABLE(table), dialog->play_sound_entry, 1, 2, 5, 6, GTK_FILL, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->play_sound_browse, 2, 3, 4, 5, + gtk_table_attach(GTK_TABLE(table), dialog->play_sound_browse,2, 3, 5, 6, GTK_FILL | GTK_EXPAND, 0, 0, 0); - gtk_table_attach(GTK_TABLE(table), dialog->play_sound_test, 3, 4, 4, 5, + gtk_table_attach(GTK_TABLE(table), dialog->play_sound_test, 3, 4, 5, 6, GTK_FILL | GTK_EXPAND, 0, 0, 0); gtk_table_set_row_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE / 2); @@ -714,7 +722,7 @@ gtk_widget_show(dialog->popup); gtk_widget_show(dialog->popup_entry); gtk_widget_show(dialog->send_msg); - gtk_widget_show(dialog->send_msg_entry); + gtk_widget_show(send_msg_imhtml); gtk_widget_show(dialog->exec_cmd); gtk_widget_show(dialog->exec_cmd_entry); gtk_widget_show(dialog->exec_cmd_browse); @@ -729,7 +737,7 @@ g_signal_connect(G_OBJECT(dialog->send_msg), "clicked", G_CALLBACK(pidgin_toggle_sensitive), - dialog->send_msg_entry); + send_msg_imhtml); g_signal_connect(G_OBJECT(dialog->popup), "clicked", G_CALLBACK(pidgin_toggle_sensitive), @@ -765,7 +773,12 @@ g_object_set_data_full(G_OBJECT(dialog->window), "sound-widgets", sound_widgets, (GDestroyNotify)g_ptr_array_free); - g_signal_connect(G_OBJECT(dialog->send_msg_entry), "activate", + g_signal_connect_swapped(G_OBJECT(dialog->send_msg_entry), "format_function_clear", + G_CALLBACK(reset_send_msg_entry), dialog); + g_signal_connect_swapped(G_OBJECT(dialog->account_menu), "changed", + G_CALLBACK(reset_send_msg_entry), dialog); + + g_signal_connect(G_OBJECT(dialog->send_msg_entry), "message_send", G_CALLBACK(save_pounce_cb), dialog); g_signal_connect(G_OBJECT(dialog->popup_entry), "activate", G_CALLBACK(save_pounce_cb), dialog); @@ -892,7 +905,7 @@ "send-message", "message")) != NULL) { - gtk_entry_set_text(GTK_ENTRY(dialog->send_msg_entry), value); + gtk_imhtml_append_text(GTK_IMHTML(dialog->send_msg_entry), value, 0); } if ((value = purple_pounce_action_get_attribute(cur_pounce, @@ -1323,11 +1336,8 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/height"); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); - gtk_window_set_role(GTK_WINDOW(win), "pounces"); - gtk_window_set_title(GTK_WINDOW(win), _("Buddy Pounces")); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(pounces_manager_destroy_cb), dialog);
--- a/pidgin/gtkprefs.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkprefs.c Wed Jun 06 12:20:11 2007 +0000 @@ -979,17 +979,7 @@ gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold")) - gtk_imhtml_toggle_bold(GTK_IMHTML(imhtml)); - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic")) - gtk_imhtml_toggle_italic(GTK_IMHTML(imhtml)); - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline")) - gtk_imhtml_toggle_underline(GTK_IMHTML(imhtml)); - - gtk_imhtml_font_set_size(GTK_IMHTML(imhtml), purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size")); - gtk_imhtml_toggle_forecolor(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor")); - gtk_imhtml_toggle_background(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor")); - gtk_imhtml_toggle_fontface(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face")); + gtk_imhtml_setup_entry(GTK_IMHTML(imhtml), PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO); g_signal_connect_after(G_OBJECT(imhtml), "format_function_toggle", G_CALLBACK(formatting_toggle_cb), toolbar); @@ -1987,11 +1977,7 @@ /* Back to instant-apply! I win! BU-HAHAHA! */ /* Create the window */ - prefs = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(prefs), "preferences"); - gtk_window_set_title(GTK_WINDOW(prefs), _("Preferences")); - gtk_window_set_resizable (GTK_WINDOW(prefs), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(prefs), PIDGIN_HIG_BORDER); + prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE); g_signal_connect(G_OBJECT(prefs), "destroy", G_CALLBACK(delete_prefs), NULL);
--- a/pidgin/gtkprivacy.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkprivacy.c Wed Jun 06 12:20:11 2007 +0000 @@ -366,11 +366,7 @@ dialog = g_new0(PidginPrivacyDialog, 1); - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE); - gtk_window_set_role(GTK_WINDOW(dialog->win), "privacy"); - gtk_window_set_title(GTK_WINDOW(dialog->win), _("Privacy")); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), PIDGIN_HIG_BORDER); + dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", FALSE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(destroy_cb), dialog);
--- a/pidgin/gtkrequest.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkrequest.c Wed Jun 06 12:20:11 2007 +0000 @@ -1069,16 +1069,12 @@ data->cbs[0] = ok_cb; data->cbs[1] = cancel_cb; - data->dialog = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - - if (title != NULL) - gtk_window_set_title(GTK_WINDOW(win), title); + #ifdef _WIN32 - gtk_window_set_title(GTK_WINDOW(win), PIDGIN_ALERT_TITLE); -#endif - - gtk_window_set_role(GTK_WINDOW(win), "multifield"); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); + data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; +#else /* !_WIN32 */ + data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ; +#endif /* _WIN32 */ g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(destroy_multifield_cb), data);
--- a/pidgin/gtkroomlist.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkroomlist.c Wed Jun 06 12:20:11 2007 +0000 @@ -371,11 +371,7 @@ dialog->account = account; /* Create the window. */ - dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(window), "room list"); - gtk_window_set_title(GTK_WINDOW(window), _("Room List")); - - gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); + dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/gtksavedstatuses.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtksavedstatuses.c Wed Jun 06 12:20:11 2007 +0000 @@ -551,11 +551,8 @@ width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width"); height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height"); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog->window = win = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); - gtk_window_set_role(GTK_WINDOW(win), "statuses"); - gtk_window_set_title(GTK_WINDOW(win), _("Saved Statuses")); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_window_destroy_cb), dialog); @@ -1085,11 +1082,7 @@ if (edit) dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status)); - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win), "status"); - gtk_window_set_title(GTK_WINDOW(win), _("Status")); - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); + dialog->window = win = pidgin_create_window (_("Status"), PIDGIN_HIG_BORDER, "status", FALSE) ; g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_editor_destroy_cb), dialog); @@ -1423,13 +1416,9 @@ dialog->status_editor = status_editor; dialog->account = account; - dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(win), "substatus"); tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account)); - gtk_window_set_title(GTK_WINDOW(win), tmp); + dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", FALSE) ; g_free(tmp); - gtk_window_set_resizable(GTK_WINDOW(win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(substatus_editor_destroy_cb), dialog);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceundomanager.c Wed Jun 06 12:20:11 2007 +0000 @@ -0,0 +1,1122 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gtksourceundomanager.c + * This file is part of GtkSourceView + * + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence + * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi + * Copyright (C) 2002-2005 Paolo Maggi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <stdlib.h> +#include <string.h> + +#include "gtksourceundomanager.h" +#include "gtksourceview-marshal.h" + + +#define DEFAULT_MAX_UNDO_LEVELS 25 + + +typedef struct _GtkSourceUndoAction GtkSourceUndoAction; +typedef struct _GtkSourceUndoInsertAction GtkSourceUndoInsertAction; +typedef struct _GtkSourceUndoDeleteAction GtkSourceUndoDeleteAction; + +typedef enum { + GTK_SOURCE_UNDO_ACTION_INSERT, + GTK_SOURCE_UNDO_ACTION_DELETE +} GtkSourceUndoActionType; + +/* + * We use offsets instead of GtkTextIters because the last ones + * require to much memory in this context without giving us any advantage. + */ + +struct _GtkSourceUndoInsertAction +{ + gint pos; + gchar *text; + gint length; + gint chars; +}; + +struct _GtkSourceUndoDeleteAction +{ + gint start; + gint end; + gchar *text; + gboolean forward; +}; + +struct _GtkSourceUndoAction +{ + GtkSourceUndoActionType action_type; + + union { + GtkSourceUndoInsertAction insert; + GtkSourceUndoDeleteAction delete; + } action; + + gint order_in_group; + + /* It is TRUE whether the action can be merged with the following action. */ + guint mergeable : 1; + + /* It is TRUE whether the action is marked as "modified". + * An action is marked as "modified" if it changed the + * state of the buffer from "not modified" to "modified". Only the first + * action of a group can be marked as modified. + * There can be a single action marked as "modified" in the actions list. + */ + guint modified : 1; +}; + +/* INVALID is a pointer to an invalid action */ +#define INVALID ((void *) "IA") + +struct _GtkSourceUndoManagerPrivate +{ + GtkTextBuffer *document; + + GList* actions; + gint next_redo; + + gint actions_in_current_group; + + gint running_not_undoable_actions; + + gint num_of_groups; + + gint max_undo_levels; + + guint can_undo : 1; + guint can_redo : 1; + + /* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1), + * the state of the buffer changed from "not modified" to "modified". + */ + guint modified_undoing_group : 1; + + /* Pointer to the action (in the action list) marked as "modified". + * It is NULL when no action is marked as "modified". + * It is INVALID when the action marked as "modified" has been removed + * from the action list (freeing the list or resizing it) */ + GtkSourceUndoAction *modified_action; +}; + +enum { + CAN_UNDO, + CAN_REDO, + LAST_SIGNAL +}; + +static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass); +static void gtk_source_undo_manager_init (GtkSourceUndoManager *um); +static void gtk_source_undo_manager_finalize (GObject *object); + +static void gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, + GtkTextIter *pos, + const gchar *text, + gint length, + GtkSourceUndoManager *um); +static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + GtkSourceUndoManager *um); +static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, + GtkSourceUndoManager *um); +static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, + GtkSourceUndoManager *um); + +static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um); + +static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action); +static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, + gint n); +static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um); + +static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action); + +static GObjectClass *parent_class = NULL; +static guint undo_manager_signals [LAST_SIGNAL] = { 0 }; + +GType +gtk_source_undo_manager_get_type (void) +{ + static GType undo_manager_type = 0; + + if (undo_manager_type == 0) + { + static const GTypeInfo our_info = + { + sizeof (GtkSourceUndoManagerClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_source_undo_manager_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkSourceUndoManager), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_source_undo_manager_init + }; + + undo_manager_type = g_type_register_static (G_TYPE_OBJECT, + "GtkSourceUndoManager", + &our_info, + 0); + } + + return undo_manager_type; +} + +static void +gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + object_class->finalize = gtk_source_undo_manager_finalize; + + klass->can_undo = NULL; + klass->can_redo = NULL; + + undo_manager_signals[CAN_UNDO] = + g_signal_new ("can_undo", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo), + NULL, NULL, + gtksourceview_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); + + undo_manager_signals[CAN_REDO] = + g_signal_new ("can_redo", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo), + NULL, NULL, + gtksourceview_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); +} + +static void +gtk_source_undo_manager_init (GtkSourceUndoManager *um) +{ + um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1); + + um->priv->actions = NULL; + um->priv->next_redo = 0; + + um->priv->can_undo = FALSE; + um->priv->can_redo = FALSE; + + um->priv->running_not_undoable_actions = 0; + + um->priv->num_of_groups = 0; + + um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS; + + um->priv->modified_action = NULL; + + um->priv->modified_undoing_group = FALSE; +} + +static void +gtk_source_undo_manager_finalize (GObject *object) +{ + GtkSourceUndoManager *um; + + g_return_if_fail (object != NULL); + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object)); + + um = GTK_SOURCE_UNDO_MANAGER (object); + + g_return_if_fail (um->priv != NULL); + + if (um->priv->actions != NULL) + { + gtk_source_undo_manager_free_action_list (um); + } + + g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), + G_CALLBACK (gtk_source_undo_manager_delete_range_handler), + um); + + g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), + G_CALLBACK (gtk_source_undo_manager_insert_text_handler), + um); + + g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), + G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), + um); + + g_free (um->priv); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GtkSourceUndoManager* +gtk_source_undo_manager_new (GtkTextBuffer* buffer) +{ + GtkSourceUndoManager *um; + + um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL)); + + g_return_val_if_fail (um->priv != NULL, NULL); + um->priv->document = buffer; + + g_signal_connect (G_OBJECT (buffer), "insert_text", + G_CALLBACK (gtk_source_undo_manager_insert_text_handler), + um); + + g_signal_connect (G_OBJECT (buffer), "delete_range", + G_CALLBACK (gtk_source_undo_manager_delete_range_handler), + um); + + g_signal_connect (G_OBJECT (buffer), "begin_user_action", + G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), + um); + + g_signal_connect (G_OBJECT (buffer), "modified_changed", + G_CALLBACK (gtk_source_undo_manager_modified_changed_handler), + um); + return um; +} + +void +gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + ++um->priv->running_not_undoable_actions; +} + +static void +gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + g_return_if_fail (um->priv->running_not_undoable_actions > 0); + + --um->priv->running_not_undoable_actions; +} + +void +gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + gtk_source_undo_manager_end_not_undoable_action_internal (um); + + if (um->priv->running_not_undoable_actions == 0) + { + gtk_source_undo_manager_free_action_list (um); + + um->priv->next_redo = -1; + + if (um->priv->can_undo) + { + um->priv->can_undo = FALSE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_UNDO], + 0, + FALSE); + } + + if (um->priv->can_redo) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_REDO], + 0, + FALSE); + } + } +} + +gboolean +gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um) +{ + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); + g_return_val_if_fail (um->priv != NULL, FALSE); + + return um->priv->can_undo; +} + +gboolean +gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um) +{ + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); + g_return_val_if_fail (um->priv != NULL, FALSE); + + return um->priv->can_redo; +} + +static void +set_cursor (GtkTextBuffer *buffer, gint cursor) +{ + GtkTextIter iter; + + /* Place the cursor at the requested position */ + gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor); + gtk_text_buffer_place_cursor (buffer, &iter); +} + +static void +insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len) +{ + GtkTextIter iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); + gtk_text_buffer_insert (buffer, &iter, text, len); +} + +static void +delete_text (GtkTextBuffer *buffer, gint start, gint end) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + + if (end < 0) + gtk_text_buffer_get_end_iter (buffer, &end_iter); + else + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + + gtk_text_buffer_delete (buffer, &start_iter, &end_iter); +} + +static gchar* +get_chars (GtkTextBuffer *buffer, gint start, gint end) +{ + GtkTextIter start_iter; + GtkTextIter end_iter; + + gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); + + if (end < 0) + gtk_text_buffer_get_end_iter (buffer, &end_iter); + else + gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); + + return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE); +} + +void +gtk_source_undo_manager_undo (GtkSourceUndoManager *um) +{ + GtkSourceUndoAction *undo_action; + gboolean modified = FALSE; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + g_return_if_fail (um->priv->can_undo); + + um->priv->modified_undoing_group = FALSE; + + gtk_source_undo_manager_begin_not_undoable_action (um); + + do + { + undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1); + g_return_if_fail (undo_action != NULL); + + /* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */ + g_return_if_fail ((undo_action->order_in_group <= 1) || + ((undo_action->order_in_group > 1) && !undo_action->modified)); + + if (undo_action->order_in_group <= 1) + { + /* Set modified to TRUE only if the buffer did not change its state from + * "not modified" to "modified" undoing an action (with order_in_group > 1) + * in current group. */ + modified = (undo_action->modified && !um->priv->modified_undoing_group); + } + + switch (undo_action->action_type) + { + case GTK_SOURCE_UNDO_ACTION_DELETE: + insert_text ( + um->priv->document, + undo_action->action.delete.start, + undo_action->action.delete.text, + strlen (undo_action->action.delete.text)); + + if (undo_action->action.delete.forward) + set_cursor ( + um->priv->document, + undo_action->action.delete.start); + else + set_cursor ( + um->priv->document, + undo_action->action.delete.end); + + break; + + case GTK_SOURCE_UNDO_ACTION_INSERT: + delete_text ( + um->priv->document, + undo_action->action.insert.pos, + undo_action->action.insert.pos + + undo_action->action.insert.chars); + + set_cursor ( + um->priv->document, + undo_action->action.insert.pos); + break; + + default: + /* Unknown action type. */ + g_return_if_reached (); + } + + ++um->priv->next_redo; + + } while (undo_action->order_in_group > 1); + + if (modified) + { + --um->priv->next_redo; + gtk_text_buffer_set_modified (um->priv->document, FALSE); + ++um->priv->next_redo; + } + + gtk_source_undo_manager_end_not_undoable_action_internal (um); + + um->priv->modified_undoing_group = FALSE; + + if (!um->priv->can_redo) + { + um->priv->can_redo = TRUE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_REDO], + 0, + TRUE); + } + + if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1)) + { + um->priv->can_undo = FALSE; + g_signal_emit (G_OBJECT (um), + undo_manager_signals [CAN_UNDO], + 0, + FALSE); + } +} + +void +gtk_source_undo_manager_redo (GtkSourceUndoManager *um) +{ + GtkSourceUndoAction *undo_action; + gboolean modified = FALSE; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + g_return_if_fail (um->priv->can_redo); + + undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); + g_return_if_fail (undo_action != NULL); + + gtk_source_undo_manager_begin_not_undoable_action (um); + + do + { + if (undo_action->modified) + { + g_return_if_fail (undo_action->order_in_group <= 1); + modified = TRUE; + } + + --um->priv->next_redo; + + switch (undo_action->action_type) + { + case GTK_SOURCE_UNDO_ACTION_DELETE: + delete_text ( + um->priv->document, + undo_action->action.delete.start, + undo_action->action.delete.end); + + set_cursor ( + um->priv->document, + undo_action->action.delete.start); + + break; + + case GTK_SOURCE_UNDO_ACTION_INSERT: + set_cursor ( + um->priv->document, + undo_action->action.insert.pos); + + insert_text ( + um->priv->document, + undo_action->action.insert.pos, + undo_action->action.insert.text, + undo_action->action.insert.length); + + break; + + default: + /* Unknown action type */ + ++um->priv->next_redo; + g_return_if_reached (); + } + + if (um->priv->next_redo < 0) + undo_action = NULL; + else + undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); + + } while ((undo_action != NULL) && (undo_action->order_in_group > 1)); + + if (modified) + { + ++um->priv->next_redo; + gtk_text_buffer_set_modified (um->priv->document, FALSE); + --um->priv->next_redo; + } + + gtk_source_undo_manager_end_not_undoable_action_internal (um); + + if (um->priv->next_redo < 0) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); + } + + if (!um->priv->can_undo) + { + um->priv->can_undo = TRUE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE); + } +} + +static void +gtk_source_undo_action_free (GtkSourceUndoAction *action) +{ + if (action == NULL) + return; + + if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) + g_free (action->action.insert.text); + else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) + g_free (action->action.delete.text); + else + g_return_if_reached (); + + g_free (action); +} + +static void +gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um) +{ + GList *l; + + l = um->priv->actions; + + while (l != NULL) + { + GtkSourceUndoAction *action = l->data; + + if (action->order_in_group == 1) + --um->priv->num_of_groups; + + if (action->modified) + um->priv->modified_action = INVALID; + + gtk_source_undo_action_free (action); + + l = g_list_next (l); + } + + g_list_free (um->priv->actions); + um->priv->actions = NULL; +} + +static void +gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, + GtkTextIter *pos, + const gchar *text, + gint length, + GtkSourceUndoManager *um) +{ + GtkSourceUndoAction undo_action; + + if (um->priv->running_not_undoable_actions > 0) + return; + + g_return_if_fail (strlen (text) >= (guint)length); + + undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT; + + undo_action.action.insert.pos = gtk_text_iter_get_offset (pos); + undo_action.action.insert.text = (gchar*) text; + undo_action.action.insert.length = length; + undo_action.action.insert.chars = g_utf8_strlen (text, length); + + if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n')) + + undo_action.mergeable = FALSE; + else + undo_action.mergeable = TRUE; + + undo_action.modified = FALSE; + + gtk_source_undo_manager_add_action (um, &undo_action); +} + +static void +gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end, + GtkSourceUndoManager *um) +{ + GtkSourceUndoAction undo_action; + GtkTextIter insert_iter; + + if (um->priv->running_not_undoable_actions > 0) + return; + + undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE; + + gtk_text_iter_order (start, end); + + undo_action.action.delete.start = gtk_text_iter_get_offset (start); + undo_action.action.delete.end = gtk_text_iter_get_offset (end); + + undo_action.action.delete.text = get_chars ( + buffer, + undo_action.action.delete.start, + undo_action.action.delete.end); + + /* figure out if the user used the Delete or the Backspace key */ + gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, + gtk_text_buffer_get_insert (buffer)); + if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start) + undo_action.action.delete.forward = TRUE; + else + undo_action.action.delete.forward = FALSE; + + if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) || + (g_utf8_get_char (undo_action.action.delete.text ) == '\n')) + undo_action.mergeable = FALSE; + else + undo_action.mergeable = TRUE; + + undo_action.modified = FALSE; + + gtk_source_undo_manager_add_action (um, &undo_action); + + g_free (undo_action.action.delete.text); + +} + +static void +gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um) +{ + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + if (um->priv->running_not_undoable_actions > 0) + return; + + um->priv->actions_in_current_group = 0; +} + +static void +gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action) +{ + GtkSourceUndoAction* action; + + if (um->priv->next_redo >= 0) + { + gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1); + } + + um->priv->next_redo = -1; + + if (!gtk_source_undo_manager_merge_action (um, undo_action)) + { + action = g_new (GtkSourceUndoAction, 1); + *action = *undo_action; + + if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) + action->action.insert.text = g_strdup (undo_action->action.insert.text); + else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) + action->action.delete.text = g_strdup (undo_action->action.delete.text); + else + { + g_free (action); + g_return_if_reached (); + } + + ++um->priv->actions_in_current_group; + action->order_in_group = um->priv->actions_in_current_group; + + if (action->order_in_group == 1) + ++um->priv->num_of_groups; + + um->priv->actions = g_list_prepend (um->priv->actions, action); + } + + gtk_source_undo_manager_check_list_size (um); + + if (!um->priv->can_undo) + { + um->priv->can_undo = TRUE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE); + } + + if (um->priv->can_redo) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); + } +} + +static void +gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, + gint n) +{ + gint i; + + if (um->priv->actions == NULL) + return; + + for (i = 0; i < n; i++) + { + GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data; + + if (action->order_in_group == 1) + --um->priv->num_of_groups; + + if (action->modified) + um->priv->modified_action = INVALID; + + gtk_source_undo_action_free (action); + + um->priv->actions = g_list_delete_link (um->priv->actions, + um->priv->actions); + + if (um->priv->actions == NULL) + return; + } +} + +static void +gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um) +{ + gint undo_levels; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + undo_levels = gtk_source_undo_manager_get_max_undo_levels (um); + + if (undo_levels < 1) + return; + + if (um->priv->num_of_groups > undo_levels) + { + GtkSourceUndoAction *undo_action; + GList *last; + + last = g_list_last (um->priv->actions); + undo_action = (GtkSourceUndoAction*) last->data; + + do + { + GList *tmp; + + if (undo_action->order_in_group == 1) + --um->priv->num_of_groups; + + if (undo_action->modified) + um->priv->modified_action = INVALID; + + gtk_source_undo_action_free (undo_action); + + tmp = g_list_previous (last); + um->priv->actions = g_list_delete_link (um->priv->actions, last); + last = tmp; + g_return_if_fail (last != NULL); + + undo_action = (GtkSourceUndoAction*) last->data; + + } while ((undo_action->order_in_group > 1) || + (um->priv->num_of_groups > undo_levels)); + } +} + +/** + * gtk_source_undo_manager_merge_action: + * @um: a #GtkSourceUndoManager. + * @undo_action: a #GtkSourceUndoAction. + * + * This function tries to merge the undo action at the top of + * the stack with a new undo action. So when we undo for example + * typing, we can undo the whole word and not each letter by itself. + * + * Return Value: %TRUE is merge was sucessful, %FALSE otherwise.² + **/ +static gboolean +gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, + const GtkSourceUndoAction *undo_action) +{ + GtkSourceUndoAction *last_action; + + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE); + g_return_val_if_fail (um->priv != NULL, FALSE); + + if (um->priv->actions == NULL) + return FALSE; + + last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0); + + if (!last_action->mergeable) + return FALSE; + + if ((!undo_action->mergeable) || + (undo_action->action_type != last_action->action_type)) + { + last_action->mergeable = FALSE; + return FALSE; + } + + if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) + { + if ((last_action->action.delete.forward != undo_action->action.delete.forward) || + ((last_action->action.delete.start != undo_action->action.delete.start) && + (last_action->action.delete.start != undo_action->action.delete.end))) + { + last_action->mergeable = FALSE; + return FALSE; + } + + if (last_action->action.delete.start == undo_action->action.delete.start) + { + gchar *str; + +#define L (last_action->action.delete.end - last_action->action.delete.start - 1) +#define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i))) + + /* Deleted with the delete key */ + if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && + (g_utf8_get_char (undo_action->action.delete.text) != '\t') && + ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') || + (g_utf8_get_char_at (last_action->action.delete.text, L) == '\t'))) + { + last_action->mergeable = FALSE; + return FALSE; + } + + str = g_strdup_printf ("%s%s", last_action->action.delete.text, + undo_action->action.delete.text); + + g_free (last_action->action.delete.text); + last_action->action.delete.end += (undo_action->action.delete.end - + undo_action->action.delete.start); + last_action->action.delete.text = str; + } + else + { + gchar *str; + + /* Deleted with the backspace key */ + if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') && + (g_utf8_get_char (undo_action->action.delete.text) != '\t') && + ((g_utf8_get_char (last_action->action.delete.text) == ' ') || + (g_utf8_get_char (last_action->action.delete.text) == '\t'))) + { + last_action->mergeable = FALSE; + return FALSE; + } + + str = g_strdup_printf ("%s%s", undo_action->action.delete.text, + last_action->action.delete.text); + + g_free (last_action->action.delete.text); + last_action->action.delete.start = undo_action->action.delete.start; + last_action->action.delete.text = str; + } + } + else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) + { + gchar* str; + +#define I (last_action->action.insert.chars - 1) + + if ((undo_action->action.insert.pos != + (last_action->action.insert.pos + last_action->action.insert.chars)) || + ((g_utf8_get_char (undo_action->action.insert.text) != ' ') && + (g_utf8_get_char (undo_action->action.insert.text) != '\t') && + ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') || + (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t'))) + ) + { + last_action->mergeable = FALSE; + return FALSE; + } + + str = g_strdup_printf ("%s%s", last_action->action.insert.text, + undo_action->action.insert.text); + + g_free (last_action->action.insert.text); + last_action->action.insert.length += undo_action->action.insert.length; + last_action->action.insert.text = str; + last_action->action.insert.chars += undo_action->action.insert.chars; + + } + else + /* Unknown action inside undo merge encountered */ + g_return_val_if_reached (TRUE); + + return TRUE; +} + +gint +gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um) +{ + g_return_val_if_fail (um != NULL, 0); + g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0); + + return um->priv->max_undo_levels; +} + +void +gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um, + gint max_undo_levels) +{ + gint old_levels; + + g_return_if_fail (um != NULL); + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + + old_levels = um->priv->max_undo_levels; + um->priv->max_undo_levels = max_undo_levels; + + if (max_undo_levels < 1) + return; + + if (old_levels > max_undo_levels) + { + /* strip redo actions first */ + while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels)) + { + gtk_source_undo_manager_free_first_n_actions (um, 1); + um->priv->next_redo--; + } + + /* now remove undo actions if necessary */ + gtk_source_undo_manager_check_list_size (um); + + /* emit "can_undo" and/or "can_redo" if appropiate */ + if (um->priv->next_redo < 0 && um->priv->can_redo) + { + um->priv->can_redo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); + } + + if (um->priv->can_undo && + um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1)) + { + um->priv->can_undo = FALSE; + g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE); + } + } +} + +static void +gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, + GtkSourceUndoManager *um) +{ + GtkSourceUndoAction *action; + GList *list; + + g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); + g_return_if_fail (um->priv != NULL); + + if (um->priv->actions == NULL) + return; + + list = g_list_nth (um->priv->actions, um->priv->next_redo + 1); + + if (list != NULL) + action = (GtkSourceUndoAction*) list->data; + else + action = NULL; + + if (gtk_text_buffer_get_modified (buffer) == FALSE) + { + if (action != NULL) + action->mergeable = FALSE; + + if (um->priv->modified_action != NULL) + { + if (um->priv->modified_action != INVALID) + um->priv->modified_action->modified = FALSE; + + um->priv->modified_action = NULL; + } + + return; + } + + if (action == NULL) + { + g_return_if_fail (um->priv->running_not_undoable_actions > 0); + + return; + } + + /* gtk_text_buffer_get_modified (buffer) == TRUE */ + + g_return_if_fail (um->priv->modified_action == NULL); + + if (action->order_in_group > 1) + um->priv->modified_undoing_group = TRUE; + + while (action->order_in_group > 1) + { + list = g_list_next (list); + g_return_if_fail (list != NULL); + + action = (GtkSourceUndoAction*) list->data; + g_return_if_fail (action != NULL); + } + + action->modified = TRUE; + um->priv->modified_action = action; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceundomanager.h Wed Jun 06 12:20:11 2007 +0000 @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gtksourceundomanager.h + * This file is part of GtkSourceView + * + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence + * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi + * Copyright (C) 2002, 2003 Paolo Maggi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. * * + */ + +#ifndef __GTK_SOURCE_UNDO_MANAGER_H__ +#define __GTK_SOURCE_UNDO_MANAGER_H__ + +#include <gtk/gtktextbuffer.h> + +#define GTK_SOURCE_TYPE_UNDO_MANAGER (gtk_source_undo_manager_get_type ()) +#define GTK_SOURCE_UNDO_MANAGER(obj) (GTK_CHECK_CAST ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManager)) +#define GTK_SOURCE_UNDO_MANAGER_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) +#define GTK_SOURCE_IS_UNDO_MANAGER(obj) (GTK_CHECK_TYPE ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER)) +#define GTK_SOURCE_IS_UNDO_MANAGER_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER)) +#define GTK_SOURCE_UNDO_MANAGER_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass)) + + +typedef struct _GtkSourceUndoManager GtkSourceUndoManager; +typedef struct _GtkSourceUndoManagerClass GtkSourceUndoManagerClass; + +typedef struct _GtkSourceUndoManagerPrivate GtkSourceUndoManagerPrivate; + +struct _GtkSourceUndoManager +{ + GObject base; + + GtkSourceUndoManagerPrivate *priv; +}; + +struct _GtkSourceUndoManagerClass +{ + GObjectClass parent_class; + + /* Signals */ + void (*can_undo) (GtkSourceUndoManager *um, gboolean can_undo); + void (*can_redo) (GtkSourceUndoManager *um, gboolean can_redo); +}; + +GType gtk_source_undo_manager_get_type (void) G_GNUC_CONST; + +GtkSourceUndoManager* gtk_source_undo_manager_new (GtkTextBuffer *buffer); + +gboolean gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um); +gboolean gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um); + +void gtk_source_undo_manager_undo (GtkSourceUndoManager *um); +void gtk_source_undo_manager_redo (GtkSourceUndoManager *um); + +void gtk_source_undo_manager_begin_not_undoable_action + (GtkSourceUndoManager *um); +void gtk_source_undo_manager_end_not_undoable_action + (GtkSourceUndoManager *um); + +gint gtk_source_undo_manager_get_max_undo_levels + (GtkSourceUndoManager *um); +void gtk_source_undo_manager_set_max_undo_levels + (GtkSourceUndoManager *um, + gint undo_levels); + +#endif /* __GTK_SOURCE_UNDO_MANAGER_H__ */ + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceview-marshal.c Wed Jun 06 12:20:11 2007 +0000 @@ -0,0 +1,95 @@ +#include "gtksourceview-marshal.h" + +#include <glib-object.h> + + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* VOID:VOID (gtksourceview-marshal.list:1) */ + +/* VOID:BOOLEAN (gtksourceview-marshal.list:2) */ + +/* VOID:BOXED (gtksourceview-marshal.list:3) */ + +/* VOID:BOXED,BOXED (gtksourceview-marshal.list:4) */ +void +gtksourceview_marshal_VOID__BOXED_BOXED (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__BOXED_BOXED) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer data2); + register GMarshalFunc_VOID__BOXED_BOXED callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_marshal_value_peek_boxed (param_values + 1), + g_marshal_value_peek_boxed (param_values + 2), + data2); +} + +/* VOID:STRING (gtksourceview-marshal.list:5) */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceview-marshal.h Wed Jun 06 12:20:11 2007 +0000 @@ -0,0 +1,32 @@ + +#ifndef __gtksourceview_marshal_MARSHAL_H__ +#define __gtksourceview_marshal_MARSHAL_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* VOID:VOID (gtksourceview-marshal.list:1) */ +#define gtksourceview_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID + +/* VOID:BOOLEAN (gtksourceview-marshal.list:2) */ +#define gtksourceview_marshal_VOID__BOOLEAN g_cclosure_marshal_VOID__BOOLEAN + +/* VOID:BOXED (gtksourceview-marshal.list:3) */ +#define gtksourceview_marshal_VOID__BOXED g_cclosure_marshal_VOID__BOXED + +/* VOID:BOXED,BOXED (gtksourceview-marshal.list:4) */ +extern void gtksourceview_marshal_VOID__BOXED_BOXED (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); + +/* VOID:STRING (gtksourceview-marshal.list:5) */ +#define gtksourceview_marshal_VOID__STRING g_cclosure_marshal_VOID__STRING + +G_END_DECLS + +#endif /* __gtksourceview_marshal_MARSHAL_H__ */ +
--- a/pidgin/gtkutils.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkutils.c Wed Jun 06 12:20:11 2007 +0000 @@ -130,6 +130,22 @@ } GtkWidget * +pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) +{ + GtkWindow *wnd = NULL; + + wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + if (title) + gtk_window_set_title(wnd, title); + gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width); + if (role) + gtk_window_set_role(wnd, role); + gtk_window_set_resizable(wnd, resizable); + + return GTK_WIDGET(wnd); +} + +GtkWidget * pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) { GtkWidget *frame; @@ -245,13 +261,14 @@ gtk_widget_show(to_toggle); } -void pidgin_separator(GtkWidget *menu) +GtkWidget *pidgin_separator(GtkWidget *menu) { GtkWidget *menuitem; menuitem = gtk_separator_menu_item_new(); gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + return menuitem; } GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str) @@ -462,7 +479,7 @@ } static GtkWidget * -aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data) +aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data) { GtkWidget *item; GtkWidget *hbox; @@ -495,6 +512,7 @@ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + g_object_set_data(G_OBJECT (item), data, per_item_data); g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data); pidgin_set_accessible_label(item, label); @@ -502,6 +520,39 @@ return item; } +static GdkPixbuf * +pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account) +{ + PurplePluginProtocolInfo *prpl_info; + const char *protoname = NULL; + char buf[MAXPATHLEN]; + char *filename = NULL; + GdkPixbuf *pixbuf; + + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + if (prpl_info->list_icon == NULL) + return NULL; + + protoname = prpl_info->list_icon(account, NULL); + if (protoname == NULL) + return NULL; + + /* + * Status icons will be themeable too, and then it will look up + * protoname from the theme + */ + g_snprintf(buf, sizeof(buf), "%s.png", protoname); + + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", + size == PIDGIN_PRPL_ICON_SMALL ? "16" : + size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48", + buf, NULL); + pixbuf = gdk_pixbuf_new_from_file(filename, NULL); + g_free(filename); + + return pixbuf; +} + static GtkWidget * aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data) { @@ -552,25 +603,6 @@ } } -static GdkPixbuf * -get_prpl_pixbuf(PurplePluginProtocolInfo *prpl_info) -{ - const char *proto_name; - GdkPixbuf *pixbuf = NULL; - char *filename; - char buf[256]; - - proto_name = prpl_info->list_icon(NULL, NULL); - g_return_val_if_fail(proto_name != NULL, NULL); - - g_snprintf(buf, sizeof(buf), "%s.png", proto_name); - filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", buf, NULL); - pixbuf = gdk_pixbuf_new_from_file(filename, NULL); - g_free(filename); - - return pixbuf; -} - static AopMenu * create_protocols_menu(const char *default_proto_id) { @@ -602,11 +634,14 @@ if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) { char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", "google-talk.png", NULL); + GtkWidget *item; + pixbuf = gdk_pixbuf_new_from_file(filename, NULL); g_free(filename); gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), - aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber")); + item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol")); + g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1)); if (pixbuf) g_object_unref(pixbuf); @@ -615,10 +650,10 @@ i++; } - pixbuf = get_prpl_pixbuf(prpl_info); + pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL); gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), - aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id)); + aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol")); if (pixbuf) g_object_unref(pixbuf); @@ -670,7 +705,6 @@ sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); for (p = list, i = 0; p != NULL; p = p->next, i++) { - PurplePluginProtocolInfo *prpl_info = NULL; PurplePlugin *plugin; if (show_all) @@ -688,18 +722,12 @@ plugin = purple_find_prpl(purple_account_get_protocol_id(account)); - if (plugin) - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); - - /* Load the image. */ - if (prpl_info) { - pixbuf = get_prpl_pixbuf(prpl_info); - - if (pixbuf) { - if (purple_account_is_disconnected(account) && show_all && - purple_connections_get_all()) - gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE); - } + pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); + + if (pixbuf) { + if (purple_account_is_disconnected(account) && show_all && + purple_connections_get_all()) + gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE); } if (purple_account_get_alias(account)) { @@ -714,7 +742,7 @@ } gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu), - aop_menu_item_new(sg, pixbuf, buf, account)); + aop_menu_item_new(sg, pixbuf, buf, account, "account")); if (pixbuf) g_object_unref(pixbuf); @@ -883,6 +911,15 @@ g_free(filename); } +void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name) +{ + PurpleNotifyUserInfo *info = purple_notify_user_info_new(); + purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving...")); + purple_notify_userinfo(conn, name, info, NULL, NULL); + purple_notify_user_info_destroy(info); + serv_get_info(conn, name); +} + gboolean pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts, PurpleAccount **ret_account, char **ret_protocol, @@ -1583,40 +1620,13 @@ pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size) { PurplePlugin *prpl; - PurplePluginProtocolInfo *prpl_info; - const char *protoname = NULL; - char buf[256]; /* TODO: We should use a define for max file length */ - char *filename = NULL; - GdkPixbuf *pixbuf; g_return_val_if_fail(account != NULL, NULL); prpl = purple_find_prpl(purple_account_get_protocol_id(account)); if (prpl == NULL) return NULL; - - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); - if (prpl_info->list_icon == NULL) - return NULL; - - protoname = prpl_info->list_icon(account, NULL); - if (protoname == NULL) - return NULL; - - /* - * Status icons will be themeable too, and then it will look up - * protoname from the theme - */ - g_snprintf(buf, sizeof(buf), "%s.png", protoname); - - filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", - size == PIDGIN_PRPL_ICON_SMALL ? "16" : - size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48", - buf, NULL); - pixbuf = gdk_pixbuf_new_from_file(filename, NULL); - g_free(filename); - - return pixbuf; + return pidgin_create_prpl_icon_from_prpl(prpl, size, account); } static void @@ -1632,62 +1642,63 @@ callback(object, data); } -void +GtkWidget * pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act, gpointer object) { + GtkWidget *menuitem; + if (act == NULL) { - pidgin_separator(menu); - } else { - GtkWidget *menuitem; - - if (act->children == NULL) { - menuitem = gtk_menu_item_new_with_mnemonic(act->label); - - if (act->callback != NULL) { - g_object_set_data(G_OBJECT(menuitem), - "purplecallback", - act->callback); - g_object_set_data(G_OBJECT(menuitem), - "purplecallbackdata", - act->data); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(menu_action_cb), - object); - } else { - gtk_widget_set_sensitive(menuitem, FALSE); - } - - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + return pidgin_separator(menu); + } + + if (act->children == NULL) { + menuitem = gtk_menu_item_new_with_mnemonic(act->label); + + if (act->callback != NULL) { + g_object_set_data(G_OBJECT(menuitem), + "purplecallback", + act->callback); + g_object_set_data(G_OBJECT(menuitem), + "purplecallbackdata", + act->data); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(menu_action_cb), + object); } else { - GList *l = NULL; - GtkWidget *submenu = NULL; - GtkAccelGroup *group; - - menuitem = gtk_menu_item_new_with_mnemonic(act->label); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - submenu = gtk_menu_new(); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); - - group = gtk_menu_get_accel_group(GTK_MENU(menu)); - if (group) { - char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label); - gtk_menu_set_accel_path(GTK_MENU(submenu), path); - g_free(path); - gtk_menu_set_accel_group(GTK_MENU(submenu), group); - } - - for (l = act->children; l; l = l->next) { - PurpleMenuAction *act = (PurpleMenuAction *)l->data; - - pidgin_append_menu_action(submenu, act, object); - } - g_list_free(act->children); - act->children = NULL; + gtk_widget_set_sensitive(menuitem, FALSE); } - purple_menu_action_free(act); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } else { + GList *l = NULL; + GtkWidget *submenu = NULL; + GtkAccelGroup *group; + + menuitem = gtk_menu_item_new_with_mnemonic(act->label); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); + + group = gtk_menu_get_accel_group(GTK_MENU(menu)); + if (group) { + char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label); + gtk_menu_set_accel_path(GTK_MENU(submenu), path); + g_free(path); + gtk_menu_set_accel_group(GTK_MENU(submenu), group); + } + + for (l = act->children; l; l = l->next) { + PurpleMenuAction *act = (PurpleMenuAction *)l->data; + + pidgin_append_menu_action(submenu, act, object); + } + g_list_free(act->children); + act->children = NULL; } + purple_menu_action_free(act); + return menuitem; } #if GTK_CHECK_VERSION(2,3,0)
--- a/pidgin/gtkutils.h Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkutils.h Wed Jun 06 12:20:11 2007 +0000 @@ -93,6 +93,18 @@ GtkWidget *pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret); /** + * Creates a new window + * + * @param title The window title, or @c NULL + * @param border_width The window's desired border width + * @param role A string indicating what the window is responsible for doing, or @c NULL + * @param resizable Whether the window should be resizable (@c TRUE) or not (@c FALSE) + * + * @since 2.1.0 + */ +GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable); + +/** * Toggles the sensitivity of a widget. * * @param widget @c NULL. Used for signal handlers. @@ -130,8 +142,10 @@ * Adds a separator to a menu. * * @param menu The menu to add a separator to. + * + * @return The separator. */ -void pidgin_separator(GtkWidget *menu); +GtkWidget *pidgin_separator(GtkWidget *menu); /** * Creates a menu item. @@ -307,6 +321,14 @@ void pidgin_load_accels(void); /** + * Get information about a user. Show immediate feedback. + * + * @param conn The connection to get information from. + * @param name The user to get information about. + */ +void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name); + +/** * Parses an application/x-im-contact MIME message and returns the * data inside. * @@ -404,8 +426,10 @@ * @param menu The menu to append to. * @param act The PurpleMenuAction to append. * @param gobject The object to be passed to the action callback. + * + * @return The menuitem added. */ -void pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act, +GtkWidget *pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act, gpointer gobject); /**
--- a/pidgin/gtkwhiteboard.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/gtkwhiteboard.c Wed Jun 06 12:20:11 2007 +0000 @@ -28,6 +28,7 @@ #include "debug.h" #include "gtkwhiteboard.h" +#include "gtkutils.h" /****************************************************************************** * Prototypes @@ -143,21 +144,14 @@ gtkwb->brush_color = 0xff0000; } - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtkwb->window = window; - gtk_widget_set_name(window, wb->who); - /* Try and set window title as the name of the buddy, else just use their * username */ buddy = purple_find_buddy(wb->account, wb->who); - if (buddy != NULL) - gtk_window_set_title((GtkWindow*)(window), purple_buddy_get_contact_alias(buddy)); - else - gtk_window_set_title((GtkWindow*)(window), wb->who); - - gtk_window_set_resizable((GtkWindow*)(window), FALSE); + window = pidgin_create_window(buddy != NULL ? purple_buddy_get_contact_alias(buddy) : wb->who, 0, NULL, FALSE); + gtkwb->window = window; + gtk_widget_set_name(window, wb->who); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(whiteboard_close_cb), gtkwb);
--- a/pidgin/plugins/cap/cap.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/plugins/cap/cap.c Wed Jun 06 12:20:11 2007 +0000 @@ -918,7 +918,8 @@ static PidginPluginUiInfo ui_info = { get_config_frame, - 0 /* page_num (reserved) */ + 0 /* page_num (reserved) */, + NULL,NULL,NULL,NULL }; static PurplePluginInfo info = { @@ -944,7 +945,8 @@ &ui_info, /**< ui_info */ NULL, /**< extra_info */ NULL, /**< prefs_info */ - NULL + NULL, + NULL,NULL,NULL,NULL }; static GtkWidget * get_config_frame(PurplePlugin *plugin) {
--- a/pidgin/plugins/gevolution/add_buddy_dialog.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/plugins/gevolution/add_buddy_dialog.c Wed Jun 06 12:20:11 2007 +0000 @@ -442,10 +442,7 @@ if (username != NULL) dialog->username = g_strdup(username); - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(dialog->win), "add_buddy"); - gtk_window_set_title(GTK_WINDOW(dialog->win), _("Add Buddy")); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12); + dialog->win = pidgin_create_window(_("Add Buddy"), PIDGIN_HIG_BORDER, "add_buddy", TRUE); gtk_widget_set_size_request(dialog->win, -1, 400); g_signal_connect(G_OBJECT(dialog->win), "delete_event",
--- a/pidgin/plugins/gevolution/assoc-buddy.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/plugins/gevolution/assoc-buddy.c Wed Jun 06 12:20:11 2007 +0000 @@ -329,9 +329,7 @@ dialog->buddy = buddy; - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(dialog->win), "assoc_buddy"); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12); + dialog->win = pidgin_create_window(NULL, PIDGIN_HIG_BORDER, "assoc_buddy", TRUE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/plugins/gevolution/new_person_dialog.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/plugins/gevolution/new_person_dialog.c Wed Jun 06 12:20:11 2007 +0000 @@ -246,11 +246,7 @@ dialog->book = book; g_object_ref(book); - dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_role(GTK_WINDOW(dialog->win), "new_person"); - gtk_window_set_title(GTK_WINDOW(dialog->win), _("New Person")); - gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE); - gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12); + dialog->win = pidgin_create_window(_("New Person"), PIDGIN_HIG_BORDER, "new_person", FALSE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/plugins/ticker/ticker.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/plugins/ticker/ticker.c Wed Jun 06 12:20:11 2007 +0000 @@ -36,6 +36,7 @@ #include "gtkblist.h" #include "gtkplugin.h" +#include "gtkutils.h" #include "gtkticker.h" @@ -70,12 +71,10 @@ return; } - tickerwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); + tickerwindow = pidgin_create_window(_("Buddy Ticker"), 0, "ticker", TRUE); gtk_window_set_default_size(GTK_WINDOW(tickerwindow), 500, -1); g_signal_connect(G_OBJECT(tickerwindow), "delete_event", G_CALLBACK (buddy_ticker_destroy_window), NULL); - gtk_window_set_title (GTK_WINDOW(tickerwindow), _("Buddy Ticker")); - gtk_window_set_role (GTK_WINDOW(tickerwindow), "ticker"); ticker = gtk_ticker_new(); gtk_ticker_set_spacing(GTK_TICKER(ticker), 20);
--- a/pidgin/plugins/xmppconsole.c Wed Jun 06 07:51:14 2007 +0000 +++ b/pidgin/plugins/xmppconsole.c Wed Jun 06 12:20:11 2007 +0000 @@ -29,6 +29,7 @@ #if !GTK_CHECK_VERSION(2,4,0) #include "pidgincombobox.h" #endif +#include "gtkutils.h" typedef struct { PurpleConnection *gc; @@ -742,10 +743,8 @@ console = g_new0(XmppConsole, 1); - console->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(console->window), _("XMPP Console")); + console->window = pidgin_create_window(_("XMPP Console"), PIDGIN_HIG_BORDER, NULL, TRUE); g_signal_connect(G_OBJECT(console->window), "destroy", G_CALLBACK(console_destroy), NULL); - gtk_container_set_border_width(GTK_CONTAINER(console->window), 12); gtk_window_set_default_size(GTK_WINDOW(console->window), 580, 400); gtk_container_add(GTK_CONTAINER(console->window), vbox);