forked from mia/Aegisub
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.
1223 lines
27 KiB
C++
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
|