#!/usr/bin/env python

#-------------------------------------------------------------------------------------------------
#
# Welcome to this demo of my overlay scrollbar concept!
# To have something to scroll, this file loads itself read-only.
# Please try the following:
#
# - Move the mouse to the scrollbar and left-drag immediately
# - Move the mouse up and down above the scrollbar
#
# - Click on the overlay arrow buttons
# - Click and hold an arrow button
# - Drag away from an arrow button
#
# - Middle mouse button click
# - Middle mouse button drag
# - Right click
#
# - Reduce the height of the window to a minimum
#
#
# Thanks to the people in #pygtk (irc.gimp.org) and the authors of following resources:
# http://www.oluyede.org/blog/writing-a-widget-using-cairo-and-pygtk-28/
# http://www.tortall.net/mu/wiki/CairoTutorial
# http://www.tortall.net/mu/wiki/PyGTKCairoTutorial
# http://python.developpez.com/cours/pygtktutorial//php/pygtken/sec-EventHandling.php
# http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
# http://python.developpez.com/cours/pygtktutorial/php/pygtken/sec-UsingAdjustmentsTheEasyWay.php
#
#
# No rights reserved, feel free to do whatever you want with this code,
# but it would be nice if you give me credit.
# Thorsten Wilms 2009-11-06
# http://thorwil.wordpress.com/
#
#-------------------------------------------------------------------------------------------------

from __future__ import division
import pygtk
pygtk.require('2.0')
import gtk
from gtk import gdk
import gobject
import cairo
import time

# For getting to know the path/filename of this script:
import os
import sys

