Index: fivestar.css =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/fivestar/fivestar.css,v retrieving revision 1.1.2.10 diff -u -r1.1.2.10 fivestar.css --- fivestar.css 7 Jan 2008 08:37:16 -0000 1.1.2.10 +++ fivestar.css 10 Mar 2008 01:30:20 -0000 @@ -86,31 +86,3 @@ div.fivestar-widget div.hover a, div.rating div a:hover { background-position: 0 -32px; } - -/* Fivestar Settings Preview */ -iframe.fivestar-preview { - border: none; - width: 200px; - height: 32px; -} -div.fivestar-widgets div.form-item { - float: left; -} - -/* Fivestar Node Type Preview */ -#fivestar-comment-form, -#fivestar-direct-form { - float: left; -} -#fivestar-comment-preview, -#fivestar-direct-preview { - float: left; - margin-left: 40px; -} -#fivestar-node-type-form fieldset { - background: transparent; -} -.fivestar-preview { - border: 1px solid #CCC; - padding: 10px; -} Index: fivestar.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/fivestar/fivestar.module,v retrieving revision 1.2.2.69 diff -u -r1.2.2.69 fivestar.module --- fivestar.module 26 Feb 2008 20:15:27 -0000 1.2.2.69 +++ fivestar.module 10 Mar 2008 01:30:21 -0000 @@ -299,6 +299,7 @@ function theme_fivestar_node_type_form($form) { drupal_add_js(drupal_get_path('module', 'fivestar') .'/fivestar-admin.js'); drupal_add_js(array('fivestar' => array('preview_url' => url('fivestar/preview/node'))), 'setting'); + drupal_add_css(drupal_get_path('module', 'fivestar') .'/fivestar-admin.css'); $output = ''; $output .= drupal_render($form['fivestar']); @@ -372,7 +373,10 @@ * Callback function for admin/settings/fivestar. Display the settings form. */ function fivestar_settings() { - $form = array(); + include_once(drupal_get_path('module', 'fivestar') .'/fivestar_color.inc'); + + $form = fivestar_color_form(); + $form['fivestar_widget'] = array( '#type' => 'radios', '#title' => t('Widget display'), @@ -393,16 +397,17 @@ function theme_fivestar_settings($form) { fivestar_add_css(); + drupal_add_css(drupal_get_path('module', 'fivestar') .'/fivestar-admin.css'); drupal_set_title(t('Fivestar Settings')); // Default preview. - $form['fivestar_widget']['default']['#description'] = 'Default '. t('Preview') .':
'; + //$form['fivestar_widget']['default']['#description'] = 'Default '. t('Preview') .':
'; // Preview for each widget. $widget_number = 0; foreach (element_children($form['fivestar_widget']) as $widget_key) { if ($widget_key != 'default') { - $form['fivestar_widget'][$widget_key]['#description'] = $form['fivestar_widget'][$widget_key]['#title'] .' '. t('Preview') .':
'; + //$form['fivestar_widget'][$widget_key]['#description'] = $form['fivestar_widget'][$widget_key]['#title'] .' '. t('Preview') .':
'; $widget_number++; } } Index: fivestar-admin.css =================================================================== RCS file: fivestar-admin.css diff -N fivestar-admin.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ fivestar-admin.css 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,27 @@ +/* Fivestar Settings Preview */ +iframe.fivestar-preview { + border: none; + width: 200px; + height: 32px; +} +div.fivestar-widgets div.form-item { + float: left; +} + +/* Fivestar Node Type Preview */ +#fivestar-comment-form, +#fivestar-direct-form { + float: left; +} +#fivestar-comment-preview, +#fivestar-direct-preview { + float: left; + margin-left: 40px; +} +#fivestar-node-type-form fieldset { + background: transparent; +} +.fivestar-preview { + border: 1px solid #CCC; + padding: 10px; +} Index: fivestar_color.inc =================================================================== RCS file: fivestar_color.inc diff -N fivestar_color.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ fivestar_color.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,507 @@ +name; + + // See if we're using a predefined scheme. + $current = implode(',', variable_get('fivestar_color_palette', array())); + + $options = array('fivestar_yellow' => t('Yellow'), 'fivestar_red' => t('Red'), 'fivestar_blue' => t('Blue'), 'fivestar_green' => t('Green')); + if (!empty($theme_info['schemes'])) { + foreach ($theme_info['schemes'] as $key => $theme_scheme) { + $colors = explode(',', $theme_scheme); + unset($colors[1], $colors[2], $colors[5]); + } + $options = array(t('Fivestar schemes') => $options, t('@theme_name schemes', array('@theme_name' => drupal_ucfirst($theme_name))) => $theme_info['schemes']); + } + + $form = array('#submit' => array('fivestar_color_form_submit')); + + // Add scheme selector. + $info['schemes'][''] = t('Custom'); + $form['scheme'] = array( + '#type' => 'select', + '#title' => t('Color set'), + '#options' => $options, + '#default_value' => $current, + ); + + // Add palette fields. + $names = array( + 'off1' => t('Off 1 color'), + 'off2' => t('Off 2 color'), + 'hover1' => t('Hover 1 color'), + 'hover2' => t('Hover 2 color'), + 'on1' => t('On 1 color'), + 'on2' => t('On 2 color'), + ); + + $default_colors = array( + 'off1' => '#cccccc', + 'off2' => '#cccccc', + 'hover1' => '#cccccc', + 'hover2' => '#cccccc', + 'on1' => '#cccccc', + 'on2' => '#cccccc', + ); + $color_scheme = variable_get('fivestar_colors', $default_colors); + + $form['palette']['#tree'] = true; + $form['palette']['#theme'] = 'fivestar_color_palette'; + foreach ($names as $key => $name) { + $form['palette'][$key] = array( + '#type' => 'textfield', + '#title' => $name, + '#default_value' => $color_scheme[$key], + '#size' => 8, + ); + } + + return $form; +} + +/** + * Theme color form. + */ +function theme_fivestar_color_palette($form) { + // Add Farbtastic color picker. + drupal_add_js(drupal_get_path('module', 'color') .'/color.js'); + drupal_add_css(drupal_get_path('module', 'color') .'/color.css'); + drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE); + drupal_add_js('misc/farbtastic/farbtastic.js'); + + // Add custom CSS/JS. + $default_colors = array( + 'off1' => '#cccccc', + 'off2' => '#cccccc', + 'hover1' => '#cccccc', + 'hover2' => '#cccccc', + 'on1' => '#cccccc', + 'on2' => '#cccccc', + ); + $color_scheme = variable_get('fivestar_colors', $default_colors); + drupal_add_js(array('color' => array('reference' => $color_scheme)), 'setting'); + + // Wrapper + $output .= '
'; + $output .= '
'; + + // Palette + $output .= '
'; + foreach (element_children($form) as $name) { + $output .= drupal_render($form[$name]); + } + $output .= '
'; + + // Preview + $output .= drupal_render($form); + $output .= '

