[Zope-CVS] CVS: Products/CompositePage - designuis.py:1.2 README.txt:1.6 __init__.py:1.5 composite.py:1.10 slot.py:1.15 tool.py:1.9

Shane Hathaway shane at zope.com
Thu Feb 26 16:38:42 EST 2004


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

Modified Files:
	README.txt __init__.py composite.py slot.py tool.py 
Added Files:
	designuis.py 
Log Message:
Merged composite-flat-ui-branch.

This adds a manual slotting interface (non-WYSIWYG).  The manual
UI still supports drag and drop, but has many links and no
context menus.


=== Products/CompositePage/designuis.py 1.1 => 1.2 ===
--- /dev/null	Thu Feb 26 16:38:42 2004
+++ Products/CompositePage/designuis.py	Thu Feb 26 16:38:11 2004
@@ -0,0 +1,306 @@
+##############################################################################
+#
+# 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$
+"""
+
+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")
+_manual = os.path.join(os.path.dirname(__file__), "manual")
+
+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 = "view"  # To be overridden
+
+    security.declarePublic("getFragments")
+    def getFragments(self, composite):
+        """Returns the fragments to be inserted in design mode.
+        """
+        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)
+        return {"header": header, "top": top, "bottom": bottom}
+
+
+    security.declarePrivate("render")
+    def render(self, composite):
+        """Renders a composite, adding scripts and styles.
+        """
+        text = composite()
+        fragments = self.getFragments(composite)
+        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 fragments['header']:
+            index = match.end(0)
+            text = "%s%s%s" % (text[:index], fragments['header'], text[index:])
+        if fragments['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], fragments['top'], text[index:])
+        if fragments['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], fragments['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("previewElement")
+    def previewElement(self, path, RESPONSE):
+        """Redirects to the preview for an element.
+        """
+        root = self.getPhysicalRoot()
+        obj = root.restrictedTraverse(path)
+        RESPONSE.redirect(obj.absolute_url())
+
+
+    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 (CommonUI):
+    """Non-WYSIWYG page design UI.
+    """
+    security = ClassSecurityInfo()
+
+    body = PageTemplateFile("body.pt", _manual)
+    manual_styles_css = InterpolatedFile(
+        "manual_styles.css", "text/css", _manual)
+    header_templates = (PageTemplateFile("header.pt", _manual),)
+    manual_js = RawFile("manual.js", "text/javascript", _manual)
+
+    security.declarePublic("render")
+    def render(self, composite):
+        """Renders a composite, adding scripts and styles.
+
+        Returns an HTML fragment (not a full page).
+        """
+        slot_data = composite.getSlotData()
+        pt = self.body.__of__(composite)
+        return pt(slot_data=slot_data)
+
+Globals.InitializeClass(ManualUI)


=== Products/CompositePage/README.txt 1.5 => 1.6 ===
--- Products/CompositePage/README.txt:1.5	Mon Oct 13 13:21:47 2003
+++ Products/CompositePage/README.txt	Thu Feb 26 16:38:11 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.5 ===
--- Products/CompositePage/__init__.py:1.4	Fri Dec 26 15:43:30 2003
+++ Products/CompositePage/__init__.py	Thu Feb 26 16:38:11 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.10 ===
--- Products/CompositePage/composite.py:1.9	Wed Dec 31 12:32:14 2003
+++ Products/CompositePage/composite.py	Thu Feb 26 16:38:11 2004
@@ -28,7 +28,7 @@
 from AccessControl.ZopeGuards import guarded_getattr
 
 from interfaces import ISlot, CompositeError
-from slot import Slot
+from slot import Slot, getIconURL
 from macro import renderMacro, getRootMacro
 import perm_names
 
@@ -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 _beginCollection(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):
@@ -73,6 +87,7 @@
         + Folder.manage_options[2:]
         )
 
+    default_ui = "common"
     template_path = "template"
     _v_editing = 0
     _v_rendering = 0
@@ -138,25 +153,19 @@
     index_html = None
 
     security.declareProtected(perm_names.change_composites, "design")
-    def design(self, transformer="common"):
+    def design(self, ui=None):
         """Renders the composite with editing features.
         """
-        tool = aq_get(self, "composite_tool", None, 1)
-        if tool is None:
-            raise CompositeError("No composite_tool found")
-
         # Never cache a design view.
         req = getattr(self, "REQUEST", None)
         if req is not None:
             req["RESPONSE"].setHeader("Cache-Control", "no-cache")
-
+        ui_obj = self.getUI(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")
@@ -165,8 +174,78 @@
         """
         return self.design("zmi")
 