# Create new widget, inherit from gtk.DrawingArea:
class dynScrollbar(gtk.DrawingArea):
	def __init__(self, adjustment, is_vertical):
		super(dynScrollbar, self).__init__()
		
		# Paramters to tweak:
		self._arrow_repeat_delay = 100 # milliseconds
		self._paging_delay = 250 # milliseconds
		self._reset_overlay_delay = 200 # milliseconds
		self._drag_threshold = 10 # pixels
		
		# gtk.Widget signals:
		self.connect("motion_notify_event", self.motion_notify)
		self.connect("enter_notify_event", self.enter_notify)
		self.connect("button_press_event", self.button_press)
		self.connect("button_release_event", self.button_release)
		self.connect("scroll_event", self.on_button_scroll_event)
		
		if is_vertical == True:
			self.connect("leave_notify_event", self.leave_notify_vertical)
			self.connect("expose_event", self.expose_vertical)
		else:
			self.connect("leave_notify_event", self.leave_notify_horizontal)
			self.connect("expose_event", self.expose_horizontal)
		
		# Unmask events:
		self.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK |
							 gtk.gdk.POINTER_MOTION_MASK |
							 gtk.gdk.ENTER_NOTIFY_MASK |
							 gtk.gdk.LEAVE_NOTIFY_MASK |
							 gtk.gdk.BUTTON_PRESS_MASK |
							 gtk.gdk.BUTTON_RELEASE_MASK)

		self._is_vertical = is_vertical # Horizontal scrollbar if false
		self._height = 0 # Set in expose
		self._width = 0 # Set in expose
		self._show_arrows = False
		self._overlay_pos = 0 # Position of the overlay arrow buttons
		self._overlay_height = 56 # From top of up-button to bottom of down-button
		self._button_size = 0 # Height and width of overlay buttons, set in expose
		self._arrow_padding = 4 # Padding around arrows (button height - arrow height = padding)
						 
		# Set position and size of the view indicator:
		self._indicator_start = 0
		self._indicator_fraction = 0.1 # Visible/whole content, set in apply_adjustment
		self._indicator_size = 0 # Set in apply_adjustment
		
		# Mouse cursor hovering over button?
		self._hover_up = False
		self._hover_down = False
		
		# Left mouse button state:
		self._button_left_down = False
		self._button_middle = False
		self._click_offset = 0
		self._is_drag = False
		
		# Track whether page-wise stepping on left-click-hold is in progress:
		self._paging = False
		
		# The overlay needs to be repositined on initial expose,
		# but not after a click (it would jump):
		self._initialize = True
		
		# Track clicking on overlay buttons:
		self._click_on_up_arrow = False
		self._click_on_down_arrow = False
		
		# For checking, whether the pointer is still outside on delayed overlay reset
		self._still_outside = True
		
		self._step = 10 # step size, set in apply_adjustment
		
		self._adjustment = adjustment
		self.apply_adjustment(adjustment)
		
	# Called if view changes adjustment:
	def apply_adjustment(self, adj):
		# adjustment: value, lower, upper, step_incr, page_incr, page_size
		self._indicator_fraction = adj.page_size / adj.upper
		self._indicator_start = adj.value * self._indicator_fraction
		self._step = adj.step_increment  * self._indicator_fraction
		self.queue_draw()
		
	# Common preparations, used by expose_vertical and expose_horizontal:
	def expose_common(self, event):
		# Create the cairo context
		cr = self.window.cairo_create()
		# Restrict Cairo to the exposed area; avoid extra work
		cr.rectangle(event.area.x, event.area.y,
		event.area.width, event.area.height)
		cr.clip()
		
		self._height = event.area.height
		self._width = event.area.width
		
		return cr

	# Redraw on expose events for vertical scrollbar:
	def expose_vertical(self, widget, event):
		# do what is common to vertical and horizontal
		cr = self.expose_common(event)
		
		self._indicator_size = self._height * self._indicator_fraction
		self._button_size = self._width - 2
		self.draw_vertical(cr, *self.window.get_size())
		
	# Redraw on expose events for horizontal scrollbar:
	def expose_horizontal(self, widget, event):
		# Do what is common to vertical and horizontal:
		cr = self.expose_common(event)
		
		self._indicator_size = self._width * self._indicator_fraction
		self._button_size = self._height - 2
		self.draw_horizontal(cr, *self.window.get_size())
		
	def enter_notify(self, widget, event):
		self.init_overlay(widget, event)
		self.adapt_cursor(event.y)
		self._still_outside = False
			
	# leave_notify for vertical scrollbar:
	def leave_notify_vertical(self, widget, event):
		# With GTK# 2.12.5 and Metacity (Ubuntu Gutsy), clicking on the 
		# DrawingArea triggers leave and enter before press and release.
		# So I have to check if this is really a leave.
		self._still_outside = False
		if event.x < 0 or event.x >= self._width or event.y < 0 or event.y >= self._height:
			self._still_outside = True
			gobject.timeout_add(self._reset_overlay_delay, self.reset_overlay)
	
	# leave_notify for horizontal scrollbar:
	def leave_notify_horizontal(self, widget, event):
		# With GTK# 2.12.5 and Metacity (Ubuntu Gutsy), clicking on the 
		# DrawingArea triggers leave and enter before press and release.
		# So I have to check if this is really a leave.
		if event.y < 0 or event.y >= self._width or event.y < 0 or event.x >= self._height:
			gobject.timeout_add(self._reset_overlay_delay, self.reset_overlay(event.y, event.x))
		
	def init_overlay (self, widget, event):
		self._show_arrows = True
		# Set initial position of the overlay:
		if self._initialize == True:
			if self._is_vertical == True:
				maximum = self._height - self._overlay_height
				self._overlay_pos = event.y - self._overlay_height / 2
			else:
				maximum = self._width - self._overlay_height
				self._overlay_pos = event.x - self._overlay_height / 2
				
			if self._overlay_pos < 0:
				self._overlay_pos = 0
			if self._overlay_pos > maximum:
				self._overlay_pos = maximum
			self._initialize = False
		
	def motion_notify(self, widget, event):
		# If this is a hint, then let's get all the necessary information, if not it's all we need:
		if event.is_hint:
			x, y, state = event.window.get_pointer()
		else:
			x = event.x
			y = event.y
			state = event.state
		
		# Set position of the overlay if no overlay arrow is pressed:
		if self._click_on_up_arrow == False and self._click_on_down_arrow == False:
			# Upper bound
			if event.y <= self._overlay_pos:
				self._overlay_pos = event.y
			# Lower bound
			if event.y >= self._overlay_pos + self._overlay_height:
				self._overlay_pos = event.y - self._overlay_height
			
		# Check if over up arrow, only if view indicator isn't all the way up
		if event.y <= self._overlay_pos + self._button_size and self._indicator_start > 0:
			self._hover_up = True
		else:
			self._hover_up = False
			
		# Check if over down arrow, only if view indicator isn't all the way down
		maximum = self._height - self._indicator_size
		if event.y >= self._overlay_pos + self._overlay_height - self._button_size and self._indicator_start != maximum:
			self._hover_down = True
		else:
			self._hover_down = False
					
		# Handle all cases with left button down:
		if self._button_left_down:
			self._hover_up = False
			self._hover_down = False
			
			# Notice a drag after threshold, so page-wise stepping can be stopped in repeat_page_up and repeat_page_down:
			if abs(self._y_on_click - event.y) >= self._drag_threshold:
				self._is_drag = True
				
				# If drag started from up/down button, stop stepping and start sliding:
				if self._click_on_up_arrow == True or self._click_on_down_arrow == True:
					self._click_on_up_arrow = False
					self._click_on_down_arrow = False
					# Reset click offset, as if button was clicked again
					self._click_offset = 0 - self._indicator_start + event.y
					self.adapt_cursor(event.y)
		
			# Set value if left mouse button is down, no arrow is pressed, no paging via click-hold is in progress
			if self._click_on_up_arrow == False and self._click_on_down_arrow == False and self._paging == False:
				maximum = self._adjustment.upper - self._adjustment.page_size
				new_value = (event.y - self._click_offset) / self._indicator_fraction
				if new_value < maximum:
					self._adjustment.set_value(new_value)
					# Reset offset to have no dead mouse-way after minimum has been reached and the pointer moves down again:
					if self._adjustment.value == 0:
						self._click_offset = 0 - self._indicator_start + event.y
				else:
					self._adjustment.set_value(maximum)
					# Reset offset to have no dead mouse-way after maximum has been reached and the pointer moves up again:
					self._click_offset = 0 - self._indicator_start + event.y
			
			# Change cursor if only one direction is possible:
			# (This doesn't work if the pointer passes the upper or lower edge before the indicator reaches min or max.)
			if self._adjustment.value < 1: # Only down:	
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_DOWN_ARROW))
			#elif self._adjustment.value + self._adjustment.page_increment +1 >= self._adjustment.upper: # Only up:
			elif event.y - self._click_offset + self._indicator_size >= self._height:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_UP_ARROW))
			else:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.DOUBLE_ARROW))
			
				
		else:
			# Change cursor if hovering over arrows, but not if left button is held down.
			# This avoids a cursor change while repeating on up/down button and moving the pointer just a bit:
			self.adapt_cursor(event.y)
			
				
		# Set value if middle button is down, centered:
		if self._button_middle == True and self._button_left_down == False:
			maximum = self._adjustment.upper - self._adjustment.page_size
			new_value = event.y / self._indicator_fraction - self._adjustment.page_size / 2
			if new_value < maximum:
				self._adjustment.set_value(new_value)
			else:
				self._adjustment.set_value(maximum)
			
			# Center overlay:
			self._overlay_pos = event.y - self._overlay_height / 2
		
		# Trigger redraw / expose:
		self.queue_draw()
									 
	def button_press(self, widget, event):
		if event.button == 1:
			self._y_on_click = event.y
			self._button_left_down = True
			self._click_offset = 0 - self._indicator_start + event.y
			
			# Up arrow?
			if self._hover_up == True:
				self._click_on_up_arrow = True
				step_up(self._adjustment, False)
				gobject.timeout_add(self._arrow_repeat_delay, self.repeat_arrows)
			# Down arrow?
			elif self._hover_down == True:
				self._click_on_down_arrow = True
				step_down(self._adjustment, False)
				gobject.timeout_add(self._arrow_repeat_delay, self.repeat_arrows)
			# Outside overlay buttons
			else:
				# Above indicator?
				if event.y < self._indicator_start:
					gobject.timeout_add(self._paging_delay, self.repeat_page_up)
				# Below indicator?
				if event.y > self._indicator_start + self._indicator_size:
					gobject.timeout_add(self._paging_delay, self.repeat_page_down)
		
		# Make indicator jump to pointer on middle click, but only if left button isn't down:
		if event.button == 2:
			self._button_middle = True
			if self._button_left_down == False:
				self._show_arrows = False
				# Set value such that mouse pointer is in the middle of the page:
				maximum = self._adjustment.upper - self._adjustment.page_size
				new_value = event.y / self._indicator_fraction - self._adjustment.page_size / 2
				if new_value < maximum:
					self._adjustment.set_value(new_value)
				else:
					self._adjustment.set_value(maximum)
				
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
					
		# Context menu:
		if event.button == 3:
			self._show_arrows = False
			menu = gtk.Menu()
			one = gtk.MenuItem("Top")
			two = gtk.MenuItem("Bottom")
			menu.append(one)
			menu.append(two)	
			one.connect("activate", self.jump_top)
			two.connect("activate", self.jump_bottom)
			
			# Disable items if they can't have an effect
			if self._adjustment.value == 0:
				one.set_sensitive(False)
				
			if self._adjustment.value == self._adjustment.upper - self._adjustment.page_size:
				two.set_sensitive(False)
				
			self._initialize = True
				
			menu.show_all()
			menu.popup(None, None, None, event.button, event.get_time())
			
		self.queue_draw() # Trigger redraw / expose
			
	def button_release(self, widget, event):
		# Click was not on up or down button:
		if self._click_on_up_arrow == False and self._click_on_down_arrow == False:
			# Cause overlay to be repositioned:
			self._initialize = True
			self.init_overlay(widget, event)
			
			indicator_middle = self._indicator_start + self._indicator_size / 2
			
			# Page up after click (no drag) placed above indicator:
			if event.y < indicator_middle and self._is_drag == False:
				if self._adjustment.value - self._adjustment.page_increment <= 0:
					self._adjustment.value = 0
				else:
					self._adjustment.value = self._adjustment.value - self._adjustment.page_increment
					
			# Page down after click (no drag) placed below indicator:
			if event.y > indicator_middle and self._is_drag == False:
				maximum = self._adjustment.upper - self._adjustment.page_size
				if self._adjustment.value + self._adjustment.page_increment > maximum:
					self._adjustment.value = maximum
				else:
					self._adjustment.value = self._adjustment.value + self._adjustment.page_increment
			
		if event.button == 1:
			self._button_left_down = False
			self._click_on_up_arrow = False
			self._click_on_down_arrow = False
			self._paging_up = False
			self._paging_down = False
			self._is_drag = False
			
		if event.button == 2:
			self._button_middle = False
			# Force reset cursor. Without setting hover_up/down False,
			# this wouldn't work if the middle click happened while hovering a button:
			self._hover_up = False
			self._hover_down = False
			self.adapt_cursor(event.y)
		
		self._show_arrows = True
		
		self.queue_draw() # Trigger redraw / expose
			
	# Repeat action while up/down arrow is held:
	def repeat_arrows (self):
		if self._click_on_up_arrow == True and self._is_drag == False:
			step_up(self._adjustment, False)
			self.queue_draw() # Trigger redraw / expose
			
			# Change cursor if the start has been reached:
			if self._adjustment.value == 0:	
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_TEE))
				
			return True # Run again
		elif self._click_on_down_arrow == True and self._is_drag == False:
			step_down(self._adjustment, False)
			self.queue_draw() # Trigger redraw / expose
			
			# Change cursor if the end has been reached:
			if self._adjustment.value == self._adjustment.upper - self._adjustment.page_size:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.BOTTOM_TEE))
				
			return True # Run again
		else:
			return False # Stop
			
	# Page up for repeat action in button_press:
	def repeat_page_up (self):
		if self._button_left_down == True and self._is_drag == False:
			old_value = self._adjustment.value
			self._paging = True
			if self._adjustment.value - self._adjustment.page_increment <= 0:
				self._adjustment.value = 0
				# Change cursor to imply this is the end:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_TEE))
			else:
				self._adjustment.value = self._adjustment.value - self._adjustment.page_increment
				# The fingerpointing hand cursor seems to be a better match for paging:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_UP_ARROW))
				
			# Correct offset to avoid jump on switching from stepping to sliding:
			self._click_offset =  self._click_offset - (self._adjustment.value - old_value) * self._indicator_fraction
			
			return True	# Run again
		else:
			self._paging = False
			
			# The cursor needs to be reset:
			self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.DOUBLE_ARROW))
			
			return False # Stop
			
	# Page down for repeat action in button_press:
	def repeat_page_down (self):
		if self._button_left_down == True and self._is_drag == False:
			old_value = self._adjustment.value
			self._paging = True
			maximum = self._adjustment.upper - self._adjustment.page_size
			if self._adjustment.value + self._adjustment.page_increment > maximum:
				self._adjustment.value = maximum
				# Change cursor to imply this is the end:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.BOTTOM_TEE))
			else:
				self._adjustment.value = self._adjustment.value + self._adjustment.page_increment
				# The fingerpointing hand cursor seems to be a better match for paging:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_DOWN_ARROW))
				
			# Correct offset to avoid jump on switching from stepping to sliding:
			self._click_offset =  self._click_offset - (self._adjustment.value - old_value) * self._indicator_fraction
				
			return True # run again
		else:		
			self._paging = False
			
			# The cursor needs to be reset:
			self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.DOUBLE_ARROW))
			
			return False # stop
			
	def on_button_scroll_event(self, widget, event):
		on_button_scroll_event(self._adjustment, event)
		
	# Move indicator to the top, called from context menu:
	def jump_top(self, widget):
		self._adjustment.set_value(0)
		self.queue_draw() # Trigger redraw / expose
		
	# Move indicator to the bottom, called from context menu:
	def jump_bottom(self, widget):
		self._adjustment.set_value(self._adjustment.upper - self._adjustment.page_size)
		self.queue_draw() # Trigger redraw / expose
	
	# Draw stuff and finally adjust adjustment.value for vertical scrollbars:
	def draw_vertical(self, cr, width, height):	
		# Diff between gradient start and stop
		gradient_diff = 0.1
		
		# Set everything that depends on this being a vertical scrollbar:
		gradient_x2 = self._width
		gradient_y2 = 0
		
		indicator_x = 1
		indicator_y = self._indicator_start
		indicator_width = self._width -2
		indicator_height = self._indicator_size
		
		button_backward_x = 1
		button_backward_y = self._overlay_pos
		
		button_forward_x = 1
		button_forward_y = self._overlay_pos + self._overlay_height - self._button_size
		
		# Background gradient:
		alpha = 1.0
		# context, gtk-element, gtk-state, gradient-diff, x, y, x, y
		self.set_source_gradient(cr, "bg", "ACTIVE", gradient_diff, 0.0, 0.0, gradient_x2, gradient_y2, alpha)
		cr.rectangle(0, 0, self._width, self._height)
		cr.fill()
		
		# Outline:
		self.set_source_color(cr, "dark", "NORMAL", alpha)
		cr.set_line_width(1)
		cr.rectangle(0, 0, self._width, self._height)
		cr.stroke()
		
		# Draw shadow, always black:
		cr.set_source_rgba(0, 0, 0, 0.3)
		cr.set_line_width(1)
		cr.set_line_cap(0)
		cr.set_line_join(0)
		cr.move_to(1, 1)
		cr.line_to(1, self._height -2)
		cr.move_to(1, 1)
		cr.line_to(self._width -1, 1)
		cr.stroke()
		
		# Lower alpha if cursor is not over up button:
		if self._hover_up == True:
			alpha_up = 1.0
		else:
			alpha_up = 0.5
			
		# Lower alpha if cursor is not over down button:
		if self._hover_down == True:
			alpha_down = 1.0
		else:
			alpha_down = 0.5
			
		# Prepare line drawing:
		cr.set_line_width(3)
		cr.set_line_cap(1)
		cr.set_line_join(1)
		
		# Draw overlay base:
		if self._show_arrows == True and self._button_left_down == False:
			
			# Up area, if view indicator is not all the way up
			if self._indicator_start > 0:
				# Base
				pat = cairo.LinearGradient (0.0, 0.0, gradient_x2, gradient_y2)
				pat.add_color_stop_rgba (0, 1, 1, 1, 1) # Left stop: position, rgba
				pat.add_color_stop_rgba (1, 1, 1, 1, 0) # Right stop: position, rgba
				cr.set_source(pat)
				
				self.set_source_color(cr, "bg", "NORMAL", alpha_up)
				cr.rectangle(button_backward_x, 1, self._button_size, button_backward_y + self._button_size)
				cr.fill()
				
				# Arrow lines starting from bottom left:
				self.set_source_color(cr, "text", "NORMAL", alpha_up)
				self.draw_up_arrow(cr, width)
			
			# Down area, if view indicator is not all the way down:
			maximum = self._height - self._indicator_size
			if self._indicator_start != maximum:
				# Down, base
				self.set_source_color(cr, "bg", "NORMAL", alpha_down)
				cr.rectangle(button_forward_x, button_forward_y, self._button_size, self._height - button_forward_y - 1)
				cr.fill()
				
				# Down, arrow lines starting from bottom left:
				self.set_source_color(cr, "text", "NORMAL", alpha_down)
				self.draw_down_arrow(cr, width)
			
				
		# Indicator	base gradient
		self.set_source_gradient(cr, "bg", "SELECTED", gradient_diff, 0.0, 0.0, gradient_x2, gradient_y2, self.get_button_alpha())
		
		# Indicator triangle.
		# Draw a little triangle if the indicator becomes so thin that it's hard to make out:
		tri_height = width / 2
		if self._indicator_size < 4:
			tri_v_center = self._indicator_start + self._indicator_size / 2
			tri_v_top = tri_v_center - tri_height / 2
			
			cr.move_to(2, tri_v_top)
			cr.rel_line_to(tri_height, tri_height / 2)
			cr.rel_line_to(- tri_height, tri_height / 2)
			cr.rel_line_to(0, - tri_height)
			cr.fill()
		
		# Indicator rectangle:
		cr.rectangle(indicator_x, indicator_y, indicator_width , indicator_height)
		cr.fill()
		
		# Draw overlay arrows:
		if self._show_arrows == True and self._button_left_down == False:
			
			# Up arrow, if view indicator is not all the way up:
			if self._indicator_start > 0:
				self.set_source_color(cr, "text", "NORMAL", alpha_up)
				self.draw_up_arrow(cr, width)
			
			# Down arrow, if view indicator is not all the way down:
			maximum = self._height - self._indicator_size
			if self._indicator_start != maximum:
				self.set_source_color(cr, "text", "NORMAL", alpha_down)
				self.draw_down_arrow(cr, width)
			
		
		# Draw up button clicked:
		if self._click_on_up_arrow == True:
			# Base
			self.set_source_color(cr, "text", "NORMAL", alpha_up)
			cr.rectangle(1, self._overlay_pos, self._button_size, width)
			cr.fill()
			
			# Up: Draw arrow lines starting from bottom left
			self.set_source_color(cr, "bg", "NORMAL", 1.0)
			self.draw_up_arrow(cr, width)
			
		# Draw down button clicked:
		if self._click_on_down_arrow == True:
			# Base 
			self.set_source_color(cr, "text", "NORMAL", alpha_down)
			cr.rectangle(1, self._overlay_pos + self._overlay_height - self._button_size, self._button_size, width)
			cr.fill()
			
			# Down: Draw arrow lines starting from bottom left
			self.set_source_color(cr, "bg", "NORMAL", alpha_down)
			self.draw_down_arrow(cr, width)
		
		# Update adjustment value:
		self._adjustment.set_value(self._indicator_start / self._indicator_fraction)
		
			
	# Draw stuff and finally adjust adjustment.value for horizontal scrollbars:
	def draw_horizontal(self, cr, width, height):	
		# Diff between gradient start and stop
		gradient_diff = 0.1
		
		# Set everything that depends on this being a horizontal scrollbar:
		gradient_x2 = 0
		gradient_y2 = self._height
		
		indicator_x = self._indicator_start
		indicator_y = 1
		indicator_width = self._indicator_size
		indicator_height = self._height -2
		
		button_backward_x = self._overlay_pos
		button_backward_y = 1
		
		button_forward_x = self._overlay_pos + self._overlay_height - self._button_size
		button_forward_y = 1
		
		# Background gradient:
		alpha = 1.0
		# context, gtk-element, gtk-state, gradient-diff, x, y, x, y
		self.set_source_gradient(cr, "bg", "ACTIVE", gradient_diff, 0.0, 0.0, gradient_x2, gradient_y2, alpha)
		cr.rectangle(0, 0, self._width, self._height)
		cr.fill()
		
		# Outline
		self.set_source_color(cr, "dark", "NORMAL", alpha)
		cr.set_line_width(1)
		cr.rectangle(0, 0, self._width, self._height)
		cr.stroke()
		
		# Indicator base gradient
		self.set_source_gradient(cr, "bg", "SELECTED", gradient_diff, 0.0, 0.0, gradient_x2, gradient_y2, self.get_button_alpha())
		
		# Indicator triangle.
		# Draw a little triangle if the indicator becomes so thin that it's hard to make out:
		tri_height = width / 2
		if self._indicator_size < 4:
			tri_v_center = self._indicator_start + self._indicator_size / 2
			tri_v_top = tri_v_center - tri_height / 2
			
			cr.move_to(2, tri_v_top)
			cr.rel_line_to(tri_height, tri_height / 2)
			cr.rel_line_to(- tri_height, tri_height / 2)
			cr.rel_line_to(0, - tri_height)
			cr.fill()
		
		# Indicator rectangle:
		cr.rectangle(indicator_x, indicator_y, indicator_width , indicator_height)
		cr.fill()
		
		# Draw shadow, always black:
		cr.set_source_rgba(0, 0, 0, 0.3)
		cr.set_line_width(1)
		cr.set_line_cap(0)
		cr.set_line_join(0)
		cr.move_to(1, 1)
		cr.line_to(1, self._height -2)
		cr.move_to(1, 1)
		cr.line_to(self._width -1, 1)
		cr.stroke()
		
		# Lower alpha if cursor is not over up button:
		if self._hover_up == True:
			alpha_up = 1.0
		else:
			alpha_up = 0.5
			
		# Lower alpha if cursor is not over down button:
		if self._hover_down == True:
			alpha_down = 1.0
		else:
			alpha_down = 0.5
			
		# Prepare line drawing:
		cr.set_line_width(3)
		cr.set_line_cap(1)
		cr.set_line_join(1)
		
		# Draw overlay buttons:
		if self._show_arrows == True and self._button_left_down == False:
			
			# Backward button, if view indicator is not at start:
			if self._indicator_start > 0:
				# Base
				self.set_source_color(cr, "bg", "NORMAL", alpha_up)
				cr.rectangle(button_backward_x, button_backward_y, self._button_size, self._button_size)
				cr.fill()
				
				# Arrow lines starting from bottom left:
				self.set_source_color(cr, "text", "NORMAL", alpha_up)
				self.draw_up_arrow(cr, width)
			
			# Forward button, if view indicator is not at end:
			maximum = self._height - self._indicator_size
			if self._indicator_start != maximum:
				# Base:
				#self.set_source_color(cr, "bg", "NORMAL", alpha_down)
				self.set_source_color(cr, "text", "NORMAL", 1.0)
				cr.rectangle(button_forward_x, button_forward_y, self._button_size, self._button_size)
				cr.fill()
				
				# Arrow lines starting from bottom left:
				self.set_source_color(cr, "text", "NORMAL", alpha_down)
				self.draw_down_arrow(cr, width)
		
		# Draw up button clicked:
		if self._click_on_up_arrow == True:
			# Base:
			self.set_source_color(cr, "text", "NORMAL", alpha_up)
			cr.rectangle(1, self._overlay_pos, self._button_size, width)
			cr.fill()
			
			# Up: Draw arrow lines starting from bottom left:
			self.set_source_color(cr, "bg", "NORMAL", 1.0)
			self.draw_up_arrow(cr, width)
			
		# Draw down button clicked
		if self._click_on_down_arrow == True:
			# Base 
			self.set_source_color(cr, "text", "NORMAL", alpha_down)
			cr.rectangle(1, self._overlay_pos + self._overlay_height - self._button_size, self._button_size, width)
			cr.fill()
			
			# Down: Draw arrow lines starting from bottom left
			self.set_source_color(cr, "bg", "NORMAL", alpha_down)
			self.draw_down_arrow(cr, width)
		
		# update adjustment value:
		self._adjustment.set_value(self._indicator_start / self._indicator_fraction)
		
	def get_button_alpha(self):
		# Lower alpha if cursor is over buttons or outside:
		if self._hover_up == False and self._hover_down == False:
			alpha = 1.0
		else:
			alpha = 0.75
		
		if self._initialize == True:
			alpha = 0.75
			
		# High alpha while button is down (be it dragging or holding):
		if self._button_left_down == True:
			alpha = 1.0
			
		return alpha
		
	# Up: Draw arrow lines starting from bottom left:
	def draw_up_arrow (self, context, width):
		context.move_to(self._arrow_padding + 1, self._overlay_pos + self._button_size - self._arrow_padding)
		context.line_to(width / 2, self._overlay_pos + self._arrow_padding)
		context.rel_line_to(self._button_size / 2 - self._arrow_padding, self._button_size - 2 * self._arrow_padding)
		context.stroke()
		
	# Down: Draw arrow lines starting from top left:
	def draw_down_arrow (self, context, width):
		context.move_to(self._arrow_padding + 1, self._overlay_pos + self._overlay_height - self._button_size + self._arrow_padding)
		context.line_to(width / 2, self._overlay_pos + self._overlay_height - self._arrow_padding)
		context.rel_line_to(self._button_size / 2 - self._arrow_padding, - (self._button_size - 2 * self._arrow_padding))
		context.stroke()
	
	# Get GTK theme color:
	def get_color (self, element, state):
		style = self.get_style()
		rgb_gtk = getattr(style, element)[getattr(gtk, "STATE_" + state)]
		r = rgb_gtk.red / 65535
		g = rgb_gtk.green / 65535
		b = rgb_gtk.blue / 65535
		rgb_cairo = [r, g, b]
		return rgb_cairo
		
	def set_source_color (self, context, element, state, alpha):
		rgb = self.get_color(element, state)
		context.set_source_rgba(rgb[0], rgb[1], rgb[2], alpha)
		
	def set_source_gradient (self, context, element, state, gradient_diff, x1, y1, x2, y2, alpha):
		rgb = self.get_color(element, state)
		r1 = rgb[0] - gradient_diff / 2
		g1 = rgb[1] - gradient_diff / 2
		b1 = rgb[2] - gradient_diff / 2
		r2 = rgb[0] + gradient_diff / 2
		g2 = rgb[1] + gradient_diff / 2
		b2 = rgb[2] + gradient_diff / 2
		
		pat = cairo.LinearGradient (x1, y1, x2, y2)
		pat.add_color_stop_rgba (0, r1, g1, b1, alpha) # Left stop: position, rgba
		pat.add_color_stop_rgba (1, r2, g2, b2, alpha) # Right stop: position, rgba
		context.set_source(pat)
		
	def adapt_cursor (self, event_y):
		if self._hover_up == True or self._hover_down == True:
			self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
		else:
			# Indicate possible directions via cursor:
			if self._adjustment.value < 1:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_DOWN_ARROW))
			#elif event_y - self._click_offset + self._indicator_size >= self._height:
			elif self._indicator_start + self._indicator_size >= self._height:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.SB_UP_ARROW))
			else:
				self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.DOUBLE_ARROW))
					
	def reset_overlay (self):
		if self._still_outside == True:
			self._initialize = True
			self._show_arrows = False
		self.queue_draw()
		return False
		
