From 95be4313bc69d6304b47977d6f23e671b0e764bc Mon Sep 17 00:00:00 2001 From: atouu <67765922+atouu@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:12:48 +0800 Subject: [PATCH 1/2] Allow drag-and-drop to import project files as either a project or tracks. --- OpenUtau/Strings/Strings.axaml | 4 ++ OpenUtau/Views/MainWindow.axaml.cs | 90 +++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index d0d52472b..4e78128c6 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -65,6 +65,10 @@ OpenUtau aims to be an open source editing environment for UTAU community, with Voice color remapping This singer has no voice color Applies to all notes in this track: + Project Import + Files to import: + Import as Project + Import as Tracks Error Error Details diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 57c883109..510349941 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -885,29 +885,77 @@ async void OnDrop(object? sender, DragEventArgs args) { //If multiple project/audio files are dropped, open/import them all. if (ProjectExts.Contains(FirstExt) || AudioExts.Contains(FirstExt)) { var projectFiles = supportedFiles.Where(file => ProjectExts.Contains(Path.GetExtension(file).ToLower())).ToArray(); - viewModel.Page = 1; if (projectFiles.Length > 0) { - try { - var loadedProjects = Formats.ReadProjects(files); - // Imports tempo for new projects, otherwise asks the user. - bool importTempo = DocManager.Inst.Project.parts.Count == 0; - if (!importTempo && loadedProjects[0].tempos.Count > 0) { - var tempoString = string.Join("\n", - loadedProjects[0].tempos - .Select(tempo => $"position: {tempo.position}, tempo: {tempo.bpm}") - ); - // Ask the user - var result = await MessageBox.Show( - this, - ThemeManager.GetString("dialogs.importtracks.importtempo") + "\n" + tempoString, - ThemeManager.GetString("dialogs.importtracks.caption"), - MessageBox.MessageBoxButtons.YesNo); - importTempo = result == MessageBox.MessageBoxResult.Yes; + bool openAsProject = true; + + if (viewModel.Page == 1) { + var openAs = new MessageBox() { + Title = ThemeManager.GetString("dialogs.projectimport.caption"), + Text = { + Text = $"{ThemeManager.GetString("dialogs.projectimport.message")}\n{string.Join("\n", projectFiles)}" + } + }; + + var btnAsProject = new Button() { + Content = ThemeManager.GetString("dialogs.projectimport.asproject") + }; + btnAsProject.Click += async (s, e) => { + await AskIfSaveAndContinue(); + openAsProject = true; + openAs.Close(); + }; + openAs.Buttons.Children.Add(btnAsProject); + + var btnAsTracks = new Button() { + Content = ThemeManager.GetString("dialogs.projectimport.astracks") + }; + btnAsTracks.Click += (s, e) => { + openAsProject = false; + openAs.Close(); + }; + openAs.Buttons.Children.Add(btnAsTracks); + + var tcs = new TaskCompletionSource(); + openAs.Closed += delegate { tcs.SetResult(); }; + openAs.Show(); + + await tcs.Task; + } + + viewModel.Page = 1; + + if (openAsProject) { + try { + viewModel.OpenProject(projectFiles); + } catch (Exception e) { + Log.Error(e, $"Failed to open files {string.Join("\n", projectFiles)}"); + _ = await MessageBox.ShowError(this, + new MessageCustomizableException($"Failed to open files {string.Join("\n", projectFiles)}", + $":\n{string.Join("\n", projectFiles)}", e)); + } + } else { + try { + var loadedProjects = Formats.ReadProjects(projectFiles); + // Imports tempo for new projects, otherwise asks the user. + bool importTempo = DocManager.Inst.Project.parts.Count == 0; + if (!importTempo && loadedProjects[0].tempos.Count > 0) { + var tempoString = string.Join("\n", + loadedProjects[0].tempos + .Select(tempo => $"position: {tempo.position}, tempo: {tempo.bpm}") + ); + // Ask the user + var result = await MessageBox.Show( + this, + ThemeManager.GetString("dialogs.importtracks.importtempo") + "\n" + tempoString, + ThemeManager.GetString("dialogs.importtracks.caption"), + MessageBox.MessageBoxButtons.YesNo); + importTempo = result == MessageBox.MessageBoxResult.Yes; + } + viewModel.ImportTracks(loadedProjects, importTempo); + } catch (Exception e) { + Log.Error(e, "Failed to import project"); + _ = await MessageBox.ShowError(this, new MessageCustomizableException("Failed to import files", "", e)); } - viewModel.ImportTracks(loadedProjects, importTempo); - } catch (Exception e) { - Log.Error(e, "Failed to import project"); - _ = await MessageBox.ShowError(this, new MessageCustomizableException("Failed to import files", "", e)); } } var audioFiles = supportedFiles.Where(file => AudioExts.Contains(Path.GetExtension(file).ToLower())).ToArray(); From 52de741a8fd957848d98d4682ee7b83ae812f8b8 Mon Sep 17 00:00:00 2001 From: atouu <67765922+atouu@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:08:31 +0800 Subject: [PATCH 2/2] return if import was cancelled --- OpenUtau/Views/MainWindow.axaml.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 510349941..d38910800 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -889,6 +889,7 @@ async void OnDrop(object? sender, DragEventArgs args) { bool openAsProject = true; if (viewModel.Page == 1) { + bool cancelled = true; var openAs = new MessageBox() { Title = ThemeManager.GetString("dialogs.projectimport.caption"), Text = { @@ -901,6 +902,7 @@ async void OnDrop(object? sender, DragEventArgs args) { }; btnAsProject.Click += async (s, e) => { await AskIfSaveAndContinue(); + cancelled = false; openAsProject = true; openAs.Close(); }; @@ -910,20 +912,24 @@ async void OnDrop(object? sender, DragEventArgs args) { Content = ThemeManager.GetString("dialogs.projectimport.astracks") }; btnAsTracks.Click += (s, e) => { + cancelled = false; openAsProject = false; openAs.Close(); }; openAs.Buttons.Children.Add(btnAsTracks); var tcs = new TaskCompletionSource(); - openAs.Closed += delegate { tcs.SetResult(); }; + openAs.Closed += delegate { + if (cancelled) { + return; + } + tcs.SetResult(); + }; openAs.Show(); await tcs.Task; } - viewModel.Page = 1; - if (openAsProject) { try { viewModel.OpenProject(projectFiles); @@ -967,6 +973,7 @@ async void OnDrop(object? sender, DragEventArgs args) { _ = await MessageBox.ShowError(this, new MessageCustomizableException("Failed to import audio", "", e)); } } + viewModel.Page = 1; return; } // Otherwise, only one installer file is handled at a time.