1 // 2 // ColorPaletteWidget.cs 3 // 4 // Author: 5 // Jonathan Pobst <monkey@jpobst.com> 6 // 7 // Copyright (c) 2010 Jonathan Pobst 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining a copy 10 // of this software and associated documentation files (the "Software"), to deal 11 // in the Software without restriction, including without limitation the rights 12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 // copies of the Software, and to permit persons to whom the Software is 14 // furnished to do so, subject to the following conditions: 15 // 16 // The above copyright notice and this permission notice shall be included in 17 // all copies or substantial portions of the Software. 18 // 19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 // THE SOFTWARE. 26 27 using System; 28 using Cairo; 29 using Pinta.Core; 30 using Mono.Unix; 31 32 namespace Pinta.Gui.Widgets 33 { 34 [System.ComponentModel.ToolboxItem (true)] 35 public class ColorPaletteWidget : Gtk.DrawingArea 36 { 37 private Rectangle primary_rect = new Rectangle (7, 7, 30, 30); 38 private Rectangle secondary_rect = new Rectangle (22, 22, 30, 30); 39 private Rectangle swap_rect = new Rectangle (37, 6, 15, 15); 40 private Rectangle reset_rect = new Rectangle (7, 37, 15, 15); 41 42 const int primarySecondaryAreaSize = 60; 43 const int swatchSize = 15; 44 const int swatchAreaMargin = 7; 45 46 OrientationEnum orientation; 47 48 const int defaultNumOfJRows = 3; 49 int jRows; 50 int iRows; 51 52 private Gdk.Pixbuf swap_icon; 53 private Gdk.Pixbuf reset_icon; 54 private Palette palette; 55 56 enum OrientationEnum 57 { 58 Horizontal, 59 Vertical 60 } 61 ColorPaletteWidget()62 public ColorPaletteWidget () 63 { 64 // Insert initialization code here. 65 this.AddEvents ((int)Gdk.EventMask.ButtonPressMask); 66 67 swap_icon = PintaCore.Resources.GetIcon ("ColorPalette.SwapIcon.png"); 68 reset_icon = PintaCore.Resources.GetIcon ("ColorPalette.ResetIcon.png"); 69 palette = PintaCore.Palette.CurrentPalette; 70 71 HasTooltip = true; 72 QueryTooltip += HandleQueryTooltip; 73 } 74 Initialize()75 public void Initialize () 76 { 77 PintaCore.Palette.PrimaryColorChanged += new EventHandler (Palette_ColorChanged); 78 PintaCore.Palette.SecondaryColorChanged += new EventHandler (Palette_ColorChanged); 79 PintaCore.Palette.CurrentPalette.PaletteChanged += new EventHandler (Palette_ColorChanged); 80 } 81 Palette_ColorChanged(object sender, EventArgs e)82 private void Palette_ColorChanged (object sender, EventArgs e) 83 { 84 // Color change events may be received while the widget is minimized, 85 // so we only call Invalidate() if the widget is shown. 86 if (IsRealized) 87 { 88 GdkWindow.Invalidate (); 89 } 90 } 91 OnButtonPressEvent(Gdk.EventButton ev)92 protected override bool OnButtonPressEvent (Gdk.EventButton ev) 93 { 94 if (swap_rect.ContainsPoint (ev.X, ev.Y)) { 95 Color temp = PintaCore.Palette.PrimaryColor; 96 PintaCore.Palette.PrimaryColor = PintaCore.Palette.SecondaryColor; 97 PintaCore.Palette.SecondaryColor = temp; 98 GdkWindow.Invalidate (); 99 } else if (reset_rect.ContainsPoint (ev.X, ev.Y)) { 100 PintaCore.Palette.PrimaryColor = new Color (0, 0, 0); 101 PintaCore.Palette.SecondaryColor = new Color (1, 1, 1); 102 GdkWindow.Invalidate (); 103 } 104 105 if (primary_rect.ContainsPoint (ev.X, ev.Y)) { 106 Gtk.ColorSelectionDialog csd = new Gtk.ColorSelectionDialog (Catalog.GetString ("Choose Primary Color")); 107 csd.TransientFor = PintaCore.Chrome.MainWindow; 108 csd.ColorSelection.PreviousColor = PintaCore.Palette.PrimaryColor.ToGdkColor (); 109 csd.ColorSelection.CurrentColor = PintaCore.Palette.PrimaryColor.ToGdkColor (); 110 csd.ColorSelection.CurrentAlpha = PintaCore.Palette.PrimaryColor.GdkColorAlpha (); 111 csd.ColorSelection.HasOpacityControl = true; 112 113 int response = csd.Run (); 114 115 if (response == (int)Gtk.ResponseType.Ok) { 116 PintaCore.Palette.PrimaryColor = csd.ColorSelection.GetCairoColor (); 117 } 118 119 csd.Destroy (); 120 } else if (secondary_rect.ContainsPoint (ev.X, ev.Y)) { 121 Gtk.ColorSelectionDialog csd = new Gtk.ColorSelectionDialog (Catalog.GetString ("Choose Secondary Color")); 122 csd.TransientFor = PintaCore.Chrome.MainWindow; 123 csd.ColorSelection.PreviousColor = PintaCore.Palette.SecondaryColor.ToGdkColor (); 124 csd.ColorSelection.CurrentColor = PintaCore.Palette.SecondaryColor.ToGdkColor (); 125 csd.ColorSelection.CurrentAlpha = PintaCore.Palette.SecondaryColor.GdkColorAlpha (); 126 csd.ColorSelection.HasOpacityControl = true; 127 128 int response = csd.Run (); 129 130 if (response == (int)Gtk.ResponseType.Ok) { 131 PintaCore.Palette.SecondaryColor = csd.ColorSelection.GetCairoColor (); 132 } 133 134 csd.Destroy (); 135 } 136 137 int pal = PointToPalette ((int)ev.X, (int)ev.Y); 138 139 if (pal >= 0) { 140 if (ev.Button == 3) 141 PintaCore.Palette.SecondaryColor = palette[pal]; 142 else if (ev.Button == 1) 143 PintaCore.Palette.PrimaryColor = palette[pal]; 144 else { 145 Gtk.ColorSelectionDialog csd = new Gtk.ColorSelectionDialog (Catalog.GetString ("Choose Palette Color")); 146 csd.TransientFor = PintaCore.Chrome.MainWindow; 147 csd.ColorSelection.PreviousColor = palette[pal].ToGdkColor (); 148 csd.ColorSelection.CurrentColor = palette[pal].ToGdkColor (); 149 csd.ColorSelection.CurrentAlpha = palette[pal].GdkColorAlpha (); 150 csd.ColorSelection.HasOpacityControl = true; 151 152 int response = csd.Run (); 153 154 if (response == (int)Gtk.ResponseType.Ok) { 155 palette[pal] = csd.ColorSelection.GetCairoColor (); 156 } 157 158 csd.Destroy (); 159 } 160 161 GdkWindow.Invalidate (); 162 } 163 164 // Insert button press handling code here. 165 return base.OnButtonPressEvent (ev); 166 } 167 OnExposeEvent(Gdk.EventExpose ev)168 protected override bool OnExposeEvent (Gdk.EventExpose ev) 169 { 170 base.OnExposeEvent (ev); 171 172 using (Context g = Gdk.CairoHelper.Create (GdkWindow)) { 173 174 // Draw Primary / Secondary Area 175 176 g.FillRectangle (secondary_rect, PintaCore.Palette.SecondaryColor); 177 178 g.DrawRectangle (new Rectangle (secondary_rect.X + 1, secondary_rect.Y + 1, secondary_rect.Width - 2, secondary_rect.Height - 2), new Color (1, 1, 1), 1); 179 g.DrawRectangle (secondary_rect, new Color (0, 0, 0), 1); 180 181 g.FillRectangle (primary_rect, PintaCore.Palette.PrimaryColor); 182 g.DrawRectangle (new Rectangle (primary_rect.X + 1, primary_rect.Y + 1, primary_rect.Width - 2, primary_rect.Height - 2), new Color (1, 1, 1), 1); 183 g.DrawRectangle (primary_rect, new Color (0, 0, 0), 1); 184 185 g.DrawPixbuf (swap_icon, swap_rect.Location ()); 186 g.DrawPixbuf (reset_icon, reset_rect.Location ()); 187 188 // Draw color swatches 189 190 int startI = primarySecondaryAreaSize; 191 int startJ = swatchAreaMargin; 192 193 int paletteIndex = 0; 194 for (int jRow = 0; jRow < jRows; jRow++) 195 { 196 for (int iRow = 0; iRow < iRows; iRow++) 197 { 198 if (paletteIndex >= palette.Count) 199 break; 200 201 int x = (orientation == OrientationEnum.Horizontal) ? startI + iRow * swatchSize : startJ + jRow * swatchSize; 202 int y = (orientation == OrientationEnum.Horizontal) ? startJ + jRow * swatchSize : startI + iRow * swatchSize; 203 204 g.FillRectangle(new Rectangle(x, y, swatchSize, swatchSize), palette[paletteIndex]); 205 206 paletteIndex++; 207 } 208 } 209 } 210 211 return true; 212 } 213 OnSizeRequested(ref Gtk.Requisition requisition)214 protected override void OnSizeRequested (ref Gtk.Requisition requisition) 215 { 216 // Calculate desired size here. 217 requisition.Height = requisition.Width = primarySecondaryAreaSize; 218 } 219 OnSizeAllocated(Gdk.Rectangle allocation)220 protected override void OnSizeAllocated(Gdk.Rectangle allocation) 221 { 222 base.OnSizeAllocated(allocation); 223 224 int iSpaceAvailable, jSpaceAvailable; 225 226 // The orientation is horizontal when the widget is wider than it is tall 227 // The direction of 'i' is the horizontal (left-to-right) when 228 // widget orientation is horizontal, 229 // and vertical (top-to-bottom) when widget orientation is vertical. 230 // 'j' is in the other direction. 231 if (Allocation.Width > Allocation.Height) 232 { 233 orientation = OrientationEnum.Horizontal; 234 iSpaceAvailable = Allocation.Width; 235 jSpaceAvailable = Allocation.Height; 236 } 237 else 238 { 239 orientation = OrientationEnum.Vertical; 240 iSpaceAvailable = Allocation.Height; 241 jSpaceAvailable = Allocation.Width; 242 } 243 244 iSpaceAvailable -= primarySecondaryAreaSize + swatchAreaMargin; 245 jSpaceAvailable -= 2 * swatchAreaMargin; 246 247 // Determine max number of rows that can be displayed in available area 248 int maxPossibleIRows = Math.Max(1, iSpaceAvailable / swatchSize); 249 int maxPossibleJRows = Math.Max(1, jSpaceAvailable / swatchSize); 250 251 int iRowsWithDefaultNumOfJRows = (int)Math.Ceiling((double)palette.Count / defaultNumOfJRows); 252 253 // Display palette in default configuration if space is available 254 // (it looks better) 255 if ((maxPossibleJRows >= defaultNumOfJRows) && (maxPossibleIRows >= iRowsWithDefaultNumOfJRows)) 256 { 257 iRows = iRowsWithDefaultNumOfJRows; 258 jRows = defaultNumOfJRows; 259 } 260 else // Compress palette to fit within available space 261 { 262 iRows = (int)Math.Ceiling((double)palette.Count / maxPossibleJRows); 263 jRows = (int)Math.Ceiling((double)palette.Count / iRows); 264 } 265 } 266 PointToPalette(int x, int y)267 private int PointToPalette (int x, int y) 268 { 269 int i, j; 270 271 if (orientation == OrientationEnum.Horizontal) 272 { 273 i = x; 274 j = y; 275 } 276 else 277 { 278 i = y; 279 j = x; 280 } 281 282 i -= primarySecondaryAreaSize; 283 j -= swatchAreaMargin; 284 285 // Determine the swatch position under the mouse pointer. 286 int iRow = i / swatchSize; 287 int jRow = j / swatchSize; 288 289 if ((i < 0) || (iRow >= iRows) || (j < 0) || (jRow >= jRows)) 290 { // Mouse pointer is outside of swatch area 291 return -1; 292 } 293 else if ((jRow * iRows + iRow) >= palette.Count) 294 { // Mouse pointer is within swatch area, but not on valid color 295 return -1; 296 } 297 else 298 { // Return the palette color number under the mouse pointer 299 return jRow * iRows + iRow; 300 } 301 } 302 303 /// <summary> 304 /// Provide a custom tooltip based on the cursor location. 305 /// </summary> HandleQueryTooltip(object o, Gtk.QueryTooltipArgs args)306 private void HandleQueryTooltip (object o, Gtk.QueryTooltipArgs args) 307 { 308 int x = args.X; 309 int y = args.Y; 310 string text = null; 311 312 if (swap_rect.ContainsPoint (x, y)) { 313 text = string.Format ( 314 "{0} {1}: {2}", 315 Catalog.GetString ("Click to switch between primary and secondary color."), 316 Catalog.GetString ("Shortcut key"), 317 "X" 318 ); 319 } else if (reset_rect.ContainsPoint (x, y)) { 320 text = Catalog.GetString ("Click to reset primary and secondary color."); 321 } else if (primary_rect.ContainsPoint (x, y)) { 322 text = Catalog.GetString ("Click to select primary color."); 323 } else if (secondary_rect.ContainsPoint (x, y)) { 324 text = Catalog.GetString ("Click to select secondary color."); 325 } else if (PointToPalette (x, y) >= 0) { 326 text = Catalog.GetString ("Left click to set primary color. Right click to set secondary color. Middle click to choose palette color."); 327 } 328 329 args.Tooltip.Text = text; 330 args.RetVal = (text != null); 331 } 332 } 333 } 334