/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.studio.scripts.execution;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import com.opendoorlogistics.api.ExecutionReport;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.scripts.parameters.Parameters;
import com.opendoorlogistics.api.scripts.parameters.Parameters.TableType;
import com.opendoorlogistics.api.scripts.parameters.ParametersControlFactory;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLListener;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.ui.Disposable;
import com.opendoorlogistics.core.api.impl.ODLApiImpl;
import com.opendoorlogistics.core.scripts.elements.Option;
import com.opendoorlogistics.core.scripts.elements.Script;
import com.opendoorlogistics.core.scripts.parameters.ParametersImpl;
import com.opendoorlogistics.core.scripts.utils.ScriptUtils;
import com.opendoorlogistics.core.tables.decorators.datastores.ListenerDecorator;
import com.opendoorlogistics.core.tables.decorators.datastores.dependencies.DataDependencies;
import com.opendoorlogistics.core.utils.LoggerUtils;
import com.opendoorlogistics.studio.GlobalMapSelectedRowsManager;
import com.opendoorlogistics.studio.GlobalMapSelectedRowsManager.GlobalSelectionChangedCB;
import com.opendoorlogistics.studio.controls.ODLScrollableToolbar;
import com.opendoorlogistics.studio.internalframes.ODLInternalFrame;
import com.opendoorlogistics.utils.ui.Icons;
final public class ReporterFrame<T extends JPanel & Disposable> extends ODLInternalFrame implements GlobalSelectionChangedCB {
private final static Logger LOGGER = Logger.getLogger(ReporterFrame.class.getName());
private final GlobalMapSelectedRowsManager gsm;
private final ReporterFrameIdentifier id;
private final Border defaultBorder;
private final Border outOfDateBorder = BorderFactory.createLineBorder(Color.RED, 2);
private final RefreshMode refreshMode;
private final ODLComponent callingComponent;
// private final JPanel parametersPanel;
private final SmartSouthPanel southPanel = new SmartSouthPanel();
// private JCheckBox autorefreshBox;
private JButton manualRefreshButton;
private JLabel refreshLabel;
private T userPanel;
private volatile boolean isDirty = false;
private OnRefreshReport refreshCB;
private ODLDatastore<? extends ODLTableReadOnly> externalDs;
private DataDependencies dependencies;
private Script unfilteredScript;
private ODLDatastoreAlterable<? extends ODLTableAlterable> parametersDs;
private HashSet<ODLListener> listeners = new HashSet<>();
private String title;
private boolean showLastRefreshedTime;
private LocalDateTime lastRefreshedTime;
private JLabel lastRefreshedTimeLabel;
private JLabel topLabel;
public enum RefreshMode {
AUTOMATIC, MANUAL_AUTO_DISABLE,MANUAL_ALWAYS_AVAILABLE, NEVER
}
public ReporterFrame(T userPanel, ReporterFrameIdentifier id, String title, ODLComponent component, RefreshMode refreshMode,boolean showLastRefreshedTime, GlobalMapSelectedRowsManager gmsrm) {
super(id.getCombinedId());
this.id = id;
this.userPanel = userPanel;
this.title = title;
this.defaultBorder = getBorder();
this.refreshMode = refreshMode;
this.gsm = gmsrm;
this.callingComponent = component;
this.showLastRefreshedTime = showLastRefreshedTime;
// this.parametersPanel = new JPanel();
// this.parametersPanel.setLayout(new BorderLayout());
// parametersPanel.setBorder(BorderFactory.createEmptyBorder());
gsm.registerListener(this);
setLayout(new BorderLayout());
add(userPanel, BorderLayout.CENTER);
saveLastRefreshedTime();
if (refreshMode == RefreshMode.MANUAL_AUTO_DISABLE || refreshMode == RefreshMode.MANUAL_ALWAYS_AVAILABLE) {
southPanel.addRefreshPanel(createRefreshControl());
}
updateAppearance();
}
private void saveLastRefreshedTime() {
lastRefreshedTime = LocalDateTime.now();
}
public void setRefresherCB(OnRefreshReport cb) {
this.refreshCB = cb;
updateAppearance();
}
public ODLComponent getComponent() {
return callingComponent;
}
/**
* South panel which adds / hides itself
*
* @author Phil
*
*/
private class SmartSouthPanel extends ODLScrollableToolbar {
private JPanel refreshPanel;
private JPanel parametersPanel;
SmartSouthPanel() {
// setLayout(new FlowLayout(FlowLayout.LEFT));
// setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
// setBorder(BorderFactory.createEmptyBorder());
// setMinimumSize(new Dimension());
getToolBar().setBorder(BorderFactory.createEmptyBorder());
}
void addRefreshPanel(JPanel panel) {
this.refreshPanel = panel;
update();
}
void addParametersPanel(JPanel panel) {
this.parametersPanel = panel;
update();
}
void update() {
ReporterFrame.this.remove(this);
getToolBar().removeAll();
int count = 0;
if (parametersPanel != null && parametersPanel.getComponentCount() > 0) {
parametersPanel.setBorder(BorderFactory.createEmptyBorder());
getToolBar().add(parametersPanel);
count++;
}
if (refreshPanel != null) {
getToolBar().add(refreshPanel);
count++;
}
if (count > 0) {
ReporterFrame.this.add(this, BorderLayout.SOUTH);
}
}
}
private JPanel createRefreshControl() {
// instantiate and configure the toolbar obejct
JPanel refreshBar = new JPanel();
refreshBar.setLayout(new BoxLayout(refreshBar, BoxLayout.X_AXIS));
// refreshBar.setFloatable(false);
// add the manual refresh label
refreshLabel = new JLabel(" Manual refresh ");
refreshBar.add(refreshLabel);
// add the manual refresh button
manualRefreshButton = new JButton(Icons.loadFromStandardPath("view-refresh-6.png"));
manualRefreshButton.setToolTipText("Manually refresh the report.");
manualRefreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (refreshCB != null && unfilteredScript != null) {
refreshCB.postReportRefreshRequest(unfilteredScript, id, false, parametersDs);
}
}
});
refreshBar.add(manualRefreshButton);
if(showLastRefreshedTime){
lastRefreshedTimeLabel = new JLabel("");
refreshBar.add(lastRefreshedTimeLabel);
}
// refreshBar.addSeparator();
return refreshBar;
}
private void updateAppearance() {
if (refreshMode == RefreshMode.MANUAL_AUTO_DISABLE || refreshMode == RefreshMode.MANUAL_ALWAYS_AVAILABLE) {
boolean manualEnabled = isDirty && refreshCB != null;
boolean alwaysEnabled = refreshCB!=null && refreshMode== RefreshMode.MANUAL_ALWAYS_AVAILABLE;
manualRefreshButton.setEnabled(manualEnabled || alwaysEnabled);
refreshLabel.setEnabled(manualEnabled|| alwaysEnabled);
// boolean autoenabled = refreshCB != null && (dependencies == null
// || dependencies.isWritten() == false);
// autorefreshBox.setEnabled(autoenabled);
setTitle(title + (isDirty ? " (OUT-OF-DATE)" : ""));
// change border
Border border;
if (isDirty) {
border = outOfDateBorder;
// }else if(defaultBorder!=null){
// border = defaultBorder;
} else {
border = defaultBorder;
}
if (getBorder() != border) {
setBorder(border);
}
} else {
setTitle(title);
}
if(lastRefreshedTimeLabel!=null && lastRefreshedTime!=null){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
lastRefreshedTimeLabel.setText(" Last refreshed " +formatter.format(lastRefreshedTime));
}
}
public ReporterFrameIdentifier getId() {
return id;
}
public JPanel getUserPanel() {
return userPanel;
}
public void setUserPanel(T panel) {
// changing panel?
if (this.userPanel != panel) {
// remove old panel
if (this.userPanel != null) {
remove(this.userPanel);
this.userPanel.dispose();
}
// add new panel
this.userPanel = panel;
add(userPanel, BorderLayout.CENTER);
}
isDirty = false;
saveLastRefreshedTime();
updateAppearance();
// need revalidate here or it doesn't always repaint (repaint doesn't
// work)
revalidate();
}
/**
* Set the reporter frame to be dirty. This can be called from other
* threads.
*/
public synchronized void setDirty() {
if (isDirty == false) {
isDirty = true;
LOGGER.info(LoggerUtils.prefix() + " - reporter frame " + id.getCombinedId() + " set dirty. Title="+getTitle());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
updateAppearance();
}
});
if (refreshMode == RefreshMode.AUTOMATIC) {
runAutorefresh();
}
}
}
public RefreshMode getRefreshMode() {
return refreshMode;
}
private void runAutorefresh() {
if (refreshCB != null && unfilteredScript != null && isDirty) {
refreshCB.postReportRefreshRequest(unfilteredScript, id, true, parametersDs);
}
}
public void setDependencies(ODLDatastore<? extends ODLTableReadOnly> ds, Script unfilteredScript, DataDependencies dependencies, ODLDatastore<? extends ODLTable> parametersDs,
ExecutionReport report) {
this.unfilteredScript = unfilteredScript;
// remove any listeners from the saved datastore
removeListeners();
if (ds != null && dependencies != null) {
this.externalDs = ds;
this.dependencies = dependencies;
// Add listener for table structure
if (this.dependencies.isReadTableSet()) {
ODLListener listener = new ODLListener() {
@Override
public void datastoreStructureChanged() {
LOGGER.info(LoggerUtils.prefix() + " - reporter frame " + id.getCombinedId() + ", datastore structure changed. Title=" + getTitle());
setDirty();
}
@Override
public void tableChanged(int tableId, int firstRow, int lastRow) {
// TODO Auto-generated method stub
}
@Override
public ODLListenerType getType() {
return ODLListenerType.DATASTORE_STRUCTURE_CHANGED;
}
};
listeners.add(listener);
ds.addListener(listener);
}
// Add listeners for read table data
int[] tableids = this.dependencies.getReadTableIds();
if (tableids.length > 0) {
ODLListener listener = new ODLListener() {
@Override
public void datastoreStructureChanged() {
}
@Override
public void tableChanged(int tableId, int firstRow, int lastRow) {
LOGGER.info(LoggerUtils.prefix() + " - reporter frame " + id.getCombinedId() + ", source table " + tableId + " is dirty. Title="+getTitle());
setDirty();
}
@Override
public ODLListenerType getType() {
return ODLListenerType.TABLE_CHANGED;
}
};
listeners.add(listener);
ds.addListener(listener, tableids);
}
} else {
this.externalDs = null;
this.dependencies = null;
}
// handle parameters and show / update the panel as needed
ODLApi api = new ODLApiImpl();
this.parametersDs = api.tables().copyDs(parametersDs);
Parameters parameters = api.scripts().parameters();
ParametersControlFactory pcf = parameters.getControlFactory();
// handle the case of overriding the visible parameters; replace with filtered table
if (id != null && id.getInstructionId() != null && parametersDs != null) {
String myOptionId = ScriptUtils.getOptionIdByInstructionId(unfilteredScript, id.getInstructionId());
Option myOption = myOptionId != null ? ScriptUtils.getOption(unfilteredScript, myOptionId) : unfilteredScript;
if (myOption != null && myOption.isOverrideVisibleParameters()) {
ODLTable parametersTable = parameters.findTable(this.parametersDs, TableType.PARAMETERS);
if (parametersTable != null) {
ODLTable filteredParamsTable = ((ParametersImpl) parameters).applyVisibleOverrides(myOption.getVisibleParametersOverride(), parametersTable, report);
if (!report.isFailed()) {
api.tables().clearTable(parametersTable);
for (int i = 0; i < filteredParamsTable.getRowCount(); i++) {
api.tables().copyRow(filteredParamsTable, i, parametersTable);
;
}
}
}
}
// }
}
// create the parameters table
JPanel parametersPanel = null;
if (parametersDs != null && pcf != null && !report.isFailed()) {
ListenerDecorator<ODLTable> listenerDecorator = new ListenerDecorator<ODLTable>(ODLTable.class, this.parametersDs);
parametersPanel = pcf.createHorizontalPanel(api, parameters.findTable(listenerDecorator, TableType.PARAMETERS), parameters.findTable(parametersDs, TableType.PARAMETER_VALUES));
if (parametersPanel != null) {
listenerDecorator.addListener(new ODLListener() {
@Override
public void tableChanged(int tableId, int firstRow, int lastRow) {
setDirty();
}
@Override
public ODLListenerType getType() {
return ODLListenerType.TABLE_CHANGED;
}
@Override
public void datastoreStructureChanged() {
setDirty();
}
}, new int[] { parameters.findTable(listenerDecorator, TableType.PARAMETERS).getImmutableId() });
}
}
southPanel.addParametersPanel(parametersPanel);
// it is assumed the control is no longer dirty after a call to set
// dependencies
isDirty = false;
saveLastRefreshedTime();
updateAppearance();
}
private void removeListeners() {
if (externalDs != null) {
for (ODLListener listener : listeners) {
this.externalDs.removeListener(listener);
}
listeners.clear();
}
}
@Override
public void dispose() {
removeListeners();
if (userPanel != null) {
userPanel.dispose();
userPanel = null;
}
gsm.unregisterListener(this);
super.dispose();
}
public static interface OnRefreshReport {
void postReportRefreshRequest(Script unfilteredScript, ReporterFrameIdentifier frameIdentifier, boolean isAutomaticRefresh, ODLDatastore<? extends ODLTable> parametersTable);
}
@Override
public void selectionChanged(GlobalMapSelectedRowsManager manager) {
if (dependencies != null && dependencies.isReadRowFlags()) {
LOGGER.info(LoggerUtils.prefix() + " - reporter frame " + id.getCombinedId() + ", reads row flags and selection has changed. Title=" + getTitle());
setDirty();
}
}
Script getUnfilteredScript() {
return unfilteredScript;
}
public ODLDatastore<? extends ODLTable> getParametersTable() {
return parametersDs;
}
// public Script getScript(){
// return script;
// }
public void setTopLabel(String topLabelText){
boolean changed=false;
if(topLabelText==null){
// remove top label if no longer needed
if(topLabel!=null){
remove(topLabel);
topLabel = null;
changed = true;
}
}else{
if(topLabel==null){
topLabel = new JLabel(topLabelText);
topLabel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1), BorderFactory.createEmptyBorder(5, 5, 5, 5)));
add(topLabel, BorderLayout.NORTH);
changed = true;
}else{
if(!topLabelText.equals(topLabel.getText())){
topLabel.setText(topLabelText);
changed = true;
}
}
}
if(changed){
revalidate();
repaint();
}
}
}