[Zope-CVS] CVS: Products/ExternalEditor - INSTALL-UNIX.txt:1.1 INSTALL-WIN32.txt:1.1 ExternalEditor.py:1.7 README.txt:1.2 zopeedit.py:1.19 INSTALL.txt:NONE

Casey Duncan casey@zope.com
Sun, 16 Jun 2002 23:18:30 -0400


Update of /cvs-repository/Products/ExternalEditor
In directory cvs.zope.org:/tmp/cvs-serv4830

Modified Files:
	ExternalEditor.py README.txt zopeedit.py 
Added Files:
	INSTALL-UNIX.txt INSTALL-WIN32.txt 
Removed Files:
	INSTALL.txt 
Log Message:
Product now Zope 2.3.x compatible
Refactored windows registry calls to use _winreg
Changed precidence of domain config section to just above general
Fixed Z order of windows dialogs and added icons
removed bare excepts
improved editor detection on windows
added windows docs
updated general docs for new features
finished windows testing


=== Added File Products/ExternalEditor/INSTALL-UNIX.txt ===
Zope External Editor Installation

  Installation is two-fold: 

  - Install the ExternalEditor product in Zope.

  - Install the helper application on the client(s) and configure the browser(s)

  Product Installation

    Download the archive and extract it into your Zope products directory. Then
    restart Zope. If you succeeded, you'll notice pencil icons next to the
    external editable objects in the Zope management screens.

  Helper Application Installation

    Dependancies: Python 2.2, Tk

    Download the helper app archive and extract into the directory where you
    want to install it (it contains one executable Python module file). ~/bin
    or /usr/local/bin might be good choices, the latter if you want to share
    it between multiple users.

    Once you have the help application installed, you need to configure your
    browser to fire it off appropriately. To do so, create an entry in the
    helper applications list for your browser(s) that associates the mime type
    "application/x-zope-edit" with the helper application.
    
    Here are the step-by-step configuration instructions for Mozilla:
    
    - From the Edit menu choose Preferences
    
    - Under Navigator choose Helper Applications
    
    - Click on the New Type button
    
    - Enter a description, like "Zope Editor"
    
    - For MIME type, enter application/x-zope-edit
    
    - for Application, select the helper application python file

  Tips
  
    The helper application can run any editor program that does not detach
    itself from the controlling process.
    
    To get terminal based editors to work, you need to spawn them inside an 
    xterm. to do this, use something like the following for the editor option::

      editor = xterm -e vi
      
    You can of course modify the above to fire up your favorite terminal and 
    editor or add any command line arguments you want.
    
    As for editors that insist on detaching from the controlling process (gvim
    does this by default), you need to configure them so that they do not
    detach. For gvim you could use::

      editor = gvim -f

 Troubleshooting

    If the helper app won't launch try the following suggestions:
    
    - Make sure you have Tk installed properly. To test this, bring up Python
      in a terminal and enter 'import Tkinter'. If it throws an exception,
      that is your problem.

    - Netscape 4 users, add a "%s" at the end of the application command line. 
      It appears the Netscape  likes to alert you with spurious things coming
      from stderr. I'll see if I can come up with a solution to that.

    - Make sure the file is marked as executable for your user 
      ('chmod +x zopeedit.py' should do it)

    - Make sure the browser is properly configured. Use a full path to the 
      helper app.

    - Make sure you are using a graphical editor (that uses X windows). To use
      a terminal based editor (like vi), setup the editor option to spawn it 
      inside an xterm. See tips above.

    - Try downloading and saving the external editor data to a file manually
      (right click on the pencil icon). Then try running the helper app from 
      the command line, passing it the path to this file. If it runs, then
      there is something wrong with the browser configuration. If not, then it
      should output a traceback to your terminal. Email me a copy of this
      traceback, and the data file and I will try to fix it.

    - If the editor launches, but the helper app complains that it lost its 
      connection to the editor process, this is because your editor detached 
      from the parent process (the helper app). Configure the editor such that 
      it does not do this. Unfortunately this prevents you from using a 
      multi-headed text edit server (like nedit and emacs can provide). 
      This is a limitation I have not found a way around yet (any ideas??)