# My own class just to add scroll-wheel handling to ScrolledWindow with scrollbars hidden:
class WheeledWindow(gtk.ScrolledWindow):
	def __init__(self, adj_v, adj_h):
		super(WheeledWindow, self).__init__()
		
		# gtk.Widget signals
		self.connect("scroll_event", self.on_button_scroll_event)

	def on_button_scroll_event(self, widget, event):
		on_button_scroll_event(self.get_vadjustment(), event)
		
def main():
	# Create a textview and load this script into it:
	textview = gtk.TextView()
	textview.set_editable(True)
	this_script = os.path.join(sys.path[0], sys.argv[0])
	textfile = open(this_script, "r")
	buffer = textview.get_buffer()
	buffer.set_text(textfile.read())
	textfile.close()
	
	# Use a ScrolledWindow as this gives us an Adjustment (textview doesn't).
	# Could use a Viewport, except it doesn't trigger value_changed on cursor movement.
	# Have to wrap ScrolledWindow to add scroll-wheel handling, as else it doesn't work with policy NONE.
	view = WheeledWindow(None, None)
	view.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
	view.set_shadow_type(gtk.SHADOW_NONE)
	view.add(textview)
	
	# Connect view and scroller:
	adjustment_v = view.get_vadjustment()
	# adjustment_h = view.get_hadjustment()
	scroller_v = dynScrollbar(adjustment_v, True) # is_vertical == True
	# scroller_h = dynScrollbar(adjustment_h, False)
	adjustment_v.connect("changed", scroller_v.apply_adjustment)
	# adjustment_h.connect("changed", scroller_h.apply_adjustment)
	# Cursor movement doesn't trigger "changed", so we also need:
	adjustment_v.connect("value_changed", scroller_v.apply_adjustment)
	# adjustment_h.connect("value_changed", scroller_h.apply_adjustment)
	
	# Layout:
	window = gtk.Window()
	# table = gtk.Table(2, 2) # Rows, Columns
	table = gtk.Table(1, 2)
	window.add(table)
	table.attach(view, 0, 1, 0, 1)
	table.attach(scroller_v, 1, 2, 0, 1, xoptions=0)
	# table.attach(scroller_h, 0, 1, 1, 2, yoptions=0)
	
	# Take care of widths and heights:
	scroller_v.set_size_request(16, 60)
	# scroller_h.set_size_request(500, 16)
	view.set_size_request(500, 150)
	window.resize(516, 416)
	
	# Showtime!
	window.connect("destroy", gtk.main_quit)
	window.realize()
	window.show_all()	
	gtk.main()

# Scrolling via wheel on either scrollbar or textview:
def on_button_scroll_event(adj, event):
	if event.direction == gtk.gdk.SCROLL_UP:
		step_up(adj, True)
	if event.direction == gtk.gdk.SCROLL_DOWN:
		step_down(adj, True)
	
# Move up a step (unless at start already):
def step_up (adj, multiply):
	if adj.value - adj.step_increment * multiplicator(multiply) <= 0:
		adj.value = 0
	else:
		adj.value = adj.value - adj.step_increment * multiplicator(multiply)

# Move down a step (unless at bottom already):
def step_down (adj, multiply):
	maximum = adj.upper - adj.page_size
	if adj.value + adj.step_increment * multiplicator(multiply) > maximum:
		adj.value = maximum
	else:
		adj.value = adj.value + adj.step_increment * multiplicator(multiply)
		
# Multiplicator for scroll-wheel only:
def multiplicator(multiply):
	if multiply == False:
		return 1
	else:
		return 4
			 
if __name__ == "__main__":
    main()
    
 # A line that only exists to be very long without any meaningful content that could lend any insight into a topic that isn't even specified, since this is just a line that only exists to be very long without any meaningful content that could lend any insight into a topic that isn't even specified.

