#!/usr/bin/python
# Maemo reader for the Anarchist FAQ
# Copyright (c) 2009 Firinel
# Licensed under the GNU GPL version 2 or above.
import gtk
import hildon
from xml.dom.minidom import parse
import ConfigParser
import gobject
import gtkhtml2
import os
import re

class AnarchismMenu(gobject.GObject):
  """
  This represents one of the menus in the application.
  """

  # Pick up the structure...

  config_path = '/home/user/.anarchism.conf'
  dom1 = parse('/opt/anarchism/structure.xml')

  # And the config, so we can remember whether
  # they've read a particular page.

  config = ConfigParser.ConfigParser()  
  config.read(config_path)

  def __init__(self, path=None):
    """
    Set up the menu.
    """
    # We have to register ourselves with GObject
    # so that we can put ourselves inside the tables
    # that appear on each menu.
    self.__gobject_init__()
    gobject.type_register(AnarchismMenu)

    if not path:

      # They wanted the root page.

      self.m_title = u'An Anarchist FAQ'
      self.element = self.dom1.documentElement
      self._grab_content()

    elif type(path)==type(u''):

      # They gave us the name of a page to look up.

      self.m_title = path
      self.m_contents = []
      self.child_count = 0
      self.unread_child_count = 0
      self.element = None

    elif str(type(path))=="<type 'instance'>":

      # They gave us a place in the structure.

      self.m_title = path.getAttribute('name')
      self.element = path
      self._grab_content()

    else:

      # We don't know what they wanted!

      raise Exception("Unknown type in menu instantiation: "+str(type(path)))

  def _count_children(self, element):
    """
    Counts the children, read and unread,
    from a place in the structure, and sets
    fields in this class accordingly.
    """
    found_children = False
    for child in element.childNodes:
      if child.nodeType == child.TEXT_NODE: continue
      found_children = True
      self.child_count += 1
      if not self.config.has_option('Read', child.getAttribute('name')):
        self.unread_child_count += 1
      self._count_children(child)

    # Don't forget that we ourselves may contain
    # useful content.

    if found_children:
      if os.path.exists(self.filename(element.getAttribute('name'))):
        self.child_count += 1
        if not self.config.has_option('Read', element.getAttribute('name')):
          self.unread_child_count += 1

  def do_child_count(self):
    """
    The friendly front-end to _count_children,
    which resets the fields before it starts.
    """
    self.child_count = 0
    self.unread_child_count = 0
    if self.element:
      self._count_children(self.element)

  def _grab_content(self):
    """
    Given a position in the structure,
    populates our contents list.
    """
    self.m_contents = []
    for c in self.element.childNodes:
      if c.nodeType == c.TEXT_NODE: continue
      self.m_contents.append(AnarchismMenu(c))

    self.do_child_count()

    if os.path.exists(self.filename()):
      self.m_contents.insert(0, AnarchismMenu(self.title()))

  def __del__(self):
    """
    It's all over for us.
    """
    self.dom1.unlink()

  def title(self):
    return self.m_title

  def title_with_markup(self):
    title = self.m_title.replace('&','&amp;').replace('"','&quot;')
    if self.child_count<2:
       return title

    unread = ''
    if self.child_count==self.unread_child_count:
      unread = ', all unread'
    elif self.unread_child_count:
      unread = ', %d unread' % (self.unread_child_count)

    return '%s <i><small>(%d sections%s)</small></i>' % (
        title,
        self.child_count,
        unread)

  def contents(self):
    return self.m_contents

  def is_read(self):
    return self.config.has_option('Read', self.m_title)

  def mark_as_read(self):
    if not self.config.has_section('Read'):
       self.config.add_section('Read')
    self.config.set('Read', self.m_title, 1)
    try:
      self.config.write(file(self.config_path, "w"))
    except:
      pass

  def filename(self, name=None):
    if not name:
      name = self.title()
    src = re.sub('[^a-z-]','',name.lower().replace('&quot;','').replace(' ','-'))
    return '/opt/anarchism/%s.html' % (src,)

###################################################

