#!/usr/bin/env python

from __future__ import (division, print_function, unicode_literals)
from future_builtins import *

from PyQt4.QtCore import (QPointF, QRectF, QSize, QString, Qt, SIGNAL, SLOT)
from PyQt4.QtGui import (QApplication, QLabel, QWidget, QColor, QDialog,
						 QFont, QFontMetricsF, QGridLayout, QLCDNumber,
						 QPainter, QPalette, QPolygonF, QSizePolicy, QSpinBox
						)

X11 = True
try:
	from PyQt4.QtGui import qt_x11_wait_for_window_manager
except ImportError:
	X11 = False

class FractionSlider(QWidget):
	
	XMARGIN = 12.0
	YMARGIN = 5.0
	WSTRING = "999"

	def __init__(self, numerator = 0, denominator = 10, parent = None):
		super(FractionSlider, self).__init__(parent)

		self.__numerator = numerator
		self.__denominator = denominator
		
		self.setFocusPolicy(Qt.WheelFocus)
		self.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed))

	def decimal(self):
		return self.__numerator / float(self.__denominator)

	def fraction(self):
		return self.__numerator, self.__denominator

	def sizeHint(self):
		return self.minimumSizeHint()

	def minimumSizeHint(self):
		font = QFont(self.font())
		font.setPointSize(font.pointSize() - 1)
		fm = QFontMetricsF(font)
		return QSize(fm.width(FractionSlider.WSTRING) * self.__denominator, (fm.height() * 4) + FractionSlider.YMARGIN)

	def setFraction(self, numerator, denominator = None):
		if denominator is not None:
			if 3 <= denominator <= 60:
				self.__denominator = denominator
			else:
				raise ValueError, "denominator out of range"
		if 0 <= numerator <= self.__denominator:
			self.__numerator = numerator
		else:
			raise ValueError, "numerator out of range"
		self.update()
		self.updateGeometry()

	def mousePressEvent(self, event):
		if event.button() == Qt.LeftButton:
			self.moveSlider(event.x())
			event.accept()
		else:
			QWidget.mousePressEvent(self, event)

	def mouseMoveEvent(self, event):
		self.moveSlider(event.x())

	def moveSlider(self, x):
		span = self.width() - (FractionSlider.XMARGIN * 2)
		offset = span - x + FractionSlider.XMARGIN
		numerator = int(round(self.__denominator * (1.0 - (offset / span))))
		numerator = max(0, min(numerator, self.__denominator))
		if numerator != self.__numerator:
			self.__numerator = numerator
			self.emit(SIGNAL("valueChanged(int,int)"), self.__numerator, self.__denominator)
			self.update()

	def keyPressEvent(self, event):
		change = 0
		if event.key() == Qt.Key_Home:
			change = -self.__denominator
		elif event.key() in (Qt.Key_Up, Qt.Key_Right):
			change = 1
		elif event.key() == Qt.Key_PageUp:
			change = (self.__denominator // 10) + 1
		elif event.key() in (Qt.Key_Down, Qt.Key_Left):
			change = -1
		elif event.key() == Qt.Key_PageDown:
			change = -((self.__denominator // 10) + 1)
		elif event.key() == Qt.Key_End:
			change = self.__denominator
		if change:
			numerator = self.__numerator
			numerator += change
			numerator = max(0, min(numerator, self.__denominator))
			if numerator != self.__numerator:
				self.__numerator = numerator
				self.emit(SIGNAL("valueChanged(int,int)"), self.__numerator, self.__denominator)
				self.update()
			event.accept()
		else:
			QWidget.keyPressEvent(self, event)

	def paintEvent(self, event):
		font = QFont(self.font())
		font.setPointSize(font.pointSize() - 1)
		fm = QFontMetricsF(font)
		
		fractWidth = fm.width(FractionSlider.WSTRING)
		indent = fm.boundingRect("9").width() / 2.0

		if not X11:
			fractWidth *= 1.5

		span = self.width() - (FractionSlider.XMARGIN * 2)
		value = self.__numerator / float(self.__denominator)
		
		painter = QPainter(self)
		painter.setRenderHint(QPainter.Antialiasing)
		painter.setRenderHint(QPainter.TextAntialiasing)
		painter.setPen(self.palette().color(QPalette.Mid))
		painter.setBrush(self.palette().brush(QPalette.AlternateBase))
		painter.drawRect(self.rect())

		segColor = QColor(Qt.green).dark(120)
		segLineColor = segColor.dark()
		painter.setPen(segLineColor)
		painter.setBrush(segColor)
		painter.drawRect(FractionSlider.XMARGIN, FractionSlider.YMARGIN, span, fm.height())

		textColor = self.palette().color(QPalette.Text)
		segWidth = span / self.__denominator
		segHeight = fm.height() * 2
		nRect = fm.boundingRect(FractionSlider.WSTRING)
		x = FractionSlider.XMARGIN
		yOffset = segHeight + fm.height()

		for i in range(self.__denominator + 1):

			painter.setPen(segLineColor)
			painter.drawLine(x, FractionSlider.YMARGIN, x, segHeight)

			painter.setPen(textColor)
			y = segHeight
			rect = QRectF(nRect)
			rect.moveCenter(QPointF(x, y + fm.height() / 2.0))
			painter.drawText(rect, Qt.AlignCenter, QString.number(i))

			y = yOffset
			rect.moveCenter(QPointF(x, y + fm.height() / 2.0))
			painter.drawText(rect, Qt.AlignCenter, QString.number(self.__denominator))
			painter.drawLine(QPointF(rect.left() + indent, y), QPointF(rect.right() - indent, y))

			x += segWidth

		span = int(span)
		y = FractionSlider.YMARGIN - 0.5
		
		triangle = [QPointF(value * span, y),
					QPointF((value * span) + (2 * FractionSlider.XMARGIN), y),
					QPointF((value * span) + FractionSlider.XMARGIN, fm.height())
				   ]

		painter.setPen(Qt.yellow)
		painter.setBrush(Qt.darkYellow)
		painter.drawPolygon(QPolygonF(triangle))

if __name__ == "__main__":
	import sys

	app = QApplication(sys.argv)
	form = QDialog()

	sliderLabel = QLabel("&Fraction")
	slider = FractionSlider(denominator = 12)
	sliderLabel.setBuddy(slider)

	denominatorLabel = QLabel("&Denominator")
	denominatorSpinBox = QSpinBox()
	denominatorLabel.setBuddy(denominatorSpinBox)
	denominatorSpinBox.setRange(3, 60)
	denominatorSpinBox.setValue(slider.fraction()[1])
	denominatorSpinBox.setAlignment(Qt.AlignRight|Qt.AlignVCenter)

	numeratorLabel = QLabel("Numberator")
	numeratorLCD = QLCDNumber()
	numeratorLCD.setSegmentStyle(QLCDNumber.Flat)

	layout = QGridLayout()
	layout.addWidget(sliderLabel, 0, 0)
	layout.addWidget(slider, 0, 1, 1, 5)
	layout.addWidget(numeratorLabel, 1, 0)
	layout.addWidget(numeratorLCD, 1, 1)
	layout.addWidget(denominatorLabel, 1, 2)
	layout.addWidget(denominatorSpinBox, 1, 3)

	form.setLayout(layout)

	def valueChanged(denominator):
		numerator = int(slider.decimal() * denominator)
		slider.setFraction(numerator, denominator)
		numeratorLCD.display(numerator)

	form.connect(slider, SIGNAL("valueChanged(int, int)"), numeratorLCD, SLOT("display(int)"))
	form.connect(denominatorSpinBox, SIGNAL("valueChanged(int)"), valueChanged)

	form.setWindowTitle("Fraction Slider")

	form.show()
	app.exec_()
