Reimplemented the window as a gtk.Dialog and documented the classes
author"German Poo-Caaman~o <gpoo@gnome.org>"
Wed, 03 Jan 2007 15:16:30 -0300
changeset 19 5019f0b46f7e
parent 18 53d38b5a97d3
child 20 fe0b5713ef32
Reimplemented the window as a gtk.Dialog and documented the classes
blogic/categories.py
--- a/blogic/categories.py	Wed Jan 03 10:46:07 2007 -0300
+++ b/blogic/categories.py	Wed Jan 03 15:16:30 2007 -0300
@@ -1,4 +1,8 @@
 #!/usr/bin/env python
+
+__doc__ = """Basic widgets for handling categories, where 
+only one can be primary and many other can be secondary."""
+
 #
 # blogic - Tool for blog's messages publishing
 # Copyright (C) 2006  German Poo-Caaman~o <gpoo@ubiobio.cl>
@@ -21,9 +25,6 @@
 pygtk.require('2.0')
 
 import gtk
-import gobject
-import gtkspell
-from gettext import bindtextdomain, textdomain
 from gettext import gettext as _
 
 (
@@ -33,22 +34,32 @@
     COLUMN_NAME,
 ) = range(4)
 
-data = (
- (0, False, False, 'Trivia'),
- (1, False, False, 'Normal'),
- (2, False, False, 'Critical'),
- (3, False, False, 'Urgent')
-)
+class CategoryView(gtk.TreeView):
+	"A custom implementation of gtk.TreeView for handle categories."
+
+	def __init__(self, data):
+		"""Initialize a gtk.TreeView creating a gtk.ListStore using
+		the list given.
 
-class CategoryView(gtk.TreeView):
-	def __init__(self, *args):
-		gtk.TreeView.__init__(self, *args)
-		self.set_model(self._create_model())
+		data is expected to be a list of tuples of (str, bool, bool, str):
+		    [ (id, is_primary, is_secondary, category),
+		      (id, is_primary, is_secondary, category),
+		      ...
+		      (id, is_primary, is_secondary, category)
+		    ]	
+
+		When more than one tuple is declared as active, only the first
+		one will be marked as primary and the others will be marked as
+		secondary.
+		"""
+		gtk.TreeView.__init__(self)
+
+		self.primary_path = None
+		self.set_model(self._create_model(data))
 		self._add_columns()
-		self.primary_path = None
 
-	def _create_model(self):
-		# Store
+	def _create_model(self, data):
+		more_than_one_primary = False
 		store = gtk.ListStore(str, bool, bool, str)
 		for item in data:
 			iter = store.append()
@@ -57,6 +68,16 @@
 			    COLUMN_PRIMARY, item[COLUMN_PRIMARY],
 			    COLUMN_SECONDARY, item[COLUMN_SECONDARY],
 			    COLUMN_NAME, item[COLUMN_NAME])
+
+			if more_than_one_primary:
+				store.set(iter, COLUMN_PRIMARY, False)
+				store.set(iter, COLUMN_SECONDARY, True)
+
+			if store.get_value(iter, COLUMN_PRIMARY):
+				store.set(iter, COLUMN_SECONDARY, True)
+				more_than_one_primary = True
+				self.primary_path = store.get_path(iter)
+
 		return store
 
 	def _add_columns(self):
@@ -64,17 +85,17 @@
 
 		cell = gtk.CellRendererToggle()
 		cell.connect('toggled', self._primary_toggled, model)
-		column = gtk.TreeViewColumn("Main", cell, active=COLUMN_PRIMARY)
+		column = gtk.TreeViewColumn(_('Main'), cell, active=COLUMN_PRIMARY)
 		self.append_column(column)
 
 		cell = gtk.CellRendererToggle()
 		cell.connect('toggled', self._secondary_toggled, model)
-		column = gtk.TreeViewColumn("Use", cell, active=COLUMN_SECONDARY)
+		column = gtk.TreeViewColumn(_('Use'), cell, active=COLUMN_SECONDARY)
 		column.set_sort_column_id(COLUMN_SECONDARY)
 		self.append_column(column)
 
 		cell = gtk.CellRendererText()
-		column = gtk.TreeViewColumn("Category", cell, text=COLUMN_NAME)
+		column = gtk.TreeViewColumn(_('Category'), cell, text=COLUMN_NAME)
 		column.set_sort_column_id(COLUMN_NAME)
 		self.append_column(column)
 
