diff --git a/test/test_notification.m b/test/test_notification.m
new file mode 100644
index 0000000000..4032ae601d
--- /dev/null
+++ b/test/test_notification.m
@@ -0,0 +1,74 @@
+function test_notification
+
+% WALLTIME 00:10:00
+% MEM 1gb
+
+% TEST ft_debug ft_info ft_notice ft_warning ft_notification
+
+sublevel(@ft_debug)
+sublevel(@ft_info)
+sublevel(@ft_notice)
+sublevel(@ft_warning)
+% sublevel(@ft_error)
+
+function sublevel(notice)
+
+fprintf('========================================\n')
+fprintf('the following should not print anything\n')
+fprintf('========================================\n')
+notice('off', 'FieldTrip:something');
+notice('FieldTrip:something', 'notice %d %d %d\n', 1, 2, 3);
+
+fprintf('========================================\n')
+fprintf('the following should print the query status\n')
+fprintf('========================================\n')
+notice('query', 'FieldTrip:something');
+
+fprintf('========================================\n')
+fprintf('the following should print "notice 4, 5, 6"\n')
+fprintf('========================================\n')
+notice('on', 'FieldTrip:something');
+notice('FieldTrip:something', 'notice %d, %d, %d\n', 4, 5, 6);
+
+fprintf('========================================\n')
+fprintf('the following should print "notice backtrace" with a backtrace\n')
+fprintf('========================================\n')
+notice('on', 'backtrace');
+notice('FieldTrip:something', 'notice backtrace\n');
+notice('off', 'backtrace');
+
+fprintf('========================================\n')
+fprintf('the following should print "pause 1", "4"\n')
+fprintf('========================================\n')
+notice('once', 'FieldTrip:once');
+notice('timeout', 3);
+for i=1:5
+ pause(1);
+ notice('FieldTrip:once', 'pause %d\n', i);
+end
+
+notice('clear')
+notice('on', '1')
+notice('on', '2')
+notice('off', 'a')
+notice('off', 'b')
+fprintf('========================================\n')
+fprintf('the following should show 1 and 2 as ''on''\n')
+fprintf('========================================\n')
+notice('off')
+notice('query')
+fprintf('========================================\n')
+fprintf('the following should show a and b as ''off''\n')
+fprintf('========================================\n')
+notice('on')
+notice('query')
+
+notice('clear')
+fprintf('========================================\n')
+fprintf('the following should show ''a''\n')
+fprintf('========================================\n')
+notice('a');
+notice('last')
+
+
+
diff --git a/utilities/private/ft_notification.m b/utilities/private/ft_notification.m
new file mode 100644
index 0000000000..4f35ef3b16
--- /dev/null
+++ b/utilities/private/ft_notification.m
@@ -0,0 +1,365 @@
+function [varargout] = ft_notification(varargin)
+
+% FT_NOTIFICATION works mostly like the WARNING and ERROR commands in MATLAB and
+% is called by FT_ERROR, FT_WARNING, FT_NOTICE, FT_INFO, FT_DEBUG. Note that you
+% should not call this function directly.
+%
+% Some examples:
+% ft_info on
+% ft_info on msgId
+% ft_info off
+% ft_info off msgId
+% ft_info once
+% ft_info once msgId
+% ft_info backtrace on
+% ft_info backtrace off
+%
+% ft_info query % shows the status of all notifications
+% ft_info last % shows the last notification
+% ft_info clear % clears the status of all notifications
+% ft_info timeout 10 % sets the timeout (for 'once') to 10 seconds
+
+% Copyright (C) 2012-2017, Robert Oostenveld, J?rn M. Horschig
+%
+% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
+% for the documentation and details.
+%
+% FieldTrip is free software: you can redistribute it and/or modify
+% it under the terms of the GNU General Public License as published by
+% the Free Software Foundation, either version 3 of the License, or
+% (at your option) any later version.
+%
+% FieldTrip is distributed in the hope that it will be useful,
+% but WITHOUT ANY WARRANTY; without even the implied warranty of
+% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+% GNU General Public License for more details.
+%
+% You should have received a copy of the GNU General Public License
+% along with FieldTrip. If not, see .
+%
+% $Id$
+
+global ft_default
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% determine who is calling
+
+stack = dbstack('-completenames');
+% st(1) is this function
+% st(2) is the calling function
+switch stack(2).name
+ case 'ft_debug'
+ level = 'debug';
+ case 'ft_info'
+ level = 'info';
+ case 'ft_notice'
+ level = 'notice';
+ case 'ft_warning'
+ level = 'warning';
+ case 'ft_error'
+ level = 'error';
+ otherwise
+ error('this function cannot be called from %s', stack(2).name);
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% set the notification state according to the input
+
+if numel(varargin)>0 && isstruct(varargin{1})
+ ft_default.notification.(level) = varargin{1};
+ return
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% handle the defaults
+
+if isfield(ft_default, 'notification') && isfield(ft_default.notification, level)
+ % this would be error, warning, notice, info, debug
+ s = ft_default.notification.(level);
+else
+ s = [];
+end
+
+% set the default notification state
+if isempty(s) || ~ismember('all', {s.identifier})
+ s(end+1).identifier = 'all';
+ s(end ).state = 'on';
+ s(end ).timestamp = nan; % this ensures that the field exists
+end
+
+% set the default backtrace state
+if isempty(s) || ~ismember('backtrace', {s.identifier})
+ s(end+1).identifier = 'backtrace';
+ s(end ).state = 'on';
+ s(end ).timestamp = nan; % this ensures that the field exists
+end
+
+% set the default timeout
+if isempty(s) || ~ismember('timeout', {s.identifier})
+ s(end+1).identifier = 'timeout';
+ s(end ).state = 60; % default is 60 seconds
+ s(end ).timestamp = nan; % this ensures that the field exists
+end
+
+% set the last notification to empty
+if isempty(s) || ~ismember('last', {s.identifier})
+ s(end+1).identifier = 'last';
+ s(end ).state.message = '';
+ s(end ).state.identifier = '';
+ s(end ).state.stack = struct('file', {}, 'name', {}, 'line', {});
+ s(end ).timestamp = nan; % this ensures that the field exists
+end
+
+if isempty(varargin)
+ varargin{1} = 'query';
+end
+
+if numel(stack)>2
+ % it is called from within a function
+ name = {stack(3:end).name};
+ defaultId = ['FieldTrip' sprintf(':%s', name{:})];
+else
+ % it is called from the command line
+ defaultId = '';
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% act according to the first input argument
+
+switch varargin{1}
+ case 'on'
+ if numel(varargin)>1
+ % switch a specific item on
+ msgId = varargin{2};
+ s = setstate(s, msgId, 'on');
+ else
+ % switch all on
+ s = setstate(s, 'all', 'on');
+ end
+
+ case 'off'
+ if numel(varargin)>1
+ % switch a specific item off
+ msgId = varargin{2};
+ s = setstate(s, msgId, 'off');
+ else
+ % switch all off
+ s = setstate(s, 'all', 'off');
+ end
+
+ case 'once'
+ if numel(varargin)>1
+ % switch a specific item to once
+ msgId = varargin{2};
+ s = setstate(s, msgId, 'once');
+ else
+ % switch all to once
+ s = setstate(s, 'all', 'once');
+ end
+
+ case 'timeout'
+ % set the timeout, this is used for 'once'
+ if ischar(varargin{2})
+ s = setstate(s, 'timeout', str2double(varargin{2}));
+ else
+ s = setstate(s, 'timeout', varargin{2});
+ end
+
+ case {'last' '-last'}
+ % return the last notification
+ varargout{1} = getstate(s, 'last');
+
+ case {'clear' '-clear'}
+ % reset the notification system
+ s = [];
+
+ case {'query' '-query'}
+ if numel(varargin)>1
+ % select a specific item
+ msgId = varargin{2};
+ msgState = getstate(s, msgId);
+ if nargout
+ r = struct('identifier', msgId, 'state', msgState);
+ varargout{1} = r;
+ else
+ fprintf('The state of %s ''%s'' is ''%s''\n', level, msgId, msgState);
+ end
+ else
+ % return all items
+ r = s;
+ % don't return the backtrace, timeout and last
+ r(strcmp('backtrace', {r.identifier})) = [];
+ r(strcmp('timeout', {r.identifier})) = [];
+ r(strcmp('last', {r.identifier})) = [];
+
+ if nargout
+ % do not return the timestamp field
+ varargout{1} = rmfield(r, 'timestamp');
+ else
+ % show the state of all items that are different from the default
+ default = getstate(s, 'all');
+ fprintf('The default %s state is ''%s''.', level, default);
+ r = r(~strcmp({r.state}, default));
+ if ~isempty(r)
+ fprintf(' Items not set to the default are\n\n');
+ end
+ for i=1:numel(r)
+ fprintf(' %4s %s\n', r(i).state, r(i).identifier);
+ end
+ fprintf('\n');
+ end
+ end
+
+ otherwise
+ % first input might be msgId
+ if any(varargin{1}==':') && numel(varargin)>1
+ msgId = varargin{1};
+ varargin = varargin(2:end); % shift them all by one
+ else
+ msgId = [];
+ end
+
+ % get the state for this notification, it will default to the 'all' state
+ msgState = getstate(s, msgId);
+
+ % errors are always to be printed
+ if strcmp(level, 'error')
+ msgState = 'on';
+ end
+
+ % ensure there is a line end
+ if isempty(regexp(varargin{1}, '\\n$', 'once')) % note the double \\
+ varargin{1} = [varargin{1} '\n'];
+ end
+
+ if strcmp(msgState, 'once')
+ timeout = getstate(s, 'timeout');
+ since = elapsed(gettimestamp(s, msgId));
+ if (since>timeout)
+ % the timeout has passed, update the timestamp and print the message
+ s = settimestamp(s, msgId, tic);
+ msgState = 'on';
+ else
+ % the timeout has not yet passed, do not print the message
+ msgState = 'off';
+ end
+ end
+
+ % use an automatically generated default identifier
+ if isempty(msgId)
+ msgId = defaultId;
+ end
+
+ % remove this function itself and the calling function
+ stack = stack(3:end);
+
+ % store the last notification
+ state.message = sprintf(varargin{:});
+ state.message = state.message(1:end-1); % remove the trailing newline
+ state.identifier = msgId;
+ state.stack = stack;
+ s = setstate(s, 'last', state);
+
+ if strcmp(msgState, 'on')
+
+ if strcmp(level, 'error')
+ % this is fully handled by the ERROR function
+ error(msgId, varargin{:});
+
+ elseif strcmp(level, 'warning')
+ % the backtrace is handled by the WARNING function
+ if istrue(getstate(s, 'backtrace'))
+ warning('backtrace', 'on');
+ else
+ warning('backtrace', 'off');
+ end
+ warning(msgId, varargin{:});
+
+ else
+ fprintf(varargin{:});
+
+ % decide whether the stack should be shown
+ if istrue(getstate(s, 'backtrace'))
+ for i=numel(stack):-1:1
+ [p, f, x] = fileparts(stack(i).file);
+ if isequal(f, stack(i).name)
+ funname = stack(i).name;
+ else
+ funname = sprintf('%s>%s', f, stack(i).name);
+ end
+ filename = stack(i).file;
+ if isempty(fileparts(filename))
+ % it requires the full path
+ filename = fullfile(pwd, filename);
+ end
+ fprintf(' In %s at line %d\n', filename, stack(i).line, funname, stack(i).line);
+ end
+ fprintf('\n');
+ end
+
+ end % if level=error, warning or otherwise
+
+ else
+ % don't print it
+ end % if msgState is on
+
+end % switch varargin{1}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% update the global variable
+ft_default.notification.(level) = s;
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% SUBFUNCTIONS
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+function state = getstate(s, msgId)
+identifier = {s.identifier};
+sel = find(strcmp(identifier, msgId));
+if numel(sel)==1
+ state = s(sel).state;
+ if isempty(state)
+ state = getstate(s, 'all');
+ end
+else
+ state = getstate(s, 'all');
+end
+
+function timestamp = gettimestamp(s, msgId)
+identifier = {s.identifier};
+sel = find(strcmp(identifier, msgId));
+if numel(sel)==1
+ timestamp = s(sel).timestamp;
+else
+ timestamp = nan;
+end
+
+function s = setstate(s, msgId, state)
+identifier = {s.identifier};
+sel = find(strcmp(identifier, msgId));
+if numel(sel)==1
+ s(sel).state = state;
+else
+ s(end+1).identifier = msgId;
+ s(end ).state = state;
+ s(end ).timestamp = nan;
+end
+
+function s = settimestamp(s, msgId, timestamp)
+identifier = {s.identifier};
+sel = find(strcmp(identifier, msgId));
+if numel(sel)==1
+ s(sel).timestamp = timestamp;
+else
+ s(end+1).identifier = msgId;
+ s(end ).state = [];
+ s(end ).timestamp = timestamp;
+end
+
+function t = elapsed(start)
+if isempty(start) || isnan(start)
+ t = inf;
+else
+ t = toc(start);
+end
\ No newline at end of file