#include "usertooltips.h" userTooltips::userTooltips() { attached = NULL; delay = 1000; wnd = NULL; lastUser = NULL; } userTooltips::~userTooltips() { GList *search; attachedWidgets *att; for (search = attached; search; search = search->next) { att = (attachedWidgets*)search->data; g_signal_handlers_disconnect_by_func(att->w, (gpointer)this->cb_tooltipLeaveNotify, this); g_signal_handlers_disconnect_by_func(att->w, (gpointer)this->cb_tooltipMoveNotify, this); delete att; break; } g_list_free(attached); if (wnd) gtk_widget_destroy(wnd); if (timeoutSource) g_source_remove(timeoutSource); } void userTooltips::attachWidget(GtkWidget* w, UTWidgetFunc func, gpointer data) { attachedWidgets *att; att = new attachedWidgets; att->w = w; att->func = func; att->data = data; attached = g_list_append(attached, att); // connect all neccessary functions g_signal_connect_swapped(w, "leave-notify-event", G_CALLBACK(this->cb_tooltipLeaveNotify), this); g_signal_connect(w, "motion-notify-event", G_CALLBACK(this->cb_tooltipMoveNotify), this); gtk_widget_add_events(w, (GdkEventMask)(GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK)); } void userTooltips::detachWidget(GtkWidget* w) { GList *search; attachedWidgets *att; for (search = attached; search; search = search->next) { att = (attachedWidgets*)search->data; if (att->w == w) { g_signal_handlers_disconnect_by_func(w, (gpointer)this->cb_tooltipLeaveNotify, this); g_signal_handlers_disconnect_by_func(w, (gpointer)this->cb_tooltipMoveNotify, this); attached = g_list_remove(attached, att); delete att; break; } } } void userTooltips::setPopupDelay(gint newDelay) { delay = newDelay; } // ----------------------------------------------------------------------------- gboolean userTooltips::cb_tooltipLeaveNotify(userTooltips* self, GdkEventCrossing* event) { GtkWidget *newWindow; GList *children; gint posX, posY; if (self->wnd) { // here comes the tricky part. If a user presses CTRL while going to the tooltip // it gets managed so information can be copied if (event->state & GDK_CONTROL_MASK) { newWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_set_app_paintable(newWindow, TRUE); gtk_widget_set_name(newWindow, "gtk-tooltips"); gtk_window_set_resizable(GTK_WINDOW(newWindow), FALSE); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(newWindow), TRUE); gtk_container_set_border_width(GTK_CONTAINER(newWindow), 10); children = gtk_container_get_children(GTK_CONTAINER(self->wnd)); gtk_widget_reparent(GTK_WIDGET(children->data), newWindow); g_list_free(children); gtk_window_get_position(GTK_WINDOW(self->wnd), &posX, &posY); gtk_window_move(GTK_WINDOW(newWindow), posX, posY); gtk_widget_show_all(newWindow); } gtk_widget_destroy(self->wnd); self->wnd = NULL; } if (self->timeoutSource) { g_source_remove(self->timeoutSource); self->timeoutSource = 0; } self->lastUser = NULL; return FALSE; } gboolean userTooltips::cb_tooltipMoveNotify(GtkWidget* w, GdkEventMotion *event, userTooltips* self) { GList *search; attachedWidgets *att; IMUserDaemon *user; user = NULL; for (search = self->attached; search; search = search->next) { att = (attachedWidgets*)search->data; if (att->w == w) { user = att->func(w, event, att->data); break; } } // no user we are pointing to? destroy the window if (!user) { if (self->wnd) { gtk_widget_destroy(self->wnd); self->wnd = NULL; } if (self->timeoutSource) { g_source_remove(self->timeoutSource); self->timeoutSource = 0; } self->lastUser = NULL; return FALSE; } // a new user -> create the tooltip if (user != self->lastUser) { if (self->wnd) { gtk_widget_destroy(self->wnd); self->wnd = NULL; } if (self->timeoutSource) { g_source_remove(self->timeoutSource); self->timeoutSource = 0; } self->timeoutSource = g_timeout_add(self->delay, (GSourceFunc)self->cb_tooltipShowTimeout, self); self->lastUser = user; } return FALSE; } #define MW_MAKE_TT_ITEM(txt, c) \ tmp3 = g_strdup_printf("%s: %%s", txt); \ tmp = g_strdup_printf(tmp3, c); \ label2 = gtk_label_new(tmp); \ gtk_label_set_selectable(GTK_LABEL(label2), TRUE); \ gtk_label_set_use_markup(GTK_LABEL(label2), TRUE); \ gtk_misc_set_alignment(GTK_MISC(label2), 0.0f, 0.0f); \ gtk_box_pack_start(GTK_BOX(vbox1), label2, FALSE, TRUE, 0); \ g_free(tmp); \ g_free(tmp3); gboolean userTooltips::cb_tooltipShowTimeout(userTooltips* self) { GtkWidget *hbox1, *vbox1, *vbox2, *label1, *label2, *label3, *img, *alignment1, *alignment2; gchar *tmp, *tmp2, *tmp3; GString *realName; IMAutoResponseManager *aman; gulong tooltipsMask; // get the mask settings_getSettings()->getProperties("contactlist", "tooltipsMask", &tooltipsMask, NULL); // create the tooltip window self->wnd = gtk_window_new(GTK_WINDOW_POPUP); gtk_widget_set_app_paintable(self->wnd, TRUE); gtk_container_set_border_width(GTK_CONTAINER(self->wnd), 10); g_signal_connect_swapped(self->wnd, "expose-event", G_CALLBACK(self->cb_tooltipPaint), self); g_signal_connect_swapped(self->wnd, "realize", G_CALLBACK(self->cb_tooltipRealize), self); gtk_widget_ensure_style(self->wnd); gtk_widget_set_name(self->wnd, "gtk-tooltips"); // clear the old content gtk_container_foreach(GTK_CONTAINER(self->wnd), (GtkCallback)(gtk_widget_destroy), NULL); // fill the new content if (self->lastUser) { vbox1 = gtk_vbox_new(FALSE, 4); // the status image img = gtk_image_new_from_pixbuf(i_getIcons()->getUserStatusPixbuf(self->lastUser)); alignment1 = gtk_alignment_new(0.0f, 0.0f, 0.0f, 0.0f); gtk_alignment_set_padding(GTK_ALIGNMENT(alignment1), 5, 0, 0, 0); gtk_container_add(GTK_CONTAINER(alignment1), img); // the relevant information tmp = g_strdup_printf("%s", self->lastUser->info->alias); label1 = gtk_label_new(tmp); gtk_label_set_selectable(GTK_LABEL(label1), TRUE); gtk_label_set_use_markup(GTK_LABEL(label1), TRUE); gtk_misc_set_alignment(GTK_MISC(label1), 0.0f, 0.0f); g_free(tmp); // insert the real name if (tooltipsMask & TT_REALNAME) { realName = g_string_new(self->lastUser->info->firstname); if (strlen(realName->str)) realName = g_string_append(realName, " "); realName = g_string_append(realName, self->lastUser->info->lastname); MW_MAKE_TT_ITEM(tr("Real Name"), realName->str); g_string_free(realName, TRUE); } // insert the status if (tooltipsMask & TT_STATUS) { if (self->lastUser->info->isInvisible) tmp2 = g_strdup_printf("%s (Invisible)", uu_getStatusDescription(self->lastUser->info->status)); else tmp2 = g_strdup(uu_getStatusDescription(self->lastUser->info->status)); MW_MAKE_TT_ITEM(tr("Status"), tmp2); g_free(tmp2); } // insert the user id if (tooltipsMask & TT_ID) { MW_MAKE_TT_ITEM(tr("User ID"), self->lastUser->info->formattedID); } // insert the ip address if (tooltipsMask & TT_IP) { MW_MAKE_TT_ITEM(tr("IP Address"), self->lastUser->info->formattedIP); } // insert the protocol name if (tooltipsMask & TT_PROTOCOL) { MW_MAKE_TT_ITEM(tr("Protocol"), ((IMOwnerDaemon*)self->lastUser->owner)->protocol->name); } // insert the client name if (tooltipsMask & TT_CLIENTNAME) { MW_MAKE_TT_ITEM(tr("Client-Name"), self->lastUser->info->clientName); } // insert the email if (tooltipsMask & TT_EMAIL && (*self->lastUser->info->email1 || *self->lastUser->info->email2 || *self->lastUser->info->email3)) { if (*self->lastUser->info->email1) tmp2 = self->lastUser->info->email1; else if (*self->lastUser->info->email2) tmp2 = self->lastUser->info->email2; else tmp2 = self->lastUser->info->email3; label2 = gtk_label_new(tmp2); tmp = g_strdup_printf("%s", tr("Email: ")); label3 = gtk_label_new(tmp); g_free(tmp); gtk_label_set_use_markup(GTK_LABEL(label3), TRUE); gtk_misc_set_alignment(GTK_MISC(label3), 0.0f, 0.0f); gtk_label_set_selectable(GTK_LABEL(label3), TRUE); hbox1 = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox1), label3, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox1), label2, FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, TRUE, 0); } // insert the online time if (tooltipsMask & TT_ONLINETIME && self->lastUser->info->status != BUDDY_STATUS_OFFLINE) { tmp2 = u_getTimeDistance(time(NULL), self->lastUser->info->stats.onlineSince); MW_MAKE_TT_ITEM(tr("Online for"), tmp2); g_free(tmp2); } // insert the IDLE time if (tooltipsMask & TT_IDLETIME && self->lastUser->info->status != BUDDY_STATUS_OFFLINE && self->lastUser->info->stats.idleSince) { tmp2 = u_getTimeDistance(time(NULL), self->lastUser->info->stats.idleSince); MW_MAKE_TT_ITEM(tr("IDLE for"), tmp2); g_free(tmp2); } // insert the auto response if (tooltipsMask & TT_AUTORESPONSE && self->lastUser->info->status != BUDDY_STATUS_OFFLINE && self->lastUser->info->status != BUDDY_STATUS_ONLINE) { aman = new IMAutoResponseManager(self->lastUser->info, TRUE); self->lastUser->addManager(aman); tmp = g_strdup_printf("%s", tr("Auto Response: ")); label2 = gtk_label_new(tmp); g_free(tmp); gtk_label_set_use_markup(GTK_LABEL(label2), TRUE); gtk_misc_set_alignment(GTK_MISC(label2), 0.0f, 0.0f); alignment2 = gtk_alignment_new(0.0f, 0.0f, 1.0f, 1.0f); if (tooltipsMask != TT_AUTORESPONSE) gtk_alignment_set_padding(GTK_ALIGNMENT(alignment2), 5, 0, 0, 0); gtk_container_add(GTK_CONTAINER(alignment2), label2); tmp = aman->getCustomResponse(); label3 = gtk_label_new(tmp); g_free(tmp); gtk_label_set_selectable(GTK_LABEL(label3), TRUE); gtk_label_set_line_wrap(GTK_LABEL(label3), TRUE); gtk_misc_set_alignment(GTK_MISC(label3), 0.0f, 0.0f); gtk_box_pack_start(GTK_BOX(vbox1), alignment2, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox1), label3, FALSE, TRUE, 0); delete aman; } // pack the vbox and the icon into a hbox hbox1 = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(hbox1), alignment1, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox1), vbox1, TRUE, TRUE, 0); // now add the headline vbox2 = gtk_vbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(vbox2), label1, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox2), hbox1, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(self->wnd), vbox2); } // show the window gtk_widget_show_all(self->wnd); self->timeoutSource = 0; return FALSE; } gboolean userTooltips::cb_tooltipPaint(userTooltips* self) { GtkRequisition req; gtk_widget_size_request(self->wnd, &req); gtk_paint_flat_box(self->wnd->style, self->wnd->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, self->wnd, "tooltip", 0, 0, req.width, req.height); return FALSE; } void userTooltips::cb_tooltipRealize(userTooltips* self) { gint posX, posY, maxX, maxY; GdkScreen *scr; GtkRequisition req; // positioning cannot be done before the content is realized, because // the size of all children is not known. Do this after the realization of // the window. scr = gdk_screen_get_default(); gdk_display_get_pointer(gdk_display_get_default(), &scr, &posX, &posY, NULL); // move the window to the right position maxX = gdk_screen_get_width(scr); maxY = gdk_screen_get_height(scr); gtk_widget_size_request(self->wnd, &req); if (posX + req.width > maxX) posX-= req.width+2; if (posY + req.height > maxY) posY-= req.height+2; gtk_window_move(GTK_WINDOW(self->wnd),posX+1,posY+1); }