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

Last change on this file since 54 was 54, checked in by umoeller, 24 years ago

Misc changes.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 45.7 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 * See dlghCreateDlg for details.
8 *
9 * Usage: All PM programs.
10 *
11 * Function prefixes (new with V0.81):
12 * -- dlg* Dialog functions
13 *
14 * Note: Version numbering in this file relates to XWorkplace version
15 * numbering.
16 *
17 *@@added V0.9.9 (2001-04-01) [umoeller]
18 *@@header "helpers\dialog.h"
19 */
20
21/*
22 * Copyright (C) 2001 Ulrich M”ller.
23 * This file is part of the "XWorkplace helpers" source package.
24 * This is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published
26 * by the Free Software Foundation, in version 2 as it comes in the
27 * "COPYING" file of the XWorkplace main distribution.
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
32 */
33
34#define OS2EMX_PLAIN_CHAR
35 // this is needed for "os2emx.h"; if this is defined,
36 // emx will define PSZ as _signed_ char, otherwise
37 // as unsigned char
38
39#define INCL_DOSERRORS
40
41#define INCL_WINWINDOWMGR
42#define INCL_WINFRAMEMGR
43#define INCL_WINDIALOGS
44#define INCL_WINBUTTONS
45#define INCL_WINSTATICS
46#define INCL_WINSYS
47
48#define INCL_GPIPRIMITIVES
49#define INCL_GPIBITMAPS
50#define INCL_GPILCIDS
51#include <os2.h>
52
53#include <stdlib.h>
54#include <string.h>
55#include <stdio.h>
56
57#include "setup.h" // code generation and debugging options
58
59#include "helpers\comctl.h"
60#include "helpers\dialog.h"
61#include "helpers\gpih.h"
62#include "helpers\linklist.h"
63#include "helpers\standards.h"
64#include "helpers\stringh.h"
65#include "helpers\winh.h"
66
67/*
68 *@@category: Helpers\PM helpers\Dialog templates
69 */
70
71/* ******************************************************************
72 *
73 * Private declarations
74 *
75 ********************************************************************/
76
77/*
78 *@@ DLGPRIVATE:
79 * private data to the dlg manager, allocated
80 * by dlghCreateDlg. This is what is really
81 * used, even though the prototype only
82 * declares DIALOGDATA.
83 */
84
85typedef struct _DLGPRIVATE
86{
87 // public data
88 HWND hwndDlg;
89
90 // definition data (private)
91 LINKLIST llTables;
92
93 HWND hwndFirstFocus;
94
95 POINTL ptlTotalOfs;
96
97 LINKLIST llControls; // linked list of all PCOLUMNDEF structs,
98 // in the order in which windows were
99 // created
100
101 const char *pcszControlsFont; // from dlghCreateDlg
102
103 HPS hps;
104 const char *pcszFontLast;
105 LONG lcidLast;
106} DLGPRIVATE, *PDLGPRIVATE;
107
108typedef struct _COLUMNDEF *PCOLUMNDEF;
109typedef struct _ROWDEF *PROWDEF;
110typedef struct _TABLEDEF *PTABLEDEF;
111
112/*
113 *@@ CONTROLPOS:
114 * control position. We don't want to use SWP.
115 */
116
117typedef struct _CONTROLPOS
118{
119 LONG x,
120 y,
121 cx,
122 cy;
123} CONTROLPOS, *PCONTROLPOS;
124
125/*
126 *@@ COLUMNDEF:
127 * representation of a table column.
128 * This is stored in a linked list in ROWDEF.
129 *
130 * A table column represents either a PM control
131 * window or another table, which may therefore
132 * be nested.
133 */
134
135typedef struct _COLUMNDEF
136{
137 PROWDEF pOwningRow; // row whose linked list this column belongs to
138
139 BOOL fIsNestedTable; // if TRUE, pvDefinition points to a nested TABLEDEF;
140 // if FALSE, pvDefinition points to a CONTROLDEF as
141 // specified by the user
142
143 PVOID pvDefinition; // either a PTABLEDEF or a PCONTROLDEF
144
145 CONTROLPOS cpControl, // real pos and size of control
146 cpColumn; // pos and size of column; can be wider, spacings applied
147
148 HWND hwndControl; // created control; NULLHANDLE for tables always
149
150} COLUMNDEF;
151
152/*
153 *@@ ROWDEF:
154 *
155 */
156
157typedef struct _ROWDEF
158{
159 PTABLEDEF pOwningTable; // table whose linked list this row belongs to
160
161 LINKLIST llColumns; // contains COLUMNDEF structs, no auto-free
162
163 CONTROLPOS cpRow;
164
165} ROWDEF;
166
167/*
168 *@@ TABLEDEF:
169 *
170 */
171
172typedef struct _TABLEDEF
173{
174 LINKLIST llRows; // contains ROWDEF structs, no auto-free
175
176 PCONTROLDEF pCtlDef; // if != NULL, we create a PM control around the table
177
178 CONTROLPOS cpTable;
179
180} TABLEDEF;
181
182/*
183 *@@ PROCESSMODE:
184 *
185 */
186
187typedef enum _PROCESSMODE
188{
189 PROCESS_CALC_SIZES, // step 1
190 PROCESS_CALC_POSITIONS, // step 2
191 PROCESS_CREATE_CONTROLS // step 3
192} PROCESSMODE;
193
194/* ******************************************************************
195 *
196 * Worker routines
197 *
198 ********************************************************************/
199
200#define PM_GROUP_SPACING_X 20
201#define PM_GROUP_SPACING_TOP 30
202
203VOID ProcessTable(PTABLEDEF pTableDef,
204 const CONTROLPOS *pcpTable,
205 PROCESSMODE ProcessMode,
206 PDLGPRIVATE pDlgData);
207
208/*
209 *@@ CalcAutoSizeText:
210 *
211 */
212
213VOID CalcAutoSizeText(PCONTROLDEF pControlDef,
214 PSIZEL pszlAuto, // out: computed size
215 PDLGPRIVATE pDlgData)
216{
217 BOOL fFind = TRUE;
218 RECTL rclText;
219
220 const char *pcszFontThis = pControlDef->pcszFont;
221 // can be NULL,
222 // or CTL_COMMON_FONT
223
224 if (pcszFontThis == CTL_COMMON_FONT)
225 pcszFontThis = pDlgData->pcszControlsFont;
226
227 if (!pDlgData->hps)
228 {
229 // first call: get a PS
230 pDlgData->hps = WinGetPS(pDlgData->hwndDlg);
231 fFind = TRUE;
232 }
233 else
234 if (strhcmp(pcszFontThis, pDlgData->pcszFontLast))
235 fFind = TRUE;
236
237 if (fFind)
238 {
239 FONTMETRICS fm;
240 LONG lcid;
241 LONG lPointSize = 0;
242 if (pDlgData->lcidLast)
243 {
244 // delete old font
245 GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
246 GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
247 }
248
249 // create new font
250 pDlgData->lcidLast = gpihFindPresFont(NULLHANDLE, // no window yet
251 FALSE,
252 pDlgData->hps,
253 pcszFontThis,
254 &fm,
255 &lPointSize);
256 GpiSetCharSet(pDlgData->hps, pDlgData->lcidLast);
257 if (fm.fsDefn & FM_DEFN_OUTLINE)
258 gpihSetPointSize(pDlgData->hps, lPointSize);
259
260 pszlAuto->cy = fm.lMaxBaselineExt + fm.lExternalLeading;
261 }
262
263 // ok, we FINALLY have a font now...
264 // get the control string and see how much space it needs
265 if (pControlDef->pcszText)
266 {
267 POINTL aptl[TXTBOX_COUNT];
268 GpiQueryTextBox(pDlgData->hps,
269 strlen(pControlDef->pcszText),
270 (PCH)pControlDef->pcszText,
271 TXTBOX_COUNT,
272 aptl);
273 pszlAuto->cx = aptl[TXTBOX_TOPRIGHT].x - aptl[TXTBOX_BOTTOMLEFT].x;
274 }
275}
276
277/*
278 *@@ CalcAutoSize:
279 *
280 */
281
282VOID CalcAutoSize(PCONTROLDEF pControlDef,
283 PSIZEL pszlAuto, // out: computed size
284 PDLGPRIVATE pDlgData)
285{
286 // dumb defaults
287 pszlAuto->cx = 100;
288 pszlAuto->cy = 30;
289
290 switch ((ULONG)pControlDef->pcszClass)
291 {
292 case 0xffff0003L: // WC_BUTTON:
293 CalcAutoSizeText(pControlDef,
294 pszlAuto,
295 pDlgData);
296 if (pControlDef->flStyle & ( BS_AUTOCHECKBOX
297 | BS_AUTORADIOBUTTON
298 | BS_AUTO3STATE
299 | BS_3STATE
300 | BS_CHECKBOX
301 | BS_RADIOBUTTON))
302 pszlAuto->cx += 20; // @@@
303 else if (pControlDef->flStyle & BS_BITMAP)
304 ;
305 else if (pControlDef->flStyle & (BS_ICON | BS_MINIICON))
306 ;
307 // we can't test for BS_PUSHBUTTON because that's 0x0000
308 else if (!(pControlDef->flStyle & BS_USERBUTTON))
309 {
310 pszlAuto->cx += (2 * WinQuerySysValue(HWND_DESKTOP, SV_CXBORDER) + 15);
311 pszlAuto->cy += (2 * WinQuerySysValue(HWND_DESKTOP, SV_CYBORDER) + 15);
312 }
313 break;
314
315 case 0xffff0005L: // WC_STATIC:
316 if (pControlDef->flStyle & SS_TEXT)
317 CalcAutoSizeText(pControlDef,
318 pszlAuto,
319 pDlgData);
320 else if (pControlDef->flStyle & SS_BITMAP)
321 {
322 HBITMAP hbm = (HBITMAP)pControlDef->pcszText;
323 if (hbm)
324 {
325 BITMAPINFOHEADER2 bmih2;
326 ZERO(&bmih2);
327 bmih2.cbFix = sizeof(bmih2);
328 if (GpiQueryBitmapInfoHeader(hbm,
329 &bmih2))
330 {
331 pszlAuto->cx = bmih2.cx;
332 pszlAuto->cy = bmih2.cy;
333 }
334 }
335 }
336 break;
337 }
338}
339
340/*
341 *@@ ProcessColumn:
342 * processes a column, which per definition is either
343 * a control or a nested subtable.
344 *
345 * A column is part of a row, which in turn is part
346 * of a table. There can be several columns in a row,
347 * and several rows in a table.
348 *
349 * Since tables may be specified as columns, it is
350 * possible to produce complex dialog layouts by
351 * nesting tables.
352 *
353 * This does the following:
354 *
355 * -- PROCESS_CALC_SIZES: size is taken from control def,
356 * or for tables, this produces a recursive ProcessTable
357 * call.
358 * Preconditions: none.
359 *
360 * -- PROCESS_CALC_POSITIONS: position of each column
361 * is taken from *plX, which is increased by the
362 * column width by this call.
363 *
364 * Preconditions: Owning row must already have its
365 * y position properly set, or we can't compute
366 * ours. Besides, plX must point to the current X
367 * in the row and will be incremented by the columns
368 * size here.
369 *
370 * -- PROCESS_CREATE_CONTROLS: well, creates the controls.
371 *
372 * For tables, this recurses again. If the table has
373 * a string assigned, this also produces a group box
374 * after the recursion.
375 *
376 */
377
378VOID ProcessColumn(PCOLUMNDEF pColumnDef,
379 PROWDEF pOwningRow,
380 PROCESSMODE ProcessMode,
381 PLONG plX, // in/out: PROCESS_CALC_POSITIONS only
382 PDLGPRIVATE pDlgData)
383{
384 pColumnDef->pOwningRow = pOwningRow;
385
386 // for PROCESS_CALC_SIZES: have control return its size
387 // plus spacings into szlControl
388 // for PROCESS_CALC_POSITIONS: have control compute its
389 // position from the column position (there may be
390 // spacings)
391 // for PROCESS_CREATE_CONTROLS: well, create the control
392 switch (ProcessMode)
393 {
394 case PROCESS_CALC_SIZES:
395 {
396 ULONG ulXSpacing = 0,
397 ulYSpacing = 0;
398 if (pColumnDef->fIsNestedTable)
399 {
400 // nested table: recurse!!
401 PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
402 ProcessTable(pTableDef,
403 NULL,
404 ProcessMode,
405 pDlgData);
406
407 // store the size of the sub-table
408 pColumnDef->cpControl.cx = pTableDef->cpTable.cx;
409 pColumnDef->cpControl.cy = pTableDef->cpTable.cy;
410
411 // should we create a PM control around the table?
412 if (pTableDef->pCtlDef)
413 {
414 // yes: make this wider
415 ulXSpacing = (2 * PM_GROUP_SPACING_X);
416 ulYSpacing = (PM_GROUP_SPACING_X + PM_GROUP_SPACING_TOP);
417 }
418 }
419 else
420 {
421 // no nested table, but control:
422 PCONTROLDEF pControlDef = (PCONTROLDEF)pColumnDef->pvDefinition;
423 PSIZEL pszl = &pControlDef->szlControlProposed;
424 SIZEL szlAuto;
425
426 if ( (pszl->cx == -1)
427 || (pszl->cy == -1)
428 )
429 {
430 CalcAutoSize(pControlDef,
431 &szlAuto,
432 pDlgData);
433 }
434
435 if (pszl->cx == -1)
436 pColumnDef->cpControl.cx = szlAuto.cx;
437 else
438 pColumnDef->cpControl.cx = pszl->cx;
439
440 if (pszl->cy == -1)
441 pColumnDef->cpControl.cy = szlAuto.cy;
442 else
443 pColumnDef->cpControl.cy = pszl->cy;
444
445 // @@@hack sizes
446
447 ulXSpacing = ulYSpacing = (2 * pControlDef->ulSpacing);
448 }
449
450 pColumnDef->cpColumn.cx = pColumnDef->cpControl.cx
451 + ulXSpacing;
452 pColumnDef->cpColumn.cy = pColumnDef->cpControl.cy
453 + ulYSpacing;
454 break; }
455
456 case PROCESS_CALC_POSITIONS:
457 {
458 // calculate column position: this includes spacing
459 ULONG ulSpacing = 0;
460
461 // column position = *plX
462 pColumnDef->cpColumn.x = *plX;
463 pColumnDef->cpColumn.y = pOwningRow->cpRow.y;
464
465 if (pColumnDef->fIsNestedTable)
466 {
467 PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
468 // should we create a PM control around the table?
469 if (pTableDef->pCtlDef)
470 // yes:
471 ulSpacing = PM_GROUP_SPACING_X;
472 }
473 else
474 {
475 // no nested table, but control:
476 PCONTROLDEF pControlDef = (PCONTROLDEF)pColumnDef->pvDefinition;
477 ulSpacing = pControlDef->ulSpacing;
478 }
479
480 // increase plX by column width
481 *plX += pColumnDef->cpColumn.cx;
482
483 // calculate control pos by applying spacing
484 pColumnDef->cpControl.x = pColumnDef->cpColumn.x
485 + ulSpacing;
486 pColumnDef->cpControl.y = pColumnDef->cpColumn.y
487 + ulSpacing;
488
489 if (pColumnDef->fIsNestedTable)
490 {
491 // nested table:
492 PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
493
494 // recurse!! to create windows for the sub-table
495 ProcessTable(pTableDef,
496 &pColumnDef->cpControl, // start pos for new table
497 ProcessMode,
498 pDlgData);
499 }
500 break; }
501
502 case PROCESS_CREATE_CONTROLS:
503 {
504 PCONTROLPOS pcp = NULL;
505 PCONTROLDEF pControlDef = NULL;
506 const char *pcszTitle = NULL;
507 ULONG flStyle = 0;
508 LHANDLE lHandleSet = NULLHANDLE;
509
510 if (pColumnDef->fIsNestedTable)
511 {
512 // nested table:
513 PTABLEDEF pTableDef = (PTABLEDEF)pColumnDef->pvDefinition;
514
515 // recurse!!
516 ProcessTable(pTableDef,
517 NULL,
518 ProcessMode,
519 pDlgData);
520
521 // should we create a PM control around the table?
522 // (do this AFTER the other controls from recursing,
523 // otherwise the stupid container doesn't show up)
524 if (pTableDef->pCtlDef)
525 {
526 // yes:
527 pcp = &pColumnDef->cpColumn; // !! not control
528 pControlDef = pTableDef->pCtlDef;
529 pcszTitle = pControlDef->pcszText;
530 flStyle = pControlDef->flStyle;
531 }
532 }
533 else
534 {
535 // no nested table, but control:
536 pControlDef = (PCONTROLDEF)pColumnDef->pvDefinition;
537 pcp = &pColumnDef->cpControl;
538 pcszTitle = pControlDef->pcszText;
539 flStyle = pControlDef->flStyle;
540
541 // change the title if this is a static with SS_BITMAP;
542 // we have used a HBITMAP in there!
543 if ( ((ULONG)pControlDef->pcszClass == 0xffff0005L) // WC_STATIC:
544 && (flStyle & SS_BITMAP)
545 )
546 {
547 // change style flag to not use SS_BITMAP;
548 // control creation fails otherwise (stupid, stupid PM)
549 flStyle = ((flStyle & ~SS_BITMAP) | SS_FGNDFRAME);
550 pcszTitle = "";
551 lHandleSet = (LHANDLE)pControlDef->pcszText;
552 }
553 }
554
555 if (pcp && pControlDef)
556 {
557 // create something:
558 PPRESPARAMS ppp = NULL;
559
560 const char *pcszFont = pControlDef->pcszFont;
561 // can be NULL, or CTL_COMMON_FONT
562 if (pcszFont == CTL_COMMON_FONT)
563 pcszFont = pDlgData->pcszControlsFont;
564
565 if (pcszFont)
566 winhStorePresParam(&ppp,
567 PP_FONTNAMESIZE,
568 strlen(pcszFont),
569 (PVOID)pcszFont);
570
571 pColumnDef->hwndControl
572 = WinCreateWindow(pDlgData->hwndDlg, // parent
573 (PSZ)pControlDef->pcszClass,
574 (pcszTitle) // hacked
575 ? (PSZ)pcszTitle
576 : "",
577 flStyle, // hacked
578 pcp->x + pDlgData->ptlTotalOfs.x,
579 pcp->y + pDlgData->ptlTotalOfs.y,
580 pcp->cx,
581 pcp->cy,
582 pDlgData->hwndDlg, // owner
583 HWND_BOTTOM,
584 pControlDef->usID,
585 NULL, // control data @@@
586 ppp);
587
588 if (pColumnDef->hwndControl && lHandleSet)
589 {
590 // subclass the damn static
591 ctlPrepareStretchedBitmap(pColumnDef->hwndControl,
592 TRUE);
593
594 WinSendMsg(pColumnDef->hwndControl,
595 SM_SETHANDLE,
596 (MPARAM)lHandleSet,
597 0);
598 }
599
600 if (ppp)
601 free(ppp);
602
603 if (pColumnDef->hwndControl)
604 {
605 lstAppendItem(&pDlgData->llControls,
606 pColumnDef);
607
608 // if this is the first control with WS_TABSTOP,
609 // we give it the focus later
610 if ( (flStyle & WS_TABSTOP)
611 && (!pDlgData->hwndFirstFocus)
612 )
613 pDlgData->hwndFirstFocus = pColumnDef->hwndControl;
614 }
615 }
616 break; }
617 }
618
619}
620
621/*
622 *@@ ProcessRow:
623 *
624 */
625
626VOID ProcessRow(PROWDEF pRowDef,
627 PTABLEDEF pOwningTable,
628 PROCESSMODE ProcessMode,
629 PLONG plY,
630 PDLGPRIVATE pDlgData)
631{
632 ULONG ul;
633 LONG lX;
634 PLISTNODE pNode;
635
636 pRowDef->pOwningTable = pOwningTable;
637
638 if (ProcessMode == PROCESS_CALC_SIZES)
639 {
640 pRowDef->cpRow.cx = 0;
641 pRowDef->cpRow.cy = 0;
642 }
643 else if (ProcessMode == PROCESS_CALC_POSITIONS)
644 {
645 // set up x and y so that the columns can
646 // base on that
647 pRowDef->cpRow.x = pOwningTable->cpTable.x;
648 // decrease y by row height
649 *plY -= pRowDef->cpRow.cy;
650 // and use that for our bottom position
651 pRowDef->cpRow.y = *plY;
652
653 // set lX to left of row; used by column calls below
654 lX = pRowDef->cpRow.x;
655 }
656
657 FOR_ALL_NODES(&pRowDef->llColumns, pNode)
658 {
659 PCOLUMNDEF pColumnDefThis = (PCOLUMNDEF)pNode->pItemData;
660
661 ProcessColumn(pColumnDefThis, pRowDef, ProcessMode, &lX, pDlgData);
662
663 if (ProcessMode == PROCESS_CALC_SIZES)
664 {
665 // row width = sum of all columns
666 pRowDef->cpRow.cx += pColumnDefThis->cpColumn.cx;
667
668 // row height = maximum height of a column
669 if (pRowDef->cpRow.cy < pColumnDefThis->cpColumn.cy)
670 pRowDef->cpRow.cy = pColumnDefThis->cpColumn.cy;
671 }
672 }
673}
674
675/*
676 *@@ ProcessTable:
677 *
678 * This routine is a bit sick because it can be
679 * called recursively if a nested table is found
680 * in a COLUMNDEF.
681 *
682 * With PROCESS_CALC_POSITIONS, pptl must specify
683 * the lower left corner of the table. For the
684 * root call, this will be {0, 0}; for nested calls,
685 * this must be the lower left corner of the column
686 * to which the nested table belongs.
687 *
688 */
689
690VOID ProcessTable(PTABLEDEF pTableDef,
691 const CONTROLPOS *pcpTable, // in: table position with PROCESS_CALC_POSITIONS
692 PROCESSMODE ProcessMode,
693 PDLGPRIVATE pDlgData)
694{
695 ULONG ul;
696 LONG lY;
697 PLISTNODE pNode;
698
699 if (ProcessMode == PROCESS_CALC_SIZES)
700 {
701 pTableDef->cpTable.cx = 0;
702 pTableDef->cpTable.cy = 0;
703 }
704 else if (ProcessMode == PROCESS_CALC_POSITIONS)
705 {
706 pTableDef->cpTable.x = pcpTable->x;
707 pTableDef->cpTable.y = pcpTable->y;
708
709 // start the rows on top
710 lY = pcpTable->y + pTableDef->cpTable.cy;
711 }
712
713 FOR_ALL_NODES(&pTableDef->llRows, pNode)
714 {
715 PROWDEF pRowDefThis = (PROWDEF)pNode->pItemData;
716
717 ProcessRow(pRowDefThis, pTableDef, ProcessMode, &lY, pDlgData);
718
719 if (ProcessMode == PROCESS_CALC_SIZES)
720 {
721 // table width = maximum width of a row
722 if (pTableDef->cpTable.cx < pRowDefThis->cpRow.cx)
723 pTableDef->cpTable.cx = pRowDefThis->cpRow.cx;
724
725 // table height = sum of all rows
726 pTableDef->cpTable.cy += pRowDefThis->cpRow.cy;
727 }
728 }
729}
730
731/*
732 *@@ ProcessAll:
733 *
734 * -- PROCESS_CALC_SIZES: calculates the sizes
735 * of all tables, rows, columns, and controls.
736 *
737 * -- PROCESS_CALC_POSITIONS: calculates the positions
738 * based on the sizes calculated before.
739 *
740 * -- PROCESS_CREATE_CONTROLS: creates the controls with the
741 * positions and sizes calculated before.
742 *
743 */
744
745VOID ProcessAll(PDLGPRIVATE pDlgData,
746 PSIZEL pszlClient,
747 PROCESSMODE ProcessMode)
748{
749 ULONG ul;
750 PLISTNODE pNode;
751 CONTROLPOS cpTable;
752 ZERO(&cpTable);
753
754 switch (ProcessMode)
755 {
756 case PROCESS_CALC_SIZES:
757 pszlClient->cx = 0;
758 pszlClient->cy = 0;
759 break;
760
761 case PROCESS_CALC_POSITIONS:
762 // start with the table on top
763 cpTable.y = pszlClient->cy;
764 break;
765 }
766
767 FOR_ALL_NODES(&pDlgData->llTables, pNode)
768 {
769 PTABLEDEF pTableDefThis = (PTABLEDEF)pNode->pItemData;
770
771 if (ProcessMode == PROCESS_CALC_POSITIONS)
772 {
773 cpTable.x = 0;
774 cpTable.y -= pTableDefThis->cpTable.cy;
775 }
776
777 ProcessTable(pTableDefThis,
778 &cpTable, // start pos
779 ProcessMode,
780 pDlgData);
781
782 if (ProcessMode == PROCESS_CALC_SIZES)
783 {
784 pszlClient->cx += pTableDefThis->cpTable.cx;
785 pszlClient->cy += pTableDefThis->cpTable.cy;
786 }
787 }
788}
789
790/*
791 *@@ CreateColumn:
792 *
793 */
794
795APIRET CreateColumn(PROWDEF pCurrentRow,
796 BOOL fIsNestedTable,
797 PVOID pvDefinition, // in: either PTABLEDEF or PCONTROLDEF
798 PCOLUMNDEF *ppColumnDef) // out: new COLUMNDEF
799{
800 APIRET arc = NO_ERROR;
801
802 if (!pCurrentRow)
803 arc = DLGERR_CONTROL_BEFORE_ROW;
804 else
805 {
806 // append the control def
807 if (!pvDefinition)
808 arc = DLGERR_NULL_CTL_DEF;
809 else
810 {
811 // create column and store ctl def
812 PCOLUMNDEF pColumnDef = NEW(COLUMNDEF);
813 if (!pColumnDef)
814 arc = ERROR_NOT_ENOUGH_MEMORY;
815 else
816 {
817 memset(pColumnDef, 0, sizeof(COLUMNDEF));
818 pColumnDef->pOwningRow = pCurrentRow;
819 pColumnDef->fIsNestedTable = fIsNestedTable;
820 pColumnDef->pvDefinition = pvDefinition;
821
822 *ppColumnDef = pColumnDef;
823 }
824 }
825 }
826
827 return (arc);
828}
829
830/* ******************************************************************
831 *
832 * Public APIs
833 *
834 ********************************************************************/
835
836/*
837 *@@ STACKITEM:
838 *
839 */
840
841typedef struct _STACKITEM
842{
843 PTABLEDEF pLastTable;
844 PROWDEF pLastRow;
845
846} STACKITEM, *PSTACKITEM;
847
848/*
849 *@@ dlghCreateDlg:
850 * replacement for WinCreateDlg for creating a dialog
851 * from a settings array in memory, which is formatted
852 * automatically.
853 *
854 * This does NOT use regular dialog templates from
855 * module resources. Instead, you pass in an array
856 * of _DLGHITEM structures, which define the controls
857 * and how they are to be formatted.
858 *
859 * A regular standard dialog would use something like
860 *
861 + FCF_TITLEBAR | FCF_SYSMENU | FCF_DLGBORDER | FCF_NOBYTEALIGN
862 *
863 * for flCreateFlags. To make the dlg sizeable, specify
864 * FCF_SIZEBORDER instead of FCF_DLGBORDER.
865 *
866 * <B>Usage:</B>
867 *
868 * Like WinLoadDlg, this creates a standard WC_FRAME and
869 * subclasses it with fnwpMyDlgProc. It then sends WM_INITDLG
870 * to the dialog with pCreateParams in mp2. You can use
871 * WinProcessDlg as usual. In your dlg proc, use WinDefDlgProc
872 * as usual.
873 *
874 * There is NO run-time overhead after creation; after this
875 * function returns, the dialog is a standard dialog as if
876 * loaded from WinLoadDlg.
877 *
878 * The array of _DLGHITEM structures defines how the
879 * dialog is set up. All this is ONLY used by this function
880 * and NOT needed after the dialog has been created.
881 *
882 * In _DLGHITEM, the "Type" field determines what this
883 * structure defines. A number of handy macros have been
884 * defined to make this easier and to provide type-checking
885 * at compile time.
886 *
887 * The macros are:
888 *
889 * -- START_TABLE starts a new table. The tables may nest,
890 * but must each be properly terminated with END_TABLE.
891 *
892 * -- START_ROW(fl) starts a new row in a table.
893 *
894 * fl specifies formatting flags for the row. This
895 * can be one of ROW_VALIGN_BOTTOM, ROW_VALIGN_CENTER,
896 * ROW_VALIGN_TOP and affects all items in the control.
897 *
898 * -- START_GROUP_TABLE(pDef) starts a group. This
899 * behaves exacly like START_TABLE, but in addition,
900 * it produces a control around the table. Useful
901 * for group boxes. pDef must point to a _CONTROLDEF,
902 * whose size parameter is ignored.
903 *
904 * This must also be terminated with END_TABLE.
905 *
906 * -- CONTROL_DEF(pDef) defines a control in a table row.
907 * pDef must point to a _CONTROLDEF structure.
908 *
909 * The main difference (and advantage) of this
910 * function is that there is NO information in
911 * _CONTROLDEF about a control's _position_.
912 * Instead, the structure only contains the _size_
913 * of the control. All positions are computed by
914 * this function, depending on the position of the
915 * control and its nesting within the various tables.
916 *
917 * If you specify SZL_AUTOSIZE, the size of the
918 * control is even computed automatically. Presently,
919 * this only works for statics with SS_TEXT and
920 * SS_BITMAP.
921 *
922 * Unless separated with TYPE_START_NEW_ROW items,
923 * subsequent control items will be considered
924 * to be in the same row (== positioned next to
925 * each other).
926 *
927 * There are a few rules, whose violation will produce
928 * an error:
929 *
930 * -- The entire array must be enclosed in a table
931 * (the "root" table).
932 *
933 * -- After START_TABLE or START_GROUP_TABLE, there must
934 * always be a START_ROW first.
935 *
936 * To create a dialog, set up arrays like the following:
937 *
938 + // control definitions referenced by DlgTemplate:
939 + CONTROLDEF
940 + (1) GroupDef = {
941 + WC_STATIC, "", ....,
942 + { 0, 0 }, // size, ignored for groups
943 + 5 // spacing
944 + },
945 + (2) CnrDef = {
946 + WC_CONTAINER, "", ....,
947 + { 50, 50 }, // size
948 + 5 // spacing
949 + },
950 + (3) Static = {
951 + WC_STATIC, "Static below cnr", ...,
952 + { SZL_AUTOSIZE, SZL_AUTOSIZE }, // size
953 + 5 // spacing
954 + },
955 + (4) OKButton = {
956 + WC_STATIC, "~OK", ...,
957 + { 100, 30 }, // size
958 + 5 // spacing
959 + },
960 + (5) CancelButton = {
961 + WC_STATIC, "~Cancel", ...,
962 + { 100, 30 }, // size
963 + 5 // spacing
964 + };
965 +
966 + DLGHITEM DlgTemplate[] =
967 + {
968 + START_TABLE, // root table, must exist
969 + START_ROW(0), // row 1 in the root table
970 + // create group on top
971 + (1) START_GROUP_TABLE(&Group),
972 + START_ROW(0),
973 + (2) CONTROL_DEF(&CnrDef),
974 + START_ROW(0),
975 + (3) CONTROL_DEF(&Static),
976 + END_TABLE, // end of group
977 + START_ROW(0), // row 2 in the root table
978 + // two buttons next to each other
979 + (4) CONTROL_DEF(&OKButton),
980 + (5) CONTROL_DEF(&CancelButton),
981 + END_TABLE
982 + }
983 *
984 * This will produce a dlg like this:
985 *
986 + ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
987 + º º
988 + º ÚÄ Group (1) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º
989 + º ³ ³ º
990 + º ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ º
991 + º ³ ³ ³ ³ º
992 + º ³ ³ Cnr inside group (2) ³ ³ º
993 + º ³ ³ ³ ³ º
994 + º ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ º
995 + º ³ Static below cnr (3) ³ º
996 + º ³ ³ º
997 + º ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º
998 + º ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ º
999 + º ³ OK (4) ³ ³ Cancel (5) ³ º
1000 + º ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ º
1001 + º º
1002 + ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍŒ
1003 *
1004 * <B>Errors:</B>
1005 *
1006 * This does not return a HWND, but an APIRET. This will be
1007 * one of the following:
1008 *
1009 * -- NO_ERROR: only in that case, the phwndDlg ptr
1010 * receives the HWND of the new dialog, which can
1011 * then be given to WinProcessDlg. Don't forget
1012 * WinDestroyWindow.
1013 *
1014 * -- ERROR_NOT_ENOUGH_MEMORY
1015 *
1016 * -- DLGERR_ROW_BEFORE_TABLE: a row definition appeared
1017 * outside a table definition.
1018 *
1019 * -- DLGERR_CONTROL_BEFORE_ROW: a control definition
1020 * appeared right after a table definition. You must
1021 * specify a row first.
1022 *
1023 * -- DLGERR_NULL_CTL_DEF: TYPE_END_TABLE was specified,
1024 * but the CONTROLDEF ptr was NULL.
1025 *
1026 * -- DLGERR_CANNOT_CREATE_FRAME: unable to create the
1027 * WC_FRAME window. Maybe an invalid owner was specified.
1028 *
1029 * -- DLGERR_INVALID_CODE: invalid "Type" field in
1030 * DLGHITEM.
1031 *
1032 * -- DLGERR_TABLE_NOT_CLOSED, DLGERR_TOO_MANY_TABLES_CLOSED:
1033 * improper nesting of TYPE_START_NEW_TABLE and
1034 * TYPE_END_TABLE fields.
1035 *
1036 * <B>Example:</B>
1037 *
1038 * The typical calling sequence would be:
1039 *
1040 + HWND hwndDlg = NULLHANDLE;
1041 + if (NO_ERROR == dlghCreateDlg(&hwndDlg,
1042 + hwndOwner,
1043 + FCF_DLGBORDER | FCF_NOBYTEALIGN,
1044 + fnwpMyDlgProc,
1045 + "My Dlg Title",
1046 + G_aMyDialogTemplate,
1047 + ARRAYITEMSIZE(G_aMyDialogTemplate),
1048 + NULL))
1049 + {
1050 + ULONG idReturn = WinProcessDlg(pDlgData->hwndDlg);
1051 + WinDestroyWindow(hwndDlg);
1052 + }
1053 *
1054 *
1055 */
1056
1057APIRET dlghCreateDlg(HWND *phwndDlg, // out: new dialog
1058 HWND hwndOwner,
1059 ULONG flCreateFlags, // in: standard FCF_* frame flags
1060 PFNWP pfnwpDialogProc,
1061 const char *pcszDlgTitle,
1062 PDLGHITEM paDlgItems, // in: definition array
1063 ULONG cDlgItems, // in: array item count (NOT array size)
1064 PVOID pCreateParams, // in: for mp2 of WM_INITDLG
1065 const char *pcszControlsFont) // in: font for ctls with CTL_COMMON_FONT
1066{
1067 APIRET arc = NO_ERROR;
1068
1069 #define SPACING 10
1070
1071 PTABLEDEF pCurrentTable = NULL;
1072 PROWDEF pCurrentRow = NULL;
1073 ULONG ul;
1074 LINKLIST llStack;
1075
1076 PDLGPRIVATE pDlgData = NEW(DLGPRIVATE);
1077
1078 if (!pDlgData)
1079 return (ERROR_NOT_ENOUGH_MEMORY);
1080
1081 ZERO(pDlgData);
1082 lstInit(&pDlgData->llTables, FALSE);
1083 lstInit(&pDlgData->llControls, FALSE);
1084
1085 pDlgData->pcszControlsFont = pcszControlsFont;
1086
1087 /*
1088 * 1) parse the table and create structures from it
1089 *
1090 */
1091
1092 lstInit(&llStack, TRUE); // this is our stack for nested table definitions
1093
1094 for (ul = 0;
1095 ul < cDlgItems;
1096 ul++)
1097 {
1098 PDLGHITEM pItemThis = &paDlgItems[ul];
1099
1100 switch (pItemThis->Type)
1101 {
1102 /*
1103 * TYPE_START_NEW_TABLE:
1104 *
1105 */
1106
1107 case TYPE_START_NEW_TABLE:
1108 {
1109 // root table or nested?
1110 BOOL fIsRoot = (pCurrentTable == NULL);
1111
1112 // push the current table on the stack
1113 PSTACKITEM pStackItem = NEW(STACKITEM);
1114 if (pStackItem)
1115 {
1116 pStackItem->pLastTable = pCurrentTable;
1117 pStackItem->pLastRow = pCurrentRow;
1118 lstPush(&llStack, pStackItem);
1119 }
1120
1121 // create new table
1122 pCurrentTable = NEW(TABLEDEF);
1123 if (!pCurrentTable)
1124 arc = ERROR_NOT_ENOUGH_MEMORY;
1125 else
1126 {
1127 ZERO(pCurrentTable);
1128
1129 lstInit(&pCurrentTable->llRows, FALSE);
1130
1131 if (pItemThis->ulData)
1132 // control specified: store it (this will become a PM group)
1133 pCurrentTable->pCtlDef = (PCONTROLDEF)pItemThis->ulData;
1134
1135 if (fIsRoot)
1136 // root table:
1137 // append to dialog data list
1138 lstAppendItem(&pDlgData->llTables, pCurrentTable);
1139 else
1140 {
1141 // nested table:
1142 // create "table" column for this
1143 PCOLUMNDEF pColumnDef;
1144 arc = CreateColumn(pCurrentRow,
1145 TRUE, // nested table
1146 pCurrentTable,
1147 &pColumnDef);
1148 if (!arc)
1149 lstAppendItem(&pCurrentRow->llColumns, pColumnDef);
1150 }
1151 }
1152
1153 pCurrentRow = NULL;
1154 break; }
1155
1156 /*
1157 * TYPE_START_NEW_ROW:
1158 *
1159 */
1160
1161 case TYPE_START_NEW_ROW:
1162 {
1163 if (!pCurrentTable)
1164 arc = DLGERR_ROW_BEFORE_TABLE;
1165 else
1166 {
1167 // create new row
1168 pCurrentRow = NEW(ROWDEF);
1169 if (!pCurrentRow)
1170 arc = ERROR_NOT_ENOUGH_MEMORY;
1171 else
1172 {
1173 ZERO(pCurrentRow);
1174
1175 pCurrentRow->pOwningTable = pCurrentTable;
1176 lstInit(&pCurrentRow->llColumns, FALSE);
1177
1178 lstAppendItem(&pCurrentTable->llRows, pCurrentRow);
1179 }
1180 }
1181 break; }
1182
1183 /*
1184 * TYPE_CONTROL_DEF:
1185 *
1186 */
1187
1188 case TYPE_CONTROL_DEF:
1189 {
1190 PCOLUMNDEF pColumnDef;
1191 arc = CreateColumn(pCurrentRow,
1192 FALSE, // no nested table
1193 (PVOID)pItemThis->ulData,
1194 &pColumnDef);
1195 if (!arc)
1196 lstAppendItem(&pCurrentRow->llColumns, pColumnDef);
1197 break; }
1198
1199 /*
1200 * TYPE_END_TABLE:
1201 *
1202 */
1203
1204 case TYPE_END_TABLE:
1205 {
1206 PLISTNODE pNode = lstPop(&llStack);
1207 if (!pNode)
1208 // nothing on the stack:
1209 arc = DLGERR_TOO_MANY_TABLES_CLOSED;
1210 else
1211 {
1212 PSTACKITEM pStackItem = (PSTACKITEM)pNode->pItemData;
1213 pCurrentTable = pStackItem->pLastTable;
1214 pCurrentRow = pStackItem->pLastRow;
1215
1216 lstRemoveNode(&llStack, pNode);
1217 }
1218 break; }
1219
1220 default:
1221 arc = DLGERR_INVALID_CODE;
1222 }
1223
1224 if (arc)
1225 break;
1226 }
1227
1228 if (arc == NO_ERROR)
1229 if (lstCountItems(&llStack))
1230 arc = DLGERR_TABLE_NOT_CLOSED;
1231
1232 lstClear(&llStack);
1233
1234 if (arc == NO_ERROR)
1235 {
1236 /*
1237 * 2) create empty dialog frame
1238 *
1239 */
1240
1241 FRAMECDATA fcData = {0};
1242
1243 #define FCF_DIALOGBOX 0x40000000L
1244
1245 fcData.cb = sizeof(FRAMECDATA);
1246 fcData.flCreateFlags = flCreateFlags | FCF_DIALOGBOX;
1247
1248 pDlgData->hwndDlg = WinCreateWindow(HWND_DESKTOP,
1249 WC_FRAME,
1250 (PSZ)pcszDlgTitle,
1251 0, // style; invisible for now
1252 0, 0, 0, 0,
1253 hwndOwner,
1254 HWND_TOP,
1255 0, // ID
1256 &fcData,
1257 NULL); // presparams
1258
1259 if (!pDlgData->hwndDlg)
1260 arc = DLGERR_CANNOT_CREATE_FRAME;
1261 else
1262 {
1263 SIZEL szlClient = {0};
1264 RECTL rclClient;
1265 HWND hwndFocusItem = NULLHANDLE;
1266
1267 /*
1268 * 3) compute size of client after computing all control sizes
1269 *
1270 */
1271
1272 ProcessAll(pDlgData,
1273 &szlClient,
1274 PROCESS_CALC_SIZES);
1275
1276 // this might have created a HPS and a font in dlgdata
1277 // for auto-sizing controls
1278 if (pDlgData->hps)
1279 {
1280 if (pDlgData->lcidLast)
1281 {
1282 // delete old font
1283 GpiSetCharSet(pDlgData->hps, LCID_DEFAULT);
1284 GpiDeleteSetId(pDlgData->hps, pDlgData->lcidLast);
1285 pDlgData->lcidLast = 0;
1286 }
1287
1288 WinReleasePS(pDlgData->hps);
1289 pDlgData->hps = NULLHANDLE;
1290 }
1291
1292 WinSubclassWindow(pDlgData->hwndDlg, pfnwpDialogProc);
1293
1294 // calculate the frame size from the client size
1295 rclClient.xLeft = 10;
1296 rclClient.yBottom = 10;
1297 rclClient.xRight = szlClient.cx + 2 * SPACING;
1298 rclClient.yTop = szlClient.cy + 2 * SPACING;
1299 WinCalcFrameRect(pDlgData->hwndDlg,
1300 &rclClient,
1301 FALSE); // frame from client
1302
1303 WinSetWindowPos(pDlgData->hwndDlg,
1304 0,
1305 10,
1306 10,
1307 rclClient.xRight,
1308 rclClient.yTop,
1309 SWP_MOVE | SWP_SIZE | SWP_NOADJUST);
1310
1311 /*
1312 * 4) compute positions of all controls
1313 *
1314 */
1315
1316 ProcessAll(pDlgData,
1317 &szlClient,
1318 PROCESS_CALC_POSITIONS);
1319
1320 /*
1321 * 5) create control windows, finally
1322 *
1323 */
1324
1325 pDlgData->ptlTotalOfs.x = SPACING;
1326 pDlgData->ptlTotalOfs.y = SPACING;
1327
1328 ProcessAll(pDlgData,
1329 &szlClient,
1330 PROCESS_CREATE_CONTROLS);
1331
1332 /*
1333 * 6) WM_INITDLG, set focus
1334 *
1335 */
1336
1337 hwndFocusItem = (pDlgData->hwndFirstFocus)
1338 ? pDlgData->hwndFirstFocus
1339 : pDlgData->hwndDlg;
1340 if (!WinSendMsg(pDlgData->hwndDlg,
1341 WM_INITDLG,
1342 (MPARAM)hwndFocusItem,
1343 (MPARAM)pCreateParams))
1344 {
1345 // if WM_INITDLG returns FALSE, this means
1346 // the dlg proc has not changed the focus;
1347 // we must then set the focus here
1348 WinSetFocus(HWND_DESKTOP, hwndFocusItem);
1349 }
1350 }
1351 }
1352
1353 if (pDlgData)
1354 {
1355 PLISTNODE pTableNode;
1356
1357 if (arc)
1358 {
1359 // error: clean up
1360 if (pDlgData->hwndDlg)
1361 WinDestroyWindow(pDlgData->hwndDlg);
1362 }
1363 else
1364 // no error: output dialog
1365 *phwndDlg = pDlgData->hwndDlg;
1366
1367 // in any case, clean up our mess:
1368
1369 // clean up the tables
1370 FOR_ALL_NODES(&pDlgData->llTables, pTableNode)
1371 {
1372 PTABLEDEF pTable = (PTABLEDEF)pTableNode->pItemData;
1373
1374 // for each table, clean up the rows
1375 PLISTNODE pRowNode;
1376 FOR_ALL_NODES(&pTable->llRows, pRowNode)
1377 {
1378 PROWDEF pRow = (PROWDEF)pRowNode->pItemData;
1379
1380 // for each row, clean up the columns
1381 PLISTNODE pColumnNode;
1382 FOR_ALL_NODES(&pRow->llColumns, pColumnNode)
1383 {
1384 PCOLUMNDEF pColumn = (PCOLUMNDEF)pColumnNode->pItemData;
1385 free(pColumn);
1386 }
1387 lstClear(&pRow->llColumns);
1388
1389 free(pRow);
1390 }
1391 lstClear(&pTable->llRows);
1392
1393 free(pTable);
1394 }
1395
1396 lstClear(&pDlgData->llTables);
1397 lstClear(&pDlgData->llControls);
1398
1399 free(pDlgData);
1400 }
1401
1402 return (arc);
1403}
1404
1405
Note: See TracBrowser for help on using the repository browser.