Substantial improvements to gadgets

svn: r9587
This commit is contained in:
Doug Blank
2007-12-26 08:26:33 +00:00
parent 30ee5eeaf7
commit 9692af5e11
2 changed files with 317 additions and 73 deletions

View File

@@ -32,19 +32,27 @@ import gobject
import traceback import traceback
import time import time
import pango import pango
import os
import Errors import Errors
import const import const
import PageView import PageView
import ManagedWindow import ManagedWindow
import ConfigParser
import Utils
AVAILABLE_GADGETS = [] AVAILABLE_GADGETS = []
GADGET_FILENAME = os.path.join(const.HOME_DIR,"gadgets.ini")
NL = "\n"
debug = False debug = False
def register_gadget(data_dict): def register_gadget(data_dict):
global AVAILABLE_GADGETS global AVAILABLE_GADGETS
AVAILABLE_GADGETS.append(data_dict) base_opts = {"state":"maximized",
"column": -1, "row": -1}
base_opts.update(data_dict)
AVAILABLE_GADGETS.append(base_opts)
def register(**data): def register(**data):
if "type" in data: if "type" in data:
@@ -59,6 +67,14 @@ def get_gadget_opts(name, opts):
return my_data return my_data
return {} return {}
def get_gadget_options_by_name(name):
if debug: print "name:", name
for opts in AVAILABLE_GADGETS:
gname = opts.get("name", None)
if gname == name:
return opts.copy()
return None
def make_requested_gadget(viewpage, name, opts, dbstate, uistate): def make_requested_gadget(viewpage, name, opts, dbstate, uistate):
for data in AVAILABLE_GADGETS: for data in AVAILABLE_GADGETS:
if data.get("name", None) == name: if data.get("name", None) == name:
@@ -78,11 +94,6 @@ class LinkTag(gtk.TextTag):
self.set_property('underline', pango.UNDERLINE_SINGLE) self.set_property('underline', pango.UNDERLINE_SINGLE)
tag_table.add(self) tag_table.add(self)
# notes on building a filter: src/Filters/_FilterParser.py
# f = GenericFilterFactory('Person')()
# r = Rules.Person.HasNameOf
# f.add_rule(r)
class GadgetWindow(ManagedWindow.ManagedWindow): class GadgetWindow(ManagedWindow.ManagedWindow):
def __init__(self, gadget): def __init__(self, gadget):
self.title = gadget.title + " Gadget" self.title = gadget.title + " Gadget"
@@ -94,9 +105,8 @@ class GadgetWindow(ManagedWindow.ManagedWindow):
None, self.title) None, self.title)
self.window.set_size_request(400,300) self.window.set_size_request(400,300)
self.window.connect('response', self.close) self.window.connect('response', self.close)
self.gadget.state = "windowed"
self.gadget.mainframe.reparent(self.window.vbox) self.gadget.mainframe.reparent(self.window.vbox)
self.window.show_all() self.window.show()
def build_menu_names(self, obj): def build_menu_names(self, obj):
return (self.title, 'Gadget') return (self.title, 'Gadget')
@@ -105,9 +115,20 @@ class GadgetWindow(ManagedWindow.ManagedWindow):
return self.title return self.title
def close(self, *args): def close(self, *args):
"""
Closes the detached GadgetWindow.
"""
self.gadget.gvclose.show()
self.gadget.gvstate.show()
self.gadget.gvproperties.show()
self.gadget.viewpage.detached_frames.remove(self.gadget)
self.gadget.state = "maximized" self.gadget.state = "maximized"
self.gadget.mainframe.reparent(self.gadget.parent) self.gadget.mainframe.reparent(self.gadget.parent)
# FIXME: need to pack as it was, not just stick it in
ManagedWindow.ManagedWindow.close(self, *args) ManagedWindow.ManagedWindow.close(self, *args)
# The save can stay here because GRAMPS doesn't run this method
# on exit, apparently:
self.gadget.viewpage.save()
#------------------------------------------------------------------------ #------------------------------------------------------------------------
@@ -133,20 +154,6 @@ class Gadget(object):
def active_changed(self, handle): def active_changed(self, handle):
pass pass
def on_save(self):
pass
def _on_save(self):
# save data
pass
def on_load(self):
pass
def _on_load(self):
# load data
pass
def _db_changed(self, db): def _db_changed(self, db):
if debug: print "%s is _connecting" % self.gui.title if debug: print "%s is _connecting" % self.gui.title
self.dbstate.db = db self.dbstate.db = db
@@ -176,6 +183,9 @@ class Gadget(object):
priority=gobject.PRIORITY_LOW) priority=gobject.PRIORITY_LOW)
def background(self): # return false finishes def background(self): # return false finishes
"""
Generator which will be run in the background.
"""
if debug: print "%s dummy" % self.gui.title if debug: print "%s dummy" % self.gui.title
yield False yield False
@@ -189,6 +199,9 @@ class Gadget(object):
self._idle_id = 0 self._idle_id = 0
def _updater(self): def _updater(self):
"""
Runs the generator.
"""
if debug: print "%s _updater" % self.gui.title if debug: print "%s _updater" % self.gui.title
try: try:
retval = self._generator.next() retval = self._generator.next()
@@ -241,6 +254,19 @@ class Gadget(object):
except Errors.WindowActiveError: except Errors.WindowActiveError:
pass pass
else: else:
# Create filters on the fly:
#f = GenericFilterFactory('Person')()
#r = Rules.Person.HasNameOf
#rule = r([person.get_primary_name.get_surname()])
#f.add_rule(rule)
#filter_info = (False, f)
#model = self.make_model(self.dbstate.db, 0,
# search=filter_info)
#self.list.set_model(self.model)
#self.dirty = False
#self.uistate.show_filter_results(self.dbstate,
# self.model.displayed,
# self.model.total)
self.gui.dbstate.change_active_person(person) self.gui.dbstate.change_active_person(person)
return True # handled event return True # handled event
return False # did not handle event return False # did not handle event
@@ -264,6 +290,8 @@ class Gadget(object):
def set_text(self, text): def set_text(self, text):
self.gui.buffer.set_text(text) self.gui.buffer.set_text(text)
def logical_true(value):
return value in ["True", True, 1, "1"]
class GuiGadget: class GuiGadget:
""" """
@@ -278,10 +306,11 @@ class GuiGadget:
self.uistate = uistate self.uistate = uistate
self.title = title self.title = title
########## Set defaults ########## Set defaults
self.expand = kwargs.get("expand", False) self.name = kwargs.get("name", "Unnamed Gadget")
self.height = kwargs.get("height", 200) self.expand = logical_true(kwargs.get("expand", False))
self.column = kwargs.get("column", -1) self.height = int(kwargs.get("height", 200))
self.row = kwargs.get("row", -1) self.column = int(kwargs.get("column", -1))
self.row = int(kwargs.get("row", -1))
self.state = kwargs.get("state", "maximized") self.state = kwargs.get("state", "maximized")
########## ##########
self.xml = gtk.glade.XML(const.GLADE_FILE, 'gvgadget', "gramps") self.xml = gtk.glade.XML(const.GLADE_FILE, 'gvgadget', "gramps")
@@ -292,10 +321,12 @@ class GuiGadget:
self.titlelabel = self.xml.get_widget('gvtitle') self.titlelabel = self.xml.get_widget('gvtitle')
self.titlelabel.set_text("<b><i>%s</i></b>" % self.title) self.titlelabel.set_text("<b><i>%s</i></b>" % self.title)
self.titlelabel.set_use_markup(True) self.titlelabel.set_use_markup(True)
self.xml.get_widget('gvclose').connect('clicked', self.close) self.gvclose = self.xml.get_widget('gvclose')
self.xml.get_widget('gvstate').connect('clicked', self.change_state) self.gvclose.connect('clicked', self.close)
self.xml.get_widget('gvproperties').connect('clicked', self.gvstate = self.xml.get_widget('gvstate')
self.set_properties) self.gvstate.connect('clicked', self.change_state)
self.gvproperties = self.xml.get_widget('gvproperties')
self.gvproperties.connect('clicked', self.set_properties)
self.xml.get_widget('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE, self.xml.get_widget('gvcloseimage').set_from_stock(gtk.STOCK_CLOSE,
gtk.ICON_SIZE_MENU) gtk.ICON_SIZE_MENU)
self.xml.get_widget('gvstateimage').set_from_stock(gtk.STOCK_REMOVE, self.xml.get_widget('gvstateimage').set_from_stock(gtk.STOCK_REMOVE,
@@ -304,27 +335,32 @@ class GuiGadget:
gtk.ICON_SIZE_MENU) gtk.ICON_SIZE_MENU)
# source: # source:
drag = self.xml.get_widget('gvproperties') drag = self.gvproperties
drag.drag_source_set(gtk.gdk.BUTTON1_MASK, drag.drag_source_set(gtk.gdk.BUTTON1_MASK,
[GuiGadget.LOCAL_DRAG_TARGET], [GuiGadget.LOCAL_DRAG_TARGET],
gtk.gdk.ACTION_COPY) gtk.gdk.ACTION_COPY)
def close(self, obj): def close(self, obj):
if self.state == "windowed":
return
del self.viewpage.gadget_map[self.title] del self.viewpage.gadget_map[self.title]
del self.viewpage.frame_map[str(self.mainframe)] del self.viewpage.frame_map[str(self.mainframe)]
self.mainframe.destroy() self.mainframe.destroy()
self.viewpage.save()
def detach(self): def detach(self, save=True):
# hide buttons:
self.gvclose.hide()
self.gvstate.hide()
self.gvproperties.hide()
# keep a pointer to old parent frame: # keep a pointer to old parent frame:
self.parent = self.mainframe.get_parent() self.parent = self.mainframe.get_parent()
self.viewpage.detached_frames.append(self)
# make a window, and attach it there # make a window, and attach it there
self.detached_window = GadgetWindow(self) self.detached_window = GadgetWindow(self)
self.state = "windowed"
def attach(self): if save:
# do this if you close window self.viewpage.save()
# move this widget to old parent frame
# remove window
pass
def set_state(self, state): def set_state(self, state):
self.state = state self.state = state
@@ -343,6 +379,7 @@ class GuiGadget:
column = self.mainframe.get_parent() # column column = self.mainframe.get_parent() # column
expand,fill,padding,pack = column.query_child_packing(self.mainframe) expand,fill,padding,pack = column.query_child_packing(self.mainframe)
column.set_child_packing(self.mainframe,self.expand,fill,padding,pack) column.set_child_packing(self.mainframe,self.expand,fill,padding,pack)
self.viewpage.save()
def change_state(self, obj): def change_state(self, obj):
if self.state == "windowed": if self.state == "windowed":
@@ -359,7 +396,7 @@ class GuiGadget:
else: else:
self.detach() self.detach()
return return
# FIXME: how to control expand/not expand and detached # FIXME: add control for expand AND detach
self.expand = not self.expand self.expand = not self.expand
if self.state == "maximized": if self.state == "maximized":
column = self.mainframe.get_parent() # column column = self.mainframe.get_parent() # column
@@ -375,6 +412,26 @@ class GuiGadget:
def set_text(self, text): def set_text(self, text):
self.buffer.set_text(text) self.buffer.set_text(text)
def get_source_widget(self):
"""
Hack to allow us to send this object to the drop_widget
method as a context.
"""
return self.gvproperties
class MyScrolledWindow(gtk.ScrolledWindow):
def show_all(self):
# first show them all:
gtk.ScrolledWindow.show_all(self)
# Hack to get around show_all that shows hidden items
# do once, the first time showing
if self.viewpage:
gadgets = [g for g in self.viewpage.gadget_map.values()]
self.viewpage = None
for gadget in gadgets:
if gadget.state == "minimized":
gadget.set_state("minimized")
class MyGrampsView(PageView.PageView): class MyGrampsView(PageView.PageView):
""" """
@@ -386,7 +443,7 @@ class MyGrampsView(PageView.PageView):
Creates a MyGrampsView, with the current dbstate and uistate Creates a MyGrampsView, with the current dbstate and uistate
""" """
PageView.PageView.__init__(self, _('My Gramps'), dbstate, uistate) PageView.PageView.__init__(self, _('My Gramps'), dbstate, uistate)
self.column_count = 3 self._popup_xy = None
def change_db(self, event): def change_db(self, event):
""" """
@@ -399,9 +456,13 @@ class MyGrampsView(PageView.PageView):
Builds the container widget for the interface. Must be overridden by the Builds the container widget for the interface. Must be overridden by the
the base class. Returns a gtk container widget. the base class. Returns a gtk container widget.
""" """
frame = gtk.ScrolledWindow() # load the user's gadgets and set columns, etc
user_gadgets = self.load_gadgets()
# build the GUI:
frame = MyScrolledWindow()
frame.viewpage = self
frame.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) frame.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
hbox = gtk.HBox(homogeneous=True) self.hbox = gtk.HBox(homogeneous=True)
# FIXME: issue when window is scrolled down; drops in wrong place # FIXME: issue when window is scrolled down; drops in wrong place
# Set up drag and drop # Set up drag and drop
frame.drag_dest_set(gtk.DEST_DEFAULT_MOTION | frame.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
@@ -410,32 +471,21 @@ class MyGrampsView(PageView.PageView):
[('GADGET', 0, 80)], [('GADGET', 0, 80)],
gtk.gdk.ACTION_COPY) gtk.gdk.ACTION_COPY)
frame.connect('drag_drop', self.drop_widget) frame.connect('drag_drop', self.drop_widget)
frame.connect('button-press-event', self._button_press)
frame.add_with_viewport(hbox) frame.add_with_viewport(self.hbox)
# Create the columns: # Create the columns:
self.columns = [] self.columns = []
for i in range(self.column_count): for i in range(self.column_count):
self.columns.append(gtk.VBox()) self.columns.append(gtk.VBox())
hbox.pack_start(self.columns[-1],expand=True) self.hbox.pack_start(self.columns[-1],expand=True)
# Load the gadgets # Load the gadgets
self.gadget_map = {} # title->gadget self.gadget_map = {} # title->gadget
self.frame_map = {} # frame->gadget self.frame_map = {} # frame->gadget
# FIXME self.detached_frames = [] # list of detached mainframes
# get the user's gadgets from .gramps # get the user's gadgets from ~/.gramps/gadgets.ini
# and/or from open database
# Load the user's gadgets: # Load the user's gadgets:
for (name, opts) in [ for (name, opts) in user_gadgets:
('Stats Gadget', {}),
('Top Surnames Gadget', {}),
#('Families Gadget', {}),
#('Families Gadget', {"title": "My Peeps"}),
('Hello World Gadget', {}),
('Shell Gadget', {}),
('Python Gadget', {}),
('TODO Gadget', {}),
('Log Gadget', {}),
#('Events Gadget', {}),
]:
all_opts = get_gadget_opts(name, opts) all_opts = get_gadget_opts(name, opts)
if "title" not in all_opts: if "title" not in all_opts:
all_opts["title"] = "Untitled Gadget" all_opts["title"] = "Untitled Gadget"
@@ -456,31 +506,119 @@ class MyGrampsView(PageView.PageView):
print "Can't make gadget of type '%s'." % name print "Can't make gadget of type '%s'." % name
else: else:
print "Ignoring duplicate named gadget '%s'." % all_opts["title"] print "Ignoring duplicate named gadget '%s'." % all_opts["title"]
self.place_gadgets()
return frame
def clear_gadgets(self):
"""
Detach all of the mainframe gadgets from the columns.
"""
gadgets = [g for g in self.gadget_map.values()]
for gadget in gadgets:
column = gadget.mainframe.get_parent()
column.remove(gadget.mainframe)
def place_gadgets(self):
"""
Place the gadget mainframes in the columns.
"""
gadgets = [g for g in self.gadget_map.values()]
# put the gadgets where they go: # put the gadgets where they go:
# sort by row
gadgets.sort(lambda a, b: cmp(a.row, b.row))
cnt = 0 cnt = 0
for gadget in self.gadget_map.values(): for gadget in gadgets:
# see if the user wants this in a particular location: # see if the user wants this in a particular location:
# and if there are that many columns # and if there are that many columns
if gadget.column >= 0 and gadget.column < len(self.columns): if gadget.column >= 0 and gadget.column < self.column_count:
pos = gadget.column pos = gadget.column
else: else:
# else, spread them out: # else, spread them out:
pos = cnt % len(self.columns) pos = cnt % self.column_count
# to make as big as possible, set to True:
# GTK BUG: can't minimize:
#if gadget.state == "minimized": # starts max, change to min it
# self.columns[pos].pack_start(gadget.mainframe, expand=False)
#else:
self.columns[pos].pack_start(gadget.mainframe, expand=gadget.expand) self.columns[pos].pack_start(gadget.mainframe, expand=gadget.expand)
gadget.column = pos
# set height on gadget.scrolledwindow here: # set height on gadget.scrolledwindow here:
gadget.scrolledwindow.set_size_request(-1, gadget.height) gadget.scrolledwindow.set_size_request(-1, gadget.height)
# GTK BUG: can't minimize: # Can't minimize here, because GRAMPS calls show_all later:
#if gadget.state == "minimized": # starts max, change to min it #if gadget.state == "minimized": # starts max, change to min it
#gadget.set_state("minimized") # minimize it # gadget.set_state("minimized") # minimize it
if gadget.state == "windowed":
gadget.detach(save=False) # don't save on this change
cnt += 1 cnt += 1
return frame
def load_gadgets(self):
self.column_count = 3
retval = []
filename = GADGET_FILENAME
if filename and os.path.exists(filename):
cp = ConfigParser.ConfigParser()
cp.read(filename)
for sec in cp.sections():
if sec == "My Gramps View Options":
if "column_count" in cp.options(sec):
self.column_count = int(cp.get(sec, "column_count"))
else:
data = {}
for opt in cp.options(sec):
data[opt] = cp.get(sec, opt).strip()
if "name" not in data:
data["name"] = "Unnamed Gadget"
retval.append((data["name"], data)) # name, opts
else:
# give defaults as currently known
for item in AVAILABLE_GADGETS:
retval.append((item["name"], item))
return retval
def save(self, *args):
if debug: print "saving"
filename = GADGET_FILENAME
fp = open(filename, "w")
fp.write(";; Gramps gadgets file" + NL)
fp.write((";; Automatically created at %s" % time.strftime("%Y/%m/%d %H:%M:%S")) + NL + NL)
fp.write("[My Gramps View Options]" + NL)
fp.write(("column_count=%d" + NL + NL) % self.column_count)
for col in range(self.column_count):
row = 0
for gframe in self.columns[col]:
gadget = self.frame_map[str(gframe)]
opts = get_gadget_options_by_name(gadget.name)
if opts != None:
base_opts = opts.copy()
for key in base_opts:
if key in gadget.__dict__:
base_opts[key] = gadget.__dict__[key]
fp.write(("[%s]" + NL) % gadget.title)
for key in base_opts:
if key == "content": continue
if key == "title": continue
if key == "column": continue
if key == "row": continue
fp.write(("%s=%s" + NL)% (key, base_opts[key]))
fp.write(("column=%d" + NL) % col)
fp.write(("row=%d" + NL) % row)
fp.write(NL)
row += 1
for gadget in self.detached_frames:
opts = get_gadget_options_by_name(gadget.name)
if opts != None:
base_opts = opts.copy()
for key in base_opts:
if key in gadget.__dict__:
base_opts[key] = gadget.__dict__[key]
fp.write(("[%s]" + NL) % gadget.title)
for key in base_opts:
if key == "content": continue
if key == "title": continue
fp.write(("%s=%s" + NL)% (key, base_opts[key]))
fp.write(NL)
fp.close()
def drop_widget(self, source, context, x, y, timedata): def drop_widget(self, source, context, x, y, timedata):
"""
This is the destination method for handling drag and drop
of a gadget onto the main scrolled window.
"""
button = context.get_source_widget() button = context.get_source_widget()
hbox = button.get_parent() hbox = button.get_parent()
mainframe = hbox.get_parent() mainframe = hbox.get_parent()
@@ -502,6 +640,7 @@ class MyGrampsView(PageView.PageView):
self.columns[col].remove(gframe) self.columns[col].remove(gframe)
stack.append(gframe) stack.append(gframe)
maingadget = self.frame_map[str(mainframe)] maingadget = self.frame_map[str(mainframe)]
maingadget.column = col
if maingadget.state == "maximized": if maingadget.state == "maximized":
expand = maingadget.expand expand = maingadget.expand
else: else:
@@ -514,6 +653,7 @@ class MyGrampsView(PageView.PageView):
else: else:
expand = False expand = False
self.columns[col].pack_start(gframe, expand=expand) self.columns[col].pack_start(gframe, expand=expand)
self.save()
return True return True
def define_actions(self): def define_actions(self):
@@ -522,7 +662,77 @@ class MyGrampsView(PageView.PageView):
View. The user typically defines self.action_list and View. The user typically defines self.action_list and
self.action_toggle_list in this function. self.action_toggle_list in this function.
""" """
return '' self.action = gtk.ActionGroup(self.title + "/Gadgets")
self.action.add_actions([('AddGadget',gtk.STOCK_ADD,_("_Add a gadget")),
('Columns1',None,_("Set columns to 1"),
None,None,
lambda obj:self.set_columns(1)),
('Columns2',None,_("Set columns to 2"),
None,None,
lambda obj:self.set_columns(2)),
('Columns3',None,_("Set columns to 3"),
None,None,
lambda obj:self.set_columns(3)),
])
self._add_action_group(self.action)
def set_columns(self, num):
# clear the gadgets:
self.clear_gadgets()
# clear the columns:
for column in self.columns:
frame = column.get_parent()
frame.remove(column)
del column
# create the new ones:
self.column_count = num
self.columns = []
for i in range(self.column_count):
self.columns.append(gtk.VBox())
self.columns[-1].show()
self.hbox.pack_start(self.columns[-1],expand=True)
# place the gadgets back in the new columns
self.place_gadgets()
self.widget.show()
self.save()
def add_gadget(self, obj):
name = obj.get_child().get_label()
all_opts = get_gadget_options_by_name(name)
if "title" not in all_opts:
all_opts["title"] = "Untitled Gadget"
# uniqify titles:
unique = all_opts["title"]
cnt = 1
while unique in self.gadget_map:
unique = all_opts["title"] + ("-%d" % cnt)
cnt += 1
all_opts["title"] = unique
if all_opts["title"] not in self.gadget_map:
g = make_requested_gadget(self, name, all_opts,
self.dbstate, self.uistate)
if g:
self.gadget_map[all_opts["title"]] = g
self.frame_map[str(g.mainframe)] = g
else:
print "Can't make gadget of type '%s'." % name
else:
print "Ignoring duplicate named gadget '%s'." % all_opts["title"]
if g:
gadget = g
if gadget.column >= 0 and gadget.column < len(self.columns):
pos = gadget.column
else:
pos = 0
self.columns[pos].pack_start(gadget.mainframe, expand=gadget.expand)
# set height on gadget.scrolledwindow here:
gadget.scrolledwindow.set_size_request(-1, gadget.height)
## now drop it in right place
if self._popup_xy != None:
self.drop_widget(self.widget, gadget,
self._popup_xy[0], self._popup_xy[1], 0)
self.save()
def get_stock(self): def get_stock(self):
""" """
@@ -534,3 +744,36 @@ class MyGrampsView(PageView.PageView):
def build_tree(self): def build_tree(self):
return return
def ui_definition(self):
return """
<ui>
<popup name="Popup">
<menuitem action="AddGadget"/>
<separator/>
<menuitem action="Columns1"/>
<menuitem action="Columns2"/>
<menuitem action="Columns3"/>
</popup>
</ui>
"""
def _button_press(self, obj, event):
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
self._popup_xy = (event.x, event.y)
menu = self.uistate.uimanager.get_widget('/Popup')
qr_menu = self.uistate.uimanager.get_widget('/Popup/AddGadget').get_submenu()
if qr_menu == None:
qr_menu = gtk.Menu()
names = []
for opts in AVAILABLE_GADGETS:
names.append(opts["name"])
names.sort()
for name in names:
Utils.add_menuitem(qr_menu, name,
None, self.add_gadget)
self.uistate.uimanager.get_widget('/Popup/AddGadget').set_submenu(qr_menu)
if menu:
menu.popup(None, None, None, event.button, event.time)
return True
return False

View File

@@ -124,8 +124,9 @@ class TopSurnamesGadget(Gadget):
### All done! ### All done!
self.set_text("") self.set_text("")
for (count, surname) in surname_sort: for (count, surname) in surname_sort:
self.append_text(" %s, %d%%\n" % self.append_text(" %d. %s, %d%% (%d)\n" %
(surname, int((float(count)/total) * 100))) (line + 1, surname,
int((float(count)/total) * 100), count))
line += 1 line += 1
if line >= 10: if line >= 10:
break break