Snapshot: refactored the SnapWidget to prepare for implementing snapshot functionality for Wayland desktops. screenshot

Mon, 01 Apr 2019 19:34:58 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 01 Apr 2019 19:34:58 +0200
branch
screenshot
changeset 6914
5ae038f273c4
parent 6912
03264dcdd83e
child 6915
57f7afc788e4

Snapshot: refactored the SnapWidget to prepare for implementing snapshot functionality for Wayland desktops.

Globals/__init__.py file | annotate | diff | comparison | revisions
Snapshot/SnapWidget.py file | annotate | diff | comparison | revisions
Snapshot/SnapshotDefaultGrabber.py file | annotate | diff | comparison | revisions
Snapshot/SnapshotFreehandGrabber.py file | annotate | diff | comparison | revisions
Snapshot/SnapshotModes.py file | annotate | diff | comparison | revisions
Snapshot/SnapshotTimer.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- a/Globals/__init__.py	Sun Mar 31 16:22:00 2019 +0200
+++ b/Globals/__init__.py	Mon Apr 01 19:34:58 2019 +0200
@@ -92,9 +92,100 @@
     if currDesktop:
         return currDesktop
     
+    currDesktop = os.environ.get("GNOME_DESKTOP_SESSION_ID", "")
+    if currDesktop:
+        return currDesktop
+    
+    currDesktop = os.environ.get("KDE_FULL_SESSION", "")
+    if currDesktop:
+        return currDesktop
+    
+    currDesktop = os.environ.get("DESKTOP_SESSION", "")
+    if currDesktop:
+        return currDesktop
+    
     return ""
 
 
+def isKdeDesktop():
+    """
+    Function to check, if the current session is a KDE desktop (Linux only).
+    
+    @return flag indicating a KDE desktop
+    @rtype bool
+    """
+    if not isLinuxPlatform():
+        return False
+    
+    isKDE = False
+    
+    desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").lower() or \
+        os.environ.get("XDG_SESSION_DESKTOP", "").lower() or \
+        os.environ.get("DESKTOP_SESSION", "").lower()
+    if desktop:
+        isKDE = "kde" in desktop or "plasma" in desktop
+    else:
+        isKDE = bool(os.environ.get("KDE_FULL_SESSION", ""))
+    
+    return isKDE
+
+
+def isGnomeDesktop():
+    """
+    Function to check, if the current session is a Gnome desktop (Linux only).
+    
+    @return flag indicating a Gnome desktop
+    @rtype bool
+    """
+    if not isLinuxPlatform():
+        return False
+    
+    isGnome = False
+    
+    desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").lower() or \
+        os.environ.get("XDG_SESSION_DESKTOP", "").lower() or \
+        os.environ.get("GDMSESSION", "").lower()
+    if desktop:
+        isGnome = "gnome" in desktop
+    else:
+        isGnome = bool(os.environ.get("GNOME_DESKTOP_SESSION_ID", ""))
+    
+    return isGnome
+
+
+def sessionType():
+    """
+    Function to determine the name of the running session (Linux only).
+    
+    @return name of the desktop environment
+    @rtype str
+    """
+    if not isLinuxPlatform():
+        return ""
+    
+    sessionType = os.environ.get("XDG_SESSION_TYPE").lower()
+    if "x11" in sessionType:
+        return "X11"
+    elif "wayland" in sessionType:
+        return "Wayland"
+    
+    sessionType = os.environ.get("WAYLAND_DISPLAY", "").lower()
+    if "wayland" in sessionType:
+        return "Wayland"
+    
+    return ""
+
+
+def isWaylandSession():
+    """
+    Function to check, if the current session is a wayland session.
+    
+    @return flag indicating a wayland session
+    @rtype bool
+    """
+    return sessionType() == "Wayland"
+
+
 def checkBlacklistedVersions():
     """
     Module functions to check for blacklisted versions of the prerequisites.
--- a/Snapshot/SnapWidget.py	Sun Mar 31 16:22:00 2019 +0200
+++ b/Snapshot/SnapWidget.py	Mon Apr 01 19:34:58 2019 +0200
@@ -29,6 +29,8 @@
 import Globals
 from Globals import qVersionTuple
 
+from .SnapshotModes import SnapshotModes
+
 
 class SnapWidget(QWidget, Ui_SnapWidget):
     """
