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