/* knob.c */

/* Volume.app -- a simple volume control
 *
 * Copyright (C) 2000
 *	Daniel Richard G. <skunk@mit.edu>,
 *	timecop <timecop@japan.co.jp>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#include <X11/xpm.h>
#include <X11/cursorfont.h>

#include "common.h"
#include "mixer.h"
#include "knob.h"

#ifndef PI
#define PI 3.14159265358979323846
#endif

/* Knob pixmaps
 */
#include "knob.xpm"
#define KNOB_WIDTH 48
#define KNOB_HEIGHT 48
#include "led-on.xpm"
#include "led-off.xpm"
#define LED_WIDTH 6
#define LED_HEIGHT 6
#define LED_POS_RADIUS 12

#define WINDOW_NAME "Volume"

static Display	*display;
static Pixmap	knob_pixmap;
static Pixmap	knob_mask;
static GC	knob_gc;
static Window	knob_win;
static Window	knob_iconwin;
static Pixmap	led_on_pixmap;
static Pixmap	led_on_mask;
static Pixmap	led_off_pixmap;
static Pixmap	led_off_mask;
static float	prev_drawn_volume = -1.0;
static Cursor	hand_cursor;
static Cursor	null_cursor;

/* This routine shamelessly nabbed from Mark Martin's unclutter-0.8
 */
static Cursor
create_null_cursor(Display *x_display)
{
	Pixmap cursor_mask;
	XGCValues gcvals;
	GC gc;
	XColor dummy_color;
	Cursor cursor;

	cursor_mask = XCreatePixmap(x_display, DefaultRootWindow(x_display), 1, 1, 1);
	gcvals.function = GXclear;
	gc = XCreateGC(x_display, cursor_mask, GCFunction, &gcvals);
	XFillRectangle(x_display, cursor_mask, gc, 0, 0, 1, 1);
	dummy_color.pixel = 0;
	dummy_color.red = 0;
	dummy_color.flags = 04;
	cursor = XCreatePixmapCursor(
		x_display,
		cursor_mask,
		cursor_mask,
		&dummy_color,
		&dummy_color,
		0, 0);
	XFreePixmap(x_display, cursor_mask);
	XFreeGC(x_display, gc);

	return cursor;
}

void
knob_init(Display *x_display)
{
	char *name = WINDOW_NAME;
	Pixel black;
	XSizeHints sizehints;
	XGCValues gcvals;
	XClassHint classhint;
	XTextProperty wname;
	XpmAttributes xpmattrs;
	XWMHints wmhints;

	display = x_display;

	black = BlackPixel(display, DefaultScreen(display));

	sizehints.flags = USSize | USPosition;
	sizehints.x = 0;
	sizehints.y = 0;
	sizehints.width = KNOB_WIDTH;
	sizehints.height = KNOB_HEIGHT;
	knob_win = XCreateSimpleWindow(
		display,
		DefaultRootWindow(display),
		sizehints.x, sizehints.y,
		sizehints.width, sizehints.height,
		1,
		black,
		black);
	knob_iconwin = XCreateSimpleWindow(
		display,
		knob_win,
		sizehints.x, sizehints.y,
		sizehints.width, sizehints.height,
		1,
		black,
		black);
	XSetWMNormalHints(display, knob_win, &sizehints);

	classhint.res_name = name;
	classhint.res_class = name;
	XSetClassHint(display, knob_win, &classhint);

#define INPUT_MASK \
	ExposureMask \
	| ButtonPressMask \
	| ButtonReleaseMask \
	| PointerMotionMask \
	| StructureNotifyMask

	XSelectInput(display, knob_win, INPUT_MASK);
	XSelectInput(display, knob_iconwin, INPUT_MASK);

#undef INPUT_MASK

	XStringListToTextProperty(&name, 1, &wname);
	XSetWMName(display, knob_win, &wname);

	gcvals.background = black;
	gcvals.foreground = black;
	gcvals.graphics_exposures = 0;
	knob_gc = XCreateGC(
		display,
		knob_win,
		GCBackground | GCForeground | GCGraphicsExposures,
		&gcvals);

	xpmattrs.exactColors = 0;
	xpmattrs.alloc_close_colors = 1;
	xpmattrs.closeness = 30000;
	xpmattrs.valuemask = XpmExactColors | XpmAllocCloseColors | XpmCloseness;
	if ((XpmCreatePixmapFromData(
		display,
		DefaultRootWindow(display),
		knob_xpm,
		&knob_pixmap,
		&knob_mask,
		&xpmattrs) != XpmSuccess) ||
	    (XpmCreatePixmapFromData(
		display,
		DefaultRootWindow(display),
		led_on_xpm,
		&led_on_pixmap,
		&led_on_mask,
		&xpmattrs) != XpmSuccess) ||
	    (XpmCreatePixmapFromData(
		display,
		DefaultRootWindow(display),
		led_off_xpm,
		&led_off_pixmap,
		&led_off_mask,
		&xpmattrs) != XpmSuccess))
	{
		fputs("Cannot allocate colors for pixmaps\n", stderr);
		exit(EXIT_FAILURE);
	}

	XShapeCombineMask(
		display,
		knob_win,
		ShapeBounding,
		0, 0,
		knob_mask,
		ShapeSet);
	XShapeCombineMask(
		display,
		knob_iconwin,
		ShapeBounding,
		0, 0,
		knob_mask,
		ShapeSet);

	wmhints.initial_state = WithdrawnState;
	wmhints.icon_window = knob_iconwin;
	wmhints.icon_x = sizehints.x;
	wmhints.icon_y = sizehints.y;
	wmhints.window_group = knob_win;
	wmhints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
	XSetWMHints(display, knob_win, &wmhints);

	hand_cursor = XCreateFontCursor(display, XC_hand2);
	null_cursor = create_null_cursor(display);
	knob_release();

	XMapWindow(display, knob_win);
}

