diff --git a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js index 70b9381f58d6b3bda23a162b9b452add52104f21..3f91894ec998f811ff15c365eb40aa98d78b2199 100644 --- a/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js +++ b/screening/source/core-plugins/microscopy/1/as/webapps/image-viewer/html/image-viewer.js @@ -1,3 +1,91 @@ +// +// IMAGE VIEWER VIEW +// + +function ImageViewerView(controller) { + this.init(controller); +} + +$.extend(ImageViewerView.prototype, { + init: function(controller){ + this.controller = controller; + this.imageLoader = new ImageLoader(); + this.panel = $("<div>"); + }, + + render: function(){ + this.panel.append(this.createChannelWidget()); + this.panel.append(this.createResolutionWidget()); + this.panel.append(this.createChannelStackWidget()); + this.panel.append(this.createImageWidget()); + + this.rendered = true; + this.refresh(); + + return this.panel; + }, + + refresh: function(){ + if(!this.rendered){ + return; + } + + this.channelWidget.setSelectedChannel(this.controller.getSelectedChannel()); + this.channelWidget.setSelectedMergedChannels(this.controller.getSelectedMergedChannels()); + this.resolutionWidget.setSelectedResolution(this.controller.getSelectedResolution()); + this.channelStackWidget.setSelectedChannelStackId(this.controller.getSelectedChannelStackId()); + this.imageWidget.setImage(this.controller.getSelectedImageData()); + }, + + createChannelWidget: function(){ + var thisView = this; + + var widget = new ChannelChooserWidget(this.controller.getAllChannels()); + widget.addChangeListener(function(){ + thisView.controller.setSelectedChannel(thisView.channelWidget.getSelectedChannel()); + thisView.controller.setSelectedMergedChannels(thisView.channelWidget.getSelectedMergedChannels()); + }); + + this.channelWidget = widget; + return widget.render(); + }, + + createResolutionWidget: function(){ + var thisView = this; + + var widget = new ResolutionChooserWidget(this.controller.getAllResolutions()); + widget.addChangeListener(function(){ + thisView.controller.setSelectedResolution(thisView.resolutionWidget.getSelectedResolution()); + }); + + this.resolutionWidget = widget; + return widget.render(); + }, + + createChannelStackWidget: function(){ + var thisView = this; + + var widget = new ChannelStackChooserWidget(this.controller.getAllChannelStacks(), function(channelStack, callback){ + var imageData = thisView.controller.getSelectedImageData(); + imageData.setChannelStackId(channelStack.id); + thisView.imageLoader.loadImage(imageData, callback); + }); + + widget.addChangeListener(function(){ + thisView.controller.setSelectedChannelStackId(thisView.channelStackWidget.getSelectedChannelStackId()); + }); + + this.channelStackWidget = widget; + return widget.render(); + }, + + createImageWidget: function(){ + this.imageWidget = new ImageWidget(this.imageLoader); + return this.imageWidget.render(); + } + +}); + // // IMAGE VIEWER // @@ -7,6 +95,7 @@ function ImageViewerWidget(openbis, dataSetCode) { $.extend(ImageViewerWidget.prototype, { init: function(openbis, dataSetCode){ + this.setView(new ImageViewerView(this)); this.facade = new OpenbisFacade(openbis); this.dataSetCode = dataSetCode; this.panel = $("<div>") @@ -16,77 +105,180 @@ $.extend(ImageViewerWidget.prototype, { if(this.loaded){ callback(); }else{ - var thisImageViewer = this; + var thisViewer = this; var manager = new CallbackManager(function(){ - thisImageViewer.loaded = true; + thisViewer.loaded = true; + + var channels = thisViewer.getAllChannels(); + + if(channels && channels.length > 0){ + thisViewer.setSelectedMergedChannels(channels.map(function(channel){ + return channel.code + })); + } + + var channelStacks = thisViewer.getAllChannelStacks(); + + if(channelStacks && channelStacks.length > 0){ + thisViewer.setSelectedChannelStackId(channelStacks[0].id); + } + callback(); }); - this.facade.tryGetDataStoreBaseURL(thisImageViewer.dataSetCode, manager.registerCallback(function(response){ - thisImageViewer.dataStoreUrl = response.result; + this.facade.tryGetDataStoreBaseURL(thisViewer.dataSetCode, manager.registerCallback(function(response){ + thisViewer.dataStoreUrl = response.result; })); - this.facade.getImageInfo(thisImageViewer.dataSetCode, manager.registerCallback(function(response){ - thisImageViewer.imageInfo = response.result; + this.facade.getImageInfo(thisViewer.dataSetCode, manager.registerCallback(function(response){ + thisViewer.imageInfo = response.result; })); - this.facade.getImageResolutions(thisImageViewer.dataSetCode, manager.registerCallback(function(response){ - thisImageViewer.imageResolutions = response.result; + this.facade.getImageResolutions(thisViewer.dataSetCode, manager.registerCallback(function(response){ + thisViewer.imageResolutions = response.result; })); } }, render: function(){ - var thisImageViewer = this; + var thisViewer = this; this.load(function(){ - - var channelWidget = new ChannelChooserWidget(thisImageViewer.getChannels()); - channelWidget.addChangeListener(function(){ - imageWidget.setChannels(channelWidget.getSelectedOrMergedChannels()); - }); - thisImageViewer.panel.append(channelWidget.render()); - - var resolutionWidget = new ResolutionChooserWidget(thisImageViewer.getResolutions()); - resolutionWidget.addChangeListener(function(){ - imageWidget.setResolution(resolutionWidget.getSelectedResolution()); - }); - thisImageViewer.panel.append(resolutionWidget.render()); - - var channelStackWidget = new ChannelStackChooserWidget(thisImageViewer.getChannelStacks()); - channelStackWidget.addChangeListener(function(){ - imageWidget.setChannelStackId(channelStackWidget.getSelectedChannelStack().id); - }); - thisImageViewer.panel.append(channelStackWidget.render()); - - var imageWidget = new ImageWidget(); - imageWidget.setSessionToken(thisImageViewer.facade.getSession()); - imageWidget.setDataStoreUrl(thisImageViewer.dataStoreUrl); - imageWidget.setDataSetCode(thisImageViewer.dataSetCode); - imageWidget.setChannelStackId(channelStackWidget.getSelectedChannelStack().id); - imageWidget.setResolution(resolutionWidget.getSelectedResolution()); - imageWidget.setChannels(channelWidget.getSelectedOrMergedChannels()); - - thisImageViewer.panel.append(imageWidget.render()); - + if(thisViewer.getView()){ + thisViewer.panel.append(thisViewer.getView().render()); + } }); return this.panel; }, - getChannels: function(){ + refresh: function(){ + if(this.getView()){ + this.view.refresh(); + } + }, + + getView: function(){ + return this.view; + }, + + setView: function(view){ + this.view = view; + this.refresh(); + }, + + getSelectedChannel: function(){ + if(this.selectedChannel != null){ + return this.selectedChannel; + }else{ + return null; + } + }, + + setSelectedChannel: function(channel){ + if(this.getSelectedChannel() != channel){ + this.selectedChannel = channel; + this.refresh(); + } + }, + + getSelectedMergedChannels: function(){ + if(this.selectedMergedChannels != null){ + return this.selectedMergedChannels; + }else{ + return []; + } + }, + + setSelectedMergedChannels: function(channels){ + if(!channels){ + channels = []; + } + + if(this.getSelectedMergedChannels().toString() != channels.toString()){ + this.selectedMergedChannels = channels; + this.refresh(); + } + }, + + getSelectedChannelStackId: function(){ + if(this.selectedChannelStackId != null){ + return this.selectedChannelStackId; + }else{ + return null; + } + }, + + setSelectedChannelStackId: function(channelStackId){ + if(this.getSelectedChannelStackId() != channelStackId){ + this.selectedChannelStackId = channelStackId; + this.refresh(); + } + }, + + getSelectedResolution: function(){ + if(this.selectedResolution != null){ + return this.selectedResolution; + }else{ + return null; + } + }, + + setSelectedResolution: function(resolution){ + if(this.getSelectedResolution() != resolution){ + this.selectedResolution = resolution; + this.refresh(); + } + }, + + getSelectedImageData: function(){ + var imageData = new ImageData(); + imageData.setDataStoreUrl(this.dataStoreUrl); + imageData.setSessionToken(this.facade.getSession()); + imageData.setDataSetCode(this.dataSetCode); + imageData.setChannelStackId(this.getSelectedChannelStackId()); + + if(this.getSelectedChannel()){ + imageData.setChannels([this.getSelectedChannel()]); + }else{ + imageData.setChannels(this.getSelectedMergedChannels()); + } + imageData.setResolution(this.getSelectedResolution()); + return imageData; + }, + + getAllChannels: function(){ return this.imageInfo.imageDataset.imageDataset.imageParameters.channels; }, - getChannelStacks: function(){ - return this.imageInfo.channelStacks; + getAllChannelStacks: function(){ + return this.imageInfo.channelStacks.sort(function(o1, o2){ + var t1 = o1.timePointOrNull; + var t2 = o2.timePointOrNull; + var d1 = o1.depthOrNull; + var d2 = o2.depthOrNull; + + var compare = function(v1, v2){ + if(v1 > v2){ + return 1; + }else if(v1 < v2){ + return -1; + }else{ + return 0; + } + } + + return compare(t1, t2) * 10 + compare(d1, d2); + }); }, - getResolutions: function(){ + getAllResolutions: function(){ return this.imageResolutions; } + // TODO add listeners for channel, resoluton, channel stack + }); // @@ -119,122 +311,207 @@ $.extend(OpenbisFacade.prototype, { }); // -// CHANNEL CHOOSER +// CHANNEL CHOOSER VIEW // -function ChannelChooserWidget(channels) { - this.init(channels); +function ChannelChooserView(controller) { + this.init(controller); } -$.extend(ChannelChooserWidget.prototype, { +$.extend(ChannelChooserView.prototype, { - init: function(channels){ - this.channels = channels; - this.selectedChannel = null; - this.mergedChannels = channels.map(function(channel){ - return channel.code; - }); - - this.listeners = new ListenerManager(); - this.panel = $("<div>").addClass("widget"); - }, - - render: function(){ + init: function(controller){ + this.controller = controller; + this.panel = $("<div>"); + }, + + render: function(){ var thisChooser = this; + + this.panel.append(this.createChannelWidget()); + this.panel.append(this.createMergedChannelsWidget()); + + this.rendered = true; + this.refresh(); - $("<div>").text("Channel:").appendTo(this.panel); + return this.panel; + }, + + refresh: function(){ + if(!this.rendered){ + return; + } + + var thisView = this; - var select = $("<select>").appendTo(this.panel); + var select = this.panel.find("select"); + var mergedChannels = this.panel.find(".mergedChannelsWidget"); + + if(this.controller.getSelectedChannel() != null){ + select.val(this.controller.getSelectedChannel()); + mergedChannels.hide(); + }else{ + select.val(""); + mergedChannels.find("input").each(function(){ + var checkbox = $(this); + checkbox.prop("checked", thisView.controller.isMergedChannelSelected(checkbox.val())); + checkbox.prop("disabled", !thisView.controller.isMergedChannelEnabled(checkbox.val())); + }); + mergedChannels.show(); + } + }, + + createChannelWidget: function(){ + var thisView = this; + var widget = $("<div>").addClass("channelWidget"); + + $("<div>").text("Channel:").appendTo(widget); + + var select = $("<select>").appendTo(widget); $("<option>").attr("value", "").text("Merged Channels").appendTo(select); - this.channels.forEach(function(channel){ + this.controller.getAllChannels().forEach(function(channel){ $("<option>").attr("value", channel.code).text(channel.label).appendTo(select); }); select.change(function(){ if(select.val() == ""){ - thisChooser.setSelectedChannel(null); + thisView.controller.setSelectedChannel(null); }else{ - thisChooser.setSelectedChannel(select.val()); + thisView.controller.setSelectedChannel(select.val()); } }); - - var mergedChannels = $("<div>").addClass("mergedChannels").appendTo(this.panel); - this.channels.forEach(function(channel){ - var mergedChannel = $("<span>").addClass("mergedChannel").appendTo(mergedChannels); + return widget; + }, + + createMergedChannelsWidget: function(){ + var thisView = this; + var widget = $("<div>").addClass("mergedChannelsWidget"); + + this.controller.getAllChannels().forEach(function(channel){ + var mergedChannel = $("<span>").addClass("mergedChannel").appendTo(widget); $("<input>").attr("type", "checkbox").attr("value", channel.code).appendTo(mergedChannel); mergedChannel.append(channel.label); }); - mergedChannels.find("input").change(function(){ + widget.find("input").change(function(){ var channels = [] - mergedChannels.find("input:checked").each(function(){ - var checkbox = $(this); - channels.push(checkbox.val()); + widget.find("input:checked").each(function(){ + channels.push($(this).val()); }); - thisChooser.setMergedChannels(channels); + thisView.controller.setSelectedMergedChannels(channels); }); - this.rendered = true; - this.refresh(); - - return this.panel; + return widget; + } + +}); + + +// +// CHANNEL CHOOSER +// +function ChannelChooserWidget(channels) { + this.init(channels); +} + +$.extend(ChannelChooserWidget.prototype, { + + init: function(channels){ + this.setView(new ChannelChooserView(this)); + this.listeners = new ListenerManager(); + this.setAllChannels(channels); }, - refresh: function(){ - if(!this.rendered){ - return; + render: function(){ + if(this.getView()){ + return this.getView().render(); } - - var thisChooser = this; - - var select = this.panel.find("select"); - var mergedChannels = this.panel.find(".mergedChannels"); - - if(this.getSelectedChannel()){ - select.val(this.getSelectedChannel()); - mergedChannels.hide(); - }else{ - select.val(""); - mergedChannels.find("input").each(function(){ - var checkbox = $(this); - var checked = $.inArray(checkbox.val(), thisChooser.getMergedChannels()) != -1; - checkbox.prop("checked", checked); - if(checked){ - checkbox.prop("disabled", thisChooser.getMergedChannels().length == 1); - } - }); - mergedChannels.show(); + }, + + refresh: function(){ + if(this.getView()){ + this.getView().refresh(); } }, + + getView: function(){ + return this.view; + }, + + setView: function(view){ + this.view = view; + this.refresh(); + }, getSelectedChannel: function(){ - return this.selectedChannel; + if(this.selectedChannel){ + return this.selectedChannel; + }else{ + return null; + } }, setSelectedChannel: function(channel){ - this.selectedChannel = channel; - this.refresh(); - this.notifyChangeListeners(); + if(this.getSelectedChannel() != channel){ + this.selectedChannel = channel; + this.refresh(); + this.notifyChangeListeners(); + } }, - getMergedChannels: function(){ - return this.mergedChannels; + getSelectedMergedChannels: function(){ + if(this.selectedMergedChannels){ + return this.selectedMergedChannels; + }else{ + return []; + } }, - setMergedChannels: function(channels){ - this.mergedChannels = channels; - this.refresh(); - this.notifyChangeListeners(); + setSelectedMergedChannels: function(channels){ + if(!channels){ + channels = []; + } + + if(this.getSelectedMergedChannels().toString() != channels.toString()){ + this.selectedMergedChannels = channels; + this.refresh(); + this.notifyChangeListeners(); + } }, - getSelectedOrMergedChannels: function(){ - if(this.getSelectedChannel()){ - return [this.getSelectedChannel()]; - }else{ - return this.getMergedChannels(); - } + isMergedChannelSelected: function(channel){ + return $.inArray(channel, this.getSelectedMergedChannels()) != -1; + }, + + isMergedChannelEnabled: function(channel){ + if(this.getSelectedMergedChannels().length == 1){ + return !this.isMergedChannelSelected(channel); + } + }, + + getAllChannels: function(){ + if(this.channels){ + return this.channels; + }else{ + return []; + } + }, + + setAllChannels: function(channels){ + if(!channels){ + channels = []; + } + + if(this.getAllChannels().toString() != channels.toString()){ + this.setSelectedChannel(null); + this.setSelectedMergedChannels(channels.map(function(channel){ + return channel.code; + })); + this.channels = channels; + this.refresh(); + } }, addChangeListener: function(listener){ @@ -247,6 +524,64 @@ $.extend(ChannelChooserWidget.prototype, { }); +// +// RESOLUTION CHOOSER VIEW +// +function ResolutionChooserView(controller) { + this.init(controller); +} + +$.extend(ResolutionChooserView.prototype, { + + init: function(controller){ + this.controller = controller; + this.panel = $("<div>").addClass("widget"); + }, + + render: function(){ + var thisView = this; + + $("<div>").text("Resolution:").appendTo(this.panel); + + var select = $("<select>").appendTo(this.panel); + + $("<option>").attr("value", "").text("Default").appendTo(select); + + this.controller.getAllResolutions().forEach(function(resolution){ + var value = resolution.width + "x" + resolution.height; + $("<option>").attr("value", value).text(value).appendTo(select); + }); + + select.change(function(){ + if(select.val() == ""){ + thisView.controller.setSelectedResolution(null); + }else{ + thisView.controller.setSelectedResolution(select.val()); + } + }); + + this.rendered = true; + this.refresh(); + + return this.panel; + }, + + refresh: function(){ + if(!this.rendered){ + return; + } + + var select = this.panel.find("select"); + + if(this.controller.getSelectedResolution() != null){ + select.val(this.controller.getSelectedResolution()); + }else{ + select.val(""); + } + } + +}); + // // RESOLUTION CHOOSER // @@ -257,59 +592,62 @@ function ResolutionChooserWidget(resolutions) { $.extend(ResolutionChooserWidget.prototype, { init: function(resolutions){ - this.resolutions = resolutions; - this.selectedResolution = null; + this.view = new ResolutionChooserView(this); this.listeners = new ListenerManager(); - this.panel = $("<div>").addClass("widget"); + this.setAllResolutions(resolutions); }, render: function(){ - var thisChooser = this; - - $("<div>").text("Resolution:").appendTo(this.panel); - - var select = $("<select>").appendTo(this.panel); - - $("<option>").attr("value", "").text("Default").appendTo(select); - - this.resolutions.forEach(function(resolution){ - var value = resolution.width + "x" + resolution.height; - $("<option>").attr("value", value).text(value).appendTo(select); - }); - - select.change(function(){ - if(select.val() == ""){ - thisChooser.setSelectedResolution(null); - }else{ - thisChooser.setSelectedResolution(select.val()); - } - }); - - this.rendered = true; - this.refresh(); - - return this.panel; + if(this.getView()){ + return this.getView().render(); + } }, refresh: function(){ - if(!this.rendered){ - return; - } - - var select = this.panel.find("select"); - if(this.selectedResolution){ - select.val(this.selectedResolution); + if(this.getView()){ + this.getView().refresh(); } }, + getView: function(){ + return this.view; + }, + + setView: function(view){ + this.view = view; + this.refresh(); + }, + getSelectedResolution: function(){ return this.selectedResolution; }, setSelectedResolution: function(resolution){ - this.selectedResolution = resolution; - this.refresh(); - this.notifyChangeListeners(); + if(this.selectedResolution != resolution){ + this.selectedResolution = resolution; + this.refresh(); + this.notifyChangeListeners(); + } + }, + + getAllResolutions: function(){ + if(this.resolutions){ + return this.resolutions; + }else{ + return []; + } + }, + + setAllResolutions: function(resolutions){ + if(!resolutions){ + resolutions = []; + } + + if(this.getAllResolutions().toString() != resolutions.toString()){ + this.resolutions = resolutions; + this.refresh(); + this.notifyChangeListeners(); + } }, addChangeListener: function(listener){ @@ -325,19 +663,19 @@ $.extend(ResolutionChooserWidget.prototype, { // // CHANNEL STACK CHOOSER // -function ChannelStackChooserWidget(channelStacks) { - this.init(channelStacks); +function ChannelStackChooserWidget(channelStacks, channelStackContentLoader) { + this.init(channelStacks, channelStackContentLoader); } $.extend(ChannelStackChooserWidget.prototype, { - init: function(channelStacks){ + init: function(channelStacks, channelStackContentLoader){ var manager = new ChannelStackManager(channelStacks); if(manager.isMatrix()){ - this.widget = new ChannelStackMatrixChooserWidget(channelStacks); + this.widget = new ChannelStackMatrixChooserWidget(channelStacks, channelStackContentLoader); }else{ - this.widget = new ChannelStackDefaultChooserWidget(channelStacks); + this.widget = new ChannelStackDefaultChooserWidget(channelStacks, channelStackContentLoader); } }, @@ -345,8 +683,12 @@ $.extend(ChannelStackChooserWidget.prototype, { return this.widget.render(); }, - getSelectedChannelStack: function(){ - return this.widget.getSelectedChannelStack(); + getSelectedChannelStackId: function(){ + return this.widget.getSelectedChannelStackId(); + }, + + setSelectedChannelStackId: function(channelStackId){ + this.widget.setSelectedChannelStackId(channelStackId); }, addChangeListener: function(listener){ @@ -360,102 +702,240 @@ $.extend(ChannelStackChooserWidget.prototype, { }); // -// CHANNEL STACK MATRIX CHOOSER +// CHANNEL STACK MATRIX CHOOSER VIEW // -function ChannelStackMatrixChooserWidget(channelStacks) { - this.init(channelStacks); + +function ChannelStackMatrixChooserView(controller) { + this.init(controller); } -$.extend(ChannelStackMatrixChooserWidget.prototype, { +$.extend(ChannelStackMatrixChooserView.prototype, { - init: function(channelStacks){ - this.channelStackManager = new ChannelStackManager(channelStacks); - this.selectedTimePoint = this.channelStackManager.getTimePoints()[0]; - this.selectedDepth = this.channelStackManager.getDepths()[0]; - this.listeners = new ListenerManager(); + init: function(controller){ + this.controller = controller; this.panel = $("<div>").addClass("widget"); }, render: function(){ - var thisChooser = this; + var thisView = this; $("<div>").text("Channel Stack:").appendTo(this.panel); + this.panel.append(this.createTimePointWidget()); + this.panel.append(this.createDepthWidget()); + this.panel.append(this.createButtonsWidget()); - $("<span>").text("T:").appendTo(this.panel); + this.rendered = true; + this.refresh(); - var timeSelect = $("<select>").addClass("timeChooser").appendTo(this.panel); + return this.panel; + }, + + refresh: function(){ + if(!this.rendered){ + return; + } + + var timeSelect = this.panel.find("select.timeChooser"); + + if(this.controller.getSelectedTimePoint() != null){ + timeSelect.val(this.controller.getSelectedTimePoint()); + // TODO get rid of empty string trick + this.buttons.setSelectedFrame(this.controller.getTimePoints().indexOf("" + this.controller.getSelectedTimePoint())); + } + + var depthSelect = this.panel.find("select.depthChooser"); + + if(this.controller.getSelectedDepth() != null){ + depthSelect.val(this.controller.getSelectedDepth()); + } + }, + + createTimePointWidget: function(){ + var thisView = this; + var widget = $("<span>"); + + $("<span>").text("T:").appendTo(widget); + + var timeSelect = $("<select>").addClass("timeChooser").appendTo(widget); - this.channelStackManager.getTimePoints().forEach(function(timePoint){ + this.controller.getTimePoints().forEach(function(timePoint){ $("<option>").attr("value", timePoint).text(timePoint).appendTo(timeSelect); }); timeSelect.change(function(){ - thisChooser.setSelectedTimePoint(timeSelect.val()); + thisView.controller.setSelectedTimePoint(timeSelect.val()); }); - $("<span>").text("D:").appendTo(this.panel); + return widget; + }, + + createDepthWidget: function(){ + var thisView = this; + var widget = $("<span>"); + + $("<span>").text("D:").appendTo(widget); - var depthSelect = $("<select>").addClass("depthChooser").appendTo(this.panel); + var depthSelect = $("<select>").addClass("depthChooser").appendTo(widget); - this.channelStackManager.getDepths().forEach(function(depth){ + this.controller.getDepths().forEach(function(depth){ $("<option>").attr("value", depth).text(depth).appendTo(depthSelect); }); depthSelect.change(function(){ - thisChooser.setSelectedDepth(depthSelect.val()); + thisView.controller.setSelectedDepth(depthSelect.val()); }); - - this.buttons = new MovieButtonsWidget(this.channelStackManager.getTimePoints().length); - this.buttons.addChangeListener(function(){ - var frame = thisChooser.buttons.getSelectedFrame(); - thisChooser.setSelectedTimePoint(thisChooser.channelStackManager.getTimePoint(frame)); + return widget; + }, + + createButtonsWidget: function(){ + var thisView = this; + + // TODO content loader should be specified via setter, if not specified an empty function should be used + buttons = new MovieButtonsWidget(this.controller.getTimePoints().length, function(frameIndex, callback){ + var timePoint = thisView.controller.getTimePoints()[frameIndex]; + var depth = thisView.controller.getSelectedDepth(); + var channelStack = thisView.controller.getChannelStackByTimePointAndDepth(timePoint, depth); + thisView.controller.loadChannelStackContent(channelStack, callback); }); - this.panel.append(this.buttons.render()); + buttons.addChangeListener(function(){ + var timePoint = thisView.controller.getTimePoints()[buttons.getSelectedFrame()]; + thisView.controller.setSelectedTimePoint(timePoint); + }); - this.rendered = true; - this.refresh(); + this.buttons = buttons; + return buttons.render(); + } - return this.panel; +}); + +// +// CHANNEL STACK MATRIX CHOOSER +// +function ChannelStackMatrixChooserWidget(channelStacks, channelStackContentLoader) { + this.init(channelStacks, channelStackContentLoader); +} + +$.extend(ChannelStackMatrixChooserWidget.prototype, { + + init: function(channelStacks, channelStackContentLoader){ + this.setView(new ChannelStackMatrixChooserView(this)); + this.channelStackManager = new ChannelStackManager(channelStacks); + this.channelStackContentLoader = channelStackContentLoader; + this.listeners = new ListenerManager(); + }, + + // TODO create abstract widget with render, refresh, getView, setView, isRendered, listeners + + render: function(){ + if(this.getView()){ + return this.getView().render(); + } }, refresh: function(){ - if(!this.rendered){ - return; - } - - var timeSelect = this.panel.find("select.timeChooser"); - - if(this.selectedTimePoint){ - timeSelect.val(this.selectedTimePoint); - } - - var depthSelect = this.panel.find("select.depthChooser"); - - if(this.selectedDepth){ - depthSelect.val(this.selectedDepth); - } + if(this.getView()){ + this.getView().refresh(); + } }, - getSelectedChannelStack: function(){ - return this.channelStackManager.getChannelStack(this.selectedTimePoint, this.selectedDepth); + getView: function(){ + return this.view; }, - setSelectedTimePoint: function(timePoint){ - if(this.selectedTimePoint != timePoint){ - this.selectedTimePoint = timePoint; - this.buttons.setSelectedFrame(this.channelStackManager.getTimePointIndex(timePoint)); + setView: function(view){ + this.view = view; + this.refresh(); + }, + + getTimePoints: function(){ + return this.channelStackManager.getTimePoints(); + }, + + getDepths: function(){ + return this.channelStackManager.getDepths(); + }, + + getChannelStacks: function(){ + return this.channelStackManager.getChannelStacks(); + }, + + getChannelStackById: function(channelStackId){ + return this.channelStackManager.getChannelStackById(channelStackId); + }, + + getChannelStackByTimePointAndDepth: function(timePoint, depth){ + return this.channelStackManager.getChannelStackByTimePointAndDepth(timePoint, depth); + }, + + loadChannelStackContent: function(channelStack, callback){ + this.channelStackContentLoader(channelStack, callback); + }, + + getSelectedChannelStackId: function(){ + return this.selectedChannelStackId; + }, + + setSelectedChannelStackId: function(channelStackId){ + if(this.selectedChannelStackId != channelStackId){ + this.selectedChannelStackId = channelStackId; this.refresh(); this.notifyChangeListeners(); } }, + getSelectedChannelStack: function(){ + var channelStackId = this.getSelectedChannelStackId(); + + if(channelStackId != null){ + return this.channelStackManager.getChannelStackById(channelStackId); + }else{ + return null; + } + }, + + setSelectedChannelStack: function(channelStack){ + if(channelStack != null){ + this.setSelectedChannelStackId(channelStack.id); + }else{ + this.setSelectedChannelStackId(null); + } + }, + + getSelectedTimePoint: function(){ + var channelStack = this.getSelectedChannelStack(); + if(channelStack != null){ + return channelStack.timePointOrNull; + }else{ + return null; + } + }, + + setSelectedTimePoint: function(timePoint){ + if(timePoint != null && this.getSelectedDepth() != null){ + var channelStack = this.channelStackManager.getChannelStackByTimePointAndDepth(timePoint, this.getSelectedDepth()); + this.setSelectedChannelStack(channelStack); + }else{ + this.setSelectedChannelStack(null); + } + }, + + getSelectedDepth: function(){ + var channelStack = this.getSelectedChannelStack(); + if(channelStack != null){ + return channelStack.depthOrNull; + }else{ + return null; + } + }, + setSelectedDepth: function(depth){ - if(this.selectedDepth != depth){ - this.selectedDepth = depth; - this.refresh(); - this.notifyChangeListeners(); + if(depth != null && this.getSelectedTimePoint() != null){ + var channelStack = this.channelStackManager.getChannelStackByTimePointAndDepth(this.getSelectedTimePoint(), depth); + this.setSelectedChannelStack(channelStack); + }else{ + this.setSelectedChannelStack(null); } }, @@ -472,13 +952,13 @@ $.extend(ChannelStackMatrixChooserWidget.prototype, { // // CHANNEL STACK DEFAULT CHOOSER // -function ChannelStackDefaultChooserWidget(channelStacks) { - this.init(channelStacks); +function ChannelStackDefaultChooserWidget(channelStacks, channelStackContentLoader) { + this.init(channelStacks, channelStackContentLoader); } $.extend(ChannelStackDefaultChooserWidget.prototype, { - init: function(channelStacks){ + init: function(channelStacks, channelStackContentLoader){ this.channelStackManager = new ChannelStackManager(channelStacks); this.listeners = new ListenerManager(); this.panel = $("<div>"); @@ -489,11 +969,11 @@ $.extend(ChannelStackDefaultChooserWidget.prototype, { return this.panel; }, - getSelectedChannelStack: function(){ + getSelectedChannelStackId: function(){ return null; }, - setSelectedChannelStack: function(channelStack){ + setSelectedChannelStackId: function(channelStackId){ }, addChangeListener: function(listener){ @@ -543,7 +1023,7 @@ $.extend(ChannelStackManager.prototype, { }, isDepthConsistent: function(){ - var map = this.getChannelStackMap(); + var map = this.getChannelStackByTimePointAndDepthMap(); var depthCounts = {}; for(timePoint in map){ @@ -621,22 +1101,33 @@ $.extend(ChannelStackManager.prototype, { return this.depthsMap[depth]; }, - getChannelStack: function(timePoint, depthLevel){ - var map = this.getChannelStackMap(); + getChannelStackByTimePointAndDepth: function(timePoint, depth){ + var map = this.getChannelStackByTimePointAndDepthMap(); var entry = map[timePoint]; if(entry){ - return entry[depthLevel]; + return entry[depth]; }else{ return null; } }, + getChannelStackById: function(channelStackId){ + if(!this.channelStackByIdMap){ + var map = {}; + this.channelStacks.forEach(function(channelStack){ + map[channelStack.id] = channelStack; + }); + this.channelStackByIdMap = map; + } + return this.channelStackByIdMap[channelStackId]; + }, + getChannelStacks: function(){ return this.channelStacks; }, - getChannelStackMap: function(){ + getChannelStackByTimePointAndDepthMap: function(){ if(!this.channelStackMap){ var map = {}; this.channelStacks.forEach(function(channelStack){ @@ -656,54 +1147,51 @@ $.extend(ChannelStackManager.prototype, { }); // -// MOVIE BUTTONS WIDGET +// MOVIE BUTTONS VIEW // -function MovieButtonsWidget(frameCount) { - this.init(frameCount); + +function MovieButtonsView(controller) { + this.init(controller); } -$.extend(MovieButtonsWidget.prototype, { +$.extend(MovieButtonsView.prototype, { - init: function(frameCount){ - this.frameCount = frameCount; - this.frameAction = null; - this.selectedDelay = 500; - this.selectedFrame = 0; - this.listeners = new ListenerManager(); + init: function(controller){ + this.controller = controller; this.panel = $("<div>").addClass("widget"); }, render: function(){ - var thisButtons = this; + var thisView = this; var play = $("<button>").addClass("play").text("Play").appendTo(this.panel); play.click(function(){ - thisButtons.play(); + thisView.controller.play(); }); var stop = $("<button>").addClass("stop").text("Stop").appendTo(this.panel); stop.click(function(){ - thisButtons.stop(); + thisView.controller.stop(); }); var prev = $("<button>").addClass("prev").text("<<").appendTo(this.panel); prev.click(function(){ - thisButtons.setSelectedFrame(thisButtons.getSelectedFrame() - 1); + thisView.controller.prev(); }); var next = $("<button>").addClass("next").text(">>").appendTo(this.panel); next.click(function(){ - thisButtons.setSelectedFrame(thisButtons.getSelectedFrame() + 1); + thisView.controller.next(); }); var delay = $("<input>").attr("type", "text").addClass("delay").appendTo(this.panel); delay.change(function(){ - thisButtons.setSelectedDelay(delay.val()); + thisView.controller.setSelectedDelay(parseInt(delay.val())); }); this.rendered = true; @@ -711,26 +1199,69 @@ $.extend(MovieButtonsWidget.prototype, { return this.panel; }, - + refresh: function(){ if(!this.rendered){ return; } var play = this.panel.find("button.play"); - play.prop("disabled", this.frameAction != null); + play.prop("disabled", this.controller.isPlaying()); var stop = this.panel.find("button.stop"); - stop.prop("disabled", this.frameAction == null); + stop.prop("disabled", this.controller.isStopped()); var prev = this.panel.find("button.prev"); - prev.prop("disabled", this.getSelectedFrame() == 0); + prev.prop("disabled", this.controller.isFirstFrameSelected()); var next = this.panel.find("button.next"); - next.prop("disabled", this.getSelectedFrame() == (this.frameCount - 1)); + next.prop("disabled", this.controller.isLastFrameSelected()); var delay = this.panel.find("input.delay"); delay.val(this.selectedDelay); + } + +}); + +// +// MOVIE BUTTONS WIDGET +// +function MovieButtonsWidget(frameCount, frameLoader) { + this.init(frameCount, frameLoader); +} + +$.extend(MovieButtonsWidget.prototype, { + + init: function(frameCount, frameLoader){ + this.setView(new MovieButtonsView(this)) + this.frameCount = frameCount; + this.frameLoader = frameLoader; + this.frameAction = null; + this.selectedDelay = 500; + this.selectedFrame = 0; + this.listeners = new ListenerManager(); + this.panel = $("<div>").addClass("widget"); + }, + + render: function(){ + if(this.getView()){ + return this.getView().render(); + } + }, + + refresh: function(){ + if(this.getView()){ + this.getView().refresh(); + } + }, + + setView: function(view){ + this.view = view; + this.refresh(); + }, + + getView: function(){ + return this.view; }, play: function(){ @@ -745,13 +1276,20 @@ $.extend(MovieButtonsWidget.prototype, { var thisButtons = this; this.frameAction = function(){ - if(!thisButtons.frameAction){ - return; - } - if(thisButtons.getSelectedFrame() < thisButtons.frameCount - 1){ - thisButtons.setSelectedFrame(thisButtons.getSelectedFrame() + 1); - setTimeout(thisButtons.frameAction, thisButtons.selectedDelay); + var frame = thisButtons.getSelectedFrame() + 1; + var startTime = Date.now(); + + thisButtons.setSelectedFrame(frame, function(){ + var prefferedDelay = thisButtons.selectedDelay; + var actualDelay = Date.now() - startTime; + + setTimeout(function(){ + if(thisButtons.frameAction){ + thisButtons.frameAction(); + } + }, Math.max(1, prefferedDelay - actualDelay)); + }); }else{ thisButtons.stop(); thisButtons.setSelectedFrame(0); @@ -759,13 +1297,40 @@ $.extend(MovieButtonsWidget.prototype, { }; this.frameAction(); + this.refresh(); }, stop: function(){ - this.frameAction = null; - this.refresh(); + if(this.frameAction){ + this.frameAction = null; + this.refresh(); + } + }, + + prev: function(){ + this.setSelectedFrame(this.getSelectedFrame() - 1); + }, + + next: function(){ + this.setSelectedFrame(this.getSelectedFrame() + 1); }, + isPlaying: function(){ + return this.frameAction != null; + }, + + isStopped: function(){ + return this.frameAction == null; + }, + + isFirstFrameSelected: function(){ + return this.getSelectedFrame() == 0; + }, + + isLastFrameSelected: function(){ + return this.getSelectedFrame() == (this.frameCount - 1) + }, + getSelectedDelay: function(){ return this.selectedDelay; }, @@ -781,11 +1346,13 @@ $.extend(MovieButtonsWidget.prototype, { return this.selectedFrame; }, - setSelectedFrame: function(frame){ + setSelectedFrame: function(frame, callback){ frame = Math.min(Math.max(0, frame), this.frameCount - 1); if(this.selectedFrame != frame){ - this.selectedFrame = frame; + log("Selected frame: " + frame); + this.selectedFrame = frame; + this.frameLoader(frame, callback); this.refresh(); this.notifyChangeListeners(); } @@ -802,75 +1369,121 @@ $.extend(MovieButtonsWidget.prototype, { }); // -// IMAGE +// IMAGE DATA // -function ImageWidget() { - this.init(); +function ImageData() { + this.init(); } -$.extend(ImageWidget.prototype, { - - init: function(){ - this.panel = $("<div>") - }, - - render: function(){ - $("<img>").appendTo(this.panel); - this.rendered = true; - this.refresh(); - return this.panel; - }, - - refresh: function(){ - if(!this.rendered){ - return; - } - - var url = this.dataStoreUrl + "/datastore_server_screening"; - url += "?sessionID=" + this.sessionToken; - url += "&dataset=" + this.dataSetCode; - url += "&channelStackId=" + this.channelStackId; +$.extend(ImageData.prototype, { - this.channels.forEach(function(channel){ - url += "&channel=" + channel; - }); - - if(this.resolution){ - url += "&mode=thumbnail" + this.resolution; - }else{ - url += "&mode=thumbnail480x480"; - } - - this.panel.find("img").attr("src", url); - }, - - setDataStoreUrl: function(dataStoreUrl){ + init: function(){ + }, + + setDataStoreUrl: function(dataStoreUrl){ this.dataStoreUrl = dataStoreUrl; - this.refresh(); }, setSessionToken: function(sessionToken){ this.sessionToken = sessionToken; - this.refresh(); }, setDataSetCode: function(dataSetCode){ this.dataSetCode = dataSetCode; - this.refresh(); }, setChannelStackId: function(channelStackId){ this.channelStackId = channelStackId; - this.refresh(); }, setChannels: function(channels){ this.channels = channels; - this.refresh(); }, setResolution: function(resolution){ this.resolution = resolution; + } + +}); + +// +// IMAGE LOADER +// +function ImageLoader() { + this.init(); +} + +$.extend(ImageLoader.prototype, { + + init: function(){ + }, + + loadImage: function(imageData, callback){ + log("loadImage: " + imageData.channelStackId); + + var url = imageData.dataStoreUrl + "/datastore_server_screening"; + url += "?sessionID=" + imageData.sessionToken; + url += "&dataset=" + imageData.dataSetCode; + url += "&channelStackId=" + imageData.channelStackId; + + imageData.channels.forEach(function(channel){ + url += "&channel=" + channel; + }); + + if(imageData.resolution){ + url += "&mode=thumbnail" + imageData.resolution; + }else{ + url += "&mode=thumbnail480x480"; + } + + $("<img>").attr("src", url).load(function(){ + if(callback){ + callback(this); + } + }); + } + +}); + +// +// IMAGE WIDGET +// +function ImageWidget(imageLoader) { + this.init(imageLoader); +} + +$.extend(ImageWidget.prototype, { + + init: function(imageLoader){ + this.imageLoader = imageLoader; + this.panel = $("<div>") + }, + + render: function(){ + this.rendered = true; + this.refresh(); + return this.panel; + }, + + refresh: function(){ + if(!this.rendered){ + return; + } + + var thisWidget = this; + + if(this.imageData){ + this.imageLoader.loadImage(this.imageData, function(image){ + thisWidget.panel.empty(); + thisWidget.panel.append(image); + }); + }else{ + this.panel.empty(); + } + }, + + setImage: function(imageData){ + this.imageData = imageData; this.refresh(); } @@ -939,3 +1552,10 @@ $.extend(ListenerManager.prototype, { } } }); + +function log(msg){ + if(console){ + var date = new Date(); + console.log(date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + "." + date.getMilliseconds() + " - " + msg); + } +}