class AnarchistFAQWindow(hildon.StackableWindow):
  """
  This represents one window in the application.
  """
  def __init__(self, menu=None):
    """
    Sets up the window.
    """
    hildon.Window.__init__(self)

    # This is the list of choices, not the app menu.
    if not menu:
       menu = AnarchismMenu()
    self.menu = menu 
    self.set_title(self.menu.title())

    appmenu = hildon.AppMenu()
    button = hildon.Button(0,
            hildon.BUTTON_ARRANGEMENT_VERTICAL, title="About...")
    button.connect("clicked", self._about)
    appmenu.append(button)
    button = hildon.Button(0,
            hildon.BUTTON_ARRANGEMENT_VERTICAL, title="On the web")
    button.connect("clicked", self._on_the_web)
    appmenu.append(button)
    self.set_app_menu(appmenu)
    appmenu.show_all()

    pan = hildon.PannableArea()

    if self.menu.child_count>1:
      pan.add(self._populate_from_choices())
    else:
      pan.add(self._populate_from_html())

    self.add(pan)

  def _about(self, dummy):
    """
    This is called when the 'about' button is pressed.
    """
    dialog = gtk.Dialog("About")
    icon = gtk.Image()
    icon.set_from_pixbuf(self._readness_icon(False))
    dialog.vbox.add(icon)
    for line in [
         'An Anarchist FAQ',
         '',
         'Copyright '+unichr(0xA9)+' 1995-2008 The Anarchist FAQ Editorial Collective',
         'Iain McKay, Gary Elkin, Dave Neal, Ed Boraas',
         'http://www.infoshop.org/faq/contact.html',
         '',
         'Permission is granted to copy, distribute and/or modify this document',
         'under the terms of the GNU Free Documentation License, Version 1.1 or',
         'any later version published by the Free Software Foundation, and/or the',
         'terms of the GNU General Public License, Version 2.0 or any later version',
         'published by the Free Software Foundation.',
       ]:
      dialog.vbox.add(gtk.Label(line))
    dialog.show_all()

  def _on_the_web(self, dummy):
    """
    This is called when the 'on the web' button is pressed.
    """
    os.system("dbus-send --print-reply --dest=com.nokia.osso_browser "+\
      "/com/nokia/osso_browser/request "+\
      "com.nokia.osso_browser.open_new_window "+\
      "string:http://www.infoshop.org/faq/")

  def _readness_icon(self, state):
    """
    Returns a picture according to whether "state" is
    true or false.
    """
    if state:
       status = 'read'
    else:
       status = 'unread'
    return gtk.gdk.pixbuf_new_from_file('/opt/anarchism/%s.png' % (status,))
     
  def _populate_from_choices(self):
    """
    Fills the window with a menu of choices.
    """
    liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, AnarchismMenu)

    liststore.append([None, '<big><b>%s</b></big>' % (self.menu.title(),), None])
    for option in self.menu.contents():
      icon = self._readness_icon(option.is_read())
      liststore.append([icon, option.title_with_markup(), option])

    treeview = hildon.GtkTreeView(0)
    treeview.set_model(liststore)

    cell = gtk.CellRendererPixbuf()
    col = gtk.TreeViewColumn("Read", cell, pixbuf=0)
    treeview.append_column(col)

    cell = gtk.CellRendererText()
    cell.set_property("wrap-mode", 2)
    cell.set_property("wrap-width", 700)
    col = gtk.TreeViewColumn("Title", cell, markup=1)
    col.set_property("expand", True)
    treeview.append_column(col)

    treeview.get_selection().unselect_all()
    treeview.connect("row_activated", self.row_activated)

    self.liststore = liststore
    return treeview

  def _populate_from_html(self):
    """
    Fills the window with some HTML content
    from a file.
    """
    view = gtkhtml2.View()
    document = gtkhtml2.Document()
    view.set_document(document)
    view.set_property("sensitive", False)

    html = self.menu.filename()

    document.open_stream('text/html')
    document.write_stream('<h1>%s</h1>' % (self.menu.title(), ))
    if os.path.exists(html):
      document.write_stream(file(html).read())
      self.menu.mark_as_read()
    else:
      document.write_stream('Sorry; this page was not installed.')
    document.close_stream()

    return view 

  def row_activated(self, view, row, column):
    """
    Called when they press a menu option.
    """
    model = view.get_model()
    target = model[row][2]
    if target:
      child = AnarchistFAQWindow(target)
      child.connect("delete-event", self.update_readness)
      child.show_all()

  def update_readness(self, oldwindow, event):
    """
    Called when one of our child windows is closed,
    so that we can update the "read" counts.
    """
    i = 1
    for option in self.menu.contents():
       option.do_child_count()
       self.liststore[i][0] = self._readness_icon(option.is_read())
       self.liststore[i][1] = option.title_with_markup()
       i += 1

###################################################

class AnarchistFAQApp(hildon.Program):
  """
  Represents the whole application.
  """
  def __init__(self):
    hildon.Program.__init__(self)

    self.window = AnarchistFAQWindow()
    self.window.connect("delete_event", self.quit)
    self.add_window(self.window)
    self.window.show_all()

  def quit(self, *args):
    gtk.main_quit()

  def run(self):
    self.window.show_all()
    gtk.main()

if __name__ == "__main__":
  app = AnarchistFAQApp()
  app.run()