/* Helper function for draw_knob()
 */
static void
draw_knob_aux(float volume, Window w)
{
	float bearing, led_x, led_y;
	int led_topleft_x, led_topleft_y;
	Pixmap led_pixmap, led_mask;
	XEvent dummy;

	/* Calculate LED position
	 */
	bearing = (1.25 * PI) - (1.5 * PI) * volume;
	led_x = (KNOB_WIDTH / 2.0) + LED_POS_RADIUS * cos(bearing);
	led_y = (KNOB_HEIGHT / 2.0) - LED_POS_RADIUS * sin(bearing);
	led_topleft_x = (int)(led_x - (LED_WIDTH / 2.0) + 0.5);
	led_topleft_y = (int)(led_y - (LED_HEIGHT / 2.0) + 0.5);

	if (mixer_is_muted()) {
		led_pixmap = led_off_pixmap;
		led_mask = led_off_mask;
	} else {
		led_pixmap = led_on_pixmap;
		led_mask = led_on_mask;
	}

	/* Draw base knob
	 */
	XCopyArea(
		display,
		knob_pixmap,
		w,
		knob_gc,
		0, 0,
		KNOB_WIDTH, KNOB_HEIGHT,
		0, 0);

	/* Draw the LED
	 */
	XSetClipOrigin(display, knob_gc, led_topleft_x, led_topleft_y);
	XSetClipMask(display, knob_gc, led_mask);
	XCopyArea(
		display,
		led_pixmap,
		w,
		knob_gc,
		0, 0,
		LED_WIDTH, LED_HEIGHT,
		led_topleft_x, led_topleft_y);
	XSetClipMask(display, knob_gc, None);

	/* Clear any queued expose events for the window
	 */
	while (XCheckTypedWindowEvent(display, w, Expose, &dummy)) ;
}

static void
draw_knob(float volume)
{
	draw_knob_aux(volume, knob_win);
	draw_knob_aux(volume, knob_iconwin);
	XFlush(display);

	prev_drawn_volume = volume;
}

void
knob_redraw(void)
{
	draw_knob(prev_drawn_volume);
}

void
knob_grab(void)
{
	XDefineCursor(display, knob_win, null_cursor);
	XDefineCursor(display, knob_iconwin, null_cursor);
}

void
knob_turn(float delta)
{
	mixer_set_volume_rel(delta);
	draw_knob(mixer_get_volume());
}

void
knob_release(void)
{
	XDefineCursor(display, knob_win, hand_cursor);
	XDefineCursor(display, knob_iconwin, hand_cursor);
}

void
knob_update(void)
{
	float volume = mixer_get_volume();
	if (volume != prev_drawn_volume)
		draw_knob(volume);
}

void
knob_toggle_mute(void)
{
	mixer_toggle_mute();
	knob_redraw();
}

/* end knob.c */
