Aegisub/aegisub/src/base_grid.cpp
Niels Martin Hansen 70d41d31b2 Remove the SelectionChangeSubscriber mechanism from the grid and implement some basic selection change notification through SelectionController.
Change SelectionListener interface so it receives the set of lines added and removed from selection, instead of just the complete new selection.
Update VisualTool<> to use SelectionListener to receive selection change notifications.

This change (temporarily, I hope) breaks feature selection in visual drag mode, when changing selection via the grid. This is caused by the grid selection change first clearing the entire selection, which sends a separate notification about selection clear. This causes the last visual feature to be deselected, and then the visual tool base reselects the active line, causing a new notification for selection to be sent. The active line happens to be the newly clicked line, and the selection notification enters during the externalChange guard being set, and is then ignored for feature update purposes. When control returns to the original SelectRow call in the grid, the line to be selected has already been selected and then nothing happens.
The best fix is to avoid two notifications being required to deselect all then reselect one line in the first place, so making the grid selection handling saner is the best fix.

Originally committed to SVN as r4602.
2010-06-26 04:38:02 +00:00

1223 lines
27 KiB
C++

// 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 Project http://www.aegisub.org/
//
// $Id$
/// @file base_grid.cpp
/// @brief Base for subtitle grid in main UI
/// @ingroup main_ui
///
////////////
// Includes
#include "config.h"
#ifndef AGI_PRE
#include <algorithm>
#include <wx/sizer.h>
#endif
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_style.h"
#include "audio_display.h"
#include "base_grid.h"
#include "compat.h"
#include "frame_main.h"
#include "main.h"
#include "options.h"
#include "subs_edit_box.h"
#include "utils.h"
#include "vfr.h"
#include "video_box.h"
#include "video_context.h"
#include "video_slider.h"
/// @brief Constructor
/// @param parent
/// @param id
/// @param pos
/// @param size
/// @param style
/// @param name
///
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;
byFrame = false;
lineHeight = 1; // non-zero to avoid div by 0
// Set scrollbar
scrollBar = new wxScrollBar(this,GRID_SCROLLBAR,wxDefaultPosition,wxDefaultSize,wxSB_VERTICAL);
scrollBar->SetScrollbar(0,10,100,10);
wxBoxSizer *scrollbarpositioner = new wxBoxSizer(wxHORIZONTAL);
scrollbarpositioner->AddStretchSpacer();
scrollbarpositioner->Add(scrollBar, 0, wxEXPAND, 0);
SetSizerAndFit(scrollbarpositioner);
// Set style
UpdateStyle();
}
/// @brief Destructor
///
BaseGrid::~BaseGrid() {
delete bmp;
}
/// @brief Update style
///
void BaseGrid::UpdateStyle() {
// Set font
wxString fontname = lagi_wxString(OPT_GET("Subtitle/Grid/Font Face")->GetString());
if (fontname.IsEmpty()) fontname = _T("Tahoma");
font.SetFaceName(fontname);
font.SetPointSize(OPT_GET("Subtitle/Grid/Font Size")->GetInt());
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
std::vector<bool> column_array;
OPT_GET("Subtitle/Grid/Column")->GetListBool(column_array);
assert(column_array.size() == columns);
for (int i=0;i<columns;i++) showCol[i] = column_array[i];
SetColumnWidths();
// Update
AdjustScrollbar();
Refresh();
}
/// @brief Clears grid
///
void BaseGrid::Clear () {
Selection lines_removed;
GetSelectedSet(lines_removed);
AnnounceSelectedSetChanged(Selection(), lines_removed);
diagMap.clear();
diagPtrMap.clear();
selMap.clear();
yPos = 0;
AdjustScrollbar();
}
/// @brief Begin batch
///
void BaseGrid::BeginBatch() {
//Freeze();
}
/// @brief End batch
///
void BaseGrid::EndBatch() {
//Thaw();
AdjustScrollbar();
}
/// @brief Makes cell visible
/// @param row
/// @param col
/// @param center
///
void BaseGrid::MakeCellVisible(int row, int col,bool center) {
// Update last row selection
lastRow = row;
// 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);
}
}
}
/// @brief Select a row
/// @param row
/// @param addToSelected
/// @param select
///
void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
if (row < 0 || (size_t)row >= selMap.size()) return;
if (!addToSelected) {
// Sends change notifications for itself
ClearSelection();
}
if (select != !!selMap[row]) {
selMap[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);
}
Selection lines_added;
Selection lines_removed;
if (select)
lines_added.insert(diagPtrMap[row]);
else
lines_removed.insert(diagPtrMap[row]);
AnnounceSelectedSetChanged(lines_added, lines_removed);
}
}
/// @brief Selects visible lines
///
void BaseGrid::SelectVisible() {
Selection lines_removed;
GetSelectedSet(lines_removed);
int rows = GetRows();
bool selectedOne = false;
for (int i=0;i<rows;i++) {
if (IsDisplayed(GetDialogue(i))) {
if (!selectedOne) {
SelectRow(i,false);
MakeCellVisible(i,0);
selectedOne = true;
}
else {
SelectRow(i,true);
}
}
}
Selection lines_added;
GetSelectedSet(lines_added);
AnnounceSelectedSetChanged(lines_added, lines_removed);
}
/// @brief Unselects all cells
///
void BaseGrid::ClearSelection() {
Selection lines_removed;
GetSelectedSet(lines_removed);
int rows = selMap.size();
for (int i=0;i<rows;i++) {
selMap[i] = false;
}
Refresh(false);
AnnounceSelectedSetChanged(Selection(), lines_removed);
}
/// @brief Is cell in selection?
/// @param row
/// @param col
/// @return
///
bool BaseGrid::IsInSelection(int row, int) const {
if ((size_t)row >= selMap.size() || row < 0) return false;
return !!selMap[row];
}
/// @brief Number of selected rows
/// @return
///
int BaseGrid::GetNumberSelection() const {
return std::count(selMap.begin(), selMap.end(), 1);
}
/// @brief Gets first selected row
/// @return
///
int BaseGrid::GetFirstSelRow() const {
std::vector<int>::const_iterator first = std::find(selMap.begin(), selMap.end(), 1);
if (first == selMap.end()) return -1;
return std::distance(selMap.begin(), first);
}
/// @brief Gets last selected row from first block selection
/// @return
///
int BaseGrid::GetLastSelRow() const {
int frow = GetFirstSelRow();
while (IsInSelection(frow)) {
frow++;
}
return frow-1;
}
/// @brief Gets all selected rows
/// @param[out] cont Is the selection contiguous, i.e. free from holes
/// @return Array with indices of selected lines
///
wxArrayInt BaseGrid::GetSelection(bool *cont) const {
// Prepare
int nrows = GetRows();
int last = -1;
bool continuous = true;
wxArrayInt selections;
// Scan
for (int i=0;i<nrows;i++) {
if (selMap[i]) {
selections.Add(i);
if (last != -1 && i != last+1) continuous = false;
last = i;
}
}
// Return
if (cont) *cont = continuous;
return selections;
}
/// @brief Get number of rows
/// @return
///
int BaseGrid::GetRows() const {
return diagMap.size();
}
///////////////
// Event table
BEGIN_EVENT_TABLE(BaseGrid,wxWindow)
EVT_PAINT(BaseGrid::OnPaint)
EVT_SIZE(BaseGrid::OnSize)
EVT_COMMAND_SCROLL(GRID_SCROLLBAR,BaseGrid::OnScroll)
EVT_MOUSE_EVENTS(BaseGrid::OnMouseEvent)
EVT_KEY_DOWN(BaseGrid::OnKeyPress)
END_EVENT_TABLE()
/// @brief Paint event
/// @param event
///
void BaseGrid::OnPaint (wxPaintEvent &event) {
// Prepare
wxPaintDC dc(this);
bool direct = false;
if (direct) {
DrawImage(dc);
}
else {
// Get size and pos
int w = 0;
int h = 0;
GetClientSize(&w,&h);
w -= scrollBar->GetSize().GetWidth();
// Prepare bitmap
if (bmp) {
if (bmp->GetWidth() < 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);
}
}
/// @brief Draw image
/// @param dc
///
void BaseGrid::DrawImage(wxDC &dc) {
// Get size and pos
int w = 0;
int h = 0;
GetClientSize(&w,&h);
w -= scrollBar->GetSize().GetWidth();
// Set font
dc.SetFont(font);
// Clear background
dc.SetBackground(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Background")->GetColour())));
dc.Clear();
// Draw labels
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Left Column")->GetColour())));
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<wxBrush> rowColors;
std::vector<wxColor> foreColors;
rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Background")->GetColour()))); // 0 = Standard
foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Standard")->GetColour()));
rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Header")->GetColour()))); // 1 = Header
foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Standard")->GetColour()));
rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Selection")->GetColour()))); // 2 = Selected
foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Comment")->GetColour()))); // 3 = Commented
foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Inframe")->GetColour()))); // 4 = Video Highlighted
foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
rowColors.push_back(wxBrush(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Background/Selected Comment")->GetColour()))); // 5 = Commented & selected
foreColors.push_back(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Selection")->GetColour()));
// First grid row
bool drawGrid = true;
if (drawGrid) {
dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Lines")->GetColour())));
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<nDraw+1;i++) {
// Prepare
int curRow = i+yPos-1;
curDiag = (curRow>=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(),false)));
}
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 = OPT_GET("Subtitle/Grid/Hide Overrides")->GetInt();
wxString value = _T("");
// Hidden overrides
if (mode == 1 || mode == 2) {
wxString replaceWith = lagi_wxString(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString());
int textlen = curDiag->Text.Length();
int depth = 0;
wxChar curChar;
for (int j=0;j<textlen;j++) {
curChar = curDiag->Text[j];
if (curChar == _T('{')) depth = 1;
else if (curChar == _T('}')) {
depth--;
if (depth == 0 && mode == 1) value += replaceWith;
else 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 (OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame")->GetBool() && 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(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Collision")->GetColour()));
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(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Lines")->GetColour())));
dc.DrawLine(0,dy+lineHeight,w,dy+lineHeight);
dc.SetPen(*wxTRANSPARENT_PEN);
}
}
// Draw grid columns
dx = 0;
if (drawGrid) {
dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Lines")->GetColour())));
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,maxH);
}
// Draw currently active line border
dc.SetPen(wxPen(lagi_wxColour(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColour())));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dy = (editBox->linen+1-yPos) * lineHeight;
dc.DrawRectangle(0,dy,w,lineHeight+1);
}
/// @brief On size
/// @param event
///
void BaseGrid::OnSize(wxSizeEvent &event) {
AdjustScrollbar();
SetColumnWidths();
Refresh(false);
}
/// @brief On scroll
/// @param event
///
void BaseGrid::OnScroll(wxScrollEvent &event) {
int newPos = event.GetPosition();
if (yPos != newPos) {
yPos = newPos;
Refresh(false);
}
}
/// @brief Mouse events
/// @param event
/// @return
///
void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
// Window size
int w,h;
GetClientSize(&w,&h);
// Modifiers
bool shift = event.m_shiftDown;
bool alt = event.m_altDown;
#ifdef __APPLE__
bool ctrl = event.m_metaDown;
#else
bool ctrl = event.m_controlDown;
#endif
// 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 (OPT_GET("Subtitle/Grid/Focus Allow")->GetBool()) {
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) {
editBox->SetToLine(row);
if (dclick) VideoContext::Get()->JumpToTime(GetDialogue(row)->Start.GetMS());
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();
}
/// @brief Scroll to
/// @param y
///
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);
}
}
/// @brief 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();
}
/// @brief Set column widths
/// @return
///
void BaseGrid::SetColumnWidths() {
if (!IsShownOnScreen()) return;
// 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
bool showMargin[3];
showMargin[0] = showMargin[1] = showMargin[2] = false;
bool showLayer = false;
int styleLen = 0;
int actorLen = 0;
int effectLen = 0;
int maxLayer = 0;
int maxStart = 0;
int maxEnd = 0;
AssDialogue *curDiag;
for (int i=0;i<GetRows();i++) {
curDiag = GetDialogue(i);
if (curDiag) {
// Layer
if (curDiag->Layer > maxLayer) {
maxLayer = curDiag->Layer;
showLayer = true;
}
// 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;
}
// Margins
for (int j=0;j<3;j++) {
if (curDiag->Margin[j] != 0) showMargin[j] = true;
}
// 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 = dynamic_cast<AssStyle*>(*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] = showLayer ? layerLen : 0;
colWidth[2] = startLen;
colWidth[3] = endLen;
colWidth[4] = styleLen;
colWidth[5] = actorLen;
colWidth[6] = effectLen;
for (int i=0;i<3;i++) colWidth[i+7] = showMargin[i] ? marginLen : 0;
// Hide columns
for (int i=0;i<columns;i++) {
if (!showCol[i]) colWidth[i] = 0;
}
// Set size of last
int total = 0;
for (int i=0;i<10;i++) total+= colWidth[i];
colWidth[10] = w-total;
}
/// @brief Gets dialogue from map
/// @param n
/// @return
///
AssDialogue *BaseGrid::GetDialogue(int n) const {
try {
if (n < 0 || (size_t)n >= diagMap.size()) return NULL;
return dynamic_cast<AssDialogue*>(*diagMap.at(n));
}
catch (...) {
return NULL;
}
}
/// @brief Check if line is being displayed
/// @param line
/// @return
///
bool BaseGrid::IsDisplayed(AssDialogue *line) {
VideoContext* con = VideoContext::Get();
if (!con->IsLoaded()) return false;
int f1 = VFR_Output.GetFrameAtTime(line->Start.GetMS(),true);
int f2 = VFR_Output.GetFrameAtTime(line->End.GetMS(),false);
if (f1 <= con->GetFrameN() && f2 >= con->GetFrameN()) return true;
return false;
}
/// @brief Update maps
///
void BaseGrid::UpdateMaps() {
// Store old
int len = selMap.size();
std::vector<AssDialogue *> tmpDiagPtrMap(diagPtrMap);
std::vector<int> tmpSelMap(selMap);
// Clear old
diagPtrMap.clear();
diagMap.clear();
selMap.clear();
// Re-generate lines
for (entryIter cur=AssFile::top->Line.begin();cur != AssFile::top->Line.end();cur++) {
AssDialogue *curdiag = dynamic_cast<AssDialogue*>(*cur);
if (curdiag) {
// Find old pos
int sel = 0;
for (int i=0;i<len;i++) {
if (tmpDiagPtrMap[i] == curdiag) {
sel = tmpSelMap[i];
break;
}
}
// Add new
diagMap.push_back(cur);
diagPtrMap.push_back(curdiag);
selMap.push_back(sel);
}
}
// Refresh
Refresh(false);
}
/// @brief Key press
/// @param event
/// @return
///
void BaseGrid::OnKeyPress(wxKeyEvent &event) {
// Get size
int w,h;
GetClientSize(&w,&h);
// Get scan code
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;
// Left/right, forward to seek bar if video is loaded
if (key == WXK_LEFT || key == WXK_RIGHT) {
if (VideoContext::Get()->IsLoaded()) {
parentFrame->videoBox->videoSlider->SetFocus();
parentFrame->videoBox->videoSlider->GetEventHandler()->ProcessEvent(event);
return;
}
event.Skip();
return;
}
// Select all
if (key == 'A' && ctrl && !alt && !shift) {
int rows = GetRows();
for (int i=0;i<rows;i++) SelectRow(i,true);
}
// Up/down
int dir = 0;
int step = 1;
if (key == WXK_UP) dir = -1;
if (key == WXK_DOWN) dir = 1;
if (key == WXK_PAGEUP) {
dir = -1;
step = h/lineHeight - 2;
}
if (key == WXK_PAGEDOWN) {
dir = 1;
step = h/lineHeight - 2;
}
if (key == WXK_HOME) {
dir = -1;
step = GetRows();
}
if (key == WXK_END) {
dir = 1;
step = GetRows();
}
// Moving
if (dir) {
// Move selection
if (!ctrl && !shift && !alt) {
// Move to extent first
int curLine = editBox->linen;
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->GetEventHandler()->ProcessEvent(event);
}
else event.Skip();
}
/// @brief Sets display by frame or not
/// @param state
/// @return
///
void BaseGrid::SetByFrame (bool state) {
// Check if it's already the same
if (byFrame == state) return;
byFrame = state;
SetColumnWidths();
Refresh(false);
}
/// @brief Generates an array covering inclusive range
/// @param n1
/// @param n2
///
wxArrayInt BaseGrid::GetRangeArray(int n1,int n2) const {
// 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;
}
// SelectionController
void BaseGrid::GetSelectedSet(Selection &selection) const {
for (size_t i = 0; i < selMap.size(); ++i) {
if (selMap[i] != 0) {
selection.insert(GetDialogue((int)i));
}
}
}