/* * CINELERRA * Copyright (C) 2014 Adam Williams * * This program 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. * * This program 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "automation.h" #include "bcdisplayinfo.h" #include "clip.h" #include "tracer.h" #include "tracerwindow.h" #include "cstrdup.h" #include "cwindow.h" #include "cwindowgui.h" #include "edl.h" #include "edlsession.h" #include "language.h" #include "mainerror.h" #include "mwindow.h" #include "plugin.h" #include "pluginserver.h" #include "theme.h" #include "track.h" #define COLOR_W xS(50) #define COLOR_H yS(30) TracerNum::TracerNum(TracerWindow *gui, int x, int y, float output) : BC_TumbleTextBox(gui, output, -32767.0f, 32767.0f, x, y, xS(120)) { this->gui = gui; set_increment(1); set_precision(1); } TracerNum::~TracerNum() { } int TracerPointX::handle_event() { if( !TracerNum::handle_event() ) return 0; TracerPointList *point_list = gui->point_list; int hot_point = point_list->get_selection_number(0, 0); TracerPoints &points = gui->plugin->config.points; int sz = points.size(); if( hot_point >= 0 && hot_point < sz ) { float v = atof(get_text()); points[hot_point]->x = v; point_list->set_point(hot_point, PT_X, v); } point_list->update_list(hot_point); gui->send_configure_change(); return 1; } int TracerPointY::handle_event() { if( !TracerNum::handle_event() ) return 0; TracerPointList *point_list = gui->point_list; int hot_point = point_list->get_selection_number(0, 0); TracerPoints &points = gui->plugin->config.points; int sz = points.size(); if( hot_point >= 0 && hot_point < sz ) { float v = atof(get_text()); points[hot_point]->y = v; point_list->set_point(hot_point, PT_Y, v); } point_list->update_list(hot_point); gui->send_configure_change(); return 1; } TracerWindow::TracerWindow(Tracer *plugin) : PluginClientWindow(plugin, xS(460), yS(420), xS(460), yS(420), 0) { this->plugin = plugin; this->title_x = 0; this->point_x = 0; this->title_y = 0; this->point_y = 0; this->new_point = 0; this->del_point = 0; this->point_up = 0; this->point_dn = 0; this->drag = 0; this->draw = 0; this->button_no = 0; this->invert = 0; this->title_r = 0; this->title_s = 0; this->feather = 0; this->radius = 0; this->last_x = 0; this->last_y = 0; this->point_list = 0; this->pending_config = 0; } TracerWindow::~TracerWindow() { delete point_x; delete point_y; } void TracerWindow::create_objects() { int x = xS(10), y = yS(10); int margin = plugin->get_theme()->widget_border; int hot_point = plugin->selected; add_subwindow(title_x = new BC_Title(x, y, _("X:"))); int x1 = x + title_x->get_w() + margin; TracerPoints &points = plugin->config.points; TracerPoint *pt = hot_point >= 0 && hot_pointx); point_x->create_objects(); x1 += point_x->get_w() + margin + xS(20); add_subwindow(new_point = new TracerNewPoint(this, plugin, x1, y)); x1 += new_point->get_w() + margin + xS(30); add_subwindow(point_up = new TracerPointUp(this, x1, y)); y += point_x->get_h() + margin; add_subwindow(title_y = new BC_Title(x, y, _("Y:"))); x1 = x + title_y->get_w() + margin; point_y = new TracerPointY(this, x1, y, !pt ? 0 : pt->y); point_y->create_objects(); x1 += point_y->get_w() + margin + xS(20); add_subwindow(del_point = new TracerDelPoint(this, plugin, x1, y)); x1 += del_point->get_w() + margin + xS(30); add_subwindow(point_dn = new TracerPointDn(this, x1, y)); y += point_y->get_h() + margin + yS(10); add_subwindow(drag = new TracerDrag(this, x, y)); if( plugin->drag ) { if( !grab(plugin->server->mwindow->cwindow->gui) ) eprintf("drag enabled, but compositor already grabbed\n"); } x1 = x + drag->get_w() + margin + xS(20); add_subwindow(draw = new TracerDraw(this, x1, y)); x1 += draw->get_w() + margin + xS(20); add_subwindow(fill = new TracerFill(this, x1, y)); x1 += drag->get_w() + margin + xS(20); int y1 = y + yS(3); add_subwindow(reset = new TracerReset(this, plugin, x1, y1)); y1 += reset->get_h() + margin; add_subwindow(invert = new TracerInvert(this, plugin, x1, y1)); y += drag->get_h() + margin + yS(15); x1 = x + xS(80); add_subwindow(title_r = new BC_Title(x, y, _("Feather:"))); add_subwindow(feather = new TracerFeather(this, x1, y, xS(150))); y += feather->get_h() + margin; add_subwindow(title_s = new BC_Title(x, y, _("Radius:"))); add_subwindow(radius = new TracerRadius(this, x1, y, xS(150))); y += radius->get_h() + margin + yS(5); add_subwindow(point_list = new TracerPointList(this, plugin, x, y)); point_list->update(plugin->selected); y += point_list->get_h() + yS(10); add_subwindow(new BC_Title(x, y, _( "Btn1: select/drag point\n" "Btn2: drag all points\n" "Btn3: add point on nearest line\n" "Btn3: shift: append point to end\n" "Wheel: rotate, centered on cursor\n" "Wheel: shift: scale, centered on cursor\n"))); show_window(1); } void TracerWindow::send_configure_change() { pending_config = 0; plugin->send_configure_change(); } int TracerWindow::grab_event(XEvent *event) { int ret = do_grab_event(event); if( pending_config && !grab_event_count() ) send_configure_change(); return ret; } int TracerWindow::do_grab_event(XEvent *event) { switch( event->type ) { case ButtonPress: break; case ButtonRelease: break; case MotionNotify: break; default: return 0; } MWindow *mwindow = plugin->server->mwindow; CWindowGUI *cwindow_gui = mwindow->cwindow->gui; CWindowCanvas *canvas = cwindow_gui->canvas; int cursor_x, cursor_y; cwindow_gui->get_relative_cursor(cursor_x, cursor_y); cursor_x -= canvas->view_x; cursor_y -= canvas->view_y; if( !button_no ) { if( cursor_x < 0 || cursor_x >= canvas->view_w || cursor_y < 0 || cursor_y >= canvas->view_h ) return 0; } switch( event->type ) { case ButtonPress: if( button_no ) return 0; button_no = event->xbutton.button; break; case ButtonRelease: if( !button_no ) return 0; button_no = 0; return 1; case MotionNotify: if( !button_no ) return 0; break; default: return 0; } float output_x = cursor_x, output_y = cursor_y, track_x, track_y; canvas->canvas_to_output(mwindow->edl, 0, output_x, output_y); plugin->output_to_track(output_x, output_y, track_x, track_y); point_x->update((int64_t)track_x); point_y->update((int64_t)track_y); TracerPoints &points = plugin->config.points; switch( event->type ) { case ButtonPress: { float s = 1.02; float th = M_PI/360.f; // .5 deg per wheel_btn int shift_down = event->xbutton.state & ShiftMask; switch( button_no ) { case WHEEL_DOWN: s = 0.98; th = -th; // fall thru case WHEEL_UP: { // shift_down scale, !shift_down rotate float st = sin(th), ct = cos(th); int sz = points.size(); for( int i=0; ix - track_x, py = pt->y - track_y; float nx = shift_down ? px*s : px*ct + py*st; float ny = shift_down ? py*s : py*ct - px*st; point_list->set_point(i, PT_X, pt->x = nx + track_x); point_list->set_point(i, PT_Y, pt->y = ny + track_y); } point_list->update(-1); button_no = 0; break; } case RIGHT_BUTTON: { // shift_down adds to end int sz = !shift_down ? points.size() : 0; int k = !shift_down ? -1 : points.size()-1; float mx = FLT_MAX; for( int i=0; ix, y0 = pt0->y; float x1 = pt1->x, y1 = pt1->y; float dx = x1-x0, dy = y1-y0; float rr = dx*dx + dy*dy; if( !rr ) continue; float u = ((x1-track_x)*dx + (y1-track_y)*dy) / rr; if( u < 0 || u > 1 ) continue; // past endpts float x = x0*u + x1*(1-u); float y = y0*u + y1*(1-u); dx = track_x-x; dy = track_y-y; float dd = dx*dx + dy*dy; // d**2 closest approach if( mx > dd ) { mx = dd; k = i; } } TracerPoint *pt = points[sz=plugin->new_point()]; int hot_point = k+1; for( int i=sz; i>hot_point; --i ) points[i] = points[i-1]; points[hot_point] = pt; pt->x = track_x; pt->y = track_y; point_list->update(hot_point); break; } case LEFT_BUTTON: { int hot_point = -1, sz = points.size(); if( sz > 0 ) { TracerPoint *pt = points[hot_point=0]; double dist = DISTANCE(track_x,track_y, pt->x,pt->y); for( int i=1; ix,pt->y); if( d >= dist ) continue; dist = d; hot_point = i; } pt = points[hot_point]; float cx, cy; plugin->track_to_output(pt->x, pt->y, cx, cy); canvas->output_to_canvas(mwindow->edl, 0, cx, cy); dist = DISTANCE(cursor_x, cursor_y, cx,cy); if( dist >= HANDLE_W ) hot_point = -1; } if( hot_point >= 0 && sz > 0 ) { TracerPoint *pt = points[hot_point]; point_list->set_point(hot_point, PT_X, pt->x = track_x); point_list->set_point(hot_point, PT_Y, pt->y = track_y); point_list->update_list(hot_point); } break; } } break; } case MotionNotify: { switch( button_no ) { case LEFT_BUTTON: { int hot_point = point_list->get_selection_number(0, 0); if( hot_point >= 0 && hot_point < points.size() ) { TracerPoint *pt = points[hot_point]; if( pt->x == track_x && pt->y == track_y ) break; point_list->set_point(hot_point, PT_X, pt->x = track_x); point_list->set_point(hot_point, PT_Y, pt->y = track_y); point_x->update(pt->x); point_y->update(pt->y); point_list->update_list(hot_point); } break; } case MIDDLE_BUTTON: { float dx = track_x - last_x, dy = track_y - last_y; int sz = points.size(); for( int i=0; iset_point(i, PT_X, pt->x += dx); point_list->set_point(i, PT_Y, pt->y += dy); } int hot_point = point_list->get_selection_number(0, 0); if( hot_point >= 0 && hot_point < sz ) { TracerPoint *pt = points[hot_point]; point_x->update(pt->x); point_y->update(pt->y); point_list->update_list(hot_point); } break; } } break; } } last_x = track_x; last_y = track_y; pending_config = 1; return 1; } void TracerWindow::done_event(int result) { ungrab(client->server->mwindow->cwindow->gui); } TracerPointList::TracerPointList(TracerWindow *gui, Tracer *plugin, int x, int y) : BC_ListBox(x, y, xS(360), yS(130), LISTBOX_TEXT) { this->gui = gui; this->plugin = plugin; titles[PT_X] = _("X"); widths[PT_X] = xS(90); titles[PT_Y] = _("Y"); widths[PT_Y] = yS(90); } TracerPointList::~TracerPointList() { clear(); } void TracerPointList::clear() { for( int i=PT_SZ; --i>=0; ) cols[i].remove_all_objects(); } int TracerPointList::column_resize_event() { for( int i=PT_SZ; --i>=0; ) widths[i] = get_column_width(i); return 1; } int TracerPointList::handle_event() { int hot_point = get_selection_number(0, 0); const char *x_text = "", *y_text = ""; TracerPoints &points = plugin->config.points; int sz = points.size(); if( hot_point >= 0 && sz > 0 ) { x_text = gui->point_list->cols[PT_X].get(hot_point)->get_text(); y_text = gui->point_list->cols[PT_Y].get(hot_point)->get_text(); } gui->point_x->update(x_text); gui->point_y->update(y_text); update(hot_point); gui->send_configure_change(); return 1; } int TracerPointList::selection_changed() { handle_event(); return 1; } void TracerPointList::new_point(const char *xp, const char *yp) { cols[PT_X].append(new BC_ListBoxItem(xp)); cols[PT_Y].append(new BC_ListBoxItem(yp)); } void TracerPointList::del_point(int i) { for( int sz1=cols[0].size()-1, c=PT_SZ; --c>=0; ) cols[c].remove_object_number(sz1-i); } void TracerPointList::set_point(int i, int c, float v) { char s[BCSTRLEN]; sprintf(s,"%0.4f",v); set_point(i,c,s); } void TracerPointList::set_point(int i, int c, const char *cp) { cols[c].get(i)->set_text(cp); } int TracerPointList::set_selected(int k) { TracerPoints &points = plugin->config.points; int sz = points.size(); if( !sz ) return -1; bclamp(k, 0, sz-1); update_selection(&cols[0], k); return k; } void TracerPointList::update_list(int k) { int sz = plugin->config.points.size(); if( k < 0 || k >= sz ) k = -1; plugin->selected = k; update_selection(&cols[0], k); int xpos = get_xposition(), ypos = get_yposition(); BC_ListBox::update(&cols[0], &titles[0],&widths[0],PT_SZ, xpos,ypos,k); center_selection(); } void TracerPointList::update(int k) { clear(); TracerPoints &points = plugin->config.points; int sz = points.size(); for( int i=0; ix); char ytxt[BCSTRLEN]; sprintf(ytxt,"%0.4f", pt->y); new_point(xtxt, ytxt); } if( k >= 0 && k < sz ) { gui->point_x->update(gui->point_list->cols[PT_X].get(k)->get_text()); gui->point_y->update(gui->point_list->cols[PT_Y].get(k)->get_text()); } update_list(k); } void TracerWindow::update_gui() { TracerConfig &config = plugin->config; drag->update(plugin->drag); draw->update(config.draw); fill->update(config.fill); feather->update(config.feather); radius->update(config.radius); invert->update(config.invert); point_list->update(-1); } TracerPointUp::TracerPointUp(TracerWindow *gui, int x, int y) : BC_GenericButton(x, y, _("Up")) { this->gui = gui; } TracerPointUp::~TracerPointUp() { } int TracerPointUp::handle_event() { TracerPoints &points = gui->plugin->config.points; int sz = points.size(); int hot_point = gui->point_list->get_selection_number(0, 0); if( sz > 1 && hot_point > 0 ) { TracerPoint *&pt0 = points[hot_point]; TracerPoint *&pt1 = points[--hot_point]; TracerPoint *t = pt0; pt0 = pt1; pt1 = t; gui->point_list->update(hot_point); } gui->send_configure_change(); return 1; } TracerPointDn::TracerPointDn(TracerWindow *gui, int x, int y) : BC_GenericButton(x, y, _("Dn")) { this->gui = gui; } TracerPointDn::~TracerPointDn() { } int TracerPointDn::handle_event() { TracerPoints &points = gui->plugin->config.points; int sz = points.size(); int hot_point = gui->point_list->get_selection_number(0, 0); if( sz > 1 && hot_point < sz-1 ) { TracerPoint *&pt0 = points[hot_point]; TracerPoint *&pt1 = points[++hot_point]; TracerPoint *t = pt0; pt0 = pt1; pt1 = t; gui->point_list->update(hot_point); } gui->send_configure_change(); return 1; } TracerDrag::TracerDrag(TracerWindow *gui, int x, int y) : BC_CheckBox(x, y, gui->plugin->drag, _("Drag")) { this->gui = gui; } int TracerDrag::handle_event() { CWindowGUI *cwindow_gui = gui->plugin->server->mwindow->cwindow->gui; int value = get_value(); if( value ) { if( !gui->grab(cwindow_gui) ) { update(value = 0); flicker(10,50); } } else gui->ungrab(cwindow_gui); gui->plugin->drag = value; gui->send_configure_change(); return 1; } int TracerWindow::handle_ungrab() { CWindowGUI *cwindow_gui = plugin->server->mwindow->cwindow->gui; int ret = ungrab(cwindow_gui); if( ret ) { drag->update(0); plugin->drag = 0; } return ret; } TracerDraw::TracerDraw(TracerWindow *gui, int x, int y) : BC_CheckBox(x, y, gui->plugin->config.draw, _("Draw")) { this->gui = gui; } int TracerDraw::handle_event() { gui->plugin->config.draw = get_value(); gui->send_configure_change(); return 1; } TracerFill::TracerFill(TracerWindow *gui, int x, int y) : BC_CheckBox(x, y, gui->plugin->config.fill, _("Fill")) { this->gui = gui; } int TracerFill::handle_event() { gui->plugin->config.fill = get_value(); gui->send_configure_change(); return 1; } TracerFeather::TracerFeather(TracerWindow *gui, int x, int y, int w) : BC_ISlider(x,y,0,w,w, -50,50, gui->plugin->config.feather) { this->gui = gui; } int TracerFeather::handle_event() { gui->plugin->config.feather = get_value(); gui->send_configure_change(); return 1; } TracerRadius::TracerRadius(TracerWindow *gui, int x, int y, int w) : BC_FSlider(x,y, 0,w,w, -5.f,5.f, gui->plugin->config.radius) { this->gui = gui; } int TracerRadius::handle_event() { gui->plugin->config.radius = get_value(); gui->send_configure_change(); return 1; } TracerNewPoint::TracerNewPoint(TracerWindow *gui, Tracer *plugin, int x, int y) : BC_GenericButton(x, y, xS(100), _("New")) { this->gui = gui; this->plugin = plugin; } TracerNewPoint::~TracerNewPoint() { } int TracerNewPoint::handle_event() { int k = plugin->new_point(); gui->point_list->update(k); gui->send_configure_change(); return 1; } TracerDelPoint::TracerDelPoint(TracerWindow *gui, Tracer *plugin, int x, int y) : BC_GenericButton(x, y, xS(100), C_("Del")) { this->gui = gui; this->plugin = plugin; } TracerDelPoint::~TracerDelPoint() { } int TracerDelPoint::handle_event() { int hot_point = gui->point_list->get_selection_number(0, 0); TracerPoints &points = plugin->config.points; if( hot_point >= 0 && hot_point < points.size() ) { plugin->config.del_point(hot_point); gui->point_list->update(--hot_point); gui->send_configure_change(); } return 1; } TracerReset::TracerReset(TracerWindow *gui, Tracer *plugin, int x, int y) : BC_GenericButton(x, y, _("Reset")) { this->gui = gui; this->plugin = plugin; } TracerReset::~TracerReset() { } int TracerReset::handle_event() { TracerConfig &config = plugin->config; if( !plugin->drag ) { MWindow *mwindow = plugin->server->mwindow; CWindowGUI *cwindow_gui = mwindow->cwindow->gui; if( gui->grab(cwindow_gui) ) plugin->drag = 1; else gui->drag->flicker(10,50); } config.draw = 1; config.fill = 0; config.invert = 0; config.feather = 0; config.radius = 1; plugin->selected = -1; TracerPoints &points = plugin->config.points; points.remove_all_objects(); gui->point_list->update(-1); gui->update_gui(); gui->send_configure_change(); return 1; } TracerInvert::TracerInvert(TracerWindow *gui, Tracer *plugin, int x, int y) : BC_CheckBox(x, y, gui->plugin->config.invert, _("Invert")) { this->gui = gui; this->plugin = plugin; } TracerInvert::~TracerInvert() { } int TracerInvert::handle_event() { plugin->config.invert = get_value(); gui->send_configure_change(); return 1; }