## $Id: viewlets.py 7705 2012-02-26 16:34:10Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## 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
##
import grok
from urllib import urlencode
from zope.i18n import translate
from zope.component import getMultiAdapter, queryAdapter, getUtility
from zope.interface import Interface
from zope.location.interfaces import ISite
from zope.traversing.browser import absoluteURL
from waeup.sirp.browser.pages import (
    UniversityPage, FacultiesContainerPage, DatacenterPage, FacultyPage,
    DepartmentPage, CoursePage, CertificatePage, CertificateCoursePage,
    UsersContainerPage, UserManageFormPage)
from waeup.sirp.browser.interfaces import (
    IFacultiesContainer, IFaculty, IDepartment, ICourse, ICertificate,
    ICertificateCourse, IBreadcrumbContainer, IUniversity, IUsersContainer)
from waeup.sirp.interfaces import (
    ISIRPUtils, ISIRPObject, ISIRPXMLExporter,
    ISIRPXMLImporter, IDataCenter, IUserAccount)
from waeup.sirp.browser.layout import SIRPPage, default_primary_nav_template
from waeup.sirp.utils.helpers import get_user_account

from waeup.sirp.interfaces import MessageFactory as _

grok.templatedir('templates')
grok.context(ISIRPObject) # Make ISIRPObject the default context

class LeftSidebar(grok.ViewletManager):
    grok.name('left')

class BreadCrumbManager(grok.ViewletManager):
    grok.name('breadcrumbs')

class LanguageManager(grok.ViewletManager):
    grok.name('languages')

class ActionBar(grok.ViewletManager):
    grok.name('actionbar')

class AdministrationTasks(grok.ViewletManager):
    grok.name('admintasks')


#
# Baseclasses that give some defaults for really used viewlets.
#
class ActionButton(grok.Viewlet):
    """A base for action buttons.

    An action button provides an icon, some text and links to a
    target.  If you want to set a different text, icon or target name
    for some active button below, just override the approriate
    attribute in the concerned viewlet.

    Action buttons provide by default dynamic attributes

     * ``alt``
          An alternative text for the icon. By default the same as
          the text.

     * ``icon_url``
          The URL of the icon.

     * ``target_url``
          The URL of the link target.

    """
    grok.baseclass()
    grok.context(ISIRPObject)
    grok.viewletmanager(ActionBar)
    icon = 'actionicon_modify.png' # File must exist in static/
    target = '@@manage' # link to this viewname.
    text = _('Edit') # Text to display on the button

    # We set the template file explicitly (instead of using
    # ``grok.template('actionbutton')``) to stick with this template
    # also in derived classes in other packages. If we didn't, those
    # derived ActionButton viewlets had to provide an own template,
    # which would not be updated automatically, when the local
    # template ``templates/actionbutton.pt`` changes.
    #
    # Inheriting viewlets that wish to use their own template anyway
    # can do so by setting their local ``grok.template(<mytemplate>)``
    # and setting ``template`` to ``None`` for the class::
    #
    # class DerivedActionButton(ActionButton):
    #   ...
    #   grok.template('overriding_template')
    #   template = None
    #   ...
    #
    template = grok.PageTemplateFile('templates/actionbutton.pt')

    @property
    def alt(self):
        """Alternative text for icon.
        """
        return self.text

    @property
    def icon_url(self):
        """Get the icon URL.
        """
        static = self.view.static
        if static is None or static.get(self.icon, None) is None:
            # In derived classes defined in other modules/packages
            # than w.s.browser, ``static`` might refer to a static dir
            # local to the derived class' module. As we often like to
            # get the icons from here
            # (i.e. waeup.sirp.browser/static), we set the directory
            # resource appropiately.
            #
            # XXX: The hardcoding of 'w.s.browser' should be replaced
            #      by something smarter.
            #
            # TODO: notes in here should go to general documentation.
            static = queryAdapter(
                self.request, Interface, name='waeup.sirp.browser')
        return static[self.icon]()

    @property
    def target_url(self):
        """Get a URL to the target...
        """
        return self.view.url(self.view.context, self.target)

class PlainActionButton(ActionButton):
    """A base for action buttons without image
    """
    grok.baseclass()
    template = grok.PageTemplateFile('templates/plainactionbutton.pt')

    
