From f13b7d9281dcdcc802bf4fd20f5caee7284844d9 Mon Sep 17 00:00:00 2001 From: cgroom Date: Wed, 10 Dec 2003 07:27:00 +0000 Subject: [PATCH] Add full HSV color model. Fix how ID3 tags are being read to prevent seg fault. Remove Debian dir per madduck's request. --- Makefile | 1 + changelog | 6 + constants.h | 4 +- debian/README.Debian | 14 -- debian/changelog | 97 --------- debian/control | 24 --- debian/copyright | 26 --- debian/dirs | 3 - debian/docs | 2 - debian/menu | 2 - debian/rules | 73 ------- gjay.c | 2 +- playlist.c | 31 ++- prefs.c | 45 ++++- prefs.h | 6 +- songs.c | 64 ++++-- songs.h | 24 +-- ui.c | 158 ++++++++++++++- ui.h | 21 +- ui_colorbox.c | 92 +++++++++ ui_colorwheel.c | 463 +++++++++++++++++++++++++++++-------------- ui_playlist_view.c | 150 +++++++------- ui_prefs_view.c | 2 + ui_selection_view.c | 75 ++++++- 24 files changed, 849 insertions(+), 536 deletions(-) delete mode 100644 debian/README.Debian delete mode 100644 debian/changelog delete mode 100644 debian/control delete mode 100644 debian/copyright delete mode 100644 debian/dirs delete mode 100644 debian/docs delete mode 100644 debian/menu delete mode 100755 debian/rules create mode 100644 ui_colorbox.c diff --git a/Makefile b/Makefile index b0488b4..8edc982 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ OBJECTS = \ ui_selection_view.o \ ui_playlist_view.o \ ui_colorwheel.o \ + ui_colorbox.o \ ui_menubar.o \ gjay_xmms.o \ analysis.o \ diff --git a/changelog b/changelog index d4f535d..6b05224 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,9 @@ +0.2.8 - December 2003 +-Bugs: + - Some (evil) ID3 tags were not readable and caused a core dump. + (Debian bug 222195) +-UI: + - Support full HSV model for song color selection. 0.2.7 - November 2003 - Bugs: - (Provided by Giacomo Catenazzi) verify opendir() diff --git a/constants.h b/constants.h index 4b9419a..e73c9c4 100644 --- a/constants.h +++ b/constants.h @@ -19,10 +19,10 @@ #define SELECT_RADIUS 3 /* Values */ -#define MAX_FREQ_VAL 10.0 -#define MAX_VOL_DIFF 10.0 #define MIN_CRITERIA 0.1 #define MAX_CRITERIA 10.0 +#define MAX_FREQ_VAL 10.0 +#define MAX_VOL_DIFF 10.0 #define MIN_RATING 0.0 #define MAX_RATING 5.0 #define MIN_BPM 100.0 diff --git a/debian/README.Debian b/debian/README.Debian deleted file mode 100644 index 275ccb5..0000000 --- a/debian/README.Debian +++ /dev/null @@ -1,14 +0,0 @@ -gjay for Debian ---------------- - -Everything about the application has changed. There is a completely different -usage paradigm; you simply select a base directory for your music, GJay does -the rest. There are fewer windows that pop up. - -GJay now runs in two processes, one for UI and the other for analysis such -that the analysis process can be detached/reattached. - -The fileformat was completely redone to make it more robust and -human-readable. - - -- martin f. krafft , Sun, 8 Sep 2002 15:29:34 +0100 diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index f27810f..0000000 --- a/debian/changelog +++ /dev/null @@ -1,97 +0,0 @@ -gjay (0.2.7-1) unstable; urgency=low - - * New upstream release (closes: #183668, #187249, #191794, #183545) - * Made vorbis a soft dependency - * Faster and better MP3 id3 tag and length scan - - -- Chuck Groom Sun, 16 Nov 2003 22:56:50 -0800 - -gjay (0.2.6-2) unstable; urgency=low - - * Allowed mpg123 to satisfy the dependency of the MP3 player as well, next - to mpg321 (which is still favoured) (closes: Bug#180151). - * Changed dependency of libvorbis0 to libvorbis0a. - - -- martin f. krafft Sat, 5 Jul 2003 14:38:40 +0200 - -gjay (0.2.6-1) unstable; urgency=low - - * New upstream release (closes: Bug#176580). - - -- martin f. krafft Wed, 29 Jan 2003 21:08:43 +0100 - -gjay (0.2.5-1) unstable; urgency=low - - * New upstream release. - - -- martin f. krafft Sat, 11 Jan 2003 12:26:15 +0100 - -gjay (0.2.4-1) unstable; urgency=low - - * New upstream release (closes: Bug#167930, Bug#170486, Bug#171905) - - -- martin f. krafft Mon, 16 Dec 2002 18:55:39 +0100 - -gjay (0.2.3-2) unstable; urgency=low - - * Updated the Standards-Version to 3.5.8 to conform with Debian Policy. - - -- martin f. krafft Wed, 4 Dec 2002 10:44:43 +0100 - -gjay (0.2.3-1) unstable; urgency=low - - * New upstream release (closes: Bug#167931) - - -- martin f. krafft Wed, 4 Dec 2002 10:06:59 +0100 - -gjay (0.2.2-1) unstable; urgency=low - - * New upstream release (closes: Bug#170418, Bug#170486) - - -- martin f. krafft Mon, 25 Nov 2002 09:03:49 +0100 - -gjay (0.2-1) unstable; urgency=low - - * New upstream release (see changelog.gz, there were many changes). - - -- martin f. krafft Sun, 22 Nov 2002 12:52:21 +0100 - -gjay (0.1.4-1) unstable; urgency=low - - * New upstream release. - - -- martin f. krafft Sun, 22 Sep 2002 21:44:21 +0200 - -gjay (0.1.3-2) unstable; urgency=low - - * Fixed a problem in debian/rules (binary-indep vs. binary-arch) that - prevented builds on other architectures than i386. I was stupid. - - -- martin f. krafft Thu, 12 Sep 2002 00:17:24 +0200 - -gjay (0.1.3-1) unstable; urgency=low - - * New upstream release (closes #160481) - * Excluded the two *.orig files from dh_clean deletion (debian/rules) - - -- martin f. krafft Wed, 11 Sep 2002 17:33:32 +0200 - -gjay (0.1.1-2) unstable; urgency=low - - * Cosmetic changes to the packaging. Nothing more. - * Added a README.Debian file to hint at the big endian problems. - - -- martin f. krafft Sun, 8 Sep 2002 19:29:11 +0200 - -gjay (0.1.1-1) unstable; urgency=low - - * New upstream release. - - -- martin f. krafft Wed, 4 Sep 2002 01:09:37 +0200 - -gjay (0.1-1) unstable; urgency=low - - * Initial Release. - - -- martin f. krafft Wed, 4 Sep 2002 00:02:08 +0200 - diff --git a/debian/control b/debian/control deleted file mode 100644 index f48a83e..0000000 --- a/debian/control +++ /dev/null @@ -1,24 +0,0 @@ -Source: gjay -Section: sound -Priority: extra -Maintainer: martin f. krafft -Build-Depends: debhelper (>> 4.0.0), libgtk2.0-dev, xmms-dev, libgsl0-dev -Standards-Version: 3.5.9 - -Package: gjay -Architecture: any -Depends: ${shlibs:Depends}, mp3info, mpg321 | mpg123 -Recommends: vorbis-tools (>> 1.0.0) -Description: An automatic and learning DJ for xmms - GJay (Gtk+ DJ) generates playlists across a collection of music (mp3, ogg, - wav) such that each song sounds good following the previous song. Matches are - based on both automatically analyzed song characteristics (BPM, frequency) as - well as user-assigned categorizations (song 'color' and rating). It is ideal - for DJs planning a set list or home users who want a non-random way to wander - large collections. - . - All you have to do is tell GJay the base directory where you store your music - files. It will then queue every file in the directory for analysis. Analysis - is done in a separate process. When you quit GJay, you can choose to let this - analysis process continue in the background. You can also choose to run GJay - as a daemon, without any user interface. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 7b457ed..0000000 --- a/debian/copyright +++ /dev/null @@ -1,26 +0,0 @@ -This package was debianized by martin f. krafft on -Wed, 4 Sep 2002 00:02:08 +0200. - -It was downloaded from http://gjay.sourceforge.net - -Upstream Author: Chuck Groom - -Copyright: - - This package 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; version 2 dated June, 1991. - - This package 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 package; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. - -On Debian GNU/Linux systems, the complete text of the GNU General -Public License can be found in `/usr/share/common-licenses/GPL'. - diff --git a/debian/dirs b/debian/dirs deleted file mode 100644 index 25c3c2b..0000000 --- a/debian/dirs +++ /dev/null @@ -1,3 +0,0 @@ -usr/bin -usr/share/gjay/icons -usr/share/man/man1 diff --git a/debian/docs b/debian/docs deleted file mode 100644 index 724e084..0000000 --- a/debian/docs +++ /dev/null @@ -1,2 +0,0 @@ -README -TODO diff --git a/debian/menu b/debian/menu deleted file mode 100644 index dbdec1e..0000000 --- a/debian/menu +++ /dev/null @@ -1,2 +0,0 @@ -?package(gjay):needs=X11 section=Apps/Sound\ - title="GJay" command="/usr/bin/gjay" diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 2c13130..0000000 --- a/debian/rules +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/make -f -# Sample debian/rules that uses debhelper. -# GNU copyright 1997 to 1999 by Joey Hess. - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -# This is the debhelper compatibility version to use. -export DH_COMPAT=3 - - - -ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) - CFLAGS += -g -endif -ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) - INSTALL_PROGRAM += -s -endif - -configure: configure-stamp -configure-stamp: - dh_testdir - - touch configure-stamp - - -build: build-stamp - -build-stamp: configure-stamp - dh_testdir - - $(MAKE) - - touch build-stamp - -clean: - dh_testdir - dh_testroot - rm -f build-stamp configure-stamp - - $(MAKE) clean - - dh_clean -XMakefile.orig -Xspectrum.c.orig - -install: build - dh_testdir - dh_testroot - dh_clean -XMakefile.orig -Xspectrum.c.orig -k - dh_installdirs - - $(MAKE) install PREFIX=$(CURDIR)/debian/gjay/usr - -binary-indep: build install - -binary-arch: build install - dh_testdir - dh_testroot - dh_installdocs - dh_installmenu - dh_installman doc/gjay.1 - dh_installchangelogs changelog - dh_link - dh_strip - dh_compress - dh_fixperms - dh_installdeb - dh_shlibdeps - dh_gencontrol - dh_md5sums - dh_builddeb - -binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/gjay.c b/gjay.c index 59460c3..45d4a8d 100644 --- a/gjay.c +++ b/gjay.c @@ -115,7 +115,7 @@ int main( int argc, char *argv[] ) { } else if (get_named_color(buffer, &rgb)) { prefs.start_color = TRUE; } - prefs.color = hsv_to_hb(rgb_to_hsv(rgb)); + prefs.color = rgb_to_hsv(rgb); i++; } else { fprintf(stderr, "Usage: -c color, where color is a hex number in the form 0xRRGGBB or a color name:\n%s\n", known_colors()); diff --git a/playlist.c b/playlist.c index 9349ea7..1868896 100644 --- a/playlist.c +++ b/playlist.c @@ -29,7 +29,6 @@ static song * current; static song * first; -static gdouble distance ( song * a, song * b ); static void remove_repeats ( song * s, GList * list); /* How much does brightness factor into matching two songs? */ @@ -47,9 +46,8 @@ static void remove_repeats ( song * s, GList * list); GList * generate_playlist ( guint minutes ) { GList * working, * final, * rand_list, * list; gint i, list_time, l, r, max_force_index, len; - gdouble min_distance, s_distance; gdouble max_force, s_force; - song * s, * repeat; + song * s; time_t t; list_time = 0; @@ -112,26 +110,25 @@ GList * generate_playlist ( guint minutes ) { gchar * latin1; latin1 = strdup_to_latin1((char *) selected_files->data); fprintf(stderr, "File '%s' not found in data file;\n"\ - "perhaps it has not been analyzed. Using different starting song.\n", + "perhaps it has not been analyzed. Using random starting song.\n", latin1); g_free(latin1); } } - if (prefs.start_color || (prefs.start_selected && !first)) { - for (min_distance = 0, list = g_list_first(working); list; + if (prefs.start_color) { + song temp_song; + bzero(&temp_song, sizeof(song)); + temp_song.no_data = TRUE; + temp_song.no_rating = TRUE; + temp_song.color = prefs.color; + for (max_force = -1000, list = g_list_first(working); + list; list = g_list_next(list)) { s = SONG(list); - if (!s->no_color) { - s_distance = - MIN(fabs(prefs.color.H - s->color.H), - ((MIN(prefs.color.H, s->color.H) + 2*M_PI) - - (MAX(prefs.color.H, s->color.H)) + - BRIGHTNESS_FACTOR * fabs(prefs.color.B - s->color.B))); - if ((s_distance < min_distance) || - ((s_distance == min_distance) && (rand() % 2))) { - min_distance = s_distance; - first = SONG(list); - } + s_force = song_force(&temp_song, s); + if (s_force > max_force) { + max_force = s_force; + first = s; } } } diff --git a/prefs.c b/prefs.c index a7b5192..4e51cd3 100644 --- a/prefs.c +++ b/prefs.c @@ -11,11 +11,12 @@ * float * ... * ... + * ... * ... * ... * ... * ... - * float float + * float float float * * * This program is free software; you can redistribute it and/or @@ -52,6 +53,7 @@ typedef enum { PE_RATING, PE_HUE, PE_BRIGHTNESS, + PE_SATURATION, PE_BPM, PE_FREQ, PE_VARIANCE, @@ -65,11 +67,12 @@ typedef enum { PE_CUTOFF, PE_VERSION, PE_USE_RATINGS, + PE_TYPE, /* values */ PE_RANDOM, PE_SELECTED, PE_SONGS, - PE_DIR, + PE_DIR, PE_LAST } pref_element_type; @@ -84,6 +87,7 @@ char * pref_element_strs[PE_LAST] = { "rating", "hue", "brightness", + "saturation", "bpm", "freq", "variance", @@ -96,6 +100,7 @@ char * pref_element_strs[PE_LAST] = { "cutoff", "version", "use_ratings", + "type", "random", "selected", "songs", @@ -139,9 +144,12 @@ void load_prefs ( void ) { prefs.bpm = prefs.freq = prefs.path_weight = DEFAULT_CRITERIA; + prefs.saturation = 1; prefs.extension_filter = TRUE; prefs.color.H = 0; - prefs.color.B = 0.5; + prefs.color.S = 0.5; + prefs.color.V = 1.0; + prefs.use_hsv = FALSE; prefs.daemon_action = PREF_DAEMON_QUIT; prefs.hide_tips = FALSE; snprintf(buffer, BUFFER_SIZE, "%s/%s/%s", getenv("HOME"), @@ -225,10 +233,12 @@ void save_prefs ( void ) { prefs.time, pref_element_strs[PE_TIME]); - fprintf(f, "<%s>%f %f\n", + fprintf(f, "<%s %s=\"hsv\">%f %f %f\n", pref_element_strs[PE_COLOR], + pref_element_strs[PE_TYPE], prefs.color.H, - prefs.color.B, + prefs.color.S, + prefs.color.V, pref_element_strs[PE_COLOR]); if (prefs.rating_cutoff) { @@ -255,6 +265,11 @@ void save_prefs ( void ) { pref_element_strs[PE_BRIGHTNESS], prefs.brightness, pref_element_strs[PE_BRIGHTNESS]); + + fprintf(f, "<%s>%f\n", + pref_element_strs[PE_SATURATION], + prefs.saturation, + pref_element_strs[PE_SATURATION]); fprintf(f, "<%s>%f\n", pref_element_strs[PE_BPM], @@ -295,6 +310,11 @@ void data_start_element (GMarkupParseContext *context, for (k = 0; attribute_names[k]; k++) { attr = get_element((char *) attribute_names[k]); switch(attr) { + case PE_TYPE: + if (strcasecmp(attribute_values[k], "hsv") == 0) { + prefs.use_hsv = TRUE; + } + break; case PE_EXTENSION_FILTER: if (*element == PE_ROOTDIR) prefs.extension_filter = TRUE; @@ -371,6 +391,9 @@ void data_text ( GMarkupParseContext *context, case PE_BRIGHTNESS: prefs.brightness = strtof_gjay(buffer, NULL); break; + case PE_SATURATION: + prefs.saturation = strtof_gjay(buffer, NULL); + break; case PE_BPM: prefs.bpm = strtof_gjay(buffer, NULL); break; @@ -384,8 +407,16 @@ void data_text ( GMarkupParseContext *context, prefs.path_weight = strtof_gjay(buffer, NULL); break; case PE_COLOR: - prefs.color.H = strtof_gjay(buffer, &buffer_str); - prefs.color.B = strtof_gjay(buffer_str, NULL); + if (prefs.use_hsv) { + prefs.color.H = strtof_gjay(buffer, &buffer_str); + prefs.color.S = strtof_gjay(buffer_str, &buffer_str); + prefs.color.V = strtof_gjay(buffer_str, NULL); + } else { + HB hb; + hb.H = strtof_gjay(buffer, &buffer_str); + hb.B = strtof_gjay(buffer_str, NULL); + prefs.color = hb_to_hsv(hb); + } break; case PE_TIME: prefs.time = atoi(buffer); diff --git a/prefs.h b/prefs.h index 4af358a..2d39e8f 100644 --- a/prefs.h +++ b/prefs.h @@ -57,12 +57,16 @@ typedef struct { float variance; float hue; float brightness; + float saturation; float bpm; float freq; float rating; float path_weight; - HB color; + HSV color; + + /* Store how a color was loaded */ + gboolean use_hsv; } app_prefs; diff --git a/songs.c b/songs.c index e2bc7df..e5862bf 100644 --- a/songs.c +++ b/songs.c @@ -50,6 +50,7 @@ typedef enum { E_NOT_SONG, E_REPEATS, E_VOL_DIFF, + E_TYPE, E_VERSION, E_LAST } element_type; @@ -71,6 +72,7 @@ static const char * element_str[E_LAST] = { "not_song", "repeats", "volume_diff", + "type", "version" }; @@ -78,6 +80,7 @@ static const char * element_str[E_LAST] = { typedef struct { gboolean is_repeat; gboolean not_song; + gboolean use_hb; gboolean new; element_type element; song * s; @@ -224,7 +227,7 @@ void song_set_color_pixbuf ( song * s) { COLOR_IMAGE_HEIGHT); rowstride = gdk_pixbuf_get_rowstride(s->color_pixbuf); data = gdk_pixbuf_get_pixels (s->color_pixbuf); - rgb = hsv_to_rgb(hb_to_hsv(s->color)); + rgb = hsv_to_rgb(s->color); r = rgb.R * 255; g = rgb.G * 255; b = rgb.B * 255; @@ -429,7 +432,7 @@ int append_daemon_file (song * s) { * int * int * float - * float float + * float float float * float float... * float * @@ -485,9 +488,10 @@ static void write_song_data (FILE * f, song * s) { if (!s->no_rating) fprintf(f, "\t%f\n", s->rating); if (!s->no_color) - fprintf(f, "\t%f %f\n", + fprintf(f, "\t%f %f %f\n", s->color.H, - s->color.B); + s->color.S, + s->color.V); } fprintf(f, "\n"); } @@ -643,6 +647,16 @@ void data_start_element (GMarkupParseContext *context, state->s->no_rating = FALSE; break; case E_COLOR: + state->use_hb = TRUE; + for (k = 0; attribute_names[k]; k++) { + switch(get_element((char *) attribute_names[k])) { + case E_TYPE: + if (strcasecmp(attribute_values[k], "hsv") == 0) { + state->use_hb = FALSE; + } + break; + } + } state->s->no_color = FALSE; break; case E_FREQ: @@ -748,8 +762,16 @@ void data_text ( GMarkupParseContext *context, } break; case E_COLOR: - state->s->color.H = strtof_gjay(buffer, &buffer_str); - state->s->color.B = strtof_gjay(buffer_str, NULL); + if (state->use_hb) { + HB hb; + hb.H = strtof_gjay(buffer, &buffer_str); + hb.B = strtof_gjay(buffer_str, NULL); + state->s->color = hb_to_hsv(hb); + } else { + state->s->color.H = strtof_gjay(buffer, &buffer_str); + state->s->color.S = strtof_gjay(buffer_str, &buffer_str); + state->s->color.V = strtof_gjay(buffer_str, NULL); + } break; case E_FREQ: buffer_str = buffer; @@ -892,13 +914,14 @@ gdouble song_force ( song * a, song * b ) { /* Attraction is a value -1...1 for the affinity between A and B, with criteria weighed by prefs */ gdouble song_attraction ( song * a, song * b ) { - gdouble a_hue, a_brightness, a_freq, a_bpm, a_path; + gdouble a_hue, a_saturation, a_brightness, a_freq, a_bpm, a_path; gdouble d, ba, bb, v_diff, a_max, attraction = 0; gint i; a_max = (prefs.hue + prefs.brightness + + prefs.saturation + prefs.freq + prefs.bpm + prefs.path_weight); @@ -910,18 +933,21 @@ gdouble song_attraction ( song * a, song * b ) { a_path = prefs.path_weight / a_max; if (!(a->no_color || b->no_color)) { - d = fabsl(a->color.H - b->color.H); - if (d > M_PI) { - d = 2.0 * M_PI - d; + /* Hue is 0...6 */ + d = fabsl(a->color.H - b->color.H) / 6.0; + if (d > 0.5) { + d = 1 - d; } - /* d is 0...PI, where 0 is more similiar */ - d *= (2.0 / M_PI); - /* d is 0...2 */ - d = 1.0 - d; - /* d is -1...1, where 1 is more similiar */ - attraction += d * a_hue; - - d = 1.0 - fabsl(a->color.B - b->color.B) * 2.0; + /* d is 0...1, where 0 is more similiar */ + d = 1.0 - d*2.0; + /* d is now -1...1, where 1 is more similiar */ + attraction += d * a_hue; + + d = 1.0 - fabsl(a->color.S - b->color.S) * 2.0; + /* d is -1 ... 1, where 1 is more similiar*/ + attraction += d * a_saturation; + + d = 1.0 - fabsl(a->color.V - b->color.V) * 2.0; /* d is -1 ... 1, where 1 is more similiar*/ attraction += d * a_brightness; } @@ -962,7 +988,7 @@ gdouble song_attraction ( song * a, song * b ) { attraction += d * a_freq; } - if (tree_depth) { + if (tree_depth && a->path && b->path) { d = explore_files_depth_distance(a->path, b->path); if (d >= 0) { d = 1.0 - 2.0 * (d / tree_depth); diff --git a/songs.h b/songs.h index ca91e43..26f7e2a 100644 --- a/songs.h +++ b/songs.h @@ -51,21 +51,21 @@ extern GHashTable * not_song_hash; struct _song { /* Characteristics (don't change). Strings are UTF8 encoded. */ - char * path; - char * fname; /* Pointer within path */ - char * title; - char * artist; - char * album; - gdouble bpm; + char * path; + char * fname; /* Pointer within path */ + char * title; + char * artist; + char * album; + gdouble bpm; gboolean bpm_undef; - gdouble freq[NUM_FREQ_SAMPLES]; - gdouble volume_diff; /* % difference in volume between loudest and - average frame */ - gint length; /* Song length, in sec */ + gdouble freq[NUM_FREQ_SAMPLES]; + gdouble volume_diff; /* % difference in volume between loudest and + average frame */ + gint length; /* Song length, in sec */ /* Attributes (user-defined values that may change) */ - HB color; - gdouble rating; + HSV color; + gdouble rating; /* Meta-info */ gboolean no_data; /* Characteristics not set */ diff --git a/ui.c b/ui.c index cb40a31..a76ee05 100644 --- a/ui.c +++ b/ui.c @@ -26,6 +26,7 @@ static GtkWidget * prefs_window, * about_window; static GtkWidget * paned; static GtkWidget * msg_window = NULL; static GtkWidget * msg_text_view = NULL; +static GtkWidget * plugin_pane[1]; static gboolean destroy_window_flag = FALSE; GdkPixbuf * pixbufs[PM_LAST]; @@ -61,7 +62,8 @@ static void respond_quit_analysis ( GtkDialog *dialog, gint arg1, gpointer user_data ); static void destroy_app ( void); - +// REMOVE +GtkWidget * plugin_new (void); GtkWidget * make_app_ui ( void ) { GtkWidget * vbox1, * hbox1, * hbox2; @@ -129,6 +131,13 @@ GtkWidget * make_app_ui ( void ) { playlist_hbox, gtk_label_new(tabs[TAB_PLAYLIST])); + // REMOVE +#if GRAV + plugin_pane[0] = plugin_new(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), + plugin_pane[0], + gtk_label_new("Gravity")); +#endif explore_view = make_explore_view(); playlist_view = make_playlist_view(); selection_view = make_selection_view(); @@ -367,6 +376,10 @@ void switch_page (GtkNotebook *notebook, gtk_widget_show(paned); gtk_widget_show(playlist_hbox); break; + default: + /* Plug-ins */ + gtk_widget_show(plugin_pane[page_num - TAB_LAST]); + break; } } @@ -542,3 +555,146 @@ void show_prefs_window( void ) { void hide_prefs_window( void ) { gtk_widget_hide(prefs_window); } + +#if GRAV +typedef struct { + song * s; + gdouble x, y, vx, vy; +} grav_song; +gboolean gravity_running = FALSE; +GList * gravity_list = NULL; + +gboolean expose_gravity (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) { + gint width, height, w, h; + GdkGC * gc; + RGB rgb; + GdkFont * font; + gc = gdk_gc_new(window->window); + gdk_rgb_gc_set_foreground(gc, 0xFFFFFF); + gdk_gc_set_clip_rectangle (gc, + &event->area); + gdk_draw_rectangle (widget->window, + gc, + TRUE, + 0, 0, + width, height); + if (gravity_running) { + gdouble m1, m2, a, d, f, dx, dy, fx, fy; + guchar r, g, b; + GList * llist1, * llist2; + grav_song * g1, * g2; + for (llist1 = g_list_first(gravity_list); + llist1; + llist1 = g_list_next(llist1)) { + g1 = llist1->data; + + for (llist2 = g_list_next(llist1); + llist2; + llist2 = g_list_next(llist2)) { + g2 = llist2->data; + a = song_attraction(g1->s, g2->s); + m1 = song_mass(g1->s); + m2 = song_mass(g2->s); + dx = g2->x - g1->x; + dy = g2->y - g1->y; + + f = m1 * m2 * (1.0 / (fabs(dx) + fabs(dy))) * a * 1000.0; + if (dx == 0) { + fx = 0; + fy = (dy > 0) ? f : -f; + } else if (dy == 0) { + fx = (dx > 0) ? f : -f; + fy = 0; + } else { + float theta = atan(dy / dx); + if ((dx < 0 && dy < 0) || + (dx < 0 && dy > 0)){ +// theta += M_PI; + } else { + theta += M_PI; + } + fx = f * cos (theta); + fy = f * sin (theta); + } + g1->vx -= fx; + g1->vy -= fy; + g2->vx += fx; + g2->vy += fy; + } + g1->vx *= 0.95; + g1->vy *= 0.95; + if (g1->vx < 0) { + g1->vx = MAX(g1->vx, -10.0); + } else { + g1->vx = MIN(g1->vx, 10.0); + } + if (g1->vy < 0) { + g1->vy = MAX(g1->vy, -10.0); + } else { + g1->vy = MIN(g1->vy, 10.0); + } + g1->x += g1->vx; + g1->y += g1->vy; + rgb = hsv_to_rgb(g1->s->color); + r = rgb.R * 255; + g = rgb.G * 255; + b = rgb.B * 255; + gdk_rgb_gc_set_foreground(gc, + r << 16 | g << 8 | b); + gdk_draw_rectangle (widget->window, + gc, + TRUE, + (int) g1->x, (int) g1->y, 2, 2); + } + } +} + +gint gravity_timeout(gpointer user_data) { + gtk_widget_queue_draw(GTK_WIDGET(user_data)); + return TRUE; +} + +void start_gravity ( GtkButton *button, + gpointer user_data ) { + GList * llist; + + gravity_running = TRUE; + for (llist = g_list_first(songs); llist; llist = g_list_next(llist)) { + grav_song * g = malloc(sizeof(grav_song)); + bzero(g, sizeof(grav_song)); + g->s = llist->data; + g->x = rand() % 500; + g->y = rand() % 400; + gravity_list = g_list_append(gravity_list, g); + } + gtk_timeout_add(500, gravity_timeout, user_data); +} + + + +GtkWidget * plugin_new (void) { + GtkWidget * hbox, * button, * drawing; + hbox = gtk_hbox_new(FALSE, 2); + + button = gtk_button_new_with_label("Start"); + drawing = gtk_drawing_area_new(); + gtk_widget_set_usize(drawing, 500, 400); + gtk_signal_connect (GTK_OBJECT (drawing), + "expose_event", + (GtkSignalFunc) expose_gravity, + NULL); + + g_signal_connect (G_OBJECT (button), + "clicked", + G_CALLBACK (start_gravity), + drawing); + + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(hbox), drawing, FALSE, FALSE, 5); + + gtk_widget_show_all(hbox); + return hbox; +} +#endif diff --git a/ui.h b/ui.h index fc8c444..d418f15 100644 --- a/ui.h +++ b/ui.h @@ -34,6 +34,10 @@ #define COLOR_IMAGE_HEIGHT FREQ_IMAGE_HEIGHT #define COLORWHEEL_DIAMETER 150 #define COLORWHEEL_SELECT 3 +#define COLORWHEEL_SPACING 5 +#define COLORWHEEL_V_SWATCH_WIDTH 0.2 +#define COLORWHEEL_V_HEIGHT 0.7 +#define COLORWHEEL_SWATCH_HEIGHT 0.2 typedef enum { @@ -133,12 +137,21 @@ void set_selected_file ( char * file, gboolean is_dir ); void update_selection_area ( void ); void set_selected_in_playlist_view ( gboolean in_view ); -void update_selected_songs_color ( gdouble angle, - gdouble radius ); +void set_selected_rating_visible ( gboolean is_visible ); + +/* Colorwheel widget (in select file pane) */ GtkWidget * create_colorwheel ( gint diameter, GList ** list, - HB * color ); -void set_selected_rating_visible ( gboolean is_visible ); + GFunc change_func, + gpointer user_data); +HSV get_colorwheel_color ( GtkWidget * colorwheel ); +void set_colorwheel_color ( GtkWidget * colorwheel, + HSV color ); + +/* Color box */ +GtkWidget * create_colorbox ( HSV * hsv ); +void set_colorbox_color ( GtkWidget * widget, + HSV hsv ); /* Playlist */ void set_playlist_rating_visible ( gboolean is_visible ); diff --git a/ui_colorbox.c b/ui_colorbox.c new file mode 100644 index 0000000..e0ff906 --- /dev/null +++ b/ui_colorbox.c @@ -0,0 +1,92 @@ +/** + * GJay, copyright (c) 2002, 2003 Chuck Groom + * + * 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 1, 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 + */ + +#include "gjay.h" +#include "ui.h" + +static gchar * data_color = "cb_color"; +static gboolean expose_event_callback (GtkWidget *widget, + GdkEventExpose *event, + gpointer data); + +GtkWidget * create_colorbox (HSV * hsv) { + HSV * color; + GtkWidget * widget; + + widget = gtk_drawing_area_new(); + color = g_malloc(sizeof(HSV)); + if (hsv) { + *color = *hsv; + } else { + color->H = 0; + color->S = 1; + color->V = 1; + } + g_object_set_data(G_OBJECT (widget), data_color, color); + + g_signal_connect (G_OBJECT (widget), "expose_event", + G_CALLBACK (expose_event_callback), NULL); + + return widget; +} + + +void set_colorbox_color (GtkWidget * widget, + HSV hsv) { + *((HSV *) g_object_get_data(G_OBJECT (widget), data_color)) = hsv; + gtk_widget_queue_draw(widget); +} + + +static gboolean expose_event_callback (GtkWidget *widget, + GdkEventExpose *event, + gpointer data) { + HSV * hsv; + RGB rgb; + guint32 rgb32 = 0; + GdkGC * black_gc, * color_gc; + + hsv = g_object_get_data(G_OBJECT (widget), data_color); + rgb = hsv_to_rgb(*hsv); + rgb32 = + ((int) (rgb.R * 255.0)) << 16 | + ((int) (rgb.G * 255.0)) << 8 | + ((int) (rgb.B * 255.0)); + black_gc = gdk_gc_new(widget->window); + color_gc = gdk_gc_new(widget->window); + gdk_rgb_gc_set_foreground(black_gc, 0); + gdk_rgb_gc_set_foreground(color_gc, rgb32); + + gdk_draw_rectangle (widget->window, + black_gc, + FALSE, + 0, 0, + widget->allocation.width - 1, + widget->allocation.height - 1); + gdk_draw_rectangle (widget->window, + color_gc, + TRUE, + 1, 1, + widget->allocation.width - 2, + widget->allocation.height - 2); + + gdk_gc_unref(black_gc); + gdk_gc_unref(color_gc); + return TRUE; +} diff --git a/ui_colorwheel.c b/ui_colorwheel.c index 41eac56..7759393 100644 --- a/ui_colorwheel.c +++ b/ui_colorwheel.c @@ -1,5 +1,5 @@ /** - * GJay, copyright (c) 2002 Chuck Groom + * GJay, copyright (c) 2002, 2003 Chuck Groom * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,13 +19,17 @@ #include +#include #include "gjay.h" #include "ui.h" -static GdkPixbuf * create_colorwheel_pixbuf( gint diameter ); +static GdkPixbuf * create_colorwheel_hs_pixbuf ( gint diameter ); +static GdkPixbuf * create_colorwheel_v_pixbuf ( gint width, + gint height ); static void colorwheel_plot_point ( guchar * data, - gfloat saturation, - gfloat hue, + HSV color, + gfloat radius, + gfloat angle, gint rowstride, gint x, gint y ); @@ -41,52 +45,114 @@ static gboolean drawing_motion_event_callback ( GtkWidget *widget, static void click_in_colorwheel (GtkWidget * widget, gdouble x, gdouble y); +static void draw_selected_color ( GtkWidget * widget, + HSV hsv ); +static void draw_swatch_color ( GtkWidget * widget, + HSV hsv ); - -static gchar * data_pixbuf = "cw_pb"; +static gchar * data_hs_pixbuf = "cw_hs_pb"; +static gchar * data_v_pixbuf = "cw_v_pb"; static gchar * data_list = "cw_list"; static gchar * data_color = "cw_color"; +static gchar * data_func = "cw_func"; +static gchar * data_user_data = "cw_user_data"; GtkWidget * create_colorwheel (gint diameter, GList ** list, - HB * color) { - GdkPixbuf * colorwheel = NULL; + GFunc change_func, + gpointer user_data) { + gint width, height, brightness_width; + GdkPixbuf * colorwheel = NULL, * brightness = NULL; GtkWidget * widget; + HSV * color; - colorwheel = create_colorwheel_pixbuf(diameter); - widget = gtk_drawing_area_new(); - gtk_drawing_area_size(GTK_DRAWING_AREA(widget), - diameter + COLORWHEEL_SELECT*2, - diameter + COLORWHEEL_SELECT*2); + brightness_width = diameter * COLORWHEEL_V_SWATCH_WIDTH; + width = diameter + COLORWHEEL_SELECT*4 + COLORWHEEL_SPACING + + brightness_width; + height = diameter + COLORWHEEL_SELECT*2; + + colorwheel = create_colorwheel_hs_pixbuf(diameter); + brightness = create_colorwheel_v_pixbuf(brightness_width, + diameter * COLORWHEEL_V_HEIGHT); + + gtk_drawing_area_size(GTK_DRAWING_AREA(widget), width, height); gtk_widget_add_events(widget, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK); - g_object_set_data(G_OBJECT (widget), data_pixbuf, colorwheel); + g_object_set_data(G_OBJECT (widget), data_hs_pixbuf, colorwheel); + g_object_set_data(G_OBJECT (widget), data_v_pixbuf, brightness); g_object_set_data(G_OBJECT (widget), data_list, list); + + color = g_malloc(sizeof(HSV)); + color->H = 0; + color->S = 1; + color->V = 1; g_object_set_data(G_OBJECT (widget), data_color, color); - + + g_object_set_data(G_OBJECT (widget), data_func, change_func); + g_object_set_data(G_OBJECT (widget), data_user_data, user_data); + g_signal_connect (G_OBJECT (widget), "expose_event", G_CALLBACK (drawing_expose_event_callback), NULL); g_signal_connect (G_OBJECT (widget), "button-press-event", G_CALLBACK (drawing_button_event_callback), NULL); g_signal_connect (G_OBJECT (widget), "motion-notify-event", G_CALLBACK (drawing_motion_event_callback), NULL); - return widget; } -static GdkPixbuf * create_colorwheel_pixbuf (gint diameter) { +HSV get_colorwheel_color ( GtkWidget * widget) { + return *((HSV *) g_object_get_data(G_OBJECT (widget), data_color)); +} + + +void set_colorwheel_color ( GtkWidget * widget, + HSV color) { + *((HSV *) g_object_get_data(G_OBJECT (widget), data_color)) = color; + gtk_widget_queue_draw(widget); +} + + +static GdkPixbuf * create_colorwheel_v_pixbuf (gint width, gint height) { + GdkPixbuf * pixbuf = NULL; + gint rowstride, x, y, v, offset; + guchar * data; + + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + width, height); + data = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride(pixbuf); + bzero (data, rowstride * height); + for (y = 0; y < height; y++) { + v = 255 - (y * 255) / height; + offset = y * rowstride; + for (x = 0; x < width; x++, offset += 4) { + memset(data + offset, v, 3); + data[offset + 3] = 255; + + } + } + return pixbuf; +} + + +static GdkPixbuf * create_colorwheel_hs_pixbuf (gint diameter) { GdkPixbuf * colorwheel = NULL; gint width, height, rowstride; float angle, percent_radius; + HSV hsv; guchar * data; gint x, y, draw_x, draw_y, p, xcenter, ycenter; + hsv.V = 1; width = height = diameter; xcenter = width / 2; ycenter = height / 2; + diameter -= 2; colorwheel = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, @@ -105,7 +171,7 @@ static GdkPixbuf * create_colorwheel_pixbuf (gint diameter) { percent_radius = (2.0 * sqrt (draw_x*draw_x + draw_y*draw_y)) / ((float) diameter); angle = atan2((double) draw_y, (double) draw_x); - colorwheel_plot_point(data, percent_radius, angle, + colorwheel_plot_point(data, hsv, percent_radius, angle, rowstride, draw_x + xcenter, draw_y + ycenter); } @@ -115,7 +181,7 @@ static GdkPixbuf * create_colorwheel_pixbuf (gint diameter) { percent_radius = (2.0 * sqrt (draw_x*draw_x + draw_y*draw_y)) / ((float) diameter); angle = atan2((double) draw_y, (double) draw_x); - colorwheel_plot_point(data, percent_radius, angle, rowstride, + colorwheel_plot_point(data, hsv, percent_radius, angle, rowstride, draw_x + xcenter, draw_y + ycenter); } @@ -124,7 +190,7 @@ static GdkPixbuf * create_colorwheel_pixbuf (gint diameter) { percent_radius = (2.0 * sqrt (draw_x*draw_x + draw_y*draw_y)) / ((float) diameter); angle = atan2((double) draw_y, (double) draw_x); - colorwheel_plot_point(data, percent_radius, angle, rowstride, + colorwheel_plot_point(data, hsv, percent_radius, angle, rowstride, draw_x + xcenter, draw_y + ycenter); } @@ -133,7 +199,7 @@ static GdkPixbuf * create_colorwheel_pixbuf (gint diameter) { percent_radius = (2.0 * sqrt (draw_x*draw_x + draw_y*draw_y)) / ((float) diameter); angle = atan2((double) draw_y, (double) draw_x); - colorwheel_plot_point(data, percent_radius, angle, rowstride, + colorwheel_plot_point(data, hsv, percent_radius, angle, rowstride, draw_x + xcenter, draw_y + ycenter); } if (p < 0) @@ -146,34 +212,31 @@ static GdkPixbuf * create_colorwheel_pixbuf (gint diameter) { static void colorwheel_plot_point ( guchar * data, - gfloat saturation, - gfloat hue, + HSV hsv, + gfloat radius, + gfloat angle, gint rowstride, gint x, gint y ) { int offset; - RGB rgb; - HB hb; - if (hue > 2*M_PI) { - hue -= 2*M_PI; - } else if (hue < 0) { - hue += 2*M_PI; + if (angle > 2*M_PI) { + angle -= 2*M_PI; + } else if (angle < 0) { + angle += 2*M_PI; } - hb.H = (3.0 * hue) / M_PI; - hb.B = MIN(1, saturation); - rgb = hsv_to_rgb (hb_to_hsv(hb)); - if (y == 150) - y--; - offset = rowstride * y + 4 * x; - data[offset] = MIN(255, rgb.R * 255); - data[offset+1] = MIN(255, rgb.G * 255); - data[offset+2] = MIN(255, rgb.B * 255); - if (saturation > 0.99) - data[offset+3] = 180; - else - data[offset+3] = 255; + /* Yep, folks -- hue is 0...6, not 0...2pi! */ + hsv.H = (angle / (2*M_PI)) * 6.0; + hsv.S = radius; + + hsv.V = 1; + rgb = hsv_to_rgb (hsv); + offset = rowstride * y + 4 * x; + data[offset] = MAX(0, MIN(255, rgb.R * 255)); + data[offset+1] = MAX(0, MIN(255, rgb.G * 255)); + data[offset+2] = MAX(0, MIN(255, rgb.B * 255)); + data[offset+3]=255; } @@ -182,21 +245,23 @@ static gboolean drawing_expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data) { GList * ll, ** list; - HB * hb; + HSV * hsv, prev_hsv; song * s; - float angle, radius, old_angle = -1, old_radius = -1; - gint xcenter, ycenter, x, y, width, height; - GdkPixbuf * colorwheel; - gboolean set = FALSE; - - colorwheel = g_object_get_data(G_OBJECT (widget), data_pixbuf); + gint xcenter, ycenter, width, height, num_colors; + GdkPixbuf * colorwheel, * brightness; + gboolean set; + + set = FALSE; + num_colors = 0; list = g_object_get_data(G_OBJECT (widget), data_list); - hb = g_object_get_data(G_OBJECT (widget), data_color); - - xcenter = widget->allocation.width / 2.0; - ycenter = widget->allocation.height / 2.0; - width = gdk_pixbuf_get_width(colorwheel); - height = gdk_pixbuf_get_height(colorwheel); + hsv = g_object_get_data(G_OBJECT (widget), data_color); + assert(hsv); + prev_hsv.H = -1; + prev_hsv.S = -1; + prev_hsv.V = -1; + + colorwheel = g_object_get_data(G_OBJECT (widget), data_hs_pixbuf); + brightness = g_object_get_data(G_OBJECT (widget), data_v_pixbuf); gdk_draw_rectangle (widget->window, widget->style->bg_gc[GTK_WIDGET_STATE (widget)], @@ -206,48 +271,58 @@ static gboolean drawing_expose_event_callback (GtkWidget *widget, event->area.width, event->area.height); - gdk_pixbuf_render_to_drawable_alpha(colorwheel, - widget->window, - 0, 0, - xcenter - width/2, - ycenter - height/2, - width, - height, - GDK_PIXBUF_ALPHA_FULL, - 127, - GDK_RGB_DITHER_NORMAL, - 0, 0); + width = gdk_pixbuf_get_width(brightness); + height = gdk_pixbuf_get_height(brightness); + gdk_pixbuf_render_to_drawable_alpha( + brightness, + widget->window, + 0, 0, + widget->allocation.width - width - COLORWHEEL_SELECT, + COLORWHEEL_SELECT, + width, + height, + GDK_PIXBUF_ALPHA_FULL, + 127, + GDK_RGB_DITHER_NORMAL, + 0, 0); + + width = gdk_pixbuf_get_width(colorwheel); + height = gdk_pixbuf_get_height(colorwheel); + xcenter = widget->allocation.height / 2.0; // Use height on purpose + ycenter = widget->allocation.height / 2.0; + gdk_pixbuf_render_to_drawable_alpha( + colorwheel, + widget->window, + 0, 0, + xcenter - width/2, + ycenter - height/2, + width, + height, + GDK_PIXBUF_ALPHA_FULL, + 127, + GDK_RGB_DITHER_NORMAL, + 0, 0); + if (list) { for (ll = g_list_first(selected_songs); ll; ll = g_list_next(ll)) { s = (song *) ll->data; if (!s->no_color) { set = TRUE; - angle = s->color.H; - radius = (s->color.B * gdk_pixbuf_get_width(colorwheel)) / 2.0; - if ((old_angle != angle) || (old_radius != radius)) { - x = radius * cos (angle); - y = radius * sin (angle); - - width = gdk_pixbuf_get_width(pixbufs[PM_COLOR_SEL]); - height = gdk_pixbuf_get_height(pixbufs[PM_COLOR_SEL]); - - gdk_pixbuf_render_to_drawable_alpha( - pixbufs[PM_COLOR_SEL], - widget->window, - 0, 0, - (xcenter + x) - width/2, (ycenter + y) - height/2, - width, - height, - GDK_PIXBUF_ALPHA_FULL, - 127, - GDK_RGB_DITHER_NORMAL, - 0, 0); - old_angle = angle; - old_radius = radius; - } + *hsv = s->color; + if (!((hsv->H == prev_hsv.H) && + (hsv->S == prev_hsv.S) && + (hsv->V == prev_hsv.V))) { + draw_selected_color(widget, s->color); + num_colors++; + prev_hsv = *hsv; + } } } - if (!set) { + if (set) { + if (num_colors == 1) { + draw_swatch_color(widget, *hsv); + } + } else { width = gdk_pixbuf_get_width(pixbufs[PM_NOT_SET]); height = gdk_pixbuf_get_height(pixbufs[PM_NOT_SET]); gdk_pixbuf_render_to_drawable_alpha( @@ -260,32 +335,108 @@ static gboolean drawing_expose_event_callback (GtkWidget *widget, 127, GDK_RGB_DITHER_NORMAL, 0, 0); + + // When the user does set a color, make it full brightness + hsv->V = 1; } - } else if (hb) { - angle = hb->H; - radius = (hb->B * gdk_pixbuf_get_width(colorwheel)) / 2.0; - x = radius * cos (angle); - y = radius * sin (angle); - - width = gdk_pixbuf_get_width(pixbufs[PM_COLOR_SEL]); - height = gdk_pixbuf_get_height(pixbufs[PM_COLOR_SEL]); - - gdk_pixbuf_render_to_drawable_alpha( - pixbufs[PM_COLOR_SEL], - widget->window, - 0, 0, - (xcenter + x) - width/2, (ycenter + y) - height/2, - width, - height, - GDK_PIXBUF_ALPHA_FULL, - 127, - GDK_RGB_DITHER_NORMAL, - 0, 0); + } else { + draw_selected_color(widget, *hsv); + draw_swatch_color(widget, *hsv); } return TRUE; } +void draw_selected_color (GtkWidget * widget, + HSV hsv) { + GdkGC * gc; + float angle, radius; + GdkPixbuf * colorwheel, * brightness; + gint xcenter, ycenter, x, y, width, height; + + gc = gdk_gc_new(widget->window); + gdk_rgb_gc_set_foreground(gc, 0); + + colorwheel = g_object_get_data(G_OBJECT (widget), data_hs_pixbuf); + brightness = g_object_get_data(G_OBJECT (widget), data_v_pixbuf); + + width = gdk_pixbuf_get_width(brightness); + height = gdk_pixbuf_get_height(brightness); + x = widget->allocation.width - + gdk_pixbuf_get_width(brightness) - + 2*COLORWHEEL_SELECT; + y = (1.0 - hsv.V) * (float) gdk_pixbuf_get_height(brightness); + gdk_draw_line( + widget->window, gc, + x, y + COLORWHEEL_SELECT, + widget->allocation.width, COLORWHEEL_SELECT + y); + + /* Hue is 0..6 */ + angle = (hsv.H / 3) * (M_PI); + radius = (hsv.S * gdk_pixbuf_get_width(colorwheel)) / 2.0; + x = radius * cos (angle); + y = radius * sin (angle); + + width = gdk_pixbuf_get_width(pixbufs[PM_COLOR_SEL]); + height = gdk_pixbuf_get_height(pixbufs[PM_COLOR_SEL]); + xcenter = widget->allocation.height / 2.0; // Use height on purpose + ycenter = widget->allocation.height / 2.0; + gdk_pixbuf_render_to_drawable_alpha( + pixbufs[PM_COLOR_SEL], + widget->window, + 0, 0, + (xcenter + x) - width/2, (ycenter + y) - height/2, + width, + height, + GDK_PIXBUF_ALPHA_FULL, + 127, + GDK_RGB_DITHER_NORMAL, + 0, 0); + gdk_gc_unref(gc); +} + + +void draw_swatch_color (GtkWidget * widget, + HSV hsv) { + GdkGC * black_gc, * color_gc; + RGB rgb; + guint32 rgb32 = 0; + gint width, height, x, y; + GdkPixbuf * brightness; + + brightness = g_object_get_data(G_OBJECT (widget), data_v_pixbuf); + + rgb = hsv_to_rgb(hsv); + rgb32 = + ((int) (rgb.R * 255.0)) << 16 | + ((int) (rgb.G * 255.0)) << 8 | + ((int) (rgb.B * 255.0)); + black_gc = gdk_gc_new(widget->window); + color_gc = gdk_gc_new(widget->window); + gdk_rgb_gc_set_foreground(black_gc, 0); + gdk_rgb_gc_set_foreground(color_gc, rgb32); + + width = gdk_pixbuf_get_width(brightness); + height = (widget->allocation.height - + 2*COLORWHEEL_SELECT) * COLORWHEEL_SWATCH_HEIGHT; + x = widget->allocation.width - + width - + COLORWHEEL_SELECT; + y = widget->allocation.height - COLORWHEEL_SELECT - height; + gdk_draw_rectangle (widget->window, + black_gc, + FALSE, + x, y, + width - 1, height - 1 ); + gdk_draw_rectangle (widget->window, + color_gc, + TRUE, + x + 1, y + 1, + width - 2, height - 2 ); + gdk_gc_unref(black_gc); + gdk_gc_unref(color_gc); +} + static gboolean drawing_button_event_callback (GtkWidget *widget, GdkEventButton *event, @@ -307,19 +458,36 @@ static gboolean drawing_motion_event_callback (GtkWidget *widget, static void click_in_colorwheel (GtkWidget * widget, gdouble x, gdouble y) { + gboolean updateHS = TRUE; + gboolean updateV = TRUE; gdouble radius, angle; gint xcenter, ycenter, diameter; - GdkPixbuf * colorwheel; + GdkPixbuf * colorwheel, * brightness; GList ** list; - HB * hb; + HSV * hsv; + GFunc change_func; + gpointer user_data; - colorwheel = g_object_get_data(G_OBJECT (widget), data_pixbuf); + colorwheel = g_object_get_data(G_OBJECT (widget), data_hs_pixbuf); + brightness = g_object_get_data(G_OBJECT (widget), data_v_pixbuf); list = g_object_get_data(G_OBJECT (widget), data_list); - hb = g_object_get_data(G_OBJECT (widget), data_color); + hsv = g_object_get_data(G_OBJECT (widget), data_color); + assert (hsv); + + /* Check for v */ + if ((x >= widget->allocation.width - gdk_pixbuf_get_width(brightness) - COLORWHEEL_SELECT) && + (x <= widget->allocation.width - COLORWHEEL_SELECT)) { + hsv->V = MIN(1.0, + MAX(0, 1.0 - + (((float) (y - COLORWHEEL_SELECT)) / + ((float) gdk_pixbuf_get_height(brightness))))); + } else { + updateV = FALSE; + } + /* Check for h-s */ diameter = gdk_pixbuf_get_width(colorwheel); - - xcenter = widget->allocation.width / 2.0; + xcenter = widget->allocation.height / 2.0; // Use height on purpose ycenter = widget->allocation.height / 2.0; x -= xcenter; @@ -329,37 +497,42 @@ static void click_in_colorwheel (GtkWidget * widget, (x > diameter / 2) || (y < - diameter / 2) || (y > diameter / 2)) - return; + updateHS = FALSE; - radius = sqrt(x*x + y*y); - if (radius > diameter/2.0) - return; - radius /= diameter/2.0; - - if (x == 0) { - if (y > 0) - angle = M_PI/2.0; - else - angle = (3.0*M_PI)/2.0; - } else if (y == 0) { - if (x > 0) - angle = 0; - else - angle = M_PI; - } else { - angle = atan(y / x); - if (x < 0) - angle += M_PI; - else if (y < 0) - angle += 2*M_PI; - } + radius = (2.0 * sqrt(x*x + y*y)) / diameter; + if (radius > 1.0) + updateHS = FALSE; + + if (updateHS) { + if (x == 0) { + if (y > 0) + angle = M_PI/2.0; + else + angle = (3.0*M_PI)/2.0; + } else if (y == 0) { + if (x > 0) + angle = 0; + else + angle = M_PI; + } else { + angle = atan(y / x); + if (x < 0) + angle += M_PI; + else if (y < 0) + angle += 2*M_PI; + } - if (list) - update_selected_songs_color(angle, radius); - if (hb) { - hb->H = angle; - hb->B = radius; + hsv->H = (3.0 * angle) / M_PI; + hsv->S = radius; + } + + if (updateHS || updateV) { + change_func = g_object_get_data(G_OBJECT (widget), data_func); + user_data = g_object_get_data(G_OBJECT (widget), data_user_data); + if (change_func) { + change_func(widget, user_data); + } + gtk_widget_queue_draw(widget); } - gtk_widget_queue_draw(widget); } diff --git a/ui_playlist_view.c b/ui_playlist_view.c index 54c7222..6e11676 100644 --- a/ui_playlist_view.c +++ b/ui_playlist_view.c @@ -1,5 +1,5 @@ /** - * GJay, copyright (c) 2002 Chuck Groom + * GJay, copyright (c) 2002, 2003 Chuck Groom * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -54,7 +54,9 @@ static void value_changed ( GtkRange *range, gpointer user_data ); static void playlist_button_clicked (GtkButton *button, gpointer user_data); - +static GtkWidget * create_float_slider_widget (gchar * name, + gchar * description, + float * value); /* Playlist window -- what to do with the list of songs we've created */ static void make_playlist_window ( GList * list); @@ -76,6 +78,9 @@ static void confirm_save_response (GtkDialog *dialog, gpointer user_data); static void save_playlist_selector (GtkWidget * file_selector); +static void set_prefs_color ( gpointer data, + gpointer user_data); + GtkWidget * make_playlist_view ( void ) { @@ -130,7 +135,11 @@ GtkWidget * make_playlist_view ( void ) { G_CALLBACK (toggled_start_color), NULL); gtk_box_pack_start(GTK_BOX(hbox1), button_start_color, FALSE, FALSE, 2); - colorwheel = create_colorwheel(PLAYLIST_CW_DIAMETER, NULL, &prefs.color); + colorwheel = create_colorwheel(PLAYLIST_CW_DIAMETER, + NULL, + set_prefs_color, + &prefs.color); + set_colorwheel_color(colorwheel, prefs.color); gtk_box_pack_start(GTK_BOX(hbox1), colorwheel, FALSE, FALSE, 2); frame = gtk_frame_new("Criteria"); @@ -142,91 +151,42 @@ GtkWidget * make_playlist_view ( void ) { hbox1 = gtk_hbox_new(FALSE, 2); gtk_box_pack_start(GTK_BOX(vbox2), hbox1, TRUE, TRUE, 2); - vbox3 = gtk_vbox_new(FALSE, 2); + vbox3 = create_float_slider_widget( + "Hue", + "Hue: Match songs by the color, ignoring lightness and intensity", + &prefs.hue); gtk_box_pack_start(GTK_BOX(hbox1), vbox3, TRUE, TRUE, 2); - label = gtk_label_new("Freq."); - event_box = gtk_event_box_new(); - gtk_container_add (GTK_CONTAINER(event_box), label); - range = gtk_vscale_new_with_range (MIN_CRITERIA, MAX_CRITERIA, .1); - gtk_range_set_value(GTK_RANGE(range), prefs.freq); - g_signal_connect (G_OBJECT (range), "value-changed", - G_CALLBACK (value_changed), &prefs.freq); - gtk_scale_set_draw_value(GTK_SCALE(range), FALSE); - gtk_range_set_inverted(GTK_RANGE(range), TRUE); - gtk_box_pack_start(GTK_BOX(vbox3), event_box, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(vbox3), range, TRUE, TRUE, 2); - gtk_tooltips_set_tip (tips, event_box, - "Frequency: Match songs by their spectrum \"fingerprint\"", - ""); - vbox3 = gtk_vbox_new(FALSE, 2); + vbox3 = create_float_slider_widget( + "Bright", + "Brightness: Match songs by the color light/darkness", + &prefs.brightness); gtk_box_pack_start(GTK_BOX(hbox1), vbox3, TRUE, TRUE, 2); - label = gtk_label_new("BPM"); - event_box = gtk_event_box_new(); - gtk_container_add (GTK_CONTAINER(event_box), label); - range = gtk_vscale_new_with_range (MIN_CRITERIA, MAX_CRITERIA, .1); - gtk_range_set_value(GTK_RANGE(range), prefs.bpm); - g_signal_connect (G_OBJECT (range), "value-changed", - G_CALLBACK (value_changed), &prefs.bpm); - gtk_range_set_inverted(GTK_RANGE(range), TRUE); - gtk_scale_set_draw_value(GTK_SCALE(range), FALSE); - gtk_box_pack_start(GTK_BOX(vbox3), event_box, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(vbox3), range, TRUE, TRUE, 2); - gtk_tooltips_set_tip (tips, event_box, - "Beats Per Minute: Match songs by their beat. Note that this may be an unreliable criteria across genres", - ""); + vbox3 = create_float_slider_widget( + "Satur.", + "Saturation: Match songs by the color intensity", + &prefs.saturation); + gtk_box_pack_start(GTK_BOX(hbox1), vbox3, TRUE, TRUE, 2); - vbox3 = gtk_vbox_new(FALSE, 2); + vbox3 = create_float_slider_widget( + "Freq", + "Frequency: Match on how songs sound", + &prefs.freq); gtk_box_pack_start(GTK_BOX(hbox1), vbox3, TRUE, TRUE, 2); - label = gtk_label_new("Hue"); - event_box = gtk_event_box_new(); - gtk_container_add (GTK_CONTAINER(event_box), label); - range = gtk_vscale_new_with_range (MIN_CRITERIA, MAX_CRITERIA, .1); - gtk_scale_set_draw_value(GTK_SCALE(range), FALSE); - gtk_range_set_value(GTK_RANGE(range), prefs.hue); - g_signal_connect (G_OBJECT (range), "value-changed", - G_CALLBACK (value_changed), &prefs.hue); - gtk_range_set_inverted(GTK_RANGE(range), TRUE); - gtk_box_pack_start(GTK_BOX(vbox3), event_box, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(vbox3), range, TRUE, TRUE, 2); - gtk_tooltips_set_tip (tips, event_box, - "Hue: Match songs by the color you assigned them, disregarding the saturation/brightness component", - ""); - vbox3 = gtk_vbox_new(FALSE, 2); + vbox3 = create_float_slider_widget( + "BPM", + "BPM: Match on beat", + &prefs.bpm); gtk_box_pack_start(GTK_BOX(hbox1), vbox3, TRUE, TRUE, 2); - label = gtk_label_new("Brightness"); - event_box = gtk_event_box_new(); - gtk_container_add (GTK_CONTAINER(event_box), label); - range = gtk_vscale_new_with_range (MIN_CRITERIA, MAX_CRITERIA, .1); - gtk_range_set_value(GTK_RANGE(range), prefs.brightness); - g_signal_connect (G_OBJECT (range), "value-changed", - G_CALLBACK (value_changed), &prefs.brightness); - gtk_scale_set_draw_value(GTK_SCALE(range), FALSE); - gtk_range_set_inverted(GTK_RANGE(range), TRUE); - gtk_box_pack_start(GTK_BOX(vbox3), event_box, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(vbox3), range, TRUE, TRUE, 2); - gtk_tooltips_set_tip (tips, event_box, - "Brightness: Match songs by the brightness portion of the color you assigned them", - ""); - vbox3 = gtk_vbox_new(FALSE, 2); + vbox3 = create_float_slider_widget( + "File Loc.", + "File Location: Match songs by their nearness in the file system. Two songs in the same directory are closer than two songs in different directories.", + &prefs.path_weight); gtk_box_pack_start(GTK_BOX(hbox1), vbox3, TRUE, TRUE, 2); - label = gtk_label_new("File Loc."); - event_box = gtk_event_box_new(); - gtk_container_add (GTK_CONTAINER(event_box), label); - range = gtk_vscale_new_with_range (MIN_CRITERIA, MAX_CRITERIA, .1); - gtk_range_set_value(GTK_RANGE(range), prefs.path_weight); - g_signal_connect (G_OBJECT (range), "value-changed", - G_CALLBACK (value_changed), &prefs.path_weight); - gtk_range_set_inverted(GTK_RANGE(range), TRUE); - gtk_scale_set_draw_value(GTK_SCALE(range), FALSE); - gtk_box_pack_start(GTK_BOX(vbox3), event_box, FALSE, FALSE, 2); - gtk_box_pack_start(GTK_BOX(vbox3), range, TRUE, TRUE, 2); - gtk_tooltips_set_tip (tips, event_box, - "File Location: Match songs by their nearness in the file system. Two songs in the same directory are closer than two songs in different directories.", - ""); + button = gtk_check_button_new_with_label("Wander"); gtk_tooltips_set_tip (tips, button, @@ -669,3 +629,37 @@ void set_playlist_rating_visible ( gboolean is_visible ) { gtk_widget_hide(rating_hbox); } } + +GtkWidget * create_float_slider_widget (gchar * name, + gchar * description, + float * value) { + GtkWidget * vbox, * event_box, * range, * label; + + vbox = gtk_vbox_new(FALSE, 2); + label = gtk_label_new(name); + event_box = gtk_event_box_new(); + gtk_container_add (GTK_CONTAINER(event_box), label); + range = gtk_vscale_new_with_range (MIN_CRITERIA, MAX_CRITERIA, .1); + gtk_range_set_value(GTK_RANGE(range), *value); + g_signal_connect (G_OBJECT (range), "value-changed", + G_CALLBACK (value_changed), value); + gtk_scale_set_draw_value(GTK_SCALE(range), FALSE); + gtk_range_set_inverted(GTK_RANGE(range), TRUE); + gtk_box_pack_start(GTK_BOX(vbox), event_box, FALSE, FALSE, 2); + gtk_box_pack_start(GTK_BOX(vbox), range, TRUE, TRUE, 2); + if (tips && description) { + gtk_tooltips_set_tip (tips, + event_box, + description, + ""); + } + return vbox; +} + + +static void set_prefs_color ( gpointer data, + gpointer user_data) { + assert(data && user_data); + *((HSV *) user_data) = get_colorwheel_color(GTK_WIDGET(data)); +} + diff --git a/ui_prefs_view.c b/ui_prefs_view.c index f454700..9f145f3 100644 --- a/ui_prefs_view.c +++ b/ui_prefs_view.c @@ -361,3 +361,5 @@ static void useratings_toggled ( GtkToggleButton *togglebutton, set_playlist_rating_visible ( prefs.use_ratings ); save_prefs(); } + + diff --git a/ui_selection_view.c b/ui_selection_view.c index 25400ae..9e57547 100644 --- a/ui_selection_view.c +++ b/ui_selection_view.c @@ -52,14 +52,24 @@ static gboolean select_all_selected (GtkWidget *widget, gpointer user_data); static void rating_changed ( GtkRange *range, gpointer user_data ); -static void populate_selected_list (void); -static void redraw_rating (void); +static void populate_selected_list ( void ); +static void redraw_rating ( void ); static void update_song_has_rating_color ( song * s ); static void update_dir_has_rating_color ( gchar * dir ); +static void update_selected_songs_color ( gpointer data, + gpointer user_data ); +static void get_selected_color ( HSV * color, + gboolean * no_color, + gboolean * multiple_colors ); /* How many chars should we truncate displayed file names to? */ #define TRUNC_NAME 18 +/* Size of color swatch */ +#define COLOR_SWATCH_WIDTH 30 +#define COLOR_SWATCH_HEIGHT 20 + + GtkWidget * make_selection_view ( void ) { GtkWidget * vbox1, * vbox2, * hbox1, * hbox2; GtkWidget * alignment, * event_box, * swin; @@ -163,9 +173,14 @@ GtkWidget * make_selection_view ( void ) { cwheel = create_colorwheel(COLORWHEEL_DIAMETER, &selected_songs, + update_selected_songs_color, NULL); + gtk_box_pack_start(GTK_BOX(hbox2), cwheel, FALSE, FALSE, 2); - + + alignment = gtk_alignment_new (0, 1, 0.1, 0.1); + gtk_box_pack_start(GTK_BOX(hbox2), alignment, FALSE, FALSE, 2); + rating_vbox = gtk_vbox_new (FALSE, 2); gtk_box_pack_start(GTK_BOX(hbox2), rating_vbox, TRUE, FALSE, 2); label_rating = gtk_label_new("Rating"); @@ -376,7 +391,13 @@ static gboolean select_all_selected (GtkWidget *widget, void update_selection_area (void) { + HSV color; + gboolean no_color; + gboolean multiple_colors; + populate_selected_list(); + get_selected_color(&color, &no_color, &multiple_colors); + redraw_rating(); gtk_widget_queue_draw(cwheel); } @@ -422,20 +443,22 @@ void populate_selected_list (void) { } -void update_selected_songs_color ( gdouble angle, - gdouble radius ) { +void update_selected_songs_color (gpointer data, + gpointer user_data) { GList * llist; song * s; gboolean had_color_rating; gchar * dir = NULL; + HSV hsv; if (!selected_songs) return; + hsv = get_colorwheel_color(GTK_WIDGET(data)); + s = SONG(selected_songs); dir = parent_dir(s->path); - for (llist = g_list_first(selected_songs); llist; llist = g_list_next(llist)) { s = (song *) llist->data; @@ -447,8 +470,7 @@ void update_selected_songs_color ( gdouble angle, } s->no_color = FALSE; - s->color.H = (float) angle; - s->color.B = (float) radius; + s->color = hsv; /* If other songs mirror this one, pass on the change */ song_set_repeat_attrs(s); @@ -580,3 +602,40 @@ void set_selected_rating_visible ( gboolean is_visible ) { gtk_widget_hide(rating_vbox); } } + + +void get_selected_color (HSV * color, + gboolean * no_color, + gboolean * multiple_colors) { + RGB rgb, rgb_sum; + int num = 0; + GList * llist; + song * s; + + assert(color && no_color && multiple_colors); + *no_color = TRUE; + bzero(&rgb_sum, sizeof(RGB)); + for (llist = g_list_first(selected_songs); + llist; + llist = g_list_next(llist)) { + s = (song *) llist->data; + assert(s); + if (!s->no_color) { + num++; + *no_color = FALSE; + rgb = hsv_to_rgb(s->color); + rgb_sum.R += rgb.R; + rgb_sum.G += rgb.G; + rgb_sum.B += rgb.B; + } + } + if (num > 1) { + *multiple_colors = TRUE; + rgb_sum.R /= (float) num; + rgb_sum.G /= (float) num; + rgb_sum.B /= (float) num; + } else { + *multiple_colors = FALSE; + } + *color = rgb_to_hsv(rgb_sum); +}