%C escape suspends colors as well as attributes
[canto-curses.git] / canto_curses / tag.py
index 2732e0d..a636d08 100644 (file)
@@ -6,7 +6,7 @@
 #   it under the terms of the GNU General Public License version 2 as 
 #   published by the Free Software Foundation.
 
-from canto_next.hooks import call_hook, on_hook, remove_hook
+from canto_next.hooks import call_hook, on_hook, unhook_all
 from canto_next.plugins import Plugin, PluginHandler
 from canto_next.rwlock import read_lock
 
@@ -31,6 +31,8 @@ log = logging.getLogger("TAG")
 class TagPlugin(Plugin):
     pass
 
+alltags = []
+
 class Tag(PluginHandler, list):
     def __init__(self, tagcore, callbacks):
         list.__init__(self)
@@ -39,6 +41,7 @@ class Tag(PluginHandler, list):
         self.tagcore = tagcore
         self.tag = tagcore.tag
         self.is_tag = True
+        self.updates_pending = 0
 
         self.pad = None
         self.footpad = None
@@ -87,23 +90,20 @@ class Tag(PluginHandler, list):
         self.post_format = ""
 
         # Global indices (for enumeration)
-        self.item_offset = None
-        self.visible_tag_offset = None
-        self.tag_offset = None
-        self.sel_offset = None
+        self.item_offset = 0
+        self.visible_tag_offset = 0
+        self.tag_offset = 0
+        self.sel_offset = 0
 
-        on_hook("curses_opt_change", self.on_opt_change)
-        on_hook("curses_tag_opt_change", self.on_tag_opt_change)
-        on_hook("curses_attributes", self.on_attributes)
+        on_hook("curses_opt_change", self.on_opt_change, self)
+        on_hook("curses_tag_opt_change", self.on_tag_opt_change, self)
+        on_hook("curses_attributes", self.on_attributes, self)
+        on_hook("curses_items_added", self.on_items_added, self)
 
         # Upon creation, this Tag adds itself to the
         # list of all tags.
 
-        config_lock.acquire_write()
-        callbacks["get_var"]("alltags").append(self)
-
-        config.eval_tags()
-        config_lock.release_write()
+        alltags.append(self)
 
         self.sync(True)
 
@@ -111,6 +111,7 @@ class Tag(PluginHandler, list):
         self.update_plugin_lookups()
 
     def die(self):
+        log.debug("tag %s die()" % self.tag)
         # Reset so items get die() called and everything
         # else is notified about items disappearing.
 
@@ -118,9 +119,9 @@ class Tag(PluginHandler, list):
             s.die()
         del self[:]
 
-        remove_hook("curses_opt_change", self.on_opt_change)
-        remove_hook("curses_tag_opt_change", self.on_tag_opt_change)
-        remove_hook("curses_attributes", self.on_attributes)
+        alltags.remove(self)
+
+        unhook_all(self)
 
     def on_item_state_change(self, item):
         self.need_redraw()
@@ -153,6 +154,14 @@ class Tag(PluginHandler, list):
                 self.need_redraw()
                 break
 
+    def on_items_added(self, tagcore, added):
+        cur_ids = self.get_ids()
+        if tagcore == self.tagcore:
+            for story_id in added:
+                if story_id not in cur_ids:
+                    self.updates_pending += 1
+            self.need_redraw()
+
     # We override eq so that empty tags don't evaluate
     # as equal and screw up things like enumeration.
 
@@ -168,48 +177,10 @@ class Tag(PluginHandler, list):
         for item in self:
             if item.id == id:
                 return item
-        return None
 
     def get_ids(self):
         return [ s.id for s in self ]
 
-    # Take a list of ordered ids and reorder ourselves, without generating any
-    # unnecessary add/remove hooks.
-
-    def reorder(self, ids):
-        cur_stories = [ s for s in self ]
-
-        # Perform the actual reorder.
-        stories = [ self.get_id(id) for id in ids ]
-
-        del self[:]
-        list.extend(self, stories)
-
-        # Deal with items that aren't listed. Usually this happens if the item
-        # would be filtered, but is protected for some reason (like selection)
-
-        # NOTE: This is bad behavior, but if we don't retain these items, other
-        # code will crap-out expecting this item to exist. Built-in transforms
-        # are hardened to never discard items with the filter-immune reason,
-        # like selection, so this is just for bad user transforms.
-
-        for s in cur_stories:
-            if s not in self:
-                log.warn("Warning: A filter is filtering filter-immune items.")
-                log.warn("Compensating. This may cause items to jump unexpectedly.")
-                list.append(self, s)
-
-        log.debug("Self: %s" % [ s for s in self ])
-
-        # Handle updating story information.
-        for i, story in enumerate(self):
-            story.set_rel_offset(i)
-            story.set_offset(self.item_offset + i)
-            story.set_sel_offset(self.sel_offset + i)
-
-        # Request redraw to update item counts.
-        self.need_redraw()
-
     # Inform the tag of global index of it's first item.
     def set_item_offset(self, offset):
         if self.item_offset != offset:
@@ -308,6 +279,7 @@ class Tag(PluginHandler, list):
                     'n' : unread,
                     "extra_tags" : extra_tags,
                     'tag' : self,
+                    'pending' : self.updates_pending,
                     'prep' : prep_for_display}
 
         # Prep all text values for display.
@@ -369,7 +341,7 @@ class Tag(PluginHandler, list):
 
             if not self.collapsed and self.border:
                 theme_print(pad, theme_border("ts") * (width - 2), width,\
-                        "%B%1"+ theme_border("tl"), theme_border("tr") + "%0%b")
+                        "%B"+ theme_border("tl"), theme_border("tr") + "%b")
                 lines += 1
         except Exception as e:
             tb = traceback.format_exc()
@@ -383,7 +355,7 @@ class Tag(PluginHandler, list):
     def render_footer(self, width, pad):
         if not self.collapsed and self.border:
             theme_print(pad, theme_border("bs") * (width - 2), width,\
-                    "%B%1" + theme_border("bl"), theme_border("br") + "%0%b")
+                    "%B" + theme_border("bl"), theme_border("br") + "%b")
             theme_reset()
             return 1
         return 0
@@ -399,10 +371,19 @@ class Tag(PluginHandler, list):
 
             self.tagcore.lock.acquire_read()
 
+            self.tagcore.ack_changes()
+
             for story in self:
                 if story.id in self.tagcore:
                     current_stories.append((self.tagcore.index(story.id), story))
                 elif story == sel:
+
+                    # If we preserve the selection in an "undead" state, then
+                    # we keep set tagcore changed so that the next sync operation
+                    # will re-evaluate it.
+
+                    self.tagcore.changed()
+
                     if current_stories:
                         place = max([ x[0] for x in current_stories ]) + .5
                     else:
@@ -419,7 +400,11 @@ class Tag(PluginHandler, list):
 
             call_hook("curses_stories_added", [ self, added_stories ])
 
-            current_stories.sort()
+            conf = config.get_conf()
+            if conf["update"]["style"] == "maintain" or self.tagcore.was_reset:
+                self.tagcore.was_reset = False
+                current_stories.sort()
+
             current_stories = [ x[1] for x in current_stories ]
 
             deleted = []
@@ -445,4 +430,4 @@ class Tag(PluginHandler, list):
         for s in self:
             s.sync()
 
-        self.tagcore.ack_changes()
+        self.updates_pending = 0