@@ -55,24 +57,37 @@
         self.copyPreviewButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
         self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png"))
         
-        self.modeCombo.addItem(self.tr("Fullscreen"),
-                               SnapWidget.ModeFullscreen)
-        if qVersionTuple() >= (5, 10, 0):
-            if len(QApplication.screens()) > 1:
-                self.modeCombo.addItem(self.tr("Current Screen"),
-                                       SnapWidget.ModeScreen)
-        else:
-            if QApplication.desktop().screenCount() > 1:
-                self.modeCombo.addItem(self.tr("Current Screen"),
-                                       SnapWidget.ModeScreen)
-        self.modeCombo.addItem(self.tr("Rectangular Selection"),
-                               SnapWidget.ModeRectangle)
-        self.modeCombo.addItem(self.tr("Elliptical Selection"),
-                               SnapWidget.ModeEllipse)
-        self.modeCombo.addItem(self.tr("Freehand Selection"),
-                               SnapWidget.ModeFreehand)
-        self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0))
-        index = self.modeCombo.findData(self.__mode)
+        from .SnapshotDefaultGrabber import SnapshotDefaultGrabber
+        self.__grabber = SnapshotDefaultGrabber(self)
+        self.__grabber.grabbed.connect(self.__captured)
+        supportedModes = self.__grabber.supportedModes()
+        
+        if SnapshotModes.Fullscreen in supportedModes:
+            self.modeCombo.addItem(self.tr("Fullscreen"),
+                                   SnapshotModes.Fullscreen)
+        if SnapshotModes.SelectedScreen in supportedModes:
+            if qVersionTuple() >= (5, 10, 0):
+                if len(QApplication.screens()) > 1:
+                    self.modeCombo.addItem(self.tr("Selected Screen"),
+                                           SnapshotModes.SelectedScreen)
+            else:
+                if QApplication.desktop().screenCount() > 1:
+                    self.modeCombo.addItem(self.tr("Selected Screen"),
+                                           SnapshotModes.SelectedScreen)
+        if SnapshotModes.SelectedWindow in supportedModes:
+            self.modeCombo.addItem(self.tr("Selected Window"),
+                                   SnapshotModes.SelectedWindow)
+        if SnapshotModes.Rectangle in supportedModes:
+            self.modeCombo.addItem(self.tr("Rectangular Selection"),
+                                   SnapshotModes.Rectangle)
+        if SnapshotModes.Ellipse in supportedModes:
+            self.modeCombo.addItem(self.tr("Elliptical Selection"),
+                                   SnapshotModes.Ellipse)
+        if SnapshotModes.Freehand in supportedModes:
+            self.modeCombo.addItem(self.tr("Freehand Selection"),
+                                   SnapshotModes.Freehand)
+        mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0))
+        index = self.modeCombo.findData(SnapshotModes(mode))
         if index == -1:
             index = 0
         self.modeCombo.setCurrentIndex(index)
@@ -94,25 +109,16 @@
             os.path.join(picturesLocation,
                          self.tr("snapshot") + "1.png"))
         
-        self.__grabber = None
         self.__snapshot = QPixmap()
         self.__savedPosition = QPoint()
         self.__modified = False
         self.__locale = QLocale()
         
-        self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint)
-        self.__grabberWidget.move(-10000, -10000)
-        self.__grabberWidget.installEventFilter(self)
-        
         self.__initFileFilters()
-        
         self.__initShortcuts()
         
         self.preview.startDrag.connect(self.__dragSnapshot)
         
-        from .SnapshotTimer import SnapshotTimer
-        self.__grabTimer = SnapshotTimer()
-        self.__grabTimer.timeout.connect(self.__grabTimerTimeout)
         self.__updateTimer = QTimer()
         self.__updateTimer.setSingleShot(True)
         self.__updateTimer.timeout.connect(self.__updatePreview)
