""" ItemView.py

Module for displaying an item to the user
"""
__copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
__license__ = """
Straw 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.

Straw 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. """


import os
import re
import urlparse
from xml.sax import saxutils
import codecs
import pygtk
pygtk.require('2.0')
import gtk
import gtk.glade
import gtkhtml2
import utils
import dialogs
import MessageManager
import FeedCategoryList
import ImageCache
import MVP
import Config
import error
import dialogs

class HTMLView(MVP.View):
    """
    Widget: gtkhtml2.View
    Model: gtkhtml.Document
    Presenter: HTMLPresenter
    """
    ui = """
    <ui>
        <popup name=\"htmlview_popup\">
            <menuitem action=\"open_link_location\"/>
            <menuitem action=\"copy_link_location\"/>
            <menuitem action=\"copy_text\"/>
            <menuitem action=\"subscribe\"/>
            <menuitem action=\"zoom_in\"/>
            <menuitem action=\"zoom_out\"/>
            <menuitem action=\"zoom_100\"/>
        </popup>
    </ui>
    """

    def _initialize(self):
        self._widget.set_document(self._model)
        # Make the article view focusable. gtkhtml2.View scrolls to the first
        # link of the document which sometimes makes the first part of the
        # document unviewable if the first link is found at the bottom part of
        # the document. This is good for now since the template includes a
        # link at the topmost page of the view.
        self._widget.set_property('can-focus', True)
        self._widget.get_vadjustment().set_value(0)
        self._widget.connect('button_press_event', self._button_press_event)
        self._css = None
        self._url = None
        self._text_selection = None
        self._popup = None
        self._init_css()
        self._create_popup()


    def _connect_signals(self, widget):
        " We don't 'auto-connect' since self._widget is not a glade file"
        pass

    @property
    def widget(self):
        return self._widget

    def _button_press_event(self, widget, event):
        if event.button == 1:
            if self._url:
                self._presenter.display_url(self._url)
            self._url = self._text_selection = None
        elif event.button == 3:
            self._text_selection = self._presenter.get_html_text_selection()
            action_group = self._uimanager.get_action_groups()[-1]
            link_action = action_group.get_action('copy_link_location')
            open_link_action = action_group.get_action('open_link_location')
            text_action = action_group.get_action('copy_text')
            subs_action = action_group.get_action('subscribe')
            if self._url and self._text_selection:
                link_action.set_visible(True)
                open_link_action.set_visible(True)
                subs_action.set_visible(True)
                text_action.set_visible(True)
            elif self._url:
                text_action.set_visible(False)
                link_action.set_visible(True)
                open_link_action.set_visible(True)
                subs_action.set_visible(True)
            elif self._text_selection:
                link_action.set_visible(False)
                open_link_action.set_visible(False)
                subs_action.set_visible(False)
                text_action.set_visible(True)
            else:
                link_action.set_visible(False)
                open_link_action.set_visible(False)
                subs_action.set_visible(False)
                text_action.set_visible(False)
            self._uimanager.ensure_update()
            self._popup.popup(None, None, None, event.button,
                              gtk.get_current_event_time())
            return True

    def _create_popup(self):
        actions = [
            ("copy_text", gtk.STOCK_COPY, "_Copy", None, None,
             self._on_copy_text),
            ("copy_link_location", None, "_Copy Link Location",
             None, None, self._on_copy_url),
            ("open_link_location", None, "_Open Link",
             None, None, self._open_link_location),
            ("subscribe", None, "_Subscribe", None, None,
             self._subscribe),
            ("zoom_in", gtk.STOCK_ZOOM_IN, "Zoom _In", None, None,
             lambda *args: self._on_magnify("in")),
            ("zoom_out", gtk.STOCK_ZOOM_OUT, "Zoom _Out", None, None,
             lambda *args: self._on_magnify("out")),
            ("zoom_100", gtk.STOCK_ZOOM_100, "_Normal Size", None, None,
             lambda *args: self._on_magnify("reset"))
            ]
        self._uimanager = gtk.UIManager()
        actiongroup = gtk.ActionGroup("HtmlViewActions")
        actiongroup.add_actions(actions)
        self._uimanager.insert_action_group(actiongroup, -1)
        self._uimanager.add_ui_from_string(self.ui)
        self._popup = self._uimanager.get_widget("/htmlview_popup")


    def _on_magnify(self, action):
        if action == "in":
            self._widget.zoom_in()
        elif action == "out":
            self._widget.zoom_out()
        else:
            self._widget.zoom_reset()
        config = Config.get_instance()
        config.text_magnification = self._widget.get_magnification()

    def _on_copy_text(self, *args):
        self._presenter.set_clipboard_text(self._text_selection)
        gtkhtml2.html_selection_clear(self._widget)
        return

    def _on_copy_url(self, *args):
        self._presenter.set_clipboard_text(self._url)
        return

    def _open_link_location(self, *args):
        utils.url_show(self._url)

    def _subscribe(self, *args):
        import subscribe
        subscribe.show(url=self._url)
        return


    def _init_css(self):
        if self._css is None:
            css = file(os.path.join(
                utils.find_data_dir(), "straw.css")).read()
            # derive colors for blockquotes and header boxes from
            # the GTK+ theme
            # the GTK+ colors are in the range 0-65535
            bgdivisor = int(65535/(9.0/10))
            fgdivisor = 65535
            borderdivisor = int(65535/(6.0/10))
            gtkstyle = self._widget.get_style()

            headerbg = "background-color: #%.2x%.2x%.2x;" % (
            	(gtkstyle.bg[gtk.STATE_NORMAL].red * 255) / bgdivisor,
              	(gtkstyle.bg[gtk.STATE_NORMAL].blue * 255) / bgdivisor,
              	(gtkstyle.bg[gtk.STATE_NORMAL].green * 255) / bgdivisor)

            headerfg = "color: #%.2x%.2x%.2x;" % (
            	(gtkstyle.fg[gtk.STATE_NORMAL].red * 255) / fgdivisor,
              	(gtkstyle.fg[gtk.STATE_NORMAL].blue * 255) / fgdivisor,
              	(gtkstyle.fg[gtk.STATE_NORMAL].green * 255) / fgdivisor)

            headerborder = "border-color: #%.2x%.2x%.2x;" % (
            	(gtkstyle.bg[gtk.STATE_NORMAL].red * 255) / borderdivisor,
              	(gtkstyle.bg[gtk.STATE_NORMAL].blue * 255) / borderdivisor,
              	(gtkstyle.bg[gtk.STATE_NORMAL].green * 255) / borderdivisor)


            css = re.sub(r"/\*\*\*HEADERBG\*/", headerbg, css)
            css = re.sub(r"/\*\*\*HEADERFG\*/", headerfg, css)
            css = re.sub(r"/\*\*\*HEADERBORDERCOLOR\*/",
                               headerborder, css)

            css = re.sub(r"/\*\*\*BQUOTEBG\*/", headerbg, css)
            css = re.sub(r"/\*\*\*BQUOTEFG\*/", headerfg, css)
            css = re.sub(r"/\*\*\*BQUOTEBORDERCOLOR\*/",
                               headerborder, css)
            self._css = css
        return

    def report_error(self, title, description):
        dialogs.report_error(title, description)

    def get_css(self):
        return self._css

    def get_adjustments(self):
        return (self._widget.get_vadjustment(), self._widget.get_hadjustment())

    def get_widget(self):
        return self._widget

    def connect_widget_signal(self, signal, callback):
        self._widget.connect(signal, callback)

    def set_on_url(self, url):
        self._url = url

    def set_magnification(self, size):
        self._widget.set_magnification(size)

