grim/guifications2
Clone
Summary
Browse
Changes
Graph
Looks like this changed with the autogen.sh update and never got checked in
org.guifications.gf2
2007-12-16, Gary Kramlich
71ed9a74e045
Looks like this changed with the autogen.sh update and never got checked in
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2005 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 HAVE_CONFIG_H
#
include
"../gf_config.h"
# if HAVE_UNISTD_H
#
include
<unistd.h>
# endif
# if HAVE_PANGOFT2
#
include
<ft2build.h>
# include FT_FREETYPE_H
#
include
<pango/pangoft2.h>
# endif
#endif
#include
<stdio.h>
#include
<stdlib.h>
#include
<glib.h>
#include
<gtk/gtk.h>
#include
<gdk/gdk.h>
#include
<pango/pango.h>
#include
<string.h>
#include
<time.h>
#include
"gf_internal.h"
#include
<account.h>
#include
<conversation.h>
#include
<debug.h>
#include
<network.h>
#include
<prpl.h>
#include
<status.h>
#include
<util.h>
#include
<xmlnode.h>
#include
<version.h>
#ifndef _WIN32
#
include
<gdk/gdkx.h>
#endif
#include
"gf_event.h"
#include
"gf_event_info.h"
#include
"gf_gtk_utils.h"
#include
"gf_item.h"
#include
"gf_item_text.h"
#include
"gf_notification.h"
#include
"gf_preferences.h"
#include
"gf_theme.h"
#include
"gf_theme_ops.h"
struct
_GfItemText
{
GfItem
*
item
;
gchar
*
format
;
gchar
*
font
;
gchar
*
color
;
GfItemTextClipping
clipping
;
gint
width
;
};
#define IS_EVEN(number) ((number & 1) == 1 ? FALSE: TRUE)
/*******************************************************************************
* Subsystem
*
* If I read the mail thread correctly, a ft2 font map caches the glyphs.
* I was disregarding the font map and everything because I only needed it to
* create the context in order to create the layout, this was causing some
* major memory issues. Therefore the gf_item_text subsystem was created. All
* this does is hold our font map and context. On win32 we do not need a font
* map so all it does is keep the context around until uninit is called. With
* the caching in ft2, we should in theory be drawing anything thats not a
* format token faster, which is a good thing. Also, we aren't constantly
* getting the context and unref'n it which will save a few cycles as well.
******************************************************************************/
static
PangoFontMap
*
map
=
NULL
;
static
PangoContext
*
context
=
NULL
;
void
gf_item_text_init
()
{
#ifndef _WIN32
gdouble
xdpi
=
75
,
ydpi
=
75
;
# if GTK_CHECK_VERSION(2,2,0)
GdkDisplay
*
display
;
GdkScreen
*
screen
;
# endif
#endif
map
=
pango_ft2_font_map_new
();
#ifndef _WIN32
# if GTK_CHECK_VERSION(2,2,0)
display
=
gdk_display_get_default
();
screen
=
gdk_display_get_screen
(
display
,
purple_prefs_get_int
(
GF_PREF_ADVANCED_SCREEN
));
xdpi
=
(
double
)((
float
)
gdk_screen_get_width
(
screen
)
/
(
float
)
gdk_screen_get_width_mm
(
screen
)
*
25.4
);
ydpi
=
(
double
)((
float
)
gdk_screen_get_height
(
screen
)
/
(
float
)
gdk_screen_get_height_mm
(
screen
)
*
25.4
);
# endif
pango_ft2_font_map_set_resolution
(
PANGO_FT2_FONT_MAP
(
map
),
xdpi
,
ydpi
);
#endif
context
=
pango_ft2_font_map_create_context
(
PANGO_FT2_FONT_MAP
(
map
));
}
void
gf_item_text_uninit
()
{
if
(
map
)
g_object_unref
(
G_OBJECT
(
map
));
if
(
context
)
g_object_unref
(
G_OBJECT
(
context
));
}
/*******************************************************************************
* API
******************************************************************************/
void
gf_item_text_destroy
(
GfItemText
*
item_text
)
{
g_return_if_fail
(
item_text
);
item_text
->
item
=
NULL
;
if
(
item_text
->
format
)
{
g_free
(
item_text
->
format
);
item_text
->
format
=
NULL
;
}
if
(
item_text
->
font
)
{
g_free
(
item_text
->
font
);
item_text
->
font
=
NULL
;
}
if
(
item_text
->
color
)
{
g_free
(
item_text
->
color
);
item_text
->
color
=
NULL
;
}
item_text
->
clipping
=
GF_ITEM_TEXT_CLIPPING_UNKNOWN
;
item_text
->
width
=
0
;
g_free
(
item_text
);
item_text
=
NULL
;
}
GfItemText
*
gf_item_text_new
(
GfItem
*
item
)
{
GfItemText
*
item_text
;
g_return_val_if_fail
(
item
,
NULL
);
item_text
=
g_new0
(
GfItemText
,
1
);
item_text
->
item
=
item
;
return
item_text
;
}
static
GfItemTextClipping
text_clipping_from_string
(
const
gchar
*
string
)
{
g_return_val_if_fail
(
string
,
GF_ITEM_TEXT_CLIPPING_UNKNOWN
);
if
(
!
g_ascii_strcasecmp
(
string
,
"truncate"
))
return
GF_ITEM_TEXT_CLIPPING_TRUNCATE
;
if
(
!
g_ascii_strcasecmp
(
string
,
"ellipsis-start"
))
return
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_START
;
if
(
!
g_ascii_strcasecmp
(
string
,
"ellipsis-middle"
))
return
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_MIDDLE
;
if
(
!
g_ascii_strcasecmp
(
string
,
"ellipsis-end"
))
return
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_END
;
else
return
GF_ITEM_TEXT_CLIPPING_UNKNOWN
;
}
static
const
gchar
*
text_clipping_to_string
(
GfItemTextClipping
clip
)
{
g_return_val_if_fail
(
clip
!=
GF_ITEM_TEXT_CLIPPING_UNKNOWN
,
NULL
);
switch
(
clip
)
{
case
GF_ITEM_TEXT_CLIPPING_TRUNCATE
:
return
"truncate"
;
break
;
case
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_START
:
return
"ellipsis-start"
;
break
;
case
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_MIDDLE
:
return
"ellipsis-middle"
;
break
;
case
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_END
:
return
"ellipsis-end"
;
break
;
case
GF_ITEM_TEXT_CLIPPING_UNKNOWN
:
default
:
return
NULL
;
break
;
}
}
GfItemText
*
gf_item_text_new_from_xmlnode
(
GfItem
*
item
,
xmlnode
*
node
)
{
GfItemText
*
item_text
;
const
gchar
*
data
=
NULL
;
g_return_val_if_fail
(
item
,
NULL
);
g_return_val_if_fail
(
node
,
NULL
);
item_text
=
gf_item_text_new
(
item
);
if
(
!
(
data
=
xmlnode_get_attrib
(
node
,
"format"
)))
{
purple_debug_info
(
"Guifications"
,
"** Error loading text item: 'No format given'
\n
"
);
gf_item_text_destroy
(
item_text
);
return
NULL
;
}
item_text
->
format
=
g_strdup
(
data
);
if
((
data
=
xmlnode_get_attrib
(
node
,
"font"
)))
item_text
->
font
=
g_strdup
(
data
);
if
((
data
=
xmlnode_get_attrib
(
node
,
"color"
)))
item_text
->
color
=
g_strdup
(
data
);
data
=
xmlnode_get_attrib
(
node
,
"clipping"
);
item_text
->
clipping
=
text_clipping_from_string
(
data
);
if
(
item_text
->
clipping
==
GF_ITEM_TEXT_CLIPPING_UNKNOWN
)
{
purple_debug_info
(
"Guifications"
,
"** Error loading text item: "
"'Unknown clipping type'
\n
"
);
gf_item_destroy
(
item
);
return
NULL
;
}
data
=
xmlnode_get_attrib
(
node
,
"width"
);
if
(
data
)
item_text
->
width
=
atoi
(
data
);
else
item_text
->
width
=
0
;
return
item_text
;
}
GfItemText
*
gf_item_text_copy
(
GfItemText
*
text
)
{
GfItemText
*
new_text
;
g_return_val_if_fail
(
text
,
NULL
);
new_text
=
gf_item_text_new
(
text
->
item
);
if
(
text
->
format
)
new_text
->
format
=
g_strdup
(
text
->
format
);
if
(
text
->
font
)
new_text
->
font
=
g_strdup
(
text
->
font
);
if
(
text
->
color
)
new_text
->
color
=
g_strdup
(
text
->
color
);
new_text
->
clipping
=
text
->
clipping
;
new_text
->
width
=
text
->
width
;
return
new_text
;
}
xmlnode
*
gf_item_text_to_xmlnode
(
GfItemText
*
text
)
{
xmlnode
*
parent
;
parent
=
xmlnode_new
(
"text"
);
if
(
text
->
format
)
xmlnode_set_attrib
(
parent
,
"format"
,
text
->
format
);
if
(
text
->
font
)
xmlnode_set_attrib
(
parent
,
"font"
,
text
->
font
);
if
(
text
->
color
)
xmlnode_set_attrib
(
parent
,
"color"
,
text
->
color
);
if
(
text
->
clipping
!=
GF_ITEM_TEXT_CLIPPING_UNKNOWN
)
xmlnode_set_attrib
(
parent
,
"clipping"
,
text_clipping_to_string
(
text
->
clipping
));
if
(
text
->
width
>=
0
)
{
gchar
*
width
=
g_strdup_printf
(
"%d"
,
text
->
width
);
xmlnode_set_attrib
(
parent
,
"width"
,
width
);
g_free
(
width
);
}
return
parent
;
}
void
gf_item_text_set_format
(
GfItemText
*
item_text
,
const
gchar
*
format
)
{
g_return_if_fail
(
item_text
);
g_return_if_fail
(
format
);
if
(
item_text
->
format
)
g_free
(
item_text
->
format
);
item_text
->
format
=
g_strdup
(
format
);
}
const
gchar
*
gf_item_text_get_format
(
GfItemText
*
item_text
)
{
g_return_val_if_fail
(
item_text
,
NULL
);
return
item_text
->
format
;
}
void
gf_item_text_set_font
(
GfItemText
*
item_text
,
const
gchar
*
font
)
{
g_return_if_fail
(
item_text
);
g_return_if_fail
(
font
);
if
(
item_text
->
font
)
g_free
(
item_text
->
font
);
item_text
->
font
=
g_strdup
(
font
);
}
const
gchar
*
gf_item_text_get_font
(
GfItemText
*
item_text
)
{
g_return_val_if_fail
(
item_text
,
NULL
);
return
item_text
->
font
;
}
void
gf_item_text_set_color
(
GfItemText
*
item_text
,
const
gchar
*
color
)
{
g_return_if_fail
(
item_text
);
g_return_if_fail
(
color
);
if
(
item_text
->
color
)
g_free
(
item_text
->
color
);
item_text
->
color
=
g_strdup
(
color
);
}
const
gchar
*
gf_item_text_get_color
(
GfItemText
*
item_text
)
{
g_return_val_if_fail
(
item_text
,
NULL
);
return
item_text
->
color
;
}
void
gf_item_text_set_clipping
(
GfItemText
*
item_text
,
GfItemTextClipping
clipping
)
{
g_return_if_fail
(
item_text
);
g_return_if_fail
(
clipping
>=
0
||
clipping
<
GF_ITEM_TEXT_CLIPPING_UNKNOWN
);
item_text
->
clipping
=
clipping
;
}
GfItemTextClipping
gf_item_text_get_clipping
(
GfItemText
*
item_text
)
{
g_return_val_if_fail
(
item_text
,
GF_ITEM_TEXT_CLIPPING_UNKNOWN
);
return
item_text
->
clipping
;
}
void
gf_item_text_set_width
(
GfItemText
*
item_text
,
gint
width
)
{
g_return_if_fail
(
item_text
);
g_return_if_fail
(
width
>=
0
);
item_text
->
width
=
width
;
}
gint
gf_item_text_get_width
(
GfItemText
*
item_text
)
{
g_return_val_if_fail
(
item_text
,
-1
);
return
item_text
->
width
;
}
void
gf_item_text_set_item
(
GfItemText
*
item_text
,
GfItem
*
item
)
{
g_return_if_fail
(
item_text
);
g_return_if_fail
(
item
);
item_text
->
item
=
item
;
}
GfItem
*
gf_item_text_get_item
(
GfItemText
*
item_text
)
{
g_return_val_if_fail
(
item_text
,
NULL
);
return
item_text
->
item
;
}
/*******************************************************************************
* Rendering stuff
******************************************************************************/
/* why did I think this was a good idea? */
static
gchar
*
gf_item_text_parse_format
(
GfItemText
*
item_text
,
GfEventInfo
*
info
)
{
GfEvent
*
event
;
GfNotification
*
notification
;
GfTheme
*
theme
;
GfThemeOptions
*
ops
;
PurpleAccount
*
account
;
PurpleBuddy
*
buddy
;
PurpleConversation
*
conv
=
NULL
;
GString
*
str
;
gchar
*
ret
;
const
gchar
*
tokens
,
*
format
,
*
time_format
,
*
date_format
,
*
warning
;
const
gchar
*
target
,
*
message
,
*
extra
;
time_t
rtime
;
static
char
buff
[
80
];
struct
tm
*
ltime
;
g_return_val_if_fail
(
item_text
,
NULL
);
g_return_val_if_fail
(
info
,
NULL
);
format
=
item_text
->
format
;
notification
=
gf_item_get_notification
(
item_text
->
item
);
theme
=
gf_notification_get_theme
(
notification
);
ops
=
gf_theme_get_theme_options
(
theme
);
time_format
=
gf_theme_options_get_time_format
(
ops
);
date_format
=
gf_theme_options_get_date_format
(
ops
);
warning
=
gf_theme_options_get_warning
(
ops
);
event
=
gf_event_info_get_event
(
info
);
target
=
gf_event_info_get_target
(
info
);
message
=
gf_event_info_get_message
(
info
);
extra
=
gf_event_info_get_extra
(
info
);
str
=
g_string_new
(
""
);
tokens
=
gf_event_get_tokens
(
event
);
time
(
&
rtime
);
ltime
=
localtime
(
&
rtime
);
account
=
gf_event_info_get_account
(
info
);
conv
=
gf_event_info_get_conversation
(
info
);
while
(
format
&&
format
[
0
])
{
if
(
format
[
0
]
==
'\\'
)
{
format
++
;
continue
;
}
if
(
format
[
0
]
!=
'%'
)
{
str
=
g_string_append_c
(
str
,
format
[
0
]);
format
++
;
continue
;
}
/* this increment is to get past the % */
format
++
;
if
(
!
format
[
0
])
break
;
if
(
!
strchr
(
tokens
,
format
[
0
]))
{
format
++
;
continue
;
}
switch
(
format
[
0
])
{
case
'%'
:
/* % */
str
=
g_string_append_c
(
str
,
'%'
);
break
;
case
'a'
:
/* account name */
str
=
g_string_append
(
str
,
purple_account_get_username
(
account
));
break
;
case
'C'
:
/* conversation title */
str
=
g_string_append
(
str
,
conv
?
purple_conversation_get_title
(
conv
)
:
target
);
break
;
case
'c'
:
/* conversation name */
if
(
conv
)
{
if
(
conv
->
type
==
PURPLE_CONV_TYPE_IM
)
{
PurpleBuddy
*
buddy
;
buddy
=
purple_find_buddy
(
account
,
conv
->
name
);
if
(
buddy
)
str
=
g_string_append
(
str
,
purple_buddy_get_contact_alias
(
buddy
));
else
str
=
g_string_append
(
str
,
conv
->
name
);
}
else
if
(
conv
->
type
==
PURPLE_CONV_TYPE_CHAT
)
{
PurpleChat
*
chat
;
chat
=
purple_blist_find_chat
(
account
,
conv
->
name
);
if
(
chat
)
{
str
=
g_string_append
(
str
,
purple_chat_get_name
(
chat
));
}
else
{
str
=
g_string_append
(
str
,
conv
->
name
);
}
}
else
{
str
=
g_string_append
(
str
,
conv
->
name
);
}
}
else
{
str
=
g_string_append
(
str
,
target
);
}
break
;
case
'D'
:
/* date */
strftime
(
buff
,
sizeof
(
buff
),
date_format
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'd'
:
/* day 01-31 */
strftime
(
buff
,
sizeof
(
buff
),
"%d"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'F'
:
/* Chat Flags */
/* CODE ME !!!
* which of course means to add theme options to give these numbers
* text to make these make sense :)
*/
break
;
case
'f'
:
/* Chat Flag Prefixes */
/* CODE ME !!!
* which means to add theme options for the prefix, these should be 1
* char, like an ircd would send us. ie: @/+
*/
break
;
case
'H'
:
/* hour 01-23 */
strftime
(
buff
,
sizeof
(
buff
),
"%H"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'h'
:
/* hour 01-12 */
strftime
(
buff
,
sizeof
(
buff
),
"%I"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'i'
:
/* ip */
str
=
g_string_append
(
str
,
purple_network_get_public_ip
());
break
;
case
'M'
:
/* month */
strftime
(
buff
,
sizeof
(
buff
),
"%m"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'm'
:
/* minute */
strftime
(
buff
,
sizeof
(
buff
),
"%M"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'N'
:
/* computer name */
gethostname
(
buff
,
sizeof
(
buff
));
str
=
g_string_append
(
str
,
buff
);
break
;
case
'n'
:
/* buddy screen name */
buddy
=
gf_event_info_get_buddy
(
info
);
if
(
buddy
)
{
const
gchar
*
alias
=
purple_buddy_get_contact_alias
(
buddy
);
str
=
g_string_append
(
str
,
alias
);
}
else
{
const
gchar
*
target
=
gf_event_info_get_target
(
info
);
if
(
target
)
{
const
gchar
*
target
;
target
=
gf_event_info_get_target
(
info
);
buddy
=
purple_find_buddy
(
account
,
target
);
if
(
buddy
)
{
const
gchar
*
alias
;
alias
=
purple_buddy_get_contact_alias
(
buddy
);
str
=
g_string_append
(
str
,
alias
);
}
else
{
str
=
g_string_append
(
str
,
target
);
}
}
}
break
;
case
'p'
:
/* protocol name */
str
=
g_string_append
(
str
,
purple_account_get_protocol_id
(
account
));
break
;
case
'r'
:
/* received message */
if
(
message
)
str
=
g_string_append
(
str
,
message
);
break
;
case
's'
:
/* seconds 00-59 */
strftime
(
buff
,
sizeof
(
buff
),
"%S"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'T'
:
/* Time according to the theme var */
strftime
(
buff
,
sizeof
(
buff
),
time_format
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
't'
:
/* seconds since the epoc */
strftime
(
buff
,
sizeof
(
buff
),
"%s"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'u'
:
/* computer user name */
str
=
g_string_append
(
str
,
g_get_user_name
());
break
;
#if !PURPLE_VERSION_CHECK(2,0,0)
case
'W'
:
/* warner */
if
(
target
)
str
=
g_string_append
(
str
,
target
);
break
;
case
'w'
:
/* warning level */
buddy
=
gf_event_info_get_buddy
(
info
);
if
(
buddy
)
{
const
char
*
prpl_id
;
prpl_id
=
purple_account_get_protocol_id
(
account
);
if
(
!
g_ascii_strcasecmp
(
prpl_id
,
"prpl-toc"
)
||
!
g_ascii_strcasecmp
(
prpl_id
,
"prpl-oscar"
))
{
g_string_append_printf
(
str
,
"%d"
,
gf_get_warning_level
(
buddy
));
}
else
{
str
=
g_string_append
(
str
,
warning
);
}
}
else
{
str
=
g_string_append
(
str
,
warning
);
}
#endif
break
;
case
'X'
:
/* extra info */
if
(
extra
)
str
=
g_string_append
(
str
,
extra
);
break
;
case
'Y'
:
/* four digit year */
strftime
(
buff
,
sizeof
(
buff
),
"%Y"
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
case
'y'
:
{
/* two digit year */
/* dirty hack to avoid compiler warning */
const
gchar
*
fmt
=
"%y"
;
strftime
(
buff
,
sizeof
(
buff
),
fmt
,
ltime
);
str
=
g_string_append
(
str
,
buff
);
break
;
}
default
:
break
;
}
/* this increment is to get past the formatting char */
format
++
;
}
ret
=
str
->
str
;
g_string_free
(
str
,
FALSE
);
return
ret
;
}
/* ugly hack to keep us working on glib 2.0 */
#if !GLIB_CHECK_VERSION(2,2,0)
static
gchar
*
g_utf8_strreverse
(
const
gchar
*
str
,
gssize
len
)
{
gchar
*
result
;
const
gchar
*
p
;
gchar
*
m
,
*
r
,
skip
;
if
(
len
<
0
)
len
=
strlen
(
str
);
result
=
g_new0
(
gchar
,
len
+
1
);
r
=
result
+
len
;
p
=
str
;
while
(
*
p
)
{
skip
=
g_utf8_skip
[
*
(
guchar
*
)
p
];
r
-=
skip
;
for
(
m
=
r
;
skip
;
skip
--
)
*
m
++
=
*
p
++
;
}
return
result
;
}
#endif
/* this will probably break.. be sure to test it!!! */
static
gchar
*
gf_utf8_strrndup
(
const
gchar
*
text
,
gint
n
)
{
gchar
*
rev
=
NULL
,
*
tmp
=
NULL
;
rev
=
g_utf8_strreverse
(
text
,
-1
);
/* ewww, what's going on here? */
/* oh, I remember... */
/* tell me then? */
/* well, g_utf8_strncpy doesn't allocate, so we use strdup to allocate for us */
/* oh neat, that's pretty ugly though */
/* yeah, there's probably a better way... */
tmp
=
g_strdup
(
rev
);
tmp
=
g_utf8_strncpy
(
tmp
,
rev
,
n
);
g_free
(
rev
);
rev
=
g_utf8_strreverse
(
tmp
,
-1
);
g_free
(
tmp
);
return
rev
;
}
/*******************************************************************************
* The dreaded text clipping functions
*
* Hopefully now "utf8 safe" (tm)
******************************************************************************/
static
void
text_truncate
(
PangoLayout
*
layout
,
gint
width
,
gint
offset
)
{
const
gchar
*
text
;
gchar
*
new_text
=
NULL
;
gint
l_width
=
0
;
g_return_if_fail
(
layout
);
while
(
1
)
{
pango_layout_get_pixel_size
(
layout
,
&
l_width
,
NULL
);
if
(
l_width
+
offset
<=
width
)
break
;
text
=
pango_layout_get_text
(
layout
);
new_text
=
g_strdup
(
text
);
new_text
=
g_utf8_strncpy
(
new_text
,
text
,
g_utf8_strlen
(
text
,
-1
)
-
1
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
}
static
void
text_ellipsis_start
(
PangoLayout
*
layout
,
gint
width
,
gint
offset
,
const
gchar
*
ellipsis_text
,
gint
ellipsis_width
)
{
const
gchar
*
text
;
gchar
*
new_text
=
NULL
;
gint
l_width
=
0
;
g_return_if_fail
(
layout
);
while
(
1
)
{
pango_layout_get_pixel_size
(
layout
,
&
l_width
,
NULL
);
if
(
l_width
+
offset
+
ellipsis_width
<=
width
)
break
;
text
=
pango_layout_get_text
(
layout
);
new_text
=
gf_utf8_strrndup
(
text
,
g_utf8_strlen
(
text
,
-1
)
-
1
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
text
=
pango_layout_get_text
(
layout
);
new_text
=
g_strdup_printf
(
"%s%s"
,
ellipsis_text
,
text
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
static
void
text_ellipsis_middle
(
PangoLayout
*
layout
,
gint
width
,
gint
offset
,
const
gchar
*
ellipsis_text
,
gint
ellipsis_width
)
{
const
gchar
*
text
;
gchar
*
new_text
=
NULL
,
*
left_text
=
NULL
,
*
right_text
=
NULL
;
gint
l_width
=
0
,
mid
;
g_return_if_fail
(
layout
);
while
(
1
)
{
pango_layout_get_pixel_size
(
layout
,
&
l_width
,
NULL
);
if
(
l_width
+
offset
+
ellipsis_width
<=
width
)
break
;
text
=
pango_layout_get_text
(
layout
);
mid
=
g_utf8_strlen
(
text
,
-1
)
/
2
;
left_text
=
g_strdup
(
text
);
left_text
=
g_utf8_strncpy
(
left_text
,
text
,
mid
);
if
(
IS_EVEN
(
g_utf8_strlen
(
text
,
-1
)))
right_text
=
gf_utf8_strrndup
(
text
,
mid
-
1
);
else
right_text
=
gf_utf8_strrndup
(
text
,
mid
);
new_text
=
g_strdup_printf
(
"%s%s"
,
left_text
,
right_text
);
g_free
(
left_text
);
g_free
(
right_text
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
text
=
pango_layout_get_text
(
layout
);
mid
=
g_utf8_strlen
(
text
,
-1
)
/
2
;
left_text
=
g_strdup
(
text
);
left_text
=
g_utf8_strncpy
(
left_text
,
text
,
mid
);
if
(
IS_EVEN
(
g_utf8_strlen
(
text
,
-1
)))
right_text
=
gf_utf8_strrndup
(
text
,
mid
-
1
);
else
right_text
=
gf_utf8_strrndup
(
text
,
mid
);
new_text
=
g_strdup_printf
(
"%s%s%s"
,
left_text
,
ellipsis_text
,
right_text
);
g_free
(
left_text
);
g_free
(
right_text
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
static
void
text_ellipsis_end
(
PangoLayout
*
layout
,
gint
width
,
gint
offset
,
const
gchar
*
ellipsis_text
,
gint
ellipsis_width
)
{
const
gchar
*
text
;
gchar
*
new_text
=
NULL
;
gint
l_width
=
0
;
g_return_if_fail
(
layout
);
while
(
1
)
{
pango_layout_get_pixel_size
(
layout
,
&
l_width
,
NULL
);
if
(
l_width
+
offset
+
ellipsis_width
<=
width
)
break
;
text
=
pango_layout_get_text
(
layout
);
new_text
=
g_strdup
(
text
);
new_text
=
g_utf8_strncpy
(
new_text
,
text
,
g_utf8_strlen
(
text
,
-1
)
-
1
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
text
=
pango_layout_get_text
(
layout
);
new_text
=
g_strdup_printf
(
"%s%s"
,
text
,
ellipsis_text
);
pango_layout_set_text
(
layout
,
new_text
,
-1
);
g_free
(
new_text
);
}
static
void
gf_item_text_clip
(
GfItemText
*
item_text
,
PangoLayout
*
layout
,
gint
pixbuf_width
)
{
GfNotification
*
notification
;
GfTheme
*
theme
;
GfThemeOptions
*
ops
;
GfItemOffset
*
ioffset
;
PangoLayout
*
ellipsis
;
const
gchar
*
ellipsis_text
;
gint
e_width
=
0
,
l_width
=
0
,
width
=
0
,
offset
=
0
;
g_return_if_fail
(
item_text
);
g_return_if_fail
(
layout
);
notification
=
gf_item_get_notification
(
item_text
->
item
);
theme
=
gf_notification_get_theme
(
notification
);
ops
=
gf_theme_get_theme_options
(
theme
);
ellipsis_text
=
gf_theme_options_get_ellipsis
(
ops
);
if
((
ioffset
=
gf_item_get_horz_offset
(
item_text
->
item
)))
{
if
(
ioffset
&&
gf_item_offset_get_is_percentage
(
ioffset
))
offset
=
(
pixbuf_width
*
gf_item_offset_get_value
(
ioffset
))
/
100
;
else
offset
=
gf_item_offset_get_value
(
ioffset
);
}
else
{
offset
=
0
;
}
width
=
item_text
->
width
;
if
(
width
==
0
)
width
=
pixbuf_width
;
else
offset
=
0
;
ellipsis
=
pango_layout_copy
(
layout
);
pango_layout_set_text
(
ellipsis
,
ellipsis_text
,
-1
);
pango_layout_get_pixel_size
(
ellipsis
,
&
e_width
,
NULL
);
g_object_unref
(
G_OBJECT
(
ellipsis
));
pango_layout_get_pixel_size
(
layout
,
&
l_width
,
NULL
);
if
(
l_width
<=
width
)
return
;
switch
(
item_text
->
clipping
)
{
case
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_START
:
text_ellipsis_start
(
layout
,
width
,
offset
,
ellipsis_text
,
e_width
);
break
;
case
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_MIDDLE
:
text_ellipsis_middle
(
layout
,
width
,
offset
,
ellipsis_text
,
e_width
);
break
;
case
GF_ITEM_TEXT_CLIPPING_ELLIPSIS_END
:
text_ellipsis_end
(
layout
,
width
,
offset
,
ellipsis_text
,
e_width
);
break
;
case
GF_ITEM_TEXT_CLIPPING_TRUNCATE
:
default
:
text_truncate
(
layout
,
width
,
offset
);
break
;
}
}
static
PangoLayout
*
gf_item_text_create_layout
(
GfItemText
*
item_text
,
GfEventInfo
*
info
,
gint
width
)
{
PangoLayout
*
layout
=
NULL
;
PangoFontDescription
*
font
=
NULL
;
gchar
*
text
=
NULL
;
g_return_val_if_fail
(
item_text
,
NULL
);
g_return_val_if_fail
(
info
,
NULL
);
layout
=
pango_layout_new
(
context
);
pango_layout_set_width
(
layout
,
-1
);
if
(
item_text
->
font
)
{
font
=
pango_font_description_from_string
(
item_text
->
font
);
pango_layout_set_font_description
(
layout
,
font
);
pango_font_description_free
(
font
);
}
else
{
pango_layout_set_font_description
(
layout
,
gf_gtk_theme_get_font
());
}
text
=
gf_item_text_parse_format
(
item_text
,
info
);
pango_layout_set_text
(
layout
,
text
,
-1
);
g_free
(
text
);
gf_item_text_clip
(
item_text
,
layout
,
width
);
return
layout
;
}
/* This function has cost me 5 days of trying to find some way to make gtk/pango/gdk do
* this. I'm only using this way because 2 gtk/pango developers said this is the only
* way at this time. So if you change it.. Your changes better fucking work :P
*/
static
GdkPixbuf
*
gf_pixbuf_new_from_ft2_bitmap
(
FT_Bitmap
*
bitmap
,
PangoColor
*
color
)
{
GdkPixbuf
*
pixbuf
;
guchar
*
buffer
,
*
pbuffer
;
gint
w
,
h
,
rowstride
;
guint8
r
,
g
,
b
,
*
alpha
;
/* Grab the colors from the PangoColor and shift 8 bits because we're only in 8 bit
* mode, and a PangoColor's elements are guint16's which are 16 bits...
*/
r
=
color
->
red
>>
8
;
g
=
color
->
green
>>
8
;
b
=
color
->
blue
>>
8
;
/* create the new pixbuf */
pixbuf
=
gdk_pixbuf_new
(
GDK_COLORSPACE_RGB
,
TRUE
,
8
,
bitmap
->
width
,
bitmap
->
rows
);
if
(
!
pixbuf
)
return
NULL
;
/* clear out the pixbuf to transparent black */
gdk_pixbuf_fill
(
pixbuf
,
0x00000000
);
buffer
=
gdk_pixbuf_get_pixels
(
pixbuf
);
rowstride
=
gdk_pixbuf_get_rowstride
(
pixbuf
);
/* ok here's the run down...
*
* From devhelp:
* Image data in a pixbuf is stored in memory in uncompressed, packed format. Rows
* in the image are stored top to bottom, and in each row pixels are stored from
* left to right. There may be padding at the end of a row. The "rowstride" value of
* a pixbuf, as returned by gdk_pixbuf_get_rowstride(), indicates the number of
* bytes between rows.
*
* So we take the height of the FT_Bitmap (the text), and increment until we're done.
* simple enough.
*
* Then we get the alpha for the row in the FT_Bitmap
*
* Next we move left to right and draw accordingly.
*
* Repeat until we've gone through the whole FT_Bitmap.
*/
for
(
h
=
0
;
h
<
bitmap
->
rows
;
h
++
)
{
pbuffer
=
buffer
+
(
h
*
rowstride
);
/* get the alpha from the FT_Bitmap */
alpha
=
bitmap
->
buffer
+
(
h
*
(
bitmap
->
pitch
));
for
(
w
=
0
;
w
<
bitmap
->
width
;
w
++
)
{
*
pbuffer
++
=
r
;
*
pbuffer
++
=
g
;
*
pbuffer
++
=
b
;
*
pbuffer
++
=
*
alpha
++
;
}
}
return
pixbuf
;
}
void
gf_item_text_render
(
GfItemText
*
item_text
,
GdkPixbuf
*
pixbuf
,
GfEventInfo
*
info
)
{
GdkPixbuf
*
t_pixbuf
=
NULL
;
PangoColor
color
;
PangoLayout
*
layout
=
NULL
;
FT_Bitmap
bitmap
;
gint
x
=
0
,
y
=
0
;
gint
n_width
=
0
,
n_height
=
0
;
gint
t_width
=
0
,
t_height
=
0
;
gint
l_width
=
0
,
l_height
=
0
;
g_return_if_fail
(
item_text
);
g_return_if_fail
(
pixbuf
);
g_return_if_fail
(
info
);
/* get the width and height of the notification pixbuf */
n_width
=
gdk_pixbuf_get_width
(
pixbuf
);
n_height
=
gdk_pixbuf_get_height
(
pixbuf
);
/* create the layout */
layout
=
gf_item_text_create_layout
(
item_text
,
info
,
n_width
);
if
(
!
layout
)
return
;
/* setup the FT_BITMAP */
pango_layout_get_pixel_size
(
layout
,
&
l_width
,
&
l_height
);
bitmap
.
rows
=
l_height
;
bitmap
.
width
=
l_width
;
bitmap
.
pitch
=
(
bitmap
.
width
+
3
)
&
~
3
;
bitmap
.
buffer
=
g_new0
(
guint8
,
bitmap
.
rows
*
bitmap
.
pitch
);
bitmap
.
num_grays
=
255
;
bitmap
.
pixel_mode
=
ft_pixel_mode_grays
;
pango_ft2_render_layout
(
&
bitmap
,
layout
,
0
,
0
);
g_object_unref
(
G_OBJECT
(
layout
));
if
(
!
item_text
->
color
)
{
GdkColor
g_color
;
gf_gtk_theme_get_fg_color
(
&
g_color
);
gf_gtk_color_pango_from_gdk
(
&
color
,
&
g_color
);
}
else
if
(
!
pango_color_parse
(
&
color
,
item_text
->
color
))
color
.
red
=
color
.
green
=
color
.
blue
=
0
;
t_pixbuf
=
gf_pixbuf_new_from_ft2_bitmap
(
&
bitmap
,
&
color
);
g_free
(
bitmap
.
buffer
);
if
(
!
t_pixbuf
)
return
;
t_width
=
gdk_pixbuf_get_width
(
t_pixbuf
);
t_height
=
gdk_pixbuf_get_height
(
t_pixbuf
);
gf_item_get_render_position
(
&
x
,
&
y
,
t_width
,
t_height
,
n_width
,
n_height
,
item_text
->
item
);
gf_gtk_pixbuf_clip_composite
(
t_pixbuf
,
x
,
y
,
pixbuf
);
g_object_unref
(
G_OBJECT
(
t_pixbuf
));
}