diff --git a/DistFiles/releaseNotes.md b/DistFiles/releaseNotes.md index f396df431..3fbb30212 100644 --- a/DistFiles/releaseNotes.md +++ b/DistFiles/releaseNotes.md @@ -2,6 +2,10 @@ * SayMore is now a 64-bit application, so it will make better use of available memory and avoid out-of-memory errors. * SayMore now targets .Net Framework 4.8, which is the latest version supported on Windows 7 through Windows 11. It will not run on Windows versions earlier than 7. +## 3.7.5 (15 March 2026) +* Added session metadata searching for most data files except audio file custom fields and audio annotations. +* Added search bar feature to the UI for the sessions tab + ## 3.7.4 (30 July 2025) * Small improvement in the logic to guess at the correct initial writing system for translations when exporting FLEx Interlinear files (flextext). @@ -238,4 +242,4 @@ SayMore doesn't provide any help with Informed Consent/permission situations whe There is no way yet to customize the file naming conventions. -When using the "Add Files..." button, you can only choose one file at a time. \ No newline at end of file +When using the "Add Files..." button, you can only choose one file at a time. diff --git a/SampleData/EdoloSample/EdoloSample.sprj b/SampleData/EdoloSample/EdoloSample.sprj index 6d7bb66cb..5ab7cece7 100644 --- a/SampleData/EdoloSample/EdoloSample.sprj +++ b/SampleData/EdoloSample/EdoloSample.sprj @@ -1,23 +1,22 @@ - + - - - - - Edolo Sample - - The Etoro, or Edolo, are a tribe and ethnic group of the Southern Highlands Province of Papua New Guinea. Their territory comprises the southern slopes of Mt. Sisa, along the southern edge of the central mountain range of New Guinea, near the Papuan Plateau. - etr: Edolo - - Southern Highlands Province - Papua New Guinea - Oceania - - None - - - - - - + + + + + Edolo Sample + + The Etoro, or Edolo, are a tribe and ethnic group of the Southern Highlands Province of Papua New Guinea. Their territory comprises the southern slopes of Mt. Sisa, along the southern edge of the central mountain range of New Guinea, near the Papuan Plateau. + etr: Edolo + eng: English + + Southern Highlands Province + Papua New Guinea + Oceania + + None + + + + \ No newline at end of file diff --git a/SampleData/EdoloSample/People/Awi Heole/Awi Heole.person b/SampleData/EdoloSample/People/Awi Heole/Awi Heole.person index d01dd24ef..628df0ee2 100644 --- a/SampleData/EdoloSample/People/Awi Heole/Awi Heole.person +++ b/SampleData/EdoloSample/People/Awi Heole/Awi Heole.person @@ -1,4 +1,4 @@ - + etr:Edolo Huya @@ -10,7 +10,8 @@ Male Grade 2 Subsistence Farmer + false - Huya + Huya \ No newline at end of file diff --git a/SampleData/EdoloSample/Sessions/ETR009/ETR009.session b/SampleData/EdoloSample/Sessions/ETR009/ETR009.session index c83b66f92..b3f32bc30 100644 --- a/SampleData/EdoloSample/Sessions/ETR009/ETR009.session +++ b/SampleData/EdoloSample/Sessions/ETR009/ETR009.session @@ -1,7 +1,7 @@ - - + + The story behind how we catch fish with poison bark - Awi Heole; Ilawi Amosa + Awi Heole; Hatton; Ilawi Amosa narrative Open Huya @@ -10,4 +10,33 @@ 2010-06-06 hello Incoming + + Magma + + + + Awi Heole + participant + 0001-01-01 + + + + Ilawi Amosa + participant + 0001-01-01 + + + + Hatton + recorder + 2010-09-10 + + + + Awi Heole + speaker + 2010-09-10 + + + \ No newline at end of file diff --git a/SampleData/EdoloSample/Sessions/ETR009/ETR009_Careful.mp3.meta b/SampleData/EdoloSample/Sessions/ETR009/ETR009_Careful.mp3.meta index 552887242..8a08f134d 100644 --- a/SampleData/EdoloSample/Sessions/ETR009/ETR009_Careful.mp3.meta +++ b/SampleData/EdoloSample/Sessions/ETR009/ETR009_Careful.mp3.meta @@ -1,24 +1,27 @@ - + Shure X2u A/D, into GoldWave Sure SM10A Headset mono This was downgraded to low bit-rate mp3 to keep the SayMore installer from getting huge. - - Hatton - recorder - 2010-09-10 - - - - Awi Heole - speaker - 2010-09-10 - - + + Hatton + recorder + 2010-09-10 + + + + Awi Heole + speaker + 2010-09-10 + + 00:00:25 44100 Hz 96 kbps + + Brtick + \ No newline at end of file diff --git a/SampleData/EdoloSample/Sessions/ETR009/ETR009_Tiny.mp4.meta b/SampleData/EdoloSample/Sessions/ETR009/ETR009_Tiny.mp4.meta index eea6611ea..136a19d4d 100644 --- a/SampleData/EdoloSample/Sessions/ETR009/ETR009_Tiny.mp4.meta +++ b/SampleData/EdoloSample/Sessions/ETR009/ETR009_Tiny.mp4.meta @@ -1,22 +1,24 @@ - + Zoom Q3 Built-in - 30 This was downgraded to small picture and low bit-rate mp3 to keep the SayMore installer from getting huge. - - Hatton - recorder - 2010-09-10 - - + + Hatton + recorder + 2010-09-10 + + - 00:00:10 - stereo - 48000 Hz - 96 kbps 218 kbps 176 x 144 30 frames/second + + 30 + 00:00:10 + stereo + 48000 Hz + 96 kbps + \ No newline at end of file diff --git a/SampleData/EdoloSample/Sessions/ETR009/SceneAroundCamera.JPG.meta b/SampleData/EdoloSample/Sessions/ETR009/SceneAroundCamera.JPG.meta index ecdb32189..31524a15b 100644 --- a/SampleData/EdoloSample/Sessions/ETR009/SceneAroundCamera.JPG.meta +++ b/SampleData/EdoloSample/Sessions/ETR009/SceneAroundCamera.JPG.meta @@ -1,2 +1,2 @@ - + \ No newline at end of file diff --git a/src/SayMore/Model/MetadataSearch.cs b/src/SayMore/Model/MetadataSearch.cs new file mode 100644 index 000000000..3046067e6 --- /dev/null +++ b/src/SayMore/Model/MetadataSearch.cs @@ -0,0 +1,166 @@ +using SayMore.Model.Files; +using SIL.Core.ClearShare; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace SayMore.Model +{ + /// ---------------------------------------------------------------------------------------- + /// + /// The class that contains the data searching methods. + /// + /// ---------------------------------------------------------------------------------------- + public class MetadataSearch + { + + private readonly ProjectContext _projectContext; + + public MetadataSearch(ProjectContext projectContext) + { + _projectContext = projectContext; + } + + // Searchable tags based on file type. + private static readonly HashSet sessionFileSearchableTags = new HashSet + { + "genre", + "title", + "setting", + "participants", + "situation", + "synopsis", + "location", + "access", + "notes", + "continent", + "country", + "region", + "address", + "sub-genre", + "name", + "contributors", + "additional_location_country", + "additional_location_continent", + "additional_location_region", + "additional_location_address", + "additional_sub-genre", + "additional_interactivity", + "additional_planning_type", + "additional_involvement", + "additional_social_context", + "additional_task" + }; + + // We know this is not how you search for annotation files. + private static readonly HashSet annotationFileSearchableTags = new HashSet + { + "annotation_value" + }; + + private static readonly HashSet otherFileSearchableTags = new HashSet + { + "notes", + "name", + "microphone", + "device", + "participants", + "annotation_value", + "recordist", + "speaker" + }; + + public IEnumerable SearchSessions(string query) + { + var allSessions = _projectContext.Project.GetAllSessions(CancellationToken.None); + + foreach (var session in allSessions) + { + bool found = false; + foreach (var componentFile in session.GetComponentFiles()) + { + // Determins what file type is being searched for to determine searchable tags. + HashSet searchableTags; + if (componentFile.FileType is SessionFileType) + { + searchableTags = new HashSet(sessionFileSearchableTags.Union(GetCustomFieldIds(componentFile))); + } + else if (componentFile is AnnotationComponentFile) + { + searchableTags = annotationFileSearchableTags; + } + else if (componentFile is OralAnnotationComponentFile) + { + searchableTags = annotationFileSearchableTags; + } + else + { + searchableTags = new HashSet(otherFileSearchableTags.Union(GetCustomFieldIds(componentFile))); + } + + // Actually peforms the search and yields the session ID if found. + var fields = componentFile.MetaDataFieldValues; + + foreach (var field in fields) + { + if (searchableTags.Contains(field.FieldId?.ToLowerInvariant()) && + (field.Value?.ToString() ?? string.Empty).IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) + { + found = true; + yield return session.Id; + break; + } + } + + // If the query wasn't found in the offical fields or the custom fields, check the contributer notes. + if (!found && componentFile.FileType is SessionFileType && ContainsContributorNotes(componentFile, query)) + { + found = true; + yield return session.Id; + } + } + if (found) break; + } + } + + + // Returns the custom fields for a given component file. + // Note: this does not work for audio files. + private HashSet GetCustomFieldIds(ComponentFile file) + { + HashSet customFields = new HashSet(); + + if (file is ProjectElementComponentFile projectElementFile) + { + customFields = new HashSet( + projectElementFile.GetCustomFields() + .Select(f => f.FieldId?.ToLowerInvariant()) + .Where(id => !string.IsNullOrEmpty(id)) + ); + } + + return customFields; + } + + // Checks through contributer notes of a given component file for a query. + private static bool ContainsContributorNotes(ComponentFile file, string query) + { + var contributionsField = file.MetaDataFieldValues + .FirstOrDefault(f => f.FieldId == SessionFileType.kContributionsFieldName); + + if (contributionsField?.Value is ContributionCollection contributions) + { + foreach (var contribution in contributions) + { + if (!string.IsNullOrEmpty(contribution.Comments) && + contribution.Comments.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) + { + return true; + } + } + } + return false; + } + } +} diff --git a/src/SayMore/Model/Project.cs b/src/SayMore/Model/Project.cs index 5d71ed929..7d0204ee1 100644 --- a/src/SayMore/Model/Project.cs +++ b/src/SayMore/Model/Project.cs @@ -660,7 +660,7 @@ public void DisplayInitialArchiveSummary( throw new OperationCanceledException(); var element = (kvp.Key.StartsWith("\n") || kvp.Key.Length > 0 ? LocalizationManager.GetString("DialogBoxes.ArchivingDlg.ContributorElementName", "Contributor") : - LocalizationManager.GetString("DialogBoxes.ArchivingDlg.SessionElementName", "Sessions")); + LocalizationManager.GetString("DialogBoxes.ArchivingDlg.SessionElementName", "Sessionsg")); model.DisplayMessage(string.Format(fmt, element, (kvp.Key.StartsWith("\n") || kvp.Key.Length > 0 ? kvp.Key.Substring(1) : Title)), ArchivingDlgViewModel.MessageType.Progress); diff --git a/src/SayMore/ProjectContext.cs b/src/SayMore/ProjectContext.cs index 31973d0ac..10b5db4a8 100644 --- a/src/SayMore/ProjectContext.cs +++ b/src/SayMore/ProjectContext.cs @@ -311,6 +311,7 @@ protected void BuildSubContainerForThisProject(string rootDirectoryPath, IContai //NB: when we move to .net 4, we can remove this and instead use Lazy in the PersonFileType constructor //builder.Register>(c => () => c.Resolve()); //builder.Register>(c => () => c.Resolve()); + builder.RegisterInstance(new MetadataSearch(this)).AsSelf(); }); } diff --git a/src/SayMore/UI/ElementListScreen/ElementListScreen.cs b/src/SayMore/UI/ElementListScreen/ElementListScreen.cs index 363580ab9..ebfca1ae3 100644 --- a/src/SayMore/UI/ElementListScreen/ElementListScreen.cs +++ b/src/SayMore/UI/ElementListScreen/ElementListScreen.cs @@ -41,10 +41,28 @@ public partial class ElementListScreen : UserControl where T : ProjectElement protected ComponentFileGrid _componentFilesControl; protected Control _tabControlHostControl; protected ImageList _tabControlImages; + protected HashSet _metadataSearchMatchingIds; protected Dictionary _tabControls = new Dictionary(); + /// ------------------------------------------------------------------------------------ + /// + /// Handle a search request from the list panel by reloading the element list + /// filtered by the search text coming from the panel. + /// + private void HandleSearchRequested(object sender, EventArgs e) + { + // Forward the current search text to the overload of LoadElementList that accepts a searchParam. + // If the search text is 2 or fewer characters, treat it as no search (show full list). + var search = _elementsListPanel?.SearchText; + // If empty or whitespace, show full list. Otherwise pass the search text + if (string.IsNullOrWhiteSpace(search)) + LoadElementList(null, null); + else + LoadElementList(null, search); + } + /// ------------------------------------------------------------------------------------ public ToolStripMenuItem MainMenuItem { get; } @@ -80,6 +98,7 @@ protected void Initialize(Control tabControlHostControl, _elementsListPanel = elementsListPanel; _elementsListPanel.NewButtonClicked += HandleAddingNewElement; _elementsListPanel.DeleteButtonClicked += HandleDeletingSelectedElements; + _elementsListPanel.SearchRequested += HandleSearchRequested; _elementsListPanel.ListControl = _elementsGrid; _componentFilesControl = componentGrid; @@ -214,15 +233,39 @@ private bool HandleFilesAddedToComponentGrid(string[] files) /// ------------------------------------------------------------------------------------ protected virtual void LoadElementList() { - LoadElementList(null); + // Backward-compatible parameterless call - forwards to the overload that + // now accepts an optional search parameter. + LoadElementList(null, null); } /// ------------------------------------------------------------------------------------ - protected virtual void LoadElementList(object itemToSelectAfterLoad) + protected virtual void LoadElementList(object itemToSelectAfterLoad, string searchParam = null) { - _elementsGrid.Items = _model.Elements.OrderBy(x => x.Id); + // Start with all elements, ordered by id + IEnumerable items = _model.Elements.OrderBy(x => x.Id); - if (_model.Elements.Any()) + // If a search parameter was provided, restrict to elements whose Id + // contains the search text (case-insensitive). This applies to any + // non-empty searchParam (including 1-2 char searches). + if(!string.IsNullOrWhiteSpace(searchParam)) + { + if (_metadataSearchMatchingIds != null) + { + // Match by metadata OR by ID substring + items = items.Where(x => !string.IsNullOrEmpty(x.Id) && + (_metadataSearchMatchingIds.Contains(x.Id) || + x.Id.IndexOf(searchParam, StringComparison.OrdinalIgnoreCase) >= 0)); + } + else + { + items = items.Where(x => !string.IsNullOrEmpty(x.Id) && + x.Id.IndexOf(searchParam, StringComparison.OrdinalIgnoreCase) >= 0); + } + } + + _elementsGrid.Items = items; + + if (items.Any()) { switch (itemToSelectAfterLoad) { @@ -246,6 +289,7 @@ protected virtual void LoadElementList(object itemToSelectAfterLoad) _elementsGrid.Refresh(); } + /// ------------------------------------------------------------------------------------ protected void UpdateComponentFileList() @@ -560,6 +604,7 @@ protected override void Dispose(bool disposing) _elementsGrid.SelectedElementChanged -= HandleSelectedElementChanged; _elementsListPanel.NewButtonClicked -= HandleAddingNewElement; _elementsListPanel.DeleteButtonClicked -= HandleDeletingSelectedElements; + _elementsListPanel.SearchRequested -= HandleSearchRequested; var frm = FindForm(); if (frm != null) diff --git a/src/SayMore/UI/ElementListScreen/SessionsListScreen.Designer.cs b/src/SayMore/UI/ElementListScreen/SessionsListScreen.Designer.cs index b45438eaa..8dff1acfe 100644 --- a/src/SayMore/UI/ElementListScreen/SessionsListScreen.Designer.cs +++ b/src/SayMore/UI/ElementListScreen/SessionsListScreen.Designer.cs @@ -209,7 +209,7 @@ private void InitializeComponent() public string NameForUsageReporting { - get { return "Sessions"; } + get { return "Sessionsc"; } } } } diff --git a/src/SayMore/UI/ElementListScreen/SessionsListScreen.cs b/src/SayMore/UI/ElementListScreen/SessionsListScreen.cs index d59a897f7..23a0b2b96 100644 --- a/src/SayMore/UI/ElementListScreen/SessionsListScreen.cs +++ b/src/SayMore/UI/ElementListScreen/SessionsListScreen.cs @@ -1,15 +1,16 @@ -using System; -using System.Drawing; -using System.Windows.Forms; using L10NSharp; -using SIL.Reporting; using SayMore.Media.Audio; using SayMore.Model; using SayMore.Properties; using SayMore.UI.ComponentEditors; -using SayMore.UI.SessionRecording; using SayMore.UI.NewSessionsFromFiles; using SayMore.UI.ProjectWindow; +using SayMore.UI.SessionRecording; +using SIL.Reporting; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; namespace SayMore.UI.ElementListScreen { @@ -17,20 +18,25 @@ namespace SayMore.UI.ElementListScreen public partial class SessionsListScreen : ConcreteSessionScreen, ISayMoreView { private readonly NewSessionsFromFileDlgViewModel.Factory _newSessionsFromFileDlgViewModel; + private readonly MetadataSearch _metadataSearch; /// ------------------------------------------------------------------------------------ public SessionsListScreen(ElementListViewModel presentationModel, NewSessionsFromFileDlgViewModel.Factory newSessionsFromFileDlgViewModel, - SessionsGrid.Factory sessionGridFactory) + SessionsGrid.Factory sessionGridFactory, MetadataSearch metadataSearch) : base(presentationModel) { Logger.WriteEvent("PersonListScreen constructor"); _elementsGrid = sessionGridFactory(); _elementsGrid.Name = "SessionsGrid"; + _metadataSearch = metadataSearch; _newSessionsFromFileDlgViewModel = newSessionsFromFileDlgViewModel; InitializeComponent(); + // Show the search box for sessions list only + _sessionsListPanel.ShowSearchBar = true; + if (DesignMode) return; @@ -58,6 +64,20 @@ public SessionsListScreen(ElementListViewModel presentationModel, _elementsListPanel.HeaderPanelBottomBorderColor = Settings.Default.SessionEditorsBorderColor; } + protected override void LoadElementList(object itemToSelectAfterLoad, string searchParam = null) + { + if (!string.IsNullOrWhiteSpace(searchParam)) + { + var matchingIds = new HashSet(_metadataSearch.SearchSessions(searchParam)); + _metadataSearchMatchingIds = matchingIds; + } + else + { + _metadataSearchMatchingIds = null; + } + base.LoadElementList(itemToSelectAfterLoad, searchParam); + } + /// ------------------------------------------------------------------------------------ protected override void HandleStringsLocalized(ILocalizationManager lm) { diff --git a/src/SayMore/UI/LowLevelControls/ListPanel.Designer.cs b/src/SayMore/UI/LowLevelControls/ListPanel.Designer.cs index f052ab11b..8cfd4ab5c 100644 --- a/src/SayMore/UI/LowLevelControls/ListPanel.Designer.cs +++ b/src/SayMore/UI/LowLevelControls/ListPanel.Designer.cs @@ -18,147 +18,165 @@ partial class ListPanel /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ListPanel)); - this.locExtender = new L10NSharp.UI.L10NSharpExtender(this.components); - this._outerPanel = new SIL.Windows.Forms.Widgets.EnhancedPanel(); - this._buttonsFlowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel(); - this._buttonNew = new System.Windows.Forms.Button(); - this._headerLabel = new SIL.Windows.Forms.Widgets.HeaderLabel(); - this._buttonColChooser = new SayMore.UI.LowLevelControls.ColumnChooserButton(); - ((System.ComponentModel.ISupportInitialize)(this.locExtender)).BeginInit(); - this._outerPanel.SuspendLayout(); - this._buttonsFlowLayoutPanel.SuspendLayout(); - this._headerLabel.SuspendLayout(); - this.SuspendLayout(); - // - // locExtender - // - this.locExtender.LocalizationManagerId = "SayMore"; - this.locExtender.PrefixForNewItems = null; - // - // _outerPanel - // - this._outerPanel.BackColor = System.Drawing.SystemColors.Window; - this._outerPanel.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); - this._outerPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this._outerPanel.ClipTextForChildControls = true; - this._outerPanel.ControlReceivingFocusOnMnemonic = null; - this._outerPanel.Controls.Add(this._buttonsFlowLayoutPanel); - this._outerPanel.Controls.Add(this._headerLabel); - this._outerPanel.Dock = System.Windows.Forms.DockStyle.Fill; - this._outerPanel.DoubleBuffered = true; - this._outerPanel.DrawOnlyBottomBorder = false; - this._outerPanel.DrawOnlyTopBorder = false; - this._outerPanel.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.World); - this._outerPanel.ForeColor = System.Drawing.SystemColors.ControlText; - this.locExtender.SetLocalizableToolTip(this._outerPanel, null); - this.locExtender.SetLocalizationComment(this._outerPanel, null); - this.locExtender.SetLocalizationPriority(this._outerPanel, L10NSharp.LocalizationPriority.NotLocalizable); - this.locExtender.SetLocalizingId(this._outerPanel, "UI.ListPanel._outerPanel"); - this._outerPanel.Location = new System.Drawing.Point(0, 0); - this._outerPanel.MnemonicGeneratesClick = false; - this._outerPanel.Name = "_outerPanel"; - this._outerPanel.PaintExplorerBarBackground = false; - this._outerPanel.Size = new System.Drawing.Size(170, 277); - this._outerPanel.TabIndex = 1; - // - // _buttonsFlowLayoutPanel - // - this._buttonsFlowLayoutPanel.AutoSize = true; - this._buttonsFlowLayoutPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this._buttonsFlowLayoutPanel.Controls.Add(this._buttonNew); - this._buttonsFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Bottom; - this._buttonsFlowLayoutPanel.Location = new System.Drawing.Point(0, 239); - this._buttonsFlowLayoutPanel.Name = "_buttonsFlowLayoutPanel"; - this._buttonsFlowLayoutPanel.Padding = new System.Windows.Forms.Padding(2); - this._buttonsFlowLayoutPanel.Size = new System.Drawing.Size(168, 36); - this._buttonsFlowLayoutPanel.TabIndex = 3; - this._buttonsFlowLayoutPanel.TabStop = true; - this._buttonsFlowLayoutPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.HandleButtonPanelPaint); - // - // _buttonNew - // - this._buttonNew.AutoSize = true; - this._buttonNew.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.locExtender.SetLocalizableToolTip(this._buttonNew, null); - this.locExtender.SetLocalizationComment(this._buttonNew, "Button for adding new sessions and people"); - this.locExtender.SetLocalizingId(this._buttonNew, "CommonToMultipleViews.ElementList.NewButtonText"); - this._buttonNew.Location = new System.Drawing.Point(5, 5); - this._buttonNew.MinimumSize = new System.Drawing.Size(75, 26); - this._buttonNew.Name = "_buttonNew"; - this._buttonNew.Size = new System.Drawing.Size(75, 26); - this._buttonNew.TabIndex = 0; - this._buttonNew.Text = "&New"; - this._buttonNew.UseVisualStyleBackColor = true; - this._buttonNew.Click += new System.EventHandler(this.HandleNewButtonClick); - // - // _headerLabel - // - this._headerLabel.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); - this._headerLabel.ClipTextForChildControls = true; - this._headerLabel.ControlReceivingFocusOnMnemonic = null; - this._headerLabel.Controls.Add(this._buttonColChooser); - this._headerLabel.Dock = System.Windows.Forms.DockStyle.Top; - this._headerLabel.DoubleBuffered = true; - this._headerLabel.DrawOnlyBottomBorder = false; - this._headerLabel.DrawOnlyTopBorder = false; - this._headerLabel.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.World); - this._headerLabel.ForeColor = System.Drawing.SystemColors.ControlText; - this.locExtender.SetLocalizableToolTip(this._headerLabel, null); - this.locExtender.SetLocalizationComment(this._headerLabel, "Do not translate - Localized in subclass"); - this.locExtender.SetLocalizationPriority(this._headerLabel, L10NSharp.LocalizationPriority.NotLocalizable); - this.locExtender.SetLocalizingId(this._headerLabel, "UI.ListPanel._headerLabel"); - this._headerLabel.Location = new System.Drawing.Point(0, 0); - this._headerLabel.MinimumSize = new System.Drawing.Size(165, 2); - this._headerLabel.MnemonicGeneratesClick = false; - this._headerLabel.Name = "_headerLabel"; - this._headerLabel.PaintExplorerBarBackground = false; - this._headerLabel.ShowWindowBackgroundOnTopAndRightEdge = true; - this._headerLabel.Size = new System.Drawing.Size(168, 23); - this._headerLabel.TabIndex = 0; - this._headerLabel.Text = "Change this text"; - this._headerLabel.Paint += new System.Windows.Forms.PaintEventHandler(this.HandleHeaderPanelPaint); - // - // _buttonColChooser - // - this._buttonColChooser.Anchor = System.Windows.Forms.AnchorStyles.Right; - this._buttonColChooser.BackColor = System.Drawing.Color.Transparent; - this._buttonColChooser.CanBeChecked = false; - this._buttonColChooser.Checked = false; - this._buttonColChooser.DrawEmpty = false; - this._buttonColChooser.DrawLeftArrowButton = false; - this._buttonColChooser.DrawRightArrowButton = false; - this._buttonColChooser.Font = new System.Drawing.Font("Marlett", 9F); - this._buttonColChooser.Image = ((System.Drawing.Image)(resources.GetObject("_buttonColChooser.Image"))); - this.locExtender.SetLocalizableToolTip(this._buttonColChooser, "Choose Columns"); - this.locExtender.SetLocalizationComment(this._buttonColChooser, null); - this.locExtender.SetLocalizingId(this._buttonColChooser, "CommonToMultipleViews.ElementList.ColumnChooserButton"); - this._buttonColChooser.Location = new System.Drawing.Point(149, 2); - this._buttonColChooser.Name = "_buttonColChooser"; - this._buttonColChooser.Size = new System.Drawing.Size(20, 20); - this._buttonColChooser.TabIndex = 0; - // - // ListPanel - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this._outerPanel); - this.DoubleBuffered = true; - this.locExtender.SetLocalizableToolTip(this, null); - this.locExtender.SetLocalizationComment(this, null); - this.locExtender.SetLocalizationPriority(this, L10NSharp.LocalizationPriority.NotLocalizable); - this.locExtender.SetLocalizingId(this, "ListPanel.ListPanel"); - this.MinimumSize = new System.Drawing.Size(125, 0); - this.Name = "ListPanel"; - this.Size = new System.Drawing.Size(170, 277); - ((System.ComponentModel.ISupportInitialize)(this.locExtender)).EndInit(); - this._outerPanel.ResumeLayout(false); - this._outerPanel.PerformLayout(); - this._buttonsFlowLayoutPanel.ResumeLayout(false); - this._buttonsFlowLayoutPanel.PerformLayout(); - this._headerLabel.ResumeLayout(false); - this.ResumeLayout(false); + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ListPanel)); + this.locExtender = new L10NSharp.UI.L10NSharpExtender(this.components); + this._outerPanel = new SIL.Windows.Forms.Widgets.EnhancedPanel(); + this._buttonsFlowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel(); + this._buttonNew = new System.Windows.Forms.Button(); + this._headerLabel = new SIL.Windows.Forms.Widgets.HeaderLabel(); + this._buttonColChooser = new SayMore.UI.LowLevelControls.ColumnChooserButton(); + this._searchTextBox = new System.Windows.Forms.TextBox(); + ((System.ComponentModel.ISupportInitialize)(this.locExtender)).BeginInit(); + this._outerPanel.SuspendLayout(); + this._buttonsFlowLayoutPanel.SuspendLayout(); + this._headerLabel.SuspendLayout(); + this.SuspendLayout(); + // + // locExtender + // + this.locExtender.LocalizationManagerId = "SayMore"; + this.locExtender.PrefixForNewItems = null; + // + // _outerPanel + // + this._outerPanel.BackColor = System.Drawing.SystemColors.Window; + this._outerPanel.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this._outerPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this._outerPanel.ClipTextForChildControls = true; + this._outerPanel.ControlReceivingFocusOnMnemonic = null; + this._outerPanel.Controls.Add(this._buttonsFlowLayoutPanel); + this._outerPanel.Controls.Add(this._headerLabel); + this._outerPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this._outerPanel.DoubleBuffered = true; + this._outerPanel.DrawOnlyBottomBorder = false; + this._outerPanel.DrawOnlyTopBorder = false; + this._outerPanel.Font = new System.Drawing.Font("Segoe UI", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.World); + this._outerPanel.ForeColor = System.Drawing.SystemColors.ControlText; + this.locExtender.SetLocalizableToolTip(this._outerPanel, null); + this.locExtender.SetLocalizationComment(this._outerPanel, null); + this.locExtender.SetLocalizationPriority(this._outerPanel, L10NSharp.LocalizationPriority.NotLocalizable); + this.locExtender.SetLocalizingId(this._outerPanel, "UI.ListPanel._outerPanel"); + this._outerPanel.Location = new System.Drawing.Point(0, 0); + this._outerPanel.MnemonicGeneratesClick = false; + this._outerPanel.Name = "_outerPanel"; + this._outerPanel.PaintExplorerBarBackground = false; + this._outerPanel.Size = new System.Drawing.Size(405, 317); + this._outerPanel.TabIndex = 1; + // + // _buttonsFlowLayoutPanel + // + this._buttonsFlowLayoutPanel.AutoSize = true; + this._buttonsFlowLayoutPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this._buttonsFlowLayoutPanel.Controls.Add(this._buttonNew); + this._buttonsFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Bottom; + this._buttonsFlowLayoutPanel.Location = new System.Drawing.Point(0, 279); + this._buttonsFlowLayoutPanel.Name = "_buttonsFlowLayoutPanel"; + this._buttonsFlowLayoutPanel.Padding = new System.Windows.Forms.Padding(2); + this._buttonsFlowLayoutPanel.Size = new System.Drawing.Size(403, 36); + this._buttonsFlowLayoutPanel.TabIndex = 3; + this._buttonsFlowLayoutPanel.TabStop = true; + this._buttonsFlowLayoutPanel.Paint += new System.Windows.Forms.PaintEventHandler(this.HandleButtonPanelPaint); + // + // _buttonNew + // + this._buttonNew.AutoSize = true; + this._buttonNew.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.locExtender.SetLocalizableToolTip(this._buttonNew, null); + this.locExtender.SetLocalizationComment(this._buttonNew, "Button for adding new sessions and people"); + this.locExtender.SetLocalizingId(this._buttonNew, "CommonToMultipleViews.ElementList.NewButtonText"); + this._buttonNew.Location = new System.Drawing.Point(5, 5); + this._buttonNew.MinimumSize = new System.Drawing.Size(75, 26); + this._buttonNew.Name = "_buttonNew"; + this._buttonNew.Size = new System.Drawing.Size(75, 26); + this._buttonNew.TabIndex = 0; + this._buttonNew.Text = "&New"; + this._buttonNew.UseVisualStyleBackColor = true; + this._buttonNew.Click += new System.EventHandler(this.HandleNewButtonClick); + // + // _headerLabel + // + this._headerLabel.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(173)))), ((int)(((byte)(179))))); + this._headerLabel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this._headerLabel.ClipTextForChildControls = true; + this._headerLabel.ControlReceivingFocusOnMnemonic = null; + this._headerLabel.Controls.Add(this._buttonColChooser); + this._headerLabel.Controls.Add(this._searchTextBox); + this._headerLabel.Dock = System.Windows.Forms.DockStyle.Top; + this._headerLabel.DoubleBuffered = true; + this._headerLabel.DrawOnlyBottomBorder = false; + this._headerLabel.DrawOnlyTopBorder = false; + this._headerLabel.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.World); + this._headerLabel.ForeColor = System.Drawing.SystemColors.ControlText; + this.locExtender.SetLocalizableToolTip(this._headerLabel, null); + this.locExtender.SetLocalizationComment(this._headerLabel, "Do not translate - Localized in subclass"); + this.locExtender.SetLocalizationPriority(this._headerLabel, L10NSharp.LocalizationPriority.NotLocalizable); + this.locExtender.SetLocalizingId(this._headerLabel, "UI.ListPanel._headerLabel"); + this._headerLabel.Location = new System.Drawing.Point(0, 0); + this._headerLabel.MinimumSize = new System.Drawing.Size(165, 2); + this._headerLabel.MnemonicGeneratesClick = false; + this._headerLabel.Name = "_headerLabel"; + this._headerLabel.PaintExplorerBarBackground = false; + this._headerLabel.ShowWindowBackgroudOnTopAndRightEdge = true; + this._headerLabel.ShowWindowBackgroundOnTopAndRightEdge = true; + this._headerLabel.Size = new System.Drawing.Size(403, 23); + this._headerLabel.TabIndex = 0; + this._headerLabel.Text = "Change this text"; + this._headerLabel.Paint += new System.Windows.Forms.PaintEventHandler(this.HandleHeaderPanelPaint); + // + // _buttonColChooser + // + this._buttonColChooser.Anchor = System.Windows.Forms.AnchorStyles.Right; + this._buttonColChooser.BackColor = System.Drawing.Color.Transparent; + this._buttonColChooser.CanBeChecked = false; + this._buttonColChooser.Checked = false; + this._buttonColChooser.DrawEmpty = false; + this._buttonColChooser.DrawLeftArrowButton = false; + this._buttonColChooser.DrawRightArrowButton = false; + this._buttonColChooser.Font = new System.Drawing.Font("Marlett", 9F); + this._buttonColChooser.Image = ((System.Drawing.Image)(resources.GetObject("_buttonColChooser.Image"))); + this.locExtender.SetLocalizableToolTip(this._buttonColChooser, "Choose Columns"); + this.locExtender.SetLocalizationComment(this._buttonColChooser, null); + this.locExtender.SetLocalizingId(this._buttonColChooser, "CommonToMultipleViews.ElementList.ColumnChooserButton"); + this._buttonColChooser.Location = new System.Drawing.Point(384, 2); + this._buttonColChooser.Name = "_buttonColChooser"; + this._buttonColChooser.Size = new System.Drawing.Size(20, 20); + this._buttonColChooser.TabIndex = 0; + // + // _searchTextBox + // + this._searchTextBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this._searchTextBox.Dock = System.Windows.Forms.DockStyle.Right; + this.locExtender.SetLocalizableToolTip(this._searchTextBox, null); + this.locExtender.SetLocalizationComment(this._searchTextBox, null); + this.locExtender.SetLocalizingId(this._searchTextBox, "ListPanel._searchTextBox"); + this._searchTextBox.Location = new System.Drawing.Point(221, 0); + this._searchTextBox.Margin = new System.Windows.Forms.Padding(2); + this._searchTextBox.Name = "_searchTextBox"; + this._searchTextBox.Size = new System.Drawing.Size(180, 23); + this._searchTextBox.TabIndex = 1; + // + // ListPanel + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this._outerPanel); + this.DoubleBuffered = true; + this.locExtender.SetLocalizableToolTip(this, null); + this.locExtender.SetLocalizationComment(this, null); + this.locExtender.SetLocalizationPriority(this, L10NSharp.LocalizationPriority.NotLocalizable); + this.locExtender.SetLocalizingId(this, "ListPanel.ListPanel"); + this.MinimumSize = new System.Drawing.Size(125, 0); + this.Name = "ListPanel"; + this.Size = new System.Drawing.Size(405, 317); + ((System.ComponentModel.ISupportInitialize)(this.locExtender)).EndInit(); + this._outerPanel.ResumeLayout(false); + this._outerPanel.PerformLayout(); + this._buttonsFlowLayoutPanel.ResumeLayout(false); + this._buttonsFlowLayoutPanel.PerformLayout(); + this._headerLabel.ResumeLayout(false); + this._headerLabel.PerformLayout(); + this.ResumeLayout(false); } @@ -170,5 +188,6 @@ private void InitializeComponent() public System.Windows.Forms.Button _buttonNew; private System.Windows.Forms.FlowLayoutPanel _buttonsFlowLayoutPanel; private ColumnChooserButton _buttonColChooser; + private System.Windows.Forms.TextBox _searchTextBox; } } diff --git a/src/SayMore/UI/LowLevelControls/ListPanel.cs b/src/SayMore/UI/LowLevelControls/ListPanel.cs index 0883e4752..93ea45f28 100644 --- a/src/SayMore/UI/LowLevelControls/ListPanel.cs +++ b/src/SayMore/UI/LowLevelControls/ListPanel.cs @@ -19,8 +19,21 @@ public partial class ListPanel : UserControl public event EventHandler NewButtonClicked; public event EventHandler DeleteButtonClicked; + /// + /// Raised when the text in the search box changes. + /// + public event EventHandler SearchTextChanged; + + /// + /// Raised when the user types in the search box and a search should be performed. + /// Only fired when there are three or more characters in the search box. + /// + public event EventHandler SearchRequested; + private readonly List