=== Added File Products/ExternalEditor/INSTALL-WIN32.txt ===
Zope External Editor Installation

  Installation is two-fold: 

  - Install the ExternalEditor product in Zope.

  - Install the helper application on the client(s) and configure the browser(s)

  Product Installation

    Download the archive and extract it into your Zope products directory. Then
    restart Zope. If you succeeded, you'll notice pencil icons next to the
    external editable objects in the Zope management screens.

  Helper Application Installation

    Dependancies: Python 2.2, Pythonwin extensions
    
    Download the helper app archive and extract into the directory where you 
    want to install it (it contains one executable Python module file).
    C:\Program Files\ZopeEdit might be a good choice. 

    Once you have the help application installed, you need to configure your
    browser to fire it off appropriately. To do so, create an entry in the
    helper applications list for your browser(s) that associates the mime type
    "application/x-zope-edit" with the helper application.

    To do this for Internet Explorer and other recent browsers, you must
    register the external editor file type with Windows:

    - From the "My Computer" window, choose "Folder Options" from the "View"
      menu.

    - In the "File Types" tab, click on the "New Type" button.

    - Enter a description such as "Zope External Editor"

    - Enter an extension not otherwise used on your system (.zope is usually a
      good choice, the exact value is not important)

    - For content type (MIME) enter: application/x-zope-edit

    - Under "Actions", click on the "New" button.

    - For Action enter: Open

    - For application used, enter or browse to the path to pythonw.exe
      (wherever you installed python2.2, such as C:\Program Files\Python22).
      Use quotes around the path if it includes spaces. Following the path to
      Python, enter the path to the helper application file, in quotes if
      needed.  Follow it with: "%1" (in quotes). On my system it looks like:

      '"C:\Program Files\Python22\pythonw.exe" C:\Casey\ExternalEditor\zopeedit.py "%1"

    - Click OK.

    - Uncheck "Confirm open after download"
      
    - Click OK, you should now see your new file type.
    
  Tips
  
    The helper application can run any editor program that does not detach
    itself from the controlling process.
    
    As for editors that insist on detaching from the controlling process
    (EditPad does this by default), you need to configure them so that they do
    not detach. For EditPad you could use::

      editor = C:\Program Files\EditPadClassic\EditPad.exe /newinstance

    Check the command line options of your favorite editor to see if it
    supports this.
    
 Troubleshooting

    If the helper app won't launch try the following suggestions:
    
    - Make sure you have Pythonwin installed properly. To test this, bring up 
      the Python console and type in 'import win32api'. If it throws an
      exception, you need to install Pythonwin.

    - Netscape 4 users, add a "%s" at the end of the application command line. 
      It appears the Netscape  likes to alert you with spurious things coming
      from stderr. I'll see if I can come up with a solution to that.

    - Make sure the file type is properly configured to launch the helper app
      when it receives files of type "application/x-zope-edit".

    - Try downloading and saving the external editor data to a file manually
      (right click on the pencil icon). Then try running the helper app from 
      the command line, passing it the path to this file. If it runs, then
      there is something wrong with the browser/system configuration. If not,
      then it should output a traceback to your terminal. Email me a copy of
      this traceback, and the data file and I will try to fix it.

    - If the editor launches, but the helper app complains that it lost its 
      connection to the editor process, this is because your editor detached 
      from the parent process (the helper app). Configure the editor such that 
      it does not do this. Unfortunately, many MDI applications do this and
      cannot be configured otherwise. Look for a solution to this in a later
      version of external editor.


=== Products/ExternalEditor/ExternalEditor.py 1.6 => 1.7 ===
 # Zope External Editor Product by Casey Duncan
 
+from string import join
 import Acquisition
 from AccessControl.SecurityManagement import getSecurityManager
 from webdav.common import rfc1123_date
@@ -23,7 +24,7 @@
 
 class ExternalEditor(Acquisition.Implicit):
     """Create a response that encapsulates the data needed by the
-       ZopeEdit help application
+       ZopeEdit helper application
     """
     
     def __before_publishing_traverse__(self, self2, request):
@@ -77,4 +78,4 @@
         RESPONSE.setHeader('Content-Type', 'application/x-zope-edit')
         RESPONSE.setHeader('Pragma', 'no-cache')
             
-        return '\n'.join(r)
+        return join(r, '\n')


=== Products/ExternalEditor/README.txt 1.1.1.1 => 1.2 ===
     This allows you to create appropriate behavior for different types of Zope
     objects and content. The configuration file is stored in the file 
-    "~/.zope-external-edit"
+    "~/.zope-external-edit" (Unix) or "~\ZopeEdit.ini" (Windows).
     
     The configuration file follows the standard Python ConfigParser format,
     which is pretty much like the old .ini file format from windows. The file
@@ -132,12 +132,16 @@
     
       The available options for all sections of the config file are:
 
-      editor -- (file path) The fully qualified path of the editor application
+      editor -- Command line used to launch the editor application. On
+      Windows, if no editor setting is found for an object you edit, the
+      helper app will search the file type registry for an appropriate editor
+      based on the content-type or file extension of the object (which can be 
+      specified using the extension option below).
 