@@ -301,113 +307,14 @@
         """
         Private slot to take a snapshot.
         """
-        self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex())
         self.__delay = self.delaySpin.value()
         
         self.__savedPosition = self.pos()
         self.hide()
         
-        if self.__delay:
-            self.__grabTimer.start(self.__delay)
-        else:
-            QTimer.singleShot(200, self.__startUndelayedGrab)
-    
-    def __grabTimerTimeout(self):
-        """
-        Private slot to perform a delayed grab operation.
-        """
-        if self.__mode == SnapWidget.ModeRectangle:
-            self.__grabRectangle()
-        elif self.__mode == SnapWidget.ModeEllipse:
-            self.__grabEllipse()
-        elif self.__mode == SnapWidget.ModeFreehand:
-            self.__grabFreehand()
-        else:
-            self.__performGrab()
-    
-    def __startUndelayedGrab(self):
-        """
-        Private slot to perform an undelayed grab operation.
-        """
-        if self.__mode == SnapWidget.ModeRectangle:
-            self.__grabRectangle()
-        elif self.__mode == SnapWidget.ModeEllipse:
-            self.__grabEllipse()
-        elif self.__mode == SnapWidget.ModeFreehand:
-            self.__grabFreehand()
-        else:
-            if Globals.isMacPlatform():
-                self.__performGrab()
-            else:
-                self.__grabberWidget.show()
-                self.__grabberWidget.grabMouse(Qt.CrossCursor)
-    
-    def __grabRectangle(self):
-        """
-        Private method to grab a rectangular screen region.
-        """
-        from .SnapshotRegionGrabber import SnapshotRegionGrabber
-        self.__grabber = SnapshotRegionGrabber(
-            mode=SnapshotRegionGrabber.Rectangle)
-        self.__grabber.grabbed.connect(self.__captured)
-    
-    def __grabEllipse(self):
-        """
-        Private method to grab an elliptical screen region.
-        """
-        from .SnapshotRegionGrabber import SnapshotRegionGrabber
-        self.__grabber = SnapshotRegionGrabber(
-            mode=SnapshotRegionGrabber.Ellipse)
-        self.__grabber.grabbed.connect(self.__captured)
-    
-    def __grabFreehand(self):
-        """
-        Private method to grab a non-rectangular screen region.
-        """
-        from .SnapshotFreehandGrabber import SnapshotFreehandGrabber
-        self.__grabber = SnapshotFreehandGrabber()
-        self.__grabber.grabbed.connect(self.__captured)
-    
-    def __performGrab(self):
-        """
-        Private method to perform a screen grab other than a selected region.
-        """
-        self.__grabberWidget.releaseMouse()
-        self.__grabberWidget.hide()
-        self.__grabTimer.stop()
-        
-        if self.__mode == SnapWidget.ModeFullscreen:
-            desktop = QApplication.desktop()
-            if qVersionTuple() >= (5, 0, 0):
-                self.__snapshot = QApplication.screens()[0].grabWindow(
-                    desktop.winId(), desktop.x(), desktop.y(),
-                    desktop.width(), desktop.height())
-            else:
-                self.__snapshot = QPixmap.grabWindow(
-                    desktop.winId(), desktop.x(), desktop.y(),
-                    desktop.width(), desktop.height())
-        elif self.__mode == SnapWidget.ModeScreen:
-            if qVersionTuple() >= (5, 10, 0):
-                screen = QApplication.screenAt(QCursor.pos())
-                geom = screen.geometry()
-            else:
-                desktop = QApplication.desktop()
-                screenId = desktop.screenNumber(QCursor.pos())
-                geom = desktop.screenGeometry(screenId)
-            x = geom.x()
-            y = geom.y()
-            if qVersionTuple() >= (5, 0, 0):
-                self.__snapshot = QApplication.screens()[0].grabWindow(
-                    desktop.winId(), x, y, geom.width(), geom.height())
-            else:
-                self.__snapshot = QPixmap.grabWindow(
-                    desktop.winId(), x, y, geom.width(), geom.height())
-        else:
-            self.__snapshot = QPixmap()
-        
-        self.__redisplay()
-        self.__modified = True
-        self.__updateCaption()
+        self.__grabber.grab(
+            self.modeCombo.itemData(self.modeCombo.currentIndex()),
+            self.delaySpin.value())
     
     def __redisplay(self):
         """
@@ -445,12 +352,8 @@
         
         @param pixmap pixmap of the snapshot (QPixmap)
         """