class ManageActionButton(ActionButton):
    """A base for 'edit' buttons
    """
    grok.baseclass()
    grok.order(2)
    grok.require('waeup.manageUniversity')
    icon = 'actionicon_modify.png'
    target = '@@manage'
    text = _('Edit')

class AddActionButton(ActionButton):
    """A base for 'add' buttons.
    """
    grok.baseclass()
    grok.order(4)
    grok.require('waeup.manageUniversity')
    icon = 'actionicon_add.png'
    target = 'add'
    text = _('Add')
    
class RemoveActionButton(ActionButton):
    """A base for 'remove' buttons.
    """
    grok.baseclass()
    grok.order(4)
    grok.require('waeup.manageUniversity')
    icon = 'actionicon_delete.png'
    target = 'remove'
    text = _('Remove')

class SearchActionButton(ActionButton):
    """A base for 'search' buttons.
    """
    grok.baseclass()
    grok.order(5)
    grok.require('waeup.manageUniversity')
    icon = 'actionicon_search.png'
    target = 'search'
    text = _('Search')


#
# General viewlets (for more than one page/context)
#

class BreadCrumbs(grok.Viewlet):
    grok.context(ISIRPObject)
    grok.viewletmanager(BreadCrumbManager)
    grok.order(1)

    def getEntries(self):
        result = []
        site = grok.getSite()
        context = self.context
        breadcrumbs = IBreadcrumbContainer(self.view)
        for breadcrumb in breadcrumbs:
            if breadcrumb.target:
                yield dict(
                    title = breadcrumb.title,
                    url = self.view.url(breadcrumb.context, breadcrumb.target)
                    )

class LanguagesLink(grok.Viewlet):
    """ The language selector itself.
    """
    grok.viewletmanager(LanguageManager)
    grok.context(ISIRPObject)
    grok.require('waeup.Public')
    title = u'Languages'

    def render(self):
        preferred_languages = getUtility(ISIRPUtils).PREFERRED_LANGUAGES_DICT
        html = u''
        for key, value in sorted(
            preferred_languages.items(), key=lambda lang: lang[1][0]):
            args = {'lang':key, 'view_name':self.view.__name__}
            url = self.view.url(
                self.context) + '/@@change_language?%s' % urlencode(args)
            html += u'| <a href="%s" title="%s">%s</a> ' % (url, value[1], key)
        return html

# Problem with circular references. Disabled for now...
# class ExportXMLAction(grok.Viewlet):
#    grok.viewletmanager(ActionBar)
#     #grok.view(Index)
#     grok.order(98)
#     grok.require('waeup.manageUniversity')

#class ImportXMLAction(grok.Viewlet):
#    grok.viewletmanager(ActionBar)
#    #grok.view(Index)
#    grok.order(99)
#    grok.require('waeup.manageUniversity')
#
#    def update(self):
#        # We cannot simply replace local sites.
#        self.can_import = not ISite.providedBy(self.context)

class LeftSidebarLink(grok.Viewlet):
    """ An entry on left sidebar.

    This is only a baseclass that won't be rendered actually. Deriving
    viewlets can override certain values and will be rendered with the
    values set here as default.
    """
    grok.baseclass()
    grok.viewletmanager(LeftSidebar)
    grok.context(ISIRPObject)
    grok.order(5)
    grok.require('waeup.manageUniversity')
    icon = 'actionicon_modify.png' # File must exist in static/
    title = 'Text of link'

    @property
    def url(self):
        return '@@index'

    @property
    def icon_url(self):
        """Get the icon URL.
        """
        if self.icon:
            static = self.view.static
            if static is None or static.get(self.icon, None) is None:
                # In derived classes defined in other modules/packages
                # than w.s.browser, ``static`` might refer to a static dir
                # local to the derived class' module. As we often like to
                # get the icons from here
                # (i.e. waeup.sirp.browser/static), we set the directory
                # resource appropiately.
                #
                # XXX: The hardcoding of 'w.s.browser' should be replaced
                #      by something smarter.
                #
                # TODO: notes in here should go to general documentation.
                static = queryAdapter(
                    self.request, Interface, name='waeup.sirp.browser')
            return static[self.icon]()
        return
    
    # Render link only if url is provided.
    def render(self):
        if self.url:
            if self.icon_url:
                return u'<li><a href="%s"><img src="%s" /> %s </a></li>' % (
                    self.url, self.icon_url, self.title)
            else:
                return u'<li><a href="%s">%s </a></li>' % (
                    self.url, self.title)
        else:
            return ''
            
