minor fix
[synfig.git] / synfig-studio / trunk / src / gtkmm / dock_navigator.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file dock_navigator.cpp
3 **      \brief Dock Nagivator File
4 **
5 **      $Id: dock_navigator.cpp,v 1.3 2005/01/12 00:31:11 darco Exp $
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **      This package is free software; you can redistribute it and/or
11 **      modify it under the terms of the GNU General Public License as
12 **      published by the Free Software Foundation; either version 2 of
13 **      the License, or (at your option) any later version.
14 **
15 **      This package is distributed in the hope that it will be useful,
16 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **      General Public License for more details.
19 **      \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include "dock_navigator.h"
33 #include "canvasview.h"
34 #include "workarea.h"
35
36 #include <cassert>
37 #include <synfig/canvas.h>
38 #include <synfig/context.h>
39 #include <synfig/target_scanline.h>
40 #include <synfig/surface.h>
41
42 #include <gtkmm/separator.h>
43
44 #include "asyncrenderer.h"
45
46 #endif
47
48 /* === U S I N G =========================================================== */
49
50 using namespace std;
51 using namespace etl;
52 using namespace synfig;
53
54 /* === M A C R O S ========================================================= */
55
56 const double log_10_2 = log(2.0);
57
58 /* === G L O B A L S ======================================================= */
59
60 /* === P R O C E D U R E S ================================================= */
61
62 /* === M E T H O D S ======================================================= */
63
64 /* === E N T R Y P O I N T ================================================= */
65 studio::Widget_NavView::Widget_NavView(CanvasView::LooseHandle cv)
66 :canvview(cv),
67 adj_zoom(0,-4,4,1,2),
68 surface(new synfig::Surface)
69 {
70         attach(drawto,0,4,0,1);
71         
72         attach(*manage(new Gtk::HSeparator),0,4,1,2,Gtk::SHRINK|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
73         
74         //zooming stuff
75         attach(zoom_print,0,1,2,3,Gtk::SHRINK|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
76         zoom_print.set_size_request(40,-1);
77         
78         Gtk::HScale *s = manage(new Gtk::HScale(adj_zoom));
79         s->set_draw_value(false);
80         //s->set_update_policy(Gtk::UPDATE_DELAYED);
81         //s->signal_event().connect(sigc::mem_fun(*this,&Dock_Navigator::on_scroll_event));
82         attach(*s,1,4,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
83                 
84         show_all();
85         
86         adj_zoom.signal_value_changed().connect(sigc::mem_fun(*this,&Widget_NavView::on_number_modify));
87         
88         if(cv)
89         {
90                 drawto.signal_expose_event().connect(sigc::mem_fun(*this,&Widget_NavView::on_expose_draw));
91                 drawto.signal_event().connect(sigc::mem_fun(*this,&Widget_NavView::on_mouse_event));
92                 
93                 drawto.add_events(Gdk::BUTTON_MOTION_MASK|Gdk::BUTTON_PRESS_MASK);
94                 
95                 //get_canvas_view()->canvas_interface()->signal_dirty_preview()
96                 //                              .connect(sigc::mem_fun(*this,&Widget_NavView::on_dirty_preview));
97                 get_canvas_view()->work_area->signal_rendering()
98                                                 .connect(sigc::mem_fun(*this,&Widget_NavView::on_dirty_preview));
99                 
100                 get_canvas_view()->work_area->signal_view_window_changed()
101                                                 .connect(sigc::mem_fun(*this,&Widget_NavView::on_workarea_view_change));
102                 
103                 //update with this canvas' view
104                 on_workarea_view_change();
105                                 
106                 dirty = true;
107                 queue_draw();
108         }
109         
110         adj_zoom.set_value(0);
111 }
112
113 studio::Widget_NavView::~Widget_NavView()
114 {
115 }
116
117
118 static void freegu8(const guint8 *p)
119 {
120         delete [] p;
121 }
122
123 void studio::Widget_NavView::on_start_render()
124 {
125         if(dirty)
126         {
127                 //synfig::warning("Nav: Starting render");
128                 //synfig::warning("Nav: Rendering canvas");
129                 etl::handle<Target_Scanline>    targ = surface_target(surface.get());
130                 
131                 targ->set_canvas(get_canvas_view()->get_canvas());
132                 targ->set_remove_alpha();
133                 targ->set_avoid_time_sync();
134                 targ->set_quality(get_canvas_view()->get_work_area()->get_quality());
135                 //synfig::info("Set the quality level to: %d", get_canvas_view()->get_work_area()->get_quality());
136                 
137                 //this should set it to render a single frame
138                 RendDesc        r = get_canvas_view()->get_canvas()->rend_desc();
139                 r.set_time(get_canvas_view()->canvas_interface()->get_time());
140                 
141                 //this changes the size of the canvas to the closest thing we can find
142                 int sw = r.get_w(), sh = r.get_h();
143                 
144                 //synfig::warning("Nav: source image is %d x %d", sw,sh);
145                 
146                 //resize so largest dimension is 128
147                 int dw = sw > sh ? 128 : sw*128/sh,
148                         dh = sh > sw ? 128 : sh*128/sw;
149                 
150                 //synfig::warning("Nav: dest image is %d x %d", dw,dh);
151                 
152                 r.set_w(dw);
153                 r.set_h(dh);
154
155                 //get the pw and ph
156                 //float pw = r.get_pw();
157                 //float ph = r.get_ph();
158                 
159                 //synfig::warning("Nav: pixel size is %f x %f", pw,ph);
160                 
161                 //this renders that single frame
162                 targ->set_rend_desc(&r);
163                 
164                 //synfig::warning("Nav: Building async renderer and starting it...");
165                 
166                 renderer = new AsyncRenderer(targ);
167                 renderer->signal_success().connect(sigc::mem_fun(*this,&Widget_NavView::on_finish_render));
168                 renderer->start();
169                 dirty = false;
170         }
171 }
172
173 void studio::Widget_NavView::on_finish_render()
174 {
175         //convert it into our pixmap
176         PixelFormat pf(PF_RGB);
177         
178         //synfig::warning("Nav: It hath succeeded!!!");
179         
180         //assert(renderer && renderer->has_success());
181         DEBUGPOINT();
182         //synfig::warning("Nav: now we know it really succeeded");
183         if(!*surface)
184         {
185                 synfig::warning("dock_navigator: Bad surface");
186                 return;
187         }
188         
189         int w = 0, h = 0;
190         int dw = surface->get_w();
191         int dh = surface->get_h();
192         
193         if(prev)
194         {
195                 w = prev->get_width();
196                 h = prev->get_height();
197         }
198         
199         if(w != dw || h != dh || !prev)
200         {
201                 const int total_bytes(dw*dh*synfig::channels(pf));
202                 
203                 //synfig::warning("Nav: Updating the pixbuf to be the right size, etc. (%d bytes)", total_bytes);
204                 
205                 prev.clear();
206                 guint8 *bytes = new guint8[total_bytes]; //24 bits per pixel
207                 
208                 //convert into our buffered dataS
209                 //synfig::warning("Nav: converting color format into buffer");
210                 convert_color_format((unsigned char *)bytes, (*surface)[0], dw*dh, pf, App::gamma);
211                 
212                 prev = 
213                 Gdk::Pixbuf::create_from_data(
214                         bytes,  // pointer to the data
215                         Gdk::COLORSPACE_RGB, // the colorspace
216                         ((pf&PF_A)==PF_A), // has alpha?
217                         8, // bits per sample
218                         dw,     // width
219                         dh,     // height
220                         dw*synfig::channels(pf), // stride (pitch)
221                         SigC::slot(freegu8)
222                 );
223         }
224         else
225         {
226                 //synfig::warning("Nav: Don't need to resize");
227                 //convert into our buffered dataS
228                 //synfig::warning("Nav: converting color format into buffer");
229                 if(prev) //just in case we're stupid
230                 {
231                         convert_color_format((unsigned char *)prev->get_pixels(), (*surface)[0], dw*dh, pf, App::gamma);
232                 }
233         }
234         queue_draw();   
235 }
236
237 /*      zoom slider is on exponential scale
238
239         map: -4,4 -> small number,1600 with 100 at 0
240
241         f(x) = 100*2^x
242 */
243
244 static double unit_to_zoom(double f)
245 {
246         return pow(2.0,f);
247 }
248
249 static double zoom_to_unit(double f)
250 {
251         if(f > 0)
252         {
253                 return log(f) / log_10_2;
254         }else return -999999.0;
255 }
256
257 bool studio::Widget_NavView::on_expose_draw(GdkEventExpose *exp)
258 {
259         //print out the zoom
260         //HACK kind of...
261         //zoom_print.set_text(strprintf("%.1f%%",100*unit_to_zoom(adj_zoom.get_value())));
262         
263         //draw the good stuff
264         on_start_render();
265         
266         //if we've got a preview etc. display it...
267         if(get_canvas_view() && prev)
268         {
269                 //axis transform from units to pixel coords
270                 float xaxis = 0, yaxis = 0;
271                 
272                 int canvw = get_canvas_view()->get_canvas()->rend_desc().get_w();
273                 //int canvh = get_canvas_view()->get_canvas()->rend_desc().get_h();
274                 
275                 float pw = get_canvas_view()->get_canvas()->rend_desc().get_pw();
276                 float ph = get_canvas_view()->get_canvas()->rend_desc().get_ph();
277                 
278                 int w = prev->get_width();
279                 int h = prev->get_height();
280                                 
281                 //scale up/down to the nearest pixel ratio...
282                 //and center in center
283                 int offx=0, offy=0;
284                 
285                 float sx, sy;
286                 int nw,nh;
287                 
288                 sx = drawto.get_width() / (float)w;
289                 sy = drawto.get_height() / (float)h;
290                 
291                 //synfig::warning("Nav redraw: now to scale the bitmap: %.3f x %.3f",sx,sy);
292                 
293                 //round to smallest scale (fit entire thing in window without distortion)
294                 if(sx > sy) sx = sy;
295                 //else sy = sx;
296                 
297                 //scaling and stuff
298                 // the point to navpixel space conversion should be:
299                 //              (navpixels / canvpixels) * (canvpixels / canvsize)
300                 //      or (navpixels / prevpixels) * (prevpixels / navpixels)
301                 xaxis = sx * w / (float)canvw;
302                 yaxis = xaxis/ph;
303                 xaxis /= pw;
304                 
305                 //scale to a new pixmap and then copy over to the window
306                 nw = (int)(w*sx);
307                 nh = (int)(h*sx);
308                 
309                 //must now center to be cool
310                 offx = (drawto.get_width() - nw)/2;
311                 offy = (drawto.get_height() - nh)/2;
312                 
313                 //trivial escape
314                 if(nw == 0 || nh == 0)return true;
315                                                         
316                 //draw to drawing area
317                 Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(drawto.get_window());
318                 
319                 //synfig::warning("Nav: Scaling pixmap to off (%d,%d) with size (%d,%d)", offx,offy,nw, nh);
320                 Glib::RefPtr<Gdk::Pixbuf> scalepx = prev->scale_simple(nw,nh,Gdk::INTERP_NEAREST);
321                 
322                 //synfig::warning("Nav: Drawing scaled bitmap");
323                 drawto.get_window()->draw_pixbuf(
324                         gc, //GC
325                         scalepx, //pixbuf
326                         0, 0,   // Source X and Y
327                         offx, offy,     // Dest X and Y
328                         -1,-1,  // Width and Height
329                         Gdk::RGB_DITHER_MAX, // RgbDither
330                         2, 2 // Dither offset X and Y
331                 );
332                                 
333                 //draw fancy red rectangle around focus point
334                 const Point &wtl = get_canvas_view()->work_area->get_window_tl(),
335                                         &wbr = get_canvas_view()->work_area->get_window_br();
336                 
337                 gc->set_rgb_fg_color(Gdk::Color("#ff0000"));
338                 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
339                 
340                 //it must be clamped to the drawing area though
341                 int l=0,rw=0,t=0,rh=0;
342                 const Point fp = -get_canvas_view()->work_area->get_focus_point();
343                 
344                 //get focus point in normal space
345                 rw = (int)(abs((wtl[0]-wbr[0])*xaxis));
346                 rh = (int)(abs((wtl[1]-wbr[1])*yaxis));
347
348                 //transform into pixel space
349                 l = (int)(drawto.get_width()/2 + fp[0]*xaxis - rw/2);
350                 t = (int)(drawto.get_height()/2 + fp[1]*yaxis - rh/2);
351         
352                 //coord system:
353                 // tl : (offx,offy)
354                 // axis multipliers = xaxis,yaxis
355                 //synfig::warning("Nav: tl (%f,%f), br (%f,%f)", wtl[0],wtl[1],wbr[0],wbr[1]);
356                 //synfig::warning("Nav: tl (%f,%f), br (%f,%f)", wtl[0],wtl[1],wbr[0],wbr[1]);
357                 //synfig::warning("Nav: Drawing Rectangle (%d,%d) with dim (%d,%d)", l,t,rw,rh);
358                 drawto.get_window()->draw_rectangle(gc,false,l,t,rw,rh);
359         }
360         
361         return false; //draw everything else too
362 }
363
364 void studio::Widget_NavView::on_dirty_preview()
365 {
366         dirty = true;
367         queue_draw();
368 }
369
370 bool studio::Widget_NavView::on_scroll_event(GdkEvent *event)
371 {       
372         if(get_canvas_view() && get_canvas_view()->get_work_area())
373         {
374                 double z = unit_to_zoom(adj_zoom.get_value());
375                 
376                 switch(event->type)
377                 {
378                         case GDK_BUTTON_PRESS:
379                         {
380                                 if(event->button.button == 1)
381                                 {
382                                         scrolling = true;
383                                         get_canvas_view()->get_work_area()->set_zoom(z);                                        
384                                         scrolling = false;
385                                 }
386                                 break;
387                         }
388                         
389                         case GDK_MOTION_NOTIFY:
390                         {
391                                 if(Gdk::ModifierType(event->motion.state) & Gdk::BUTTON1_MASK)
392                                 {
393                                         scrolling = true;
394                                         get_canvas_view()->get_work_area()->set_zoom(z);
395                                         scrolling = false;
396                                 }
397                                 break;
398                         }
399                         
400                         default:
401                                 break;
402                 }
403         }
404         
405         return false;
406 }
407
408 void studio::Widget_NavView::on_number_modify()
409 {
410         double z = unit_to_zoom(adj_zoom.get_value());
411         zoom_print.set_text(strprintf("%.1f%%",z*100.0));       
412         //synfig::warning("Updating zoom to %f",adj_zoom.get_value());
413         
414         if(get_canvas_view() && z != get_canvas_view()->get_work_area()->get_zoom())
415         {
416                 scrolling = true;
417                 get_canvas_view()->get_work_area()->set_zoom(z);
418                 scrolling = false;
419         }
420 }
421
422 void studio::Widget_NavView::on_workarea_view_change()
423 {
424         double wz = get_canvas_view()->get_work_area()->get_zoom();
425         double z = zoom_to_unit(wz);
426
427         //synfig::warning("Updating zoom to %f -> %f",wz,z);
428         if(!scrolling && z != adj_zoom.get_value())
429         {
430                 adj_zoom.set_value(z);
431                 //adj_zoom.value_changed();
432         }
433         queue_draw();
434 }
435
436 bool studio::Widget_NavView::on_mouse_event(GdkEvent * e)
437 {
438         Point p;
439         bool    setpos = false;
440                 
441         if(e->type == GDK_BUTTON_PRESS && e->button.button == 1)
442         {
443                 p[0] = e->button.x - drawto.get_width()/2;
444                 p[1] = e->button.y - drawto.get_height()/2;
445                 
446                 setpos = true;
447         }
448         
449         if(e->type == GDK_MOTION_NOTIFY && (Gdk::ModifierType(e->motion.state) & Gdk::BUTTON1_MASK))
450         {               
451                 p[0] = e->motion.x - drawto.get_width()/2;
452                 p[1] = e->motion.y - drawto.get_height()/2;     
453                 
454                 setpos = true;
455         }
456         
457         if(setpos && prev && get_canvas_view())
458         {
459                 const Point &tl = get_canvas_view()->get_canvas()->rend_desc().get_tl();
460                 const Point &br = get_canvas_view()->get_canvas()->rend_desc().get_br();
461                 
462                 float max = abs((br[0]-tl[0]) / drawto.get_width());
463                 
464                 if((prev->get_width() / drawto.get_width()) < (prev->get_height() / drawto.get_height()))
465                         max = abs((br[1]-tl[1]) / drawto.get_height());
466                 
467                 float signx = (br[0]-tl[0]) < 0 ? -1 : 1;
468                 float signy = (br[1]-tl[1]) < 0 ? -1 : 1;
469                 
470                 Point pos;
471                 
472                 pos[0] = p[0] * max * signx;
473                 pos[1] = p[1] * max * signy;
474                 
475                 get_canvas_view()->get_work_area()->set_focus_point(-pos);
476                 
477                 return true;
478         }
479         
480         return false;
481 }
482
483 //Navigator Dock Definitions
484
485 studio::Dock_Navigator::Dock_Navigator()
486 :Dock_CanvasSpecific("navigator",_("Navigator"),Gtk::StockID("synfig-navigator"))
487 {
488         add(dummy);
489 }
490
491 studio::Dock_Navigator::~Dock_Navigator()
492 {
493 }
494
495 void studio::Dock_Navigator::changed_canvas_view_vfunc(etl::loose_handle<CanvasView> canvas_view)
496 {       
497         if(canvas_view)
498         {               
499                 Widget *v = canvas_view->get_ext_widget("navview");
500                 
501                 if(!v)
502                 {
503                         v = new Widget_NavView(canvas_view);
504                         canvas_view->set_ext_widget("navview",v);
505                 }
506
507                 add(*v);
508         }else
509         {
510                 clear_previous();
511                 //add(dummy);                   
512         }
513 }