-        self.__grabber.close()
         self.__snapshot = QPixmap(pixmap)
         
-        self.__grabber.grabbed.disconnect(self.__captured)
-        self.__grabber = None
-        
         self.__redisplay()
         self.__modified = True
         self.__updateCaption()
@@ -486,23 +389,6 @@
         drag.setPixmap(self.preview.pixmap())
         drag.exec_(Qt.CopyAction)
     
-    def eventFilter(self, obj, evt):
-        """
-        Public method to handle event for other objects.
-        
-        @param obj reference to the object (QObject)
-        @param evt reference to the event (QEvent)
-        @return flag indicating that the event should be filtered out (boolean)
-        """
-        if obj == self.__grabberWidget and \
-                evt.type() == QEvent.MouseButtonPress:
-            if QWidget.mouseGrabber() != self.__grabberWidget:
-                return False
-            if evt.button() == Qt.LeftButton:
-                self.__performGrab()
-        
-        return False
-    
     def closeEvent(self, evt):
         """
         Protected method handling the close event.
@@ -529,7 +415,7 @@
             "Snapshot/Delay", self.delaySpin.value())
         Preferences.Prefs.settings.setValue(
             "Snapshot/Mode",
-            self.modeCombo.itemData(self.modeCombo.currentIndex()))
+            self.modeCombo.itemData(self.modeCombo.currentIndex()).value)
         Preferences.Prefs.settings.setValue(
             "Snapshot/Filename", self.__filename)
         Preferences.Prefs.settings.sync()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Snapshot/SnapshotDefaultGrabber.py	Mon Apr 01 19:34:58 2019 +0200
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a grabber object for non-Wayland desktops.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSignal, Qt, QObject, QTimer, QEvent
+from PyQt5.QtGui import QPixmap, QCursor
+from PyQt5.QtWidgets import QWidget, QApplication
+
+from .SnapshotModes import SnapshotModes
+
+import Globals
+
+
+class SnapshotDefaultGrabber(QObject):
+    """
+    Class implementing a grabber object for non-Wayland desktops.
+    
+    @signal grabbed(QPixmap) emitted after the grab operation is finished
+    """
+    grabbed = pyqtSignal(QPixmap)
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(SnapshotDefaultGrabber, self).__init__(parent)
+        
+        self.__grabber = None
+        self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint)
+        self.__grabberWidget.move(-10000, -10000)
+        self.__grabberWidget.installEventFilter(self)
+        
+        from .SnapshotTimer import SnapshotTimer
+        self.__grabTimer = SnapshotTimer()
+        self.__grabTimer.timeout.connect(self.__grabTimerTimeout)
+    
+    def supportedModes(self):
+        """
+        Public method to get the supported screenshot modes.
+        
+        @return tuple of supported screenshot modes
+        @rtype tuple of SnapshotModes
+        """
+        return (
+            SnapshotModes.Fullscreen,
+            SnapshotModes.SelectedScreen,
+            SnapshotModes.Rectangle,
+            SnapshotModes.Freehand,
+            SnapshotModes.Ellipse,
+        )
+    
+    def grab(self, mode, delay=0):
+        """
+        Public method to perform a grab operation potentially after a delay.
+        
+        @param mode screenshot mode
+        @type ScreenshotModes
+        @param delay delay in seconds
+        @type int
+        """
+        self.__mode = mode
+        if delay:
+            self.__grabTimer.start(delay)
+        else:
+            QTimer.singleShot(200, self.__startUndelayedGrab)
+    
+    def __grabTimerTimeout(self):
+        """
+        Private slot to perform a delayed grab operation.
+        """
+        if self.__mode == SnapshotModes.Rectangle:
+            self.__grabRectangle()
+        elif self.__mode == SnapshotModes.Ellipse:
+            self.__grabEllipse()
+        elif self.__mode == SnapshotModes.Freehand:
+            self.__grabFreehand()
+        else:
+            self.__performGrab(self.__mode)
+    
+    def __startUndelayedGrab(self):
+        """
+        Private slot to perform an undelayed grab operation.
+        """
+        if self.__mode == SnapshotModes.Rectangle:
+            self.__grabRectangle()
+        elif self.__mode == SnapshotModes.Ellipse:
+            self.__grabEllipse()
+        elif self.__mode == SnapshotModes.Freehand:
+            self.__grabFreehand()
+        else:
+            if Globals.isMacPlatform():
+                self.__performGrab(self.__mode)
+            else:
+                self.__grabberWidget.show()
+                self.__grabberWidget.grabMouse(Qt.CrossCursor)
+    
+    def __grabRectangle(self):
+        """
+        Private method to grab a rectangular screen region.
+        """
+        from .SnapshotRegionGrabber import SnapshotRegionGrabber
+        self.__grabber = SnapshotRegionGrabber(
+            mode=SnapshotRegionGrabber.Rectangle)
+        self.__grabber.grabbed.connect(self.__captured)
+    
+    def __grabEllipse(self):
+        """
+        Private method to grab an elliptical screen region.
+        """
+        from .SnapshotRegionGrabber import SnapshotRegionGrabber
+        self.__grabber = SnapshotRegionGrabber(
+            mode=SnapshotRegionGrabber.Ellipse)
+        self.__grabber.grabbed.connect(self.__captured)
+    
+    def __grabFreehand(self):
+        """
+        Private method to grab a non-rectangular screen region.
+        """
+        from .SnapshotFreehandGrabber import SnapshotFreehandGrabber
+        self.__grabber = SnapshotFreehandGrabber()
+        self.__grabber.grabbed.connect(self.__captured)
+    
+    def __performGrab(self, mode):
+        """
+        Private method to perform a screen grab other than a selected region.
+        
+        @param mode screenshot mode
+        @type SnapshotModes
+        """
+        self.__grabberWidget.releaseMouse()
+        self.__grabberWidget.hide()
+        self.__grabTimer.stop()
+        
+        if mode == SnapshotModes.Fullscreen:
+            desktop = QApplication.desktop()
+            if Globals.qVersionTuple() >= (5, 0, 0):
+                snapshot = QApplication.screens()[0].grabWindow(
+                    desktop.winId(), desktop.x(), desktop.y(),
+                    desktop.width(), desktop.height())
+            else:
+                snapshot = QPixmap.grabWindow(
+                    desktop.winId(), desktop.x(), desktop.y(),
+                    desktop.width(), desktop.height())
+        elif mode == SnapshotModes.SelectedScreen:
+            desktop = QApplication.desktop()
+            if Globals.qVersionTuple() >= (5, 10, 0):
+                screen = QApplication.screenAt(QCursor.pos())
+                geom = screen.geometry()
+            else:
+                screenId = desktop.screenNumber(QCursor.pos())
+                geom = desktop.screenGeometry(screenId)
+            x = geom.x()
+            y = geom.y()
+            if Globals.qVersionTuple() >= (5, 0, 0):
+                snapshot = QApplication.screens()[0].grabWindow(
+                    desktop.winId(), x, y, geom.width(), geom.height())
+            else:
+                snapshot = QPixmap.grabWindow(
+                    desktop.winId(), x, y, geom.width(), geom.height())
+        else:
+            snapshot = QPixmap()
+        
+        self.grabbed.emit(snapshot)
+    
+    def __captured(self, pixmap):
+        """
+        Private slot to show a preview of the snapshot.
+        
+        @param pixmap pixmap of the snapshot (QPixmap)
+        """
+        self.__grabber.close()
+        snapshot = QPixmap(pixmap)
+        
+        self.__grabber.grabbed.disconnect(self.__captured)
+        self.__grabber = None
+        
+        self.grabbed.emit(snapshot)
+    
+    def eventFilter(self, obj, evt):
+        """
+        Public method to handle event for other objects.
+        
+        @param obj reference to the object (QObject)
+        @param evt reference to the event (QEvent)
+        @return flag indicating that the event should be filtered out (boolean)
+        """
+        if obj == self.__grabberWidget and \
+                evt.type() == QEvent.MouseButtonPress:
+            if QWidget.mouseGrabber() != self.__grabberWidget:
+                return False
+            if evt.button() == Qt.LeftButton:
+                self.__performGrab(self.__mode)
+        
+        return False
--- a/Snapshot/SnapshotFreehandGrabber.py	Sun Mar 31 16:22:00 2019 +0200
+++ b/Snapshot/SnapshotFreehandGrabber.py	Mon Apr 01 19:34:58 2019 +0200
@@ -34,7 +34,7 @@
     painter.setClipRegion(clip)
     painter.setPen(pen)
     painter.drawPolygon(QPolygon(polygon))
-    if fill.isValid():
+    if fill and fill.isValid():
         painter.setClipping(False)
         painter.setBrush(fill or QColor())
         painter.drawPolygon(QPolygon(polygon))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Snapshot/SnapshotModes.py	Mon Apr 01 19:34:58 2019 +0200
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the snapshot mode enumeration.
+"""
+
+from __future__ import unicode_literals
+
+try:
+    from enum import Enum
+except ImportError:
+    from ThirdParty.enum import Enum
+
+
+class SnapshotModes(Enum):
+    """
+    Class implementing the snapshot modes.
+    """
+    Fullscreen = 0
+    SelectedScreen = 1
+    Rectangle = 2
+    Freehand = 3
+    Ellipse = 4
+    SelectedWindow = 5
--- a/Snapshot/SnapshotTimer.py	Sun Mar 31 16:22:00 2019 +0200
+++ b/Snapshot/SnapshotTimer.py	Mon Apr 01 19:34:58 2019 +0200
@@ -15,6 +15,7 @@
 
 from Globals import qVersionTuple
 
