[Zope-CVS] CVS: Products/CompositePage - designuis.py:1.1.2.1 README.txt:1.5.2.1 __init__.py:1.4.2.1 composite.py:1.9.2.1 tool.py:1.8.2.1

Shane Hathaway shane at zope.com
Fri Feb 20 12:00:17 EST 2004


Update of /cvs-repository/Products/CompositePage
In directory cvs.zope.org:/tmp/cvs-serv23783

Modified Files:
      Tag: composite-flat-ui-branch
	README.txt __init__.py composite.py tool.py 
Added Files:
      Tag: composite-flat-ui-branch
	designuis.py 
Log Message:
Started a branch for the work on a manual page composition UI.

Replaced the concept of a transformer with a "design UI".  Also
added functionality for listing what slots a template uses.


=== Added File Products/CompositePage/designuis.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Page design UI classes.

$Id: designuis.py,v 1.1.2.1 2004/02/20 16:59:46 shane Exp $
"""

import os
import re

import Globals
from Acquisition import aq_base, aq_inner, aq_parent
from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from AccessControl import ClassSecurityInfo
from AccessControl.ZopeGuards import guarded_getattr

from rawfile import RawFile, InterpolatedFile


_common = os.path.join(os.path.dirname(__file__), "common")
_zmi = os.path.join(os.path.dirname(__file__), "zmi")
_cmf = os.path.join(os.path.dirname(__file__), "cmf")

start_of_head_search = re.compile("(<head[^>]*>)", re.IGNORECASE).search
start_of_body_search = re.compile("(<body[^>]*>)", re.IGNORECASE).search
end_of_body_search = re.compile("(</body[^>]*>)", re.IGNORECASE).search

default_html_page = """<html>
<head>
<title>Composite Page</title>
</head>
<body>
%s
</body>
</html>
"""

close_dialog_html = '''<html>
<script type="text/javascript">
if (window.opener)
  window.opener.location.reload();
window.close();
</script>
</html>
'''

class CommonUI (SimpleItem):
    """Basic page design UI.

    Adds editing features to a rendered composite.
    """

    security = ClassSecurityInfo()

    security.declarePublic(
        "pdlib_js", "design_js", "pdstyles_css", "designstyles_css")
    pdlib_js = RawFile("pdlib.js", "text/javascript", _common)
    edit_js = RawFile("edit.js", "text/javascript", _common)
    pdstyles_css = RawFile("pdstyles.css", "text/css", _common)
    editstyles_css = InterpolatedFile("editstyles.css", "text/css", _common)
    target_image = RawFile("target.gif", "image/gif", _common)
    target_image_hover = RawFile("target_hover.gif", "image/gif", _common)
    target_image_active = RawFile("target_active.gif", "image/gif", _common)
    element_image = RawFile("element.gif", "image/gif", _common)

    header_templates = (PageTemplateFile("header.pt", _common),)
    top_templates = ()
    bottom_templates = (PageTemplateFile("bottom.pt", _common),)

    changeViewForm = PageTemplateFile("changeViewForm.pt", _common)

    workspace_view_name = ""  # To be overridden

    security.declarePrivate("render")
    def render(self, composite):
        """Renders a composite and adds scripts.
        """
        text = composite()
        params = {
            "tool": aq_parent(aq_inner(self)),
            "ui": self,
            "composite": composite,
            }
        header = ""
        top = ""
        bottom = ""
        for t in self.header_templates:
            header += t.__of__(self)(**params)
        for t in self.top_templates:
            top += t.__of__(self)(**params)
        for t in self.bottom_templates:
            bottom += t.__of__(self)(**params)
            
        match = start_of_head_search(text)
        if match is None:
            # Turn it into a page.
            text = default_html_page % text
            match = start_of_head_search(text)
            if match is None:
                raise CompositeError("Could not find header")
        if header:
            index = match.end(0)
            text = "%s%s%s" % (text[:index], header, text[index:])
        if top:
            match = start_of_body_search(text)
            if match is None:
                raise CompositeError("No 'body' tag found")
            index = match.end(0)
            text = "%s%s%s" % (text[:index], top, text[index:])
        if bottom:
            match = end_of_body_search(text)
            if match is None:
                raise CompositeError("No 'body' end tag found")
            m = match
            while m is not None:
                # Find the *last* occurrence of "</body>".
                match = m
                m = end_of_body_search(text, match.end(0))
            index = match.start(0)
            text = "%s%s%s" % (text[:index], bottom, text[index:])

        return text


    security.declarePublic("showElement")
    def showElement(self, path, RESPONSE):
        """Redirects to the workspace for an element.
        """
        root = self.getPhysicalRoot()
        obj = root.restrictedTraverse(path)
        RESPONSE.redirect("%s/%s" % (
            obj.absolute_url(), self.workspace_view_name))


    security.declarePublic("showSlot")
    def showSlot(self, path, RESPONSE):
        """Redirects to (and possibly creates) the workspace for a slot.
        """
        from composite import Composite

        obj = self.getPhysicalRoot()
        parts = str(path).split('/')
        for name in parts:
            obj = obj.restrictedTraverse(name)
            try:
                is_comp = isinstance(obj, Composite)
            except TypeError:
                is_comp = 0  # Python 2.1 bug
            if is_comp:
                gen = guarded_getattr(obj, "generateSlots")
                gen()
        RESPONSE.redirect("%s/%s" % (
            obj.absolute_url(), self.workspace_view_name))


    security.declarePublic("getViewChangeInfo")
    def getViewChangeInfo(self, paths):
        """Returns information for changing the view applied to objects.
        """
        root = self.getPhysicalRoot()
        tool = aq_parent(aq_inner(self))
        obs = []
        all_choices = None  # {view -> 1}
        current = None
        for path in str(paths).split(':'):
            ob = root.restrictedTraverse(path)
            obs.append(ob)
            renderer = tool.getRendererFor(ob)
            m = guarded_getattr(renderer, "getInlineView")
            view = m()
            if current is None:
                current = view
            elif current and current != view:
                # The current view isn't the same for all of the elements,
                # so there is no common current view.  Spell this condition
                # using a non-string value.
                current = 0
            m = guarded_getattr(renderer, "listAllowableInlineViews")
            views = m()
            d = {}
            for view in views:
                d[view] = 1
            if all_choices is None:
                all_choices = d
            else:
                for view in all_choices.keys():
                    if not d.has_key(view):
                        del all_choices[view]
        views = all_choices.keys()
        views.sort()
        return {"obs": obs, "views": views, "current_view": current}


    security.declarePublic("changeView")
    def changeView(self, paths, view, REQUEST=None):
        """Changes the view for objects.
        """
        info = self.getViewChangeInfo(paths)
        if view not in info["views"]:
            raise KeyError("View %s is not among the choices" % view)
        tool = aq_parent(aq_inner(self))
        for ob in info["obs"]:
            renderer = tool.getRendererFor(ob)
            m = guarded_getattr(renderer, "setInlineView")
            m(view)
        if REQUEST is not None:
            return close_dialog_html

Globals.InitializeClass(CommonUI)



class ZMIUI (CommonUI):
    """Page design UI meant to fit the Zope management interface.

    Adds editing features to a rendered composite.
    """
    security = ClassSecurityInfo()

    workspace_view_name = "manage_workspace"

    security.declarePublic("zmi_edit_js")
    zmi_edit_js = RawFile("zmi_edit.js", "text/javascript", _zmi)

    header_templates = CommonUI.header_templates + (
        PageTemplateFile("header.pt", _zmi),)
    top_templates = CommonUI.top_templates + (
        PageTemplateFile("top.pt", _zmi),)
    bottom_templates = (PageTemplateFile("bottom.pt", _zmi),
                        ) + CommonUI.bottom_templates

Globals.InitializeClass(ZMIUI)



class CMFUI (CommonUI):
    """Page design UI meant to fit CMF.

    Adds CMF-specific scripts and styles to a page.
    """
    security = ClassSecurityInfo()

    workspace_view_name = "view"

    security.declarePublic("cmf_edit_js")
    cmf_edit_js = RawFile("cmf_edit.js", "text/javascript", _cmf)

    header_templates = CommonUI.header_templates + (
        PageTemplateFile("header.pt", _cmf),)
    bottom_templates = (PageTemplateFile("bottom.pt", _cmf),
                        ) + CommonUI.bottom_templates

Globals.InitializeClass(CMFUI)


class ManualUI (SimpleItem):
    """Page design UI based on a simple list of slots.

    Does not display the actual template.
    """
    security = ClassSecurityInfo()

    security.declarePrivate("render")
    def render(self, composite):
        return 'foo'

Globals.InitializeClass(ManualUI)


=== Products/CompositePage/README.txt 1.5 => 1.5.2.1 ===
--- Products/CompositePage/README.txt:1.5	Mon Oct 13 13:21:47 2003
+++ Products/CompositePage/README.txt	Fri Feb 20 11:59:46 2004
@@ -131,20 +131,20 @@
 
 Rendering in edit mode:
 
-When requested, the composite renders its template and slots with edit
-mode turned on.  In edit mode, slots add 'class', 'source_path',
-'target_path', and 'target_index' attributes to HTML tags to mark
-movable objects and available drop targets.  Slots add HTML markup for
-drop targets automatically.  When rendering using the single() method,
-slots provide a drop target only if the slot is empty.  When rendering
-using the multiple() method, slots insert drop targets between the
-elements and to the beginning and end of the slot.
-
-After the composite is rendered, the rendered HTML is passed through a
-transformer.  The transformer uses regular expressions to find the
-'head' and 'body' tags.  Then the transformer inserts scripts, styles,
-and HTML elements.  The result of the transformation is sent back to
-the browser.
+When requested, the composite calls upon a "UI" object to render its
+template and slots with edit mode turned on.  In edit mode, slots add
+'class', 'source_path', 'target_path', and 'target_index' attributes
+to HTML tags to mark movable objects and available drop targets.
+Slots add HTML markup for drop targets automatically.  When rendering
+using the single() method, slots provide a drop target only if the
+slot is empty.  When rendering using the multiple() method, slots
+insert drop targets between the elements and to the beginning and end
+of the slot.
+
+The UI object can use various mechanisms to make the page editable.
+Most UI objects use regular expressions to find the 'head' and 'body'
+tags.  Then the UI object inserts scripts, styles, and HTML elements.
+The result of the transformation is sent back to the browser.
 
 
 Drag and drop:
@@ -192,10 +192,9 @@
 
 CompositePage provides a default user interface that integrates with
 the Zope management interface, but mechanisms are provided for
-integrating with any user interface.  Look at transformers.py, the
-'common' subdirectory, and the 'zmi' subdirectory for guidance.
-Simple customizations probably do not require more code than the 'zmi'
-transformer.
+integrating with any user interface.  Look at design.py, the 'common'
+subdirectory, and the 'zmi' subdirectory for guidance.  Simple
+customizations probably do not require more code than ZMIUI.
 
 
 


=== Products/CompositePage/__init__.py 1.4 => 1.4.2.1 ===
--- Products/CompositePage/__init__.py:1.4	Fri Dec 26 15:43:30 2003
+++ Products/CompositePage/__init__.py	Fri Feb 20 11:59:46 2004
@@ -15,11 +15,12 @@
 $Id$
 """
 
