1 /* === S Y N F I G ========================================================= */
2 /*!	\file widget_gradient.cpp
3 **	\brief Template File
4 **
5 **	$Id$
6 **
7 **	\legal
8 **	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **	Copyright (c) 2007 Chris Moore
10 **
11 **	This package is free software; you can redistribute it and/or
12 **	modify it under the terms of the GNU General Public License as
13 **	published by the Free Software Foundation; either version 2 of
14 **	the License, or (at your option) any later version.
15 **
16 **	This package is distributed in the hope that it will be useful,
17 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **	General Public License for more details.
20 **	\endlegal
21 */
22 /* ========================================================================= */
23 
24 /* === H E A D E R S ======================================================= */
25 
26 #ifdef USING_PCH
27 #	include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #	include <config.h>
31 #endif
32 
33 #include <synfig/general.h>
34 
35 #include "widgets/widget_gradient.h"
36 #include "app.h"
37 #include <gtkmm/menu.h>
38 #include <synfig/exception.h>
39 #include <ETL/misc>
40 
41 #include <gui/localization.h>
42 
43 #endif
44 
45 /* === U S I N G =========================================================== */
46 
47 using namespace std;
48 using namespace etl;
49 using namespace synfig;
50 using namespace studio;
51 
52 /* === M A C R O S ========================================================= */
53 
54 #define ARROW_NEGATIVE_THRESHOLD 0.4
55 
56 /* === G L O B A L S ======================================================= */
57 
58 /* === P R O C E D U R E S ================================================= */
59 
60 void
render_gradient_to_window(const Cairo::RefPtr<Cairo::Context> & cr,const Gdk::Rectangle & ca,const synfig::Gradient & gradient)61 studio::render_gradient_to_window(const Cairo::RefPtr<Cairo::Context>& cr,const Gdk::Rectangle& ca,const synfig::Gradient &gradient)
62 {
63 	double	height = ca.get_height();
64 	double	width = ca.get_width();
65 
66 	Cairo::RefPtr<Cairo::LinearGradient> gpattern = Cairo::LinearGradient::create(ca.get_x(), ca.get_y(), ca.get_x()+width, ca.get_y());
67 	double a, r, g, b;
68 	Gradient::CPoint cp;
69 	Gradient::const_iterator iter;
70 	for(iter=gradient.begin();iter!=gradient.end(); iter++)
71 	{
72 		cp=*iter;
73 		a=cp.color.get_a();
74 		r=cp.color.get_r();
75 		g=cp.color.get_g();
76 		b=cp.color.get_b();
77 		gpattern->add_color_stop_rgba(cp.pos, r, g, b, a);
78 	}
79 
80 	cr->save();
81 	cr->rectangle(ca.get_x(), ca.get_y(), ca.get_width()-2, ca.get_height());
82 	cr->set_source(gpattern);
83 	cr->fill();
84 	cr->restore();
85 
86 	cr->save();
87 	cr->set_line_width(1.0);
88 	cr->set_source_rgb(1.0, 1.0, 1.0);
89 	cr->rectangle(ca.get_x()+1.5, ca.get_y()+1.5, width-3, height-3);
90 	cr->stroke();
91 	cr->restore();
92 	cr->save();
93 	cr->set_line_width(1.0);
94 	cr->set_source_rgb(0.0, 0.0, 0.0);
95 	cr->rectangle(ca.get_x()+0.5, ca.get_y()+0.5, width-1, height-1);
96 	cr->stroke();
97 	cr->restore();
98 }
99 
100 /* === M E T H O D S ======================================================= */
101 
Widget_Gradient()102 Widget_Gradient::Widget_Gradient():
103 	editable_(false)
104 {
105 	set_size_request(-1,64);
106 	add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
107 	add_events(Gdk::BUTTON1_MOTION_MASK);
108 
109 }
110 
~Widget_Gradient()111 Widget_Gradient::~Widget_Gradient()
112 {
113 }
114 
115 #define CONTROL_HEIGHT		16
116 bool
on_draw(const::Cairo::RefPtr<::Cairo::Context> & cr)117 Widget_Gradient::on_draw(const ::Cairo::RefPtr< ::Cairo::Context>& cr)
118 {
119 	const int h(get_height());
120 	const int w(get_width());
121 
122 	Gdk::Rectangle area(0,0,w,h);
123 	if(!editable_)
124 	{
125 		render_gradient_to_window(cr,area,gradient_);
126 		return true;
127 	}
128 
129 	render_gradient_to_window(cr,Gdk::Rectangle(0,0,w,h),gradient_);
130 
131 	Gradient::iterator iter,selected_iter;
132 	bool show_selected(false);
133 	for(iter=gradient_.begin();iter!=gradient_.end();iter++)
134 	{
135 		if(*iter!=selected_cpoint)
136 		{
137 			get_style_context()->render_arrow(
138 				cr,
139 				1.5*M_PI,
140 				int(iter->pos*w)-CONTROL_HEIGHT/2+1,
141 				h-CONTROL_HEIGHT,
142 				CONTROL_HEIGHT
143 			);
144 		}
145 		else
146 		{
147 			selected_iter=iter;
148 			show_selected=true;
149 		}
150 	}
151 
152 	// we do this so that we can be sure that
153 	// the selected marker is shown on top
154   // show 2 arrows for selected, to compensate for lack of contrast in some color schemes, such as ubuntu default theme
155   // Gtk::STATE_SELECTED was used, but resulted in a barely visible arrow in some color scheme, hence double arrow with contrasting color
156 
157 	if(show_selected)
158 	{
159 		get_style_context()->render_arrow(
160 			cr,
161 			1.5*M_PI,
162 			round_to_int(selected_iter->pos*w)-CONTROL_HEIGHT/2+1,
163 			h-CONTROL_HEIGHT,
164 			CONTROL_HEIGHT
165 		);
166 		get_style_context()->render_arrow(
167 			cr,
168 			1.5*M_PI,
169 			round_to_int(selected_iter->pos*w)-CONTROL_HEIGHT/2+1,
170 			h-CONTROL_HEIGHT*1.3,
171 			CONTROL_HEIGHT
172 		);
173 	}
174 
175 	return true;
176 }
177 
178 void
insert_cpoint(float x)179 Widget_Gradient::insert_cpoint(float x)
180 {
181 	Gradient::CPoint new_cpoint;
182 	new_cpoint.pos=x;
183 	new_cpoint.color=gradient_(x);
184 	gradient_.push_back(new_cpoint);
185 	gradient_.sort();
186 	gradient_.sort();
187 	set_selected_cpoint(new_cpoint);
188 	queue_draw();
189 }
190 
191 void
remove_cpoint(float x)192 Widget_Gradient::remove_cpoint(float x)
193 {
194 	gradient_.erase(gradient_.proximity(x));
195 	signal_value_changed_();
196 	queue_draw();
197 }
198 
199 void
popup_menu(float x)200 Widget_Gradient::popup_menu(float x)
201 {
202 	Gtk::Menu* menu(manage(new Gtk::Menu()));
203 	menu->signal_hide().connect(sigc::bind(sigc::ptr_fun(&delete_widget), menu));
204 
205 	std::vector<Gtk::Widget*> children = menu->get_children();
206 	for(std::vector<Gtk::Widget*>::iterator i = children.begin(); i != children.end(); ++i)
207 		menu->remove(**i);
208 
209 	Gtk::MenuItem *item = NULL;
210 
211 	item = manage(new Gtk::MenuItem(_("Insert Color Stop")));
212 	item->signal_activate().connect(
213 		sigc::bind(
214 			sigc::mem_fun(*this,&studio::Widget_Gradient::insert_cpoint),
215 			x ));
216 	item->show();
217 	menu->append(*item);
218 
219 	if(!gradient_.empty())
220 	{
221 		item = manage(new Gtk::MenuItem(_("Remove Color Stop")));
222 		item->signal_activate().connect(
223 			sigc::bind(
224 				sigc::mem_fun(*this,&studio::Widget_Gradient::remove_cpoint),
225 				x ));
226 		item->show();
227 		menu->append(*item);
228 	}
229 
230 	menu->popup(0,0);
231 }
232 
233 void
set_value(const synfig::Gradient & x)234 Widget_Gradient::set_value(const synfig::Gradient& x)
235 {
236 	gradient_=x;
237 	if(gradient_.size())
238 		set_selected_cpoint(*gradient_.proximity(0.0f));
239 	queue_draw();
240 }
241 
242 void
set_selected_cpoint(const synfig::Gradient::CPoint & x)243 Widget_Gradient::set_selected_cpoint(const synfig::Gradient::CPoint &x)
244 {
245 	selected_cpoint=x;
246 	signal_cpoint_selected_(selected_cpoint);
247 	queue_draw();
248 }
249 
250 void
update_cpoint(const synfig::Gradient::CPoint & x)251 Widget_Gradient::update_cpoint(const synfig::Gradient::CPoint &x)
252 {
253 	try
254 	{
255 		Gradient::iterator iter(gradient_.find(x));
256 		iter->pos=x.pos;
257 		iter->color=x.color;
258 		gradient_.sort();
259 		queue_draw();
260 	}
261 	catch(synfig::Exception::NotFound)
262 	{
263 		// Yotta...
264 	}
265 }
266 
267 bool
on_event(GdkEvent * event)268 Widget_Gradient::on_event(GdkEvent *event)
269 {
270 	//if(editable_)
271 	{
272 		const int x(static_cast<int>(event->button.x));
273 		const int y(static_cast<int>(event->button.y));
274 
275 		float pos((float)x/(float)get_width());
276 		if(pos<0.0f)pos=0.0f;
277 		if(pos>1.0f)pos=1.0f;
278 
279 		switch(event->type)
280 		{
281 		case GDK_MOTION_NOTIFY:
282 			if(editable_ && y>get_height()-CONTROL_HEIGHT)
283 			{
284 				if(!gradient_.size()) return true;
285 				Gradient::iterator iter(gradient_.find(selected_cpoint));
286 				//! Use SHIFT to stack two CPoints together.
287 				if(event->button.state&GDK_SHIFT_MASK)
288 				{
289 					float begin(-100000000),end(100000000);
290 					Gradient::iterator before(iter),after(iter);
291 					after++;
292 					if(iter!=gradient_.begin())
293 					{
294 						before--;
295 						begin=before->pos;
296 					}
297 					if(after!=gradient_.end())
298 					{
299 						end=after->pos;
300 					}
301 
302 					if(pos>end)
303 						pos=end;
304 					if(pos<begin)
305 						pos=begin;
306 
307 					iter->pos=pos;
308 				}
309 				else
310 				{
311 					iter->pos=pos;
312 					gradient_.sort();
313 				}
314 
315 //				signal_value_changed_();
316 				changed_=true;
317 				queue_draw();
318 				return true;
319 			}
320 			break;
321 		case GDK_BUTTON_PRESS:
322 			changed_=false;
323 			if(event->button.button==1)
324 			{
325 				if(editable_ && y>get_height()-CONTROL_HEIGHT)
326 				{
327 					set_selected_cpoint(*gradient_.proximity(pos));
328 					queue_draw();
329 					return true;
330 				}
331 				else
332 				{
333 					signal_clicked_();
334 					return true;
335 				}
336 			}
337 			else if(editable_ && event->button.button==3)
338 			{
339 				popup_menu(pos);
340 				return true;
341 			}
342 			break;
343 		case GDK_BUTTON_RELEASE:
344 			if(editable_ && event->button.button==1 && y>get_height()-CONTROL_HEIGHT)
345 			{
346 				set_selected_cpoint(*gradient_.proximity(pos));
347 				if(changed_)signal_value_changed_();
348 				return true;
349 			}
350 			break;
351 		default:
352 			break;
353 		}
354 	}
355 
356 	return false;
357 }
358