Skip to content

Commit 3aa8677

Browse files
authored
Merge pull request #840 from oxygen-dioxide/note-find
Refactor search dialog to a searchbar like in browsers and text editors
2 parents cfd78ec + 402abb2 commit 3aa8677

File tree

6 files changed

+200
-7
lines changed

6 files changed

+200
-7
lines changed

OpenUtau/Controls/SearchBar.axaml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:vm="using:OpenUtau.App.ViewModels"
6+
x:Class="OpenUtau.App.Controls.SearchBar" Focusable="False">
7+
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="114,54,28,28,28,28" Width="280" Background="{DynamicResource SystemControlBackgroundAltHighBrush}">
8+
<!--Row 1-->
9+
<TextBox Name="box" Grid.Row="0" Padding="4,3" Margin="0" Height="24" VerticalAlignment="Center" HorizontalAlignment="Stretch"
10+
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
11+
Text="{Binding SearchWord}" Focusable="True" Watermark="{DynamicResource pianoroll.menu.searchnote}"
12+
GotFocus="Box_GotFocus" KeyDown="Box_KeyDown"/>
13+
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ResultCount}" Width="54" VerticalAlignment="Center" TextAlignment="Center"/>
14+
<Button Grid.Row="0" Grid.Column="2" Command="{Binding SelectCommand}" CommandParameter="prev"
15+
ToolTip.Tip="{DynamicResource pianoroll.menu.searchnote.prev}"
16+
Width="24" Height="24" Margin="2,2,2,2"
17+
Background="Transparent" BorderThickness="0">
18+
<!-- icon source: https://github.com/microsoft/vscode-icons/blob/main/icons/light/arrow-left.svg -->
19+
<Path Classes="clear" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center"
20+
Data="M7.00024 3.0929L2.00024 8.09288L2.00024 8.79999L7.00024 13.8L7.70734 13.0929L3.56091 8.94644L14.0002 8.94644L14.0002 7.94644L3.56091 7.94644L7.70734 3.8L7.00024 3.0929Z"
21+
Fill="{StaticResource NeutralAccentBrush}">
22+
<Path.RenderTransform>
23+
<TransformGroup>
24+
<ScaleTransform ScaleX=".75" ScaleY=".75"/>
25+
<TranslateTransform X="0" Y="1"/>
26+
</TransformGroup>
27+
</Path.RenderTransform>
28+
</Path>
29+
</Button>
30+
<Button Grid.Row="0" Grid.Column="3" Command="{Binding SelectCommand}" CommandParameter="next"
31+
ToolTip.Tip="{DynamicResource pianoroll.menu.searchnote.next}"
32+
Width="24" Height="24" Margin="2,2,2,2"
33+
Background="Transparent" BorderThickness="0">
34+
<!-- icon source: https://github.com/microsoft/vscode-icons/blob/main/icons/light/arrow-right.svg -->
35+
<Path Classes="clear" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center"
36+
Data="M9.00025 13.8871L14.0002 8.8871L14.0002 8.17999L9.00025 3.17999L8.29314 3.8871L12.4396 8.03354L2.00024 8.03354L2.00024 9.03354L12.4396 9.03354L8.29314 13.18L9.00025 13.8871Z"
37+
Fill="{StaticResource NeutralAccentBrush}">
38+
<Path.RenderTransform>
39+
<TransformGroup>
40+
<ScaleTransform ScaleX=".75" ScaleY=".75"/>
41+
<TranslateTransform X="0" Y="1"/>
42+
</TransformGroup>
43+
</Path.RenderTransform>
44+
</Path>
45+
</Button>
46+
<Button Grid.Row="0" Grid.Column="4" Command="{Binding SelectCommand}" CommandParameter="all"
47+
ToolTip.Tip="{DynamicResource pianoroll.menu.searchnote.all}"
48+
Width="24" Height="24" Margin="2,2,2,2"
49+
Background="Transparent" BorderThickness="0">
50+
<!-- icon source: https://github.com/microsoft/vscode-icons/blob/main/icons/light/new-folder.svg -->
51+
<Path Classes="clear" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center"
52+
Data="M7.00024 3H4.00024V0H3.00024V3H0.000244141V4H3.00024V7H4.00024V4H7.00024V3ZM5.50024 7H5.00024V6H5.30024L6.10024 5.1L6.50024 5H14.0002V4H8.00024V3H14.5002L15.0002 3.5V13.5L14.5002 14H1.50024L1.00024 13.5V6.5V6V5H2.00024V6V6.5V13H14.0002V7V6H6.70024L5.90024 6.9L5.50024 7Z"
53+
Fill="{StaticResource NeutralAccentBrush}">
54+
<Path.RenderTransform>
55+
<TransformGroup>
56+
<ScaleTransform ScaleX=".75" ScaleY=".75"/>
57+
<TranslateTransform X="0" Y="1"/>
58+
</TransformGroup>
59+
</Path.RenderTransform>
60+
</Path>
61+
</Button>
62+
<Button Grid.Row="0" Grid.Column="5" Click="OnClose"
63+
ToolTip.Tip="{DynamicResource pianoroll.menu.searchnote.close}"
64+
Width="24" Height="24" Margin="2,2,2,2"
65+
Background="Transparent" BorderThickness="0">
66+
<!-- icon source: https://github.com/microsoft/vscode-icons/blob/main/icons/light/close.svg -->
67+
<Path Classes="clear" Width="16" Height="16" HorizontalAlignment="Center" VerticalAlignment="Center"
68+
Data="M8.00028 8.70711L11.6467 12.3536L12.3538 11.6465L8.70739 8.00001L12.3538 4.35356L11.6467 3.64645L8.00028 7.2929L4.35384 3.64645L3.64673 4.35356L7.29317 8.00001L3.64673 11.6465L4.35384 12.3536L8.00028 8.70711Z"
69+
Fill="{StaticResource NeutralAccentBrush}">
70+
<Path.RenderTransform>
71+
<TransformGroup>
72+
<ScaleTransform ScaleX=".75" ScaleY=".75"/>
73+
<TranslateTransform X="0" Y="1"/>
74+
</TransformGroup>
75+
</Path.RenderTransform>
76+
</Path>
77+
</Button>
78+
</Grid>
79+
</UserControl>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using Avalonia.Controls;
3+
using Avalonia.Input;
4+
using Avalonia.Interactivity;
5+
using Avalonia.Threading;
6+
using OpenUtau.App.ViewModels;
7+
using OpenUtau.Core.Ustx;
8+
9+
namespace OpenUtau.App.Controls {
10+
public partial class SearchBar : UserControl {
11+
SearchNoteViewModel? viewModel;
12+
private DispatcherTimer? focusTimer;
13+
14+
public SearchBar() {
15+
InitializeComponent();
16+
IsVisible = false;
17+
}
18+
19+
public void Show(NotesViewModel notesViewModel) {
20+
viewModel = new SearchNoteViewModel(notesViewModel);
21+
DataContext = viewModel;
22+
//If there is a note selected, use its lyric as the search word
23+
if (notesViewModel.Part != null && notesViewModel.Part.notes.Count > 0) {
24+
if (notesViewModel.Selection.Count > 0) {
25+
if (notesViewModel.Selection.FirstOrDefault() is UNote note) {
26+
if (!string.IsNullOrEmpty(note.lyric)) {
27+
(viewModel).SearchWord = note.lyric;
28+
}
29+
}
30+
}
31+
}
32+
IsVisible = true;
33+
box.SelectAll();
34+
focusTimer = new DispatcherTimer(
35+
TimeSpan.FromMilliseconds(15),
36+
DispatcherPriority.Normal,
37+
FocusTimer_Tick);
38+
focusTimer.Start();
39+
}
40+
41+
private void FocusTimer_Tick(object? sender, System.EventArgs e) {
42+
box.Focus();
43+
if (focusTimer != null) {
44+
focusTimer.Tick -= FocusTimer_Tick;
45+
focusTimer.Stop();
46+
focusTimer = null;
47+
}
48+
}
49+
50+
public void OnClose(object sender, RoutedEventArgs args) {
51+
IsVisible = false;
52+
}
53+
54+
private void Box_GotFocus(object? sender, GotFocusEventArgs e) {
55+
box.SelectAll();
56+
}
57+
58+
private void Box_KeyDown(object? sender, KeyEventArgs e){
59+
if(!IsVisible){
60+
return;
61+
}
62+
bool isShift = e.KeyModifiers == KeyModifiers.Shift;
63+
switch (e.Key){
64+
case Key.Enter:
65+
if (DataContext is SearchNoteViewModel viewModel){
66+
if(isShift){
67+
viewModel.Prev();
68+
}else{
69+
viewModel.Next();
70+
}
71+
}
72+
e.Handled = true;
73+
break;
74+
case Key.Escape:
75+
IsVisible = false;
76+
e.Handled = true;
77+
break;
78+
default:
79+
break;
80+
}
81+
}
82+
}
83+
}

