# Copyright (C) 2007 daelstorm. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# Original copyright below
# Copyright (c) 2003-2004 Hyriand. All rights reserved.
from __future__ import division
import webbrowser
import gtk
import gobject
import pango
import time
import locale
import os, sys
import string
import re
import types
import urllib
from struct import unpack
import imghdr
from pynicotine import slskmessages
from pynicotine.utils import _, executeCommand, findBestEncoding
from countrycodes import code2name
DECIMALSEP = ""
URL_RE = re.compile("(\\w+\\://.+?)[\\s\\(\\)]|(www\\.\\w+\\.\\w+.*?)[\\s\\(\\)]|(mailto\\:\\w.+?)[\\s\\(\\)]")
PROTOCOL_HANDLERS = {}
CATCH_URLS = 0
HUMANIZE_URLS = 0
USERNAMEHOTSPOTS = 0
NICOTINE = None
def popupWarning(parent, title, warning, icon=None):
dlg = gtk.Dialog(title = title, parent = parent,
buttons = (gtk.STOCK_OK, gtk.RESPONSE_OK))
dlg.set_default_response(gtk.RESPONSE_OK)
dlg.set_icon(icon)
dlg.set_border_width(10)
dlg.vbox.set_spacing(10)
hbox = gtk.HBox(spacing=5)
hbox.set_border_width(5)
hbox.show()
dlg.vbox.pack_start(hbox)
image = gtk.Image()
image.set_padding(0, 0)
icon = gtk.STOCK_DIALOG_WARNING
image.set_from_stock(icon, 4)
image.show()
hbox.pack_start(image)
label = gtk.Label()
label.set_markup(warning)
label.set_line_wrap(True)
hbox.pack_start(label, True, True)
dlg.vbox.show_all()
result = None
if dlg.run() == gtk.RESPONSE_OK:
dlg.destroy()
return 0
def numfmt(value):
v = str(float(value)) + '0000'
i = v.index('.')
if i < 4:
return v[:5]
else:
return v[:i+2]
# we could move this into a new class
previouscountrypath = None
def showCountryTooltip(widget, x, y, tooltip, sourcecolumn, stripprefix='flag_'):
global previouscountrypath
try:
# the returned path of widget.get_path_at_pos is not correct since
# this function pretends there's no header! This also means we
# cannot look up the column for the very last user in the list
# since the y is too big. Therefore we'll use a y-value of 0 on all
# lookups
(incorrectpath, column, cx, cy) = widget.get_path_at_pos(x, 0)
# the return path of this func is okay, but it doesn't return the
# column -_-
(path, droppos) = widget.get_dest_row_at_pos(x, y)
except TypeError:
# Either function returned None
return False
# If the mouse is pointing at a new path destroy the tooltip so it can be recreated next time
if path != previouscountrypath:
previouscountrypath = path
return False
title = column.get_title()
if (title != _("Country")):
#print "Title %s is not country" % (title)
return False
model = widget.get_model()
iter = model.get_iter(path)
value = model.get_value(iter, sourcecolumn)
if not value.startswith(stripprefix):
#print "Value %s doesn't have '%s' as prefix." % (value, stripprefix)
tooltip.set_text(_("Unknown"))
return True
value = value[len(stripprefix):]
if value:
countryname = code2name(value)
else:
countryname = "Earth"
if countryname:
countryname = _(countryname)
else:
countryname = _("Unknown (%(countrycode)s)") % {'countrycode':value}
tooltip.set_text(countryname)
return True
def HumanizeBytes(size):
if size is None:
return None
try:
s = int(size)
if s >= 1000*1024*1024:
r = _("%s GB") % numfmt(float(s) / 1073741824.0 )
elif s >= 1000*1024:
r = _("%s MB") % numfmt(float(s) / 1048576.0)
elif s >= 1000:
r = _("%s KB") % numfmt(float(s) / 1024.0)
else:
r = _("%s B") % numfmt(float(s) )
return r
except Exception, e:
Output(e)
return size
return str(size)
def recode(s):
try:
return s.decode(locale.nl_langinfo(locale.CODESET), "replace").encode("utf-8", "replace")
except:
return s
def recode2(s):
try:
return s.decode("utf-8", "replace").encode(locale.nl_langinfo(locale.CODESET), "replace")
except:
return s
def InitialiseColumns(treeview, *args):
i = 0
cols = []
for c in args:
if c[2] == "text":
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn(c[0], renderer, text = i)
elif c[2] == "number":
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn(c[0], renderer, text = i)
renderer.set_property("xalign", 1)
elif c[2] == "colored":
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn(c[0], renderer, text = i, foreground = c[3][0], background = c[3][1])
elif c[2] == "edit":
renderer = gtk.CellRendererText()
renderer.set_property('editable', True)
column = gtk.TreeViewColumn(c[0], renderer, text = i)
elif c[2] == "combo":
renderer = gtk.CellRendererCombo()
renderer.set_property('text-column', 0)
renderer.set_property('editable', True)
column = gtk.TreeViewColumn(c[0], renderer, text = i)
elif c[2] == "progress":
renderer = gtk.CellRendererProgress()
column = gtk.TreeViewColumn(c[0], renderer, value = i)
elif c[2] == "toggle":
renderer = gtk.CellRendererToggle()
column = gtk.TreeViewColumn(c[0], renderer, active = i)
renderer.set_property("xalign", 1)
else:
renderer = gtk.CellRendererPixbuf()
column = gtk.TreeViewColumn(c[0], renderer, pixbuf = i)
if c[1] == -1:
column.set_resizable(False)
column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
else:
column.set_resizable(True)
if c[1] == 0:
column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
else:
column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
column.set_fixed_width(c[1])
column.set_min_width(0)
if len(c) > 3 and type(c[3]) is not list:
column.set_cell_data_func(renderer, c[3])
column.set_reorderable(True)
column.set_widget(gtk.Label(c[0]))
column.get_widget().show()
treeview.append_column(column)
cols.append(column)
i += 1
return cols
def PressHeader(widget, event):
if event.button != 3:
return False
columns = widget.get_parent().get_columns()
visible_columns = [column for column in columns if column.get_visible()]
one_visible_column = len(visible_columns) == 1
menu = gtk.Menu()
pos = 1
for column in columns:
title = column.get_title()
if title == "":
title = _("Column #%i") %pos
item = gtk.CheckMenuItem(title)
if column in visible_columns:
item.set_active(True)
if one_visible_column:
item.set_sensitive(False)
else:
item.set_active(False)
item.connect('activate', header_toggle, column)
menu.append(item)
pos += 1
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
return True
def header_toggle(menuitem, column):
column.set_visible(not column.get_visible())
NICOTINE.SaveColumns()
def ScrollBottom(widget):
va = widget.get_vadjustment()
if va is None:
return False
va.set_value(va.upper - va.page_size)
return False
def UrlEvent(tag, widget, event, iter, url):
if tag.last_event_type == gtk.gdk.BUTTON_PRESS and event.type == gtk.gdk.BUTTON_RELEASE and event.button == 1:
if url[:4] == "www.":
url = "http://" + url
OpenUri(url)
tag.last_event_type = event.type
def OpenUri(uri):
"""Open a URI in an external (web) browser. The given argument has
to be a properly formed URI including the scheme (fe. HTTP).
As of now failures will be silently discarded."""
# Situation 1, user defined a way of handling the protocol
protocol = uri[:uri.find(":")]
if protocol in PROTOCOL_HANDLERS:
if NICOTINE.browser is not None and NICOTINE.np.config.sections["ui"]["open_in_mozembed"] and protocol in ("http", 'https'):
NICOTINE.browser.load_url(uri, 0)
return
if PROTOCOL_HANDLERS[protocol].__class__ is types.MethodType:
PROTOCOL_HANDLERS[protocol](uri.strip())
return
if PROTOCOL_HANDLERS[protocol]:
executeCommand(PROTOCOL_HANDLERS[protocol], uri)
return
# Situation 2, user did not define a way of handling the protocol, we'll leave it up to python
if webbrowser:
webbrowser.open(uri)
return
# Situation 3a, we let Gnome (new way?) deal with it
try:
import gnomevfs
gnomevfs.url_show(uri)
return
except Exception, e:
pass
# Situation 3b, we let Gnome (old way?) deal with it
try:
# import gnome.vfs <-- doesn't make sense, vfs was never used!
import gnome
gnome.url_show(uri)
return
except:
pass
def AppendLine(textview, line, tag = None, timestamp = None, showstamp=True, timestamp_format = "%H:%M:%S", username=None, usertag=None, scroll=True):
if type(line) not in (type(""), type(u"")):
line = str(line) # Error messages are sometimes tuples
def _makeurltag(buffer, tag, url):
props = {}
props["foreground_gdk"] = gtk.gdk.color_parse(NICOTINE.np.config.sections["ui"]["urlcolor"])
props["underline"] = pango.UNDERLINE_SINGLE
tag = buffer.create_tag(**props)
tag.last_event_type = -1
tag.connect("event", UrlEvent, url)
return tag
def _append(buffer, text, tag):
iter = buffer.get_end_iter()
if tag is not None:
buffer.insert_with_tags(iter, text, tag)
else:
buffer.insert(iter, text)
def _usertag(buffer, section):
# Tag usernames with popup menu creating tag, and away/online/offline colors
if USERNAMEHOTSPOTS and username != None and usertag != None:
np = re.compile(re.escape(username))
match = np.search(section)
if match != None:
start2 = section[:match.start()]
name = match.group()[:]
start = section[match.end():]
_append(buffer, start2, tag)
_append(buffer, name, usertag)
_append(buffer, start, tag)
else:
_append(buffer, section, tag)
else:
_append(buffer, section, tag)
scrolledwindow = textview.get_parent()
va = scrolledwindow.get_vadjustment()
bottom = va.value >= (va.upper - int(va.page_size*1.5))
buffer = textview.get_buffer()
linenr = buffer.get_line_count()
ME = 0
if line.startswith("* "):
ME = 1
TIMESTAMP = None
TS = 0
if NICOTINE.np.config.sections["logging"]["timestamps"] and showstamp:
if timestamp_format and not timestamp:
TIMESTAMP = recode(time.strftime(timestamp_format))
line = "%s %s\n" % (TIMESTAMP, line)
elif timestamp_format and timestamp:
TIMESTAMP = recode(time.strftime(timestamp_format, time.localtime(timestamp)))
line = "%s %s\n" % (TIMESTAMP, line)
else:
line += "\n"
if TIMESTAMP is not None:
TS = len(TIMESTAMP)
# Append timestamp, if one exists, cut it from remaining line (to avoid matching against username)
_append(buffer, line[:TS], tag)
line = line[TS:]
# Match first url
match = URL_RE.search(line)
# Highlight urls, if found and tag them
while CATCH_URLS and match:
start = line[:match.start()]
_usertag(buffer, start)
url = match.group()[:-1]
urltag = _makeurltag(buffer, tag, url)
line = line[match.end()-1:]
if url.startswith("slsk://") and HUMANIZE_URLS:
url = urllib.url2pathname( url)
_append(buffer, url, urltag)
# Match remaining url
match = URL_RE.search(line)
if line:
_usertag(buffer, line)
if scroll and bottom:
gobject.idle_add(ScrollBottom, scrolledwindow)
return linenr
class ImageLabel(gtk.HBox):
def __init__(self, label = "", image = None, onclose = None, closebutton = False, angle = 0, show_image = True, statusimage=None, show_status_image=False):
gtk.HBox.__init__(self)
self.closebutton = closebutton
self.angle = angle
self._show_image = show_image
self._show_status_image = show_status_image
self.notify = 0
self._entered = 0
self._pressed = 0
self.onclose = onclose
self.status_img = None
self.statusimage = gtk.Image()
self.label = gtk.Label()
self.text = label
if NICOTINE.np.config.sections["ui"]["tab_colors"]:
color = NICOTINE.np.config.sections["ui"]["tab_default"]
else:
color = ""
if not color:
self.label.set_text("%s" % self.text)
else:
self.label.set_markup("%s" % (color, self.text.replace("<", "<").replace(">", ">").replace("&", "&")))
self.label.set_alignment(0.0, 0.50)
self.label.set_angle(angle)
self.label.show()
if self._show_status_image:
self.set_status_image(statusimage)
self.statusimage.show()
self.image = gtk.Image()
self.set_image(image)
if self._show_image:
self.image.show()
self._pack_children()
self._order_children()
def _pack_children(self):
self.set_spacing(0)
if "Box" in self.__dict__:
for widget in self.Box.get_children():
self.Box.remove(widget)
self.remove(self.Box)
self.Box.destroy()
del self.Box
if self.angle in ( 90, -90):
self.Box = gtk.VBox()
else:
self.angle = 0
self.Box = gtk.HBox()
self.Box.set_spacing(2)
self.add(self.Box)
self.Box.show()
self.Box.pack_start(self.statusimage, False, False)
self.Box.pack_start(self.label, True, True)
self.Box.pack_start(self.image, False, False)
if self.closebutton and self.onclose is not None:
self._add_close_button()
def _order_children(self):
if self.angle == 90:
if "button" in self.__dict__ and self.closebutton != 0:
self.Box.reorder_child(self.button, 0)
self.Box.reorder_child(self.image, 1)
self.Box.reorder_child(self.label, 2)
self.Box.reorder_child(self.statusimage, 3)
else:
self.Box.reorder_child(self.image, 0)
self.Box.reorder_child(self.label, 1)
self.Box.reorder_child(self.statusimage, 2)
else:
self.Box.reorder_child(self.statusimage, 0)
self.Box.reorder_child(self.label, 1)
self.Box.reorder_child(self.image, 2)
if "button" in self.__dict__ and self.closebutton != 0:
self.Box.reorder_child(self.button, 3)
def set_onclose(self, closebutton):
self.closebutton = closebutton
if self.closebutton:
self._add_close_button()
else:
self._remove_close_button()
self._order_children()
def show_image(self, show = True):
self._show_image = show
if self._show_image:
self.image.show()
else:
self.image.hide()
def set_angle(self, angle):
self.angle = angle
self.label.set_angle(self.angle)
self._remove_close_button()
self._pack_children()
self._order_children()
def _add_close_button(self):
if "button" in self.__dict__:
return
self.button = gtk.Button()
img = gtk.Image()
img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
self.button.add(img)
if self.onclose is not None:
self.button.connect("clicked", self.onclose)
self.button.set_relief(gtk.RELIEF_NONE)
self.button.show_all()
self.Box.pack_start(self.button, False, False)
def _remove_close_button(self):
if "button" not in self.__dict__:
return
self.Box.remove(self.button)
self.button.destroy()
del self.button
def set_text_color(self, notify = None, text = None):
if notify is None:
notify = self.notify
else:
self.notify = notify
if NICOTINE.np.config.sections["ui"]["tab_colors"]:
if notify == 1:
color = NICOTINE.np.config.sections["ui"]["tab_changed"]
elif notify == 2:
color = NICOTINE.np.config.sections["ui"]["tab_hilite"]
else:
color = NICOTINE.np.config.sections["ui"]["tab_default"]
try:
gtk.gdk.color_parse(color)
except:
color = ""
else:
color = ""
if text is not None:
self.text = text
if not color:
self.label.set_text("%s" % self.text)
else:
self.label.set_markup("%s" % (color, self.text.replace("<", "<").replace(">", ">").replace("&", "&")))
def set_image(self, img):
self.img = img
self.image.set_from_pixbuf(img)
def get_image(self):
return self.img
def set_status_image(self, img):
if img is self.status_img:
return
if NICOTINE:
if NICOTINE.np.config.sections["ui"]["tab_status_icons"]:
self.statusimage.show()
else:
self.statusimage.hide()
self.status_img = img
self.statusimage.set_from_pixbuf(img)
def get_status_image(self):
return self.status_img
def set_text(self, lbl):
if NICOTINE.np.config.sections["ui"]["tab_colors"]:
self.set_text_color( notify = None, text = lbl)
else:
self.text = lbl
self.label.set_text(lbl)
def get_text(self):
return self.label.get_text()
class IconNotebook(gtk.Notebook):
def __init__(self, images, angle = 0, tabclosers = False, show_image = True, reorderable = True, show_status_image = False):
self.tabclosers = tabclosers
self.reorderable = reorderable
gtk.Notebook.__init__(self)
self.images = images
self._show_image = show_image
self._show_status_image = show_status_image
self.pages = []
self.detached_tabs = []
self.connect("switch-page", self.dismiss_icon)
self.connect("key_press_event", self.OnKeyPress)
self.set_scrollable(True)
self.angle = angle
def set_reorderable(self, reorderable):
self.reorderable = reorderable
for data in self.pages:
page, label_tab, status, label_tab_menu = data
try:
self.set_tab_reorderable(page, self.reorderable)
except:
pass
def set_tab_closers(self, closers):
self.tabclosers = closers
for data in self.pages:
page, label_tab, status, label_tab_menu = data
label_tab.set_onclose(self.tabclosers)
def show_images(self, show_image = True):
self._show_image = show_image
for data in self.pages:
page, label_tab, status, label_tab_menu = data
label_tab.show_image(self._show_image)
def set_tab_angle(self, angle):
if angle == self.angle:
return
self.angle = angle
for data in self.pages:
page, label_tab, status, label_tab_menu = data
label_tab.set_angle(angle)
def OnKeyPress(self, widget, event):
if event.state & (gtk.gdk.MOD1_MASK | gtk.gdk.CONTROL_MASK) != gtk.gdk.MOD1_MASK:
return False
if event.keyval in [gtk.gdk.keyval_from_name("Up"), gtk.gdk.keyval_from_name("Left")]:
self.prev_page()
elif event.keyval in [gtk.gdk.keyval_from_name("Down"), gtk.gdk.keyval_from_name("Right")]:
self.next_page()
else:
return False
widget.emit_stop_by_name("key_press_event")
return True
def append_page(self, page, label, onclose = None, angle = 0):
self.set_tab_angle(angle)
closebutton = self.tabclosers
label_tab = ImageLabel(label, self.images["empty"], onclose, closebutton = closebutton, angle = angle, show_image = self._show_image, statusimage=None, show_status_image=self._show_status_image)
# menu for all tabs
label_tab_menu = ImageLabel(label, self.images["empty"])
self.pages.append([page, label_tab, 0, label_tab_menu])
eventbox = gtk.EventBox()
eventbox.set_visible_window(False)
label_tab.show()
eventbox.add(label_tab)
eventbox.show()
eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)
eventbox.connect('button_press_event', self.on_tab_click, page)
gtk.Notebook.append_page_menu(self, page, eventbox, label_tab_menu)
try:
self.set_tab_reorderable(page, self.reorderable)
#self.set_tab_detachable(page, True)
except:
# Old PyGTK2
pass
def OnTabWindowDestroy(self, widget, page):
if self.is_tab_detached(page):
self.attach_tab(page, destroying=True)
def detach_tab(self, page, title=_("Nicotine+")):
label = None
if self.is_tab_detached(page):
return
for i in self.pages[:]:
if i[0] == page:
pagewidget, label_tab, status, label_tab_menu = i
label = label_tab.label.get_text()
label_tab.get_parent().remove(label_tab)
break
if label is None:
return
for i in self.detached_tabs:
if i[0] == label or i[1] is page:
return
gtk.Notebook.remove_page(self, self.page_num(page))
window = gtk.Window()
window.set_title(title)
#window.add_accel_group(self.accel_group)
window.set_icon(NICOTINE.images["n"])
window.resize(600, 400)
vbox = gtk.VBox(False, spacing=5)
vbox.set_border_width(5)
vbox.pack_start(page)
vbox.show()
window.add(vbox)
window.connect("destroy", self.OnTabWindowDestroy, page)
window.connect("focus_in_event", self.OnFocusIn)
window.connect("focus_out_event", self.OnFocusOut)
self.detached_tabs.append([page, label, window, False])
window.show()
def OnFocusIn(self, widget, event):
for item in self.detached_tabs:
if item[2] == widget:
item[3] = True
self.OnFocused(item)
def OnFocusOut(self, widget, event):
for item in self.detached_tabs:
if item[2] == widget:
item[3] = False
#self.OnFocused(item)
def OnFocused(self, item):
(page, label, window, focused) = item
self.frame.Notifications.ClearPage(self, item)
self.set_detached_icon(page, 0)
def attach_tab(self, page, destroying=False):
pagewidget = label_tab = label_tab_menu = label = status = None
for item in self.detached_tabs:
if item[0] is page:
label = item[1]
window = item[2]
break
if label is None or window is None:
return
for i in self.pages[:]:
if i[0] == page:
label = i[1].label.get_text()
pagewidget, label_tab, status, label_tab_menu = i
break
for i in (pagewidget, label_tab, label_tab_menu, label, status):
if i is None:
return
window.get_child().remove(pagewidget)
#self.pages.append([page, label_tab, status, label_tab_menu])
eventbox = gtk.EventBox()
eventbox.set_visible_window(False)
label_tab.show()
eventbox.add(label_tab)
eventbox.show()
eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)
eventbox.connect('button_press_event', self.on_tab_click, page)
gtk.Notebook.append_page_menu(self, pagewidget, eventbox, label_tab_menu)
try:
self.set_tab_reorderable(page, self.reorderable)
except:
# Old PyGTK2
pass
self.detached_tabs.remove(item)
if not destroying:
window.destroy()
def is_tab_detached(self, page):
for item in self.detached_tabs:
if item[0] is page:
return True
return False
def is_detached_tab_focused(self, page):
for item in self.detached_tabs:
if item[0] is page:
return item[3]
return False
def set_detached_icon(self, page, status):
image = self.images[("n", "hilite3", "hilite")[status]]
for item in self.detached_tabs:
if item[0] is page:
window = item[2]
window.set_icon(image)
def set_detached_tab_title(self, page, title):
for item in self.detached_tabs:
if item[0] is page:
window = item[2]
window.set_title(title)
def on_tab_click(self, widget, event, child):
pass
def set_status_image(self, page, status):
image = self.images[("offline", "away", "online")[status]]
for i in self.pages:
if page == i[0]:
i[1].set_status_image(image)
i[3].set_status_image(image)
return
def set_image(self, page, status):
image = self.images[("empty", "hilite3", "hilite")[status]]
for i in self.pages:
if page == i[0]:
if status == 1 and i[2] == 2:
return
if i[2] != status:
i[1].set_image(image)
i[3].set_image(image)
i[2] = status
return
def set_text(self, page, label):
for i in self.pages:
if i[0] == page:
i[1].set_text(label)
i[3].set_text(label)
return
def set_text_colors(self, color = None):
for i in self.pages:
i[1].set_text_color(color)
def set_text_color(self, page, color = None):
for i in self.pages:
if i[0] == page:
i[1].set_text_color(color)
return
def dismiss_icon(self, notebook, page, page_num):
page = self.get_nth_page(page_num)
self.set_image(page, 0)
self.set_text_color(page, 0)
def request_hilite(self, page):
if self.is_tab_detached(page):
if not self.is_detached_tab_focused(page):
self.set_detached_icon(page, 2)
return
# Don't set 'tab' notification icons for detached tabs
current = self.get_nth_page(self.get_current_page())
if current == page:
return
self.set_image(page, 2)
self.set_text_color(page, 2)
def request_changed(self, page):
if self.is_tab_detached(page):
if not self.is_detached_tab_focused(page):
self.set_detached_icon(page, 1)
return
# Don't set 'tab' notification icons for detached tabs
current = self.get_nth_page(self.get_current_page())
if current == page:
return
self.set_image(page, 1)
self.set_text_color(page, 1)
def remove_page(self, page):
for i in self.pages[:]:
if i[0] == page:
gtk.Notebook.remove_page(self, self.page_num(page))
i[1].destroy()
i[3].destroy()
self.pages.remove(i)
return
class PopupMenu(gtk.Menu):
def __init__(self, frame = None):
gtk.Menu.__init__(self)
self.frame = frame
self.user = None
self.useritem = None
self.handlers = {}
self.editing = False
def setup(self, *items):
for item in items:
if item[0] == "":
menuitem = gtk.MenuItem()
elif item[0] == "USER":
menuitem = gtk.MenuItem(item[1])
self.useritem = menuitem
if len(item) >= 3:
self.handlers[menuitem] = menuitem.connect("activate", item[2])
else:
menuitem.set_sensitive(False)
elif item[0] == 1:
menuitem = gtk.MenuItem(item[1])
menuitem.set_submenu(item[2])
if len(item) == 5 and item[4] is not None and item[3] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[3], item[4])
elif item[3] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[3])
elif item[0] == "USERMENU":
menuitem = gtk.MenuItem(item[1])
menuitem.set_submenu(item[2])
if item[3] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[3])
self.useritem = menuitem
elif item[0] == 2:
menuitem = gtk.ImageMenuItem(item[1])
menuitem.set_submenu(item[2])
if len(item) == 5 and item[4] is not None and item[3] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[3], item[4])
elif item[3] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[3])
img = gtk.image_new_from_stock(item[4], gtk.ICON_SIZE_MENU)
menuitem.set_image(img)
else:
if item[0][0] == "$":
menuitem = gtk.CheckMenuItem(item[0][1:])
elif item[0][0] == "#":
menuitem = gtk.ImageMenuItem(item[0][1:])
img = gtk.image_new_from_stock(item[2], gtk.ICON_SIZE_MENU)
menuitem.set_image(img)
elif item[0][0] == "%":
menuitem = gtk.ImageMenuItem(item[0][1:])
img = gtk.Image()
img.set_from_pixbuf(item[2])
menuitem.set_image(img)
else:
menuitem = gtk.MenuItem(item[0])
if len(item) >= 4 and item[3] is not None and item[1] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[1], item[3])
elif item[1] is not None:
self.handlers[menuitem] = menuitem.connect("activate", item[1])
self.append(menuitem)
menuitem.show()
return self
def clear(self):
for (w, widget) in self.handlers.iteritems():
w.disconnect(widget)
self.handlers.clear()
for widget in self.get_children():
self.remove(widget)
widget.destroy()
if self.useritem is not None:
self.useritem.destroy()
self.useritem = None
def set_user(self, user):
self.user = user
if self.useritem:
self.useritem.get_child().set_text(user)
def get_user(self):
return self.user
def OnSearchUser(self, widget):
self.frame.SearchMethod.set_active_iter(self.frame.searchmethods[_("User")])
self.frame.UserSearchCombo.child.set_text(self.user)
self.frame.ChangeMainPage(None, "search")
def OnSendMessage(self, widget):
self.frame.privatechats.SendMessage(self.user, None, 1)
self.frame.ChangeMainPage(None, "private")
def OnShowIPaddress(self, widget):
if self.user not in self.frame.np.ip_requested:
self.frame.np.ip_requested.append(self.user)
self.frame.np.queue.put(slskmessages.GetPeerAddress(self.user))
def OnGetUserInfo(self, widget):
self.frame.LocalUserInfoRequest(self.user)
def OnBrowseUser(self, widget):
self.frame.BrowseUser(self.user)
def OnPrivateRoomAddUser(self, widget, room):
self.frame.PrivateRoomAddUser(room, self.user)
def OnPrivateRoomRemoveUser(self, widget, room):
self.frame.PrivateRoomRemoveUser(room, self.user)
def OnPrivateRoomAddOperator(self, widget, room):
self.frame.PrivateRoomAddOperator(room, self.user)
def OnPrivateRoomRemoveOperator(self, widget, room):
self.frame.PrivateRoomRemoveOperator(room, self.user)
def OnAddToList(self, widget):
if self.editing: return
if widget.get_active():
self.frame.userlist.AddToList(self.user)
else:
self.frame.userlist.RemoveFromList(self.user)
def OnBanUser(self, widget):
if self.editing: return
if widget.get_active():
self.frame.BanUser(self.user)
else:
self.frame.UnbanUser(self.user)
def OnBlockUser(self, widget):
if self.editing: return
if widget.get_active():
self.frame.OnBlockUser(self.user)
else:
self.frame.OnUnBlockUser(self.user)
def OnIgnoreIP(self, widget):
if self.editing: return
if widget.get_active():
self.frame.OnIgnoreIP(self.user)
else:
self.frame.OnUnIgnoreIP(self.user)
def OnIgnoreUser(self, widget):
if self.editing: return
if widget.get_active():
self.frame.IgnoreUser(self.user)
else:
self.frame.UnignoreUser(self.user)
def OnVersion(self, widget):
self.frame.privatechats.SendMessage(self.user, "\x01VERSION\x01", bytestring=True)
def OnCopyUser(self, widget):
self.frame.clip.set_text(self.user)
def OnGivePrivileges(self, widget):
self.frame.np.queue.put(slskmessages.CheckPrivileges())
if self.frame.np.privileges_left is None:
days = _("Unknown")
else:
days = self.frame.np.privileges_left // 60 // 60 // 24
text = InputDialog(self.frame.MainWindow, _("Give privileges")+" "+_("to %(user)s") %{"user": self.user}, _("Give how many days of global privileges to this user?") + " ("+ _("%(days)s days left") %{'days':days} +")" )
if text:
try:
days = int(text)
self.frame.GivePrivileges(self.user, days)
except Exception, e:
print e
def OnPrivateRooms(self, widget):
if self.user == None or self.user == self.frame.np.config.sections["server"]["login"]:
return False
user = self.user
items = []
popup = self.frame.userlist.Popup_Menu_PrivateRooms
popup.clear()
popup.set_user(self.user)
#print self.roomsctrl.PrivateRooms
for room in self.frame.chatrooms.roomsctrl.PrivateRooms:
if not (self.frame.chatrooms.roomsctrl.IsPrivateRoomOwned(room) or self.frame.chatrooms.roomsctrl.IsPrivateRoomOperator(room)):
continue
if self.user in self.frame.chatrooms.roomsctrl.PrivateRooms[room]["users"]:
items.append(("#" + _("Remove from private room %s" %room), popup.OnPrivateRoomRemoveUser, gtk.STOCK_REMOVE, room))
else:
items.append(("#" + _("Add to private room %s" %room), popup.OnPrivateRoomAddUser, gtk.STOCK_ADD, room))
if self.frame.chatrooms.roomsctrl.IsPrivateRoomOwned(room):
if self.user in self.frame.chatrooms.roomsctrl.PrivateRooms[room]["operators"]:
items.append(("#" + _("Remove as operator of %s" %room), popup.OnPrivateRoomRemoveOperator, gtk.STOCK_REMOVE, room))
else:
items.append(("#" + _("Add as operator of %s" %room), popup.OnPrivateRoomAddOperator, gtk.STOCK_ADD, room))
popup.setup(*items)
return True
def InputDialog(parent, title, message, default = ""):
dlg = gtk.Dialog(title = title, parent = parent,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))
dlg.set_default_response(gtk.RESPONSE_OK)
dlg.set_border_width(10)
dlg.vbox.set_spacing(10)
l = gtk.Label(message)
l.set_alignment(0, 0.5)
dlg.vbox.pack_start(l, False, False)
entry = gtk.Entry()
entry.set_activates_default(True)
entry.set_text(default)
dlg.vbox.pack_start(entry, True, True)
dlg.vbox.show_all()
result = None
if dlg.run() == gtk.RESPONSE_OK:
result = entry.get_text()
dlg.destroy()
return result
class FastListModel(gtk.GenericTreeModel):
COLUMNS = 1
COLUMN_TYPES = [gobject.TYPE_STRING]
def __init__(self):
gtk.GenericTreeModel.__init__(self)
self.data = []
def on_get_flags(self):
'''returns the GtkTreeModelFlags for this particular type of model'''
return gtk.TREE_MODEL_LIST_ONLY
def on_get_n_columns(self):
'''returns the number of columns in the model'''
return self.COLUMNS
def on_get_column_type(self, index):
'''returns the type of a column in the model'''
return self.COLUMN_TYPES[index]
def on_get_path(self, iter):
'''returns the tree path (a tuple of indices at the various
levels) for a particular node.'''
return (iter,)
def on_get_iter(self, path):
'''returns the node corresponding to the given path. In our
case, the node is the path'''
if path[0] < len(self.data):
return path[0]
else:
return None
def on_get_value(self, iter, column):
'''returns the value stored in a particular column for the node'''
return self.data[iter][column]
def on_iter_next(self, iter):
'''returns the next node at this level of the tree'''
if iter + 1 < len(self.data):
return iter + 1
else:
return None
def on_iter_children(self, iter):
'''returns the first child of this node'''
return 0
def on_iter_has_child(self, iter):
'''returns true if this node has children'''
return False
def on_iter_n_children(self, iter):
'''returns the number of children of this node'''
return len(self.data)
def on_iter_nth_child(self, iter, n):
'''returns the nth child of this node'''
return n
def on_iter_parent(self, iter):
'''returns the parent of this node'''
return None
def string_sort_func(model, iter1, iter2, column):
val1 = model.get_value(iter1, column)
val2 = model.get_value(iter2, column)
if val1 is None:
val1 = ""
return locale.strcoll(val1, val2)
def int_sort_func(model, iter1, iter2, column):
try:
val1 = int(model.get_value(iter1, column))
except:
val1 = 0
try:
val2 = int(model.get_value(iter2, column))
except:
val2 = 0
return cmp(val1, val2)
def float_sort_func(model, iter1, iter2, column):
try:
val1 = float(model.get_value(iter1, column))
except:
val1 = 0.0
try:
val2 = float(model.get_value(iter2, column))
except:
val2 = 0.0
return cmp(val1, val2)
def WriteLog(logfile, logsdir, fn, msg):
if logfile is None:
oldumask = os.umask(0077)
if not os.path.exists(logsdir):
os.makedirs(logsdir)
logfile = open(os.path.join(logsdir, fixpath(fn.replace(os.sep, "-")) + ".log"), 'a', 0)
os.umask(oldumask)
text = "%s %s\n" % (recode(time.strftime(NICOTINE.np.config.sections["logging"]["log_timestamp"])), msg)
logfile.write(text.encode('UTF-8', 'replace'))
logfile.flush()
return logfile
def fixpath(path):
try:
if sys.platform == "win32":
chars = ["?", "/", "\\", "\"", ":", ">", "<", "|", "*"]
for char in chars:
path = path.replace(char, "_")
return path
except:
return path
def HumanSize(number):
try:
s = float(int(number))
if s >= 1000*1024*1024:
r = _("%.2f GiB") % (s / (1024.0*1024.0*1024.0))
elif s >= 1000*1024:
r = _("%.2f MiB") % (s / (1024.0*1024.0))
elif s >= 1000:
r = _("%.2f KiB") % (s / 1024.0)
else:
r = _("%d Bytes") % s
return r
except Exception, e:
return number
def HumanSpeed(number):
try:
s = float(int(number))
if s >= 1000*1024*1024:
r = _("%.2f GiB/s") % (s / (1024.0*1024.0*1024.0))
elif s >= 1000*1024:
r = _("%.2f MiB/s") % (s / (1024.0*1024.0))
elif s >= 1000:
r = _("%.2f KiB/s") % (s / 1024.0)
else:
r = _("%d B/s") % (number)
return r
except Exception, e:
return number
def Humanize(number):
fashion = DECIMALSEP
if fashion == "" or fashion == "":
return str(number)
elif fashion == "":
fashion = " "
number = str(number)
if number[0] == "-":
neg = "-"
number = number[1:]
else:
neg = ""
ret = ""
while number[-3:]:
part, number = number[-3:], number[:-3]
ret = "%s%s%s" % (part, fashion, ret)
return neg + ret[:-1]
def is_alias(aliases, cmd):
if not cmd:
return False
if cmd[0] != "/":
return False
cmd = cmd[1:].split(" ")
if cmd[0] in aliases:
return True
return False
def expand_alias(aliases, cmd):
output = _expand_alias(aliases, cmd)
return findBestEncoding(output, ['UTF-8', 'ASCII'])
def _expand_alias(aliases, cmd):
def getpart(line):
if line[0] != "(":
return ""
ix = 1
ret = ""
level = 0
while ix < len(line):
if line[ix] == "(":
level = level + 1
if line[ix] == ")":
if level == 0:
return ret
else:
level = level - 1
ret = ret + line[ix]
ix = ix + 1
return ""
if not is_alias(aliases, cmd):
return None
try:
cmd = cmd[1:].split(" ")
alias = aliases[cmd[0]]
ret = ""
i = 0
while i < len(alias):
if alias[i:i+2] == "$(":
arg=getpart(alias[i+1:])
if not arg:
ret = ret + "$"
i = i + 1
continue
i = i + len(arg) + 3
args = arg.split("=",1)
if len(args) > 1:
default = args[1]
else:
default = ""
args = args[0].split(":")
if len(args) == 1:
first = last = int(args[0])
else:
if args[0]:
first = int(args[0])
else:
first = 1
if args[1]:
last = int(args[1])
else:
last = len(cmd)
v = string.join(cmd[first:last+1])
if not v: v = default
ret = ret + v
elif alias[i:i+2] == "|(":
arg = getpart(alias[i+1:])
if not arg:
ret = ret + "|"
i = i + 1
continue
i = i + len(arg) + 3
for j in range(len(cmd)-1, -1, -1):
arg = arg.replace("$%i" % j, cmd[j])
arg = arg.replace("$@", string.join(cmd[1:], " "))
version = sys.version_info
if version[0] == 3 or (version[0] >= 2 and version[1] >= 4):
import subprocess
p = subprocess.Popen(arg, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=(not sys.platform.startswith("win")))
exit = p.wait()
(stdout, stdin) = (p.stdout, p.stdin)
v = stdout.read().split("\n")
r = ""
for l in v:
l = l.strip()
if l:
r = r + l + "\n"
ret = ret + r.strip()
stdin.close()
stdout.close()
else:
stdin, stdout = os.popen2(arg)
v = stdout.read().split("\n")
r = ""
for l in v:
l = l.strip()
if l:
r = r + l + "\n"
ret = ret + r.strip()
stdin.close()
stdout.close()
try:
os.wait()
except OSError, error:
pass
except Exception, error:
pass
else:
ret = ret + alias[i]
i = i + 1
return ret
except Exception, error:
print error
pass
return ""
def EncodingsMenu(np, section = None, entry = None):
if section and entry and entry in np.config.sections["server"][section]:
encoding = np.config.sections["server"][section][entry]
else:
encoding = np.config.sections["server"]["enc"]
z = []
for enc in np.getencodings():
z.append(enc)
return encoding, z
def SaveEncoding(np, section, entry, encoding):
if encoding != np.config.sections["server"]["enc"]:
np.config.sections["server"][section][entry] = encoding
elif entry in np.config.sections["server"][section]:
del np.config.sections["server"][section][entry]
np.config.writeConfiguration()
class ImportWinSlskConfig:
def __init__(self, config, Path, Queue, Login, Rooms, BuddyList, BanList, IgnoreList, UserInfo, UserImage):
self.config = config
self.Path = Path
self.Queue = Queue
self.Login = Login
self.Rooms = Rooms
self.BuddyList = BuddyList
self.BanList = BanList
self.IgnoreList = IgnoreList
self.UserInfo = UserInfo
self.UserImage = UserImage
def Run(self):
if not self.check_slskdir():
return 0
if not self.Queue and not self.Login and not self.Rooms and not self.BuddyList and not self.IgnoreList and not self.BanList and not self.UserInfo and not self.UserImage:
return 2
if self.Queue:
# Get download queue
windownloads = self.get_downloads(self.winpath('queue2.cfg'))
for i in windownloads:
if not i in self.config.sections["transfers"]["downloads"]:
#print i
self.config.sections["transfers"]["downloads"].append(i)
if self.BuddyList:
# Getting userlist
users = self.get_basic_config(self.winpath('hotlist.cfg'))
for i in users:
if not self.is_in_user_list(i, self.config.sections["server"]["userlist"]):
#print [i,'']
self.config.sections["server"]["userlist"].append([i, "", 0, 0, 0, "", ""])
if self.Login:
# Get login and password
(login,passw) = self.get_user_and_pass(self.winpath('login.cfg'))
self.config.sections["server"]["login"] = login
self.config.sections["server"]["passw"] = passw
if self.Rooms:
# Get the list of autojoined chatrooms
chatrooms = self.get_basic_config(self.winpath('chatrooms.cfg'))
chatrooms.append('nicotine')
for i in chatrooms:
if i not in self.config.sections["server"]["autojoin"]:
self.config.sections["server"]["autojoin"].append(i)
if self.BanList:
# Get the list of banned users
banlist = self.get_basic_config(self.winpath('dlbans.cfg'))
for i in banlist:
if i not in self.config.sections["server"]["banlist"]:
self.config.sections["server"]["banlist"].append(i)
if self.IgnoreList:
# Get the list of ignored users
ignorelist = self.get_basic_config(self.winpath('ignores.cfg'))
for i in ignorelist:
if i not in self.config.sections["server"]["ignorelist"]:
self.config.sections["server"]["ignorelist"].append(i)
if self.UserInfo:
# Get userinfo and image
(descr, imgdata) = self.get_userinfo(self.winpath('userinfo.cfg'))
if descr:
self.config.sections["userinfo"]['descr'] = descr.__repr__()
if self.UserImage:
(descr, imgdata) = self.get_userinfo(self.winpath('userinfo.cfg'))
if imgdata:
img = self.save_image(imgdata)
if img != "":
self.config.sections["userinfo"]['pic'] = img
return 1
def check_slskdir(self):
path = ""
# we check if the file queue2.cfg exists under the slsk dir
if self.Queue:
path = self.winpath('queue2.cfg')
elif self.BuddyList:
path = self.winpath('hotlist.cfg')
elif self.Login:
path = self.winpath('login.cfg')
elif self.Rooms:
path = self.winpath('chatrooms.cfg')
elif self.BanList:
path = self.winpath('dlbans.cfg')
elif self.IgnoreList:
path = self.winpath('ignores.cfg')
elif self.UserInfo:
path = self.winpath('userinfo.cfg')
if not os.access(path, os.F_OK):
return 0
return 1
def winpath(self, file):
return os.path.join(self.Path, file)
def get_downloads(self, fname):
"""Returns the list of downloads in the queue2.cnf file
The windows slsk queue2.cnf file format:
- little-endian, wordsize=4
- The file starts with 3 ints (4bytes per int).
- First 2 ints are unknown
- The 3rd one is the number of entries in the file
Then for each download entry:
- The length of the username, followed by the username
- The length of the remote filename, followed by the remote filename
- The length of the local dir, followed by the local dir
The file can contain some unknown stuff at the end."""
infile = open(fname, 'rb')
intsize = 4
downloads = []
str = infile.read(3*intsize)
i1, i2, n_entries = unpack("iii", str)
for i in range(n_entries):
length = unpack("i", infile.read(intsize))[0]
uname = infile.read(length)
length = unpack("i", infile.read(intsize))[0]
remfile = infile.read(length)
length = unpack("i", infile.read(intsize))[0]
localdir = infile.read(length)
downloads.append([uname, remfile, localdir])
infile.close()
return downloads
def get_user_and_pass(self, fname):
"""Returns a (user,passwd) tuple.
File format of login.cfg (see also get_downloads):
- two unknown ints at the beginning
- the number of bytes of the username, followed by the username
- the number of bytes from the passwd, followed by the passwd
- followed by some undetermined bytes"""
infile = open(fname, 'rb')
intsize = 4
downloads = []
str = infile.read(3*intsize)
i1, i2, length = unpack("iii", str)
user = infile.read(length)
length = unpack("i", infile.read(intsize))[0]
passwd = infile.read(length)
infile.close()
return (user, passwd)
def get_basic_config(self, fname):
"""Works for userlist, chatrooms, dlbans, etc.
The hotlist.cnf file format (see get_downloads for more info):
- The file starts with an int: the number of users in the list
Then for each user:
- The length of the username, followed by the username"""
infile = open(fname, 'rb')
intsize = 4
users = []
str = infile.read(1*intsize)
n_entries = unpack("i", str)[0]
for i in range(n_entries):
length = unpack("i", infile.read(intsize))[0]
user = infile.read(length)
users.append(user)
infile.close()
return users
def get_userinfo(self, fname):
"""The userinfo file format:
- an int with the size of the text part, followed by the text part
- one byte, an indication for an image. 0 is no image, 1 is image
- an int with the image size, followed by the image data """
infile = open(fname, 'rb')
intsize = 4
imgdata = None
str = infile.read(intsize)
length = unpack("i", str)[0]
descr = infile.read(length)
descr = string.replace(descr, "\r", "")
has_image = unpack("b",infile.read(1))[0]
if has_image:
length = unpack("i", infile.read(intsize))[0]
imgdata = infile.read(length)
infile.close()
return (descr, imgdata)
def save_image(self, imgdata):
"""Save IMGDATA to file and return the filename or None."""
if not imgdata:
print "No image data. No image saved."
return ""
extension = imghdr.what(None, imgdata)
if not extension:
# how to print to stderr in python? (no real problem here)
print "Could not determine image type. Image not saved."
return ""
fname = os.path.abspath(self.config.filename + '-userinfo.' + extension)
outfile = open(fname, "wb")
outfile.write(imgdata)
outfile.close()
print "Wrote image to \"%s\"" % (fname)
return fname
def is_in_user_list(self, user, user_list):
"""Checks if USER is in USER_LIST, ignoring the comment field"""
for i in user_list:
if type(i) == type(''):
sys.stderr.write("\nError: The nicotine userlist is in an old pyslsk format.\n" +
"Please run Nicotine once before running this script.\n" +
"Config file not updated.\n")
break
if user == i[0]:
return 1
return 0