/*
* Copyright (C) 2012 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.webapp.client.widget;
import com.google.gwt.core.client.Scheduler;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.Progressbar;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.CloseClickEvent;
import com.smartgwt.client.widgets.events.CloseClickHandler;
import com.smartgwt.client.widgets.layout.HStack;
import com.smartgwt.client.widgets.layout.VLayout;
import cz.cas.lib.proarc.webapp.client.ClientMessages;
import cz.cas.lib.proarc.webapp.client.ClientUtils;
import cz.cas.lib.proarc.webapp.client.ds.RestConfig;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Shows progress in canvas or window.
*
* <p/>Use {@link #setInit() setInit}, {@link #setProgress setProgress}, {@link #setDone setDone}
* to manually control progress.
*
* <p>Or use {@link #setInit() setInit}, {@link #setDataSource setDataSource} to bind data source that
* supports paging.
*
* @author Jan Pokorsky
*/
public final class ProgressTracker {
private static final Logger LOG = Logger.getLogger(ProgressTracker.class.getName());
private final VLayout widget;
private final Progressbar progressbar;
private final Label label;
private int lastDone;
private int lastTotal;
private DataSource datasource;
private Criteria criteria;
private ProgressHandler progressHandler;
private Runnable exitCallback;
private Window window;
private final ClientMessages i18n;
private String progressPrefix;
private IButton closeBtn;
public ProgressTracker(ClientMessages i18n) {
this.i18n = i18n;
widget = new VLayout(4);
label = new Label();
label.setWidth100();
label.setAutoHeight();
progressbar = new Progressbar();
progressbar.setVertical(false);
progressbar.setLength(400);
progressbar.setBreadth(24);
widget.setMembers(label, progressbar);
widget.setWidth100();
widget.setAutoHeight();
progressPrefix = i18n.ProgressTracker_Progress_0();
closeBtn = new IButton(i18n.ProgressTracker_CloseBtn_Title(), new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
stop();
}
});
closeBtn.setTooltip(i18n.ProgressTracker_CloseBtn_Hint());
closeBtn.setAutoFit(true);
}
public VLayout asPanel() {
return widget;
}
public void showInWindow(Runnable exitCallback) {
showInWindow(exitCallback, i18n.ProgressTracker_Window_Title());
}
public void showInWindow(Runnable exitCallback, String title) {
this.exitCallback = exitCallback;
if (window == null) {
window = new Window();
window.setWidth(400);
window.setAutoSize(true);
window.setAutoCenter(true);
window.setIsModal(true);
widget.setMargin(10);
window.setTitle(title);
window.setShowMinimizeButton(false);
window.setShowModalMask(true);
window.addCloseClickHandler(new CloseClickHandler() {
@Override
public void onCloseClick(CloseClickEvent event) {
stop();
}
});
window.addItem(widget);
window.addItem(createControls());
}
window.show();
}
private Canvas createControls() {
HStack btnLayout = new HStack(5);
btnLayout.setAutoHeight();
btnLayout.setMargin(10);
btnLayout.setLayoutAlign(Alignment.RIGHT);
btnLayout.setMembers(closeBtn);
return btnLayout;
}
public void setInit() {
lastDone = lastTotal = 0;
label.setIcon(null);
label.setContents(i18n.ProgressTracker_Initializing_Msg());
progressbar.setPercentDone(0);
if (datasource != null) {
progressHandler = new ProgressHandler(datasource, criteria);
progressHandler.fetch(0, 10);
}
}
public void setProgress(int done, int total) {
done = Math.max(0, done);
total = Math.max(0, total);
done = Math.min(done, total);
lastDone = done;
lastTotal = total;
int progress = total == 0 ? 0 : done * 100 / total;
String msg = i18n.ProgressTracker_Progress_Msg(
progressPrefix, String.valueOf(done), String.valueOf(total));
label.setContents(msg);
progressbar.setPercentDone(progress);
}
public void setProgressPrefix(String prefix) {
this.progressPrefix = prefix;
}
public void setDone(String msg) {
setDone(msg, false);
}
public void setDone(String msg, boolean failure) {
setProgress(lastTotal, lastTotal);
if (msg != null) {
label.setContents(msg);
}
if (failure) {
label.setIcon("[SKIN]/Dialog/error.png");
}
}
/**
* Overrides default description of the close button.
*
* @see ClientMessages#ProgressTracker_CloseBtn_Title
* @see ClientMessages#ProgressTracker_CloseBtn_Hint
*/
public void setCloseButton(String title, String hint) {
closeBtn.setTitle(title == null ? i18n.ProgressTracker_CloseBtn_Title(): title);
closeBtn.setTooltip(hint == null ? i18n.ProgressTracker_CloseBtn_Hint() : hint);
}
public void stop() {
if (progressHandler != null) {
progressHandler.done();
progressHandler = null;
}
if (window != null) {
window.hide();
}
if (exitCallback != null) {
exitCallback.run();
}
}
public int getLastDone() {
return lastDone;
}
public int getLastTotal() {
return lastTotal;
}
public void setDataSource(DataSource ds, Criteria criteria) {
this.datasource = ds;
this.criteria = criteria;
}
private final class ProgressHandler implements Runnable {
private ProgressTracker tracker = ProgressTracker.this;
private boolean done;
private final DataSource ds;
private final Criteria criteria;
public ProgressHandler(DataSource ds, Criteria criteria) {
this.ds = ds;
this.criteria = criteria;
}
public void fetch(int startRow, int endRow, int delay) {
Scheduler.get().scheduleFixedPeriod(() -> {
fetch(startRow, endRow);
return false;
}, delay);
}
public void fetch(int startRow, int endRow) {
// #470: use fetch instead of ResultSet that cannot handle empty responses since SmartGWT 6.0
final DSRequest reqProps = new DSRequest();
reqProps.setStartRow(startRow);
reqProps.setEndRow(endRow);
reqProps.setWillHandleError(true);
ds.fetchData(criteria, (dsResponse, data, dsRequest) -> {
if (RestConfig.isStatusOk(dsResponse)) {
onDataArrived(dsResponse.getStartRow(), dsResponse.getEndRow(), dsResponse.getTotalRows());
} else {
onHandleError(dsResponse);
}
}, reqProps);
}
@Override
public void run() {
done();
}
public void done() {
done = true;
}
private void onDataArrived(int startRow, int endRow, final int length) {
if (done) {
return ;
}
Boolean lengthIsKnown = true;
ClientUtils.log(LOG, Level.FINE, "onDataArrived: [%s,%s,%s], lengthIsKnown: %s", startRow, endRow, length, lengthIsKnown);
tracker.setProgress(endRow, length);
if (lengthIsKnown && endRow == length) {
// done
ProgressTracker.this.stop();
ClientUtils.log(LOG, Level.FINE, "onDataArrived.done");
done();
} else {
ClientUtils.log(LOG, Level.FINE, "onDataArrived.next: [%s,%s]", endRow, length);
fetch(endRow, length, 2000);
}
}
private void onHandleError(DSResponse response) {
ClientUtils.log(LOG, Level.FINE, "onHandleError");
if (done) {
return ;
}
StringBuilder sb = new StringBuilder();
Map<?, ?> errors = response.getErrors();
if (errors != null) {
for (Iterator<?> it = errors.values().iterator(); it.hasNext();) {
sb.append(it.hasNext());
sb.append("<p>\n");
}
}
if (sb.length() == 0) {
sb.append(response.getHttpResponseText());
}
if (sb.length() == 0) {
sb.append(i18n.ProgressTracker_Failure_UnknownReason_Title());
}
String msg = i18n.ProgressTracker_Failure_Msg(sb.toString());
tracker.setDone(msg, true);
done();
}
}
}