OpenUtau/Strings/Strings.axaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@
238238
<system:String x:Key="pianoroll.menu.plugin.reload">Reload</system:String>
239239
<system:String x:Key="pianoroll.menu.searchnote">Search Note</system:String>
240240
<system:String x:Key="pianoroll.menu.searchnote.all">Select All</system:String>
241+
<system:String x:Key="pianoroll.menu.searchnote.close">Close</system:String>
241242
<system:String x:Key="pianoroll.menu.searchnote.next">Next</system:String>
242243
<system:String x:Key="pianoroll.menu.searchnote.prev">Prev</system:String>
243244
<system:String x:Key="pianoroll.toggle.finalpitch">View Final Pitch to Render (R)</system:String>

OpenUtau/ViewModels/SearchNoteViewModel.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ class SearchNoteViewModel : ViewModelBase {
1616
NotesViewModel notesViewModel { get; }
1717
List<UNote> notes = new List<UNote>();
1818
int selection = -1;
19+
[Reactive]public string ResultCount { get; private set; } = "";
1920
static string searchWord = "";
21+
bool CaseSensitive{ get; set; } = true;
22+
bool WholeWord{ get; set; } = false;
2023

2124
public SearchNoteViewModel(NotesViewModel notesViewModel) {
2225
this.notesViewModel = notesViewModel;
@@ -39,17 +42,28 @@ public SearchNoteViewModel(NotesViewModel notesViewModel) {
3942
break;
4043
}
4144
});
42-
}
45+
}
46+
47+
bool IsMatch(UNote note){
48+
string noteStr = CaseSensitive ? note.lyric : note.lyric.ToLower();
49+
string matchStr = CaseSensitive ? SearchWord : SearchWord.ToLower();
50+
if(WholeWord){
51+
return noteStr == matchStr;
52+
} else {
53+
return noteStr.Contains(matchStr);
54+
}
55+
}
4356

