Skip to content

Commit 7ff0e31

Browse files
committed
Merge branch 'dev'
2 parents e00e83d + 0bd37c9 commit 7ff0e31

File tree

207 files changed

+17653
-2004
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

207 files changed

+17653
-2004
lines changed

+dat/listSubjects.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
mainPath = dat.reposPath('main', 'remote');
1515

1616
dirs = unique(cellflat(rmEmpty(file.list(mainPath, 'dirs'))));
17-
subjects = dirs(~cellfun(@(d)startsWith(d, '@'), dirs)); % exclude misc directories
17+
subjects = dirs(~startsWith(dirs, '@')); % exclude misc directories

+dat/newExp.m

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434

3535
% check the subject exists in the database
3636
exists = any(strcmp(dat.listSubjects, subject));
37-
assert(exists, sprintf('"%s" does not exist', subject));
37+
errMsg = sprintf('subject "%s" does not exist', subject);
38+
assert(exists, 'Rigbox:dat:newExp:subjectNotFound', errMsg);
3839

3940
% retrieve list of experiments for subject
4041
[~, dateList, seqList] = dat.listExps(subject);
@@ -52,11 +53,23 @@
5253
% main repository is the reference location for which experiments exist
5354
[expPath, expRef] = dat.expPath(subject, floor(expDate), expSeq, 'main');
5455
% ensure nothing went wrong in making a "unique" ref and path to hold
55-
assert(~any(file.exists(expPath)), ...
56-
sprintf('Something went wrong as experiment folders already exist for "%s".', expRef));
56+
present = file.exists(expPath);
57+
assert(~any(present), 'Rigbox:dat:newExp:expFoldersAlreadyExist', ...
58+
'The following experiment folder(s) already exist(s) for "%s":\r\t%s', ...
59+
expRef, strjoin(expPath(present), '\n\t'))
60+
61+
try
62+
% now make the folder(s) to hold the new experiment
63+
created = cellfun(@mkdir, expPath);
64+
assert(all(created))
65+
catch
66+
% Delete any folders we just created and rethrow error
67+
cellfun(@rmdir, expPath(created));
68+
error('Rigbox:dat:newExp:mkdirFailed', ...
69+
'Failed to create the following directories:\r\t%s', ...
70+
strjoin(expPath(~created), '\n\t'))
71+
end
5772

58-
% now make the folder(s) to hold the new experiment
59-
assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed');
6073

6174
% if the parameters had an experiment definition function, save a copy in
6275
% the experiment's folder

+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

+eui/SignalsTest.m

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,21 @@
196196

197197
InitializePsychSound
198198
d = PsychPortAudio('GetDevices');
199-
d = d([d.NrOutputChannels] == 2);
200-
[~,I] = min([d.LowOutputLatency]);
201-
obj.Hardware.audioDevices = d(I);
202-
obj.Hardware.audioDevices.DeviceName = 'default';
199+
200+
% Keep output devices
201+
d = d([d.NrOutputChannels] > 0);
202+
obj.Hardware.audioDevices = d([d.NrOutputChannels] > 0);
203+
204+
% Select the lowest latency stereo output as the default
205+
stereo = [d.NrOutputChannels] == 2;
206+
[~,I] = min([d(stereo).LowOutputLatency]);
207+
I = I + sum(~stereo(1:I));
208+
if isempty(I)
209+
warning('Rigbox:eui:SignalsTest:NoStereoOutput', ...
210+
'No stereo output audio devices detected; default unassigned')
211+
else
212+
obj.Hardware.audioDevices(I).DeviceName = 'default';
213+
end
203214
else
204215
obj.Hardware = rig;
205216
end

0 commit comments

Comments
 (0)