+    security.declareProtected(perm_names.change_composites, "getUI")
+    def getUI(self, ui=None):
+        """Returns a UI object.
+        """
+        if not ui:
+            ui = self.default_ui
+        tool = aq_get(self, "composite_tool", None, 1)
+        if tool is None:
+            raise CompositeError("No composite_tool found")
+        return guarded_getattr(tool.uis, ui)
+
+    security.declareProtected(perm_names.change_composites, "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
+
+    security.declareProtected(perm_names.change_composites, "getSlotData")
+    def getSlotData(self):
+        """Prepares information about slot contents for presentation.
+        """
+        contents = []  # [{name, slot_info}]
+        seen = {}
+        names = self.getSlotNames()
+        if hasattr(self, 'portal_url'):
+            icon_base_url = self.portal_url()
+        else:
+            icon_base_url = self.REQUEST['BASEPATH1']
+        for name in names:
+            if seen.has_key(name):
+                # Don't show duplicate uses of a slot.
+                continue
+            seen[name] = 1
+            slot = self.slots[name]
+            elements = []
+            index = 0
+            slot_values = slot.objectValues()
+            for element in slot_values:
+                icon = getIconURL(element, icon_base_url)
+                element_info = {
+                    'title': element.title_or_id(),
+                    'icon': icon,
+                    'source_path': '/'.join(element.getPhysicalPath()),
+                    'index': index,
+                    'next_index': index + 1,
+                    'can_move_up': (index > 0),
+                    'can_move_down': (index < len(slot_values) - 1),
+                    'view': 'xxx',
+                    'available_views': ('yyy', 'zzz'),
+                    }
+                elements.append(element_info)
+                index += 1
+            slot_info = {
+                'title': name,  # XXX need to get a real slot title somehow.
+                'slot': slot,
+                'target_path': '/'.join(slot.getPhysicalPath()),
+                'elements': elements,
+                }
+            contents.append(slot_info)
+        return contents
+
     security.declareProtected(perm_names.view, "isEditing")
     def isEditing(self):
+        """Returns true if currently rendering in design mode.
+        """
         return self._v_editing
 
 Globals.InitializeClass(Composite)


=== Products/CompositePage/slot.py 1.14 => 1.15 ===
--- Products/CompositePage/slot.py:1.14	Tue Jan  6 11:38:28 2004
+++ Products/CompositePage/slot.py	Thu Feb 26 16:38:11 2004
@@ -166,19 +166,7 @@
                 text = self._handleError(editing)
 
             if editing:
-                base = aq_base(obj)
-
-                if hasattr(base, 'getIcon'):
-                    icon = str(obj.getIcon())
-                elif hasattr(base, 'icon'):
-                    icon = str(obj.icon)
-                else:
-                    icon = ""
-                if icon and '://' not in icon:
-                    if not icon.startswith('/'):
-                        icon = '/' + icon
-                    icon = icon_base_url + icon
-
+                icon = getIconURL(obj, icon_base_url)
                 title = obj.title_and_id()
                 path = escape('/'.join(obj.getPhysicalPath()))
                 res.append(edit_tag % (path,
@@ -220,6 +208,20 @@
 
 Globals.InitializeClass(Slot)
 
+
+def getIconURL(obj, icon_base_url):
+    base = aq_base(obj)
+    if hasattr(base, 'getIcon'):
+        icon = str(obj.getIcon())
+    elif hasattr(base, 'icon'):
+        icon = str(obj.icon)
+    else:
+        icon = ""
+    if icon and '://' not in icon:
+        if not icon.startswith('/'):
+            icon = '/' + icon
+        icon = icon_base_url + icon
+    return icon
 
 
 addSlotForm = PageTemplateFile("addSlotForm", _www)


=== Products/CompositePage/tool.py 1.8 => 1.9 ===
--- Products/CompositePage/tool.py:1.8	Sat Dec 27 23:32:47 2003
+++ Products/CompositePage/tool.py	Thu Feb 26 16:38:11 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