Fix tag.die() declaration
[canto-curses.git] / canto_curses / tag.py
1 # -*- coding: utf-8 -*-
2 #Canto-curses - ncurses RSS reader
3 #   Copyright (C) 2010 Jack Miller <jack@codezen.org>
4 #
5 #   This program is free software; you can redistribute it and/or modify
6 #   it under the terms of the GNU General Public License version 2 as 
7 #   published by the Free Software Foundation.
8
9 from canto_next.hooks import call_hook, on_hook, remove_hook
10
11 from theme import FakePad, WrapPad, theme_print
12 from story import Story
13
14 import logging
15 import curses
16
17 log = logging.getLogger("TAG")
18
19 # The Tag class manages stories. Externally, it looks
20 # like a Tag takes IDs from the backend and renders an ncurses pad. No class
21 # other than Tag actually touches Story objects directly.
22
23 class Tag(list):
24     def __init__(self, tag, callbacks):
25         list.__init__(self)
26         self.tag = tag
27         self.pad = None
28
29         # Note that Tag() is only given the top-level CantoCursesGui
30         # callbacks as it shouldn't be doing input / refreshing
31         # itself.
32
33         self.callbacks = callbacks.copy()
34
35         # Modify our own callbacks so that *_tag_opt assumes
36         # the current tag.
37
38         self.callbacks["get_tag_opt"] =\
39                 lambda x : callbacks["get_tag_opt"](self, x)
40         self.callbacks["set_tag_opt"] =\
41                 lambda x, y : callbacks["set_tag_opt"](self, x, y)
42
43         # Are there changes pending?
44         self.changed = True
45
46         # Information from last refresh
47         self.lines = 0
48         self.width = 0
49
50         # Global indices (for enumeration)
51         self.item_offset = 0
52         self.visible_tag_offset = 0
53         self.tag_offset = 0
54
55         on_hook("opt_change", self.on_opt_change)
56
57         # Upon creation, this Tag adds itself to the
58         # list of all tags.
59
60         callbacks["get_var"]("alltags").append(self)
61
62     def die(self):
63         remove_hook("opt_change", self.on_opt_change)
64
65     def on_opt_change(self, opts):
66         if "taglist.tags_enumerated" in opts or \
67                 "taglist.tags_enumerated_absolute" in opts:
68             self.need_redraw()
69
70     # We override eq so that empty tags don't evaluate
71     # as equal and screw up things like enumeration.
72
73     def __eq__(self, other):
74         if self.tag != other.tag:
75             return False
76         return list.__eq__(self, other)
77
78     # Create Story from ID before appending to list.
79
80     def add_items(self, ids):
81         added = []
82         for id in ids:
83             s = Story(id, self.callbacks)
84             self.append(s)
85             added.append(s)
86
87             rel = len(self) - 1
88             s.set_rel_offset(rel)
89             s.set_offset(self.item_offset + rel)
90
91         call_hook("items_added", [ self, added ] )
92
93     # Remove Story based on ID
94
95     def remove_items(self, ids):
96         removed = []
97
98         # Copy self so we can remove from self
99         # without screwing up iteration.
100
101         for idx, item in enumerate(self[:]):
102             if item.id in ids:
103                 log.debug("removing: %s" % (item.id,))
104
105                 list.remove(self, item)
106                 item.die()
107                 removed.append(item)
108
109         # Update indices of items.
110         for i, story in enumerate(self):
111             story.set_rel_offset(i)
112             story.set_offset(self.item_offset + i)
113
114         call_hook("items_removed", [ self, removed ] )
115
116     # Remove all stories from this tag.
117
118     def reset(self):
119         for item in self:
120             item.die()
121
122         call_hook("items_removed", [ self, self[:] ])
123         del self[:]
124
125     def get_id(self, id):
126         for item in self:
127             if item.id == id:
128                 return item
129         return None
130
131     def get_ids(self):
132         return [ s.id for s in self ]
133
134     # Inform the tag of global index of it's first item.
135     def set_item_offset(self, offset):
136         if self.item_offset != offset:
137             self.item_offset = offset
138             for i, item in enumerate(self):
139                 item.set_offset(offset + i)
140
141     def set_visible_tag_offset(self, offset):
142         if self.visible_tag_offset != offset:
143             self.visible_tag_offset = offset
144             self.need_redraw()
145
146     def set_tag_offset(self, offset):
147         if self.tag_offset != offset:
148             self.tag_offset = offset
149             self.need_redraw()
150
151     def need_redraw(self):
152         self.changed = True
153         self.callbacks["set_var"]("needs_redraw", True)
154
155     def do_changes(self, width):
156         if width != self.width or self.changed:
157             self.refresh(width)
158
159     def refresh(self, width):
160         self.width = width
161
162         lines = self.render_header(width, FakePad(width))
163
164         self.pad = curses.newpad(lines, width)
165         self.render_header(width, WrapPad(self.pad))
166
167         self.lines = lines
168         self.changed = False
169
170     def render_header(self, width, pad):
171         enumerated = self.callbacks["get_opt"]("taglist.tags_enumerated")
172         enumerated_absolute =\
173             self.callbacks["get_opt"]("taglist.tags_enumerated_absolute")
174
175         # Make sure to strip out the category from category:name
176         header = self.tag.split(':', 1)[1] + u"\n"
177
178         # Tags can be both absolute and relatively enumerated at once,
179         # in this case the absolute enumeration is the first listed and thus
180         # it's added to the front of the string last.
181
182         if enumerated:
183             header = ("[%d] " % self.visible_tag_offset) + header
184
185         if enumerated_absolute:
186             header = ("[%d] " % self.tag_offset) + header
187
188         lines = 0
189
190         while header:
191             header = theme_print(pad, header, width, u"", u"")
192             lines += 1
193
194         return lines