source: trunk/src/helpers/dialog.c@ 257

Last change on this file since 257 was 257, checked in by umoeller, 22 years ago

Fixes that have piled up in the last three months.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 159.1 KB
Line 
1
2/*
3 *@@sourcefile dialog.c:
4 * contains PM helper functions to create and
5 * auto-format dialogs from control arrays in memory.
6 *
7 * As a summary, this code allows you to get rid of
8 * PM dialog resources completely.
9 *
10 * See dlghCreateDlg for an introduction.
11 *
12 * See @dlg_algorithm for the gory details of the new
13 * algorithm used since V1.0.0. Even though much
14 * of this file was rewritten, the new dialog is
15 * backwards-compatible with all the hacks that existing
16 * dialogs might use for aligning things properly.
17 *
18 * In addition, this has dlghMessageBox (a WinMessageBox
19 * replacement) and some helper functions for simulating
20 * dialog behavior in regular window procs (see
21 * dlghSetPrevFocus and others).
22 *
23 * If you are out to find all workarounds to get certain
24 * buggy PM controls aligned right, this file is definitely
25 * the place.
26 *
27 * Usage: All PM programs.
28 *
29 * Function prefixes (new with V0.81):
30 * -- dlg* Dialog functions
31 *
32 * Note: Version numbering in this file relates to XWorkplace version
33 * numbering.
34 *
35 *@@added V0.9.9 (2001-04-01) [umoeller]
36 *@@header "helpers\dialog.h"
37 */
38
39/*
40 * Copyright (C) 2001-2002 Ulrich M”ller.
41 * This file is part of the "XWorkplace helpers" source package.
42 * This is free software; you can redistribute it and/or modify
43 * it under the terms of the GNU General Public License as published
44 * by the Free Software Foundation, in version 2 as it comes in the
45 * "COPYING" file of the XWorkplace main distribution.
46 * This program is distributed in the hope that it will be useful,
47 * but WITHOUT ANY WARRANTY; without even the implied warranty of
48 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
49 * GNU General Public License for more details.
50 */
51
52// #define DEBUG_DIALOG_WINDOWS 1
53 // blue frame is table
54 // green frame is column
55 // red frame is control
56
57#define OS2EMX_PLAIN_CHAR
58 // this is needed for "os2emx.h"; if this is defined,
59 // emx will define PSZ as _signed_ char, otherwise
60 // as unsigned char
61
62#define INCL_DOSPROCESS
63#define INCL_DOSEXCEPTIONS
64#define INCL_DOSERRORS
65
66#define INCL_WINWINDOWMGR
67#define INCL_WINFRAMEMGR
68#define INCL_WINPOINTERS
69#define INCL_WININPUT
70#define INCL_WINDIALOGS
71#define INCL_WINSTATICS
72#define INCL_WINBUTTONS
73#define INCL_WINENTRYFIELDS
74#define INCL_WINSTDCNR
75#define INCL_WINSYS
76
77#define INCL_GPIPRIMITIVES
78#define INCL_GPIBITMAPS
79#define INCL_GPILCIDS
80#include <os2.h>
81
82#include <stdlib.h>
83#include <string.h>
84#include <stdio.h>
85#include <setjmp.h>
86
87#include "setup.h" // code generation and debugging options
88
89#include "helpers\comctl.h"
90#include "helpers\dialog.h"
91#include "helpers\except.h"
92#include "helpers\gpih.h"
93#include "helpers\linklist.h"
94#include "helpers\nlscache.h"
95#include "helpers\standards.h"
96#include "helpers\stringh.h"
97#include "helpers\textview.h"
98#include "helpers\winh.h"
99#include "helpers\xstring.h"
100
101#pragma hdrstop
102
103/*
104 *@@category: Helpers\PM helpers\Dialog templates
105 */
106
107/* ******************************************************************
108 *
109 * Glossary entries
110 *
111 ********************************************************************/
112
113/*
114 *@@gloss: dlg_using_macros Using the Dialog Formatter Macros
115 *
116 * The DLGHITEM macros are:
117 *
118 * -- START_TABLE starts a new table. The tables may nest,
119 * but must each be properly terminated with END_TABLE.
120 *
121 * Note that as opposed to HTML tables, the columns
122 * in the rows of the table are NOT aligned under each
123 * other. If that is what you want, use START_TABLE_ALIGN
124 * instead.
125 *
126 * -- START_GROUP_TABLE(pDef) starts a group. This
127 * behaves exacly like START_TABLE, but in addition,
128 * it produces a static group control around the table.
129 * Useful for group boxes. pDef must point to a
130 * CONTROLDEF describing the control to be used for
131 * the group (usually a WC_STATIC with SS_GROUP style),
132 * whose size parameter is ignored.
133 *
134 * As with START_TABLE, START_GROUP_TABLE must be
135 * terminated with END_TABLE.
136 *
137 * As with START_TABLE, columns in the rows of the table
138 * are NOT aligned under each other. If that is what you
139 * want, use START_GROUP_TABLE_ALIGN instead.
140 *
141 * -- START_ROW(fl) starts a new row in a table (regular
142 * or group). This must also be the first item after
143 * the (group) table tag.
144 *
145 * fl specifies formatting flags for the row. This
146 * can be one of ROW_VALIGN_BOTTOM, ROW_VALIGN_CENTER,
147 * ROW_VALIGN_TOP and affects all items in the row.
148 *
149 * -- CONTROL_DEF(pDef) defines a control in a table row.
150 * pDef must point to a CONTROLDEF structure.
151 *
152 * Again, there is is NO information in CONTROLDEF
153 * about a control's _position_. Instead, the structure
154 * only contains the _size_ of the control. All
155 * positions are computed by this function, depending
156 * on the sizes of the controls and their nesting within
157 * the various tables.
158 *
159 * If you specify SZL_AUTOSIZE with either cx or cy
160 * or both, the size of the control is even computed
161 * automatically. Presently, this only works for statics
162 * with SS_TEXT, SS_ICON, and SS_BITMAP, push buttons,
163 * and radio and check boxes.
164 *
165 * Unless separated with START_ROW items, subsequent
166 * control items will be considered to be in the same
167 * row (== positioned next to each other).
168 *
169 * Columns will only be aligned horizontally if the
170 * container table was specified with START_TABLE_ALIGN
171 * or START_GROUP_TABLE_ALIGN.
172 *
173 * There are a few rules, whose violation will produce
174 * an error:
175 *
176 * -- The entire array must be enclosed in a table
177 * (the "root" table).
178 *
179 * -- After START_TABLE or START_GROUP_TABLE, there must
180 * always be a START_ROW first.
181 *
182 * While it is possible to set up the CONTROLDEFs manually
183 * as static structures, I recommend using the bunch of
184 * other macros that were defined in dialog.h for this.
185 * For example, you can use CONTROLDEF_PUSHBUTTON to create
186 * a push button, and many more.
187 *
188 * To create a dialog, set up arrays like the following:
189 *
190 + // control definitions referenced by DlgTemplate:
191 + CONTROLDEF
192 + (1) GroupDef = CONTROLDEF_GROUP("Group",
193 + -1, // ID
194 + SZL_AUTOSIZE,
195 + SZL_AUTOSIZE),
196 + (2) CnrDef = CONTROLDEF_CONTAINER(-1, // ID,
197 + 50,
198 + 50),
199 + (3) Static = CONTROLDEF_TEXT("Static below cnr",
200 + -1, // ID
201 + SZL_AUTOSIZE,
202 + SZL_AUTOSIZE),
203 + (4) OKButton = CONTROLDEF_DEFPUSHBUTTON("~OK",
204 + DID_OK,
205 + SZL_AUTOSIZE,
206 + SZL_AUTOSIZE),
207 + (5) CancelButton = CONTROLDEF_PUSHBUTTON("~Cancel",
208 + DID_CANCEL,
209 + SZL_AUTOSIZE,
210 + SZL_AUTOSIZE);
211 +
212 + DLGHITEM DlgTemplate[] =
213 + {
214 + START_TABLE, // root table, required
215 + START_ROW(0), // row 1 in the root table, required
216 + // create group on top
217 + (1) START_GROUP_TABLE(&Group),
218 + START_ROW(0),
219 + (2) CONTROL_DEF(&CnrDef),
220 + START_ROW(0),
221 + (3) CONTROL_DEF(&Static),
222 + END_TABLE, // end of group
223 + START_ROW(0), // row 2 in the root table
224 + // two buttons next to each other
225 + (4) CONTROL_DEF(&OKButton),
226 + (5) CONTROL_DEF(&CancelButton),
227 + END_TABLE
228 + }
229 *
230 * This will produce a dlg like this:
231 *
232 + ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
233 + º º
234 + º ÚÄ Group (1) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º
235 + º ³ ³ º
236 + º ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ º
237 + º ³ ³ ³ ³ º
238 + º ³ ³ Cnr inside group (2) ³ ³ º
239 + º ³ ³ ³ ³ º
240 + º ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ º
241 + º ³ ³ º
242 + º ³ Static below cnr (3) ³ º
243 + º ³ ³ º
244 + º ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º
245 + º º
246 + º ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º
247 + º ³ OK (4) ³ ³ Cancel (5) ³ º
248 + º ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º
249 + º º
250 + ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍŒ
251 */
252
253/*
254 *@@gloss: dlg_boxmodel Dialog formatter box model
255 *
256 * We now use the CSS box model for the dialog
257 * formatter (V1.0.0).
258 *
259 + ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
260 + ³ margin ³
261 + ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³
262 + ³ ³# border ###########³ ³
263 + ³ ³##ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿##³ ³
264 + ³ ³##³ padding ³##³ ³
265 + ³ ³##³ ÚÄÄÄÄÄÄÄÄÄ¿ ³##³ ³
266 + ³ ³##³ ³ content ³ ³##³ ³
267 + ³ ³##³ ÀÄÄÄÄÄÄÄÄÄÙ ³##³ ³
268 + ³ ³##³ ³##³ ³
269 + ³ ³##ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ##³ ³
270 + ³ ³####################³ ³
271 + ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³
272 + ³ ³
273 + ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
274 *
275 * See "http://www.w3.org/TR/REC-CSS1#formatting-model".
276 *
277 * Remark on the box model for subtables:
278 *
279 * We do not want to apply spacing twice for nested tables. This
280 * is especially important for group boxes (i.e. tables which
281 * have a PM group static control created around them). As a
282 * result, the definition is that ONLY COLUMNS HAVE SPACING,
283 * EVER. Neither tables nor rows do.
284 */
285
286/*
287 *@@gloss: dlg_algorithm Dialog formatter algorithm
288 *
289 * See @dlg_boxmodel for information about how the
290 * rectangles are defined.
291 *
292 * The dialog formatter allows you to specify PM dialog layouts
293 * as nested tables, without having to specify a single control
294 * position. In fact, it is impossible to give the dialog
295 * formatter fixed positions. Instead, you give the formatter
296 * the control _sizes_, and everything is layed out automatically.
297 * You may even have the formatter compute the sizes automatically
298 * based on the control classes and values (strings); it is possible
299 * to create dialogs without specifying a single size also.
300 *
301 * There are several tricks to how this works.
302 *
303 * 1) The dialog is supposed to consist of one outer table,
304 * which in turn has rows. The rows in turn have columns.
305 * We process the outer table by iterating over its rows
306 * (see ProcessTable). For each row, we iterate over the
307 * columns (see ProcessRow). Most of the processing then
308 * actually occurs in ProcessColumn.
309 *
310 * 2) Each column can contain a PM control, as specified in a
311 * CONTROLDEF structure on input. Alternatively, a column
312 * can be a nested subtable (with more rows and columns).
313 * If so, ProcessColumn recurses back into ProcessTable
314 * (which again goes into ProcessRow and ProcessColumn).
315 * There is no limit to how deep tables may nest, except
316 * for the stack size of the current thread, of course.
317 *
318 * 3) This whole recursive iteration is performed several times.
319 * In fact, we run through the entire data up to seven times
320 * presently. Only in the last run, we actually create the
321 * controls. Details follow.
322 *
323 * 4) We have sophisticated means for computing the size of
324 * certain controls automatically. See CalcAutoSize.
325 *
326 * The algorithm used here has been greatly reworked with V1.0.0.
327 * Essentially, the new trick is that we first compute all widths
328 * (which requires several iterations since widths may now inherit
329 * from each other), then all heights (which, for many controls,
330 * depend on the widths), then all positions. The previous approach
331 * to calculate widths and heights at the same time has turned out
332 * to not work very well.
333 *
334 * With each of the seven recursive iterations, ProcessTable,
335 * ProcessRow, and ProcessColumn receive a ProcessMode variable,
336 * which is just an integer that is raised with each run. These
337 * are the PROCESS_* constants all over the code.
338 *
339 * In the seven iterations, the following happens:
340 *
341 * 1) PROCESS_1_CALC_MIN_WIDTHS: calculates cxMinContent of all
342 * columns, rows, and tables from the inside to the outside
343 * recursively. cxMinContent specifies the minimum width of
344 * the column, row, and table, respectively. This value is
345 * required for all subsequent computations.
346 *
347 * This works as follows:
348 *
349 * -- From the root table, process all rows. [1]
350 *
351 * -- From each row, process all columns.
352 *
353 * -- For each column, calculate the minimum size of
354 * the content.
355 *
356 * (*) If the column is a control leaf node (i.e.
357 * does not hold a subtable), get the minimum
358 * size of the control.
359 *
360 * (*) If the CONTROLDEF specified an explicit
361 * width, copy it to cxMinContent.
362 *
363 * (*) If the CONTROLDEF specified SZL_AUTOSIZE
364 * for cx, evaluate the control type (icon,
365 * bitmap, text etc.) and set the minimum size
366 * to the result. See CalcAutoSize.
367 *
368 * (*) The control may specify its desired width
369 * as a percentage of the parent table's
370 * width. In that case, set the cxMinContent
371 * to 0 for now because it is to be ignored
372 * in the computation of the parent row and
373 * table widths.
374 *
375 * (*) If the column is a subtable, recurse with
376 * the subtable ([1] above) by calling ProcessTable
377 * again. Then set the control's cxMinContent to
378 * the table's cxMinBox, which was set by the
379 * subtable's ProcessTable (see below).
380 *
381 * After this, we have set the column's cxMinContent
382 * to either a minimum size (explicit or automatic )
383 * or 0.
384 *
385 * -- Set the row's cxMinBox to the sum of the minimum
386 * widths of the contained columns, with padding,
387 * border, and margin applied to each column.
388 *
389 * -- Set the table's cxMinBox to the cxMinBox of the table's
390 * widest row.
391 *
392 * 2) PROCESS_2_CALC_RELATIVE_WIDTHS: calculates the widths of
393 * those columns that have specified that their size should
394 * be a percentage of the parent table's width, and calculate
395 * the content rectangles of all columns and the box rectangles
396 * of the parent rows and tables. Again, do this recursively
397 * from the inside columns to the outside tables.
398 *
399 * First, we inherit table sizes from parent tables in
400 * ProcessTable. If a column has a subtable that has the
401 * TABLE_INHERIT_SIZE table flag set, compute a new cxMinBox
402 * for that table. _After_ that, recurse into the table to allow
403 * subtables of that subtable to inherit that size as well.
404 *
405 * In the columns, this then sets cpContent.cx in the columns,
406 * with the following processing in the control leaf columns:
407 *
408 * (*) If the control has specified an explicit or automatic
409 * width, we have set cxMinContent in PROCESS_1_CALC_MIN_WIDTHS;
410 * copy that width to cpContent.cx.
411 *
412 * (*) However, if the control had specified its desired
413 * width as a percentage of the parent table's width,
414 * calculate the cpContent.cx for the control from the
415 * table's cxMinBox (from PROCESS_1_CALC_MIN_WIDTHS).
416 *
417 * In the rows and tables, cpBox.cx and cpTable.cx is set as
418 * above from the column cpContent.cx values, with spacing applied.
419 *
420 * 3) PROCESS_3_ALIGN_COLUMNS: if the table has the TABLE_ALIGN_COLUMNS
421 * flag set, for each column in the table, find the widest
422 * instance in all rows, and set the cpContent.cx of all
423 * columns in that row to that width.
424 *
425 * 4) PROCESS_4_REMAINDER_WIDTHS: for all controls that have
426 * SZL_REMAINDER set for their widths, fill those so that
427 * they can use up the remaining width in the row.
428 *
429 * 5) PROCESS_5_CALC_HEIGHTS: Now that we know all widths,
430 * calculate the heights. We set cpContent.cy of all columns,
431 * cpRowBox.cy of all rows, and cpTableBox.cy of the tables,
432 * with the following processing in the control leaf columns:
433 *
434 * (*) If the control has specified an explicit or automatic
435 * height, use or compute that. In the case of autosize
436 * height, we now know the final width of the control,
437 * which is important for computing the height of multi-line
438 * static controls (which depend on the width, obviously).
439 * The new approach finally works for the case where a
440 * multi-line static control specifies its width as a
441 * percentage of the parent table, but still wants an
442 * autosize height.
443 *
444 * 6) PROCESS_6_CALC_POSITIONS: Now that we know all heights and
445 * widths, for each column, calculate cpContent.x and y.
446 *
447 * 7) PROCESS_7_CREATE_CONTROLS: finally creates the controls
448 * at the cpContent.x and y position, with the cpContent.cx
449 * and cy size.
450 */
451
452/* ******************************************************************
453 *
454 * Private declarations
455 *
456 ********************************************************************/
457
458/*
459 *@@ DLGPRIVATE:
460 * private data to the dlg manager, allocated
461 * by dlghCreateDlg.
462 *
463 * This only exists while the dialog is being
464 * created and is not stored with the new dialog.
465 */
466
467typedef struct _DLGPRIVATE
468{
469 HWND hwndDlg; // dialog frame
470
471 // definition data
472 LINKLIST llTables; // linked list of TABLEDEF structs
473
474 HWND hwndFirstFocus,
475 hwndDefPushbutton; // V0.9.14 (2001-08-21) [umoeller]
476
477 POINTL ptlTotalOfs;
478
479 PLINKLIST pllControls; // linked list of HWNDs in the order
480 // in which controls were created;
481 // ptr can be NULL
482
483 PCSZ pcszControlsFont; // from dlghCreateDlg
484
485 // size of the client to be created
486 SIZEL szlClient;
487
488 // various cached data V0.9.14 (2001-08-01) [umoeller]
489 HPS hps;
490 PCSZ pcszFontLast;
491 LONG lcidLast;
492 FONTMETRICS fmLast;
493
494 LONG cxBorder,
495 cyBorder; // cached now V0.9.19 (2002-04-17) [umoeller]
496
497 double dFactorX, // correlation factors for dialog units
498 dFactorY; // V0.9.19 (2002-04-24) [umoeller]
499
500 ULONG flNeedsProcessing; // flags to avoid unnecessary recursions:
501 #define FL_REMAINDER_WIDTHS 0x0001
502 // dialog needs PROCESS_4_REMAINDER_WIDTHS processing
503
504} DLGPRIVATE, *PDLGPRIVATE;
505
506// macros for the dlg units conversion;
507#define FACTOR_X (pDlgData->dFactorX)
508#ifdef USE_SQUARE_CORRELATION
509#define FACTOR_Y (pDlgData->dFactorX)
510#else
511#define FACTOR_Y (pDlgData->dFactorY)
512#endif
513
514typedef struct _COLUMNDEF *PCOLUMNDEF;
515typedef struct _ROWDEF *PROWDEF;
516typedef struct _TABLEDEF *PTABLEDEF;
517
518/*
519 *@@ CONTROLPOS:
520 * control position. We don't want to use SWP.
521 * We could have used RECTL, but the xRight
522 * etc. fields are always confusing with what
523 * we're doing... so we just define this.
524 */
525
526typedef struct _CONTROLPOS
527{
528 LONG x,
529 y,
530 cx,
531 cy;
532} CONTROLPOS, *PCONTROLPOS;
533
534/*
535 *@@ enProcessMode:
536 * enumeration that specifies the current processing
537 * mode while recursing through the tables, rows,
538 * and columns.
539 *
540 * For each processing mode, ProcessAll gets called
541 * while the dialog is being set up, which in turn
542 * calls ProcessTable for all tables, which in turn
543 * calls ProcessRow for all rows in that table,
544 * which in turn calls ProcessColumn for all rows
545 * in that column. ProcessColumn will recurse back
546 * into ProcessTable if a column represents a
547 * subtable.
548 *
549 * See @dlg_algorithm for what processing takes
550 * place with each of the process modes.
551 */
552
553typedef enum _enProcessMode
554{
555 PROCESS_1_CALC_MIN_WIDTHS,
556 PROCESS_2_CALC_RELATIVE_WIDTHS,
557 PROCESS_3_ALIGN_COLUMNS,
558 PROCESS_4_REMAINDER_WIDTHS,
559 PROCESS_5_CALC_HEIGHTS,
560 PROCESS_6_CALC_POSITIONS,
561 PROCESS_7_CREATE_CONTROLS
562} enProcessMode;
563
564/*
565 *@@ COLUMNDEF:
566 * representation of a column in a table row.
567 *
568 * Each COLUMNDEF is stored in a linked list
569 * in ROWDEF.llColumns.
570 *
571 * Such a column represents either a PM control
572 * window or another table, which may therefore
573 * be nested.
574 *
575 * See @dlg_algorithm for the new algorithm
576 * used since V1.0.0.
577 *
578 * With V1.0.0, we now follow the CSS box model
579 * to render these things better without all
580 * the hacks I am no longer able to understand
581 * myself. See @dlg_box_model for details.
582 *
583 *@@changed V1.0.0 (2002-08-18) [umoeller]: completely replaced
584 */
585
586typedef struct _COLUMNDEF
587{
588 // 1) input table set up by the DLGHITEM parser (CreateColumn):
589
590 // a) column position in recursion hierarchy
591
592 PROWDEF pOwningRow; // row whose linked list this column belongs to
593
594 PTABLEDEF pNestedTable; // if != NULL, this column represents a nested table,
595 // and this points to the contained table definition
596 // with which we must recurse back into ProcessTable().
597 // In that case, if (pcszClass != NULL) also, it
598 // must represent the PM static group control to
599 // create around the table.
600 // If (pNestedTable == NULL), this column is a control
601 // leaf node, and pcszClass and the following represent
602 // the control to create for the column.
603
604 ULONG ulColumnIndex; // zero-based column index in parent row
605 // V1.0.0 (2002-08-18) [umoeller]
606
607 // b) information for control window to be created:
608
609 PCSZ pcszClass; // class of control to create; if NULL, create no
610 // control; NULL is valid only for subtable columns
611 PCSZ pcszText; // window text; if NULL, it is replaced with "" in
612 // ColumnCreateControl
613 ULONG flStyle; // window style (should have at least WS_VISIBLE)
614 USHORT usID; // window ID or -1 for "don't care"
615 PCSZ pcszFont; // font to use for this window; this has CTL_COMMON_FONT
616 // resolved to the dialog default font already
617 SIZEL szlProposed; // proposed size for the control; copied from the
618 // CONTROLDEF with the dialog units already converted
619 // to pixels. Can be
620 // -- SZL_AUTOSIZE (-1) if the control wants to be autosized,
621 // -- SZL_REMAINDER (0) if the control wants the remaining
622 // space in its column,
623 // -- a negative number from [-100, -1] to specify the width
624 // as a percentage of the parent table _box_.
625 PVOID pvCtlData; // control-specific data for WinCreateWindow
626
627 // c) spacing:
628
629 RECTL rclPadding, // spacing, in pixels, to apply to the four borders of
630 rclMargin; // the content to get the box; calculated from CONTROLDEF
631 // V1.0.0 (2002-08-16) [umoeller]
632
633 LONG cxSpacingTotal; // sum of rclPadding and rclMargin xLeft and
634 // xRight, respectively
635
636 // 2) data from CalcAutoSize:
637
638 SIZEL szlAuto;
639
640 // 3) result data after recursion:
641
642 LONG cxMinContent; // minimum width of the content box;
643 // computed in PROCESS_1_CALC_MIN_WIDTHS
644
645 CONTROLPOS cpContent; // cx computed in PROCESS_2_CALC_RELATIVE_WIDTHS,
646 // cy computed in PROCESS_5_CALC_HEIGHTS,
647 // x and y computed in PROCESS_6_CALC_POSITIONS;
648 // this is where the control will be created
649
650 CONTROLPOS cpBox; // cx and cy computed in PROCESS_5_CALC_HEIGHTS,
651 // x and y computed in PROCESS_6_CALC_POSITIONS
652
653 HWND hwndControl; // created control after PROCESS_7_CREATE_CONTROLS;
654 // for tables, this is only set if we have created
655 // a PM group around them. Note: For text box
656 // controls, we create the control earlier,
657 // in CalcAutoSizeTextView during PROCESS_5_CALC_HEIGHTS,
658 // because only then we can ask the control for
659 // its required height.
660
661} COLUMNDEF;
662
663/*
664 *@@ ROWDEF:
665 * representation of a row in a table.
666 *
667 * Each ROWDEF is stored in a linked list in
668 * TABLEDEF.llRows and in turn contains a
669 * linked list of the contained COLUMNDEF structs
670 * in its llColumns member.
671 */
672
673typedef struct _ROWDEF
674{
675 // input table set up by the DLGHITEM parser:
676
677 PTABLEDEF pOwningTable; // table whose linked list this row belongs to
678
679 LINKLIST llColumns; // contains COLUMNDEF structs, no auto-free
680
681 ULONG flRowFormat; // one of:
682 // -- ROW_VALIGN_BOTTOM 0x0000
683 // -- ROW_VALIGN_CENTER 0x0001
684 // -- ROW_VALIGN_TOP 0x0002
685
686 ULONG cColumns; // no. of columns in this row
687 // V1.0.0 (2002-08-18) [umoeller]
688
689 // result data after recursion:
690
691 LONG cxMinBox; // set by PROCESS_1_CALC_MIN_WIDTHS to the sum of
692 // the minimum widths of the contained columns,
693 // with padding, border, and margin applied to each
694 // column
695
696 CONTROLPOS cpRowBox; // cx computed in PROCESS_2_CALC_RELATIVE_WIDTHS,
697 // cy computed in PROCESS_5_CALC_HEIGHTS,
698 // x and y computed in PROCESS_6_CALC_POSITIONS
699
700 ULONG cWantRemainders; // count of controls in the row that have
701 // SZL_REMAINDER set; counted in ProcessColumn
702 // with PROCESS_2_CALC_RELATIVE_WIDTHS
703 // so that we can quickly process
704 // PROCESS_4_REMAINDER_WIDTHS
705
706 LONG cxNonRemainder; // sum of the widths of those controls that do
707 // not have SZL_REMAINDER set; counted in
708 // ProcessColumn with PROCESS_3_ALIGN_COLUMNS
709 // so that we can quickly process
710 // PROCESS_4_REMAINDER_WIDTHS
711
712} ROWDEF;
713
714/*
715 *@@ TABLEDEF:
716 * representation of a table. If pOwningColumn
717 * is NULL, this is the dialog's root table.
718 * Otherwise this is a nested subtable in the
719 * specified column.
720 *
721 * Each TABLEDEF has its contained rows
722 * in the llRows member.
723 */
724
725typedef struct _TABLEDEF
726{
727 // input table set up by the DLGHITEM parser:
728
729 PCOLUMNDEF pOwningColumn; // != NULL if this is a nested table
730
731 LINKLIST llRows; // contains ROWDEF structs, no auto-free
732
733 ULONG flTable; // TABLE_* flags, copied from DLGHITEM
734
735 // result data after recursion:
736
737 LONG cxMinBox; // set by PROCESS_1_CALC_MIN_WIDTHS to the cxMinBox of
738 // the widest row
739
740 PROWDEF pWidestRow; // used during PROCESS_2_CALC_RELATIVE_WIDTHS to
741 // calculate percentage widths
742
743 CONTROLPOS cpTableBox; // cx computed in PROCESS_2_CALC_RELATIVE_WIDTHS,
744 // cy computed in PROCESS_5_CALC_HEIGHTS,
745 // x and y computed in PROCESS_6_CALC_POSITIONS
746
747} TABLEDEF;
748
749/* ******************************************************************
750 *
751 * Debug frames
752 *
753 ********************************************************************/
754
755#ifdef DEBUG_DIALOG_WINDOWS
756
757STATIC PFNWP G_pfnwpDebugStaticOrig = NULL;
758
759/*
760 *@@ fnwpDebugFrame:
761 * window proc for the subclassed static that is used in
762 * debug mode to mark rectangles in the dialog. This
763 * was added to support painting dotted rectangles. The
764 * line type is expected to sit in QWL_USER. We use
765 * PP_FOREGROUNDCOLOR as the paint color for the rectangle.
766 *
767 * This is only compiled if DEBUG_DIALOG_WINDOWS is
768 * set.
769 *
770 *@@added V1.0.0 (2002-08-16) [umoeller]
771 */
772
773STATIC MRESULT EXPENTRY fnwpDebugFrame(HWND hwndBox, ULONG msg, MPARAM mp1, MPARAM mp2)
774{
775 MRESULT mrc = 0;
776
777 switch (msg)
778 {
779 case WM_PAINT:
780 {
781 LONG lcol = winhQueryPresColor2(hwndBox,
782 PP_FOREGROUNDCOLOR,
783 PP_FOREGROUNDCOLORINDEX,
784 FALSE,
785 -1);
786 LONG lLineType = WinQueryWindowULong(hwndBox, QWL_USER);
787
788 HPS hps;
789 if (hps = WinBeginPaint(hwndBox, NULLHANDLE, NULL))
790 {
791 RECTL rcl;
792 POINTL ptl;
793 WinQueryWindowRect(hwndBox, &rcl);
794
795 gpihSwitchToRGB(hps);
796
797 GpiSetColor(hps, lcol);
798 GpiSetLineType(hps, lLineType);
799 ptl.x = 0;
800 ptl.y = 0;
801 GpiMove(hps, &ptl);
802 ptl.x = rcl.xRight - 1;
803 ptl.y = rcl.yTop - 1;
804 GpiBox(hps,
805 DRO_OUTLINE,
806 &ptl,
807 0,
808 0);
809 WinEndPaint(hps);
810 }
811 }
812 break;
813
814 default:
815 mrc = G_pfnwpDebugStaticOrig(hwndBox, msg, mp1, mp2);
816 }
817
818 return mrc;
819}
820
821/*
822 *@@ CreateDebugFrame:
823 * in debug mode, creates a subclassed static that
824 * shows the rectangles that were produced in the
825 * dialog. See fnwpDebugFrame.
826 *
827 * This is only compiled if DEBUG_DIALOG_WINDOWS is
828 * set.
829 *
830 *@@added V1.0.0 (2002-08-16) [umoeller]
831 */
832
833HWND CreateDebugFrame(HWND hwndParent,
834 LONG x,
835 LONG y,
836 LONG cx,
837 LONG cy,
838 LONG lcol,
839 LONG lLineType)
840{
841 HWND hwndDebug;
842 if (hwndDebug = WinCreateWindow(hwndParent,
843 WC_STATIC,
844 "",
845 WS_VISIBLE,
846 x,
847 y,
848 cx,
849 cy,
850 hwndParent, // owner
851 HWND_BOTTOM,
852 -1,
853 NULL,
854 NULL))
855 {
856 G_pfnwpDebugStaticOrig = WinSubclassWindow(hwndDebug, fnwpDebugFrame);
857 winhSetPresColor(hwndDebug, PP_FOREGROUNDCOLOR, lcol);
858 WinSetWindowULong(hwndDebug, QWL_USER, lLineType);
859 }
860
861 return hwndDebug;
862}
863
864#endif // DEBUG_DIALOG_WINDOWS
865
866/* ******************************************************************
867 *
868 * Column processing
869 *
870 ********************************************************************/
871
872// forward declaration so we can recurse into tables from columns
873
874STATIC APIRET ProcessTable(PTABLEDEF pTableDef,
875 const CONTROLPOS *pcpTableBox,
876 enProcessMode ProcessMode,
877 PDLGPRIVATE pDlgData);
878
879/*
880 *@@ SetDlgFont:
881 * refreshes the cached font data in DLGPRIVATE
882 * for the given font. Used all the time with
883 * CalcAutoSize.
884 *
885 * In DLGPRIVATE, this modifies hps, lcidLast,
886 * and pcszFontLast. After this, the FONTMETRICS
887 * in fmLast are set.
888 *
889 *@@added V0.9.16 (2001-10-11) [umoeller]
890 */
891
892STATIC VOID SetDlgFont(PCSZ pcszFontThis,
893 PDLGPRIVATE pDlgData)
894{
895 LONG lPointSize = 0;
896
897 if (!pDlgData->hps)
898 pDlgData->hps = WinGetPS(pDlgData->hwndDlg);
899
900 // check if we can reuse font data from last time
901 // V0.9.14 (2001-08-01) [umoeller]
902 if (strhcmp(pcszFontThis, // can be NULL!
903 pDlgData->pcszFontLast))
904 {
905 // different font than last time:
906
907 // delete old font?
908 if (pDlgData->lcidLast)
909 {
910 GpiSetCharSet(pDlgData->hps, LCID_DEFAULT); // LCID_DEFAULT == 0
911 GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
912 }
913
914 if (pcszFontThis)
915 {
916 // create new font
917 pDlgData->lcidLast = gpihFindPresFont(NULLHANDLE, // no window yet
918 FALSE,
919 pDlgData->hps,
920 pcszFontThis,
921 &pDlgData->fmLast,
922 &lPointSize);
923
924 GpiSetCharSet(pDlgData->hps, pDlgData->lcidLast);
925 if (pDlgData->fmLast.fsDefn & FM_DEFN_OUTLINE)
926 gpihSetPointSize(pDlgData->hps, lPointSize);
927 }
928 else
929 {
930 // use default font:
931 // @@todo handle presparams, maybe inherited?
932 GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
933 GpiQueryFontMetrics(pDlgData->hps,
934 sizeof(pDlgData->fmLast),
935 &pDlgData->fmLast);
936 }
937
938 pDlgData->pcszFontLast = pcszFontThis; // can be NULL
939 }
940}
941
942/*
943 *@@ CalcAutoSizeTextSingle:
944 * implementation for CalcAutoSize for single-line
945 * static text controls.
946 *
947 *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed various things with statics
948 *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed broken fonts
949 *@@changed V0.9.14 (2001-08-01) [umoeller]: now caching fonts, which is significantly faster
950 *@@changed V0.9.16 (2001-10-15) [umoeller]: added APIRET
951 *@@changed V0.9.16 (2002-02-02) [umoeller]: added ulWidth
952 *@@changed V1.0.0 (2002-08-18) [umoeller]: renamed; moved multi-line processing to CalcAutoSizeTextMulti
953 */
954
955STATIC APIRET CalcAutoSizeTextSingle(PCOLUMNDEF pColumn,
956 PDLGPRIVATE pDlgData)
957{
958 SetDlgFont(pColumn->pcszFont, pDlgData);
959
960 pColumn->szlAuto.cy = pDlgData->fmLast.lMaxBaselineExt
961 + pDlgData->fmLast.lExternalLeading;
962
963 // get the control string and see how much space it needs
964 if (pColumn->pcszText)
965 {
966 POINTL aptl[TXTBOX_COUNT];
967 if (!GpiQueryTextBox(pDlgData->hps,
968 strlen(pColumn->pcszText),
969 (PCH)pColumn->pcszText,
970 TXTBOX_COUNT,
971 aptl))
972 return DLGERR_GPIQUERYTEXTBOX;
973
974 pColumn->szlAuto.cx = aptl[TXTBOX_TOPRIGHT].x - aptl[TXTBOX_BOTTOMLEFT].x;
975
976 return NO_ERROR;
977 }
978
979 return DLGERR_INVALID_CONTROL_TITLE;
980}
981
982/*
983 *@@ CalcAutoSizeTextMulti:
984 * implementation for CalcAutoSize for multi-line
985 * static text controls.
986 *
987 *@@added V1.0.0 (2002-08-18) [umoeller]
988 */
989
990STATIC APIRET CalcAutoSizeTextMulti(PCOLUMNDEF pColumn,
991 ULONG ulWidth, // in: proposed width of control (req.)
992 PDLGPRIVATE pDlgData)
993{
994 SetDlgFont(pColumn->pcszFont, pDlgData);
995
996 // get the control string and see how much space it needs
997 if (pColumn->pcszText)
998 {
999 RECTL rcl = {0, 0, 0, 0};
1000 rcl.xRight = ulWidth;
1001 if (pColumn->szlProposed.cy > 0)
1002 rcl.yTop = pColumn->szlProposed.cy;
1003 else
1004 rcl.yTop = G_cyScreen * 2 / 3;
1005
1006 winhDrawFormattedText(pDlgData->hps,
1007 &rcl,
1008 pColumn->pcszText,
1009 DT_LEFT | DT_TOP | DT_WORDBREAK | DT_QUERYEXTENT);
1010 pColumn->szlAuto.cx = rcl.xRight - rcl.xLeft;
1011 pColumn->szlAuto.cy = rcl.yTop - rcl.yBottom;
1012
1013 return NO_ERROR;
1014 }
1015
1016 return DLGERR_INVALID_CONTROL_TITLE;
1017}
1018
1019/*
1020 *@@ CalcAutoSizeTextView:
1021 * implementation for CalcAutoSize for the XTextView
1022 * control.
1023 *
1024 * This is slightly sick. We create the control already
1025 * here in order to be able to have it format the text
1026 * and then send TXM_QUERYTEXTEXTENT to it, which was
1027 * added to the control with V0.9.20 for this very
1028 * purpose. The control that we create here will then be
1029 * reused by ColumnCreateControl and not be recreated,
1030 * which would be way too expensive.
1031 *
1032 *@@added V0.9.20 (2002-08-10) [umoeller]
1033 *@@changed V1.0.0 (2002-08-18) [umoeller]: removed temp windows list
1034 */
1035
1036STATIC APIRET CalcAutoSizeTextView(PCOLUMNDEF pColumn,
1037 ULONG ulWidth, // in: proposed width of control (req.)
1038 PDLGPRIVATE pDlgData)
1039{
1040 APIRET arc = NO_ERROR;
1041
1042 PCSZ pcszTitle;
1043
1044 if ( (pColumn->hwndControl)
1045 || (pColumn->hwndControl = WinCreateWindow(pDlgData->hwndDlg, // parent
1046 (PSZ)pColumn->pcszClass,
1047 NULL,
1048 pColumn->flStyle,
1049 0,
1050 0,
1051 ulWidth,
1052 2000, // cy, for now
1053 pDlgData->hwndDlg, // owner
1054 HWND_BOTTOM,
1055 pColumn->usID,
1056 pColumn->pvCtlData,
1057 NULL))
1058 )
1059 {
1060 HWND hwnd = pColumn->hwndControl;
1061 SIZEL szlTemp;
1062
1063 if (pColumn->pcszFont)
1064 winhSetWindowFont(hwnd,
1065 pColumn->pcszFont);
1066
1067 WinSetWindowText(hwnd,
1068 (pcszTitle = pColumn->pcszText)
1069 ? (PSZ)pcszTitle
1070 : "");
1071
1072 WinSendMsg(hwnd,
1073 TXM_QUERYTEXTEXTENT,
1074 (MPARAM)&szlTemp,
1075 0);
1076
1077 pColumn->szlAuto.cy = szlTemp.cy;
1078 }
1079 else
1080 arc = DLGERR_CANNOT_CREATE_CONTROL;
1081
1082 return arc;
1083}
1084
1085/*
1086 *@@ CalcAutoSize:
1087 * helper func that gets called from ColumnCalcSizes for
1088 * every control that has set SZL_AUTOSIZE for its size.
1089 *
1090 * We try to be smart and set a correct size for the
1091 * control, depending on its class and data.
1092 *
1093 * This sets pColumn->szlAuto and possibly pre-creates
1094 * a window for the control and stores the window
1095 * in pColumn->hwndControl.
1096 *
1097 * Presently this works for
1098 *
1099 * -- static text, single and multiline
1100 *
1101 * -- static icons and bitmaps
1102 *
1103 * -- pushbuttons, radio buttons, and checkboxes
1104 *
1105 * -- the XTextView control (yes! V0.9.20).
1106 *
1107 *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed various things with statics
1108 *@@changed V0.9.16 (2001-10-15) [umoeller]: added APIRET
1109 *@@changed V0.9.20 (2002-08-10) [umoeller]: added support for textview
1110 */
1111
1112STATIC APIRET CalcAutoSize(PCOLUMNDEF pColumn,
1113 ULONG ulWidth, // in: proposed width of control
1114 PDLGPRIVATE pDlgData)
1115{
1116 APIRET arc = NO_ERROR;
1117
1118 switch ((ULONG)pColumn->pcszClass)
1119 {
1120 case 0xffff0003L: // WC_BUTTON:
1121 if (!(arc = CalcAutoSizeTextSingle(pColumn,
1122 pDlgData)))
1123 {
1124 if (pColumn->flStyle & ( BS_AUTOCHECKBOX
1125 | BS_AUTORADIOBUTTON
1126 | BS_AUTO3STATE
1127 | BS_3STATE
1128 | BS_CHECKBOX
1129 | BS_RADIOBUTTON))
1130 {
1131 // give a little extra width for the box bitmap
1132 // V0.9.19 (2002-04-24) [umoeller]
1133 pColumn->szlAuto.cx += ctlQueryCheckboxSize() + 4;
1134 // and height
1135 pColumn->szlAuto.cy += 2;
1136 }
1137 else if (pColumn->flStyle & BS_BITMAP)
1138 ; // @@todo
1139 else if (pColumn->flStyle & (BS_ICON | BS_MINIICON))
1140 ; // @@todo
1141 // we can't test for BS_PUSHBUTTON because that's 0x0000
1142 else if (!(pColumn->flStyle & BS_USERBUTTON))
1143 {
1144 pColumn->szlAuto.cx += (2 * pDlgData->cxBorder + 15);
1145 pColumn->szlAuto.cy += (2 * pDlgData->cyBorder + 15);
1146 }
1147 }
1148 break;
1149
1150 case 0xffff0005L: // WC_STATIC:
1151 switch (pColumn->flStyle & 0x0F)
1152 {
1153 case SS_TEXT:
1154 if (pColumn->flStyle & DT_WORDBREAK)
1155 // multi-line:
1156 arc = CalcAutoSizeTextMulti(pColumn,
1157 ulWidth,
1158 pDlgData);
1159 else
1160 // single-line:
1161 arc = CalcAutoSizeTextSingle(pColumn,
1162 pDlgData);
1163 break;
1164
1165 case SS_BITMAP:
1166 {
1167 HBITMAP hbm;
1168 if (hbm = (HBITMAP)pColumn->pcszText)
1169 {
1170 BITMAPINFOHEADER2 bmih2;
1171 ZERO(&bmih2);
1172 bmih2.cbFix = sizeof(bmih2);
1173 if (GpiQueryBitmapInfoHeader(hbm,
1174 &bmih2))
1175 {
1176 pColumn->szlAuto.cx = bmih2.cx;
1177 pColumn->szlAuto.cy = bmih2.cy;
1178 }
1179 else
1180 arc = DLGERR_GPIQUERYBITMAPINFOHEADER;
1181 }
1182 else
1183 arc = DLGERR_INVALID_STATIC_BITMAP;
1184 }
1185 break;
1186
1187 case SS_ICON:
1188 pColumn->szlAuto.cx = G_cxIcon;
1189 pColumn->szlAuto.cy = G_cyIcon;
1190 break;
1191 }
1192 break;
1193
1194 default:
1195 // added support for textview V0.9.20 (2002-08-10) [umoeller]
1196 if ( (((ULONG)pColumn->pcszClass & 0xFFFF0000) != 0xFFFF0000)
1197 // don't run strcmp on the PM pseudo-strings
1198 && (!strcmp(pColumn->pcszClass, WC_XTEXTVIEW))
1199 )
1200 {
1201 arc = CalcAutoSizeTextView(pColumn,
1202 ulWidth,
1203 pDlgData);
1204 }
1205 else
1206 {
1207 // any other control (just to be safe):
1208 SetDlgFont(pColumn->pcszFont, pDlgData);
1209 pColumn->szlAuto.cx = 50;
1210 pColumn->szlAuto.cy = pDlgData->fmLast.lMaxBaselineExt
1211 + pDlgData->fmLast.lExternalLeading
1212 + 7; // some space
1213 }
1214 }
1215
1216 return arc;
1217}
1218
1219/*
1220 *@@ ColumnCalcPositions:
1221 * implementation for PROCESS_6_CALC_POSITIONS in
1222 * ProcessColumn.
1223 *
1224 *@@added V0.9.15 (2001-08-26) [umoeller]
1225 *@@changed V0.9.16 (2001-10-15) [umoeller]: added APIRET
1226 *@@changed V0.9.19 (2002-04-24) [umoeller]: fixed PM groups alignment
1227 *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm
1228 */
1229
1230STATIC APIRET ColumnCalcPositions(PCOLUMNDEF pColumn,
1231 PROWDEF pOwningRow, // in: current row from ProcessRow
1232 PLONG plX, // in/out: PROCESS_6_CALC_POSITIONS only
1233 PDLGPRIVATE pDlgData)
1234{
1235 APIRET arc = NO_ERROR;
1236
1237 // column box = *plX on ProcessRow stack
1238 pColumn->cpBox.x = *plX;
1239 pColumn->cpBox.y = pOwningRow->cpRowBox.y;
1240
1241 // check vertical alignment of row;
1242 // we might need to increase column y
1243 switch (pOwningRow->flRowFormat & ROW_VALIGN_MASK)
1244 {
1245 // case ROW_VALIGN_BOTTOM: // do nothing
1246
1247 case ROW_VALIGN_CENTER:
1248 if (pColumn->cpBox.cy < pOwningRow->cpRowBox.cy)
1249 pColumn->cpBox.y += ( (pOwningRow->cpRowBox.cy - pColumn->cpBox.cy)
1250 / 2);
1251 break;
1252
1253 case ROW_VALIGN_TOP:
1254 if (pColumn->cpBox.cy < pOwningRow->cpRowBox.cy)
1255 pColumn->cpBox.y += (pOwningRow->cpRowBox.cy - pColumn->cpBox.cy);
1256 break;
1257 }
1258
1259 // increase plX by box width
1260 *plX += pColumn->cpBox.cx;
1261
1262 // calculate CONTROL pos from COLUMN pos by applying spacing
1263 pColumn->cpContent.x = pColumn->cpBox.x
1264 + pColumn->rclPadding.xLeft
1265 + pColumn->rclMargin.xLeft;
1266 pColumn->cpContent.y = pColumn->cpBox.y
1267 + pColumn->rclPadding.yBottom
1268 + pColumn->rclMargin.yBottom;
1269
1270 if (pColumn->pNestedTable)
1271 {
1272 // nested table:
1273 // recurse!! to create windows for the sub-table
1274 arc = ProcessTable(pColumn->pNestedTable,
1275 &pColumn->cpContent, // start pos for new table
1276 PROCESS_6_CALC_POSITIONS,
1277 pDlgData);
1278 }
1279
1280 return arc;
1281}
1282
1283/*
1284 *@@ ColumnCreateControl:
1285 * implementation for PROCESS_7_CREATE_CONTROLS in
1286 * ProcessColumn.
1287 *
1288 *@@added V0.9.15 (2001-08-26) [umoeller]
1289 *@@changed V0.9.16 (2001-10-15) [umoeller]: fixed ugly group table spacings
1290 *@@changed V0.9.16 (2001-12-08) [umoeller]: fixed entry field ES_MARGIN positioning
1291 *@@changed V0.9.19 (2002-04-17) [umoeller]: fixes for the STUPID drop-down comboboxes
1292 *@@changed V0.9.19 (2002-04-24) [umoeller]: fixed PM groups alignment
1293 *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm
1294 *@@changed V1.0.0 (2002-08-18) [umoeller]: setting entry field length to CCHMAXPATH per default now
1295 */
1296
1297STATIC APIRET ColumnCreateControl(PCOLUMNDEF pColumn,
1298 PDLGPRIVATE pDlgData)
1299{
1300 APIRET arc = NO_ERROR;
1301
1302 PCSZ pcszClass = NULL;
1303 PCSZ pcszText = NULL;
1304 ULONG flStyle = 0;
1305 LHANDLE lHandleSet = NULLHANDLE;
1306 ULONG flOld = 0;
1307
1308 if (pColumn->pNestedTable)
1309 {
1310 // nested table:
1311 // recurse!!
1312 if (!(arc = ProcessTable(pColumn->pNestedTable,
1313 NULL,
1314 PROCESS_7_CREATE_CONTROLS,
1315 pDlgData)))
1316 {
1317 // should we create a PM control around the table?
1318 // (do this AFTER the other controls from recursing,
1319 // otherwise the stupid container doesn't show up)
1320 if (pcszClass = pColumn->pcszClass)
1321 {
1322 // yes:
1323 pcszText = pColumn->pcszText;
1324 flStyle = pColumn->flStyle;
1325
1326 // V1.0.0 (2002-08-16) [umoeller]
1327 // Removed all the terrible hacks for the PM group
1328 // control. We now use rclPadding and rclMargin
1329 // in the CONTROLDEF for determining the group
1330 // box position and size. Those were set initially
1331 // by the DLGHITEM parser. See CreateColumn().
1332 // If the group box had an explicit size set,
1333 // that is used by ProcessTable for the table
1334 // box also.
1335 }
1336
1337#ifdef DEBUG_DIALOG_WINDOWS
1338 // debug: create a frame with the exact size
1339 // of the table in blue
1340 CreateDebugFrame(pDlgData->hwndDlg, // parent
1341 pTableDef->cpTableBox.x + pDlgData->ptlTotalOfs.x,
1342 pTableDef->cpTableBox.y + pDlgData->ptlTotalOfs.y,
1343 pTableDef->cpTableBox.cx,
1344 pTableDef->cpTableBox.cy,
1345 RGBCOL_BLUE,
1346 LINETYPE_DOT);
1347#endif
1348 }
1349 }
1350 else
1351 {
1352 // no nested table, but control:
1353 pcszClass = pColumn->pcszClass;
1354 pcszText = pColumn->pcszText;
1355 flStyle = pColumn->flStyle;
1356 }
1357
1358 if ( (!arc) // check error code V1.0.0 (2002-08-16) [umoeller]
1359 && (pcszClass)
1360 )
1361 {
1362 // create something:
1363 LONG x, cx, y, cy; // for control hacks
1364 HWND hwndFound = NULLHANDLE;
1365 LONG cxUse;
1366 PCSZ pcszTextEF = NULL; // for entry field
1367
1368 // V1.0.0 (2002-08-16) [umoeller]
1369 // determine the position where to create the
1370 // control; this is the content box, to which
1371 // we add the absolute position on the dlgdata
1372 // stack, and padding. Padding is presently
1373 // only set for the group box around tables.
1374
1375 // To create the control, we use the explicit
1376 // or automatic size of the control instead
1377 // of the column width. Otherwise all buttons
1378 // are expanded to the column width, for example.
1379 if ( (!(cxUse = pColumn->cxMinContent))
1380 || (pColumn->pNestedTable)
1381 )
1382 cxUse = pColumn->cpContent.cx;
1383
1384 x = pColumn->cpContent.x
1385 - pColumn->rclPadding.xLeft
1386 + pDlgData->ptlTotalOfs.x;
1387 cx = cxUse
1388 + pColumn->rclPadding.xLeft
1389 + pColumn->rclPadding.xRight;
1390 y = pColumn->cpContent.y
1391 - pColumn->rclPadding.yBottom
1392 + pDlgData->ptlTotalOfs.y;
1393 cy = pColumn->cpContent.cy
1394 + pColumn->rclPadding.yBottom
1395 + pColumn->rclPadding.yTop;
1396
1397 // now implement hacks for certain controls
1398 switch ((ULONG)pcszClass)
1399 {
1400 case 0xffff0005L: // WC_STATIC:
1401 // change the title if this is a static with SS_BITMAP;
1402 // we have used a HBITMAP in there!
1403 if ( ( ((flStyle & 0x0F) == SS_BITMAP)
1404 || ((flStyle & 0x0F) == SS_ICON)
1405 )
1406 )
1407 {
1408 // change style flag to not use SS_BITMAP nor SS_ICON;
1409 // control creation fails otherwise (stupid, stupid PM)
1410 flOld = flStyle;
1411 flStyle = ((flStyle & ~0x0F) | SS_FGNDFRAME);
1412 pcszText = "";
1413 lHandleSet = (LHANDLE)pColumn->pcszText;
1414 }
1415 break;
1416
1417 case 0xffff0002L: // combobox
1418 if (flStyle & (CBS_DROPDOWN | CBS_DROPDOWNLIST))
1419 {
1420 // while calculating the column height,
1421 // we have set pColumn->cpContent.cy
1422 // to the height of a single line to get
1423 // the position calculations right...
1424 LONG cyDelta = pColumn->szlProposed.cy - pColumn->cpContent.cy;
1425 y -= cyDelta;
1426 cy += cyDelta;
1427 }
1428 break;
1429
1430 case 0xffff0006L: // entry field
1431 case 0xffff000AL: // MLE:
1432 // the stupid entry field resizes itself if it has
1433 // the ES_MARGIN style, so correlate that too... dammit
1434 // V0.9.16 (2001-12-08) [umoeller]
1435 if (flStyle & ES_MARGIN)
1436 {
1437 LONG cxMargin = 3 * pDlgData->cxBorder;
1438 LONG cyMargin = 3 * pDlgData->cyBorder;
1439
1440 x += cxMargin;
1441 y += cyMargin;
1442 cx -= 2 * cxMargin;
1443 cy -= 2 * cyMargin;
1444 }
1445
1446 if ((ULONG)pcszClass == 0xffff0006L)
1447 {
1448 // entry field:
1449 // defer setting the text because we should
1450 // first set the entry field limit
1451 // V1.0.0 (2002-08-18) [umoeller]
1452 pcszTextEF = pcszText;
1453 pcszText = ""; // for now
1454 }
1455 break;
1456 } // end switch ((ULONG)pControlDef->pcszClass)
1457
1458 if (pColumn->hwndControl)
1459 {
1460 // control was already created by CalcAutoSize:
1461 // resize it to what we really need
1462 WinSetWindowPos(pColumn->hwndControl,
1463 HWND_BOTTOM,
1464 x,
1465 y,
1466 cx,
1467 cy,
1468 SWP_SIZE | SWP_MOVE | SWP_ZORDER);
1469 }
1470 else
1471 {
1472 TRY_QUIET(excpt1)
1473 {
1474 pColumn->hwndControl = WinCreateWindow(pDlgData->hwndDlg, // parent
1475 (PSZ)pcszClass, // window class, hacked
1476 (pcszText) // control text, hacked
1477 ? (PSZ)pcszText
1478 : "",
1479 flStyle, // window style, hacked
1480 x,
1481 y,
1482 cx,
1483 cy,
1484 pDlgData->hwndDlg, // owner == parent
1485 HWND_BOTTOM,
1486 pColumn->usID,
1487 pColumn->pvCtlData,
1488 NULL);
1489 }
1490 CATCH(excpt1)
1491 {
1492 CHAR szClass2[100] = "Invalid mem",
1493 szText2[100] = "Invalid mem";
1494 ULONG Size, Attr;
1495 if (!(arc = DosQueryMem((PVOID)pcszClass, &Size, &Attr)))
1496 strhncpy0(szClass2, pcszClass, sizeof(szClass2));
1497 if (!(arc = DosQueryMem((PVOID)pcszClass, &Size, &Attr)))
1498 strhncpy0(szText2, pcszText, sizeof(szText2));
1499
1500 #ifdef DEBUG_DIALOG_WINDOWS
1501 _Pmpf(("Crash creating control of class 0x%lX (%s), title 0x%lX (%s)",
1502 szClass2,
1503 szText2));
1504 #endif
1505
1506 arc = ERROR_PROTECTION_VIOLATION;
1507 } END_CATCH();
1508 }
1509
1510 if (pColumn->hwndControl)
1511 {
1512 // control created, or reused above:
1513
1514 if (lHandleSet)
1515 {
1516 // subclass the damn static
1517 if ((flOld & 0x0F) == SS_ICON)
1518 // this was a static:
1519 ctlPrepareStaticIcon(pColumn->hwndControl,
1520 1);
1521 else
1522 // this was a bitmap:
1523 ctlPrepareStretchedBitmap(pColumn->hwndControl,
1524 TRUE);
1525
1526 WinSendMsg(pColumn->hwndControl,
1527 SM_SETHANDLE,
1528 (MPARAM)lHandleSet,
1529 0);
1530 }
1531 else
1532 {
1533 if (pcszTextEF)
1534 {
1535 // entry field:
1536 // set text limit to CCHMAXPATH per default,
1537 // and set the real text now
1538 // V1.0.0 (2002-08-18) [umoeller]
1539 winhSetEntryFieldLimit(pColumn->hwndControl, CCHMAXPATH);
1540 WinSetWindowText(pColumn->hwndControl, (PSZ)pcszTextEF);
1541 }
1542
1543 if (pColumn->pcszFont)
1544 // we must set the font explicitly here...
1545 // doesn't always work with WinCreateWindow
1546 // presparams parameter, for some reason
1547 // V0.9.12 (2001-05-31) [umoeller]
1548 winhSetWindowFont(pColumn->hwndControl,
1549 pColumn->pcszFont);
1550 }
1551
1552#ifdef DEBUG_DIALOG_WINDOWS
1553 // if (!pColumn->fIsNestedTable)
1554 {
1555 // debug: create a frame with the exact size
1556 // of the box (not the content), so this
1557 // includes spacing
1558 CreateDebugFrame(pDlgData->hwndDlg, // parent
1559 pColumn->cpBox.x + pDlgData->ptlTotalOfs.x,
1560 pColumn->cpBox.y + pDlgData->ptlTotalOfs.y,
1561 pColumn->cpBox.cx,
1562 pColumn->cpBox.cy,
1563 RGBCOL_DARKGREEN,
1564 LINETYPE_SOLID);
1565
1566 // and another one for the content (control size)
1567 CreateDebugFrame(pDlgData->hwndDlg, // parent
1568 pColumn->cpContent.x + pDlgData->ptlTotalOfs.x,
1569 pColumn->cpContent.y + pDlgData->ptlTotalOfs.y,
1570 pColumn->cpContent.cx,
1571 pColumn->cpContent.cy,
1572 RGBCOL_RED,
1573 LINETYPE_ALTERNATE);
1574 }
1575#endif
1576
1577 // append window that was created
1578 // V0.9.18 (2002-03-03) [umoeller]
1579 if (pDlgData->pllControls)
1580 lstAppendItem(pDlgData->pllControls,
1581 (PVOID)pColumn->hwndControl);
1582
1583 // if this is the first control with WS_TABSTOP,
1584 // we'll give it the focus later
1585 if ( (flStyle & WS_TABSTOP)
1586 && (!pDlgData->hwndFirstFocus)
1587 )
1588 pDlgData->hwndFirstFocus = pColumn->hwndControl;
1589
1590 // if this is the first default push button,
1591 // go store it too
1592 // V0.9.14 (2001-08-21) [umoeller]
1593 if ( (!pDlgData->hwndDefPushbutton)
1594 && ((ULONG)pColumn->pcszClass == 0xffff0003L)
1595 && (pColumn->flStyle & BS_DEFAULT)
1596 )
1597 pDlgData->hwndDefPushbutton = pColumn->hwndControl;
1598 }
1599 else
1600 // V0.9.14 (2001-08-03) [umoeller]
1601 arc = DLGERR_CANNOT_CREATE_CONTROL;
1602 }
1603
1604 return arc;
1605}
1606
1607/* ******************************************************************
1608 *
1609 * Recursive workers for columns, rows, tables
1610 *
1611 ********************************************************************/
1612
1613/*
1614 *@@ ProcessColumn:
1615 * processes a column, which per definition is either
1616 * a control or a nested subtable.
1617 *
1618 * A column is part of a row, which in turn is part
1619 * of a table. There can be several columns in a row,
1620 * and several rows in a table.
1621 *
1622 * Since tables may be specified as columns, it is
1623 * possible to produce complex dialog layouts by
1624 * nesting tables.
1625 *
1626 * Preconditions:
1627 *
1628 * -- PROCESS_6_CALC_POSITIONS: position of each column
1629 * is taken from *plX, which is increased by the
1630 * column width by this call.
1631 *
1632 * Owning row must already have its y position properly
1633 * set, or we can't compute ours. Besides, plX must
1634 * point to the current X in the row and will be
1635 * incremented by the column's size here.
1636 *
1637 *@@changed V0.9.12 (2001-05-31) [umoeller]: added control data
1638 *@@changed V0.9.12 (2001-05-31) [umoeller]: fixed font problems
1639 *@@changed V0.9.20 (2002-08-08) [umoeller]: added support for aligning columns horizontally
1640 *@@changed V1.0.0 (2002-08-16) [umoeller]: calc size rewritten for new algorithm
1641 */
1642
1643STATIC APIRET ProcessColumn(PCOLUMNDEF pColumn,
1644 PROWDEF pOwningRow, // in: current row from ProcessRow
1645 enProcessMode ProcessMode, // in: processing mode (see ProcessAll)
1646 PLONG plX, // in/out: PROCESS_6_CALC_POSITIONS only
1647 PDLGPRIVATE pDlgData)
1648{
1649 APIRET arc = NO_ERROR;
1650
1651 // pColumn->pOwningRow = pOwningRow;
1652
1653 switch (ProcessMode)
1654 {
1655 /*
1656 * PROCESS_1_CALC_MIN_WIDTHS:
1657 *
1658 */
1659
1660 case PROCESS_1_CALC_MIN_WIDTHS:
1661 // rewritten V1.0.0 (2002-08-16) [umoeller]
1662
1663 if (!pColumn->pNestedTable)
1664 {
1665 // control leaf node, not subtable:
1666
1667 // check if the control title is valid... we
1668 // crash later otherwise
1669 if ((ULONG)pColumn->pcszText > 0xFFFF0000)
1670 arc = DLGERR_INVALID_CONTROL_TITLE;
1671 else
1672 {
1673 if (pColumn->szlProposed.cx > 0)
1674 {
1675 // explicit size:
1676 pColumn->cxMinContent = pColumn->szlProposed.cx;
1677 }
1678 else if (pColumn->szlProposed.cx == SZL_AUTOSIZE) // -1
1679 {
1680 // autosize width:
1681 // calc then
1682 arc = CalcAutoSize(pColumn,
1683 1000, // width, whatever...
1684 // that's what we want to know!
1685 pDlgData);
1686
1687 pColumn->cxMinContent = pColumn->szlAuto.cx;
1688 }
1689 // else: -100 < width < -1: percentage of parent table,
1690 // leave cxMinContent == 0 for now
1691
1692 #ifdef DEBUG_DIALOG_WINDOWS
1693 if (pControlDef->pcszText)
1694 {
1695 CHAR szTemp[40];
1696 strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp));
1697 strcpy(szTemp + 35, "...");
1698 _PmpfF(("PROCESS_1_CALC_MIN_WIDTHS [%s] cxMinContent %d",
1699 szTemp,
1700 pColumn->cxMinContent));
1701 }
1702 #endif
1703 }
1704 }
1705 else
1706 {
1707 // column represents subtable:
1708 // recurse!
1709 if (!(arc = ProcessTable(pColumn->pNestedTable,
1710 NULL,
1711 ProcessMode,
1712 pDlgData)))
1713 {
1714 // ProcessTable has set pTableDef->cxMinBox,
1715 // that is the size of the entire table box...
1716 // use that for our cxMinContent
1717 pColumn->cxMinContent = pColumn->pNestedTable->cxMinBox;
1718 }
1719 }
1720
1721 break; // PROCESS_1_CALC_MIN_WIDTHS
1722
1723 /*
1724 * PROCESS_2_CALC_RELATIVE_WIDTHS:
1725 *
1726 */
1727
1728 case PROCESS_2_CALC_RELATIVE_WIDTHS:
1729 // rewritten V1.0.0 (2002-08-16) [umoeller]
1730
1731 if (!pColumn->pNestedTable)
1732 {
1733 // control leaf node, not subtable:
1734 if ( (pColumn->szlProposed.cx == SZL_AUTOSIZE) // -1
1735 || (pColumn->szlProposed.cx > 0) // not SZL_REMAINDER
1736 )
1737 {
1738 // explicit size or autosize:
1739 // that was computed with PROCESS_1_CALC_MIN_WIDTHS,
1740 // so copy from cxMinContent
1741 pColumn->cpContent.cx = pColumn->cxMinContent;
1742 }
1743 else if (pColumn->szlProposed.cx < 0)
1744 {
1745 // control wants percentage of parent table width:
1746 PTABLEDEF pOwningTable = pOwningRow->pOwningTable;
1747 PROWDEF pWidestRow;
1748
1749 #ifdef DEBUG_DIALOG_WINDOWS
1750 {
1751 CHAR szTemp[40];
1752 strhncpy0(szTemp,
1753 (pControlDef->pcszText) ? pControlDef->pcszText : "NULL",
1754 sizeof(szTemp));
1755 strcpy(szTemp + 35, "...");
1756 _PmpfF(("PROCESS_2_CALC_RELATIVE_WIDTHS [%s]"));
1757 }
1758 #endif
1759
1760 if (pWidestRow = pOwningTable->pWidestRow)
1761 {
1762 PLISTNODE pNode;
1763 PCOLUMNDEF pSubColumn,
1764 pTablesColumn;
1765 LONG cxWidestRowsMinBox = pOwningTable->cxMinBox; // pWidestRow->cxMinBox;
1766
1767 #ifdef DEBUG_DIALOG_WINDOWS
1768 _Pmpf((" pWidestRow->cxMinBox %d", pWidestRow->cxMinBox));
1769 _Pmpf((" pOwningTable->cxMinBox %d", pOwningTable->cxMinBox));
1770 #endif
1771
1772 // now, since we're supposed to set our own CONTENT
1773 // rectangle, subtract our own padding and margin
1774 cxWidestRowsMinBox -= pColumn->cxSpacingTotal;
1775
1776 pColumn->cpContent.cx = (double)cxWidestRowsMinBox
1777 * -pColumn->szlProposed.cx
1778 / 100;
1779 }
1780
1781 #ifdef DEBUG_DIALOG_WINDOWS
1782 {
1783 _Pmpf((" cpContent.cx %d",
1784 pColumn->cpContent.cx));
1785 _Pmpf((" pWidestRow was 0x%lX", pWidestRow));
1786 }
1787 #endif
1788 } // end if (pControlDef->szlDlgUnits.cx < 0)
1789 else if (pColumn->szlProposed.cx == SZL_REMAINDER) // 0
1790 {
1791 // SZL_REMAINDER is calculated in PROCESS_4_REMAINDER_WIDTHS,
1792 // just count the remainders in the ROWDEF for now so
1793 // we can process them quickly later
1794 ++(pOwningRow->cWantRemainders);
1795
1796 // set the flag so that Dlg2_CalcSizes knows we'll
1797 // need PROCESS_4_REMAINDER_WIDTHS
1798 pDlgData->flNeedsProcessing |= FL_REMAINDER_WIDTHS;
1799 }
1800 }
1801 else
1802 {
1803 // column represents subtable:
1804 // recurse!
1805 if (!(arc = ProcessTable(pColumn->pNestedTable,
1806 NULL,
1807 ProcessMode,
1808 pDlgData)))
1809 {
1810 // ProcessTable has set pTableDef->cpTableBox.cx,
1811 // that is the size of the entire table box...
1812 // use that for our cpContent.cx
1813 pColumn->cpContent.cx = pColumn->pNestedTable->cpTableBox.cx;
1814 }
1815 }
1816 break; // PROCESS_2_CALC_RELATIVE_WIDTHS
1817
1818 /*
1819 * PROCESS_3_ALIGN_COLUMNS:
1820 *
1821 */
1822
1823 case PROCESS_3_ALIGN_COLUMNS:
1824 // rewritten V1.0.0 (2002-08-16) [umoeller]
1825
1826 if (!pColumn->pNestedTable)
1827 {
1828 // control leaf node, not subtable:
1829
1830 LONG cxWidest = 0;
1831 PTABLEDEF pOwningTable;
1832
1833 // if our owning table has the TABLE_ALIGN_COLUMNS
1834 // flag set, set this columns width to the width
1835 // of the widest column in all rows of this table,
1836 // but align this column ONLY if it does not want
1837 // SZL_REMAINDER
1838
1839 if ( (pColumn->szlProposed.cx != SZL_REMAINDER) // 0
1840 && (pOwningTable = pOwningRow->pOwningTable)
1841 && (pOwningTable->flTable & TABLE_ALIGN_COLUMNS)
1842 )
1843 {
1844 // find the widest corresponding column in the
1845 // surrounding table; pColumn->ulColumnIndex
1846 // has our index, so we need to run through
1847 // the rows and compare the widths of all
1848 // columns with that index
1849 PLISTNODE pRowNode;
1850
1851 FOR_ALL_NODES(&pOwningTable->llRows, pRowNode)
1852 {
1853 PROWDEF pRowThis = (PROWDEF)pRowNode->pItemData;
1854 PCOLUMNDEF pCorrespondingColumn;
1855 if ( (pCorrespondingColumn = (PCOLUMNDEF)lstItemFromIndex(&pRowThis->llColumns,
1856 pColumn->ulColumnIndex))
1857 && (pCorrespondingColumn->cxMinContent > cxWidest)
1858 )
1859 {
1860 #ifdef DEBUG_DIALOG_WINDOWS
1861 {
1862 const CONTROLDEF *pControlDef = (const CONTROLDEF *)pCorrespondingColumn->pvDefinition;
1863 if (pControlDef->pcszText)
1864 {
1865 CHAR szTemp[40];
1866 strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp));
1867 strcpy(szTemp + 35, "...");
1868
1869 #ifdef DEBUG_DIALOG_WINDOWS
1870 _PmpfF((" widest column is [%s] cxMinContent %d",
1871 szTemp,
1872 pCorrespondingColumn->cxMinContent));
1873 #endif
1874 }
1875 }
1876 #endif
1877
1878 cxWidest = pCorrespondingColumn->cxMinContent;
1879 }
1880 }
1881
1882 pColumn->cpContent.cx = cxWidest;
1883
1884 #ifdef DEBUG_DIALOG_WINDOWS
1885 {
1886 if (pControlDef->pcszText)
1887 {
1888 CHAR szTemp[40];
1889 strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp));
1890 strcpy(szTemp + 35, "...");
1891 _PmpfF(("PROCESS_3_ALIGN_COLUMNS [%s] cpContent.cx %d",
1892 szTemp,
1893 pColumn->cpContent.cx));
1894 }
1895 }
1896 #endif
1897 }
1898 }
1899 else
1900 {
1901 // column represents subtable:
1902 // recurse!
1903 arc = ProcessTable(pColumn->pNestedTable,
1904 NULL,
1905 ProcessMode,
1906 pDlgData);
1907
1908 // the width of a table can NOT change from this
1909 // processing, so no need to adjust
1910 // (individual rows might have changed though)
1911 // @@todo wrong, if the first column is made
1912 // wider, the second column might now move over
1913 // the table width, fix that
1914 }
1915
1916 // in any case (control or subtable), count the
1917 // widths of all columns that do not have SZL_REMAINDER
1918 // set for PROCESS_4_REMAINDER_WIDTHS in the next loop
1919
1920 // NOTE: we add _all_ columns, including those that have
1921 // SZL_REMAINDER set (for which cpContent.cx is null
1922 // presently), because the "non remainder" space needs
1923 // to include the spacing of _all_ columns (see the
1924 // PROCESS_4_REMAINDER_WIDTHS processing below)
1925 pOwningRow->cxNonRemainder += pColumn->cpContent.cx
1926 + pColumn->cxSpacingTotal;
1927
1928 break; // PROCESS_3_ALIGN_COLUMNS
1929
1930 /*
1931 * PROCESS_4_REMAINDER_WIDTHS:
1932 *
1933 */
1934
1935 case PROCESS_4_REMAINDER_WIDTHS:
1936 if (!pColumn->pNestedTable)
1937 {
1938 // control leaf node, not subtable:
1939 PTABLEDEF pOwningTable;
1940
1941 if ( (pColumn->szlProposed.cx == SZL_REMAINDER) // 0
1942 && (pOwningTable = pOwningRow->pOwningTable)
1943 )
1944 {
1945 LONG cxRemaining = pOwningTable->cpTableBox.cx - pOwningRow->cxNonRemainder;
1946 // NOTE: pOwningRow->cxNonRemainder has
1947 // -- the sum of the column content rectangle widths that did not
1948 // have SZL_REMAINDER set
1949 // -- the sum of _all_ column spacings, even if the column had
1950 // SZL_REMAINDER set,
1951 // so this really has the leftover space now.
1952 // Divide that by the no. of columns in the row that have
1953 // SZL_REMAINDER set so that we evenly distribute the remaining
1954 // space among those columns.
1955 pColumn->cpContent.cx = ( cxRemaining
1956 / pOwningRow->cWantRemainders
1957 // this cannot be 0 since we have
1958 // that flag and were thus counted
1959 );
1960
1961 #ifdef DEBUG_DIALOG_WINDOWS
1962 {
1963 CHAR szTemp[40] = "NULL";
1964
1965 if (pControlDef->pcszText)
1966 {
1967 strhncpy0(szTemp, pControlDef->pcszText, sizeof(szTemp));
1968 strcpy(szTemp + 35, "...");
1969 }
1970
1971 _PmpfF(("PROCESS_4_REMAINDER_WIDTHS [%s] pOwningTable->cpTableBox.cx %d",
1972 szTemp,
1973 pOwningTable->cpTableBox.cx));
1974 _PmpfF((" pOwningRow->cxNonRemainder %d",
1975 pOwningRow->cxNonRemainder));
1976 _PmpfF((" pOwningRow->cWantRemainders %d",
1977 pOwningRow->cWantRemainders));
1978 _PmpfF((" cpContent.cx %d",
1979 pColumn->cpContent.cx));
1980 }
1981 #endif
1982
1983 }
1984 }
1985 else
1986 {
1987 // column represents subtable:
1988 // recurse!
1989 arc = ProcessTable(pColumn->pNestedTable,
1990 NULL,
1991 ProcessMode,
1992 pDlgData);
1993 }
1994 break;
1995
1996 /*
1997 * PROCESS_5_CALC_HEIGHTS:
1998 *
1999 */
2000
2001 case PROCESS_5_CALC_HEIGHTS:
2002 if (!pColumn->pNestedTable)
2003 {
2004 // control leaf node, not subtable:
2005 if (pColumn->szlProposed.cy > 0)
2006 {
2007 // explicit size:
2008 pColumn->cpContent.cy = pColumn->szlProposed.cy;
2009
2010 if ((ULONG)pColumn->pcszClass == 0xffff0002L)
2011 {
2012 // hack the stupid drop-down combobox where the
2013 // size of the drop-down is the full size of the
2014 // control: when creating the control, we _do_
2015 // specify the full size, but for the column,
2016 // we must rather use a single line with
2017 // the current font
2018 // V0.9.19 (2002-04-17) [umoeller]
2019 if (pColumn->flStyle & (CBS_DROPDOWN | CBS_DROPDOWNLIST))
2020 {
2021 SetDlgFont(pColumn->pcszFont, pDlgData);
2022
2023 pColumn->cpContent.cy
2024 = pDlgData->fmLast.lMaxBaselineExt
2025 + pDlgData->fmLast.lExternalLeading
2026 + 2 * 3 * pDlgData->cyBorder;
2027 }
2028 }
2029 }
2030 else if (pColumn->szlProposed.cy == SZL_AUTOSIZE) // -1
2031 {
2032 // autosize height:
2033 // we MIGHT have run autosize already above
2034 // (e.g. for the typical bitmap or icon statics),
2035 // so check if szlAuto.cy is already set
2036 if (!(pColumn->cpContent.cy = pColumn->szlAuto.cy))
2037 {
2038 // not yet determined:
2039 // calc then
2040 arc = CalcAutoSize(pColumn,
2041 // width: now we know
2042 pColumn->cpContent.cx,
2043 pDlgData);
2044 pColumn->cpContent.cy = pColumn->szlAuto.cy;
2045 }
2046 }
2047 // else: -100 < width < -1: @@todo
2048 // add another stage for using the percentage of
2049 // the table height
2050 }
2051 else
2052 {
2053 // column represents subtable:
2054 // recurse!
2055 if (!(arc = ProcessTable(pColumn->pNestedTable,
2056 NULL,
2057 ProcessMode,
2058 pDlgData)))
2059 {
2060 // ProcessTable has set pTableDef->cpTableBox.cy,
2061 // that is the size of the entire table box...
2062 // use that for our cpContent.cy
2063 pColumn->cpContent.cy = pColumn->pNestedTable->cpTableBox.cy;
2064 }
2065 }
2066
2067 // we finally know the DEFINITE width and height
2068 // of the content, so we can set the box width
2069 // and height here now (ProcessRow relies on this
2070 // to determine the row height)
2071 pColumn->cpBox.cx = pColumn->cpContent.cx
2072 + pColumn->cxSpacingTotal;
2073 pColumn->cpBox.cy = pColumn->cpContent.cy
2074 + pColumn->rclPadding.yBottom
2075 + pColumn->rclMargin.yBottom
2076 + pColumn->rclPadding.yTop
2077 + pColumn->rclMargin.yTop;
2078 break;
2079
2080 /*
2081 * PROCESS_6_CALC_POSITIONS:
2082 *
2083 */
2084
2085 case PROCESS_6_CALC_POSITIONS:
2086 arc = ColumnCalcPositions(pColumn,
2087 pOwningRow,
2088 plX,
2089 pDlgData);
2090 break;
2091
2092 /*
2093 * PROCESS_7_CREATE_CONTROLS:
2094 *
2095 */
2096
2097 case PROCESS_7_CREATE_CONTROLS:
2098 arc = ColumnCreateControl(pColumn,
2099 pDlgData);
2100 break;
2101 }
2102
2103 return arc;
2104}
2105
2106/*
2107 *@@ ProcessRow:
2108 * level-3 procedure (called from ProcessTable),
2109 * which in turn calls ProcessColumn for each column
2110 * in the row.
2111 *
2112 *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm
2113 */
2114
2115STATIC APIRET ProcessRow(PROWDEF pRowDef,
2116 PTABLEDEF pOwningTable, // in: current table from ProcessTable
2117 enProcessMode ProcessMode, // in: processing mode (see ProcessAll)
2118 PLONG plY, // in/out: current y position (decremented)
2119 PDLGPRIVATE pDlgData)
2120{
2121 APIRET arc = NO_ERROR;
2122 LONG lXRowBox;
2123 PLISTNODE pNode;
2124
2125 pRowDef->pOwningTable = pOwningTable;
2126
2127 switch (ProcessMode)
2128 {
2129 case PROCESS_3_ALIGN_COLUMNS:
2130 // we've computed pRowDef->cpRowBox.cx in the
2131 // previous run (PROCESS_2_CALC_RELATIVE_WIDTHS),
2132 // but since we keep adding to it, we must
2133 // reset it now... the row will probably become
2134 // larger!
2135 pRowDef->cpRowBox.cx = 0;
2136 break;
2137
2138 case PROCESS_6_CALC_POSITIONS:
2139 // set up x and y so that the columns can
2140 // base on that
2141 pRowDef->cpRowBox.x = pOwningTable->cpTableBox.x;
2142 // decrease y by row height
2143 *plY -= pRowDef->cpRowBox.cy;
2144 // and use that for our bottom position
2145 pRowDef->cpRowBox.y = *plY;
2146
2147 // set lX to left of row; used by column calls below
2148 lXRowBox = pRowDef->cpRowBox.x;
2149 break;
2150 }
2151
2152 FOR_ALL_NODES(&pRowDef->llColumns, pNode)
2153 {
2154 PCOLUMNDEF pColumnThis = (PCOLUMNDEF)pNode->pItemData;
2155
2156 if (!(arc = ProcessColumn(pColumnThis,
2157 pRowDef,
2158 ProcessMode,
2159 &lXRowBox,
2160 pDlgData)))
2161 {
2162 switch (ProcessMode)
2163 {
2164 case PROCESS_1_CALC_MIN_WIDTHS:
2165 // row width = sum of all columns
2166 pRowDef->cxMinBox += pColumnThis->cxMinContent
2167 + pColumnThis->cxSpacingTotal;
2168 break;
2169
2170 case PROCESS_2_CALC_RELATIVE_WIDTHS:
2171 case PROCESS_3_ALIGN_COLUMNS: // reapply, was reset above
2172 // row width = sum of all columns
2173 pRowDef->cpRowBox.cx += pColumnThis->cpContent.cx
2174 + pColumnThis->cxSpacingTotal;
2175 break;
2176
2177 case PROCESS_5_CALC_HEIGHTS:
2178 // row height = maximum height of a column
2179 if (pRowDef->cpRowBox.cy < pColumnThis->cpBox.cy)
2180 pRowDef->cpRowBox.cy = pColumnThis->cpBox.cy;
2181 }
2182 }
2183 // we should stop on errors V0.9.20 (2002-08-10) [umoeller]
2184 else
2185 break;
2186 }
2187
2188 return arc;
2189}
2190
2191/*
2192 *@@ ProcessTable:
2193 * level-2 procedure (called from ProcessAll),
2194 * which in turn calls ProcessRow for each row
2195 * in the table (which in turn calls ProcessColumn
2196 * for each column in the row).
2197 *
2198 * See ProcessAll for the meaning of ProcessMode.
2199 *
2200 * This routine is a bit sick because it can even be
2201 * called recursively from ProcessColumn (!) if a
2202 * nested table is found in a COLUMNDEF.
2203 *
2204 * With PROCESS_6_CALC_POSITIONS, pptl must specify
2205 * the lower left corner of the table. For the
2206 * root call, this will be {0, 0}; for nested calls,
2207 * this must be the lower left corner of the column
2208 * to which the nested table belongs.
2209 *
2210 *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm
2211 */
2212
2213STATIC APIRET ProcessTable(PTABLEDEF pTableDef,
2214 const CONTROLPOS *pcpTableBox, // in: table position with PROCESS_6_CALC_POSITIONS
2215 enProcessMode ProcessMode, // in: processing mode (see ProcessAll)
2216 PDLGPRIVATE pDlgData)
2217{
2218 APIRET arc = NO_ERROR;
2219 LONG lY;
2220 PLISTNODE pNode;
2221
2222 switch (ProcessMode)
2223 {
2224 case PROCESS_2_CALC_RELATIVE_WIDTHS:
2225 // before calculating relative widths for
2226 // the table, inherit size from parent table
2227 // if the TABLE_INHERIT_SIZE table flag is
2228 // set; this was originally designed
2229 // as a separate step (PROCESS_2_INHERIT_TABLE_SIZES),
2230 // but we can save ourselves a loop here
2231 // V1.0.0 (2002-08-16) [umoeller]
2232 if (pTableDef->flTable & TABLE_INHERIT_SIZE)
2233 {
2234 PCOLUMNDEF pOwningColumn;
2235 PROWDEF pOwningRow;
2236 PTABLEDEF pOwningTable;
2237 if ( (!(pOwningColumn = pTableDef->pOwningColumn))
2238 || (!(pOwningRow = pOwningColumn->pOwningRow))
2239 || (!(pOwningTable = pOwningRow->pOwningTable))
2240 )
2241 arc = DLGERR_ROOT_TABLE_INHERIT_SIZE;
2242 else
2243 {
2244 PLISTNODE pRowNode;
2245 LONG cxMax = 0;
2246 FOR_ALL_NODES(&pOwningTable->llRows, pRowNode)
2247 {
2248 PROWDEF pRowThis = (PROWDEF)pRowNode->pItemData;
2249 if (pRowThis->cxMinBox > cxMax)
2250 cxMax = pRowThis->cxMinBox;
2251 }
2252
2253 #ifdef DEBUG_DIALOG_WINDOWS
2254 _Pmpf((" cxMax found is %d", cxMax));
2255 #endif
2256
2257 pTableDef->cxMinBox = cxMax
2258 - pOwningColumn->cxSpacingTotal;
2259 }
2260 }
2261 break;
2262
2263 case PROCESS_6_CALC_POSITIONS:
2264 pTableDef->cpTableBox.x = pcpTableBox->x;
2265 pTableDef->cpTableBox.y = pcpTableBox->y;
2266
2267 // start the rows on top
2268 lY = pcpTableBox->y + pTableDef->cpTableBox.cy;
2269 break;
2270 }
2271
2272 if (!arc)
2273 {
2274 FOR_ALL_NODES(&pTableDef->llRows, pNode)
2275 {
2276 PROWDEF pRowDefThis = (PROWDEF)pNode->pItemData;
2277
2278 if (!(arc = ProcessRow(pRowDefThis, pTableDef, ProcessMode, &lY, pDlgData)))
2279 {
2280 switch (ProcessMode)
2281 {
2282 case PROCESS_1_CALC_MIN_WIDTHS:
2283 // table width = maximum width of a row
2284 if (pTableDef->cxMinBox < pRowDefThis->cxMinBox)
2285 {
2286 pTableDef->cxMinBox = pRowDefThis->cxMinBox;
2287
2288 // remember the widest row so that
2289 // PROCESS_2_CALC_RELATIVE_WIDTHS can calc relative
2290 // widths from it
2291
2292 pTableDef->pWidestRow = pRowDefThis;
2293 }
2294 break;
2295
2296 case PROCESS_2_CALC_RELATIVE_WIDTHS:
2297 case PROCESS_3_ALIGN_COLUMNS:
2298 {
2299 PCOLUMNDEF pOwningColumn;
2300
2301 // table width = maximum width of a row
2302 if (pTableDef->cpTableBox.cx < pRowDefThis->cpRowBox.cx)
2303 pTableDef->cpTableBox.cx = pRowDefThis->cpRowBox.cx;
2304
2305 // preserve the inherited width if it was larger
2306 if (pTableDef->cxMinBox > pTableDef->cpTableBox.cx)
2307 pTableDef->cpTableBox.cx = pTableDef->cxMinBox;
2308
2309 // if the table has a group box with an explicit
2310 // size, use it if it's larger
2311 if ( (pOwningColumn = pTableDef->pOwningColumn)
2312 && (pOwningColumn->pcszClass)
2313 && (pOwningColumn->szlProposed.cx > 0)
2314 && (pOwningColumn->szlProposed.cx > pTableDef->cpTableBox.cx)
2315 )
2316 pTableDef->cpTableBox.cx = pOwningColumn->szlProposed.cx;
2317 }
2318 break;
2319
2320 case PROCESS_5_CALC_HEIGHTS:
2321 // table height = sum of all rows
2322 pTableDef->cpTableBox.cy += pRowDefThis->cpRowBox.cy;
2323 break;
2324 }
2325 }
2326 else
2327 break;
2328 }
2329 }
2330
2331 return arc;
2332}
2333
2334/*
2335 *@@ ProcessAll:
2336 * level-1 procedure, which in turn calls ProcessTable
2337 * for each root-level table found (which in turn
2338 * calls ProcessRow for each row in the table, which
2339 * in turn calls ProcessColumn for each column in
2340 * the row).
2341 */
2342
2343STATIC APIRET ProcessAll(PDLGPRIVATE pDlgData,
2344 enProcessMode ProcessMode)
2345{
2346 APIRET arc = NO_ERROR;
2347 PLISTNODE pNode;
2348 CONTROLPOS cpTableBox;
2349 ZERO(&cpTableBox);
2350
2351 switch (ProcessMode)
2352 {
2353 case PROCESS_1_CALC_MIN_WIDTHS:
2354 pDlgData->szlClient.cx = 0;
2355 pDlgData->szlClient.cy = 0;
2356 break;
2357
2358 case PROCESS_6_CALC_POSITIONS:
2359 // start with the table on top
2360 cpTableBox.y = pDlgData->szlClient.cy;
2361 break;
2362 }
2363
2364 FOR_ALL_NODES(&pDlgData->llTables, pNode)
2365 {
2366 PTABLEDEF pTableDefThis = (PTABLEDEF)pNode->pItemData;
2367
2368 if (ProcessMode == PROCESS_6_CALC_POSITIONS)
2369 {
2370 cpTableBox.x = 0;
2371 cpTableBox.y -= pTableDefThis->cpTableBox.cy;
2372 }
2373
2374 if (!(arc = ProcessTable(pTableDefThis,
2375 &cpTableBox, // start pos
2376 ProcessMode,
2377 pDlgData)))
2378 {
2379 switch (ProcessMode)
2380 {
2381 case PROCESS_5_CALC_HEIGHTS:
2382 {
2383 // all sizes have now been computed:
2384 pDlgData->szlClient.cx += pTableDefThis->cpTableBox.cx;
2385 pDlgData->szlClient.cy += pTableDefThis->cpTableBox.cy;
2386 }
2387 }
2388 }
2389 // we should stop on errors V0.9.20 (2002-08-10) [umoeller]
2390 else
2391 break;
2392 }
2393
2394 return arc;
2395}
2396
2397/* ******************************************************************
2398 *
2399 * DLGHITEM parser
2400 *
2401 ********************************************************************/
2402
2403/*
2404 *@@ CreateColumn:
2405 *
2406 *@@changed V1.0.0 (2002-08-18) [umoeller]: mostly rewritten for new algorithm
2407 *@@changed V1.0.1 (2002-12-11) [umoeller]: added interface into nlsGetString
2408 */
2409
2410STATIC APIRET CreateColumn(PDLGPRIVATE pDlgData,
2411 PROWDEF pCurrentRow,
2412 PTABLEDEF pNestedTable,
2413 const CONTROLDEF *pControlDef,
2414 PCOLUMNDEF *ppColumn) // out: new COLUMNDEF
2415{
2416 APIRET arc = NO_ERROR;
2417
2418 if (!pCurrentRow)
2419 arc = DLGERR_CONTROL_BEFORE_ROW;
2420 else
2421 {
2422 // create column and store ctl def
2423 PCOLUMNDEF pColumn;
2424 if (!(pColumn = NEW(COLUMNDEF)))
2425 arc = ERROR_NOT_ENOUGH_MEMORY;
2426 else
2427 {
2428 ZERO(pColumn);
2429
2430 pColumn->pOwningRow = pCurrentRow;
2431
2432 if (pColumn->pNestedTable = pNestedTable)
2433 {
2434 // should we create a PM control around the table?
2435 if (pControlDef)
2436 {
2437 // yes:
2438 pColumn->rclPadding.xLeft
2439 = pColumn->rclPadding.xRight
2440 = GROUP_INNER_SPACING_X * FACTOR_X;
2441
2442 pColumn->rclPadding.yBottom = GROUP_INNER_SPACING_BOTTOM * FACTOR_Y;
2443
2444 pColumn->rclPadding.yTop = GROUP_INNER_SPACING_TOP * FACTOR_Y;
2445
2446 pColumn->rclMargin.xLeft = GROUP_OUTER_SPACING_X * FACTOR_X;
2447 pColumn->rclMargin.xRight = GROUP_OUTER_SPACING_X * FACTOR_X;
2448 pColumn->rclMargin.yBottom = GROUP_OUTER_SPACING_BOTTOM * FACTOR_Y;
2449 pColumn->rclMargin.yTop = GROUP_OUTER_SPACING_TOP * FACTOR_Y;
2450 }
2451 }
2452 else
2453 if (!pControlDef)
2454 arc = DLGERR_NULL_CONTROLDEF;
2455
2456 if (pControlDef)
2457 {
2458 // copy control fields
2459 pColumn->pcszClass = pControlDef->pcszClass;
2460
2461 if (pControlDef->pcszText == LOAD_STRING)
2462 pColumn->pcszText = nlsGetString(pControlDef->usID);
2463 // V1.0.1 (2002-12-11) [umoeller]
2464 else
2465 pColumn->pcszText = pControlDef->pcszText;
2466
2467 pColumn->flStyle = pControlDef->flStyle;
2468 pColumn->usID = pControlDef->usID;
2469
2470 // resolve font
2471 if (pControlDef->pcszFont == CTL_COMMON_FONT)
2472 pColumn->pcszFont = pDlgData->pcszControlsFont;
2473 else
2474 pColumn->pcszFont = pControlDef->pcszFont;
2475 // can be NULL
2476
2477 // copy and convert proposed size
2478 if (pControlDef->szlDlgUnits.cx > 0)
2479 // not SZL_AUTOSIZE, not SZL_REMAINDER, not percentage:
2480 // convert from dlgunits to pixels
2481 pColumn->szlProposed.cx = pControlDef->szlDlgUnits.cx * FACTOR_X;
2482 else
2483 pColumn->szlProposed.cx = pControlDef->szlDlgUnits.cx;
2484
2485 if (pControlDef->szlDlgUnits.cy > 0)
2486 // not SZL_AUTOSIZE, not SZL_REMAINDER, not percentage:
2487 // convert from dlgunits to pixels
2488 pColumn->szlProposed.cy = pControlDef->szlDlgUnits.cy * FACTOR_Y;
2489 else
2490 pColumn->szlProposed.cy = pControlDef->szlDlgUnits.cy;
2491
2492 pColumn->pvCtlData = pControlDef->pvCtlData;
2493
2494 // note: we increase the margin here... it was null
2495 // unless set above because we had a PM group control
2496 // around a table
2497 pColumn->rclMargin.xLeft += pControlDef->duSpacing * FACTOR_X;
2498 pColumn->rclMargin.xRight += pControlDef->duSpacing * FACTOR_X;
2499
2500 pColumn->rclMargin.yBottom += pControlDef->duSpacing * FACTOR_Y;
2501 pColumn->rclMargin.yTop += pControlDef->duSpacing * FACTOR_Y;
2502 }
2503
2504 // calculate the column's total spacing width
2505 // for speed
2506 pColumn->cxSpacingTotal = pColumn->rclPadding.xLeft
2507 + pColumn->rclMargin.xLeft
2508 + pColumn->rclPadding.xRight
2509 + pColumn->rclMargin.xRight;
2510
2511 // set the column index V1.0.0 (2002-08-18) [umoeller]
2512 pColumn->ulColumnIndex = (pCurrentRow->cColumns)++;
2513
2514 *ppColumn = pColumn;
2515 }
2516 }
2517
2518 return arc;
2519}
2520
2521/*
2522 *@@ FreeTable:
2523 * frees the specified table and recurses
2524 * into nested tables, if necessary.
2525 *
2526 * This was added with V0.9.14 to fix the
2527 * bad memory leaks with nested tables.
2528 *
2529 *@@added V0.9.14 (2001-08-01) [umoeller]
2530 */
2531
2532STATIC VOID FreeTable(PTABLEDEF pTable)
2533{
2534 // for each table, clean up the rows
2535 PLISTNODE pRowNode;
2536 FOR_ALL_NODES(&pTable->llRows, pRowNode)
2537 {
2538 PROWDEF pRow = (PROWDEF)pRowNode->pItemData;
2539
2540 // for each row, clean up the columns
2541 PLISTNODE pColumnNode;
2542 FOR_ALL_NODES(&pRow->llColumns, pColumnNode)
2543 {
2544 PCOLUMNDEF pColumn = (PCOLUMNDEF)pColumnNode->pItemData;
2545
2546 if (pColumn->pNestedTable)
2547 // nested table: recurse!
2548 FreeTable(pColumn->pNestedTable);
2549
2550 free(pColumn);
2551 }
2552 lstClear(&pRow->llColumns);
2553
2554 free(pRow);
2555 }
2556 lstClear(&pTable->llRows);
2557
2558 free(pTable);
2559}
2560
2561/*
2562 *@@ STACKITEM:
2563 *
2564 */
2565
2566typedef struct _STACKITEM
2567{
2568 PTABLEDEF pLastTable;
2569 PROWDEF pLastRow;
2570
2571} STACKITEM, *PSTACKITEM;
2572
2573/*
2574 *@@ Dlg0_Init:
2575 *
2576 *@@added V0.9.15 (2001-08-26) [umoeller]
2577 *@@changed V0.9.18 (2002-03-03) [umoeller]: added pllWindows
2578 *@@changed V0.9.19 (2002-04-24) [umoeller]: added resolution correlation
2579 */
2580
2581STATIC APIRET Dlg0_Init(PDLGPRIVATE *ppDlgData,
2582 PCSZ pcszControlsFont,
2583 PLINKLIST pllControls)
2584{
2585 PDLGPRIVATE pDlgData;
2586 POINTL ptl = {100, 100};
2587
2588 if (!(pDlgData = NEW(DLGPRIVATE)))
2589 return ERROR_NOT_ENOUGH_MEMORY;
2590
2591 ZERO(pDlgData);
2592 lstInit(&pDlgData->llTables, FALSE);
2593
2594 if (pllControls)
2595 pDlgData->pllControls = pllControls;
2596
2597 pDlgData->pcszControlsFont = pcszControlsFont;
2598
2599 // cache these now too V0.9.19 (2002-04-17) [umoeller]
2600 pDlgData->cxBorder = WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER);
2601 pDlgData->cyBorder = WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER);
2602
2603 // check how many pixels we get out of the
2604 // dlgunits (100/100) for mapping all sizes
2605 // V0.9.19 (2002-04-24) [umoeller]
2606 if (WinMapDlgPoints(NULLHANDLE,
2607 &ptl,
2608 1,
2609 TRUE))
2610 {
2611 // this worked:
2612 // for 1024x768, I get 200/250 out of the above,
2613 // so calculate a factor from that; we multiply
2614 // szlDlgUnits with this factor when calculating
2615 // the sizes
2616 pDlgData->dFactorX = (double)ptl.x / (double)100; // 2 on 1024x768
2617 pDlgData->dFactorY = (double)ptl.y / (double)100; // 2.5 on 1024x768
2618 }
2619 else
2620 {
2621 // didn't work:
2622 pDlgData->dFactorX = 2;
2623 pDlgData->dFactorY = 2.5;
2624 }
2625
2626 *ppDlgData = pDlgData;
2627
2628 return NO_ERROR;
2629}
2630
2631/*
2632 *@@ Dlg1_ParseTables:
2633 *
2634 *@@added V0.9.15 (2001-08-26) [umoeller]
2635 */
2636
2637STATIC APIRET Dlg1_ParseTables(PDLGPRIVATE pDlgData,
2638 PCDLGHITEM paDlgItems, // in: definition array
2639 ULONG cDlgItems) // in: array item count (NOT array size)
2640{
2641 APIRET arc = NO_ERROR;
2642
2643 LINKLIST llStack;
2644 ULONG ul;
2645 PTABLEDEF pCurrentTable = NULL;
2646 PROWDEF pCurrentRow = NULL;
2647
2648 lstInit(&llStack, TRUE); // this is our stack for nested table definitions
2649
2650 for (ul = 0;
2651 ul < cDlgItems;
2652 ul++)
2653 {
2654 PCDLGHITEM pItemThis = &paDlgItems[ul];
2655
2656 switch (pItemThis->Type)
2657 {
2658 /*
2659 * TYPE_START_NEW_TABLE:
2660 *
2661 */
2662
2663 case TYPE_START_NEW_TABLE:
2664 {
2665 // root table or nested?
2666 BOOL fIsRoot = (pCurrentTable == NULL);
2667
2668 // push the current table on the stack
2669 PSTACKITEM pStackItem;
2670 if (!(pStackItem = NEW(STACKITEM)))
2671 {
2672 arc = ERROR_NOT_ENOUGH_MEMORY;
2673 break;
2674 }
2675 else
2676 {
2677 pStackItem->pLastTable = pCurrentTable;
2678 pStackItem->pLastRow = pCurrentRow;
2679 lstPush(&llStack, pStackItem);
2680 }
2681
2682 // create new table
2683 if (!(pCurrentTable = NEW(TABLEDEF)))
2684 arc = ERROR_NOT_ENOUGH_MEMORY;
2685 else
2686 {
2687 const CONTROLDEF *pControlDef;
2688 ZERO(pCurrentTable);
2689
2690 lstInit(&pCurrentTable->llRows, FALSE);
2691
2692 // if control specified: store it (this will become a PM group)
2693 // pCurrentTable->pCtlDef = (const CONTROLDEF *)pItemThis->ul1; // pItemThis->pCtlDef;
2694 pControlDef = (const CONTROLDEF *)pItemThis->ul1;
2695 // can be NULL for plain table
2696
2697 pCurrentTable->flTable = pItemThis->ul2; // V0.9.20 (2002-08-08) [umoeller]
2698
2699 if (fIsRoot)
2700 // root table:
2701 // append to dialog data list
2702 lstAppendItem(&pDlgData->llTables, pCurrentTable);
2703 else
2704 {
2705 // nested table:
2706 // create "table" column for this in the current row
2707 PCOLUMNDEF pColumn;
2708 if (!(arc = CreateColumn(pDlgData,
2709 pCurrentRow,
2710 pCurrentTable, // nested table
2711 pControlDef,
2712 &pColumn)))
2713 {
2714 pCurrentTable->pOwningColumn = pColumn;
2715 lstAppendItem(&pCurrentRow->llColumns,
2716 pColumn);
2717 }
2718 }
2719 }
2720
2721 // reset current row so we can detect
2722 // wrong DLGHITEM ordering
2723 pCurrentRow = NULL;
2724 }
2725 break;
2726
2727 /*
2728 * TYPE_START_NEW_ROW:
2729 *
2730 */
2731
2732 case TYPE_START_NEW_ROW:
2733 if (!pCurrentTable)
2734 arc = DLGERR_ROW_BEFORE_TABLE;
2735 else
2736 {
2737 // create new row
2738 if (!(pCurrentRow = NEW(ROWDEF)))
2739 arc = ERROR_NOT_ENOUGH_MEMORY;
2740 else
2741 {
2742 ZERO(pCurrentRow);
2743
2744 pCurrentRow->pOwningTable = pCurrentTable;
2745 lstInit(&pCurrentRow->llColumns, FALSE);
2746
2747 pCurrentRow->flRowFormat = pItemThis->ul2;
2748
2749 lstAppendItem(&pCurrentTable->llRows, pCurrentRow);
2750 }
2751 }
2752 break;
2753
2754 /*
2755 * TYPE_CONTROL_DEF:
2756 *
2757 */
2758
2759 case TYPE_CONTROL_DEF:
2760 {
2761 PCOLUMNDEF pColumn;
2762 if (!(arc = CreateColumn(pDlgData,
2763 pCurrentRow,
2764 FALSE, // no nested table
2765 (const CONTROLDEF *)pItemThis->ul1, // pCtlDef, V1.0.0 (2002-08-18) [umoeller]
2766 &pColumn)))
2767 lstAppendItem(&pCurrentRow->llColumns,
2768 pColumn);
2769 }
2770 break;
2771
2772 /*
2773 * TYPE_END_TABLE:
2774 *
2775 */
2776
2777 case TYPE_END_TABLE:
2778 {
2779 PLISTNODE pNode;
2780 if (!(pNode = lstPop(&llStack)))
2781 // nothing on the stack:
2782 arc = DLGERR_TOO_MANY_TABLES_CLOSED;
2783 else
2784 {
2785 PSTACKITEM pStackItem = (PSTACKITEM)pNode->pItemData;
2786 pCurrentTable = pStackItem->pLastTable;
2787 pCurrentRow = pStackItem->pLastRow;
2788
2789 lstRemoveNode(&llStack, pNode);
2790 }
2791 }
2792 break;
2793
2794 default:
2795 arc = DLGERR_INVALID_CODE;
2796 }
2797
2798 if (arc)
2799 break;
2800 }
2801
2802 // all tables should be closed now
2803 if ( (!arc)
2804 && (lstCountItems(&llStack))
2805 )
2806 arc = DLGERR_TABLE_NOT_CLOSED;
2807
2808 lstClear(&llStack);
2809
2810 return arc;
2811}
2812
2813/*
2814 *@@ Dlg2_CalcSizes:
2815 * calls ProcessAll with the first four process modes
2816 * for calculating the final sizes (but not yet the
2817 * positions) of the controls.
2818 *
2819 * See @dlg_algorithm for details.
2820 *
2821 * After this, DLGPRIVATE.szlClient is valid.
2822 *
2823 *@@added V0.9.15 (2001-08-26) [umoeller]
2824 *@@changed V1.0.0 (2002-08-16) [umoeller]: adjusted for new algorithm
2825 */
2826
2827STATIC APIRET Dlg2_CalcSizes(PDLGPRIVATE pDlgData)
2828{
2829 APIRET arc;
2830
2831 // call process mode with the first for "calc size"
2832 // process modes
2833 // changed V1.0.0 (2002-08-16) [umoeller]
2834 if (!(arc = ProcessAll(pDlgData,
2835 PROCESS_1_CALC_MIN_WIDTHS)))
2836 if (!(arc = ProcessAll(pDlgData,
2837 PROCESS_2_CALC_RELATIVE_WIDTHS)))
2838 if (!(arc = ProcessAll(pDlgData,
2839 PROCESS_3_ALIGN_COLUMNS)))
2840 // skip PROCESS_4_REMAINDER_WIDTHS if the dlg doesn't need it
2841 if ( (!(pDlgData->flNeedsProcessing & FL_REMAINDER_WIDTHS))
2842 || (!(arc = ProcessAll(pDlgData,
2843 PROCESS_4_REMAINDER_WIDTHS)))
2844 )
2845 arc = ProcessAll(pDlgData,
2846 PROCESS_5_CALC_HEIGHTS);
2847
2848 // free the cached font resources that
2849 // might have been created here
2850 if (pDlgData->hps)
2851 {
2852 if (pDlgData->lcidLast)
2853 {
2854 GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
2855 GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
2856 }
2857
2858 WinReleasePS(pDlgData->hps);
2859 }
2860
2861 return arc;
2862}
2863
2864/*
2865 *@@ Dlg3_PositionAndCreate:
2866 *
2867 *@@added V0.9.15 (2001-08-26) [umoeller]
2868 *@@changed V0.9.15 (2001-08-26) [umoeller]: BS_DEFAULT for other than first button was ignored, fixed
2869 *@@changed V0.9.20 (2002-08-10) [umoeller]: return code checking was missing, fixed
2870 */
2871
2872STATIC APIRET Dlg3_PositionAndCreate(PDLGPRIVATE pDlgData,
2873 HWND *phwndFocusItem) // out: item to give focus to
2874{
2875 APIRET arc = NO_ERROR;
2876
2877 /*
2878 * 5) compute _positions_ of all controls
2879 *
2880 */
2881
2882 // this was missing a return code, fixed V0.9.20 (2002-08-10) [umoeller]
2883 if (!(arc = ProcessAll(pDlgData,
2884 PROCESS_6_CALC_POSITIONS)))
2885 {
2886 /*
2887 * 6) create control windows, finally
2888 *
2889 */
2890
2891 pDlgData->ptlTotalOfs.x = DLG_OUTER_SPACING_X * FACTOR_X;
2892 pDlgData->ptlTotalOfs.y = DLG_OUTER_SPACING_Y * FACTOR_Y;
2893
2894 // this was missing a return code, fixed V0.9.20 (2002-08-10) [umoeller]
2895 if (!(arc = ProcessAll(pDlgData,
2896 PROCESS_7_CREATE_CONTROLS)))
2897 {
2898 if (pDlgData->hwndDefPushbutton)
2899 {
2900 // we had a default pushbutton:
2901 // go set it V0.9.14 (2001-08-21) [umoeller]
2902 WinSetWindowULong(pDlgData->hwndDlg,
2903 QWL_DEFBUTTON,
2904 pDlgData->hwndDefPushbutton);
2905 *phwndFocusItem = pDlgData->hwndDefPushbutton;
2906 // V0.9.15 (2001-08-26) [umoeller]
2907 }
2908 else
2909 *phwndFocusItem = (pDlgData->hwndFirstFocus)
2910 ? pDlgData->hwndFirstFocus
2911 : pDlgData->hwndDlg;
2912 }
2913 }
2914
2915 return arc;
2916}
2917
2918/*
2919 *@@ Dlg9_Cleanup:
2920 *
2921 *@@added V0.9.15 (2001-08-26) [umoeller]
2922 */
2923
2924STATIC VOID Dlg9_Cleanup(PDLGPRIVATE *ppDlgData)
2925{
2926 PDLGPRIVATE pDlgData;
2927 if ( (ppDlgData)
2928 && (pDlgData = *ppDlgData)
2929 )
2930 {
2931 PLISTNODE pTableNode;
2932
2933 // clean up the tables
2934 FOR_ALL_NODES(&pDlgData->llTables, pTableNode)
2935 {
2936 PTABLEDEF pTable = (PTABLEDEF)pTableNode->pItemData;
2937
2938 FreeTable(pTable);
2939 // this may recurse for nested tables
2940 }
2941
2942 lstClear(&pDlgData->llTables);
2943
2944 free(pDlgData);
2945 *ppDlgData = NULL;
2946 }
2947}
2948
2949/* ******************************************************************
2950 *
2951 * Dialog formatter entry points
2952 *
2953 ********************************************************************/
2954
2955/*
2956 *@@ dlghCreateDlg:
2957 * replacement for WinCreateDlg/WinLoadDlg for creating a
2958 * dialog from a settings array in memory, which is
2959 * formatted automatically.
2960 *
2961 * This does NOT use regular dialog templates from
2962 * module resources. Instead, you pass in an array
2963 * of DLGHITEM structures, which define the controls
2964 * and how they are to be formatted.
2965 *
2966 * The main advantage compared to dialog resources is
2967 * that with this function, you will never have to
2968 * define control _positions_. Instead, you only specify
2969 * the control _sizes_, and all positions are computed
2970 * automatically here. Even better, for many controls,
2971 * auto-sizing is supported according to the control's
2972 * text (e.g. for statics and checkboxes). This is
2973 * quite similar to HTML tables.
2974 *
2975 * A regular standard dialog would use something like
2976 *
2977 + FCF_TITLEBAR | FCF_SYSMENU | FCF_DLGBORDER | FCF_NOBYTEALIGN | FCF_CLOSEBUTTON
2978 *
2979 * for flCreateFlags. To make the dlg sizeable, specify
2980 * FCF_SIZEBORDER instead of FCF_DLGBORDER.
2981 *
2982 * dialog.h defines FCF_FIXED_DLG and FCF_SIZEABLE_DLG
2983 * to make this more handy.
2984 *
2985 * <B>Usage:</B>
2986 *
2987 * Like WinLoadDlg, this creates a standard WC_FRAME and
2988 * subclasses it with fnwpMyDlgProc. It then sends WM_INITDLG
2989 * to the dialog with pCreateParams in mp2.
2990 *
2991 * If this func returns no error, you can then use
2992 * WinProcessDlg with the newly created dialog as usual. In
2993 * your dlg proc, use WinDefDlgProc as usual.
2994 *
2995 * There is NO run-time overhead for either code or memory
2996 * after dialog creation; after this function returns, the
2997 * dialog is a standard dialog as if loaded from WinLoadDlg.
2998 * The array of DLGHITEM structures defines how the
2999 * dialog is set up. All this is ONLY used by this function
3000 * and NOT needed after the dialog has been created.
3001 *
3002 * In DLGHITEM, the "Type" field determines what this
3003 * structure defines. A number of handy macros have been
3004 * defined to make this easier and to provide type-checking
3005 * at compile time. See dialog.h for the complete (and
3006 * ever-expanding) list of definitions.
3007 *
3008 * See @dlg_using_macros for how to use these macros.
3009 *
3010 * Essentially, such a dialog item operates similarly to
3011 * HTML tables. There are rows and columns in the table,
3012 * and each control which is specified must be a column
3013 * in some table. Tables may also nest (see below).
3014 *
3015 * See @dlg_algorithm for the gory details of the new
3016 * algorithm used since V1.0.0.
3017 *
3018 * See @dlg_boxmodel for information about how the
3019 * rectangles are defined.
3020 *
3021 * <B>Example:</B>
3022 *
3023 * The typical calling sequence would be:
3024 *
3025 + HWND hwndDlg = NULLHANDLE;
3026 + if (NO_ERROR == dlghCreateDlg(&hwndDlg,
3027 + hwndOwner,
3028 + FCF_TITLEBAR | FCF_SYSMENU
3029 + | FCF_DLGBORDER | FCF_NOBYTEALIGN,
3030 + fnwpMyDlgProc,
3031 + "My Dlg Title",
3032 + dlgTemplate, // DLGHITEM array
3033 + ARRAYITEMCOUNT(dlgTemplate),
3034 + NULL, // mp2 for WM_INITDLG
3035 + "9.WarpSans")) // default font
3036 + {
3037 + ULONG idReturn = WinProcessDlg(hwndDlg);
3038 + WinDestroyWindow(hwndDlg);
3039 + }
3040 *
3041 * <B>Errors:</B>
3042 *
3043 * This does not return a HWND, but an APIRET. This will be
3044 * one of the following:
3045 *
3046 * -- NO_ERROR: only in that case, the phwndDlg ptr
3047 * receives the HWND of the new dialog, which can
3048 * then be given to WinProcessDlg. Don't forget
3049 * WinDestroyWindow.
3050 *
3051 * -- ERROR_NOT_ENOUGH_MEMORY
3052 *
3053 * -- DLGERR_ROW_BEFORE_TABLE: a row definition appeared
3054 * outside a table definition.
3055 *
3056 * -- DLGERR_CONTROL_BEFORE_ROW: a control definition
3057 * appeared right after a table definition. You must
3058 * specify a row first.
3059 *
3060 * -- DLGERR_NULL_CTL_DEF: A required CONTROLDEF ptr
3061 * was NULL.
3062 *
3063 * -- DLGERR_CANNOT_CREATE_FRAME: unable to create the
3064 * WC_FRAME window. Typically an invalid owner was
3065 * specified.
3066 *
3067 * -- DLGERR_INVALID_CODE: invalid "Type" field in
3068 * DLGHITEM.
3069 *
3070 * -- DLGERR_TABLE_NOT_CLOSED, DLGERR_TOO_MANY_TABLES_CLOSED:
3071 * improper nesting of TYPE_START_NEW_TABLE and
3072 * TYPE_END_TABLE fields.
3073 *
3074 * -- DLGERR_CANNOT_CREATE_CONTROL: creation of some
3075 * sub-control failed. Maybe an invalid window class
3076 * was specified.
3077 *
3078 * -- DLGERR_INVALID_CONTROL_TITLE: bad window title in
3079 * control.
3080 *
3081 * -- DLGERR_INVALID_STATIC_BITMAP: static bitmap contains
3082 * an invalid bitmap handle.
3083 *
3084 *@@changed V0.9.14 (2001-07-07) [umoeller]: fixed disabled mouse with hwndOwner == HWND_DESKTOP
3085 *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed major memory leaks with nested tables
3086 *@@changed V0.9.14 (2001-08-21) [umoeller]: fixed default push button problems
3087 *@@changed V0.9.16 (2001-12-06) [umoeller]: fixed bad owner if not direct desktop child
3088 *@@changed V0.9.19 (2002-04-24) [umoeller]: added excpt handling
3089 +@@changed V1.0.0 (2002-08-21) [umoeller]: now setting wait pointer
3090 */
3091
3092APIRET dlghCreateDlg(HWND *phwndDlg, // out: new dialog
3093 HWND hwndOwner, // in: owner for dialog
3094 ULONG flCreateFlags, // in: standard FCF_* frame flags
3095 PFNWP pfnwpDialogProc, // in: dialog winproc or WinDefDlgProc
3096 PCSZ pcszDlgTitle, // in: title to set for dlg frame's titlebar
3097 PCDLGHITEM paDlgItems, // in: definition array with tables, rows, and columns
3098 ULONG cDlgItems, // in: ARRAYITEMCOUNT(paDlgItems)
3099 PVOID pCreateParams, // in: create param for mp2 of WM_INITDLG
3100 PCSZ pcszControlsFont) // in: font for ctls with CTL_COMMON_FONT
3101{
3102 APIRET arc = NO_ERROR;
3103
3104 HPOINTER hptrOld = winhSetWaitPointer(); // V1.0.0 (2002-08-21) [umoeller]
3105
3106 TRY_LOUD(excpt1)
3107 {
3108 ULONG ul;
3109
3110 PDLGPRIVATE pDlgData = NULL;
3111
3112 HWND hwndDesktop = WinQueryDesktopWindow(NULLHANDLE, NULLHANDLE);
3113 // works with a null HAB
3114
3115 /*
3116 * 1) parse the table and create structures from it
3117 *
3118 */
3119
3120 if (!(arc = Dlg0_Init(&pDlgData,
3121 pcszControlsFont,
3122 NULL)))
3123 {
3124 if (!(arc = Dlg1_ParseTables(pDlgData,
3125 paDlgItems,
3126 cDlgItems)))
3127 {
3128 /*
3129 * 2) create empty dialog frame
3130 *
3131 */
3132
3133 FRAMECDATA fcData = {0};
3134 ULONG flStyle = 0;
3135 HWND hwndOwnersParent;
3136
3137 fcData.cb = sizeof(FRAMECDATA);
3138 fcData.flCreateFlags = flCreateFlags | 0x40000000L;
3139
3140 if (flCreateFlags & FCF_SIZEBORDER)
3141 // dialog has size border:
3142 // add "clip siblings" style
3143 flStyle |= WS_CLIPSIBLINGS;
3144
3145 if (hwndOwner == HWND_DESKTOP)
3146 // there's some dumb XWorkplace code left
3147 // which uses this, and this disables the
3148 // mouse for some reason
3149 // V0.9.14 (2001-07-07) [umoeller]
3150 hwndOwner = NULLHANDLE;
3151
3152 // now, make sure the owner window is child of
3153 // HWND_DESKTOP... if it is not, we'll only disable
3154 // some dumb child window, which is not sufficient
3155 // V0.9.16 (2001-12-06) [umoeller]
3156 while ( (hwndOwner)
3157 && (hwndOwnersParent = WinQueryWindow(hwndOwner, QW_PARENT))
3158 && (hwndOwnersParent != hwndDesktop)
3159 )
3160 hwndOwner = hwndOwnersParent;
3161
3162 if (!(pDlgData->hwndDlg = WinCreateWindow(HWND_DESKTOP,
3163 WC_FRAME,
3164 (PSZ)pcszDlgTitle,
3165 flStyle, // style; invisible for now
3166 0, 0, 0, 0,
3167 hwndOwner,
3168 HWND_TOP,
3169 0, // ID
3170 &fcData,
3171 NULL))) // presparams
3172 arc = DLGERR_CANNOT_CREATE_FRAME;
3173 else
3174 {
3175 HWND hwndDlg = pDlgData->hwndDlg;
3176 HWND hwndFocusItem = NULLHANDLE;
3177 RECTL rclClient;
3178
3179 /*
3180 * 3) compute size of all controls
3181 *
3182 */
3183
3184 if (!(arc = Dlg2_CalcSizes(pDlgData)))
3185 {
3186 WinSubclassWindow(hwndDlg, pfnwpDialogProc);
3187
3188 /*
3189 * 4) compute size of dialog client from total
3190 * size of all controls
3191 */
3192
3193 // calculate the frame size from the client size
3194 rclClient.xLeft = DLG_OUTER_SPACING_X * FACTOR_X; // 10;
3195 rclClient.yBottom = DLG_OUTER_SPACING_Y * FACTOR_Y; // 10;
3196 rclClient.xRight = pDlgData->szlClient.cx
3197 + 2 * (DLG_OUTER_SPACING_X * FACTOR_X)
3198 - 1;
3199 rclClient.yTop = pDlgData->szlClient.cy
3200 + 2 * (DLG_OUTER_SPACING_Y * FACTOR_Y)
3201 - 1;
3202 WinCalcFrameRect(hwndDlg,
3203 &rclClient,
3204 FALSE); // frame from client
3205
3206 WinSetWindowPos(hwndDlg,
3207 0,
3208 10,
3209 10,
3210 rclClient.xRight,
3211 rclClient.yTop,
3212 SWP_MOVE | SWP_SIZE | SWP_NOADJUST);
3213
3214 arc = Dlg3_PositionAndCreate(pDlgData,
3215 &hwndFocusItem);
3216
3217 /*
3218 * 7) WM_INITDLG, set focus
3219 *
3220 */
3221
3222 if (!WinSendMsg(pDlgData->hwndDlg,
3223 WM_INITDLG,
3224 (MPARAM)hwndFocusItem,
3225 (MPARAM)pCreateParams))
3226 {
3227 // if WM_INITDLG returns FALSE, this means
3228 // the dlg proc has not changed the focus;
3229 // we must then set the focus here
3230 WinSetFocus(HWND_DESKTOP, hwndFocusItem);
3231 }
3232 }
3233 }
3234 }
3235
3236 if (arc)
3237 {
3238 // error: clean up
3239 if (pDlgData->hwndDlg)
3240 {
3241 WinDestroyWindow(pDlgData->hwndDlg);
3242 pDlgData->hwndDlg = NULLHANDLE;
3243 }
3244 }
3245 else
3246 // no error: output dialog
3247 *phwndDlg = pDlgData->hwndDlg;
3248
3249 Dlg9_Cleanup(&pDlgData);
3250 }
3251 }
3252 CATCH(excpt1)
3253 {
3254 arc = ERROR_PROTECTION_VIOLATION;
3255 } END_CATCH();
3256
3257 if (arc)
3258 {
3259 CHAR szErr[300];
3260 sprintf(szErr, "Error %d occured in " __FUNCTION__ ".", arc);
3261 winhDebugBox(hwndOwner,
3262 "Error in Dialog Manager",
3263 szErr);
3264 }
3265
3266 WinSetPointer(HWND_DESKTOP, hptrOld); // V1.0.0 (2002-08-21) [umoeller]
3267
3268 return arc;
3269}
3270
3271/*
3272 *@@ dlghFormatDlg:
3273 * similar to dlghCreateDlg in that this can
3274 * dynamically format dialog items.
3275 *
3276 * The differences however are the following:
3277 *
3278 * -- This assumes that hwndDlg already points
3279 * to a valid dialog frame and that this
3280 * dialog should be modified according to
3281 * flFlags.
3282 *
3283 * This is what's used in XWorkplace for notebook
3284 * settings pages since these always have to be
3285 * based on a resource dialog (which is loaded
3286 * empty).
3287 *
3288 * flFlags can be any combination of the following:
3289 *
3290 * -- DFFL_CREATECONTROLS: paDlgItems points to
3291 * an array of cDlgItems DLGHITEM structures
3292 * (see dlghCreateDlg) which is used for creating
3293 * subwindows in hwndDlg. By using this flag, the
3294 * function will essentially work like dlghCreateDlg,
3295 * except that the frame is already created.
3296 *
3297 * If pszlClient is specified, it receives the required
3298 * size of the client to surround all controls properly.
3299 * You can then use dlghResizeFrame to resize the frame
3300 * with a bit of spacing, if desired.
3301 *
3302 *@@added V0.9.16 (2001-09-29) [umoeller]
3303 *@@changed V0.9.18 (2002-03-03) [umoeller]: added pszlClient, fixed output
3304 *@@changed V0.9.19 (2002-04-24) [umoeller]: added excpt handling
3305 */
3306
3307APIRET dlghFormatDlg(HWND hwndDlg, // in: dialog frame to work on
3308 PCDLGHITEM paDlgItems, // in: definition array
3309 ULONG cDlgItems, // in: array item count (NOT array size)
3310 PCSZ pcszControlsFont, // in: font for ctls with CTL_COMMON_FONT
3311 ULONG flFlags, // in: DFFL_* flags
3312 PSIZEL pszlClient, // out: size of all controls (ptr can be NULL)
3313 PVOID *ppllControls) // out: new LINKLIST receiving HWNDs of created controls (ptr can be NULL)
3314{
3315 APIRET arc = NO_ERROR;
3316
3317 TRY_LOUD(excpt1)
3318 {
3319 ULONG ul;
3320
3321 PDLGPRIVATE pDlgData = NULL;
3322 PLINKLIST pllControls = NULL;
3323
3324 /*
3325 * 1) parse the table and create structures from it
3326 *
3327 */
3328
3329 if (ppllControls)
3330 pllControls = *(PLINKLIST*)ppllControls = lstCreate(FALSE);
3331
3332 if (!(arc = Dlg0_Init(&pDlgData,
3333 pcszControlsFont,
3334 pllControls)))
3335 {
3336 if (!(arc = Dlg1_ParseTables(pDlgData,
3337 paDlgItems,
3338 cDlgItems)))
3339 {
3340 HWND hwndFocusItem;
3341
3342 /*
3343 * 2) create empty dialog frame
3344 *
3345 */
3346
3347 pDlgData->hwndDlg = hwndDlg;
3348
3349 /*
3350 * 3) compute size of all controls
3351 *
3352 */
3353
3354 Dlg2_CalcSizes(pDlgData);
3355
3356 if (pszlClient)
3357 {
3358 pszlClient->cx = pDlgData->szlClient.cx
3359 + 2 * (DLG_OUTER_SPACING_X * FACTOR_X);
3360 pszlClient->cy = pDlgData->szlClient.cy
3361 + 2 * (DLG_OUTER_SPACING_Y * FACTOR_Y);
3362 }
3363
3364 if (flFlags & DFFL_CREATECONTROLS)
3365 {
3366 if (!(arc = Dlg3_PositionAndCreate(pDlgData,
3367 &hwndFocusItem)))
3368 WinSetFocus(HWND_DESKTOP, hwndFocusItem);
3369 }
3370 }
3371
3372 Dlg9_Cleanup(&pDlgData);
3373 }
3374 }
3375 CATCH(excpt1)
3376 {
3377 arc = ERROR_PROTECTION_VIOLATION;
3378 } END_CATCH();
3379
3380 if (arc)
3381 {
3382 CHAR szErr[300];
3383 sprintf(szErr, "Error %d occured in " __FUNCTION__ ".", arc);
3384 winhDebugBox(NULLHANDLE,
3385 "Error in Dialog Manager",
3386 szErr);
3387 }
3388
3389 return arc;
3390}
3391
3392/*
3393 *@@ dlghResizeFrame:
3394 *
3395 *@@added V0.9.18 (2002-03-03) [umoeller]
3396 */
3397
3398VOID dlghResizeFrame(HWND hwndDlg,
3399 PSIZEL pszlClient)
3400{
3401 // calculate the frame size from the client size
3402 RECTL rclClient;
3403 rclClient.xLeft = 10;
3404 rclClient.yBottom = 10;
3405 rclClient.xRight = pszlClient->cx;
3406 rclClient.yTop = pszlClient->cy;
3407 WinCalcFrameRect(hwndDlg,
3408 &rclClient,
3409 FALSE); // frame from client
3410
3411 WinSetWindowPos(hwndDlg,
3412 0,
3413 10,
3414 10,
3415 rclClient.xRight,
3416 rclClient.yTop,
3417 SWP_MOVE | SWP_SIZE | SWP_NOADJUST);
3418}
3419
3420/* ******************************************************************
3421 *
3422 * Dialog arrays
3423 *
3424 ********************************************************************/
3425
3426/*
3427 *@@ dlghCreateArray:
3428 * creates a "dialog array" for dynamically
3429 * building a dialog template in memory.
3430 *
3431 * A dialog array is simply an array of
3432 * DLGHITEM structures, as you would normally
3433 * define them statically in the source.
3434 * However, there are situations where you
3435 * might want to leave out certain controls
3436 * depending on certain conditions, which
3437 * can be difficult with static arrays.
3438 *
3439 * As a result, these "array" functions have
3440 * been added to allow for adding static
3441 * DLGHITEM subarrays to a dynamic array in
3442 * memory, which can then be passed to the
3443 * formatter.
3444 *
3445 * Usage:
3446 *
3447 * 1) Call this function with the maximum
3448 * amount of DLGHITEM's that will need
3449 * to be allocated in cMaxItems. Set this
3450 * to the total sum of all DLGHITEM's
3451 * in all the subarrays.
3452 *
3453 * 2) For each of the subarrays, call
3454 * dlghAppendToArray to have the subarray
3455 * appended to the dialog array.
3456 * After each call, DLGARRAY.cDlgItemsNow
3457 * will contain the actual total count of
3458 * DLGHITEM's that were added.
3459 *
3460 * 3) Call dlghCreateDialog with the dialog
3461 * array.
3462 *
3463 * 4) Call dlghFreeArray.
3464 *
3465 * Sort of like this (error checking omitted):
3466 *
3467 + DLGHITEM dlgSampleFront = ... // always included
3468 + DLGHITEM dlgSampleSometimes = ... // not always included
3469 + DLGHITEM dlgSampleTail = ... // always included
3470 +
3471 + PDLGARRAY pArraySample = NULL;
3472 + // create array with sufficient size
3473 + dlghCreateArray( ARRAYITEMCOUNT(dlgSampleFront)
3474 + + ARRAYITEMCOUNT(dlgSampleSometimes)
3475 + + ARRAYITEMCOUNT(dlgSampleTail),
3476 + &pArraySample);
3477 +
3478 + // always include front
3479 + dlghAppendToArray(pArraySample,
3480 + dlgSampleFront,
3481 + ARRAYITEMCOUNT(dlgSampleFront));
3482 + // include "sometimes" conditionally
3483 + if (...)
3484 + dlghAppendToArray(pArraySample,
3485 + dlgSampleSometimes,
3486 + ARRAYITEMCOUNT(dlgSampleSometimes));
3487 + // include tail always
3488 + dlghAppendToArray(pArraySample,
3489 + dlgSampleTail,
3490 + ARRAYITEMCOUNT(dlgSampleTail));
3491 +
3492 + // now create the dialog from the array
3493 + dlghCreateDialog(&hwndDlg,
3494 + hwndOwner,
3495 + FCF_ ...
3496 + fnwpMyDialogProc,
3497 + "Title",
3498 + pArray->paDlgItems, // dialog array!
3499 + pArray->cDlgItemsNow, // real count of items!
3500 + NULL,
3501 + NULL);
3502 +
3503 + dlghFreeArray(&pArraySample);
3504 *
3505 *@@added V0.9.16 (2001-10-15) [umoeller]
3506 */
3507
3508APIRET dlghCreateArray(ULONG cMaxItems,
3509 PDLGARRAY *ppArray) // out: DLGARRAY
3510{
3511 APIRET arc = NO_ERROR;
3512 PDLGARRAY pArray;
3513
3514 if (pArray = NEW(DLGARRAY))
3515 {
3516 ULONG cb;
3517
3518 ZERO(pArray);
3519 if ( (cb = cMaxItems * sizeof(DLGHITEM))
3520 && (pArray->paDlgItems = (DLGHITEM*)malloc(cb))
3521 )
3522 {
3523 memset(pArray->paDlgItems, 0, cb);
3524 pArray->cDlgItemsMax = cMaxItems;
3525 *ppArray = pArray;
3526 }
3527 else
3528 arc = ERROR_NOT_ENOUGH_MEMORY;
3529
3530 if (arc)
3531 dlghFreeArray(&pArray);
3532 }
3533 else
3534 arc = ERROR_NOT_ENOUGH_MEMORY;
3535
3536 return arc;
3537}
3538
3539/*
3540 *@@ dlghFreeArray:
3541 * frees a dialog array created by dlghCreateArray.
3542 *
3543 *@@added V0.9.16 (2001-10-15) [umoeller]
3544 */
3545
3546APIRET dlghFreeArray(PDLGARRAY *ppArray)
3547{
3548 PDLGARRAY pArray;
3549 if ( (ppArray)
3550 && (pArray = *ppArray)
3551 )
3552 {
3553 if (pArray->paDlgItems)
3554 free(pArray->paDlgItems);
3555 free(pArray);
3556 }
3557 else
3558 return ERROR_INVALID_PARAMETER;
3559
3560 return NO_ERROR;
3561}
3562
3563/*
3564 *@@ dlghAppendToArray:
3565 * appends a subarray of DLGHITEM's to the
3566 * given DLGARRAY. See dlghCreateArray for
3567 * usage.
3568 *
3569 * Returns:
3570 *
3571 * -- NO_ERROR
3572 *
3573 * -- ERROR_INVALID_PARAMETER
3574 *
3575 * -- DLGERR_ARRAY_TOO_SMALL: pArray does not
3576 * have enough memory to hold the new items.
3577 * The cMaxItems parameter given to dlghCreateArray
3578 * wasn't large enough.
3579 *
3580 *@@added V0.9.16 (2001-10-15) [umoeller]
3581 */
3582
3583APIRET dlghAppendToArray(PDLGARRAY pArray, // in: dialog array created by dlghCreateArray
3584 PCDLGHITEM paItems, // in: subarray to be appended
3585 ULONG cItems) // in: subarray item count (NOT array size)
3586{
3587 APIRET arc = NO_ERROR;
3588 if (pArray)
3589 {
3590 if ( (pArray->cDlgItemsMax >= cItems)
3591 && (pArray->cDlgItemsMax - pArray->cDlgItemsNow >= cItems)
3592 )
3593 {
3594 // enough space left in the array:
3595 memcpy(&pArray->paDlgItems[pArray->cDlgItemsNow],
3596 paItems, // source
3597 cItems * sizeof(DLGHITEM));
3598 pArray->cDlgItemsNow += cItems;
3599 }
3600 else
3601 arc = DLGERR_ARRAY_TOO_SMALL;
3602 }
3603 else
3604 arc = ERROR_INVALID_PARAMETER;
3605
3606 return arc;
3607}
3608
3609/* ******************************************************************
3610 *
3611 * Standard dialogs
3612 *
3613 ********************************************************************/
3614
3615/*
3616 *@@ fnwpMessageBox:
3617 *
3618 *@@added V0.9.19 (2002-04-24) [umoeller]
3619 */
3620
3621MRESULT EXPENTRY fnwpMessageBox(HWND hwndBox, ULONG msg, MPARAM mp1, MPARAM mp2)
3622{
3623 switch (msg)
3624 {
3625 case WM_HELP:
3626 {
3627 PFNHELP pfnHelp;
3628 if (pfnHelp = (PFNHELP)WinQueryWindowPtr(hwndBox, QWL_USER))
3629 pfnHelp(hwndBox);
3630
3631 return 0;
3632 }
3633 }
3634
3635 return WinDefDlgProc(hwndBox, msg, mp1, mp2);
3636}
3637
3638/*
3639 *@@ dlghCreateMessageBox:
3640 *
3641 *@@added V0.9.13 (2001-06-21) [umoeller]
3642 *@@changed V0.9.14 (2001-07-26) [umoeller]: fixed missing focus on buttons
3643 *@@changed V0.9.19 (2002-04-24) [umoeller]: added pfnHelp
3644 *@@changed V0.9.20 (2002-07-12) [umoeller]: made icon spacing wider
3645 *@@changed V0.9.20 (2002-08-10) [umoeller]: fixed missing close button
3646 *@@changed V1.0.0 (2002-08-16) [umoeller]: now using table alignment
3647 */
3648
3649APIRET dlghCreateMessageBox(HWND *phwndDlg,
3650 HWND hwndOwner,
3651 HPOINTER hptrIcon,
3652 PCSZ pcszTitle,
3653 PCSZ pcszMessage,
3654 PFNHELP pfnHelp, // in: help callback or NULL
3655 ULONG flFlags,
3656 PCSZ pcszFont,
3657 const MSGBOXSTRINGS *pStrings,
3658 PULONG pulAlarmFlag) // out: alarm sound to be played
3659{
3660 APIRET arc;
3661
3662 CONTROLDEF
3663 Icon = CONTROLDEF_ICON_WIDER(NULLHANDLE, 0),
3664 // made icon spacing wider V0.9.20 (2002-07-12) [umoeller]
3665 Spacing = CONTROLDEF_TEXT(NULL, 0, 1, 1),
3666 InfoText = CONTROLDEF_TEXT_WORDBREAK(NULL, 10, MSGBOX_TEXTWIDTH),
3667 Buttons[] =
3668 {
3669 CONTROLDEF_PUSHBUTTON(NULL, 1, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT),
3670 CONTROLDEF_PUSHBUTTON(NULL, 2, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT),
3671 CONTROLDEF_PUSHBUTTON(NULL, 3, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT),
3672 CONTROLDEF_HELPPUSHBUTTON(NULL, 4, STD_BUTTON_WIDTH, STD_BUTTON_HEIGHT)
3673 };
3674
3675 DLGHITEM MessageBoxFront[] =
3676 {
3677 START_TABLE_ALIGN,
3678 START_ROW(ROW_VALIGN_CENTER),
3679 CONTROL_DEF(&Icon),
3680 START_TABLE,
3681 START_ROW(ROW_VALIGN_CENTER),
3682 CONTROL_DEF(&Spacing),
3683 START_ROW(ROW_VALIGN_CENTER),
3684 CONTROL_DEF(&InfoText),
3685 START_ROW(ROW_VALIGN_CENTER),
3686 CONTROL_DEF(&Spacing),
3687 END_TABLE,
3688 START_ROW(ROW_VALIGN_CENTER),
3689 CONTROL_DEF(&Spacing),
3690 START_TABLE,
3691 START_ROW(ROW_VALIGN_CENTER),
3692 CONTROL_DEF(&Buttons[0]),
3693 CONTROL_DEF(&Buttons[1]),
3694 CONTROL_DEF(&Buttons[2]),
3695 },
3696 MessageBoxHelp[] =
3697 {
3698 CONTROL_DEF(&Buttons[3]),
3699 },
3700 MessageBoxTail[] =
3701 {
3702 END_TABLE,
3703 END_TABLE
3704 };
3705
3706 ULONG flButtons = flFlags & 0xF; // low nibble contains MB_YESNO etc.
3707 PDLGARRAY pArrayBox;
3708
3709 PCSZ p0 = "Error",
3710 p1 = NULL,
3711 p2 = NULL;
3712
3713 Icon.pcszText = (PCSZ)hptrIcon;
3714 InfoText.pcszText = pcszMessage;
3715
3716 // now work on the three buttons of the dlg template:
3717 // give them proper titles or hide them
3718 if (flButtons == MB_OK)
3719 {
3720 p0 = pStrings->pcszOK;
3721 }
3722 else if (flButtons == MB_OKCANCEL)
3723 {
3724 p0 = pStrings->pcszOK;
3725 p1 = pStrings->pcszCancel;
3726 }
3727 else if (flButtons == MB_RETRYCANCEL)
3728 {
3729 p0 = pStrings->pcszRetry;
3730 p1 = pStrings->pcszCancel;
3731 }
3732 else if (flButtons == MB_ABORTRETRYIGNORE)
3733 {
3734 p0 = pStrings->pcszAbort;
3735 p1 = pStrings->pcszRetry;
3736 p2 = pStrings->pcszIgnore;
3737 }
3738 else if (flButtons == MB_YESNO)
3739 {
3740 p0 = pStrings->pcszYes;
3741 p1 = pStrings->pcszNo;
3742 }
3743 else if (flButtons == MB_YESNOCANCEL)
3744 {
3745 p0 = pStrings->pcszYes;
3746 p1 = pStrings->pcszNo;
3747 p2 = pStrings->pcszCancel;
3748 }
3749 else if (flButtons == MB_CANCEL)
3750 {
3751 p0 = pStrings->pcszCancel;
3752 }
3753 else if (flButtons == MB_ENTER)
3754 {
3755 p0 = pStrings->pcszEnter;
3756 }
3757 else if (flButtons == MB_ENTERCANCEL)
3758 {
3759 p0 = pStrings->pcszEnter;
3760 p1 = pStrings->pcszCancel;
3761 }
3762 else if (flButtons == MB_YES_YES2ALL_NO)
3763 {
3764 p0 = pStrings->pcszYes;
3765 p1 = pStrings->pcszYesToAll;
3766 p2 = pStrings->pcszNo;
3767 }
3768
3769 // now set strings and hide empty buttons
3770 Buttons[0].pcszText = p0;
3771
3772 if (p1)
3773 Buttons[1].pcszText = p1;
3774 else
3775 Buttons[1].flStyle &= ~WS_VISIBLE;
3776
3777 if (p2)
3778 Buttons[2].pcszText = p2;
3779 else
3780 Buttons[2].flStyle &= ~WS_VISIBLE;
3781
3782 // query default button IDs
3783 if (flFlags & MB_DEFBUTTON2)
3784 Buttons[1].flStyle |= BS_DEFAULT;
3785 else if (flFlags & MB_DEFBUTTON3)
3786 Buttons[2].flStyle |= BS_DEFAULT;
3787 else
3788 Buttons[0].flStyle |= BS_DEFAULT;
3789
3790 *pulAlarmFlag = WA_NOTE;
3791 if (flFlags & (MB_ICONHAND | MB_ERROR))
3792 *pulAlarmFlag = WA_ERROR;
3793 else if (flFlags & (MB_ICONEXCLAMATION | MB_WARNING))
3794 *pulAlarmFlag = WA_WARNING;
3795
3796 if (pfnHelp)
3797 Buttons[3].pcszText = pStrings->pcszHelp;
3798
3799 if (!(arc = dlghCreateArray( ARRAYITEMCOUNT(MessageBoxFront)
3800 + ARRAYITEMCOUNT(MessageBoxHelp)
3801 + ARRAYITEMCOUNT(MessageBoxTail),
3802 &pArrayBox)))
3803 {
3804 if ( (!(arc = dlghAppendToArray(pArrayBox,
3805 MessageBoxFront,
3806 ARRAYITEMCOUNT(MessageBoxFront))))
3807 && ( (!pfnHelp)
3808 || (!(arc = dlghAppendToArray(pArrayBox,
3809 MessageBoxHelp,
3810 ARRAYITEMCOUNT(MessageBoxHelp))))
3811 )
3812 && (!(arc = dlghAppendToArray(pArrayBox,
3813 MessageBoxTail,
3814 ARRAYITEMCOUNT(MessageBoxTail))))
3815 )
3816 {
3817 if (!(arc = dlghCreateDlg(phwndDlg,
3818 hwndOwner,
3819 FCF_TITLEBAR | FCF_SYSMENU | FCF_DLGBORDER | FCF_NOBYTEALIGN
3820 | FCF_CLOSEBUTTON, // was missing V0.9.20 (2002-08-10) [umoeller]
3821 fnwpMessageBox,
3822 pcszTitle,
3823 pArrayBox->paDlgItems,
3824 pArrayBox->cDlgItemsNow,
3825 NULL,
3826 pcszFont)))
3827 // added help callback V0.9.19 (2002-04-24) [umoeller]
3828 WinSetWindowPtr(*phwndDlg, QWL_USER, (PVOID)pfnHelp);
3829 }
3830
3831 dlghFreeArray(&pArrayBox);
3832 }
3833
3834 return arc;
3835}
3836
3837/*
3838 *@@ dlghProcessMessageBox:
3839 *
3840 *@@added V0.9.13 (2001-06-21) [umoeller]
3841 */
3842
3843ULONG dlghProcessMessageBox(HWND hwndDlg,
3844 ULONG ulAlarmFlag,
3845 ULONG flFlags)
3846{
3847 ULONG ulrcDlg;
3848 ULONG flButtons = flFlags & 0xF; // low nibble contains MB_YESNO etc.
3849
3850 winhCenterWindow(hwndDlg);
3851
3852 if (flFlags & MB_SYSTEMMODAL)
3853 WinSetSysModalWindow(HWND_DESKTOP, hwndDlg);
3854
3855 if (ulAlarmFlag)
3856 WinAlarm(HWND_DESKTOP, ulAlarmFlag);
3857
3858 ulrcDlg = WinProcessDlg(hwndDlg);
3859
3860 WinDestroyWindow(hwndDlg);
3861
3862 if (flButtons == MB_OK)
3863 return MBID_OK;
3864 else if (flButtons == MB_OKCANCEL)
3865 switch (ulrcDlg)
3866 {
3867 case 1: return MBID_OK;
3868 default: return MBID_CANCEL;
3869 }
3870 else if (flButtons == MB_RETRYCANCEL)
3871 switch (ulrcDlg)
3872 {
3873 case 1: return MBID_RETRY;
3874 default: return MBID_CANCEL;
3875 }
3876 else if (flButtons == MB_ABORTRETRYIGNORE)
3877 switch (ulrcDlg)
3878 {
3879 case 2: return MBID_RETRY;
3880 case 3: return MBID_IGNORE;
3881 default: return MBID_ABORT;
3882 }
3883 else if (flButtons == MB_YESNO)
3884 switch (ulrcDlg)
3885 {
3886 case 1: return MBID_YES;
3887 default: return MBID_NO;
3888 }
3889 else if (flButtons == MB_YESNOCANCEL)
3890 switch (ulrcDlg)
3891 {
3892 case 1: return MBID_YES;
3893 case 2: return MBID_NO;
3894 default: return MBID_CANCEL;
3895 }
3896 else if (flButtons == MB_CANCEL)
3897 return MBID_CANCEL;
3898 else if (flButtons == MB_ENTER)
3899 return MBID_ENTER;
3900 else if (flButtons == MB_ENTERCANCEL)
3901 switch (ulrcDlg)
3902 {
3903 case 1: return MBID_ENTER;
3904 default: return MBID_CANCEL;
3905 }
3906 else if (flButtons == MB_YES_YES2ALL_NO)
3907 switch (ulrcDlg)
3908 {
3909 case 1: return MBID_YES;
3910 case 2: return MBID_YES2ALL;
3911 default: return MBID_NO;
3912 }
3913
3914 return MBID_CANCEL;
3915}
3916
3917/*
3918 *@@ dlghMessageBox:
3919 * WinMessageBox replacement, which uses dlghCreateDlg
3920 * internally.
3921 *
3922 * This has all the flags of the standard call,
3923 * but looks much prettier. Besides, it allows
3924 * you to specify any icon to be displayed.
3925 *
3926 * Currently the following flStyle's are supported:
3927 *
3928 * -- MB_OK 0x0000
3929 * -- MB_OKCANCEL 0x0001
3930 * -- MB_RETRYCANCEL 0x0002
3931 * -- MB_ABORTRETRYIGNORE 0x0003
3932 * -- MB_YESNO 0x0004
3933 * -- MB_YESNOCANCEL 0x0005
3934 * -- MB_CANCEL 0x0006
3935 * -- MB_ENTER 0x0007 (not implemented yet)
3936 * -- MB_ENTERCANCEL 0x0008 (not implemented yet)
3937 *
3938 * -- MB_YES_YES2ALL_NO 0x0009
3939 * This is new: this has three buttons called "Yes"
3940 * (MBID_YES), "Yes to all" (MBID_YES2ALL), "No" (MBID_NO).
3941 *
3942 * -- MB_DEFBUTTON2 (for two-button styles)
3943 * -- MB_DEFBUTTON3 (for three-button styles)
3944 *
3945 * -- MB_ICONHAND
3946 * -- MB_ICONEXCLAMATION
3947 *
3948 * If (pfnHelp != NULL), a "Help" button is also added and
3949 * pfnHelp gets called when the user presses it or the F1
3950 * key.
3951 *
3952 * Returns MBID_* codes like WinMessageBox.
3953 *
3954 *@@added V0.9.13 (2001-06-21) [umoeller]
3955 *@@changed V0.9.19 (2002-04-24) [umoeller]: added pfnHelp
3956 */
3957
3958ULONG dlghMessageBox(HWND hwndOwner, // in: owner for msg box
3959 HPOINTER hptrIcon, // in: icon to display
3960 PCSZ pcszTitle, // in: title
3961 PCSZ pcszMessage, // in: message
3962 PFNHELP pfnHelp, // in: help callback or NULL
3963 ULONG flFlags, // in: standard message box flags
3964 PCSZ pcszFont, // in: font (e.g. "9.WarpSans")
3965 const MSGBOXSTRINGS *pStrings) // in: strings array
3966{
3967 HWND hwndDlg;
3968 ULONG ulAlarmFlag;
3969 APIRET arc;
3970 CHAR szMsg[100];
3971
3972 if ( (!(arc = dlghCreateMessageBox(&hwndDlg,
3973 hwndOwner,
3974 hptrIcon,
3975 pcszTitle,
3976 pcszMessage,
3977 pfnHelp,
3978 flFlags,
3979 pcszFont,
3980 pStrings,
3981 &ulAlarmFlag)))
3982 && (hwndDlg)
3983 )
3984 {
3985 // SHOW DIALOG
3986 return dlghProcessMessageBox(hwndDlg,
3987 ulAlarmFlag,
3988 flFlags);
3989 }
3990
3991 sprintf(szMsg, "dlghCreateMessageBox reported error %u.", arc);
3992 WinMessageBox(HWND_DESKTOP,
3993 NULLHANDLE,
3994 "Error",
3995 szMsg,
3996 0,
3997 MB_CANCEL | MB_MOVEABLE);
3998
3999 return DID_CANCEL;
4000}
4001
4002/*
4003 *@@ cmnTextEntryBox:
4004 * common dialog for entering a text string.
4005 * The dialog has a descriptive text on top
4006 * with an entry field below and "OK" and "Cancel"
4007 * buttons.
4008 *
4009 * The string from the user is returned in a
4010 * new buffer, which must be free'd by the caller.
4011 * Returns NULL if the user pressed "Cancel".
4012 *
4013 * fl can be any combination of the following
4014 * flags:
4015 *
4016 * -- TEBF_REMOVETILDE: tilde ("~") characters
4017 * are removed from pcszTitle before setting
4018 * the title. Useful for reusing menu item
4019 * texts.
4020 *
4021 * -- TEBF_REMOVEELLIPSE: ellipse ("...") strings
4022 * are removed from pcszTitle before setting
4023 * the title. Useful for reusing menu item
4024 * texts.
4025 *
4026 * -- TEBF_SELECTALL: the default text in the
4027 * entry field is initially highlighted.
4028 *
4029 *@@added V0.9.15 (2001-09-14) [umoeller]
4030 */
4031
4032PSZ dlghTextEntryBox(HWND hwndOwner,
4033 PCSZ pcszTitle, // in: dlg title
4034 PCSZ pcszDescription, // in: descriptive text above entry field
4035 PCSZ pcszDefault, // in: default text for entry field or NULL
4036 PCSZ pcszOK, // in: "OK" string
4037 PCSZ pcszCancel, // in: "Cancel" string
4038 ULONG ulMaxLen, // in: maximum length for entry
4039 ULONG fl, // in: TEBF_* flags
4040 PCSZ pcszFont) // in: font (e.g. "9.WarpSans")
4041{
4042 CONTROLDEF
4043 Static = CONTROLDEF_TEXT_WORDBREAK(
4044 NULL,
4045 -1,
4046 150),
4047 Entry = CONTROLDEF_ENTRYFIELD(
4048 NULL,
4049 999,
4050 150,
4051 SZL_AUTOSIZE),
4052 OKButton = CONTROLDEF_DEFPUSHBUTTON(
4053 NULL,
4054 DID_OK,
4055 STD_BUTTON_WIDTH,
4056 STD_BUTTON_HEIGHT),
4057 CancelButton = CONTROLDEF_PUSHBUTTON(
4058 NULL,
4059 DID_CANCEL,
4060 STD_BUTTON_WIDTH,
4061 STD_BUTTON_HEIGHT);
4062
4063 DLGHITEM DlgTemplate[] =
4064 {
4065 START_TABLE,
4066 START_ROW(0),
4067 CONTROL_DEF(&Static),
4068 START_ROW(0),
4069 CONTROL_DEF(&Entry),
4070 START_ROW(0),
4071 CONTROL_DEF(&OKButton),
4072 CONTROL_DEF(&CancelButton),
4073 END_TABLE
4074 };
4075
4076 HWND hwndDlg = NULLHANDLE;
4077 PSZ pszReturn = NULL;
4078 XSTRING strTitle;
4079
4080 xstrInitCopy(&strTitle, pcszTitle, 0);
4081
4082 if (fl & (TEBF_REMOVEELLIPSE | TEBF_REMOVETILDE))
4083 {
4084 ULONG ulOfs;
4085 if (fl & TEBF_REMOVEELLIPSE)
4086 {
4087 ulOfs = 0;
4088 while (xstrFindReplaceC(&strTitle,
4089 &ulOfs,
4090 "...",
4091 ""))
4092 ;
4093 }
4094
4095 if (fl & TEBF_REMOVETILDE)
4096 {
4097 ulOfs = 0;
4098 while (xstrFindReplaceC(&strTitle,
4099 &ulOfs,
4100 "~",
4101 ""))
4102 ;
4103 }
4104 }
4105
4106 Static.pcszText = pcszDescription;
4107
4108 OKButton.pcszText = pcszOK;
4109 CancelButton.pcszText = pcszCancel;
4110
4111 if (NO_ERROR == dlghCreateDlg(&hwndDlg,
4112 hwndOwner,
4113 FCF_FIXED_DLG,
4114 WinDefDlgProc,
4115 strTitle.psz,
4116 DlgTemplate, // DLGHITEM array
4117 ARRAYITEMCOUNT(DlgTemplate),
4118 NULL,
4119 pcszFont))
4120 {
4121 HWND hwndEF = WinWindowFromID(hwndDlg, 999);
4122 winhCenterWindow(hwndDlg);
4123 winhSetEntryFieldLimit(hwndEF, ulMaxLen);
4124 if (pcszDefault)
4125 {
4126 WinSetWindowText(hwndEF, (PSZ)pcszDefault);
4127 if (fl & TEBF_SELECTALL)
4128 winhEntryFieldSelectAll(hwndEF);
4129 }
4130 WinSetFocus(HWND_DESKTOP, hwndEF);
4131 if (DID_OK == WinProcessDlg(hwndDlg))
4132 pszReturn = winhQueryWindowText(hwndEF);
4133
4134 WinDestroyWindow(hwndDlg);
4135 }
4136
4137 xstrClear(&strTitle);
4138
4139 return pszReturn;
4140}
4141
4142/* ******************************************************************
4143 *
4144 * Dialog input handlers
4145 *
4146 ********************************************************************/
4147
4148/*
4149 *@@ dlghSetPrevFocus:
4150 * "backward" function for rotating the focus
4151 * in a dialog when the "shift+tab" keys get
4152 * pressed.
4153 *
4154 * pllWindows must be a linked list with the
4155 * plain HWND window handles of the focussable
4156 * controls in the dialog.
4157 */
4158
4159VOID dlghSetPrevFocus(PVOID pvllWindows)
4160{
4161 PLINKLIST pllWindows = (PLINKLIST)pvllWindows;
4162
4163 // check current focus
4164 HWND hwndFocus = WinQueryFocus(HWND_DESKTOP);
4165
4166 PLISTNODE pNode = lstNodeFromItem(pllWindows, (PVOID)hwndFocus);
4167
4168 BOOL fRestart = FALSE;
4169
4170 while (pNode)
4171 {
4172 CHAR szClass[100];
4173
4174 // previos node
4175 pNode = pNode->pPrevious;
4176
4177 if ( (!pNode) // no next, or not found:
4178 && (!fRestart) // avoid infinite looping if bad list
4179 )
4180 {
4181 pNode = lstQueryLastNode(pllWindows);
4182 fRestart = TRUE;
4183 }
4184
4185 if (pNode)
4186 {
4187 // check if this is a focusable control
4188 if (WinQueryClassName((HWND)pNode->pItemData,
4189 sizeof(szClass),
4190 szClass))
4191 {
4192 if ( (strcmp(szClass, "#5")) // not static
4193 )
4194 break;
4195 // else: go for next then
4196 }
4197 }
4198 }
4199
4200 if (pNode)
4201 {
4202 WinSetFocus(HWND_DESKTOP,
4203 (HWND)pNode->pItemData);
4204 }
4205}
4206
4207/*
4208 *@@ dlghSetNextFocus:
4209 * "forward" function for rotating the focus
4210 * in a dialog when the "ab" key gets pressed.
4211 *
4212 * pllWindows must be a linked list with the
4213 * plain HWND window handles of the focussable
4214 * controls in the dialog.
4215 */
4216
4217VOID dlghSetNextFocus(PVOID pvllWindows)
4218{
4219 PLINKLIST pllWindows = (PLINKLIST)pvllWindows;
4220
4221 // check current focus
4222 HWND hwndFocus = WinQueryFocus(HWND_DESKTOP);
4223
4224 PLISTNODE pNode = lstNodeFromItem(pllWindows, (PVOID)hwndFocus);
4225
4226 BOOL fRestart = FALSE;
4227
4228 while (pNode)
4229 {
4230 CHAR szClass[100];
4231
4232 // next focus in node
4233 pNode = pNode->pNext;
4234
4235 if ( (!pNode) // no next, or not found:
4236 && (!fRestart) // avoid infinite looping if bad list
4237 )
4238 {
4239 pNode = lstQueryFirstNode(pllWindows);
4240 fRestart = TRUE;
4241 }
4242
4243 if (pNode)
4244 {
4245 // check if this is a focusable control
4246 if (WinQueryClassName((HWND)pNode->pItemData,
4247 sizeof(szClass),
4248 szClass))
4249 {
4250 if ( (strcmp(szClass, "#5")) // not static
4251 )
4252 break;
4253 // else: go for next then
4254 }
4255 }
4256 }
4257
4258 if (pNode)
4259 {
4260 WinSetFocus(HWND_DESKTOP,
4261 (HWND)pNode->pItemData);
4262 }
4263}
4264
4265/*
4266 *@@ dlghProcessMnemonic:
4267 * finds the control which matches usch
4268 * and gives it the focus. If this is a
4269 * static control, the next control in the
4270 * list is given focus instead. (Standard
4271 * dialog behavior.)
4272 *
4273 * Pass in usch from WM_CHAR. It is assumed
4274 * that the caller has already tested for
4275 * the "alt" key to be depressed.
4276 *
4277 *@@added V0.9.9 (2001-03-17) [umoeller]
4278 */
4279
4280HWND dlghProcessMnemonic(PVOID pvllWindows,
4281 USHORT usch)
4282{
4283 PLINKLIST pllWindows = (PLINKLIST)pvllWindows;
4284
4285 HWND hwndFound = NULLHANDLE;
4286 PLISTNODE pNode = lstQueryFirstNode(pllWindows);
4287 CHAR szClass[100];
4288
4289 while (pNode)
4290 {
4291 HWND hwnd = (HWND)pNode->pItemData;
4292
4293 if (WinSendMsg(hwnd,
4294 WM_MATCHMNEMONIC,
4295 (MPARAM)usch,
4296 0))
4297 {
4298 // according to the docs, only buttons and static
4299 // return TRUE to that msg;
4300 // if this is a static, give focus to the next
4301 // control
4302
4303 // _Pmpf((__FUNCTION__ ": hwnd 0x%lX", hwnd));
4304
4305 // check if this is a focusable control
4306 if (WinQueryClassName(hwnd,
4307 sizeof(szClass),
4308 szClass))
4309 {
4310 if (!strcmp(szClass, "#3"))
4311 // it's a button: click it
4312 WinSendMsg(hwnd, BM_CLICK, (MPARAM)TRUE, 0);
4313 else if (!strcmp(szClass, "#5"))
4314 {
4315 // it's a static: give focus to following control
4316 pNode = pNode->pNext;
4317 if (pNode)
4318 WinSetFocus(HWND_DESKTOP, (HWND)pNode->pItemData);
4319 }
4320 }
4321 else
4322 // any other control (are there any?): give them focus
4323 WinSetFocus(HWND_DESKTOP, hwnd);
4324
4325 // in any case, stop
4326 hwndFound = hwnd;
4327 break;
4328 }
4329
4330 pNode = pNode->pNext;
4331 }
4332
4333 return hwndFound;
4334}
4335
4336/*
4337 *@@ dlghEnter:
4338 * presses the first button with BS_DEFAULT.
4339 */
4340
4341BOOL dlghEnter(PVOID pvllWindows)
4342{
4343 PLINKLIST pllWindows = (PLINKLIST)pvllWindows;
4344
4345 PLISTNODE pNode = lstQueryFirstNode(pllWindows);
4346 CHAR szClass[100];
4347 while (pNode)
4348 {
4349 HWND hwnd = (HWND)pNode->pItemData;
4350 if (WinQueryClassName(hwnd,
4351 sizeof(szClass),
4352 szClass))
4353 {
4354 if (!strcmp(szClass, "#3")) // button
4355 {
4356 // _Pmpf((__FUNCTION__ ": found button"));
4357 if ( (WinQueryWindowULong(hwnd, QWL_STYLE) & (BS_PUSHBUTTON | BS_DEFAULT))
4358 == (BS_PUSHBUTTON | BS_DEFAULT)
4359 )
4360 {
4361 // _Pmpf((" is default!"));
4362 WinPostMsg(hwnd,
4363 BM_CLICK,
4364 (MPARAM)TRUE, // upclick
4365 0);
4366 return TRUE;
4367 }
4368 }
4369 }
4370
4371 pNode = pNode->pNext;
4372 }
4373
4374 return FALSE;
4375}
4376
4377
Note: See TracBrowser for help on using the repository browser.