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.