mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-15 06:49:56 +00:00
Merge branch 'master' into bug1301
Change-Id: Ide785c2c981f7968672c66d71bac3ca9838d3b2c
This commit is contained in:
commit
b56d46f1c0
21 files changed
+866
-272
No files matched your search
+10
-1
@@ -3,19 +3,24 @@ package com.sap.sailing.domain.common.dto;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.sap.sse.common.Duration;
|
||||
|
||||
public class VideoMetadataDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private boolean canDownload;
|
||||
private Date recordStartedTime;
|
||||
private boolean spherical;
|
||||
private String message;
|
||||
private Duration duration;
|
||||
|
||||
public VideoMetadataDTO() {
|
||||
|
||||
}
|
||||
|
||||
public VideoMetadataDTO(boolean canDownload, boolean spherical, Date recordStartedTime, String message) {
|
||||
public VideoMetadataDTO(boolean canDownload, Duration duration, boolean spherical, Date recordStartedTime,
|
||||
String message) {
|
||||
this.canDownload = canDownload;
|
||||
this.duration = duration;
|
||||
this.spherical = spherical;
|
||||
this.recordStartedTime = recordStartedTime;
|
||||
this.message = message;
|
||||
@@ -25,6 +30,10 @@ public class VideoMetadataDTO implements Serializable {
|
||||
return canDownload;
|
||||
}
|
||||
|
||||
public Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public boolean isSpherical() {
|
||||
return spherical;
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class RaceBoardPerspectiveOwnSettings extends AbstractGenericSerializable
|
||||
this.showCompetitorsChart = new BooleanSetting("showCompetitorsChart", this, false);
|
||||
this.activeCompetitorsFilterSetName = new StringSetting("activeCompetitorsFilterSetName", this, null);
|
||||
this.canReplayDuringLiveRaces = new BooleanSetting("canReplayDuringLiveRaces", this, false);
|
||||
this.initialDurationAfterRaceStartInReplay = new DurationSetting("initialDurationAfterRaceStartInReplay", this, null);
|
||||
this.initialDurationAfterRaceStartInReplay = new DurationSetting(PARAM_TIME_AFTER_RACE_START_AS_HOURS_COLON_MILLIS_COLON_SECONDS, this, null);
|
||||
}
|
||||
|
||||
public RaceBoardPerspectiveOwnSettings(String activeCompetitorsFilterSetName, Boolean showLeaderboard,
|
||||
|
||||
+23
-2
@@ -25,12 +25,15 @@ public class DurationConverter implements ValueConverter<Duration> {
|
||||
|
||||
@Override
|
||||
public String toStringValue(Duration value) {
|
||||
return value == null ? null : Long.toString(value.asMillis());
|
||||
// String value is used in URL where we accept the formats matching [[hh:]mm:]ss
|
||||
// and the number of digits per element is not restricted to 2.
|
||||
// This makes an integer number to simply be parsed as seconds value.
|
||||
return value == null ? null : Long.toString((long)value.asSeconds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration fromStringValue(String stringValue) {
|
||||
return stringValue == null ? null : new MillisecondsDurationImpl(Long.parseLong(stringValue));
|
||||
return stringValue == null ? null : parseDuration(stringValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,4 +46,22 @@ public class DurationConverter implements ValueConverter<Duration> {
|
||||
public Value toValue(Duration value) {
|
||||
return value == null ? null : new LongValue(value.asMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Understands [[hh:]mm:]ss and parses into a {@link Duration}. If {@code durationAsString} is {@code null} then
|
||||
* so is the result.
|
||||
*/
|
||||
private static Duration parseDuration(String durationAsString) {
|
||||
final Duration result;
|
||||
if (durationAsString == null) {
|
||||
result = null;
|
||||
} else {
|
||||
long seconds = 0;
|
||||
for (final String hhmmss : durationAsString.split(":")) {
|
||||
seconds = 60*seconds + Long.valueOf(hhmmss);
|
||||
}
|
||||
result = new MillisecondsDurationImpl(1000l * seconds);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -27,6 +27,7 @@ public interface MediaService extends RemoteService {
|
||||
|
||||
void updateRace(MediaTrack mediaTrack);
|
||||
|
||||
VideoMetadataDTO checkMetadata(String url);
|
||||
VideoMetadataDTO checkMetadata(byte[] start, byte[] end, Long skipped);
|
||||
|
||||
VideoMetadataDTO checkMetadata(String url);
|
||||
}
|
||||
+2
@@ -29,6 +29,8 @@ public interface MediaServiceAsync {
|
||||
|
||||
void updateRace(MediaTrack mediaTrack, AsyncCallback<Void> asyncCallback);
|
||||
|
||||
void checkMetadata(byte[] jStart, byte[] jEnd, Long skipped, AsyncCallback<VideoMetadataDTO> asyncCallback);
|
||||
|
||||
void checkMetadata(String url, AsyncCallback<VideoMetadataDTO> asyncCallback);
|
||||
|
||||
}
|
||||
+1
@@ -2049,6 +2049,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String importFinishedGotoEvent();
|
||||
String chooseAName();
|
||||
String unexpectedErrorDuringFileImport();
|
||||
String couldNotDownload(String url);
|
||||
String windFinderWindSourceTypeName();
|
||||
String windFinderWindSourceTypeTooltip();
|
||||
String windFinder();
|
||||
|
||||
+1
@@ -2037,6 +2037,7 @@ importFinishedGotoRaceboard=Open raceboard
|
||||
importFinishedGotoEvent=Open event
|
||||
chooseAName=Choose a name
|
||||
unexpectedErrorDuringFileImport=An unexpected error occurred during file upload.
|
||||
couldNotDownload=Could not download file: {0}
|
||||
windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Measured wind from one or more spots of www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
|
||||
+1
@@ -2034,6 +2034,7 @@ importFinishedGotoRaceboard=Raceboard öffnen
|
||||
importFinishedGotoEvent=Event öffnen
|
||||
chooseAName=Namen auswählen
|
||||
unexpectedErrorDuringFileImport=Ein unerwarteter Fehler ist beim Datei-Import aufgetreten.
|
||||
couldNotDownload=Konnte Datei nicht laden: {0}
|
||||
windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Gemessener Wind von einer oder mehreren Stationen von www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package com.sap.sailing.gwt.ui.client.media;
|
||||
|
||||
import com.google.gwt.typedarrays.shared.Int8Array;
|
||||
|
||||
/**
|
||||
* Helper class to obtain the last and the first REQUIRED_SIZE bytes of a resource defined by a given url.
|
||||
*/
|
||||
public class JSDownloadUtils {
|
||||
private static final Double REQUIRED_SIZE = 1000000.0;
|
||||
|
||||
interface JSDownloadCallback {
|
||||
void progress(Double current, Double total);
|
||||
|
||||
void error(Object msg);
|
||||
|
||||
void complete(Int8Array start, Int8Array end, Double skipped);
|
||||
}
|
||||
|
||||
interface JSSizeCallback {
|
||||
void size(Double total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the required parts of the file, or if necessary the full file and slices the required parts from it.
|
||||
*/
|
||||
public static void getData(String url, JSDownloadCallback callback){
|
||||
getFileSizeIfFastPath(url, new JSSizeCallback() {
|
||||
|
||||
@Override
|
||||
public void size(Double total) {
|
||||
if (total != 0 && total > 0) {
|
||||
//used if range and accept headers are set correctly
|
||||
getDataFast(url, callback, total, REQUIRED_SIZE);
|
||||
} else {
|
||||
//fallback will always work, if cors is supported, does not require any other server configuration
|
||||
getDataSlow(url, callback, REQUIRED_SIZE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast file download method based on range requests, only the first and the last REQUIRED_SIZE is loaded.
|
||||
*/
|
||||
private native static void getDataFast(String url, JSDownloadCallback callback, Double length, Double REQUIRED_SIZE) /*-{
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.setRequestHeader("Range", "bytes=0-" + REQUIRED_SIZE);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onprogress = function(evt) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total);
|
||||
}
|
||||
xhr.error = function(error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error);
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
var state = xhr.readyState;
|
||||
if (state == 4) {
|
||||
if (xhr.response) {
|
||||
var startLength = xhr.response.byteLength;
|
||||
var start = new Int8Array(xhr.response);
|
||||
|
||||
try {
|
||||
var xhr2 = new XMLHttpRequest();
|
||||
xhr2.open("GET", url, true);
|
||||
xhr2.setRequestHeader("Range", "bytes=" + (length - REQUIRED_SIZE));
|
||||
xhr2.responseType = "arraybuffer";
|
||||
xhr2.onprogress = function(evt) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total);
|
||||
}
|
||||
xhr2.error = function(error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error);
|
||||
}
|
||||
|
||||
xhr2.onreadystatechange = function() {
|
||||
var state = xhr2.readyState;
|
||||
if (state == 4) {
|
||||
if (xhr2.response) {
|
||||
var endLength = xhr2.response.byteLength;
|
||||
var end = new Int8Array(xhr2.response);
|
||||
var sparse = length - startLength - endLength;
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::complete(Lcom/google/gwt/typedarrays/shared/Int8Array;Lcom/google/gwt/typedarrays/shared/Int8Array;Ljava/lang/Double;)(start, end, sparse);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr2.send();
|
||||
} catch (error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
} catch (error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error);
|
||||
}
|
||||
}-*/;
|
||||
|
||||
/**
|
||||
* Determines if a server can deliver a file with range requests, and if so returns the size of the file
|
||||
*/
|
||||
private native static void getFileSizeIfFastPath(String url, JSSizeCallback callback) /*-{
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("HEAD", url, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.error = function(error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSSizeCallback::size(Ljava/lang/Double;)(-1.0);
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
var state = xhr.readyState;
|
||||
if (state == 4) {
|
||||
if (xhr.response) {
|
||||
var length = xhr.getResponseHeader("Content-Length");
|
||||
var range = xhr.getResponseHeader("Content-Range");
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSSizeCallback::size(Ljava/lang/Double;)(length);
|
||||
} else {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSSizeCallback::size(Ljava/lang/Double;)(-1.0);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
} catch (error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSSizeCallback::size(Ljava/lang/Double;)(-1.0);
|
||||
}
|
||||
}-*/;
|
||||
|
||||
/**
|
||||
* If range requests are not possible, the whole file is downloaded and then slices to the required parts
|
||||
*/
|
||||
private native static void getDataSlow(String url, JSDownloadCallback callback, Double REQUIRED_SIZE) /*-{
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onprogress = function(evt) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::progress(Ljava/lang/Double;Ljava/lang/Double;)(evt.loaded, evt.total);
|
||||
}
|
||||
xhr.error = function(error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error);
|
||||
}
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
var state = xhr.readyState;
|
||||
if (state == 4) {
|
||||
if (xhr.response) {
|
||||
var length = xhr.response.byteLength;
|
||||
var start = new Int8Array(xhr.response.slice(0, REQUIRED_SIZE));
|
||||
var end = new Int8Array(xhr.response.slice(length - REQUIRED_SIZE));
|
||||
var sparse = length - 2 * REQUIRED_SIZE;
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::complete(Lcom/google/gwt/typedarrays/shared/Int8Array;Lcom/google/gwt/typedarrays/shared/Int8Array;Ljava/lang/Double;)(start, end, sparse);
|
||||
} else {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)("No result");
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
} catch (error) {
|
||||
callback.@com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback::error(Ljava/lang/Object;)(error);
|
||||
}
|
||||
}-*/;
|
||||
|
||||
}
|
||||
+13
-2
@@ -526,6 +526,15 @@ public class MediaPlayerManagerComponent extends AbstractComponent<MediaPlayerSe
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private TimePoint getTrackingStartTime() {
|
||||
Date startOfTracking = raceTimesInfoProvider.getRaceTimesInfo(getCurrentRace()).startOfTracking;
|
||||
if (startOfTracking != null) {
|
||||
return new MillisecondsTimePoint(startOfTracking);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeFloatingVideo(MediaTrack videoTrack) {
|
||||
@@ -606,8 +615,10 @@ public class MediaPlayerManagerComponent extends AbstractComponent<MediaPlayerSe
|
||||
|
||||
@Override
|
||||
public void addMediaTrack() {
|
||||
TimePoint raceStartTime = getRaceStartTime();
|
||||
TimePoint defaultStartTime = raceStartTime;
|
||||
TimePoint defaultStartTime = getRaceStartTime();
|
||||
if (defaultStartTime == null) {
|
||||
defaultStartTime = getTrackingStartTime();
|
||||
}
|
||||
NewMediaDialog dialog = new NewMediaDialog(mediaService, defaultStartTime,
|
||||
MediaPlayerManagerComponent.this.stringMessages, this.getCurrentRace(),
|
||||
new DialogCallback<MediaTrack>() {
|
||||
|
||||
+49
-30
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.client.media;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Display;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.dom.client.KeyUpEvent;
|
||||
@@ -19,10 +20,10 @@ public class MediaSynchControl implements EditFlag {
|
||||
|
||||
private static final int FAST = 1000;
|
||||
private static final int SLOW = 100;
|
||||
|
||||
|
||||
private final MediaServiceAsync mediaService;
|
||||
private final MediaSynchAdapter mediaSynchAdapter;
|
||||
private final ErrorReporter errorReporter;
|
||||
private final ErrorReporter errorReporter;
|
||||
private final MediaTrack backupVideoTrack;
|
||||
|
||||
private final FlowPanel mainPanel;
|
||||
@@ -31,19 +32,32 @@ public class MediaSynchControl implements EditFlag {
|
||||
private final FlowPanel fineTuningPanel;
|
||||
|
||||
private final TextBox titleEdit;
|
||||
private final Button editButton;
|
||||
private final EditButtonProxy editButton;
|
||||
private final Button previewButton;
|
||||
private final Button saveButton;
|
||||
private final Button discardButton;
|
||||
|
||||
private boolean isEditing = false;
|
||||
|
||||
public MediaSynchControl(MediaSynchAdapter mediaSynchAdapter, MediaServiceAsync mediaService, ErrorReporter errorReporter) {
|
||||
|
||||
/**
|
||||
* We dont want to force the caller to use a specific button or anchor class for future flexibility
|
||||
*/
|
||||
interface EditButtonProxy {
|
||||
void addAction(Runnable runnable);
|
||||
|
||||
void setTitle(String string);
|
||||
|
||||
void setEnabled(boolean b);
|
||||
}
|
||||
|
||||
public MediaSynchControl(MediaSynchAdapter mediaSynchAdapter, MediaServiceAsync mediaService,
|
||||
ErrorReporter errorReporter, EditButtonProxy editButtonProxy) {
|
||||
this.mediaService = mediaService;
|
||||
this.mediaSynchAdapter = mediaSynchAdapter;
|
||||
this.errorReporter = errorReporter;
|
||||
MediaTrack videoTrack = this.mediaSynchAdapter.getMediaTrack();
|
||||
backupVideoTrack = new MediaTrack(videoTrack.title, videoTrack.url, videoTrack.startTime, videoTrack.duration, videoTrack.mimeType, videoTrack.assignedRaces);
|
||||
MediaTrack videoTrack = this.mediaSynchAdapter.getMediaTrack();
|
||||
backupVideoTrack = new MediaTrack(videoTrack.title, videoTrack.url, videoTrack.startTime, videoTrack.duration,
|
||||
videoTrack.mimeType, videoTrack.assignedRaces);
|
||||
mainPanel = new FlowPanel();
|
||||
mainPanel.addStyleName("main-panel");
|
||||
editPanel = new FlowPanel();
|
||||
@@ -51,12 +65,14 @@ public class MediaSynchControl implements EditFlag {
|
||||
fineTuningPanel.addStyleName("finetuning-panel");
|
||||
commitPanel = new FlowPanel();
|
||||
commitPanel.addStyleName("button-panel");
|
||||
|
||||
|
||||
this.editButton = editButtonProxy;
|
||||
|
||||
titleEdit = new TextBox();
|
||||
titleEdit.setText(videoTrack.title);
|
||||
titleEdit.addStyleName("title-edit");
|
||||
titleEdit.addKeyUpHandler(new KeyUpHandler() {
|
||||
|
||||
|
||||
@Override
|
||||
public void onKeyUp(KeyUpEvent event) {
|
||||
String text = titleEdit.getText();
|
||||
@@ -64,7 +80,7 @@ public class MediaSynchControl implements EditFlag {
|
||||
updateUiState();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Button fastRewindButton = new Button("-1s «", new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
@@ -89,22 +105,25 @@ public class MediaSynchControl implements EditFlag {
|
||||
fastForward();
|
||||
}
|
||||
});
|
||||
editButton = new Button("Edit", new ClickHandler() {
|
||||
editButton.addAction(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
public void run() {
|
||||
edit();
|
||||
}
|
||||
});
|
||||
editButton.setTitle("Pauses race and decouples race from video playback. Use video controls, race time slider or fine tuning buttons for time alignment, then press Preview to re-couple race and video playback.");
|
||||
|
||||
editButton.setTitle(
|
||||
"Pauses race and decouples race from video playback. Use video controls, race time slider or fine tuning buttons for time alignment, then press Preview to re-couple race and video playback.");
|
||||
|
||||
previewButton = new Button("Preview", new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
preview();
|
||||
}
|
||||
});
|
||||
previewButton.setTitle("Re-couples race and video playback. If ok, press Save to write changes back to database. Press Cancel to reset the changes.");
|
||||
|
||||
previewButton.setTitle(
|
||||
"Re-couples race and video playback. If ok, press Save to write changes back to database. Press Cancel to reset the changes.");
|
||||
|
||||
saveButton = new Button("Save", new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
@@ -123,17 +142,15 @@ public class MediaSynchControl implements EditFlag {
|
||||
fineTuningPanel.add(slowRewindButton);
|
||||
fineTuningPanel.add(slowForwardButton);
|
||||
fineTuningPanel.add(fastForwardButton);
|
||||
|
||||
|
||||
mainPanel.add(titleEdit);
|
||||
editPanel.add(editButton);
|
||||
editPanel.add(previewButton);
|
||||
mainPanel.add(editPanel);
|
||||
mainPanel.add(fineTuningPanel);
|
||||
|
||||
|
||||
commitPanel.add(saveButton);
|
||||
commitPanel.add(discardButton);
|
||||
|
||||
|
||||
|
||||
mainPanel.add(commitPanel);
|
||||
|
||||
updateUiState();
|
||||
@@ -166,14 +183,14 @@ public class MediaSynchControl implements EditFlag {
|
||||
pausePlayback();
|
||||
mediaSynchAdapter.forceAlign();
|
||||
updateUiState();
|
||||
// For now, only start time can be changed.
|
||||
// getMediaTrack().title = backupVideoTrack.title;
|
||||
// getMediaTrack().url = backupVideoTrack.url;
|
||||
// getMediaTrack().duration = backupVideoTrack.duration;
|
||||
// For now, only start time can be changed.
|
||||
// getMediaTrack().title = backupVideoTrack.title;
|
||||
// getMediaTrack().url = backupVideoTrack.url;
|
||||
// getMediaTrack().duration = backupVideoTrack.duration;
|
||||
}
|
||||
|
||||
private void save() {
|
||||
|
||||
|
||||
if (!backupVideoTrack.startTime.equals(mediaSynchAdapter.getMediaTrack().startTime)) {
|
||||
mediaService.updateStartTime(mediaSynchAdapter.getMediaTrack(), new AsyncCallback<Void>() {
|
||||
|
||||
@@ -216,17 +233,19 @@ public class MediaSynchControl implements EditFlag {
|
||||
}
|
||||
}
|
||||
mediaSynchAdapter.setControlsVisible(isEditing);
|
||||
|
||||
|
||||
editButton.setEnabled(!isEditing);
|
||||
previewButton.setEnabled(isEditing);
|
||||
|
||||
|
||||
mainPanel.getElement().getStyle().setDisplay(this.isEditing() ? Display.BLOCK : Display.NONE);
|
||||
boolean isDirty = isDirty();
|
||||
saveButton.setEnabled(!isEditing && isDirty);
|
||||
saveButton.setEnabled(isEditing || isDirty);
|
||||
discardButton.setEnabled(isEditing || isDirty);
|
||||
}
|
||||
|
||||
private boolean isDirty() {
|
||||
return !backupVideoTrack.startTime.equals(mediaSynchAdapter.getMediaTrack().startTime) || !backupVideoTrack.title.equals(titleEdit.getText());
|
||||
return !backupVideoTrack.startTime.equals(mediaSynchAdapter.getMediaTrack().startTime)
|
||||
|| !backupVideoTrack.title.equals(titleEdit.getText());
|
||||
}
|
||||
|
||||
private void fastForward() {
|
||||
|
||||
+106
-38
@@ -16,6 +16,7 @@ import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.media.client.Audio;
|
||||
import com.google.gwt.media.client.MediaBase;
|
||||
import com.google.gwt.typedarrays.shared.Int8Array;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
@@ -33,6 +34,7 @@ import com.sap.sailing.domain.common.dto.VideoMetadataDTO;
|
||||
import com.sap.sailing.domain.common.media.MediaTrack;
|
||||
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.media.JSDownloadUtils.JSDownloadCallback;
|
||||
import com.sap.sailing.gwt.ui.common.client.YoutubeApi;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
@@ -140,13 +142,11 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
}
|
||||
|
||||
native void addLoadMetadataHandler(MediaElement mediaElement) /*-{
|
||||
var that = this;
|
||||
mediaElement
|
||||
.addEventListener(
|
||||
'loadedmetadata',
|
||||
function() {
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::loadedmetadata(Lcom/google/gwt/dom/client/MediaElement;)(mediaElement);
|
||||
});
|
||||
var that = this;
|
||||
mediaElement.addEventListener('loadedmetadata',
|
||||
function() {
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::loadedmetadata(Lcom/google/gwt/dom/client/MediaElement;)(mediaElement);
|
||||
});
|
||||
}-*/;
|
||||
|
||||
public void loadedmetadata(MediaElement mediaElement) {
|
||||
@@ -242,6 +242,11 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
remoteMp4WasStarted = false;
|
||||
remoteMp4WasFinished = false;
|
||||
refreshUI();
|
||||
if (MimeType.mp4.equals(mediaTrack.mimeType)) {
|
||||
processMp4(mediaTrack);
|
||||
} else {
|
||||
loadMediaDuration();
|
||||
}
|
||||
}
|
||||
|
||||
private String sliceBefore(String lastPathSegment, String slicer) {
|
||||
@@ -265,13 +270,13 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
}
|
||||
|
||||
private native void registerNativeMethods() /*-{
|
||||
var that = this;
|
||||
window.youtubeMetadataCallback = function(metadata) {
|
||||
var title = metadata.entry.media$group.media$title.$t;
|
||||
var duration = metadata.entry.media$group.yt$duration.seconds;
|
||||
var description = metadata.entry.media$group.media$description.$t;
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::youtubeMetadataCallback(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(title, duration, description);
|
||||
}
|
||||
var that = this;
|
||||
window.youtubeMetadataCallback = function(metadata) {
|
||||
var title = metadata.entry.media$group.media$title.$t;
|
||||
var duration = metadata.entry.media$group.yt$duration.seconds;
|
||||
var description = metadata.entry.media$group.media$description.$t;
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::youtubeMetadataCallback(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(title, duration, description);
|
||||
}
|
||||
}-*/;
|
||||
|
||||
/**
|
||||
@@ -280,28 +285,24 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
* @param youtubeId
|
||||
*/
|
||||
public native void loadYoutubeMetadata(String youtubeId) /*-{
|
||||
var that = this;
|
||||
|
||||
//Create temporary script element.
|
||||
window.youtubeMetadataCallbackScript = document.createElement("script");
|
||||
window.youtubeMetadataCallbackScript.src = "http://gdata.youtube.com/feeds/api/videos/"
|
||||
+ youtubeId
|
||||
+ "?alt=json&orderby=published&format=6&callback=youtubeMetadataCallback";
|
||||
document.body.appendChild(window.youtubeMetadataCallbackScript);
|
||||
|
||||
// Cancel meta data capturing after has 2-seconds timeout.
|
||||
setTimeout(
|
||||
function() {
|
||||
//Remove temporary script element.
|
||||
if (window != null && window.youtubeMetadataCallbackScript != null) {
|
||||
document.body
|
||||
.removeChild(window.youtubeMetadataCallbackScript);
|
||||
|
||||
delete window.youtubeMetadataCallbackScript;
|
||||
}
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::setUiEnabled(Z)(true);
|
||||
}, 2000);
|
||||
var that = this;
|
||||
//Create temporary script element.
|
||||
window.youtubeMetadataCallbackScript = document.createElement("script");
|
||||
window.youtubeMetadataCallbackScript.src = "http://gdata.youtube.com/feeds/api/videos/"
|
||||
+ youtubeId
|
||||
+ "?alt=json&orderby=published&format=6&callback=youtubeMetadataCallback";
|
||||
document.body.appendChild(window.youtubeMetadataCallbackScript);
|
||||
|
||||
// Cancel meta data capturing after has 2-seconds timeout.
|
||||
setTimeout(
|
||||
function() {
|
||||
//Remove temporary script element.
|
||||
if (window != null && window.youtubeMetadataCallbackScript != null) {
|
||||
document.body.removeChild(window.youtubeMetadataCallbackScript);
|
||||
delete window.youtubeMetadataCallbackScript;
|
||||
}
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::setUiEnabled(Z)(true);
|
||||
}, 2000);
|
||||
}-*/;
|
||||
|
||||
public void youtubeMetadataCallback(String title, String durationInSeconds, String description) {
|
||||
@@ -372,17 +373,84 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
setUiEnabled(true);
|
||||
remoteMp4WasFinished = true;
|
||||
manualMimeTypeSelection(caught.getMessage(), mediaTrack);
|
||||
loadMediaDuration();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given url that points to an mp4 video, attempts are made to parse the header, to determine the actual
|
||||
* starttime of the video and to check for a 360° flag. The video will be analyzed by the backendserver, either via
|
||||
* direct download, or proxied by the client, if a video is only available locally. If the video header cannot be
|
||||
* read, default values are used instead.
|
||||
*/
|
||||
private void checkMetadata(String url, Label lbl, AsyncCallback<VideoMetadataDTO> asyncCallback) {
|
||||
// check on server first
|
||||
mediaService.checkMetadata(mediaTrack.url, new AsyncCallback<VideoMetadataDTO>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(VideoMetadataDTO result) {
|
||||
remoteMp4WasFinished = true;
|
||||
if (result.isDownloadable()) {
|
||||
setUiEnabled(true);
|
||||
mp4MetadataResult(result);
|
||||
} else {
|
||||
checkMetadataOnClient(url, lbl, asyncCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
// try on client instead
|
||||
checkMetadataOnClient(url, lbl, asyncCallback);
|
||||
}
|
||||
|
||||
private void checkMetadataOnClient(String url, Label lbl, AsyncCallback<VideoMetadataDTO> asyncCallback) {
|
||||
JSDownloadUtils.getData(url, new JSDownloadCallback() {
|
||||
|
||||
@Override
|
||||
public void progress(Double current, Double total) {
|
||||
lbl.setText(stringMessages.transferStarted() + " " + Math.round(current / 1024 / 1024) + "/"
|
||||
+ Math.round(total / 1024 / 1024) + " MB");
|
||||
infoLabel.setWidget(lbl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Object msg) {
|
||||
asyncCallback
|
||||
.onSuccess(new VideoMetadataDTO(false, null, false, null, msg == null ? "" : msg.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(Int8Array start, Int8Array end, Double skipped) {
|
||||
lbl.setText(stringMessages.analyze());
|
||||
infoLabel.setWidget(lbl);
|
||||
byte[] jStart = new byte[start.byteLength()];
|
||||
for (int i = 0; i < start.byteLength(); i++) {
|
||||
jStart[i] = start.get(i);
|
||||
}
|
||||
byte[] jEnd = new byte[end.byteLength()];
|
||||
for (int i = 0; i < end.byteLength(); i++) {
|
||||
jEnd[i] = end.get(i);
|
||||
}
|
||||
// Due to js represeting everything as 64double, the max safe file is around 4 petabytes
|
||||
mediaService.checkMetadata(jStart, jEnd, skipped.longValue(), asyncCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void mp4MetadataResult(VideoMetadataDTO result) {
|
||||
if (!result.isDownloadable()) {
|
||||
Window.alert("Could not download file " + mediaTrack.url);
|
||||
Window.alert(stringMessages.couldNotDownload(mediaTrack.url));
|
||||
manualMimeTypeSelection(result.getMessage(), mediaTrack);
|
||||
} else {
|
||||
mediaTrack.duration = result.getDuration();
|
||||
mediaTrack.mimeType = result.isSpherical() ? MimeType.mp4panorama : MimeType.mp4;
|
||||
mediaTrack.startTime = new MillisecondsTimePoint(result.getRecordStartedTime());
|
||||
if(result.getRecordStartedTime() != null){
|
||||
mediaTrack.startTime = new MillisecondsTimePoint(result.getRecordStartedTime());
|
||||
}
|
||||
refreshUI();
|
||||
}
|
||||
}
|
||||
@@ -403,7 +471,7 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
mediaTrack.mimeType = MimeType.valueOf(mimeTypeListBox.getSelectedValue());
|
||||
}
|
||||
});
|
||||
mimeTypeListBox.setSelectedIndex(MimeType.mp4.equals(mediaTrack.mimeType)?0:1);
|
||||
mimeTypeListBox.setSelectedIndex(MimeType.mp4.equals(mediaTrack.mimeType) ? 0 : 1);
|
||||
fp.add(mimeTypeListBox);
|
||||
infoLabel.setWidget(fp);
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.client.media;
|
||||
|
||||
import com.google.gwt.resources.client.ClientBundle;
|
||||
import com.google.gwt.resources.client.ImageResource;
|
||||
|
||||
interface VideoContainerRessource extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/edit.png")
|
||||
ImageResource editIcon();
|
||||
}
|
||||
+55
-14
@@ -1,35 +1,70 @@
|
||||
package com.sap.sailing.gwt.ui.client.media;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||
import com.google.gwt.dom.client.Style.Display;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.logical.shared.CloseEvent;
|
||||
import com.google.gwt.event.logical.shared.CloseHandler;
|
||||
import com.google.gwt.user.client.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.PopupPanel;
|
||||
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.media.MediaSynchControl.EditButtonProxy;
|
||||
import com.sap.sailing.gwt.ui.client.media.popup.PopoutWindowPlayer.PlayerCloseListener;
|
||||
import com.sap.sailing.gwt.ui.client.media.shared.VideoSynchPlayer;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.dialog.WindowBox;
|
||||
|
||||
public class VideoFloatingContainer extends AbstractVideoContainer implements VideoContainer {
|
||||
VideoContainerRessource res = GWT.create(VideoContainerRessource.class);
|
||||
|
||||
private static final int MIN_WIDTH = 420;
|
||||
private final WindowBox dialogBox;
|
||||
private final MediaSynchControl mediaSynchControl;
|
||||
private final PopupPositionProvider popupPositionProvider;
|
||||
|
||||
public VideoFloatingContainer(VideoSynchPlayer videoPlayer, PopupPositionProvider popupPositionProvider, boolean showSynchControls, MediaServiceAsync mediaService, ErrorReporter errorReporter, PlayerCloseListener playerCloseListener, PopoutListener popoutListener) {
|
||||
private Anchor edit;
|
||||
|
||||
public VideoFloatingContainer(VideoSynchPlayer videoPlayer, PopupPositionProvider popupPositionProvider,
|
||||
boolean showSynchControls, MediaServiceAsync mediaService, ErrorReporter errorReporter,
|
||||
PlayerCloseListener playerCloseListener, PopoutListener popoutListener) {
|
||||
super(new FlowPanel(), videoPlayer, popoutListener, playerCloseListener);
|
||||
|
||||
this.popupPositionProvider = popupPositionProvider;
|
||||
|
||||
|
||||
rootPanel.addStyleName("video-root-panel");
|
||||
rootPanel.add(videoPlayer.getWidget());
|
||||
|
||||
if (showSynchControls) {
|
||||
mediaSynchControl = new MediaSynchControl(this.videoPlayer, mediaService, errorReporter);
|
||||
mediaSynchControl.widget().addStyleName("media-synch-control");
|
||||
|
||||
if (showSynchControls) {
|
||||
this.edit = new Anchor();
|
||||
this.edit.getElement().getStyle().setBackgroundImage("url('" + res.editIcon().getSafeUri().asString() + "')");
|
||||
EditButtonProxy proxy = new EditButtonProxy() {
|
||||
|
||||
@Override
|
||||
public void setTitle(String string) {
|
||||
edit.setTitle(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAction(Runnable runnable) {
|
||||
edit.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean b) {
|
||||
edit.getElement().getStyle().setDisplay(b ? Display.BLOCK : Display.NONE);
|
||||
}
|
||||
};
|
||||
mediaSynchControl = new MediaSynchControl(this.videoPlayer, mediaService, errorReporter, proxy);
|
||||
mediaSynchControl.widget().addStyleName("media-synch-control");
|
||||
rootPanel.add(mediaSynchControl.widget());
|
||||
} else {
|
||||
mediaSynchControl = null;
|
||||
@@ -37,14 +72,20 @@ public class VideoFloatingContainer extends AbstractVideoContainer implements Vi
|
||||
|
||||
videoPlayer.setEditFlag(mediaSynchControl);
|
||||
|
||||
this.dialogBox = new WindowBox(videoPlayer.getMediaTrack().title, videoPlayer.getMediaTrack().toString(), rootPanel, new WindowBox.PopoutHandler() {
|
||||
this.dialogBox = new WindowBox(videoPlayer.getMediaTrack().title, videoPlayer.getMediaTrack().toString(),
|
||||
rootPanel, new WindowBox.PopoutHandler() {
|
||||
|
||||
@Override
|
||||
public void popout() {
|
||||
VideoFloatingContainer.this.popoutListener.popoutVideo(VideoFloatingContainer.this.videoPlayer.getMediaTrack());
|
||||
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void popout() {
|
||||
VideoFloatingContainer.this.popoutListener
|
||||
.popoutVideo(VideoFloatingContainer.this.videoPlayer.getMediaTrack());
|
||||
}
|
||||
});
|
||||
// values required for js and youtube player to stay useable
|
||||
dialogBox.setMinWidth(MIN_WIDTH);
|
||||
if(edit != null){
|
||||
dialogBox.addBeforeBarButtons(edit);
|
||||
}
|
||||
dialogBox.addCloseHandler(new CloseHandler<PopupPanel>() {
|
||||
|
||||
@Override
|
||||
@@ -62,7 +103,7 @@ public class VideoFloatingContainer extends AbstractVideoContainer implements Vi
|
||||
void show() {
|
||||
dialogBox.show();
|
||||
dialogBox.setVisible(false);
|
||||
|
||||
|
||||
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
+26
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.settings.client.raceboard.RaceBoardPerspectiveOwnSettings;
|
||||
import com.sap.sailing.gwt.ui.actions.GetLeaderboardByNameAction;
|
||||
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
|
||||
import com.sap.sailing.gwt.ui.client.RaceTimePanel;
|
||||
@@ -19,7 +20,11 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet.Competitor
|
||||
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.SingleRaceLeaderboardPanel;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sse.gwt.client.player.Timer;
|
||||
import com.sap.sse.gwt.client.player.Timer.PlayModes;
|
||||
|
||||
/**
|
||||
* Abstract base class for implementing a {@link RaceBoardMode}. The {@link #applyTo(RaceBoardPanel)} method registers
|
||||
@@ -191,4 +196,25 @@ public abstract class AbstractRaceBoardMode implements RaceBoardMode, RaceTimesI
|
||||
protected RaceColumnDTO getRaceColumn() {
|
||||
return raceColumn;
|
||||
}
|
||||
|
||||
protected void setTimerOrUseCustomStart(final TimePoint startPlayingAt) {
|
||||
final PlayModes playMode = getTimer().getPlayMode();
|
||||
TimePoint startPlayingAtOverride = null;
|
||||
if (playMode != PlayModes.Live && getRaceTimesInfoForRace() != null
|
||||
&& getRaceTimesInfoForRace().getStartOfRace() != null) {
|
||||
final Date startOfRace = getRaceTimesInfoForRace().getStartOfRace();
|
||||
final RaceBoardPerspectiveOwnSettings perspectiveOwnSettings = raceBoardPanel.getSettings()
|
||||
.getPerspectiveOwnSettings();
|
||||
if (perspectiveOwnSettings != null) {
|
||||
final Duration initialDurationAfterRaceStartInReplay = perspectiveOwnSettings
|
||||
.getInitialDurationAfterRaceStartInReplay();
|
||||
if (initialDurationAfterRaceStartInReplay != null) {
|
||||
startPlayingAtOverride = new MillisecondsTimePoint(
|
||||
startOfRace.getTime() + initialDurationAfterRaceStartInReplay.asMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
getTimer().setTime(
|
||||
startPlayingAtOverride != null ? startPlayingAtOverride.asMillis() : startPlayingAt.asMillis());
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.common.DetailType;
|
||||
import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardSettingsFactory;
|
||||
import com.sap.sailing.gwt.settings.client.leaderboard.SingleRaceLeaderboardSettings;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.SingleRaceLeaderboardPanel;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sse.gwt.client.player.Timer.PlayModes;
|
||||
import com.sap.sse.gwt.client.player.Timer.PlayStates;
|
||||
import com.sap.sse.security.ui.settings.ComponentContextWithSettingsStorageAndAdditionalSettingsLayers.OnSettingsReloadedCallback;
|
||||
@@ -34,7 +35,7 @@ public class FullAnalysisMode extends AbstractRaceBoardMode {
|
||||
if (getTimer().getPlayMode() == PlayModes.Live) {
|
||||
getTimer().setPlayMode(PlayModes.Replay);
|
||||
}
|
||||
getTimer().setTime(getRaceTimesInfoForRace().endOfRace.getTime());
|
||||
setTimerOrUseCustomStart(new MillisecondsTimePoint(getRaceTimesInfoForRace().endOfRace));
|
||||
}
|
||||
if (!leaderboardSettingsAdjusted && getLeaderboard() != null) {
|
||||
leaderboardSettingsAdjusted = true;
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ public class PlayerMode extends AbstractRaceBoardMode {
|
||||
}
|
||||
if (startPlayingAt != null) {
|
||||
timerAdjusted = true;
|
||||
getTimer().setTime(startPlayingAt.asMillis());
|
||||
setTimerOrUseCustomStart(startPlayingAt);
|
||||
getTimer().play();
|
||||
// we've done our adjustments; remove listener and let go
|
||||
stopReceivingRaceTimesInfos();
|
||||
|
||||
+178
-33
@@ -1,6 +1,14 @@
|
||||
package com.sap.sailing.gwt.ui.server;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
import java.text.DateFormat;
|
||||
@@ -9,6 +17,7 @@ import java.util.Date;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.mp4parser.IsoFile;
|
||||
import org.mp4parser.boxes.UserBox;
|
||||
@@ -20,6 +29,7 @@ import org.osgi.util.tracker.ServiceTracker;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
@@ -27,15 +37,19 @@ import com.sap.sailing.domain.common.dto.VideoMetadataDTO;
|
||||
import com.sap.sailing.domain.common.media.MediaTrack;
|
||||
import com.sap.sailing.gwt.ui.client.MediaService;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
|
||||
public class MediaServiceImpl extends RemoteServiceServlet implements MediaService {
|
||||
|
||||
// private static final Logger logger = Logger.getLogger(MediaServiceImpl.class.getName());
|
||||
// private static final Logger logger = Logger.getLogger(MediaServiceImpl.class.getName());
|
||||
|
||||
private static final int METADATA_CONNECTION_TIMEOUT = 10000;
|
||||
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
|
||||
|
||||
private ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
|
||||
|
||||
private static final int REQUIRED_SIZE = 1000000;
|
||||
private static final long serialVersionUID = -8917349579281305977L;
|
||||
|
||||
public MediaServiceImpl() {
|
||||
@@ -49,12 +63,12 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
|
||||
private RacingEventService racingEventService() {
|
||||
return racingEventServiceTracker.getService();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
|
||||
return racingEventService().getMediaTracksForRace(regattaAndRaceIdentifier);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
|
||||
return racingEventService().getMediaTracksInTimeRange(regattaAndRaceIdentifier);
|
||||
@@ -106,42 +120,173 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
|
||||
|
||||
@Override
|
||||
public VideoMetadataDTO checkMetadata(String url) {
|
||||
boolean canDownload = false;
|
||||
VideoMetadataDTO response = null;
|
||||
try {
|
||||
URL input = new URL(url);
|
||||
// check size and do rangerequests if possible
|
||||
long fileSize = determineFileSize(input);
|
||||
if (fileSize > 0) {
|
||||
response = checkMetadataByPartialDownloads(input, fileSize);
|
||||
} else {
|
||||
response = checkMetadataByFullFileDownload(input);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
response = new VideoMetadataDTO(false, null, false, null, e.getMessage());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private long determineFileSize(URL input) throws IOException, ProtocolException {
|
||||
//we need the size for efficient downloading
|
||||
HttpURLConnection connection = (HttpURLConnection) input.openConnection();
|
||||
connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT);
|
||||
connection.setRequestMethod("HEAD");
|
||||
long fileSize = -1;
|
||||
try (InputStream inStream = connection.getInputStream()) {
|
||||
fileSize = connection.getContentLengthLong();
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
|
||||
connection = (HttpURLConnection) input.openConnection();
|
||||
connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT);
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Range", "bytes=0-100");
|
||||
try (InputStream inStream = connection.getInputStream()) {
|
||||
if(connection.getResponseCode() != 206){
|
||||
fileSize = -1;
|
||||
}
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
private VideoMetadataDTO checkMetadataByPartialDownloads(URL input, long fileSize)
|
||||
throws IOException, ProtocolException {
|
||||
byte[] start = new byte[REQUIRED_SIZE];
|
||||
byte[] end = new byte[REQUIRED_SIZE];
|
||||
downloadPartOfFile(input, start, "bytes=0-" + REQUIRED_SIZE);
|
||||
downloadPartOfFile(input, end, "bytes=" + (fileSize - REQUIRED_SIZE) + "-");
|
||||
long skipped = fileSize - start.length - end.length;
|
||||
return checkMetadata(start, end, skipped);
|
||||
}
|
||||
|
||||
private void downloadPartOfFile(URL input, byte[] store, String range) throws IOException, ProtocolException {
|
||||
HttpURLConnection connection;
|
||||
connection = (HttpURLConnection) input.openConnection();
|
||||
connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT);
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Range", range);
|
||||
try (InputStream inStream = connection.getInputStream()) {
|
||||
DataInputStream dataInStream = new DataInputStream(inStream);
|
||||
dataInStream.readFully(store);
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private VideoMetadataDTO checkMetadataByFullFileDownload(URL input)
|
||||
throws ParserConfigurationException, SAXException, IOException {
|
||||
try (IsoFile isof = new IsoFile(Channels.newChannel(input.openStream()))) {
|
||||
boolean canDownload = true;
|
||||
Date recordStartedTimer = determineRecordingStart(isof);
|
||||
Duration duration = determineDuration(isof);
|
||||
boolean spherical = determine360(isof);
|
||||
return new VideoMetadataDTO(canDownload, duration, spherical, recordStartedTimer, "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoMetadataDTO checkMetadata(byte[] start, byte[] end, Long skipped) {
|
||||
File tmp = null;
|
||||
boolean spherical = false;
|
||||
Duration duration = null;
|
||||
Date recordStartedTimer = null;
|
||||
String message = "";
|
||||
try {
|
||||
URL input = new URL(url);
|
||||
try (IsoFile isof = new IsoFile(Channels.newChannel(input.openStream()))) {
|
||||
canDownload = true;
|
||||
// MovieHeaderBox movieHeaderBox = Path.getPath(isof, "moov[0]/mvhd");
|
||||
// System.out.println(movieHeaderBox.getCreationTime());
|
||||
UserBox uuidBox = Path.getPath(isof, "moov[0]/trak[0]/uuid");
|
||||
|
||||
MovieBox mbox = isof.getMovieBox();
|
||||
if (mbox != null) {
|
||||
MovieHeaderBox mhb = mbox.getMovieHeaderBox();
|
||||
if (mhb != null) {
|
||||
recordStartedTimer = mhb.getCreationTime();
|
||||
}
|
||||
}
|
||||
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
Document doc = dBuilder.parse(new ByteArrayInputStream(uuidBox.getData()));
|
||||
|
||||
NodeList childs = doc.getDocumentElement().getChildNodes();
|
||||
for (int i = 0; i < childs.getLength(); i++) {
|
||||
Node child = childs.item(i);
|
||||
if (child.getNodeName().toLowerCase().contains(":spherical")) {
|
||||
spherical = true;
|
||||
}
|
||||
}
|
||||
tmp = createFileFromData(start, end, skipped);
|
||||
try (IsoFile isof = new IsoFile(tmp)) {
|
||||
recordStartedTimer = determineRecordingStart(isof);
|
||||
spherical = determine360(isof);
|
||||
duration = determineDuration(isof);
|
||||
}
|
||||
throw new RuntimeException("remote error test");
|
||||
} catch (Exception e) {
|
||||
message = e.getMessage();
|
||||
} finally {
|
||||
if (tmp != null) {
|
||||
tmp.delete();
|
||||
}
|
||||
}
|
||||
return new VideoMetadataDTO(canDownload, spherical, recordStartedTimer, message);
|
||||
return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dummy mp4 file, only containing the start and the end, the middle is filled with 0 so that all file
|
||||
* internal pointers from start to end are working
|
||||
*/
|
||||
private File createFileFromData(byte[] start, byte[] end, Long skipped) throws IOException, FileNotFoundException {
|
||||
File tmp;
|
||||
tmp = File.createTempFile("upload", "metadataCheck");
|
||||
try (FileOutputStream fw = new FileOutputStream(tmp)) {
|
||||
fw.write(start);
|
||||
while (skipped > 0) {
|
||||
// ensure that no absurd amount of memory is required, and that more then 4gb files can be analysed
|
||||
if (skipped > 10000000) {
|
||||
byte[] dummy = new byte[10000000];
|
||||
fw.write(dummy);
|
||||
skipped = skipped - 10000000;
|
||||
} else {
|
||||
byte[] dummy = new byte[skipped.intValue()];
|
||||
fw.write(dummy);
|
||||
skipped = skipped - skipped.intValue();
|
||||
}
|
||||
}
|
||||
fw.write(end);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException {
|
||||
boolean spherical = false;
|
||||
UserBox uuidBox = Path.getPath(isof, "moov[0]/trak[0]/uuid");
|
||||
if (uuidBox != null) {
|
||||
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
|
||||
Document doc = dBuilder.parse(new ByteArrayInputStream(uuidBox.getData()));
|
||||
|
||||
NodeList childs = doc.getDocumentElement().getChildNodes();
|
||||
for (int i = 0; i < childs.getLength(); i++) {
|
||||
Node child = childs.item(i);
|
||||
if (child.getNodeName().toLowerCase().contains(":spherical")) {
|
||||
spherical = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return spherical;
|
||||
}
|
||||
|
||||
private Duration determineDuration(IsoFile isof) {
|
||||
Duration duration = null;
|
||||
MovieBox mbox = isof.getMovieBox();
|
||||
if (mbox != null) {
|
||||
MovieHeaderBox mhb = mbox.getMovieHeaderBox();
|
||||
if (mhb != null) {
|
||||
duration = new MillisecondsDurationImpl(mhb.getDuration()*1000 / mhb.getTimescale());
|
||||
}
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
private Date determineRecordingStart(IsoFile isof) {
|
||||
Date creationTime = null;
|
||||
MovieBox mbox = isof.getMovieBox();
|
||||
if (mbox != null) {
|
||||
MovieHeaderBox mhb = mbox.getMovieHeaderBox();
|
||||
if (mhb != null) {
|
||||
creationTime = mhb.getCreationTime();
|
||||
}
|
||||
}
|
||||
return creationTime;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,10 @@
|
||||
|
||||
<h2 class="articleSubheadline">January 2018</h2>
|
||||
<ul class="bulletList">
|
||||
<li>The auto detection for the media add dialog was improved. It will now attempt to only load the parts of a video, that are required to parse the tags. Additionally if the server cannot reach a video, the client (browser) will make an attempt to load the video. This allows to analyze locally hosted videos without the need to upload them.</li>
|
||||
<li>In the raceboard the floating video box was improved, leading to a more solid resizing and moving behavior.</li>
|
||||
<li>In the raceboard, the adding of videos has now an additional fallback startDate, if no startOfRace could be determined, and the file did not contain a proper creationtime tag, startOfTracking is used.</li>
|
||||
<li>In the raceboard, the floating video box will now only display an edit button in the header, next to close and minimize. Upon clicking it, the box will expand and show the prior always shown editing buttons. This saves a lot of screenspace for logged in admin enabled users.
|
||||
<li>Events have now an additional "WindFinder" tab in their configuration dialog.
|
||||
WindFinder can provide reviewed subsets of their measurement spots and
|
||||
gives an ID for such a reviewed spot collection. Enter one or more of such
|
||||
@@ -39,12 +43,12 @@
|
||||
|
||||
<h2 class="articleSubheadline">December 2017</h2>
|
||||
<ul class="bulletList">
|
||||
<li>In the raceboard it is now possible to upload 360� videos in addition to the already supported video types.
|
||||
360° Videos must use MimeType mp4panorama, 2d mp4 videos must still use mimetype mp4, as in older releases.
|
||||
<li>In the raceboard it is now possible to upload 360° videos in addition to the already supported video types.
|
||||
360° Videos must use MimeType mp4panorama, 2d mp4 videos must still use mimetype mp4, as in older releases.
|
||||
The dialog will try an auto-detection for the mimetype upon entering the link.
|
||||
Additionally, for mp4 videos it is now possible to use the start time, written in the file, as
|
||||
an start time preset for the mediatrack, simplifying the video linking.</li>
|
||||
<li> In Home it is now also possible to use 360� videos, for example as an highlight video</li>
|
||||
Additionally, for mp4 videos it is now possible to use the start time, written in the file, as
|
||||
a start time preset for the mediatrack, simplifying the video linking.</li>
|
||||
<li>In Home it is now also possible to use 360° videos, for example as a highlight video</li>
|
||||
<li>Various Fix importers can now properly report errors and other states</li>
|
||||
<li>Pairing list generation is now supported. Add competitors to a regatta, then use the new
|
||||
action icons in the Leaderboards/Leaderboards panel in the administration console to generate
|
||||
@@ -212,7 +216,7 @@
|
||||
64-bit "double" value for all components such at latitude, longitude, speed over ground or course over
|
||||
ground, "smaller" data types such as "int" and "short" are now employed. The reduction of accuracy
|
||||
that this brings about is negligible given the accuracy of the tracking systems used. For example,
|
||||
the latitude values are encoded such that while covering the full range from -90° to +90°, the
|
||||
the latitude values are encoded such that while covering the full range from -90° to +90°, the
|
||||
resolution is at 4.6mm. Similarly, speeds are represented in their compact form in such a way
|
||||
that speeds up to 500kts can be represented at a resolution of 0.015kts.
|
||||
</li>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.gwt-extras-WindowBox .dialogTopCenterInner {
|
||||
background-color: #bbb;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.gwt-extras-WindowBox .Caption {
|
||||
@@ -60,6 +60,11 @@
|
||||
background: url(images/resize_arrow.png) center center no-repeat;
|
||||
}
|
||||
|
||||
.gwt-extras-WindowBox .dialogBottomLeftInner {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.gwt-extras-WindowBox .media-synch-control {
|
||||
background: #efefef;
|
||||
padding: 10px;
|
||||
|
||||
@@ -23,6 +23,8 @@ import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.dom.client.Style.Cursor;
|
||||
import com.google.gwt.dom.client.Style.Position;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.dom.client.DomEvent;
|
||||
@@ -44,9 +46,9 @@ import com.google.gwt.user.client.ui.RootPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
|
||||
/**
|
||||
* Extension of the standard GWT DialogBox to provide a more "window"-like functionality. The WindowBox has
|
||||
* two control-buttons in the top right corner of the header, which allow the box to be turned into a dedicated browser window
|
||||
* ("popout") or the whole box to be closed. <br>
|
||||
* Extension of the standard GWT DialogBox to provide a more "window"-like functionality. The WindowBox has two
|
||||
* control-buttons in the top right corner of the header, which allow the box to be turned into a dedicated browser
|
||||
* window ("popout") or the whole box to be closed. <br>
|
||||
* <br>
|
||||
* The WindowBox relies on the css settings of {@link DialogBox} for styling of the border and header. It also uses the
|
||||
* following classes to style the additional elements:
|
||||
@@ -83,7 +85,6 @@ public class WindowBox extends DialogBox {
|
||||
CssResource windowBoxCss();
|
||||
}
|
||||
|
||||
|
||||
public interface PopoutHandler {
|
||||
void popout();
|
||||
}
|
||||
@@ -102,59 +103,111 @@ public class WindowBox extends DialogBox {
|
||||
private int minWidth = MIN_WIDTH;
|
||||
private int minHeight = MIN_HEIGHT;
|
||||
|
||||
private int dragMode;
|
||||
private enum DragMode {
|
||||
NORTH_WEST_RESIZE(true, false, true, false), SOUTH_EAST_RESIZE(false, true, false, true), NORTH_RESIZE(true,
|
||||
false, false, false), NORTH_EAST_RESIZE(true, false, false, true), WEST_RESIZE(false, false, true,
|
||||
false), EAST_RESIZE(false, false, false, true), SOUTH_WEST_RESIZE(false, true, true,
|
||||
false), SOUTH_RESIZE(false, true, false, false), MOVE(false, false, false, false);
|
||||
private boolean north;
|
||||
private boolean south;
|
||||
private boolean west;
|
||||
private boolean east;
|
||||
|
||||
DragMode(boolean north, boolean south, boolean west, boolean east) {
|
||||
this.north = north;
|
||||
this.south = south;
|
||||
this.west = west;
|
||||
this.east = east;
|
||||
}
|
||||
|
||||
public boolean isNorth() {
|
||||
return north;
|
||||
}
|
||||
|
||||
public boolean isSouth() {
|
||||
return south;
|
||||
}
|
||||
|
||||
public boolean isWest() {
|
||||
return west;
|
||||
}
|
||||
|
||||
public boolean isEast() {
|
||||
return east;
|
||||
}
|
||||
}
|
||||
|
||||
private DragMode dragMode;
|
||||
private PopoutHandler popoutHandler;
|
||||
private FlowPanel blocker;
|
||||
private Grid ctrlGrid;
|
||||
|
||||
public void setMinHeight(int minHeight) {
|
||||
this.minHeight = minHeight;
|
||||
}
|
||||
|
||||
public void setMinWidth(int minWidth) {
|
||||
this.minWidth = minWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper method to change the cursor for a given element when resizing is enabled.
|
||||
*
|
||||
* @param dm
|
||||
* @param dragMode
|
||||
* The code describing the position of the element in question
|
||||
* @param element
|
||||
* The {@link com.google.gwt.dom.client.Element} to set the cursor on
|
||||
*/
|
||||
protected static void updateCursor(int dm, com.google.gwt.dom.client.Element element) {
|
||||
protected void updateCursor(DragMode dragMode, com.google.gwt.dom.client.Element element) {
|
||||
Cursor cursor;
|
||||
|
||||
switch (dm) {
|
||||
case 0:
|
||||
cursor = Cursor.NW_RESIZE;
|
||||
break;
|
||||
if (dragMode != null)
|
||||
switch (dragMode) {
|
||||
case MOVE:
|
||||
cursor = Cursor.MOVE;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
cursor = Cursor.N_RESIZE;
|
||||
break;
|
||||
case NORTH_WEST_RESIZE:
|
||||
cursor = Cursor.NW_RESIZE;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
cursor = Cursor.NE_RESIZE;
|
||||
break;
|
||||
case NORTH_RESIZE:
|
||||
cursor = Cursor.N_RESIZE;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
cursor = Cursor.W_RESIZE;
|
||||
break;
|
||||
case NORTH_EAST_RESIZE:
|
||||
cursor = Cursor.NE_RESIZE;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
cursor = Cursor.E_RESIZE;
|
||||
break;
|
||||
case WEST_RESIZE:
|
||||
cursor = Cursor.W_RESIZE;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
cursor = Cursor.SW_RESIZE;
|
||||
break;
|
||||
case EAST_RESIZE:
|
||||
cursor = Cursor.E_RESIZE;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
cursor = Cursor.S_RESIZE;
|
||||
break;
|
||||
case SOUTH_WEST_RESIZE:
|
||||
cursor = Cursor.SW_RESIZE;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
cursor = Cursor.SE_RESIZE;
|
||||
break;
|
||||
case SOUTH_RESIZE:
|
||||
cursor = Cursor.S_RESIZE;
|
||||
break;
|
||||
|
||||
default:
|
||||
case SOUTH_EAST_RESIZE:
|
||||
cursor = Cursor.SE_RESIZE;
|
||||
break;
|
||||
|
||||
default:
|
||||
cursor = Cursor.AUTO;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
cursor = Cursor.AUTO;
|
||||
break;
|
||||
}
|
||||
|
||||
element.getStyle().setCursor(cursor);
|
||||
|
||||
}
|
||||
|
||||
public WindowBox(String title, String titleTooltip, Widget content, PopoutHandler popoutHandler) {
|
||||
@@ -163,6 +216,7 @@ public class WindowBox extends DialogBox {
|
||||
this.popoutHandler = popoutHandler;
|
||||
this.setStyleName("gwt-extras-WindowBox", true);
|
||||
this.container = new FlowPanel();
|
||||
super.setWidget(container);
|
||||
this.close = new Anchor();
|
||||
this.close.setStyleName("gwt-extras-dialog-close");
|
||||
this.close.addClickHandler(new ClickHandler() {
|
||||
@@ -177,45 +231,44 @@ public class WindowBox extends DialogBox {
|
||||
onPopoutClick(event);
|
||||
}
|
||||
});
|
||||
Grid ctrlGrid = null;
|
||||
|
||||
ctrlGrid = null;
|
||||
if (popoutHandler != null) {
|
||||
ctrlGrid = new Grid(1, 2);
|
||||
ctrlGrid.setWidget(0, 0, this.minimize);
|
||||
ctrlGrid.setWidget(0, 1, this.close);
|
||||
ctrlGrid = new Grid(1, 3);
|
||||
ctrlGrid.setWidget(0, 1, this.minimize);
|
||||
ctrlGrid.setWidget(0, 2, this.close);
|
||||
} else {
|
||||
ctrlGrid = new Grid(1, 1);
|
||||
ctrlGrid.setWidget(0, 0, this.close);
|
||||
ctrlGrid = new Grid(1, 2);
|
||||
ctrlGrid.setWidget(0, 1, this.close);
|
||||
}
|
||||
this.controlsPanel = new FlowPanel();
|
||||
this.controlsPanel.setStyleName("gwt-extras-dialog-controls");
|
||||
this.controlsPanel.add(ctrlGrid);
|
||||
this.dragMode = -1;
|
||||
this.container.add(this.controlsPanel);
|
||||
setTitle(titleTooltip);
|
||||
setText(title);
|
||||
setWidget(content);
|
||||
blocker = new FlowPanel();
|
||||
blocker.getElement().getStyle().setZIndex(1000);
|
||||
blocker.getElement().getStyle().setTop(0, Unit.PX);
|
||||
blocker.getElement().getStyle().setLeft(0, Unit.PX);
|
||||
blocker.getElement().getStyle().setOpacity(0.1f);
|
||||
blocker.getElement().getStyle().setBackgroundColor("grey");
|
||||
blocker.getElement().getStyle().setPosition(Position.ABSOLUTE);
|
||||
dragResizeWidget(this, 0, 0);
|
||||
}
|
||||
|
||||
public void addBeforeBarButtons(Widget widget) {
|
||||
ctrlGrid.setWidget(0, 0, widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cursor to indicate resizability for a specified "drag-mode" (i.e. how the box is being resized) on the
|
||||
* dialog box. The position is described by an integer, as follows:
|
||||
*
|
||||
* <pre>
|
||||
* 0-- --1-- --2
|
||||
* | |
|
||||
*
|
||||
* | |
|
||||
* 3 -1 5
|
||||
* | |
|
||||
*
|
||||
* | |
|
||||
* 6-- --7-- --8
|
||||
* </pre>
|
||||
*
|
||||
* passing <code>-1</code> resets the cursor to the default.
|
||||
* dialog box. The position is described by an enum
|
||||
*
|
||||
* @param dragMode
|
||||
*/
|
||||
protected void updateCursor(int dragMode) {
|
||||
protected void updateCursor(DragMode dragMode) {
|
||||
updateCursor(dragMode, this.getElement());
|
||||
com.google.gwt.dom.client.Element top = this.getCellElement(0, 1);
|
||||
updateCursor(dragMode, top);
|
||||
@@ -240,7 +293,7 @@ public class WindowBox extends DialogBox {
|
||||
case Event.ONMOUSEMOVE:
|
||||
case Event.ONMOUSEOVER:
|
||||
case Event.ONMOUSEOUT:
|
||||
if (this.dragMode >= 0 || calcDragMode(event.getClientX(), event.getClientY()) >= 0) {
|
||||
if (this.dragMode != null || calcDragMode(event.getClientX(), event.getClientY()) != null) {
|
||||
// paste'n'copy from Widget.onBrowserEvent
|
||||
switch (DOM.eventGetType(event)) {
|
||||
case Event.ONMOUSEOVER:
|
||||
@@ -258,7 +311,7 @@ public class WindowBox extends DialogBox {
|
||||
DomEvent.fireNativeEvent(event, this, this.getElement());
|
||||
return;
|
||||
}
|
||||
if (this.dragMode < 0) {
|
||||
if (this.dragMode == null) {
|
||||
this.updateCursor(this.dragMode);
|
||||
}
|
||||
}
|
||||
@@ -296,15 +349,16 @@ public class WindowBox extends DialogBox {
|
||||
* @return A value in range [-1..8] describing the position of the mouse (see {@link #updateCursor(int)} for more
|
||||
* information)
|
||||
*/
|
||||
protected int calcDragMode(int clientX, int clientY) {
|
||||
protected DragMode calcDragMode(int clientX, int clientY) {
|
||||
int tolerance = 5;
|
||||
com.google.gwt.dom.client.Element resize = this.getCellElement(2, 2).getParentElement();
|
||||
int xr = this.getRelX(resize, clientX);
|
||||
int yr = this.getRelY(resize, clientY);
|
||||
int w = resize.getClientWidth();
|
||||
int h = resize.getClientHeight();
|
||||
if ((xr >= 0 && xr <= w && yr >= -tolerance && yr <= h) || (yr >= 0 && yr <= h && xr >= -tolerance && xr <= w)) {
|
||||
return 8;
|
||||
if ((xr >= 0 && xr <= w && yr >= -tolerance && yr <= h)
|
||||
|| (yr >= 0 && yr <= h && xr >= -tolerance && xr <= w)) {
|
||||
return DragMode.SOUTH_EAST_RESIZE;
|
||||
}
|
||||
resize = this.getCellElement(2, 0).getParentElement();
|
||||
xr = this.getRelX(resize, clientX);
|
||||
@@ -313,33 +367,8 @@ public class WindowBox extends DialogBox {
|
||||
h = resize.getClientHeight();
|
||||
if ((xr >= 0 && xr <= w && yr >= -tolerance && yr <= h)
|
||||
|| (yr >= 0 && yr <= h && xr >= 0 && xr <= w + tolerance)) {
|
||||
return 6;
|
||||
return DragMode.SOUTH_WEST_RESIZE;
|
||||
}
|
||||
/*
|
||||
* resize = this.getCellElement(0, 2).getParentElement(); xr = this.getRelX(resize, clientX); yr =
|
||||
* this.getRelY(resize, clientY);
|
||||
*
|
||||
* w = resize.getClientWidth(); h = resize.getClientHeight();
|
||||
*
|
||||
* if ((xr >= 0 && xr <= w && yr >= 0 && yr <= h + tolerance) || (yr >= 0 && yr <= h && xr >= -tolerance && xr
|
||||
* <= w)) return 2;
|
||||
*
|
||||
* resize = this.getCellElement(0, 0).getParentElement(); xr = this.getRelX(resize, clientX); yr =
|
||||
* this.getRelY(resize, clientY);
|
||||
*
|
||||
* w = resize.getClientWidth(); h = resize.getClientHeight();
|
||||
*
|
||||
* if ((xr >= 0 && xr <= w && yr >= 0 && yr <= h + tolerance) || (yr >= 0 && yr <= h && xr >= 0 && xr <= w +
|
||||
* tolerance)) return 0;
|
||||
*
|
||||
* resize = this.getCellElement(0, 1).getParentElement(); xr = this.getRelX(resize, clientX); yr =
|
||||
* this.getRelY(resize, clientY);
|
||||
*
|
||||
* w = resize.getClientWidth(); h = resize.getClientHeight();
|
||||
*
|
||||
* if (yr >= 0 && yr <= h) return 1;
|
||||
*/
|
||||
|
||||
resize = this.getCellElement(1, 0).getParentElement();
|
||||
xr = this.getRelX(resize, clientX);
|
||||
yr = this.getRelY(resize, clientY);
|
||||
@@ -348,7 +377,7 @@ public class WindowBox extends DialogBox {
|
||||
h = resize.getClientHeight();
|
||||
|
||||
if (xr >= 0 && xr <= w)
|
||||
return 3;
|
||||
return DragMode.WEST_RESIZE;
|
||||
|
||||
resize = this.getCellElement(2, 1).getParentElement();
|
||||
xr = this.getRelX(resize, clientX);
|
||||
@@ -358,7 +387,7 @@ public class WindowBox extends DialogBox {
|
||||
h = resize.getClientHeight();
|
||||
|
||||
if (yr >= 0 && yr <= h)
|
||||
return 7;
|
||||
return DragMode.SOUTH_RESIZE;
|
||||
|
||||
resize = this.getCellElement(1, 2).getParentElement();
|
||||
xr = this.getRelX(resize, clientX);
|
||||
@@ -368,9 +397,18 @@ public class WindowBox extends DialogBox {
|
||||
h = resize.getClientHeight();
|
||||
|
||||
if (xr >= 0 && xr <= w) {
|
||||
return 5;
|
||||
return DragMode.EAST_RESIZE;
|
||||
}
|
||||
return -1;
|
||||
|
||||
resize = this.getCellElement(0, 1).getParentElement();
|
||||
xr = this.getRelX(resize, clientX);
|
||||
yr = this.getRelY(resize, clientY);
|
||||
w = resize.getClientWidth();
|
||||
h = resize.getClientHeight();
|
||||
if (yr >= 0 && yr <= h && xr < w - 100)
|
||||
return DragMode.MOVE;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,38 +419,59 @@ public class WindowBox extends DialogBox {
|
||||
* @param dy
|
||||
*/
|
||||
protected void dragResizeWidget(PopupPanel panel, int dx, int dy) {
|
||||
Widget widget = panel.getWidget();
|
||||
|
||||
int x = this.getPopupLeft();
|
||||
int y = this.getPopupTop();
|
||||
Widget widget = panel.getWidget();
|
||||
// left + right
|
||||
if ((this.dragMode % 3) != 1) {
|
||||
int w = widget.getOffsetWidth();
|
||||
// left edge -> move left
|
||||
if ((this.dragMode % 3) == 0) {
|
||||
x += dx;
|
||||
w -= dx;
|
||||
} else {
|
||||
w += dx;
|
||||
int w = widget.getOffsetWidth();
|
||||
int h = widget.getOffsetHeight();
|
||||
|
||||
if (dragMode != null) {
|
||||
// left + right
|
||||
if (this.dragMode.isEast() || dragMode.isWest()) {
|
||||
// left edge -> move left
|
||||
if (dragMode.isWest()) {
|
||||
x += dx;
|
||||
w -= dx;
|
||||
if (w < minWidth) {
|
||||
int toAdd = minWidth - w;
|
||||
x -= toAdd;
|
||||
w += toAdd;
|
||||
}
|
||||
} else {
|
||||
w += dx;
|
||||
if (w < minWidth) {
|
||||
w = minWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
w = w < this.minWidth ? this.minWidth : w;
|
||||
widget.setWidth(w + "px");
|
||||
}
|
||||
// up + down
|
||||
if ((this.dragMode / 3) != 1) {
|
||||
int h = widget.getOffsetHeight();
|
||||
// up = dy is negative
|
||||
if ((this.dragMode / 3) == 0) {
|
||||
y += dy;
|
||||
h -= dy;
|
||||
} else {
|
||||
h += dy;
|
||||
// up + down
|
||||
if (dragMode.isNorth() || dragMode.isSouth()) {
|
||||
// up = dy is negative
|
||||
if (dragMode.isNorth()) {
|
||||
y += dy;
|
||||
h -= dy;
|
||||
if (h < minHeight) {
|
||||
int toAdd = minHeight - h;
|
||||
h += toAdd;
|
||||
y -= toAdd;
|
||||
}
|
||||
} else {
|
||||
h += dy;
|
||||
if (h < minHeight) {
|
||||
h = minHeight;
|
||||
}
|
||||
}
|
||||
h = h < this.minHeight ? this.minHeight : h;
|
||||
}
|
||||
h = h < this.minHeight ? this.minHeight : h;
|
||||
widget.setHeight(h + "px");
|
||||
}
|
||||
if (this.dragMode / 3 == 0 || this.dragMode % 3 == 0) {
|
||||
panel.setPopupPosition(x, y);
|
||||
}
|
||||
|
||||
widget.setHeight(h + "px");
|
||||
widget.setWidth(w + "px");
|
||||
panel.setPopupPosition(x, y);
|
||||
|
||||
blocker.setWidth(getOffsetWidth() + "px");
|
||||
blocker.setHeight(getOffsetHeight() + "px");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -422,8 +481,11 @@ public class WindowBox extends DialogBox {
|
||||
*/
|
||||
@Override
|
||||
protected void beginDragging(MouseDownEvent event) {
|
||||
int dm = this.calcDragMode(event.getClientX(), event.getClientY());
|
||||
if (dm >= 0) {
|
||||
container.add(blocker);
|
||||
blocker.setWidth(getOffsetWidth() + "px");
|
||||
blocker.setHeight(getOffsetHeight() + "px");
|
||||
DragMode dm = this.calcDragMode(event.getClientX(), event.getClientY());
|
||||
if (dm != null && dm != DragMode.MOVE) {
|
||||
this.dragMode = dm;
|
||||
DOM.setCapture(getElement());
|
||||
this.dragX = event.getClientX();
|
||||
@@ -441,7 +503,7 @@ public class WindowBox extends DialogBox {
|
||||
*/
|
||||
@Override
|
||||
protected void continueDragging(MouseMoveEvent event) {
|
||||
if (this.dragMode >= 0) {
|
||||
if (this.dragMode != null && dragMode != DragMode.MOVE) {
|
||||
this.updateCursor(this.dragMode);
|
||||
int dx = event.getClientX() - this.dragX;
|
||||
int dy = event.getClientY() - this.dragY;
|
||||
@@ -450,7 +512,7 @@ public class WindowBox extends DialogBox {
|
||||
dragResizeWidget(this, dx, dy);
|
||||
} else {
|
||||
// this updates the cursor when dragging is NOT activated
|
||||
int dm = calcDragMode(event.getClientX(), event.getClientY());
|
||||
DragMode dm = calcDragMode(event.getClientX(), event.getClientY());
|
||||
this.updateCursor(dm);
|
||||
super.continueDragging(event);
|
||||
}
|
||||
@@ -469,7 +531,7 @@ public class WindowBox extends DialogBox {
|
||||
// is dragged.
|
||||
NativeEvent nativeEvent = event.getNativeEvent();
|
||||
if (!event.isCanceled() && (event.getTypeInt() == Event.ONMOUSEDOWN)
|
||||
&& calcDragMode(nativeEvent.getClientX(), nativeEvent.getClientY()) >= 0) {
|
||||
&& calcDragMode(nativeEvent.getClientX(), nativeEvent.getClientY()) != null) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
super.onPreviewNativeEvent(event);
|
||||
@@ -482,11 +544,13 @@ public class WindowBox extends DialogBox {
|
||||
*/
|
||||
@Override
|
||||
protected void endDragging(MouseUpEvent event) {
|
||||
if (this.dragMode >= 0) {
|
||||
container.remove(blocker);
|
||||
|
||||
if (this.dragMode != null && this.dragMode != DragMode.MOVE) {
|
||||
DOM.releaseCapture(getElement());
|
||||
this.dragX = event.getClientX() - this.dragX;
|
||||
this.dragY = event.getClientY() - this.dragY;
|
||||
this.dragMode = -1;
|
||||
this.dragMode = null;
|
||||
this.updateCursor(this.dragMode);
|
||||
RootPanel.get().getElement().getStyle().setCursor(Cursor.AUTO);
|
||||
} else {
|
||||
@@ -501,18 +565,11 @@ public class WindowBox extends DialogBox {
|
||||
*/
|
||||
@Override
|
||||
public void setWidget(Widget widget) {
|
||||
if (this.container.getWidgetCount() == 0) {
|
||||
// setup
|
||||
this.container.add(this.controlsPanel);
|
||||
// this.container.add(this.content);
|
||||
super.setWidget(this.container);
|
||||
} else {
|
||||
// remove the old one
|
||||
this.container.remove(1);
|
||||
Widget old = getWidget();
|
||||
if (old != null) {
|
||||
this.container.remove(old);
|
||||
}
|
||||
this.container.add(widget);
|
||||
// add the new widget
|
||||
// this.content.add(widget);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -522,11 +579,17 @@ public class WindowBox extends DialogBox {
|
||||
*/
|
||||
@Override
|
||||
public Widget getWidget() {
|
||||
if (this.container.getWidgetCount() > 1) {
|
||||
return this.container.getWidget(1);
|
||||
} else {
|
||||
return null;
|
||||
for (int i = 0; i < container.getWidgetCount(); i++) {
|
||||
Widget w = container.getWidget(i);
|
||||
if (w == blocker) {
|
||||
continue;
|
||||
}
|
||||
if (w == controlsPanel) {
|
||||
continue;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in new issue
Block a user