""" ItemList.py

Handles listing of feed items in a view (i.e. GTK+ TreeView)
"""
__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
__license__ = """GNU General Public License

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 2 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, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
"""

from xml.sax import saxutils
import pygtk
pygtk.require('2.0')
import gobject
import gtk
import pango
import Event
import MVP
import error
import utils

class Column:
    """
    Constants for the item list treeview columns
    """
    TITLE = 0
    STICKY = 1
    ITEM = 2
    WEIGHT = 3
    STICKY_FLAG = 4
    FOREGROUND = 5

class ItemsView(MVP.WidgetView):
    ui = """
    <ui>
        <popup name=\"itemlist_popup\">
            <menuitem action=\"mark_as_unread\"/>
        </popup>
    </ui>
    """

    def _initialize(self):
        self._widget.connect("button_press_event",self._on_button_press_event)
        self._widget.connect("popup-menu", self._on_popup_menu)
        self._widget.connect("row-activated", self._on_row_activated)
        self._popup = None

    def scroll_to_cell(self, path):
        selection = self._widget.get_selection()
        selection.select_path(path)
        self._widget.scroll_to_cell(path)
        self._widget.set_cursor(path)
        return

    def get_widget_selection(self):
        return self._widget.get_selection()

    def _create_popup(self):
        actions = [
            ("mark_as_unread", gtk.STOCK_CONVERT, "Mark as _Unread", None,
             "Mark this item as unread", self._on_menu_mark_as_unread_activate)
            ]
        self._uimanager = gtk.UIManager()
        actiongroup = gtk.ActionGroup("ItemListActions")
        actiongroup.add_actions(actions)
        self._uimanager.insert_action_group(actiongroup, 0)
        self._uimanager.add_ui_from_string(self.ui)
        self._popup = self._uimanager.get_widget('/itemlist_popup')

    def _on_popup_menu(self, treeview, *args):
        self._popup.popup(None,None,None,0,0)

    def _on_button_press_event(self, treeview, event):
        val = 0
        if event.button == 3:
            x = int(event.x)
            y = int(event.y)
            time = gtk.get_current_event_time()
            path = treeview.get_path_at_pos(x, y)
            if path is None:
                return 1
            path, col, cellx, celly = path
            treeview.grab_focus()
            treeview.set_cursor( path, col, 0)
            self._popup.popup(None, None, None, event.button, time)
            val = 1
        return val

    def _on_menu_mark_as_unread_activate(self, *args):
        self._presenter.mark_selected_item_as_unread()

    def _item_selection_changed(self, selection):
        selected = selection.get_selected()
        if selected:
            self._presenter.selection_changed(selected)
        return

    def _on_row_activated(self, treeview, path, column):
        model = self._widget.get_model()
        try: treeiter = model.get_iter(path)
        except ValueError:
            return
        link = model.get_value(treeiter, Column.ITEM).link
        if link:
            utils.url_show(link)
        return

class ItemListView(ItemsView):
    """
    Widget: gtk.Treeview
    Model: ListStore
    Presenter: ItemListPresenter
    """
    def _initialize(self):
        ItemsView._initialize(self)
        self._widget.set_rules_hint(False)
        self._create_popup()
        selection = self._widget.get_selection()
        selection.connect("changed", self._item_selection_changed)

    def _model_set(self):
        self._widget.set_model(self._model)
        self._create_columns()

    def _sticky_toggled(self, cell, path):
        self._presenter.sticky_toggled(cell, path,
                                       Column.ITEM, Column.STICKY_FLAG)
        return

    def _create_columns(self):
        renderer = gtk.CellRendererToggle()
        column = gtk.TreeViewColumn(_('Keep'), renderer,
                                    active=Column.STICKY_FLAG)
        column.set_resizable(True)
        column.set_reorderable(True)
        self._widget.append_column(column)
        renderer.connect('toggled', self._sticky_toggled)

        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(_('_Title'), renderer,
                                    text=Column.TITLE,
                                    foreground=Column.FOREGROUND,
                                    weight=Column.WEIGHT)
        column.set_resizable(True)
        column.set_reorderable(True)
        self._widget.append_column(column)
        return

class ItemsPresenter(MVP.BasicPresenter):
    def _connect(self):
        self.initialize_slots(Event.ItemSelectionChangedSignal)
        self._selected_item = None
        return

    def selection_changed(self, selected):
        model, treeiter = selected
        if not treeiter: return
        assert model is self._model
        path = model.get_path(treeiter)
        self._item_selected(path)
        self._view.scroll_to_cell(path)

    def _item_selected(self, path):
        item = self._model[path][Column.ITEM]
        if self._selected_item is not item:
            self._selected_item = item
            if not self._selected_item.seen:
                self._selected_item.seen = 1
            self.emit_signal(Event.ItemSelectionChangedSignal(self,self._selected_item))
        return

    def mark_selected_item_as_unread(self):
        self._selected_item.seen = 0
        self._mark_item(self._selected_item)

    def display_empty_feed(self):
        self._model.clear()
        return

    def _set_column_weight(self, item, path):
        tree_iter = self._model.get_iter(path)
        weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.seen]
        colour = ("#0000FF", "#000000")[item.seen]
        self._model.set_value(tree_iter, Column.FOREGROUND, colour)
        self._model.set_value(tree_iter, Column.WEIGHT, weight)
        return

    def _mark_item(self, item, index=None):
        """
        Marks an item as 'READ'

        Subclass SHOULD implement this
        """
        raise NotImplemented