+
 class SnapshotTimer(QWidget):
     """
     Class implementing the snapshot timer widget.
--- a/eric6.e4p	Sun Mar 31 16:22:00 2019 +0200
+++ b/eric6.e4p	Mon Apr 01 19:34:58 2019 +0200
@@ -1037,7 +1037,9 @@
     <Source>QScintilla/ZoomDialog.py</Source>
     <Source>QScintilla/__init__.py</Source>
     <Source>Snapshot/SnapWidget.py</Source>
+    <Source>Snapshot/SnapshotDefaultGrabber.py</Source>
     <Source>Snapshot/SnapshotFreehandGrabber.py</Source>
+    <Source>Snapshot/SnapshotModes.py</Source>
     <Source>Snapshot/SnapshotPreview.py</Source>
     <Source>Snapshot/SnapshotRegionGrabber.py</Source>
     <Source>Snapshot/SnapshotTimer.py</Source>
@@ -2259,14 +2261,14 @@
   </Resources>
   <Others>
     <Other>.hgignore</Other>
-    <Other>APIs/Python/zope-2.10.7.api</Other>
-    <Other>APIs/Python/zope-2.11.2.api</Other>
-    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/Python3/PyQt4.bas</Other>
     <Other>APIs/Python3/PyQt5.bas</Other>
     <Other>APIs/Python3/QScintilla2.bas</Other>
     <Other>APIs/Python3/eric6.api</Other>
     <Other>APIs/Python3/eric6.bas</Other>
+    <Other>APIs/Python/zope-2.10.7.api</Other>
+    <Other>APIs/Python/zope-2.11.2.api</Other>
+    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/QSS/qss.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>

eric ide

mercurial