Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SubprocessWidget improvements #237

Merged
merged 4 commits into from
Jun 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions qcodes/widgets/display.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
"""Helper for adding content stored in a file to a jupyter notebook."""
import os
from pkg_resources import resource_string
from IPython.display import display, Javascript, HTML


# Originally I implemented this using regular open() and read(), so it
# could use relative paths from the importing file.
#
# But for distributable packages, pkg_resources.resource_string is the
# best way to load data files, because it works even if the package is
# in an egg or zip file. See:
# http://pythonhosted.org/setuptools/setuptools.html#accessing-data-files-at-runtime

def display_auto(qcodes_path, file_type=None):
'''
Display some javascript, css, or html content in the notebook
from a package-relative file path. Will use the file extension
to determine file type unless overridden by file_type

qcodes_path: the path to the target file within the qcodes package

file_type: optionally override the file extension to determine
what type of file this is
'''

# Originally I implemented this using regular open() and read(), so it
# could use relative paths from the importing file.
#
# But for distributable packages, pkg_resources.resource_string is the
# best way to load data files, because it works even if the package is
# in an egg or zip file. See:
# http://pythonhosted.org/setuptools/setuptools.html#accessing-data-files-at-runtime
"""
Display some javascript, css, or html content in a jupyter notebook.

Content comes from a package-relative file path. Will use the file
extension to determine file type unless overridden by file_type

Args:
qcodes_path (str): the path to the target file within the qcodes
package, like 'widgets/widgets.js'

file_type (Optional[str]): Override the file extension to determine
what type of file this is. Case insensitive, supported values
are 'js', 'css', and 'html'
"""
contents = resource_string('qcodes', qcodes_path).decode('utf-8')

if file_type is None:
ext = os.path.splitext(qcodes_path)[1].lower()
elif 'js' in file_type.lower():
Expand Down
29 changes: 28 additions & 1 deletion qcodes/widgets/widgets.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,37 @@
box-shadow: 0 0 12px 1px rgba(87, 87, 87, 0.2);
}

