/* *@@sourcefile dialog.c: * contains PM helper functions to create and * auto-format dialogs from control arrays in memory. * * As a summary, this code allows you to get rid of * PM dialog resources completely. * * See dlghCreateDlg for an introduction. * * See @dlg_algorithm for the gory details of the new * algorithm used since V1.0.0. Even though much * of this file was rewritten, the new dialog is * backwards-compatible with all the hacks that existing * dialogs might use for aligning things properly. * * In addition, this has dlghMessageBox (a WinMessageBox * replacement) and some helper functions for simulating * dialog behavior in regular window procs (see * dlghSetPrevFocus and others). * * If you are out to find all workarounds to get certain * buggy PM controls aligned right, this file is definitely * the place. * * Usage: All PM programs. * * Function prefixes (new with V0.81): * -- dlg* Dialog functions * * Note: Version numbering in this file relates to XWorkplace version * numbering. * *@@added V0.9.9 (2001-04-01) [umoeller] *@@header "helpers\dialog.h" */ /* * Copyright (C) 2001-2006 Ulrich M”ller. * This file is part of the "XWorkplace helpers" source package. * This 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, in version 2 as it comes in the * "COPYING" file of the XWorkplace main distribution. * 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. */ // #define DEBUG_DIALOG_WINDOWS 1 // blue frame is table // green frame is column // red frame is control #define OS2EMX_PLAIN_CHAR // this is needed for "os2emx.h"; if this is defined, // emx will define PSZ as _signed_ char, otherwise // as unsigned char #define INCL_DOSPROCESS #define INCL_DOSEXCEPTIONS #define INCL_DOSERRORS #define INCL_WINWINDOWMGR #define INCL_WINFRAMEMGR #define INCL_WINPOINTERS #define INCL_WININPUT #define INCL_WINDIALOGS #define INCL_WINSTATICS #define INCL_WINBUTTONS #define INCL_WINENTRYFIELDS #define INCL_WINSTDCNR #define INCL_WINSYS #define INCL_GPIPRIMITIVES #define INCL_GPIBITMAPS #define INCL_GPILCIDS #include #include #include #include #include #include "setup.h" // code generation and debugging options #include "helpers\comctl.h" #include "helpers\dialog.h" #include "helpers\except.h" #include "helpers\gpih.h" #include "helpers\linklist.h" #include "helpers\standards.h" #include "helpers\stringh.h" #include "helpers\textview.h" #include "helpers\winh.h" #include "helpers\xstring.h" #pragma hdrstop /* *@@category: Helpers\PM helpers\Dialog templates */ /* ****************************************************************** * * Glossary entries * ********************************************************************/ /* *@@gloss: dlg_using_macros Using the Dialog Formatter Macros * * The DLGHITEM macros are: * * -- START_TABLE starts a new table. The tables may nest, * but must each be properly terminated with END_TABLE. * * Note that as opposed to HTML tables, the columns * in the rows of the table are NOT aligned under each * other. If that is what you want, use START_TABLE_ALIGN * instead. * * -- START_GROUP_TABLE(pDef) starts a group. This * behaves exacly like START_TABLE, but in addition, * it produces a static group control around the table. * Useful for group boxes. pDef must point to a * CONTROLDEF describing the control to be used for * the group (usually a WC_STATIC with SS_GROUP style), * whose size parameter is ignored. * * As with START_TABLE, START_GROUP_TABLE must be * terminated with END_TABLE. * * As with START_TABLE, columns in the rows of the table * are NOT aligned under each other. If that is what you * want, use START_GROUP_TABLE_ALIGN instead. * * -- START_ROW(fl) starts a new row in a table (regular * or group). This must also be the first item after * the (group) table tag. * * fl specifies formatting flags for the row. This * can be one of ROW_VALIGN_BOTTOM, ROW_VALIGN_CENTER, * ROW_VALIGN_TOP and affects all items in the row. * * -- CONTROL_DEF(pDef) defines a control in a table row. * pDef must point to a CONTROLDEF structure. * * Again, there is is NO information in CONTROLDEF * about a control's _position_. Instead, the structure * only contains the _size_ of the control. All * positions are computed by this function, depending * on the sizes of the controls and their nesting within * the various tables. * * If you specify SZL_AUTOSIZE with either cx or cy * or both, the size of the control is even computed * automatically. Presently, this only works for statics * with SS_TEXT, SS_ICON, and SS_BITMAP, push buttons, * and radio and check boxes. * * Unless separated with START_ROW items, subsequent * control items will be considered to be in the same * row (== positioned next to each other). * * Columns will only be aligned horizontally if the * container table was specified with START_TABLE_ALIGN * or START_GROUP_TABLE_ALIGN. * * There are a few rules, whose violation will produce * an error: * * -- The entire array must be enclosed in a table * (the "root" table). * * -- After START_TABLE or START_GROUP_TABLE, there must * always be a START_ROW first. * * While it is possible to set up the CONTROLDEFs manually * as static structures, I recommend using the bunch of * other macros that were defined in dialog.h for this. * For example, you can use CONTROLDEF_PUSHBUTTON to create * a push button, and many more. * * To create a dialog, set up arrays like the following: * + // control definitions referenced by DlgTemplate: + CONTROLDEF + (1) GroupDef = CONTROLDEF_GROUP("Group", + -1, // ID + SZL_AUTOSIZE, + SZL_AUTOSIZE), + (2) CnrDef = CONTROLDEF_CONTAINER(-1, // ID, + 50, + 50), + (3) Static = CONTROLDEF_TEXT("Static below cnr", + -1, // ID + SZL_AUTOSIZE, + SZL_AUTOSIZE), + (4) OKButton = CONTROLDEF_DEFPUSHBUTTON("~OK", + DID_OK, + SZL_AUTOSIZE, + SZL_AUTOSIZE), + (5) CancelButton = CONTROLDEF_PUSHBUTTON("~Cancel", + DID_CANCEL, + SZL_AUTOSIZE, + SZL_AUTOSIZE); + + DLGHITEM DlgTemplate[] = + { + START_TABLE, // root table, required + START_ROW(0), // row 1 in the root table, required + // create group on top + (1) START_GROUP_TABLE(&Group), + START_ROW(0), + (2) CONTROL_DEF(&CnrDef), + START_ROW(0), + (3) CONTROL_DEF(&Static), + END_TABLE, // end of group + START_ROW(0), // row 2 in the root table + // two buttons next to each other + (4) CONTROL_DEF(&OKButton), + (5) CONTROL_DEF(&CancelButton), + END_TABLE + } * * This will produce a dlg like this: * + ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» + º º + º ÚÄ Group (1) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º + º ³ ³ º + º ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ º + º ³ ³ ³ ³ º + º ³ ³ Cnr inside group (2) ³ ³ º + º ³ ³ ³ ³ º + º ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ º + º ³ ³ º + º ³ Static below cnr (3) ³ º + º ³ ³ º + º ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º + º º + º ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º + º ³ OK (4) ³ ³ Cancel (5) ³ º + º ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º + º º + ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ */ /* *@@gloss: dlg_boxmodel Dialog formatter box model * * We now use the CSS box model for the dialog * formatter (V1.0.0). * + ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ + ³ margin ³ + ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ + ³ ³# border ###########³ ³ + ³ ³##ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿##³ ³ + ³ ³##³ padding ³##³ ³ + ³ ³##³ ÚÄÄÄÄÄÄÄÄÄ¿ ³##³ ³ + ³ ³##³ ³ content ³ ³##³ ³ + ³ ³##³ ÀÄÄÄÄÄÄÄÄÄÙ ³##³ ³ + ³ ³##³ ³##³ ³ + ³ ³##ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ##³ ³ + ³ ³####################³ ³ + ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ + ³ ³ + ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ * * See "http://www.w3.org/TR/REC-CSS1#formatting-model". * * Remark on the box model for subtables: * * We do not want to apply spacing twice for nested tables. This * is especially important for group boxes (i.e. tables which * have a PM group static control created around them). As a * result, the definition is that ONLY COLUMNS HAVE SPACING, * EVER. Neither tables nor rows do. */ /* *@@gloss: dlg_algorithm Dialog formatter algorithm * * See @dlg_boxmodel for information about how the * rectangles are defined. * * The dialog formatter allows you to specify PM dialog layouts * as nested tables, without having to specify a single control * position. In fact, it is impossible to give the dialog * formatter fixed positions. Instead, you give the formatter * the control _sizes_, and everything is layed out automatically. * You may even have the formatter compute the sizes automatically * based on the control classes and values (strings); it is possible * to create dialogs without specifying a single size also. * * There are several tricks to how this works. * * 1) The dialog is supposed to consist of one outer table, * which in turn has rows. The rows in turn have columns. * We process the outer table by iterating over its rows * (see ProcessTable). For each row, we iterate over the * columns (see ProcessRow). Most of the processing then * actually occurs in ProcessColumn. * * 2) Each column can contain a PM control, as specified in a * CONTROLDEF structure on input. Alternatively, a column * can be a nested subtable (with more rows and columns). * If so, ProcessColumn recurses back into ProcessTable * (which again goes into ProcessRow and ProcessColumn). * There is no limit to how deep tables may nest, except * for the stack size of the current thread, of course. * * 3) This whole recursive iteration is performed several times. * In fact, we run through the entire data up to seven times * presently. Only in the last run, we actually create the * controls. Details follow. * * 4) We have sophisticated means for computing the size of * certain controls automatically. See CalcAutoSize. * * The algorithm used here has been greatly reworked with V1.0.0. * Essentially, the new trick is that we first compute all widths * (which requires several iterations since widths may now inherit * from each other), then all heights (which, for many controls, * depend on the widths), then all positions. The previous approach * to calculate widths and heights at the same time has turned out * to not work very well. * * With each of the seven recursive iterations, ProcessTable, * ProcessRow, and ProcessColumn receive a ProcessMode variable, * which is just an integer that is raised with each run. These * are the PROCESS_* constants all over the code. * * In the seven iterations, the following happens: * * 1) PROCESS_1_CALC_MIN_WIDTHS: calculates cxMinContent of all * columns, rows, and tables from the inside to the outside * recursively. cxMinContent specifies the minimum width of * the column, row, and table, respectively. This value is * required for all subsequent computations. * * This works as follows: * * -- From the root table, process all rows. [1] * * -- From each row, process all columns. * * -- For each column, calculate the minimum size of * the content. * * (*) If the column is a control leaf node (i.e. * does not hold a subtable), get the minimum * size of the control. * * (*) If the CONTROLDEF specified an explicit * width, copy it to cxMinContent. * * (*) If the CONTROLDEF specified SZL_AUTOSIZE * for cx, evaluate the control type (icon, * bitmap, text etc.) and set the minimum size * to the result. See CalcAutoSize. * * (*) The control may specify its desired width * as a percentage of the parent table's * width. In that case, set the cxMinContent * to 0 for now because it is to be ignored * in the computation of the parent row and * table widths. * * (*) If the column is a subtable, recurse with * the subtable ([1] above) by calling ProcessTable * again. Then set the control's cxMinContent to * the table's cxMinBox, which was set by the * subtable's ProcessTable (see below). * * After this, we have set the column's cxMinContent * to either a minimum size (explicit or automatic ) * or 0. * * -- Set the row's cxMinBox to the sum of the minimum * widths of the contained columns, with padding, * border, and margin applied to each column. * * -- Set the table's cxMinBox to the cxMinBox of the table's * widest row. * * 2) PROCESS_2_CALC_RELATIVE_WIDTHS: calculates the widths of * those columns that have specified that their size should * be a percentage of the parent table's width, and calculate * the content rectangles of all columns and the box rectangles * of the parent rows and tables. Again, do this recursively * from the inside columns to the outside tables. * * First, we inherit table sizes from parent tables in * ProcessTable. If a column has a subtable that has the * TABLE_INHERIT_SIZE table flag set, compute a new cxMinBox * for that table. _After_ that, recurse into the table to allow * subtables of that subtable to inherit that size as well. * * In the columns, this then sets cpContent.cx in the columns, * with the following processing in the control leaf columns: * * (*) If the control has specified an explicit or automatic * width, we have set cxMinContent in PROCESS_1_CALC_MIN_WIDTHS; * copy that width to cpContent.cx. * * (*) However, if the control had specified its desired * width as a percentage of the parent table's width, * calculate the cpContent.cx for the control from the * table's cxMinBox (from PROCESS_1_CALC_MIN_WIDTHS). * * In the rows and tables, cpBox.cx and cpTable.cx is set as * above from the column cpContent.cx values, with spacing applied. * * 3) PROCESS_3_ALIGN_COLUMNS: if the table has the TABLE_ALIGN_COLUMNS * flag set, for each column in the table, find the widest * instance in all rows, and set the cpContent.cx of all * columns in that row to that width. * * 4) PROCESS_4_REMAINDER_WIDTHS: for all controls that have * SZL_REMAINDER set for their widths, fill those so that * they can use up the remaining width in the row. * * 5) PROCESS_5_CALC_HEIGHTS: Now that we know all widths, * calculate the heights. We set cpContent.cy of all columns, * cpRowBox.cy of all rows, and cpTableBox.cy of the tables, * with the following processing in the control leaf columns: * * (*) If the control has specified an explicit or automatic * height, use or compute that. In the case of autosize * height, we now know the final width of the control, * which is important for computing the height of multi-line * static controls (which depend on the width, obviously). * The new approach finally works for the case where a * multi-line static control specifies its width as a * percentage of the parent table, but still wants an * autosize height. * * 6) PROCESS_6_CALC_POSITIONS: Now that we know all heights and * widths, for each column, calculate cpContent.x and y. * * 7) PROCESS_7_CREATE_CONTROLS: finally creates the controls * at the cpContent.x and y position, with the cpContent.cx * and cy size. */ /* ****************************************************************** * * Private declarations * ********************************************************************/ /* *@@ DLGPRIVATE: * private data to the dlg manager, allocated * by dlghCreateDlg. * * This only exists while the dialog is being * created and is not stored with the new dialog. */ typedef struct _DLGPRIVATE { HWND hwndDlg; // dialog frame // definition data LINKLIST llTables; // linked list of TABLEDEF structs HWND hwndFirstFocus, hwndDefPushbutton; // V0.9.14 (2001-08-21) [umoeller] POINTL ptlTotalOfs; PLINKLIST pllControls; // linked list of HWNDs in the order // in which controls were created; // ptr can be NULL PCSZ pcszControlsFont; // from dlghCreateDlg // size of the client to be created SIZEL szlClient; // various cached data V0.9.14 (2001-08-01) [umoeller] HPS hps; PCSZ pcszFontLast; LONG lcidLast; FONTMETRICS fmLast; LONG cxBorder, cyBorder; // cached now V0.9.19 (2002-04-17) [umoeller] double dFactorX, // correlation factors for dialog units dFactorY; // V0.9.19 (2002-04-24) [umoeller] ULONG flNeedsProcessing; // flags to avoid unnecessary recursions: #define FL_REMAINDER_WIDTHS 0x0001 // dialog needs PROCESS_4_REMAINDER_WIDTHS processing } DLGPRIVATE, *PDLGPRIVATE; // macros for the dlg units conversion; #define FACTOR_X (pDlgData->dFactorX) #ifdef USE_SQUARE_CORRELATION #define FACTOR_Y (pDlgData->dFactorX) #else #define FACTOR_Y (pDlgData->dFactorY) #endif typedef struct _COLUMNDEF *PCOLUMNDEF; typedef struct _ROWDEF *PROWDEF; typedef struct _TABLEDEF *PTABLEDEF; /* *@@ CONTROLPOS: * control position. We don't want to use SWP. * We could have used RECTL, but the xRight * etc. fields are always confusing with what * we're doing... so we just define this. */ typedef struct _CONTROLPOS { LONG x, y, cx, cy; } CONTROLPOS, *PCONTROLPOS; /* *@@ enProcessMode: * enumeration that specifies the current processing * mode while recursing through the tables, rows, * and columns. * * For each processing mode, ProcessAll gets called * while the dialog is being set up, which in turn * calls ProcessTable for all tables, which in turn * calls ProcessRow for all rows in that table, * which in turn calls ProcessColumn for all rows * in that column. ProcessColumn will recurse back * into ProcessTable if a column represents a * subtable. * * See @dlg_algorithm for what processing takes * place with each of the process modes. */ typedef enum _enProcessMode { PROCESS_1_CALC_MIN_WIDTHS, PROCESS_2_CALC_RELATIVE_WIDTHS, PROCESS_3_ALIGN_COLUMNS, PROCESS_4_REMAINDER_WIDTHS, PROCESS_5_CALC_HEIGHTS, PROCESS_6_CALC_POSITIONS, PROCESS_7_CREATE_CONTROLS } enProcessMode; /* *@@ COLUMNDEF: * representation of a column in a table row. * * Each COLUMNDEF is stored in a linked list * in ROWDEF.llColumns. * * Such a column represents either a PM control * window or another table, which may therefore * be nested. * * See @dlg_algorithm for the new algorithm * used since V1.0.0. * * With V1.0.0, we now follow the CSS box model * to render these things better without all * the hacks I am no longer able to understand * myself. See @dlg_box_model for details. * *@@changed V1.0.0 (2002-08-18) [umoeller]: completely replaced */ typedef struct _COLUMNDEF { // 1) input table set up by the DLGHITEM parser (CreateColumn): // a) column position in recursion hierarchy PROWDEF pOwningRow; // row whose linked list this column belongs to PTABLEDEF pNestedTable; // if != NULL, this column represents a nested table, // and this points to the contained table definition // with which we must recurse back into ProcessTable(). // In that case, if (pcszClass != NULL) also, it // must represent the PM static group control to // create around the table. // If (pNestedTable == NULL), this column is a control // leaf node, and pcszClass and the following represent // the control to create for the column. ULONG ulColumnIndex; // zero-based column index in parent row // V1.0.0 (2002-08-18) [umoeller] // b) information for control window to be created: PCSZ pcszClass; // class of control to create; if NULL, create no // control; NULL is valid only for subtable columns PCSZ pcszText; // window text; if NULL, it is replaced with "" in // ColumnCreateControl ULONG flStyle; // window style (should have at least WS_VISIBLE) USHORT usID; // window ID or -1 for "don't care" PCSZ pcszFont; // font to use for this window; this has CTL_COMMON_FONT // resolved to the dialog default font already SIZEL szlProposed; // proposed size for the control; copied from the // CONTROLDEF with the dialog units already converted // to pixels. Can be // -- SZL_AUTOSIZE (-1) if the control wants to be autosized, // -- SZL_REMAINDER (0) if the control wants the remaining // space in its column, // -- a negative number from [-100, -1] to specify the width // as a percentage of the parent table _box_. PVOID pvCtlData; // control-specific data for WinCreateWindow // c) spacing: RECTL rclPadding, // spacing, in pixels, to apply to the four borders of rclMargin; // the content to get the box; calculated from CONTROLDEF // V1.0.0 (2002-08-16) [umoeller] LONG cxSpacingTotal; // sum of rclPadding and rclMargin xLeft and // xRight, respectively // 2) data from CalcAutoSize: SIZEL szlAuto; // 3) result data after recursion: LONG cxMinContent; // minimum width of the content box; // computed in PROCESS_1_CALC_MIN_WIDTHS CONTROLPOS cpContent; // cx computed in PROCESS_2_CALC_RELATIVE_WIDTHS, // cy computed in PROCESS_5_CALC_HEIGHTS, // x and y computed in PROCESS_6_CALC_POSITIONS; // this is where the control will be created CONTROLPOS cpBox; // cx and cy computed in PROCESS_5_CALC_HEIGHTS, // x and y computed in PROCESS_6_CALC_POSITIONS HWND hwndControl; // created control after PROCESS_7_CREATE_CONTROLS; // for tables, this is only set if we have created // a PM group around them. Note: For text box // controls, we create the control earlier, // in CalcAutoSizeTextView during PROCESS_5_CALC_HEIGHTS, // because only then we can ask the control for // its required height. } COLUMNDEF; /* *@@ ROWDEF: * representation of a row in a table. * * Each ROWDEF is stored in a linked list in * TABLEDEF.llRows and in turn contains a * linked list of the contained COLUMNDEF structs * in its llColumns member. */ typedef struct _ROWDEF { // input table set up by the DLGHITEM parser: PTABLEDEF pOwningTable; // table whose linked list this row belongs to LINKLIST llColumns; // contains COLUMNDEF structs, no auto-free ULONG flRowFormat; // one of: // -- ROW_VALIGN_BOTTOM 0x0000 // -- ROW_VALIGN_CENTER 0x0001 // -- ROW_VALIGN_TOP 0x0002 ULONG cColumns; // no. of columns in this row // V1.0.0 (2002-08-18) [umoeller] // result data after recursion: LONG cxMinBox; // set by PROCESS_1_CALC_MIN_WIDTHS to the sum of // the minimum widths of the contained columns, // with padding, border, and margin applied to each // column CONTROLPOS cpRowBox; // cx computed in PROCESS_2_CALC_RELATIVE_WIDTHS, // cy computed in PROCESS_5_CALC_HEIGHTS, // x and y computed in PROCESS_6_CALC_POSITIONS ULONG cWantRemainders; // count of controls in the row that have // SZL_REMAINDER set; counted in ProcessColumn // with PROCESS_2_CALC_RELATIVE_WIDTHS // so that we can quickly process // PROCESS_4_REMAINDER_WIDTHS LONG cxNonRemainder; // sum of the widths of those controls that do // not have SZL_REMAINDER set; counted in // ProcessColumn with PROCESS_3_ALIGN_COLUMNS // so that we can quickly process // PROCESS_4_REMAINDER_WIDTHS } ROWDEF; /* *@@ TABLEDEF: * representation of a table. If pOwningColumn * is NULL, this is the dialog's root table. * Otherwise this is a nested subtable in the * specified column. * * Each TABLEDEF has its contained rows * in the llRows member. */ typedef struct _TABLEDEF { // input table set up by the DLGHITEM parser: PCOLUMNDEF pOwningColumn; // != NULL if this is a nested table LINKLIST llRows; // contains ROWDEF structs, no auto-free ULONG flTable; // TABLE_* flags, copied from DLGHITEM // result data after recursion: LONG cxMinBox; // set by PROCESS_1_CALC_MIN_WIDTHS to the cxMinBox of // the widest row PROWDEF pWidestRow; // used during PROCESS_2_CALC_RELATIVE_WIDTHS to // calculate percentage widths CONTROLPOS cpTableBox; // cx computed in PROCESS_2_CALC_RELATIVE_WIDTHS, // cy computed in PROCESS_5_CALC_HEIGHTS, // x and y computed in PROCESS_6_CALC_POSITIONS } TABLEDEF; /* ****************************************************************** * * Debug frames * ********************************************************************/ #ifdef DEBUG_DIALOG_WINDOWS STATIC PFNWP G_pfnwpDebugStaticOrig = NULL; /* *@@ fnwpDebugFrame: * window proc for the subclassed static that is used in * debug mode to mark rectangles in the dialog. This * was added to support painting dotted rectangles. The * line type is expected to sit in QWL_USER. We use * PP_FOREGROUNDCOLOR as the paint color for the rectangle. * * This is only compiled if DEBUG_DIALOG_WINDOWS is * set. * *@@added V1.0.0 (2002-08-16) [umoeller] */ STATIC MRESULT EXPENTRY fnwpDebugFrame(HWND hwndBox, ULONG msg, MPARAM mp1, MPARAM mp2) { MRESULT mrc = 0; switch (msg) { case WM_PAINT: { LONG lcol = winhQueryPresColor2(hwndBox, PP_FOREGROUNDCOLOR, PP_FOREGROUNDCOLORINDEX, FALSE, -1); LONG lLineType = WinQueryWindowULong(hwndBox, QWL_USER); HPS hps; if (hps = WinBeginPaint(hwndBox, NULLHANDLE, NULL)) { RECTL rcl; POINTL ptl; WinQueryWindowRect(hwndBox, &rcl); gpihSwitchToRGB(hps); GpiSetColor(hps, lcol); GpiSetLineType(hps, lLineType); ptl.x = 0; ptl.y = 0; GpiMove(hps, &ptl); ptl.x = rcl.xRight - 1; ptl.y = rcl.yTop - 1; GpiBox(hps, DRO_OUTLINE, &ptl, 0, 0); WinEndPaint(hps); } } break; default: mrc = G_pfnwpDebugStaticOrig(hwndBox, msg, mp1, mp2); } return mrc; } /* *@@ CreateDebugFrame: * in debug mode, creates a subclassed static that * shows the rectangles that were produced in the * dialog. See fnwpDebugFrame. * * This is only compiled if DEBUG_DIALOG_WINDOWS is * set. * *@@added V1.0.0 (2002-08-16) [umoeller] */ HWND CreateDebugFrame(HWND hwndParent, LONG x, LONG y, LONG cx, LONG cy, LONG lcol, LONG lLineType) { HWND hwndDebug; if (hwndDebug = WinCreateWindow(hwndParent, WC_STATIC, "", WS_VISIBLE, x, y, cx, cy, hwndParent, // owner HWND_BOTTOM, -1, NULL, NULL)) { G_pfnwpDebugStaticOrig = WinSubclassWindow(hwndDebug, fnwpDebugFrame); winhSetPresColor(hwndDebug, PP_FOREGROUNDCOLOR, lcol); WinSetWindowULong(hwndDebug, QWL_USER, lLineType); } return hwndDebug; } #endif // DEBUG_DIALOG_WINDOWS /* ****************************************************************** * * Column processing * ********************************************************************/ // forward declaration so we can recurse into tables from columns STATIC APIRET ProcessTable(PTABLEDEF pTableDef, const CONTROLPOS *pcpTableBox, enProcessMode ProcessMode, PDLGPRIVATE pDlgData); /* *@@ SetDlgFont: * refreshes the cached font data in DLGPRIVATE * for the given font. Used all the time with * CalcAutoSize. * * In DLGPRIVATE, this modifies hps, lcidLast, * and pcszFontLast. After this, the FONTMETRICS * in fmLast are set. * *@@added V0.9.16 (2001-10-11) [umoeller] */ STATIC VOID SetDlgFont(PCSZ pcszFontThis, PDLGPRIVATE pDlgData) { LONG lPointSize = 0; if (!pDlgData->hps) pDlgData->hps = WinGetPS(pDlgData->hwndDlg); // check if we can reuse font data from last time // V0.9.14 (2001-08-01) [umoeller] if (strhcmp(pcszFontThis, // can be NULL! pDlgData->pcszFontLast)) { // different font than last time: // delete old font? if (pDlgData->lcidLast) { GpiSetCharSet(pDlgData->hps, LCID_DEFAULT); // LCID_DEFAULT == 0 GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast); } if (pcszFontThis) { // create new font pDlgData->lcidLast = gpihFindPresFont(NULLHANDLE, // no window yet FALSE, pDlgData->hps, pcszFontThis, &pDlgData->fmLast, &lPointSize); GpiSetCharSet(pDlgData->hps, pDlgData->lcidLast); if (pDlgData->fmLast.fsDefn & FM_DEFN_OUTLINE) gpihSetPointSize(pDlgData->hps, lPointSize); } else { // use default font: // @@todo handle presparams, maybe inherited? GpiSetCharSet(pDlgData->hps, LCID_DEFAULT); GpiQueryFontMetrics(pDlgData->hps, sizeof(pDlgData->fmLast), &pDlgData->fmLast); } pDlgData->pcszFontLast = pcszFontThis; // can be NULL } } /* *@@ CalcAutoSizeTextSingle: * implementation for CalcAutoSize for single-line * static text controls. * *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed various things with statics *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed broken fonts *@@changed V0.9.14 (2001-08-01) [umoeller]: now caching fonts, which is significantly faster *@@changed V0.9.16 (2001-10-15) [umoeller]: added APIRET *@@changed V0.9.16 (2002-02-02) [umoeller]: added ulWidth *@@changed V1.0.0 (2002-08-18) [umoeller]: renamed; moved multi-line processing to CalcAutoSizeTextMulti */ STATIC APIRET CalcAutoSizeTextSingle(PCOLUMNDEF pColumn, PDLGPRIVATE pDlgData) { SetDlgFont(pColumn->pcszFont, pDlgData); pColumn->szlAuto.cy = pDlgData->fmLast.lMaxBaselineExt + pDlgData->fmLast.lExternalLeading; // get the control string and see how much space it needs if (pColumn->pcszText) { POINTL aptl[TXTBOX_COUNT]; if (!GpiQueryTextBox(pDlgData->hps, strlen(pColumn->pcszText), (PCH)pColumn->pcszText, TXTBOX_COUNT, aptl)) return DLGERR_GPIQUERYTEXTBOX; pColumn->szlAuto.cx = aptl[TXTBOX_TOPRIGHT].x - aptl[TXTBOX_BOTTOMLEFT].x; return NO_ERROR; } return DLGERR_INVALID_CONTROL_TITLE; } /* *@@ CalcAutoSizeTextMulti: * implementation for CalcAutoSize for multi-line * static text controls. * *@@added V1.0.0 (2002-08-18) [umoeller] */ STATIC APIRET CalcAutoSizeTextMulti(PCOLUMNDEF pColumn, ULONG ulWidth, // in: proposed width of control (req.) PDLGPRIVATE pDlgData) { SetDlgFont(pColumn->pcszFont, pDlgData); // get the control string and see how much space it needs if (pColumn->pcszText) { RECTL rcl = {0, 0, 0, 0}; rcl.xRight = ulWidth; if (pColumn->szlProposed.cy > 0) rcl.yTop = pColumn->szlProposed.cy; else rcl.yTop = winhQueryScreenCY() * 2 / 3; winhDrawFormattedText(pDlgData->hps, &rcl, pColumn->pcszText, DT_LEFT | DT_TOP | DT_WORDBREAK | DT_QUERYEXTENT); pColumn->szlAuto.cx = rcl.xRight - rcl.xLeft; pColumn->szlAuto.cy = rcl.yTop - rcl.yBottom; return NO_ERROR; } return DLGERR_INVALID_CONTROL_TITLE; } /* *@@ CalcAutoSizeTextView: * implementation for CalcAutoSize for the XTextView * control. * * This is slightly sick. We create the control already * here in order to be able to have it format the text * and then send TXM_QUERYTEXTEXTENT to it, which was * added to the control with V0.9.20 for this very * purpose. The control that we create here will then be * reused by ColumnCreateControl and not be recreated, * which would be way too expensive. * *@@added V0.9.20 (2002-08-10) [umoeller] *@@changed V1.0.0 (2002-08-18) [umoeller]: removed temp windows list */ STATIC APIRET CalcAutoSizeTextView(PCOLUMNDEF pColumn, ULONG ulWidth, // in: proposed width of control (req.) PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; PCSZ pcszTitle; if ( (pColumn->hwndControl) || (pColumn->hwndControl = WinCreateWindow(pDlgData->hwndDlg, // parent (PSZ)pColumn->pcszClass, NULL, pColumn->flStyle, 0, 0, ulWidth, 2000, // cy, for now pDlgData->hwndDlg, // owner HWND_BOTTOM, pColumn->usID, pColumn->pvCtlData, NULL)) ) { HWND hwnd = pColumn->hwndControl; SIZEL szlTemp; if (pColumn->pcszFont) winhSetWindowFont(hwnd, pColumn->pcszFont); WinSetWindowText(hwnd, (pcszTitle = pColumn->pcszText) ? (PSZ)pcszTitle : ""); WinSendMsg(hwnd, TXM_QUERYTEXTEXTENT, (MPARAM)&szlTemp, 0); pColumn->szlAuto.cy = szlTemp.cy; } else arc = DLGERR_CANNOT_CREATE_CONTROL; return arc; } /* *@@ CalcAutoSize: * helper func that gets called from ColumnCalcSizes for * every control that has set SZL_AUTOSIZE for its size. * * We try to be smart and set a correct size for the * control, depending on its class and data. * * This sets pColumn->szlAuto and possibly pre-creates * a window for the control and stores the window * in pColumn->hwndControl. * * Presently this works for * * -- static text, single and multiline * * -- static icons and bitmaps * * -- pushbuttons, radio buttons, and checkboxes * * -- the XTextView control (yes! V0.9.20). * *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed various things with statics *@@changed V0.9.16 (2001-10-15) [umoeller]: added APIRET *@@changed V0.9.20 (2002-08-10) [umoeller]: added support for textview */ STATIC APIRET CalcAutoSize(PCOLUMNDEF pColumn, ULONG ulWidth, // in: proposed width of control PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; switch ((ULONG)pColumn->pcszClass) { case 0xffff0003L: // WC_BUTTON: if (!(arc = CalcAutoSizeTextSingle(pColumn, pDlgData))) { if (pColumn->flStyle & ( BS_AUTOCHECKBOX | BS_AUTORADIOBUTTON | BS_AUTO3STATE | BS_3STATE | BS_CHECKBOX | BS_RADIOBUTTON)) { // give a little extra width for the box bitmap // V0.9.19 (2002-04-24) [umoeller] pColumn->szlAuto.cx += ctlQueryCheckboxSize() + 4; // and height pColumn->szlAuto.cy += 2; } else if (pColumn->flStyle & BS_BITMAP) ; // @@todo else if (pColumn->flStyle & (BS_ICON | BS_MINIICON)) ; // @@todo // we can't test for BS_PUSHBUTTON because that's 0x0000 else if (!(pColumn->flStyle & BS_USERBUTTON)) { pColumn->szlAuto.cx += (2 * pDlgData->cxBorder + 15); pColumn->szlAuto.cy += (2 * pDlgData->cyBorder + 15); } } break; case 0xffff0005L: // WC_STATIC: switch (pColumn->flStyle & 0x0F) { case SS_TEXT: if (pColumn->flStyle & DT_WORDBREAK) // multi-line: arc = CalcAutoSizeTextMulti(pColumn, ulWidth, pDlgData); else // single-line: arc = CalcAutoSizeTextSingle(pColumn, pDlgData); break; case SS_BITMAP: { HBITMAP hbm; if (hbm = (HBITMAP)pColumn->pcszText) { BITMAPINFOHEADER2 bmih2; ZERO(&bmih2); bmih2.cbFix = sizeof(bmih2); if (GpiQueryBitmapInfoHeader(hbm, &bmih2)) { pColumn->szlAuto.cx = bmih2.cx; pColumn->szlAuto.cy = bmih2.cy; } else arc = DLGERR_GPIQUERYBITMAPINFOHEADER; } else arc = DLGERR_INVALID_STATIC_BITMAP; } break; case SS_ICON: pColumn->szlAuto.cx = WinQuerySysValue(HWND_DESKTOP, SV_CXICON); pColumn->szlAuto.cy = WinQuerySysValue(HWND_DESKTOP, SV_CYICON); break; } break; default: // added support for textview V0.9.20 (2002-08-10) [umoeller] if ( (((ULONG)pColumn->pcszClass & 0xFFFF0000) != 0xFFFF0000) // don't run strcmp on the PM pseudo-strings && (!strcmp(pColumn->pcszClass, WC_XTEXTVIEW)) ) { arc = CalcAutoSizeTextView(pColumn, ulWidth, pDlgData); } else { // any other control (just to be safe): SetDlgFont(pColumn->pcszFont, pDlgData); pColumn->szlAuto.cx = 50; pColumn->szlAuto.cy = pDlgData->fmLast.lMaxBaselineExt + pDlgData->fmLast.lExternalLeading + 7; // some space } } return arc; } /* *@@ ColumnCalcPositions: * implementation for PROCESS_6_CALC_POSITIONS in * ProcessColumn. * *@@added V0.9.15 (2001-08-26) [umoeller] *@@changed V0.9.16 (2001-10-15) [umoeller]: added APIRET *@@changed V0.9.19 (2002-04-24) [umoeller]: fixed PM groups alignment *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm */ STATIC APIRET ColumnCalcPositions(PCOLUMNDEF pColumn, PROWDEF pOwningRow, // in: current row from ProcessRow PLONG plX, // in/out: PROCESS_6_CALC_POSITIONS only PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; // column box = *plX on ProcessRow stack pColumn->cpBox.x = *plX; pColumn->cpBox.y = pOwningRow->cpRowBox.y; // check vertical alignment of row; // we might need to increase column y switch (pOwningRow->flRowFormat & ROW_VALIGN_MASK) { // case ROW_VALIGN_BOTTOM: // do nothing case ROW_VALIGN_CENTER: if (pColumn->cpBox.cy < pOwningRow->cpRowBox.cy) pColumn->cpBox.y += ( (pOwningRow->cpRowBox.cy - pColumn->cpBox.cy) / 2); break; case ROW_VALIGN_TOP: if (pColumn->cpBox.cy < pOwningRow->cpRowBox.cy) pColumn->cpBox.y += (pOwningRow->cpRowBox.cy - pColumn->cpBox.cy); break; } // increase plX by box width *plX += pColumn->cpBox.cx; // calculate CONTROL pos from COLUMN pos by applying spacing pColumn->cpContent.x = pColumn->cpBox.x + pColumn->rclPadding.xLeft + pColumn->rclMargin.xLeft; pColumn->cpContent.y = pColumn->cpBox.y + pColumn->rclPadding.yBottom + pColumn->rclMargin.yBottom; if (pColumn->pNestedTable) { // nested table: // recurse!! to create windows for the sub-table arc = ProcessTable(pColumn->pNestedTable, &pColumn->cpContent, // start pos for new table PROCESS_6_CALC_POSITIONS, pDlgData); } return arc; } /* *@@ ColumnCreateControl: * implementation for PROCESS_7_CREATE_CONTROLS in * ProcessColumn. * *@@added V0.9.15 (2001-08-26) [umoeller] *@@changed V0.9.16 (2001-10-15) [umoeller]: fixed ugly group table spacings *@@changed V0.9.16 (2001-12-08) [umoeller]: fixed entry field ES_MARGIN positioning *@@changed V0.9.19 (2002-04-17) [umoeller]: fixes for the STUPID drop-down comboboxes *@@changed V0.9.19 (2002-04-24) [umoeller]: fixed PM groups alignment *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm *@@changed V1.0.0 (2002-08-18) [umoeller]: setting entry field length to CCHMAXPATH per default now */ STATIC APIRET ColumnCreateControl(PCOLUMNDEF pColumn, PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; volatile PCSZ pcszClass = NULL; // XWP V1.0.4 (2005-10-09) [pr] volatile PCSZ pcszText = NULL; volatile ULONG flStyle = 0; volatile LHANDLE lHandleSet = NULLHANDLE; volatile ULONG flOld = 0; if (pColumn->pNestedTable) { // nested table: // recurse!! if (!(arc = ProcessTable(pColumn->pNestedTable, NULL, PROCESS_7_CREATE_CONTROLS, pDlgData))) { // should we create a PM control around the table? // (do this AFTER the other controls from recursing, // otherwise the stupid container doesn't show up) if (pcszClass = pColumn->pcszClass) { // yes: pcszText = pColumn->pcszText; flStyle = pColumn->flStyle; // V1.0.0 (2002-08-16) [umoeller] // Removed all the terrible hacks for the PM group // control. We now use rclPadding and rclMargin // in the CONTROLDEF for determining the group // box position and size. Those were set initially // by the DLGHITEM parser. See CreateColumn(). // If the group box had an explicit size set, // that is used by ProcessTable for the table // box also. } #ifdef DEBUG_DIALOG_WINDOWS // debug: create a frame with the exact size // of the table in blue CreateDebugFrame(pDlgData->hwndDlg, // parent pTableDef->cpTableBox.x + pDlgData->ptlTotalOfs.x, pTableDef->cpTableBox.y + pDlgData->ptlTotalOfs.y, pTableDef->cpTableBox.cx, pTableDef->cpTableBox.cy, RGBCOL_BLUE, LINETYPE_DOT); #endif } } else { // no nested table, but control: pcszClass = pColumn->pcszClass; pcszText = pColumn->pcszText; flStyle = pColumn->flStyle; } if ( (!arc) // check error code V1.0.0 (2002-08-16) [umoeller] && (pcszClass) ) { // create something: LONG x, cx, y, cy; // for control hacks HWND hwndFound = NULLHANDLE; LONG cxUse; PCSZ pcszTextEF = NULL; // for entry field // V1.0.0 (2002-08-16) [umoeller] // determine the position where to create the // control; this is the content box, to which // we add the absolute position on the dlgdata // stack, and padding. Padding is presently // only set for the group box around tables. // To create the control, we use the explicit // or automatic size of the control instead // of the column width. Otherwise all buttons // are expanded to the column width, for example. if ( (!(cxUse = pColumn->cxMinContent)) || (pColumn->pNestedTable) ) cxUse = pColumn->cpContent.cx; x = pColumn->cpContent.x - pColumn->rclPadding.xLeft + pDlgData->ptlTotalOfs.x; cx = cxUse + pColumn->rclPadding.xLeft + pColumn->rclPadding.xRight; y = pColumn->cpContent.y - pColumn->rclPadding.yBottom + pDlgData->ptlTotalOfs.y; cy = pColumn->cpContent.cy + pColumn->rclPadding.yBottom + pColumn->rclPadding.yTop; // now implement hacks for certain controls switch ((ULONG)pcszClass) { case 0xffff0005L: // WC_STATIC: // change the title if this is a static with SS_BITMAP; // we have used a HBITMAP in there! if ( ( ((flStyle & 0x0F) == SS_BITMAP) || ((flStyle & 0x0F) == SS_ICON) ) ) { // change style flag to not use SS_BITMAP nor SS_ICON; // control creation fails otherwise (stupid, stupid PM) flOld = flStyle; flStyle = ((flStyle & ~0x0F) | SS_FGNDFRAME); pcszText = ""; lHandleSet = (LHANDLE)pColumn->pcszText; } break; case 0xffff0002L: // combobox if (flStyle & (CBS_DROPDOWN | CBS_DROPDOWNLIST)) { // while calculating the column height, // we have set pColumn->cpContent.cy // to the height of a single line to get // the position calculations right... LONG cyDelta = pColumn->szlProposed.cy - pColumn->cpContent.cy; y -= cyDelta; cy += cyDelta; } break; case 0xffff0006L: // entry field case 0xffff000AL: // MLE: // the stupid entry field resizes itself if it has // the ES_MARGIN style, so correlate that too... dammit // V0.9.16 (2001-12-08) [umoeller] if (flStyle & ES_MARGIN) { LONG cxMargin = 3 * pDlgData->cxBorder; LONG cyMargin = 3 * pDlgData->cyBorder; x += cxMargin; y += cyMargin; cx -= 2 * cxMargin; cy -= 2 * cyMargin; } if ((ULONG)pcszClass == 0xffff0006L) { // entry field: // defer setting the text because we should // first set the entry field limit // V1.0.0 (2002-08-18) [umoeller] pcszTextEF = pcszText; pcszText = ""; // for now } break; } // end switch ((ULONG)pControlDef->pcszClass) if (pColumn->hwndControl) { // control was already created by CalcAutoSize: // resize it to what we really need WinSetWindowPos(pColumn->hwndControl, HWND_BOTTOM, x, y, cx, cy, SWP_SIZE | SWP_MOVE | SWP_ZORDER); } else { TRY_QUIET(excpt1) { pColumn->hwndControl = WinCreateWindow(pDlgData->hwndDlg, // parent (PSZ)pcszClass, // window class, hacked (pcszText) // control text, hacked ? (PSZ)pcszText : "", flStyle, // window style, hacked x, y, cx, cy, pDlgData->hwndDlg, // owner == parent HWND_BOTTOM, pColumn->usID, pColumn->pvCtlData, NULL); } CATCH(excpt1) { CHAR szClass2[100] = "Invalid mem", szText2[100] = "Invalid mem"; ULONG Size, Attr; if (!(arc = DosQueryMem((PVOID)pcszClass, &Size, &Attr))) strhncpy0(szClass2, pcszClass, sizeof(szClass2)); if (!(arc = DosQueryMem((PVOID)pcszClass, &Size, &Attr))) strhncpy0(szText2, pcszText, sizeof(szText2)); _Pmpf(("Crash creating control of class 0x%lX (%s), title 0x%lX (%s)", szClass2, szText2)); arc = ERROR_PROTECTION_VIOLATION; } END_CATCH(); } if (pColumn->hwndControl) { // control created, or reused above: if (lHandleSet) { // subclass the damn static if ((flOld & 0x0F) == SS_ICON) // this was a static: ctlPrepareStaticIcon(pColumn->hwndControl, 1); else // this was a bitmap: ctlPrepareStretchedBitmap(pColumn->hwndControl, TRUE); WinSendMsg(pColumn->hwndControl, SM_SETHANDLE, (MPARAM)lHandleSet, 0); } else { if (pcszTextEF) { // entry field: // set text limit to CCHMAXPATH per default, // and set the real text now // V1.0.0 (2002-08-18) [umoeller] winhSetEntryFieldLimit(pColumn->hwndControl, CCHMAXPATH); WinSetWindowText(pColumn->hwndControl, (PSZ)pcszTextEF); } if (pColumn->pcszFont) // we must set the font explicitly here... // doesn't always work with WinCreateWindow // presparams parameter, for some reason // V0.9.12 (2001-05-31) [umoeller] winhSetWindowFont(pColumn->hwndControl, pColumn->pcszFont); } #ifdef DEBUG_DIALOG_WINDOWS // if (!pColumn->fIsNestedTable) { // debug: create a frame with the exact size // of the box (not the content), so this // includes spacing CreateDebugFrame(pDlgData->hwndDlg, // parent pColumn->cpBox.x + pDlgData->ptlTotalOfs.x, pColumn->cpBox.y + pDlgData->ptlTotalOfs.y, pColumn->cpBox.cx, pColumn->cpBox.cy, RGBCOL_DARKGREEN, LINETYPE_SOLID); // and another one for the content (control size) CreateDebugFrame(pDlgData->hwndDlg, // parent pColumn->cpContent.x + pDlgData->ptlTotalOfs.x, pColumn->cpContent.y + pDlgData->ptlTotalOfs.y, pColumn->cpContent.cx, pColumn->cpContent.cy, RGBCOL_RED, LINETYPE_ALTERNATE); } #endif // append window that was created // V0.9.18 (2002-03-03) [umoeller] if (pDlgData->pllControls) lstAppendItem(pDlgData->pllControls, (PVOID)pColumn->hwndControl); // if this is the first control with WS_TABSTOP, // we'll give it the focus later if ( (flStyle & WS_TABSTOP) && (!pDlgData->hwndFirstFocus) ) pDlgData->hwndFirstFocus = pColumn->hwndControl; // if this is the first default push button, // go store it too // V0.9.14 (2001-08-21) [umoeller] if ( (!pDlgData->hwndDefPushbutton) && ((ULONG)pColumn->pcszClass == 0xffff0003L) && (pColumn->flStyle & BS_DEFAULT) ) pDlgData->hwndDefPushbutton = pColumn->hwndControl; } else // V0.9.14 (2001-08-03) [umoeller] arc = DLGERR_CANNOT_CREATE_CONTROL; } return arc; } /* ****************************************************************** * * Recursive workers for columns, rows, tables * ********************************************************************/ /* *@@ ProcessColumn: * processes a column, which per definition is either * a control or a nested subtable. * * A column is part of a row, which in turn is part * of a table. There can be several columns in a row, * and several rows in a table. * * Since tables may be specified as columns, it is * possible to produce complex dialog layouts by * nesting tables. * * Preconditions: * * -- PROCESS_6_CALC_POSITIONS: position of each column * is taken from *plX, which is increased by the * column width by this call. * * Owning row must already have its y position properly * set, or we can't compute ours. Besides, plX must * point to the current X in the row and will be * incremented by the column's size here. * *@@changed V0.9.12 (2001-05-31) [umoeller]: added control data *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed font problems *@@changed V0.9.20 (2002-08-08) [umoeller]: added support for aligning columns horizontally *@@changed V1.0.0 (2002-08-16) [umoeller]: calc size rewritten for new algorithm */ STATIC APIRET ProcessColumn(PCOLUMNDEF pColumn, PROWDEF pOwningRow, // in: current row from ProcessRow enProcessMode ProcessMode, // in: processing mode (see ProcessAll) PLONG plX, // in/out: PROCESS_6_CALC_POSITIONS only PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; // pColumn->pOwningRow = pOwningRow; switch (ProcessMode) { /* * PROCESS_1_CALC_MIN_WIDTHS: * */ case PROCESS_1_CALC_MIN_WIDTHS: // rewritten V1.0.0 (2002-08-16) [umoeller] if (!pColumn->pNestedTable) { // control leaf node, not subtable: // check if the control title is valid... we // crash later otherwise if ((ULONG)pColumn->pcszText > 0xFFFF0000) arc = DLGERR_INVALID_CONTROL_TITLE; else { if (pColumn->szlProposed.cx > 0) { // explicit size: pColumn->cxMinContent = pColumn->szlProposed.cx; } else if (pColumn->szlProposed.cx == SZL_AUTOSIZE) // -1 { // autosize width: // calc then arc = CalcAutoSize(pColumn, 1000, // width, whatever... // that's what we want to know! pDlgData); pColumn->cxMinContent = pColumn->szlAuto.cx; } // else: -100 < width < -1: percentage of parent table, // leave cxMinContent == 0 for now #ifdef DEBUG_DIALOG_WINDOWS if (pControlDef->pcszText) { CHAR szTemp[40]; strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp)); strcpy(szTemp + 35, "..."); _PmpfF(("PROCESS_1_CALC_MIN_WIDTHS [%s] cxMinContent %d", szTemp, pColumn->cxMinContent)); } #endif } } else { // column represents subtable: // recurse! if (!(arc = ProcessTable(pColumn->pNestedTable, NULL, ProcessMode, pDlgData))) { // ProcessTable has set pTableDef->cxMinBox, // that is the size of the entire table box... // use that for our cxMinContent pColumn->cxMinContent = pColumn->pNestedTable->cxMinBox; } } break; // PROCESS_1_CALC_MIN_WIDTHS /* * PROCESS_2_CALC_RELATIVE_WIDTHS: * */ case PROCESS_2_CALC_RELATIVE_WIDTHS: // rewritten V1.0.0 (2002-08-16) [umoeller] if (!pColumn->pNestedTable) { // control leaf node, not subtable: if ( (pColumn->szlProposed.cx == SZL_AUTOSIZE) // -1 || (pColumn->szlProposed.cx > 0) // not SZL_REMAINDER ) { // explicit size or autosize: // that was computed with PROCESS_1_CALC_MIN_WIDTHS, // so copy from cxMinContent pColumn->cpContent.cx = pColumn->cxMinContent; } else if (pColumn->szlProposed.cx < 0) { // control wants percentage of parent table width: PTABLEDEF pOwningTable = pOwningRow->pOwningTable; PROWDEF pWidestRow; #ifdef DEBUG_DIALOG_WINDOWS { CHAR szTemp[40]; strhncpy0(szTemp, (pControlDef->pcszText) ? pControlDef->pcszText : "NULL", sizeof(szTemp)); strcpy(szTemp + 35, "..."); _PmpfF(("PROCESS_2_CALC_RELATIVE_WIDTHS [%s]")); } #endif if (pWidestRow = pOwningTable->pWidestRow) { PLISTNODE pNode; PCOLUMNDEF pSubColumn, pTablesColumn; LONG cxWidestRowsMinBox = pOwningTable->cxMinBox; // pWidestRow->cxMinBox; _Pmpf((" pWidestRow->cxMinBox %d", pWidestRow->cxMinBox)); _Pmpf((" pOwningTable->cxMinBox %d", pOwningTable->cxMinBox)); // now, since we're supposed to set our own CONTENT // rectangle, subtract our own padding and margin cxWidestRowsMinBox -= pColumn->cxSpacingTotal; pColumn->cpContent.cx = (double)cxWidestRowsMinBox * -pColumn->szlProposed.cx / 100; } #ifdef DEBUG_DIALOG_WINDOWS { _Pmpf((" cpContent.cx %d", pColumn->cpContent.cx)); _Pmpf((" pWidestRow was 0x%lX", pWidestRow)); } #endif } // end if (pControlDef->szlDlgUnits.cx < 0) else if (pColumn->szlProposed.cx == SZL_REMAINDER) // 0 { // SZL_REMAINDER is calculated in PROCESS_4_REMAINDER_WIDTHS, // just count the remainders in the ROWDEF for now so // we can process them quickly later ++(pOwningRow->cWantRemainders); // set the flag so that Dlg2_CalcSizes knows we'll // need PROCESS_4_REMAINDER_WIDTHS pDlgData->flNeedsProcessing |= FL_REMAINDER_WIDTHS; } } else { // column represents subtable: // recurse! if (!(arc = ProcessTable(pColumn->pNestedTable, NULL, ProcessMode, pDlgData))) { // ProcessTable has set pTableDef->cpTableBox.cx, // that is the size of the entire table box... // use that for our cpContent.cx pColumn->cpContent.cx = pColumn->pNestedTable->cpTableBox.cx; } } break; // PROCESS_2_CALC_RELATIVE_WIDTHS /* * PROCESS_3_ALIGN_COLUMNS: * */ case PROCESS_3_ALIGN_COLUMNS: // rewritten V1.0.0 (2002-08-16) [umoeller] if (!pColumn->pNestedTable) { // control leaf node, not subtable: LONG cxWidest = 0; PTABLEDEF pOwningTable; // if our owning table has the TABLE_ALIGN_COLUMNS // flag set, set this columns width to the width // of the widest column in all rows of this table, // but align this column ONLY if it does not want // SZL_REMAINDER if ( (pColumn->szlProposed.cx != SZL_REMAINDER) // 0 && (pOwningTable = pOwningRow->pOwningTable) && (pOwningTable->flTable & TABLE_ALIGN_COLUMNS) ) { // find the widest corresponding column in the // surrounding table; pColumn->ulColumnIndex // has our index, so we need to run through // the rows and compare the widths of all // columns with that index PLISTNODE pRowNode; FOR_ALL_NODES(&pOwningTable->llRows, pRowNode) { PROWDEF pRowThis = (PROWDEF)pRowNode->pItemData; PCOLUMNDEF pCorrespondingColumn; if ( (pCorrespondingColumn = (PCOLUMNDEF)lstItemFromIndex(&pRowThis->llColumns, pColumn->ulColumnIndex)) && (pCorrespondingColumn->cxMinContent > cxWidest) ) { #ifdef DEBUG_DIALOG_WINDOWS { const CONTROLDEF *pControlDef = (const CONTROLDEF *)pCorrespondingColumn->pvDefinition; if (pControlDef->pcszText) { CHAR szTemp[40]; strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp)); strcpy(szTemp + 35, "..."); _PmpfF((" widest column is [%s] cxMinContent %d", szTemp, pCorrespondingColumn->cxMinContent)); } } #endif cxWidest = pCorrespondingColumn->cxMinContent; } } pColumn->cpContent.cx = cxWidest; #ifdef DEBUG_DIALOG_WINDOWS { if (pControlDef->pcszText) { CHAR szTemp[40]; strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp)); strcpy(szTemp + 35, "..."); _PmpfF(("PROCESS_3_ALIGN_COLUMNS [%s] cpContent.cx %d", szTemp, pColumn->cpContent.cx)); } } #endif } } else { // column represents subtable: // recurse! arc = ProcessTable(pColumn->pNestedTable, NULL, ProcessMode, pDlgData); // the width of a table can NOT change from this // processing, so no need to adjust // (individual rows might have changed though) // @@todo wrong, if the first column is made // wider, the second column might now move over // the table width, fix that } // in any case (control or subtable), count the // widths of all columns that do not have SZL_REMAINDER // set for PROCESS_4_REMAINDER_WIDTHS in the next loop // NOTE: we add _all_ columns, including those that have // SZL_REMAINDER set (for which cpContent.cx is null // presently), because the "non remainder" space needs // to include the spacing of _all_ columns (see the // PROCESS_4_REMAINDER_WIDTHS processing below) pOwningRow->cxNonRemainder += pColumn->cpContent.cx + pColumn->cxSpacingTotal; break; // PROCESS_3_ALIGN_COLUMNS /* * PROCESS_4_REMAINDER_WIDTHS: * */ case PROCESS_4_REMAINDER_WIDTHS: if (!pColumn->pNestedTable) { // control leaf node, not subtable: PTABLEDEF pOwningTable; if ( (pColumn->szlProposed.cx == SZL_REMAINDER) // 0 && (pOwningTable = pOwningRow->pOwningTable) ) { LONG cxRemaining = pOwningTable->cpTableBox.cx - pOwningRow->cxNonRemainder; // NOTE: pOwningRow->cxNonRemainder has // -- the sum of the column content rectangle widths that did not // have SZL_REMAINDER set // -- the sum of _all_ column spacings, even if the column had // SZL_REMAINDER set, // so this really has the leftover space now. // Divide that by the no. of columns in the row that have // SZL_REMAINDER set so that we evenly distribute the remaining // space among those columns. pColumn->cpContent.cx = ( cxRemaining / pOwningRow->cWantRemainders // this cannot be 0 since we have // that flag and were thus counted ); #ifdef DEBUG_DIALOG_WINDOWS { CHAR szTemp[40] = "NULL"; if (pControlDef->pcszText) { strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp)); strcpy(szTemp + 35, "..."); } _PmpfF(("PROCESS_4_REMAINDER_WIDTHS [%s] pOwningTable->cpTableBox.cx %d", szTemp, pOwningTable->cpTableBox.cx)); _PmpfF((" pOwningRow->cxNonRemainder %d", pOwningRow->cxNonRemainder)); _PmpfF((" pOwningRow->cWantRemainders %d", pOwningRow->cWantRemainders)); _PmpfF((" cpContent.cx %d", pColumn->cpContent.cx)); } #endif } } else { // column represents subtable: // recurse! arc = ProcessTable(pColumn->pNestedTable, NULL, ProcessMode, pDlgData); } break; /* * PROCESS_5_CALC_HEIGHTS: * */ case PROCESS_5_CALC_HEIGHTS: if (!pColumn->pNestedTable) { // control leaf node, not subtable: if (pColumn->szlProposed.cy > 0) { // explicit size: pColumn->cpContent.cy = pColumn->szlProposed.cy; if ((ULONG)pColumn->pcszClass == 0xffff0002L) { // hack the stupid drop-down combobox where the // size of the drop-down is the full size of the // control: when creating the control, we _do_ // specify the full size, but for the column, // we must rather use a single line with // the current font // V0.9.19 (2002-04-17) [umoeller] if (pColumn->flStyle & (CBS_DROPDOWN | CBS_DROPDOWNLIST)) { SetDlgFont(pColumn->pcszFont, pDlgData); pColumn->cpContent.cy = pDlgData->fmLast.lMaxBaselineExt + pDlgData->fmLast.lExternalLeading + 2 * 3 * pDlgData->cyBorder; } } } else if (pColumn->szlProposed.cy == SZL_AUTOSIZE) // -1 { // autosize height: // we MIGHT have run autosize already above // (e.g. for the typical bitmap or icon statics), // so check if szlAuto.cy is already set if (!(pColumn->cpContent.cy = pColumn->szlAuto.cy)) { // not yet determined: // calc then arc = CalcAutoSize(pColumn, // width: now we know pColumn->cpContent.cx, pDlgData); pColumn->cpContent.cy = pColumn->szlAuto.cy; } } // else: -100 < width < -1: @@todo // add another stage for using the percentage of // the table height } else { // column represents subtable: // recurse! if (!(arc = ProcessTable(pColumn->pNestedTable, NULL, ProcessMode, pDlgData))) { // ProcessTable has set pTableDef->cpTableBox.cy, // that is the size of the entire table box... // use that for our cpContent.cy pColumn->cpContent.cy = pColumn->pNestedTable->cpTableBox.cy; } } // we finally know the DEFINITE width and height // of the content, so we can set the box width // and height here now (ProcessRow relies on this // to determine the row height) pColumn->cpBox.cx = pColumn->cpContent.cx + pColumn->cxSpacingTotal; pColumn->cpBox.cy = pColumn->cpContent.cy + pColumn->rclPadding.yBottom + pColumn->rclMargin.yBottom + pColumn->rclPadding.yTop + pColumn->rclMargin.yTop; break; /* * PROCESS_6_CALC_POSITIONS: * */ case PROCESS_6_CALC_POSITIONS: arc = ColumnCalcPositions(pColumn, pOwningRow, plX, pDlgData); break; /* * PROCESS_7_CREATE_CONTROLS: * */ case PROCESS_7_CREATE_CONTROLS: arc = ColumnCreateControl(pColumn, pDlgData); break; } return arc; } /* *@@ ProcessRow: * level-3 procedure (called from ProcessTable), * which in turn calls ProcessColumn for each column * in the row. * *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm */ STATIC APIRET ProcessRow(PROWDEF pRowDef, PTABLEDEF pOwningTable, // in: current table from ProcessTable enProcessMode ProcessMode, // in: processing mode (see ProcessAll) PLONG plY, // in/out: current y position (decremented) PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; LONG lXRowBox; PLISTNODE pNode; pRowDef->pOwningTable = pOwningTable; switch (ProcessMode) { case PROCESS_3_ALIGN_COLUMNS: // we've computed pRowDef->cpRowBox.cx in the // previous run (PROCESS_2_CALC_RELATIVE_WIDTHS), // but since we keep adding to it, we must // reset it now... the row will probably become // larger! pRowDef->cpRowBox.cx = 0; break; case PROCESS_6_CALC_POSITIONS: // set up x and y so that the columns can // base on that pRowDef->cpRowBox.x = pOwningTable->cpTableBox.x; // decrease y by row height *plY -= pRowDef->cpRowBox.cy; // and use that for our bottom position pRowDef->cpRowBox.y = *plY; // set lX to left of row; used by column calls below lXRowBox = pRowDef->cpRowBox.x; break; } FOR_ALL_NODES(&pRowDef->llColumns, pNode) { PCOLUMNDEF pColumnThis = (PCOLUMNDEF)pNode->pItemData; if (!(arc = ProcessColumn(pColumnThis, pRowDef, ProcessMode, &lXRowBox, pDlgData))) { switch (ProcessMode) { case PROCESS_1_CALC_MIN_WIDTHS: // row width = sum of all columns pRowDef->cxMinBox += pColumnThis->cxMinContent + pColumnThis->cxSpacingTotal; break; case PROCESS_2_CALC_RELATIVE_WIDTHS: case PROCESS_3_ALIGN_COLUMNS: // reapply, was reset above // row width = sum of all columns pRowDef->cpRowBox.cx += pColumnThis->cpContent.cx + pColumnThis->cxSpacingTotal; break; case PROCESS_5_CALC_HEIGHTS: // row height = maximum height of a column if (pRowDef->cpRowBox.cy < pColumnThis->cpBox.cy) pRowDef->cpRowBox.cy = pColumnThis->cpBox.cy; } } // we should stop on errors V0.9.20 (2002-08-10) [umoeller] else break; } return arc; } /* *@@ ProcessTable: * level-2 procedure (called from ProcessAll), * which in turn calls ProcessRow for each row * in the table (which in turn calls ProcessColumn * for each column in the row). * * See ProcessAll for the meaning of ProcessMode. * * This routine is a bit sick because it can even be * called recursively from ProcessColumn (!) if a * nested table is found in a COLUMNDEF. * * With PROCESS_6_CALC_POSITIONS, pptl must specify * the lower left corner of the table. For the * root call, this will be {0, 0}; for nested calls, * this must be the lower left corner of the column * to which the nested table belongs. * *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm */ STATIC APIRET ProcessTable(PTABLEDEF pTableDef, const CONTROLPOS *pcpTableBox, // in: table position with PROCESS_6_CALC_POSITIONS enProcessMode ProcessMode, // in: processing mode (see ProcessAll) PDLGPRIVATE pDlgData) { APIRET arc = NO_ERROR; LONG lY; PLISTNODE pNode; switch (ProcessMode) { case PROCESS_2_CALC_RELATIVE_WIDTHS: // before calculating relative widths for // the table, inherit size from parent table // if the TABLE_INHERIT_SIZE table flag is // set; this was originally designed // as a separate step (PROCESS_2_INHERIT_TABLE_SIZES), // but we can save ourselves a loop here // V1.0.0 (2002-08-16) [umoeller] if (pTableDef->flTable & TABLE_INHERIT_SIZE) { PCOLUMNDEF pOwningColumn; PROWDEF pOwningRow; PTABLEDEF pOwningTable; if ( (!(pOwningColumn = pTableDef->pOwningColumn)) || (!(pOwningRow = pOwningColumn->pOwningRow)) || (!(pOwningTable = pOwningRow->pOwningTable)) ) arc = DLGERR_ROOT_TABLE_INHERIT_SIZE; else { PLISTNODE pRowNode; LONG cxMax = 0; FOR_ALL_NODES(&pOwningTable->llRows, pRowNode) { PROWDEF pRowThis = (PROWDEF)pRowNode->pItemData; if (pRowThis->cxMinBox > cxMax) cxMax = pRowThis->cxMinBox; } _Pmpf((" cxMax found is %d", cxMax)); pTableDef->cxMinBox = cxMax - pOwningColumn->cxSpacingTotal; } } break; case PROCESS_6_CALC_POSITIONS: pTableDef->cpTableBox.x = pcpTableBox->x; pTableDef->cpTableBox.y = pcpTableBox->y; // start the rows on top lY = pcpTableBox->y + pTableDef->cpTableBox.cy; break; } if (!arc) { FOR_ALL_NODES(&pTableDef->llRows, pNode) { PROWDEF pRowDefThis = (PROWDEF)pNode->pItemData; if (!(arc = ProcessRow(pRowDefThis, pTableDef, ProcessMode, &lY, pDlgData))) { switch (ProcessMode) { case PROCESS_1_CALC_MIN_WIDTHS: // table width = maximum width of a row if (pTableDef->cxMinBox < pRowDefThis->cxMinBox) { pTableDef->cxMinBox = pRowDefThis->cxMinBox; // remember the widest row so that // PROCESS_2_CALC_RELATIVE_WIDTHS can calc relative // widths from it pTableDef->pWidestRow = pRowDefThis; } break; case PROCESS_2_CALC_RELATIVE_WIDTHS: case PROCESS_3_ALIGN_COLUMNS: { PCOLUMNDEF pOwningColumn; // table width = maximum width of a row if (pTableDef->cpTableBox.cx < pRowDefThis->cpRowBox.cx) pTableDef->cpTableBox.cx = pRowDefThis->cpRowBox.cx; // preserve the inherited width if it was larger if (pTableDef->cxMinBox > pTableDef->cpTableBox.cx) pTableDef->cpTableBox.cx = pTableDef->cxMinBox; // if the table has a group box with an explicit // size, use it if it's larger if ( (pOwningColumn = pTableDef->pOwningColumn) && (pOwningColumn->pcszClass) && (pOwningColumn->szlProposed.cx > 0) && (pOwningColumn->szlProposed.cx > pTableDef->cpTableBox.cx) ) pTableDef->cpTableBox.cx = pOwningColumn->szlProposed.cx; } break; case PROCESS_5_CALC_HEIGHTS: // table height = sum of all rows pTableDef->cpTableBox.cy += pRowDefThis->cpRowBox.cy; break; } } else break; } } return arc; } /* *@@ ProcessAll: * level-1 procedure, which in turn calls ProcessTable * for each root-level table found (which in turn * calls ProcessRow for each row in the table, which * in turn calls ProcessColumn for each column in * the row). */ STATIC APIRET ProcessAll(PDLGPRIVATE pDlgData, enProcessMode ProcessMode) { APIRET arc = NO_ERROR; PLISTNODE pNode; CONTROLPOS cpTableBox; ZERO(&cpTableBox); switch (ProcessMode) { case PROCESS_1_CALC_MIN_WIDTHS: pDlgData->szlClient.cx = 0; pDlgData->szlClient.cy = 0; break; case PROCESS_6_CALC_POSITIONS: // start with the table on top cpTableBox.y = pDlgData->szlClient.cy; break; } FOR_ALL_NODES(&pDlgData->llTables, pNode) { PTABLEDEF pTableDefThis = (PTABLEDEF)pNode->pItemData; if (ProcessMode == PROCESS_6_CALC_POSITIONS) { cpTableBox.x = 0; cpTableBox.y -= pTableDefThis->cpTableBox.cy; } if (!(arc = ProcessTable(pTableDefThis, &cpTableBox, // start pos ProcessMode, pDlgData))) { switch (ProcessMode) { case PROCESS_5_CALC_HEIGHTS: { // all sizes have now been computed: pDlgData->szlClient.cx += pTableDefThis->cpTableBox.cx; pDlgData->szlClient.cy += pTableDefThis->cpTableBox.cy; } } } // we should stop on errors V0.9.20 (2002-08-10) [umoeller] else break; } return arc; } /* ****************************************************************** * * DLGHITEM parser * ********************************************************************/ /* *@@ CreateColumn: * *@@changed V1.0.0 (2002-08-18) [umoeller]: mostly rewritten for new algorithm */ STATIC APIRET CreateColumn(PDLGPRIVATE pDlgData, PROWDEF pCurrentRow, PTABLEDEF pNestedTable, const CONTROLDEF *pControlDef, PCOLUMNDEF *ppColumn) // out: new COLUMNDEF { APIRET arc = NO_ERROR; if (!pCurrentRow) arc = DLGERR_CONTROL_BEFORE_ROW; else { // create column and store ctl def PCOLUMNDEF pColumn; if (!(pColumn = NEW(COLUMNDEF))) arc = ERROR_NOT_ENOUGH_MEMORY; else { ZERO(pColumn); pColumn->pOwningRow = pCurrentRow; if (pColumn->pNestedTable = pNestedTable) { // should we create a PM control around the table? if (pControlDef) { // yes: pColumn->rclPadding.xLeft = pColumn->rclPadding.xRight = GROUP_INNER_SPACING_X * FACTOR_X; pColumn->rclPadding.yBottom = GROUP_INNER_SPACING_BOTTOM * FACTOR_Y; pColumn->rclPadding.yTop = GROUP_INNER_SPACING_TOP * FACTOR_Y; pColumn->rclMargin.xLeft = GROUP_OUTER_SPACING_X * FACTOR_X; pColumn->rclMargin.xRight = GROUP_OUTER_SPACING_X * FACTOR_X; pColumn->rclMargin.yBottom = GROUP_OUTER_SPACING_BOTTOM * FACTOR_Y; pColumn->rclMargin.yTop = GROUP_OUTER_SPACING_TOP * FACTOR_Y; } } else if (!pControlDef) arc = DLGERR_NULL_CONTROLDEF; if (pControlDef) { // copy control fields pColumn->pcszClass = pControlDef->pcszClass; pColumn->pcszText = pControlDef->pcszText; pColumn->flStyle = pControlDef->flStyle; pColumn->usID = pControlDef->usID; // resolve font if (pControlDef->pcszFont == CTL_COMMON_FONT) pColumn->pcszFont = pDlgData->pcszControlsFont; else pColumn->pcszFont = pControlDef->pcszFont; // can be NULL // copy and convert proposed size if (pControlDef->szlDlgUnits.cx > 0) // not SZL_AUTOSIZE, not SZL_REMAINDER, not percentage: // convert from dlgunits to pixels pColumn->szlProposed.cx = pControlDef->szlDlgUnits.cx * FACTOR_X; else pColumn->szlProposed.cx = pControlDef->szlDlgUnits.cx; if (pControlDef->szlDlgUnits.cy > 0) // not SZL_AUTOSIZE, not SZL_REMAINDER, not percentage: // convert from dlgunits to pixels pColumn->szlProposed.cy = pControlDef->szlDlgUnits.cy * FACTOR_Y; else pColumn->szlProposed.cy = pControlDef->szlDlgUnits.cy; pColumn->pvCtlData = pControlDef->pvCtlData; // note: we increase the margin here... it was null // unless set above because we had a PM group control // around a table pColumn->rclMargin.xLeft += pControlDef->duSpacing * FACTOR_X; pColumn->rclMargin.xRight += pControlDef->duSpacing * FACTOR_X; pColumn->rclMargin.yBottom += pControlDef->duSpacing * FACTOR_Y; pColumn->rclMargin.yTop += pControlDef->duSpacing * FACTOR_Y; } // calculate the column's total spacing width // for speed pColumn->cxSpacingTotal = pColumn->rclPadding.xLeft + pColumn->rclMargin.xLeft + pColumn->rclPadding.xRight + pColumn->rclMargin.xRight; // set the column index V1.0.0 (2002-08-18) [umoeller] pColumn->ulColumnIndex = (pCurrentRow->cColumns)++; *ppColumn = pColumn; } } return arc; } /* *@@ FreeTable: * frees the specified table and recurses * into nested tables, if necessary. * * This was added with V0.9.14 to fix the * bad memory leaks with nested tables. * *@@added V0.9.14 (2001-08-01) [umoeller] */ STATIC VOID FreeTable(PTABLEDEF pTable) { // for each table, clean up the rows PLISTNODE pRowNode; FOR_ALL_NODES(&pTable->llRows, pRowNode) { PROWDEF pRow = (PROWDEF)pRowNode->pItemData; // for each row, clean up the columns PLISTNODE pColumnNode; FOR_ALL_NODES(&pRow->llColumns, pColumnNode) { PCOLUMNDEF pColumn = (PCOLUMNDEF)pColumnNode->pItemData; if (pColumn->pNestedTable) // nested table: recurse! FreeTable(pColumn->pNestedTable); free(pColumn); } lstClear(&pRow->llColumns); free(pRow); } lstClear(&pTable->llRows); free(pTable); } /* *@@ STACKITEM: * */ typedef struct _STACKITEM { PTABLEDEF pLastTable; PROWDEF pLastRow; } STACKITEM, *PSTACKITEM; /* *@@ Dlg0_Init: * *@@added V0.9.15 (2001-08-26) [umoeller] *@@changed V0.9.18 (2002-03-03) [umoeller]: added pllWindows *@@changed V0.9.19 (2002-04-24) [umoeller]: added resolution correlation */ STATIC APIRET Dlg0_Init(PDLGPRIVATE *ppDlgData, PCSZ pcszControlsFont, PLINKLIST pllControls) { PDLGPRIVATE pDlgData; POINTL ptl = {100, 100}; if (!(pDlgData = NEW(DLGPRIVATE))) return (ERROR_NOT_ENOUGH_MEMORY); ZERO(pDlgData); lstInit(&pDlgData->llTables, FALSE); if (pllControls) pDlgData->pllControls = pllControls; pDlgData->pcszControlsFont = pcszControlsFont; // cache these now too V0.9.19 (2002-04-17) [umoeller] pDlgData->cxBorder = WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER); pDlgData->cyBorder = WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER); // check how many pixels we get out of the // dlgunits (100/100) for mapping all sizes // V0.9.19 (2002-04-24) [umoeller] if (WinMapDlgPoints(NULLHANDLE, &ptl, 1, TRUE)) { // this worked: // for 1024x768, I get 200/250 out of the above, // so calculate a factor from that; we multiply // szlDlgUnits with this factor when calculating // the sizes pDlgData->dFactorX = (double)ptl.x / (double)100; // 2 on 1024x768 pDlgData->dFactorY = (double)ptl.y / (double)100; // 2.5 on 1024x768 } else { // didn't work: pDlgData->dFactorX = 2; pDlgData->dFactorY = 2.5; } *ppDlgData = pDlgData; return NO_ERROR; } /* *@@ Dlg1_ParseTables: * *@@added V0.9.15 (2001-08-26) [umoeller] */ STATIC APIRET Dlg1_ParseTables(PDLGPRIVATE pDlgData, PCDLGHITEM paDlgItems, // in: definition array ULONG cDlgItems) // in: array item count (NOT array size) { APIRET arc = NO_ERROR; LINKLIST llStack; ULONG ul; PTABLEDEF pCurrentTable = NULL; PROWDEF pCurrentRow = NULL; lstInit(&llStack, TRUE); // this is our stack for nested table definitions for (ul = 0; ul < cDlgItems; ul++) { PCDLGHITEM pItemThis = &paDlgItems[ul]; switch (pItemThis->Type) { /* * TYPE_START_NEW_TABLE: * */ case TYPE_START_NEW_TABLE: { // root table or nested? BOOL fIsRoot = (pCurrentTable == NULL); // push the current table on the stack PSTACKITEM pStackItem; if (!(pStackItem = NEW(STACKITEM))) { arc = ERROR_NOT_ENOUGH_MEMORY; break; } else { pStackItem->pLastTable = pCurrentTable; pStackItem->pLastRow = pCurrentRow; lstPush(&llStack, pStackItem); } // create new table if (!(pCurrentTable = NEW(TABLEDEF))) arc = ERROR_NOT_ENOUGH_MEMORY; else { const CONTROLDEF *pControlDef; ZERO(pCurrentTable); lstInit(&pCurrentTable->llRows, FALSE); // if control specified: store it (this will become a PM group) // pCurrentTable->pCtlDef = (const CONTROLDEF *)pItemThis->ul1; // pItemThis->pCtlDef; pControlDef = (const CONTROLDEF *)pItemThis->ul1; // can be NULL for plain table pCurrentTable->flTable = pItemThis->ul2; // V0.9.20 (2002-08-08) [umoeller] if (fIsRoot) // root table: // append to dialog data list lstAppendItem(&pDlgData->llTables, pCurrentTable); else { // nested table: // create "table" column for this in the current row PCOLUMNDEF pColumn; if (!(arc = CreateColumn(pDlgData, pCurrentRow, pCurrentTable, // nested table pControlDef, &pColumn))) { pCurrentTable->pOwningColumn = pColumn; lstAppendItem(&pCurrentRow->llColumns, pColumn); } } } // reset current row so we can detect // wrong DLGHITEM ordering pCurrentRow = NULL; } break; /* * TYPE_START_NEW_ROW: * */ case TYPE_START_NEW_ROW: if (!pCurrentTable) arc = DLGERR_ROW_BEFORE_TABLE; else { // create new row if (!(pCurrentRow = NEW(ROWDEF))) arc = ERROR_NOT_ENOUGH_MEMORY; else { ZERO(pCurrentRow); pCurrentRow->pOwningTable = pCurrentTable; lstInit(&pCurrentRow->llColumns, FALSE); pCurrentRow->flRowFormat = pItemThis->ul2; lstAppendItem(&pCurrentTable->llRows, pCurrentRow); } } break; /* * TYPE_CONTROL_DEF: * */ case TYPE_CONTROL_DEF: { PCOLUMNDEF pColumn; if (!(arc = CreateColumn(pDlgData, pCurrentRow, FALSE, // no nested table (const CONTROLDEF *)pItemThis->ul1, // pCtlDef, V1.0.0 (2002-08-18) [umoeller] &pColumn))) lstAppendItem(&pCurrentRow->llColumns, pColumn); } break; /* * TYPE_END_TABLE: * */ case TYPE_END_TABLE: { PLISTNODE pNode; if (!(pNode = lstPop(&llStack))) // nothing on the stack: arc = DLGERR_TOO_MANY_TABLES_CLOSED; else { PSTACKITEM pStackItem = (PSTACKITEM)pNode->pItemData; pCurrentTable = pStackItem->pLastTable; pCurrentRow = pStackItem->pLastRow; lstRemoveNode(&llStack, pNode); } } break; default: arc = DLGERR_INVALID_CODE; } if (arc) break; } // all tables should be closed now if ( (!arc) && (lstCountItems(&llStack)) ) arc = DLGERR_TABLE_NOT_CLOSED; lstClear(&llStack); return arc; } /* *@@ Dlg2_CalcSizes: * calls ProcessAll with the first four process modes * for calculating the final sizes (but not yet the * positions) of the controls. * * See @dlg_algorithm for details. * * After this, DLGPRIVATE.szlClient is valid. * *@@added V0.9.15 (2001-08-26) [umoeller] *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm */ STATIC APIRET Dlg2_CalcSizes(PDLGPRIVATE pDlgData) { APIRET arc; // call process mode with the first for "calc size" // process modes // changed V1.0.0 (2002-08-16) [umoeller] if (!(arc = ProcessAll(pDlgData, PROCESS_1_CALC_MIN_WIDTHS))) if (!(arc = ProcessAll(pDlgData, PROCESS_2_CALC_RELATIVE_WIDTHS))) if (!(arc = ProcessAll(pDlgData, PROCESS_3_ALIGN_COLUMNS))) // skip PROCESS_4_REMAINDER_WIDTHS if the dlg doesn't need it if ( (!(pDlgData->flNeedsProcessing & FL_REMAINDER_WIDTHS)) || (!(arc = ProcessAll(pDlgData, PROCESS_4_REMAINDER_WIDTHS))) ) arc = ProcessAll(pDlgData, PROCESS_5_CALC_HEIGHTS); // free the cached font resources that // might have been created here if (pDlgData->hps) { if (pDlgData->lcidLast) { GpiSetCharSet(pDlgData->hps, LCID_DEFAULT); GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast); } WinReleasePS(pDlgData->hps); } return arc; } /* *@@ Dlg3_PositionAndCreate: * *@@added V0.9.15 (2001-08-26) [umoeller] *@@changed V0.9.15 (2001-08-26) [umoeller]: BS_DEFAULT for other than first button was ignored, fixed *@@changed V0.9.20 (2002-08-10) [umoeller]: return code checking was missing, fixed */ STATIC APIRET Dlg3_PositionAndCreate(PDLGPRIVATE pDlgData, HWND *phwndFocusItem) // out: item to give focus to { APIRET arc = NO_ERROR; /* * 5) compute _positions_ of all controls * */ // this was missing a return code, fixed V0.9.20 (2002-08-10) [umoeller] if (!(arc = ProcessAll(pDlgData, PROCESS_6_CALC_POSITIONS))) { /* * 6) create control windows, finally * */ pDlgData->ptlTotalOfs.x = DLG_OUTER_SPACING_X * FACTOR_X; pDlgData->ptlTotalOfs.y = DLG_OUTER_SPACING_Y * FACTOR_Y; // this was missing a return code, fixed V0.9.20 (2002-08-10) [umoeller] if (!(arc = ProcessAll(pDlgData, PROCESS_7_CREATE_CONTROLS))) { if (pDlgData->hwndDefPushbutton) { // we had a default pushbutton: // go set it V0.9.14 (2001-08-21) [umoeller] WinSetWindowULong(pDlgData->hwndDlg, QWL_DEFBUTTON, pDlgData->hwndDefPushbutton); *phwndFocusItem = pDlgData->hwndDefPushbutton; // V0.9.15 (2001-08-26) [umoeller] } else *phwndFocusItem = (pDlgData->hwndFirstFocus) ? pDlgData->hwndFirstFocus : pDlgData->hwndDlg; } } return arc; } /* *@@ Dlg9_Cleanup: * *@@added V0.9.15 (2001-08-26) [umoeller] */ STATIC VOID Dlg9_Cleanup(PDLGPRIVATE *ppDlgData) { PDLGPRIVATE pDlgData; if ( (ppDlgData) && (pDlgData = *ppDlgData) ) { PLISTNODE pTableNode; // clean up the tables FOR_ALL_NODES(&pDlgData->llTables, pTableNode) { PTABLEDEF pTable = (PTABLEDEF)pTableNode->pItemData; FreeTable(pTable); // this may recurse for nested tables } lstClear(&pDlgData->llTables); free(pDlgData); *ppDlgData = NULL; } } /* ****************************************************************** * * Dialog formatter entry points * ********************************************************************/ /* *@@ dlghCreateDlg: * replacement for WinCreateDlg/WinLoadDlg for creating a * dialog from a settings array in memory, which is * formatted automatically. * * This does NOT use regular dialog templates from * module resources. Instead, you pass in an array * of DLGHITEM structures, which define the controls * and how they are to be formatted. * * The main advantage compared to dialog resources is * that with this function, you will never have to * define control _positions_. Instead, you only specify * the control _sizes_, and all positions are computed * automatically here. Even better, for many controls, * auto-sizing is supported according to the control's * text (e.g. for statics and checkboxes). This is * quite similar to HTML tables. * * A regular standard dialog would use something like * + FCF_TITLEBAR | FCF_SYSMENU | FCF_DLGBORDER | FCF_NOBYTEALIGN | FCF_CLOSEBUTTON * * for flCreateFlags. To make the dlg sizeable, specify * FCF_SIZEBORDER instead of FCF_DLGBORDER. * * dialog.h defines FCF_FIXED_DLG and FCF_SIZEABLE_DLG * to make this more handy. * * Usage: * * Like WinLoadDlg, this creates a standard WC_FRAME and * subclasses it with fnwpMyDlgProc. It then sends WM_INITDLG * to the dialog with pCreateParams in mp2. * * If this func returns no error, you can then use * WinProcessDlg with the newly created dialog as usual. In * your dlg proc, use WinDefDlgProc as usual. * * There is NO run-time overhead for either code or memory * after dialog creation; after this function returns, the * dialog is a standard dialog as if loaded from WinLoadDlg. * The array of DLGHITEM structures defines how the * dialog is set up. All this is ONLY used by this function * and NOT needed after the dialog has been created. * * In DLGHITEM, the "Type" field determines what this * structure defines. A number of handy macros have been * defined to make this easier and to provide type-checking * at compile time. See dialog.h for the complete (and * ever-expanding) list of definitions. * * See @dlg_using_macros for how to use these macros. * * Essentially, such a dialog item operates similarly to * HTML tables. There are rows and columns in the table, * and each control which is specified must be a column * in some table. Tables may also nest (see below). * * See @dlg_algorithm for the gory details of the new * algorithm used since V1.0.0. * * See @dlg_boxmodel for information about how the * rectangles are defined. * * Example: * * The typical calling sequence would be: * + HWND hwndDlg = NULLHANDLE; + if (NO_ERROR == dlghCreateDlg(&hwndDlg, + hwndOwner, + FCF_TITLEBAR | FCF_SYSMENU + | FCF_DLGBORDER | FCF_NOBYTEALIGN, + fnwpMyDlgProc, + "My Dlg Title", + dlgTemplate, // DLGHITEM array + ARRAYITEMCOUNT(dlgTemplate), + NULL, // mp2 for WM_INITDLG + "9.WarpSans")) // default font + { + ULONG idReturn = WinProcessDlg(hwndDlg); + WinDestroyWindow(hwndDlg); + } * * Errors: * * This does not return a HWND, but an APIRET. This will be * one of the following: * * -- NO_ERROR: only in that case, the phwndDlg ptr * receives the HWND of the new dialog, which can * then be given to WinProcessDlg. Don't forget * WinDestroyWindow. * * -- ERROR_NOT_ENOUGH_MEMORY * * -- DLGERR_ROW_BEFORE_TABLE: a row definition appeared * outside a table definition. * * -- DLGERR_CONTROL_BEFORE_ROW: a control definition * appeared right after a table definition. You must * specify a row first. * * -- DLGERR_NULL_CTL_DEF: A required CONTROLDEF ptr * was NULL. * * -- DLGERR_CANNOT_CREATE_FRAME: unable to create the * WC_FRAME window. Typically an invalid owner was * specified. * * -- DLGERR_INVALID_CODE: invalid "Type" field in * DLGHITEM. * * -- DLGERR_TABLE_NOT_CLOSED, DLGERR_TOO_MANY_TABLES_CLOSED: * improper nesting of TYPE_START_NEW_TABLE and * TYPE_END_TABLE fields. * * -- DLGERR_CANNOT_CREATE_CONTROL: creation of some * sub-control failed. Maybe an invalid window class * was specified. * * -- DLGERR_INVALID_CONTROL_TITLE: bad window title in * control. * * -- DLGERR_INVALID_STATIC_BITMAP: static bitmap contains * an invalid bitmap handle. * *@@changed V0.9.14 (2001-07-07) [umoeller]: fixed disabled mouse with hwndOwner == HWND_DESKTOP *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed major memory leaks with nested tables *@@changed V0.9.14 (2001-08-21) [umoeller]: fixed default push button problems *@@changed V0.9.16 (2001-12-06) [umoeller]: fixed bad owner if not direct desktop child *@@changed V0.9.19 (2002-04-24) [umoeller]: added excpt handling +@@changed V1.0.0 (2002-08-21) [umoeller]: now setting wait pointer */ APIRET dlghCreateDlg(HWND *phwndDlg, // out: new dialog HWND hwndOwner, // in: owner for dialog ULONG flCreateFlags, // in: standard FCF_* frame flags PFNWP pfnwpDialogProc, // in: dialog winproc or WinDefDlgProc PCSZ pcszDlgTitle, // in: title to set for dlg frame's titlebar PCDLGHITEM paDlgItems, // in: definition array with tables, rows, and columns ULONG cDlgItems, // in: ARRAYITEMCOUNT(paDlgItems) PVOID pCreateParams, // in: create param for mp2 of WM_INITDLG PCSZ pcszControlsFont) // in: font for ctls with CTL_COMMON_FONT { APIRET arc = NO_ERROR; volatile HPOINTER hptrOld = winhSetWaitPointer(); // XWP V1.0.4 (2005-10-09) [pr] TRY_LOUD(excpt1) { ULONG ul; PDLGPRIVATE pDlgData = NULL; HWND hwndDesktop = WinQueryDesktopWindow(NULLHANDLE, NULLHANDLE); // works with a null HAB /* * 1) parse the table and create structures from it * */ if (!(arc = Dlg0_Init(&pDlgData, pcszControlsFont, NULL))) { if (!(arc = Dlg1_ParseTables(pDlgData, paDlgItems, cDlgItems))) { /* * 2) create empty dialog frame * */ FRAMECDATA fcData = {0}; ULONG flStyle = 0; HWND hwndOwnersParent; fcData.cb = sizeof(FRAMECDATA); fcData.flCreateFlags = flCreateFlags | 0x40000000L; if (flCreateFlags & FCF_SIZEBORDER) // dialog has size border: // add "clip siblings" style flStyle |= WS_CLIPSIBLINGS; if (hwndOwner == HWND_DESKTOP) // there's some dumb XWorkplace code left // which uses this, and this disables the // mouse for some reason // V0.9.14 (2001-07-07) [umoeller] hwndOwner = NULLHANDLE; // now, make sure the owner window is child of // HWND_DESKTOP... if it is not, we'll only disable // some dumb child window, which is not sufficient // V0.9.16 (2001-12-06) [umoeller] while ( (hwndOwner) && (hwndOwnersParent = WinQueryWindow(hwndOwner, QW_PARENT)) && (hwndOwnersParent != hwndDesktop) ) hwndOwner = hwndOwnersParent; if (!(pDlgData->hwndDlg = WinCreateWindow(HWND_DESKTOP, WC_FRAME, (PSZ)pcszDlgTitle, flStyle, // style; invisible for now 0, 0, 0, 0, hwndOwner, HWND_TOP, 0, // ID &fcData, NULL))) // presparams arc = DLGERR_CANNOT_CREATE_FRAME; else { HWND hwndDlg = pDlgData->hwndDlg; HWND hwndFocusItem = NULLHANDLE; RECTL rclClient; /* * 3) compute size of all controls * */ if (!(arc = Dlg2_CalcSizes(pDlgData))) { WinSubclassWindow(hwndDlg, pfnwpDialogProc); /* * 4) compute size of dialog client from total * size of all controls */ // calculate the frame size from the client size rclClient.xLeft = DLG_OUTER_SPACING_X * FACTOR_X; // 10; rclClient.yBottom = DLG_OUTER_SPACING_Y * FACTOR_Y; // 10; rclClient.xRight = pDlgData->szlClient.cx + 2 * (DLG_OUTER_SPACING_X * FACTOR_X) - 1; rclClient.yTop = pDlgData->szlClient.cy + 2 * (DLG_OUTER_SPACING_Y * FACTOR_Y) - 1; WinCalcFrameRect(hwndDlg, &rclClient, FALSE); // frame from client WinSetWindowPos(hwndDlg, 0, 10, 10, rclClient.xRight, rclClient.yTop, SWP_MOVE | SWP_SIZE | SWP_NOADJUST); arc = Dlg3_PositionAndCreate(pDlgData, &hwndFocusItem); /* * 7) WM_INITDLG, set focus * */ if (!WinSendMsg(pDlgData->hwndDlg, WM_INITDLG, (MPARAM)hwndFocusItem, (MPARAM)pCreateParams)) { // if WM_INITDLG returns FALSE, this means // the dlg proc has not changed the focus; // we must then set the focus here WinSetFocus(HWND_DESKTOP, hwndFocusItem); } } } } if (arc) { // error: clean up if (pDlgData->hwndDlg) { WinDestroyWindow(pDlgData->hwndDlg); pDlgData->hwndDlg = NULLHANDLE; } } else // no error: output dialog *phwndDlg = pDlgData->hwndDlg; Dlg9_Cleanup(&pDlgData); } } CATCH(excpt1) { arc = ERROR_PROTECTION_VIOLATION; } END_CATCH(); if (arc) { CHAR szErr[300]; sprintf(szErr, "Error %d occurred in " __FUNCTION__ ".", arc); winhDebugBox(hwndOwner, "Error in Dialog Manager", szErr); } WinSetPointer(HWND_DESKTOP, hptrOld); // V1.0.0 (2002-08-21) [umoeller] return arc; } /* *@@ dlghFormatDlg: * similar to dlghCreateDlg in that this can * dynamically format dialog items. * * The differences however are the following: * * -- This assumes that hwndDlg already points * to a valid dialog frame and that this * dialog should be modified according to * flFlags. * * This is what's used in XWorkplace for notebook * settings pages since these always have to be * based on a resource dialog (which is loaded * empty). * * flFlags can be any combination of the following: * * -- DFFL_CREATECONTROLS: paDlgItems points to * an array of cDlgItems DLGHITEM structures * (see dlghCreateDlg) which is used for creating * subwindows in hwndDlg. By using this flag, the * function will essentially work like dlghCreateDlg, * except that the frame is already created. * * If pszlClient is specified, it receives the required * size of the client to surround all controls properly. * You can then use dlghResizeFrame to resize the frame * with a bit of spacing, if desired. * *@@added V0.9.16 (2001-09-29) [umoeller] *@@changed V0.9.18 (2002-03-03) [umoeller]: added pszlClient, fixed output *@@changed V0.9.19 (2002-04-24) [umoeller]: added excpt handling */ APIRET dlghFormatDlg(HWND hwndDlg, // in: dialog frame to work on PCDLGHITEM paDlgItems, // in: definition array ULONG cDlgItems, // in: array item count (NOT array size) PCSZ pcszControlsFont, // in: font for ctls with CTL_COMMON_FONT ULONG flFlags, // in: DFFL_* flags PSIZEL pszlClient, // out: size of all controls (ptr can be NULL) PVOID *ppllControls) // out: new LINKLIST receiving HWNDs of created controls (ptr can be NULL) { APIRET arc = NO_ERROR; TRY_LOUD(excpt1) { ULONG ul; PDLGPRIVATE pDlgData = NULL; PLINKLIST pllControls = NULL; /* * 1) parse the table and create structures from it * */ if (ppllControls) pllControls = *(PLINKLIST*)ppllControls = lstCreate(FALSE); if (!(arc = Dlg0_Init(&pDlgData, pcszControlsFont, pllControls))) { if (!(arc = Dlg1_ParseTables(pDlgData, paDlgItems, cDlgItems))) { HWND hwndFocusItem; /* * 2) create empty dialog frame * */ pDlgData->hwndDlg = hwndDlg; /* * 3) compute size of all controls * */ Dlg2_CalcSizes(pDlgData); if (pszlClient) { pszlClient->cx = pDlgData->szlClient.cx + 2 * (DLG_OUTER_SPACING_X * FACTOR_X); pszlClient->cy = pDlgData->szlClient.cy + 2 * (DLG_OUTER_SPACING_Y * FACTOR_Y); } if (flFlags & DFFL_CREATECONTROLS) { if (!(arc = Dlg3_PositionAndCreate(pDlgData, &hwndFocusItem))) WinSetFocus(HWND_DESKTOP, hwndFocusItem); } } Dlg9_Cleanup(&pDlgData); } } CATCH(excpt1) { arc = ERROR_PROTECTION_VIOLATION; } END_CATCH(); if (arc) { CHAR szErr[300]; sprintf(szErr, "Error %d occurred in " __FUNCTION__ ".", arc); winhDebugBox(NULLHANDLE, "Error in Dialog Manager", szErr); } return arc; } /* *@@ dlghResizeFrame: * *@@added V0.9.18 (2002-03-03) [umoeller] */ VOID dlghResizeFrame(HWND hwndDlg, PSIZEL pszlClient) { // calculate the frame size from the client size RECTL rclClient; rclClient.xLeft = 10; rclClient.yBottom = 10; rclClient.xRight = pszlClient->cx; rclClient.yTop = pszlClient->cy; WinCalcFrameRect(hwndDlg, &rclClient, FALSE); // frame from client WinSetWindowPos(hwndDlg, 0, 10, 10, rclClient.xRight, rclClient.yTop, SWP_MOVE | SWP_SIZE | SWP_NOADJUST); } /* ****************************************************************** * * Dialog arrays * ********************************************************************/ /* *@@ dlghCreateArray: * creates a "dialog array" for dynamically * building a dialog template in memory. * * A dialog array is simply an array of * DLGHITEM structures, as you would normally * define them statically in the source. * However, there are situations where you * might want to leave out certain controls * depending on certain conditions, which * can be difficult with static arrays. * * As a result, these "array" functions have * been added to allow for adding static * DLGHITEM subarrays to a dynamic array in * memory, which can then be passed to the * formatter. * * Usage: * * 1) Call this function with the maximum * amount of DLGHITEM's that will need * to be allocated in cMaxItems. Set this * to the total sum of all DLGHITEM's * in all the subarrays. * * 2) For each of the subarrays, call * dlghAppendToArray to have the subarray * appended to the dialog array. * After each call, DLGARRAY.cDlgItemsNow * will contain the actual total count of * DLGHITEM's that were added. * * 3) Call dlghCreateDialog with the dialog * array. * * 4) Call dlghFreeArray. * * Sort of like this (error checking omitted): * + DLGHITEM dlgSampleFront = ... // always included + DLGHITEM dlgSampleSometimes = ... // not always included + DLGHITEM dlgSampleTail = ... // always included + + PDLGARRAY pArraySample = NULL; + // create array with sufficient size + dlghCreateArray( ARRAYITEMCOUNT(dlgSampleFront) + + ARRAYITEMCOUNT(dlgSampleSometimes) + + ARRAYITEMCOUNT(dlgSampleTail), + &pArraySample); + + // always include front + dlghAppendToArray(pArraySample, + dlgSampleFront, + ARRAYITEMCOUNT(dlgSampleFront)); + // include "sometimes" conditionally + if (...) + dlghAppendToArray(pArraySample, + dlgSampleSometimes, + ARRAYITEMCOUNT(dlgSampleSometimes)); + // include tail always + dlghAppendToArray(pArraySample, + dlgSampleTail, + ARRAYITEMCOUNT(dlgSampleTail)); + + // now create the dialog from the array + dlghCreateDialog(&hwndDlg, + hwndOwner, + FCF_ ... + fnwpMyDialogProc, + "Title", + pArray->paDlgItems, // dialog array! + pArray->cDlgItemsNow, // real count of items! + NULL, + NULL); + + dlghFreeArray(&pArraySample); * *@@added V0.9.16 (2001-10-15) [umoeller] */ APIRET dlghCreateArray(ULONG cMaxItems, PDLGARRAY *ppArray) // out: DLGARRAY { APIRET arc = NO_ERROR; PDLGARRAY pArray; if (pArray = NEW(DLGARRAY)) { ULONG cb; ZERO(pArray); if ( (cb = cMaxItems * sizeof(DLGHITEM)) && (pArray->paDlgItems = (DLGHITEM*)malloc(cb)) ) { memset(pArray->paDlgItems, 0, cb); pArray->cDlgItemsMax = cMaxItems; *ppArray = pArray; } else arc = ERROR_NOT_ENOUGH_MEMORY; if (arc) dlghFreeArray(&pArray); } else arc = ERROR_NOT_ENOUGH_MEMORY; return arc; } /* *@@ dlghFreeArray: * frees a dialog array created by dlghCreateArray. * *@@added V0.9.16 (2001-10-15) [umoeller] */ APIRET dlghFreeArray(PDLGARRAY *ppArray) { PDLGARRAY pArray; if ( (ppArray) && (pArray = *ppArray) ) { if (pArray->paDlgItems) free(pArray->paDlgItems); free(pArray); } else return ERROR_INVALID_PARAMETER; return NO_ERROR; } /* *@@ dlghAppendToArray: * appends a subarray of DLGHITEM's to the * given DLGARRAY. See dlghCreateArray for * usage. * * Returns: * * -- NO_ERROR * * -- ERROR_INVALID_PARAMETER * * -- DLGERR_ARRAY_TOO_SMALL: pArray does not * have enough memory to hold the new items. * The cMaxItems parameter given to dlghCreateArray * wasn't large enough. * *@@added V0.9.16 (2001-10-15) [umoeller] */ APIRET dlghAppendToArray(PDLGARRAY pArray, // in: dialog array created by dlghCreateArray PCDLGHITEM paItems, // in: subarray to be appended ULONG cItems) // in: subarray item count (NOT array size) { APIRET arc = NO_ERROR; if (pArray) { if ( (pArray->cDlgItemsMax >= cItems) && (pArray->cDlgItemsMax - pArray->cDlgItemsNow >= cItems) ) { // enough space left in the array: memcpy(&pArray->paDlgItems[pArray->cDlgItemsNow], paItems, // source cItems * sizeof(DLGHITEM)); pArray->cDlgItemsNow += cItems; } else arc = DLGERR_ARRAY_TOO_SMALL; } else arc = ERROR_INVALID_PARAMETER; return arc; } /* ****************************************************************** * * Standard dialogs * ********************************************************************/ /* *@@ fnwpMessageBox: * *@@added V0.9.19 (2002-04-24) [umoeller] */ MRESULT EXPENTRY fnwpMessageBox(HWND hwndBox, ULONG msg, MPARAM mp1, MPARAM mp2) { switch (msg) { case WM_HELP: { PFNHELP pfnHelp; if (pfnHelp = (PFNHELP)WinQueryWindowPtr(hwndBox, QWL_USER)) pfnHelp(hwndBox); return 0; } } return WinDefDlgProc(hwndBox, msg, mp1, mp2); } /* *@@ dlghCreateMessageBox: * *@@added V0.9.13 (2001-06-21) [umoeller] *@@changed V0.9.14 (2001-07-26) [umoeller]: fixed missing focus on buttons *@@changed V0.9.19 (2002-04-24) [umoeller]: added pfnHelp *@@changed V0.9.20 (2002-07-12) [umoeller]: made icon spacing wider *@@changed V0.9.20 (2002-08-10) [umoeller]: fixed missing close button *@@changed V1.0.0 (2002-08-16) [umoeller]: now using table alignment *@@changed WarpIN V1.0.11 (2006-05-22) [pr]: add to Tasklist */ APIRET dlghCreateMessageBox(HWND *phwndDlg, HWND hwndOwner, HPOINTER hptrIcon, PCSZ pcszTitle, PCSZ pcszMessage, PFNHELP pfnHelp, // in: help callback or NULL ULONG flFlags, PCSZ pcszFont, const MSGBOXSTRINGS *pStrings, PULONG pulAlarmFlag) // out: alarm sound to be played { APIRET arc; CONTROLDEF Icon = CONTROLDEF_ICON_WIDER(NULLHANDLE, 0), // made icon spacing wider V0.9.20 (2002-07-12) [umoeller] Spacing = CONTROLDEF_TEXT(NULL, 0, 1, 1), InfoText = CONTROLDEF_TEXT_WORDBREAK(NULL, 10, MSGBOX_TEXTWIDTH), Buttons[] = { CONTROLDEF_PUSHBUTTON(NULL, 1, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT), CONTROLDEF_PUSHBUTTON(NULL, 2, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT), CONTROLDEF_PUSHBUTTON(NULL, 3, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT), CONTROLDEF_HELPPUSHBUTTON(NULL, 4, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT) }; DLGHITEM MessageBoxFront[] = { START_TABLE_ALIGN, START_ROW(ROW_VALIGN_CENTER), CONTROL_DEF(&Icon), START_TABLE, START_ROW(ROW_VALIGN_CENTER), CONTROL_DEF(&Spacing), START_ROW(ROW_VALIGN_CENTER), CONTROL_DEF(&InfoText), START_ROW(ROW_VALIGN_CENTER), CONTROL_DEF(&Spacing), END_TABLE, START_ROW(ROW_VALIGN_CENTER), CONTROL_DEF(&Spacing), START_TABLE, START_ROW(ROW_VALIGN_CENTER), CONTROL_DEF(&Buttons[0]), CONTROL_DEF(&Buttons[1]), CONTROL_DEF(&Buttons[2]), }, MessageBoxHelp[] = { CONTROL_DEF(&Buttons[3]), }, MessageBoxTail[] = { END_TABLE, END_TABLE }; ULONG flButtons = flFlags & 0xF; // low nibble contains MB_YESNO etc. PDLGARRAY pArrayBox; PCSZ p0 = "Error", p1 = NULL, p2 = NULL; Icon.pcszText = (PCSZ)hptrIcon; InfoText.pcszText = pcszMessage; // now work on the three buttons of the dlg template: // give them proper titles or hide them if (flButtons == MB_OK) { p0 = pStrings->pcszOK; } else if (flButtons == MB_OKCANCEL) { p0 = pStrings->pcszOK; p1 = pStrings->pcszCancel; } else if (flButtons == MB_RETRYCANCEL) { p0 = pStrings->pcszRetry; p1 = pStrings->pcszCancel; } else if (flButtons == MB_ABORTRETRYIGNORE) { p0 = pStrings->pcszAbort; p1 = pStrings->pcszRetry; p2 = pStrings->pcszIgnore; } else if (flButtons == MB_YESNO) { p0 = pStrings->pcszYes; p1 = pStrings->pcszNo; } else if (flButtons == MB_YESNOCANCEL) { p0 = pStrings->pcszYes; p1 = pStrings->pcszNo; p2 = pStrings->pcszCancel; } else if (flButtons == MB_CANCEL) { p0 = pStrings->pcszCancel; } else if (flButtons == MB_ENTER) { p0 = pStrings->pcszEnter; } else if (flButtons == MB_ENTERCANCEL) { p0 = pStrings->pcszEnter; p1 = pStrings->pcszCancel; } else if (flButtons == MB_YES_YES2ALL_NO) { p0 = pStrings->pcszYes; p1 = pStrings->pcszYesToAll; p2 = pStrings->pcszNo; } // now set strings and hide empty buttons Buttons[0].pcszText = p0; if (p1) Buttons[1].pcszText = p1; else Buttons[1].flStyle &= ~WS_VISIBLE; if (p2) Buttons[2].pcszText = p2; else Buttons[2].flStyle &= ~WS_VISIBLE; // query default button IDs if (flFlags & MB_DEFBUTTON2) Buttons[1].flStyle |= BS_DEFAULT; else if (flFlags & MB_DEFBUTTON3) Buttons[2].flStyle |= BS_DEFAULT; else Buttons[0].flStyle |= BS_DEFAULT; *pulAlarmFlag = WA_NOTE; if (flFlags & (MB_ICONHAND | MB_ERROR)) *pulAlarmFlag = WA_ERROR; else if (flFlags & (MB_ICONEXCLAMATION | MB_WARNING)) *pulAlarmFlag = WA_WARNING; if (pfnHelp) Buttons[3].pcszText = pStrings->pcszHelp; if (!(arc = dlghCreateArray( ARRAYITEMCOUNT(MessageBoxFront) + ARRAYITEMCOUNT(MessageBoxHelp) + ARRAYITEMCOUNT(MessageBoxTail), &pArrayBox))) { if ( (!(arc = dlghAppendToArray(pArrayBox, MessageBoxFront, ARRAYITEMCOUNT(MessageBoxFront)))) && ( (!pfnHelp) || (!(arc = dlghAppendToArray(pArrayBox, MessageBoxHelp, ARRAYITEMCOUNT(MessageBoxHelp)))) ) && (!(arc = dlghAppendToArray(pArrayBox, MessageBoxTail, ARRAYITEMCOUNT(MessageBoxTail)))) ) { if (!(arc = dlghCreateDlg(phwndDlg, hwndOwner, FCF_TITLEBAR | FCF_SYSMENU | FCF_DLGBORDER | FCF_NOBYTEALIGN | FCF_CLOSEBUTTON, // was missing V0.9.20 (2002-08-10) [umoeller] fnwpMessageBox, pcszTitle, pArrayBox->paDlgItems, pArrayBox->cDlgItemsNow, NULL, pcszFont))) { // WarpIN V1.0.11 (2006-05-22) [pr] winhAddToTasklist(*phwndDlg, NULLHANDLE); // added help callback V0.9.19 (2002-04-24) [umoeller] WinSetWindowPtr(*phwndDlg, QWL_USER, (PVOID)pfnHelp); } } dlghFreeArray(&pArrayBox); } return arc; } /* *@@ dlghProcessMessageBox: * *@@added V0.9.13 (2001-06-21) [umoeller] */ ULONG dlghProcessMessageBox(HWND hwndDlg, ULONG ulAlarmFlag, ULONG flFlags) { ULONG ulrcDlg; ULONG flButtons = flFlags & 0xF; // low nibble contains MB_YESNO etc. winhCenterWindow(hwndDlg); if (flFlags & MB_SYSTEMMODAL) WinSetSysModalWindow(HWND_DESKTOP, hwndDlg); if (ulAlarmFlag) WinAlarm(HWND_DESKTOP, ulAlarmFlag); ulrcDlg = WinProcessDlg(hwndDlg); WinDestroyWindow(hwndDlg); if (flButtons == MB_OK) return MBID_OK; else if (flButtons == MB_OKCANCEL) switch (ulrcDlg) { case 1: return MBID_OK; default: return MBID_CANCEL; } else if (flButtons == MB_RETRYCANCEL) switch (ulrcDlg) { case 1: return MBID_RETRY; default: return MBID_CANCEL; } else if (flButtons == MB_ABORTRETRYIGNORE) switch (ulrcDlg) { case 2: return MBID_RETRY; case 3: return MBID_IGNORE; default: return MBID_ABORT; } else if (flButtons == MB_YESNO) switch (ulrcDlg) { case 1: return MBID_YES; default: return MBID_NO; } else if (flButtons == MB_YESNOCANCEL) switch (ulrcDlg) { case 1: return MBID_YES; case 2: return MBID_NO; default: return MBID_CANCEL; } else if (flButtons == MB_CANCEL) return MBID_CANCEL; else if (flButtons == MB_ENTER) return MBID_ENTER; else if (flButtons == MB_ENTERCANCEL) switch (ulrcDlg) { case 1: return MBID_ENTER; default: return MBID_CANCEL; } else if (flButtons == MB_YES_YES2ALL_NO) switch (ulrcDlg) { case 1: return MBID_YES; case 2: return MBID_YES2ALL; default: return MBID_NO; } return (MBID_CANCEL); } /* *@@ dlghMessageBox: * WinMessageBox replacement, which uses dlghCreateDlg * internally. * * This has all the flags of the standard call, * but looks much prettier. Besides, it allows * you to specify any icon to be displayed. * * Currently the following flStyle's are supported: * * -- MB_OK 0x0000 * -- MB_OKCANCEL 0x0001 * -- MB_RETRYCANCEL 0x0002 * -- MB_ABORTRETRYIGNORE 0x0003 * -- MB_YESNO 0x0004 * -- MB_YESNOCANCEL 0x0005 * -- MB_CANCEL 0x0006 * -- MB_ENTER 0x0007 (not implemented yet) * -- MB_ENTERCANCEL 0x0008 (not implemented yet) * * -- MB_YES_YES2ALL_NO 0x0009 * This is new: this has three buttons called "Yes" * (MBID_YES), "Yes to all" (MBID_YES2ALL), "No" (MBID_NO). * * -- MB_DEFBUTTON2 (for two-button styles) * -- MB_DEFBUTTON3 (for three-button styles) * * -- MB_ICONHAND * -- MB_ICONEXCLAMATION * * If (pfnHelp != NULL), a "Help" button is also added and * pfnHelp gets called when the user presses it or the F1 * key. * * Returns MBID_* codes like WinMessageBox. * *@@added V0.9.13 (2001-06-21) [umoeller] *@@changed V0.9.19 (2002-04-24) [umoeller]: added pfnHelp */ ULONG dlghMessageBox(HWND hwndOwner, // in: owner for msg box HPOINTER hptrIcon, // in: icon to display PCSZ pcszTitle, // in: title PCSZ pcszMessage, // in: message PFNHELP pfnHelp, // in: help callback or NULL ULONG flFlags, // in: standard message box flags PCSZ pcszFont, // in: font (e.g. "9.WarpSans") const MSGBOXSTRINGS *pStrings) // in: strings array { HWND hwndDlg; ULONG ulAlarmFlag; APIRET arc; CHAR szMsg[100]; if ( (!(arc = dlghCreateMessageBox(&hwndDlg, hwndOwner, hptrIcon, pcszTitle, pcszMessage, pfnHelp, flFlags, pcszFont, pStrings, &ulAlarmFlag))) && (hwndDlg) ) { // SHOW DIALOG return (dlghProcessMessageBox(hwndDlg, ulAlarmFlag, flFlags)); } sprintf(szMsg, "dlghCreateMessageBox reported error %u.", arc); WinMessageBox(HWND_DESKTOP, NULLHANDLE, "Error", szMsg, 0, MB_CANCEL | MB_MOVEABLE); return (DID_CANCEL); } /* *@@ cmnTextEntryBox: * common dialog for entering a text string. * The dialog has a descriptive text on top * with an entry field below and "OK" and "Cancel" * buttons. * * The string from the user is returned in a * new buffer, which must be free'd by the caller. * Returns NULL if the user pressed "Cancel". * * fl can be any combination of the following * flags: * * -- TEBF_REMOVETILDE: tilde ("~") characters * are removed from pcszTitle before setting * the title. Useful for reusing menu item * texts. * * -- TEBF_REMOVEELLIPSE: ellipse ("...") strings * are removed from pcszTitle before setting * the title. Useful for reusing menu item * texts. * * -- TEBF_SELECTALL: the default text in the * entry field is initially highlighted. * *@@added V0.9.15 (2001-09-14) [umoeller] */ PSZ dlghTextEntryBox(HWND hwndOwner, PCSZ pcszTitle, // in: dlg title PCSZ pcszDescription, // in: descriptive text above entry field PCSZ pcszDefault, // in: default text for entry field or NULL PCSZ pcszOK, // in: "OK" string PCSZ pcszCancel, // in: "Cancel" string ULONG ulMaxLen, // in: maximum length for entry ULONG fl, // in: TEBF_* flags PCSZ pcszFont) // in: font (e.g. "9.WarpSans") { CONTROLDEF Static = CONTROLDEF_TEXT_WORDBREAK( NULL, -1, 150), Entry = CONTROLDEF_ENTRYFIELD( NULL, 999, 150, SZL_AUTOSIZE), OKButton = CONTROLDEF_DEFPUSHBUTTON( NULL, DID_OK, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT), CancelButton = CONTROLDEF_PUSHBUTTON( NULL, DID_CANCEL, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT); DLGHITEM DlgTemplate[] = { START_TABLE, START_ROW(0), CONTROL_DEF(&Static), START_ROW(0), CONTROL_DEF(&Entry), START_ROW(0), CONTROL_DEF(&OKButton), CONTROL_DEF(&CancelButton), END_TABLE }; HWND hwndDlg = NULLHANDLE; PSZ pszReturn = NULL; XSTRING strTitle; xstrInitCopy(&strTitle, pcszTitle, 0); if (fl & (TEBF_REMOVEELLIPSE | TEBF_REMOVETILDE)) { ULONG ulOfs; if (fl & TEBF_REMOVEELLIPSE) { ulOfs = 0; while (xstrFindReplaceC(&strTitle, &ulOfs, "...", "")) ; } if (fl & TEBF_REMOVETILDE) { ulOfs = 0; while (xstrFindReplaceC(&strTitle, &ulOfs, "~", "")) ; } } Static.pcszText = pcszDescription; OKButton.pcszText = pcszOK; CancelButton.pcszText = pcszCancel; if (NO_ERROR == dlghCreateDlg(&hwndDlg, hwndOwner, FCF_FIXED_DLG, WinDefDlgProc, strTitle.psz, DlgTemplate, // DLGHITEM array ARRAYITEMCOUNT(DlgTemplate), NULL, pcszFont)) { HWND hwndEF = WinWindowFromID(hwndDlg, 999); winhCenterWindow(hwndDlg); winhSetEntryFieldLimit(hwndEF, ulMaxLen); if (pcszDefault) { WinSetWindowText(hwndEF, (PSZ)pcszDefault); if (fl & TEBF_SELECTALL) winhEntryFieldSelectAll(hwndEF); } WinSetFocus(HWND_DESKTOP, hwndEF); if (DID_OK == WinProcessDlg(hwndDlg)) pszReturn = winhQueryWindowText(hwndEF); WinDestroyWindow(hwndDlg); } xstrClear(&strTitle); return (pszReturn); } /* ****************************************************************** * * Dialog input handlers * ********************************************************************/ /* *@@ dlghSetPrevFocus: * "backward" function for rotating the focus * in a dialog when the "shift+tab" keys get * pressed. * * pllWindows must be a linked list with the * plain HWND window handles of the focussable * controls in the dialog. */ VOID dlghSetPrevFocus(PVOID pvllWindows) { PLINKLIST pllWindows = (PLINKLIST)pvllWindows; // check current focus HWND hwndFocus = WinQueryFocus(HWND_DESKTOP); PLISTNODE pNode = lstNodeFromItem(pllWindows, (PVOID)hwndFocus); BOOL fRestart = FALSE; while (pNode) { CHAR szClass[100]; // previos node pNode = pNode->pPrevious; if ( (!pNode) // no next, or not found: && (!fRestart) // avoid infinite looping if bad list ) { pNode = lstQueryLastNode(pllWindows); fRestart = TRUE; } if (pNode) { // check if this is a focusable control if (WinQueryClassName((HWND)pNode->pItemData, sizeof(szClass), szClass)) { if ( (strcmp(szClass, "#5")) // not static ) break; // else: go for next then } } } if (pNode) { WinSetFocus(HWND_DESKTOP, (HWND)pNode->pItemData); } } /* *@@ dlghSetNextFocus: * "forward" function for rotating the focus * in a dialog when the "ab" key gets pressed. * * pllWindows must be a linked list with the * plain HWND window handles of the focussable * controls in the dialog. */ VOID dlghSetNextFocus(PVOID pvllWindows) { PLINKLIST pllWindows = (PLINKLIST)pvllWindows; // check current focus HWND hwndFocus = WinQueryFocus(HWND_DESKTOP); PLISTNODE pNode = lstNodeFromItem(pllWindows, (PVOID)hwndFocus); BOOL fRestart = FALSE; while (pNode) { CHAR szClass[100]; // next focus in node pNode = pNode->pNext; if ( (!pNode) // no next, or not found: && (!fRestart) // avoid infinite looping if bad list ) { pNode = lstQueryFirstNode(pllWindows); fRestart = TRUE; } if (pNode) { // check if this is a focusable control if (WinQueryClassName((HWND)pNode->pItemData, sizeof(szClass), szClass)) { if ( (strcmp(szClass, "#5")) // not static ) break; // else: go for next then } } } if (pNode) { WinSetFocus(HWND_DESKTOP, (HWND)pNode->pItemData); } } /* *@@ dlghProcessMnemonic: * finds the control which matches usch * and gives it the focus. If this is a * static control, the next control in the * list is given focus instead. (Standard * dialog behavior.) * * Pass in usch from WM_CHAR. It is assumed * that the caller has already tested for * the "alt" key to be depressed. * *@@added V0.9.9 (2001-03-17) [umoeller] */ HWND dlghProcessMnemonic(PVOID pvllWindows, USHORT usch) { PLINKLIST pllWindows = (PLINKLIST)pvllWindows; HWND hwndFound = NULLHANDLE; PLISTNODE pNode = lstQueryFirstNode(pllWindows); CHAR szClass[100]; while (pNode) { HWND hwnd = (HWND)pNode->pItemData; if (WinSendMsg(hwnd, WM_MATCHMNEMONIC, (MPARAM)usch, 0)) { // according to the docs, only buttons and static // return TRUE to that msg; // if this is a static, give focus to the next // control // _Pmpf((__FUNCTION__ ": hwnd 0x%lX", hwnd)); // check if this is a focusable control if (WinQueryClassName(hwnd, sizeof(szClass), szClass)) { if (!strcmp(szClass, "#3")) // it's a button: click it WinSendMsg(hwnd, BM_CLICK, (MPARAM)TRUE, 0); else if (!strcmp(szClass, "#5")) { // it's a static: give focus to following control pNode = pNode->pNext; if (pNode) WinSetFocus(HWND_DESKTOP, (HWND)pNode->pItemData); } } else // any other control (are there any?): give them focus WinSetFocus(HWND_DESKTOP, hwnd); // in any case, stop hwndFound = hwnd; break; } pNode = pNode->pNext; } return (hwndFound); } /* *@@ dlghEnter: * presses the first button with BS_DEFAULT. */ BOOL dlghEnter(PVOID pvllWindows) { PLINKLIST pllWindows = (PLINKLIST)pvllWindows; PLISTNODE pNode = lstQueryFirstNode(pllWindows); CHAR szClass[100]; while (pNode) { HWND hwnd = (HWND)pNode->pItemData; if (WinQueryClassName(hwnd, sizeof(szClass), szClass)) { if (!strcmp(szClass, "#3")) // button { // _Pmpf((__FUNCTION__ ": found button")); if ( (WinQueryWindowULong(hwnd, QWL_STYLE) & (BS_PUSHBUTTON | BS_DEFAULT)) == (BS_PUSHBUTTON | BS_DEFAULT) ) { // _Pmpf((" is default!")); WinPostMsg(hwnd, BM_CLICK, (MPARAM)TRUE, // upclick 0); return (TRUE); } } } pNode = pNode->pNext; } return (FALSE); }