class ItemListPresenter(ItemsPresenter):
    def _initialize(self):
        model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_OBJECT,
                              gobject.TYPE_PYOBJECT, gobject.TYPE_INT,
                              gobject.TYPE_BOOLEAN, gobject.TYPE_STRING)
        self.model = model
        self._connect()
        self._selected_feed = None
        selection = self._view.get_widget_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)

    def select_previous_item(self):
        """
        Selects the item before the current selection. If there
        is no current selection, selects the last item. If there is no
        previous row, returns False.
        """
        selection = self._view.get_widget_selection()
        model, treeiter = selection.get_selected()
        # select the first item if there is no selection
        if not treeiter:
            treeiter = model.get_iter_first()
        path = self._model.get_path(treeiter)
        # check if there's an item
        if not path:
            return False
        prev_path = path[-1]-1
        # we don't cycle through the items so return False if there are no
        # more items to go to
        if prev_path < 0:
            return False
        selection.select_path(path[-1]-1,)
        return True

    def select_next_item(self):
        """
        Selects the item after the current selection. If there is no current
        selection, selects the first item. If there is no next row, returns False.
        """
        selection = self._view.get_widget_selection()
        model, treeiter = selection.get_selected()
        if not treeiter:
            treeiter = model.get_iter_first()
        next_iter = model.iter_next(treeiter)
        if not next_iter or not self._model.iter_is_valid(treeiter):
            return False
        next_path = model.get_path(next_iter)
        selection.select_path(next_path)
        return True

    def select_last_item(self):
        """
        Selects the last item in this list.
        """
        selection = self._view.get_widget_selection()
        selection.select_path(len(self._model) - 1)
        return True

    def select_next_unread_item(self):
        """
        Selects the first unread item after the current selection. If there is
        no current selection, or if there are no unread items, returns False..

        By returning False, the caller can either go to the next feed, or go
        to the next feed with an unread item.
        """
        has_unread = False
        selection = self._view.get_widget_selection()
        selected_row = selection.get_selected()
        # check if we have a selection. if none,
        # start searching from the first item
        if not selected_row[1]:
            treerow = self._model[0]
        else:
            model, treeiter = selected_row
            if not treeiter:
                return has_unread
            nextiter = model.iter_next(treeiter)
            if not nextiter: # no more rows to iterate
                return has_unread
            treerow = self._model[model.get_path(nextiter)]
        while(treerow):
            item = treerow[Column.ITEM]
            if not item.seen:
                has_unread = True
                selection.select_path(treerow.path)
                break
            treerow = treerow.next
        return has_unread


    def sticky_toggled(self, cell, path, item_column, stickyflag_column):
        treeiter = self._model.get_iter((int(path),))
        item = self._model.get_value(treeiter, item_column)
        item.sticky = not item.sticky
        self._model.set(treeiter, stickyflag_column, item.sticky)

    def display_feed_items(self, feed, select_first=1):
        redisplay = self._selected_feed is feed
        if self._selected_feed and not redisplay:
            self._selected_feed.signal_disconnect(Event.RefreshFeedDisplaySignal,
                                         self._feed_order_changed)
            self._selected_feed.signal_disconnect(Event.AllItemsReadSignal,
                                         self._all_items_read)
            self._selected_feed.signal_disconnect(Event.ItemReadSignal,
                                         self._item_read)
        self._selected_feed = feed
        if not redisplay:
            self._selected_feed.signal_connect(Event.RefreshFeedDisplaySignal,
                                self._feed_order_changed)
            self._selected_feed.signal_connect(Event.AllItemsReadSignal,
                                self._all_items_read)
            self._selected_feed.signal_connect(Event.ItemReadSignal,
                                self._item_read)
        count = self._render_feed(self._selected_feed)
        if not redisplay and count:
            if not self.select_next_unread_item():
                selection = self._view.get_widget_selection()
                selection.select_path((0,))
        return

    def _feed_order_changed(self, event):
        if event.sender is self._selected_feed:
            item = None
            selection = self._view.get_widget_selection()
            model, treeiter = selection.get_selected()
            if treeiter is not None:
                path = model.get_path(treeiter)
                item = model[path[0]][Column.ITEM]
            self._render_feed(event.sender)
            if item:
                path = (item.feed.get_item_index(item),)
            else:
                path = (0,) # first item
            selection.select_path(path)

    def _all_items_read(self, signal):
        for index, item in signal.changed:
            self._mark_item(item, index)

    def _item_read(self, signal):
        self._mark_item(signal.item)

    def _render_feed(self, feed):
        self._model.clear()
        curr_iter = None
        for item in feed.items:
            weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.seen]
            colour = ("#0000FF", "#000000")[item.seen]
            treeiter = self._model.append()
            self._model.set(treeiter,
                            Column.TITLE, item.title,
                            Column.ITEM, item,
                            Column.WEIGHT, weight,
                            Column.STICKY_FLAG, item.sticky,
                            Column.FOREGROUND, colour)
            if item is self._selected_item:
                curr_iter = treeiter

        if curr_iter:
            path = self._model.get_path(curr_iter)
            self._view.scroll_to_cell(path)
        return len(feed.items)

    def _mark_item(self, item, path=None):
        if path is None:
            path = (item.feed.get_item_index(item),)
        self._set_column_weight(item, path)
        return


syntax highlighted by Code2HTML, v. 0.9.1