#
# waeup.sirp.app.University viewlets...
#
class Login(grok.Viewlet):
    """This viewlet allows to login in the sidebar.
    """
    grok.viewletmanager(LeftSidebar)
    grok.context(ISIRPObject)
    grok.view(Interface)
    grok.order(2)
    grok.require('waeup.Anonymous')
    text = _('Login')
    link = 'login'

    def render(self):
        if self.request.principal.id != 'zope.anybody':
            return ''
        url = self.view.url(grok.getSite(), self.link)
        return u'<li"><a href="%s">%s</a></li>' % (
                url, self.text)


class ManageLink(grok.Viewlet):
    """A link displayed in the upper left box.

    This viewlet renders a link to the application object's settings
    form (the 'manage' view).

    In derived classes you can create different links by setting a
    different link and text attribute. The `link` parameter is
    understood relative to the respective application object, so that
    ``@@manage`` will create a link to
    ``localhost:8080/app/@@manage``.

    Links defined by descendants from this viewlet are displayed on
    every page the user is allowed to go to, if the user has also the
    permissions set by `grok.require()`. By default only users with
    ``waeup.manageUniversity`` permission will see links defined by
    this or derivated classes.
    """
    grok.viewletmanager(LeftSidebar)
    grok.context(ISIRPObject)
    grok.view(Interface)
    grok.order(5)
    # This link is only displayed, if the user is
    # allowed to use it!
    grok.require('waeup.managePortalConfiguration')

    link = 'configuration'
    text = _(u'Portal Configuration')
    
    def render(self):
        url = self.view.url(grok.getSite(), self.link)
        text = translate(self.text, context=self.request)
        return u'<li><a href="%s">%s</a></li>' % (
                url, text)

class ManageUsersLink(ManageLink):
    """A link to users management, placed in upper left box.
    """
    grok.order(6)
    grok.require('waeup.manageUsers')

    link = u'users'
    text = _(u'Portal Users')

class ContactActionButton(ManageActionButton):
    grok.order(4)
    grok.context(IUserAccount)
    grok.view(UserManageFormPage)
    grok.require('waeup.manageUsers')
    icon = 'actionicon_mail.png'
    text = _('Send email')
    target = 'contactuser'

class ManageDataCenter(ManageLink):
    """A link to datacenter, placed in upper left box.
    """
    grok.order(6)
    grok.require('waeup.manageDataCenter')

    link = u'datacenter'
    text = _(u'Data Center')

class MyPreferences(LeftSidebarLink):
    """A link to personal preferences, placed in upper left box.
    """
    grok.order(7)
    grok.require('waeup.Public')
    title = _(u'My Preferences')
    icon = ''

    @property
    def url(self):
        account_object = get_user_account(self.request)
        if account_object:
            return self.view.url(account_object)
        return

class MyRoles(LeftSidebarLink):
    """A link to display site and local roles.
    """
    grok.order(8)
    grok.require('waeup.Public')
    title = _(u'My Roles')
    icon = ''

    @property
    def url(self):
        account_object = get_user_account(self.request)
        if account_object:
            return self.view.url(account_object) + '/my_roles'
        return

#
# Manage ("Edit settings") actions...
#

class ManageDataCenterActionButton(ManageActionButton):
    """ 'Edit settings' button for datacenter.
    """
    grok.context(IDataCenter)
    grok.view(DatacenterPage)
    text = _('Edit settings')

class ManageFacultiesContainerActionButton(ManageActionButton):
    """ 'Manage settings' button for faculties.
    """
    grok.context(IFacultiesContainer)
    grok.view(FacultiesContainerPage)
    text = _('Manage academic section')

class SearchFacultiesContainerActionButton(ManageActionButton):
    """ 'Manage settings' button for faculties.
    """
    grok.context(IFacultiesContainer)
    grok.view(FacultiesContainerPage)
    text = _('Search academic section')
    icon = 'actionicon_search.png'
    target = '@@search'
    
class ManageFacultyActionButton(ManageActionButton):
    """ 'Manage settings' button for faculties.
    """
    grok.context(IFaculty)
    grok.view(FacultyPage)
    text = _('Manage faculty')

class ManageDepartmentActionButton(ManageActionButton):
    """ 'Manage settings' button for departments.
    """
    grok.context(IDepartment)
    grok.view(DepartmentPage)
    text = _('Manage department')

