[kepi] ability to use subkeys. Fixes #6051
[gajim.git] / src / gtkgui_helpers.py
blob49e4a41f7d3eaba128d2adc8d7ade9010a1773d2
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 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')
234 else:
235 gajim.config.set('openwith', 'custom')
237 def user_supports_xdg_open():
238 import os.path
239 return os.path.isfile('/usr/bin/xdg-open')
241 def user_runs_gnome():
242 return 'gnome-session' in get_running_processes()
244 def user_runs_kde():
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:
250 return True
251 return False
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')]
284 # list of processes
285 processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f \
286 in files]
288 return processes
289 return []
291 def move_window(window, x, y):
293 Move the window, but also check if out of screen
295 if x < 0:
296 x = 0
297 if y < 0:
298 y = 0
299 w, h = window.get_size()
300 if x + w > screen_w:
301 x = screen_w - w
302 if y + h > screen_h:
303 y = screen_h - h
304 window.move(x, y)
306 def resize_window(window, w, h):
308 Resize window, but also checks if huge window or negative values
310 if not w or not h:
311 return
312 if w > screen_w:
313 w = screen_w
314 if h > screen_h:
315 h = screen_h
316 window.resize(abs(w), abs(h))
318 class HashDigest:
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, '')
327 return id_
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])
337 else:
338 return False
340 return sa == oa and sd == od
342 def __ne__(self, other):
343 return not self == other
345 def __hash__(self):
346 return self.algo ^ self.digest
348 def __str__(self):
349 prettydigest = ''
350 for i in xrange(0, len(self.digest), 2):
351 prettydigest += self.digest[i:i + 2] + ':'
352 return prettydigest[:-1]
354 def __repr__(self):
355 return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self)))
357 class ServersXMLHandler(xml.sax.ContentHandler):
358 def __init__(self):
359 xml.sax.ContentHandler.__init__(self)
360 self.servers = []
362 def startElement(self, name, attributes):
363 if name == 'item':
364 if 'jid' in attributes.getNames():
365 self.servers.append(attributes.getValue('jid'))
367 def endElement(self, name):
368 pass
370 def parse_server_xml(path_to_file):
371 try:
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
385 messages or not
387 if gajim.config.get('use_urgency_hint'):
388 if unread_messages_no > 0:
389 window.props.urgency_hint = True
390 else:
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
399 type_ = 'svn'
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'
410 try:
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'
416 f.write(script)
417 f.close()
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)
426 type_ = 'install'
427 # always make it like '/usr/local/bin/gajim'
428 path_to_script = helpers.is_in_path(scriptname, True)
431 if want_type:
432 return path_to_script, type_
433 else:
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()
442 try:
443 pixbufloader.write(file_data)
444 pixbufloader.close()
445 pixbuf = pixbufloader.get_pixbuf()
446 except gobject.GError: # 'unknown image format'
447 pixbufloader.close()
448 pixbuf = None
449 if want_type:
450 return None, None
451 else:
452 return None
454 if want_type:
455 typ = pixbufloader.get_format()['name']
456 return pixbuf, typ
457 else:
458 return pixbuf
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)
464 return cursor
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')
477 if prop is not None:
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
485 virtual desktop
487 NOTE: Window is a GDK window.
489 if os.name == 'nt':
490 return False
492 root_window = gtk.gdk.screen_get_default().get_root_window()
493 # current user's vd
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
505 window.present()
506 return True
507 return False
509 def file_is_locked(path_to_file):
511 Return True if file is locked
513 NOTE: Windows only.
515 if os.name != 'nt': # just in case
516 return
518 if not HAS_PYWIN32:
519 return
521 secur_att = pywintypes.SECURITY_ATTRIBUTES()
522 secur_att.Initialize()
524 try:
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
530 secur_att,
531 win32con.OPEN_EXISTING, # existing file only
532 win32con.FILE_ATTRIBUTE_NORMAL, # normal file
533 0 # no attr. template
535 except pywintypes.error:
536 return True
537 else: # in case all went ok, close file handle (go to hell WinAPI)
538 hfile.Close()
539 return False
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
547 if selected:
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
552 else:
553 state = gtk.STATE_NORMAL
554 bg = style.base[state]
555 fg = style.text[state]
557 p = 0.3 # background
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:
573 return None
575 # Pixbuf size
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)
583 if ratio > 1:
584 w = width
585 h = int(w / ratio)
586 else:
587 h = height
588 w = int(h * ratio)
589 scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER)
590 return scaled_buf
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
595 shown)
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
605 return None
607 if any(jid in gajim.contacts.get_gc_list(acc) for acc in \
608 gajim.contacts.get_accounts()):
609 is_groupchat_contact = True
610 else:
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'
619 else:
620 path = os.path.join(gajim.VCARD_PATH, puny_jid)
621 local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
622 '_local'
623 if use_local:
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()
629 avatar_file.close()
630 return get_pixbuf_from_data(avatar_data)
632 if not os.path.isfile(path):
633 return 'ask'
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
638 return 'ask'
639 if 'PHOTO' not in vcard_dict:
640 return None
641 pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0]
642 return pixbuf
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.
651 return month + 1
653 def make_python_month_gtk_month(month):
654 return month - 1
656 def make_color_string(color):
658 Create #aabbcc color string from gtk color
660 col = '#'
661 for i in ('red', 'green', 'blue'):
662 h = hex(getattr(color, i) / (16*16)).split('x')[1]
663 if len(h) == 1:
664 h = '0' + h
665 col += h
666 return col
668 def make_pixbuf_grayscale(pixbuf):
669 pixbuf2 = pixbuf.copy()
670 pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False)
671 return pixbuf2
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
680 if jid:
681 # we want an avatar
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)
706 else:
707 for file_path in file_paths:
708 try:
709 file_path = file_path.decode(sys.getfilesystemencoding())
710 except Exception:
711 try:
712 file_path = file_path.decode('utf-8')
713 except Exception:
714 pass
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')
727 else:
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:
736 if typ == 'svn':
737 command = path_to_gajim_script + ' handle_uri %s'
738 else: # 'installed'
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',
744 command)
745 client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal',
746 False)
748 # setting for KDE
749 if path_to_kde_file is not None: # user has run kde at least once
750 try:
751 f = open(path_to_kde_file, 'a')
752 f.write('''\
753 [Protocol]
754 exec=%s "%%u"
755 protocol=xmpp
756 input=none
757 output=none
758 helper=true
759 listing=false
760 reading=false
761 writing=false
762 makedir=false
763 deleting=false
764 icon=gajim
765 Description=xmpp
766 ''' % command)
767 f.close()
768 except IOError:
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)
774 try:
775 import gconf
776 # in try because daemon may not be there
777 client = gconf.client_get_default()
778 except Exception:
779 return
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
784 we_set = True
785 elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file):
786 # only the first time (KDE)
787 we_set = True
788 else:
789 we_set = False
791 if we_set:
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 '
798 'client on startup')
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
809 accelerators
811 return s.replace('_', '__')
813 def get_state_image_from_file_path_show(file_path, show):
814 state_file = show.replace(' ', '_')
815 files = []
816 files.append(os.path.join(file_path, state_file + '.png'))
817 files.append(os.path.join(file_path, state_file + '.gif'))
818 image = gtk.Image()
819 image.set_from_pixbuf(None)
820 for file_ in files:
821 if os.path.exists(file_):
822 image.set_from_file(file_)
823 break
825 return image
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
834 return event.button
836 def destroy_widget(widget):
837 widget.destroy()
839 def on_avatar_save_as_menuitem_activate(widget, jid, default_name=''):
840 def on_continue(response, file_path):
841 if response < 0:
842 return
843 pixbuf = get_avatar_pixbuf_from_cache(jid)
844 extension = os.path.splitext(file_path)[1]
845 if not extension:
846 # Silently save as Jpeg image
847 image_format = 'jpeg'
848 file_path += '.jpeg'
849 elif extension == 'jpg':
850 image_format = 'jpeg'
851 else:
852 image_format = extension[1:] # remove leading dot
854 # Save image
855 try:
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):
860 os.remove(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))
869 else:
870 dialog.destroy()
872 def on_ok(widget):
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.'))
882 return
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)
888 else:
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 '
893 'this directory.'))
894 return
896 on_continue(0, file_path)
898 def on_cancel(widget):
899 dialog.destroy()
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:
910 on_cancel(widget))
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)
924 i = -1
925 for value in value_list:
926 liststore.append(value)
927 if selected_value == value[1]:
928 i = value_list.index(value)
929 if i > -1:
930 combobox.set_active(i)
931 combobox.show_all()
932 return combobox
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)
951 treeview.show_all()
952 return treeview
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
957 static images
959 path += '/'
960 if transport:
961 list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
962 'not in roster')
963 else:
964 list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
965 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
966 'closed', 'not in roster', 'muc_active', 'muc_inactive')
967 if pixbuf2:
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),
996 category, '')
997 if activity is None:
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
1005 contacts
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)
1015 else:
1016 pixo = None
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)
1023 else:
1024 pixc = None
1025 return pixo, pixc
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
1030 each static images
1032 imgs = {}
1033 for icon in icons_list:
1034 # try to open a pixfile with the correct method
1035 icon_file = icon.replace(' ', '_')
1036 files = []
1037 files.append(path + icon_file + '.gif')
1038 files.append(path + icon_file + '.png')
1039 image = gtk.Image()
1040 image.show()
1041 imgs[icon] = image
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)
1053 break
1054 return imgs
1056 def make_jabber_state_images():
1058 Initialize jabber_state_images dictionary
1060 iconset = gajim.config.get('iconset')
1061 if 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)
1067 else:
1068 iconset = gajim.config.DEFAULT_ICONSET
1069 gajim.config.set('iconset', iconset)
1070 else:
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:
1110 return
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]
1116 if lh_old != lh:
1117 widget.set_size_request (-1, lh / pango.SCALE)