Skip to content

Commit 09ebcc6

Browse files
committed
Added URL parameter to AlyxPanel
1 parent 98887dd commit 09ebcc6

File tree

3 files changed

+86
-14
lines changed

3 files changed

+86
-14
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

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)