Snapshot/SnapWidget.py

changeset 1772
f325dfdc8f6b
parent 1770
c17e67e69ef5
child 1776
6198675d24ea
child 1778
31e70a6f8e7f
equal deleted inserted replaced
1771:561e97413395 1772:f325dfdc8f6b
5 5
6 """ 6 """
7 Module implementing the snapshot widget. 7 Module implementing the snapshot widget.
8 """ 8 """
9 9
10 from PyQt4.QtCore import pyqtSlot, QFile, QFileInfo 10 #
11 from PyQt4.QtGui import QWidget, QImageWriter, QApplication 11 # SnapWidget and it's associated modules are PyQt4 ports of Ksnapshot.
12 #
13
14 import os
15
16 from PyQt4.QtCore import pyqtSlot, QFile, QFileInfo, QTimer, QPoint, QMimeData, Qt, \
17 QEvent, QRegExp
18 from PyQt4.QtGui import QWidget, QImageWriter, QApplication, QPixmap, QCursor, QDrag, \
19 QShortcut, QKeySequence, QDesktopServices
12 20
13 from E5Gui import E5FileDialog, E5MessageBox 21 from E5Gui import E5FileDialog, E5MessageBox
14 22
15 from .Ui_SnapWidget import Ui_SnapWidget 23 from .Ui_SnapWidget import Ui_SnapWidget
16 24
17 from .SnapshotRegionGrabber import SnapshotRegionGrabber 25 from .SnapshotRegionGrabber import SnapshotRegionGrabber
26 from .SnapshotFreehandGrabber import SnapshotFreehandGrabber
27 from .SnapshotTimer import SnapshotTimer
18 28
19 import UI.PixmapCache 29 import UI.PixmapCache
20 import Preferences 30 import Preferences
21 31
22 32
23 class SnapWidget(QWidget, Ui_SnapWidget): 33 class SnapWidget(QWidget, Ui_SnapWidget):
24 """ 34 """
25 Class implementing the snapshot widget. 35 Class implementing the snapshot widget.
26 """ 36 """
37 ModeFullscreen = 0
38 ModeScreen = 1
39 ModeRectangle = 2
40 ModeFreehand = 3
41 ModeEllipse = 4
42
27 def __init__(self, parent=None): 43 def __init__(self, parent=None):
28 """ 44 """
29 Constructor 45 Constructor
30 46
31 @param parent reference to the parent widget (QWidget) 47 @param parent reference to the parent widget (QWidget)
37 self.takeButton.setIcon(UI.PixmapCache.getIcon("cameraPhoto.png")) 53 self.takeButton.setIcon(UI.PixmapCache.getIcon("cameraPhoto.png"))
38 self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png")) 54 self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
39 self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png")) 55 self.setWindowIcon(UI.PixmapCache.getIcon("ericSnap.png"))
40 56
41 self.modeCombo.addItem(self.trUtf8("Fullscreen"), 57 self.modeCombo.addItem(self.trUtf8("Fullscreen"),
42 SnapshotRegionGrabber.ModeFullscreen) 58 SnapWidget.ModeFullscreen)
43 self.modeCombo.addItem(self.trUtf8("Rectangular Selection"), 59 self.modeCombo.addItem(self.trUtf8("Rectangular Selection"),
44 SnapshotRegionGrabber.ModeRectangle) 60 SnapWidget.ModeRectangle)
45 mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0)) 61 self.modeCombo.addItem(self.trUtf8("Ellipical Selection"),
46 index = self.modeCombo.findData(mode) 62 SnapWidget.ModeEllipse)
63 self.modeCombo.addItem(self.trUtf8("Freehand Selection"),
64 SnapWidget.ModeFreehand)
65 if QApplication.desktop().numScreens() > 1:
66 self.modeCombo.addItem(self.trUtf8("Current Screen"),
67 SnapWidget.ModeScreen)
68 self.__mode = int(Preferences.Prefs.settings.value("Snapshot/Mode", 0))
69 index = self.modeCombo.findData(self.__mode)
70 if index == -1:
71 index = 0
47 self.modeCombo.setCurrentIndex(index) 72 self.modeCombo.setCurrentIndex(index)
48 73
49 self.delaySpin.setValue(int(Preferences.Prefs.settings.value( 74 self.__delay = int(Preferences.Prefs.settings.value( "Snapshot/Delay", 0))
50 "Snapshot/Delay", 0))) 75 self.delaySpin.setValue(self.__delay)
76
77 self.__filename = Preferences.Prefs.settings.value( "Snapshot/Filename",
78 os.path.join(
79 QDesktopServices.storageLocation(QDesktopServices.PicturesLocation),
80 self.trUtf8("snapshot") + "1.png"))
51 81
52 self.__grabber = None 82 self.__grabber = None
83 self.__snapshot = QPixmap()
84 self.__savedPosition = QPoint()
85 self.__modified = False
86
87 self.__grabberWidget = QWidget(None, Qt.X11BypassWindowManagerHint)
88 self.__grabberWidget.move(-10000, -10000)
89 self.__grabberWidget.installEventFilter(self);
53 90
54 self.__initFileFilters() 91 self.__initFileFilters()
92
93 self.__initShortcuts()
94
95 self.preview.startDrag.connect(self.__dragSnapshot)
96
97 self.__grabTimer = SnapshotTimer()
98 self.__grabTimer.timeout.connect(self.__grabTimerTimeout)
99 self.__updateTimer = QTimer()
100 self.__updateTimer.setSingleShot(True)
101 self.__updateTimer.timeout.connect(self.__updatePreview)
102
103 self.__updateCaption()
104 self.takeButton.setFocus()
55 105
56 def __initFileFilters(self): 106 def __initFileFilters(self):
57 """ 107 """
58 Private method to define the supported image file filters. 108 Private method to define the supported image file filters.
59 """ 109 """
86 outputFormats.sort() 136 outputFormats.sort()
87 self.__outputFilter = ';;'.join(outputFormats) 137 self.__outputFilter = ';;'.join(outputFormats)
88 138
89 self.__defaultFilter = filters['png'] 139 self.__defaultFilter = filters['png']
90 140
91 @pyqtSlot(bool) 141 def __initShortcuts(self):
92 def on_saveButton_clicked(self, checked): 142 """
143 Private method to initialize the keyboard shortcuts.
144 """
145 self.__quitShortcut = QShortcut(QKeySequence(QKeySequence.Quit), self, self.close)
146
147 self.__copyShortcut = QShortcut(QKeySequence(QKeySequence.Copy), self,
148 self.copyButton.animateClick)
149
150 self.__quickSaveShortcut = QShortcut(QKeySequence(Qt.Key_Q), self,
151 self.__quickSave)
152
153 self.__save1Shortcut = QShortcut(QKeySequence(QKeySequence.Save), self,
154 self.saveButton.animateClick)
155 self.__save2Shortcut = QShortcut(QKeySequence(Qt.Key_S), self,
156 self.saveButton.animateClick)
157
158 self.__grab1Shortcut = QShortcut(QKeySequence(QKeySequence.New), self,
159 self.takeButton.animateClick)
160 self.__grab2Shortcut = QShortcut(QKeySequence(Qt.Key_N), self,
161 self.takeButton.animateClick)
162 self.__grab3Shortcut = QShortcut(QKeySequence(Qt.Key_Space), self,
163 self.takeButton.animateClick)
164
165 def __quickSave(self):
166 """
167 Private slot to save the snapshot bypassing the file selection dialog.
168 """
169 if not self.__snapshot.isNull():
170 while os.path.exists(self.__filename):
171 self.__autoIncFilename()
172
173 if self.__saveImage(self.__filename):
174 self.__modified = False
175 self.__autoIncFilename()
176 self.__updateCaption()
177
178 @pyqtSlot()
179 def on_saveButton_clicked(self):
93 """ 180 """
94 Private slot to save the snapshot. 181 Private slot to save the snapshot.
95 """ 182 """
96 fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( 183 if not self.__snapshot.isNull():
97 self, 184 while os.path.exists(self.__filename):
98 self.trUtf8("Save Snapshot"), 185 self.__autoIncFilename()
99 "", 186
100 self.__outputFilter, 187 fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
101 self.__defaultFilter, 188 self,
102 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) 189 self.trUtf8("Save Snapshot"),
103 if not fileName: 190 self.__filename,
104 return 191 self.__outputFilter,
105 192 self.__defaultFilter,
106 ext = QFileInfo(fileName).suffix() 193 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
107 if not ext: 194 if not fileName:
108 ex = selectedFilter.split("(*")[1].split(")")[0] 195 return
109 if ex: 196
110 fileName += ex 197 ext = QFileInfo(fileName).suffix()
198 if not ext:
199 ex = selectedFilter.split("(*")[1].split(")")[0]
200 if ex:
201 fileName += ex
202
203 if self.__saveImage(fileName):
204 self.__modified = False
205 self.__filename = fileName
206 self.__autoIncFilename()
207 self.__updateCaption()
208
209 def __saveImage(self, fileName):
210 """
211 Private method to save the snapshot.
212
213 @param fileName name of the file to save to (string)
214 @return flag indicating success (boolean)
215 """
111 if QFileInfo(fileName).exists(): 216 if QFileInfo(fileName).exists():
112 res = E5MessageBox.yesNo(self, 217 res = E5MessageBox.yesNo(self,
113 self.trUtf8("Save Snapshot"), 218 self.trUtf8("Save Snapshot"),
114 self.trUtf8("<p>The file <b>{0}</b> already exists." 219 self.trUtf8("<p>The file <b>{0}</b> already exists."
115 " Overwrite it?</p>").format(fileName), 220 " Overwrite it?</p>").format(fileName),
116 icon=E5MessageBox.Warning) 221 icon=E5MessageBox.Warning)
117 if not res: 222 if not res:
118 return 223 return False
119 224
120 file = QFile(fileName) 225 file = QFile(fileName)
121 if not file.open(QFile.WriteOnly): 226 if not file.open(QFile.WriteOnly):
122 E5MessageBox.warning(self, self.trUtf8("Save Snapshot"), 227 E5MessageBox.warning(self, self.trUtf8("Save Snapshot"),
123 self.trUtf8("Cannot write file '{0}:\n{1}.")\ 228 self.trUtf8("Cannot write file '{0}:\n{1}.")\
124 .format(fileName, file.errorString())) 229 .format(fileName, file.errorString()))
125 return 230 return False
126 231
127 res = self.preview.pixmap().save(file) 232 ok = self.__snapshot.save(file)
128 file.close() 233 file.close()
129 234
130 if not res: 235 if not ok:
131 E5MessageBox.warning(self, self.trUtf8("Save Snapshot"), 236 E5MessageBox.warning(self, self.trUtf8("Save Snapshot"),
132 self.trUtf8("Cannot write file '{0}:\n{1}.")\ 237 self.trUtf8("Cannot write file '{0}:\n{1}.")\
133 .format(fileName, file.errorString())) 238 .format(fileName, file.errorString()))
134 239
135 @pyqtSlot(bool) 240 return ok
136 def on_takeButton_clicked(self, checked): 241
242 def __autoIncFilename(self):
243 """
244 Private method to auto-increment the file name.
245 """
246 # Extract the file name
247 name = os.path.basename(self.__filename)
248
249 # If the name contains a number, then increment it.
250 numSearch = QRegExp("(^|[^\\d])(\\d+)") # We want to match as far left as
251 # possible, and when the number is
252 # at the start of the name.
253
254 # Does it have a number?
255 start = numSearch.lastIndexIn(name)
256 if start != -1:
257 # It has a number, increment it.
258 start = numSearch.pos(2) # Only the second group is of interest.
259 numAsStr = numSearch.capturedTexts()[2]
260 number = "{0:0{width}d}".format(int(numAsStr) + 1, width=len(numAsStr))
261 name = name[:start] + number + name[start+len(numAsStr):]
262 else:
263 # no number
264 start = name.rfind('.')
265 if start != -1:
266 # has a '.' somewhere, e.g. it has an extension
267 name = name [:start] + '1' + name[start:]
268 else:
269 # no extension, just tack it on to the end
270 name += '1'
271
272 self.__filename = os.path.join(os.path.dirname(self.__filename), name)
273 self.__updateCaption()
274
275 @pyqtSlot()
276 def on_takeButton_clicked(self):
137 """ 277 """
138 Private slot to take a snapshot. 278 Private slot to take a snapshot.
139 """ 279 """
140 mode = self.modeCombo.itemData(self.modeCombo.currentIndex()) 280 self.__mode = self.modeCombo.itemData(self.modeCombo.currentIndex())
141 delay = self.delaySpin.value() 281 self.__delay = self.delaySpin.value()
142 282
143 Preferences.Prefs.settings.setValue("Snapshot/Delay", delay) 283 self.__savedPosition = self.pos()
144 Preferences.Prefs.settings.setValue("Snapshot/Mode", mode)
145
146 self.hide() 284 self.hide()
147 285
148 self.__grabber = SnapshotRegionGrabber(mode, delay) 286 if self.__delay:
287 self.__grabTimer.start(self.__delay)
288 else:
289 QTimer.singleShot(200, self.__startUndelayedGrab)
290
291 def __grabTimerTimeout(self):
292 """
293 Private slot to perform a delayed grab operation.
294 """
295 if self.__mode == SnapWidget.ModeRectangle:
296 self.__grabRectangle()
297 elif self.__mode == SnapWidget.ModeEllipse:
298 self.__grabEllipse()
299 elif self.__mode == SnapWidget.ModeFreehand:
300 self.__grabFreehand()
301 else:
302 self.__performGrab()
303
304 def __startUndelayedGrab(self):
305 """
306 Private slot to perform an undelayed grab operation.
307 """
308 if self.__mode == SnapWidget.ModeRectangle:
309 self.__grabRectangle()
310 elif self.__mode == SnapWidget.ModeEllipse:
311 self.__grabEllipse()
312 elif self.__mode == SnapWidget.ModeFreehand:
313 self.__grabFreehand()
314 else:
315 self.__grabberWidget.show()
316 self.__grabberWidget.grabMouse(Qt.CrossCursor)
317
318 def __grabRectangle(self):
319 """
320 Private method to grab a rectangular screen region.
321 """
322 self.__grabber = SnapshotRegionGrabber(mode=SnapshotRegionGrabber.Rectangle)
149 self.__grabber.grabbed.connect(self.__captured) 323 self.__grabber.grabbed.connect(self.__captured)
324
325 def __grabEllipse(self):
326 """
327 Private method to grab an elliptical screen region.
328 """
329 self.__grabber = SnapshotRegionGrabber(mode=SnapshotRegionGrabber.Ellipse)
330 self.__grabber.grabbed.connect(self.__captured)
331
332 def __grabFreehand(self):
333 """
334 Private method to grab a non-rectangular screen region.
335 """
336 self.__grabber = SnapshotFreehandGrabber()
337 self.__grabber.grabbed.connect(self.__captured)
338
339 def __performGrab(self):
340 """
341 Private method to perform a screen grab other than a selected region.
342 """
343 self.__grabberWidget.releaseMouse()
344 self.__grabberWidget.hide()
345 self.__grabTimer.stop()
346
347 if self.__mode == SnapWidget.ModeFullscreen:
348 self.__snapshot = QPixmap.grabWindow(QApplication.desktop().winId())
349 elif self.__mode == SnapWidget.ModeScreen:
350 desktop = QApplication.desktop()
351 screenId = desktop.screenNumber(QCursor.pos())
352 geom = desktop.screenGeometry(screenId)
353 x = geom.x()
354 y = geom.y()
355 self.__snapshot = QPixmap.grabWindow(
356 desktop.winId(), x, y, geom.width(), geom.height())
357 else:
358 self.__snapshot = QPixmap()
359
360 self.__redisplay()
361 self.__modified = True
362 self.__updateCaption()
363
364 def __redisplay(self):
365 """
366 Private method to redisplay the window.
367 """
368 self.__updatePreview()
369 QApplication.restoreOverrideCursor()
370 if not self.__savedPosition.isNull():
371 self.move(self.__savedPosition)
372 self.show()
373
374 self.saveButton.setEnabled(not self.__snapshot.isNull())
375 self.copyButton.setEnabled(not self.__snapshot.isNull())
150 376
151 @pyqtSlot() 377 @pyqtSlot()
152 def on_copyButton_clicked(self): 378 def on_copyButton_clicked(self):
153 """ 379 """
154 Private slot to copy the snapshot to the clipboard. 380 Private slot to copy the snapshot to the clipboard.
160 Private slot to show a preview of the snapshot. 386 Private slot to show a preview of the snapshot.
161 387
162 @param pixmap pixmap of the snapshot (QPixmap) 388 @param pixmap pixmap of the snapshot (QPixmap)
163 """ 389 """
164 self.__grabber.close() 390 self.__grabber.close()
165 self.preview.setPixmap(pixmap) 391 self.__snapshot = QPixmap(pixmap)
166 392
393 self.__grabber.grabbed.disconnect(self.__captured)
167 self.__grabber = None 394 self.__grabber = None
168 395
169 self.saveButton.setEnabled(True) 396 self.__redisplay()
170 self.copyButton.setEnabled(True) 397 self.__modified = True
171 self.show() 398 self.__updateCaption()
399
400 def __updatePreview(self):
401 """
402 Private slot to update the preview picture.
403 """
404 self.preview.setToolTip(
405 self.trUtf8("Preview of the snapshot image ({0:n} x {1:n})").format(
406 self.__snapshot.width(), self.__snapshot.height()))
407 self.preview.setPreview(self.__snapshot)
408 self.preview.adjustSize()
409
410 def resizeEvent(self, evt):
411 """
412 Protected method handling a resizing of the window.
413
414 @param evt resize event (QResizeEvent)
415 """
416 self.__updateTimer.start(200)
417
418 def __dragSnapshot(self):
419 """
420 Private slot handling the dragging of the preview picture.
421 """
422 drag = QDrag(self)
423 mimeData = QMimeData()
424 mimeData.setImageData(self.__snapshot)
425 drag.setMimeData(mimeData)
426 drag.setPixmap(self.preview.pixmap())
427 drag.exec_(Qt.CopyAction)
428
429 def eventFilter(self, obj, evt):
430 """
431 Public method to handle event for other objects.
432
433 @param obj reference to the object (QObject)
434 @param evt reference to the event (QEvent)
435 @return flag indicating that the event should be filtered out (boolean)
436 """
437 if obj == self.__grabberWidget and evt.type() == QEvent.MouseButtonPress:
438 if QWidget.mouseGrabber() != self.__grabberWidget:
439 return False
440 if evt.button() == Qt.LeftButton:
441 self.__performGrab()
442
443 return False
444
445 def closeEvent(self, evt):
446 """
447 Protected method handling the close event.
448
449 @param evt close event (QCloseEvent)
450 """
451 if self.__modified:
452 res = E5MessageBox.question(self,
453 self.trUtf8("eric5 Snapshot"),
454 self.trUtf8("""The application contains an unsaved snapshot."""),
455 E5MessageBox.StandardButtons(
456 E5MessageBox.Abort | \
457 E5MessageBox.Discard | \
458 E5MessageBox.Save))
459 if res == E5MessageBox.Abort:
460 evt.ignore()
461 return
462 elif res == E5MessageBox.Save:
463 self.on_saveButton_clicked()
464
465 Preferences.Prefs.settings.setValue("Snapshot/Delay", self.delaySpin.value())
466 Preferences.Prefs.settings.setValue("Snapshot/Mode",
467 self.modeCombo.itemData(self.modeCombo.currentIndex()))
468 Preferences.Prefs.settings.setValue("Snapshot/Filename", self.__filename)
469 Preferences.Prefs.settings.sync()
470
471 def __updateCaption(self):
472 """
473 Private method to update the window caption.
474 """
475 self.setWindowTitle("{0}[*] - {1}".format(
476 os.path.basename(self.__filename),
477 self.trUtf8("eric5 Snapshot")))
478 self.setWindowModified(self.__modified)
479 self.pathNameEdit.setText(os.path.dirname(self.__filename))

eric ide

mercurial