-import tool, composite, slot, slotdef, transformers, interfaces
+import tool, composite, slot, slotdef, designuis, interfaces
 
-tool.registerTransformer("common", transformers.CommonTransformer())
-tool.registerTransformer("zmi", transformers.ZMITransformer())
-tool.registerTransformer("cmf", transformers.CMFTransformer())
+tool.registerUI("common", designuis.CommonUI())
+tool.registerUI("zmi", designuis.ZMIUI())
+tool.registerUI("cmf", designuis.CMFUI())
+tool.registerUI("manual", designuis.ManualUI())
 
 
 def initialize(context):


=== Products/CompositePage/composite.py 1.9 => 1.9.2.1 ===
--- Products/CompositePage/composite.py:1.9	Wed Dec 31 12:32:14 2003
+++ Products/CompositePage/composite.py	Fri Feb 20 11:59:46 2004
@@ -39,10 +39,13 @@
     """Automatically makes slots available to the template.
     """
     _slot_class = Slot
+    _v_used_slots = None
 
     def __getitem__(self, name):
         composite = aq_parent(aq_inner(self))
         slots = composite.filled_slots
+        if self._v_used_slots is not None:
+            self._v_used_slots.append(name)
         try:
             return slots[name]
         except (KeyError, AttributeError):
@@ -57,6 +60,17 @@
                 s._p_jar = jar
             return s.__of__(slots)
 
