diff --git a/code/+nansen/+ui/+metatable/EntityTableMetaTableViewer.m b/code/+nansen/+ui/+metatable/EntityTableMetaTableViewer.m index 170f3c176..c1912a23f 100644 --- a/code/+nansen/+ui/+metatable/EntityTableMetaTableViewer.m +++ b/code/+nansen/+ui/+metatable/EntityTableMetaTableViewer.m @@ -15,6 +15,7 @@ CellEditCallback KeyPressCallback MouseDoubleClickedFcn = [] + Theme (1,1) string = "light" DeleteColumnFcn = [] UpdateColumnFcn = [] @@ -588,7 +589,24 @@ function fitColumnsToTableWidth(obj) RowKey=options.RowKey, ... ColumnSpecs=options.ColumnSpecs, ... SelectionMode=options.SelectionMode, ... - Backend=backend); + Backend=backend, ... + Theme=obj.Theme); + end + + function applyFigureTheme(obj, parent) + if isempty(parent) || strlength(obj.Theme) == 0 + return + end + + hFigure = ancestor(parent, 'figure'); + if isempty(hFigure) || ~isvalid(hFigure) || ~isprop(hFigure, 'Theme') + return + end + + hFigure.Theme = obj.Theme; + if isprop(hFigure, 'ToolBar') + hFigure.ToolBar = 'none'; + end end function cleanup = suspendEntityTableRefresh(obj) @@ -634,6 +652,7 @@ function createEntityTable(obj, dataTable, columnSpecs) obj.Parent = uifigure(... 'Name', 'NANSEN Metadata Table', ... 'Visible', 'on'); + obj.applyFigureTheme(obj.Parent) end tableLayout = obj.captureTableLayout(); diff --git a/code/dashboards/+roimanager/RoimanagerDashboard.m b/code/dashboards/+roimanager/RoimanagerDashboard.m index 1ad4b029b..9f50d92d3 100644 --- a/code/dashboards/+roimanager/RoimanagerDashboard.m +++ b/code/dashboards/+roimanager/RoimanagerDashboard.m @@ -71,6 +71,8 @@ obj@applify.DashBoard() obj@imviewer.plugin.RoiManager('CreateContextMenu', false) + obj.configureModernFigure() + % Todo: Get figure position from properties. obj.hFigure.Position = [100, 50, obj.FigureSize]; obj.keepFigureOnScreen() @@ -89,7 +91,7 @@ % Todo: This dashboard should implement roimanager as a % property, not a superclass... - % Todo: Why do I need this. Should not roimanager take care of + % Todo: Why is this needed? Should not roimanager take care of % this? if numel(obj.RoiGroup) > 1 roiGroup = roimanager.CompositeRoiGroup(obj.RoiGroup); @@ -192,9 +194,17 @@ function createPanels(obj) panelParameters = {'Parent', obj.hMainPanel, ... 'BorderType', 'line', 'BorderWidth', 1, ... - 'Background', bgColor2, 'ShadowColor', shColor, ... - 'Foreground', fgColor, 'HighlightColor', hlColor }; - + 'Background', bgColor2, 'Foreground', fgColor}; + + if nansen.util.useModernUiComponents() + panelParameters = [panelParameters, {'BorderColor', shColor}]; + else + warningCleanup = nansen.common.suppressWarning(... + 'MATLAB:Uipanel:UnsupportedProperty'); %#ok + panelParameters = [panelParameters, {'ShadowColor', shColor}]; + panelParameters = [panelParameters, {'HighlightColor', hlColor}]; + end + % Create each of the panels: for i = 1:numel(obj.PanelTitles) iTitle = obj.PanelTitles{i}; @@ -262,6 +272,17 @@ function resizePanels(obj) newPos = cell(numPanelsA, 1); + if nansen.util.useModernUiComponents() + % Modern panels should be placed at (0,0), not (1,1) + originOffset = -1; + else + originOffset = 0; + end + xPosA = xPosA + originOffset; + xPosB = xPosB + originOffset; + xPosC = xPosC + originOffset; + yPos = yPos + originOffset; + for i = [1,3,2] % Resize imviewer latest... newPos{i} = [xPosA(i), yPos(iA), Wa(i), H(iA)]; setpixelposition(obj.hPanels(panelNumsA(i)), newPos{i}) @@ -284,11 +305,10 @@ function resizePanels(obj) set(obj.hPanels(3), 'Visible', 'on'); obj.hMainPanel.Visible = mainPanelVisibility; + obj.applyModernDockedAppInsets() %obj.hFigure.Visible = 'on'; - %drawnow - end end @@ -298,6 +318,7 @@ function initializeImviewer(obj, varargin) %initializeImviewer Initialize the imviewer module h = imviewer(obj.hPanels(2), varargin{:}); + obj.applyModernDockedAppInsets() h.resizePanelContents() obj.AppModules = h; obj.configurePanelResizeButton(obj.hPanels(2).Children(1), h) @@ -309,6 +330,7 @@ function initializeSignalViewer(obj, roiGroup) %initializeSignalViewer Initialize the signalviewer module if ~obj.Imviewer.ImageStack.isDummyStack() obj.openSignalViewer(obj.hPanels(4), roiGroup) + obj.applyModernDockedAppInsets() obj.addPanelResizeButton(obj.hPanels(4).Children(1)) obj.AppModules(end+1) = obj.SignalViewer; end @@ -317,6 +339,7 @@ function initializeSignalViewer(obj, roiGroup) function initializeRoiTable(obj, roiGroup) %initializeRoiTable Initialize the roi table module h = roimanager.RoiTable(obj.hPanels(3), roiGroup); + obj.applyModernDockedAppInsets() % Note: table catches all key events by default. Setting the % following callbacks will pass uncaught key events to the % roimanager/imviewer. @@ -329,12 +352,78 @@ function initializeRoiTable(obj, roiGroup) function initializeRoiThumbnailDisplay(obj, roiGroup) obj.RoiThumbnailViewer = roimanager.RoiThumbnailDisplay(obj.hPanels(6), roiGroup); + obj.applyModernDockedAppInsets() obj.RoiThumbnailViewer.ImageStack = obj.ImviewerObj.ImageStack; obj.RoiThumbnailViewer.Dashboard = obj; obj.RoiThumbnailViewer.ThumbnailSize = obj.settings.RoiDisplayPreferences.roiThumbnailSize; obj.RoiThumbnailViewer.ActiveChannel = obj.ActiveChannel; %obj.AppModules(end+1) = obj.RoiThumbnailViewer; end + + function configureModernFigure(obj) + if ~nansen.util.useModernUiComponents() + return + end + + if isprop(obj.hFigure, 'Theme') + obj.hFigure.Theme = 'dark'; + end + if isprop(obj.hFigure, 'ToolBar') + obj.hFigure.ToolBar = 'none'; + end + drawnow limitrate + end + + function applyModernDockedAppInsets(obj) + if ~nansen.util.useModernUiComponents() + return + end + + % Todo: Can this be simplified? + + % For modern panels, content should be placed at (0,0). + % The Innerposition is measured starting at (1,1), so we add an + % originOffset. + originOffset = -1; + + % For modern panels, we need to subtract 4 pixels (ad-hoc) to + % avoid top-clipping of panel content. + topInset = 4; + + for i = 1:numel(obj.hPanels) + hPanel = obj.hPanels(i); + hAppPanels = obj.findDockedAppPanels(hPanel); + if isempty(hAppPanels) + continue + end + + previousUnits = hPanel.Units; + hPanel.Units = 'pixels'; + panelPosition = hPanel.Position; + innerPosition = hPanel.InnerPosition; + contentPosition = [innerPosition(1:2) - panelPosition(1:2) + originOffset, ... + innerPosition(3:4)]; + contentPosition(4) = max(1, contentPosition(4) - topInset); + + for j = 1:numel(hAppPanels) + hAppPanels(j).Units = 'pixels'; + setpixelposition(hAppPanels(j), round(contentPosition), false) + end + hPanel.Units = previousUnits; + end + end + + function hAppPanels = findDockedAppPanels(~, hPanel) + hChildren = hPanel.Children; + isAppPanel = false(size(hChildren)); + + for i = 1:numel(hChildren) + isAppPanel(i) = isprop(hChildren(i), 'Tag') && ... + strcmp(hChildren(i).Tag, 'App Content Panel'); + end + + hAppPanels = hChildren(isAppPanel); + end end methods (Access = protected) % Create/configure modules diff --git a/code/tools/+roimanager/RoiTable.m b/code/tools/+roimanager/RoiTable.m index 3d1eb5cd0..f85123c8e 100644 --- a/code/tools/+roimanager/RoiTable.m +++ b/code/tools/+roimanager/RoiTable.m @@ -37,6 +37,7 @@ properties (Access = private) WindowMousePressListener + SelectionChangedListener TableUpdatedListener end @@ -58,25 +59,24 @@ if strcmp(obj.mode, 'standalone') obj.Figure.Position = obj.initializeFigurePosition(); + if nansen.util.useModernUiTable() + obj.configureModernFigure() + end end roiTable = obj.rois2table(cat(1, roiGroup.roiArray)); obj.roiTable = roiTable; - nansen.assert('WidgetsToolboxInstalled') - obj.UITable = nansen.MetaTableViewer(obj.Panel, roiTable, 'MetaTableType', 'roi'); - - % Set table properties - if ismac - obj.UITable.HTable.hideHorizontalScroller() + if ~nansen.util.useModernUiTable() + nansen.assert('WidgetsToolboxInstalled') end - %obj.UITable.HTable.hideVerticalScroller() - obj.UITable.HTable.RowHeight = 18; - obj.UITable.HTable.CellSelectionCallback = @obj.onTableSelectionChanged; - obj.UITable.HTable.CellEditCallback = @obj.onTableCellEdited; - obj.UITable.HTable.KeyPressFcn = @obj.onKeyPressedInTable; - obj.UITable.HTable.KeyReleaseFcn = @obj.onKeyReleasedInTable; - obj.UITable.HTable.ColumnResizePolicy = 'off'; + metaTableViewerArgs = {obj.Panel, roiTable, 'MetaTableType', 'roi'}; + if nansen.util.useModernUiTable() + metaTableViewerArgs = [metaTableViewerArgs, {'Theme', 'dark'}]; + end + obj.UITable = nansen.MetaTableViewer(metaTableViewerArgs{:}); + obj.configureTableBackend() + obj.applyTableTheme() % Load and set column model settings from preferences. tableColumnSettings = obj.getPreference('TableColumnSettings', []); @@ -94,6 +94,8 @@ obj.WindowMousePressListener = listener(obj.Figure, ... 'WindowMousePress', @obj.onMousePressedInFigure); + obj.SelectionChangedListener = listener(obj.UITable, ... + 'SelectionChanged', @obj.onTableSelectionChanged); obj.TableUpdatedListener = listener(obj.UITable, ... 'TableUpdated', @obj.onTableUpdated); @@ -117,6 +119,9 @@ function delete(obj) if ~isempty(obj.WindowMousePressListener) delete(obj.WindowMousePressListener) end + if ~isempty(obj.SelectionChangedListener) + delete(obj.SelectionChangedListener) + end if ~isempty(obj.TableUpdatedListener) delete(obj.TableUpdatedListener) end @@ -143,7 +148,7 @@ function removeRois(obj) roiIdxToRemove = obj.SelectedRois; - obj.UITable.HTable.Enable = 'off'; + obj.setTableComponentProperty('Enable', 'off') C = onCleanup(@obj.enableTable); % Important: Change roi selection to first element in list @@ -154,11 +159,11 @@ function removeRois(obj) obj.RoiGroup.removeRois(roiIdxToRemove); newSelection = obj.UITable.getSelectedEntries(); obj.RoiGroup.changeRoiSelection([], newSelection) - obj.UITable.HTable.JTable.requestFocus() + obj.UITable.focusTable() end function enableTable(obj) - obj.UITable.HTable.Enable = 'on'; + obj.setTableComponentProperty('Enable', 'on') end function classifyRois(obj, classificationIdx, currentRoiInd) @@ -201,13 +206,26 @@ function resetTableFilters(obj) methods % Set/get function set.SelectionMode(obj, newMode) - if ~isempty(obj.UITable.HTable) - obj.UITable.HTable.SelectionMode = newMode; + if isempty(obj.UITable) + return end + if obj.UITable.usesModernBackend() + return + end + obj.setTableComponentProperty('SelectionMode', newMode) end function mode = get.SelectionMode(obj) - if ~isempty(obj.UITable.HTable) + if isempty(obj.UITable) + mode = ''; + return + end + if obj.UITable.usesModernBackend() + mode = 'multiple'; + return + end + if ~isempty(obj.UITable.HTable) && ... + isprop(obj.UITable.HTable, 'SelectionMode') mode = obj.UITable.HTable.SelectionMode; else mode = ''; @@ -222,6 +240,66 @@ function resetTableFilters(obj) end methods (Access = private) + + function configureTableBackend(obj) + if ismac + obj.callTableComponentMethod('hideHorizontalScroller') + end + obj.setTableComponentProperty('RowHeight', 18) + obj.setTableComponentProperty('KeyReleaseFcn', @obj.onKeyReleasedInTable) + obj.setTableComponentProperty('ColumnResizePolicy', 'off') + + obj.UITable.CellEditCallback = @obj.onTableCellEdited; + obj.UITable.KeyPressCallback = @obj.onKeyPressedInTable; + obj.UITable.setKeyPressFcn(@obj.onKeyPressedInTable) + end + + function applyTableTheme(obj) + if isempty(obj.UITable) + return + end + + if obj.UITable.usesModernBackend() + if strcmp(obj.mode, 'standalone') + obj.configureModernFigure() + end + else + S = obj.Theme; + obj.setTableComponentProperty('BackgroundColor', S.HeaderBgColor) + obj.setTableComponentProperty('Theme', S.TableTheme) + end + end + + function setTableComponentProperty(obj, propertyName, propertyValue) + if isempty(obj.UITable) || isempty(obj.UITable.HTable) || ... + ~isvalid(obj.UITable.HTable) || ... + ~isprop(obj.UITable.HTable, propertyName) + return + end + obj.UITable.HTable.(propertyName) = propertyValue; + end + + function callTableComponentMethod(obj, methodName) + if isempty(obj.UITable) || isempty(obj.UITable.HTable) || ... + ~isvalid(obj.UITable.HTable) || ... + ~ismethod(obj.UITable.HTable, methodName) + return + end + feval(methodName, obj.UITable.HTable) + end + + function configureModernFigure(obj) + if isempty(obj.Figure) || ~isvalid(obj.Figure) + return + end + + if isprop(obj.Figure, 'Theme') + obj.Figure.Theme = 'dark'; + end + if isprop(obj.Figure, 'ToolBar') + obj.Figure.ToolBar = 'none'; + end + end function onMousePressedInFigure(obj, src, evt) % Hide filter if mouse is pressed anywhere in figure. @@ -405,14 +483,21 @@ function initializeTableColumnSettings(obj) function resizePanel(obj, src, evt) parentPosition = getpixelposition(obj.Panel); - obj.UITable.HTable.Units = 'pixel'; - obj.UITable.HTable.Position(1:2) = [5,5]; - obj.UITable.HTable.Position(3) = parentPosition(3)-10; + if isempty(obj.UITable) || isempty(obj.UITable.HTable) || ... + ~isvalid(obj.UITable.HTable) || ... + ~isprop(obj.UITable.HTable, 'Position') + return + end + obj.setTableComponentProperty('Units', 'pixel') + tablePosition = obj.UITable.HTable.Position; + tablePosition(1:2) = [5,5]; + tablePosition(3) = parentPosition(3)-10; if strcmp(obj.mode, 'standalone') - obj.UITable.HTable.Position(4) = parentPosition(4)-10; + tablePosition(4) = parentPosition(4)-10; else - obj.UITable.HTable.Position(4) = parentPosition(4)-20; + tablePosition(4) = parentPosition(4)-20; end + obj.UITable.HTable.Position = tablePosition; end end @@ -600,10 +685,7 @@ function onKeyReleased(obj, src, evt) function onThemeChanged(obj) % Override superclass implementation onThemeChanged@applify.ModularApp(obj) - S = obj.Theme; - - obj.UITable.HTable.BackgroundColor = S.HeaderBgColor; - obj.UITable.HTable.Theme = S.TableTheme; + obj.applyTableTheme() end end end