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