// Copyright (c) 2006, 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 "base_grid.h" #include "utils.h" #include "ass_file.h" #include "ass_dialogue.h" #include "ass_style.h" #include "options.h" #include "vfr.h" #include "subs_edit_box.h" #include "frame_main.h" #include "video_box.h" #include "video_slider.h" #include "video_context.h" #include "audio_display.h" /////////////// // Constructor BaseGrid::BaseGrid(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name) : wxWindow(parent, id, pos, size, style, name) { // Misc variables lastRow = -1; yPos = 0; extendRow = -1; bmp = NULL; holding = false; // Set scrollbar scrollBar = new wxScrollBar(this,GRID_SCROLLBAR,wxDefaultPosition,wxDefaultSize,wxSB_VERTICAL); scrollBar->SetScrollbar(0,10,100,10); // Set style UpdateStyle(); } ////////////// // Destructor BaseGrid::~BaseGrid() { delete bmp; } //////////////// // Update style void BaseGrid::UpdateStyle() { // Set font wxString fontname = Options.AsText(_T("Grid Font Face")); if (fontname.IsEmpty()) fontname = _T("Tahoma"); font.SetFaceName(fontname); font.SetPointSize(Options.AsInt(_T("Grid font size"))); font.SetWeight(wxFONTWEIGHT_NORMAL); // Set line height { wxClientDC dc(this); dc.SetFont(font); int fw,fh; dc.GetTextExtent(_T("#TWFfgGhH"), &fw, &fh, NULL, NULL, &font); lineHeight = fh+4; } // Set column widths for (int i=0;i<10;i++) showCol[i] = Options.AsBool(_T("Grid show column ") + IntegerToString(i)); SetColumnWidths(); // Update AdjustScrollbar(); Refresh(); } /////////////// // Clears grid void BaseGrid::Clear () { diagMap.clear(); diagPtrMap.clear(); selMap.clear(); yPos = 0; AdjustScrollbar(); } /////////////// // Begin batch void BaseGrid::BeginBatch() { //Freeze(); } ///////////// // End batch void BaseGrid::EndBatch() { //Thaw(); AdjustScrollbar(); } ////////////////////// // Makes cell visible void BaseGrid::MakeCellVisible(int row, int col,bool center) { // Get size int w = 0; int h = 0; GetClientSize(&w,&h); bool forceCenter = !center; // Get min and max visible int minVis = yPos+1; int maxVis = yPos+h/lineHeight-3; // Make visible if (forceCenter || row < minVis || row > maxVis) { if (center) { ScrollTo(row - h/lineHeight/2 + 1); } else { if (row < minVis) ScrollTo(row - 1); if (row > maxVis) ScrollTo(row - h/lineHeight + 3); } } } //////////////// // Select a row void BaseGrid::SelectRow(int row, bool addToSelected, bool select) { if (!addToSelected) ClearSelection(); try { bool cur = selMap.at(row); if (select != cur) { selMap.at(row) = select; if (!addToSelected) Refresh(false); else { int w = 0; int h = 0; GetClientSize(&w,&h); RefreshRect(wxRect(0,(row+1-yPos)*lineHeight,w,lineHeight),false); } } } catch (...) {} } ///////////////////////// // Selects visible lines void BaseGrid::SelectVisible() { int rows = GetRows(); bool selectedOne = false; for (int i=0;iGetWidth() < w || bmp->GetHeight() < h) { delete bmp; bmp = NULL; } } if (!bmp) bmp = new wxBitmap(w,h); // Draw bitmap wxMemoryDC bmpDC; bmpDC.SelectObject(*bmp); DrawImage(bmpDC); dc.Blit(0,0,w,h,&bmpDC,0,0); } } ////////////// // Draw image void BaseGrid::DrawImage(wxDC &dc) { // Get size and pos int w = 0; int h = 0; GetClientSize(&w,&h); // Set font dc.SetFont(font); // Clear background dc.SetBackground(wxBrush(Options.AsColour(_T("Grid Background")))); dc.Clear(); // Draw labels dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxBrush(Options.AsColour(_T("Grid left column")))); dc.DrawRectangle(0,lineHeight,colWidth[0],h-lineHeight); // Visible lines int drawPerScreen = h/lineHeight + 1; int nDraw = MID(0,drawPerScreen,GetRows()-yPos); int maxH = (nDraw+1) * lineHeight; // Row colors std::vector rowColors; std::vector foreColors; rowColors.push_back(wxBrush(Options.AsColour(_T("Grid Background")))); // 0 = Standard foreColors.push_back(Options.AsColour(_T("Grid standard foreground"))); rowColors.push_back(wxBrush(Options.AsColour(_T("Grid Header")))); // 1 = Header foreColors.push_back(Options.AsColour(_T("Grid standard foreground"))); rowColors.push_back(wxBrush(Options.AsColour(_T("Grid selection background")))); // 2 = Selected foreColors.push_back(Options.AsColour(_T("Grid selection foreground"))); rowColors.push_back(wxBrush(Options.AsColour(_T("Grid comment background")))); // 3 = Commented foreColors.push_back(Options.AsColour(_T("Grid selection foreground"))); rowColors.push_back(wxBrush(Options.AsColour(_T("Grid inframe background")))); // 4 = Video Highlighted foreColors.push_back(Options.AsColour(_T("Grid selection foreground"))); rowColors.push_back(wxBrush(Options.AsColour(_T("Grid selected comment background")))); // 5 = Commented & selected foreColors.push_back(Options.AsColour(_T("Grid selection foreground"))); // First grid row bool drawGrid = true; if (drawGrid) { dc.SetPen(wxPen(Options.AsColour(_T("Grid lines")))); dc.DrawLine(0,0,w,0); dc.SetPen(*wxTRANSPARENT_PEN); } // Draw rows int dx = 0; int dy = 0; int curColor = 0; AssDialogue *curDiag; for (int i=0;i=0) ? GetDialogue(curRow) : NULL; dx = 0; dy = i*lineHeight; // Check for collisions bool collides = false; if (curDiag) { AssDialogue *sel = GetDialogue(editBox->linen); if (sel && sel != curDiag) { if (curDiag->CollidesWith(sel)) collides = true; } } // Text array wxArrayString strings; // Header if (i == 0) { strings.Add(_("#")); strings.Add(_("L")); strings.Add(_("Start")); strings.Add(_("End")); strings.Add(_("Style")); strings.Add(_("Actor")); strings.Add(_("Effect")); strings.Add(_("Left")); strings.Add(_("Right")); strings.Add(_("Vert")); strings.Add(_("Text")); curColor = 1; } // Lines else if (curDiag) { // Set fields strings.Add(wxString::Format(_T("%i"),curRow+1)); strings.Add(wxString::Format(_T("%i"),curDiag->Layer)); if (byFrame) { strings.Add(wxString::Format(_T("%i"),VFR_Output.GetFrameAtTime(curDiag->Start.GetMS(),true))); strings.Add(wxString::Format(_T("%i"),VFR_Output.GetFrameAtTime(curDiag->End.GetMS(),true))); } else { strings.Add(curDiag->Start.GetASSFormated()); strings.Add(curDiag->End.GetASSFormated()); } strings.Add(curDiag->Style); strings.Add(curDiag->Actor); strings.Add(curDiag->Effect); strings.Add(curDiag->GetMarginString(0)); strings.Add(curDiag->GetMarginString(1)); strings.Add(curDiag->GetMarginString(2)); // Set text int mode = Options.AsInt(_T("Grid Hide Overrides")); wxString value = _T(""); // Hidden overrides if (mode == 1 || mode == 2) { wxString replaceWith = Options.AsText(_T("Grid hide overrides char")); int textlen = curDiag->Text.Length(); int depth = 0; wxChar curChar; for (int i=0;iText[i]; if (curChar == _T('{')) depth = 1; else if (curChar == _T('}')) { depth--; if (depth == 0 && mode == 1) value += replaceWith; if (depth < 0) depth = 0; } else if (depth != 1) value += curChar; } } // Show overrides else value = curDiag->Text; // Cap length and set text if (value.Length() > 512) value = value.Left(512) + _T("..."); strings.Add(value); // Set color curColor = 0; bool inSel = IsInSelection(curRow,0); if (inSel && curDiag->Comment) curColor = 5; else if (inSel) curColor = 2; else if (curDiag->Comment) curColor = 3; else if (Options.AsBool(_T("Highlight subs in frame")) && IsDisplayed(curDiag)) curColor = 4; } else { for (int j=0;j<11;j++) strings.Add(_T("?")); } // Draw row background color if (curColor) { dc.SetBrush(rowColors[curColor]); dc.DrawRectangle((curColor == 1) ? 0 : colWidth[0],i*lineHeight+1,w,lineHeight); } // Set text color if (collides) dc.SetTextForeground(Options.AsColour(_T("Grid collision foreground"))); else { dc.SetTextForeground(foreColors[curColor]); } // Draw text wxRect cur; bool isCenter; for (int j=0;j<11;j++) { // Check width if (colWidth[j] == 0) continue; // Is center? isCenter = !(j == 4 || j == 5 || j == 6 || j == 10); // Calculate clipping cur = wxRect(dx+4,dy,colWidth[j]-6,lineHeight); // Set clipping dc.DestroyClippingRegion(); dc.SetClippingRegion(cur); // Draw dc.DrawLabel(strings[j],cur,isCenter ? wxALIGN_CENTER : (wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT)); dx += colWidth[j]; } //if (collides) dc.SetPen(wxPen(wxColour(255,0,0))); // Draw grid dc.DestroyClippingRegion(); if (drawGrid) { dc.SetPen(wxPen(Options.AsColour(_T("Grid lines")))); dc.DrawLine(0,dy+lineHeight,w,dy+lineHeight); dc.SetPen(*wxTRANSPARENT_PEN); } } // Draw grid columns dx = 0; if (drawGrid) { dc.SetPen(wxPen(Options.AsColour(_T("Grid lines")))); for (int i=0;i<10;i++) { dx += colWidth[i]; dc.DrawLine(dx,0,dx,maxH); } dc.DrawLine(0,0,0,maxH); dc.DrawLine(w-1,0,w-1,h); } // Draw currently active line border dc.SetPen(wxPen(Options.AsColour(_T("Grid Active border")))); dc.SetBrush(*wxTRANSPARENT_BRUSH); dy = (editBox->linen+1-yPos) * lineHeight; dc.DrawRectangle(0,dy,w,lineHeight+1); } /////////// // On size void BaseGrid::OnSize(wxSizeEvent &event) { AdjustScrollbar(); SetColumnWidths(); Refresh(false); } ///////////// // On scroll void BaseGrid::OnScroll(wxScrollEvent &event) { int newPos = event.GetPosition(); if (yPos != newPos) { yPos = newPos; Refresh(false); } } //////////////// // Mouse events void BaseGrid::OnMouseEvent(wxMouseEvent &event) { // Window size int w,h; GetClientSize(&w,&h); // Modifiers bool shift = event.m_shiftDown; bool alt = event.m_altDown; bool ctrl = event.m_controlDown; // Row that mouse is over bool click = event.ButtonDown(wxMOUSE_BTN_LEFT); bool dclick = event.LeftDClick(); int row = event.GetY()/lineHeight + yPos - 1; bool headerClick = row < yPos; if (holding && !click) { row = MID(0,row,GetRows()-1); } bool validRow = row >= 0 && row < GetRows(); if (!validRow) row = -1; // Get focus if (event.ButtonDown()) { if (Options.AsBool(_T("Grid Allow Focus"))) { SetFocus(); } } // Click type bool startedHolding = false; if (click && !holding && validRow) { holding = true; startedHolding = true; CaptureMouse(); } if (!event.ButtonIsDown(wxMOUSE_BTN_LEFT) && holding) { holding = false; ReleaseMouse(); } // Scroll to keep visible if (holding) { // Find direction int minVis = yPos+1; int maxVis = yPos+h/lineHeight-3; int delta = 0; if (row < minVis) delta = -1; if (row > maxVis) delta = +1; // Scroll if (delta) { ScrollTo(yPos+delta*3); if (startedHolding) { holding = false; ReleaseMouse(); } } } // Click if ((click || holding || dclick) && validRow) { // Disable extending extendRow = -1; // Toggle selected if (click && ctrl && !shift && !alt) { SelectRow(row,true,!IsInSelection(row,0)); parentFrame->UpdateToolbar(); return; } // Normal click if ((click || dclick) && !shift && !ctrl && !alt) { if (editBox->linen != row) editBox->SetToLine(row); if (dclick) VideoContext::Get()->JumpToFrame(VFR_Output.GetFrameAtTime(GetDialogue(row)->Start.GetMS(),true)); SelectRow(row,false); parentFrame->UpdateToolbar(); lastRow = row; return; } // Keep selection if (click && !shift && !ctrl && alt) { editBox->SetToLine(row); return; } // Block select if ((click && shift && !ctrl && !alt) || (holding && !ctrl && !alt && !shift)) { if (lastRow != -1) { // Set boundaries int i1 = row; int i2 = lastRow; if (i1 > i2) { int aux = i1; i1 = i2; i2 = aux; } // Toggle each bool notFirst = false; for (int i=i1;i<=i2;i++) { SelectRow(i,notFirst,true); notFirst = true; } parentFrame->UpdateToolbar(); } return; } return; } // Popup if (event.ButtonDown(wxMOUSE_BTN_RIGHT)) { OnPopupMenu(headerClick); } // Mouse wheel if (event.GetWheelRotation() != 0) { int step = 3 * event.GetWheelRotation() / event.GetWheelDelta(); ScrollTo(yPos - step); return; } event.Skip(); } ///////////// // Scroll to void BaseGrid::ScrollTo(int y) { int w,h; GetClientSize(&w,&h); int nextY = MID(0,y,GetRows()+2 - h/lineHeight); if (yPos != nextY) { yPos = nextY; if (scrollBar->IsEnabled()) scrollBar->SetThumbPosition(yPos); Refresh(false); } } //////////////////// // Adjust scrollbar void BaseGrid::AdjustScrollbar() { // Variables int w,h,sw,sh; GetClientSize(&w,&h); int drawPerScreen = h/lineHeight; int rows = GetRows(); bool barToEnable = drawPerScreen < rows+2; bool barEnabled = scrollBar->IsEnabled(); // Set yPos yPos = MID(0,yPos,rows - drawPerScreen); // Set size scrollBar->Freeze(); scrollBar->GetSize(&sw,&sh); scrollBar->SetSize(w-sw,0,sw,h); // Set parameters if (barEnabled) { scrollBar->SetScrollbar(yPos,drawPerScreen,rows+2,drawPerScreen-2,true); } if (barToEnable != barEnabled) scrollBar->Enable(barToEnable); scrollBar->Thaw(); } ///////////////////// // Set column widths void BaseGrid::SetColumnWidths() { // Width/height int w = 0; int h = 0; GetClientSize(&w,&h); // DC for text extents test wxClientDC dc(this); dc.SetFont(font); int fw,fh; //dc.GetTextExtent(_T("#TWFfgGhH"), &fw, &fh, NULL, NULL, &font); // O(1) widths dc.GetTextExtent(_T("0000"), &fw, &fh, NULL, NULL, &font); int marginLen = fw + 10; dc.GetTextExtent(wxString::Format(_T("%i"),GetRows()), &fw, &fh, NULL, NULL, &font); int labelLen = fw + 10; int startLen = 0; int endLen = 0; if (!byFrame) { AssTime time; dc.GetTextExtent(time.GetASSFormated(), &fw, &fh, NULL, NULL, &font); startLen = fw + 10; endLen = fw + 10; } // O(n) widths int styleLen = 0; int actorLen = 0; int effectLen = 0; int maxLayer = 0; int maxStart = 0; int maxEnd = 0; AssDialogue *curDiag; for (int i=0;iLayer > maxLayer) maxLayer = curDiag->Layer; // Actor if (!curDiag->Actor.IsEmpty()) { dc.GetTextExtent(curDiag->Actor, &fw, &fh, NULL, NULL, &font); if (fw > actorLen) actorLen = fw; } // Style if (!curDiag->Style.IsEmpty()) { dc.GetTextExtent(curDiag->Style, &fw, &fh, NULL, NULL, &font); if (fw > styleLen) styleLen = fw; } // Effect if (!curDiag->Effect.IsEmpty()) { dc.GetTextExtent(curDiag->Effect, &fw, &fh, NULL, NULL, &font); if (fw > effectLen) effectLen = fw; } // Times if (byFrame) { int tmp = VFR_Output.GetFrameAtTime(curDiag->Start.GetMS(),true); if (tmp > maxStart) maxStart = tmp; tmp = VFR_Output.GetFrameAtTime(curDiag->End.GetMS(),true); if (tmp > maxEnd) maxEnd = tmp; } } } // Finish layer dc.GetTextExtent(wxString::Format(_T("%i"),maxLayer), &fw, &fh, NULL, NULL, &font); int layerLen = fw + 10; // Finish times if (byFrame) { dc.GetTextExtent(wxString::Format(_T("%i"),maxStart), &fw, &fh, NULL, NULL, &font); startLen = fw + 10; dc.GetTextExtent(wxString::Format(_T("%i"),maxEnd), &fw, &fh, NULL, NULL, &font); endLen = fw + 10; } // Style length if (false && AssFile::top) { AssStyle *curStyle; for (entryIter curIter=AssFile::top->Line.begin();curIter!=AssFile::top->Line.end();curIter++) { curStyle = AssEntry::GetAsStyle(*curIter); if (curStyle) { dc.GetTextExtent(curStyle->name, &fw, &fh, NULL, NULL, &font); if (fw > styleLen) styleLen = fw; } } } // Finish actor/effect/style if (actorLen) actorLen += 10; if (effectLen) effectLen += 10; if (styleLen) styleLen += 10; // Set column widths colWidth[0] = labelLen; colWidth[1] = layerLen; colWidth[2] = startLen; colWidth[3] = endLen; colWidth[4] = styleLen; colWidth[5] = actorLen; colWidth[6] = effectLen; colWidth[7] = marginLen; colWidth[8] = marginLen; colWidth[9] = marginLen; // Hide columns for (int i=0;i<10;i++) { if (showCol[i] == false) colWidth[i] = 0; } // Set size of last int total = 0; for (int i=0;i<10;i++) total+= colWidth[i]; colWidth[10] = w-total; } ////////////////////////// // Gets dialogue from map AssDialogue *BaseGrid::GetDialogue(int n) { try { AssEntry *e = *diagMap.at(n); if (e->GetType() != ENTRY_DIALOGUE) return NULL; return AssEntry::GetAsDialogue(e); } catch (...) { return NULL; } } //////////////////////////////////// // Check if line is being displayed bool BaseGrid::IsDisplayed(AssDialogue *line) { if (!VideoContext::Get()->IsLoaded()) return false; int f1 = VFR_Output.GetFrameAtTime(line->Start.GetMS(),true); int f2 = VFR_Output.GetFrameAtTime(line->End.GetMS(),false); if (f1 <= VideoContext::Get()->GetFrameN() && f2 >= VideoContext::Get()->GetFrameN()) return true; return false; } /////////////// // Update maps void BaseGrid::UpdateMaps() { // Store old int len = diagMap.size(); std::vector tmpDiagPtrMap; std::vector tmpSelMap; for (int i=0;iLine.begin();cur != AssFile::top->Line.end();cur++) { curdiag = AssEntry::GetAsDialogue(*cur); if (curdiag) { // Find old pos bool sel = false; for (int i=0;iIsLoaded()) { parentFrame->videoBox->videoSlider->SetFocus(); parentFrame->videoBox->videoSlider->AddPendingEvent(event); return; } event.Skip(); return; } // Select all if (key == 'A' && ctrl && !alt && !shift) { int rows = GetRows(); for (int i=0;ilinen; if (extendRow != -1) { curLine = extendRow; extendRow = -1; } int next = MID(0,curLine+dir*step,GetRows()-1); editBox->SetToLine(next); SelectRow(next); MakeCellVisible(next,0,false); return; } // Move active only if (alt && !shift && !ctrl) { extendRow = -1; int next = MID(0,editBox->linen+dir*step,GetRows()-1); editBox->SetToLine(next); Refresh(false); MakeCellVisible(next,0,false); return; } // Shift-selection if (shift && !ctrl && !alt) { // Find end if (extendRow == -1) extendRow = editBox->linen; extendRow = MID(0,extendRow+dir*step,GetRows()-1); // Set range int i1 = editBox->linen; int i2 = extendRow; if (i2 < i1) { int aux = i1; i1 = i2; i2 = aux; } // Select range ClearSelection(); for (int i=i1;i<=i2;i++) { SelectRow(i,true); } MakeCellVisible(extendRow,0,false); return; } } // Other events, send to audio display if (VideoContext::Get()->audio->loaded) { VideoContext::Get()->audio->AddPendingEvent(event); } else event.Skip(); } //////////////////////////////// // Sets display by frame or not void BaseGrid::SetByFrame (bool state) { // Check if it's already the same if (byFrame == state) return; byFrame = state; SetColumnWidths(); Refresh(false); } /////////////////////////////////////////////// // Generates an array covering inclusive range wxArrayInt BaseGrid::GetRangeArray(int n1,int n2) { // Swap if in wrong order if (n2 < n1) { int aux = n1; n1 = n2; n2 = aux; } // Generate array wxArrayInt target; for (int i=n1;i<=n2;i++) { target.Add(i); } return target; }