Skip to content

Commit ab46500

Browse files
committed
Merge remote-tracking branch 'origin/AlyxPanelURLChange' into dev
2 parents 2933a9a + 2246143 commit ab46500

File tree

4 files changed

+94
-15
lines changed

4 files changed

+94
-15
lines changed

+eui/AlyxPanel.m

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,20 @@
5959
end
6060

6161
methods
62-
function obj = AlyxPanel(parent, active)
62+
function obj = AlyxPanel(parent, url)
6363
% Constructor to build all the UI elements and set callbacks to
6464
% the relevant functions. If a handle to parant UI object is
6565
% not specified, a seperate figure is created. An optional
6666
% handle to a logging display panal may be provided, otherwise
67-
% one is created. If the active flag is set to false, the panel
68-
% is inactive and the instance of Alyx will be set to headless.
69-
% The panel defaults to active only if the databaseURL field is
70-
% populated in the paths.
67+
% one is created. The panel will be rendered inactive and the
68+
% instance of Alyx will be set to headless if an empty database
69+
% URL is provided, or if no URL is provided and the databaseURL
70+
% field is not populated in the paths.
7171
%
7272
% See also Alyx
73-
73+
noParent = ~nargin || isempty(parent);
7474
obj.AlyxInstance = Alyx('','');
75-
if ~nargin % No parant object: create new figure
75+
if noParent % No parant object: create new figure
7676
f = figure('Name', 'alyx GUI',...
7777
'MenuBar', 'none',...
7878
'Toolbar', 'none',...
@@ -91,15 +91,18 @@
9191
% be set by any other GUI that instantiates this object (e.g.
9292
% MControl using this as a panel.
9393
obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt));
94+
else
95+
% We'll need the figure handle for adding a context menu
96+
f = ancestor(parent, 'Figure');
9497
end
9598

9699
% Check to see if there is a remote database url defined in
97100
% the paths, if so activate AlyxPanel
98101
if nargin < 2
99102
url = char(getOr(dat.paths, 'databaseURL', ''));
100-
active = ~isempty(url);
101103
end
102104

105+
obj.AlyxInstance.BaseURL = url;
103106
obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx');
104107
alyxbox = uiextras.VBox('Parent', obj.RootContainer);
105108

@@ -114,8 +117,8 @@
114117
'Callback', @(~,~)obj.login);
115118
loginbox.Widths = [-1 75];
116119

117-
% If active flag set as false, make Alyx headless
118-
if ~active
120+
% If URL is empty, make Alyx headless
121+
if isempty(url)
119122
obj.AlyxInstance.Headless = true;
120123
set(obj.LoginButton, 'Enable', 'off')
121124
end
@@ -195,7 +198,7 @@
195198
'Enable', 'off',...
196199
'Callback', @(~,~)obj.launchSessionURL);
197200

198-
if ~nargin
201+
if noParent
199202
% logging message area
200203
obj.LoggingDisplay = uicontrol('Parent', parent, 'Style', 'listbox',...
201204
'Enable', 'inactive', 'String', {});
@@ -204,6 +207,12 @@
204207
% Use parent's logging display
205208
obj.LoggingDisplay = findobj('Tag', 'Logging Display');
206209
end
210+
obj.log('using database %s', obj.AlyxInstance.BaseURL)
211+
% Add context menu for changing database
212+
c = uicontextmenu(f);
213+
obj.RootContainer.UIContextMenu = c;
214+
uimenu(c, 'Label', 'Change database URL', ...
215+
'MenuSelectedFcn', @(~,~)obj.changeURL());
207216
end
208217

209218
function delete(obj)
@@ -301,6 +310,25 @@ function login(obj, varargin)
301310
obj.dispWaterReq()
302311
end
303312

313+
function changeURL(obj, url)
314+
% Sets the database URL of the AlyxInstance. When called with
315+
% no inputs, the user is prompted to type in a new URL. If
316+
% logged in, the current token is deleted before changing URL
317+
% (unless the new URL is identical).
318+
current = obj.AlyxInstance.BaseURL;
319+
if nargin == 1 % Prompt user for input
320+
prompt = sprintf(['Please enter a database URL to connect to.\n'...
321+
'This will log you out of the current database.\n']);
322+
url = newid(prompt,'Database URL', [1 50], {current});
323+
end
324+
% Return if user pressed 'Close' or 'x', or url unchanged
325+
if isempty(url) || strcmpi(url, current), return, end
326+
% Clear current token, if there is one
327+
if obj.AlyxInstance.IsLoggedIn, obj.login; end
328+
obj.AlyxInstance.BaseURL = url; % Change URL
329+
obj.log('using database %s', obj.AlyxInstance.BaseURL)
330+
end
331+
304332
function giveWater(obj)
305333
% Callback to the give water button. Posts the value entered
306334
% in the text box as either liquid or gel depending on the

docs/scripts/paths_conflicts.m

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
% Another way to avoid these conflicts occuring over time is to reset your
8080
% paths each time MATLAB starts up. You can do this by adding the
8181
% following to <https://uk.mathworks.com/help/matlab/ref/startup.html
82-
% MATLAB's startup script>:
82+
% MATLAB's startup script> (make sure the path locations are correct):
8383

