| 1 | ## gtkgui_helpers.py |
|---|
| 2 | ## |
|---|
| 3 | ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 4 | ## Copyright (C) 2004-2005 Vincent Hanquez <tab@snarc.org> |
|---|
| 5 | ## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org> |
|---|
| 6 | ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com> |
|---|
| 7 | ## Copyright (C) 2005 Travis Shirk <travis@pobox.com> |
|---|
| 8 | ## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za> |
|---|
| 9 | ## |
|---|
| 10 | ## This program is free software; you can redistribute it and/or modify |
|---|
| 11 | ## it under the terms of the GNU General Public License as published |
|---|
| 12 | ## by the Free Software Foundation; version 2 only. |
|---|
| 13 | ## |
|---|
| 14 | ## This program is distributed in the hope that it will be useful, |
|---|
| 15 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 16 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 17 | ## GNU General Public License for more details. |
|---|
| 18 | ## |
|---|
| 19 | |
|---|
| 20 | import xml.sax.saxutils |
|---|
| 21 | import gtk |
|---|
| 22 | import gobject |
|---|
| 23 | import pango |
|---|
| 24 | import os |
|---|
| 25 | import sys |
|---|
| 26 | |
|---|
| 27 | import vcard |
|---|
| 28 | |
|---|
| 29 | |
|---|
| 30 | HAS_PYWIN32 = True |
|---|
| 31 | if os.name == 'nt': |
|---|
| 32 | try: |
|---|
| 33 | import win32file |
|---|
| 34 | import win32con |
|---|
| 35 | import pywintypes |
|---|
| 36 | except ImportError: |
|---|
| 37 | HAS_PYWIN32 = False |
|---|
| 38 | |
|---|
| 39 | from common import i18n |
|---|
| 40 | i18n.init() |
|---|
| 41 | _ = i18n._ |
|---|
| 42 | from common import gajim |
|---|
| 43 | from common import helpers |
|---|
| 44 | |
|---|
| 45 | screen_w = gtk.gdk.screen_width() |
|---|
| 46 | screen_h = gtk.gdk.screen_height() |
|---|
| 47 | |
|---|
| 48 | GLADE_DIR = os.path.join('..', 'data', 'glade') |
|---|
| 49 | def get_glade(file_name, root = None): |
|---|
| 50 | file_path = os.path.join(GLADE_DIR, file_name) |
|---|
| 51 | return gtk.glade.XML(file_path, root=root, domain=i18n.APP) |
|---|
| 52 | |
|---|
| 53 | def popup_emoticons_under_button(menu, button, parent_win): |
|---|
| 54 | ''' pops emoticons menu under button, which is in parent_win''' |
|---|
| 55 | window_x1, window_y1 = parent_win.get_origin() |
|---|
| 56 | def position_menu_under_button(menu): |
|---|
| 57 | # inline function, which will not keep refs, when used as CB |
|---|
| 58 | button_x, button_y = button.allocation.x, button.allocation.y |
|---|
| 59 | |
|---|
| 60 | # now convert them to X11-relative |
|---|
| 61 | window_x, window_y = window_x1, window_y1 |
|---|
| 62 | x = window_x + button_x |
|---|
| 63 | y = window_y + button_y |
|---|
| 64 | |
|---|
| 65 | menu_width, menu_height = menu.size_request() |
|---|
| 66 | |
|---|
| 67 | ## should we pop down or up? |
|---|
| 68 | if (y + button.allocation.height + menu_height |
|---|
| 69 | < gtk.gdk.screen_height()): |
|---|
| 70 | # now move the menu below the button |
|---|
| 71 | y += button.allocation.height |
|---|
| 72 | else: |
|---|
| 73 | # now move the menu above the button |
|---|
| 74 | y -= menu_height |
|---|
| 75 | |
|---|
| 76 | # push_in is True so all the menuitems are always inside screen |
|---|
| 77 | push_in = True |
|---|
| 78 | return (x, y, push_in) |
|---|
| 79 | |
|---|
| 80 | menu.popup(None, None, position_menu_under_button, 1, 0) |
|---|
| 81 | |
|---|
| 82 | def get_theme_font_for_option(theme, option): |
|---|
| 83 | '''return string description of the font, stored in |
|---|
| 84 | theme preferences''' |
|---|
| 85 | font_name = gajim.config.get_per('themes', theme, option) |
|---|
| 86 | font_desc = pango.FontDescription() |
|---|
| 87 | font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs') |
|---|
| 88 | if font_prop_str: |
|---|
| 89 | if font_prop_str.find('B') != -1: |
|---|
| 90 | font_desc.set_weight(pango.WEIGHT_BOLD) |
|---|
| 91 | if font_prop_str.find('I') != -1: |
|---|
| 92 | font_desc.set_style(pango.STYLE_ITALIC) |
|---|
| 93 | fd = pango.FontDescription(font_name) |
|---|
| 94 | fd.merge(font_desc, True) |
|---|
| 95 | return fd.to_string() |
|---|
| 96 | |
|---|
| 97 | def get_default_font(): |
|---|
| 98 | '''Get the desktop setting for application font |
|---|
| 99 | first check for GNOME, then XFCE and last KDE |
|---|
| 100 | it returns None on failure or else a string 'Font Size' ''' |
|---|
| 101 | |
|---|
| 102 | try: |
|---|
| 103 | import gconf |
|---|
| 104 | # in try because daemon may not be there |
|---|
| 105 | client = gconf.client_get_default() |
|---|
| 106 | |
|---|
| 107 | return helpers.ensure_unicode_string( |
|---|
| 108 | client.get_string('/desktop/gnome/interface/font_name')) |
|---|
| 109 | except: |
|---|
| 110 | pass |
|---|
| 111 | |
|---|
| 112 | # try to get xfce default font |
|---|
| 113 | # Xfce 4.2 adopts freedesktop.org's Base Directory Specification |
|---|
| 114 | # see http://www.xfce.org/~benny/xfce/file-locations.html |
|---|
| 115 | # and http://freedesktop.org/Standards/basedir-spec |
|---|
| 116 | xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '') |
|---|
| 117 | if xdg_config_home == '': |
|---|
| 118 | xdg_config_home = os.path.expanduser('~/.config') # default |
|---|
| 119 | xfce_config_file = os.path.join(xdg_config_home, 'xfce4/mcs_settings/gtk.xml') |
|---|
| 120 | |
|---|
| 121 | kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals') |
|---|
| 122 | |
|---|
| 123 | if os.path.exists(xfce_config_file): |
|---|
| 124 | try: |
|---|
| 125 | for line in file(xfce_config_file): |
|---|
| 126 | if line.find('name="Gtk/FontName"') != -1: |
|---|
| 127 | start = line.find('value="') + 7 |
|---|
| 128 | return helpers.ensure_unicode_string( |
|---|
| 129 | line[start:line.find('"', start)]) |
|---|
| 130 | except: |
|---|
| 131 | #we talk about file |
|---|
| 132 | print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file |
|---|
| 133 | |
|---|
| 134 | elif os.path.exists(kde_config_file): |
|---|
| 135 | try: |
|---|
| 136 | for line in file(kde_config_file): |
|---|
| 137 | if line.find('font=') == 0: # font=Verdana,9,other_numbers |
|---|
| 138 | start = 5 # 5 is len('font=') |
|---|
| 139 | line = line[start:] |
|---|
| 140 | values = line.split(',') |
|---|
| 141 | font_name = values[0] |
|---|
| 142 | font_size = values[1] |
|---|
| 143 | font_string = '%s %s' % (font_name, font_size) # Verdana 9 |
|---|
| 144 | return helpers.ensure_unicode_string(font_string) |
|---|
| 145 | except: |
|---|
| 146 | #we talk about file |
|---|
| 147 | print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file |
|---|
| 148 | |
|---|
| 149 | return None |
|---|
| 150 | |
|---|
| 151 | def reduce_chars_newlines(text, max_chars = 0, max_lines = 0): |
|---|
| 152 | '''Cut the chars after 'max_chars' on each line |
|---|
| 153 | and show only the first 'max_lines'. |
|---|
| 154 | If any of the params is not present (None or 0) the action |
|---|
| 155 | on it is not performed''' |
|---|
| 156 | |
|---|
| 157 | def _cut_if_long(str): |
|---|
| 158 | if len(str) > max_chars: |
|---|
| 159 | str = str[:max_chars - 3] + '...' |
|---|
| 160 | return str |
|---|
| 161 | |
|---|
| 162 | if max_lines == 0: |
|---|
| 163 | lines = text.split('\n') |
|---|
| 164 | else: |
|---|
| 165 | lines = text.split('\n', max_lines)[:max_lines] |
|---|
| 166 | if max_chars > 0: |
|---|
| 167 | if lines: |
|---|
| 168 | lines = map(lambda e: _cut_if_long(e), lines) |
|---|
| 169 | if lines: |
|---|
| 170 | reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines) |
|---|
| 171 | else: |
|---|
| 172 | reduced_text = '' |
|---|
| 173 | return reduced_text |
|---|
| 174 | |
|---|
| 175 | def escape_for_pango_markup(string): |
|---|
| 176 | # escapes < > & ' " |
|---|
| 177 | # for pango markup not to break |
|---|
| 178 | if string is None: |
|---|
| 179 | return |
|---|
| 180 | if gtk.pygtk_version >= (2, 8, 0) and gtk.gtk_version >= (2, 8, 0): |
|---|
| 181 | escaped_str = gobject.markup_escape_text(string) |
|---|
| 182 | else: |
|---|
| 183 | escaped_str = xml.sax.saxutils.escape(string, {"'": ''', |
|---|
| 184 | '"': '"'}) |
|---|
| 185 | |
|---|
| 186 | return escaped_str |
|---|
| 187 | |
|---|
| 188 | def autodetect_browser_mailer(): |
|---|
| 189 | # recognize the environment for appropriate browser/mailer |
|---|
| 190 | if os.path.isdir('/proc'): |
|---|
| 191 | # under Linux: checking if 'gnome-session' or |
|---|
| 192 | # 'startkde' programs were run before gajim, by |
|---|
| 193 | # checking /proc (if it exists) |
|---|
| 194 | # |
|---|
| 195 | # if something is unclear, read `man proc`; |
|---|
| 196 | # if /proc exists, directories that have only numbers |
|---|
| 197 | # in their names contain data about processes. |
|---|
| 198 | # /proc/[xxx]/exe is a symlink to executable started |
|---|
| 199 | # as process number [xxx]. |
|---|
| 200 | # filter out everything that we are not interested in: |
|---|
| 201 | files = os.listdir('/proc') |
|---|
| 202 | |
|---|
| 203 | # files that doesn't have only digits in names... |
|---|
| 204 | files = filter(str.isdigit, files) |
|---|
| 205 | |
|---|
| 206 | # files that aren't directories... |
|---|
| 207 | files = filter(lambda f:os.path.isdir('/proc/' + f), files) |
|---|
| 208 | |
|---|
| 209 | # processes owned by somebody not running gajim... |
|---|
| 210 | # (we check if we have access to that file) |
|---|
| 211 | files = filter(lambda f:os.access('/proc/' + f +'/exe', os.F_OK), files) |
|---|
| 212 | |
|---|
| 213 | # be sure that /proc/[number]/exe is really a symlink |
|---|
| 214 | # to avoid TBs in incorrectly configured systems |
|---|
| 215 | files = filter(lambda f:os.path.islink('/proc/' + f + '/exe'), files) |
|---|
| 216 | |
|---|
| 217 | # list of processes |
|---|
| 218 | processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] |
|---|
| 219 | if 'gnome-session' in processes: |
|---|
| 220 | gajim.config.set('openwith', 'gnome-open') |
|---|
| 221 | elif 'startkde' in processes: |
|---|
| 222 | gajim.config.set('openwith', 'kfmclient exec') |
|---|
| 223 | else: |
|---|
| 224 | gajim.config.set('openwith', 'custom') |
|---|
| 225 | |
|---|
| 226 | def move_window(window, x, y): |
|---|
| 227 | '''moves the window but also checks if out of screen''' |
|---|
| 228 | if x < 0: |
|---|
| 229 | x = 0 |
|---|
| 230 | if y < 0: |
|---|
| 231 | y = 0 |
|---|
| 232 | window.move(x, y) |
|---|
| 233 | |
|---|
| 234 | def resize_window(window, w, h): |
|---|
| 235 | '''resizes window but also checks if huge window or negative values''' |
|---|
| 236 | if not w or not h: |
|---|
| 237 | return |
|---|
| 238 | if w > screen_w: |
|---|
| 239 | w = screen_w |
|---|
| 240 | if h > screen_h: |
|---|
| 241 | h = screen_h |
|---|
| 242 | window.resize(abs(w), abs(h)) |
|---|
| 243 | |
|---|
| 244 | class TagInfoHandler(xml.sax.ContentHandler): |
|---|
| 245 | def __init__(self, tagname1, tagname2): |
|---|
| 246 | xml.sax.ContentHandler.__init__(self) |
|---|
| 247 | self.tagname1 = tagname1 |
|---|
| 248 | self.tagname2 = tagname2 |
|---|
| 249 | self.servers = [] |
|---|
| 250 | |
|---|
| 251 | def startElement(self, name, attributes): |
|---|
| 252 | if name == self.tagname1: |
|---|
| 253 | for attribute in attributes.getNames(): |
|---|
| 254 | if attribute == 'jid': |
|---|
| 255 | jid = attributes.getValue(attribute) |
|---|
| 256 | # we will get the port next time so we just set it 0 here |
|---|
| 257 | self.servers.append([jid, 0]) |
|---|
| 258 | elif name == self.tagname2: |
|---|
| 259 | for attribute in attributes.getNames(): |
|---|
| 260 | if attribute == 'port': |
|---|
| 261 | port = attributes.getValue(attribute) |
|---|
| 262 | # we received the jid last time, so we now assign the port |
|---|
| 263 | # number to the last jid in the list |
|---|
| 264 | self.servers[-1][1] = port |
|---|
| 265 | |
|---|
| 266 | def endElement(self, name): |
|---|
| 267 | pass |
|---|
| 268 | |
|---|
| 269 | def parse_server_xml(path_to_file): |
|---|
| 270 | try: |
|---|
| 271 | handler = TagInfoHandler('item', 'active') |
|---|
| 272 | xml.sax.parse(path_to_file, handler) |
|---|
| 273 | return handler.servers |
|---|
| 274 | # handle exception if unable to open file |
|---|
| 275 | except IOError, message: |
|---|
| 276 | print >> sys.stderr, _('Error reading file:'), message |
|---|
| 277 | # handle exception parsing file |
|---|
| 278 | except xml.sax.SAXParseException, message: |
|---|
| 279 | print >> sys.stderr, _('Error parsing file:'), message |
|---|
| 280 | |
|---|
| 281 | def set_unset_urgency_hint(window, unread_messages_no): |
|---|
| 282 | '''sets/unsets urgency hint in window argument |
|---|
| 283 | depending if we have unread messages or not''' |
|---|
| 284 | if gtk.gtk_version >= (2, 8, 0) and gtk.pygtk_version >= (2, 8, 0) and \ |
|---|
| 285 | gajim.config.get('use_urgency_hint'): |
|---|
| 286 | if unread_messages_no > 0: |
|---|
| 287 | window.props.urgency_hint = True |
|---|
| 288 | else: |
|---|
| 289 | window.props.urgency_hint = False |
|---|
| 290 | |
|---|
| 291 | def get_abspath_for_script(scriptname, want_type = False): |
|---|
| 292 | '''checks if we are svn or normal user and returns abspath to asked script |
|---|
| 293 | if want_type is True we return 'svn' or 'install' ''' |
|---|
| 294 | if os.path.isdir('.svn'): # we are svn user |
|---|
| 295 | type = 'svn' |
|---|
| 296 | cwd = os.getcwd() # it's always ending with src |
|---|
| 297 | |
|---|
| 298 | if scriptname == 'gajim-remote': |
|---|
| 299 | path_to_script = cwd + '/gajim-remote.py' |
|---|
| 300 | |
|---|
| 301 | elif scriptname == 'gajim': |
|---|
| 302 | script = '#!/bin/sh\n' # the script we may create |
|---|
| 303 | script += 'cd %s' % cwd |
|---|
| 304 | path_to_script = cwd + '/../scripts/gajim_sm_script' |
|---|
| 305 | |
|---|
| 306 | try: |
|---|
| 307 | if os.path.exists(path_to_script): |
|---|
| 308 | os.remove(path_to_script) |
|---|
| 309 | |
|---|
| 310 | f = open(path_to_script, 'w') |
|---|
| 311 | script += '\nexec python -OOt gajim.py $0 $@\n' |
|---|
| 312 | f.write(script) |
|---|
| 313 | f.close() |
|---|
| 314 | os.chmod(path_to_script, 0700) |
|---|
| 315 | except OSError: # do not traceback (could be a permission problem) |
|---|
| 316 | #we talk about a file here |
|---|
| 317 | s = _('Could not write to %s. Session Management support will not work') % path_to_script |
|---|
| 318 | print >> sys.stderr, s |
|---|
| 319 | |
|---|
| 320 | else: # normal user (not svn user) |
|---|
| 321 | type = 'install' |
|---|
| 322 | # always make it like '/usr/local/bin/gajim' |
|---|
| 323 | path_to_script = helpers.is_in_path(scriptname, True) |
|---|
| 324 | |
|---|
| 325 | |
|---|
| 326 | if want_type: |
|---|
| 327 | return path_to_script, type |
|---|
| 328 | else: |
|---|
| 329 | return path_to_script |
|---|
| 330 | |
|---|
| 331 | def get_pixbuf_from_data(file_data, want_type = False): |
|---|
| 332 | '''Gets image data and returns gtk.gdk.Pixbuf |
|---|
| 333 | if want_type is True it also returns 'jpeg', 'png' etc''' |
|---|
| 334 | pixbufloader = gtk.gdk.PixbufLoader() |
|---|
| 335 | try: |
|---|
| 336 | pixbufloader.write(file_data) |
|---|
| 337 | pixbufloader.close() |
|---|
| 338 | pixbuf = pixbufloader.get_pixbuf() |
|---|
| 339 | except gobject.GError: # 'unknown image format' |
|---|
| 340 | pixbuf = None |
|---|
| 341 | |
|---|
| 342 | if want_type: |
|---|
| 343 | typ = pixbufloader.get_format()['name'] |
|---|
| 344 | return pixbuf, typ |
|---|
| 345 | else: |
|---|
| 346 | return pixbuf |
|---|
| 347 | |
|---|
| 348 | def get_invisible_cursor(): |
|---|
| 349 | pixmap = gtk.gdk.Pixmap(None, 1, 1, 1) |
|---|
| 350 | color = gtk.gdk.Color() |
|---|
| 351 | cursor = gtk.gdk.Cursor(pixmap, pixmap, color, color, 0, 0) |
|---|
| 352 | return cursor |
|---|
| 353 | |
|---|
| 354 | def get_current_desktop(window): |
|---|
| 355 | '''returns the current virtual desktop for given window |
|---|
| 356 | NOTE: window is GDK window''' |
|---|
| 357 | prop = window.property_get('_NET_CURRENT_DESKTOP') |
|---|
| 358 | if prop is None: # it means it's normal window (not root window) |
|---|
| 359 | # so we look for it's current virtual desktop in another property |
|---|
| 360 | prop = window.property_get('_NET_WM_DESKTOP') |
|---|
| 361 | |
|---|
| 362 | if prop is not None: |
|---|
| 363 | # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0] |
|---|
| 364 | current_virtual_desktop_no = prop[2][0] |
|---|
| 365 | return current_virtual_desktop_no |
|---|
| 366 | |
|---|
| 367 | def possibly_move_window_in_current_desktop(window): |
|---|
| 368 | '''moves GTK window to current virtual desktop if it is not in the |
|---|
| 369 | current virtual desktop |
|---|
| 370 | window is GTK window''' |
|---|
| 371 | if os.name == 'nt': |
|---|
| 372 | return |
|---|
| 373 | |
|---|
| 374 | root_window = gtk.gdk.screen_get_default().get_root_window() |
|---|
| 375 | # current user's vd |
|---|
| 376 | current_virtual_desktop_no = get_current_desktop(root_window) |
|---|
| 377 | |
|---|
| 378 | # vd roster window is in |
|---|
| 379 | window_virtual_desktop = get_current_desktop(window.window) |
|---|
| 380 | |
|---|
| 381 | # if one of those is None, something went wrong and we cannot know |
|---|
| 382 | # VD info, just hide it (default action) and not show it afterwards |
|---|
| 383 | if None not in (window_virtual_desktop, current_virtual_desktop_no): |
|---|
| 384 | if current_virtual_desktop_no != window_virtual_desktop: |
|---|
| 385 | # we are in another VD that the window was |
|---|
| 386 | # so show it in current VD |
|---|
| 387 | window.show() |
|---|
| 388 | |
|---|
| 389 | def file_is_locked(path_to_file): |
|---|
| 390 | '''returns True if file is locked (WINDOWS ONLY)''' |
|---|
| 391 | if os.name != 'nt': # just in case |
|---|
| 392 | return |
|---|
| 393 | |
|---|
| 394 | if not HAS_PYWIN32: |
|---|
| 395 | return |
|---|
| 396 | |
|---|
| 397 | secur_att = pywintypes.SECURITY_ATTRIBUTES() |
|---|
| 398 | secur_att.Initialize() |
|---|
| 399 | |
|---|
| 400 | try: |
|---|
| 401 | # try make a handle for READING the file |
|---|
| 402 | hfile = win32file.CreateFile( |
|---|
| 403 | path_to_file, # path to file |
|---|
| 404 | win32con.GENERIC_READ, # open for reading |
|---|
| 405 | 0, # do not share with other proc |
|---|
| 406 | secur_att, |
|---|
| 407 | win32con.OPEN_EXISTING, # existing file only |
|---|
| 408 | win32con.FILE_ATTRIBUTE_NORMAL, # normal file |
|---|
| 409 | 0 # no attr. template |
|---|
| 410 | ) |
|---|
| 411 | except pywintypes.error, e: |
|---|
| 412 | return True |
|---|
| 413 | else: # in case all went ok, close file handle (go to hell WinAPI) |
|---|
| 414 | hfile.Close() |
|---|
| 415 | return False |
|---|
| 416 | |
|---|
| 417 | def _get_fade_color(treeview, selected, focused): |
|---|
| 418 | '''get a gdk color that is between foreground and background in 0.3 |
|---|
| 419 | 0.7 respectively colors of the cell for the given treeview''' |
|---|
| 420 | style = treeview.style |
|---|
| 421 | if selected: |
|---|
| 422 | if focused: # is the window focused? |
|---|
| 423 | state = gtk.STATE_SELECTED |
|---|
| 424 | else: # is it not? NOTE: many gtk themes change bg on this |
|---|
| 425 | state = gtk.STATE_ACTIVE |
|---|
| 426 | else: |
|---|
| 427 | state = gtk.STATE_NORMAL |
|---|
| 428 | bg = style.base[state] |
|---|
| 429 | fg = style.text[state] |
|---|
| 430 | |
|---|
| 431 | p = 0.3 # background |
|---|
| 432 | q = 0.7 # foreground # p + q should do 1.0 |
|---|
| 433 | return gtk.gdk.Color(int(bg.red*p + fg.red*q), |
|---|
| 434 | int(bg.green*p + fg.green*q), |
|---|
| 435 | int(bg.blue*p + fg.blue*q)) |
|---|
| 436 | |
|---|
| 437 | def get_scaled_pixbuf(pixbuf, kind): |
|---|
| 438 | '''returns scaled pixbuf, keeping ratio etc or None |
|---|
| 439 | kind is either "chat" or "roster" or "notification" or "tooltip"''' |
|---|
| 440 | |
|---|
| 441 | # resize to a width / height for the avatar not to have distortion |
|---|
| 442 | # (keep aspect ratio) |
|---|
| 443 | width = gajim.config.get(kind + '_avatar_width') |
|---|
| 444 | height = gajim.config.get(kind + '_avatar_height') |
|---|
| 445 | if width < 1 or height < 1: |
|---|
| 446 | return None |
|---|
| 447 | |
|---|
| 448 | # Pixbuf size |
|---|
| 449 | pix_width = pixbuf.get_width() |
|---|
| 450 | pix_height = pixbuf.get_height() |
|---|
| 451 | # don't make avatars bigger than they are |
|---|
| 452 | if pix_width < width and pix_height < height: |
|---|
| 453 | return pixbuf # we don't want to make avatar bigger |
|---|
| 454 | |
|---|
| 455 | ratio = float(pix_width) / float(pix_height) |
|---|
| 456 | if ratio > 1: |
|---|
| 457 | w = width |
|---|
| 458 | h = int(w / ratio) |
|---|
| 459 | else: |
|---|
| 460 | h = height |
|---|
| 461 | w = int(h * ratio) |
|---|
| 462 | scaled_buf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_HYPER) |
|---|
| 463 | return scaled_buf |
|---|
| 464 | |
|---|
| 465 | def get_avatar_pixbuf_from_cache(fjid, is_fake_jid = False): |
|---|
| 466 | '''checks if jid has cached avatar and if that avatar is valid image |
|---|
| 467 | (can be shown) |
|---|
| 468 | returns None if there is no image in vcard |
|---|
| 469 | returns 'ask' if cached vcard should not be used (user changed his vcard, |
|---|
| 470 | so we have new sha) or if we don't have the vcard''' |
|---|
| 471 | |
|---|
| 472 | jid, nick = gajim.get_room_and_nick_from_fjid(fjid) |
|---|
| 473 | if gajim.config.get('hide_avatar_of_transport') and\ |
|---|
| 474 | gajim.jid_is_transport(jid): |
|---|
| 475 | # don't show avatar for the transport itself |
|---|
| 476 | return None |
|---|
| 477 | |
|---|
| 478 | puny_jid = helpers.sanitize_filename(jid) |
|---|
| 479 | if is_fake_jid: |
|---|
| 480 | puny_nick = helpers.sanitize_filename(nick) |
|---|
| 481 | path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick) |
|---|
| 482 | else: |
|---|
| 483 | path = os.path.join(gajim.VCARD_PATH, puny_jid) |
|---|
| 484 | if not os.path.isfile(path): |
|---|
| 485 | return 'ask' |
|---|
| 486 | |
|---|
| 487 | vcard_dict = gajim.connections.values()[0].get_cached_vcard(fjid, |
|---|
| 488 | is_fake_jid) |
|---|
| 489 | if not vcard_dict: # This can happen if cached vcard is too old |
|---|
| 490 | return 'ask' |
|---|
| 491 | if not vcard_dict.has_key('PHOTO'): |
|---|
| 492 | return None |
|---|
| 493 | pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0] |
|---|
| 494 | return pixbuf |
|---|
| 495 | |
|---|
| 496 | def make_gtk_month_python_month(month): |
|---|
| 497 | '''gtk start counting months from 0, so January is 0 |
|---|
| 498 | but python's time start from 1, so align to python |
|---|
| 499 | month MUST be integer''' |
|---|
| 500 | return month + 1 |
|---|
| 501 | |
|---|
| 502 | def make_python_month_gtk_month(month): |
|---|
| 503 | return month - 1 |
|---|
| 504 | |
|---|
| 505 | def make_color_string(color): |
|---|
| 506 | '''create #aabbcc color string from gtk color''' |
|---|
| 507 | col = '#' |
|---|
| 508 | for i in (color.red, color.green, color.blue): |
|---|
| 509 | h = hex(i)[2:4] |
|---|
| 510 | if len(h) == 1: |
|---|
| 511 | h = '0' + h |
|---|
| 512 | col += h |
|---|
| 513 | return col |
|---|
| 514 | |
|---|
| 515 | def make_pixbuf_grayscale(pixbuf): |
|---|
| 516 | pixbuf2 = pixbuf.copy() |
|---|
| 517 | pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False) |
|---|
| 518 | return pixbuf2 |
|---|
| 519 | |
|---|
| 520 | def get_path_to_generic_or_avatar(generic, jid = None, suffix = None): |
|---|
| 521 | '''Chooses between avatar image and default image. |
|---|
| 522 | Returns full path to the avatar image if it exists, |
|---|
| 523 | otherwise returns full path to the image.''' |
|---|
| 524 | if jid: |
|---|
| 525 | puny_jid = helpers.sanitize_filename(jid) |
|---|
| 526 | path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix |
|---|
| 527 | if os.path.exists(path_to_file): |
|---|
| 528 | return path_to_file |
|---|
| 529 | return os.path.abspath(generic) |
|---|
| 530 | |
|---|
| 531 | def decode_filechooser_file_paths(file_paths): |
|---|
| 532 | '''decode as UTF-8 under Windows and |
|---|
| 533 | ask sys.getfilesystemencoding() in POSIX |
|---|
| 534 | file_paths MUST be LIST''' |
|---|
| 535 | file_paths_list = list() |
|---|
| 536 | |
|---|
| 537 | if os.name == 'nt': # decode as UTF-8 under Windows |
|---|
| 538 | for file_path in file_paths: |
|---|
| 539 | file_path = file_path.decode('utf8') |
|---|
| 540 | file_paths_list.append(file_path) |
|---|
| 541 | else: |
|---|
| 542 | for file_path in file_paths: |
|---|
| 543 | try: |
|---|
| 544 | file_path = file_path.decode(sys.getfilesystemencoding()) |
|---|
| 545 | except: |
|---|
| 546 | try: |
|---|
| 547 | file_path = file_path.decode('utf-8') |
|---|
| 548 | except: |
|---|
| 549 | pass |
|---|
| 550 | file_paths_list.append(file_path) |
|---|
| 551 | |
|---|
| 552 | return file_paths_list |
|---|
| 553 | |
|---|
| 554 | def possibly_set_gajim_as_xmpp_handler(): |
|---|
| 555 | '''registers (by default only the first time) xmmp: to Gajim.''' |
|---|
| 556 | try: |
|---|
| 557 | import gconf |
|---|
| 558 | # in try because daemon may not be there |
|---|
| 559 | client = gconf.client_get_default() |
|---|
| 560 | except: |
|---|
| 561 | return |
|---|
| 562 | |
|---|
| 563 | |
|---|
| 564 | path_to_dot_kde = os.path.expanduser('~/.kde') |
|---|
| 565 | if os.path.exists(path_to_dot_kde): |
|---|
| 566 | path_to_kde_file = os.path.join(path_to_dot_kde, |
|---|
| 567 | 'share/services/xmpp.protocol') |
|---|
| 568 | if not os.path.exists(path_to_kde_file): |
|---|
| 569 | path_to_kde_file = None |
|---|
| 570 | else: |
|---|
| 571 | path_to_kde_file = None |
|---|
| 572 | |
|---|
| 573 | if gajim.config.get('set_xmpp://_handler_everytime'): |
|---|
| 574 | # it's false by default |
|---|
| 575 | we_set = True |
|---|
| 576 | elif client.get_string('/desktop/gnome/url-handlers/xmpp/command') is None: |
|---|
| 577 | # only the first time (GNOME/GCONF) |
|---|
| 578 | we_set = True |
|---|
| 579 | elif path_to_kde_file is not None and not os.path.exists(path_to_kde_file): |
|---|
| 580 | # only the first time (KDE) |
|---|
| 581 | we_set = True |
|---|
| 582 | else: |
|---|
| 583 | we_set = False |
|---|
| 584 | |
|---|
| 585 | if not we_set: |
|---|
| 586 | return |
|---|
| 587 | |
|---|
| 588 | path_to_gajim_script, typ = get_abspath_for_script('gajim-remote', True) |
|---|
| 589 | if path_to_gajim_script: |
|---|
| 590 | if typ == 'svn': |
|---|
| 591 | command = path_to_gajim_script + ' open_chat %s' |
|---|
| 592 | else: # 'installed' |
|---|
| 593 | command = 'gajim-remote open_chat %s' |
|---|
| 594 | |
|---|
| 595 | # setting for GNOME/Gconf |
|---|
| 596 | client.set_bool('/desktop/gnome/url-handlers/xmpp/enabled', True) |
|---|
| 597 | client.set_string('/desktop/gnome/url-handlers/xmpp/command', command) |
|---|
| 598 | client.set_bool('/desktop/gnome/url-handlers/xmpp/needs_terminal', False) |
|---|
| 599 | |
|---|
| 600 | # setting for KDE |
|---|
| 601 | if path_to_kde_file is not None: # user has run kde at least once |
|---|
| 602 | f = open(path_to_kde_file, 'w') |
|---|
| 603 | f.write('''\ |
|---|
| 604 | [Protocol] |
|---|
| 605 | exec=%s "%%u" |
|---|
| 606 | protocol=xmpp |
|---|
| 607 | input=none |
|---|
| 608 | output=none |
|---|
| 609 | helper=true |
|---|
| 610 | listing=false |
|---|
| 611 | reading=false |
|---|
| 612 | writing=false |
|---|
| 613 | makedir=false |
|---|
| 614 | deleting=false |
|---|
| 615 | icon=gajim |
|---|
| 616 | Description=xmpp |
|---|
| 617 | ''' % command) |
|---|
| 618 | f.close() |
|---|
| 619 | |
|---|
| 620 | def escape_underscore(s): |
|---|
| 621 | '''Escape underlines to prevent them from being interpreted |
|---|
| 622 | as keyboard accelerators''' |
|---|
| 623 | return s.replace('_', '__') |
|---|
| 624 | |
|---|
| 625 | def get_state_image_from_file_path_show(file_path, show): |
|---|
| 626 | state_file = show.replace(' ', '_') |
|---|
| 627 | files = [] |
|---|
| 628 | files.append(os.path.join(file_path, state_file + '.png')) |
|---|
| 629 | files.append(os.path.join(file_path, state_file + '.gif')) |
|---|
| 630 | image = gtk.Image() |
|---|
| 631 | image.set_from_pixbuf(None) |
|---|
| 632 | for file_ in files: |
|---|
| 633 | if os.path.exists(file_): |
|---|
| 634 | image.set_from_file(file_) |
|---|
| 635 | break |
|---|
| 636 | |
|---|
| 637 | return image |
|---|
| 638 | |
|---|
| 639 | def get_possible_button_event(event): |
|---|
| 640 | '''mouse or keyboard caused the event?''' |
|---|
| 641 | if event.type == gtk.gdk.KEY_PRESS: |
|---|
| 642 | event_button = 0 # no event.button so pass 0 |
|---|
| 643 | else: # BUTTON_PRESS event, so pass event.button |
|---|
| 644 | event_button = event.button |
|---|
| 645 | |
|---|
| 646 | return event_button |
|---|
| 647 | |
|---|
| 648 | def destroy_widget(widget): |
|---|
| 649 | widget.destroy() |
|---|