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