Snapshot/SnapWidget.py

changeset 1772
f325dfdc8f6b
parent 1770
c17e67e69ef5
child 1776
6198675d24ea
child 1778
31e70a6f8e7f
--- a/Snapshot/SnapWidget.py	Sun Apr 08 12:45:44 2012 +0200
+++ b/Snapshot/SnapWidget.py	Mon Apr 09 17:02:31 2012 +0200
@@ -7,14 +7,24 @@
 Module implementing the snapshot widget.
 """
 
-from PyQt4.QtCore import pyqtSlot, QFile, QFileInfo
-from PyQt4.QtGui import QWidget, QImageWriter, QApplication
+#
+# SnapWidget and it's associated modules are PyQt4 ports of Ksnapshot.
+#
+
+import os
+
+from PyQt4.QtCore import pyqtSlot, QFile, QFileInfo, QTimer, QPoint, QMimeData, Qt, \
+    QEvent, QRegExp
+from PyQt4.QtGui import QWidget, QImageWriter, QApplication, QPixmap, QCursor, QDrag, \
+    QShortcut, QKeySequence, QDesktopServices
 
 from E5Gui import E5FileDialog, E5MessageBox
 
 from .Ui_SnapWidget import Ui_SnapWidget
 
 from .SnapshotRegionGrabber import SnapshotRegionGrabber
+from .SnapshotFreehandGrabber import SnapshotFreehandGrabber
+from .SnapshotTimer import SnapshotTimer
 
 import UI.PixmapCache
 import Preferences
@@ -24,6 +34,12 @@
     """
     Class implementing the snapshot widget.
     """
+    ModeFullscreen = 0
+    ModeScreen = 1
+    ModeRectangle = 2
+    ModeFreehand = 3
+    ModeEllipse = 4
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -39,19 +55,53 @@
         self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png"))
         
         self.modeCombo.addItem(self.trUtf8("Fullscreen"),
-                               SnapshotRegionGrabber.ModeFullscreen)
+                               SnapWidget.ModeFullscreen)
         self.modeCombo.addItem(self.trUtf8("Rectangular Selection"),
-                               SnapshotRegionGrabber.ModeRectangle)
-        mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0))
-        index = self.modeCombo.findData(mode)
+                               SnapWidget.ModeRectangle)
+        self.modeCombo.addItem(self.trUtf8("Ellipical Selection"),
+                               SnapWidget.ModeEllipse)
+        self.modeCombo.addItem(self.trUtf8("Freehand Selection"),
+                               SnapWidget.ModeFreehand)
+        if QApplication.desktop().numScreens() > 1:
+            self.modeCombo.addItem(self.trUtf8("Current Screen"),
+                                   SnapWidget.ModeScreen)
+        self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0))
+        index = self.modeCombo.findData(self.__mode)
+        if index == -1:
+            index = 0
         self.modeCombo.setCurrentIndex(index)
         
-        self.delaySpin.setValue(int(Preferences.Prefs.settings.value(
-            "Snapshot/Delay", 0)))
+        self.__delay = int(Preferences.Prefs.settings.value( "Snapshot/Delay", 0))
+        self.delaySpin.setValue(self.__delay)
+        
+        self.__filename = Preferences.Prefs.settings.value( "Snapshot/Filename",
+            os.path.join(
+                QDesktopServices.storageLocation(QDesktopServices.PicturesLocation),
+                self.trUtf8("snapshot") + "1.png"))
         
         self.__grabber = None
+        self.__snapshot = QPixmap()
+        self.__savedPosition = QPoint()
+        self.__modified = False
+        
+        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)
+        
+        self.__grabTimer = SnapshotTimer()
+        self.__grabTimer.timeout.connect(self.__grabTimerTimeout)
+        self.__updateTimer = QTimer()
+        self.__updateTimer.setSingleShot(True)
+        self.__updateTimer.timeout.connect(self.__updatePreview)
+        
+        self.__updateCaption()
+        self.takeButton.setFocus()
     
     def __initFileFilters(self):
         """
@@ -88,26 +138,81 @@
         
         self.__defaultFilter = filters['png']
     