-      save_interval -- (float) The interval in seconds that the helper application
-      checks the edited file for changes.
+      save_interval -- (float) The interval in seconds that the helper 
+      application checks the edited file for changes.
 
-      use_locks -- (1 or 0) Whether to use WebDAV locking
+      use_locks -- (1 or 0) Whether to use WebDAV locking.
 
       cleanup_files -- (1 or 0) Whether to delete the temp files created.
       WARNING the temp file coming from the browser contains authentication
@@ -149,6 +153,9 @@
       extension -- (text) The file extension to add to the content file. Allows
       better handling of images and can improve syntax highlighting.
 
+      temp_dir -- (path) Path to store local copies of object data being
+      edited. Defaults to operating system temp directory (new in 0.3).
+
     Sections
     
       The sections of the configuration file specify the types of objects and
@@ -167,12 +174,15 @@
       - '[content-type:text/*]' -- Options by major content-type come second.
       
       - '[meta-type:File]' -- Options by Zope meta-type are third.
+
+      - '[domain:www.mydomain.com]' -- Options by domain follow. Several
+        sections can be added for each domain level if desired (new in 0.3).
       
       - '[general]' -- General options are last.
       
       This scheme allows you to specify an extension by content-type, the
-      editor by meta-type and the remaining options under general for a given
-      object. This is how images are handled, for example.
+      editor by meta-type, the locking setting by domain and the remaining 
+      options under general for a given object.
       
   Integrating with External Editor
   


=== Products/ExternalEditor/zopeedit.py 1.18 => 1.19 ===
             domains.append('domain:%s' % '.'.join(host_domain[i:]))
         domains.reverse()
+
+        sections = ['general']
+        sections.extend(domains)
+        sections.append('meta-type:%s' % meta_type)
+        sections.append('content-type:%s' % general_type)
+        sections.append('content-type:%s' % content_type)
         
-        sections = ('general', 
-                    'meta-type:%s' % meta_type,
-                    'content-type:%s' % general_type,
-                    'content-type:%s' % content_type,
-                   ) + tuple(domains)
-                   
         for section in sections:
             if self.config.has_section(section):
                 for option in self.config.options(section):
@@ -182,20 +182,23 @@
         editor = self.options.get('editor')
         
         if win32 and editor is None:
-            from win32api import FindExecutable, RegOpenKeyEx, RegQueryValueEx
+            from _winreg import HKEY_CLASSES_ROOT, OpenKeyEx, \
+                                QueryValueEx, EnumKey
+            from win32api import FindExecutable, RegOpenKeyEx, \
+                                 RegQueryValueEx, RegEnumKey
             from win32con import HKEY_CLASSES_ROOT
+            import pywintypes
             # Find editor application based on mime type and extension
             content_type = self.metadata.get('content_type')
-            extension = self.metadata.get('extension')
+            extension = self.options.get('extension')
             
             if content_type:
                 # Search registry for the extension by MIME type
                 try:
-                    reg_key = 'MIME\\Database\\Content Type\\%s' % content_type
-                    hk = RegOpenKeyEx(HKEY_CLASSES_ROOT, reg_key)
-                    extension, nil = RegQueryValueEx(hk, 'Extension')
-                except:
-                    traceback.print_exc(file=sys.stderr)
+                    key = 'MIME\\Database\\Content Type\\%s' % content_type
+                    key = OpenKeyEx(HKEY_CLASSES_ROOT, key)
+                    extension, nil = QueryValueEx(key, 'Extension')
+                except EnvironmentError:
                     pass
             
             if extension is None:
@@ -206,34 +209,59 @@
                     extension = url[dot:]
 
             if extension is not None:
-                hk = RegOpenKeyEx(HKEY_CLASSES_ROOT, extension)
-                classname, nil = RegQueryValueEx(hk, None)
                 try:
-                    hk = RegOpenKeyEx(HKEY_CLASSES_ROOT, 
-                                      classname+'\\Shell\\Edit\\Command')
-                    editor, nil = RegQueryValueEx(hk, None)
-                except:
-                    traceback.print_exc(file=sys.stderr)
-                    pass
+                    key = OpenKeyEx(HKEY_CLASSES_ROOT, extension)
+                    classname, nil = QueryValueEx(key, None)
+                except EnvironmentError:
+                    classname = None
+
+                if classname is not None:
+                    try:
+                        # Look for Edit action in registry
+                        key = OpenKeyEx(HKEY_CLASSES_ROOT, 
+                                        classname+'\\Shell\\Edit\\Command')
+                        editor, nil = QueryValueEx(key, None)
+                    except EnvironmentError:
+                        pass
+
+                if classname is not None and editor is None:
+                    # Enumerate the actions looking for one
+                    # starting with 'Edit'
+                    try:
+                        key = OpenKeyEx(HKEY_CLASSES_ROOT, classname+'\\Shell')
+                        index = 0
+                        while 1:
+                            try:
+                                subkey = EnumKey(key, index)
+                                index += 1
+                                if str(subkey).lower().startswith('edit'):
+                                    subkey = OpenKeyEx(key, 
+                                                       subkey + '\\Command')
+                                    editor, nil = QueryValueEx(subkey, None)
+                                else:
+                                    continue
+                            except EnvironmentError:
+                                break
+                    except EnvironmentError:
+                        pass
 
                 if editor is None:
                     try:
