1 /* 2 * Copyright (C) 2008, 2009 Mihai Şucan 3 * 4 * This file is part of PaintWeb. 5 * 6 * PaintWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * PaintWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with PaintWeb. If not, see <http://www.gnu.org/licenses/>. 18 * 19 * $URL: http://code.google.com/p/paintweb $ 20 * $Date: 2009-07-02 15:37:38 +0300 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview Holds the color picker implementation. 26 */ 27 28 /** 29 * @class The color picker tool. 30 * 31 * @param {PaintWeb} app Reference to the main paint application object. 32 */ 33 pwlib.tools.cpicker = function (app) { 34 var _self = this, 35 colormixer = app.extensions.colormixer, 36 context = app.layer.context, 37 gui = app.gui, 38 lang = app.lang, 39 MathRound = Math.round, 40 mouse = app.mouse; 41 42 /** 43 * Holds the ID of the previously active tool. Once the user completes the 44 * color picking operation, the previous tool is activated. 45 * 46 * @private 47 * @type String 48 */ 49 var prevTool = null; 50 51 /** 52 * Holds a reference to the target color input. This is a GUI color input 53 * component. 54 * 55 * @private 56 * @type pwlib.guiColorInput 57 */ 58 var targetInput = null; 59 60 /** 61 * Holds the previous color values - before the user started picking 62 * a different color. 63 * 64 * @private 65 * @type Object 66 */ 67 var prevColor = null; 68 69 /** 70 * Tells if the color mixer is active for the current target input. 71 * 72 * @private 73 * @type Boolean 74 */ 75 var colormixerActive = false; 76 77 /** 78 * Tells if the current color values are accepted by the user. This value is 79 * used by the tool deactivation code. 80 * 81 * @private 82 * @type Boolean 83 */ 84 var colorAccepted = false; 85 86 /** 87 * The <code>preActivate</code> event handler. This method checks if the 88 * browser implements the <code>getImageData()</code> context method. If not, 89 * the color picker tool cannot be used. 90 */ 91 this.preActivate = function () { 92 // The latest versions of all browsers which implement Canvas, also 93 // implement the getImageData() method. This was only a problem with some 94 // old versions (eg. Opera 9.2). 95 if (!context.getImageData) { 96 alert(lang.errorCpickerUnsupported); 97 return false; 98 } 99 100 if (app.tool && app.tool._id) { 101 prevTool = app.tool._id; 102 } 103 104 return true; 105 }; 106 107 /** 108 * The <code>activate</code> event handler. This method determines the current 109 * target input in the Color Mixer, if any. Canvas shadow rendering is 110 * disallowed. 111 */ 112 this.activate = function () { 113 // When the color mixer panel is active, the color picker uses the same 114 // target input. 115 if (colormixer && colormixer.targetInput) { 116 targetInput = gui.colorInputs[colormixer.targetInput.id]; 117 } 118 119 if (targetInput) { 120 gui.statusShow('cpicker_' + targetInput.id); 121 } else { 122 gui.statusShow('cpickerNormal'); 123 } 124 125 app.shadowDisallow(); 126 }; 127 128 /** 129 * The <code>deactivate</code> event handler. This method allows shadow 130 * rendering again, and resets the color input values if the user did not 131 * accept the new color. 132 */ 133 this.deactivate = function () { 134 if (!colorAccepted && targetInput && prevColor) { 135 updateColor(null, true); 136 } 137 138 app.shadowAllow(); 139 }; 140 141 /** 142 * The <code>mousedown</code> event handler. This method starts the color 143 * picking operation. 144 * 145 * @param {Event} ev The DOM Event object. 146 */ 147 this.mousedown = function (ev) { 148 // We check again, because the user might have opened/closed the color 149 // mixer. 150 if (colormixer && colormixer.targetInput) { 151 targetInput = gui.colorInputs[colormixer.targetInput.id]; 152 } 153 154 if (targetInput) { 155 colormixerActive = true; 156 gui.statusShow('cpicker_' + targetInput.id); 157 } else { 158 colormixerActive = false; 159 gui.statusShow('cpickerNormal'); 160 161 // The context menu (right-click). This is unsupported by Opera. 162 // Also allow Shift+Click for changing the stroke color (making it easier for Opera users). 163 if (ev.button === 2 || ev.shiftKey) { 164 targetInput = gui.colorInputs.strokeStyle; 165 } else { 166 targetInput = gui.colorInputs.fillStyle; 167 } 168 } 169 170 updatePrevColor(); 171 172 _self.mousemove = updateColor; 173 updateColor(ev); 174 175 return true; 176 }; 177 178 /** 179 * Perform color update. This function updates the target input or the Color 180 * Mixer to hold the color value under the mouse - it actually performs the 181 * color picking operation. 182 * 183 * <p>This function is also the <code>mousemove</code> event handler for this 184 * tool. 185 * 186 * @param {Event} ev The DOM Event object. 187 * @param {Boolean} [usePrevColor=false] Tells the function to use the 188 * previous color values we have stored. This is used when the user cancels 189 * the color picking operation. 190 */ 191 function updateColor (ev, usePrevColor) { 192 if (!targetInput) { 193 return; 194 } 195 196 var p = usePrevColor ? prevColor : 197 context.getImageData(mouse.x, mouse.y, 1, 1), 198 color = { 199 red: p.data[0] / 255, 200 green: p.data[1] / 255, 201 blue: p.data[2] / 255, 202 alpha: (p.data[3] / 255).toFixed(3) 203 }; 204 205 if (colormixerActive) { 206 colormixer.color.red = color.red; 207 colormixer.color.green = color.green; 208 colormixer.color.blue = color.blue; 209 colormixer.color.alpha = color.alpha; 210 colormixer.update_color('rgb'); 211 212 } else { 213 targetInput.updateColor(color); 214 } 215 }; 216 217 /** 218 * The <code>mouseup</code> event handler. This method completes the color 219 * picking operation, and activates the previous tool. 220 * 221 * <p>The {@link pwlib.appEvent.configChange} application event is also 222 * dispatched for the configuration property associated to the target input. 223 * 224 * @param {Event} ev The DOM Event object. 225 */ 226 this.mouseup = function (ev) { 227 if (!targetInput) { 228 return false; 229 } 230 231 delete _self.mousemove; 232 updateColor(ev); 233 colorAccepted = true; 234 235 if (!colormixerActive) { 236 var color = targetInput.color, 237 configProperty = targetInput.configProperty, 238 configGroup = targetInput.configGroup, 239 configGroupRef = targetInput.configGroupRef, 240 prevVal = configGroupRef[configProperty], 241 newVal = 'rgba(' + MathRound(color.red * 255) + ',' + 242 MathRound(color.green * 255) + ',' + 243 MathRound(color.blue * 255) + ',' + 244 color.alpha + ')'; 245 246 if (prevVal !== newVal) { 247 configGroupRef[configProperty] = newVal; 248 app.events.dispatch(new pwlib.appEvent.configChange(newVal, prevVal, 249 configProperty, configGroup, configGroupRef)); 250 } 251 } 252 253 if (prevTool) { 254 app.toolActivate(prevTool, ev); 255 } 256 257 return true; 258 }; 259 260 /** 261 * The <code>keydown</code> event handler. This method allows the user to 262 * press the <kbd>Escape</kbd> key to cancel the color picking operation. By 263 * doing so, the original color values are restored. 264 * 265 * @param {Event} ev The DOM Event object. 266 * @returns {Boolean} True if the keyboard shortcut was recognized, or false 267 * if not. 268 */ 269 this.keydown = function (ev) { 270 if (!prevTool || ev.kid_ !== 'Escape') { 271 return false; 272 } 273 274 mouse.buttonDown = false; 275 app.toolActivate(prevTool, ev); 276 277 return true; 278 }; 279 280 /** 281 * The <code>contextmenu</code> event handler. This method only cancels the 282 * context menu. 283 */ 284 // Unfortunately, the contextmenu event is unsupported by Opera. 285 this.contextmenu = function () { 286 return true; 287 }; 288 289 /** 290 * Store the color values from the target color input, before this tool 291 * changes the colors. The previous color values are used when the user 292 * decides to cancel the color picking operation. 293 * @private 294 */ 295 function updatePrevColor () { 296 // If the color mixer panel is visible, then we store the color values from 297 // the color mixer, instead of those from the color input object. 298 var color = colormixerActive ? colormixer.color : targetInput.color; 299 300 prevColor = { 301 width: 1, 302 height: 1, 303 data: [ 304 MathRound(color.red * 255), 305 MathRound(color.green * 255), 306 MathRound(color.blue * 255), 307 color.alpha * 255 308 ] 309 }; 310 }; 311 }; 312 313 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 314 315