Пример #1
0
	def __init__(self):
		# Initialize UI
		QtGui.QMainWindow.__init__(self)
		
		# Set classes
		self.image = None			# The ImageObject instance
		self.qimage = None			# The Qt image copy
		self.frameRegion = None
		self.imheight = 0
		self.imwidth = 0
		self.sourceRect = QtCore.QRect(0,0,0,0)
		self.targetRect = QtCore.QRect(0,0,0,0)
		self.checker = None
		self.checkerEnabled = False
				
		# Setup UI
		self.setupUi(self)
		self.setupKeyboardHooks()
		
		# Weird hack ...
		# First, a central widget
		# That widget will get a grid layout layout
		# That will hold the painting area, as well as the scrollbars
		
		# 1.) Create the central widget
		central = QtGui.QWidget()
		central.setBackgroundRole(QtGui.QPalette.Dark)
		self.setCentralWidget(central)
		
		# 2.) Add the layout
		layout = QtGui.QGridLayout()
		layout.setMargin(0)			# Set a zero margin so it fills the frame
		layout.setSpacing(0)		# -"- spacing for similar reasons
		central.setLayout(layout)
				
		# 3.) Create the picture frame
		self.pictureFrame = PictureFrame(self)
		self.pictureFrame.setVisible(False)
		
		# 4.) Add the picture frame to the grid
		layout.addWidget(self.pictureFrame, 0, 0)

		# 5.) Create the scrollbars
		self.vscroll = QtGui.QScrollBar(QtCore.Qt.Vertical)
		self.vscroll.setPageStep(200)
		layout.addWidget(self.vscroll, 0, 1)
		self.hscroll = QtGui.QScrollBar(QtCore.Qt.Horizontal)
		self.hscroll.setPageStep(200)
		layout.addWidget(self.hscroll, 1, 0)
		
		# 5.5) Connect the sliders
		self.connect(self.vscroll, QtCore.SIGNAL("valueChanged(int)"), self.scrollBarSlided)
		self.connect(self.hscroll, QtCore.SIGNAL("valueChanged(int)"), self.scrollBarSlided)
		
		# Scrollbars	
		self.enableHorizontalScrollBar(False)
		self.enableVerticalScrollBar(False)

		# Finally, add the PictureFrame to the Scrollarea		
		self.pictureFrame.setVisible(True)
		
		# Refresh
		self.refreshViewport()
		
		# Create statusbar widgets
		self.positionLabel = QtGui.QLabel()
		self.statusBar().addPermanentWidget(self.positionLabel)
		self.colorLabel = QtGui.QLabel()
		self.statusBar().addPermanentWidget(self.colorLabel)
		self.colorLabel2 = QtGui.QLabel()

		# Set options
		self.setAskOnExit(False)
		self.setFileDialogDirectory(None)
		self.setImageAreaBackgroundColor("#909090")
		
		return
