Skip to content

Commit 7efff9e

Browse files
committed
Merge: bottom log strip + uitable inspector
2 parents f18545d + 97cd894 commit 7efff9e

2 files changed

Lines changed: 229 additions & 125 deletions

File tree

libs/FastSenseCompanion/FastSenseCompanion.m

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
hLeftPanel_ = [] % left pane uipanel
5252
hMidPanel_ = [] % middle pane uipanel
5353
hRightPanel_ = [] % right pane uipanel
54+
hLogPanel_ = [] % bottom log uipanel (full-width)
55+
hLogText_ = [] % uitextarea inside hLogPanel_ (newest line first)
5456
Theme_ = [] % resolved CompanionTheme struct
5557
Listeners_ = {} % all addlistener return values; deleted on close
5658
CatalogPane_ = [] % TagCatalogPane instance
@@ -132,28 +134,36 @@
132134
'Visible', 'off');
133135
obj.hFig_.Color = obj.Theme_.DashboardBackground;
134136

135-
% Step 8 — Root grid
136-
obj.hLayout_ = uigridlayout(obj.hFig_, [1 3]);
137+
% Step 8 — Root grid (2 rows: top = 3 panes, bottom = log strip)
138+
obj.hLayout_ = uigridlayout(obj.hFig_, [2 3]);
137139
obj.hLayout_.ColumnWidth = {220, '1x', 280};
138-
obj.hLayout_.RowHeight = {'1x'};
140+
obj.hLayout_.RowHeight = {'1x', 140};
139141
obj.hLayout_.Padding = [24 24 24 24];
140142
obj.hLayout_.ColumnSpacing = 16;
141-
obj.hLayout_.RowSpacing = 0;
143+
obj.hLayout_.RowSpacing = 12;
142144
obj.hLayout_.BackgroundColor = obj.Theme_.DashboardBackground;
143145

144-
% Step 9 — Three uipanels (order matters: grid assigns col 1, 2, 3)
146+
% Step 9 — Three uipanels in row 1 + log panel spanning row 2.
145147
obj.hLeftPanel_ = uipanel(obj.hLayout_);
148+
obj.hLeftPanel_.Layout.Row = 1; obj.hLeftPanel_.Layout.Column = 1;
146149
obj.hMidPanel_ = uipanel(obj.hLayout_);
150+
obj.hMidPanel_.Layout.Row = 1; obj.hMidPanel_.Layout.Column = 2;
147151
obj.hRightPanel_ = uipanel(obj.hLayout_);
152+
obj.hRightPanel_.Layout.Row = 1; obj.hRightPanel_.Layout.Column = 3;
153+
obj.hLogPanel_ = uipanel(obj.hLayout_);
154+
obj.hLogPanel_.Layout.Row = 2; obj.hLogPanel_.Layout.Column = [1 3];
148155

149156
% Apply panel styling from theme
150-
for hp = {obj.hLeftPanel_, obj.hMidPanel_, obj.hRightPanel_}
157+
for hp = {obj.hLeftPanel_, obj.hMidPanel_, obj.hRightPanel_, obj.hLogPanel_}
151158
hp{1}.BackgroundColor = obj.Theme_.WidgetBackground;
152159
hp{1}.BorderColor = obj.Theme_.WidgetBorderColor;
153160
hp{1}.BorderType = 'line';
154161
hp{1}.BorderWidth = 1;
155162
end
156163

164+
% Build log strip (Header + uitextarea in a 2-row inner grid)
165+
obj.buildLogStrip_();
166+
157167
% Step 10 — Instantiate pane objects and attach
158168
obj.CatalogPane_ = TagCatalogPane();
159169
obj.ListPane_ = DashboardListPane();
@@ -376,6 +386,31 @@ function removeDashboard(obj, key)
376386
end
377387
end
378388

389+
function addLogEntry(obj, level, msg)
390+
%ADDLOGENTRY Append a timestamped log line to the bottom log strip.
391+
% level — 'info' | 'warn' | 'error' (any short tag accepted)
392+
% msg — char/string. Anything else is sprintf'd through %s.
393+
% Newest line is at the top so the user always sees the latest
394+
% without scrolling. Buffer capped at 500 lines.
395+
if isempty(obj.hLogText_) || ~isvalid(obj.hLogText_); return; end
396+
try
397+
ts = char(datetime('now', 'Format', 'HH:mm:ss'));
398+
if isstring(msg) && isscalar(msg); msg = char(msg); end
399+
if ~ischar(msg); msg = sprintf('%s', msg); end
400+
line = sprintf('[%s] %-5s %s', ts, upper(char(level)), msg);
401+
cur = obj.hLogText_.Value;
402+
if isempty(cur) || (iscell(cur) && numel(cur)==1 && isempty(cur{1}))
403+
cur = {};
404+
end
405+
if ~iscell(cur); cur = {cur}; end
406+
cur = [{line}, reshape(cur, 1, [])];
407+
if numel(cur) > 500; cur = cur(1:500); end
408+
obj.hLogText_.Value = cur;
409+
catch
410+
% Logging must never crash the UI.
411+
end
412+
end
413+
379414
function refreshCatalog(obj)
380415
%REFRESHCATALOG Re-snapshot tags from registry and rebuild the tag catalog.
381416
% Call after externally mutating TagRegistry to update the visible catalog.
@@ -391,6 +426,31 @@ function refreshCatalog(obj)
391426

