198198
199199 handles = buildmenus(handles );
200200
201+ % centred overlay shown while a blocking operation runs; a panel inside the
202+ % figure rather than a separate window, so it is guaranteed to paint and is
203+ % destroyed together with the browser
204+ handles.pnBusy = uipanel(handles .fmMain, ' units' , ' normalized' , ' position' , [0.30 0.44 0.40 0.12 ], ...
205+ ' visible' , ' off' , ' backgroundcolor' , [1 0.95 0.75 ]);
206+ handles.txBusy = uicontrol(handles .pnBusy, ' style' , ' text' , ' units' , ' normalized' , ...
207+ ' position' , [0.04 0.1 0.92 0.8 ], ' backgroundcolor' , [1 0.95 0.75 ], ...
208+ ' fontweight' , ' bold' , ' HorizontalAlignment' , ' center' , ' string' , ' ' );
209+
201210 set(handles .fmMain, ' userdata' , handles );
202211 set(handles .fmMain, ' WindowButtonDownFcn' , @(src , events ) splitterdown(handles .fmMain));
203212 set(handles .fmMain, ' WindowButtonUpFcn' , @(src , events ) splitterup(handles .fmMain));
@@ -962,20 +971,73 @@ function setenable(h, tf)
962971end
963972
964973% --------------------------------------------------------------------------
965- function setbusy(hwin , isbusy )
966- % show progress by switching the figure pointer - unlike the previously used
967- % hidden msgbox, this is destroyed together with the browser window
974+ function setbusy(hwin , state )
975+ % make a blocking operation visible: grey out the lists and tools so a second
976+ % one cannot be started, show a centred message panel and a watch pointer.
977+ %
978+ % state is false to clear, true for a generic wait, or a message string.
979+ % MATLAB and Octave both run callbacks on the main thread, so a blocking call
980+ % cannot animate itself - the indicator is painted before the call starts and
981+ % callers advance it between steps with setbusy(hwin, 'next phase...').
968982
969983if (~ishandle(hwin ))
970984 return
971985end
972- if (isbusy )
986+ handles = get(hwin , ' userdata' );
987+ if (~isstruct(handles ) || ~isfield(handles , ' pnBusy' ))
988+ return
989+ end
990+
991+ busy = ~(isequal(state , false ) || isequal(state , 0 ));
992+ lists = [handles .lsDb, handles .lsDs, handles .lsJSON, handles .lsPeek];
993+
994+ if (busy )
995+ message = ' Working...' ;
996+ if (ischar(state ) && ~isempty(state ))
997+ message = state ;
998+ end
999+ set(handles .txBusy, ' string' , [spinchar(hwin ) ' ' message ]);
1000+ set(handles .pnBusy, ' visible' , ' on' );
1001+ set(handles .txStatus, ' string' , message , ' backgroundcolor' , [1 0.95 0.75 ], ' fontweight' , ' bold' );
9731002 set(hwin , ' pointer' , ' watch' );
1003+ set(lists , ' enable' , ' off' );
1004+ settoolenable(handles , false );
9741005else
1006+ set(handles .pnBusy, ' visible' , ' off' );
1007+ set(handles .txStatus, ' backgroundcolor' , get(hwin , ' color' ), ' fontweight' , ' normal' );
9751008 set(hwin , ' pointer' , ' arrow' );
1009+ set(lists , ' enable' , ' on' );
1010+ updateactions(hwin );
9761011end
9771012drawnow ;
9781013
1014+ % --------------------------------------------------------------------------
1015+ function ch = spinchar(hwin )
1016+ % advance a one-character spinner; it turns whenever a caller reports a new
1017+ % phase, which is the only motion achievable while the main thread is blocked
1018+
1019+ frames = ' |/-\' ;
1020+ idx = getappdata(hwin , ' spinstep' );
1021+ if (isempty(idx ))
1022+ idx = 0 ;
1023+ end
1024+ idx = mod(idx , length(frames )) + 1 ;
1025+ setappdata(hwin , ' spinstep' , idx );
1026+ ch = frames(idx );
1027+
1028+ % --------------------------------------------------------------------------
1029+ function settoolenable(handles , tf )
1030+ % toggle every toolbar button and context-menu entry at once; the contextual
1031+ % per-item state is restored afterwards by updateactions()
1032+
1033+ names = {' btDownload' , ' btPreview' , ' btSaveAs' , ' btSubtree' , ' btAttach' , ...
1034+ ' miDownload' , ' miPreview' , ' miSaveAs' , ' miSubtree' , ' miAttach' };
1035+ for i = 1 : length(names )
1036+ if (isfield(handles , names{i }))
1037+ setenable(handles.(names{i }), tf );
1038+ end
1039+ end
1040+
9791041% --------------------------------------------------------------------------
9801042function tf = isactivated(handles , event )
9811043% a list entry is "activated" by a double-click or by pressing enter; the
@@ -1830,7 +1892,7 @@ function peekselect(src, events, hwin)
18301892 peekkeys = getappdata(hwin , ' peekkeys' );
18311893 idx = get(handles .lsPeek, ' value' );
18321894 if (node .ok && ~isempty(peekkeys ) && idx(1 ) >= 1 && idx(1 ) <= length(peekkeys ))
1833- setbusy(hwin , true );
1895+ setbusy(hwin , ' Opening... ' );
18341896 try
18351897 descendinto(hwin , node .key);
18361898 selectkey(hwin , peekkeys{idx(1 )});
@@ -2088,7 +2150,7 @@ function actdownload(hwin)
20882150 return
20892151end
20902152
2091- setbusy(hwin , true );
2153+ setbusy(hwin , sprintf( ' Downloading %s ... ' , node .label) );
20922154cachedfile = ' ' ;
20932155try
20942156 [~ , cachedfile ] = jdlink(node .meta.url);
@@ -2115,14 +2177,15 @@ function actpreview(hwin)
21152177 return
21162178end
21172179
2118- setbusy(hwin , true );
2180+ setbusy(hwin , sprintf( ' Decoding %s for preview... ' , node .label) );
21192181try
21202182 data = resolvenode(node );
21212183catch err
21222184 setbusy(hwin , false );
21232185 errordlg([' Cannot decode the selected data: ' err .message], ' Preview' );
21242186 return
21252187end
2188+ setbusy(hwin , ' Rendering preview...' );
21262189setbusy(hwin , false );
21272190try
21282191 previewdata(data , node .label);
@@ -2156,7 +2219,7 @@ function actsaveas(hwin)
21562219end
21572220target = fullfile(fpath , fname );
21582221
2159- setbusy(hwin , true );
2222+ setbusy(hwin , sprintf( ' Saving %s ... ' , fname ) );
21602223try
21612224 if (strcmp(node .meta.kind, ' datalink' ) && ~isempty(node .meta.cachefile))
21622225 copyfile(node .meta.cachefile, target );
@@ -2237,7 +2300,7 @@ function actdownloadattachments(hwin)
22372300 return
22382301end
22392302
2240- setbusy(hwin , true );
2303+ setbusy(hwin , sprintf( ' Scanning " %s " for linked files... ' , label ) );
22412304links = subtreelinks(data );
22422305setbusy(hwin , false );
22432306if (isempty(links ))
@@ -2354,7 +2417,7 @@ function actexportsubtree(hwin)
23542417end
23552418target = fullfile(fpath , fname );
23562419
2357- setbusy(hwin , true );
2420+ setbusy(hwin , sprintf( ' Exporting subtree to %s ... ' , fname ) );
23582421try
23592422 writesubtree(data , target );
23602423catch err
@@ -2745,7 +2808,7 @@ function exportdataset(hwin)
27452808 return
27462809end
27472810
2748- setbusy(hwin , true );
2811+ setbusy(hwin , sprintf( ' Exporting %s / %s to a folder... ' , dbname , dsname ) );
27492812try
27502813 res = neuroj(' export' , dbname , dsname );
27512814catch err
@@ -2764,7 +2827,7 @@ function exportdataset(hwin)
27642827function loaddb(src , event , hwin )
27652828
27662829handles = get(hwin , ' userdata' );
2767- setbusy(hwin , true );
2830+ setbusy(hwin , ' Listing databases on neurojson.io... ' );
27682831try
27692832 dbs = neuroj(' list' );
27702833 dbids = cellfun(@(x ) x .id, dbs .database, ' UniformOutput' , false );
@@ -2844,7 +2907,7 @@ function dosearch(hwin)
28442907
28452908param = [param , ' limit' , strtrim(get(handles .hLimit, ' string' )), ...
28462909 ' skip' , strtrim(get(handles .hSkip, ' string' ))];
2847- setbusy(hwin , true );
2910+ setbusy(hwin , ' Searching neurojson.io... ' );
28482911try
28492912 result = webread(baseurl , param{: });
28502913catch err
@@ -3018,7 +3081,7 @@ function loadds(src, event, hwin)
30183081handles = get(hwin , ' userdata' );
30193082if (isactivated(handles , event ))
30203083 dbname = selecteditem(hwin , handles .lsDb, ' dbkeys' );
3021- setbusy(hwin , true );
3084+ setbusy(hwin , sprintf( ' Listing datasets in %s ... ' , dbname ) );
30223085 try
30233086 searchdatasets = getappdata(hwin , ' searchDatasets' );
30243087 if (~isempty(searchdatasets ) && isa(searchdatasets , ' containers.Map' ) && isKey(searchdatasets , dbname ))
@@ -3052,7 +3115,7 @@ function loaddsdata(src, event, hwin)
30523115 setstatus(hwin , ' Please select a database and a dataset first' );
30533116 return
30543117 end
3055- setbusy(hwin , true );
3118+ setbusy(hwin , sprintf( ' Loading %s / %s ... ' , dbname , dsname ) );
30563119 try
30573120 % a search result lists the matching subjects instead of the document tree
30583121 searchsubjects = getappdata(hwin , ' searchSubjects' );
@@ -3087,9 +3150,10 @@ function loaddataset(hwin, dbid, dsname)
30873150% load a document without decoding JData constructs, so the browser can show
30883151% the _DataLink_/_ArrayType_ metadata and decode only what the user asks for
30893152
3090- handles = get (hwin , ' userdata ' );
3153+ setbusy (hwin , sprintf( ' Downloading %s / %s ... ' , dbid , dsname ) );
30913154data = neuroj(' get' , dbid , dsname , ' ' , ' jdatadecode' , 0 );
30923155
3156+ setbusy(hwin , sprintf(' Building the tree for %s ...' , dsname ));
30933157setappdata(hwin , ' rootdata' , data );
30943158setappdata(hwin , ' pathstack' , {});
30953159setappdata(hwin , ' dbname' , dbid );
@@ -3110,7 +3174,7 @@ function expandjsontree(src, event, hwin)
31103174 if (~activated )
31113175 shownodeinfo(hwin );
31123176 else
3113- setbusy(hwin , true );
3177+ setbusy(hwin , ' Opening... ' );
31143178 try
31153179 stepintonode(hwin );
31163180 catch err
@@ -3119,7 +3183,7 @@ function expandjsontree(src, event, hwin)
31193183 setbusy(hwin , false );
31203184 end
31213185elseif (activated && ~isempty(getappdata(hwin , ' subjectrows' )))
3122- setbusy(hwin , true );
3186+ setbusy(hwin , ' Loading the dataset for this subject... ' );
31233187 try
31243188 opensubject(hwin );
31253189 catch err
0 commit comments