-    @pyqtSlot(bool)
-    def on_saveButton_clicked(self, checked):
+    def __initShortcuts(self):
+        """
+        Private method to initialize the keyboard shortcuts.
+        """
+        self.__quitShortcut = QShortcut(QKeySequence(QKeySequence.Quit), self, self.close)
+        
+        self.__copyShortcut = QShortcut(QKeySequence(QKeySequence.Copy), self,
+            self.copyButton.animateClick)
+        
+        self.__quickSaveShortcut = QShortcut(QKeySequence(Qt.Key_Q), self,
+            self.__quickSave)
+        
+        self.__save1Shortcut = QShortcut(QKeySequence(QKeySequence.Save), self,
+            self.saveButton.animateClick)
+        self.__save2Shortcut = QShortcut(QKeySequence(Qt.Key_S), self,
+            self.saveButton.animateClick)
+        
+        self.__grab1Shortcut = QShortcut(QKeySequence(QKeySequence.New), self,
+            self.takeButton.animateClick)
+        self.__grab2Shortcut = QShortcut(QKeySequence(Qt.Key_N), self,
+            self.takeButton.animateClick)
+        self.__grab3Shortcut = QShortcut(QKeySequence(Qt.Key_Space), self,
+            self.takeButton.animateClick)
+    
+    def __quickSave(self):
+        """
+        Private slot to save the snapshot bypassing the file selection dialog.
+        """
+        if not self.__snapshot.isNull():
+            while os.path.exists(self.__filename):
+                self.__autoIncFilename()
+            
+            if self.__saveImage(self.__filename):
+                self.__modified = False
+                self.__autoIncFilename()
+                self.__updateCaption()
+    
+    @pyqtSlot()
+    def on_saveButton_clicked(self):
         """
         Private slot to save the snapshot.
         """
-        fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
-            self,
-            self.trUtf8("Save Snapshot"),
-            "",
-            self.__outputFilter,
-            self.__defaultFilter,
-            E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
-        if not fileName:
-            return
+        if not self.__snapshot.isNull():
+            while os.path.exists(self.__filename):
+                self.__autoIncFilename()
+            
+            fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
+                self,
+                self.trUtf8("Save Snapshot"),
+                self.__filename,
+                self.__outputFilter,
+                self.__defaultFilter,
+                E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
+            if not fileName:
+                return
+            
+            ext = QFileInfo(fileName).suffix()
+            if not ext:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fileName += ex
+            
+            if self.__saveImage(fileName):
+                self.__modified = False
+                self.__filename = fileName
+                self.__autoIncFilename()
+                self.__updateCaption()
+    
+    def __saveImage(self, fileName):
+        """
+        Private method to save the snapshot.
         
-        ext = QFileInfo(fileName).suffix()
-        if not ext:
-            ex = selectedFilter.split("(*")[1].split(")")[0]
-            if ex:
-                fileName += ex
+        @param fileName name of the file to save to (string)
+        @return flag indicating success (boolean)
+        """
         if QFileInfo(fileName).exists():
             res = E5MessageBox.yesNo(self,
                 self.trUtf8("Save Snapshot"),
@@ -115,39 +220,160 @@
                             " Overwrite it?</p>").format(fileName),
                 icon=E5MessageBox.Warning)
             if not res:
-                return
+                return False
         
         file = QFile(fileName)
         if not file.open(QFile.WriteOnly):
             E5MessageBox.warning(self, self.trUtf8("Save Snapshot"),
                                 self.trUtf8("Cannot write file '{0}:\n{1}.")\
                                     .format(fileName, file.errorString()))
-            return
+            return False
         
-        res = self.preview.pixmap().save(file)
+        ok = self.__snapshot.save(file)
         file.close()
         
-        if not res:
+        if not ok:
             E5MessageBox.warning(self, self.trUtf8("Save Snapshot"),
                                 self.trUtf8("Cannot write file '{0}:\n{1}.")\
                                     .format(fileName, file.errorString()))
+        
+        return ok
     
-    @pyqtSlot(bool)
-    def on_takeButton_clicked(self, checked):
+    def __autoIncFilename(self):
+        """
+        Private method to auto-increment the file name.
+        """
+        # Extract the file name
+        name = os.path.basename(self.__filename)
+        
+        # If the name contains a number, then increment it.
+        numSearch = QRegExp("(^|[^\\d])(\\d+)")     # We want to match as far left as
+                                                    # possible, and when the number is
+                                                    # at the start of the name.
+        
+        # Does it have a number?
+        start = numSearch.lastIndexIn(name)
+        if start != -1:
+            # It has a number, increment it.
+            start = numSearch.pos(2)    # Only the second group is of interest.
+            numAsStr = numSearch.capturedTexts()[2]
+            number = "{0:0{width}d}".format(int(numAsStr) + 1, width=len(numAsStr))
+            name = name[:start] + number + name[start+len(numAsStr):]
+        else:
+            # no number
+            start = name.rfind('.')
+            if start != -1:
+                # has a '.' somewhere, e.g. it has an extension
+                name = name [:start] + '1' + name[start:]
+            else:
+                # no extension, just tack it on to the end
+                name += '1'
+        
+        self.__filename = os.path.join(os.path.dirname(self.__filename), name)
+        self.__updateCaption()
+    
+    @pyqtSlot()
+    def on_takeButton_clicked(self):
         """
         Private slot to take a snapshot.
         """
-        mode = self.modeCombo.itemData(self.modeCombo.currentIndex())
-        delay = self.delaySpin.value()
+        self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex())
+        self.__delay = self.delaySpin.value()
         
-        Preferences.Prefs.settings.setValue("Snapshot/Delay", delay)
-        Preferences.Prefs.settings.setValue("Snapshot/Mode", mode)
-        
+        self.__savedPosition = self.pos()
         self.hide()
         
