/******************************************************************************
* Product: Posterita Ajax UI *
* Copyright (C) 2007 Posterita Ltd. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* Posterita Ltd., 3, Draper Avenue, Quatre Bornes, Mauritius *
* or via info@posterita.org or http://www.posterita.org/ *
*****************************************************************************/
package org.adempiere.webui.adwindow;
import static org.compiere.model.MSysConfig.ZK_GRID_AFTER_FIND;
import static org.compiere.model.SystemIDs.PROCESS_AD_CHANGELOG_REDO;
import static org.compiere.model.SystemIDs.PROCESS_AD_CHANGELOG_UNDO;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.adempiere.exceptions.DBException;
import org.adempiere.util.Callback;
import org.adempiere.webui.AdempiereIdGenerator;
import org.adempiere.webui.AdempiereWebUI;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.Extensions;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.WArchive;
import org.adempiere.webui.WRequest;
import org.adempiere.webui.WZoomAcross;
import org.adempiere.webui.adwindow.validator.WindowValidatorEvent;
import org.adempiere.webui.adwindow.validator.WindowValidatorEventType;
import org.adempiere.webui.adwindow.validator.WindowValidatorManager;
import org.adempiere.webui.apps.AEnv;
import org.adempiere.webui.apps.BusyDialogTemplate;
import org.adempiere.webui.apps.HelpWindow;
import org.adempiere.webui.apps.ProcessModalDialog;
import org.adempiere.webui.apps.form.WCreateFromFactory;
import org.adempiere.webui.apps.form.WCreateFromWindow;
import org.adempiere.webui.apps.form.WQuickForm;
import org.adempiere.webui.component.DesktopTabpanel;
import org.adempiere.webui.component.Mask;
import org.adempiere.webui.component.ProcessInfoDialog;
import org.adempiere.webui.component.Window;
import org.adempiere.webui.component.ZkCssHelper;
import org.adempiere.webui.editor.IProcessButton;
import org.adempiere.webui.editor.WButtonEditor;
import org.adempiere.webui.editor.WEditor;
import org.adempiere.webui.editor.WStringEditor;
import org.adempiere.webui.event.ActionEvent;
import org.adempiere.webui.event.ActionListener;
import org.adempiere.webui.event.DialogEvents;
import org.adempiere.webui.event.ToolbarListener;
import org.adempiere.webui.exception.ApplicationException;
import org.adempiere.webui.factory.InfoManager;
import org.adempiere.webui.info.InfoWindow;
import org.adempiere.webui.panel.ADForm;
import org.adempiere.webui.panel.InfoPanel;
import org.adempiere.webui.panel.WAttachment;
import org.adempiere.webui.panel.WDocActionPanel;
import org.adempiere.webui.panel.action.CSVImportAction;
import org.adempiere.webui.panel.action.ExportAction;
import org.adempiere.webui.panel.action.FileImportAction;
import org.adempiere.webui.panel.action.ReportAction;
import org.adempiere.webui.part.AbstractUIPart;
import org.adempiere.webui.part.ITabOnSelectHandler;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.util.ZKUpdateUtil;
import org.adempiere.webui.window.CustomizeGridViewDialog;
import org.adempiere.webui.window.Dialog;
import org.adempiere.webui.window.FindWindow;
import org.adempiere.webui.window.LabelAction;
import org.adempiere.webui.window.WChat;
import org.adempiere.webui.window.WPostIt;
import org.adempiere.webui.window.WRecordAccessDialog;
import org.compiere.grid.ICreateFrom;
import org.compiere.model.DataStatusEvent;
import org.compiere.model.DataStatusListener;
import org.compiere.model.GridField;
import org.compiere.model.GridTab;
import org.compiere.model.GridTable;
import org.compiere.model.GridWindow;
import org.compiere.model.GridWindowVO;
import org.compiere.model.I_M_Product;
import org.compiere.model.MImage;
import org.compiere.model.MPInstance;
import org.compiere.model.MProcess;
import org.compiere.model.MProjectIssue;
import org.compiere.model.MQuery;
import org.compiere.model.MRecentItem;
import org.compiere.model.MRole;
import org.compiere.model.MSysConfig;
import org.compiere.model.MTable;
import org.compiere.model.MUserPreference;
import org.compiere.model.MWindow;
import org.compiere.model.PO;
import org.compiere.model.StateChangeEvent;
import org.compiere.model.SystemProperties;
import org.compiere.model.X_AD_CtxHelp;
import org.compiere.process.DocAction;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ProcessInfoLog;
import org.compiere.process.ProcessInfoUtil;
import org.compiere.tools.FileUtil;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.zkoss.zk.au.out.AuScript;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.sys.ExecutionCtrl;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Div;
import org.zkoss.zul.Menuitem;
import org.zkoss.zul.Menupopup;
import org.zkoss.zul.Popup;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Window.Mode;
import org.zkoss.zul.impl.LabelImageElement;
/**
*
* Abstract model class for the content of AD Window (toolbar+breadcrumb+tabs+statusbar).
*
* @author Ashley G Ramdass
* @author Low Heng Sin
* @date Feb 25, 2007
* @version $Revision: 0.10 $
*
* @author Cristina Ghita, www.arhipac.ro
* see FR [ 2877111 ] See identifiers columns when delete records https://sourceforge.net/p/adempiere/feature-requests/855/
*
* @author hengsin, hengsin.low@idalica.com
* see FR [2887701] https://sourceforge.net/p/adempiere/feature-requests/866/
* @sponsor www.metas.de
*
* @author Teo Sarca, teo.sarca@gmail.com
*
BF [ 2992540 ] Grid/Panel not refreshed after process run
* https://sourceforge.net/p/adempiere/zk-web-client/418/
* BF [ 2985892 ] Opening a window using a new record query is not working
* https://sourceforge.net/p/adempiere/zk-web-client/411/
*/
public abstract class AbstractADWindowContent extends AbstractUIPart implements ToolbarListener,
EventListener, DataStatusListener, ActionListener, ITabOnSelectHandler
{
/**
* Execution attribute to hold detail ADTabpanel that changes have just been
* saved
*/
private static final String DETAIL_TABPANEL_SAVED_ATTR = "detail.adtabpanel.saved";
/** onFocus event that's defer behind other event using echo */
private static final String ON_FOCUS_DEFER_EVENT = "onFocusDefer";
/**
* Event to set selected tab of detail pane. Defer behind other event using echo.
* Event data: data[0] is tab index and data[1] is current row
*/
private static final String ON_DEFER_SET_DETAILPANE_SELECTION_EVENT = "onDeferSetDetailpaneSelection";
/** ProcessModalDialog attribute to store reference for post process execution callback */
private static final String PROCESS_POST_CALLBACK_ATTRIBUTE = "processPostCallbackAttribute";
private static final CLogger logger;
static
{
logger = CLogger.getCLogger(AbstractADWindowContent.class);
}
/** Env ctx */
private Properties ctx;
/** VO for AD Window */
private GridWindow gridWindow;
/** status bar for message and record info */
protected StatusBar statusBar;
/** UI part for AD_Tabs */
protected IADTabbox adTabbox;
/** register window (desktop tab) no */
private int curWindowNo;
/**
* True to show only unprocessed or the one updated within x days (default is 1 day before today)
*/
private boolean m_onlyCurrentRows = true;
protected ADWindowToolbar toolbar;
/** window title **/
protected String title;
/**
* if > 0, filter records with created >= current_date - m_onlyCurrentDays
*/
private int m_onlyCurrentDays = 0;
/** true if find window cancel by user */
private boolean m_findCancelled;
/** true if user press new button at find window */
private boolean m_findCreateNew;
/** true when initial query for first tab is running */
private boolean m_queryInitiating;
/** path to selected tab */
protected BreadCrumb breadCrumb;
/** AD_Window.AD_Window_ID */
private int adWindowId;
/** image for window title */
private MImage image;
/** delete confirmation logic for selected tab */
private String deleteConfirmationLogic;
/**
* Quick Form Status bar
*/
protected StatusBar statusBarQF;
/**
* Maintain no of quick form tabs open
*/
protected ArrayList quickFormOpenTabs = new ArrayList ();
/** track last focus field editor component */
protected Component lastFocusEditor = null;
/**
* Constructor
* @param ctx
* @param windowNo
* @param adWindowId
*/
public AbstractADWindowContent(Properties ctx, int windowNo, int adWindowId)
{
this.ctx = ctx;
this.curWindowNo = windowNo;
this.adWindowId = adWindowId;
initComponents();
}
/**
* Create {@link IADTabbox} and setup listeners.
* Call from {@link ADWindow}, don't call this directly.
* @param parent
* @return Component
*/
public Component createPart(Object parent)
{
adTabbox = createADTab();
adTabbox.setSelectionEventListener(this);
adTabbox.setADWindowPanel(this);
Component comp = super.createPart(parent);
comp.addEventListener(LayoutUtils.ON_REDRAW_EVENT, this);
comp.addEventListener(ON_DEFER_SET_DETAILPANE_SELECTION_EVENT, this);
comp.addEventListener(ON_FOCUS_DEFER_EVENT, this);
comp.setAttribute(ITabOnSelectHandler.ATTRIBUTE_KEY, this);
return comp;
}
/**
*
* @return {@link BreadCrumb}
*/
public BreadCrumb getBreadCrumb()
{
return breadCrumb;
}
/**
* @return {@link StatusBar}
*/
public StatusBar getStatusBar()
{
return statusBar;
}
/**
* Create {@link #toolbar}, {@link #statusBar} and {@link #gridWindow}.
*/
private void initComponents()
{
/** Initialise toolbar */
toolbar = new ADWindowToolbar(this, getWindowNo());
toolbar.setId("windowToolbar");
toolbar.addListener(this);
statusBar = new StatusBar();
GridWindowVO gWindowVO = AEnv.getMWindowVO(curWindowNo, adWindowId, 0);
if (gWindowVO == null)
{
throw new ApplicationException(Msg.getMsg(ctx,
"AccessTableNoView")
+ "(No Window Model Info)");
}
gridWindow = new GridWindow(gWindowVO, true);
title = gridWindow.getName();
image = gridWindow.getMImage();
}
/**
* Override to create {@link IADTabbox} instance.
* @return {@link IADTabbox}
*/
protected abstract IADTabbox createADTab();
/**
* Handle switching of editing status.
* Override to set isEditting to true/false at widget side.
* @param editStatus true if editing (dirty)
*/
protected abstract void switchEditStatus(boolean editStatus);
/**
* set focus to active tab panel
*/
public void focusToActivePanel() {
IADTabpanel adTabPanel = adTabbox.getSelectedTabpanel();
focusToTabpanel(adTabPanel);
}
/**
* Echo ON_FOCUS_DEFER_EVENT event for {@link ADTabpanel}
* @param adTabPanel
*/
private void focusToTabpanel(IADTabpanel adTabPanel ) {
if (adTabPanel != null && adTabPanel instanceof HtmlBasedComponent) {
Events.echoEvent(ON_FOCUS_DEFER_EVENT, getComponent(), (HtmlBasedComponent)adTabPanel);
}
}
/**
* Init all tab panels.
* Delegate the init of each individual tab panel to {@link #initTab(MQuery, int)}.
* @param query
* @return boolean
*/
public boolean initPanel(MQuery query)
{
// This temporary validation code is added to check the reported bug
// [ adempiere-ZK Web Client-2832968 ] User context lost?
// https://sourceforge.net/p/adempiere/zk-web-client/303/
// it's harmless, if there is no bug then this must never fail
Session currSess = Executions.getCurrent().getDesktop().getSession();
int checkad_user_id = -1;
if (currSess != null && currSess.getAttribute(AdempiereWebUI.CHECK_AD_USER_ID_ATTR) != null)
checkad_user_id = (Integer)currSess.getAttribute(AdempiereWebUI.CHECK_AD_USER_ID_ATTR);
if (checkad_user_id!=Env.getAD_User_ID(ctx))
{
String msg = "Timestamp=" + new Date()
+ ", Bug 2832968 SessionUser="
+ checkad_user_id
+ ", ContextUser="
+ Env.getAD_User_ID(ctx)
+ ". Please report conditions to your system administrator or in sf tracker 2832968";
ApplicationException ex = new ApplicationException(msg);
logger.log(Level.SEVERE, msg, ex);
throw ex;
}
// End of temporary code for [ adempiere-ZK Web Client-2832968 ] User context lost?
// Set AutoCommit for this Window
Env.setAutoCommit(ctx, curWindowNo, Env.isAutoCommit(ctx));
boolean autoNew = Env.isAutoNew(ctx);
Env.setAutoNew(ctx, curWindowNo, autoNew);
// WindowName variable preserved for backward compatibility
// please consider it as DEPRECATED and use _WinInfo_WindowName instead
Env.setContext(ctx, curWindowNo, "WindowName", gridWindow.getName()); // deprecated
Env.setContext(ctx, curWindowNo, "_WinInfo_WindowName", gridWindow.getName());
Env.setContext(ctx, curWindowNo, "_WinInfo_AD_Window_ID", gridWindow.getAD_Window_ID());
Env.setContext(ctx, curWindowNo, "_WinInfo_AD_Window_UU", gridWindow.getAD_Window_UU());
// Set SO/AutoNew for Window
Env.setContext(ctx, curWindowNo, "IsSOTrx", gridWindow.isSOTrx());
if (!autoNew && gridWindow.isTransaction())
{
Env.setAutoNew(ctx, curWindowNo, true);
}
m_onlyCurrentRows = gridWindow.isTransaction();
MQuery detailQuery = null;
//check is query a query for detail tab
if (query != null && query.getZoomTableName() != null && query.getZoomColumnName() != null)
{
if (!query.getZoomTableName().equalsIgnoreCase(gridWindow.getTab(0).getTableName()))
{
detailQuery = query;
query = new MQuery();
query.addRestriction("1=2");
query.setRecordCount(0);
}
}
int tabSize = gridWindow.getTabCount();
GridTab gridTab = null;
for (int tab = 0; tab < tabSize; tab++)
{
gridTab = initTab(query, tab);
if (tab == 0 && gridTab == null && m_findCancelled)
return false;
}
if (gridTab != null)
gridTab.getTableModel().setChanged(false);
adTabbox.setSelectedIndex(0);
// set again IsSOTrx for window if context for window is clear at AbstractADTab.prepareContext,
if (Env.getContext(ctx, curWindowNo, "IsSOTrx", true) == null)
Env.setContext(ctx, curWindowNo, "IsSOTrx", gridWindow.isSOTrx());
toolbar.enableTabNavigation(adTabbox.getTabCount() > 1);
toolbar.enableFind(true);
adTabbox.evaluate(null);
toolbar.updateToolbarAccess();
updateToolbar();
if (query == null && toolbar.initDefaultQuery()) {
doOnQueryChange();
}
if (detailQuery != null && zoomToDetailTab(detailQuery))
{
return true;
}
SessionManager.getAppDesktop().updateHelpContext(X_AD_CtxHelp.CTXTYPE_Tab, adTabbox.getSelectedGridTab().getAD_Tab_ID());
return true;
}
/**
* Zoom to detail tab.
* Delegate to {@link #doZoomToDetail(GridTab, MQuery, int)}.
* @param query detail tab query
* @return true if zoom is ok
*/
private boolean zoomToDetailTab(MQuery query) {
//zoom to detail
if (query != null && query.getZoomTableName() != null && query.getZoomColumnName() != null)
{
GridTab gTab = gridWindow.getTab(0);
if (!query.getZoomTableName().equalsIgnoreCase(gTab.getTableName()))
{
int tabSize = gridWindow.getTabCount();
for (int tab = 0; tab < tabSize; tab++)
{
gTab = gridWindow.getTab(tab);
if (gTab.isSortTab())
continue;
if (gTab.getTableName().equalsIgnoreCase(query.getZoomTableName()))
{
if (doZoomToDetail(gTab, query, tab)) {
return true;
}
}
}
}
}
return false;
}
/**
* Execute zoom to detail tab.
* @param gTab GridTab of tab at tabIndex
* @param query detail tab query
* @param tabIndex
* @return true if successfully zoom to detail tab
*/
private boolean doZoomToDetail(GridTab gTab, MQuery query, int tabIndex) {
GridField[] fields = gTab.getFields();
for (GridField field : fields)
{
if (field.getColumnName().equalsIgnoreCase(query.getZoomColumnName()))
{
gridWindow.initTab(tabIndex);
//init parent tab by parent ids
StringBuilder sql = new StringBuilder("SELECT ").append(gTab.getLinkColumnName()).append(" FROM ").append(gTab.getTableName()).append(" WHERE ").append(query.getWhereClause());
List> parentIds = DB.getSQLArrayObjectsEx(null, sql.toString());
if (parentIds!=null && parentIds.size() > 0)
{
GridTab parentTab = null;
MapqueryMap = new TreeMap();
for (List