+    def _startCollection(self):
+        """Starts collecting the names of slots used.
+        """
+        self._v_used_slots = []
+
+    def _endCollection(self):
+        """Stops collecting slot names and returns the names in order of use.
+        """
+        res = self._v_used_slots
+        self._v_used_slots = None
+        return res
 
 
 class Composite(Folder):
@@ -138,7 +152,7 @@
     index_html = None
 
     security.declareProtected(perm_names.change_composites, "design")
-    def design(self, transformer="common"):
+    def design(self, ui="common"):
         """Renders the composite with editing features.
         """
         tool = aq_get(self, "composite_tool", None, 1)
@@ -150,13 +164,12 @@
         if req is not None:
             req["RESPONSE"].setHeader("Cache-Control", "no-cache")
 
+        ui_obj = guarded_getattr(tool.uis, ui)
         self._v_editing = 1
         try:
-            text = self()
+            return ui_obj.render(self)
         finally:
             self._v_editing = 0
-        tf = guarded_getattr(tool.transformers, transformer)
-        return tf.transform(self, text)
 
     security.declareProtected(perm_names.change_composites,
                               "manage_designForm")
@@ -167,7 +180,22 @@
 
     security.declareProtected(perm_names.view, "isEditing")
     def isEditing(self):
+        """Returns true if currently rendering in design mode.
+        """
         return self._v_editing