class ShowDepartmentStudentsActionButton(ManageActionButton):
    """ 'Show students' button for departments.
    """
    grok.context(IDepartment)
    grok.view(DepartmentPage)
    grok.require('waeup.showStudents')
    icon = 'actionicon_student.png'
    text = _('Show students')
    target = 'showdepstudents'

class ManageCourseActionButton(ManageActionButton):
    """ 'Edit settings' button for courses.
    """
    grok.context(ICourse)
    grok.view(CoursePage)
    text = _('Edit course')
    
class ManageCertificateActionButton(ManageActionButton):
    """ 'Manage settings' button for certificates.
    """
    grok.context(ICertificate)
    grok.view(CertificatePage)
    text = _('Manage certificate')

class ShowCertificateStudentsActionButton(ManageActionButton):
    """ 'Show students' button for certificates.
    """
    grok.context(ICertificate)
    grok.view(CertificatePage)
    grok.require('waeup.showStudents')
    icon = 'actionicon_student.png'
    text = _('Show students')
    target = 'showcertstudents'

class ManageCertificateCourseActionButton(ManageActionButton):
    """ 'Manage settings' button for certificate courses.
    """
    grok.context(ICertificateCourse)
    grok.view(CertificateCoursePage)
    text = _('Edit course referrer')

#
# Add actions...
#

class AddUserActionButton(AddActionButton):
    grok.require('waeup.manageUsers')
    grok.context(IUsersContainer)
    grok.view(UsersContainerPage)
    text = _('Add user')

#
# Actions with a 'browse' icon...
#
class BrowseActionButton(ActionButton):
    grok.baseclass()
    grok.context(ISIRPObject)
    grok.template('actionbutton')
    grok.viewletmanager(ActionBar)
    grok.require('waeup.manageUniversity')
    icon = 'actionicon_manage.png' # File must exist in static/
    target = '@@show' # link to this viewname.
    text = _('Show batch logs') # Text to display on the button

class BrowseDatacenterLogs(BrowseActionButton):
    grok.context(IDataCenter)
    grok.view(DatacenterPage)
    grok.order(4)
    icon = 'documentinfo_templet.png'
    target = '@@logs'
    text = _('Show batch logs')

#
# Misc. buttons...
#
class BatchOpButton(ActionButton):
    grok.context(IDataCenter)
    grok.view(DatacenterPage)
    grok.require('waeup.manageUniversity')
    grok.order(6)
    icon = 'actionbox_templet.png'
    target = '@@import1'
    text = _('Batch processing')

class UploadCSVButton(ActionButton):
    grok.context(IDataCenter)
    grok.view(DatacenterPage)
    grok.require('waeup.manageUniversity')
    grok.order(5)
    icon = 'go-up-16x16.png'
    target = '@@upload'
    text = _('Upload CSV file')

#
# Primary navigation tabs (in upper left navigation bar)...
#
class PrimaryNavManager(grok.ViewletManager):
    """Viewlet manager for the primary navigation tab.
    """
    grok.name('primary_nav')

class PrimaryNavTab(grok.Viewlet):
    """Base for primary nav tabs.
    """
    grok.baseclass()
    grok.viewletmanager(PrimaryNavManager)
    grok.order(1)
    grok.require('waeup.Public')
    template = default_primary_nav_template

    pnav = 0 # This is a kind of id of a tab. If some page provides
             # also a 'pnav' attribute with the same value (here: 0),
             # then the tab will be rendered as 'active' when the page
             # gets rendered.
             #
             # This way you can assign certain pages to certain
             # primary nav tabs. Each primary tab should therefore set
             # the 'pnav' attribute to a different value (or several
             # tabs might be rendered as active simultanously when the
             # page gets rendered.
    tab_title = u'Some Text'
    
    @property
    def link_target(self):
        return self.view.application_url()

    @property
    def active(self):
        view_pnav = getattr(self.view, 'pnav', 0)
        if view_pnav == self.pnav:
            return 'active'
        return ''

#class HomeTab(PrimaryNavTab):
#    """Home-tab in primary navigation.
#    """
#    grok.order(1)
#    grok.require('waeup.Public')

#    pnav = 0
#    tab_title = u'Home'

class FacultiesTab(PrimaryNavTab):
    """Faculties-tab in primary navigation.
    """
    grok.order(2)
    grok.require('waeup.viewAcademics')

    pnav = 1
    tab_title = _(u'Academics')

    @property
    def link_target(self):
        return self.view.application_url('faculties')

    
