grim/guifications2
Clone
Summary
Browse
Changes
Graph
the start of breaking out the win32 and x11 stuff into separate helper libraries...
2011-12-04, Gary Kramlich
9435c774f922
the start of breaking out the win32 and x11 stuff into separate helper libraries...
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2008 Gary Kramlich
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
*/
#ifdef _WIN32
# ifndef WINVER
# define WINVER 0x0500
/* This is Windows 2000 */
# endif
/* WINVER */
#
include
<windows.h>
typedef
HMONITOR
WINAPI
GF_MonitorFromRect
(
LPCRECT
,
DWORD
);
typedef
BOOL
WINAPI
GF_GetMonitorInfo
(
HMONITOR
,
LPMONITORINFO
);
#endif
/* _WIN32 */
#ifdef HAVE_CONFIG_H
#
include
"../gf_config.h"
#endif
#include
"gf_internal.h"
#include
<glib.h>
#include
<gtk/gtk.h>
#include
<gdk/gdk.h>
#include
<math.h>
#include
<string.h>
#include
<debug.h>
#include
<prefs.h>
#include
<version.h>
#include
"gf_action.h"
#include
"gf_event_info.h"
#include
"gf_notification.h"
#include
"gf_preferences.h"
typedef
enum
{
GF_DISPLAY_STATE_UNKNOWN
=
0
,
GF_DISPLAY_STATE_SHOWING
,
GF_DISPLAY_STATE_SHOWN
,
GF_DISPLAY_STATE_HIDING
,
GF_DISPLAY_STATE_DESTROYED
}
GfDisplayState
;
struct
_GfDisplay
{
/* Widgets */
GtkWidget
*
window
;
GtkWidget
*
event
;
GtkWidget
*
image
;
/* Display stuff */
GfDisplayState
state
;
GdkPixbuf
*
pixbuf
;
GdkRectangle
partial
;
gboolean
has_alpha
;
gint
height
;
gint
width
;
gint
x
;
gint
y
;
/* Timer intervals */
gint
anim_time
;
gint
disp_time
;
/* Animation stuff */
gint
round
;
gint
rounds
;
/* Action stuff */
GfEventInfo
*
info
;
guint
button
;
};
#include
"gf_display.h"
/*******************************************************************************
* consts/macros
******************************************************************************/
#define GF_DISPLAY_ROUND(x) ((x)+0.5)
static
const
double
DELTA_SIZE
=
1.333
;
static
const
gint
DELTA_TIME
=
33.33
;
/*******************************************************************************
* Globals
******************************************************************************/
static
GList
*
displays
=
NULL
;
static
gboolean
vertical
,
animate
;
static
GfDisplayPosition
position
;
#if GTK_CHECK_VERSION(2,2,0)
static
gint
disp_screen
=
0
,
disp_monitor
=
0
;
#endif
/* GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* Prototypes...
*
* Yeah, I know they're bad, but sometimes you just can't avoid them...
*
* Well, I could, but it'd add more complication...
******************************************************************************/
static
gboolean
gf_display_shown_cb
(
gpointer
data
);
static
gboolean
gf_display_animate_cb
(
gpointer
data
);
/*******************************************************************************
* Callbacks
******************************************************************************/
static
gboolean
gf_display_button_press_cb
(
GtkWidget
*
w
,
GdkEventButton
*
e
,
gpointer
data
)
{
GfDisplay
*
display
=
GF_DISPLAY
(
data
);
gint
x
=
0
,
y
=
0
;
if
(
e
->
type
==
GDK_BUTTON_PRESS
)
{
display
->
button
=
e
->
button
;
return
TRUE
;
}
else
if
(
e
->
type
==
GDK_BUTTON_RELEASE
)
{
GfAction
*
action
=
NULL
;
const
gchar
*
pref
=
NULL
;
gdk_window_get_pointer
(
w
->
window
,
&
x
,
&
y
,
NULL
);
if
(
x
<
0
||
x
>
display
->
width
||
y
<
0
||
y
>
display
->
height
)
return
FALSE
;
switch
(
display
->
button
)
{
case
1
:
pref
=
GF_PREF_MOUSE_LEFT
;
break
;
case
2
:
pref
=
GF_PREF_MOUSE_MIDDLE
;
break
;
case
3
:
pref
=
GF_PREF_MOUSE_RIGHT
;
break
;
default
:
pref
=
NULL
;
break
;
}
if
(
!
pref
)
return
FALSE
;
action
=
gf_action_find_with_name
(
purple_prefs_get_string
(
pref
));
if
(
!
action
)
return
FALSE
;
gf_action_execute
(
action
,
display
,
e
);
return
TRUE
;
}
return
FALSE
;
}
/*******************************************************************************
* Gtk 2.0.0 stuff
*
* This is the default behavior of 2.0 and 1.x
******************************************************************************/
#if !GTK_CHECK_VERSION(2,2,0)
static
void
gf_display_get_geometry
(
gint
*
x
,
gint
*
y
,
gint
*
width
,
gint
*
height
)
{
*
x
=
*
y
=
0
;
*
width
=
gdk_screen_width
();
*
height
=
gdk_screen_height
();
}
static
void
gf_display_shape
(
GfDisplay
*
display
)
{
if
(
display
->
has_alpha
)
{
GdkBitmap
*
bitmap
=
NULL
;
GdkPixbuf
*
pixbuf
;
if
(
display
->
state
==
GF_DISPLAY_STATE_SHOWING
||
display
->
state
==
GF_DISPLAY_STATE_HIDING
)
{
pixbuf
=
gtk_image_get_pixbuf
(
GTK_IMAGE
(
display
->
image
));
if
(
!
pixbuf
)
{
/* image has no pixbuf.. so we have no business continuing */
return
;
}
}
else
{
pixbuf
=
display
->
pixbuf
;
}
gdk_pixbuf_render_pixmap_and_mask
(
pixbuf
,
NULL
,
&
bitmap
,
255
);
if
(
bitmap
)
{
gtk_widget_shape_combine_mask
(
display
->
window
,
bitmap
,
0
,
0
);
g_object_unref
(
G_OBJECT
(
bitmap
));
}
}
}
static
void
gf_display_move
(
GfDisplay
*
display
)
{
gtk_window_move
(
GTK_WINDOW
(
display
->
window
),
display
->
x
,
display
->
y
);
}
#else
/* !GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* Gtk 2.2.0 and up stuff
*
* Allows users to specify which screen to display the notifications on and
* in the case of xinerama, to position it correctly
******************************************************************************/
static
void
gf_display_get_geometry
(
gint
*
x
,
gint
*
y
,
gint
*
width
,
gint
*
height
)
{
GdkDisplay
*
display
;
GdkScreen
*
screen
;
GdkRectangle
geo
,
m_geo
,
w_geo
;
display
=
gdk_display_get_default
();
screen
=
gdk_display_get_screen
(
display
,
disp_screen
);
gdk_screen_get_monitor_geometry
(
screen
,
disp_monitor
,
&
m_geo
);
if
(
gf_display_get_workarea
(
&
w_geo
))
{
gdk_rectangle_intersect
(
&
w_geo
,
&
m_geo
,
&
geo
);
}
else
{
geo
.
x
=
m_geo
.
x
;
geo
.
y
=
m_geo
.
y
;
geo
.
width
=
m_geo
.
width
;
geo
.
height
=
m_geo
.
height
;
}
*
x
=
geo
.
x
;
*
y
=
geo
.
y
;
*
width
=
geo
.
width
;
*
height
=
geo
.
height
;
}
static
void
gf_display_shape
(
GfDisplay
*
display
)
{
if
(
display
->
has_alpha
)
{
GdkBitmap
*
bmap
;
GdkColormap
*
cmap
;
GdkPixbuf
*
pixbuf
;
GdkScreen
*
screen
;
screen
=
gdk_display_get_screen
(
gdk_display_get_default
(),
disp_screen
);
cmap
=
gdk_screen_get_system_colormap
(
screen
);
if
(
display
->
state
==
GF_DISPLAY_STATE_SHOWING
||
display
->
state
==
GF_DISPLAY_STATE_HIDING
)
{
pixbuf
=
gtk_image_get_pixbuf
(
GTK_IMAGE
(
display
->
image
));
if
(
!
pixbuf
)
{
/* image has no pixbuf.. so we have no business continuing */
return
;
}
}
else
{
pixbuf
=
display
->
pixbuf
;
}
gdk_pixbuf_render_pixmap_and_mask_for_colormap
(
pixbuf
,
cmap
,
NULL
,
&
bmap
,
255
);
if
(
bmap
)
{
gtk_widget_shape_combine_mask
(
display
->
window
,
bmap
,
0
,
0
);
g_object_unref
(
G_OBJECT
(
bmap
));
}
}
}
static
void
gf_display_move
(
GfDisplay
*
display
)
{
GdkScreen
*
screen_t
,
*
screen_s
;
screen_t
=
gdk_display_get_screen
(
gdk_display_get_default
(),
disp_screen
);
screen_s
=
gtk_window_get_screen
(
GTK_WINDOW
(
display
->
window
));
if
(
gdk_screen_get_number
(
screen_s
)
!=
gdk_screen_get_number
(
screen_t
))
{
if
(
display
->
has_alpha
)
gtk_widget_shape_combine_mask
(
display
->
window
,
NULL
,
0
,
0
);
gtk_window_set_screen
(
GTK_WINDOW
(
display
->
window
),
screen_t
);
if
(
display
->
has_alpha
)
gf_display_shape
(
display
);
}
gtk_window_move
(
GTK_WINDOW
(
display
->
window
),
display
->
x
,
display
->
y
);
}
gint
gf_display_get_default_screen
()
{
GdkScreen
*
screen
;
screen
=
gdk_screen_get_default
();
return
gdk_screen_get_number
(
screen
);
}
gint
gf_display_get_screen_count
()
{
return
gdk_display_get_n_screens
(
gdk_display_get_default
())
-
1
;
}
gint
gf_display_get_default_monitor
()
{
return
0
;
}
gint
gf_display_get_monitor_count
()
{
GdkDisplay
*
display
;
GdkScreen
*
screen
=
NULL
;
gint
screens
=
0
,
monitors
=
0
,
i
=
0
;
display
=
gdk_display_get_default
();
screens
=
gdk_display_get_n_screens
(
display
);
for
(
i
=
0
;
i
<
screens
;
i
++
)
{
screen
=
gdk_display_get_screen
(
display
,
i
);
monitors
=
MAX
(
monitors
,
gdk_screen_get_n_monitors
(
screen
));
}
return
monitors
-
1
;
}
#endif
/* GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* The normal code (tm)
******************************************************************************/
static
void
gf_display_position
(
GfDisplay
*
new_display
)
{
GfDisplay
*
display
;
GList
*
l
=
NULL
;
gint
pad_x
=
0
,
pad_y
=
0
;
gint
disp_width
=
0
,
disp_height
=
0
;
gint
width
=
0
,
height
=
0
;
gint
total
=
0
;
g_return_if_fail
(
new_display
);
gf_display_get_geometry
(
&
pad_x
,
&
pad_y
,
&
width
,
&
height
);
for
(
l
=
displays
;
l
;
l
=
l
->
next
)
{
display
=
GF_DISPLAY
(
l
->
data
);
if
(
display
==
new_display
)
break
;
if
(
vertical
)
total
+=
display
->
height
;
else
total
+=
display
->
width
;
}
if
(
new_display
->
state
==
GF_DISPLAY_STATE_SHOWING
||
new_display
->
state
==
GF_DISPLAY_STATE_HIDING
)
{
disp_width
=
new_display
->
partial
.
width
;
disp_height
=
new_display
->
partial
.
height
;
}
else
{
disp_width
=
new_display
->
width
;
disp_height
=
new_display
->
height
;
}
/* set the correct size for the window */
gtk_widget_set_size_request
(
new_display
->
window
,
disp_width
,
disp_height
);
switch
(
position
)
{
case
GF_DISPLAY_POSITION_NW
:
if
(
vertical
)
{
new_display
->
x
=
pad_x
;
new_display
->
y
=
pad_y
+
total
;
}
else
{
new_display
->
x
=
pad_x
+
total
;
new_display
->
y
=
pad_y
;
}
break
;
case
GF_DISPLAY_POSITION_NE
:
if
(
vertical
)
{
new_display
->
x
=
pad_x
+
width
-
disp_width
;
new_display
->
y
=
pad_y
+
total
;
}
else
{
new_display
->
x
=
pad_x
+
width
-
(
total
+
disp_width
);
new_display
->
y
=
pad_y
;
}
break
;
case
GF_DISPLAY_POSITION_SW
:
if
(
vertical
)
{
new_display
->
x
=
pad_x
;
new_display
->
y
=
pad_y
+
height
-
(
total
+
disp_height
);
}
else
{
new_display
->
x
=
pad_x
+
total
;
new_display
->
y
=
pad_y
+
height
-
(
disp_height
);
}
break
;
case
GF_DISPLAY_POSITION_SE
:
if
(
vertical
)
{
new_display
->
x
=
pad_x
+
width
-
disp_width
;
new_display
->
y
=
pad_y
+
height
-
(
total
+
disp_height
);
}
else
{
new_display
->
x
=
pad_x
+
width
-
(
total
+
disp_width
);
new_display
->
y
=
pad_y
+
height
-
disp_height
;
}
break
;
default
:
break
;
}
gf_display_move
(
new_display
);
}
static
void
gf_displays_position
()
{
GfDisplay
*
display
;
GList
*
l
;
for
(
l
=
displays
;
l
;
l
=
l
->
next
)
{
display
=
GF_DISPLAY
(
l
->
data
);
gf_display_position
(
display
);
}
}
GfDisplay
*
gf_display_new
()
{
GfDisplay
*
display
;
display
=
g_new0
(
GfDisplay
,
1
);
return
display
;
}
void
gf_display_destroy
(
GfDisplay
*
display
)
{
g_return_if_fail
(
display
);
displays
=
g_list_remove
(
displays
,
display
);
if
(
display
->
window
)
{
gtk_widget_destroy
(
display
->
window
);
display
->
window
=
NULL
;
}
if
(
display
->
pixbuf
)
{
g_object_unref
(
G_OBJECT
(
display
->
pixbuf
));
display
->
pixbuf
=
NULL
;
}
if
(
display
->
info
)
{
gf_event_info_destroy
(
display
->
info
);
display
->
info
=
NULL
;
}
g_free
(
display
);
display
=
NULL
;
gf_displays_position
();
}
static
gboolean
gf_display_shown_cb
(
gpointer
data
)
{
GfDisplay
*
display
=
GF_DISPLAY
(
data
);
guint
timeout_id
;
/* in case something wicked happened */
g_return_val_if_fail
(
display
,
FALSE
);
display
->
state
=
GF_DISPLAY_STATE_HIDING
;
timeout_id
=
g_timeout_add
(
DELTA_TIME
,
gf_display_animate_cb
,
data
);
gf_event_info_set_timeout_id
(
display
->
info
,
timeout_id
);
return
FALSE
;
}
static
inline
gint
gf_display_calculate_change
(
GfDisplay
*
display
,
gint
maximum
)
{
/* This function calculates an an exponential change.
*
* It's a basic scalable exponential movement formula, given to us by
* Nathaniel Waters.
*/
gint
ret
;
/* this could probably use some type casting somewhere.. */
ret
=
GF_DISPLAY_ROUND
(((
gdouble
)
maximum
/
pow
(
DELTA_SIZE
,
display
->
rounds
))
*
pow
(
DELTA_SIZE
,
display
->
round
));
return
ret
;
}
static
gboolean
gf_display_animate
(
GfDisplay
*
display
)
{
GdkPixbuf
*
pixbuf
;
GdkRectangle
full
;
gint
total
,
current
;
gboolean
ret
=
TRUE
;
/* figure out what we're modifing */
if
(
vertical
)
total
=
display
->
height
;
else
total
=
display
->
width
;
/* calculate the change */
current
=
gf_display_calculate_change
(
display
,
total
);
/* create our rects */
full
.
x
=
0
;
full
.
y
=
0
;
full
.
width
=
display
->
width
;
full
.
height
=
display
->
height
;
/* ugh too many possibilities */
switch
(
position
)
{
case
GF_DISPLAY_POSITION_NW
:
if
(
vertical
)
{
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
height
-
current
;
display
->
partial
.
width
=
full
.
width
;
display
->
partial
.
height
=
current
;
}
else
{
display
->
partial
.
x
=
full
.
width
-
current
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
current
;
display
->
partial
.
height
=
full
.
height
;
}
break
;
case
GF_DISPLAY_POSITION_NE
:
if
(
vertical
)
{
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
full
.
width
;
display
->
partial
.
height
=
current
;
}
else
{
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
current
;
display
->
partial
.
height
=
full
.
height
;
}
break
;
case
GF_DISPLAY_POSITION_SW
:
if
(
vertical
)
{
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
full
.
width
;
display
->
partial
.
height
=
current
;
}
else
{
display
->
partial
.
x
=
full
.
width
-
current
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
current
;
display
->
partial
.
height
=
full
.
height
;
}
break
;
case
GF_DISPLAY_POSITION_SE
:
if
(
vertical
)
{
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
full
.
width
;
display
->
partial
.
height
=
current
;
}
else
{
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
current
;
display
->
partial
.
height
=
full
.
height
;
}
break
;
default
:
display
->
partial
.
x
=
full
.
x
;
display
->
partial
.
y
=
full
.
y
;
display
->
partial
.
width
=
full
.
width
;
display
->
partial
.
height
=
full
.
height
;
break
;
}
/* Add some sanity checks */
if
(
display
->
partial
.
width
<=
0
)
display
->
partial
.
width
=
1
;
if
(
display
->
partial
.
height
<=
0
)
display
->
partial
.
height
=
1
;
/* create our partial pixbuf and fill it */
pixbuf
=
gdk_pixbuf_new
(
GDK_COLORSPACE_RGB
,
display
->
has_alpha
,
8
,
display
->
partial
.
width
,
display
->
partial
.
height
);
if
(
!
pixbuf
)
{
purple_debug_info
(
"guifications"
,
"failed to create partial pixbuf, "
"destroying display %p
\n
"
,
display
);
gf_display_destroy
(
display
);
return
FALSE
;
}
gdk_pixbuf_copy_area
(
display
->
pixbuf
,
display
->
partial
.
x
,
display
->
partial
.
y
,
display
->
partial
.
width
,
display
->
partial
.
height
,
pixbuf
,
0
,
0
);
/* force the image and event box to be the same size as the partial
* pixbuf
*/
gtk_widget_set_size_request
(
display
->
image
,
display
->
partial
.
width
,
display
->
partial
.
height
);
/* update the image's pixbuf */
gtk_image_set_from_pixbuf
(
GTK_IMAGE
(
display
->
image
),
pixbuf
);
/* unref the partial pixbuf */
g_object_unref
(
G_OBJECT
(
pixbuf
));
/* update the display and force gdk to redraw it */
gf_display_shape
(
display
);
gf_display_position
(
display
);
gdk_window_process_updates
(
GDK_WINDOW
(
display
->
window
->
window
),
TRUE
);
/* Finish up */
if
(
display
->
state
==
GF_DISPLAY_STATE_SHOWING
)
{
display
->
round
++
;
if
(
display
->
round
>
display
->
rounds
)
{
guint
timeout_id
;
/* set rounds back to the max */
display
->
round
=
display
->
rounds
-
1
;
display
->
state
=
GF_DISPLAY_STATE_SHOWN
;
timeout_id
=
g_timeout_add
(
display
->
disp_time
,
gf_display_shown_cb
,
(
gpointer
)
display
);
gf_event_info_set_timeout_id
(
display
->
info
,
timeout_id
);
ret
=
FALSE
;
}
}
else
{
/* if(display->state == GF_DISPLAY_STATE_HIDING) { */
display
->
round
--
;
if
(
display
->
round
<=
0
)
{
gf_display_destroy
(
display
);
ret
=
FALSE
;
}
}
return
ret
;
}
static
gboolean
gf_display_animate_cb
(
gpointer
data
)
{
GfDisplay
*
display
=
GF_DISPLAY
(
data
);
gboolean
ret
;
/* in the event something wicked happened... */
g_return_val_if_fail
(
display
,
FALSE
);
/* we call a function to do our animation here to modularize the process
* so we can do other types of animation, like fading depending on other
* conditions. These conditions should be checked here, and then called
* accordingly.
*
* The animation function should return a boolean so we can tell the
* timer handler whether or not to keep the timer around.
*/
ret
=
gf_display_animate
(
display
);
return
ret
;
}
static
gboolean
gf_display_destroy_cb
(
gpointer
data
)
{
GfDisplay
*
display
=
GF_DISPLAY
(
data
);
gf_display_destroy
(
display
);
return
FALSE
;
}
static
inline
gboolean
gf_display_condense_check_buddy
(
GfEventInfo
*
info1
,
GfEventInfo
*
info2
)
{
PurpleAccount
*
account1
=
NULL
,
*
account2
=
NULL
;
PurpleBuddy
*
buddy1
=
NULL
,
*
buddy2
=
NULL
;
const
gchar
*
name1
=
NULL
,
*
name2
=
NULL
;
buddy1
=
gf_event_info_get_buddy
(
info1
);
buddy2
=
gf_event_info_get_buddy
(
info2
);
/* if we don't have any buddies, we can't do anything, so bail */
if
(
!
buddy1
||
!
buddy2
)
return
FALSE
;
name1
=
purple_buddy_get_name
(
buddy1
);
name2
=
purple_buddy_get_name
(
buddy2
);
account1
=
purple_buddy_get_account
(
buddy1
);
account2
=
purple_buddy_get_account
(
buddy2
);
return
(
!
g_utf8_collate
(
name1
,
name2
)
&&
account1
==
account2
);
}
static
inline
gboolean
gf_display_condense_check_contact
(
GfEventInfo
*
info1
,
GfEventInfo
*
info2
)
{
PurpleBuddy
*
buddy1
=
NULL
,
*
buddy2
=
NULL
;
PurpleContact
*
contact1
=
NULL
,
*
contact2
=
NULL
;
buddy1
=
gf_event_info_get_buddy
(
info1
);
buddy2
=
gf_event_info_get_buddy
(
info2
);
/* if we don't have any buddies, we can't do anything, so bail */
if
(
!
buddy1
||
!
buddy2
)
return
FALSE
;
contact1
=
purple_buddy_get_contact
(
buddy1
);
contact2
=
purple_buddy_get_contact
(
buddy2
);
return
(
contact1
==
contact2
);
}
static
inline
gboolean
gf_display_condense_check_target
(
GfEventInfo
*
info1
,
GfEventInfo
*
info2
)
{
PurpleConversation
*
conv1
=
NULL
,
*
conv2
=
NULL
;
const
gchar
*
target1
=
NULL
,
*
target2
=
NULL
;
conv1
=
gf_event_info_get_conversation
(
info1
);
conv2
=
gf_event_info_get_conversation
(
info2
);
/* if we don't have any conversations, we can't do anythin, so bail */
if
(
!
conv1
||
!
conv2
)
return
FALSE
;
if
(
conv1
!=
conv2
)
return
FALSE
;
target1
=
gf_event_info_get_target
(
info1
);
target2
=
gf_event_info_get_target
(
info2
);
return
(
!
g_utf8_collate
(
target1
,
target2
));
}
#define GF_DISPLAY_CHECK_PRIORITY G_STMT_START { \
if(priority1 >= priority2) { \
gf_display_destroy(display); \
continue; \
} else { \
ret = TRUE; \
} \
} G_STMT_END
/* Yes this big ugly function... isn't so big and ugly anymore...
* It checks for existing notification from the same buddy, or conv with
* with the same target, and condenses contacts by the use of helper funcs.
*
* It returns TRUE if the new notification/event should be destroyed because
* theres a matching notification that has a higher priority than the new
* notification/event
*/
static
gboolean
gf_display_condense
(
GfEventInfo
*
info
)
{
GfDisplay
*
display
=
NULL
;
GfEvent
*
event1
=
NULL
,
*
event2
=
NULL
;
GfEventPriority
priority1
,
priority2
;
GList
*
l
=
NULL
,
*
ll
=
NULL
;
gboolean
ret
=
FALSE
;
event1
=
gf_event_info_get_event
(
info
);
priority1
=
gf_event_get_priority
(
event1
);
for
(
l
=
displays
;
l
;
l
=
ll
)
{
display
=
GF_DISPLAY
(
l
->
data
);
ll
=
l
->
next
;
if
(
!
display
)
continue
;
if
(
!
display
->
info
)
continue
;
event2
=
gf_event_info_get_event
(
display
->
info
);
priority2
=
gf_event_get_priority
(
event2
);
if
(
gf_display_condense_check_buddy
(
info
,
display
->
info
))
GF_DISPLAY_CHECK_PRIORITY
;
if
(
gf_display_condense_check_contact
(
info
,
display
->
info
))
GF_DISPLAY_CHECK_PRIORITY
;
if
(
gf_display_condense_check_target
(
info
,
display
->
info
))
GF_DISPLAY_CHECK_PRIORITY
;
}
/* we only check the stack count if ret is true, because that means one
* has been removed, so we have space.
*/
if
(
ret
==
FALSE
)
{
gint
throttle
=
purple_prefs_get_int
(
GF_PREF_BEHAVIOR_THROTTLE
);
if
(
throttle
>
0
&&
g_list_length
(
displays
)
+
1
>
throttle
)
{
display
=
GF_DISPLAY
(
g_list_nth_data
(
displays
,
0
));
if
(
display
)
gf_display_destroy
(
display
);
gf_displays_position
();
}
}
return
ret
;
}
void
gf_display_show_event
(
GfEventInfo
*
info
,
GfNotification
*
notification
)
{
GfDisplay
*
display
=
NULL
;
gint
display_time
;
guint
timeout_id
=
0
;
g_return_if_fail
(
info
);
/* Here we kill the notification if the screen saver is running */
if
(
gf_display_screen_saver_is_running
())
{
gf_event_info_destroy
(
info
);
return
;
}
/* we should never make it here with out a notification, but just in
* case we do....
*/
if
(
!
notification
)
{
const
gchar
*
event_name
=
gf_event_get_name
(
gf_event_info_get_event
(
info
));
purple_debug_info
(
"Guifications"
,
"could not find a notification for the event
\"
%s
\"\n
"
,
event_name
?
event_name
:
""
);
return
;
}
/* condense contacts and same buddy notifications, a return value of TRUE
* means theres a higher priority notification already being displayed
*/
if
(
gf_display_condense
(
info
))
{
gf_event_info_destroy
(
info
);
return
;
}
/* create the display */
display
=
gf_display_new
();
display
->
info
=
info
;
/* Render the pixbuf, if we get NULL destroy the display and return */
display
->
pixbuf
=
gf_notification_render
(
notification
,
display
->
info
);
if
(
!
display
->
pixbuf
)
{
GfTheme
*
theme
=
gf_notification_get_theme
(
notification
);
GfThemeInfo
*
info
=
gf_theme_get_theme_info
(
theme
);
purple_debug_info
(
"Guifications"
,
"render '%s' failed for theme '%s'
\n
"
,
gf_notification_get_type
(
notification
),
gf_theme_info_get_name
(
info
));
gf_display_destroy
(
display
);
return
;
}
/* grab info about the pixbuf */
display
->
has_alpha
=
gdk_pixbuf_get_has_alpha
(
display
->
pixbuf
);
display
->
height
=
gdk_pixbuf_get_height
(
display
->
pixbuf
);
display
->
width
=
gdk_pixbuf_get_width
(
display
->
pixbuf
);
/* if we've made it this far, we can create the window safely */
display
->
window
=
gtk_window_new
(
GTK_WINDOW_POPUP
);
gtk_window_set_role
(
GTK_WINDOW
(
display
->
window
),
"guification"
);
/* Create the event box, you know, so the mouse works! */
display
->
event
=
gtk_event_box_new
();
if
(
!
gtk_check_version
(
2
,
4
,
0
))
g_object_set
(
G_OBJECT
(
display
->
event
),
"visible-window"
,
FALSE
,
NULL
);
gtk_container_add
(
GTK_CONTAINER
(
display
->
window
),
display
->
event
);
/* now we create all our mouse signals that should play nice with
* other apps..
*/
g_signal_connect
(
G_OBJECT
(
display
->
window
),
"button-press-event"
,
G_CALLBACK
(
gf_display_button_press_cb
),
(
gpointer
)
display
);
g_signal_connect
(
G_OBJECT
(
display
->
window
),
"button-release-event"
,
G_CALLBACK
(
gf_display_button_press_cb
),
(
gpointer
)
display
);
/* create the image but don't put the pixbuf in it quite yet. */
display
->
image
=
gtk_image_new
();
gtk_container_add
(
GTK_CONTAINER
(
display
->
event
),
display
->
image
);
/* grab the display time */
display_time
=
1000
*
purple_prefs_get_int
(
GF_PREF_BEHAVIOR_DISPLAY_TIME
);
/* animation is set, this is a new notification, so we animate it */
if
(
animate
)
{
/* we explicitly set the size request because the window is created
* with an image with no child, so it defaults to some insane size.
*/
gtk_widget_set_size_request
(
display
->
window
,
display
->
width
,
display
->
height
);
/* calculate our timer intervals. anim_time is used for showing and
* hiding, so that's 2/8ths which is 1/4th.
*
* Remember display time is stored in seconds!
*/
display
->
anim_time
=
display_time
/
8
;
display
->
disp_time
=
display_time
*
3
/
4
;
/* Calculate and store some more information about animating */
display
->
rounds
=
GF_DISPLAY_ROUND
((
gfloat
)
display
->
anim_time
/
(
gfloat
)
DELTA_TIME
);
display
->
round
=
0
;
/* Set the state of the display */
display
->
state
=
GF_DISPLAY_STATE_SHOWING
;
/* setup a timeout of DELTA_TIME ms for animating */
timeout_id
=
g_timeout_add
(
DELTA_TIME
,
gf_display_animate_cb
,
(
gpointer
)
display
);
}
else
{
/* no animation! */
/* set the image since we're not animating */
gtk_image_set_from_pixbuf
(
GTK_IMAGE
(
display
->
image
),
display
->
pixbuf
);
/* shape it */
gf_display_shape
(
display
);
/* We're showing! */
display
->
state
=
GF_DISPLAY_STATE_SHOWN
;
/* build our timeout */
timeout_id
=
g_timeout_add
(
display_time
,
gf_display_destroy_cb
,
(
gpointer
)
display
);
}
gf_event_info_set_timeout_id
(
info
,
timeout_id
);
/* position and show the widget */
gf_display_position
(
display
);
gtk_widget_show_all
(
display
->
window
);
/* add the display to the list */
displays
=
g_list_append
(
displays
,
display
);
}
GfEventInfo
*
gf_display_get_event_info
(
GfDisplay
*
display
)
{
g_return_val_if_fail
(
display
,
NULL
);
return
display
->
info
;
}
static
void
gf_display_position_changed_cb
(
const
gchar
*
name
,
PurplePrefType
type
,
gconstpointer
val
,
gpointer
data
)
{
vertical
=
purple_prefs_get_bool
(
GF_PREF_APPEARANCE_VERTICAL
);
position
=
purple_prefs_get_int
(
GF_PREF_APPEARANCE_POSITION
);
gf_displays_position
();
}
static
void
gf_display_animate_changed_cb
(
const
gchar
*
name
,
PurplePrefType
type
,
gconstpointer
val
,
gpointer
data
)
{
animate
=
GPOINTER_TO_INT
(
val
);
}
/*******************************************************************************
* Pref callbacks for the display/screen/monitor stuff
******************************************************************************/
#if GTK_CHECK_VERSION(2,2,0)
static
void
gf_display_screen_changed_cb
(
const
gchar
*
name
,
PurplePrefType
type
,
gconstpointer
val
,
gpointer
data
)
{
disp_screen
=
GPOINTER_TO_INT
(
val
);
gf_item_text_uninit
();
gf_displays_position
();
gf_item_text_init
();
}
static
void
gf_display_monitor_changed_cb
(
const
gchar
*
name
,
PurplePrefType
type
,
gconstpointer
val
,
gpointer
data
)
{
disp_monitor
=
GPOINTER_TO_INT
(
val
);
gf_displays_position
();
}
static
guint
scr_chg_id
=
0
,
mon_chg_id
=
0
;
#endif
/* GTK_CHECK_VERSION(2,2,0) */
/*******************************************************************************
* Regular pref callbacks
******************************************************************************/
static
guint
pos_chg_id
=
0
,
ver_chg_id
=
0
,
ani_chg_id
=
0
;
void
gf_display_init
(
PurplePlugin
*
plugin
)
{
/* since we just use the callbacks to get the changes we need to know
* what these are initially at.
*/
position
=
purple_prefs_get_int
(
GF_PREF_APPEARANCE_POSITION
);
vertical
=
purple_prefs_get_bool
(
GF_PREF_APPEARANCE_VERTICAL
);
animate
=
purple_prefs_get_bool
(
GF_PREF_APPEARANCE_ANIMATE
);
/* Connect our pref callbacks */
pos_chg_id
=
purple_prefs_connect_callback
(
plugin
,
GF_PREF_APPEARANCE_POSITION
,
gf_display_position_changed_cb
,
NULL
);
ver_chg_id
=
purple_prefs_connect_callback
(
plugin
,
GF_PREF_APPEARANCE_VERTICAL
,
gf_display_position_changed_cb
,
NULL
);
ani_chg_id
=
purple_prefs_connect_callback
(
plugin
,
GF_PREF_APPEARANCE_ANIMATE
,
gf_display_animate_changed_cb
,
NULL
);
#if GTK_CHECK_VERSION(2,2,0)
/* setup our multi screen prefs */
disp_screen
=
purple_prefs_get_int
(
GF_PREF_ADVANCED_SCREEN
);
disp_monitor
=
purple_prefs_get_int
(
GF_PREF_ADVANCED_MONITOR
);
scr_chg_id
=
purple_prefs_connect_callback
(
plugin
,
GF_PREF_ADVANCED_SCREEN
,
gf_display_screen_changed_cb
,
NULL
);
mon_chg_id
=
purple_prefs_connect_callback
(
plugin
,
GF_PREF_ADVANCED_MONITOR
,
gf_display_monitor_changed_cb
,
NULL
);
#endif
/* GTK_CHECK_VERSION(2,2,0) */
}
void
gf_display_uninit
()
{
purple_prefs_disconnect_callback
(
pos_chg_id
);
purple_prefs_disconnect_callback
(
ver_chg_id
);
#if GTK_CHECK_VERSION(2,2,0)
purple_prefs_disconnect_callback
(
scr_chg_id
);
purple_prefs_disconnect_callback
(
mon_chg_id
);
#endif
/* GTK_CHECK_VERSION(2,2,0) */
}