X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=synfig-studio%2Ftrunk%2Fsrc%2Fgtkmm%2Fpreview.cpp;h=d681d3e6e9d5ecaab5fb65958806363577812267;hb=9459638ad6797b8139f1e9f0715c96076dbf0890;hp=23d3d2395c71af84cd1778a985caae5343b1661f;hpb=02252941b29de64037116f4d37991a38d9ff0d94;p=synfig.git diff --git a/synfig-studio/trunk/src/gtkmm/preview.cpp b/synfig-studio/trunk/src/gtkmm/preview.cpp index 23d3d23..d681d3e 100644 --- a/synfig-studio/trunk/src/gtkmm/preview.cpp +++ b/synfig-studio/trunk/src/gtkmm/preview.cpp @@ -2,19 +2,21 @@ /*! \file preview.cpp ** \brief Preview implementation file ** -** $Id: preview.cpp,v 1.2 2005/01/10 08:13:44 darco Exp $ +** $Id$ ** ** \legal -** Copyright (c) 2002 Robert B. Quattlebaum Jr. +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2007 Chris Moore ** -** This software and associated documentation -** are CONFIDENTIAL and PROPRIETARY property of -** the above-mentioned copyright holder. +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. ** -** You may not copy, print, publish, or in any -** other way distribute this software without -** a prior written agreement with -** the copyright holder. +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. ** \endlegal */ /* ========================================================================= */ @@ -39,6 +41,9 @@ #include #include "asyncrenderer.h" + +#include "general.h" + #endif /* === U S I N G =========================================================== */ @@ -61,15 +66,15 @@ using namespace studio; class studio::Preview::Preview_Target : public Target_Scanline { Surface surface; - + sigc::signal signal_frame_done_; - + int scanline; - + double tbegin,tend; - + int nframes,curframe; - + public: Preview_Target() @@ -89,25 +94,25 @@ public: /*synfig::warning("Succeeded in setting the desc to new one: %d x %d, %.2f fps [%.2f,%.2f]", desc.get_w(),desc.get_h(),desc.get_frame_rate(), (float)desc.get_time_start(),(float)desc.get_time_end());*/ - + surface.set_wh(desc.get_w(),desc.get_h()); - - curframe = 0; + + curframe = 0; nframes = (int)floor((desc.get_time_end() - desc.get_time_start())*desc.get_frame_rate()); - + tbegin = desc.get_time_start(); tend = tbegin + nframes/desc.get_frame_rate(); - - return true; + + return true; } return false; } - - virtual bool start_frame(ProgressCallback *cb=NULL) + + virtual bool start_frame(ProgressCallback */*cb*/=NULL) { - return true; + return true; } - + virtual void end_frame() { //ok... notify our subscribers... @@ -115,19 +120,19 @@ public: curframe += 1; //synfig::warning("Finished the frame stuff, and changed time to %.3f",t); } - + virtual Color * start_scanline(int scanline) { return surface[scanline]; } - + virtual bool end_scanline() {return true;} - + sigc::signal &signal_frame_done() {return signal_frame_done_;} - + const Surface &get_surface() const {return surface;} - - float get_time() const + + float get_time() const { double time = ((nframes-curframe)/(double)nframes)*tbegin + ((curframe)/(double)nframes)*tend; @@ -145,7 +150,7 @@ studio::Preview::Preview(const studio::CanvasView::LooseHandle &h, float zoom, f void studio::Preview::set_canvasview(const studio::CanvasView::LooseHandle &h) { canvasview = h; - + if(canvasview) { //perhaps reset override values... @@ -170,36 +175,36 @@ void studio::Preview::render() { //render using the preview target etl::handle target = new Preview_Target; - + //connect our information to his... //synfig::warning("Connecting to the end frame function..."); target->signal_frame_done().connect(sigc::mem_fun(*this,&Preview::frame_finish)); - + //set the options //synfig::warning("Setting Canvas"); target->set_canvas(get_canvas()); target->set_quality(quality); - + //render description RendDesc desc = get_canvas()->rend_desc(); - + //set the global fps of the preview set_global_fps(desc.get_frame_rate()); - + desc.clear_flags(); - + int neww = (int)floor(desc.get_w()*zoom+0.5), newh = (int)floor(desc.get_h()*zoom+0.5); float newfps = fps; - + /*synfig::warning("Setting the render description: %d x %d, %f fps, [%f,%f]", neww,newh,newfps, overbegin?begintime:(float)desc.get_time_start(), overend?endtime:(float)desc.get_time_end());*/ - + desc.set_w(neww); desc.set_h(newh); desc.set_frame_rate(newfps); - + if(overbegin) { desc.set_time_start(std::max(begintime,(float)desc.get_time_start())); @@ -210,17 +215,17 @@ void studio::Preview::render() desc.set_time_end(std::min(endtime,(float)desc.get_time_end())); //synfig::warning("Set end time to %.2f...",(float)desc.get_time_end()); } - + //setting the description - - //HACK - BECAUSE THE RENDERER CAN'T RENDER INCLUDING THE LAST FRAME - desc.set_time_end(desc.get_time_end() + 1.3/fps); - + + //HACK - add on one extra frame because the renderer can't render the last frame + desc.set_time_end(desc.get_time_end() + 1.000001/fps); + target->set_rend_desc(&desc); - + //... first we must clear our current selves of space frames.resize(0); - + //now tell it to go... with inherited prog. reporting... //synfig::info("Rendering Asynchronously..."); if(renderer) renderer->stop(); @@ -241,28 +246,28 @@ void studio::Preview::frame_finish(const Preview_Target *targ) float time = targ->get_time(); const Surface &surf = targ->get_surface(); const RendDesc& r = targ->get_rend_desc(); - + //synfig::warning("Finished a frame at %f s",time); - + //copy EVERYTHING! PixelFormat pf(PF_RGB); const int total_bytes(r.get_w()*r.get_h()*synfig::channels(pf)); - + //synfig::warning("Creating a buffer"); unsigned char *buffer((unsigned char*)malloc(total_bytes)); if(!buffer) return; - //convert all the pixles to the pixbuf... buffer... thing... - //synfig::warning("Converting..."); + //convert all the pixels to the pixbuf... buffer... thing... + //synfig::warning("Converting..."); convert_color_format(buffer, surf[0], surf.get_w()*surf.get_h(), pf, App::gamma); - + //load time - fe.t = time; + fe.t = time; //uses and manages the memory for the buffer... //synfig::warning("Create a pixmap..."); - fe.buf = + fe.buf = Gdk::Pixbuf::create_from_data( buffer, // pointer to the data Gdk::COLORSPACE_RGB, // the colorspace @@ -273,44 +278,47 @@ void studio::Preview::frame_finish(const Preview_Target *targ) surf.get_w()*synfig::channels(pf), // stride (pitch) sigc::ptr_fun(free_guint8) ); - + //add the flipbook element to the list (assume time is correct) //synfig::info("Prev: Adding %f s to the list", time); frames.push_back(fe); - + signal_changed()(); } -#define IMAGIFY_BUTTON(button,stockid) \ +#define IMAGIFY_BUTTON(button,stockid,tooltip) \ icon=manage(new Gtk::Image(Gtk::StockID(stockid),Gtk::ICON_SIZE_BUTTON)); \ button->add(*icon); \ + tooltips.set_tip(*button,tooltip); \ icon->set_padding(0,0);\ - icon->show(); + icon->show(); Widget_Preview::Widget_Preview() :Gtk::Table(5,5,false), adj_time_scrub(0,0,1000,1,10,0), scr_time_scrub(adj_time_scrub), b_loop(/*_("Loop")*/), +currentindex(0), +audiotime(0), adj_sound(0,0,4), l_lasttime("0s"), playing(false) { //connect to expose events //signal_expose_event().connect(sigc::mem_fun(*this, &studio::Widget_Preview::redraw)); - + //manage all the change in values etc... adj_time_scrub.signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Preview::slider_move)); scr_time_scrub.signal_event().connect(sigc::mem_fun(*this,&Widget_Preview::scroll_move_event)); draw_area.signal_expose_event().connect(sigc::mem_fun(*this,&Widget_Preview::redraw)); - + disp_sound.set_time_adjustment(&adj_sound); timedisp = -1; - + //Set up signals to modify time value as it should be... disp_sound.signal_start_scrubbing().connect(sigc::mem_fun(*this,&Widget_Preview::scrub_updated)); disp_sound.signal_scrub().connect(sigc::mem_fun(*this,&Widget_Preview::scrub_updated)); - + /* --------------------------------- | | @@ -323,87 +331,87 @@ playing(false) --------------------------------- |loop|play|stop | hbox |lastl|lastt|rerender|haltrend | hbox - | + | |sound | */ - + Gtk::HBox *hbox = 0; Gtk::Button *button = 0; Gtk::Image *icon = 0; - - //should set up the dialog using attach etc. + + //should set up the dialog using attach etc. attach(draw_area, 0, 1, 0, 1); attach(scr_time_scrub, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK); - + #if 1 - + //2nd row hbox = manage(new Gtk::HBox); - + button = &b_loop; - IMAGIFY_BUTTON(button,Gtk::Stock::REFRESH); + IMAGIFY_BUTTON(button,Gtk::Stock::REFRESH,_("Toggle Looping")); hbox->pack_start(b_loop,Gtk::PACK_SHRINK,0); //attach(b_loop,0,1,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + button = manage(new Gtk::Button(/*_("Play")*/)); button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::play)); - IMAGIFY_BUTTON(button,Gtk::Stock::GO_FORWARD); + IMAGIFY_BUTTON(button,Gtk::Stock::GO_FORWARD,_("Play")); hbox->pack_start(*button,Gtk::PACK_SHRINK,0); //attach(*button,1,2,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + button = manage(new Gtk::Button(/*_("Stop")*/)); button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::stop)); - IMAGIFY_BUTTON(button,Gtk::Stock::NO); + IMAGIFY_BUTTON(button,Gtk::Stock::NO,_("Stop")); hbox->pack_start(*button,Gtk::PACK_SHRINK,0); //attach(*button,2,3,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + //attack the stop render and erase all buttons to same line... { Gtk::VSeparator *vsep = manage(new Gtk::VSeparator); hbox->pack_start(*vsep,Gtk::PACK_SHRINK,0); } - + button = manage(new Gtk::Button(/*_("Halt Render")*/)); button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::stoprender)); - IMAGIFY_BUTTON(button,Gtk::Stock::STOP); + IMAGIFY_BUTTON(button,Gtk::Stock::STOP,_("Halt Render")); hbox->pack_start(*button,Gtk::PACK_SHRINK,0); //attach(*button,2,3,3,4,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + button = manage(new Gtk::Button(/*_("Re-Preview")*/)); button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::repreview)); - IMAGIFY_BUTTON(button,Gtk::Stock::CONVERT); + IMAGIFY_BUTTON(button,Gtk::Stock::CONVERT,_("Re-Preview")); hbox->pack_start(*button,Gtk::PACK_SHRINK,0); //attach(*button,0,2,4,5,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + button = manage(new Gtk::Button(/*_("Erase All")*/)); button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::eraseall)); - //IMAGIFY_BUTTON(button,Gtk::Stock::DELETE); + IMAGIFY_BUTTON(button,Gtk::Stock::DELETE,_("Erase All")); hbox->pack_start(*button,Gtk::PACK_SHRINK,0); //attach(*button,2,3,4,5,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + hbox->show_all(); attach(*hbox,0,1,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL); - + //3rd row hbox = manage(new Gtk::HBox); { - Gtk::Label *label = manage(new Gtk::Label("Last Rendered: ")); + Gtk::Label *label = manage(new Gtk::Label(_("Last Rendered: "))); //label->show(); hbox->pack_start(*label,Gtk::PACK_SHRINK,10); - //attach(*manage(new Gtk::Label("Last Rendered: ")),0,1,3,4,Gtk::SHRINK,Gtk::SHRINK); + //attach(*manage(new Gtk::Label(_("Last Rendered: "))),0,1,3,4,Gtk::SHRINK,Gtk::SHRINK); } //l_lasttime.show(); hbox->pack_start(l_lasttime,Gtk::PACK_SHRINK,0); hbox->show_all(); attach(*hbox,0,1,3,4,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); //attach(l_lasttime,0,1,3,4,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + //5th row disp_sound.set_size_request(-1,32); attach(disp_sound,0,1,4,5,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK); - + show_all(); - + //if(draw_area.get_window()) gc_area = Gdk::GC::create(draw_area.get_window()); #endif } @@ -416,32 +424,32 @@ void studio::Widget_Preview::update() { //the meat goes in this locker... double time = adj_time_scrub.get_value(); - + //find the frame and display it... if(preview) { - //synfig::warning("Updating at %.3f s",time); - + //synfig::warning("Updating at %.3f s",time); + //use time to find closest frame... studio::Preview::FlipBook::const_iterator beg = preview->begin(),end = preview->end(); studio::Preview::FlipBook::const_iterator i; - + i = beg; - + //go to current hint if need be... if(currentindex >= 0 && currentindex < (int)preview->numframes()) { i = beg+currentindex; } - + //we can't have a picture if there are none to get if(beg != end) - { + { //don't bother with binary search it will just be slower... - + //synfig::info("Search for time %f",time); - - //incrementally go in either direction + + //incrementally go in either direction //(bias downward towards beg, because that's what we want) for(;i != end;++i) { @@ -449,10 +457,10 @@ void studio::Widget_Preview::update() if(i->t > time) break; //synfig::info("Go past..."); } - - //if(i!=beg)--i; - - //bias down, so we can't be at end... and it still is valid... + + //if(i!=beg)--i; + + //bias down, so we can't be at end... and it still is valid... for(;i != beg;) { --i; @@ -460,18 +468,18 @@ void studio::Widget_Preview::update() if(i->t <= time) break; //synfig::info("Go past..."); } - + /*i = preview->begin(); end = preview->end(); if(i == end) return; - + j = i; for(;i != end; j = i++) { if(i->t > time) break; }*/ - + //we should be at a valid edge since we biased downward - + //don't get the closest, round down... (if we can) if(i == end) { @@ -489,18 +497,18 @@ void studio::Widget_Preview::update() timedisp = i->t; //synfig::warning("Update at: %f seconds (%f s)",time,timedisp); preview_draw(); - //synfig::warning("success!"); + //synfig::warning("success!"); } } } } - + if(disp_sound.get_profile() && adj_sound.get_value() != time) { //timeupdate = time; - + //Set the position of the sound (short circuited for sound modifying the time) - + disp_sound.set_position(time); disp_sound.queue_draw(); } @@ -510,36 +518,36 @@ void studio::Widget_Preview::preview_draw() draw_area.queue_draw();//on_expose_event(); } -bool studio::Widget_Preview::redraw(GdkEventExpose *heh) -{ +bool studio::Widget_Preview::redraw(GdkEventExpose */*heh*/) +{ //And render the drawing area Glib::RefPtr pxnew, px = currentbuf; - - if(!px || draw_area.get_height() == 0 + + if(!px || draw_area.get_height() == 0 || px->get_height() == 0 || px->get_width() == 0 /*|| is_visible()*/) //made not need this line return true; - + //figure out the scaling factors... float sx, sy; int nw,nh; sx = draw_area.get_width() / (float)px->get_width(); sy = draw_area.get_height() / (float)px->get_height(); - + //synfig::info("widget_preview redraw: now to scale the bitmap: %.3f x %.3f",sx,sy); - + //round to smallest scale (fit entire thing in window without distortion) if(sx > sy) sx = sy; //else sy = sx; - + //scale to a new pixmap and then copy over to the window nw = (int)(px->get_width()*sx); nh = (int)(px->get_height()*sx); - + if(nw == 0 || nh == 0)return true; - + pxnew = px->scale_simple(nw,nh,Gdk::INTERP_NEAREST); - + //synfig::info("Now to draw to the window..."); //copy to window Glib::RefPtr wind = draw_area.get_window(); @@ -553,7 +561,7 @@ bool studio::Widget_Preview::redraw(GdkEventExpose *heh) if(!wind) synfig::warning("The destination window is broken..."); if(!surf) synfig::warning("The destination is not drawable..."); - + if(surf) { /* Options for drawing... @@ -566,7 +574,7 @@ bool studio::Widget_Preview::redraw(GdkEventExpose *heh) + better memory footprint */ //px->composite(const Glib::RefPtr& dest, int dest_x, int dest_y, int dest_width, int dest_height, double offset_x, double offset_y, double scale_x, double scale_y, InterpType interp_type, int overall_alpha) const - + surf->draw_pixbuf( gc, //GC pxnew, //pixbuf @@ -576,15 +584,15 @@ bool studio::Widget_Preview::redraw(GdkEventExpose *heh) Gdk::RGB_DITHER_NONE, // RgbDither 0, 0 // Dither offset X and Y ); - + if(timedisp >= 0) { - Glib::RefPtr layout(Pango::Layout::create(get_pango_context())); + Glib::RefPtr layout(Pango::Layout::create(get_pango_context())); Glib::ustring timecode(Time((double)timedisp).round(preview->get_global_fps()) .get_string(preview->get_global_fps(), App::get_time_format())); //synfig::info("Time for preview draw is: %s for time %g", timecode.c_str(), adj_time_scrub.get_value()); - + gc->set_rgb_fg_color(Gdk::Color("#FF0000")); layout->set_text(timecode); surf->draw_layout(gc,4,4,layout); @@ -592,10 +600,10 @@ bool studio::Widget_Preview::redraw(GdkEventExpose *heh) } draw_area.get_window()->end_paint(); - + //synfig::warning("Refresh the draw area"); //make sure the widget refreshes - + return false; } @@ -603,24 +611,24 @@ bool studio::Widget_Preview::play_update() { float diff = timer.pop_time(); //synfig::info("Play update: diff = %.2f",diff); - + if(playing) { //we go to the next one... double time = adj_time_scrub.get_value() + diff; - + //adjust it to be synced with the audio if it can... { double newtime = audiotime; if(audio && audio->is_playing()) audio->get_current_time(newtime); - + if(newtime != audiotime) { //synfig::info("Adjusted time from %.3lf to %.3lf", time,newtime); time = audiotime = newtime; } } - + //Looping conditions... if(time >= adj_time_scrub.get_upper()) { @@ -634,16 +642,16 @@ bool studio::Widget_Preview::play_update() adj_time_scrub.set_value(time); play_stop(); update(); - + //synfig::info("Play Stopped: time set to %f",adj_time_scrub.get_value()); return false; } } - + //set the new time... adj_time_scrub.set_value(time); adj_time_scrub.value_changed(); - + //update the window to the correct image we might want to do this later... //update(); //synfig::warning("Did update pu"); @@ -664,26 +672,26 @@ void studio::Widget_Preview::slider_move() void studio::Widget_Preview::scrub_updated(double t) { stop(); - + //Attempt at being more accurate... the time is adjusted to be exactly where the sound says it is //double oldt = t; if(audio) { - if(!audio->isPaused()) + if(!audio->isPaused()) { audio->get_current_time(t); } } - + //synfig::info("Scrubbing to %.3f, setting adj to %.3f",oldt,t); - + if(adj_time_scrub.get_value() != t) { adj_time_scrub.set_value(t); adj_time_scrub.value_changed(); } } - + void studio::Widget_Preview::disconnect_preview(Preview *prev) { if(prev == preview) @@ -693,15 +701,15 @@ void studio::Widget_Preview::disconnect_preview(Preview *prev) } } -void studio::Widget_Preview::set_preview(handle prev) +void studio::Widget_Preview::set_preview(etl::handle prev) { preview = prev; - + synfig::info("Setting preview"); - + //stop playing the mini animation... stop(); - + if(preview) { //set the internal values @@ -711,15 +719,15 @@ void studio::Widget_Preview::set_preview(handle prev) { float start = preview->get_begintime(); float end = preview->get_endtime(); - + rate = 1/rate; - + adj_time_scrub.set_lower(start); adj_time_scrub.set_upper(end); adj_time_scrub.set_value(start); adj_time_scrub.set_step_increment(rate); adj_time_scrub.set_page_increment(10*rate); - + //if the begin time and the end time are the same there is only a single frame singleframe = end==start; }else @@ -728,10 +736,10 @@ void studio::Widget_Preview::set_preview(handle prev) adj_time_scrub.set_upper(0); adj_time_scrub.set_value(0); adj_time_scrub.set_step_increment(0); - adj_time_scrub.set_page_increment(0); + adj_time_scrub.set_page_increment(0); singleframe = true; } - + //connect so future information will be found... prevchanged = prev->signal_changed().connect(sigc::mem_fun(*this,&Widget_Preview::whenupdated)); prev->signal_destroyed().connect(sigc::mem_fun(*this,&Widget_Preview::disconnect_preview)); @@ -763,25 +771,25 @@ void studio::Widget_Preview::play() //audiotime = adj_time_scrub.get_value(); playing = true; - //adj_time_scrub.set_value(adj_time_scrub.get_lower()); + //adj_time_scrub.set_value(adj_time_scrub.get_lower()); update(); //we don't want to call play update because that will try to advance the timer //synfig::warning("Did update p"); - + //approximate length of time in seconds, right? double rate = /*std::min(*/adj_time_scrub.get_step_increment()/*,1/30.0)*/; int timeout = (int)floor(1000*rate); - + //synfig::info(" rate = %.3lfs = %d ms",rate,timeout); - + signal_play_(adj_time_scrub.get_value()); - + //play the audio... if(audio) audio->play(adj_time_scrub.get_value()); - - timecon = Glib::signal_timeout().connect(sigc::mem_fun(*this,&Widget_Preview::play_update),timeout); - timer.reset(); + + timecon = Glib::signal_timeout().connect(sigc::mem_fun(*this,&Widget_Preview::play_update),timeout); + timer.reset(); } - + } void studio::Widget_Preview::play_stop() @@ -810,10 +818,10 @@ bool studio::Widget_Preview::scroll_move_event(GdkEvent *event) stop(); } } - + default: break; } - + return false; } @@ -825,10 +833,10 @@ void studio::Widget_Preview::set_audioprofile(etl::handle p) void studio::Widget_Preview::set_audio(etl::handle a) { audio = a; - + //disconnect any previous signals scrstartcon.disconnect(); scrstopcon.disconnect(); scrubcon.disconnect(); - + //connect the new signals scrstartcon = disp_sound.signal_start_scrubbing().connect(sigc::mem_fun(*a,&AudioContainer::start_scrubbing)); scrstopcon = disp_sound.signal_stop_scrubbing().connect(sigc::mem_fun(*a,&AudioContainer::stop_scrubbing)); @@ -855,20 +863,29 @@ void studio::Widget_Preview::stoprender() { if(preview) { - preview->renderer.detach(); + // don't crash if the render has already been stopped + if (!preview->renderer) + return; + +#ifdef SINGLE_THREADED + if (preview->renderer->updating) + preview->renderer->stop(); + else +#endif + preview->renderer.detach(); } } - + void studio::Widget_Preview::eraseall() { stop(); stoprender(); - + currentbuf.clear(); currentindex = 0; timedisp = 0; queue_draw(); - + if(preview) { preview->clear();