392427
methods (Access = private)
393428

429+
function buildLogStrip_(obj)
430+
%BUILDLOGSTRIP_ Construct header label + uitextarea inside hLogPanel_.
431+
t = obj.Theme_;
432+
g = uigridlayout(obj.hLogPanel_, [2 1]);
433+
g.RowHeight = {18, '1x'};
434+
g.ColumnWidth = {'1x'};
435+
g.Padding = [8 4 8 4];
436+
g.RowSpacing = 4;
437+
g.BackgroundColor = t.WidgetBackground;
438+
hLbl = uilabel(g);
439+
hLbl.Layout.Row = 1; hLbl.Layout.Column = 1;
440+
hLbl.Text = 'Log'; hLbl.FontWeight = 'bold'; hLbl.FontSize = 11;
441+
hLbl.FontColor = t.ForegroundColor;
442+
hLbl.HorizontalAlignment = 'left'; hLbl.VerticalAlignment = 'center';
443+
obj.hLogText_ = uitextarea(g);
444+
obj.hLogText_.Layout.Row = 2; obj.hLogText_.Layout.Column = 1;
445+
obj.hLogText_.Editable = 'off';
446+
obj.hLogText_.FontName = 'Menlo';
447+
obj.hLogText_.FontSize = 10;
448+
obj.hLogText_.BackgroundColor = t.DashboardBackground;
449+
obj.hLogText_.FontColor = t.ForegroundColor;
450+
obj.hLogText_.Value = {sprintf('[%s] INFO Companion ready.', ...
451+
char(datetime('now', 'Format', 'HH:mm:ss')))};
452+
end
453+
394454
function applyPlaceholderColors_(obj)
395455
%APPLYPLACEHOLDERCOLORS_ Set FontColor on all placeholder uilabels.
396456
% Called after attach() on each pane so the theme color is applied.
@@ -414,7 +474,10 @@ function onDashboardSelected_(obj, ~, ed)
414474
obj.SelectedDashboardIdx_ = ed.Index;
415475
obj.LastInteraction_ = 'dashboard';
416476
obj.resolveInspectorState_();
477+
obj.addLogEntry('info', sprintf('Selected dashboard: %s', ...
478+
char(ed.Engine.Name)));
417479
catch err
480+
obj.addLogEntry('error', sprintf('Dashboard select failed: %s', err.message));
418481
uialert(obj.hFig_, err.message, 'FastSense Companion');
419482
end
420483
end
@@ -428,7 +491,10 @@ function onOpenDashboardRequested_(obj, ~, ed)
428491
obj.SelectedDashboardIdx_ = ed.Index;
429492
obj.LastInteraction_ = 'dashboard';
430493
obj.resolveInspectorState_();
494+
obj.addLogEntry('info', sprintf('Opened dashboard: %s', ...
495+
char(ed.Engine.Name)));
431496
catch err
497+
obj.addLogEntry('error', sprintf('Open dashboard failed: %s', err.message));
432498
uialert(obj.hFig_, err.message, 'FastSense Companion');
433499
end
434500
end
@@ -441,7 +507,18 @@ function onTagSelectionChanged_(obj, ~, ~)
441507
obj.SelectedTagKeys_ = obj.CatalogPane_.getSelectedKeys();
442508
obj.LastInteraction_ = 'tags';
443509
obj.resolveInspectorState_();
510+
if isempty(obj.SelectedTagKeys_)
511+
obj.addLogEntry('info', 'Tag selection cleared');
512+
elseif numel(obj.SelectedTagKeys_) == 1
513+
obj.addLogEntry('info', sprintf('Selected tag: %s', ...
514+
char(obj.SelectedTagKeys_{1})));
515+
else
516+
obj.addLogEntry('info', sprintf('Selected %d tags: %s', ...
517+
numel(obj.SelectedTagKeys_), ...
518+
strjoin(obj.SelectedTagKeys_, ', ')));
519+
end
444520
catch err
521+
obj.addLogEntry('error', sprintf('Tag select failed: %s', err.message));
445522
uialert(obj.hFig_, err.message, 'FastSense Companion');
446523
end
447524
end
@@ -530,14 +607,21 @@ function onOpenAdHocPlotRequested_(obj, ~, evt)
530607
end
531608
end
532609
[~, skipped] = openAdHocPlot(tags, mode, obj.Theme);
610+
obj.addLogEntry('info', sprintf( ...
611+
'Opened ad-hoc plot: %d tag(s) [%s]', ...
612+
numel(tags), char(mode)));
533613
if ~isempty(skipped)
614+
obj.addLogEntry('warn', sprintf( ...
615+
'Ad-hoc plot skipped %d tag(s): %s', ...
616+
numel(skipped), strjoin(skipped, ', ')));
534617
msg = sprintf( ...
535618
'Plot opened, but some tags were skipped:\n - %s', ...
536619
strjoin(skipped, sprintf('\n - ')));
537620
uialert(obj.hFig_, msg, 'FastSense Companion', ...
538621
'Icon', 'warning');
539622
end
540623
catch ME
624+
obj.addLogEntry('error', sprintf('Ad-hoc plot failed: %s', ME.message));
541625
if ~isempty(obj.hFig_) && isvalid(obj.hFig_)
542626
uialert(obj.hFig_, ...
543627
sprintf('Failed to open plot: %s', ME.message), ...

0 commit comments

Comments
 (0)