class EnquiriesTab(PrimaryNavTab):
    """Contact tab in primary navigation.

    Display tab only for anonymous. Authenticated users can call a
    contact form from the user navigation bar.
    """
    grok.order(6)
    grok.require('waeup.Anonymous')
    tab_title = _(u'Enquiries')
    pnav = 2

    # Also zope.manager has role Anonymous.
    # To avoid displaying this tab, we have to check the principal id too.
    @property
    def link_target(self):
        if self.request.principal.id == 'zope.anybody':
            return self.view.application_url('enquiries')
        return

#
# Administration tasks
#
class AdminTask(grok.Viewlet):
    """The base for task entries on administration page.
    """
    grok.baseclass()
    grok.order(1)
    grok.viewletmanager(AdministrationTasks)
    grok.require('waeup.manageUniversity')
    grok.template('admintask')

    link_title = 'Manage users' # How the link to the target will be titled.
    target_viewname = 'users'   # The name of the target view.
    
    @property
    def link_target(self):
        return self.view.url(self.context[self.target_viewname])

class AdminTaskPortalConfiguration(AdminTask):
    """Entry on administration page that link to portal settings.
    """
    grok.order(1)
    grok.require('waeup.managePortalConfiguration')

    link_title = _('Portal Configuration')
    def link_target(self):
        return self.view.url(self.view.context, 'configuration')

class AdminTaskUsers(AdminTask):
    """Entry on administration page that link to user folder.
    """
    grok.order(2)
    grok.require('waeup.manageUsers')

    link_title = _('Portal Users')
    target_viewname = 'users'
    
class AdminTaskDatacenter(AdminTask):
    """Entry on administration page that link to datacenter.
    """
    grok.order(3)
    grok.require('waeup.manageDataCenter')

    link_title = _('Data Center')
    target_viewname = 'datacenter'

# The SubobjectLister and its viewlets below are not used in SIRP.

class SubobjectLister(grok.ViewletManager):
    """Very special viewlet manager that displays lists of subobjects.
    """
    grok.name('subobjectlist')
    grok.template('subobjectlist')

    def update(self):
        # The default implementation of update() sets self.viewlets to
        # a list of viewlets for the current context
        # (self.context). We make use of that fact by retrieving all
        # viewlets for all items in our context container by simply
        # setting these items as context while we call the default
        # update() method. So we get a list of lists of viewlets for
        # each item in a 'row' (where a single item is a row).
        rows = []
        orig_context = self.context
        for name, value in self.context.items():
            # Retrieve all viewlets for the current item (not the context)
            self.context = value
            super(SubobjectLister, self).update() # sets self.viewlets
            rows.append(self.viewlets)
            self.context = orig_context
        self.rows = rows
        # Finally, set the viewlets we would retrieve normally...
        super(SubobjectLister, self).update()
        return


class FacultiesContainerListHead(grok.Viewlet):
    """The header line of faculty container subobject lists.
    """
    grok.order(1)
    grok.viewletmanager(SubobjectLister)
    grok.context(IFacultiesContainer)
    grok.require('waeup.viewAcademics')

    def render(self):
        return u'<th>Code</th><th>Title</th><th></th>'

class FacultyListName(grok.Viewlet):
    """Display a the title of a faculty as link in a list.
    """
    grok.order(1)
    grok.viewletmanager(SubobjectLister)
    grok.context(IFaculty)
    grok.require('waeup.viewAcademics')

    def render(self):
        return u'<a href="%s">%s</a>' % (
            self.view.url(self.context), self.context.__name__)

class FacultyListTitle(grok.Viewlet):
    """Display the title of a faculty in a list.
    """
    grok.order(2)
    grok.viewletmanager(SubobjectLister)
    grok.context(IFaculty)
    grok.require('waeup.viewAcademics')

    def render(self):
        return self.context.title

class FacultyRemoveButton(grok.Viewlet):
    """Render a remove button for faculty lists.
    """
    grok.order(3)
    grok.viewletmanager(SubobjectLister)
    grok.context(IFaculty)
    grok.require('waeup.manageUniversity')

    def render(self):
        return u'<div class="text-right"><form method="POST">' + \
            '<input class="text-right" type="submit" value="remove" />' + \
            '</form></div>'