8484
disp 'Resetting paths...'
8585
restoredefaultpath % Restore all paths to factory state
@@ -91,6 +91,7 @@
9191
% Change these paths to your install locations
9292
rigbox_path = fullfile(userDir, 'Github', 'rigbox');
9393
ptb_path = fullfile(userDir, 'PTB', 'Psychtoolbox');
94+
add_ons = genpath(fullfile(userDir, 'MATLAB', 'Add-Ons'));
9495

9596
% Add Psychtoolbox paths
9697
disp '...'
@@ -105,6 +106,9 @@
105106
cd(rigbox_path)
106107
addRigboxPaths('Strict', false)
107108

109+
% Add Add-Ons folder
110+
addpath(add_ons)
111+
108112
% Return to default working directory
109113
cd(userpath)
110114
clear variables
@@ -113,6 +117,6 @@
113117
%% Etc.
114118
% Author: Miles Wells
115119
%
116-
% v1.0.0
120+
% v1.0.1
117121
%
118122
% <index.html Home> > <./troubleshooting.html Troubleshooting> > Paths Conflicts

docs/scripts/release_notes_v260.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
% * |git.listVersions| - Lists the previous versions of Rigbox availiable
7777
% * |git.switchVersion| - Allows you to switched between Rigbox versions
7878
% * |git.repoVersion| - Returns release tag of repository, if available
79+
% * |eui.AlyxPanel| - The database URL can now be set via a UI context menu
80+
% or by passing a URL to the constructor.
7981
% * |eui.SignalsTest| - Warning instead of error when no stereo output
8082
% device found
8183
%
@@ -256,6 +258,7 @@
256258
% *Tests*
257259
%
258260
% * block2Alf - Full test coverage
261+
% * Explicit test for correct base session creation
259262

260263
%% wheelAnalysis
261264
%

tests/AlyxPanel_test.m

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
methods (TestClassSetup)
3939
function killFigures(testCase)
4040
testCase.FigureVisibleDefault = get(0,'DefaultFigureVisible');
41-
set(0,'DefaultFigureVisible','off');
41+
set(0,'DefaultFigureVisible', 'off');
4242
end
4343

4444
function loadData(testCase)
@@ -405,7 +405,6 @@ function test_updateWeightButton(testCase)
405405
end
406406

407407
function test_giveWater(testCase)
408-
testCase.Panel;
409408
% Tolerance for water verifications, required due to rounding
410409
tol2dp = 0.01; % rounding to 2 d.p.
411410
tol1dp = 0.1; % rounding to 1 d.p.
@@ -463,6 +462,51 @@ function test_giveWater(testCase)
463462
end
464463
end
465464

465+
function test_changeURL(testCase)
466+
% We only need to test with one URL
467+
if ~strcmp(testCase.Panel.AlyxInstance.BaseURL, testCase.BaseURL{1})
468+
disp('Skipping test')
469+
return
470+
end
471+
472+
% Test changing URL through method call (assumes we're logged in)
473+
url = 'db.example.com';
474+
testCase.Panel.changeURL(url);
475+
actual = testCase.Panel.AlyxInstance.BaseURL;
476+
testCase.verifyMatches(actual, url, 'failed to set database url')
477+
478+
% Check new URL printed to log
479+
logPanel = findobj(testCase.hPanel, 'Tag', 'Logging Display');
480+
testCase.verifyMatches(logPanel.String{end}, url, 'failed to log new url')
481+
482+
% Should now be logged out
483+
testCase.verifyFalse(testCase.Panel.AlyxInstance.IsLoggedIn)
484+
485+
% Now test uimenu callback and prompt
486+
testCase.Mock.InTest = true;
487+
testCase.Mock.UseDefaults = false;
488+
489+
% Find Change URL child menu
490+
button = testCase.Parent.UIContextMenu.Children(1);
491+
testCase.assertTrue(numel(button) == 1, 'UI menu not found');
492+
493+
responses = {testCase.BaseURL{1}, testCase.BaseURL{1}}; % Call twice
494+
testCase.Mock.Dialogs('Database URL') = fun.CellSeq.create(responses);
495+
496+
% Run uimenu callback
497+
button.MenuSelectedFcn()
498+
actual = testCase.Panel.AlyxInstance.BaseURL;
499+
expected = testCase.BaseURL{1};
500+
testCase.verifyMatches(actual, expected, 'failed to set database url')
501+
testCase.verifyMatches(logPanel.String{end}, expected, 'failed to log new url')
502+
503+
% When calling again with the same input we expect the function to
504+
% return without changing or logging anything
505+
nLogs = length(logPanel.String);
506+
button.MenuSelectedFcn()
507+
testCase.verifyEqual(length(logPanel.String), nLogs, 'unexpected update to log')
508+
end
509+
466510
function test_giveFutureWater(testCase)
467511
subject = 'ZM_335';
468512
testCase.SubjectUI.Selected = subject;

0 commit comments

Comments
 (0)