diff --git a/OpenUtau.Core/Commands/TrackCommands.cs b/OpenUtau.Core/Commands/TrackCommands.cs
index 7d2eb1718..031b42b82 100644
--- a/OpenUtau.Core/Commands/TrackCommands.cs
+++ b/OpenUtau.Core/Commands/TrackCommands.cs
@@ -78,10 +78,16 @@ public MoveTrackCommand(UProject project, UTrack track, bool up) {
}
public override string ToString() => "Move track";
public override void Execute() {
+ if (index < 0 || index + 1 >= project.tracks.Count) {
+ return;
+ }
project.tracks.Reverse(index, 2);
UpdateTrackNo();
}
public override void Unexecute() {
+ if (index < 0 || index + 1 >= project.tracks.Count) {
+ return;
+ }
project.tracks.Reverse(index, 2);
UpdateTrackNo();
}
@@ -113,6 +119,36 @@ public ChangeTrackColorCommand(UProject project, UTrack track, string colorName)
public override void Unexecute() => track.TrackColor = oldName;
}
+ public class TrackChangeSettingsCommand : TrackCommand {
+ readonly bool newMute;
+ readonly bool oldMute;
+ readonly double newVolume;
+ readonly double oldVolume;
+ readonly double newPan;
+ readonly double oldPan;
+ public TrackChangeSettingsCommand(UProject project, UTrack track, bool mute, double volume, double pan) {
+ this.project = project;
+ this.track = track;
+ newMute = mute;
+ newVolume = volume;
+ newPan = pan;
+ oldMute = track.Mute;
+ oldVolume = track.Volume;
+ oldPan = track.Pan;
+ }
+ public override string ToString() => "Change track settings";
+ public override void Execute() {
+ track.Mute = newMute;
+ track.Volume = newVolume;
+ track.Pan = newPan;
+ }
+ public override void Unexecute() {
+ track.Mute = oldMute;
+ track.Volume = oldVolume;
+ track.Pan = oldPan;
+ }
+ }
+
public class TrackChangeSingerCommand : TrackCommand {
readonly USinger newSinger, oldSinger;
public TrackChangeSingerCommand(UProject project, UTrack track, USinger newSinger) {
diff --git a/OpenUtau/Controls/ApplyToAllTracksButton.cs b/OpenUtau/Controls/ApplyToAllTracksButton.cs
new file mode 100644
index 000000000..fd22f6d49
--- /dev/null
+++ b/OpenUtau/Controls/ApplyToAllTracksButton.cs
@@ -0,0 +1,32 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
+using Avalonia.Media;
+
+namespace OpenUtau.App.Controls {
+
+ public class ApplyToAllTracksButton : Button {
+ public ApplyToAllTracksButton() {
+ Padding = new Avalonia.Thickness(0);
+ BorderThickness = new Avalonia.Thickness(0);
+ Background = Brushes.Transparent;
+ Focusable = false;
+ Content = new Path {
+ Stroke = ThemeManager.AccentBrush2,
+ StrokeThickness = 1.75,
+ Data = Geometry.Parse("M3,4 H11 M3,8 H11 M3,12 H11 M10,2 L13,4 L10,6 M10,6 L13,4 L10,2"),
+ };
+ }
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e) {
+ base.OnPointerPressed(e);
+ e.Handled = true;
+ }
+
+ protected override void OnPointerReleased(PointerReleasedEventArgs e) {
+ base.OnPointerReleased(e);
+ e.Handled = true;
+ }
+ }
+
+}
diff --git a/OpenUtau/Controls/FavouriteToggleButton.cs b/OpenUtau/Controls/FavouriteToggleButton.cs
index e1ffcc931..93e93374d 100644
--- a/OpenUtau/Controls/FavouriteToggleButton.cs
+++ b/OpenUtau/Controls/FavouriteToggleButton.cs
@@ -1,44 +1,54 @@
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
+using Avalonia.Input;
using Avalonia.Media;
using OpenUtau.App;
-public class FavouriteToggleButton : ToggleButton
-{
- private readonly Path _iconPath;
-
- public FavouriteToggleButton()
- {
- //this.Height = 20;
- //this.Width = 20;
- // Create icon Path.
- _iconPath = new Path
- {
- Fill = SolidColorBrush.Parse("#00000000"),
- Stroke = ThemeManager.AccentBrush3,
- StrokeThickness = 2,
- Data = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36,2,12.28,2,8.5C2,5.42,4.42,3,7.5,3C9.24,3,10.91,3.81,12,5.09C13.09,3.81,14.76,3,16.5,3C19.58,3,22,5.42,22,8.5C22,12.28,18.6,15.36,13.45,20.04L12,21.35Z"),
- RenderTransform = new ScaleTransform { ScaleX = 0.6,ScaleY = 0.6}
- };
-
- this.Content = _iconPath;
-
- // Change icon on click.
- this.PropertyChanged += (sender, e) =>
- {
- if (e.Property == IsCheckedProperty)
- {
- UpdateIcon(IsChecked ?? false);
+namespace OpenUtau.App.Controls {
+
+ public class FavouriteToggleButton : ToggleButton {
+ private readonly Path _iconPath;
+
+ public FavouriteToggleButton() {
+ Padding = new Avalonia.Thickness(0);
+ //this.Height = 20;
+ //this.Width = 20;
+ // Create icon Path.
+ _iconPath = new Path {
+ Fill = SolidColorBrush.Parse("#00000000"),
+ Stroke = ThemeManager.AccentBrush3,
+ StrokeThickness = 2,
+ Data = Geometry.Parse("M12,21.35L10.55,20.03C5.4,15.36,2,12.28,2,8.5C2,5.42,4.42,3,7.5,3C9.24,3,10.91,3.81,12,5.09C13.09,3.81,14.76,3,16.5,3C19.58,3,22,5.42,22,8.5C22,12.28,18.6,15.36,13.45,20.04L12,21.35Z"),
+ RenderTransform = new ScaleTransform { ScaleX = 0.5, ScaleY = 0.5 }
+ };
+
+ this.Content = _iconPath;
+
+ // Change icon on click.
+ this.PropertyChanged += (sender, e) => {
+ if (e.Property == IsCheckedProperty) {
+ UpdateIcon(IsChecked ?? false);
+ }
+ };
+ }
+
+ private void UpdateIcon(bool isChecked) {
+ if (isChecked) {
+ _iconPath.Fill = ThemeManager.AccentBrush3;
+ } else {
+ _iconPath.Fill = SolidColorBrush.Parse("#00000000");
}
- };
- }
+ }
- private void UpdateIcon(bool isChecked)
- {
- if (isChecked) {
- _iconPath.Fill = ThemeManager.AccentBrush3;
- } else {
- _iconPath.Fill = SolidColorBrush.Parse("#00000000");
+ protected override void OnPointerPressed(PointerPressedEventArgs e) {
+ base.OnPointerPressed(e);
+ e.Handled = true;
+ }
+
+ protected override void OnPointerReleased(PointerReleasedEventArgs e) {
+ base.OnPointerReleased(e);
+ e.Handled = true;
}
}
+
}
diff --git a/OpenUtau/Controls/TrackHeader.axaml b/OpenUtau/Controls/TrackHeader.axaml
index b724ea1d2..2b30222ef 100644
--- a/OpenUtau/Controls/TrackHeader.axaml
+++ b/OpenUtau/Controls/TrackHeader.axaml
@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:controls="clr-namespace:OpenUtau.App.Controls"
xmlns:vm="using:OpenUtau.App.ViewModels"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="104"
x:Class="OpenUtau.App.Controls.TrackHeader" Width="300" Height="104" TrackNo="{Binding TrackNo}">
@@ -53,18 +54,42 @@
HorizontalOffset="-3" ItemsSource="{Binding PhonemizerMenuItems}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
diff --git a/OpenUtau/Controls/TrackHeader.axaml.cs b/OpenUtau/Controls/TrackHeader.axaml.cs
index 13a8ec35c..5c7559326 100644
--- a/OpenUtau/Controls/TrackHeader.axaml.cs
+++ b/OpenUtau/Controls/TrackHeader.axaml.cs
@@ -44,12 +44,15 @@ public int TrackNo {
private double trackHeight;
private Point offset;
private int trackNo;
+ private readonly KeyModifiers cmdKey =
+ OS.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control;
public TrackHeaderViewModel? ViewModel;
private List unbinds = new List();
private UTrack? track;
+ private TrackHeaderCanvas? canvas;
public TrackHeader() {
InitializeComponent();
@@ -66,6 +69,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
internal void Bind(UTrack track, TrackHeaderCanvas canvas) {
this.track = track;
+ this.canvas = canvas;
unbinds.Add(this.Bind(TrackHeightProperty, canvas.GetObservable(TrackHeaderCanvas.TrackHeightProperty)));
unbinds.Add(this.Bind(HeightProperty, canvas.GetObservable(TrackHeaderCanvas.TrackHeightProperty)));
unbinds.Add(this.Bind(OffsetProperty, canvas.WhenAnyValue(x => x.TrackOffset, trackOffset => new Point(0, -trackOffset * TrackHeight))));
@@ -82,6 +86,21 @@ private void SetPosition() {
}
}
+ void HeaderPointerPressed(object? sender, PointerPressedEventArgs args) {
+ if (!args.GetCurrentPoint(this).Properties.IsLeftButtonPressed ||
+ track == null ||
+ canvas?.DataContext is not TracksViewModel tracksViewModel) {
+ return;
+ }
+ if (args.KeyModifiers == KeyModifiers.Shift) {
+ tracksViewModel.SelectTracksUntil(track);
+ } else if (args.KeyModifiers == cmdKey) {
+ tracksViewModel.ToggleSelectTrack(track);
+ } else {
+ tracksViewModel.SelectTrack(track);
+ }
+ }
+
void TrackNameButtonClicked(object sender, RoutedEventArgs args) {
ViewModel?.Rename();
args.Handled = true;
diff --git a/OpenUtau/Controls/TrackHeaderCanvas.cs b/OpenUtau/Controls/TrackHeaderCanvas.cs
index ca716e8fd..a945f71ea 100644
--- a/OpenUtau/Controls/TrackHeaderCanvas.cs
+++ b/OpenUtau/Controls/TrackHeaderCanvas.cs
@@ -2,11 +2,11 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
+using System.Linq;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Controls;
using OpenUtau.App.ViewModels;
-using OpenUtau.Core;
using OpenUtau.Core.Ustx;
using ReactiveUI;
@@ -98,6 +98,21 @@ public TrackHeaderCanvas() {
}
}
});
+ MessageBus.Current.Listen()
+ .Subscribe(e => {
+ var selectedTracks = new HashSet(e.selectedTracks);
+ foreach (var (track, header) in trackHeaders) {
+ if (header.ViewModel != null) {
+ header.ViewModel.IsSelected = selectedTracks.Contains(track);
+ }
+ }
+ });
+ MessageBus.Current.Listen()
+ .Subscribe(_ => {
+ foreach (var (_, header) in trackHeaders) {
+ header.ViewModel?.RefreshSelectionStyle();
+ }
+ });
}
protected override void OnInitialized() {
@@ -153,6 +168,9 @@ private void Items_CollectionChanged(object? sender, NotifyCollectionChangedEven
void Add(UTrack track) {
var vm = new TrackHeaderViewModel(track);
+ if (DataContext is TracksViewModel tracksViewModel) {
+ vm.IsSelected = tracksViewModel.SelectedTracks.Contains(track);
+ }
var header = new TrackHeader() {
DataContext = vm,
ViewModel = vm,
diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml
index d6787a479..50edeacc2 100644
--- a/OpenUtau/Strings/Strings.axaml
+++ b/OpenUtau/Strings/Strings.axaml
@@ -772,7 +772,7 @@ General
Space: Play
◀ Scroll here to zoom horizontally ▶
Scroll here to zoom vertically ▶
-
+
Duplicate Track
Duplicate Track Settings
Favorites
@@ -789,6 +789,7 @@ General
Open singers location
Remove
Rename track
+ Rotate singers across selected tracks
Select Renderer
Select Singer
(Singer default)
@@ -796,6 +797,7 @@ General
Solo additionally (which not removes solo from other tracks)
Solo this only (which removes solo from other tracks)
Unsolo all
+ Standardize Track Settings
Change track color
Track Settings
diff --git a/OpenUtau/ViewModels/MenuItemViewModel.cs b/OpenUtau/ViewModels/MenuItemViewModel.cs
index 701d3e05d..ca847037c 100644
--- a/OpenUtau/ViewModels/MenuItemViewModel.cs
+++ b/OpenUtau/ViewModels/MenuItemViewModel.cs
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Windows.Input;
using Avalonia.Controls.Shapes;
-using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Threading;
using OpenUtau.Core.Ustx;
@@ -10,6 +9,7 @@ namespace OpenUtau.App.ViewModels {
public class MenuItemViewModel {
public string? Header { get; set; }
public ICommand? Command { get; set; }
+ public ICommand? SecondaryCommand { get; set; }
public object? CommandParameter { get; set; }
public IList? Items { get; set; }
public double Height { get; set; } = 24;
@@ -17,6 +17,7 @@ public class MenuItemViewModel {
public KeyGesture? InputGesture { get; set; }
public bool IsEnabled { get; set; } = true;
public object? Icon { get; set; }
+ public virtual object HeaderViewModel => this;
public MenuItemViewModel() { }
public MenuItemViewModel(bool isChecked) {
@@ -44,19 +45,7 @@ public bool IsFavourite {
}
}
}
- private object? _icon;
- public new object? Icon {
- get {
- if(_icon == null) {
- if (CommandParameter is USinger) {
- _icon = new FavouriteToggleButton() {
- [!FavouriteToggleButton.IsCheckedProperty] = new Binding("IsFavourite")
- };
- }
- }
- return _icon;
- }
- }
+
public string? Location {
get {
if (CommandParameter is USinger singer) {
@@ -66,4 +55,7 @@ public string? Location {
}
}
}
+
+ public class PhonemizerMenuItemViewModel : MenuItemViewModel {
+ }
}
diff --git a/OpenUtau/ViewModels/TrackHeaderViewModel.cs b/OpenUtau/ViewModels/TrackHeaderViewModel.cs
index 1c62570aa..a7d4a4c0b 100644
--- a/OpenUtau/ViewModels/TrackHeaderViewModel.cs
+++ b/OpenUtau/ViewModels/TrackHeaderViewModel.cs
@@ -3,7 +3,6 @@
using System.IO;
using System.Linq;
using System.Reactive;
-using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
@@ -27,9 +26,11 @@ public class TrackHeaderViewModel : ViewModelBase, IActivatableViewModel {
public string PhonemizerTag => track.Phonemizer.Tag;
public Core.Render.IRenderer Renderer => track.RendererSettings.Renderer;
public IReadOnlyList? SingerMenuItems { get; set; }
- public ReactiveCommand SelectSingerCommand { get; }
+ public ReactiveCommand SelectSingerCommand { get; }
+ public ReactiveCommand AllSetSingerCommand { get; }
public IReadOnlyList? PhonemizerMenuItems { get; set; }
public ReactiveCommand SelectPhonemizerCommand { get; }
+ public ReactiveCommand AllSetPhonemizerCommand { get; }
public IReadOnlyList? RenderersMenuItems { get; set; }
public ReactiveCommand SelectRendererCommand { get; }
[Reactive] public string TrackName { get; set; } = string.Empty;
@@ -40,10 +41,12 @@ public class TrackHeaderViewModel : ViewModelBase, IActivatableViewModel {
[Reactive] public bool Mute { get; set; }
[Reactive] public bool Muted { get; set; }
[Reactive] public bool Solo { get; set; }
+ [Reactive] public bool IsSelected { get; set; }
[Reactive] public Bitmap? Avatar { get; set; }
- [Reactive] public bool IsSingerVisible { get; set; }
- [Reactive] public bool IsPhonemizerVisible { get; set; }
- [Reactive] public bool IsRendererVisible { get; set; }
+ [Reactive] public bool IsSingerVisible { get; set; }
+ [Reactive] public bool IsPhonemizerVisible { get; set; }
+ [Reactive] public bool IsRendererVisible { get; set; }
+ [Reactive] public IBrush HeaderBorderBrush { get; set; } = ThemeManager.NeutralAccentBrushSemi;
public ViewModelActivator Activator { get; }
@@ -51,45 +54,27 @@ public class TrackHeaderViewModel : ViewModelBase, IActivatableViewModel {
// Parameterless constructor for Avalonia preview only.
public TrackHeaderViewModel() {
- SelectSingerCommand = ReactiveCommand.Create(_ => { });
+ SelectSingerCommand = ReactiveCommand.Create(_ => { });
+ AllSetSingerCommand = ReactiveCommand.Create(_ => { });
SelectPhonemizerCommand = ReactiveCommand.Create(_ => { });
+ AllSetPhonemizerCommand = ReactiveCommand.Create(_ => { });
SelectRendererCommand = ReactiveCommand.Create(_ => { });
Activator = new ViewModelActivator();
track = new UTrack(DocManager.Inst.Project);
+ this.WhenAnyValue(x => x.IsSelected)
+ .Subscribe(_ => RefreshSelectionStyle());
+ RefreshSelectionStyle();
}
public TrackHeaderViewModel(UTrack track) {
this.track = track;
- SelectSingerCommand = ReactiveCommand.Create(singer => {
+ SelectSingerCommand = ReactiveCommand.Create(singer => {
if (track.Singer != singer) {
DocManager.Inst.StartUndoGroup("command.track.singer");
- Log.Information($"Loading Singer: {singer.Name}");
- DocManager.Inst.ExecuteCmd(new TrackChangeSingerCommand(DocManager.Inst.Project, track, singer));
- if (!string.IsNullOrEmpty(singer?.Id) &&
- Preferences.Default.SingerPhonemizers.TryGetValue(Singer.Id, out var phonemizerName) &&
- TryChangePhonemizer(phonemizerName)) {
- } else if (!string.IsNullOrEmpty(singer?.DefaultPhonemizer)) {
- TryChangePhonemizer(singer.DefaultPhonemizer);
- }
- if (singer == null || !singer.Found) {
- var settings = new URenderSettings();
- DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, track, settings));
- } else if (singer.SingerType != track.RendererSettings.Renderer?.SingerType) {
- var settings = new URenderSettings {
- renderer = Core.Render.Renderers.GetDefaultRenderer(singer.SingerType),
- };
- DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, track, settings));
- }
+ ApplySingerToTrack(track, singer);
DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(track.TrackNo, true));
DocManager.Inst.EndUndoGroup();
- if (!string.IsNullOrEmpty(singer?.Id) && singer.Found) {
- Preferences.Default.RecentSingers.Remove(singer.Id);
- Preferences.Default.RecentSingers.Insert(0, singer.Id);
- if (Preferences.Default.RecentSingers.Count > 16) {
- Preferences.Default.RecentSingers.RemoveRange(
- 16, Preferences.Default.RecentSingers.Count - 16);
- }
- }
+ UpdateRecentSingers(singer);
Preferences.Save();
MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part"));
}
@@ -97,6 +82,28 @@ public TrackHeaderViewModel(UTrack track) {
this.RaisePropertyChanged(nameof(Renderer));
RefreshAvatar();
});
+ AllSetSingerCommand = ReactiveCommand.Create(singer => {
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count <= 1) {
+ targetTracks = DocManager.Inst.Project.tracks;
+ }
+ DocManager.Inst.StartUndoGroup("command.track.singer");
+ foreach (var targetTrack in targetTracks) {
+ ApplySingerToTrack(targetTrack, singer);
+ }
+ DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(-1, true));
+ DocManager.Inst.EndUndoGroup();
+ UpdateRecentSingers(singer);
+ Preferences.Save();
+ MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part"));
+ MessageBus.Current.SendMessage(new TracksRefreshEvent());
+ this.RaisePropertyChanged(nameof(Singer));
+ this.RaisePropertyChanged(nameof(Renderer));
+ RefreshAvatar();
+ });
SelectPhonemizerCommand = ReactiveCommand.Create(factory => {
if (track.Phonemizer.GetType() != factory.type) {
DocManager.Inst.StartUndoGroup("command.track.setting");
@@ -119,6 +126,39 @@ public TrackHeaderViewModel(UTrack track) {
this.RaisePropertyChanged(nameof(Phonemizer));
this.RaisePropertyChanged(nameof(PhonemizerTag));
});
+ AllSetPhonemizerCommand = ReactiveCommand.Create(factory => {
+ if (factory == null) {
+ return;
+ }
+ var name = factory.type.FullName!;
+ Log.Information($"Loading Phonemizer: {factory}");
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count <= 1) {
+ targetTracks = DocManager.Inst.Project.tracks;
+ }
+ DocManager.Inst.StartUndoGroup("command.track.setting");
+ foreach (var targetTrack in targetTracks) {
+ var phonemizer = factory.Create();
+ if (phonemizer != null) {
+ DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, targetTrack, phonemizer));
+ }
+ var targetSingerId = targetTrack.Singer?.Id;
+ if (!string.IsNullOrEmpty(targetSingerId) && targetTrack.Singer?.Found == true && phonemizer != null) {
+ Preferences.Default.SingerPhonemizers[targetSingerId] = name;
+ }
+ }
+ Preferences.Default.RecentPhonemizers.Remove(name);
+ Preferences.Default.RecentPhonemizers.Insert(0, name);
+ DocManager.Inst.EndUndoGroup();
+ Preferences.Save();
+ MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part"));
+ MessageBus.Current.SendMessage(new TracksRefreshEvent());
+ this.RaisePropertyChanged(nameof(Phonemizer));
+ this.RaisePropertyChanged(nameof(PhonemizerTag));
+ });
SelectRendererCommand = ReactiveCommand.Create(name => {
var settings = new URenderSettings {
renderer = name,
@@ -130,14 +170,6 @@ public TrackHeaderViewModel(UTrack track) {
});
Activator = new ViewModelActivator();
- this.WhenActivated((CompositeDisposable disposables) => {
- Disposable.Create(() => {
- MessageBus.Current.Listen()
- .Subscribe(_ => {
- ManuallyRaise();
- }).DisposeWith(disposables);
- });
- });
TrackName = track.TrackName;
TrackAccentColor = ThemeManager.GetTrackColor(track.TrackColor).AccentColor;
@@ -172,8 +204,17 @@ public TrackHeaderViewModel(UTrack track) {
.Subscribe(solo => {
track.Solo = solo;
});
+ this.WhenAnyValue(x => x.IsSelected)
+ .Subscribe(_ => RefreshSelectionStyle());
RefreshAvatar();
+ RefreshSelectionStyle();
+ }
+
+ public void RefreshSelectionStyle() {
+ HeaderBorderBrush = IsSelected
+ ? TrackAccentColor
+ : ThemeManager.NeutralAccentBrushSemi;
}
public void ToggleSolo() {
@@ -234,12 +275,64 @@ public void JudgeMuted() {
this.RaisePropertyChanged(nameof(Muted));
}
- private bool TryChangePhonemizer(string phonemizerName) {
+ private static bool IsTrackSelected(UTrack projectTrack) {
+ var tracksViewModel = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)
+ ?.MainWindow?.DataContext as MainWindowViewModel;
+ return tracksViewModel?.TracksViewModel.SelectedTracks.Contains(projectTrack) == true;
+ }
+
+ private void ApplySingerToTrack(UTrack targetTrack, USinger? singer) {
+ if (singer is USinger selectedSinger) {
+ Log.Information($"Loading Singer: {selectedSinger.Name}");
+ DocManager.Inst.ExecuteCmd(new TrackChangeSingerCommand(DocManager.Inst.Project, targetTrack, selectedSinger));
+ if (!string.IsNullOrEmpty(selectedSinger.Id) &&
+ Preferences.Default.SingerPhonemizers.TryGetValue(selectedSinger.Id, out var phonemizerName) &&
+ TryChangePhonemizer(targetTrack, phonemizerName)) {
+ } else if (!string.IsNullOrEmpty(selectedSinger.DefaultPhonemizer)) {
+ TryChangePhonemizer(targetTrack, selectedSinger.DefaultPhonemizer);
+ }
+ if (!selectedSinger.Found || selectedSinger.SingerType != targetTrack.RendererSettings.Renderer?.SingerType) {
+ var settings = new URenderSettings();
+ if (selectedSinger.Found) {
+ settings = new URenderSettings {
+ renderer = Core.Render.Renderers.GetDefaultRenderer(selectedSinger.SingerType),
+ };
+ }
+ DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, targetTrack, settings));
+ }
+ } else {
+ DocManager.Inst.ExecuteCmd(new TrackChangeSingerCommand(DocManager.Inst.Project, targetTrack, USinger.CreateMissing(string.Empty)));
+ var settings = new URenderSettings();
+ DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, targetTrack, settings));
+ }
+ }
+
+ private void UpdateRecentSingers(USinger? singer) {
+ if (!string.IsNullOrEmpty(singer?.Id) && singer.Found) {
+ Preferences.Default.RecentSingers.Remove(singer.Id);
+ Preferences.Default.RecentSingers.Insert(0, singer.Id);
+ if (Preferences.Default.RecentSingers.Count > 16) {
+ Preferences.Default.RecentSingers.RemoveRange(
+ 16, Preferences.Default.RecentSingers.Count - 16);
+ }
+ }
+ }
+
+ private SingerMenuItemViewModel CreateSingerMenuItem(USinger singer) {
+ return new SingerMenuItemViewModel() {
+ Header = singer.LocalizedName,
+ Command = SelectSingerCommand,
+ SecondaryCommand = AllSetSingerCommand,
+ CommandParameter = singer,
+ };
+ }
+
+ private bool TryChangePhonemizer(UTrack targetTrack, string phonemizerName) {
try {
var factory = PhonemizerFactory.Get(phonemizerName);
var phonemizer = factory?.Create();
if (phonemizer != null) {
- DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, track, phonemizer));
+ DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, targetTrack, phonemizer));
return true;
}
} catch (Exception e) {
@@ -254,33 +347,21 @@ public void RefreshSingers() {
items.AddRange(Preferences.Default.RecentSingers
.Select(id => SingerManager.Inst.Singers.Values.FirstOrDefault(singer => singer.Id == id))
.OfType()
- .Select(singer => new SingerMenuItemViewModel() {
- Header = singer.LocalizedName,
- Command = SelectSingerCommand,
- CommandParameter = singer,
- }));
- items.Add(new SingerMenuItemViewModel() {
+ .Select(CreateSingerMenuItem));
+ items.Add(new MenuItemViewModel() {
Header = ThemeManager.GetString("tracks.favorite") + " ...",
Items = Preferences.Default.FavoriteSingers
.Select(id => SingerManager.Inst.Singers.Values.FirstOrDefault(singer => singer.Id == id))
.OfType()
.LocalizedOrderBy(singer => singer.LocalizedName)
- .Select(singer => new SingerMenuItemViewModel() {
- Header = singer.LocalizedName,
- Command = SelectSingerCommand,
- CommandParameter = singer,
- }).ToArray(),
+ .Select(CreateSingerMenuItem).ToArray(),
});
var keys = SingerManager.Inst.SingerGroups.Keys.OrderBy(k => k);
foreach (var key in keys) {
- items.Add(new SingerMenuItemViewModel() {
+ items.Add(new MenuItemViewModel() {
Header = $"{key} ...",
Items = SingerManager.Inst.SingerGroups[key]
- .Select(singer => new SingerMenuItemViewModel() {
- Header = singer.LocalizedName,
- Command = SelectSingerCommand,
- CommandParameter = singer,
- }).ToArray(),
+ .Select(CreateSingerMenuItem).ToArray(),
});
}
} else {
@@ -388,7 +469,7 @@ public void RefreshPhonemizers() {
if (track != null && track.Singer != null && track.Singer.Found) {
var factory = FindPhonemizerByName(track.Singer.DefaultPhonemizer);
if (factory != null) {
- items.Add(new MenuItemViewModel() {
+ items.Add(new PhonemizerMenuItemViewModel() {
Header = ThemeManager.GetString("tracks.singerdefault") + factory.ToString(),
Command = SelectPhonemizerCommand,
CommandParameter = factory,
@@ -400,9 +481,10 @@ public void RefreshPhonemizers() {
.Select(name => FindPhonemizerByName(name))
.OfType()
.OrderBy(factory => factory.tag)
- .Select(factory => new MenuItemViewModel() {
+ .Select(factory => new PhonemizerMenuItemViewModel() {
Header = factory.ToString(),
Command = SelectPhonemizerCommand,
+ SecondaryCommand = AllSetPhonemizerCommand,
CommandParameter = factory,
}));
//more phonemizers grouped by singing language
@@ -412,9 +494,10 @@ public void RefreshPhonemizers() {
.OrderBy(group => group.Key)
.Select(group => new MenuItemViewModel() {
Header = GetPhonemizerGroupHeader(group.Key),
- Items = group.Select(factory => new MenuItemViewModel() {
+ Items = group.Select(factory => new PhonemizerMenuItemViewModel() {
Header = factory.ToString(),
Command = SelectPhonemizerCommand,
+ SecondaryCommand = AllSetPhonemizerCommand,
CommandParameter = factory,
}).ToArray(),
}).ToArray()
@@ -453,8 +536,17 @@ public void RefreshAvatar() {
}
public void ManuallyRaise() {
+ TrackName = track.TrackName;
+ TrackAccentColor = ThemeManager.GetTrackColor(track.TrackColor).AccentColor;
+ TrackColor = Preferences.Default.UseTrackColor
+ ? ThemeManager.GetTrackColor(track.TrackColor)
+ : ThemeManager.GetTrackColor("Blue");
+ RefreshSelectionStyle();
this.RaisePropertyChanged(nameof(Singer));
this.RaisePropertyChanged(nameof(TrackNo));
+ this.RaisePropertyChanged(nameof(TrackName));
+ this.RaisePropertyChanged(nameof(TrackAccentColor));
+ this.RaisePropertyChanged(nameof(TrackColor));
this.RaisePropertyChanged(nameof(Phonemizer));
this.RaisePropertyChanged(nameof(PhonemizerTag));
this.RaisePropertyChanged(nameof(Renderer));
@@ -468,7 +560,16 @@ public void ManuallyRaise() {
public void Remove() {
DocManager.Inst.StartUndoGroup("command.track.delete");
- DocManager.Inst.ExecuteCmd(new RemoveTrackCommand(DocManager.Inst.Project, track));
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ targetTracks = new List() { this.track };
+ }
+ foreach (var track in targetTracks) {
+ DocManager.Inst.ExecuteCmd(new RemoveTrackCommand(DocManager.Inst.Project, track));
+ }
DocManager.Inst.EndUndoGroup();
}
@@ -477,7 +578,21 @@ public void MoveUp() {
return;
}
DocManager.Inst.StartUndoGroup("command.track.order");
- DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, true));
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ targetTracks = new List() { this.track };
+ } else {
+ targetTracks = targetTracks
+ .Where(targetTrack => targetTrack != DocManager.Inst.Project.tracks.First())
+ .OrderBy(targetTrack => targetTrack.TrackNo)
+ .ToList();
+ }
+ foreach (var track in targetTracks) {
+ DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, true));
+ }
DocManager.Inst.EndUndoGroup();
}
@@ -486,7 +601,21 @@ public void MoveDown() {
return;
}
DocManager.Inst.StartUndoGroup("command.track.order");
- DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, false));
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ targetTracks = new List() { this.track };
+ } else {
+ targetTracks = targetTracks
+ .Where(targetTrack => targetTrack != DocManager.Inst.Project.tracks.Last())
+ .OrderByDescending(targetTrack => targetTrack.TrackNo)
+ .ToList();
+ }
+ foreach (var track in targetTracks) {
+ DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, false));
+ }
DocManager.Inst.EndUndoGroup();
}
@@ -497,8 +626,19 @@ public void Rename() {
dialog.onFinish = name => {
if (!string.IsNullOrWhiteSpace(name) && name != track.TrackName) {
DocManager.Inst.StartUndoGroup("command.track.setting");
- this.TrackName = name;
- DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, track, name));
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ this.TrackName = name;
+ DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, track, name));
+ } else {
+ for (int i = 0; i < targetTracks.Count; i++) {
+ string name_ = $"{name}_{i:000}";
+ DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, targetTracks[i], name_));
+ }
+ }
DocManager.Inst.EndUndoGroup();
}
};
@@ -508,65 +648,188 @@ public void Rename() {
}
public async void SelectTrackColor() {
- var dialog = new TrackColorDialog();
- dialog.DataContext = new TrackColorViewModel(track);
-
- if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null) {
- await dialog.ShowDialog(desktop.MainWindow);
- TrackAccentColor = ThemeManager.GetTrackColor(track.TrackColor).AccentColor;
- TrackColor = Preferences.Default.UseTrackColor
- ? ThemeManager.GetTrackColor(track.TrackColor)
- : ThemeManager.GetTrackColor("Blue");
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ var dialogs = new List();
+ if (targetTracks.Count < 2) {
+ var dialog = new TrackColorDialog();
+ dialog.DataContext = new TrackColorViewModel(track);
+ dialogs.Add(dialog);
+ } else {
+ foreach (var track in targetTracks) {
+ var dialog = new TrackColorDialog();
+ dialog.DataContext = new TrackColorViewModel(track);
+ dialogs.Add(dialog);
+ }
+ }
+ foreach (var dialog in dialogs) {
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null) {
+ await dialog.ShowDialog(desktop.MainWindow);
+ TrackAccentColor = ThemeManager.GetTrackColor(track.TrackColor).AccentColor;
+ TrackColor = Preferences.Default.UseTrackColor
+ ? ThemeManager.GetTrackColor(track.TrackColor)
+ : ThemeManager.GetTrackColor("Blue");
+ RefreshSelectionStyle();
+ }
}
}
public void Duplicate() {
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ targetTracks = new List() { this.track };
+ } else {
+ targetTracks = targetTracks
+ .OrderByDescending(targetTrack => targetTrack.TrackNo)
+ .ToList();
+ }
DocManager.Inst.StartUndoGroup("command.track.duplicate");
- var newTrack = new UTrack(track.TrackName + "_copy") {
- TrackNo = track.TrackNo + 1,
- Singer = track.Singer,
- Phonemizer = track.Phonemizer,
- RendererSettings = track.RendererSettings,
- Mute = track.Mute,
- Muted = track.Muted,
- Solo = false,
- Volume = track.Volume,
- Pan = track.Pan,
- TrackColor = track.TrackColor,
- TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList()
- };
- DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, newTrack));
- var parts = DocManager.Inst.Project.parts
- .Where(part => part.trackNo == track.TrackNo)
- .Select(part => part.Clone()).ToList();
- foreach (var part in parts) {
- part.trackNo = newTrack.TrackNo;
- DocManager.Inst.ExecuteCmd(new AddPartCommand(DocManager.Inst.Project, part));
+ foreach (var track in targetTracks) {
+ var sourceTrackNo = track.TrackNo;
+ var newTrack = new UTrack(track.TrackName + "_copy") {
+ TrackNo = sourceTrackNo + 1,
+ Singer = track.Singer,
+ Phonemizer = track.Phonemizer,
+ RendererSettings = track.RendererSettings,
+ Mute = track.Mute,
+ Muted = track.Muted,
+ Solo = false,
+ Volume = track.Volume,
+ Pan = track.Pan,
+ TrackColor = track.TrackColor,
+ TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList()
+ };
+ DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, newTrack));
+ var parts = DocManager.Inst.Project.parts
+ .Where(part => part.trackNo == sourceTrackNo)
+ .Select(part => part.Clone()).ToList();
+ foreach (var part in parts) {
+ part.trackNo = newTrack.TrackNo;
+ DocManager.Inst.ExecuteCmd(new AddPartCommand(DocManager.Inst.Project, part));
+ }
}
DocManager.Inst.EndUndoGroup();
}
public void DuplicateSettings() {
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ targetTracks = new List() { this.track };
+ } else {
+ targetTracks = targetTracks
+ .OrderByDescending(targetTrack => targetTrack.TrackNo)
+ .ToList();
+ }
DocManager.Inst.StartUndoGroup("command.track.duplicate");
- DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, new UTrack(track.TrackName + "_copy") {
- TrackNo = track.TrackNo + 1,
- Singer = track.Singer,
- Phonemizer = track.Phonemizer,
- RendererSettings = track.RendererSettings,
- Mute = track.Mute,
- Muted = track.Muted,
- Solo = false,
- Volume = track.Volume,
- Pan = track.Pan,
- TrackColor = track.TrackColor,
- TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList()
- }));
+ foreach (var track in targetTracks) {
+ DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, new UTrack(track.TrackName + "_copy") {
+ TrackNo = track.TrackNo + 1,
+ Singer = track.Singer,
+ Phonemizer = track.Phonemizer,
+ RendererSettings = track.RendererSettings,
+ Mute = track.Mute,
+ Muted = track.Muted,
+ Solo = false,
+ Volume = track.Volume,
+ Pan = track.Pan,
+ TrackColor = track.TrackColor,
+ TrackExpressions = track.TrackExpressions.Select(exp => exp.Clone()).ToList()
+ }));
+ }
+ DocManager.Inst.EndUndoGroup();
+ }
+
+ public void StandardizeSettings() {
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count <= 1) {
+ targetTracks = DocManager.Inst.Project.tracks;
+ }
+ var phonemizerFactory = PhonemizerFactory.Get(track.Phonemizer.GetType());
+ DocManager.Inst.StartUndoGroup("command.track.setting");
+ foreach (var targetTrack in targetTracks) {
+ if (targetTrack == track) {
+ continue;
+ }
+ if (track.Singer != targetTrack.Singer) {
+ ApplySingerToTrack(targetTrack, track.Singer);
+ }
+ if (targetTrack.Phonemizer.GetType() != track.Phonemizer.GetType()) {
+ var phonemizer = phonemizerFactory?.Create();
+ if (phonemizer != null) {
+ DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, targetTrack, phonemizer));
+ }
+ }
+ DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(
+ DocManager.Inst.Project,
+ targetTrack,
+ track.RendererSettings.Clone()));
+ DocManager.Inst.ExecuteCmd(new TrackChangeSettingsCommand(
+ DocManager.Inst.Project,
+ targetTrack,
+ track.Mute,
+ track.Volume,
+ track.Pan));
+ DocManager.Inst.ExecuteCmd(new ChangeTrackColorCommand(
+ DocManager.Inst.Project,
+ targetTrack,
+ track.TrackColor));
+ DocManager.Inst.ExecuteCmd(new ConfigureExpressionsCommand(
+ DocManager.Inst.Project,
+ DocManager.Inst.Project.expressions.Values.ToArray(),
+ targetTrack,
+ track.TrackExpressions.Select(exp => exp.Clone()).ToArray()));
+ }
+ DocManager.Inst.EndUndoGroup();
+ MessageBus.Current.SendMessage(new TracksRefreshEvent());
+ MessageBus.Current.SendMessage(new PianorollRefreshEvent("TrackColor"));
+ }
+
+ public void RotateSelectedTrackSingers() {
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .OrderBy(targetTrack => targetTrack.TrackNo)
+ .ToList();
+ if (targetTracks.Count <= 1) {
+ targetTracks = DocManager.Inst.Project.tracks;
+ }
+ var singers = targetTracks
+ .Select(targetTrack => targetTrack.Singer)
+ .ToList();
+ DocManager.Inst.StartUndoGroup("command.track.singer");
+ for (int i = 0; i < targetTracks.Count; i++) {
+ var singer = singers[(i + 1) % singers.Count];
+ ApplySingerToTrack(targetTracks[i], singer);
+ }
DocManager.Inst.EndUndoGroup();
+ DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(-1, true));
+ MessageBus.Current.SendMessage(new TracksRefreshEvent());
+ MessageBus.Current.SendMessage(new PianorollRefreshEvent("Part"));
}
public void VoiceColorRemapping() {
- if (track.Singer != null && track.Singer.Found && track.VoiceColorExp != null) {
- DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(track.TrackNo, false));
+ var targetTracks = DocManager.Inst.Project.tracks
+ .Where(projectTrack => projectTrack != null)
+ .Where(projectTrack => IsTrackSelected(projectTrack))
+ .ToList();
+ if (targetTracks.Count < 2) {
+ targetTracks = new List() { this.track };
+ }
+ foreach (var track in targetTracks) {
+ if (track.Singer != null && track.Singer.Found && track.VoiceColorExp != null) {
+ DocManager.Inst.ExecuteCmd(new VoiceColorRemappingNotification(track.TrackNo, false));
+ }
}
}
}
diff --git a/OpenUtau/ViewModels/TracksViewModel.cs b/OpenUtau/ViewModels/TracksViewModel.cs
index 7ecc60458..bfd26869b 100644
--- a/OpenUtau/ViewModels/TracksViewModel.cs
+++ b/OpenUtau/ViewModels/TracksViewModel.cs
@@ -31,6 +31,12 @@ public TracksMuteEvent(int trackNo, bool allmute) {
this.allmute = allmute;
}
}
+ public class TrackSelectionEvent {
+ public readonly UTrack[] selectedTracks;
+ public TrackSelectionEvent(UTrack[] selectedTracks) {
+ this.selectedTracks = selectedTracks;
+ }
+ }
public class PartsSelectionEvent {
public readonly UPart[] selectedParts;
public readonly UPart[] tempSelectedParts;
@@ -94,6 +100,7 @@ public class TracksViewModel : ViewModelBase, ICmdSubscriber {
private readonly ObservableAsPropertyHelper smallChangeY;
public readonly List SelectedParts = new List();
+ public readonly List SelectedTracks = new List();
private readonly HashSet TempSelectedParts = new HashSet();
public TracksViewModel() {
@@ -265,6 +272,44 @@ public void DeselectParts() {
SelectedParts.ToArray(), TempSelectedParts.ToArray()));
}
+ public void DeselectTracks() {
+ SelectedTracks.Clear();
+ MessageBus.Current.SendMessage(new TrackSelectionEvent(SelectedTracks.ToArray()));
+ }
+
+ public void SelectTrack(UTrack track) {
+ if (SelectedTracks.Count == 1 && SelectedTracks[0] == track) {
+ return;
+ }
+ SelectedTracks.Clear();
+ SelectedTracks.Add(track);
+ MessageBus.Current.SendMessage(new TrackSelectionEvent(SelectedTracks.ToArray()));
+ }
+
+ public void ToggleSelectTrack(UTrack track) {
+ if (SelectedTracks.Contains(track)) {
+ SelectedTracks.Remove(track);
+ } else {
+ SelectedTracks.Add(track);
+ }
+ MessageBus.Current.SendMessage(new TrackSelectionEvent(SelectedTracks.ToArray()));
+ }
+
+ public void SelectTracksUntil(UTrack track) {
+ if (SelectedTracks.Count == 0) {
+ SelectTrack(track);
+ return;
+ }
+ int start = SelectedTracks.Min(selected => selected.TrackNo);
+ int end = track.TrackNo;
+ if (start > end) {
+ (start, end) = (end, start);
+ }
+ SelectedTracks.Clear();
+ SelectedTracks.AddRange(Project.tracks.Where(t => start <= t.TrackNo && t.TrackNo <= end));
+ MessageBus.Current.SendMessage(new TrackSelectionEvent(SelectedTracks.ToArray()));
+ }
+
public void SelectPart(UPart part) {
TempSelectedParts.Clear();
SelectedParts.Add(part);
@@ -424,19 +469,23 @@ public void OnNext(UCommand cmd, bool isUndo) {
} else if (cmd is RemoveTrackCommand removeTrack) {
if (!isUndo) {
Tracks.Remove(removeTrack.track);
+ SelectedTracks.Remove(removeTrack.track);
} else {
Tracks.Add(removeTrack.track);
}
}
Notify();
MessageBus.Current.SendMessage(new TracksRefreshEvent());
+ MessageBus.Current.SendMessage(new TrackSelectionEvent(SelectedTracks.ToArray()));
} else if (cmd is UNotification) {
if (cmd is LoadProjectNotification loadProjectNotif) {
Parts.Clear();
Parts.AddRange(loadProjectNotif.project.parts);
Tracks.Clear();
Tracks.AddRange(loadProjectNotif.project.tracks);
+ SelectedTracks.Clear();
MessageBus.Current.SendMessage(new TracksRefreshEvent());
+ MessageBus.Current.SendMessage(new TrackSelectionEvent(SelectedTracks.ToArray()));
} else if (cmd is SetPlayPosTickNotification setPlayPosTick) {
SetPlayPos(setPlayPosTick.playPosTick, setPlayPosTick.waitingRendering);
if (!setPlayPosTick.pause || Preferences.Default.LockStartTime == 1) {
@@ -447,6 +496,9 @@ public void OnNext(UCommand cmd, bool isUndo) {
DeselectParts();
SelectPart(loadPartNotif.part);
}
+ if (0 <= loadPartNotif.part.trackNo && loadPartNotif.part.trackNo < Project.tracks.Count) {
+ SelectTrack(Project.tracks[loadPartNotif.part.trackNo]);
+ }
}
Notify();
}