From 9e106b4e67ece0094d9880cfc30980641446ad03 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 11:44:01 +0100 Subject: [PATCH 01/30] Added functionality in metadata initialization ui to select multiple folders to build session id from --- .../@DataLocationModel/DataLocationModel.m | 61 ++- .../MetadataInitializationUI.m | 438 ++++++++++++++---- 2 files changed, 397 insertions(+), 102 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 2df68acb..818cd0c9 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -66,6 +66,7 @@ 'StringDetectMode', repmat({'ind'}, 1, numVars), ... 'StringDetectInput', repmat({'1:end'}, 1, numVars), ... 'StringFormat', repmat({''}, 1, numVars), ... + 'Separator', repmat({''}, 1, numVars), ... 'FunctionName', repmat({''}, 1, numVars) ); end @@ -720,7 +721,10 @@ function restore(obj, data) % substring containing the value of a variable given by varName. % The substring is obtained from the given pathStr based on % instructions from the DataLocationModel's MetaDataDef property. - + % + % SubfolderLevel may be an array; substrings from each selected + % folder level are concatenated using the Separator field. + % Initialize output substring = ''; dataLocationName = obj.Data(dataLocationIndex).Name; @@ -735,31 +739,50 @@ function restore(obj, data) end strPattern = S.StringDetectInput; - folderLevel = S.SubfolderLevel; - + folderLevels = S.SubfolderLevel; + % Abort if instructions are not present. - if isempty(strPattern) || isempty(folderLevel) + if isempty(strPattern) || isempty(folderLevels) return; end - - % Get the index of the folder containing the substring, - % counting backward from the deepest subfolder level. - reversedFolderIdx = S.NumSubfolders - folderLevel; - - folderNames = strsplit(pathStr, filesep); - folderName = folderNames{end-reversedFolderIdx}; % Unpack from cell array - - % Get the substring using either indexing or regular - % expressions. + + separator = ''; + if isfield(S, 'Separator') + separator = S.Separator; + end + + % Split path into folder name parts once. + folderPathParts = strsplit(pathStr, filesep); + numSubfolders = S.NumSubfolders; + + % Collect the folder name at each selected level, then combine + % them into a single string. The pattern is then applied to the + % combined string, so the user only needs one extraction rule. + folderNameParts = cell(1, numel(folderLevels)); + for k = 1:numel(folderLevels) + reversedFolderIdx = numSubfolders - folderLevels(k); + folderIdx = numel(folderPathParts) - reversedFolderIdx; + + if folderIdx >= 1 && folderIdx <= numel(folderPathParts) + folderNameParts{k} = folderPathParts{folderIdx}; + else + folderNameParts{k} = ''; + end + end + + combinedFolderName = strjoin(folderNameParts, separator); + try switch lower(mode) - case 'ind' - %substring = folderName(strPattern); - substring = eval( ['folderName([' strPattern '])'] ); - + substring = eval(['combinedFolderName([' strPattern '])']); case 'expr' - substring = regexp(folderName, strPattern, 'match', 'once'); + result = regexp(combinedFolderName, strPattern, 'match', 'once'); + if isempty(result) + substring = ''; + else + substring = result; + end end catch substring = ''; diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index a8d5892f..353c88f5 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -30,6 +30,7 @@ properties (Access = protected) StringFormat = cell(1, 4); % Store stringformat for each session metadata item. Relevant for date and time. FunctionName = cell(1, 4) + Separator = cell(1, 4) % Store folder-level separator for each session metadata item. % Todo: This should be incorporated better, saving directly to the model. end @@ -97,11 +98,22 @@ function assignDefaultTablePropertyValues(obj) hRow.FolderNameSelector.Position = [xi y wi h]; hRow.FolderNameSelector.FontName = obj.FontName; hRow.FolderNameSelector.ValueChangedFcn = @obj.onFolderNameSelectionChanged; + hRow.FolderNameSelector.UserData = struct('FolderItems', {{}}); obj.centerComponent(hRow.FolderNameSelector, y) - - % Todo: Get folders from DataLocation. hRow.FolderNameSelector.Items = {'Select foldername...'}; hRow.FolderNameSelector.Value = 'Select foldername...'; + + % Button shown in place of the dropdown when multiple folder + % levels are selected (initially hidden). + hRow.FolderMultiSelector = uibutton(obj.TablePanel); + hRow.FolderMultiSelector.Position = [xi y wi h]; + hRow.FolderMultiSelector.FontName = obj.FontName; + hRow.FolderMultiSelector.HorizontalAlignment = 'left'; + hRow.FolderMultiSelector.Text = 'Select folder(s)...'; + hRow.FolderMultiSelector.ButtonPushedFcn = @obj.onFolderSelectorButtonPushed; + hRow.FolderMultiSelector.UserData = struct('FolderItems', {{}}, 'SelectedIndices', []); + hRow.FolderMultiSelector.Visible = 'off'; + obj.centerComponent(hRow.FolderMultiSelector, y) % % Create Togglebutton group for selecting string detection mode i = 3; @@ -206,13 +218,38 @@ function createToolbarComponents(obj, hPanel) methods (Access = private) %Callbacks for userinteraction with controls function onFolderNameSelectionChanged(obj, src, ~) - % Add value to tooltip of control - + % Callback for the folder-level dropdown + rowNumber = obj.getComponentRowNumber(src); - idx = obj.getSubfolderLevel(rowNumber); - - obj.Data(rowNumber).SubfolderLevel = idx; - + + if strcmp(src.Value, 'Select multiple folders...') + % Open multi-select dialog + hFig = ancestor(src, 'figure'); + folderItems = src.UserData.FolderItems; + currentSeparator = obj.Separator{rowNumber}; + if isempty(currentSeparator); currentSeparator = ''; end + + [selectedIndices, separator] = obj.showFolderSelectorDialog( ... + folderItems, [], currentSeparator, hFig.Position); + + if numel(selectedIndices) > 1 + obj.exitFuncModeIfActive(rowNumber); + obj.switchToMultiSelectMode(rowNumber, selectedIndices, separator); + elseif isscalar(selectedIndices) + % User picked exactly one — stay in dropdown mode + obj.exitFuncModeIfActive(rowNumber); + src.Value = folderItems{selectedIndices}; + else + % User cancelled — reset dropdown, leave mode unchanged + src.Value = src.Items{1}; + return + end + else + obj.exitFuncModeIfActive(rowNumber); + end + + obj.Data(rowNumber).SubfolderLevel = obj.getSubfolderLevel(rowNumber); + try obj.updateStringResult(rowNumber) catch ME @@ -223,21 +260,65 @@ function onFolderNameSelectionChanged(obj, src, ~) uialert(hFig, ME.message, 'Update Failed') end - obj.updateStringResult(rowNumber) + src.Tooltip = src.Value; + obj.IsDirty = true; + end - %obj.onStringInputValueChanged(hComp) + function onFolderSelectorButtonPushed(obj, src, ~) + % Callback for the multi-select button (shown when >1 levels selected) + + rowNumber = obj.getComponentRowNumber(src); + folderItems = src.UserData.FolderItems; + currentIndices = src.UserData.SelectedIndices; + currentSeparator = obj.Separator{rowNumber}; + if isempty(currentSeparator); currentSeparator = ''; end + + hFig = ancestor(src, 'figure'); + [selectedIndices, separator] = obj.showFolderSelectorDialog( ... + folderItems, currentIndices, currentSeparator, hFig.Position); + + if isequal(selectedIndices, currentIndices) && strcmp(separator, currentSeparator) + return % No change — leave mode unchanged + end + + obj.exitFuncModeIfActive(rowNumber); + + if isscalar(selectedIndices) + % Reduced to a single folder — revert to dropdown + obj.switchToSingleSelectMode(rowNumber, selectedIndices); + elseif numel(selectedIndices) > 1 + src.UserData.SelectedIndices = selectedIndices; + obj.Separator{rowNumber} = separator; + obj.updateFolderSelectorButtonText(src, folderItems, selectedIndices); + else + % User cleared the selection — revert to dropdown, no selection + obj.switchToSingleSelectMode(rowNumber, []); + end + + obj.Data(rowNumber).SubfolderLevel = obj.getSubfolderLevel(rowNumber); + + try + obj.updateStringResult(rowNumber) + catch ME + if strcmp(ME.identifier, 'MATLAB:badsubscript') + ME = obj.getModifiedBadSubscriptException(); + end + uialert(hFig, ME.message, 'Update Failed') + end - src.Tooltip = src.Value; obj.IsDirty = true; end function onSelectSubstringButtonPushed(obj, src, evt) % Open a dialog window for selecting letter positions. - + % Get foldername for the row which user pushed button from rowNumber = obj.getComponentRowNumber(src); hRow = obj.RowControls(rowNumber); - folderName = hRow.FolderNameSelector.Value; + + % Build the combined folder string from the selected levels and + % separator — this is the string the pattern is applied to. + folderName = obj.getCombinedFolderName(rowNumber); % Create a dialog where the user can select a substring from % the foldername @@ -393,9 +474,11 @@ function onEditFunctionButtonClicked(obj, src, evt) function onButtonGroupStrfindModeButtonDown(obj, src, evt) % onButtonGroupStrfindModeButtonDown - Selection changed callback - + rowNumber = obj.getComponentRowNumber(src); obj.setFunctionButtonVisibility(rowNumber) + hInputEditbox = obj.RowControls(rowNumber).StrfindInputEditbox; + obj.onStringInputValueChanged(hInputEditbox) end function onDataLocationSelectionChanged(obj, src, evt) @@ -417,17 +500,34 @@ function onDataLocationSelectionChanged(obj, src, evt) %getFolderSubString Get folder substring based on user selections mode = obj.getStrSearchMode(rowNumber); strPattern = obj.getStrSearchPattern(rowNumber, mode); - folderName = obj.RowControls(rowNumber).FolderNameSelector.Value; - - switch lower(mode) - case 'ind' - substring = eval( ['folderName([' strPattern '])'] ); - - case 'expr' - substring = regexp(folderName, strPattern, 'match', 'once'); + switch lower(mode) case 'func' substring = obj.getSubstringFromRowFunction(rowNumber); + return + end + + if isempty(obj.getSubfolderLevel(rowNumber)) + substring = ''; + return + end + + combinedFolderName = obj.getCombinedFolderName(rowNumber); + + try + switch lower(mode) + case 'ind' + substring = eval(['combinedFolderName([' strPattern '])']); + case 'expr' + result = regexp(combinedFolderName, strPattern, 'match', 'once'); + if isempty(result) + substring = ''; + else + substring = result; + end + end + catch + substring = ''; end end end @@ -463,23 +563,17 @@ function updateDataLocationModel(obj) function S = getMetaDataDefinitionStruct(obj) %getMetaDataDefinitionStruct Get struct of values from UI controls - + S = obj.DataLocationModel.getDefaultMetadataStructure(); - + % Retrieve values from controls and add to struct for i = 1:obj.NumRows S(i).StringDetectMode = obj.getStrSearchMode(i); S(i).StringDetectInput = obj.getStrSearchPattern(i); S(i).SubfolderLevel = obj.getSubfolderLevel(i); S(i).StringFormat = obj.StringFormat{i}; + S(i).Separator = obj.Separator{i}; S(i).FunctionName = obj.FunctionName{i}; - - if isnan(S(i).SubfolderLevel) - % Revert to the original value if current value is nan. - % Current value might be nan if there are currently no - % available folders in the dropdown selector. - S(i).SubfolderLevel = obj.Data(i).SubfolderLevel; - end end end @@ -502,6 +596,11 @@ function onModelSet(obj) for i = 1:obj.NumRows % Set stringformat from datalocation model. obj.StringFormat{i} = thisDataLocation.MetaDataDef(i).StringFormat; + if isfield(thisDataLocation.MetaDataDef(i), 'Separator') + obj.Separator{i} = thisDataLocation.MetaDataDef(i).Separator; + else + obj.Separator{i} = ''; + end try obj.FunctionName{i} = thisDataLocation.MetaDataDef(i).FunctionName; catch @@ -525,56 +624,61 @@ function onModelSet(obj) end function setFolderSelectionItems(obj) - %setFolderSelectionItems Add model's folder names to each dropdown - - % TODO: Fix error that will occur if several subfolders are - % given the same subfolder type? - + %setFolderSelectionItems Populate the folder-level dropdown items for each row + dlIdx = obj.DataLocationIndex; thisDataLocation = obj.DataLocationModel.Data(dlIdx); - - % Get all the folder selector controls - h = [obj.RowControls.FolderNameSelector]; - - % Get the folder choice examples from the data location model + subFolderStructure = thisDataLocation.SubfolderStructure; - folderChoices = ['Select foldername...', {subFolderStructure.Name}]; + folderItems = {subFolderStructure.Name}; + folderItems(cellfun(@isempty, folderItems)) = {'Foldername not found'}; + + for i = 1:obj.NumRows + hRow = obj.RowControls(i); + + % Store the clean folder list in both controls for index lookups. + hRow.FolderNameSelector.UserData.FolderItems = folderItems; + hRow.FolderMultiSelector.UserData.FolderItems = folderItems; - folderChoices(cellfun(@isempty, folderChoices)) = deal({'Foldername not found'}); - set(h, 'Items', folderChoices) + % Build dropdown items — Session ID gets the multi-select option. + dropdownItems = ['Select foldername...', folderItems]; + if strcmp(hRow.VariableName.Text, 'Session ID') + dropdownItems = [dropdownItems, {'Select multiple folders...'}]; + end + hRow.FolderNameSelector.Items = dropdownItems; + end end function updateFolderSelectionValue(obj, M) - %updateFolderSelectionValue Set the dropdown value based on the model - % Get all the folder selector controls - h = [obj.RowControls.FolderNameSelector]; + %updateFolderSelectionValue Restore the folder selection controls from the model dlIdx = obj.DataLocationIndex; thisDataLocation = obj.DataLocationModel.Data(dlIdx); subFolderStructure = thisDataLocation.SubfolderStructure; - for i = 1:numel(h) - % Todo: Subfolder level needs to be reset if the number of - % subfolders is changed in the model. See newt warning - % below + for i = 1:obj.NumRows + folderItems = obj.RowControls(i).FolderNameSelector.UserData.FolderItems; + itemIdx = M(i).SubfolderLevel; - - % If there is no selection, try to infer from the data - % organization. + + % If there is no selection, try to infer from the data organization. if isempty(itemIdx) itemIdx = obj.initFolderSelectionItemIndex(i, subFolderStructure); end - - if isempty(itemIdx) - itemIdx = 0; - elseif ~isscalar(itemIdx) - itemIdx = itemIdx(1); + + % Clamp to valid range (0 means no selection). + if isscalar(itemIdx) && itemIdx == 0 + itemIdx = []; + else + itemIdx = itemIdx(itemIdx >= 1 & itemIdx <= numel(folderItems)); end - - if itemIdx > numel(h(i).Items) - 1 - warning('Model is inconsistent. Working on fix') + + if numel(itemIdx) > 1 + separator = ''; + if isfield(M(i), 'Separator'); separator = M(i).Separator; end + obj.switchToMultiSelectMode(i, itemIdx, separator); else - set(h(i), 'Value', h(i).Items{itemIdx+1}) + obj.switchToSingleSelectMode(i, itemIdx); end end end @@ -692,27 +796,13 @@ function setStringSearchMode(obj, rowNumber, value) end end - function num = getSubfolderLevel(obj, rowNumber) - - hDropdown = obj.RowControls(rowNumber).FolderNameSelector; - - if strcmp(hDropdown.Value, 'Foldername not found') || ... - strcmp(hDropdown.Value, 'Data location root folder not found') - num = nan; + function indices = getSubfolderLevel(obj, rowNumber) + hRow = obj.RowControls(rowNumber); + if strcmp(hRow.FolderMultiSelector.Visible, 'on') + indices = hRow.FolderMultiSelector.UserData.SelectedIndices; else - items = hDropdown.Items(2:end); % Exclude first choice. - num = find(strcmp(items, hDropdown.Value)); - - % Note: important to exclude first entry. If no folder was - % explicitly selected, the value of num should be empty. - end - - % Todo: Make this more robust. Is it ever going to happen - % unless the folder is not found like above? - if numel( num ) > 1 - num = num(1); - warning(['Multiple folders has the same name. Selected the first ' ... - 'one in the list to use for metadata detection' ] ) + folderItems = hRow.FolderNameSelector.UserData.FolderItems; + indices = find(strcmp(folderItems, hRow.FolderNameSelector.Value)); end end end @@ -851,6 +941,7 @@ function setRowDisplayMode(obj, rowNum, showAdvanced) hRow = obj.RowControls(rowNum); hRow.FolderNameSelector.Position(3) = hRow.FolderNameSelector.Position(3) + xOffset; + hRow.FolderMultiSelector.Position(3) = hRow.FolderMultiSelector.Position(3) + xOffset; hRow.SelectSubstringButton.Position(1) = hRow.SelectSubstringButton.Position(1) + xOffset; hRow.SelectSubstringButton.Visible = visibility_; @@ -948,6 +1039,169 @@ function onDataLocationRemoved(obj, ~, evt) dataLocationName = thisDataLocation.Name; substring = feval(obj.FunctionName{rowNumber}, pathStr, dataLocationName); end + + function exitFuncModeIfActive(obj, rowNumber) + %exitFuncModeIfActive Switch from func to ind mode when folder selection changes + % + % func mode ignores folder selection entirely, so keeping it active + % when the user changes the folder would produce a silent no-op. + % Switching back to ind is the predictable default; the user can + % re-select expr or func manually if needed. + + if strcmp(obj.getStrSearchMode(rowNumber), 'func') + obj.setStringSearchMode(rowNumber, 'ind') + obj.setFunctionButtonVisibility(rowNumber) + end + end + + function switchToMultiSelectMode(obj, rowNumber, selectedIndices, separator) + %switchToMultiSelectMode Show button, hide dropdown, store selection + + hRow = obj.RowControls(rowNumber); + folderItems = hRow.FolderNameSelector.UserData.FolderItems; + + hRow.FolderMultiSelector.UserData.FolderItems = folderItems; + hRow.FolderMultiSelector.UserData.SelectedIndices = selectedIndices; + obj.Separator{rowNumber} = separator; + + obj.updateFolderSelectorButtonText( ... + hRow.FolderMultiSelector, folderItems, selectedIndices); + + hRow.FolderNameSelector.Visible = 'off'; + hRow.FolderMultiSelector.Visible = 'on'; + end + + function switchToSingleSelectMode(obj, rowNumber, selectedIndex) + %switchToSingleSelectMode Hide button, show dropdown, clear multi-selection + + hRow = obj.RowControls(rowNumber); + folderItems = hRow.FolderNameSelector.UserData.FolderItems; + + hRow.FolderMultiSelector.Visible = 'off'; + hRow.FolderMultiSelector.UserData.SelectedIndices = []; + obj.Separator{rowNumber} = ''; + + hRow.FolderNameSelector.Visible = 'on'; + + if ~isempty(selectedIndex) && selectedIndex >= 1 && selectedIndex <= numel(folderItems) + hRow.FolderNameSelector.Value = folderItems{selectedIndex}; + else + hRow.FolderNameSelector.Value = hRow.FolderNameSelector.Items{1}; + end + end + + function combinedName = getCombinedFolderName(obj, rowNumber) + %getCombinedFolderName Get the combined folder name for a row + % + % Collects folder names at each selected level from the example + % path and joins them with the row's separator. This is the + % string the extraction pattern (ind/expr) is applied to. + + folderLevels = obj.getSubfolderLevel(rowNumber); + separator = obj.Separator{rowNumber}; + if isempty(separator); separator = ''; end + + dlIdx = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dlIdx); + examplePath = thisDataLocation.ExamplePath; + numSubfolders = numel(thisDataLocation.SubfolderStructure); + folderPathParts = strsplit(examplePath, filesep); + + if isempty(folderLevels) + combinedName = ''; + return + end + + folderNameParts = cell(1, numel(folderLevels)); + for k = 1:numel(folderLevels) + reversedIdx = numSubfolders - folderLevels(k); + folderIdx = numel(folderPathParts) - reversedIdx; + if folderIdx >= 1 && folderIdx <= numel(folderPathParts) + folderNameParts{k} = folderPathParts{folderIdx}; + else + folderNameParts{k} = ''; + end + end + + combinedName = strjoin(folderNameParts, separator); + end + + function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices) + %updateFolderSelectorButtonText Update button label and tooltip to reflect selection + + if isempty(selectedIndices) + hButton.Text = 'Select folder(s)...'; + hButton.Tooltip = ''; + else + arrowStr = ' ▼'; + fullName = strjoin(folderItems(selectedIndices), ' + '); + label = truncateTextForWidth(fullName, hButton.Position(3), ... + hButton.FontSize, arrowStr); + hButton.Text = [label arrowStr]; + hButton.Tooltip = fullName; + end + end + + function [selectedIndices, separator] = showFolderSelectorDialog( ... + obj, folderItems, currentIndices, currentSeparator, parentPosition) + %showFolderSelectorDialog Modal dialog for selecting folder levels + % + % Opens a figure with a multi-select listbox and a separator + % field. Returns the selected indices and separator string, or + % the original values if the user cancels. + + selectedIndices = currentIndices; + separator = currentSeparator; + + dialogWidth = 300; + dialogHeight = 300; + dialogX = parentPosition(1) + (parentPosition(3) - dialogWidth) / 2; + dialogY = parentPosition(2) + (parentPosition(4) - dialogHeight) / 2; + + dialogFigure = uifigure( ... + 'Name', 'Select Folder Level(s)', ... + 'Position', [dialogX, dialogY, dialogWidth, dialogHeight], ... + 'WindowStyle', 'modal', ... + 'Resize', 'off'); + + uilabel(dialogFigure, ... + 'Text', 'Select one or more folder levels:', ... + 'Position', [15 265 270 22]); + + hListbox = uilistbox(dialogFigure, ... + 'Items', folderItems, ... + 'Multiselect', 'on', ... + 'Position', [15 110 270 150]); + + if ~isempty(currentIndices) && max(currentIndices) <= numel(folderItems) + hListbox.Value = folderItems(currentIndices); + end + + uilabel(dialogFigure, 'Text', 'Separator:', 'Position', [15 75 80 22]); + hSeparatorField = uieditfield(dialogFigure, 'text', ... + 'Value', currentSeparator, ... + 'Position', [100 75 185 22], ... + 'Placeholder', 'e.g. _ (leave blank for none)'); + + uibutton(dialogFigure, 'Text', 'OK', ... + 'Position', [195 30 90 30], ... + 'ButtonPushedFcn', @(~,~) uiresume(dialogFigure)); + uibutton(dialogFigure, 'Text', 'Cancel', ... + 'Position', [100 30 90 30], ... + 'ButtonPushedFcn', @(~,~) delete(dialogFigure)); + + uiwait(dialogFigure); + + % If figure still exists, the user pressed OK — read the values. + if isvalid(dialogFigure) + selected = hListbox.Value; + if ischar(selected); selected = {selected}; end + selectedIndices = find(ismember(folderItems, selected)); + separator = hSeparatorField.Value; + delete(dialogFigure); + end + % else: user cancelled or closed — return the original values. + end end methods (Static, Access = private) @@ -1041,6 +1295,24 @@ function onDataLocationRemoved(obj, ~, evt) end end +function truncatedText = truncateTextForWidth(text, widthPx, fontSizePt, ~) +%truncateTextForWidth Truncate text so it fits within a pixel width budget +% +% Estimates character width as 0.6x the font size (pt→px, proportional +% font approximation) and reserves a fixed pixel budget for the arrow +% indicator. Replaces the last character with '…' when truncation occurs. + + arrowReservedPx = 16; + pixelsPerChar = fontSizePt * 0.6; + maxChars = floor((widthPx - arrowReservedPx) / pixelsPerChar); + + if numel(text) > maxChars && maxChars > 3 + truncatedText = [text(1:maxChars-1) '…']; + else + truncatedText = text; + end +end + function functionName = createFunctionName(identifierName) %Example: subjectId -> getSubjectId From 6767ffbcca457732e6bcbcd5be28f7981a0c5934 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 13:15:38 +0100 Subject: [PATCH 02/30] Refactor metadata extraction: deduplicate logic and improve API - Extract combineFolderNamesFromPath and applyExtractionPattern as Static Hidden methods on DataLocationModel - Move getSubstringFromFolder to Static Hidden; remove unused obj param, replace dataLocationIndex with dataLocationName - MetadataInitializationUI.getFolderSubString and getCombinedFolderName now delegate to the model methods instead of duplicating logic Co-Authored-By: Claude Sonnet 4.6 --- .../@DataLocationModel/DataLocationModel.m | 187 +++++++++++------- .../MetadataInitializationUI.m | 82 +++----- 2 files changed, 141 insertions(+), 128 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 818cd0c9..1ba7dc80 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -208,7 +208,7 @@ function tempDevFix(obj) end function set.DefaultDataLocation(obj, newValue) - + assert(ischar(newValue), 'Please provide a character vector with the name of a data location') % Check if data location with given name exists... @@ -265,7 +265,7 @@ function validateRootPath(obj, dataLocIdx) end function createRootPath(obj, dataLocIdx, rootIdx) - + if nargin < 3; rootIdx = 1; end thisRootPath = obj.Data(dataLocIdx).RootPath(rootIdx).Value; @@ -287,7 +287,7 @@ function updateMetaDataDefinitions(obj, newStruct, dataLocIdx) % % Just replaces the struct in the MetaDataDef property with the % input struct S. - + oldStruct = obj.Data(dataLocIdx).MetaDataDef; obj.Data(dataLocIdx).MetaDataDef = newStruct; @@ -305,7 +305,7 @@ function updateSubfolderStructure(obj, newStruct, idx) % % Just replaces the struct in the SubfolderStructure property % with the input struct S. - + if nargin < 3 idx = 1; end @@ -345,7 +345,7 @@ function updateSubfolderStructure(obj, newStruct, idx) % the operating system % % Todo: Consolidate with session/fixDataLocations - + if isempty(dataLocationStructArray); return; end if isa(dataLocationStructArray, 'cell') @@ -411,7 +411,7 @@ function updateVolumeInfo(obj) function addDataLocation(obj, newDataLocation) %addDataLocation Add data location item to data - + if isempty(newDataLocation.Name) newDataLocation.Name = obj.getNewName(); end @@ -482,14 +482,13 @@ function addDataLocationFromTemplateName(obj, templateName, options) 'No data location templates matching name "%s"', templateName) end end - function removeDataLocation(obj, dataLocationName) %removeDataLocation Remove data location item from data % Todo: Necessary if a undo operation is implemented... %oldValue = obj.getItem(dataLocationName); - + [~, idx] = obj.containsItem(dataLocationName); obj.removeItem(dataLocationName) @@ -512,7 +511,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) % dataLocationName is the name of the data location to modify. If % the modification is on the name itself, the dataLocationName % should be the current (old) name. - + [tf, idx] = obj.containsItem(dataLocationName); if ~any(tf) @@ -556,7 +555,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) end function pathStr = getExampleFolderPath(obj, dataLocationName) - + dataLocation = obj.getItem(dataLocationName); pathStr = dataLocation.ExamplePath; end @@ -566,24 +565,26 @@ function modifyDataLocation(obj, dataLocationName, field, value) function substring = getSubjectID(obj, pathStr, dataLocationIndex) % getSubjectID - Extract subject ID from a path string - + if nargin < 3 || isempty(dataLocationIndex) dataLocationIndex = 1; end S = obj.getMetavariableStruct('Subject ID', dataLocationIndex); - substring = obj.getSubstringFromFolder(pathStr, S, dataLocationIndex); + dataLocationName = obj.Data(dataLocationIndex).Name; + substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); end function substring = getSessionID(obj, pathStr, dataLocationIndex) % getSessionID - Extract session ID from a path string - + if nargin < 3 || isempty(dataLocationIndex) dataLocationIndex = 1; end S = obj.getMetavariableStruct('Session ID', dataLocationIndex); - substring = obj.getSubstringFromFolder(pathStr, S, dataLocationIndex); + dataLocationName = obj.Data(dataLocationIndex).Name; + substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); end function value = getTime(obj, pathStr, dataLocationIndex) @@ -594,7 +595,8 @@ function modifyDataLocation(obj, dataLocationName, field, value) end S = obj.getMetavariableStruct('Experiment Time', dataLocationIndex); - substring = obj.getSubstringFromFolder(pathStr, S, dataLocationIndex); + dataLocationName = obj.Data(dataLocationIndex).Name; + substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); % Convert to datetime type. if isfield(S, 'StringFormat') && ~isempty(S.StringFormat) @@ -618,7 +620,8 @@ function modifyDataLocation(obj, dataLocationName, field, value) end S = obj.getMetavariableStruct('Experiment Date', dataLocationIndex); - substring = obj.getSubstringFromFolder(pathStr, S, dataLocationIndex); + dataLocationName = obj.Data(dataLocationIndex).Name; + substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); % Convert to datetime type. if isfield(S, 'StringFormat') && ~isempty(S.StringFormat) @@ -641,6 +644,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) % RootPath : Key, Value pair of local rootpath. % Todo: Why is this sometimes a cell? + if isa(dlStruct, 'cell') dlStruct = dlStruct{1}; warning('Data is in an unexpected format. This is not critical, but should be investigated.') @@ -669,6 +673,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) end function dlStruct = reduceDataLocationInfo(~, dlStruct) + fieldsToRemove = {'Name', 'Type', 'RootPath'}; for i = 1:numel(fieldsToRemove) if isfield(dlStruct, fieldsToRemove{i}) @@ -698,42 +703,49 @@ function restore(obj, data) % % Get struct containing instructions for how to find substring % (value of a metadata variable) from a directory path. - + if nargin < 3 || isempty(dataLocationIdx) dataLocationIdx = 1; end - + S = obj.Data(dataLocationIdx).MetaDataDef; % Find struct entry corresponding to requested variable variableIdx = strcmp({S.VariableName}, varName); S = S(variableIdx); - + % Need to know how many subfolders the data location has numSubfolders = numel(obj.Data(dataLocationIdx).SubfolderStructure); S.NumSubfolders = numSubfolders; end + end - function substring = getSubstringFromFolder(obj, pathStr, S, dataLocationIndex) + methods (Static, Hidden) % Extraction helpers — public but hidden from the API + + function substring = getSubstringFromFolder(pathStr, S, dataLocationName) %getSubstringFromFolder Find substring from a pathstring. % - % substring = getSubstringFromFolder(obj, pathStr, varName) Get a - % substring containing the value of a variable given by varName. - % The substring is obtained from the given pathStr based on - % instructions from the DataLocationModel's MetaDataDef property. + % substring = getSubstringFromFolder(pathStr, S, dataLocationName) + % extracts a metadata value from pathStr using the extraction + % rules in S (a MetaDataDef entry augmented with NumSubfolders). % - % SubfolderLevel may be an array; substrings from each selected - % folder level are concatenated using the Separator field. + % SubfolderLevel may be an array; the folder names at each level + % are combined with Separator before pattern extraction, so a + % single ind/expr rule applies to the whole combined string. + + arguments + pathStr + S + dataLocationName + end - % Initialize output substring = ''; - dataLocationName = obj.Data(dataLocationIndex).Name; mode = S.StringDetectMode; if strcmp(mode, 'func') substring = feval(S.FunctionName, pathStr, dataLocationName); - % nb: substring could be datetime value + % nb: substring could be a datetime value if ischar(substring) && strcmp(substring, 'N/A'); substring = ''; end return end @@ -741,52 +753,18 @@ function restore(obj, data) strPattern = S.StringDetectInput; folderLevels = S.SubfolderLevel; - % Abort if instructions are not present. if isempty(strPattern) || isempty(folderLevels) - return; + return end separator = ''; - if isfield(S, 'Separator') - separator = S.Separator; - end + if isfield(S, 'Separator'); separator = S.Separator; end - % Split path into folder name parts once. - folderPathParts = strsplit(pathStr, filesep); - numSubfolders = S.NumSubfolders; - - % Collect the folder name at each selected level, then combine - % them into a single string. The pattern is then applied to the - % combined string, so the user only needs one extraction rule. - folderNameParts = cell(1, numel(folderLevels)); - for k = 1:numel(folderLevels) - reversedFolderIdx = numSubfolders - folderLevels(k); - folderIdx = numel(folderPathParts) - reversedFolderIdx; - - if folderIdx >= 1 && folderIdx <= numel(folderPathParts) - folderNameParts{k} = folderPathParts{folderIdx}; - else - folderNameParts{k} = ''; - end - end + combinedFolderName = DataLocationModel.combineFolderNamesFromPath( ... + pathStr, folderLevels, S.NumSubfolders, separator); - combinedFolderName = strjoin(folderNameParts, separator); - - try - switch lower(mode) - case 'ind' - substring = eval(['combinedFolderName([' strPattern '])']); - case 'expr' - result = regexp(combinedFolderName, strPattern, 'match', 'once'); - if isempty(result) - substring = ''; - else - substring = result; - end - end - catch - substring = ''; - end + substring = DataLocationModel.applyExtractionPattern( ... + combinedFolderName, mode, strPattern); end end @@ -809,6 +787,7 @@ function restore(obj, data) % % Create type object instance. + for i = 1:numel(S.Data) if ~isfield(S.Data(i), 'Type') || isempty(S.Data(i).Type) S.Data(i).Type = nansen.config.dloc.DataLocationType('recorded'); @@ -841,7 +820,7 @@ function restore(obj, data) methods (Access = private) function onDataLocationRenamed(obj, oldName, newName) - + obj.assignItemNames() % Update value default data location if this was the one that @@ -890,7 +869,7 @@ function onProjectRenamed(obj, oldName, newName) % Note: Function names for extracting data identifiers % (subjectId, sessionId, experimentData & experimentTime) depend on % the project name - + for i = 1:obj.NumDataLocations for j = 1:numel(obj.Data(i).MetaDataDef) if isfield(obj.Data(i).MetaDataDef(j), 'FunctionName') @@ -954,6 +933,7 @@ function addDiskNameToAllRootPaths(obj) end function rootPathStruct = addDiskNameToRootPathStruct(obj, rootPathStruct) + for i = 1:numel(rootPathStruct) rootPathStruct(i).DiskName = ... obj.resolveDiskName(rootPathStruct(i).Value); @@ -978,6 +958,7 @@ function addDiskTypeToAllRootPaths(obj) % fields and use universal unique ids???? % Update root data type from cell array to struct. + if numel(S.Data) > 0 if isa(S.Data(1).RootPath, 'cell') for i = 1:numel(S.Data) @@ -1013,4 +994,68 @@ function addDiskTypeToAllRootPaths(obj) end end end + + methods (Static, Hidden) % Low-level extraction utilities + + function combinedName = combineFolderNamesFromPath( ... + pathStr, folderLevels, numSubfolders, separator) + %combineFolderNamesFromPath Collect and join folder names at given levels + % + % Counts subfolder positions backward from the deepest level so + % that the indices in folderLevels are independent of the root + % path depth. Returns an empty string if folderLevels is empty. + + if isempty(folderLevels) + combinedName = ''; + return + end + + folderPathParts = strsplit(pathStr, filesep); + + folderNameParts = cell(1, numel(folderLevels)); + for k = 1:numel(folderLevels) + reversedIdx = numSubfolders - folderLevels(k); + folderIdx = numel(folderPathParts) - reversedIdx; + if folderIdx >= 1 && folderIdx <= numel(folderPathParts) + folderNameParts{k} = folderPathParts{folderIdx}; + else + folderNameParts{k} = ''; + end + end + + combinedName = strjoin(folderNameParts, separator); + end + + function substring = applyExtractionPattern(text, mode, pattern) + %applyExtractionPattern Apply an ind or expr extraction rule to text + % + % mode 'ind' — evaluates pattern as a character index expression + % (e.g. '1:5', '3:end') + % mode 'expr' — treats pattern as a regexp and returns first match + + arguments + text + mode + pattern + end + + try + switch lower(mode) + case 'ind' + substring = eval(['text([' pattern '])']); + case 'expr' + result = regexp(text, pattern, 'match', 'once'); + if isempty(result) + substring = ''; + else + substring = result; + end + otherwise + substring = ''; + end + catch + substring = ''; + end + end + end end diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 353c88f5..088a51bf 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -497,38 +497,25 @@ function onDataLocationSelectionChanged(obj, src, evt) methods % Methods for updating the Result column function substring = getFolderSubString(obj, rowNumber) - %getFolderSubString Get folder substring based on user selections - mode = obj.getStrSearchMode(rowNumber); - strPattern = obj.getStrSearchPattern(rowNumber, mode); - - switch lower(mode) - case 'func' - substring = obj.getSubstringFromRowFunction(rowNumber); - return - end + %getFolderSubString Get folder substring based on current UI selections + % + % Builds an S struct from the current UI state and delegates the + % full extraction to DataLocationModel.getSubstringFromFolder. - if isempty(obj.getSubfolderLevel(rowNumber)) - substring = ''; - return - end + dlIdx = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dlIdx); - combinedFolderName = obj.getCombinedFolderName(rowNumber); + S = struct(); + S.StringDetectMode = obj.getStrSearchMode(rowNumber); + S.StringDetectInput = obj.getStrSearchPattern(rowNumber, S.StringDetectMode); + S.SubfolderLevel = obj.getSubfolderLevel(rowNumber); + S.Separator = obj.Separator{rowNumber}; + S.NumSubfolders = numel(thisDataLocation.SubfolderStructure); + S.FunctionName = obj.FunctionName{rowNumber}; - try - switch lower(mode) - case 'ind' - substring = eval(['combinedFolderName([' strPattern '])']); - case 'expr' - result = regexp(combinedFolderName, strPattern, 'match', 'once'); - if isempty(result) - substring = ''; - else - substring = result; - end - end - catch - substring = ''; - end + dataLocationName = thisDataLocation.Name; + substring = obj.DataLocationModel.getSubstringFromFolder( ... + thisDataLocation.ExamplePath, S, dataLocationName); end end @@ -1091,39 +1078,20 @@ function switchToSingleSelectMode(obj, rowNumber, selectedIndex) end function combinedName = getCombinedFolderName(obj, rowNumber) - %getCombinedFolderName Get the combined folder name for a row + %getCombinedFolderName Get the combined folder string for a row % - % Collects folder names at each selected level from the example - % path and joins them with the row's separator. This is the - % string the extraction pattern (ind/expr) is applied to. - - folderLevels = obj.getSubfolderLevel(rowNumber); - separator = obj.Separator{rowNumber}; - if isempty(separator); separator = ''; end + % Used by onSelectSubstringButtonPushed to show the user the + % string that the extraction pattern will be applied to. dlIdx = obj.DataLocationIndex; thisDataLocation = obj.DataLocationModel.Data(dlIdx); - examplePath = thisDataLocation.ExamplePath; - numSubfolders = numel(thisDataLocation.SubfolderStructure); - folderPathParts = strsplit(examplePath, filesep); - - if isempty(folderLevels) - combinedName = ''; - return - end - - folderNameParts = cell(1, numel(folderLevels)); - for k = 1:numel(folderLevels) - reversedIdx = numSubfolders - folderLevels(k); - folderIdx = numel(folderPathParts) - reversedIdx; - if folderIdx >= 1 && folderIdx <= numel(folderPathParts) - folderNameParts{k} = folderPathParts{folderIdx}; - else - folderNameParts{k} = ''; - end - end + separator = obj.Separator{rowNumber}; - combinedName = strjoin(folderNameParts, separator); + combinedName = nansen.config.dloc.DataLocationModel.combineFolderNamesFromPath( ... + thisDataLocation.ExamplePath, ... + obj.getSubfolderLevel(rowNumber), ... + numel(thisDataLocation.SubfolderStructure), ... + separator); end function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices) From 8cb96174bd331626af72e8e9f76ba6f3bc6620f5 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 13:25:17 +0100 Subject: [PATCH 03/30] Clean up whitespace --- .../@DataLocationModel/DataLocationModel.m | 283 +++++++++--------- .../+dloc/@DataLocationModel/getBlankItem.m | 1 - .../+dloc/@DataLocationModel/getDefaultItem.m | 21 +- .../@DataLocationModel/listDataFolders.m | 2 +- .../MetadataInitializationUI.m | 264 ++++++++-------- 5 files changed, 283 insertions(+), 288 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 1ba7dc80..7bddbf05 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -1,19 +1,19 @@ classdef DataLocationModel < utility.data.StorableCatalog %DataLocationModel Interface for detecting path of data/session folders - + % TODOS: % QUESTIONS: - + properties (Constant, Hidden) ITEM_TYPE = 'Data Location' end - + properties (Dependent, SetAccess = private) DataLocationNames NumDataLocations end - + properties (Dependent) IsDirty % Todo: Dependent on whether data backup is different than data DefaultDataLocation @@ -23,23 +23,23 @@ DataBackup % Todo: assign this on construction and when model is marked as clean(?) LocalRootPathManager nansen.config.dloc.LocalRootPathManager end - + events DataLocationAdded DataLocationModified DataLocationRemoved end - + methods (Static) % Methods in separate files - %S = getEmptyItem() - + % S = getEmptyItem() + S = getBlankItem() - + S = getDefaultItem() end methods (Static) - + % function S = getEmptyObject() % % import nansen.config.dloc.DataLocationModel @@ -56,10 +56,10 @@ % end function S = getDefaultMetadataStructure() - + varNames = {'Subject ID', 'Session ID', 'Experiment Date', 'Experiment Time'}; numVars = numel(varNames); - + S = struct(... 'VariableName', varNames, ... 'SubfolderLevel', {[]}, ... @@ -69,7 +69,7 @@ 'Separator', repmat({''}, 1, numVars), ... 'FunctionName', repmat({''}, 1, numVars) ); end - + function S = getDefaultSubfolderStructure() %getDefaultSubfolderStructure Create a default struct S = struct(... @@ -81,59 +81,59 @@ % Todo: Add ShortName, i.e sub / ses (ref BIDS) end end - + methods % Constructor function obj = DataLocationModel(varargin) % Superclass constructor. Loads given (or default) archive obj@utility.data.StorableCatalog(varargin{:}) - + % Initialize the local root path manager obj.initializeLocalRootPathManager(); - + obj.updateMetadataExtractorFunctionNames() obj.tempDevFix() end - + function tempDevFix(obj) - + dirty = false; - + % Add default data location to preferences if ~isfield(obj.Preferences, 'DefaultDataLocation') obj.fixDefaultDataLocation() dirty = true; end - + % Rootpath field changed from cell array with 2 cells to root % array with single to multiple cells ( remove empty cell(s) ) for i = 1:numel(obj.Data) rootPath = obj.Data(i).RootPath; - + if any(strcmp(rootPath, '')) rootPath(strcmp(rootPath, '')) = []; dirty = true; end - + obj.Data(i).RootPath = rootPath; end - + % Add 'Type' as a table variable on the third column if ~isfield(obj.Data, 'Type') obj.addTypeAsTableVariable() dirty = true; end - + % Reorder so that Type is the third table variable fieldNames = fieldnames(obj.Data); if ~strcmp(fieldNames{3}, 'Type') fieldNamesNew = setdiff(fieldNames, 'Type', 'stable'); - + obj.Data = orderfields(obj.Data, ... [fieldNamesNew(1:2); 'Type'; fieldNamesNew(3:end)]); - + dirty = true; end - + if ~isfield(obj.Preferences, 'SourceID') obj.Preferences.SourceID = utility.system.getComputerName(true); dirty = true; @@ -187,51 +187,50 @@ function tempDevFix(obj) end end end - + methods % Set/get methods - + function numDataLocations = get.NumDataLocations(obj) numDataLocations = numel(obj.Data); end - + function dataLocationNames = get.DataLocationNames(obj) dataLocationNames = obj.ItemNames; end - + function defaultDataLocation = get.DefaultDataLocation(obj) - + if isempty(obj.Data); defaultDataLocation = ''; return; end - + dataLocationUuid = obj.Preferences.DefaultDataLocation; defaultDataLocation = obj.getNameFromUuid(dataLocationUuid); - end - + function set.DefaultDataLocation(obj, newValue) assert(ischar(newValue), 'Please provide a character vector with the name of a data location') - + % Check if data location with given name exists... message = sprintf('"%s" can not be a default data location because no data location with this name exists.', newValue); assert(any(strcmp(obj.DataLocationNames, newValue)), message) - + % Check if data location is allowed to be a default data location. dataLocationItem = obj.getDataLocation(newValue); message = sprintf('"%s" can not be a default data location because the data location is of type "%s".', newValue, dataLocationItem.Type.Name); assert(dataLocationItem.Type.AllowAsDefault, message) - + dataLocationUuid = dataLocationItem.Uuid; - + obj.Preferences.DefaultDataLocation = dataLocationUuid; end end - + methods % Defined in separate files dataFolders = listDataFolders(obj, dataLocationName, options) end methods % Modify save/load to include local settings... - + % % function load(obj) % % % % load@utility.data.StorableCatalog(obj) @@ -242,11 +241,10 @@ function tempDevFix(obj) % % % % end % % - end - + methods - + function configureLocalRootpath(obj, localRootPath, originalRootPath) obj.LocalRootPathManager.configureLocalRootPath(localRootPath, originalRootPath) obj.load() @@ -254,7 +252,7 @@ function configureLocalRootpath(obj, localRootPath, originalRootPath) function validateRootPath(obj, dataLocIdx) %validateRootPath Check if root path exists - + % Todo: Loop through all entries in cell array (if many are present) thisDataLoc = obj.Data(dataLocIdx); @@ -263,25 +261,25 @@ function validateRootPath(obj, dataLocIdx) error('Root path for DataLocation "%s" does not exist', thisName) end end - + function createRootPath(obj, dataLocIdx, rootIdx) if nargin < 3; rootIdx = 1; end thisRootPath = obj.Data(dataLocIdx).RootPath(rootIdx).Value; - + if ~isfolder(thisRootPath) mkdir(thisRootPath) fprintf('Created root directory for DataLocation %s\n', obj.Data(dataLocIdx).Name) end end - + function diskName = resolveDiskName(obj, rootPath) diskName = obj.LocalRootPathManager.resolveDiskName(rootPath); end end - + methods % Methods for updating substructs of data location - + function updateMetaDataDefinitions(obj, newStruct, dataLocIdx) %updateMetaDataDefinitions Update the metadata definition struct % @@ -290,7 +288,7 @@ function updateMetaDataDefinitions(obj, newStruct, dataLocIdx) oldStruct = obj.Data(dataLocIdx).MetaDataDef; obj.Data(dataLocIdx).MetaDataDef = newStruct; - + % Trigger ModelChanged event evtData = uiw.event.EventData('DataLocationIndex', dataLocIdx, ... 'SubField', 'MetadataDefiniton', 'OldData', oldStruct, ... @@ -299,7 +297,7 @@ function updateMetaDataDefinitions(obj, newStruct, dataLocIdx) % % % Not needed at the moment % % % obj.notify('DataLocationModified', evtData) end - + function updateSubfolderStructure(obj, newStruct, idx) %updateSubfolderStructure Update the SubfolderStructure struct % @@ -309,23 +307,23 @@ function updateSubfolderStructure(obj, newStruct, idx) if nargin < 3 idx = 1; end - + dataLocationName = obj.Data(idx).Name; - + obj.modifyDataLocation(dataLocationName, ... 'SubfolderStructure', newStruct) - - %oldStruct = obj.Data(idx).SubfolderStructure; - %obj.Data(idx).SubfolderStructure = newStruct; - + + % oldStruct = obj.Data(idx).SubfolderStructure; + % obj.Data(idx).SubfolderStructure = newStruct; + % Update example path subFolderNames = {newStruct.Name}; - + if ~isempty(obj.Data(idx).RootPath) obj.Data(idx).ExamplePath = ... fullfile(obj.Data(idx).RootPath(1).Value, subFolderNames{:}); end - + % % % Trigger ModelChanged event % % evtData = uiw.event.EventData('DataLocationIndex', idx, ... % % 'SubField', 'SubfolderStructure', 'OldData', oldStruct, ... @@ -333,7 +331,7 @@ function updateSubfolderStructure(obj, newStruct, idx) % % % % obj.notify('DataLocationModified', evtData) end - + function dataLocationStructArray = validateDataLocationPaths(obj, dataLocationStructArray) %validateSubfolders Validate subfolders of data locations % @@ -347,19 +345,19 @@ function updateSubfolderStructure(obj, newStruct, idx) % % Todo: Consolidate with session/fixDataLocations if isempty(dataLocationStructArray); return; end - + if isa(dataLocationStructArray, 'cell') dataLocationStructArray = utility.struct.structcat(1, dataLocationStructArray{:}); end - + if ~isfield(dataLocationStructArray, 'Subfolders'); return; end - + % Assume all subfolders are equal... - + [numItems, numDatalocations] = size(dataLocationStructArray); - + for i = 1:numDatalocations - + dlUuid = dataLocationStructArray(1,i).Uuid; dlInfo = obj.getItem(dlUuid); @@ -368,10 +366,10 @@ function updateSubfolderStructure(obj, newStruct, idx) % Update the root directory from the model rootUid = dataLocationStructArray(j, i).RootUid; rootIdx = find( strcmp( {dlInfo.RootPath.Key}, rootUid ) ); - + if ~isempty(rootIdx) rootPathStr = dlInfo.RootPath(rootIdx).Value; - + if ispc % Todo: % Assign correct drive letter. % Check and assign correct drive letter @@ -385,7 +383,7 @@ function updateSubfolderStructure(obj, newStruct, idx) end dataLocationStructArray(j, i).RootIdx = rootIdx; dataLocationStructArray(j, i).Diskname = diskName; - + % Make sure file separators match the file system. iSubfolder = dataLocationStructArray(j,i).Subfolders; if isempty(iSubfolder) @@ -399,25 +397,25 @@ function updateSubfolderStructure(obj, newStruct, idx) end end end - + function updateVolumeInfo(obj) %updateVolumeInfo Update the volume info table obj.LocalRootPathManager.updateVolumeInfo(); obj.Data = obj.LocalRootPathManager.updateRootPathFromDiskName(obj.Data); end end - + methods % Methods for accessing/modifying items - + function addDataLocation(obj, newDataLocation) %addDataLocation Add data location item to data if isempty(newDataLocation.Name) newDataLocation.Name = obj.getNewName(); end - + newDataLocation = obj.insertItem(newDataLocation); - + % Trigger DataLocationAdded event evtData = uiw.event.EventData(... 'NewValue', newDataLocation); @@ -446,15 +444,15 @@ function addDataLocationFromTemplateName(obj, templateName, options) options.Type (1,1) string = missing options.RootDirectory (1,1) string = missing end - + % Will look for template in project and included modules project = nansen.getCurrentProject(); templates = project.getTable('DataLocations'); - + isMatch = templates.Name == templateName; if any(isMatch) dataLocation = table2struct(templates(isMatch, :)); - + if ~ismissing(options.Name) dataLocation.Name = char(options.Name); end @@ -472,7 +470,7 @@ function addDataLocationFromTemplateName(obj, templateName, options) end dataLocation.ExamplePath = ''; dataLocation.DataSubfolders = {}; - + % Remove template props dataLocation = rmfield(dataLocation, ["x_type", "x_version", "DataType"]); @@ -482,27 +480,27 @@ function addDataLocationFromTemplateName(obj, templateName, options) 'No data location templates matching name "%s"', templateName) end end - + function removeDataLocation(obj, dataLocationName) %removeDataLocation Remove data location item from data - + % Todo: Necessary if a undo operation is implemented... - %oldValue = obj.getItem(dataLocationName); + % oldValue = obj.getItem(dataLocationName); [~, idx] = obj.containsItem(dataLocationName); - + obj.removeItem(dataLocationName) - + % Todo: Unset default data location if this was the default % data location - + % Trigger ModelChanged event evtData = uiw.event.EventData(... 'DataLocationIndex', idx, ... 'DataLocationName', dataLocationName); obj.notify('DataLocationRemoved', evtData) end - + function modifyDataLocation(obj, dataLocationName, field, value) %modifyDataLocation Change data field of DataLocation % @@ -513,21 +511,21 @@ function modifyDataLocation(obj, dataLocationName, field, value) % should be the current (old) name. [tf, idx] = obj.containsItem(dataLocationName); - + if ~any(tf) error('DataLocation with name "%s" does not exist', dataLocationName) end - + % Make sure data location type is one of the type enumeration % members: if strcmp(field, 'Type') && ischar(value) value = nansen.config.dloc.DataLocationType(value); % Todo: Make sure default datalocation is still allowed type: end - + oldValue = obj.Data(idx).(field); obj.Data(idx).(field) = value; - + if strcmp(field, 'Name') % Special case if name is changed obj.onDataLocationRenamed(dataLocationName, value) dataLocationName = value; @@ -539,30 +537,30 @@ function modifyDataLocation(obj, dataLocationName, field, value) 'DataField', field, ... 'NewValue', value, ... 'OldValue', oldValue); - + obj.notify('DataLocationModified', evtData) end - + function dataLocationItem = getDefaultDataLocation(obj) %getDefaultDataLocation Get the default datalocation item dataLocationName = obj.DefaultDataLocation; dataLocationItem = obj.getDataLocation(dataLocationName); end - + function S = getDataLocation(obj, dataLocationName) %getDataLocation Get datalocation item by name S = obj.getItem(dataLocationName); end - + function pathStr = getExampleFolderPath(obj, dataLocationName) dataLocation = obj.getItem(dataLocationName); pathStr = dataLocation.ExamplePath; end end - + methods % Methods for getting data descriptions from filepaths - + function substring = getSubjectID(obj, pathStr, dataLocationIndex) % getSubjectID - Extract subject ID from a path string @@ -574,7 +572,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) dataLocationName = obj.Data(dataLocationIndex).Name; substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); end - + function substring = getSessionID(obj, pathStr, dataLocationIndex) % getSessionID - Extract session ID from a path string @@ -586,7 +584,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) dataLocationName = obj.Data(dataLocationIndex).Name; substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); end - + function value = getTime(obj, pathStr, dataLocationIndex) % getTime - Extract experiment time from a path string @@ -597,7 +595,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) S = obj.getMetavariableStruct('Experiment Time', dataLocationIndex); dataLocationName = obj.Data(dataLocationIndex).Name; substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); - + % Convert to datetime type. if isfield(S, 'StringFormat') && ~isempty(S.StringFormat) try @@ -611,18 +609,18 @@ function modifyDataLocation(obj, dataLocationName, field, value) value = substring; end end - + function value = getDate(obj, pathStr, dataLocationIndex) % getDate - Extract experiment date from a path string if nargin < 3 || isempty(dataLocationIndex) dataLocationIndex = 1; end - + S = obj.getMetavariableStruct('Experiment Date', dataLocationIndex); dataLocationName = obj.Data(dataLocationIndex).Name; substring = obj.getSubstringFromFolder(pathStr, S, dataLocationName); - + % Convert to datetime type. if isfield(S, 'StringFormat') && ~isempty(S.StringFormat) value = datetime(substring, 'InputFormat', S.StringFormat); @@ -631,9 +629,9 @@ function modifyDataLocation(obj, dataLocationName, field, value) end end end - + methods % Utility methods - + function dlStruct = expandDataLocationInfo(obj, dlStruct) %expandDataLocation Expand information of data location structure % @@ -642,7 +640,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) % Name : Name of datalocation % Type : Datalocation type % RootPath : Key, Value pair of local rootpath. - + % Todo: Why is this sometimes a cell? if isa(dlStruct, 'cell') @@ -653,7 +651,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) for iDl = 1:numel(dlStruct) %obj.NumDataLocations dlUuid = dlStruct(iDl).Uuid; - + thisDlItem = obj.getItem(dlUuid); % Add name and type fields @@ -671,7 +669,7 @@ function modifyDataLocation(obj, dataLocationName, field, value) end end end - + function dlStruct = reduceDataLocationInfo(~, dlStruct) fieldsToRemove = {'Name', 'Type', 'RootPath'}; @@ -682,22 +680,22 @@ function modifyDataLocation(obj, dataLocationName, field, value) end end end - + methods (Access = ?nansen.config.dloc.DataLocationModelApp) - + function restore(obj, data) obj.Data = data; obj.save() end end - + methods (Access = protected) - + function item = validateItem(obj, item) % Todo... item = validateItem@utility.data.StorableCatalog(obj, item); end - + function S = getMetavariableStruct(obj, varName, dataLocationIdx) %getMetavariableStruct Get metadata struct for given variable % @@ -767,21 +765,20 @@ function restore(obj, data) combinedFolderName, mode, strPattern); end end - + methods (Access = protected) % Override superclass methods - + function S = cleanStructOnSave(obj, S) %cleanStructOnSave DataLocationModel specific changes when saving for i = 1:numel(S.Data) S.Data(i).Type = S.Data(i).Type.Name; end - + % Export local paths and restore original paths using LocalRootPathManager [S.Data, ~] = obj.LocalRootPathManager.exportLocalRootPaths(S.Data, S.Preferences); - end - + function S = modifyStructOnLoad(obj, S) %modifyStructOnLoad DataLocationModel specific changes when loading % @@ -795,18 +792,18 @@ function restore(obj, data) S.Data(i).Type = nansen.config.dloc.DataLocationType(S.Data(i).Type); end end - + if ~isfield(S.Preferences, 'SourceID') S.Preferences.SourceID = utility.system.getComputerName(true); end - + S = obj.updateRootPathDataType(S); % Todo_ temp: remove before release - + % Import local root paths using LocalRootPathManager if isempty(obj.LocalRootPathManager) obj.initializeLocalRootPathManager(); if isempty(obj.LocalRootPathManager) - % If it is still empty, + % If it is still empty, return end end @@ -817,12 +814,12 @@ function restore(obj, data) S.Data = obj.LocalRootPathManager.updateRootPathFromDiskName(S.Data); end end - + methods (Access = private) function onDataLocationRenamed(obj, oldName, newName) obj.assignItemNames() - + % Update value default data location if this was the one that % was renamed.. if strcmp(obj.DefaultDataLocation, oldName) @@ -830,11 +827,11 @@ function onDataLocationRenamed(obj, oldName, newName) end end end - + methods (Access = private) % Internal - + function updateMetadataExtractorFunctionNames(obj) - + % TODO: This should not be hardcoded here. Ideally users can % also add their own variables. variableNames = {'SubjectId', 'SessionId', 'ExperimentDate', 'ExperimentTime'}; @@ -851,7 +848,7 @@ function updateMetadataExtractorFunctionNames(obj) end obj.save() end - + function initializeLocalRootPathManager(obj) import nansen.config.project.ProjectManager localProjectFolder = string(ProjectManager.getProjectPath('current', 'local')); @@ -860,12 +857,12 @@ function initializeLocalRootPathManager(obj) end end end - + methods %(Access = ?nansen.config.project.Project) - + function onProjectRenamed(obj, oldName, newName) % onProjectRenamed - Rename configs that depend on project name - + % Note: Function names for extracting data identifiers % (subjectId, sessionId, experimentData & experimentTime) depend on % the project name @@ -886,10 +883,10 @@ function onProjectRenamed(obj, oldName, newName) end methods (Static) - + function pathString = getDefaultFilePath() %getFilePath Get filepath for loading/saving datalocation settings - + error('NANSEN:DefaultDataLocationNotImplemented', ... ['Please specify a file path for a data location model. ' ... 'There is currently no default data location model.']) @@ -921,11 +918,11 @@ function addTypeAsTableVariable(obj) for i = 1:numel(obj.Data) obj.Data(i).Type = 'recorded'; end - + obj.Data = orderfields(obj.Data, ... [fieldNamesOld(1:2); 'Type'; fieldNamesOld(3:end)]); end - + function addDiskNameToAllRootPaths(obj) for i = 1:numel(obj.Data) obj.Data(i).RootPath = obj.addDiskNameToRootPathStruct(obj.Data(i).RootPath); @@ -948,31 +945,31 @@ function addDiskTypeToAllRootPaths(obj) end end end - + methods (Static) - + function S = updateRootPathDataType(S) % TEMP: Todo: remove %updateRootPathDataType - + % Todo: Should we make struct array instead, with key value % fields and use universal unique ids???? - + % Update root data type from cell array to struct. if numel(S.Data) > 0 if isa(S.Data(1).RootPath, 'cell') for i = 1:numel(S.Data) - + sNew = struct(); for j = 1:numel(S.Data(i).RootPath) sNew(j).Key = nansen.util.getuuid(); sNew(j).Value = S.Data(i).RootPath{j}; sNew(j).Diskname = ''; end - + S.Data(i).RootPath = sNew; end - + elseif isa(S.Data(1).RootPath, 'struct') && ~isfield(S.Data(1).RootPath, 'Key') for i = 1:numel(S.Data) rootKeys = fieldnames(S.Data(i).RootPath); @@ -984,7 +981,7 @@ function addDiskTypeToAllRootPaths(obj) end elseif isa(S.Data(1).RootPath, 'struct') && isempty(S.Data(1).RootPath) return - + elseif isa(S.Data(1).RootPath, 'struct') && isfield(S.Data(1).RootPath, 'Key') && isa(S.Data(1).RootPath(1).Key, 'cell') for i = 1:numel(S.Data) S.Data(i).RootPath(1).Key = nansen.util.getuuid(); @@ -999,7 +996,7 @@ function addDiskTypeToAllRootPaths(obj) function combinedName = combineFolderNamesFromPath( ... pathStr, folderLevels, numSubfolders, separator) - %combineFolderNamesFromPath Collect and join folder names at given levels + % combineFolderNamesFromPath Collect and join folder names at given levels % % Counts subfolder positions backward from the deepest level so % that the indices in folderLevels are independent of the root diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/getBlankItem.m b/code/+nansen/+config/+dloc/@DataLocationModel/getBlankItem.m index a565087a..2ddc859f 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/getBlankItem.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/getBlankItem.m @@ -12,5 +12,4 @@ S.SubfolderStructure = DataLocationModel.getDefaultSubfolderStructure(); S.MetaDataDef = DataLocationModel.getDefaultMetadataStructure(); - end diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/getDefaultItem.m b/code/+nansen/+config/+dloc/@DataLocationModel/getDefaultItem.m index 922e18e3..2d0a2f7c 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/getDefaultItem.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/getDefaultItem.m @@ -17,27 +17,26 @@ S(i) = nansen.config.dloc.DataLocationModel.getBlankItem(); S(i).Name = 'Rawdata'; S(i).Type = nansen.config.dloc.DataLocationType('recorded'); - %S(i).RootPath = {'', ''}; - %S(i).ExamplePath = ''; - %S(i).DataSubfolders = {}; - - %S(i).SubfolderStructure = struct('Name', {}, 'Type', {}, 'Expression', {}, 'IgnoreList', {}); + % S(i).RootPath = {'', ''}; + % S(i).ExamplePath = ''; + % S(i).DataSubfolders = {}; + + % S(i).SubfolderStructure = struct('Name', {}, 'Type', {}, 'Expression', {}, 'IgnoreList', {}); S(i).SubfolderStructure(1).Name = ''; S(i).SubfolderStructure(1).Type = ''; S(i).SubfolderStructure(1).Expression = ''; S(i).SubfolderStructure(1).IgnoreList = {}; - + i = i + 1; S(i) = nansen.config.dloc.DataLocationModel.getBlankItem(); S(i).Name = 'Processed'; S(i).Type = nansen.config.dloc.DataLocationType('processed'); - %S(i).RootPath = {'', ''}; - %S(i).ExamplePath = ''; - %S(i).DataSubfolders = {}; + % S(i).RootPath = {'', ''}; + % S(i).ExamplePath = ''; + % S(i).DataSubfolders = {}; S(i).SubfolderStructure(1).Type = 'Subject'; S(i).SubfolderStructure(2).Type = 'Session'; - + P.DefaultDataLocation = 'Processed'; - end diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/listDataFolders.m b/code/+nansen/+config/+dloc/@DataLocationModel/listDataFolders.m index c4ad8974..968fc290 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/listDataFolders.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/listDataFolders.m @@ -18,7 +18,7 @@ end dataFolders = struct; % Initialize output - + for i = ind rootPath = {obj.Data(i).RootPath.Value}; S = obj.Data(i).SubfolderStructure; diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 088a51bf..338ac32c 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -22,45 +22,45 @@ IsDirty = false; IsAdvancedView = false end - + properties (SetAccess = private) % Todo: make this public when support for changing it is added. DataLocationIndex = 1; %Todo: Select which dloc to use... end - + properties (Access = protected) StringFormat = cell(1, 4); % Store stringformat for each session metadata item. Relevant for date and time. FunctionName = cell(1, 4) Separator = cell(1, 4) % Store folder-level separator for each session metadata item. % Todo: This should be incorporated better, saving directly to the model. end - + properties (Access = private) % Toolbar Components SelectDatalocationDropDownLabel SelectDataLocationDropDown AdvancedOptionsButton end - + methods % Structors function obj = MetadataInitializationUI(dataLocationModel, varargin) %FolderOrganizationUI Construct a FolderOrganizationUI instance - + obj@nansen.config.mixin.HasDataLocationModel(dataLocationModel) - + % Todo: Make it possible to select which datalocation to use.. varargin = [varargin, {'Data', dataLocationModel.Data(1).MetaDataDef}]; obj@applify.apptable(varargin{:}) - + obj.onModelSet() - + % Reset IsDirty flag because it will be triggered when model is % set. obj.IsDirty = false; end end - + methods (Access = protected) % Methods for creation - + function assignDefaultTablePropertyValues(obj) obj.ColumnNames = {'Variable name', 'Select foldername', ... @@ -70,25 +70,25 @@ function assignDefaultTablePropertyValues(obj) obj.RowSpacing = 20; obj.ColumnSpacing = 25; end - + function hRow = createTableRowComponents(obj, rowData, rowNum) - + hRow = struct(); - - %rootPath = mfilename('fullpath') ; - %imgPath = fullfile(rootPath, '_graphics'); - + + % rootPath = mfilename('fullpath') ; + % imgPath = fullfile(rootPath, '_graphics'); + % % Create VariableName label i = 1; [xi, y, wi, h] = obj.getCellPosition(rowNum, i); - + hRow.VariableName = uilabel(obj.TablePanel); hRow.VariableName.Position = [xi y wi h]; hRow.VariableName.FontName = obj.FontName; obj.centerComponent(hRow.VariableName, y) - + hRow.VariableName.Text = rowData.VariableName; - + % % Create Filename Expression edit field i = 2; [xi, y, wi, h] = obj.getCellPosition(rowNum, i); @@ -114,7 +114,7 @@ function assignDefaultTablePropertyValues(obj) hRow.FolderMultiSelector.UserData = struct('FolderItems', {{}}, 'SelectedIndices', []); hRow.FolderMultiSelector.Visible = 'off'; obj.centerComponent(hRow.FolderMultiSelector, y) - + % % Create Togglebutton group for selecting string detection mode i = 3; [xi, y, wi, h] = obj.getCellPosition(rowNum, i); @@ -125,7 +125,7 @@ function assignDefaultTablePropertyValues(obj) hRow.SelectSubstringButton.Text = 'Select Substring...'; hRow.SelectSubstringButton.ButtonPushedFcn = @obj.onSelectSubstringButtonPushed; obj.centerComponent(hRow.SelectSubstringButton, y) - + % Create button group hRow.ButtonGroupStrfindMode = uibuttongroup(obj.TablePanel); hRow.ButtonGroupStrfindMode.BorderType = 'none'; @@ -138,7 +138,7 @@ function assignDefaultTablePropertyValues(obj) @obj.onButtonGroupStrfindModeButtonDown; obj.centerComponent(hRow.ButtonGroupStrfindMode, y) - + % Create ModeButton1 ModeButton1 = uitogglebutton(hRow.ButtonGroupStrfindMode); ModeButton1.Text = 'ind'; @@ -154,7 +154,7 @@ function assignDefaultTablePropertyValues(obj) ModeButton3 = uitogglebutton(hRow.ButtonGroupStrfindMode); ModeButton3.Text = 'func'; ModeButton3.Position = [81 1 41 22]; - + % % Create Editbox for string expression input i = 4; [xi, y, wi, h] = obj.getCellPosition(rowNum, i); @@ -163,17 +163,17 @@ function assignDefaultTablePropertyValues(obj) hRow.StrfindInputEditbox.Position = [xi y wi h]; hRow.StrfindInputEditbox.FontName = obj.FontName; hRow.StrfindInputEditbox.ValueChangedFcn = @obj.onStringInputValueChanged; - + obj.centerComponent(hRow.StrfindInputEditbox, y) hRow.StrfindInputEditbox.Enable = 'on'; - + if ~isempty(rowData.StringDetectInput) hRow.StrfindInputEditbox.Value = rowData.StringDetectInput; end % % Advanced (function) Create buttons for edit/running function import uim.utility.layout.subdividePosition - + [xii, wii] = subdividePosition(xi, wi, [0.5,0.5], 5); hRow.EditFunctionButton = uibutton(obj.TablePanel); hRow.EditFunctionButton.Text = 'Edit'; @@ -201,22 +201,22 @@ function assignDefaultTablePropertyValues(obj) hRow.StrfindResultEditbox.FontName = obj.FontName; obj.centerComponent(hRow.StrfindResultEditbox, y) end - + function createToolbarComponents(obj, hPanel) %createToolbarComponents Create "toolbar" components above table. if nargin < 2; hPanel = obj.Parent.Parent; end - + obj.createAdvancedOptionsButton(hPanel) obj.createDataLocationSelector(hPanel) end - + function toolbarComponents = getToolbarComponents(obj) toolbarComponents = obj.AdvancedOptionsButton; end end - + methods (Access = private) %Callbacks for userinteraction with controls - + function onFolderNameSelectionChanged(obj, src, ~) % Callback for the folder-level dropdown @@ -319,7 +319,7 @@ function onSelectSubstringButtonPushed(obj, src, evt) % Build the combined folder string from the selected levels and % separator — this is the string the pattern is applied to. folderName = obj.getCombinedFolderName(rowNumber); - + % Create a dialog where the user can select a substring from % the foldername hFig = ancestor(src, 'figure'); @@ -337,12 +337,12 @@ function onSelectSubstringButtonPushed(obj, src, evt) % If the variable is date or time, try to convert to % datetime value: if obj.isDateTimeVariable(hRow.VariableName.Text) - + shortName = strrep(hRow.VariableName.Text, 'Experiment', ''); - + substring = obj.getFolderSubString(rowNumber); [dtInFormat, dtOutFormat] = obj.uiGetDateTimeFormat(hRow.VariableName.Text, substring); - + if ~isempty(dtInFormat) try datetimeValue = datetime(substring, 'InputFormat', dtInFormat); @@ -360,21 +360,21 @@ function onSelectSubstringButtonPushed(obj, src, evt) obj.updateStringResult(rowNumber) end end - + obj.IsDirty = true; - + figure(hFig) % Bring uifigure back into focus end function onStringInputValueChanged(obj, src, event) %onStringInputValueChanged Updates result editfield when the string % input/selection indices are modified. - + substring = ''; - + thisDataLocation = obj.DataLocationModel.Data(obj.DataLocationIndex); M = thisDataLocation.MetaDataDef; - + rowNumber = obj.getComponentRowNumber(src); hRow = obj.RowControls(rowNumber); identifierName = obj.getIdNameForRow(rowNumber); @@ -387,7 +387,7 @@ function onStringInputValueChanged(obj, src, event) errorMessage = sprintf('Failed to extract "%s" from folder path. Caused by:\n\n%s', identifierName, ME.message); uialert(hFig, errorMessage, 'String extraction failed') end - + % Convert date/time value if date/time format is available if obj.isDateTimeVariable(M(rowNumber).VariableName) if isa(substring, 'datetime') @@ -410,10 +410,10 @@ function onStringInputValueChanged(obj, src, event) hRow.StrfindResultEditbox.Value = substring; hRow.StrfindResultEditbox.Tooltip = substring; - + obj.IsDirty = true; end - + function onRunFunctionButtonClicked(obj, src, evt) rowNumber = obj.getComponentRowNumber(src); @@ -434,14 +434,14 @@ function onRunFunctionButtonClicked(obj, src, evt) uialert(hFig, message, 'Function missing...','Icon', 'info') return end - + obj.onStringInputValueChanged(src, evt) end function onEditFunctionButtonClicked(obj, src, evt) - + rowNumber = obj.getComponentRowNumber(src); - + identifierName = obj.getIdNameForRow(rowNumber); functionName = createFunctionName(identifierName); % local function @@ -454,7 +454,7 @@ function onEditFunctionButtonClicked(obj, src, evt) if ~isfile(functionFilePath) nansen.config.dloc.createFunctionFromTemplate(dataLocations, identifierName, functionName) - + hFig = ancestor(src, 'figure'); message = sprintf( ['The function "%s" will be opened in MATLAB''s editor. ', ... 'Please update the function so that it extracts the correct value ', ... @@ -463,10 +463,10 @@ function onEditFunctionButtonClicked(obj, src, evt) edit(functionFilePath) else % Todo : - %nansen.config.dloc.updateFunctionTemplate(functionFilePath, dataLocations); + % nansen.config.dloc.updateFunctionTemplate(functionFilePath, dataLocations); edit(functionFilePath) end - + fullFuntionName = utility.path.abspath2funcname(functionFilePath); obj.FunctionName{rowNumber} = fullFuntionName; obj.IsDirty = true; @@ -485,7 +485,7 @@ function onDataLocationSelectionChanged(obj, src, evt) % onDataLocationSelectionChanged - Dropdown value changed callback newInd = obj.DataLocationModel.getItemIndex(evt.Value); - + % Update datalocationmodel with data from ui obj.updateDataLocationModel() @@ -518,9 +518,9 @@ function onDataLocationSelectionChanged(obj, src, evt) thisDataLocation.ExamplePath, S, dataLocationName); end end - + methods % Methods for updating - + function set.DataLocationIndex(obj, newIndex) obj.DataLocationIndex = newIndex; obj.onModelSet() @@ -529,25 +529,25 @@ function onDataLocationSelectionChanged(obj, src, evt) function set.IsDirty(obj, newValue) obj.IsDirty = newValue; end - + function setActive(obj) %setActive Execute actions needed for ui activation % Use if UI is part of an app with tabs, and the tab is selected end - + function setInactive(obj) %setInactive Execute actions needed for ui inactivation % Use if UI is part of an app with tabs, and the tab is unselected obj.updateDataLocationModel() end - + function updateDataLocationModel(obj) %updateDataLocationModel Update DLModel with changes from UI S = obj.getMetaDataDefinitionStruct(); dataLocationIdx = obj.DataLocationIndex; obj.DataLocationModel.updateMetaDataDefinitions(S, dataLocationIdx) end - + function S = getMetaDataDefinitionStruct(obj) %getMetaDataDefinitionStruct Get struct of values from UI controls @@ -563,18 +563,18 @@ function updateDataLocationModel(obj) S(i).FunctionName = obj.FunctionName{i}; end end - + function onModelSet(obj) %onModelSet Callback for when DatalocationModel is set/reset % % % Update control values based on the DataLocationModel - + dlIdx = obj.DataLocationIndex; thisDataLocation = obj.DataLocationModel.Data(dlIdx); - + % Update Items of subfolder dropdown obj.setFolderSelectionItems() - + % Update values of subfolder dropdown based on the metadata % definitions M = thisDataLocation.MetaDataDef; @@ -596,7 +596,7 @@ function onModelSet(obj) end obj.updateFolderSelectionValue(M) - + % Update results for i = 1:obj.NumRows % Update detection mode @@ -676,7 +676,7 @@ function updateFolderSelectionValue(obj, M) % For each subfolder level in the folder organization, there is a % type. If the type matches with the current row, use the index % of that subfolder level as the initial choice. - + itemIdx = 0; switch obj.RowControls(rowNumber).VariableName.Text case 'Subject ID' @@ -700,9 +700,9 @@ function updateFolderSelectionValue(obj, M) itemIdx = 0; end end - + function updateStringResult(obj, rowNumber) - + hRow = obj.RowControls(rowNumber); % Update values in editboxes @@ -735,15 +735,15 @@ function updateDataLocationSelector(obj) end end end - + methods - + function markClean(obj) obj.IsDirty = false; end - + function mode = getStrSearchMode(obj, rowNumber) - + hBtnGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; h = hBtnGroup.SelectedObject; mode = h.Text; @@ -756,33 +756,33 @@ function setStringSearchMode(obj, rowNumber, value) isMatch = strcmp({hButtons.Text}, value); hBtnGroup.SelectedObject = hButtons(isMatch); end - + function strPattern = getStrSearchPattern(obj, rowNumber, mode) - + if nargin < 3 mode = obj.getStrSearchMode(rowNumber); end - + hRow = obj.RowControls(rowNumber); strInd = hRow.StrfindInputEditbox.Value; - + strPattern = strInd; return - + switch lower(mode) - + case 'ind' % strInd = strrep(strInd, '-', ':'); % % strInd = sprintf('[%s]', strInd); % % strPattern = eval(strInd); - + case 'expr' strPattern = strInd; end end - + function indices = getSubfolderLevel(obj, rowNumber) hRow = obj.RowControls(rowNumber); if strcmp(hRow.FolderMultiSelector.Visible, 'on') @@ -795,24 +795,24 @@ function setStringSearchMode(obj, rowNumber, value) end methods % Show/hide advanced options. - + function createDataLocationSelector(obj, hPanel) - + import uim.utility.layout.subdividePosition - + toolbarPosition = obj.getToolbarPosition(); - + dataLocationLabelWidth = 110; dataLocationSelectorWidth = 125; Wl_init = [dataLocationLabelWidth, dataLocationSelectorWidth]; - + % Get component positions for the components on the left [Xl, Wl] = subdividePosition(toolbarPosition(1), ... toolbarPosition(3), Wl_init, 10); - + Y = toolbarPosition(2); - + % Create SelectDatalocationDropDownLabel obj.SelectDatalocationDropDownLabel = uilabel(hPanel); obj.SelectDatalocationDropDownLabel.Position = [Xl(1) Y Wl(1) 22]; @@ -824,15 +824,15 @@ function createDataLocationSelector(obj, hPanel) obj.SelectDataLocationDropDown.ValueChangedFcn = @obj.onDataLocationSelectionChanged; obj.SelectDataLocationDropDown.Position = [Xl(2) Y Wl(2) 22]; obj.SelectDataLocationDropDown.Value = 'Rawdata'; - + obj.updateDataLocationSelector() end function createAdvancedOptionsButton(obj, hPanel) %createAdvancedOptionsButton Create button to toggle advanced options - + buttonSize = [160, 22]; - + toolbarPosition = obj.getToolbarPosition(); location(1) = sum(toolbarPosition([1,3])) - buttonSize(1); location(2) = toolbarPosition(2); @@ -842,13 +842,13 @@ function createAdvancedOptionsButton(obj, hPanel) obj.AdvancedOptionsButton.Position = [location buttonSize]; obj.AdvancedOptionsButton.Text = 'Show Advanced Options...'; end - + function onShowAdvancedOptionsButtonPushed(obj, src, ~) %onShowAdvancedOptionsButtonPushed Button pushed callback % % Toggle the view for advanced options and update the button % label according to button state - + switch src.Text case 'Show Advanced Options...' obj.showAdvancedOptions() @@ -858,9 +858,9 @@ function onShowAdvancedOptionsButtonPushed(obj, src, ~) obj.AdvancedOptionsButton.Text = 'Show Advanced Options...'; end end - + function showAdvancedOptions(obj) - + % Relocate / show header elements obj.setColumnHeaderDisplayMode(true) obj.IsAdvancedView = true; @@ -870,12 +870,12 @@ function showAdvancedOptions(obj) obj.setRowDisplayMode(i, true) obj.setFunctionButtonVisibility(i) end - + drawnow end - + function hideAdvancedOptions(obj) - + % Relocate / show header elements obj.setColumnHeaderDisplayMode(false) obj.IsAdvancedView = false; @@ -885,26 +885,26 @@ function hideAdvancedOptions(obj) obj.setRowDisplayMode(i, false) obj.setFunctionButtonVisibility(i) end - + drawnow end - + function setColumnHeaderDisplayMode(obj, showAdvanced) - + xOffset = sum(obj.ColumnWidths(4))+obj.ColumnSpacing; visibility = 'off'; - + if showAdvanced xOffset = -1 * xOffset; visibility = 'on'; end - + % Relocate / show header elements obj.ColumnHeaderLabels{3}.Position(1) = obj.ColumnHeaderLabels{3}.Position(1) + xOffset; obj.ColumnLabelHelpButton{3}.Position(1) = obj.ColumnLabelHelpButton{3}.Position(1) + xOffset; obj.ColumnHeaderLabels{4}.Visible = visibility; obj.ColumnLabelHelpButton{4}.Visible = visibility; - + if showAdvanced obj.ColumnHeaderLabels{3}.Text = 'Selection mode'; obj.ColumnLabelHelpButton{3}.Tag = 'Selection mode'; @@ -913,19 +913,19 @@ function setColumnHeaderDisplayMode(obj, showAdvanced) obj.ColumnLabelHelpButton{3}.Tag = 'Select string'; end end - + function setRowDisplayMode(obj, rowNum, showAdvanced) - + xOffset = sum(obj.ColumnWidths(4))+obj.ColumnSpacing; visibility = 'off'; visibility_ = 'on'; - + if showAdvanced xOffset = -1 * xOffset; visibility = 'on'; visibility_ = 'off'; end - + hRow = obj.RowControls(rowNum); hRow.FolderNameSelector.Position(3) = hRow.FolderNameSelector.Position(3) + xOffset; hRow.FolderMultiSelector.Position(3) = hRow.FolderMultiSelector.Position(3) + xOffset; @@ -935,11 +935,11 @@ function setRowDisplayMode(obj, rowNum, showAdvanced) hRow.ButtonGroupStrfindMode.Visible = visibility; hRow.StrfindInputEditbox.Visible = visibility; end - + function setFunctionButtonVisibility(obj, rowNumber) - + hRow = obj.RowControls(rowNumber); - + showButtons = strcmp(hRow.ButtonGroupStrfindMode.SelectedObject.Text, 'func') ... && obj.IsAdvancedView; @@ -958,7 +958,7 @@ function setFunctionButtonVisibility(obj, rowNumber) end end end - + methods (Access = protected) % Listener callbacks inherited from HasDataLocationModel function onDataLocationModified(obj, ~, evt) @@ -967,15 +967,15 @@ function onDataLocationModified(obj, ~, evt) % This method is inherited from the HasDataLocationModel % superclass and is triggered by the DataLocationModified event % on the DataLocationModel object - + switch evt.DataField case 'SubfolderStructure' - + % Todo: Should this be more specific? i.e does not need % to invoke this method know when filters change... - + [~, idx] = obj.DataLocationModel.containsItem(evt.DataLocationName); - + % Currently, only the first data location requires an % update of this ui. if idx == obj.DataLocationIndex @@ -996,28 +996,28 @@ function onDataLocationModified(obj, ~, evt) % No change is necessary end end - + function onDataLocationAdded(obj, ~, evt) %onDataLocationAdded Callback for DataLocationModel event % % This method is inherited from the HasDataLocationModel % superclass and is triggered by the DataLocationAdded event on % the DataLocationModel object - + obj.updateDataLocationSelector() end - + function onDataLocationRemoved(obj, ~, evt) %onDataLocationRemoved Callback for DataLocationModel event % % This method is inherited from the HasDataLocationModel % superclass and is triggered by the DataLocationRemoved event on % the DataLocationModel object - + obj.updateDataLocationSelector() end end - + methods (Access = private) function substring = getSubstringFromRowFunction(obj, rowNumber) dlIdx = obj.DataLocationIndex; @@ -1112,7 +1112,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices function [selectedIndices, separator] = showFolderSelectorDialog( ... obj, folderItems, currentIndices, currentSeparator, parentPosition) - %showFolderSelectorDialog Modal dialog for selecting folder levels + % showFolderSelectorDialog Modal dialog for selecting folder levels % % Opens a figure with a multi-select listbox and a separator % field. Returns the selected indices and separator string, or @@ -1173,7 +1173,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end methods (Static, Access = private) - + function identifierName = getIdNameForRow(rowNumber) identifierNames = {'subjectId', 'sessionId', 'experimentDate', 'experimentTime'}; identifierName = identifierNames{rowNumber}; @@ -1182,10 +1182,10 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices function tf = isDateTimeVariable(variableName) tf = contains(variableName, {'Date', 'Time'}); end - + function [inFormat, outFormat] = uiGetDateTimeFormat(variableName, strValue) %uiGetDateTimeFormat Get datetime input and output format - + % Get datetime values for date & time variables. if strcmp(variableName, 'Experiment Date') dlgTitle = 'Enter Date Format'; @@ -1196,10 +1196,10 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices msg = sprintf('Please enter time format for the selected text: "%s". For example: HH-mm-ss.', strValue); outFormat = 'HH:mm:ss'; end - + msg = strjoin({msg, 'See the MATLAB documentation for "datetime" for a full list of examples (type ''doc datetime'' in MATLAB''s Command Window).'}); answer = inputdlg(msg, dlgTitle); - + if ~isempty(answer) && ~isempty(answer{1}) inFormat = answer{1}; else @@ -1208,32 +1208,32 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end function outFormat = getDateTimeOutFormat(variableName) - + if strcmp(variableName, 'Experiment Date') outFormat = 'MMM-dd-yyyy'; elseif strcmp(variableName, 'Experiment Time') outFormat = 'HH:mm:ss'; end end - + function IND = simplifyInd(IND) %simplifyInd Simplify the indices, by joining all subsequent using - %the colon separator, i.e 1 2 3 4 5 -> 1:5 - + % the colon separator, i.e 1 2 3 4 5 -> 1:5 + indOrig = num2str(IND); - + indNew = {}; count = 1; - + finished = false; while ~finished - + % Find number in list which is not increment of previous lastSequenceIdx = find(diff(IND, 2) ~= 0, 1, 'first') + 1; if isempty(lastSequenceIdx) lastSequenceIdx = numel(IND); end - + % Add indices of format first:last to results indNew{count} = sprintf('%d:%d', IND(1), IND(lastSequenceIdx)); @@ -1248,7 +1248,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices % Join sequences IND = strjoin(indNew, ','); - + % Keep the shortest character vector if numel(IND) > indOrig IND = indOrig; @@ -1283,7 +1283,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices function functionName = createFunctionName(identifierName) %Example: subjectId -> getSubjectId - + identifierName(1) = upper(identifierName(1)); functionName = sprintf('get%s', identifierName); end From fca8431212b02e4c63b5d9bcfd20cae32826c56c Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:13:31 +0100 Subject: [PATCH 04/30] =?UTF-8?q?rename:=20simplifyInd=20=E2=86=92=20simpl?= =?UTF-8?q?ifyIndices=20(expand=20abbreviation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 338ac32c..8b067349 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -332,7 +332,7 @@ function onSelectSubstringButtonPushed(obj, src, evt) return % ...Or update data and controls else - hRow.StrfindInputEditbox.Value = obj.simplifyInd(IND); + hRow.StrfindInputEditbox.Value = obj.simplifyIndices(IND); % If the variable is date or time, try to convert to % datetime value: @@ -1216,8 +1216,8 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end end - function IND = simplifyInd(IND) - %simplifyInd Simplify the indices, by joining all subsequent using + function IND = simplifyIndices(IND) + %simplifyIndices Simplify the indices, by joining all subsequent using % the colon separator, i.e 1 2 3 4 5 -> 1:5 indOrig = num2str(IND); From d551fc4a9e7cc5027ef4c86d1a9510a82beb245b Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:13:49 +0100 Subject: [PATCH 05/30] =?UTF-8?q?rename:=20getIdNameForRow=20=E2=86=92=20g?= =?UTF-8?q?etIdentifierNameForRow=20(expand=20abbreviation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 8b067349..03e322bb 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -377,7 +377,7 @@ function onStringInputValueChanged(obj, src, event) rowNumber = obj.getComponentRowNumber(src); hRow = obj.RowControls(rowNumber); - identifierName = obj.getIdNameForRow(rowNumber); + identifierName = obj.getIdentifierNameForRow(rowNumber); try substring = obj.getFolderSubString(rowNumber); @@ -442,7 +442,7 @@ function onEditFunctionButtonClicked(obj, src, evt) rowNumber = obj.getComponentRowNumber(src); - identifierName = obj.getIdNameForRow(rowNumber); + identifierName = obj.getIdentifierNameForRow(rowNumber); functionName = createFunctionName(identifierName); % local function pm = nansen.ProjectManager(); @@ -1174,7 +1174,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices methods (Static, Access = private) - function identifierName = getIdNameForRow(rowNumber) + function identifierName = getIdentifierNameForRow(rowNumber) identifierNames = {'subjectId', 'sessionId', 'experimentDate', 'experimentTime'}; identifierName = identifierNames{rowNumber}; end From 0bbcbcb66b0ec2d3d655f615bbedd166cbd16d57 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:14:09 +0100 Subject: [PATCH 06/30] =?UTF-8?q?rename:=20getFolderSubString=20=E2=86=92?= =?UTF-8?q?=20getFolderSubstring=20(fix=20capitalisation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 03e322bb..1219a2f5 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -340,7 +340,7 @@ function onSelectSubstringButtonPushed(obj, src, evt) shortName = strrep(hRow.VariableName.Text, 'Experiment', ''); - substring = obj.getFolderSubString(rowNumber); + substring = obj.getFolderSubstring(rowNumber); [dtInFormat, dtOutFormat] = obj.uiGetDateTimeFormat(hRow.VariableName.Text, substring); if ~isempty(dtInFormat) @@ -380,7 +380,7 @@ function onStringInputValueChanged(obj, src, event) identifierName = obj.getIdentifierNameForRow(rowNumber); try - substring = obj.getFolderSubString(rowNumber); + substring = obj.getFolderSubstring(rowNumber); catch ME hFig = ancestor(src, 'figure'); substring = 'N/A'; @@ -496,8 +496,8 @@ function onDataLocationSelectionChanged(obj, src, evt) methods % Methods for updating the Result column - function substring = getFolderSubString(obj, rowNumber) - %getFolderSubString Get folder substring based on current UI selections + function substring = getFolderSubstring(obj, rowNumber) + %getFolderSubstring Get folder substring based on current UI selections % % Builds an S struct from the current UI state and delegates the % full extraction to DataLocationModel.getSubstringFromFolder. @@ -706,7 +706,7 @@ function updateStringResult(obj, rowNumber) hRow = obj.RowControls(rowNumber); % Update values in editboxes - substring = obj.getFolderSubString(rowNumber); + substring = obj.getFolderSubstring(rowNumber); hRow.StrfindResultEditbox.Value = char( substring ); hRow.StrfindResultEditbox.Tooltip = char( substring ); From 3c6d90282236e803558c797484a1f715363069e4 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:14:35 +0100 Subject: [PATCH 07/30] =?UTF-8?q?rename:=20onButtonGroupStrfindModeButtonD?= =?UTF-8?q?own=20=E2=86=92=20onStrfindModeSelectionChanged=20(name=20match?= =?UTF-8?q?es=20what=20the=20callback=20actually=20does)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 1219a2f5..8b2b6c81 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -133,9 +133,9 @@ function assignDefaultTablePropertyValues(obj) hRow.ButtonGroupStrfindMode.Position = [xi y wi h]; hRow.ButtonGroupStrfindMode.FontName = obj.FontName; hRow.ButtonGroupStrfindMode.ButtonDownFcn = ... - @obj.onButtonGroupStrfindModeButtonDown; + @obj.onStrfindModeSelectionChanged; hRow.ButtonGroupStrfindMode.SelectionChangedFcn = ... - @obj.onButtonGroupStrfindModeButtonDown; + @obj.onStrfindModeSelectionChanged; obj.centerComponent(hRow.ButtonGroupStrfindMode, y) @@ -472,8 +472,8 @@ function onEditFunctionButtonClicked(obj, src, evt) obj.IsDirty = true; end - function onButtonGroupStrfindModeButtonDown(obj, src, evt) - % onButtonGroupStrfindModeButtonDown - Selection changed callback + function onStrfindModeSelectionChanged(obj, src, evt) + % onStrfindModeSelectionChanged Callback when strfind mode selection changes rowNumber = obj.getComponentRowNumber(src); obj.setFunctionButtonVisibility(rowNumber) From a530646d2176a29558f487f679f6b18837dfeeee Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:14:51 +0100 Subject: [PATCH 08/30] =?UTF-8?q?rename:=20fullFuntionName=20=E2=86=92=20f?= =?UTF-8?q?ullFunctionName=20(fix=20typo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 8b2b6c81..b45ac278 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -467,8 +467,8 @@ function onEditFunctionButtonClicked(obj, src, evt) edit(functionFilePath) end - fullFuntionName = utility.path.abspath2funcname(functionFilePath); - obj.FunctionName{rowNumber} = fullFuntionName; + fullFunctionName = utility.path.abspath2funcname(functionFilePath); + obj.FunctionName{rowNumber} = fullFunctionName; obj.IsDirty = true; end From fcdd25554035b37ceae0eec67defa346643c3d14 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:14:59 +0100 Subject: [PATCH 09/30] =?UTF-8?q?rename:=20existFunction=20=E2=86=92=20fun?= =?UTF-8?q?ctionExists=20(predicate=20reads=20more=20naturally)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index b45ac278..2b35d9d4 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -421,13 +421,13 @@ function onRunFunctionButtonClicked(obj, src, evt) try feval(obj.FunctionName{rowNumber}, '', '') catch ME - existFunction = ~strcmp(ME.identifier, 'MATLAB:UndefinedFunction'); + functionExists = ~strcmp(ME.identifier, 'MATLAB:UndefinedFunction'); end else - existFunction = false; + functionExists = false; end - if ~existFunction + if ~functionExists hFig = ancestor(src, 'figure'); message = sprintf( ['The function does not exist yet. ', ... 'Please press "Edit" to initialize the function from a template.']); From c5cfecd6ea3dc8b3bfa0be2e29c5531db058d1ca Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:15:28 +0100 Subject: [PATCH 10/30] =?UTF-8?q?rename:=20getStrSearch*=20=E2=86=92=20get?= =?UTF-8?q?StringSearch*=20(harmonise=20abbreviated=20prefix=20with=20setS?= =?UTF-8?q?tringSearchMode)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 2b35d9d4..de95052b 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -506,8 +506,8 @@ function onDataLocationSelectionChanged(obj, src, evt) thisDataLocation = obj.DataLocationModel.Data(dlIdx); S = struct(); - S.StringDetectMode = obj.getStrSearchMode(rowNumber); - S.StringDetectInput = obj.getStrSearchPattern(rowNumber, S.StringDetectMode); + S.StringDetectMode = obj.getStringSearchMode(rowNumber); + S.StringDetectInput = obj.getStringSearchPattern(rowNumber, S.StringDetectMode); S.SubfolderLevel = obj.getSubfolderLevel(rowNumber); S.Separator = obj.Separator{rowNumber}; S.NumSubfolders = numel(thisDataLocation.SubfolderStructure); @@ -555,8 +555,8 @@ function updateDataLocationModel(obj) % Retrieve values from controls and add to struct for i = 1:obj.NumRows - S(i).StringDetectMode = obj.getStrSearchMode(i); - S(i).StringDetectInput = obj.getStrSearchPattern(i); + S(i).StringDetectMode = obj.getStringSearchMode(i); + S(i).StringDetectInput = obj.getStringSearchPattern(i); S(i).SubfolderLevel = obj.getSubfolderLevel(i); S(i).StringFormat = obj.StringFormat{i}; S(i).Separator = obj.Separator{i}; @@ -742,7 +742,7 @@ function markClean(obj) obj.IsDirty = false; end - function mode = getStrSearchMode(obj, rowNumber) + function mode = getStringSearchMode(obj, rowNumber) hBtnGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; h = hBtnGroup.SelectedObject; @@ -757,10 +757,10 @@ function setStringSearchMode(obj, rowNumber, value) hBtnGroup.SelectedObject = hButtons(isMatch); end - function strPattern = getStrSearchPattern(obj, rowNumber, mode) + function strPattern = getStringSearchPattern(obj, rowNumber, mode) if nargin < 3 - mode = obj.getStrSearchMode(rowNumber); + mode = obj.getStringSearchMode(rowNumber); end hRow = obj.RowControls(rowNumber); @@ -1035,7 +1035,7 @@ function exitFuncModeIfActive(obj, rowNumber) % Switching back to ind is the predictable default; the user can % re-select expr or func manually if needed. - if strcmp(obj.getStrSearchMode(rowNumber), 'func') + if strcmp(obj.getStringSearchMode(rowNumber), 'func') obj.setStringSearchMode(rowNumber, 'ind') obj.setFunctionButtonVisibility(rowNumber) end From f72c3764d67730ab89188c31964d3ecf42958230 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:15:36 +0100 Subject: [PATCH 11/30] =?UTF-8?q?rename:=20hBtnGroup=20=E2=86=92=20buttonG?= =?UTF-8?q?roup=20(expand=20abbreviation=20in=20local=20variables)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index de95052b..2df89bca 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -744,17 +744,17 @@ function markClean(obj) function mode = getStringSearchMode(obj, rowNumber) - hBtnGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; - h = hBtnGroup.SelectedObject; + buttonGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; + h = buttonGroup.SelectedObject; mode = h.Text; end function setStringSearchMode(obj, rowNumber, value) - hBtnGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; - hButtons = hBtnGroup.Children; + buttonGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; + hButtons = buttonGroup.Children; isMatch = strcmp({hButtons.Text}, value); - hBtnGroup.SelectedObject = hButtons(isMatch); + buttonGroup.SelectedObject = hButtons(isMatch); end function strPattern = getStringSearchPattern(obj, rowNumber, mode) From 9b534e89736e46d2e1d51f21798192b0de33a02c Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:16:36 +0100 Subject: [PATCH 12/30] =?UTF-8?q?rename:=20M=20=E2=86=92=20metadataDef=20(?= =?UTF-8?q?replace=20single-letter=20variable=20with=20descriptive=20name)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 2df89bca..cacd0e40 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -373,7 +373,7 @@ function onStringInputValueChanged(obj, src, event) substring = ''; thisDataLocation = obj.DataLocationModel.Data(obj.DataLocationIndex); - M = thisDataLocation.MetaDataDef; + metadataDef = thisDataLocation.MetaDataDef; rowNumber = obj.getComponentRowNumber(src); hRow = obj.RowControls(rowNumber); @@ -389,13 +389,13 @@ function onStringInputValueChanged(obj, src, event) end % Convert date/time value if date/time format is available - if obj.isDateTimeVariable(M(rowNumber).VariableName) + if obj.isDateTimeVariable(metadataDef(rowNumber).VariableName) if isa(substring, 'datetime') substring = char(substring); else examplePath = thisDataLocation.ExamplePath; try - switch M(rowNumber).VariableName + switch metadataDef(rowNumber).VariableName case 'Experiment Time' value = obj.DataLocationModel.getTime(examplePath, obj.DataLocationIndex); case 'Experiment Date' @@ -577,35 +577,35 @@ function onModelSet(obj) % Update values of subfolder dropdown based on the metadata % definitions - M = thisDataLocation.MetaDataDef; + metadataDef = thisDataLocation.MetaDataDef; - % Update internal values from M + % Update internal values from metadataDef for i = 1:obj.NumRows % Set stringformat from datalocation model. - obj.StringFormat{i} = thisDataLocation.MetaDataDef(i).StringFormat; - if isfield(thisDataLocation.MetaDataDef(i), 'Separator') - obj.Separator{i} = thisDataLocation.MetaDataDef(i).Separator; + obj.StringFormat{i} = metadataDef(i).StringFormat; + if isfield(metadataDef(i), 'Separator') + obj.Separator{i} = metadataDef(i).Separator; else obj.Separator{i} = ''; end try - obj.FunctionName{i} = thisDataLocation.MetaDataDef(i).FunctionName; + obj.FunctionName{i} = metadataDef(i).FunctionName; catch obj.FunctionName{i} = ''; end end - obj.updateFolderSelectionValue(M) + obj.updateFolderSelectionValue(metadataDef) % Update results for i = 1:obj.NumRows % Update detection mode - obj.setStringSearchMode(i, M(i).StringDetectMode) + obj.setStringSearchMode(i, metadataDef(i).StringDetectMode) obj.setFunctionButtonVisibility(i) hComp = obj.RowControls(i).StrfindInputEditbox; % Update value in string detection input - hComp.Value = M(i).StringDetectInput; + hComp.Value = metadataDef(i).StringDetectInput; obj.onStringInputValueChanged(hComp) end end @@ -636,7 +636,7 @@ function setFolderSelectionItems(obj) end end - function updateFolderSelectionValue(obj, M) + function updateFolderSelectionValue(obj, metadataDef) %updateFolderSelectionValue Restore the folder selection controls from the model dlIdx = obj.DataLocationIndex; @@ -646,7 +646,7 @@ function updateFolderSelectionValue(obj, M) for i = 1:obj.NumRows folderItems = obj.RowControls(i).FolderNameSelector.UserData.FolderItems; - itemIdx = M(i).SubfolderLevel; + itemIdx = metadataDef(i).SubfolderLevel; % If there is no selection, try to infer from the data organization. if isempty(itemIdx) @@ -662,7 +662,7 @@ function updateFolderSelectionValue(obj, M) if numel(itemIdx) > 1 separator = ''; - if isfield(M(i), 'Separator'); separator = M(i).Separator; end + if isfield(metadataDef(i), 'Separator'); separator = metadataDef(i).Separator; end obj.switchToMultiSelectMode(i, itemIdx, separator); else obj.switchToSingleSelectMode(i, itemIdx); From d50e8be416fb8ab1559c537f560d010d82f85504 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:16:57 +0100 Subject: [PATCH 13/30] =?UTF-8?q?rename:=20Wl=5Finit/Wl/Xl/Y=20=E2=86=92?= =?UTF-8?q?=20requestedWidths/adjustedWidths/xPositions/yPosition=20(repla?= =?UTF-8?q?ce=20cryptic=20layout=20variables)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index cacd0e40..de047996 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -805,24 +805,24 @@ function createDataLocationSelector(obj, hPanel) dataLocationLabelWidth = 110; dataLocationSelectorWidth = 125; - Wl_init = [dataLocationLabelWidth, dataLocationSelectorWidth]; + requestedWidths = [dataLocationLabelWidth, dataLocationSelectorWidth]; % Get component positions for the components on the left - [Xl, Wl] = subdividePosition(toolbarPosition(1), ... - toolbarPosition(3), Wl_init, 10); + [xPositions, adjustedWidths] = subdividePosition(toolbarPosition(1), ... + toolbarPosition(3), requestedWidths, 10); - Y = toolbarPosition(2); + yPosition = toolbarPosition(2); % Create SelectDatalocationDropDownLabel obj.SelectDatalocationDropDownLabel = uilabel(hPanel); - obj.SelectDatalocationDropDownLabel.Position = [Xl(1) Y Wl(1) 22]; + obj.SelectDatalocationDropDownLabel.Position = [xPositions(1) yPosition adjustedWidths(1) 22]; obj.SelectDatalocationDropDownLabel.Text = 'Select data location:'; % Create SelectDataLocationDropDown obj.SelectDataLocationDropDown = uidropdown(hPanel); obj.SelectDataLocationDropDown.Items = {'Rawdata'}; obj.SelectDataLocationDropDown.ValueChangedFcn = @obj.onDataLocationSelectionChanged; - obj.SelectDataLocationDropDown.Position = [Xl(2) Y Wl(2) 22]; + obj.SelectDataLocationDropDown.Position = [xPositions(2) yPosition adjustedWidths(2) 22]; obj.SelectDataLocationDropDown.Value = 'Rawdata'; obj.updateDataLocationSelector() From a8b3cd7c66dd72b4ac60594bfcd773f78d60abc9 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:17:57 +0100 Subject: [PATCH 14/30] =?UTF-8?q?rename:=20StrfindInputEditbox/StrfindResu?= =?UTF-8?q?ltEditbox=20=E2=86=92=20StringDetectInputField/StringDetectResu?= =?UTF-8?q?ltField=20(expand=20abbreviations,=20align=20with=20data=20fiel?= =?UTF-8?q?d=20names)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also rename hInputEditbox/hComp → inputField (consistent, descriptive local variable name) Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index de047996..fbc8e5e8 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -159,16 +159,16 @@ function assignDefaultTablePropertyValues(obj) i = 4; [xi, y, wi, h] = obj.getCellPosition(rowNum, i); - hRow.StrfindInputEditbox = uieditfield(obj.TablePanel, 'text'); - hRow.StrfindInputEditbox.Position = [xi y wi h]; - hRow.StrfindInputEditbox.FontName = obj.FontName; - hRow.StrfindInputEditbox.ValueChangedFcn = @obj.onStringInputValueChanged; + hRow.StringDetectInputField = uieditfield(obj.TablePanel, 'text'); + hRow.StringDetectInputField.Position = [xi y wi h]; + hRow.StringDetectInputField.FontName = obj.FontName; + hRow.StringDetectInputField.ValueChangedFcn = @obj.onStringInputValueChanged; - obj.centerComponent(hRow.StrfindInputEditbox, y) - hRow.StrfindInputEditbox.Enable = 'on'; + obj.centerComponent(hRow.StringDetectInputField, y) + hRow.StringDetectInputField.Enable = 'on'; if ~isempty(rowData.StringDetectInput) - hRow.StrfindInputEditbox.Value = rowData.StringDetectInput; + hRow.StringDetectInputField.Value = rowData.StringDetectInput; end % % Advanced (function) Create buttons for edit/running function @@ -195,11 +195,11 @@ function assignDefaultTablePropertyValues(obj) i = 5; [xi, y, wi, h] = obj.getCellPosition(rowNum, i); - hRow.StrfindResultEditbox = uieditfield(obj.TablePanel, 'text'); - hRow.StrfindResultEditbox.Position = [xi y wi h]; - hRow.StrfindResultEditbox.Enable = 'off'; - hRow.StrfindResultEditbox.FontName = obj.FontName; - obj.centerComponent(hRow.StrfindResultEditbox, y) + hRow.StringDetectResultField = uieditfield(obj.TablePanel, 'text'); + hRow.StringDetectResultField.Position = [xi y wi h]; + hRow.StringDetectResultField.Enable = 'off'; + hRow.StringDetectResultField.FontName = obj.FontName; + obj.centerComponent(hRow.StringDetectResultField, y) end function createToolbarComponents(obj, hPanel) @@ -332,7 +332,7 @@ function onSelectSubstringButtonPushed(obj, src, evt) return % ...Or update data and controls else - hRow.StrfindInputEditbox.Value = obj.simplifyIndices(IND); + hRow.StringDetectInputField.Value = obj.simplifyIndices(IND); % If the variable is date or time, try to convert to % datetime value: @@ -347,7 +347,7 @@ function onSelectSubstringButtonPushed(obj, src, evt) try datetimeValue = datetime(substring, 'InputFormat', dtInFormat); datetimeValue.Format = dtOutFormat; - hRow.StrfindResultEditbox.Value = char(datetimeValue); + hRow.StringDetectResultField.Value = char(datetimeValue); obj.StringFormat{rowNumber} = dtInFormat; catch ME uialert(hFig, ME.message, sprintf('%s Format Error', shortName)) @@ -408,8 +408,8 @@ function onStringInputValueChanged(obj, src, event) end end - hRow.StrfindResultEditbox.Value = substring; - hRow.StrfindResultEditbox.Tooltip = substring; + hRow.StringDetectResultField.Value = substring; + hRow.StringDetectResultField.Tooltip = substring; obj.IsDirty = true; end @@ -477,8 +477,8 @@ function onStrfindModeSelectionChanged(obj, src, evt) rowNumber = obj.getComponentRowNumber(src); obj.setFunctionButtonVisibility(rowNumber) - hInputEditbox = obj.RowControls(rowNumber).StrfindInputEditbox; - obj.onStringInputValueChanged(hInputEditbox) + inputField = obj.RowControls(rowNumber).StringDetectInputField; + obj.onStringInputValueChanged(inputField) end function onDataLocationSelectionChanged(obj, src, evt) @@ -603,10 +603,10 @@ function onModelSet(obj) obj.setStringSearchMode(i, metadataDef(i).StringDetectMode) obj.setFunctionButtonVisibility(i) - hComp = obj.RowControls(i).StrfindInputEditbox; + inputField = obj.RowControls(i).StringDetectInputField; % Update value in string detection input - hComp.Value = metadataDef(i).StringDetectInput; - obj.onStringInputValueChanged(hComp) + inputField.Value = metadataDef(i).StringDetectInput; + obj.onStringInputValueChanged(inputField) end end @@ -707,8 +707,8 @@ function updateStringResult(obj, rowNumber) % Update values in editboxes substring = obj.getFolderSubstring(rowNumber); - hRow.StrfindResultEditbox.Value = char( substring ); - hRow.StrfindResultEditbox.Tooltip = char( substring ); + hRow.StringDetectResultField.Value = char( substring ); + hRow.StringDetectResultField.Tooltip = char( substring ); if ~isempty( obj.StringFormat{rowNumber} ) dtInFormat = obj.StringFormat{rowNumber}; @@ -718,8 +718,8 @@ function updateStringResult(obj, rowNumber) datetimeValue.Format = dtOutFormat; substring = char(datetimeValue); - hRow.StrfindResultEditbox.Value = substring; - hRow.StrfindResultEditbox.Tooltip = substring; + hRow.StringDetectResultField.Value = substring; + hRow.StringDetectResultField.Tooltip = substring; end end @@ -764,7 +764,7 @@ function setStringSearchMode(obj, rowNumber, value) end hRow = obj.RowControls(rowNumber); - strInd = hRow.StrfindInputEditbox.Value; + strInd = hRow.StringDetectInputField.Value; strPattern = strInd; return @@ -933,7 +933,7 @@ function setRowDisplayMode(obj, rowNum, showAdvanced) hRow.SelectSubstringButton.Visible = visibility_; hRow.ButtonGroupStrfindMode.Visible = visibility; - hRow.StrfindInputEditbox.Visible = visibility; + hRow.StringDetectInputField.Visible = visibility; end function setFunctionButtonVisibility(obj, rowNumber) @@ -946,14 +946,14 @@ function setFunctionButtonVisibility(obj, rowNumber) if showButtons hRow.RunFunctionButton.Visible = 'on'; hRow.EditFunctionButton.Visible = 'on'; - hRow.StrfindInputEditbox.Visible = 'off'; + hRow.StringDetectInputField.Visible = 'off'; else hRow.RunFunctionButton.Visible = 'off'; hRow.EditFunctionButton.Visible = 'off'; if obj.IsAdvancedView - hRow.StrfindInputEditbox.Visible = 'on'; + hRow.StringDetectInputField.Visible = 'on'; else - hRow.StrfindInputEditbox.Visible = 'off'; + hRow.StringDetectInputField.Visible = 'off'; end end end @@ -985,8 +985,8 @@ function onDataLocationModified(obj, ~, evt) % Update result of string indexing based on model... for i = 1:obj.NumRows - hComp = obj.RowControls(i).StrfindInputEditbox; - obj.onStringInputValueChanged(hComp) + inputField = obj.RowControls(i).StringDetectInputField; + obj.onStringInputValueChanged(inputField) end end case 'Name' From 0359391c22674ac1b6002c17fa00fe412288a400 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:18:15 +0100 Subject: [PATCH 15/30] =?UTF-8?q?rename:=20ButtonGroupStrfindMode=20?= =?UTF-8?q?=E2=86=92=20StringDetectModeButtonGroup=20(expand=20abbreviatio?= =?UTF-8?q?n,=20conventional=20type-suffix=20ordering)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index fbc8e5e8..dbfddb09 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -127,31 +127,31 @@ function assignDefaultTablePropertyValues(obj) obj.centerComponent(hRow.SelectSubstringButton, y) % Create button group - hRow.ButtonGroupStrfindMode = uibuttongroup(obj.TablePanel); - hRow.ButtonGroupStrfindMode.BorderType = 'none'; - hRow.ButtonGroupStrfindMode.BackgroundColor = [1 1 1]; - hRow.ButtonGroupStrfindMode.Position = [xi y wi h]; - hRow.ButtonGroupStrfindMode.FontName = obj.FontName; - hRow.ButtonGroupStrfindMode.ButtonDownFcn = ... + hRow.StringDetectModeButtonGroup = uibuttongroup(obj.TablePanel); + hRow.StringDetectModeButtonGroup.BorderType = 'none'; + hRow.StringDetectModeButtonGroup.BackgroundColor = [1 1 1]; + hRow.StringDetectModeButtonGroup.Position = [xi y wi h]; + hRow.StringDetectModeButtonGroup.FontName = obj.FontName; + hRow.StringDetectModeButtonGroup.ButtonDownFcn = ... @obj.onStrfindModeSelectionChanged; - hRow.ButtonGroupStrfindMode.SelectionChangedFcn = ... + hRow.StringDetectModeButtonGroup.SelectionChangedFcn = ... @obj.onStrfindModeSelectionChanged; - obj.centerComponent(hRow.ButtonGroupStrfindMode, y) + obj.centerComponent(hRow.StringDetectModeButtonGroup, y) % Create ModeButton1 - ModeButton1 = uitogglebutton(hRow.ButtonGroupStrfindMode); + ModeButton1 = uitogglebutton(hRow.StringDetectModeButtonGroup); ModeButton1.Text = 'ind'; ModeButton1.Position = [1 1 41 22]; ModeButton1.Value = true; % Create ModeButton2 - ModeButton2 = uitogglebutton(hRow.ButtonGroupStrfindMode); + ModeButton2 = uitogglebutton(hRow.StringDetectModeButtonGroup); ModeButton2.Text = 'expr'; ModeButton2.Position = [41 1 41 22]; % Create ModeButton3 - ModeButton3 = uitogglebutton(hRow.ButtonGroupStrfindMode); + ModeButton3 = uitogglebutton(hRow.StringDetectModeButtonGroup); ModeButton3.Text = 'func'; ModeButton3.Position = [81 1 41 22]; @@ -744,13 +744,13 @@ function markClean(obj) function mode = getStringSearchMode(obj, rowNumber) - buttonGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; + buttonGroup = obj.RowControls(rowNumber).StringDetectModeButtonGroup; h = buttonGroup.SelectedObject; mode = h.Text; end function setStringSearchMode(obj, rowNumber, value) - buttonGroup = obj.RowControls(rowNumber).ButtonGroupStrfindMode; + buttonGroup = obj.RowControls(rowNumber).StringDetectModeButtonGroup; hButtons = buttonGroup.Children; isMatch = strcmp({hButtons.Text}, value); @@ -932,7 +932,7 @@ function setRowDisplayMode(obj, rowNum, showAdvanced) hRow.SelectSubstringButton.Position(1) = hRow.SelectSubstringButton.Position(1) + xOffset; hRow.SelectSubstringButton.Visible = visibility_; - hRow.ButtonGroupStrfindMode.Visible = visibility; + hRow.StringDetectModeButtonGroup.Visible = visibility; hRow.StringDetectInputField.Visible = visibility; end @@ -940,7 +940,7 @@ function setFunctionButtonVisibility(obj, rowNumber) hRow = obj.RowControls(rowNumber); - showButtons = strcmp(hRow.ButtonGroupStrfindMode.SelectedObject.Text, 'func') ... + showButtons = strcmp(hRow.StringDetectModeButtonGroup.SelectedObject.Text, 'func') ... && obj.IsAdvancedView; if showButtons From eb71e84fe0aa226a0903ec420ff08d095741edab Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:18:39 +0100 Subject: [PATCH 16/30] =?UTF-8?q?rename:=20strInd=20=E2=86=92=20inputValue?= =?UTF-8?q?=20in=20getStringSearchPattern=20(name=20reflects=20what=20it?= =?UTF-8?q?=20holds)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index dbfddb09..e5403c10 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -764,22 +764,22 @@ function setStringSearchMode(obj, rowNumber, value) end hRow = obj.RowControls(rowNumber); - strInd = hRow.StringDetectInputField.Value; + inputValue = hRow.StringDetectInputField.Value; - strPattern = strInd; + strPattern = inputValue; return switch lower(mode) case 'ind' -% strInd = strrep(strInd, '-', ':'); +% inputValue = strrep(inputValue, '-', ':'); % -% strInd = sprintf('[%s]', strInd); +% inputValue = sprintf('[%s]', inputValue); % -% strPattern = eval(strInd); +% strPattern = eval(inputValue); case 'expr' - strPattern = strInd; + strPattern = inputValue; end end From 993d566829f70c5c91674d49998ff38032edef4a Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:18:57 +0100 Subject: [PATCH 17/30] =?UTF-8?q?rename:=20indOrig/indNew/lastSequenceIdx?= =?UTF-8?q?=20=E2=86=92=20originalString/simplifiedParts/lastSequenceIndex?= =?UTF-8?q?=20in=20simplifyIndices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index e5403c10..b1f9f4ba 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -1220,25 +1220,25 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices %simplifyIndices Simplify the indices, by joining all subsequent using % the colon separator, i.e 1 2 3 4 5 -> 1:5 - indOrig = num2str(IND); + originalString = num2str(IND); - indNew = {}; + simplifiedParts = {}; count = 1; finished = false; while ~finished % Find number in list which is not increment of previous - lastSequenceIdx = find(diff(IND, 2) ~= 0, 1, 'first') + 1; - if isempty(lastSequenceIdx) - lastSequenceIdx = numel(IND); + lastSequenceIndex = find(diff(IND, 2) ~= 0, 1, 'first') + 1; + if isempty(lastSequenceIndex) + lastSequenceIndex = numel(IND); end % Add indices of format first:last to results - indNew{count} = sprintf('%d:%d', IND(1), IND(lastSequenceIdx)); + simplifiedParts{count} = sprintf('%d:%d', IND(1), IND(lastSequenceIndex)); % Remove all numbers that were part of sequence - IND(1:lastSequenceIdx) = []; + IND(1:lastSequenceIndex) = []; count = count+1; if isempty(IND) @@ -1247,11 +1247,11 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end % Join sequences - IND = strjoin(indNew, ','); + IND = strjoin(simplifiedParts, ','); % Keep the shortest character vector - if numel(IND) > indOrig - IND = indOrig; + if numel(IND) > originalString + IND = originalString; end end From b43edf1b16cf1022d392cdb15336a921552f71b7 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:19:11 +0100 Subject: [PATCH 18/30] =?UTF-8?q?rename:=20newInd=20=E2=86=92=20newIndex?= =?UTF-8?q?=20in=20onDataLocationSelectionChanged=20(expand=20abbreviation?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index b1f9f4ba..20079d72 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -484,13 +484,13 @@ function onStrfindModeSelectionChanged(obj, src, evt) function onDataLocationSelectionChanged(obj, src, evt) % onDataLocationSelectionChanged - Dropdown value changed callback - newInd = obj.DataLocationModel.getItemIndex(evt.Value); + newIndex = obj.DataLocationModel.getItemIndex(evt.Value); % Update datalocationmodel with data from ui obj.updateDataLocationModel() % Change current data location - obj.DataLocationIndex = newInd; + obj.DataLocationIndex = newIndex; end end From c3f782716bba1c0258b5e7fc9e74e3eb4e8897d8 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:20:04 +0100 Subject: [PATCH 19/30] =?UTF-8?q?rename:=20dlIdx=20=E2=86=92=20dataLocatio?= =?UTF-8?q?nIndex,=20IND=20=E2=86=92=20indices/selectedIndices=20(expand?= =?UTF-8?q?=20abbreviations)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 20079d72..5b8e5e25 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -323,16 +323,16 @@ function onSelectSubstringButtonPushed(obj, src, evt) % Create a dialog where the user can select a substring from % the foldername hFig = ancestor(src, 'figure'); - IND = uim.dialog.createStringSelectorDialog(folderName, hFig.Position); + selectedIndices = uim.dialog.createStringSelectorDialog(folderName, hFig.Position); % Return if user canceled... - if isempty(IND) + if isempty(selectedIndices) pause(0.1) figure(hFig) % Bring uifigure back to focus return % ...Or update data and controls else - hRow.StringDetectInputField.Value = obj.simplifyIndices(IND); + hRow.StringDetectInputField.Value = obj.simplifyIndices(selectedIndices); % If the variable is date or time, try to convert to % datetime value: @@ -502,8 +502,8 @@ function onDataLocationSelectionChanged(obj, src, evt) % Builds an S struct from the current UI state and delegates the % full extraction to DataLocationModel.getSubstringFromFolder. - dlIdx = obj.DataLocationIndex; - thisDataLocation = obj.DataLocationModel.Data(dlIdx); + dataLocationIndex = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); S = struct(); S.StringDetectMode = obj.getStringSearchMode(rowNumber); @@ -569,8 +569,8 @@ function onModelSet(obj) % % % Update control values based on the DataLocationModel - dlIdx = obj.DataLocationIndex; - thisDataLocation = obj.DataLocationModel.Data(dlIdx); + dataLocationIndex = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); % Update Items of subfolder dropdown obj.setFolderSelectionItems() @@ -613,8 +613,8 @@ function onModelSet(obj) function setFolderSelectionItems(obj) %setFolderSelectionItems Populate the folder-level dropdown items for each row - dlIdx = obj.DataLocationIndex; - thisDataLocation = obj.DataLocationModel.Data(dlIdx); + dataLocationIndex = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); subFolderStructure = thisDataLocation.SubfolderStructure; folderItems = {subFolderStructure.Name}; @@ -639,8 +639,8 @@ function setFolderSelectionItems(obj) function updateFolderSelectionValue(obj, metadataDef) %updateFolderSelectionValue Restore the folder selection controls from the model - dlIdx = obj.DataLocationIndex; - thisDataLocation = obj.DataLocationModel.Data(dlIdx); + dataLocationIndex = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); subFolderStructure = thisDataLocation.SubfolderStructure; for i = 1:obj.NumRows @@ -1020,8 +1020,8 @@ function onDataLocationRemoved(obj, ~, evt) methods (Access = private) function substring = getSubstringFromRowFunction(obj, rowNumber) - dlIdx = obj.DataLocationIndex; - thisDataLocation = obj.DataLocationModel.Data(dlIdx); + dataLocationIndex = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); pathStr = thisDataLocation.ExamplePath; dataLocationName = thisDataLocation.Name; substring = feval(obj.FunctionName{rowNumber}, pathStr, dataLocationName); @@ -1083,8 +1083,8 @@ function switchToSingleSelectMode(obj, rowNumber, selectedIndex) % Used by onSelectSubstringButtonPushed to show the user the % string that the extraction pattern will be applied to. - dlIdx = obj.DataLocationIndex; - thisDataLocation = obj.DataLocationModel.Data(dlIdx); + dataLocationIndex = obj.DataLocationIndex; + thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); separator = obj.Separator{rowNumber}; combinedName = nansen.config.dloc.DataLocationModel.combineFolderNamesFromPath( ... @@ -1216,11 +1216,11 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end end - function IND = simplifyIndices(IND) + function indices = simplifyIndices(indices) %simplifyIndices Simplify the indices, by joining all subsequent using % the colon separator, i.e 1 2 3 4 5 -> 1:5 - originalString = num2str(IND); + originalString = num2str(indices); simplifiedParts = {}; count = 1; @@ -1229,29 +1229,29 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices while ~finished % Find number in list which is not increment of previous - lastSequenceIndex = find(diff(IND, 2) ~= 0, 1, 'first') + 1; + lastSequenceIndex = find(diff(indices, 2) ~= 0, 1, 'first') + 1; if isempty(lastSequenceIndex) - lastSequenceIndex = numel(IND); + lastSequenceIndex = numel(indices); end % Add indices of format first:last to results - simplifiedParts{count} = sprintf('%d:%d', IND(1), IND(lastSequenceIndex)); + simplifiedParts{count} = sprintf('%d:%d', indices(1), indices(lastSequenceIndex)); % Remove all numbers that were part of sequence - IND(1:lastSequenceIndex) = []; + indices(1:lastSequenceIndex) = []; count = count+1; - if isempty(IND) + if isempty(indices) finished = true; end end % Join sequences - IND = strjoin(simplifiedParts, ','); + indices = strjoin(simplifiedParts, ','); % Keep the shortest character vector - if numel(IND) > originalString - IND = originalString; + if numel(indices) > originalString + indices = originalString; end end From 4847adee1f21553a87f6bcc6817c76e36a80c86c Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 14:21:49 +0100 Subject: [PATCH 20/30] =?UTF-8?q?rename:=20itemIdx/rowNum/dataLocationIdx/?= =?UTF-8?q?pathStr/arrowStr=20=E2=86=92=20selectedItemIndex/rowNumber/data?= =?UTF-8?q?LocationIndex/folderPath/arrowIndicator=20(expand=20abbreviatio?= =?UTF-8?q?ns)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 5b8e5e25..dd5b8342 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -71,7 +71,7 @@ function assignDefaultTablePropertyValues(obj) obj.ColumnSpacing = 25; end - function hRow = createTableRowComponents(obj, rowData, rowNum) + function hRow = createTableRowComponents(obj, rowData, rowNumber) hRow = struct(); @@ -80,7 +80,7 @@ function assignDefaultTablePropertyValues(obj) % % Create VariableName label i = 1; - [xi, y, wi, h] = obj.getCellPosition(rowNum, i); + [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); hRow.VariableName = uilabel(obj.TablePanel); hRow.VariableName.Position = [xi y wi h]; @@ -91,7 +91,7 @@ function assignDefaultTablePropertyValues(obj) % % Create Filename Expression edit field i = 2; - [xi, y, wi, h] = obj.getCellPosition(rowNum, i); + [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); hRow.FolderNameSelector = uidropdown(obj.TablePanel); hRow.FolderNameSelector.BackgroundColor = [1 1 1]; @@ -117,7 +117,7 @@ function assignDefaultTablePropertyValues(obj) % % Create Togglebutton group for selecting string detection mode i = 3; - [xi, y, wi, h] = obj.getCellPosition(rowNum, i); + [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); % Insert dialog button hRow.SelectSubstringButton = uibutton(obj.TablePanel); @@ -157,7 +157,7 @@ function assignDefaultTablePropertyValues(obj) % % Create Editbox for string expression input i = 4; - [xi, y, wi, h] = obj.getCellPosition(rowNum, i); + [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); hRow.StringDetectInputField = uieditfield(obj.TablePanel, 'text'); hRow.StringDetectInputField.Position = [xi y wi h]; @@ -193,7 +193,7 @@ function assignDefaultTablePropertyValues(obj) % % Create Editbox to show detected string. i = 5; - [xi, y, wi, h] = obj.getCellPosition(rowNum, i); + [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); hRow.StringDetectResultField = uieditfield(obj.TablePanel, 'text'); hRow.StringDetectResultField.Position = [xi y wi h]; @@ -544,8 +544,8 @@ function setInactive(obj) function updateDataLocationModel(obj) %updateDataLocationModel Update DLModel with changes from UI S = obj.getMetaDataDefinitionStruct(); - dataLocationIdx = obj.DataLocationIndex; - obj.DataLocationModel.updateMetaDataDefinitions(S, dataLocationIdx) + dataLocationIndex = obj.DataLocationIndex; + obj.DataLocationModel.updateMetaDataDefinitions(S, dataLocationIndex) end function S = getMetaDataDefinitionStruct(obj) @@ -646,58 +646,58 @@ function updateFolderSelectionValue(obj, metadataDef) for i = 1:obj.NumRows folderItems = obj.RowControls(i).FolderNameSelector.UserData.FolderItems; - itemIdx = metadataDef(i).SubfolderLevel; + selectedItemIndex = metadataDef(i).SubfolderLevel; % If there is no selection, try to infer from the data organization. - if isempty(itemIdx) - itemIdx = obj.initFolderSelectionItemIndex(i, subFolderStructure); + if isempty(selectedItemIndex) + selectedItemIndex = obj.initFolderSelectionItemIndex(i, subFolderStructure); end % Clamp to valid range (0 means no selection). - if isscalar(itemIdx) && itemIdx == 0 - itemIdx = []; + if isscalar(selectedItemIndex) && selectedItemIndex == 0 + selectedItemIndex = []; else - itemIdx = itemIdx(itemIdx >= 1 & itemIdx <= numel(folderItems)); + selectedItemIndex = selectedItemIndex(selectedItemIndex >= 1 & selectedItemIndex <= numel(folderItems)); end - if numel(itemIdx) > 1 + if numel(selectedItemIndex) > 1 separator = ''; if isfield(metadataDef(i), 'Separator'); separator = metadataDef(i).Separator; end - obj.switchToMultiSelectMode(i, itemIdx, separator); + obj.switchToMultiSelectMode(i, selectedItemIndex, separator); else - obj.switchToSingleSelectMode(i, itemIdx); + obj.switchToSingleSelectMode(i, selectedItemIndex); end end end - function itemIdx = initFolderSelectionItemIndex(obj, rowNumber, subFolderStructure) + function selectedItemIndex = initFolderSelectionItemIndex(obj, rowNumber, subFolderStructure) %initFolderSelectionItemIndex Guess which index should be selected % % For each subfolder level in the folder organization, there is a % type. If the type matches with the current row, use the index % of that subfolder level as the initial choice. - itemIdx = 0; + selectedItemIndex = 0; switch obj.RowControls(rowNumber).VariableName.Text case 'Subject ID' isMatched = strcmp({subFolderStructure.Type}, 'Subject'); if any(isMatched) - itemIdx = find(isMatched); + selectedItemIndex = find(isMatched); end case 'Session ID' isMatched = strcmp({subFolderStructure.Type}, 'Session'); if any(isMatched) - itemIdx = find(isMatched); + selectedItemIndex = find(isMatched); end case {'Date', 'Experiment Date'} isMatched = strcmp({subFolderStructure.Type}, 'Date'); if any(isMatched) - itemIdx = find(isMatched); + selectedItemIndex = find(isMatched); end case {'Time', 'Experiment Time'} - itemIdx = 0; + selectedItemIndex = 0; otherwise - itemIdx = 0; + selectedItemIndex = 0; end end @@ -914,7 +914,7 @@ function setColumnHeaderDisplayMode(obj, showAdvanced) end end - function setRowDisplayMode(obj, rowNum, showAdvanced) + function setRowDisplayMode(obj, rowNumber, showAdvanced) xOffset = sum(obj.ColumnWidths(4))+obj.ColumnSpacing; visibility = 'off'; @@ -926,7 +926,7 @@ function setRowDisplayMode(obj, rowNum, showAdvanced) visibility_ = 'off'; end - hRow = obj.RowControls(rowNum); + hRow = obj.RowControls(rowNumber); hRow.FolderNameSelector.Position(3) = hRow.FolderNameSelector.Position(3) + xOffset; hRow.FolderMultiSelector.Position(3) = hRow.FolderMultiSelector.Position(3) + xOffset; hRow.SelectSubstringButton.Position(1) = hRow.SelectSubstringButton.Position(1) + xOffset; @@ -1022,9 +1022,9 @@ function onDataLocationRemoved(obj, ~, evt) function substring = getSubstringFromRowFunction(obj, rowNumber) dataLocationIndex = obj.DataLocationIndex; thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); - pathStr = thisDataLocation.ExamplePath; + folderPath = thisDataLocation.ExamplePath; dataLocationName = thisDataLocation.Name; - substring = feval(obj.FunctionName{rowNumber}, pathStr, dataLocationName); + substring = feval(obj.FunctionName{rowNumber}, folderPath, dataLocationName); end function exitFuncModeIfActive(obj, rowNumber) @@ -1101,11 +1101,11 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices hButton.Text = 'Select folder(s)...'; hButton.Tooltip = ''; else - arrowStr = ' ▼'; + arrowIndicator = ' ▼'; fullName = strjoin(folderItems(selectedIndices), ' + '); label = truncateTextForWidth(fullName, hButton.Position(3), ... - hButton.FontSize, arrowStr); - hButton.Text = [label arrowStr]; + hButton.FontSize, arrowIndicator); + hButton.Text = [label arrowIndicator]; hButton.Tooltip = fullName; end end From 57ef1e4eec18827efd696de08db48ef5d09f68f9 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 20:12:09 +0100 Subject: [PATCH 21/30] =?UTF-8?q?rename:=20metadataDef=20=E2=86=92=20metad?= =?UTF-8?q?ataDefinitions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spell out completely --- .../MetadataInitializationUI.m | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index dd5b8342..7584b7f2 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -13,7 +13,7 @@ % [ ] Do the centering when getting the cell locations. % [ ] Set fontsize/bg color and other properties in batch. % -% [ ]Update DL Model whenever new values are entered. - Why??? +% [ ] Update DL Model whenever new values are entered. - Why??? % % [ ] Fix error that will occur if several subfolders are % given the same subfolder type? @@ -366,14 +366,12 @@ function onSelectSubstringButtonPushed(obj, src, evt) figure(hFig) % Bring uifigure back into focus end - function onStringInputValueChanged(obj, src, event) + function onStringInputValueChanged(obj, src, ~) %onStringInputValueChanged Updates result editfield when the string % input/selection indices are modified. - substring = ''; - thisDataLocation = obj.DataLocationModel.Data(obj.DataLocationIndex); - metadataDef = thisDataLocation.MetaDataDef; + metadataDefinitions = thisDataLocation.MetaDataDef; rowNumber = obj.getComponentRowNumber(src); hRow = obj.RowControls(rowNumber); @@ -389,13 +387,13 @@ function onStringInputValueChanged(obj, src, event) end % Convert date/time value if date/time format is available - if obj.isDateTimeVariable(metadataDef(rowNumber).VariableName) + if obj.isDateTimeVariable(metadataDefinitions(rowNumber).VariableName) if isa(substring, 'datetime') substring = char(substring); else examplePath = thisDataLocation.ExamplePath; try - switch metadataDef(rowNumber).VariableName + switch metadataDefinitions(rowNumber).VariableName case 'Experiment Time' value = obj.DataLocationModel.getTime(examplePath, obj.DataLocationIndex); case 'Experiment Date' @@ -577,35 +575,35 @@ function onModelSet(obj) % Update values of subfolder dropdown based on the metadata % definitions - metadataDef = thisDataLocation.MetaDataDef; + metadataDefinitions = thisDataLocation.MetaDataDef; - % Update internal values from metadataDef + % Update internal values from metadataDefinitions for i = 1:obj.NumRows % Set stringformat from datalocation model. - obj.StringFormat{i} = metadataDef(i).StringFormat; - if isfield(metadataDef(i), 'Separator') - obj.Separator{i} = metadataDef(i).Separator; + obj.StringFormat{i} = metadataDefinitions(i).StringFormat; + if isfield(metadataDefinitions(i), 'Separator') + obj.Separator{i} = metadataDefinitions(i).Separator; else obj.Separator{i} = ''; end try - obj.FunctionName{i} = metadataDef(i).FunctionName; + obj.FunctionName{i} = metadataDefinitions(i).FunctionName; catch obj.FunctionName{i} = ''; end end - obj.updateFolderSelectionValue(metadataDef) + obj.updateFolderSelectionValue(metadataDefinitions) % Update results for i = 1:obj.NumRows % Update detection mode - obj.setStringSearchMode(i, metadataDef(i).StringDetectMode) + obj.setStringSearchMode(i, metadataDefinitions(i).StringDetectMode) obj.setFunctionButtonVisibility(i) inputField = obj.RowControls(i).StringDetectInputField; % Update value in string detection input - inputField.Value = metadataDef(i).StringDetectInput; + inputField.Value = metadataDefinitions(i).StringDetectInput; obj.onStringInputValueChanged(inputField) end end @@ -636,7 +634,7 @@ function setFolderSelectionItems(obj) end end - function updateFolderSelectionValue(obj, metadataDef) + function updateFolderSelectionValue(obj, metadataDefinitions) %updateFolderSelectionValue Restore the folder selection controls from the model dataLocationIndex = obj.DataLocationIndex; @@ -646,7 +644,7 @@ function updateFolderSelectionValue(obj, metadataDef) for i = 1:obj.NumRows folderItems = obj.RowControls(i).FolderNameSelector.UserData.FolderItems; - selectedItemIndex = metadataDef(i).SubfolderLevel; + selectedItemIndex = metadataDefinitions(i).SubfolderLevel; % If there is no selection, try to infer from the data organization. if isempty(selectedItemIndex) @@ -662,7 +660,7 @@ function updateFolderSelectionValue(obj, metadataDef) if numel(selectedItemIndex) > 1 separator = ''; - if isfield(metadataDef(i), 'Separator'); separator = metadataDef(i).Separator; end + if isfield(metadataDefinitions(i), 'Separator'); separator = metadataDefinitions(i).Separator; end obj.switchToMultiSelectMode(i, selectedItemIndex, separator); else obj.switchToSingleSelectMode(i, selectedItemIndex); From 21e6ff3c5ecf0fb0876a5aa9c1673608a1f3ccec Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 20:24:54 +0100 Subject: [PATCH 22/30] Update MetadataInitializationUI.m Fixed code issues removed stale code --- .../MetadataInitializationUI.m | 42 ++++++------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 7584b7f2..442ead97 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -309,7 +309,7 @@ function onFolderSelectorButtonPushed(obj, src, ~) obj.IsDirty = true; end - function onSelectSubstringButtonPushed(obj, src, evt) + function onSelectSubstringButtonPushed(obj, src, ~) % Open a dialog window for selecting letter positions. % Get foldername for the row which user pushed button from @@ -436,7 +436,7 @@ function onRunFunctionButtonClicked(obj, src, evt) obj.onStringInputValueChanged(src, evt) end - function onEditFunctionButtonClicked(obj, src, evt) + function onEditFunctionButtonClicked(obj, src, ~) rowNumber = obj.getComponentRowNumber(src); @@ -470,7 +470,7 @@ function onEditFunctionButtonClicked(obj, src, evt) obj.IsDirty = true; end - function onStrfindModeSelectionChanged(obj, src, evt) + function onStrfindModeSelectionChanged(obj, src, ~) % onStrfindModeSelectionChanged Callback when strfind mode selection changes rowNumber = obj.getComponentRowNumber(src); @@ -479,7 +479,7 @@ function onStrfindModeSelectionChanged(obj, src, evt) obj.onStringInputValueChanged(inputField) end - function onDataLocationSelectionChanged(obj, src, evt) + function onDataLocationSelectionChanged(obj, ~, evt) % onDataLocationSelectionChanged - Dropdown value changed callback newIndex = obj.DataLocationModel.getItemIndex(evt.Value); @@ -505,7 +505,7 @@ function onDataLocationSelectionChanged(obj, src, evt) S = struct(); S.StringDetectMode = obj.getStringSearchMode(rowNumber); - S.StringDetectInput = obj.getStringSearchPattern(rowNumber, S.StringDetectMode); + S.StringDetectInput = obj.getStringSearchPattern(rowNumber); S.SubfolderLevel = obj.getSubfolderLevel(rowNumber); S.Separator = obj.Separator{rowNumber}; S.NumSubfolders = numel(thisDataLocation.SubfolderStructure); @@ -528,7 +528,7 @@ function onDataLocationSelectionChanged(obj, src, evt) obj.IsDirty = newValue; end - function setActive(obj) + function setActive(obj) %#ok %setActive Execute actions needed for ui activation % Use if UI is part of an app with tabs, and the tab is selected end @@ -628,7 +628,7 @@ function setFolderSelectionItems(obj) % Build dropdown items — Session ID gets the multi-select option. dropdownItems = ['Select foldername...', folderItems]; if strcmp(hRow.VariableName.Text, 'Session ID') - dropdownItems = [dropdownItems, {'Select multiple folders...'}]; + dropdownItems = [dropdownItems, {'Select multiple folders...'}]; %#ok end hRow.FolderNameSelector.Items = dropdownItems; end @@ -755,30 +755,12 @@ function setStringSearchMode(obj, rowNumber, value) buttonGroup.SelectedObject = hButtons(isMatch); end - function strPattern = getStringSearchPattern(obj, rowNumber, mode) - - if nargin < 3 - mode = obj.getStringSearchMode(rowNumber); - end + function strPattern = getStringSearchPattern(obj, rowNumber) hRow = obj.RowControls(rowNumber); inputValue = hRow.StringDetectInputField.Value; strPattern = inputValue; - return - - switch lower(mode) - - case 'ind' -% inputValue = strrep(inputValue, '-', ':'); -% -% inputValue = sprintf('[%s]', inputValue); -% -% strPattern = eval(inputValue); - - case 'expr' - strPattern = inputValue; - end end function indices = getSubfolderLevel(obj, rowNumber) @@ -995,7 +977,7 @@ function onDataLocationModified(obj, ~, evt) end end - function onDataLocationAdded(obj, ~, evt) + function onDataLocationAdded(obj, ~, ~) %onDataLocationAdded Callback for DataLocationModel event % % This method is inherited from the HasDataLocationModel @@ -1005,7 +987,7 @@ function onDataLocationAdded(obj, ~, evt) obj.updateDataLocationSelector() end - function onDataLocationRemoved(obj, ~, evt) + function onDataLocationRemoved(obj, ~, ~) %onDataLocationRemoved Callback for DataLocationModel event % % This method is inherited from the HasDataLocationModel @@ -1109,7 +1091,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end function [selectedIndices, separator] = showFolderSelectorDialog( ... - obj, folderItems, currentIndices, currentSeparator, parentPosition) + ~, folderItems, currentIndices, currentSeparator, parentPosition) % showFolderSelectorDialog Modal dialog for selecting folder levels % % Opens a figure with a multi-select listbox and a separator @@ -1233,7 +1215,7 @@ function updateFolderSelectorButtonText(~, hButton, folderItems, selectedIndices end % Add indices of format first:last to results - simplifiedParts{count} = sprintf('%d:%d', indices(1), indices(lastSequenceIndex)); + simplifiedParts{count} = sprintf('%d:%d', indices(1), indices(lastSequenceIndex)); %#ok We don't know beforehand how long this array will be % Remove all numbers that were part of sequence indices(1:lastSequenceIndex) = []; From f4dd82c323ac822b2092de1d39e76ce20ffcc992 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 20:56:42 +0100 Subject: [PATCH 23/30] =?UTF-8?q?rename:=20StringDetect*/StringSearch*/Str?= =?UTF-8?q?find*=20=E2=86=92=20StringExtract*=20in=20MetadataInitializatio?= =?UTF-8?q?nUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unifies three inconsistent naming variants for the same UI concept. Model field names (StringDetectMode, StringDetectInput) preserved for backward compatibility. Co-Authored-By: Claude Sonnet 4.6 --- .../MetadataInitializationUI.m | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 442ead97..819e4bff 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -127,31 +127,31 @@ function assignDefaultTablePropertyValues(obj) obj.centerComponent(hRow.SelectSubstringButton, y) % Create button group - hRow.StringDetectModeButtonGroup = uibuttongroup(obj.TablePanel); - hRow.StringDetectModeButtonGroup.BorderType = 'none'; - hRow.StringDetectModeButtonGroup.BackgroundColor = [1 1 1]; - hRow.StringDetectModeButtonGroup.Position = [xi y wi h]; - hRow.StringDetectModeButtonGroup.FontName = obj.FontName; - hRow.StringDetectModeButtonGroup.ButtonDownFcn = ... - @obj.onStrfindModeSelectionChanged; - hRow.StringDetectModeButtonGroup.SelectionChangedFcn = ... - @obj.onStrfindModeSelectionChanged; - - obj.centerComponent(hRow.StringDetectModeButtonGroup, y) + hRow.StringExtractModeButtonGroup = uibuttongroup(obj.TablePanel); + hRow.StringExtractModeButtonGroup.BorderType = 'none'; + hRow.StringExtractModeButtonGroup.BackgroundColor = [1 1 1]; + hRow.StringExtractModeButtonGroup.Position = [xi y wi h]; + hRow.StringExtractModeButtonGroup.FontName = obj.FontName; + hRow.StringExtractModeButtonGroup.ButtonDownFcn = ... + @obj.onStringExtractModeChanged; + hRow.StringExtractModeButtonGroup.SelectionChangedFcn = ... + @obj.onStringExtractModeChanged; + + obj.centerComponent(hRow.StringExtractModeButtonGroup, y) % Create ModeButton1 - ModeButton1 = uitogglebutton(hRow.StringDetectModeButtonGroup); + ModeButton1 = uitogglebutton(hRow.StringExtractModeButtonGroup); ModeButton1.Text = 'ind'; ModeButton1.Position = [1 1 41 22]; ModeButton1.Value = true; % Create ModeButton2 - ModeButton2 = uitogglebutton(hRow.StringDetectModeButtonGroup); + ModeButton2 = uitogglebutton(hRow.StringExtractModeButtonGroup); ModeButton2.Text = 'expr'; ModeButton2.Position = [41 1 41 22]; % Create ModeButton3 - ModeButton3 = uitogglebutton(hRow.StringDetectModeButtonGroup); + ModeButton3 = uitogglebutton(hRow.StringExtractModeButtonGroup); ModeButton3.Text = 'func'; ModeButton3.Position = [81 1 41 22]; @@ -159,16 +159,16 @@ function assignDefaultTablePropertyValues(obj) i = 4; [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); - hRow.StringDetectInputField = uieditfield(obj.TablePanel, 'text'); - hRow.StringDetectInputField.Position = [xi y wi h]; - hRow.StringDetectInputField.FontName = obj.FontName; - hRow.StringDetectInputField.ValueChangedFcn = @obj.onStringInputValueChanged; + hRow.StringExtractInputField = uieditfield(obj.TablePanel, 'text'); + hRow.StringExtractInputField.Position = [xi y wi h]; + hRow.StringExtractInputField.FontName = obj.FontName; + hRow.StringExtractInputField.ValueChangedFcn = @obj.onStringInputValueChanged; - obj.centerComponent(hRow.StringDetectInputField, y) - hRow.StringDetectInputField.Enable = 'on'; + obj.centerComponent(hRow.StringExtractInputField, y) + hRow.StringExtractInputField.Enable = 'on'; if ~isempty(rowData.StringDetectInput) - hRow.StringDetectInputField.Value = rowData.StringDetectInput; + hRow.StringExtractInputField.Value = rowData.StringDetectInput; end % % Advanced (function) Create buttons for edit/running function @@ -195,11 +195,11 @@ function assignDefaultTablePropertyValues(obj) i = 5; [xi, y, wi, h] = obj.getCellPosition(rowNumber, i); - hRow.StringDetectResultField = uieditfield(obj.TablePanel, 'text'); - hRow.StringDetectResultField.Position = [xi y wi h]; - hRow.StringDetectResultField.Enable = 'off'; - hRow.StringDetectResultField.FontName = obj.FontName; - obj.centerComponent(hRow.StringDetectResultField, y) + hRow.StringExtractResultField = uieditfield(obj.TablePanel, 'text'); + hRow.StringExtractResultField.Position = [xi y wi h]; + hRow.StringExtractResultField.Enable = 'off'; + hRow.StringExtractResultField.FontName = obj.FontName; + obj.centerComponent(hRow.StringExtractResultField, y) end function createToolbarComponents(obj, hPanel) @@ -215,7 +215,7 @@ function createToolbarComponents(obj, hPanel) end end - methods (Access = private) %Callbacks for userinteraction with controls + methods (Access = private) % Callbacks for userinteraction with controls function onFolderNameSelectionChanged(obj, src, ~) % Callback for the folder-level dropdown @@ -332,7 +332,7 @@ function onSelectSubstringButtonPushed(obj, src, ~) return % ...Or update data and controls else - hRow.StringDetectInputField.Value = obj.simplifyIndices(selectedIndices); + hRow.StringExtractInputField.Value = obj.simplifyIndices(selectedIndices); % If the variable is date or time, try to convert to % datetime value: @@ -347,7 +347,7 @@ function onSelectSubstringButtonPushed(obj, src, ~) try datetimeValue = datetime(substring, 'InputFormat', dtInFormat); datetimeValue.Format = dtOutFormat; - hRow.StringDetectResultField.Value = char(datetimeValue); + hRow.StringExtractResultField.Value = char(datetimeValue); obj.StringFormat{rowNumber} = dtInFormat; catch ME uialert(hFig, ME.message, sprintf('%s Format Error', shortName)) @@ -406,8 +406,8 @@ function onStringInputValueChanged(obj, src, ~) end end - hRow.StringDetectResultField.Value = substring; - hRow.StringDetectResultField.Tooltip = substring; + hRow.StringExtractResultField.Value = substring; + hRow.StringExtractResultField.Tooltip = substring; obj.IsDirty = true; end @@ -470,12 +470,12 @@ function onEditFunctionButtonClicked(obj, src, ~) obj.IsDirty = true; end - function onStrfindModeSelectionChanged(obj, src, ~) - % onStrfindModeSelectionChanged Callback when strfind mode selection changes + function onStringExtractModeChanged(obj, src, ~) + % onStringExtractModeChanged Callback when string extract mode selection changes rowNumber = obj.getComponentRowNumber(src); obj.setFunctionButtonVisibility(rowNumber) - inputField = obj.RowControls(rowNumber).StringDetectInputField; + inputField = obj.RowControls(rowNumber).StringExtractInputField; obj.onStringInputValueChanged(inputField) end @@ -504,8 +504,8 @@ function onDataLocationSelectionChanged(obj, ~, evt) thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); S = struct(); - S.StringDetectMode = obj.getStringSearchMode(rowNumber); - S.StringDetectInput = obj.getStringSearchPattern(rowNumber); + S.StringDetectMode = obj.getStringExtractMode(rowNumber); + S.StringDetectInput = obj.getStringExtractPattern(rowNumber); S.SubfolderLevel = obj.getSubfolderLevel(rowNumber); S.Separator = obj.Separator{rowNumber}; S.NumSubfolders = numel(thisDataLocation.SubfolderStructure); @@ -553,8 +553,8 @@ function updateDataLocationModel(obj) % Retrieve values from controls and add to struct for i = 1:obj.NumRows - S(i).StringDetectMode = obj.getStringSearchMode(i); - S(i).StringDetectInput = obj.getStringSearchPattern(i); + S(i).StringDetectMode = obj.getStringExtractMode(i); + S(i).StringDetectInput = obj.getStringExtractPattern(i); S(i).SubfolderLevel = obj.getSubfolderLevel(i); S(i).StringFormat = obj.StringFormat{i}; S(i).Separator = obj.Separator{i}; @@ -598,10 +598,10 @@ function onModelSet(obj) % Update results for i = 1:obj.NumRows % Update detection mode - obj.setStringSearchMode(i, metadataDefinitions(i).StringDetectMode) + obj.setStringExtractMode(i, metadataDefinitions(i).StringDetectMode) obj.setFunctionButtonVisibility(i) - inputField = obj.RowControls(i).StringDetectInputField; + inputField = obj.RowControls(i).StringExtractInputField; % Update value in string detection input inputField.Value = metadataDefinitions(i).StringDetectInput; obj.onStringInputValueChanged(inputField) @@ -705,8 +705,8 @@ function updateStringResult(obj, rowNumber) % Update values in editboxes substring = obj.getFolderSubstring(rowNumber); - hRow.StringDetectResultField.Value = char( substring ); - hRow.StringDetectResultField.Tooltip = char( substring ); + hRow.StringExtractResultField.Value = char( substring ); + hRow.StringExtractResultField.Tooltip = char( substring ); if ~isempty( obj.StringFormat{rowNumber} ) dtInFormat = obj.StringFormat{rowNumber}; @@ -716,8 +716,8 @@ function updateStringResult(obj, rowNumber) datetimeValue.Format = dtOutFormat; substring = char(datetimeValue); - hRow.StringDetectResultField.Value = substring; - hRow.StringDetectResultField.Tooltip = substring; + hRow.StringExtractResultField.Value = substring; + hRow.StringExtractResultField.Tooltip = substring; end end @@ -740,25 +740,25 @@ function markClean(obj) obj.IsDirty = false; end - function mode = getStringSearchMode(obj, rowNumber) + function mode = getStringExtractMode(obj, rowNumber) - buttonGroup = obj.RowControls(rowNumber).StringDetectModeButtonGroup; + buttonGroup = obj.RowControls(rowNumber).StringExtractModeButtonGroup; h = buttonGroup.SelectedObject; mode = h.Text; end - function setStringSearchMode(obj, rowNumber, value) - buttonGroup = obj.RowControls(rowNumber).StringDetectModeButtonGroup; + function setStringExtractMode(obj, rowNumber, value) + buttonGroup = obj.RowControls(rowNumber).StringExtractModeButtonGroup; hButtons = buttonGroup.Children; isMatch = strcmp({hButtons.Text}, value); buttonGroup.SelectedObject = hButtons(isMatch); end - function strPattern = getStringSearchPattern(obj, rowNumber) + function strPattern = getStringExtractPattern(obj, rowNumber) hRow = obj.RowControls(rowNumber); - inputValue = hRow.StringDetectInputField.Value; + inputValue = hRow.StringExtractInputField.Value; strPattern = inputValue; end @@ -912,28 +912,28 @@ function setRowDisplayMode(obj, rowNumber, showAdvanced) hRow.SelectSubstringButton.Position(1) = hRow.SelectSubstringButton.Position(1) + xOffset; hRow.SelectSubstringButton.Visible = visibility_; - hRow.StringDetectModeButtonGroup.Visible = visibility; - hRow.StringDetectInputField.Visible = visibility; + hRow.StringExtractModeButtonGroup.Visible = visibility; + hRow.StringExtractInputField.Visible = visibility; end function setFunctionButtonVisibility(obj, rowNumber) hRow = obj.RowControls(rowNumber); - showButtons = strcmp(hRow.StringDetectModeButtonGroup.SelectedObject.Text, 'func') ... + showButtons = strcmp(hRow.StringExtractModeButtonGroup.SelectedObject.Text, 'func') ... && obj.IsAdvancedView; if showButtons hRow.RunFunctionButton.Visible = 'on'; hRow.EditFunctionButton.Visible = 'on'; - hRow.StringDetectInputField.Visible = 'off'; + hRow.StringExtractInputField.Visible = 'off'; else hRow.RunFunctionButton.Visible = 'off'; hRow.EditFunctionButton.Visible = 'off'; if obj.IsAdvancedView - hRow.StringDetectInputField.Visible = 'on'; + hRow.StringExtractInputField.Visible = 'on'; else - hRow.StringDetectInputField.Visible = 'off'; + hRow.StringExtractInputField.Visible = 'off'; end end end @@ -965,7 +965,7 @@ function onDataLocationModified(obj, ~, evt) % Update result of string indexing based on model... for i = 1:obj.NumRows - inputField = obj.RowControls(i).StringDetectInputField; + inputField = obj.RowControls(i).StringExtractInputField; obj.onStringInputValueChanged(inputField) end end @@ -1015,8 +1015,8 @@ function exitFuncModeIfActive(obj, rowNumber) % Switching back to ind is the predictable default; the user can % re-select expr or func manually if needed. - if strcmp(obj.getStringSearchMode(rowNumber), 'func') - obj.setStringSearchMode(rowNumber, 'ind') + if strcmp(obj.getStringExtractMode(rowNumber), 'func') + obj.setStringExtractMode(rowNumber, 'ind') obj.setFunctionButtonVisibility(rowNumber) end end From 66af233c372eea994eaf85b6c68dd040875b2ecd Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 21:12:03 +0100 Subject: [PATCH 24/30] refactor: move subfolder-level inference from MetadataInitializationUI to DataLocationModel Adds DataLocationModel.getDefaultSubfolderLevelForVariable() as a public static method, encoding the mapping between metadata variable names and subfolder types. Removes the private initFolderSelectionItemIndex() from MetadataInitializationUI and updates the call site to use the model method, passing variableName from metadataDefinitions instead of reading from a UI control label. Also fixes a latent bug where find(isMatched) without a limit returned multiple indices when duplicate subfolder types existed. Co-Authored-By: Claude Sonnet 4.6 --- .../@DataLocationModel/DataLocationModel.m | 35 ++++++++++++++++++ .../MetadataInitializationUI.m | 37 ++----------------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 7bddbf05..434c4995 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -992,6 +992,41 @@ function addDiskTypeToAllRootPaths(obj) end end + methods (Static) % Methods for inferring default metadata configuration + + function subfolderIndex = getDefaultSubfolderLevelForVariable(variableName, subfolderStructure) + %getDefaultSubfolderLevelForVariable Infer the default subfolder level for a metadata variable + % + % subfolderIndex = getDefaultSubfolderLevelForVariable(variableName, subfolderStructure) + % returns the index into subfolderStructure whose Type matches the + % semantic role of variableName, or 0 if no match is found. + % + % This encodes the mapping between metadata variable names + % ('Subject ID', 'Session ID', 'Experiment Date') and subfolder + % types ('Subject', 'Session', 'Date') as defined by the + % SubfolderStructure of a data location. + + arguments + variableName (1,:) char + subfolderStructure struct + end + + switch variableName + case 'Subject ID' + subfolderIndex = find(strcmp({subfolderStructure.Type}, 'Subject'), 1); + case 'Session ID' + subfolderIndex = find(strcmp({subfolderStructure.Type}, 'Session'), 1); + case {'Date', 'Experiment Date'} + subfolderIndex = find(strcmp({subfolderStructure.Type}, 'Date'), 1); + otherwise + subfolderIndex = []; + end + if isempty(subfolderIndex) + subfolderIndex = 0; + end + end + end + methods (Static, Hidden) % Low-level extraction utilities function combinedName = combineFolderNamesFromPath( ... diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 819e4bff..f5aa7bf8 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -15,8 +15,6 @@ % % [ ] Update DL Model whenever new values are entered. - Why??? % -% [ ] Fix error that will occur if several subfolders are -% given the same subfolder type? properties IsDirty = false; @@ -648,7 +646,9 @@ function updateFolderSelectionValue(obj, metadataDefinitions) % If there is no selection, try to infer from the data organization. if isempty(selectedItemIndex) - selectedItemIndex = obj.initFolderSelectionItemIndex(i, subFolderStructure); + variableName = metadataDefinitions(i).VariableName; + selectedItemIndex = nansen.config.dloc.DataLocationModel.getDefaultSubfolderLevelForVariable( ... + variableName, subFolderStructure); end % Clamp to valid range (0 means no selection). @@ -668,37 +668,6 @@ function updateFolderSelectionValue(obj, metadataDefinitions) end end - function selectedItemIndex = initFolderSelectionItemIndex(obj, rowNumber, subFolderStructure) - %initFolderSelectionItemIndex Guess which index should be selected - % - % For each subfolder level in the folder organization, there is a - % type. If the type matches with the current row, use the index - % of that subfolder level as the initial choice. - - selectedItemIndex = 0; - switch obj.RowControls(rowNumber).VariableName.Text - case 'Subject ID' - isMatched = strcmp({subFolderStructure.Type}, 'Subject'); - if any(isMatched) - selectedItemIndex = find(isMatched); - end - case 'Session ID' - isMatched = strcmp({subFolderStructure.Type}, 'Session'); - if any(isMatched) - selectedItemIndex = find(isMatched); - end - case {'Date', 'Experiment Date'} - isMatched = strcmp({subFolderStructure.Type}, 'Date'); - if any(isMatched) - selectedItemIndex = find(isMatched); - end - case {'Time', 'Experiment Time'} - selectedItemIndex = 0; - otherwise - selectedItemIndex = 0; - end - end - function updateStringResult(obj, rowNumber) hRow = obj.RowControls(rowNumber); From c3499a8863564b21cc11d974a1be24144c1db88b Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 21:20:22 +0100 Subject: [PATCH 25/30] Update MetadataInitializationUI.m Update docstring Fix bug with uninitialized boolean --- .../MetadataInitializationUI.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index f5aa7bf8..20e26448 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -1,12 +1,16 @@ classdef MetadataInitializationUI < applify.apptable & nansen.config.mixin.HasDataLocationModel -% Class interface for editing metadata specifications in a uifigure +% MetadataInitializationUI - UI panel for configuring session metadata extraction rules % +% Provides a table-based interface where the user defines how each +% session metadata variable (Subject ID, Session ID, Experiment Date, +% Experiment Time) is extracted from a folder path string. Three +% extraction modes are supported: index-based substring selection, +% regular expression matching, and a custom MATLAB function. % +% The panel reads its initial state from a DataLocationModel and writes +% back to it when the user applies changes. A toolbar dropdown allows +% switching between data locations when multiple are configured. -% Note: The data in this ui will only depend on the first datalocation. It -% might be an idea to let the user select which data location to use for -% detecting session information, but for simplicity the first data location -% is used. % Todo: Simplify component creation. % [ ] Get cell locations as array with one entry for each column of a row. @@ -413,14 +417,14 @@ function onStringInputValueChanged(obj, src, ~) function onRunFunctionButtonClicked(obj, src, evt) rowNumber = obj.getComponentRowNumber(src); + functionExists = false; if ~isempty(obj.FunctionName{rowNumber}) try feval(obj.FunctionName{rowNumber}, '', '') + functionExists = true; catch ME functionExists = ~strcmp(ME.identifier, 'MATLAB:UndefinedFunction'); end - else - functionExists = false; end if ~functionExists From 47727738fd80ad97fb08c7ff6830587b845a11b1 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 21:23:39 +0100 Subject: [PATCH 26/30] Update DataLocationModel.m Fixed static method calls --- .../+config/+dloc/@DataLocationModel/DataLocationModel.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 434c4995..6dea3908 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -758,10 +758,10 @@ function restore(obj, data) separator = ''; if isfield(S, 'Separator'); separator = S.Separator; end - combinedFolderName = DataLocationModel.combineFolderNamesFromPath( ... + combinedFolderName = nansen.config.dloc.DataLocationModel.combineFolderNamesFromPath( ... pathStr, folderLevels, S.NumSubfolders, separator); - substring = DataLocationModel.applyExtractionPattern( ... + substring = nansen.config.dloc.DataLocationModel.applyExtractionPattern( ... combinedFolderName, mode, strPattern); end end From 097740f4fb47c58e262d2fba99dac5a2015d47fb Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 21:43:17 +0100 Subject: [PATCH 27/30] fix: proper error handling for missing metadata extraction functions DataLocationModel.getSubstringFromFolder now throws a typed error (NANSEN:DataLocationModel:FunctionNotFound) instead of propagating the raw MATLAB:UndefinedFunction when a func-mode extraction function is missing. MetadataInitializationUI catches this error in getFolderSubstring and delegates to a new private alertFunctionNotFound method, which also replaces the inline alert in onRunFunctionButtonClicked. The alert now includes the variable name (e.g. "Subject ID") for clearer user feedback. Also fixes an uninitialized functionExists variable in onRunFunctionButtonClicked. Co-Authored-By: Claude Sonnet 4.6 --- .../@DataLocationModel/DataLocationModel.m | 13 ++++++++- .../MetadataInitializationUI.m | 28 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 6dea3908..40a6aace 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -742,7 +742,18 @@ function restore(obj, data) mode = S.StringDetectMode; if strcmp(mode, 'func') - substring = feval(S.FunctionName, pathStr, dataLocationName); + try + substring = feval(S.FunctionName, pathStr, dataLocationName); + catch ME + switch ME.identifier + case 'MATLAB:UndefinedFunction' + error('NANSEN:DataLocationModel:FunctionNotFound', ... + 'The metadata extraction function "%s" was not found on the MATLAB path.', ... + S.FunctionName) + otherwise + rethrow(ME) + end + end % nb: substring could be a datetime value if ischar(substring) && strcmp(substring, 'N/A'); substring = ''; end return diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 20e26448..546e17bb 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -428,10 +428,7 @@ function onRunFunctionButtonClicked(obj, src, evt) end if ~functionExists - hFig = ancestor(src, 'figure'); - message = sprintf( ['The function does not exist yet. ', ... - 'Please press "Edit" to initialize the function from a template.']); - uialert(hFig, message, 'Function missing...','Icon', 'info') + obj.alertFunctionNotFound(rowNumber) return end @@ -514,8 +511,18 @@ function onDataLocationSelectionChanged(obj, ~, evt) S.FunctionName = obj.FunctionName{rowNumber}; dataLocationName = thisDataLocation.Name; - substring = obj.DataLocationModel.getSubstringFromFolder( ... - thisDataLocation.ExamplePath, S, dataLocationName); + substring = ''; + try + substring = obj.DataLocationModel.getSubstringFromFolder( ... + thisDataLocation.ExamplePath, S, dataLocationName); + catch ME + switch ME.identifier + case 'NANSEN:DataLocationModel:FunctionNotFound' + obj.alertFunctionNotFound(rowNumber) + otherwise + rethrow(ME) + end + end end end @@ -972,6 +979,15 @@ function onDataLocationRemoved(obj, ~, ~) end methods (Access = private) + function alertFunctionNotFound(obj, rowNumber) + %alertFunctionNotFound Show a uialert when the extraction function is missing + hFig = ancestor(obj.RowControls(rowNumber).VariableName, 'figure'); + variableName = obj.RowControls(rowNumber).VariableName.Text; + message = sprintf(['A function for extracting "%s" does not exist yet. ' ... + 'Please press "Edit" to initialize the function from a template.'], variableName); + uialert(hFig, message, 'Function missing...', 'Icon', 'info') + end + function substring = getSubstringFromRowFunction(obj, rowNumber) dataLocationIndex = obj.DataLocationIndex; thisDataLocation = obj.DataLocationModel.Data(dataLocationIndex); From 55b8f20465475297c11e4f9eea5cd5071532ec78 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 22:20:05 +0100 Subject: [PATCH 28/30] fix: surface extraction errors inline instead of silently swallowing or showing modal alerts - applyExtractionPattern now throws meaningful errors (InvalidIndexPattern, IndexOutOfRange, InvalidRegexPattern) instead of silently returning '' - Add refreshStringResult for passive/programmatic updates: errors shown inline in the result field (no modal alert) - Passive callers in onModelSet and onDataLocationModified now use refreshStringResult instead of onStringInputValueChanged, avoiding spurious IsDirty=true and out-of-context uialert popups - User-driven callbacks (onStringInputValueChanged and updateStringResult call sites) retain uialert behaviour Co-Authored-By: Claude Sonnet 4.6 --- .../@DataLocationModel/DataLocationModel.m | 44 +++++++++++++------ .../MetadataInitializationUI.m | 19 ++++++-- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m index 40a6aace..7c74e8f8 100644 --- a/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m +++ b/code/+nansen/+config/+dloc/@DataLocationModel/DataLocationModel.m @@ -1082,22 +1082,38 @@ function addDiskTypeToAllRootPaths(obj) pattern end - try - switch lower(mode) - case 'ind' - substring = eval(['text([' pattern '])']); - case 'expr' + switch lower(mode) + case 'ind' + + if strcmp(pattern, '1:end') % Special case + pattern = sprintf('1:%d', strlength(text)); + end + try + indices = eval(['[' pattern ']']); + catch + error('NANSEN:DataLocationModel:InvalidIndexPattern', ... + 'Index pattern "%s" is not valid MATLAB index syntax.', pattern) + end + if any(indices > strlength(text)) + error('NANSEN:DataLocationModel:IndexOutOfRange', ... + 'Index range [%s] exceeds the string length (%d characters).', ... + pattern, strlength(text)) + end + substring = text(indices); + case 'expr' + try result = regexp(text, pattern, 'match', 'once'); - if isempty(result) - substring = ''; - else - substring = result; - end - otherwise + catch + error('NANSEN:DataLocationModel:InvalidRegexPattern', ... + 'Regular expression "%s" is not valid.', pattern) + end + if isempty(result) substring = ''; - end - catch - substring = ''; + else + substring = result; + end + otherwise + substring = ''; end end end diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 546e17bb..8c899731 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -10,6 +10,8 @@ % The panel reads its initial state from a DataLocationModel and writes % back to it when the user applies changes. A toolbar dropdown allows % switching between data locations when multiple are configured. +% +% See also nansen.config.dloc.DataLocationModel % Todo: Simplify component creation. @@ -613,7 +615,7 @@ function onModelSet(obj) inputField = obj.RowControls(i).StringExtractInputField; % Update value in string detection input inputField.Value = metadataDefinitions(i).StringDetectInput; - obj.onStringInputValueChanged(inputField) + obj.refreshStringResult(i) end end @@ -679,6 +681,18 @@ function updateFolderSelectionValue(obj, metadataDefinitions) end end + function refreshStringResult(obj, rowNumber) + %refreshStringResult Update result field for passive/programmatic updates. + % Extraction errors are shown inline in the result field — no alert. + hRow = obj.RowControls(rowNumber); + try + obj.updateStringResult(rowNumber) + catch ME + hRow.StringExtractResultField.Value = sprintf('error: %s', ME.message); + hRow.StringExtractResultField.Tooltip = ME.message; + end + end + function updateStringResult(obj, rowNumber) hRow = obj.RowControls(rowNumber); @@ -945,8 +959,7 @@ function onDataLocationModified(obj, ~, evt) % Update result of string indexing based on model... for i = 1:obj.NumRows - inputField = obj.RowControls(i).StringExtractInputField; - obj.onStringInputValueChanged(inputField) + obj.refreshStringResult(i) end end case 'Name' From d47c1db8d65b23dc48b00bc5d93dd77c5633d478 Mon Sep 17 00:00:00 2001 From: ehennestad Date: Tue, 17 Mar 2026 22:29:19 +0100 Subject: [PATCH 29/30] Refresh folder selector button text Call updateFolderSelectorButtonText after shifting folder selector controls so the FolderMultiSelector button displays the correct text based on its UserData (FolderItems and SelectedIndices). This ensures the UI label stays in sync after layout changes. --- .../@MetadataInitializationUI/MetadataInitializationUI.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m index 8c899731..cf546125 100644 --- a/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m +++ b/code/+nansen/+config/+dloc/@MetadataInitializationUI/MetadataInitializationUI.m @@ -903,6 +903,10 @@ function setRowDisplayMode(obj, rowNumber, showAdvanced) hRow = obj.RowControls(rowNumber); hRow.FolderNameSelector.Position(3) = hRow.FolderNameSelector.Position(3) + xOffset; hRow.FolderMultiSelector.Position(3) = hRow.FolderMultiSelector.Position(3) + xOffset; + obj.updateFolderSelectorButtonText( ... + hRow.FolderMultiSelector, ... + hRow.FolderMultiSelector.UserData.FolderItems, ... + hRow.FolderMultiSelector.UserData.SelectedIndices); hRow.SelectSubstringButton.Position(1) = hRow.SelectSubstringButton.Position(1) + xOffset; hRow.SelectSubstringButton.Visible = visibility_; From 51984bc6e9aa8f6cd79d384fa9a403d29bb54c63 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:41:12 +0000 Subject: [PATCH 30/30] Update GitHub badges --- .github/badges/code_issues.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg index 9d1b92d3..a0288219 100644 --- a/.github/badges/code_issues.svg +++ b/.github/badges/code_issues.svg @@ -1 +1 @@ -code issuescode issues18111811 \ No newline at end of file +code issuescode issues17981798 \ No newline at end of file