@@ -93,6 +114,9 @@
 		self.primary_path = path
 
 	def _secondary_toggled(self, cell, path, model):
+		"""Handle when the secondary category is selected/unselected.
+		When it gets unselected and it was also primary, we
+		unselected as primary as well."""
 		iter = model.get_iter(path)
 		secondary = not model.get_value(iter, COLUMN_SECONDARY)
 		model.set(iter, COLUMN_SECONDARY, secondary)
@@ -101,41 +125,88 @@
 			model.set(iter, COLUMN_PRIMARY, False)
 			self.primary_path = None
 
-class CategoryWindow(gtk.Window):
-	def __init__(self, *args):
-		gtk.Window.__init__(self, *args)
+	def get_data(self):
+		"""Returns two dictionaries. The firts one with the primary
+		category selected and the second one with the secondaries
+		categories selected.
+		"""
+
+		model = self.get_model()
+		primary = {} 
+		secondary = {}
+		for row in model:
+			if not row[COLUMN_PRIMARY] and row[COLUMN_SECONDARY]:
+				id = row[COLUMN_ID]
+				secondary[id] = row[COLUMN_NAME]
+			elif row[COLUMN_PRIMARY]:
+				id = row[COLUMN_ID]
+				primary[id]  = row[COLUMN_NAME]
 
-		self.set_border_width(8)
-		self.set_title('Choose categories')
+		# If we only have secondary categories, we need to choose
+		# whichever of them as a primary
+		if not primary and secondary:
+			import random
+			rand = random.Random()
+			id = rand.choice(secondary.keys())
+			primary[id] = secondary[id]
+			secondary.pop(id)
+		return (primary, secondary)
+
+class CategoryDialog(gtk.Dialog):
+	"""Implements a dialog to choose categories using the widget
+	CategoryView.
+	"""
+	def __init__(self, data, title='Choose categories', parent=None, 
+	             flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+	             buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                          gtk.STOCK_OK, gtk.RESPONSE_OK)):
+		"""It has the same behavior as gtk.Dialog, but expects an
+		extra parameter (data) wich is used to fill the model
+		of the widget CategoryView.
+
+		data is expected to be a list of tuples of (str, bool, bool, str):
+		    [ (id, is_primary, is_secondary, category),
+		      (id, is_primary, is_secondary, category),
+		      ...
+		      (id, is_primary, is_secondary, category)
+		    ]	
+
+		When more than one tuple is declared as active, only the first
+		one will be marked as primary and the others will be marked as
+		secondary.
+		"""
+		gtk.Dialog.__init__(self, title, parent, flags, buttons)
 
 		scroll = gtk.ScrolledWindow()
 		scroll.set_shadow_type(gtk.SHADOW_ETCHED_IN)
 		scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
-		treeview = CategoryView()
-		treeview.set_rules_hint(True)
+		self.treeview = CategoryView(data)
+		self.treeview.set_rules_hint(True)
+		scroll.add(self.treeview)
 
-		scroll.add(treeview)
-
-		vbox = gtk.VBox(False, 8)
-		vbox.pack_start(scroll)
+		self.vbox.pack_start(scroll)
 
-		bbox = gtk.HButtonBox()
-		bbox.set_layout(gtk.BUTTONBOX_END)
-		bbox.set_spacing(8)
-		#bbox.set_border_width(12)
-		bbox.add(gtk.Button(stock='gtk-cancel'))
-		bbox.add(gtk.Button(stock='gtk-ok'))
-
-		vbox.pack_start(bbox, False, False)
-
-		self.add(vbox)
+	def get_categories_choosen(self):
+		"""Returns two dictionaries. The firts one with the primary
+		category selected and the second one with the secondaries
+		categories selected.
+		"""
+		return self.treeview.get_data()
 
 if __name__ == "__main__":
-	window = CategoryWindow(gtk.WINDOW_TOPLEVEL)
-	#window = create_window()
-	window.set_default_size(300,250)
-	window.connect('delete-event', gtk.main_quit, None)
-	window.show_all()
+	test_data = [
+	 (0, False, False, 'Trivia'),
+	 (1, True, False, 'Normal'),
+	 (2, False, True, 'Critical'),
+	 (3, True, False, 'Urgent')
+	]
 
-	gtk.main()
+	dialog = CategoryWindow(data=test_data)
+	dialog.set_default_size(300,250)
+	dialog.show_all()
+	result = dialog.run()
+
+	while result == gtk.RESPONSE_OK:
+		print dialog.get_categories_choosen()
+		result = dialog.run()