2 ## src/gtkgui_helpers.py
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
6 ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
8 ## Copyright (C) 2006-2007 Junglecow J <junglecow AT gmail.com>
9 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
10 ## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
11 ## Julien Pivotto <roidelapluie AT gmail.com>
12 ## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
13 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
15 ## This file is part of Gajim.
17 ## Gajim is free software; you can redistribute it and/or modify
18 ## it under the terms of the GNU General Public License as published
19 ## by the Free Software Foundation; version 3 only.
21 ## Gajim is distributed in the hope that it will be useful,
22 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ## GNU General Public License for more details.
26 ## You should have received a copy of the GNU General Public License
27 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
30 import xml
.sax
.saxutils
39 log
= logging
.getLogger('gajim.gtkgui_helpers')
41 from common
import i18n
42 from common
import gajim
44 gtk_icon_theme
= gtk
.icon_theme_get_default()
45 gtk_icon_theme
.append_search_path(gajim
.ICONS_DIR
)
47 def get_icon_pixmap(icon_name
, size
=16):
49 return gtk_icon_theme
.load_icon(icon_name
, size
, 0)
50 except gobject
.GError
, e
:
51 log
.error('Unable to load icon %s: %s' % (icon_name
, str(e
)))
53 def get_icon_path(icon_name
, size
=16):
55 icon_info
= gtk_icon_theme
.lookup_icon(icon_name
, size
, 0)
57 log
.error('Icon not found: %s' % icon_name
)
60 return icon_info
.get_filename()
61 except gobject
.GError
, e
:
62 log
.error("Unable to find icon %s: %s" % (icon_name
, str(e
)))
77 from common
import helpers
79 screen_w
= gtk
.gdk
.screen_width()
80 screen_h
= gtk
.gdk
.screen_height()
82 def add_image_to_menuitem(menuitem
, icon_name
):
84 path_img
= get_icon_path(icon_name
)
85 img
.set_from_file(path_img
)
86 menuitem
.set_image(img
)
88 def add_image_to_button(button
, icon_name
):
89 add_image_to_menuitem(button
, icon_name
)
91 GUI_DIR
= os
.path
.join(gajim
.DATA_DIR
, 'gui')
92 def get_gtk_builder(file_name
, widget
=None):
93 file_path
= os
.path
.join(GUI_DIR
, file_name
)
94 builder
= gtk
.Builder()
95 builder
.set_translation_domain(i18n
.APP
)
97 builder
.add_objects_from_file(file_path
, [widget
])
99 builder
.add_from_file(file_path
)
102 def get_completion_liststore(entry
):
104 Create a completion model for entry widget completion list consists of
107 completion
= gtk
.EntryCompletion()
108 liststore
= gtk
.ListStore(gtk
.gdk
.Pixbuf
, str)
110 render_pixbuf
= gtk
.CellRendererPixbuf()
111 completion
.pack_start(render_pixbuf
, expand
= False)
112 completion
.add_attribute(render_pixbuf
, 'pixbuf', 0)
114 render_text
= gtk
.CellRendererText()
115 completion
.pack_start(render_text
, expand
= True)
116 completion
.add_attribute(render_text
, 'text', 1)
117 completion
.set_property('text_column', 1)
118 completion
.set_model(liststore
)
119 entry
.set_completion(completion
)
123 def popup_emoticons_under_button(menu
, button
, parent_win
):
125 Popup the emoticons menu under button, which is in parent_win
127 window_x1
, window_y1
= parent_win
.get_origin()
128 def position_menu_under_button(menu
):
129 # inline function, which will not keep refs, when used as CB
130 button_x
, button_y
= button
.allocation
.x
, button
.allocation
.y
132 # now convert them to X11-relative
133 window_x
, window_y
= window_x1
, window_y1
134 x
= window_x
+ button_x
135 y
= window_y
+ button_y
137 menu_height
= menu
.size_request()[1]
139 ## should we pop down or up?
140 if (y
+ button
.allocation
.height
+ menu_height
141 < gtk
.gdk
.screen_height()):
142 # now move the menu below the button
143 y
+= button
.allocation
.height
145 # now move the menu above the button
148 # push_in is True so all the menuitems are always inside screen
150 return (x
, y
, push_in
)
152 menu
.popup(None, None, position_menu_under_button
, 1, 0)
154 def get_theme_font_for_option(theme
, option
):
156 Return string description of the font, stored in theme preferences
158 font_name
= gajim
.config
.get_per('themes', theme
, option
)
159 font_desc
= pango
.FontDescription()
160 font_prop_str
= gajim
.config
.get_per('themes', theme
, option
+ 'attrs')
162 if font_prop_str
.find('B') != -1:
163 font_desc
.set_weight(pango
.WEIGHT_BOLD
)
164 if font_prop_str
.find('I') != -1:
165 font_desc
.set_style(pango
.STYLE_ITALIC
)
166 fd
= pango
.FontDescription(font_name
)
167 fd
.merge(font_desc
, True)
168 return fd
.to_string()
170 def get_default_font():
172 Get the desktop setting for application font first check for GNOME, then
173 Xfce and last KDE it returns None on failure or else a string 'Font Size'
176 from gconf
import client_get_default
177 client
= client_get_default()
178 value
= client
.get_string("/desktop/gnome/interface/font_name")
179 return value
.decode("utf8")
180 except ImportError, glib
.GError
:
183 # try to get Xfce default font
184 # Xfce 4.2 and higher follow freedesktop.org's Base Directory Specification
185 # see http://www.xfce.org/~benny/xfce/file-locations.html
186 # and http://freedesktop.org/Standards/basedir-spec
187 xdg_config_home
= os
.environ
.get('XDG_CONFIG_HOME', '')
188 if xdg_config_home
== '':
189 xdg_config_home
= os
.path
.expanduser('~/.config') # default
190 xfce_config_file
= os
.path
.join(xdg_config_home
,
191 'xfce4/mcs_settings/gtk.xml')
193 kde_config_file
= os
.path
.expanduser('~/.kde/share/config/kdeglobals')
195 if os
.path
.exists(xfce_config_file
):
197 for line
in open(xfce_config_file
):
198 if line
.find('name="Gtk/FontName"') != -1:
199 start
= line
.find('value="') + 7
200 return line
[start
:line
.find('"', start
)].decode('utf-8')
203 print >> sys
.stderr
, _('Error: cannot open %s for reading') % \
206 elif os
.path
.exists(kde_config_file
):
208 for line
in open(kde_config_file
):
209 if line
.find('font=') == 0: # font=Verdana,9,other_numbers
210 start
= 5 # 5 is len('font=')
212 values
= line
.split(',')
213 font_name
= values
[0]
214 font_size
= values
[1]
215 font_string
= '%s %s' % (font_name
, font_size
) # Verdana 9
216 return font_string
.decode('utf-8')
219 print >> sys
.stderr
, _('Error: cannot open %s for reading') % \
224 def get_running_processes():
226 Return running processes or None (if /proc does not exist)
228 if os
.path
.isdir('/proc'):
229 # under Linux: checking if 'gnome-session' or
230 # 'startkde' programs were run before gajim, by
231 # checking /proc (if it exists)
233 # if something is unclear, read `man proc`;
234 # if /proc exists, directories that have only numbers
235 # in their names contain data about processes.
236 # /proc/[xxx]/exe is a symlink to executable started
237 # as process number [xxx].
238 # filter out everything that we are not interested in:
239 files
= os
.listdir('/proc')
241 # files that doesn't have only digits in names...
242 files
= [f
for f
in files
if f
.isdigit()]
244 # files that aren't directories...
245 files
= [f
for f
in files
if os
.path
.isdir('/proc/' + f
)]
247 # processes owned by somebody not running gajim...
248 # (we check if we have access to that file)
249 files
= [f
for f
in files
if os
.access('/proc/' + f
+'/exe', os
.F_OK
)]
251 # be sure that /proc/[number]/exe is really a symlink
252 # to avoid TBs in incorrectly configured systems
253 files
= [f
for f
in files
if os
.path
.islink('/proc/' + f
+ '/exe')]
256 processes
= [os
.path
.basename(os
.readlink('/proc/' + f
+'/exe')) for f \
262 def move_window(window
, x
, y
):
264 Move the window, but also check if out of screen
270 w
, h
= window
.get_size()
277 def resize_window(window
, w
, h
):
279 Resize window, but also checks if huge window or negative values
287 window
.resize(abs(w
), abs(h
))
290 def __init__(self
, algo
, digest
):
291 self
.algo
= self
.cleanID(algo
)
292 self
.digest
= self
.cleanID(digest
)
294 def cleanID(self
, id_
):
295 id_
= id_
.strip().lower()
296 for strip
in (' :.-_'):
297 id_
= id_
.replace(strip
, '')
300 def __eq__(self
, other
):
301 sa
, sd
= self
.algo
, self
.digest
302 if isinstance(other
, self
.__class
__):
303 oa
, od
= other
.algo
, other
.digest
304 elif isinstance(other
, basestring
):
305 sa
, oa
, od
= None, None, self
.cleanID(other
)
306 elif isinstance(other
, tuple) and len(other
) == 2:
307 oa
, od
= self
.cleanID(other
[0]), self
.cleanID(other
[1])
311 return sa
== oa
and sd
== od
313 def __ne__(self
, other
):
314 return not self
== other
317 return self
.algo ^ self
.digest
321 for i
in xrange(0, len(self
.digest
), 2):
322 prettydigest
+= self
.digest
[i
:i
+ 2] + ':'
323 return prettydigest
[:-1]
326 return "%s(%s, %s)" % (self
.__class
__, repr(self
.algo
), repr(str(self
)))
328 class ServersXMLHandler(xml
.sax
.ContentHandler
):
330 xml
.sax
.ContentHandler
.__init
__(self
)
333 def startElement(self
, name
, attributes
):
335 if 'jid' in attributes
.getNames():
336 self
.servers
.append(attributes
.getValue('jid'))
338 def endElement(self
, name
):
341 def parse_server_xml(path_to_file
):
343 handler
= ServersXMLHandler()
344 xml
.sax
.parse(path_to_file
, handler
)
345 return handler
.servers
346 # handle exception if unable to open file
347 except IOError, message
:
348 print >> sys
.stderr
, _('Error reading file:'), message
349 # handle exception parsing file
350 except xml
.sax
.SAXParseException
, message
:
351 print >> sys
.stderr
, _('Error parsing file:'), message
353 def set_unset_urgency_hint(window
, unread_messages_no
):
355 Sets/unset urgency hint in window argument depending if we have unread
358 if gajim
.config
.get('use_urgency_hint'):
359 if unread_messages_no
> 0:
360 window
.props
.urgency_hint
= True
362 window
.props
.urgency_hint
= False
364 def get_abspath_for_script(scriptname
, want_type
= False):
366 Check if we are svn or normal user and return abspath to asked script if
367 want_type is True we return 'svn' or 'install'
369 if os
.path
.isdir('.svn'): # we are svn user
371 cwd
= os
.getcwd() # it's always ending with src
373 if scriptname
== 'gajim-remote':
374 path_to_script
= cwd
+ '/gajim-remote.py'
376 elif scriptname
== 'gajim':
377 script
= '#!/bin/sh\n' # the script we may create
378 script
+= 'cd %s' % cwd
379 path_to_script
= cwd
+ '/../scripts/gajim_sm_script'
382 if os
.path
.exists(path_to_script
):
383 os
.remove(path_to_script
)
385 f
= open(path_to_script
, 'w')
386 script
+= '\nexec python -OOt gajim.py $0 $@\n'
389 os
.chmod(path_to_script
, 0700)
390 except OSError: # do not traceback (could be a permission problem)
391 #we talk about a file here
392 s
= _('Could not write to %s. Session Management support will '
393 'not work') % path_to_script
394 print >> sys
.stderr
, s
396 else: # normal user (not svn user)
398 # always make it like '/usr/local/bin/gajim'
399 path_to_script
= helpers
.is_in_path(scriptname
, True)
403 return path_to_script
, type_
405 return path_to_script
407 def get_pixbuf_from_data(file_data
, want_type
= False):
409 Get image data and returns gtk.gdk.Pixbuf if want_type is True it also
410 returns 'jpeg', 'png' etc
412 pixbufloader
= gtk
.gdk
.PixbufLoader()
414 pixbufloader
.write(file_data
)
416 pixbuf
= pixbufloader
.get_pixbuf()
417 except gobject
.GError
: # 'unknown image format'
426 typ
= pixbufloader
.get_format()['name']
431 def get_invisible_cursor():
432 pixmap
= gtk
.gdk
.Pixmap(None, 1, 1, 1)
433 color
= gtk
.gdk
.Color()
434 cursor
= gtk
.gdk
.Cursor(pixmap
, pixmap
, color
, color
, 0, 0)
437 def get_current_desktop(window
):
439 Return the current virtual desktop for given window
441 NOTE: Window is a GDK window.
443 prop
= window
.property_get('_NET_CURRENT_DESKTOP')
444 if prop
is None: # it means it's normal window (not root window)
445 # so we look for it's current virtual desktop in another property
446 prop
= window
.property_get('_NET_WM_DESKTOP')
449 # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0]
450 current_virtual_desktop_no
= prop
[2][0]
451 return current_virtual_desktop_no
453 def possibly_move_window_in_current_desktop(window
):
455 Moves GTK window to current virtual desktop if it is not in the current
458 NOTE: Window is a GDK window.
463 root_window
= gtk
.gdk
.screen_get_default().get_root_window()
465 current_virtual_desktop_no
= get_current_desktop(root_window
)
467 # vd roster window is in
468 window_virtual_desktop
= get_current_desktop(window
.window
)
470 # if one of those is None, something went wrong and we cannot know
471 # VD info, just hide it (default action) and not show it afterwards
472 if None not in (window_virtual_desktop
, current_virtual_desktop_no
):
473 if current_virtual_desktop_no
!= window_virtual_desktop
:
474 # we are in another VD that the window was
475 # so show it in current VD
480 def file_is_locked(path_to_file
):
482 Return True if file is locked
486 if os
.name
!= 'nt': # just in case
492 secur_att
= pywintypes
.SECURITY_ATTRIBUTES()
493 secur_att
.Initialize()
496 # try make a handle for READING the file
497 hfile
= win32file
.CreateFile(
498 path_to_file
, # path to file
499 win32con
.GENERIC_READ
, # open for reading
500 0, # do not share with other proc
502 win32con
.OPEN_EXISTING
, # existing file only
503 win32con
.FILE_ATTRIBUTE_NORMAL
, # normal file
504 0 # no attr. template
506 except pywintypes
.error
:
508 else: # in case all went ok, close file handle (go to hell WinAPI)
512 def get_fade_color(treeview
, selected
, focused
):
514 Get a gdk color that is between foreground and background in 0.3
515 0.7 respectively colors of the cell for the given treeview
517 style
= treeview
.style
519 if focused
: # is the window focused?
520 state
= gtk
.STATE_SELECTED
521 else: # is it not? NOTE: many gtk themes change bg on this
522 state
= gtk
.STATE_ACTIVE
524 state
= gtk
.STATE_NORMAL
525 bg
= style
.base
[state
]
526 fg
= style
.text
[state
]
529 q
= 0.7 # foreground # p + q should do 1.0
530 return gtk
.gdk
.Color(int(bg
.red
*p
+ fg
.red
*q
),
531 int(bg
.green
*p
+ fg
.green
*q
),
532 int(bg
.blue
*p
+ fg
.blue
*q
))
534 def get_scaled_pixbuf(pixbuf
, kind
):
536 Return scaled pixbuf, keeping ratio etc or None kind is either "chat",
537 "roster", "notification", "tooltip", "vcard"
539 # resize to a width / height for the avatar not to have distortion
540 # (keep aspect ratio)
541 width
= gajim
.config
.get(kind
+ '_avatar_width')
542 height
= gajim
.config
.get(kind
+ '_avatar_height')
543 if width
< 1 or height
< 1:
547 pix_width
= pixbuf
.get_width()
548 pix_height
= pixbuf
.get_height()
549 # don't make avatars bigger than they are
550 if pix_width
< width
and pix_height
< height
:
551 return pixbuf
# we don't want to make avatar bigger
553 ratio
= float(pix_width
) / float(pix_height
)
560 scaled_buf
= pixbuf
.scale_simple(w
, h
, gtk
.gdk
.INTERP_HYPER
)
563 def get_avatar_pixbuf_from_cache(fjid
, use_local
=True):
565 Check if jid has cached avatar and if that avatar is valid image (can be
568 Returns None if there is no image in vcard/
569 Returns 'ask' if cached vcard should not be used (user changed his vcard, so
570 we have new sha) or if we don't have the vcard
572 jid
, nick
= gajim
.get_room_and_nick_from_fjid(fjid
)
573 if gajim
.config
.get('hide_avatar_of_transport') and\
574 gajim
.jid_is_transport(jid
):
575 # don't show avatar for the transport itself
578 if any(jid
in gajim
.contacts
.get_gc_list(acc
) for acc
in \
579 gajim
.contacts
.get_accounts()):
580 is_groupchat_contact
= True
582 is_groupchat_contact
= False
584 puny_jid
= helpers
.sanitize_filename(jid
)
585 if is_groupchat_contact
:
586 puny_nick
= helpers
.sanitize_filename(nick
)
587 path
= os
.path
.join(gajim
.VCARD_PATH
, puny_jid
, puny_nick
)
588 local_avatar_basepath
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
,
589 puny_nick
) + '_local'
591 path
= os
.path
.join(gajim
.VCARD_PATH
, puny_jid
)
592 local_avatar_basepath
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
) + \
595 for extension
in ('.png', '.jpeg'):
596 local_avatar_path
= local_avatar_basepath
+ extension
597 if os
.path
.isfile(local_avatar_path
):
598 avatar_file
= open(local_avatar_path
, 'rb')
599 avatar_data
= avatar_file
.read()
601 return get_pixbuf_from_data(avatar_data
)
603 if not os
.path
.isfile(path
):
606 vcard_dict
= gajim
.connections
.values()[0].get_cached_vcard(fjid
,
607 is_groupchat_contact
)
608 if not vcard_dict
: # This can happen if cached vcard is too old
610 if 'PHOTO' not in vcard_dict
:
612 pixbuf
= vcard
.get_avatar_pixbuf_encoded_mime(vcard_dict
['PHOTO'])[0]
615 def make_gtk_month_python_month(month
):
617 GTK starts counting months from 0, so January is 0 but Python's time start
618 from 1, so align to Python
620 NOTE: Month MUST be an integer.
624 def make_python_month_gtk_month(month
):
627 def make_color_string(color
):
629 Create #aabbcc color string from gtk color
632 for i
in ('red', 'green', 'blue'):
633 h
= hex(getattr(color
, i
) / (16*16)).split('x')[1]
639 def make_pixbuf_grayscale(pixbuf
):
640 pixbuf2
= pixbuf
.copy()
641 pixbuf
.saturate_and_pixelate(pixbuf2
, 0.0, False)
644 def get_path_to_generic_or_avatar(generic
, jid
= None, suffix
= None):
646 Choose between avatar image and default image
648 Returns full path to the avatar image if it exists, otherwise returns full
649 path to the image. generic must be with extension and suffix without
653 puny_jid
= helpers
.sanitize_filename(jid
)
654 path_to_file
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
) + suffix
655 path_to_local_file
= path_to_file
+ '_local'
656 for extension
in ('.png', '.jpeg'):
657 path_to_local_file_full
= path_to_local_file
+ extension
658 if os
.path
.exists(path_to_local_file_full
):
659 return path_to_local_file_full
660 for extension
in ('.png', '.jpeg'):
661 path_to_file_full
= path_to_file
+ extension
662 if os
.path
.exists(path_to_file_full
):
663 return path_to_file_full
664 return os
.path
.abspath(generic
)
666 def decode_filechooser_file_paths(file_paths
):
668 Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX
669 file_paths MUST be LIST
671 file_paths_list
= list()
673 if os
.name
== 'nt': # decode as UTF-8 under Windows
674 for file_path
in file_paths
:
675 file_path
= file_path
.decode('utf8')
676 file_paths_list
.append(file_path
)
678 for file_path
in file_paths
:
680 file_path
= file_path
.decode(sys
.getfilesystemencoding())
683 file_path
= file_path
.decode('utf-8')
686 file_paths_list
.append(file_path
)
688 return file_paths_list
690 def possibly_set_gajim_as_xmpp_handler():
692 Register (by default only the first time) 'xmmp:' to Gajim
694 path_to_dot_kde
= os
.path
.expanduser('~/.kde')
695 if os
.path
.exists(path_to_dot_kde
):
696 path_to_kde_file
= os
.path
.join(path_to_dot_kde
,
697 'share/services/xmpp.protocol')
699 path_to_kde_file
= None
701 def set_gajim_as_xmpp_handler(is_checked
=None):
702 if is_checked
is not None:
703 # come from confirmation dialog
704 gajim
.config
.set('check_if_gajim_is_default', is_checked
)
705 path_to_gajim_script
, typ
= get_abspath_for_script('gajim-remote', True)
706 if path_to_gajim_script
:
708 command
= path_to_gajim_script
+ ' handle_uri %s'
710 command
= 'gajim-remote handle_uri %s'
712 # setting for GNOME/Gconf
713 client
.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True)
714 client
.set_string('/desktop/gnome/url-handlers/xmpp/command',
716 client
.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal',
720 if path_to_kde_file
is not None: # user has run kde at least once
722 f
= open(path_to_kde_file
, 'a')
740 log
.debug("I/O Error writing settings to %s",
741 repr(path_to_kde_file
), exc_info
=True)
742 else: # no gajim remote, stop ask user everytime
743 gajim
.config
.set('check_if_gajim_is_default', False)
747 # in try because daemon may not be there
748 client
= gconf
.client_get_default()
752 old_command
= client
.get_string('/desktop/gnome/url-handlers/xmpp/command')
753 if not old_command
or old_command
.endswith(' open_chat %s'):
754 # first time (GNOME/GCONF) or old Gajim version
756 elif path_to_kde_file
is not None and not os
.path
.exists(path_to_kde_file
):
757 # only the first time (KDE)
763 set_gajim_as_xmpp_handler()
764 elif old_command
and not old_command
.endswith(' handle_uri %s'):
765 # xmpp: is currently handled by another program, so ask the user
766 pritext
= _('Gajim is not the default Jabber client')
767 sectext
= _('Would you like to make Gajim the default Jabber client?')
768 checktext
= _('Always check to see if Gajim is the default Jabber '
770 def on_cancel(checked
):
771 gajim
.config
.set('check_if_gajim_is_default', checked
)
772 dlg
= dialogs
.ConfirmationDialogCheck(pritext
, sectext
, checktext
,
773 set_gajim_as_xmpp_handler
, on_cancel
)
774 if gajim
.config
.get('check_if_gajim_is_default'):
775 dlg
.checkbutton
.set_active(True)
777 def escape_underscore(s
):
779 Escape underlines to prevent them from being interpreted as keyboard
782 return s
.replace('_', '__')
784 def get_state_image_from_file_path_show(file_path
, show
):
785 state_file
= show
.replace(' ', '_')
787 files
.append(os
.path
.join(file_path
, state_file
+ '.png'))
788 files
.append(os
.path
.join(file_path
, state_file
+ '.gif'))
790 image
.set_from_pixbuf(None)
792 if os
.path
.exists(file_
):
793 image
.set_from_file(file_
)
798 def get_possible_button_event(event
):
800 Mouse or keyboard caused the event?
802 if event
.type == gtk
.gdk
.KEY_PRESS
:
803 return 0 # no event.button so pass 0
804 # BUTTON_PRESS event, so pass event.button
807 def destroy_widget(widget
):
810 def on_avatar_save_as_menuitem_activate(widget
, jid
, default_name
=''):
811 def on_continue(response
, file_path
):
814 pixbuf
= get_avatar_pixbuf_from_cache(jid
)
815 extension
= os
.path
.splitext(file_path
)[1]
817 # Silently save as Jpeg image
818 image_format
= 'jpeg'
820 elif extension
== 'jpg':
821 image_format
= 'jpeg'
823 image_format
= extension
[1:] # remove leading dot
827 pixbuf
.save(file_path
, image_format
)
828 except glib
.GError
, e
:
829 log
.debug('Error saving avatar: %s' % str(e
))
830 if os
.path
.exists(file_path
):
832 new_file_path
= '.'.join(file_path
.split('.')[:-1]) + '.jpeg'
833 def on_ok(file_path
, pixbuf
):
834 pixbuf
.save(file_path
, 'jpeg')
835 dialogs
.ConfirmationDialog(_('Extension not supported'),
836 _('Image cannot be saved in %(type)s format. Save as '
837 '%(new_filename)s?') % {'type': image_format
,
838 'new_filename': new_file_path
},
839 on_response_ok
= (on_ok
, new_file_path
, pixbuf
))
844 file_path
= dialog
.get_filename()
845 file_path
= decode_filechooser_file_paths((file_path
,))[0]
846 if os
.path
.exists(file_path
):
847 # check if we have write permissions
848 if not os
.access(file_path
, os
.W_OK
):
849 file_name
= os
.path
.basename(file_path
)
850 dialogs
.ErrorDialog(_('Cannot overwrite existing file "%s"') % \
851 file_name
, _('A file with this name already exists and you '
852 'do not have permission to overwrite it.'))
854 dialog2
= dialogs
.FTOverwriteConfirmationDialog(
855 _('This file already exists'), _('What do you want to do?'),
856 propose_resume
=False, on_response
=(on_continue
, file_path
))
857 dialog2
.set_transient_for(dialog
)
858 dialog2
.set_destroy_with_parent(True)
860 dirname
= os
.path
.dirname(file_path
)
861 if not os
.access(dirname
, os
.W_OK
):
862 dialogs
.ErrorDialog(_('Directory "%s" is not writable') % \
863 dirname
, _('You do not have permission to create files in '
867 on_continue(0, file_path
)
869 def on_cancel(widget
):
872 dialog
= dialogs
.FileChooserDialog(title_text
=_('Save Image as...'),
873 action
=gtk
.FILE_CHOOSER_ACTION_SAVE
, buttons
=(gtk
.STOCK_CANCEL
,
874 gtk
.RESPONSE_CANCEL
, gtk
.STOCK_SAVE
, gtk
.RESPONSE_OK
),
875 default_response
=gtk
.RESPONSE_OK
,
876 current_folder
=gajim
.config
.get('last_save_dir'), on_response_ok
=on_ok
,
877 on_response_cancel
=on_cancel
)
879 dialog
.set_current_name(default_name
+ '.jpeg')
880 dialog
.connect('delete-event', lambda widget
, event
:
883 def on_bm_header_changed_state(widget
, event
):
884 widget
.set_state(gtk
.STATE_NORMAL
) #do not allow selected_state
886 def create_combobox(value_list
, selected_value
= None):
888 Value_list is [(label1, value1)]
890 liststore
= gtk
.ListStore(str, str)
891 combobox
= gtk
.ComboBox(liststore
)
892 cell
= gtk
.CellRendererText()
893 combobox
.pack_start(cell
, True)
894 combobox
.add_attribute(cell
, 'text', 0)
896 for value
in value_list
:
897 liststore
.append(value
)
898 if selected_value
== value
[1]:
899 i
= value_list
.index(value
)
901 combobox
.set_active(i
)
905 def create_list_multi(value_list
, selected_values
=None):
907 Value_list is [(label1, value1)]
909 liststore
= gtk
.ListStore(str, str)
910 treeview
= gtk
.TreeView(liststore
)
911 treeview
.get_selection().set_mode(gtk
.SELECTION_MULTIPLE
)
912 treeview
.set_headers_visible(False)
913 col
= gtk
.TreeViewColumn()
914 treeview
.append_column(col
)
915 cell
= gtk
.CellRendererText()
916 col
.pack_start(cell
, True)
917 col
.set_attributes(cell
, text
=0)
918 for value
in value_list
:
919 iter = liststore
.append(value
)
920 if value
[1] in selected_values
:
921 treeview
.get_selection().select_iter(iter)
925 def load_iconset(path
, pixbuf2
=None, transport
=False):
927 Load full iconset from the given path, and add pixbuf2 on top left of each
932 list_
= ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
935 list_
= ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
936 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
937 'closed', 'not in roster', 'muc_active', 'muc_inactive')
939 list_
= ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
940 'offline', 'error', 'requested', 'event', 'not in roster')
941 return _load_icon_list(list_
, path
, pixbuf2
)
943 def load_icon(icon_name
):
945 Load an icon from the iconset in 16x16
947 iconset
= gajim
.config
.get('iconset')
948 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16', '')
949 icon_list
= _load_icon_list([icon_name
], path
)
950 return icon_list
[icon_name
]
952 def load_mood_icon(icon_name
):
954 Load an icon from the mood iconset in 16x16
956 iconset
= gajim
.config
.get('mood_iconset')
957 path
= os
.path
.join(helpers
.get_mood_iconset_path(iconset
), '')
958 icon_list
= _load_icon_list([icon_name
], path
)
959 return icon_list
[icon_name
]
961 def load_activity_icon(category
, activity
= None):
963 Load an icon from the activity iconset in 16x16
965 iconset
= gajim
.config
.get('activity_iconset')
966 path
= os
.path
.join(helpers
.get_activity_iconset_path(iconset
),
969 activity
= 'category'
970 icon_list
= _load_icon_list([activity
], path
)
971 return icon_list
[activity
]
973 def load_icons_meta():
975 Load and return - AND + small icons to put on top left of an icon for meta
978 iconset
= gajim
.config
.get('iconset')
979 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
980 # try to find opened_meta.png file, else opened.png else nopixbuf merge
981 path_opened
= os
.path
.join(path
, 'opened_meta.png')
982 if not os
.path
.isfile(path_opened
):
983 path_opened
= os
.path
.join(path
, 'opened.png')
984 if os
.path
.isfile(path_opened
):
985 pixo
= gtk
.gdk
.pixbuf_new_from_file(path_opened
)
988 # Same thing for closed
989 path_closed
= os
.path
.join(path
, 'opened_meta.png')
990 if not os
.path
.isfile(path_closed
):
991 path_closed
= os
.path
.join(path
, 'closed.png')
992 if os
.path
.isfile(path_closed
):
993 pixc
= gtk
.gdk
.pixbuf_new_from_file(path_closed
)
998 def _load_icon_list(icons_list
, path
, pixbuf2
= None):
1000 Load icons in icons_list from the given path, and add pixbuf2 on top left of
1004 for icon
in icons_list
:
1005 # try to open a pixfile with the correct method
1006 icon_file
= icon
.replace(' ', '_')
1008 files
.append(path
+ icon_file
+ '.gif')
1009 files
.append(path
+ icon_file
+ '.png')
1013 for file_
in files
: # loop seeking for either gif or png
1014 if os
.path
.exists(file_
):
1015 image
.set_from_file(file_
)
1016 if pixbuf2
and image
.get_storage_type() == gtk
.IMAGE_PIXBUF
:
1017 # add pixbuf2 on top-left corner of image
1018 pixbuf1
= image
.get_pixbuf()
1019 pixbuf2
.composite(pixbuf1
, 0, 0,
1020 pixbuf2
.get_property('width'),
1021 pixbuf2
.get_property('height'), 0, 0, 1.0, 1.0,
1022 gtk
.gdk
.INTERP_NEAREST
, 255)
1023 image
.set_from_pixbuf(pixbuf1
)
1027 def make_jabber_state_images():
1029 Initialize jabber_state_images dictionary
1031 iconset
= gajim
.config
.get('iconset')
1033 if helpers
.get_iconset_path(iconset
):
1034 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
1035 if not os
.path
.exists(path
):
1036 iconset
= gajim
.config
.DEFAULT_ICONSET
1037 gajim
.config
.set('iconset', iconset
)
1039 iconset
= gajim
.config
.DEFAULT_ICONSET
1040 gajim
.config
.set('iconset', iconset
)
1042 iconset
= gajim
.config
.DEFAULT_ICONSET
1043 gajim
.config
.set('iconset', iconset
)
1045 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '32x32')
1046 gajim
.interface
.jabber_state_images
['32'] = load_iconset(path
)
1048 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
1049 gajim
.interface
.jabber_state_images
['16'] = load_iconset(path
)
1051 pixo
, pixc
= load_icons_meta()
1052 gajim
.interface
.jabber_state_images
['opened'] = load_iconset(path
, pixo
)
1053 gajim
.interface
.jabber_state_images
['closed'] = load_iconset(path
, pixc
)
1055 def reload_jabber_state_images():
1056 make_jabber_state_images()
1057 gajim
.interface
.roster
.update_jabber_state_images()
1059 def label_set_autowrap(widget
):
1061 Make labels automatically re-wrap if their containers are resized.
1062 Accepts label or container widgets
1064 if isinstance (widget
, gtk
.Container
):
1065 children
= widget
.get_children()
1066 for i
in xrange (len (children
)):
1067 label_set_autowrap(children
[i
])
1068 elif isinstance(widget
, gtk
.Label
):
1069 widget
.set_line_wrap(True)
1070 widget
.connect_after('size-allocate', __label_size_allocate
)
1072 def __label_size_allocate(widget
, allocation
):
1074 Callback which re-allocates the size of a label
1076 layout
= widget
.get_layout()
1078 lw_old
, lh_old
= layout
.get_size()
1079 # fixed width labels
1080 if lw_old
/pango
.SCALE
== allocation
.width
:
1083 # set wrap width to the pango.Layout of the labels ###
1084 layout
.set_width (allocation
.width
* pango
.SCALE
)
1085 lh
= layout
.get_size()[1]
1088 widget
.set_size_request (-1, lh
/ pango
.SCALE
)