4457
void Search() {
4558
if (!string.IsNullOrEmpty(SearchWord) && notesViewModel.Part != null) {
46-
notes = notesViewModel.Part.notes.Where(note => note.lyric.Contains(SearchWord)).ToList();
59+
notes = notesViewModel.Part.notes.Where(IsMatch).ToList();
4760
Count = notes.Count();
4861
} else {
4962
notes.Clear();
5063
Count = 0;
5164
}
5265
selection = -1;
66+
UpdateResult();
5367
}
5468

5569
public void Prev() {
@@ -69,6 +83,7 @@ public void Prev() {
6983
DocManager.Inst.ExecuteCmd(new FocusNoteNotification(notesViewModel.Part, note));
7084
}
7185
}
86+
UpdateResult();
7287
}
7388

7489
public void Next() {
@@ -88,6 +103,7 @@ public void Next() {
88103
DocManager.Inst.ExecuteCmd(new FocusNoteNotification(notesViewModel.Part, note));
89104
}
90105
}
106+
UpdateResult();
91107
}
92108

93109
public void SelectAll() {
@@ -98,6 +114,14 @@ public void SelectAll() {
98114
MessageBus.Current.SendMessage(new NotesSelectionEvent(notesViewModel.Selection));
99115
}
100116

117+
public void UpdateResult(){
118+
if (selection >= 0) {
119+
ResultCount = $"{selection + 1}/{Count}";
120+
}else{
121+
ResultCount = $"{Count}";
122+
}
123+
}
124+
101125
public void OnClose() {
102126
searchWord = SearchWord;
103127
}

OpenUtau/Views/PianoRollWindow.axaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,12 @@
405405
</Border>
406406
</Border>
407407
</StackPanel>
408+
<StackPanel Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Bottom"
409+
Margin="12,12,12,70" Orientation="Horizontal">
410+
<Border CornerRadius="2" BoxShadow="0 0 5 1 #1F000000">
411+
<c:SearchBar Name="SearchBar"/>
412+
</Border>
413+
</StackPanel>
408414
<Rectangle Grid.Row="2" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
409415
Margin="0,0,0,60" Height="6" Opacity="0.15" IsVisible="{Binding NotesViewModel.ShowPhoneme}">
410416
<Rectangle.Fill>

OpenUtau/Views/PianoRollWindow.axaml.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,7 @@ void SearchNote() {
9696
if (ViewModel.NotesViewModel.Part == null || ViewModel.NotesViewModel.Part.notes.Count == 0) {
9797
return;
9898
}
99-
var vm = new SearchNoteViewModel(ViewModel.NotesViewModel);
100-
var dialog = new SearchNoteDialog() {
101-
DataContext = vm,
102-
};
103-
dialog.ShowDialog(this);
99+
SearchBar.Show(ViewModel.NotesViewModel);
104100
}
105101

106102
void ReplaceLyrics() {
@@ -843,6 +839,10 @@ void OnKeyDown(object sender, KeyEventArgs args) {
843839
args.Handled = false;
844840
return;
845841
}
842+
if (SearchBar != null && SearchBar.IsVisible && SearchBar.box.IsFocused) {
843+
args.Handled = false;
844+
return;
845+
}
846846
var notesVm = ViewModel.NotesViewModel;
847847
if (notesVm.Part == null) {
848848
args.Handled = false;

0 commit comments

Comments
 (0)