class HTMLPresenter(MVP.BasicPresenter):
    """
    Model: gtkhtml2.Document
    View: HTMLView
    """
    def _initialize(self):
        self._model.connect('request-url', self._request_url)
        self._view.connect_widget_signal('on_url', self._on_url)
        self._item = None

    def _on_url(self, view, url):
        self._view.set_on_url(url)
        if url:
            url = utils.complete_url(url, self._item.feed.location)
        else:
            url = ""
        mmgr = MessageManager.get_instance()
        mmgr.post_message(url)
        return

    def _request_url(self, document, url, stream):
        feed = self._item.feed
        try:
            try:
                url = utils.complete_url(url, self._item.feed.location)
                if urlparse.urlparse(url)[0] == 'file':
                    # local URL, don't use the cache.
                    f = file(urlparse.urlparse(url)[2])
                    stream.write(f.read())
                    f.close()
                else:
                    image = ImageCache.cache[url]
                    stream.write(image.get_data())
            except Exception, ex:
                error.log("Error reading image in %s: %s" % (url, ex))
        finally:
            stream.close()
            stream = None
        return

    def set_clipboard_text(self, text):
        utils.set_clipboard_text(text)

    def get_html_text_selection(self):
        return gtkhtml2.html_selection_get_text(self.view.widget)

    def display_url(self, link):
        link = link.strip()
        link = utils.complete_url(link, self._item.feed.location)
        try:
            utils.url_show(link)
        except Exception, ex:
            self._view.report_error(_("Error Loading Browser"),
                                    _("Please check your browser settings and try again."))
        return

    def get_view_adjustments(self):
        return self._view.get_adjustments()

    def get_view_widget(self):
        return self._view.get_widget()

    def display_item(self, item, encoding):
        self._item = item
        content = self._htmlify_item(item, encoding)
        self._prepare_stream(content)
        return

    def display_empty_feed(self):
        content = """<p class=\"emptyfeed\"/>"""# _("No data yet, need to poll first.") </p>"""
        self._prepare_stream(content)

    def display_empty_search(self):
        content = """
        <h2>Search Subscriptions</h2>
        <p>
        Begin searching by typing your text on the text box on the left side.
        </p>
        """
        self._prepare_stream(content)
        return

    def set_view_magnification(self, size):
        self.view.set_magnification(size)

    def _encode_for_html(self, unicode_data, encoding='utf-8'):
        """ From Python Cookbook, 2/ed, section 1.23
        'html_replace' is in the utils module
        """
        return unicode_data.encode(encoding, 'html_replace')

    def _prepare_stream(self, content):
        html = self._generate_html(content)
        html = self._encode_for_html(html)
        self._model.clear()
        self._model.open_stream("text/html")
        self._model.write_stream(html)
        self._model.close_stream()
        return

    def _generate_html(self, body):
        # heading
        html = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
        <html>
        <head><title>title</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />"""

        # stylesheet
        if Config.get_instance().reload_css:
            html += """<link rel="stylesheet" type="text/css" href="file://"
            """ + os.path.join(utils.find_data_dir(), "straw.css") + """/>"""
        else:
            html += """<style type="text/css">""" + self._view.get_css() + """</style>"""

        # body
        html += "</head><body>%s</body></html>" % body
        return html

    def _htmlify_item(self, item, encoding):
        feed = item.feed
        ret = []

        # item header
        ret.append('<div id="itemheader">')
        if item.title is not None:
            if item.link is not None:
                ret.append('<div class="title"><a href="%s">%s</a></div>' % (item.link,item.title))
            else:
                ret.append(item.title)
        ret.append('<table id="itemhead" cellspacing="0" cellpadding="0">')
        if item.pub_date is not None:
            timestr = utils.format_date(
                item.pub_date, utils.get_date_format(), encoding)
            ret.append(''.join(('<tr><td class="headleft" id="date">%s</td><td class="headright"></td></tr>' % str(timestr))))

        ret.append('</table>')
        ret.append('</div>')

        # item body
        if item.description is not None:
            item.description.replace('\n', '<br/>')
            ret.append('<div class="description">%s</div>' % item.description)

        if item.publication_name is not None:
            ret.append('<div class="description">')
            ret.append('<b>%s:</b> %s<br/>' % (_("Publication"),
                                               item.publication_name))
            if item.publication_volume is not None:
                ret.append('<b>%s:</b> %s ' % (_("Volume"),
                                                    item.publication_volume))
                if item.publication_number is not None:
                    ret.append('( %s )<br />' % item.publication_number)
            if item.publication_section is not None:
                ret.append('<b>%s:</b> %s<br />' % (_("Section"),
                                                    item.publication_section))
            if item.publication_starting_page is not None:
                ret.append('<b>%s:</b> %s' % (_("Starting Page"),
                                              item.publication_starting_page))
            ret.append('</div>')

        # freshmeat fields
        freshmeat_data = []
        if item.fm_license != '' and item.fm_license is not None:
            freshmeat_data.append('<p><b>%s:</b> %s</p>' %
                                  (_("Software license"), item.fm_license))
        if item.fm_changes != '' and item.fm_changes is not None:
            freshmeat_data.append('<p><b>%s:</b> %s</p>' %
                                  (_("Changes"), item.fm_changes))
        if len(freshmeat_data) > 0:
            ret.append('<div class="description">')
            ret.extend(freshmeat_data)
            ret.append('</div>')
        # empty paragraph to make sure that we get space here
        ret.append('<p></p>')
        # Additional information
        dcret = []
        
        # RSS Enclosures
        
        if item.enclosures:
            dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><table>' % _("Enclosed Media"))
            for enc in item.enclosures: # rss 2.0 defines only one enclosure per item
                size = int(enc.length)
                unit = _('bytes')
                if size > 1024:
                    unit = _('KB')
                    size /= 1024.0
                if size > 1024:
                    unit = _('MB')
                    size /= 1024.0
                link_text = enc['href'].split('/')[-1]
                
                # find what kind of media is that. enc[type] will have something like audio/mp3 or video/mpeg (mimetypes)
                # some symlinks are not present on the tango icon theme mimetype dir. audio and application are 2 good examples. So I am not relying on the symlinks now...
                kind = enc['type'].split('/')[0]
                if kind == 'audio':
                    icon_name = 'audio-x-generic'
                elif kind == 'video':
                    icon_name = 'video-x-generic'
                elif kind == 'image':
                    icon_name = 'image-x-generic'
                elif kind == 'application':
                    icon_name = 'binary'
                elif kind == 'text':
                    icon_name = 'text-x-generic'
                else:
                    icon_name = "unknown"
                
                it = gtk.icon_theme_get_default()
                ii = it.lookup_icon(icon_name, 32, gtk.ICON_LOOKUP_NO_SVG)
                imgsrc = 'file://' + ii.get_filename()
                dcret.append('<tr><td><div style="vertical-align: middle"><a class="dclink" href="%s" style="vertical-align: middle"><img style="padding: 0px 15px 5px 0px" src=%s /> %s</a> (%.2f %s - %s)</div></td></tr>' % (enc['href'], imgsrc, link_text, size, unit, enc['type']))
            dcret.append('</table></td></tr>')
        
        if item.creator is not None:
            dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><span>%s</span></td></tr>' % (_("Posted by"), item.creator))
        if item.contributors is not None and len(item.contributors):
            for c in item.contributors:
                dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><span>%s</span></td></tr>' \
                             % (_("Contributor:"), c.name))
        if item.source:
            url = utils.get_url_location(item.source['url'])
            text = saxutils.escape(url)
            dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><a class="dclink" href="%s"><span>%s</span></a></td></tr>' %
                         (_("Item Source"), url, text))

        if item.guid is not None and item.guid != "" and item.guidislink:
            dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><a class="dclink" href="%s"><span>%s</span></a></td></tr>' % (_("Permalink"), item.guid, item.guid))
        # check for not guidislink for the case where there is guid but
        # isPermalink="false" and yet link is the same as guid (link is
        # always assumed to be a valid link)
        if item.link != "" and item.link is not None and (item.link != item.guid or not item.guidislink):
             dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><a class="dclink" href="%s"><span>%s</span></a></td></tr>' %
                        (_("Complete story"), item.link, item.link))

        if item.license_urls:
            for l in item.license_urls:
                if l:
                    dcret.append('<tr class="tr.dc"><td class="dcname"><span>%s</span></td><td class="dcvalue"><a class="dclink" href="%s"><span>%s</span></a></td></tr>' % (_("License"), l, l))

        if len(dcret):
            ret.append('<div class="dcinfo">%s<table class="dc" id="footer">' % _("Additional information"))
            ret.append("".join(dcret))
            ret.append('</table>')
        ret.append('</div>')
        return "".join(ret)

class ScrollView(MVP.WidgetView):
    """
    Widget: html_scrolled_window
    """
    def set_adjustments(self, vadjustment, hadjustment):
        self._widget.set_hadjustment(hadjustment)
        self._widget.set_vadjustment(vadjustment)
        return

    def add_child(self, widget):
        self._widget.add(widget)
        return

    def show(self):
        self._widget.show_all()
        return

    def adjust_vertical_adjustment(self):
        va = self._widget.get_vadjustment()
        va.set_value(va.lower)
        return

    def get_vadjustment(self):
        return self._widget.get_vadjustment()

class ScrollPresenter(MVP.BasicPresenter):
    """
    View: ScrollView
    """
    def set_view_adjustments(self, vadjustment, hadjustment):
        self._view.set_adjustments(vadjustment, hadjustment)
        return

    def update_view(self):
        self._view.adjust_vertical_adjustment()
        return

    def scroll_down(self):
        va = self._view.get_vadjustment()
        old_value = va.get_value()
        new_value = old_value + va.page_increment
        limit = va.upper - va.page_size
        if new_value > limit:
            new_value = limit
        va.set_value(new_value)
        return new_value > old_value

    def show_view(self):
        self._view.show()
        return

class ItemView:
    def __init__(self, item_view_container):
        self._encoding = utils.get_locale_encoding()
        widget_tree = gtk.glade.get_widget_tree(item_view_container)
        document = gtkhtml2.Document()
        widget = gtkhtml2.View()
        html_view = HTMLView(widget, document)
        self._html_presenter = HTMLPresenter(document, html_view)

        widget = widget_tree.get_widget('html_scrolled_window')
        scroll_view = ScrollView(widget)
        self._scroll_presenter = ScrollPresenter(view=scroll_view)

        vadj, hadj = self._html_presenter.get_view_adjustments()
        child = self._html_presenter.get_view_widget()
        self._scroll_presenter.set_view_adjustments(vadj, hadj)
        self._scroll_presenter.view.add_child(child)
        self._scroll_presenter.show_view()

        config = Config.get_instance()
        self._html_presenter.set_view_magnification(config.text_magnification)

    def item_selection_changed(self, signal):
        if signal.item:
            self._display_item(signal.item)

    def _display_item(self, item):
        self._html_presenter.display_item(item, self._encoding)
        self._scroll_presenter.update_view()

    def display_empty_feed(self):
        self._html_presenter.display_empty_feed()

    def display_empty_search(self):
        self._html_presenter.display_empty_search()

    def scroll_down(self):
        return self._scroll_presenter.scroll_down()

    def get_selected_text(self):
        return self._html_presenter.get_html_text_selection()

