use webbrowser module to open uri instead of using popen. Fixes #5751
[gajim.git] / src / gtkgui_helpers.py
blob5b62aca0dd9d1c2204b6aa7d7069058557f8d966
1 # -*- coding:utf-8 -*-
2 ## src/gtkgui_helpers.py
3 ##
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
31 import gtk
32 import glib
33 import gobject
34 import pango
35 import os
36 import sys
38 import logging
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):
48 try:
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):
54 try:
55 icon_info = gtk_icon_theme.lookup_icon(icon_name, size, 0)
56 if icon_info == None:
57 log.error('Icon not found: %s' % icon_name)
58 return ""
59 else:
60 return icon_info.get_filename()
61 except gobject.GError, e:
62 log.error("Unable to find icon %s: %s" % (icon_name, str(e)))
64 import vcard
65 import dialogs
68 HAS_PYWIN32 = True
69 if os.name == 'nt':
70 try:
71 import win32file
72 import win32con
73 import pywintypes
74 except ImportError:
75 HAS_PYWIN32 = False
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):
83 img = gtk.Image()
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)
96 if widget:
97 builder.add_objects_from_file(file_path, [widget])
98 else:
99 builder.add_from_file(file_path)
100 return builder
102 def get_completion_liststore(entry):
104 Create a completion model for entry widget completion list consists of
105 (Pixbuf, Text) rows
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)
120 return liststore
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
144 else:
145 # now move the menu above the button
146 y -= menu_height
148 # push_in is True so all the menuitems are always inside screen
149 push_in = True
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')
161 if font_prop_str:
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'
175 try:
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:
181 pass
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):
196 try:
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')
201 except Exception:
202 #we talk about file
203 print >> sys.stderr, _('Error: cannot open %s for reading') % \
204 xfce_config_file
206 elif os.path.exists(kde_config_file):
207 try:
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=')
211 line = line[start:]
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')
217 except Exception:
218 #we talk about file
219 print >> sys.stderr, _('Error: cannot open %s for reading') % \
220 kde_config_file
222 return None
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')]
255 # list of processes
256 processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f \
257 in files]
259 return processes
260 return []
262 def move_window(window, x, y):
264 Move the window, but also check if out of screen
266 if x < 0:
267 x = 0
268 if y < 0:
269 y = 0
270 w, h = window.get_size()
271 if x + w > screen_w:
272 x = screen_w - w
273 if y + h > screen_h:
274 y = screen_h - h
275 window.move(x, y)
277 def resize_window(window, w, h):
279 Resize window, but also checks if huge window or negative values
281 if not w or not h:
282 return
283 if w > screen_w:
284 w = screen_w
285 if h > screen_h:
286 h = screen_h
287 window.resize(abs(w), abs(h))
289 class HashDigest:
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, '')
298 return id_
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])
308 else:
309 return False
311 return sa == oa and sd == od
313 def __ne__(self, other):
314 return not self == other
316 def __hash__(self):
317 return self.algo ^ self.digest
319 def __str__(self):
320 prettydigest = ''
321 for i in xrange(0, len(self.digest), 2):
322 prettydigest += self.digest[i:i + 2] + ':'
323 return prettydigest[:-1]
325 def __repr__(self):
326 return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self)))
328 class ServersXMLHandler(xml.sax.ContentHandler):
329 def __init__(self):
330 xml.sax.ContentHandler.__init__(self)
331 self.servers = []
333 def startElement(self, name, attributes):
334 if name == 'item':
335 if 'jid' in attributes.getNames():
336 self.servers.append(attributes.getValue('jid'))
338 def endElement(self, name):
339 pass
341 def parse_server_xml(path_to_file):
342 try:
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
356 messages or not
358 if gajim.config.get('use_urgency_hint'):
359 if unread_messages_no > 0:
360 window.props.urgency_hint = True
361 else:
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
370 type_ = 'svn'
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'
381 try:
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'
387 f.write(script)
388 f.close()
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)
397 type_ = 'install'
398 # always make it like '/usr/local/bin/gajim'
399 path_to_script = helpers.is_in_path(scriptname, True)
402 if want_type:
403 return path_to_script, type_
404 else:
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()
413 try:
414 pixbufloader.write(file_data)
415 pixbufloader.close()
416 pixbuf = pixbufloader.get_pixbuf()
417 except gobject.GError: # 'unknown image format'
418 pixbufloader.close()
419 pixbuf = None
420 if want_type:
421 return None, None
422 else:
423 return None
425 if want_type:
426 typ = pixbufloader.get_format()['name']
427 return pixbuf, typ
428 else:
429 return pixbuf
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)
435 return cursor
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')
448 if prop is not None:
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
456 virtual desktop
458 NOTE: Window is a GDK window.
460 if os.name == 'nt':
461 return False
463 root_window = gtk.gdk.screen_get_default().get_root_window()
464 # current user's vd
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
476 window.present()
477 return True
478 return False
480 def file_is_locked(path_to_file):
482 Return True if file is locked
484 NOTE: Windows only.
486 if os.name != 'nt': # just in case
487 return
489 if not HAS_PYWIN32:
490 return
492 secur_att = pywintypes.SECURITY_ATTRIBUTES()
493 secur_att.Initialize()
495 try:
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
501 secur_att,
502 win32con.OPEN_EXISTING, # existing file only
503 win32con.FILE_ATTRIBUTE_NORMAL, # normal file
504 0 # no attr. template
506 except pywintypes.error:
507 return True
508 else: # in case all went ok, close file handle (go to hell WinAPI)
509 hfile.Close()
510 return False
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
518 if selected:
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
523 else:
524 state = gtk.STATE_NORMAL
525 bg = style.base[state]
526 fg = style.text[state]
528 p = 0.3 # background
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:
544 return None
546 # Pixbuf size
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)
554 if ratio > 1:
555 w = width
556 h = int(w / ratio)
557 else:
558 h = height
559 w = int(h * ratio)
560 scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
561 return scaled_buf
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
566 shown)
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
576 return None
578 if any(jid in gajim.contacts.get_gc_list(acc) for acc in \
579 gajim.contacts.get_accounts()):
580 is_groupchat_contact = True
581 else:
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'
590 else:
591 path = os.path.join(gajim.VCARD_PATH, puny_jid)
592 local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
593 '_local'
594 if use_local:
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()
600 avatar_file.close()
601 return get_pixbuf_from_data(avatar_data)
603 if not os.path.isfile(path):
604 return 'ask'
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
609 return 'ask'
610 if 'PHOTO' not in vcard_dict:
611 return None
612 pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0]
613 return pixbuf
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.
622 return month + 1
624 def make_python_month_gtk_month(month):
625 return month - 1
627 def make_color_string(color):
629 Create #aabbcc color string from gtk color
631 col = '#'
632 for i in ('red', 'green', 'blue'):
633 h = hex(getattr(color, i) / (16*16)).split('x')[1]
634 if len(h) == 1:
635 h = '0' + h
636 col += h
637 return col
639 def make_pixbuf_grayscale(pixbuf):
640 pixbuf2 = pixbuf.copy()
641 pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False)
642 return pixbuf2
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
651 if jid:
652 # we want an avatar
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)
677 else:
678 for file_path in file_paths:
679 try:
680 file_path = file_path.decode(sys.getfilesystemencoding())
681 except Exception:
682 try:
683 file_path = file_path.decode('utf-8')
684 except Exception:
685 pass
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')
698 else:
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:
707 if typ == 'svn':
708 command = path_to_gajim_script + ' handle_uri %s'
709 else: # 'installed'
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',
715 command)
716 client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal',
717 False)
719 # setting for KDE
720 if path_to_kde_file is not None: # user has run kde at least once
721 try:
722 f = open(path_to_kde_file, 'a')
723 f.write('''\
724 [Protocol]
725 exec=%s "%%u"
726 protocol=xmpp
727 input=none
728 output=none
729 helper=true
730 listing=false
731 reading=false
732 writing=false
733 makedir=false
734 deleting=false
735 icon=gajim
736 Description=xmpp
737 ''' % command)
738 f.close()
739 except IOError:
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)
745 try:
746 import gconf
747 # in try because daemon may not be there
748 client = gconf.client_get_default()
749 except Exception:
750 return
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
755 we_set = True
756 elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file):
757 # only the first time (KDE)
758 we_set = True
759 else:
760 we_set = False
762 if we_set:
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 '
769 'client on startup')
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
780 accelerators
782 return s.replace('_', '__')
784 def get_state_image_from_file_path_show(file_path, show):
785 state_file = show.replace(' ', '_')
786 files = []
787 files.append(os.path.join(file_path, state_file + '.png'))
788 files.append(os.path.join(file_path, state_file + '.gif'))
789 image = gtk.Image()
790 image.set_from_pixbuf(None)
791 for file_ in files:
792 if os.path.exists(file_):
793 image.set_from_file(file_)
794 break
796 return image
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
805 return event.button
807 def destroy_widget(widget):
808 widget.destroy()
810 def on_avatar_save_as_menuitem_activate(widget, jid, default_name=''):
811 def on_continue(response, file_path):
812 if response < 0:
813 return
814 pixbuf = get_avatar_pixbuf_from_cache(jid)
815 extension = os.path.splitext(file_path)[1]
816 if not extension:
817 # Silently save as Jpeg image
818 image_format = 'jpeg'
819 file_path += '.jpeg'
820 elif extension == 'jpg':
821 image_format = 'jpeg'
822 else:
823 image_format = extension[1:] # remove leading dot
825 # Save image
826 try:
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):
831 os.remove(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))
840 else:
841 dialog.destroy()
843 def on_ok(widget):
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.'))
853 return
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)
859 else:
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 '
864 'this directory.'))
865 return
867 on_continue(0, file_path)
869 def on_cancel(widget):
870 dialog.destroy()
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:
881 on_cancel(widget))
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)
895 i = -1
896 for value in value_list:
897 liststore.append(value)
898 if selected_value == value[1]:
899 i = value_list.index(value)
900 if i > -1:
901 combobox.set_active(i)
902 combobox.show_all()
903 return combobox
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)
922 treeview.show_all()
923 return treeview
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
928 static images
930 path += '/'
931 if transport:
932 list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
933 'not in roster')
934 else:
935 list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
936 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
937 'closed', 'not in roster', 'muc_active', 'muc_inactive')
938 if pixbuf2:
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),
967 category, '')
968 if activity is None:
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
976 contacts
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)
986 else:
987 pixo = None
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)
994 else:
995 pixc = None
996 return pixo, pixc
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
1001 each static images
1003 imgs = {}
1004 for icon in icons_list:
1005 # try to open a pixfile with the correct method
1006 icon_file = icon.replace(' ', '_')
1007 files = []
1008 files.append(path + icon_file + '.gif')
1009 files.append(path + icon_file + '.png')
1010 image = gtk.Image()
1011 image.show()
1012 imgs[icon] = image
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)
1024 break
1025 return imgs
1027 def make_jabber_state_images():
1029 Initialize jabber_state_images dictionary
1031 iconset = gajim.config.get('iconset')
1032 if 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)
1038 else:
1039 iconset = gajim.config.DEFAULT_ICONSET
1040 gajim.config.set('iconset', iconset)
1041 else:
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:
1081 return
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]
1087 if lh_old != lh:
1088 widget.set_size_request (-1, lh / pango.SCALE)