From 211d2f67b0b095057dc2247c6329403d48cc4b91 Mon Sep 17 00:00:00 2001 From: Dirk Niemeyer Date: Wed, 4 Apr 2012 11:29:44 -0500 Subject: [PATCH] IDEMPIERE-177 Complete Window Customization functionality --- .../model/CalloutWindowCustomization.java | 137 +++++++++++++++ .../src/org/compiere/model/GridFieldVO.java | 30 ++++ .../src/org/compiere/model/GridTabVO.java | 17 ++ .../src/org/compiere/model/GridWindowVO.java | 14 ++ .../src/org/compiere/model/MTree.java | 15 +- .../src/org/compiere/model/MUserDefField.java | 113 +++++++++++++ .../src/org/compiere/model/MUserDefTab.java | 115 +++++++++++++ .../src/org/compiere/model/MUserDefWin.java | 160 ++++++++++++++++++ 8 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 org.adempiere.base.callout/src/org/compiere/model/CalloutWindowCustomization.java create mode 100644 org.adempiere.base/src/org/compiere/model/MUserDefField.java create mode 100644 org.adempiere.base/src/org/compiere/model/MUserDefTab.java create mode 100644 org.adempiere.base/src/org/compiere/model/MUserDefWin.java diff --git a/org.adempiere.base.callout/src/org/compiere/model/CalloutWindowCustomization.java b/org.adempiere.base.callout/src/org/compiere/model/CalloutWindowCustomization.java new file mode 100644 index 0000000000..68c0eece09 --- /dev/null +++ b/org.adempiere.base.callout/src/org/compiere/model/CalloutWindowCustomization.java @@ -0,0 +1,137 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Dirk Niemeyer * + * Copyright (C) 2012 action 42 GmbH * + * 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. * + *****************************************************************************/ +package org.compiere.model; + +import java.util.Properties; + +import org.adempiere.model.GridTabWrapper; +import org.compiere.util.Env; + + +/** + * Window Customization Callout + * + * @author Dirk Niemeyer, action42 GmbH + */ +public class CalloutWindowCustomization extends CalloutEngine +{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Set fields to current values from DB for selected window. + * @param ctx context + * @param WindowNo window no + * @param mTab tab + * @param mField field + * @param value value + * @return null or error message + */ + public String window (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value) + { + Integer AD_Window_ID = (Integer)value; + if (AD_Window_ID == null || AD_Window_ID.intValue() == 0) + return ""; + + I_AD_UserDef_Win ud_win = GridTabWrapper.create(mTab, I_AD_UserDef_Win.class); + + MWindow window = new MWindow(Env.getCtx(),AD_Window_ID, null); + String lang = (String)mTab.getValue("AD_Language"); + + ud_win.setName(window.get_Translation("Name", lang)); + ud_win.setDescription(window.get_Translation("Description", lang)); + ud_win.setHelp(window.get_Translation("Help", lang)); + + // XXX what for? + ud_win.setIsDefault(window.isDefault()); + + // default from menu, actual from role + // XXX Read Only + // XXX User updateable + + return NO_ERROR; + } // storeAttachmentOnFilesystem + + /** + * Set fields to current values from DB for selected Tab + * @param ctx context + * @param WindowNo window no + * @param mTab tab + * @param mField field + * @param value value + * @return null or error message + */ + public String tab (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value) + { + Integer p_AD_Tab_ID = (Integer)value; + if (p_AD_Tab_ID == null || p_AD_Tab_ID.intValue() == 0) + return ""; + + I_AD_UserDef_Tab ud_tab = GridTabWrapper.create(mTab, I_AD_UserDef_Tab.class); + + MTab tab = new MTab(Env.getCtx(),p_AD_Tab_ID, null); + String lang = Env.getContext(ctx, WindowNo, "AD_Language"); + + ud_tab.setName(tab.get_Translation("Name", lang)); + ud_tab.setDescription(tab.get_Translation("Description", lang)); + ud_tab.setHelp(tab.get_Translation("Help", lang)); + + ud_tab.setIsSingleRow(tab.isSingleRow()); + ud_tab.setIsReadOnly(tab.isReadOnly()); + + return NO_ERROR; + } // storeArchiveOnFileSystem + + /** + * Set fields to current values from DB for selected Tab + * @param ctx context + * @param WindowNo window no + * @param mTab tab + * @param mField field + * @param value value + * @return null or error message + */ + public String field (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object value) + { + Integer p_AD_Field_ID = (Integer)value; + if (p_AD_Field_ID == null || p_AD_Field_ID.intValue() == 0) + return ""; + + I_AD_UserDef_Field ud_field = GridTabWrapper.create(mTab, I_AD_UserDef_Field.class); + + MField field = new MField(Env.getCtx(),p_AD_Field_ID, null); + String lang = Env.getContext(ctx, WindowNo, "AD_Language"); + + ud_field.setName(field.get_Translation("Name", lang)); + ud_field.setDescription(field.get_Translation("Description", lang)); + ud_field.setHelp(field.get_Translation("Help", lang)); + + ud_field.setIsDisplayed(field.isDisplayed()); + ud_field.setDisplayLength(field.getDisplayLength()); + ud_field.setDisplayLogic(field.getDisplayLogic()); + ud_field.setIsReadOnly(field.isReadOnly()); + // XXX from column? set to true for starters + ud_field.setIsUpdateable(true); + ud_field.setSeqNo(field.getSeqNo()); + ud_field.setIsSameLine(field.isSameLine()); + ud_field.setSortNo(field.getSortNo().intValue()); + + return NO_ERROR; + } // storeArchiveOnFileSystem + +} // CalloutClient diff --git a/org.adempiere.base/src/org/compiere/model/GridFieldVO.java b/org.adempiere.base/src/org/compiere/model/GridFieldVO.java index 446291c4a3..5eb1868686 100644 --- a/org.adempiere.base/src/org/compiere/model/GridFieldVO.java +++ b/org.adempiere.base/src/org/compiere/model/GridFieldVO.java @@ -203,6 +203,36 @@ public class GridFieldVO implements Serializable if (! client.isDisplayField(AD_Field_ID)) vo.IsDisplayed = false; } + // FR IDEMPIERE-177 + // Field Customization + if (vo.IsDisplayed) { + MUserDefField userDef = null; + userDef = MUserDefField.get(vo.ctx,AD_Field_ID, AD_Tab_ID, AD_Window_ID); + if (userDef != null) + { + vo.IsDisplayed = userDef.isDisplayed(); + if (userDef.getName() != null) + vo.Header = userDef.getName(); + if (userDef.getDescription() != null) + vo.Description = userDef.getDescription(); + if (userDef.getHelp() != null) + vo.Help = userDef.getHelp(); + vo.IsReadOnly = userDef.isReadOnly(); + vo.IsSameLine = userDef.isSameLine(); + vo.IsUpdateable = userDef.isUpdateable(); + if (userDef.getDisplayLength() > 0) + vo.DisplayLength = userDef.getDisplayLength(); + if (userDef.getDisplayLogic() != null) + vo.DisplayLogic = userDef.getDisplayLogic(); + if (userDef.getDefaultValue() != null) + vo.DefaultValue = userDef.getDefaultValue(); + if (userDef.getSortNo() > 0) + vo.SortNo = userDef.getSortNo(); + // ToDo SeqNo + //if (userDef.getSeqNo() > 0) + // vo.SeqNo = userDef.getSeqNo(); + } + } // vo.initFinish(); return vo; diff --git a/org.adempiere.base/src/org/compiere/model/GridTabVO.java b/org.adempiere.base/src/org/compiere/model/GridTabVO.java index 747fe23203..0ba49db8f8 100644 --- a/org.adempiere.base/src/org/compiere/model/GridTabVO.java +++ b/org.adempiere.base/src/org/compiere/model/GridTabVO.java @@ -101,7 +101,11 @@ public class GridTabVO implements Evaluatee, Serializable { vo.AD_Tab_ID = rs.getInt("AD_Tab_ID"); Env.setContext(vo.ctx, vo.WindowNo, vo.TabNo, GridTab.CTX_AD_Tab_ID, String.valueOf(vo.AD_Tab_ID)); + // FR IDEMPIERE-177 + MUserDefTab userDef = MUserDefTab.get(vo.ctx, vo.AD_Tab_ID, vo.AD_Window_ID); vo.Name = rs.getString("Name"); + if (userDef != null) + vo.Name = userDef.getName(); Env.setContext(vo.ctx, vo.WindowNo, vo.TabNo, GridTab.CTX_Name, vo.Name); // Translation Tab ** @@ -159,7 +163,12 @@ public class GridTabVO implements Evaluatee, Serializable } if (rs.getString("IsReadOnly").equals("Y")) vo.IsReadOnly = true; + if (userDef != null) + vo.IsReadOnly = userDef.isReadOnly(); vo.ReadOnlyLogic = rs.getString("ReadOnlyLogic"); + if (userDef != null) + vo.ReadOnlyLogic = userDef.get_ValueAsString("ReadOnlyLogic"); + if (rs.getString("IsInsertRecord").equals("N")) vo.IsInsertRecord = false; @@ -167,12 +176,20 @@ public class GridTabVO implements Evaluatee, Serializable vo.Description = rs.getString("Description"); if (vo.Description == null) vo.Description = ""; + if (userDef != null && userDef.getDescription() != null) + vo.Description = userDef.getDescription(); + vo.Help = rs.getString("Help"); if (vo.Help == null) vo.Help = ""; + if (userDef != null && userDef.getHelp() != null) + vo.Help = userDef.getHelp(); if (rs.getString("IsSingleRow").equals("Y")) vo.IsSingleRow = true; + if (userDef != null) + vo.IsSingleRow = userDef.isSingleRow(); + if (rs.getString("HasTree").equals("Y")) vo.HasTree = true; diff --git a/org.adempiere.base/src/org/compiere/model/GridWindowVO.java b/org.adempiere.base/src/org/compiere/model/GridWindowVO.java index a20ca7ab17..ccd6b2b0b1 100644 --- a/org.adempiere.base/src/org/compiere/model/GridWindowVO.java +++ b/org.adempiere.base/src/org/compiere/model/GridWindowVO.java @@ -183,6 +183,20 @@ public class GridWindowVO implements Serializable return null; } + // FR IDEMPIERE-177 + // User Customization + MUserDefWin userDef = MUserDefWin.getBestMatch(ctx, AD_Window_ID); + if (userDef != null) + { + if (userDef.getName() != null) + vo.Name = userDef.getName(); + if (userDef.getDescription() != null) + vo.Description = userDef.getDescription(); + if (userDef.getHelp() != null) + vo.Help = userDef.getHelp(); + // ToDo userDef.isDefault, userDef.isReadOnly, userDef.isUserUpdateable + } + // Create Tabs createTabs (vo); if (vo.Tabs == null || vo.Tabs.size() == 0) diff --git a/org.adempiere.base/src/org/compiere/model/MTree.java b/org.adempiere.base/src/org/compiere/model/MTree.java index 08c351e21d..8ca2f8aef0 100644 --- a/org.adempiere.base/src/org/compiere/model/MTree.java +++ b/org.adempiere.base/src/org/compiere/model/MTree.java @@ -509,7 +509,20 @@ public class MTree extends MTree_Base MRole role = MRole.getDefault(getCtx(), false); Boolean access = null; if (X_AD_Menu.ACTION_Window.equals(actionColor)) - access = role.getWindowAccess(AD_Window_ID); + { + access = role.getWindowAccess(AD_Window_ID); + // FR XXX + // Get Window Customization + MUserDefWin userDef = null; + userDef = MUserDefWin.getBestMatch(getCtx(), AD_Window_ID); + if (userDef != null) + { + if (userDef.getName() != null) + name = userDef.getName(); + if (userDef.getDescription() != null) + description = userDef.getDescription(); + } + } else if (X_AD_Menu.ACTION_Process.equals(actionColor) || X_AD_Menu.ACTION_Report.equals(actionColor)) access = role.getProcessAccess(AD_Process_ID); diff --git a/org.adempiere.base/src/org/compiere/model/MUserDefField.java b/org.adempiere.base/src/org/compiere/model/MUserDefField.java new file mode 100644 index 0000000000..58dd863bcc --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/MUserDefField.java @@ -0,0 +1,113 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Dirk Niemeyer * + * Copyright (C) 2012 action 42 GmbH * + * 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. * + *****************************************************************************/ +package org.compiere.model; + +import java.sql.*; +import java.util.*; +import java.util.logging.Level; + +import org.compiere.util.CLogger; +import org.compiere.util.DB; + + +/** + * + * @author Dirk Niemeyer, action42 GmbH + * @version $Id$ + */ +public class MUserDefField extends X_AD_UserDef_Field +{ + /** + * + */ + private static final long serialVersionUID = 1L; + + + /** + * Standard constructor. + * You must implement this constructor for Adempiere Persistency + * @param ctx context + * @param ID the primary key ID + * @param trxName transaction + */ + public MUserDefField (Properties ctx, int ID, String trxName) + { + super (ctx, ID, trxName); + } // MyModelExample + + /** + * Optional Load Constructor. + * You would use this constructor to load several business objects. + * + * SELECT * FROM MyModelExample WHERE ... + * + * @param ctx context + * @param rs result set + * @param trxName transaction + */ + public MUserDefField (Properties ctx, ResultSet rs, String trxName) + { + super (ctx, rs, trxName); + } // MyModelExample + + + public static MUserDefField get (Properties ctx, int AD_Field_ID, int AD_Tab_ID, int AD_Window_ID ) + { + + MUserDefWin userdefWin = MUserDefWin.getBestMatch(ctx, AD_Window_ID); + if (userdefWin == null) + return null; + MUserDefTab userdefTab = MUserDefTab.getMatch(ctx, AD_Tab_ID, userdefWin.getAD_UserDef_Win_ID()); + if (userdefTab == null) + return null; + + MUserDefField retValue = null; + + StringBuffer sql = new StringBuffer("SELECT * " + + " FROM AD_UserDef_Field f " + + " WHERE f.AD_Field_ID=? AND f.IsActive='Y' " + + " AND f.AD_UserDef_Tab_ID=? "); + + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + // create statement + pstmt = DB.prepareStatement(sql.toString(), null); + pstmt.setInt(1, AD_Field_ID); + pstmt.setInt(2, userdefTab.getAD_UserDef_Tab_ID()); + // get data + rs = pstmt.executeQuery(); + if (rs.next()) + { + retValue = new MUserDefField(ctx,rs,null); + } + } + catch (SQLException ex) + { + CLogger.get().log(Level.SEVERE, sql.toString(), ex); + return null; + } + finally + { + DB.close(rs, pstmt); + rs = null; + pstmt = null; + } + + return retValue; + } + +} // MyModelExample diff --git a/org.adempiere.base/src/org/compiere/model/MUserDefTab.java b/org.adempiere.base/src/org/compiere/model/MUserDefTab.java new file mode 100644 index 0000000000..b9ad53bd19 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/MUserDefTab.java @@ -0,0 +1,115 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Dirk Niemeyer * + * Copyright (C) 2012 action 42 GmbH * + * 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. * + *****************************************************************************/ +package org.compiere.model; + +import java.sql.*; +import java.util.*; +import java.util.logging.Level; + +import org.compiere.util.CLogger; +import org.compiere.util.DB; + + +/** + * + * @author Dirk Niemeyer, action 42 GmbH + * @version $Id$ + */ +public class MUserDefTab extends X_AD_UserDef_Tab +{ + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Standard constructor. + * You must implement this constructor for Adempiere Persistency + * @param ctx context + * @param ID the primary key ID + * @param trxName transaction + */ + public MUserDefTab (Properties ctx, int ID, String trxName) + { + super (ctx, ID, trxName); + } // MyModelExample + + /** + * Optional Load Constructor. + * You would use this constructor to load several business objects. + * + * SELECT * FROM MyModelExample WHERE ... + * + * @param ctx context + * @param rs result set + * @param trxName transaction + */ + public MUserDefTab (Properties ctx, ResultSet rs, String trxName) + { + super (ctx, rs, trxName); + } // MyModelExample + + + public static MUserDefTab getMatch (Properties ctx, int AD_Tab_ID, int AD_UserDefWin_ID ) + { + + MUserDefTab retValue = null; + + StringBuffer sql = new StringBuffer("SELECT * " + + " FROM AD_UserDef_Tab " + + " WHERE AD_UserDef_Win_ID=? AND IsActive='Y' " + + " AND AD_Tab_ID=? "); + + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + // create statement + pstmt = DB.prepareStatement(sql.toString(), null); + pstmt.setInt(1, AD_UserDefWin_ID); + pstmt.setInt(2, AD_Tab_ID); + // get data + rs = pstmt.executeQuery(); + if (rs.next()) + { + retValue = new MUserDefTab(ctx,rs,null); + } + } + catch (SQLException ex) + { + CLogger.get().log(Level.SEVERE, sql.toString(), ex); + return null; + } + finally + { + DB.close(rs, pstmt); + rs = null; + pstmt = null; + } + + return retValue; + } + + public static MUserDefTab get (Properties ctx, int AD_Tab_ID, int AD_Window_ID) { + + MUserDefWin userdefWin = MUserDefWin.getBestMatch(ctx, AD_Window_ID); + if (userdefWin == null) + return null; + + return getMatch(ctx, AD_Tab_ID, userdefWin.getAD_UserDef_Win_ID()); + + } + +} // MyModelExample diff --git a/org.adempiere.base/src/org/compiere/model/MUserDefWin.java b/org.adempiere.base/src/org/compiere/model/MUserDefWin.java new file mode 100644 index 0000000000..6a7d93acd5 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/MUserDefWin.java @@ -0,0 +1,160 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Dirk Niemeyer * + * Copyright (C) 2012 action 42 GmbH * + * 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. * + *****************************************************************************/ +package org.compiere.model; + +import java.sql.*; +import java.util.*; +import java.util.logging.Level; + +import org.compiere.util.CLogger; +import org.compiere.util.DB; +import org.compiere.util.Env; + +/** + * + * @author Dirk Niemeyer, action42 GmbH + * @version $Id$ + */ +public class MUserDefWin extends X_AD_UserDef_Win +{ + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Standard constructor. + * You must implement this constructor for Adempiere Persistency + * @param ctx context + * @param ID the primary key ID + * @param trxName transaction + */ + public MUserDefWin (Properties ctx, int ID, String trxName) + { + super (ctx, ID, trxName); + } // MyModelExample + + /** + * Optional Load Constructor. + * You would use this constructor to load several business objects. + * + * SELECT * FROM MyModelExample WHERE ... + * + * @param ctx context + * @param rs result set + * @param trxName transaction + */ + public MUserDefWin (Properties ctx, ResultSet rs, String trxName) + { + super (ctx, rs, trxName); + } // MyModelExample + + + private static MUserDefWin[] getAll (Properties ctx, int window_ID ) + { + + List list = new ArrayList(); + + StringBuffer sql = new StringBuffer("SELECT * " + + " FROM AD_UserDef_Win w " + + " WHERE w.AD_Window_ID=? AND w.IsActive='Y' " + + " AND w.AD_Language=? " + + " AND w.AD_Client_ID=? "); + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + // create statement + pstmt = DB.prepareStatement(sql.toString(), null); + pstmt.setInt(1, window_ID); + pstmt.setString(2, Env.getAD_Language(ctx)); + pstmt.setInt(3, Env.getAD_Client_ID(ctx)); + // get data + rs = pstmt.executeQuery(); + while (rs.next()) + { + MUserDefWin userDef = new MUserDefWin(Env.getCtx(),rs,null); + list.add(userDef); + } + } + catch (SQLException ex) + { + CLogger.get().log(Level.SEVERE, sql.toString(), ex); + return null; + } + finally + { + DB.close(rs, pstmt); + rs = null; + pstmt = null; + } + + if (list.size() == 0) + return null; + + return list.toArray(new MUserDefWin[list.size()]); + + } + + public static MUserDefWin getBestMatch (Properties ctx, int window_ID) + { + // parameters + final int AD_Org_ID = Env.getAD_Org_ID(ctx); + //final int anyOrg = 0; + final int AD_Role_ID = Env.getAD_Role_ID(ctx); + //final String anyRole = "NULL"; + final int AD_User_ID = Env.getAD_User_ID(ctx); + //final String anyUser = "NULL"; + + // candidates + MUserDefWin[] candidates = getAll(ctx, window_ID); + if (candidates == null) + return null; + final int size = candidates.length; + int[] weight = new int[size]; + + // this user + this role + this org => weight = 7 + // this user + this role + any org => weight = 6 + // this user + any role + this org => weight = 5 + // this user + any role + any org => weight = 4 + // any user + this role + this org => weight = 3 + // any user + this role + any org => weight = 2 + // any user + any role + this org => weight = 1 + // any user + any role + any org => weight = 0 + for (int i=0; i < size; i++) + { + weight[i] = 0; + if (candidates[i].getAD_User_ID() == AD_User_ID) + weight[i] = weight[i] + 4; + if (candidates[i].getAD_Role_ID() == AD_Role_ID) + weight[i] = weight[i] + 2; + if (candidates[i].getAD_Org_ID() == AD_Org_ID) + weight[i] = weight[i] + 1; + // others are implicit + } + + int maximum = weight[0]; // start with the first value + int maxindex = 0; + for (int j=0; j maximum) { + maximum = weight[j]; // new maximum + maxindex = j; + } + } + + return candidates[maxindex]; + } + +} // MUserDefWin