diff --git a/lang/en/admin.php b/lang/en/admin.php index 504f59dcfde..81b6e55caa9 100755 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -631,6 +631,7 @@ $string['latexpreamble'] = 'LaTeX preamble'; $string['latexsettings'] = 'LaTeX renderer Settings'; $string['latinexcelexport'] = 'Excel encoding'; $string['licensesettings'] = 'License settings'; +$string['loading'] = 'Loading'; $string['localetext'] = 'Sitewide locale'; $string['localstringcustomization'] = 'Local string customization'; $string['location'] = 'Location'; diff --git a/lib/adminlib.php b/lib/adminlib.php index 35b54027bad..7f8d29bc7c8 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -6940,3 +6940,96 @@ class admin_setting_managewebservicetokens extends admin_setting { return highlight($query, $return); } } + +/** + * Colour picker + * + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class admin_setting_configcolourpicker extends admin_setting { + + /** + * Information for previewing the colour + * + * @var array|null + */ + protected $previewconfig = null; + + /** + * + * @param string $name + * @param string $visiblename + * @param string $description + * @param string $defaultsetting + * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor'); + */ + public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig=null) { + $this->previewconfig = $previewconfig; + parent::__construct($name, $visiblename, $description, $defaultsetting); + } + + /** + * Return the setting + * + * @return mixed returns config if successful else null + */ + public function get_setting() { + return $this->config_read($this->name); + } + + /** + * Saves the setting + * + * @param string $data + * @return bool + */ + public function write_setting($data) { + $data = $this->validate($data); + if ($data === false) { + return get_string('validateerror', 'admin'); + } + return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); + } + + /** + * Validates the colour that was entered by the user + * + * @param string $data + * @return string|false + */ + protected function validate($data) { + if (preg_match('/^#?([a-fA-F0-9]{3}){1,2}$/', $data)) { + if (strpos($data, '#')!==0) { + $data = '#'.$data; + } + return $data; + } else if (preg_match('/^[a-zA-Z]{3, 25}$/')) { + return $data; + } else { + return false; + } + } + + /** + * Generates the HTML for the setting + * + * @global moodle_page $PAGE + * @global core_renderer $OUTPUT + * @param string $data + * @param string $query + */ + public function output_html($data, $query = '') { + global $PAGE, $OUTPUT; + $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig)); + $content = html_writer::start_tag('div', array('class'=>'form-colourpicker defaultsnext')); + $content .= html_writer::tag('div', $OUTPUT->pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', array('class'=>'loadingicon')), array('class'=>'admin_colourpicker clearfix')); + $content .= html_writer::empty_tag('input', array('type'=>'text','id'=>$this->get_id(), 'name'=>$this->get_full_name(), 'value'=>$this->get_setting(), 'size'=>'12')); + if (!empty($this->previewconfig)) { + $content .= html_writer::empty_tag('input', array('type'=>'button','id'=>$this->get_id().'_preview', 'value'=>get_string('preview'), 'class'=>'admin_colourpicker_preview')); + } + $content .= html_writer::end_tag('div'); + return format_admin_setting($this, $this->visiblename, $content, $this->description, false, '', $this->get_defaultsetting(), $query); + } + +} \ No newline at end of file diff --git a/lib/javascript-static.js b/lib/javascript-static.js index 3a346ac304c..4a81f9e360c 100644 --- a/lib/javascript-static.js +++ b/lib/javascript-static.js @@ -427,6 +427,166 @@ M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname }, node); } +/** + * Initialises a colour picker + * + * Designed to be used with admin_setting_configcolourpicker although could be used + * anywhere, just give a text input an id and insert a div with the class admin_colourpicker + * above or below the input (must have the same parent) and then call this with the + * id. + * + * This code was mostly taken from my [Sam Hemelryk] css theme tool available in + * contrib/blocks. For better docs refer to that. + * + * @param {YUI} Y + * @param {int} id + * @param {object} previewconf + */ +M.util.init_colour_picker = function(Y, id, previewconf) { + /** + * We need node and event-mouseenter + */ + Y.use('node', 'event-mouseenter', function(){ + /** + * The colour picker object + */ + var colourpicker = { + box : null, + input : null, + image : null, + preview : null, + current : null, + eventClick : null, + eventMouseEnter : null, + eventMouseLeave : null, + eventMouseMove : null, + width : 300, + height : 100, + factor : 5, + /** + * Initalises the colour picker by putting everything together and wiring the events + */ + init : function() { + this.input = Y.one('#'+id); + this.box = this.input.ancestor().one('.admin_colourpicker'); + this.image = Y.Node.create(''); + this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle')); + this.preview = Y.Node.create('
'); + this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value')); + this.current = Y.Node.create('
'); + this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value')); + this.box.setContent('').append(this.image).append(this.preview).append(this.current); + + if (typeof(previewconf) === 'object' && previewconf !== null) { + Y.one('#'+id+'_preview').on('click', function(e){ + Y.one(previewconf.selector).setStyle(previewconf.style, this.input.get('value')); + }, this); + } + + this.eventClick = this.image.on('click', this.pickColour, this); + this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this); + }, + /** + * Starts to follow the mouse once it enter the image + */ + startFollow : function(e) { + this.eventMouseEnter.detach(); + this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this); + this.eventMouseMove = this.image.on('mousemove', function(e){ + this.preview.setStyle('backgroundColor', this.determineColour(e)); + }, this); + }, + /** + * Stops following the mouse + */ + endFollow : function(e) { + this.eventMouseMove.detach(); + this.eventMouseLeave.detach(); + this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this); + }, + /** + * Picks the colour the was clicked on + */ + pickColour : function(e) { + var colour = this.determineColour(e); + this.input.set('value', colour); + this.current.setStyle('backgroundColor', colour); + }, + /** + * Calculates the colour fromthe given co-ordinates + */ + determineColour : function(e) { + var eventx = Math.floor(e.pageX-e.target.getX()); + var eventy = Math.floor(e.pageY-e.target.getY()); + + var imagewidth = this.width; + var imageheight = this.height; + var factor = this.factor; + var colour = [255,0,0]; + + var matrices = [ + [ 0, 1, 0], + [ -1, 0, 0], + [ 0, 0, 1], + [ 0, -1, 0], + [ 1, 0, 0], + [ 0, 0, -1] + ]; + + var matrixcount = matrices.length; + var limit = Math.round(imagewidth/matrixcount); + var heightbreak = Math.round(imageheight/2); + + for (var x = 0; x < imagewidth; x++) { + var divisor = Math.floor(x / limit); + var matrix = matrices[divisor]; + + colour[0] += matrix[0]*factor; + colour[1] += matrix[1]*factor; + colour[2] += matrix[2]*factor; + + if (eventx==x) { + break; + } + } + + var pixel = [colour[0], colour[1], colour[2]]; + if (eventy < heightbreak) { + pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy)); + pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy)); + pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy)); + } else if (eventy > heightbreak) { + pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak)); + pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak)); + pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak)); + } + + return this.convert_rgb_to_hex(pixel); + }, + /** + * Converts an RGB value to Hex + */ + convert_rgb_to_hex : function(rgb) { + var hex = '#'; + var hexchars = "0123456789ABCDEF"; + for (var i=0; i<3; i++) { + var number = Math.abs(rgb[i]); + if (number == 0 || isNaN(number)) { + hex += '00'; + } else { + hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16); + } + } + return hex; + } + } + /** + * Initialise the colour picker :) Hoorah + */ + colourpicker.init(); + }); +} + //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code === function popupchecker(msg) { diff --git a/pix/.cvsignore b/pix/.cvsignore new file mode 100644 index 00000000000..e43b0f98895 --- /dev/null +++ b/pix/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/pix/i/colourpicker.png b/pix/i/colourpicker.png new file mode 100644 index 00000000000..d90661444ad Binary files /dev/null and b/pix/i/colourpicker.png differ diff --git a/theme/base/style/admin.css b/theme/base/style/admin.css index 2094ffcde1a..5d624f4fa2a 100644 --- a/theme/base/style/admin.css +++ b/theme/base/style/admin.css @@ -148,4 +148,13 @@ #adminsettings .form-item .patherror {margin-left: 0.5em;} #adminthemeselector .selectedtheme td.c0 {border:1px solid;border-right-width:0;} -#adminthemeselector .selectedtheme td.c1 {border:1px solid;border-left-width:0;} \ No newline at end of file +#adminthemeselector .selectedtheme td.c1 {border:1px solid;border-left-width:0;} + +.admin_colourpicker, +.admin_colourpicker_preview {display:none;} +.jsenabled .admin_colourpicker_preview {display:inline;} +.jsenabled .admin_colourpicker {display:block;height:102px;width:410px;margin-bottom:10px;} +.admin_colourpicker .loadingicon {vertical-align:center;margin-left:auto;} +.admin_colourpicker .colourdialogue {float:left;border:1px solid #000;} +.admin_colourpicker .previewcolour {border:1px solid #000;margin-left:301px;} +.admin_colourpicker .currentcolour {border:1px solid #000;margin-left:301px;border-top-width:0;}