.qcodes-output-header {
.qcodes-output-header {
float: right;
}

.qcodes-highlight {
animation: pulse 1s linear;
background-color: #fa4;
}

@keyframes pulse {
0% {
background-color: #f00;
}
100% {
background-color: #fa4;
}
}

.qcodes-process-list {
float: left;
max-width: 780px;
margin: 3px 5px 3px 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.qcodes-output-view[qcodes-state=minimized] .qcodes-process-list {
max-width: 300px;
}

.qcodes-output-view span {
padding: 2px 6px 3px 12px;
}
Expand Down
128 changes: 94 additions & 34 deletions qcodes/widgets/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ require([

var SubprocessView = UpdateView.extend({
render: function() {
var me = window.SPVIEW = this;
var me = this;
me._interval = 0;
me._minimize = '<i class="fa-minus fa"></i>';
me._restore = '<i class="fa-plus fa"></i>';

// max lines of output to show
me.maxOutputLength = 500;

// in case there is already an outputView present,
// like from before restarting the kernel
$('.qcodes-output-view').not(me.$el).remove();
Expand All @@ -70,7 +73,8 @@ require([
.attr('qcodes-state', 'docked')
.html(
'<div class="qcodes-output-header toolbar">' +
'<span></span>' +
'<div class="qcodes-process-list"></div>' +
'<button class="btn qcodes-processlines"><i class="fa-list fa"></i></button>' +
'<button class="btn qcodes-abort-loop disabled">Abort</button>' +
'<button class="btn qcodes-clear-output disabled qcodes-content">Clear</button>' +
'<button class="btn js-state qcodes-minimized"><i class="fa-minus fa"></i></button>' +
Expand All @@ -83,8 +87,11 @@ require([
me.clearButton = me.$el.find('.qcodes-clear-output');
me.minButton = me.$el.find('.qcodes-minimize');
me.outputArea = me.$el.find('pre');
me.subprocessList = me.$el.find('span');
me.subprocessList = me.$el.find('.qcodes-process-list');
me.abortButton = me.$el.find('.qcodes-abort-loop');
me.processLinesButton = me.$el.find('.qcodes-processlines')

me.outputLines = [];

me.clearButton.click(function() {
me.outputArea.html('');
Expand All @@ -95,6 +102,12 @@ require([
me.send({abort: true});
});

me.processLinesButton.click(function() {
// toggle multiline process list display
me.subprocessesMultiline = !me.subprocessesMultiline;
me.showSubprocesses();
});

me.$el.find('.js-state').click(function() {
var oldState = me.$el.attr('qcodes-state'),
state = this.className.substr(this.className.indexOf('qcodes'))
Expand All @@ -112,53 +125,100 @@ require([
me.$el.attr('qcodes-state', state);

if(state === 'floated') {
me.$el.draggable().css({
left: window.innerWidth - me.$el.width() - 15,
top: window.innerHeight - me.$el.height() - 10
});
me.$el
.draggable({stop: function() { me.clipBounds(); }})
.css({
left: window.innerWidth - me.$el.width() - 15,
top: window.innerHeight - me.$el.height() - 10
});
}
});

$(window).resize(function() {
if(me.$el.attr('qcodes-state') === 'floated') {
var position = me.$el.position(),
minVis = 20,
maxLeft = window.innerWidth - minVis,
maxTop = window.innerHeight - minVis;

if(position.left > maxLeft) me.$el.css('left', maxLeft);
if(position.top > maxTop) me.$el.css('top', maxTop);
}
// any previous highlighting is
me.$el.removeClass('qcodes-highlight');
});

$(window)
.off('resize.qcodes')
.on('resize.qcodes', function() {me.clipBounds();});

me.update();
},

clipBounds: function() {
var me = this;
if(me.$el.attr('qcodes-state') === 'floated') {
var bounds = me.$el[0].getBoundingClientRect(),
minVis = 40,
maxLeft = window.innerWidth - minVis,
minLeft = minVis - bounds.width,
maxTop = window.innerHeight - minVis;

if(bounds.left > maxLeft) me.$el.css('left', maxLeft);
else if(bounds.left < minLeft) me.$el.css('left', minLeft);

if(bounds.top > maxTop) me.$el.css('top', maxTop);
else if(bounds.top < 0) me.$el.css('top', 0);
console.log(bounds);
}
},

display: function(message) {
var me = this;
if(message) {
var initialScroll = this.outputArea.scrollTop();
this.outputArea.scrollTop(this.outputArea.prop('scrollHeight'));
var scrollBottom = this.outputArea.scrollTop();

if(this.$el.attr('qcodes-state') === 'minimized') {
this.$el.find('.qcodes-docked').click();
// always scroll to the bottom if we're restoring
// because of a new message
initialScroll = scrollBottom;
var initialScroll = me.outputArea.scrollTop();
me.outputArea.scrollTop(me.outputArea.prop('scrollHeight'));
var scrollBottom = me.outputArea.scrollTop();

if(me.$el.attr('qcodes-state') === 'minimized') {
// if we add text and the box is minimized, highlight the
// title bar to alert the user that there are new messages.
// remove then add the class, so we get the animation again
// if it's already highlighted
me.$el.removeClass('qcodes-highlight');
setTimeout(function(){
me.$el.addClass('qcodes-highlight');
}, 0);
}

var newLines = message.split('\n'),
out = me.outputLines,
outLen = out.length;
if(outLen) out[outLen - 1] += newLines[0];
else out.push(newLines[0]);

for(var i = 1; i < newLines.length; i++) {
out.push(newLines[i]);
}

this.outputArea.append(message);
this.clearButton.removeClass('disabled');
if(out.length > me.maxOutputLength) {
out.splice(0, out.length - me.maxOutputLength + 1,
'<<< Output clipped >>>');
}

me.outputArea.text(out.join('\n'));
me.clearButton.removeClass('disabled');

// if we were scrolled to the bottom initially, make sure
// we stay that way.
this.outputArea.scrollTop(initialScroll === scrollBottom ?
this.outputArea.prop('scrollHeight') : initialScroll);
me.outputArea.scrollTop(initialScroll === scrollBottom ?
me.outputArea.prop('scrollHeight') : initialScroll);
}

var processes = this.model.get('_processes') || 'No subprocesses';
this.abortButton.toggleClass('disabled', processes.indexOf('Measurement')===-1);
this.subprocessList.text(processes);
var processes = me.model.get('_processes');
me.abortButton.toggleClass('disabled', processes.indexOf('Measurement')===-1);
me._processes = processes;
me.showSubprocesses();
},

showSubprocesses: function() {
var me = this,
replacer = me.subprocessesMultiline ? '<br>' : ', ',
processes = (me._processes || '').replace(/\n/g, '&gt;' + replacer + '&lt;');

if(processes) processes = '&lt;' + processes + '&gt;';
else processes = 'No subprocesses';

me.subprocessList.html(processes);
}
});
manager.WidgetManager.register_widget_view('SubprocessView', SubprocessView);
Expand Down
Loading