+
+    security.declareProtected(perm_names.view, "getSlotNames")
+    def getSlotNames(self):
+        """Returns the names of the slots in order of use.
+
+        May return duplicates.
+        """
+        self.slots._beginCollection()
+        try:
+            self()
+        finally:
+            names = self.slots._endCollection()
+            return names
 
 Globals.InitializeClass(Composite)
 


=== Products/CompositePage/tool.py 1.8 => 1.8.2.1 ===
--- Products/CompositePage/tool.py:1.8	Sat Dec 27 23:32:47 2003
+++ Products/CompositePage/tool.py	Fri Feb 20 11:59:46 2004
@@ -30,22 +30,22 @@
 from utils import copyOf
 
 
-_transformers = {}
+_uis = {}
 
-def registerTransformer(name, obj):
-    """Registers a transformer for use with the composite tool.
+def registerUI(name, obj):
+    """Registers a page design UI for use with the composite tool.
     """
-    if _transformers.has_key(name):
-        raise RuntimeError("There is already a transformer named %s" % name)
+    if _uis.has_key(name):
+        raise RuntimeError("There is already a UI named %s" % name)
     obj._setId(name)
-    _transformers[name] = obj
+    _uis[name] = obj
 
 
 
-class Transformers(SimpleItem):
-    """The container of transformer objects.
+class DesignUIs(SimpleItem):
+    """The container of design user interface objects.
 
-    Makes page transformers accessible through URL traversal.
+    Makes page design UIs accessible through URL traversal.
     """
 
     def __init__(self, id):
@@ -53,7 +53,7 @@
 
     def __getattr__(self, name):
         try:
-            return _transformers[name]
+            return _uis[name]
         except KeyError:
             raise AttributeError, name
 
@@ -75,8 +75,8 @@
 
     security = ClassSecurityInfo()
 
-    security.declarePublic("transformers")
-    transformers = Transformers("transformers")
+    security.declarePublic("uis")
+    uis = DesignUIs("uis")
 
     _properties = Folder._properties + (
         {'id': 'default_inline_views', 'mode': 'w', 'type': 'lines',




More information about the Zope-CVS mailing list