'. t('Preview') .'

'; + $output .= '
'; + + // Close wrapper + $output .= '
'; + $output .= '
'; + + return $output; +} + +/** + * Submit handler for color change form. + */ +function fivstar_color_form_submit($form_id, $values) { + $theme = variable_get('theme_default', 'garland'); + + // Resolve palette + $palette = $values['palette']; + if ($values['scheme'] != '') { + $scheme = explode(',', $values['scheme']); + foreach ($palette as $k => $color) { + $palette[$k] = array_shift($scheme); + } + } + + // Make sure enough memory is available, if PHP's memory limit is compiled in. + if (function_exists('memory_get_usage')) { + // Fetch source image dimensions. + $source = drupal_get_path('theme', $theme) .'/'. $info['base_image']; + list($width, $height) = getimagesize($source); + + // We need at least a copy of the source and a target buffer of the same + // size (both at 32bpp). + $required = $width * $height * 8; + $usage = memory_get_usage(); + $limit = parse_size(ini_get('memory_limit')); + if ($usage + $required > $limit) { + drupal_set_message(t('There is not enough memory available to PHP to change this theme\'s color scheme. You need at least %size more. Check the PHP documentation for more information.', array('%size' => format_size($usage + $required - $limit), '@url' => 'http://www.php.net/manual/en/ini.core.php#ini.sect.resource-limits')), 'error'); + return; + } + } + + // Delete old files + foreach (variable_get('color_'. $theme .'_files', array()) as $file) { + @unlink($file); + } + if ($file = dirname($file)) { + @rmdir($file); + } + + // Don't render the default colorscheme, use the standard theme instead. + if (implode(',', color_get_palette($theme, true)) == implode(',', $palette) + || $values['op'] == t('Reset to defaults')) { + variable_del('color_'. $theme .'_palette'); + variable_del('color_'. $theme .'_stylesheet'); + variable_del('color_'. $theme .'_logo'); + variable_del('color_'. $theme .'_files'); + variable_del('color_'. $theme .'_screenshot'); + return; + } + + // Prepare target locations for generated files + $id = $theme .'-'. substr(md5(serialize($palette) . microtime()), 0, 8); + $paths['color'] = file_directory_path() .'/color'; + $paths['target'] = $paths['color'] .'/'. $id; + foreach ($paths as $path) { + file_check_directory($path, FILE_CREATE_DIRECTORY); + } + $paths['target'] = $paths['target'] .'/'; + $paths['id'] = $id; + $paths['source'] = drupal_get_path('theme', $theme) .'/'; + $paths['stylesheet'] = $paths['target'] .'style.css'; + $paths['files'] = $paths['map'] = array(); + + // Save palette and stylesheet location + variable_set('color_'. $theme .'_palette', $palette); + variable_set('color_'. $theme .'_stylesheet', $paths['stylesheet']); + variable_set('color_'. $theme .'_logo', $paths['target'] .'logo.png'); + + // Copy over neutral images + foreach ($info['copy'] as $file) { + $base = basename($file); + $source = $paths['source'] . $file; + file_copy($source, $paths['target'] . $base); + $paths['map'][$file] = $base; + $paths['files'][] = $paths['target'] . $base; + } + + // Render new images + _color_render_images($theme, $info, $paths, $palette); + + // Rewrite stylesheet + _color_rewrite_stylesheet($theme, $info, $paths, $palette); + + // Maintain list of files + variable_set('color_'. $theme .'_files', $paths['files']); +} + +/** + * Rewrite the stylesheet to match the colors in the palette. + */ +function _fivestar_color_rewrite_stylesheet($theme, $palette) { + // Load stylesheet + $style = file_get_contents($paths['source'] .'style.css'); + + // Prepare color conversion table + $conversion = $palette; + unset($conversion['base']); + foreach ($conversion as $k => $v) { + $conversion[$k] = drupal_strtolower($v); + } + $default = color_get_palette($theme, true); + + // Split off the "Don't touch" section of the stylesheet. + list($style, $fixed) = explode("Color Module: Don't touch", $style); + + // Look for @import commands and insert the referenced stylesheets. + $cwd = getcwd(); + chdir(drupal_get_path('theme', $theme)); + $style = preg_replace_callback('/@import\s*["\']([^"\']+)["\'];/', '_color_import_stylesheet', $style); + chdir($cwd); + + // Find all colors in the stylesheet and the chunks in between. + $style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE); + $is_color = false; + $output = ''; + $base = 'base'; + + // Iterate over all parts + foreach ($style as $chunk) { + if ($is_color) { + $chunk = drupal_strtolower($chunk); + // Check if this is one of the colors in the default palette + if ($key = array_search($chunk, $default)) { + $chunk = $conversion[$key]; + } + // Not a pre-set color. Extrapolate from the base. + else { + $chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']); + } + } + else { + // Determine the most suitable base color for the next color. + + // 'a' declarations. Use link. + if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) { + $base = 'link'; + } + // 'color:' styles. Use text. + else if (preg_match('/(? $after) { + $output = str_replace($before, $after, $output); + } + + // Write new stylesheet + $file = fopen($paths['stylesheet'], 'w+'); + fwrite($file, $output); + fclose($file); + $paths['files'][] = $paths['stylesheet']; + + // Set standard file permissions for webserver-generated files + @chmod($paths['stylesheet'], 0664); +} + +/** + * Helper function for _color_rewrite_stylesheet. + */ +function _fivestar_color_import_stylesheet($matches) { + return preg_replace('/url\(([\'"]?)(?![a-z]+:)/i', 'url(\1'. dirname($matches[1]) .'/', file_get_contents($matches[1])); +} + +/** + * Render images that match a given palette. + */ +function _fivestar_color_render_images($theme, &$info, &$paths, $palette) { + + // Prepare template image. + $source = $paths['source'] .'/'. $info['base_image']; + $source = imagecreatefrompng($source); + $width = imagesx($source); + $height = imagesy($source); + + // Prepare target buffer. + $target = imagecreatetruecolor($width, $height); + imagealphablending($target, true); + + // Fill regions of solid color. + foreach ($info['fill'] as $color => $fill) { + imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color])); + } + + // Render gradient. + for ($y = 0; $y < $info['gradient'][3]; ++$y) { + $color = _color_blend($target, $palette['top'], $palette['bottom'], $y / ($info['gradient'][3] - 1)); + imagefilledrectangle($target, $info['gradient'][0], $info['gradient'][1] + $y, $info['gradient'][0] + $info['gradient'][2], $info['gradient'][1] + $y + 1, $color); + } + + // Blend over template. + imagecopy($target, $source, 0, 0, 0, 0, $width, $height); + + // Clean up template image. + imagedestroy($source); + + // Cut out slices. + foreach ($info['slices'] as $file => $coord) { + list($x, $y, $width, $height) = $coord; + $base = basename($file); + $image = $paths['target'] . $base; + + // Cut out slice. + if ($file == 'screenshot.png') { + $slice = imagecreatetruecolor(150, 90); + imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height); + variable_set('color_'. $theme .'_screenshot', $image); + } + else { + $slice = imagecreatetruecolor($width, $height); + imagecopy($slice, $target, 0, 0, $x, $y, $width, $height); + } + + // Save image. + imagepng($slice, $image); + imagedestroy($slice); + $paths['files'][] = $image; + + // Set standard file permissions for webserver-generated files + @chmod(realpath($image), 0664); + + // Build before/after map of image paths. + $paths['map'][$file] = $base; + } + + // Clean up target buffer. + imagedestroy($target); +} + +/** + * Shift a given color, using a reference pair and a target blend color. + * + * Note: this function is significantly different from the JS version, as it + * is written to match the blended images perfectly. + * + * Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction delta + * then (return == target + (given - target) * delta) + * + * Loose constraint: Preserve relative positions in saturation and luminance + * space. + */ +function _fivestar_color_shift($given, $ref1, $ref2, $target) { + + // We assume that ref2 is a blend of ref1 and target and find + // delta based on the length of the difference vectors: + + // delta = 1 - |ref2 - ref1| / |white - ref1| + $target = _color_unpack($target, true); + $ref1 = _color_unpack($ref1, true); + $ref2 = _color_unpack($ref2, true); + for ($i = 0; $i < 3; ++$i) { + $numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]); + $denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]); + } + $delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0; + + // Calculate the color that ref2 would be if the assumption was true. + for ($i = 0; $i < 3; ++$i) { + $ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta; + } + + // If the assumption is not true, there is a difference between ref2 and ref3. + // We measure this in HSL space. Notation: x' = hsl(x). + $ref2 = _color_rgb2hsl($ref2); + $ref3 = _color_rgb2hsl($ref3); + for ($i = 0; $i < 3; ++$i) { + $shift[$i] = $ref2[$i] - $ref3[$i]; + } + + // Take the given color, and blend it towards the target. + $given = _color_unpack($given, true); + for ($i = 0; $i < 3; ++$i) { + $result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta; + } + + // Finally, we apply the extra shift in HSL space. + // Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0. + $result = _color_rgb2hsl($result); + for ($i = 0; $i < 3; ++$i) { + $result[$i] = min(1, max(0, $result[$i] + $shift[$i])); + } + $result = _color_hsl2rgb($result); + + // Return hex color. + return _color_pack($result, true); +} + +/** + * Convert a hex triplet into a GD color. + */ +function _fivestar_color_gd($img, $hex) { + $c = array_merge(array($img), _color_unpack($hex)); + return call_user_func_array('imagecolorallocate', $c); +} + +/** + * Blend two hex colors and return the GD color. + */ +function _fivestar_color_blend($img, $hex1, $hex2, $alpha) { + $in1 = _color_unpack($hex1); + $in2 = _color_unpack($hex2); + $out = array($img); + for ($i = 0; $i < 3; ++$i) { + $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha; + } + return call_user_func_array('imagecolorallocate', $out); +} + +/** + * Convert a hex color into an RGB triplet. + */ +function _fivestar_color_unpack($hex, $normalize = false) { + if (strlen($hex) == 4) { + $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3]; + } + $c = hexdec($hex); + for ($i = 16; $i >= 0; $i -= 8) { + $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1); + } + return $out; +} + +/** + * Convert an RGB triplet to a hex color. + */ +function _fivestar_color_pack($rgb, $normalize = false) { + foreach ($rgb as $k => $v) { + $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8)); + } + return '#'. str_pad(dechex($out), 6, 0, STR_PAD_LEFT); +} + +/** + * Convert a HSL triplet into RGB + */ +function _fivestar_color_hsl2rgb($hsl) { + $h = $hsl[0]; + $s = $hsl[1]; + $l = $hsl[2]; + $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s; + $m1 = $l * 2 - $m2; + return array(_color_hue2rgb($m1, $m2, $h + 0.33333), + _color_hue2rgb($m1, $m2, $h), + _color_hue2rgb($m1, $m2, $h - 0.33333)); +} + +/** + * Helper function for _color_hsl2rgb(). + */ +function _fivestar_color_hue2rgb($m1, $m2, $h) { + $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h); + if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6; + if ($h * 2 < 1) return $m2; + if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6; + return $m1; +} + +/** + * Convert an RGB triplet to HSL. + */ +function _fivestar_color_rgb2hsl($rgb) { + $r = $rgb[0]; + $g = $rgb[1]; + $b = $rgb[2]; + $min = min($r, min($g, $b)); + $max = max($r, max($g, $b)); + $delta = $max - $min; + $l = ($min + $max) / 2; + $s = 0; + if ($l > 0 && $l < 1) { + $s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l)); + } + $h = 0; + if ($delta > 0) { + if ($max == $r && $max != $g) $h += ($g - $b) / $delta; + if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta); + if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta); + $h /= 6; + } + return array($h, $s, $l); +} +