// Copyright (c) 2005, Rodrigo Braz Monteiro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // ----------------------------------------------------------------------------- // // AEGISUB // // Website: http://aegisub.cellosoft.com // Contact: mailto:zeratul@cellosoft.com // //////////// // Includes #include #include #include "video_slider.h" #include "video_display.h" #include "subs_grid.h" #include "ass_dialogue.h" #include "vfr.h" #include "subs_edit_box.h" #include "options.h" #include "utils.h" /////////////// // Constructor VideoSlider::VideoSlider (wxWindow* parent, wxWindowID id) : wxWindow (parent,id,wxDefaultPosition,wxDefaultSize,wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE) { Display = NULL; SetClientSize(20,25); locked = false; SetRange(0,1); } ///////////// // Get value int VideoSlider::GetValue() { return val; } ///////////// // Set value void VideoSlider::SetValue(int value) { if (locked) return; val = value; if (val < min) val = min; if (val > max) val = max; Refresh(false); } ///////////// // Set range void VideoSlider::SetRange(int from,int to) { if (from > to) from = to; locked = false; min = from; max = to; if (val < from) val = from; if (val > to) val = to; } ////////////////// // Get value at X int VideoSlider::GetValueAtX(int x) { // Get dimensions int w,h; GetClientSize(&w,&h); // Special case if (w <= 10) return 0; // Calculate return (int64_t)(x-5)*(int64_t)(max-min)/(int64_t)(w-10)+min; } ////////////////// // Get X at value int VideoSlider::GetXAtValue(int value) { // Get dimensions int w,h; GetClientSize(&w,&h); // Special case if (max-min <= 0) return 0; // Calculate return (int64_t)(value-min)*(int64_t)(w-10)/(int64_t)(max-min)+5; } ///////////////////// // Next frame hotkey void VideoSlider::NextFrame() { if (VideoContext::Get()->IsPlaying()) return; //don't request out of range frames if (GetValue() < max) VideoContext::Get()->JumpToFrame(GetValue()+1); Refresh(false); Update(); } ///////////////////////// // Previous frame hotkey void VideoSlider::PrevFrame() { if (VideoContext::Get()->IsPlaying()) return; //don't request out of range frames if (GetValue() > min) VideoContext::Get()->JumpToFrame(GetValue()-1); Refresh(false); Update(); } /////////////// // Event table BEGIN_EVENT_TABLE(VideoSlider, wxWindow) EVT_MOUSE_EVENTS(VideoSlider::OnMouse) EVT_KEY_DOWN(VideoSlider::OnKeyDown) EVT_PAINT(VideoSlider::OnPaint) EVT_SET_FOCUS(VideoSlider::OnFocus) EVT_KILL_FOCUS(VideoSlider::OnFocus) EVT_ERASE_BACKGROUND(VideoSlider::OnEraseBackground) END_EVENT_TABLE() /////////////////// // Change position void VideoSlider::UpdateVideo() { if (Display) { if (VideoContext::Get()->IsPlaying()) return; locked = true; VideoContext::Get()->JumpToFrame(GetValue()); locked = false; } } //////////////// // Mouse events void VideoSlider::OnMouse(wxMouseEvent &event) { // Coordinates int x = event.GetX(); //int y = event.GetY(); bool shift = event.m_shiftDown; // Left click if (event.ButtonIsDown(wxMOUSE_BTN_LEFT)) { // Check if it's OK to drag bool canDrag = wxWindow::FindFocus() == this; if (!canDrag) { int tolerance = 4; int curX = GetXAtValue(GetValue()); if (x-curX < -tolerance || x-curX > tolerance) canDrag = true; } // Drag if (canDrag) { // Shift click to snap to keyframe if (shift && Display) { wxArrayInt KeyFrames = VideoContext::Get()->GetKeyFrames(); int keys = KeyFrames.Count(); int clickedFrame = GetValueAtX(x); int closest = 0; int cur; // Find closest for (int i=0;iIsPlaying()) { VideoContext::Get()->Stop(); UpdateVideo(); VideoContext::Get()->Play(); } else UpdateVideo(); } // Get focus SetFocus(); } // Right/middle click if (event.ButtonDown(wxMOUSE_BTN_RIGHT) || event.ButtonDown(wxMOUSE_BTN_MIDDLE)) { SetFocus(); } // Something else else if (!VideoContext::Get()->IsPlaying()) event.Skip(); } ////////////////// // Key down event void VideoSlider::OnKeyDown(wxKeyEvent &event) { if (VideoContext::Get()->IsPlaying()) return; // Get flags int key = event.GetKeyCode(); #ifdef __APPLE__ bool ctrl = event.m_metaDown; #else bool ctrl = event.m_controlDown; #endif bool alt = event.m_altDown; bool shift = event.m_shiftDown; int direction = 0; // Pick direction if (key == WXK_LEFT) direction = -1; else if (key == WXK_RIGHT) direction = 1; // If a direction was actually pressed if (direction) { // Standard move if (!ctrl && !shift && !alt) { if (direction == 1) NextFrame(); else PrevFrame(); return; } // Fast move if (!ctrl && !shift && alt) { if (VideoContext::Get()->IsPlaying()) return; int target = MID(min,GetValue() + direction * Options.AsInt(_T("Video Fast Jump Step")),max); if (target != GetValue()) VideoContext::Get()->JumpToFrame(target); return; } // Boundaries if (ctrl && !shift && !alt) { // Prepare wxArrayInt sel = grid->GetSelection(); int cur; if (sel.Count() > 0) cur = sel[0]; else { grid->editBox->SetToLine(0); grid->SelectRow(0); cur = 0; } AssDialogue *curDiag = grid->GetDialogue(cur); if (!curDiag) return; // Jump to next sub boundary if (direction != 0) { int target1 = VFR_Output.GetFrameAtTime(curDiag->Start.GetMS(),true); int target2 = VFR_Output.GetFrameAtTime(curDiag->End.GetMS(),false); bool drawn = false; // Forward if (direction == 1) { if (VideoContext::Get()->GetFrameN() < target1) VideoContext::Get()->JumpToFrame(target1); else if (VideoContext::Get()->GetFrameN() < target2) VideoContext::Get()->JumpToFrame(target2); else { if (cur+1 >= grid->GetRows()) return; grid->editBox->SetToLine(cur+1); grid->SelectRow(cur+1); grid->MakeCellVisible(cur+1,0); grid->SetVideoToSubs(true); grid->Refresh(false); drawn = true; } return; } // Backward else { if (VideoContext::Get()->GetFrameN() > target2) VideoContext::Get()->JumpToFrame(target2); else if (VideoContext::Get()->GetFrameN() > target1) VideoContext::Get()->JumpToFrame(target1); else { if (cur-1 < 0) return; grid->editBox->SetToLine(cur-1); grid->SelectRow(cur-1); grid->MakeCellVisible(cur-1,0); grid->SetVideoToSubs(false); grid->Refresh(false); drawn = true; } return; } } } // Snap to keyframe if (shift && !ctrl && !alt) { if (direction != 0) { // Prepare int prevKey = 0; int nextKey = VideoContext::Get()->GetLength()-1; wxArrayInt KeyFrames = VideoContext::Get()->GetKeyFrames(); int keys = KeyFrames.Count(); int cur = VideoContext::Get()->GetFrameN(); int i; int temp; // Find previous keyframe // This algorithm does unnecessary loops, but it ensures it works even if keyframes are out of order. for (i=0;i prevKey) prevKey = temp; } // Find next keyframe for (i=0;i cur && temp < nextKey) nextKey = KeyFrames[i]; } if (direction == -1) VideoContext::Get()->JumpToFrame(prevKey); if (direction == 1) VideoContext::Get()->JumpToFrame(nextKey); return; } } } // Forward up/down to grid if (key == WXK_UP || key == WXK_DOWN) { grid->AddPendingEvent(event); grid->SetFocus(); return; } event.Skip(); } /////////////// // Paint event void VideoSlider::OnPaint(wxPaintEvent &event) { wxPaintDC dc(this); DrawImage(dc); } ////////////// // Draw image void VideoSlider::DrawImage(wxDC &destdc) { // Get dimensions int w,h; GetClientSize(&w,&h); // Back buffer wxMemoryDC dc; wxBitmap bmp(w,h); dc.SelectObject(bmp); // Colors wxColour shad = wxSystemSettings::GetColour(wxSYS_COLOUR_3DDKSHADOW); wxColour high = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); wxColour face = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE); wxColour sel(123,251,232); wxColour notSel(sel.Red()*2/5,sel.Green()*2/5,sel.Blue()*2/5); wxColour bord(0,0,0); int x1,x2,y1,y2; // Background dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(face); dc.DrawRectangle(0,0,w,h); // Selection border bool selected = wxWindow::FindFocus() == this; if (selected) { dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(wxPen(shad,1,wxDOT)); dc.DrawRectangle(0,0,w,h); } // Draw slider x1 = 5; x2 = w-5; y1 = 8; y2 = h-8; dc.SetPen(wxPen(shad)); dc.DrawLine(x1,y1,x2,y1); dc.DrawLine(x1,y1,x1,y2); dc.SetPen(wxPen(high)); dc.DrawLine(x1,y2,x2,y2); dc.DrawLine(x2,y1,x2,y2); // Draw keyframes int curX; if (Display && Options.AsBool(_T("Show keyframes on video slider"))) { dc.SetPen(wxPen(shad)); wxArrayInt KeyFrames = VideoContext::Get()->GetKeyFrames(); int keys = KeyFrames.Count(); for (int i=0;i