-        self.__grabber = SnapshotRegionGrabber(mode, delay)
+        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:
+            self.__grabberWidget.show()
+            self.__grabberWidget.grabMouse(Qt.CrossCursor)
+    
+    def __grabRectangle(self):
+        """
+        Private method to grab a rectangular screen region.
+        """
+        self.__grabber = SnapshotRegionGrabber(mode=SnapshotRegionGrabber.Rectangle)
+        self.__grabber.grabbed.connect(self.__captured)
+    
+    def __grabEllipse(self):
+        """
+        Private method to grab an elliptical screen region.
+        """
+        self.__grabber = SnapshotRegionGrabber(mode=SnapshotRegionGrabber.Ellipse)
         self.__grabber.grabbed.connect(self.__captured)
     
+    def __grabFreehand(self):
+        """
+        Private method to grab a non-rectangular screen region.
+        """
+        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:
+            self.__snapshot = QPixmap.grabWindow(QApplication.desktop().winId())
+        elif self.__mode == SnapWidget.ModeScreen:
+            desktop = QApplication.desktop()
+            screenId = desktop.screenNumber(QCursor.pos())
+            geom = desktop.screenGeometry(screenId)
+            x = geom.x()
+            y = geom.y()
+            self.__snapshot = QPixmap.grabWindow(
+                desktop.winId(), x, y, geom.width(), geom.height())
+        else:
+            self.__snapshot = QPixmap()
+        
+        self.__redisplay()
+        self.__modified = True
+        self.__updateCaption()
+    
+    def __redisplay(self):
+        """
+        Private method to redisplay the window.
+        """
+        self.__updatePreview()
+        QApplication.restoreOverrideCursor()
+        if not self.__savedPosition.isNull():
+            self.move(self.__savedPosition)
+        self.show()
+        
+        self.saveButton.setEnabled(not self.__snapshot.isNull())
+        self.copyButton.setEnabled(not self.__snapshot.isNull())
+    
     @pyqtSlot()
     def on_copyButton_clicked(self):
         """
@@ -162,10 +388,92 @@
         @param pixmap pixmap of the snapshot (QPixmap)
         """
         self.__grabber.close()
-        self.preview.setPixmap(pixmap)
+        self.__snapshot = QPixmap(pixmap)
         
+        self.__grabber.grabbed.disconnect(self.__captured)
         self.__grabber = None
         
-        self.saveButton.setEnabled(True)
-        self.copyButton.setEnabled(True)
-        self.show()
+        self.__redisplay()
+        self.__modified = True
+        self.__updateCaption()
+    
+    def __updatePreview(self):
+        """
+        Private slot to update the preview picture.
+        """
+        self.preview.setToolTip(
+            self.trUtf8("Preview of the snapshot image ({0:n} x {1:n})").format(
+            self.__snapshot.width(), self.__snapshot.height()))
+        self.preview.setPreview(self.__snapshot)
+        self.preview.adjustSize()
+    
+    def resizeEvent(self, evt):
+        """
+        Protected method handling a resizing of the window.
+        
+        @param evt resize event (QResizeEvent)
+        """
+        self.__updateTimer.start(200)
+    
+    def __dragSnapshot(self):
+        """
+        Private slot handling the dragging of the preview picture.
+        """
+        drag = QDrag(self)
+        mimeData = QMimeData()
+        mimeData.setImageData(self.__snapshot)
+        drag.setMimeData(mimeData)
+        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.
+        
+        @param evt close event (QCloseEvent)
+        """
+        if self.__modified:
+            res = E5MessageBox.question(self,
+                self.trUtf8("eric5 Snapshot"),
+                self.trUtf8("""The application contains an unsaved snapshot."""),
+                E5MessageBox.StandardButtons(
+                    E5MessageBox.Abort | \
+                    E5MessageBox.Discard | \
+                    E5MessageBox.Save))
+            if res == E5MessageBox.Abort:
+                evt.ignore()
+                return
+            elif res == E5MessageBox.Save:
+                self.on_saveButton_clicked()
+        
+        Preferences.Prefs.settings.setValue("Snapshot/Delay", self.delaySpin.value())
+        Preferences.Prefs.settings.setValue("Snapshot/Mode",
+            self.modeCombo.itemData(self.modeCombo.currentIndex()))
+        Preferences.Prefs.settings.setValue("Snapshot/Filename", self.__filename)
+        Preferences.Prefs.settings.sync()
+    
+    def __updateCaption(self):
+        """
+        Private method to update the window caption.
+        """
+        self.setWindowTitle("{0}[*] - {1}".format(
+            os.path.basename(self.__filename),
+            self.trUtf8("eric5 Snapshot")))
+        self.setWindowModified(self.__modified)
+        self.pathNameEdit.setText(os.path.dirname(self.__filename))

eric ide

mercurial