|
10 | 10 |
|
11 | 11 | #include "wx/glcanvas.h" |
12 | 12 | #include "wx/wx.h" |
| 13 | +#include <wx/choicdlg.h> |
13 | 14 | #include <wx/clipbrd.h> |
14 | 15 | #include <wx/numdlg.h> |
15 | 16 | #include <wx/progdlg.h> |
|
64 | 65 |
|
65 | 66 | #include <cmath> |
66 | 67 | #include <limits> |
| 68 | +#include <map> |
67 | 69 |
|
68 | 70 | #include <log.h> |
69 | 71 |
|
@@ -125,6 +127,7 @@ const long EffectsGrid::ID_GRID_MNU_AUTOLABEL = wxNewId(); |
125 | 127 | const long EffectsGrid::ID_GRID_MNU_HALVETIMINGS = wxNewId(); |
126 | 128 | const long EffectsGrid::ID_GRID_MNU_BREAKDOWN_WORD = wxNewId(); |
127 | 129 | const long EffectsGrid::ID_GRID_MNU_BREAKDOWN_WORDS = wxNewId(); |
| 130 | +const long EffectsGrid::ID_GRID_MNU_DUPLICATE_TO_TIMING_TRACK = wxNewId(); |
128 | 131 | const long EffectsGrid::ID_GRID_MNU_ALIGN_START_TIMES = wxNewId(); |
129 | 132 | const long EffectsGrid::ID_GRID_MNU_ALIGN_END_TIMES = wxNewId(); |
130 | 133 | const long EffectsGrid::ID_GRID_MNU_ALIGN_BOTH_TIMES = wxNewId(); |
@@ -672,6 +675,11 @@ void EffectsGrid::rightClick(wxMouseEvent& event) { |
672 | 675 | mnuLayer.Append(ID_GRID_MNU_REMOVE_SHIMMER, "Remove \"-shimmer\""); |
673 | 676 | } |
674 | 677 | mSelectedEffect = selectedEffect; |
| 678 | + |
| 679 | + if (mSequenceElements->GetNumberOfTimingElements() > 1) { |
| 680 | + wxMenuItem* menu_duplicate_to_track = mnuLayer.Append(ID_GRID_MNU_DUPLICATE_TO_TIMING_TRACK, "Duplicate Selection to Timing Track..."); |
| 681 | + menu_duplicate_to_track->Enable(selectedEffect->GetParentEffectLayer()->GetSelectedEffectCount() >= 1); |
| 682 | + } |
675 | 683 | } |
676 | 684 | mnuLayer.AppendSeparator(); |
677 | 685 | wxMenuItem* menu_effect_lock = mnuLayer.Append(ID_GRID_MNU_LOCK, "Lock"); |
@@ -1499,6 +1507,9 @@ void EffectsGrid::OnGridPopup(wxCommandEvent& event) { |
1499 | 1507 | wxCommandEvent eventRowHeaderChanged(EVT_ROW_HEADINGS_CHANGED); |
1500 | 1508 | wxPostEvent(mParent, eventRowHeaderChanged); |
1501 | 1509 | } |
| 1510 | + } else if (id == ID_GRID_MNU_DUPLICATE_TO_TIMING_TRACK) { |
| 1511 | + spdlog::debug("OnGridPopup - ID_GRID_MNU_DUPLICATE_TO_TIMING_TRACK"); |
| 1512 | + DuplicateSelectedTimingToTrack(); |
1502 | 1513 | } else if (id == ID_GRID_MNU_FIND) { |
1503 | 1514 | Find(); |
1504 | 1515 | } else if (id == ID_GRID_MNU_FIND_NEXT) { |
@@ -9338,6 +9349,103 @@ void EffectsGrid::DuplicateSelectedEffects() { |
9338 | 9349 | } |
9339 | 9350 | } |
9340 | 9351 |
|
| 9352 | +void EffectsGrid::DuplicateSelectedTimingToTrack() { |
| 9353 | + if (mSelectedEffect == nullptr) { |
| 9354 | + return; |
| 9355 | + } |
| 9356 | + |
| 9357 | + TimingElement* sourceElement = dynamic_cast<TimingElement*>(mSelectedEffect->GetParentEffectLayer()->GetParentElement()); |
| 9358 | + if (sourceElement == nullptr) { |
| 9359 | + return; |
| 9360 | + } |
| 9361 | + |
| 9362 | + // Gather the highlighted marks from every layer (Phrase/Word/Phoneme) of the |
| 9363 | + // source track, not just the row that was right-clicked - a selection can span |
| 9364 | + // multiple layers (e.g. a phrase and its words highlighted together). |
| 9365 | + std::map<int, std::vector<Effect*>> selectedEffectsByLayer; |
| 9366 | + for (size_t li = 0; li < sourceElement->GetEffectLayerCount(); ++li) { |
| 9367 | + EffectLayer* layer = sourceElement->GetEffectLayer((int)li); |
| 9368 | + for (int i = 0; i < layer->GetEffectCount(); ++i) { |
| 9369 | + Effect* e = layer->GetEffect(i); |
| 9370 | + if (e->GetSelected() != EFFECT_NOT_SELECTED) { |
| 9371 | + selectedEffectsByLayer[(int)li].push_back(e); |
| 9372 | + } |
| 9373 | + } |
| 9374 | + } |
| 9375 | + if (selectedEffectsByLayer.empty()) { |
| 9376 | + int layerIndex = -1; |
| 9377 | + EffectLayer* sourceLayer = mSelectedEffect->GetParentEffectLayer(); |
| 9378 | + for (size_t i = 0; i < sourceElement->GetEffectLayerCount(); ++i) { |
| 9379 | + if (sourceElement->GetEffectLayer((int)i) == sourceLayer) { |
| 9380 | + layerIndex = (int)i; |
| 9381 | + break; |
| 9382 | + } |
| 9383 | + } |
| 9384 | + if (layerIndex < 0) { |
| 9385 | + return; |
| 9386 | + } |
| 9387 | + selectedEffectsByLayer[layerIndex].push_back(mSelectedEffect); |
| 9388 | + } |
| 9389 | + |
| 9390 | + wxArrayString choices; |
| 9391 | + std::vector<TimingElement*> candidates; |
| 9392 | + int numTiming = mSequenceElements->GetNumberOfTimingElements(); |
| 9393 | + for (int i = 0; i < numTiming; ++i) { |
| 9394 | + TimingElement* te = mSequenceElements->GetTimingElement(i); |
| 9395 | + if (te == nullptr || te == sourceElement) { |
| 9396 | + continue; |
| 9397 | + } |
| 9398 | + choices.Add(te->GetName()); |
| 9399 | + candidates.push_back(te); |
| 9400 | + } |
| 9401 | + |
| 9402 | + if (choices.IsEmpty()) { |
| 9403 | + wxMessageBox("No other timing tracks found in this sequence.", "Duplicate Selection to Timing Track", |
| 9404 | + wxOK | wxICON_INFORMATION, (wxWindow*)mParent); |
| 9405 | + return; |
| 9406 | + } |
| 9407 | + |
| 9408 | + wxSingleChoiceDialog dlg((wxWindow*)mParent, "Duplicate the selected timing marks to which timing track?", |
| 9409 | + "Duplicate Selection to Timing Track", choices); |
| 9410 | + if (dlg.ShowModal() != wxID_OK) { |
| 9411 | + return; |
| 9412 | + } |
| 9413 | + |
| 9414 | + TimingElement* destElement = candidates[dlg.GetSelection()]; |
| 9415 | + |
| 9416 | + mSequenceElements->get_undo_mgr().CreateUndoStep(); |
| 9417 | + int skipped = 0; |
| 9418 | + for (auto& [layerIndex, selectedEffects] : selectedEffectsByLayer) { |
| 9419 | + while ((int)destElement->GetEffectLayerCount() <= layerIndex) { |
| 9420 | + destElement->AddEffectLayer(); |
| 9421 | + } |
| 9422 | + EffectLayer* destLayer = destElement->GetEffectLayer(layerIndex); |
| 9423 | + destLayer->UnSelectAllEffects(); |
| 9424 | + for (Effect* e : selectedEffects) { |
| 9425 | + long start = e->GetStartTimeMS(); |
| 9426 | + long end = e->GetEndTimeMS(); |
| 9427 | + if (destLayer->HasEffectsInTimeRange(start, end)) { |
| 9428 | + skipped++; |
| 9429 | + continue; |
| 9430 | + } |
| 9431 | + Effect* newef = destLayer->AddEffect(0, e->GetEffectName(), e->GetSettingsAsString(), e->GetPaletteAsString(), start, end, EFFECT_SELECTED, false); |
| 9432 | + if (newef != nullptr) { |
| 9433 | + mSequenceElements->get_undo_mgr().CaptureAddedEffect(destElement->GetName(), destLayer->GetIndex(), newef->GetID()); |
| 9434 | + } |
| 9435 | + } |
| 9436 | + } |
| 9437 | + destElement->SetCollapsed(false); |
| 9438 | + |
| 9439 | + if (skipped > 0) { |
| 9440 | + wxMessageBox(wxString::Format("%d timing mark(s) were skipped because they overlap existing marks on the destination track.", skipped), |
| 9441 | + "Duplicate Selection to Timing Track", wxOK | wxICON_WARNING, (wxWindow*)mParent); |
| 9442 | + } |
| 9443 | + |
| 9444 | + mSequenceElements->PopulateRowInformation(); |
| 9445 | + wxCommandEvent eventRowHeaderChanged(EVT_ROW_HEADINGS_CHANGED); |
| 9446 | + wxPostEvent(mParent, eventRowHeaderChanged); |
| 9447 | +} |
| 9448 | + |
9341 | 9449 | void EffectsGrid::DrawSongStructureOverlays(xlGraphicsContext* ctx) { |
9342 | 9450 | if (mSequenceElements == nullptr || mTimeline == nullptr) return; |
9343 | 9451 | const SongStructureManager& ssm = mSequenceElements->GetSongStructureManager(); |
|
0 commit comments