Пример #2
0
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
	"""
	This class represents the main window of the image viewer
	"""

	# Initializes the class
	def __init__(self):
		# Initialize UI
		QtGui.QMainWindow.__init__(self)
		
		# Set classes
		self.image = None			# The ImageObject instance
		self.qimage = None			# The Qt image copy
		self.frameRegion = None
		self.imheight = 0
		self.imwidth = 0
		self.sourceRect = QtCore.QRect(0,0,0,0)
		self.targetRect = QtCore.QRect(0,0,0,0)
		self.checker = None
		self.checkerEnabled = False
				
		# Setup UI
		self.setupUi(self)
		self.setupKeyboardHooks()
		
		# Weird hack ...
		# First, a central widget
		# That widget will get a grid layout layout
		# That will hold the painting area, as well as the scrollbars
		
		# 1.) Create the central widget
		central = QtGui.QWidget()
		central.setBackgroundRole(QtGui.QPalette.Dark)
		self.setCentralWidget(central)
		
		# 2.) Add the layout
		layout = QtGui.QGridLayout()
		layout.setMargin(0)			# Set a zero margin so it fills the frame
		layout.setSpacing(0)		# -"- spacing for similar reasons
		central.setLayout(layout)
				
		# 3.) Create the picture frame
		self.pictureFrame = PictureFrame(self)
		self.pictureFrame.setVisible(False)
		
		# 4.) Add the picture frame to the grid
		layout.addWidget(self.pictureFrame, 0, 0)

		# 5.) Create the scrollbars
		self.vscroll = QtGui.QScrollBar(QtCore.Qt.Vertical)
		self.vscroll.setPageStep(200)
		layout.addWidget(self.vscroll, 0, 1)
		self.hscroll = QtGui.QScrollBar(QtCore.Qt.Horizontal)
		self.hscroll.setPageStep(200)
		layout.addWidget(self.hscroll, 1, 0)
		
		# 5.5) Connect the sliders
		self.connect(self.vscroll, QtCore.SIGNAL("valueChanged(int)"), self.scrollBarSlided)
		self.connect(self.hscroll, QtCore.SIGNAL("valueChanged(int)"), self.scrollBarSlided)
		
		# Scrollbars	
		self.enableHorizontalScrollBar(False)
		self.enableVerticalScrollBar(False)

		# Finally, add the PictureFrame to the Scrollarea		
		self.pictureFrame.setVisible(True)
		
		# Refresh
		self.refreshViewport()
		
		# Create statusbar widgets
		self.positionLabel = QtGui.QLabel()
		self.statusBar().addPermanentWidget(self.positionLabel)
		self.colorLabel = QtGui.QLabel()
		self.statusBar().addPermanentWidget(self.colorLabel)
		self.colorLabel2 = QtGui.QLabel()

		# Set options
		self.setAskOnExit(False)
		self.setFileDialogDirectory(None)
		self.setImageAreaBackgroundColor("#909090")
		
		return

	# Enables or disables the horizontal scrollbar
	def enableHorizontalScrollBar(self, enabled):
		"""Enables or disables the horizontal scrollbar"""
		self.hscroll.setVisible(enabled)

	# Enables or disables the vertica scrollbar
	def enableVerticalScrollBar(self, enabled):
		"""Enables or disables the vertical scrollbar"""
		self.vscroll.setVisible(enabled)
		
	# Gets the hscroll value
	def getHorizontalScrollBarValue(self):
		"""Gets the value of the horizontal scrollbar"""
		if self.hscroll.isVisible():
			return self.hscroll.value()
		return 0
		
	# Gets the vscroll value
	def getVerticalScrollBarValue(self):
		"""Gets the value of the vertical scrollbar"""
		if self.vscroll.isVisible():
			return self.vscroll.value()
		return 0

	# Keyboard hooks

	# Initialize keyboard shortcuts
	def setupKeyboardHooks(self):
		"""Initializes the keyboard shortcuts"""

		# Set secondary "quit" shortcut
		shortcut = QtGui.QShortcut(
			QtGui.QKeySequence( self.tr("Ctrl+Q", "File|Quit")),
			self
			)
		self.connect(shortcut, QtCore.SIGNAL("activated()"), self.on_actionFileQuit_triggered)
		shortcut = QtGui.QShortcut(
			QtGui.QKeySequence( self.tr("Q", "File|Quit")),
			self
			)
		self.connect(shortcut, QtCore.SIGNAL("activated()"), self.on_actionFileQuit_triggered)

		# Set secondary "open" shortcut
		shortcut = QtGui.QShortcut(
			QtGui.QKeySequence( self.tr("Ctrl+O", "File|Open")),
			self
			)
		self.connect(shortcut, QtCore.SIGNAL("activated()"), self.on_actionFileOpen_triggered)
		return

	# Options

	# Sets the background color
	def setImageAreaBackgroundColor(self, color):
		"""
		Sets the background color of the image area.
		The color is a string with a hex RGB color code,
		i.e. "#FF0000" for red
		"""
		
		self.bgColor = QtGui.QColor(color)
		self.bgbrush = QtGui.QBrush(self.bgColor)
		self.pictureFrame.forceRepaint()
				
		return

	# Sets the initial directory for file dialogs
	def setFileDialogDirectory(self, directory):
		"""Sets the initial directory for file dialogs"""
		self.fileDialogDirectory = directory
		return

	# Sets whether a dialog box shall be shown when the
	# window is about to close
	def setAskOnExit(self, enabled):
		"""
		If enabled, the user will be asked if the window
		is about to close. If disabled, the window will close
		without further intervention
		"""
		self.AskOnExit = enabled
		return

	# Convenience functions

	# Center the window on the screen
	def centerWindow(self):
		"""Centers the window on the screen"""
		# Get desktop size
		desktop = QtGui.QApplication.desktop()
		screenWidth = desktop.width()
		screenHeight = desktop.height()
		# Get window size
		windowSize = self.size()
		width = windowSize.width()
		height = windowSize.height()
		# Calculate new position
		x = (screenWidth-width) / 2
		y = (screenHeight-height) / 2
		# Set position
		self.move(x, y)
		return

	# Menu handling

	# File|Open was selected
	@QtCore.pyqtSignature("")
	def on_actionFileOpen_triggered(self):
		self.openFileWithDialog()
		return

	# File|Close was clicked
	@QtCore.pyqtSignature("")
	def on_actionFileQuit_triggered(self):
		self.close()
		return

	# Repaint action was triggered
	@QtCore.pyqtSignature("")
	def on_actionDebugRepaint_triggered(self):
		self.pictureFrame.forceRepaint()
		return

	# File handling

	# Shows the file open dialog and eventually opens the file
	def openFileWithDialog(self):
		"""
		Shows the "File Open" dialog and eventually opens the file.
		Returns True if the user clicked "OK" and False otherwise.
		"""

		# Define file filters
		# TODO: Extend this list to every supported type
		filters = QtCore.QStringList()
		filters << self.tr("Pictures", "File dialog") + " (*.jpg *.gif *.png *.xpm)"
		filters << self.tr("All files", "File dialog") + " (*)"

		# Create the dialog
		dialog = QtGui.QFileDialog(self)
		dialog.setFileMode(QtGui.QFileDialog.ExistingFile)
		dialog.setViewMode(QtGui.QFileDialog.Detail)
		dialog.setFilters(filters)
		if self.fileDialogDirectory != None:
			dialog.setDirectory(self.fileDialogDirectory)

		# Show the dialog
		if( dialog.exec_() ):
			# Get the filename
			fileNames = dialog.selectedFiles()
			fileName = fileNames[0]
			# Open the file
			self.openFile(fileName)
			return True

		return False

	# Notifies the system that the loading of a file has started
	def __onOpenFileStarted(self, filepath):
		"""Notifies the system that the loading of a file has started"""
		# Emit signal
		self.emit(QtCore.SIGNAL("openFileStarted(char*)"), filepath)
		return

	# Notifies the system that the loading of a file has finished
	def __onOpenFileFinished(self, filepath, successful = True):
		"""Notifies the system that the loading of a file has finished"""
		# Emit signal
		self.emit(QtCore.SIGNAL("openFileFinished(char*, bool)"), str(filepath), successful)
		# Sanity check
		if not successful:
			return
		# Display statistics
		width, height = self.image.getSize()
		self.statusBar().showMessage(str(width) + "x" + str(height))
		# Display image
		self.pictureFrame.forceRepaint()
		return

	# Opens the specified file
	def openFile(self, filepath):
		"""Opens the specified file"""
		# Open the file
		self.__openFileSync(filepath)
		return

	# Opens a file synchronously
	def __openFileSync(self, filepath):
		"""
		Synchronously opens the file.
		The function will block until the image is loaded.
		"""

		# Notify
		print("Synchronously loading image: " + filepath)
		self.__onOpenFileStarted(filepath)

		# Load image using PIL
		pilimage = ImageObject()
		pilimage.loadImageFromFile(filepath)
		if not pilimage:
			self.__onOpenFileFinished(filepath, False)
			return False
		self.image = pilimage
		self.imwidth, self.imheight = pilimage.getsize()

		# Convert image to Qt QImage
		if not self.updateQImage():
			self.qimage = None
			self.__onOpenFileFinished(filepath, False)
			return False

		# Region set scrollbars
		self.updateScrollbarSizeFromImage()
		self.vscroll.setValue(0)
		self.hscroll.setValue(0)
		
		# Enable or disable opaque mode
		rgba = pilimage.image.mode == "RGBA"
		self.pictureFrame.setOpaqueMode(not rgba)
		self.setCheckerEnabled(rgba)

		# Return
		print("Done loading image")
		self.__onOpenFileFinished(filepath, True)
		return True

	# Updates the local QImage copy
	def updateQImage(self):
		"""Updates the QImage copy"""
		if not self.image:
			self.qimage = None
			return None
			
		qimage = self.image.convertToQtImage()
		self.qimage = qimage
		
		# Refresh the viewport
		self.refreshViewport()
		
		return qimage

	# General event handling

	# Overrides the default behavior for the window close event
	def closeEvent(self, event):
		# Check if asking is enabled
		if not self.AskOnExit:
			return

		# Ask
		reply = QtGui.QMessageBox.question(self,
				self.tr("Closing ..."),
				self.tr("Are you sure you want to quit?"),
				QtGui.QMessageBox.Yes,
				QtGui.QMessageBox.No
				)
		if reply == QtGui.QMessageBox.Yes:
			event.accept()
		else:
			event.ignore()
		return
		
	# Scrollbar value has changed, so refresh the viewports
	def scrollBarSlided(self, value):
		self.calculateViewport()
		self.pictureFrame.forceRepaint()
		return
		
	def calculateViewport(self):
		"""Calculates the visible area of the image"""
		
		# Get the display frame size
		sourceRect = self.pictureFrame.rect()
		
		# Offset the rectangle
		leftOffset = self.getHorizontalScrollBarValue()
		topOffset = self.getVerticalScrollBarValue()		
		sourceRect.adjust(leftOffset, topOffset, leftOffset, topOffset)
		
		# Adjust the height of the source rect,
		# if necessary
		if sourceRect.width() > self.imwidth:
			sourceRect.setWidth(self.imwidth)
		if sourceRect.height() > self.imheight:
			sourceRect.setHeight(self.imheight)
		
		# Create the target rect
		targetRect = self.pictureFrame.rect()
		if targetRect.width() > self.imwidth:
			targetRect.setWidth(self.imwidth)
		if targetRect.height() > self.imheight:
			targetRect.setHeight(self.imheight)
			
		# Center target rect
		leftOffset = (self.pictureFrame.width() - targetRect.width()) / 2
		topOffset = (self.pictureFrame.height() - targetRect.height()) / 2
		targetRect.adjust(leftOffset, topOffset, leftOffset, topOffset)

		# Calculate the clipping regions			
		self.targetRegion = QtGui.QRegion( targetRect )
		
		# Set it
		self.sourceRect = sourceRect
		self.targetRect = targetRect
		return
	
	def updateScrollbarSizeFromImage(self):
		"""
		Sets the max values of the scrollbars according to 
		the image's size
		"""
		if not self.image:
			return
		
		# Calculate the possible sizes
		# If either of these values is lte 0, the scrollbar
		# may be hidden
		height = self.imheight - self.pictureFrame.height()
		width = self.imwidth - self.pictureFrame.width()
		
		# Set height scrollbar
		if height > 0:
			self.vscroll.setVisible(True)
			self.vscroll.setMinimum(0)
			self.vscroll.setMaximum(height)
			self.vscroll.setPageStep(4*self.imheight/5)
		else:
			self.vscroll.setVisible(False)
		
		# Set width scrollbar
		if width > 0:
			self.hscroll.setVisible(True)
			self.hscroll.setMinimum(0)
			self.hscroll.setMaximum(width)
			self.hscroll.setPageStep(4*self.imwidth/5)
		else:
			self.hscroll.setVisible(False)
		
		return
	
	def refreshViewport(self):
		"""Refreshes the viewport"""
		self.frameRegion = QtGui.QRegion( self.pictureFrame.rect() )
	
		self.calculateViewport()
		self.updateScrollbarSizeFromImage()
		self.calculateViewport()
		self.pictureFrame.forceRepaint()
		return
	
	# Resize event
	def resizeEvent(self, event):
		return
	
	def resizeHook(self, frame):
		self.refreshViewport()
		return
	
	# Gets the color
	def getColor(self, x, y):
		"""
		Gets the color at the given coordinates.
		x,y is relative to the source rect.
		The result is a RGB tuple
		"""
		# Sanity check
		if (x >= self.imwidth) or (x < 0):
			return None
		if (y >= self.imheight) or (y < 0):
			return None
		return self.image.getPixel(x, y)
		
	# Gets the color
	def getColorHex(self, x, y):
		"""
		Gets the color at the given coordinates.
		x,y is relative to the source rect.
		The result is a web (hex) color representation.
		"""
		color = self.getColor(x, y)
		if not color:
			return None
		value = "#%02X%02X%02X"%(color[0],color[1],color[2])
		return value
	
	# Painting hook
	def paintHook(self, frame, painter):
		"""This function will be called from within the picture frame"""

		pictureClipRegion = self.frameRegion.intersected( self.targetRegion )
		backgroundClipRegion = self.frameRegion.xored( self.targetRegion )
		
		# Fill the background	
		if self.qimage and self.pictureFrame.isOpaque:
			painter.setClipRegion( backgroundClipRegion )

		if self.getCheckerEnabled():
			self.renderCheckerBoard(painter)
		else:
			painter.fillRect( frame.rect(), self.bgbrush )
			
		# Draw the image
		if self.qimage:
			painter.setClipRegion( pictureClipRegion )
			painter.drawImage(self.targetRect, self.qimage, self.sourceRect )

		return
	
	def getCheckerEnabled(self):
		"""
		Returns True if checker painting is enabled
		"""
		return self.checkerEnabled
	
	def setCheckerEnabled(self, enabled):
		"""
		Activates or deactivates the checker background pattern.
		"""
		print("%s checkerboard." % ("Enabling" if enabled else "Disabling"))
		if enabled == self.checkerEnabled:
			return
		self.checkerEnabled = enabled
		if enabled: self.getCheckerBoard()
		self.pictureFrame.forceRepaint()
		return
	
	def getCheckerBoard(self):
		"""
		Prepares the checkerbox pattern.
		"""
		if not self.checkerEnabled:
			return None
		if self.checker:
			return self.checker
		
		# Prepare
		lightbrush = QtGui.QBrush(QtGui.QColor("#B0B0B0"))
		darkbrush  = QtGui.QBrush(QtGui.QColor("#808080"))

		size = 12		
		width = 8*size
		height = 8*size
		pixmap = QtGui.QPixmap(width, height)
		painter = QtGui.QPainter(pixmap)
		painter.fillRect(0, 0, width, height, lightbrush)
		on = False
		
		# Paint the checker board
		for y in range(0, height+1):
			on = not on
			top = y * size
			for x in range(0 if on else 1, width+1, 2):
				left = x * size
				checkerRect = QtCore.QRect( left, top, size, size )
				painter.fillRect( checkerRect, darkbrush )
				
		self.checker = pixmap
		return pixmap
		
	def renderCheckerBoard(self, painter):
		"""
		Renders a checkerboard pattern using the given painter.
		"""
		checker = self.getCheckerBoard()
		if not checker:
			return
		for y in range(0, self.pictureFrame.height()+1, checker.height()):
			for x in range(0, self.pictureFrame.width()+1, checker.width()):
				painter.drawPixmap(x, y, checker)
		return
		
	def mouseMoveHook(self, event):
		# Get the mouse position
		x = event.x() + self.sourceRect.left() - self.targetRect.left()
		y = event.y() + self.sourceRect.top() - self.targetRect.top()
		
		# Set the position
		if (x < self.imwidth) and (x > 0) and (y < self.imheight) and (y > 0):
			self.positionLabel.setText( str(x) + "x" + str(y))
			self.positionLabel.setVisible(True)
		else:
			self.positionLabel.setVisible(False)
		
		# Get and set the color
		color = self.getColorHex(x, y)
		if color:
			self.colorLabel.setText(color)
			self.colorLabel.setVisible(True)
		else:
			self.colorLabel.setVisible(False)
		return