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 autodetect_browser_mailer():
225 # recognize the environment and set appropriate browser/mailer
226 if user_supports_xdg_open():
227 gajim
.config
.set('openwith', 'xdg-open')
228 elif user_runs_gnome():
229 gajim
.config
.set('openwith', 'gnome-open')
230 elif user_runs_kde():
231 gajim
.config
.set('openwith', 'kfmclient exec')
232 elif user_runs_xfce():
233 gajim
.config
.set('openwith', 'exo-open')
235 gajim
.config
.set('openwith', 'custom')
237 def user_supports_xdg_open():
239 return os
.path
.isfile('/usr/bin/xdg-open')
241 def user_runs_gnome():
242 return 'gnome-session' in get_running_processes()
245 return 'startkde' in get_running_processes()
247 def user_runs_xfce():
248 procs
= get_running_processes()
249 if 'startxfce4' in procs
or 'xfce4-session' in procs
:
253 def get_running_processes():
255 Return running processes or None (if /proc does not exist)
257 if os
.path
.isdir('/proc'):
258 # under Linux: checking if 'gnome-session' or
259 # 'startkde' programs were run before gajim, by
260 # checking /proc (if it exists)
262 # if something is unclear, read `man proc`;
263 # if /proc exists, directories that have only numbers
264 # in their names contain data about processes.
265 # /proc/[xxx]/exe is a symlink to executable started
266 # as process number [xxx].
267 # filter out everything that we are not interested in:
268 files
= os
.listdir('/proc')
270 # files that doesn't have only digits in names...
271 files
= [f
for f
in files
if f
.isdigit()]
273 # files that aren't directories...
274 files
= [f
for f
in files
if os
.path
.isdir('/proc/' + f
)]
276 # processes owned by somebody not running gajim...
277 # (we check if we have access to that file)
278 files
= [f
for f
in files
if os
.access('/proc/' + f
+'/exe', os
.F_OK
)]
280 # be sure that /proc/[number]/exe is really a symlink
281 # to avoid TBs in incorrectly configured systems
282 files
= [f
for f
in files
if os
.path
.islink('/proc/' + f
+ '/exe')]
285 processes
= [os
.path
.basename(os
.readlink('/proc/' + f
+'/exe')) for f \
291 def move_window(window
, x
, y
):
293 Move the window, but also check if out of screen
299 w
, h
= window
.get_size()
306 def resize_window(window
, w
, h
):
308 Resize window, but also checks if huge window or negative values
316 window
.resize(abs(w
), abs(h
))
319 def __init__(self
, algo
, digest
):
320 self
.algo
= self
.cleanID(algo
)
321 self
.digest
= self
.cleanID(digest
)
323 def cleanID(self
, id_
):
324 id_
= id_
.strip().lower()
325 for strip
in (' :.-_'):
326 id_
= id_
.replace(strip
, '')
329 def __eq__(self
, other
):
330 sa
, sd
= self
.algo
, self
.digest
331 if isinstance(other
, self
.__class
__):
332 oa
, od
= other
.algo
, other
.digest
333 elif isinstance(other
, basestring
):
334 sa
, oa
, od
= None, None, self
.cleanID(other
)
335 elif isinstance(other
, tuple) and len(other
) == 2:
336 oa
, od
= self
.cleanID(other
[0]), self
.cleanID(other
[1])
340 return sa
== oa
and sd
== od
342 def __ne__(self
, other
):
343 return not self
== other
346 return self
.algo ^ self
.digest
350 for i
in xrange(0, len(self
.digest
), 2):
351 prettydigest
+= self
.digest
[i
:i
+ 2] + ':'
352 return prettydigest
[:-1]
355 return "%s(%s, %s)" % (self
.__class
__, repr(self
.algo
), repr(str(self
)))
357 class ServersXMLHandler(xml
.sax
.ContentHandler
):
359 xml
.sax
.ContentHandler
.__init
__(self
)
362 def startElement(self
, name
, attributes
):
364 if 'jid' in attributes
.getNames():
365 self
.servers
.append(attributes
.getValue('jid'))
367 def endElement(self
, name
):
370 def parse_server_xml(path_to_file
):
372 handler
= ServersXMLHandler()
373 xml
.sax
.parse(path_to_file
, handler
)
374 return handler
.servers
375 # handle exception if unable to open file
376 except IOError, message
:
377 print >> sys
.stderr
, _('Error reading file:'), message
378 # handle exception parsing file
379 except xml
.sax
.SAXParseException
, message
:
380 print >> sys
.stderr
, _('Error parsing file:'), message
382 def set_unset_urgency_hint(window
, unread_messages_no
):
384 Sets/unset urgency hint in window argument depending if we have unread
387 if gajim
.config
.get('use_urgency_hint'):
388 if unread_messages_no
> 0:
389 window
.props
.urgency_hint
= True
391 window
.props
.urgency_hint
= False
393 def get_abspath_for_script(scriptname
, want_type
= False):
395 Check if we are svn or normal user and return abspath to asked script if
396 want_type is True we return 'svn' or 'install'
398 if os
.path
.isdir('.svn'): # we are svn user
400 cwd
= os
.getcwd() # it's always ending with src
402 if scriptname
== 'gajim-remote':
403 path_to_script
= cwd
+ '/gajim-remote.py'
405 elif scriptname
== 'gajim':
406 script
= '#!/bin/sh\n' # the script we may create
407 script
+= 'cd %s' % cwd
408 path_to_script
= cwd
+ '/../scripts/gajim_sm_script'
411 if os
.path
.exists(path_to_script
):
412 os
.remove(path_to_script
)
414 f
= open(path_to_script
, 'w')
415 script
+= '\nexec python -OOt gajim.py $0 $@\n'
418 os
.chmod(path_to_script
, 0700)
419 except OSError: # do not traceback (could be a permission problem)
420 #we talk about a file here
421 s
= _('Could not write to %s. Session Management support will '
422 'not work') % path_to_script
423 print >> sys
.stderr
, s
425 else: # normal user (not svn user)
427 # always make it like '/usr/local/bin/gajim'
428 path_to_script
= helpers
.is_in_path(scriptname
, True)
432 return path_to_script
, type_
434 return path_to_script
436 def get_pixbuf_from_data(file_data
, want_type
= False):
438 Get image data and returns gtk.gdk.Pixbuf if want_type is True it also
439 returns 'jpeg', 'png' etc
441 pixbufloader
= gtk
.gdk
.PixbufLoader()
443 pixbufloader
.write(file_data
)
445 pixbuf
= pixbufloader
.get_pixbuf()
446 except gobject
.GError
: # 'unknown image format'
455 typ
= pixbufloader
.get_format()['name']
460 def get_invisible_cursor():
461 pixmap
= gtk
.gdk
.Pixmap(None, 1, 1, 1)
462 color
= gtk
.gdk
.Color()
463 cursor
= gtk
.gdk
.Cursor(pixmap
, pixmap
, color
, color
, 0, 0)
466 def get_current_desktop(window
):
468 Return the current virtual desktop for given window
470 NOTE: Window is a GDK window.
472 prop
= window
.property_get('_NET_CURRENT_DESKTOP')
473 if prop
is None: # it means it's normal window (not root window)
474 # so we look for it's current virtual desktop in another property
475 prop
= window
.property_get('_NET_WM_DESKTOP')
478 # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0]
479 current_virtual_desktop_no
= prop
[2][0]
480 return current_virtual_desktop_no
482 def possibly_move_window_in_current_desktop(window
):
484 Moves GTK window to current virtual desktop if it is not in the current
487 NOTE: Window is a GDK window.
492 root_window
= gtk
.gdk
.screen_get_default().get_root_window()
494 current_virtual_desktop_no
= get_current_desktop(root_window
)
496 # vd roster window is in
497 window_virtual_desktop
= get_current_desktop(window
.window
)
499 # if one of those is None, something went wrong and we cannot know
500 # VD info, just hide it (default action) and not show it afterwards
501 if None not in (window_virtual_desktop
, current_virtual_desktop_no
):
502 if current_virtual_desktop_no
!= window_virtual_desktop
:
503 # we are in another VD that the window was
504 # so show it in current VD
509 def file_is_locked(path_to_file
):
511 Return True if file is locked
515 if os
.name
!= 'nt': # just in case
521 secur_att
= pywintypes
.SECURITY_ATTRIBUTES()
522 secur_att
.Initialize()
525 # try make a handle for READING the file
526 hfile
= win32file
.CreateFile(
527 path_to_file
, # path to file
528 win32con
.GENERIC_READ
, # open for reading
529 0, # do not share with other proc
531 win32con
.OPEN_EXISTING
, # existing file only
532 win32con
.FILE_ATTRIBUTE_NORMAL
, # normal file
533 0 # no attr. template
535 except pywintypes
.error
:
537 else: # in case all went ok, close file handle (go to hell WinAPI)
541 def get_fade_color(treeview
, selected
, focused
):
543 Get a gdk color that is between foreground and background in 0.3
544 0.7 respectively colors of the cell for the given treeview
546 style
= treeview
.style
548 if focused
: # is the window focused?
549 state
= gtk
.STATE_SELECTED
550 else: # is it not? NOTE: many gtk themes change bg on this
551 state
= gtk
.STATE_ACTIVE
553 state
= gtk
.STATE_NORMAL
554 bg
= style
.base
[state
]
555 fg
= style
.text
[state
]
558 q
= 0.7 # foreground # p + q should do 1.0
559 return gtk
.gdk
.Color(int(bg
.red
*p
+ fg
.red
*q
),
560 int(bg
.green
*p
+ fg
.green
*q
),
561 int(bg
.blue
*p
+ fg
.blue
*q
))
563 def get_scaled_pixbuf(pixbuf
, kind
):
565 Return scaled pixbuf, keeping ratio etc or None kind is either "chat",
566 "roster", "notification", "tooltip", "vcard"
568 # resize to a width / height for the avatar not to have distortion
569 # (keep aspect ratio)
570 width
= gajim
.config
.get(kind
+ '_avatar_width')
571 height
= gajim
.config
.get(kind
+ '_avatar_height')
572 if width
< 1 or height
< 1:
576 pix_width
= pixbuf
.get_width()
577 pix_height
= pixbuf
.get_height()
578 # don't make avatars bigger than they are
579 if pix_width
< width
and pix_height
< height
:
580 return pixbuf
# we don't want to make avatar bigger
582 ratio
= float(pix_width
) / float(pix_height
)
589 scaled_buf
= pixbuf
.scale_simple(w
, h
, gtk
.gdk
.INTERP_HYPER
)
592 def get_avatar_pixbuf_from_cache(fjid
, use_local
=True):
594 Check if jid has cached avatar and if that avatar is valid image (can be
597 Returns None if there is no image in vcard/
598 Returns 'ask' if cached vcard should not be used (user changed his vcard, so
599 we have new sha) or if we don't have the vcard
601 jid
, nick
= gajim
.get_room_and_nick_from_fjid(fjid
)
602 if gajim
.config
.get('hide_avatar_of_transport') and\
603 gajim
.jid_is_transport(jid
):
604 # don't show avatar for the transport itself
607 if any(jid
in gajim
.contacts
.get_gc_list(acc
) for acc
in \
608 gajim
.contacts
.get_accounts()):
609 is_groupchat_contact
= True
611 is_groupchat_contact
= False
613 puny_jid
= helpers
.sanitize_filename(jid
)
614 if is_groupchat_contact
:
615 puny_nick
= helpers
.sanitize_filename(nick
)
616 path
= os
.path
.join(gajim
.VCARD_PATH
, puny_jid
, puny_nick
)
617 local_avatar_basepath
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
,
618 puny_nick
) + '_local'
620 path
= os
.path
.join(gajim
.VCARD_PATH
, puny_jid
)
621 local_avatar_basepath
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
) + \
624 for extension
in ('.png', '.jpeg'):
625 local_avatar_path
= local_avatar_basepath
+ extension
626 if os
.path
.isfile(local_avatar_path
):
627 avatar_file
= open(local_avatar_path
, 'rb')
628 avatar_data
= avatar_file
.read()
630 return get_pixbuf_from_data(avatar_data
)
632 if not os
.path
.isfile(path
):
635 vcard_dict
= gajim
.connections
.values()[0].get_cached_vcard(fjid
,
636 is_groupchat_contact
)
637 if not vcard_dict
: # This can happen if cached vcard is too old
639 if 'PHOTO' not in vcard_dict
:
641 pixbuf
= vcard
.get_avatar_pixbuf_encoded_mime(vcard_dict
['PHOTO'])[0]
644 def make_gtk_month_python_month(month
):
646 GTK starts counting months from 0, so January is 0 but Python's time start
647 from 1, so align to Python
649 NOTE: Month MUST be an integer.
653 def make_python_month_gtk_month(month
):
656 def make_color_string(color
):
658 Create #aabbcc color string from gtk color
661 for i
in ('red', 'green', 'blue'):
662 h
= hex(getattr(color
, i
) / (16*16)).split('x')[1]
668 def make_pixbuf_grayscale(pixbuf
):
669 pixbuf2
= pixbuf
.copy()
670 pixbuf
.saturate_and_pixelate(pixbuf2
, 0.0, False)
673 def get_path_to_generic_or_avatar(generic
, jid
= None, suffix
= None):
675 Choose between avatar image and default image
677 Returns full path to the avatar image if it exists, otherwise returns full
678 path to the image. generic must be with extension and suffix without
682 puny_jid
= helpers
.sanitize_filename(jid
)
683 path_to_file
= os
.path
.join(gajim
.AVATAR_PATH
, puny_jid
) + suffix
684 path_to_local_file
= path_to_file
+ '_local'
685 for extension
in ('.png', '.jpeg'):
686 path_to_local_file_full
= path_to_local_file
+ extension
687 if os
.path
.exists(path_to_local_file_full
):
688 return path_to_local_file_full
689 for extension
in ('.png', '.jpeg'):
690 path_to_file_full
= path_to_file
+ extension
691 if os
.path
.exists(path_to_file_full
):
692 return path_to_file_full
693 return os
.path
.abspath(generic
)
695 def decode_filechooser_file_paths(file_paths
):
697 Decode as UTF-8 under Windows and ask sys.getfilesystemencoding() in POSIX
698 file_paths MUST be LIST
700 file_paths_list
= list()
702 if os
.name
== 'nt': # decode as UTF-8 under Windows
703 for file_path
in file_paths
:
704 file_path
= file_path
.decode('utf8')
705 file_paths_list
.append(file_path
)
707 for file_path
in file_paths
:
709 file_path
= file_path
.decode(sys
.getfilesystemencoding())
712 file_path
= file_path
.decode('utf-8')
715 file_paths_list
.append(file_path
)
717 return file_paths_list
719 def possibly_set_gajim_as_xmpp_handler():
721 Register (by default only the first time) 'xmmp:' to Gajim
723 path_to_dot_kde
= os
.path
.expanduser('~/.kde')
724 if os
.path
.exists(path_to_dot_kde
):
725 path_to_kde_file
= os
.path
.join(path_to_dot_kde
,
726 'share/services/xmpp.protocol')
728 path_to_kde_file
= None
730 def set_gajim_as_xmpp_handler(is_checked
=None):
731 if is_checked
is not None:
732 # come from confirmation dialog
733 gajim
.config
.set('check_if_gajim_is_default', is_checked
)
734 path_to_gajim_script
, typ
= get_abspath_for_script('gajim-remote', True)
735 if path_to_gajim_script
:
737 command
= path_to_gajim_script
+ ' handle_uri %s'
739 command
= 'gajim-remote handle_uri %s'
741 # setting for GNOME/Gconf
742 client
.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True)
743 client
.set_string('/desktop/gnome/url-handlers/xmpp/command',
745 client
.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal',
749 if path_to_kde_file
is not None: # user has run kde at least once
751 f
= open(path_to_kde_file
, 'a')
769 log
.debug("I/O Error writing settings to %s",
770 repr(path_to_kde_file
), exc_info
=True)
771 else: # no gajim remote, stop ask user everytime
772 gajim
.config
.set('check_if_gajim_is_default', False)
776 # in try because daemon may not be there
777 client
= gconf
.client_get_default()
781 old_command
= client
.get_string('/desktop/gnome/url-handlers/xmpp/command')
782 if not old_command
or old_command
.endswith(' open_chat %s'):
783 # first time (GNOME/GCONF) or old Gajim version
785 elif path_to_kde_file
is not None and not os
.path
.exists(path_to_kde_file
):
786 # only the first time (KDE)
792 set_gajim_as_xmpp_handler()
793 elif old_command
and not old_command
.endswith(' handle_uri %s'):
794 # xmpp: is currently handled by another program, so ask the user
795 pritext
= _('Gajim is not the default Jabber client')
796 sectext
= _('Would you like to make Gajim the default Jabber client?')
797 checktext
= _('Always check to see if Gajim is the default Jabber '
799 def on_cancel(checked
):
800 gajim
.config
.set('check_if_gajim_is_default', checked
)
801 dlg
= dialogs
.ConfirmationDialogCheck(pritext
, sectext
, checktext
,
802 set_gajim_as_xmpp_handler
, on_cancel
)
803 if gajim
.config
.get('check_if_gajim_is_default'):
804 dlg
.checkbutton
.set_active(True)
806 def escape_underscore(s
):
808 Escape underlines to prevent them from being interpreted as keyboard
811 return s
.replace('_', '__')
813 def get_state_image_from_file_path_show(file_path
, show
):
814 state_file
= show
.replace(' ', '_')
816 files
.append(os
.path
.join(file_path
, state_file
+ '.png'))
817 files
.append(os
.path
.join(file_path
, state_file
+ '.gif'))
819 image
.set_from_pixbuf(None)
821 if os
.path
.exists(file_
):
822 image
.set_from_file(file_
)
827 def get_possible_button_event(event
):
829 Mouse or keyboard caused the event?
831 if event
.type == gtk
.gdk
.KEY_PRESS
:
832 return 0 # no event.button so pass 0
833 # BUTTON_PRESS event, so pass event.button
836 def destroy_widget(widget
):
839 def on_avatar_save_as_menuitem_activate(widget
, jid
, default_name
=''):
840 def on_continue(response
, file_path
):
843 pixbuf
= get_avatar_pixbuf_from_cache(jid
)
844 extension
= os
.path
.splitext(file_path
)[1]
846 # Silently save as Jpeg image
847 image_format
= 'jpeg'
849 elif extension
== 'jpg':
850 image_format
= 'jpeg'
852 image_format
= extension
[1:] # remove leading dot
856 pixbuf
.save(file_path
, image_format
)
857 except glib
.GError
, e
:
858 log
.debug('Error saving avatar: %s' % str(e
))
859 if os
.path
.exists(file_path
):
861 new_file_path
= '.'.join(file_path
.split('.')[:-1]) + '.jpeg'
862 def on_ok(file_path
, pixbuf
):
863 pixbuf
.save(file_path
, 'jpeg')
864 dialogs
.ConfirmationDialog(_('Extension not supported'),
865 _('Image cannot be saved in %(type)s format. Save as '
866 '%(new_filename)s?') % {'type': image_format
,
867 'new_filename': new_file_path
},
868 on_response_ok
= (on_ok
, new_file_path
, pixbuf
))
873 file_path
= dialog
.get_filename()
874 file_path
= decode_filechooser_file_paths((file_path
,))[0]
875 if os
.path
.exists(file_path
):
876 # check if we have write permissions
877 if not os
.access(file_path
, os
.W_OK
):
878 file_name
= os
.path
.basename(file_path
)
879 dialogs
.ErrorDialog(_('Cannot overwrite existing file "%s"') % \
880 file_name
, _('A file with this name already exists and you '
881 'do not have permission to overwrite it.'))
883 dialog2
= dialogs
.FTOverwriteConfirmationDialog(
884 _('This file already exists'), _('What do you want to do?'),
885 propose_resume
=False, on_response
=(on_continue
, file_path
))
886 dialog2
.set_transient_for(dialog
)
887 dialog2
.set_destroy_with_parent(True)
889 dirname
= os
.path
.dirname(file_path
)
890 if not os
.access(dirname
, os
.W_OK
):
891 dialogs
.ErrorDialog(_('Directory "%s" is not writable') % \
892 dirname
, _('You do not have permission to create files in '
896 on_continue(0, file_path
)
898 def on_cancel(widget
):
901 dialog
= dialogs
.FileChooserDialog(title_text
=_('Save Image as...'),
902 action
=gtk
.FILE_CHOOSER_ACTION_SAVE
, buttons
=(gtk
.STOCK_CANCEL
,
903 gtk
.RESPONSE_CANCEL
, gtk
.STOCK_SAVE
, gtk
.RESPONSE_OK
),
904 default_response
=gtk
.RESPONSE_OK
,
905 current_folder
=gajim
.config
.get('last_save_dir'), on_response_ok
=on_ok
,
906 on_response_cancel
=on_cancel
)
908 dialog
.set_current_name(default_name
+ '.jpeg')
909 dialog
.connect('delete-event', lambda widget
, event
:
912 def on_bm_header_changed_state(widget
, event
):
913 widget
.set_state(gtk
.STATE_NORMAL
) #do not allow selected_state
915 def create_combobox(value_list
, selected_value
= None):
917 Value_list is [(label1, value1)]
919 liststore
= gtk
.ListStore(str, str)
920 combobox
= gtk
.ComboBox(liststore
)
921 cell
= gtk
.CellRendererText()
922 combobox
.pack_start(cell
, True)
923 combobox
.add_attribute(cell
, 'text', 0)
925 for value
in value_list
:
926 liststore
.append(value
)
927 if selected_value
== value
[1]:
928 i
= value_list
.index(value
)
930 combobox
.set_active(i
)
934 def create_list_multi(value_list
, selected_values
=None):
936 Value_list is [(label1, value1)]
938 liststore
= gtk
.ListStore(str, str)
939 treeview
= gtk
.TreeView(liststore
)
940 treeview
.get_selection().set_mode(gtk
.SELECTION_MULTIPLE
)
941 treeview
.set_headers_visible(False)
942 col
= gtk
.TreeViewColumn()
943 treeview
.append_column(col
)
944 cell
= gtk
.CellRendererText()
945 col
.pack_start(cell
, True)
946 col
.set_attributes(cell
, text
=0)
947 for value
in value_list
:
948 iter = liststore
.append(value
)
949 if value
[1] in selected_values
:
950 treeview
.get_selection().select_iter(iter)
954 def load_iconset(path
, pixbuf2
=None, transport
=False):
956 Load full iconset from the given path, and add pixbuf2 on top left of each
961 list_
= ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
964 list_
= ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
965 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
966 'closed', 'not in roster', 'muc_active', 'muc_inactive')
968 list_
= ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
969 'offline', 'error', 'requested', 'event', 'not in roster')
970 return _load_icon_list(list_
, path
, pixbuf2
)
972 def load_icon(icon_name
):
974 Load an icon from the iconset in 16x16
976 iconset
= gajim
.config
.get('iconset')
977 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16', '')
978 icon_list
= _load_icon_list([icon_name
], path
)
979 return icon_list
[icon_name
]
981 def load_mood_icon(icon_name
):
983 Load an icon from the mood iconset in 16x16
985 iconset
= gajim
.config
.get('mood_iconset')
986 path
= os
.path
.join(helpers
.get_mood_iconset_path(iconset
), '')
987 icon_list
= _load_icon_list([icon_name
], path
)
988 return icon_list
[icon_name
]
990 def load_activity_icon(category
, activity
= None):
992 Load an icon from the activity iconset in 16x16
994 iconset
= gajim
.config
.get('activity_iconset')
995 path
= os
.path
.join(helpers
.get_activity_iconset_path(iconset
),
998 activity
= 'category'
999 icon_list
= _load_icon_list([activity
], path
)
1000 return icon_list
[activity
]
1002 def load_icons_meta():
1004 Load and return - AND + small icons to put on top left of an icon for meta
1007 iconset
= gajim
.config
.get('iconset')
1008 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
1009 # try to find opened_meta.png file, else opened.png else nopixbuf merge
1010 path_opened
= os
.path
.join(path
, 'opened_meta.png')
1011 if not os
.path
.isfile(path_opened
):
1012 path_opened
= os
.path
.join(path
, 'opened.png')
1013 if os
.path
.isfile(path_opened
):
1014 pixo
= gtk
.gdk
.pixbuf_new_from_file(path_opened
)
1017 # Same thing for closed
1018 path_closed
= os
.path
.join(path
, 'opened_meta.png')
1019 if not os
.path
.isfile(path_closed
):
1020 path_closed
= os
.path
.join(path
, 'closed.png')
1021 if os
.path
.isfile(path_closed
):
1022 pixc
= gtk
.gdk
.pixbuf_new_from_file(path_closed
)
1027 def _load_icon_list(icons_list
, path
, pixbuf2
= None):
1029 Load icons in icons_list from the given path, and add pixbuf2 on top left of
1033 for icon
in icons_list
:
1034 # try to open a pixfile with the correct method
1035 icon_file
= icon
.replace(' ', '_')
1037 files
.append(path
+ icon_file
+ '.gif')
1038 files
.append(path
+ icon_file
+ '.png')
1042 for file_
in files
: # loop seeking for either gif or png
1043 if os
.path
.exists(file_
):
1044 image
.set_from_file(file_
)
1045 if pixbuf2
and image
.get_storage_type() == gtk
.IMAGE_PIXBUF
:
1046 # add pixbuf2 on top-left corner of image
1047 pixbuf1
= image
.get_pixbuf()
1048 pixbuf2
.composite(pixbuf1
, 0, 0,
1049 pixbuf2
.get_property('width'),
1050 pixbuf2
.get_property('height'), 0, 0, 1.0, 1.0,
1051 gtk
.gdk
.INTERP_NEAREST
, 255)
1052 image
.set_from_pixbuf(pixbuf1
)
1056 def make_jabber_state_images():
1058 Initialize jabber_state_images dictionary
1060 iconset
= gajim
.config
.get('iconset')
1062 if helpers
.get_iconset_path(iconset
):
1063 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
1064 if not os
.path
.exists(path
):
1065 iconset
= gajim
.config
.DEFAULT_ICONSET
1066 gajim
.config
.set('iconset', iconset
)
1068 iconset
= gajim
.config
.DEFAULT_ICONSET
1069 gajim
.config
.set('iconset', iconset
)
1071 iconset
= gajim
.config
.DEFAULT_ICONSET
1072 gajim
.config
.set('iconset', iconset
)
1074 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '32x32')
1075 gajim
.interface
.jabber_state_images
['32'] = load_iconset(path
)
1077 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
1078 gajim
.interface
.jabber_state_images
['16'] = load_iconset(path
)
1080 pixo
, pixc
= load_icons_meta()
1081 gajim
.interface
.jabber_state_images
['opened'] = load_iconset(path
, pixo
)
1082 gajim
.interface
.jabber_state_images
['closed'] = load_iconset(path
, pixc
)
1084 def reload_jabber_state_images():
1085 make_jabber_state_images()
1086 gajim
.interface
.roster
.update_jabber_state_images()
1088 def label_set_autowrap(widget
):
1090 Make labels automatically re-wrap if their containers are resized.
1091 Accepts label or container widgets
1093 if isinstance (widget
, gtk
.Container
):
1094 children
= widget
.get_children()
1095 for i
in xrange (len (children
)):
1096 label_set_autowrap(children
[i
])
1097 elif isinstance(widget
, gtk
.Label
):
1098 widget
.set_line_wrap(True)
1099 widget
.connect_after('size-allocate', __label_size_allocate
)
1101 def __label_size_allocate(widget
, allocation
):
1103 Callback which re-allocates the size of a label
1105 layout
= widget
.get_layout()
1107 lw_old
, lh_old
= layout
.get_size()
1108 # fixed width labels
1109 if lw_old
/pango
.SCALE
== allocation
.width
:
1112 # set wrap width to the pango.Layout of the labels ###
1113 layout
.set_width (allocation
.width
* pango
.SCALE
)
1114 lh
= layout
.get_size()[1]
1117 widget
.set_size_request (-1, lh
/ pango
.SCALE
)