-                        hk = RegOpenKeyEx(HKEY_CLASSES_ROOT, 
-                                          classname+'\\Shell\\Open\\Command')
-                        editor, nil = RegQueryValueEx(hk, None)
-                    except: 
-                        traceback.print_exc(file=sys.stderr)
+                        # Look for Open action in registry
+                        key = OpenKeyEx(HKEY_CLASSES_ROOT, 
+                                        classname+'\\Shell\\Open\\Command')
+                        editor, nil = QueryValueEx(key, None)
+                    except EnvironmentError:
                         pass
 
                 if editor is None:
                     try:
                         nil, editor = FindExecutable(self.body_file, '')
-                    except:
-                        traceback.print_exc(file=sys.stderr)
+                    except pywintypes.error:
                         pass
             
             # Don't use IE as an "editor"
-            if editor.find('\\iexplore.exe') != -1:
+            if editor is not None and editor.find('\\iexplore.exe') != -1:
                 editor = None
 
         if not editor and not win32 and has_tk():
@@ -248,7 +276,8 @@
         if editor is not None:            
             return editor
         else:
-            fatalError('Editor not specified in configuration file.')
+            fatalError('No editor was found for that object.\n'
+                       'Specify an editor in the configuration file.')
         
     def launch(self):
         """Launch external editor"""
@@ -256,7 +285,11 @@
         use_locks = int(self.options.get('use_locks', 0))
         launch_success = 0
         last_mtime = path.getmtime(self.content_file)
-        command = '%s %s' % (self.getEditorCommand(), self.content_file)
+        command = self.getEditorCommand()
+        if command.find('%1') > -1:
+            command = command.replace('%1', self.content_file)
+        else:
+            command = '%s %s' % (command, self.content_file)
         editor = EditorProcess(command)
         
         if use_locks:
@@ -280,7 +313,8 @@
         if not launch_success:
             fatalError('Editor did not launch properly.\n'
                        'External editor lost connection '
-                       'to editor process.')
+                       'to editor process.\n'
+                       '(%s)' % command)
         
         if use_locks:
             self.unlock()
@@ -456,25 +490,32 @@
     from win32ui import MessageBox
     from win32process import CreateProcess, GetExitCodeProcess, STARTUPINFO
     from win32event import WaitForSingleObject
+    from win32con import MB_OK, MB_OKCANCEL, MB_YESNO, MB_RETRYCANCEL, \
+                         MB_SYSTEMMODAL, MB_ICONERROR, MB_ICONQUESTION, \
+                         MB_ICONEXCLAMATION
     import pywintypes
 
     def errorDialog(message):
-        MessageBox(message, title, 16)
+        MessageBox(message, title, MB_OK + MB_ICONERROR + MB_SYSTEMMODAL)
         sys.stderr.write(message + '\n')
 
     def askRetryCancel(message):
-        return MessageBox(message, title, 53) == 4
+        return MessageBox(message, title, 
+                          MB_OK + MB_RETRYCANCEL + MB_ICONEXCLAMATION 
+                          + MB_SYSTEMMODAL) == 4
 
     def askYesNo(message):
-        return MessageBox(message, title, 52) == 6
+        return MessageBox(message, title, 
+                          MB_OK + MB_YESNO + MB_ICONQUESTION +
+                          MB_SYSTEMMODAL) == 6
 
     class EditorProcess:
         def __init__(self, command):
             """Launch editor process"""
             try:
-                self.handle, ht, pid, tid = CreateProcess(None, command, None, 
-                                                          None, 1, 0, None, 
-                                                          None, STARTUPINFO())
+                self.handle, nil, nil, nil = CreateProcess(None, command, None, 
+                                                           None, 1, 0, None, 
+                                                           None, STARTUPINFO())
             except pywintypes.error, e:
                 fatalError('Error launching editor process\n'
                            '(%s):\n%s' % (command, e[2]))

=== Removed File Products/ExternalEditor/INSTALL.txt ===