summaryrefslogtreecommitdiff
path: root/cad/src/outtakes/PropMgrBaseClass.py
blob: bed23fd338394e8b29c1a078c398e6aada039e1d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
# Copyright 2006-2007 Nanorex, Inc.  See LICENSE file for details.
"""
PropMgrBaseClass.py
@author: Mark
@version: $Id$
@copyright: 2006-2007 Nanorex, Inc.  All rights reserved.

History:

mark 2007-05-20: Split PropMgrBaseClass out of PropertyManagerMixin into
                 this file.
mark 2007-05-23: New PropMgrBaseClass with support for the following PropMgr
                 widget classes:
                 - PropMgrGroupBox (subclass of Qt's QGroupBox widget)
                 - PropMgrComboBox (subclass of Qt's QComboBox widget)
                 - PropMgrDoubleSpinBox (subclass of Qt's QDoubleSpinBox widget)
                 - PropMgrSpinBox (subclass of Qt's QSpinBox widget)
                 - PropMgrTextEdit (subclass of Qt's QTextEdit widget)
mark 2007-05-25: Added PropMgrPushButton
mark 2007-05-28: Added PropMgrLineEdit, PropMgrCheckBox and PropMgrListWidget
ninad 2007-06-03: Added PropMgrToolButton
ninad 2007-06-05: Added PropMgrRadioButton
bruce 2007-06-15: partly cleaned up inheritance.

"""

__author__ = "Mark"

# Mark's To Do List (by order of priority): updated 2007-06-21
# - Add new "parentPropMgr" attr for all widget classes. (important)
# - Support horizontal resizing using splitter (A9.1 backlog item)
# - Finish support for Restore Defaults:
#    - PropMgrComboBox.setItems() and PropMgrComboBox.setCurrentIndex()
#    - PropMgrComboBox.setText()
# - Add color theme user pref in Preferences dialog. (minor)
# - Set title button color via style sheet (see getTitleButtonStyleSheet)
# - "range" attr (str) that can be included in What's This text. (minor)
# - override setObjectName() in PropMgrWidgetMixin class to create
#   standard names.
# - Add important PropMgr widget classes:
#   - PropMgrMMKit (needed by BuildAtomsPropertyManager, a future PropMgr)
#   - PropMgrColorChooser
#   - PropMgrLabel
#   - PropMgrGroupAction (needed by PlanePropertyManager)

import os

from PyQt4 import QtCore

from PyQt4.Qt import Qt
from PyQt4.Qt import QGroupBox
from PyQt4.Qt import QTextEdit
from PyQt4.Qt import QDoubleSpinBox
from PyQt4.Qt import QSpinBox
from PyQt4.Qt import QComboBox
from PyQt4.Qt import QPushButton
from PyQt4.Qt import QLineEdit
from PyQt4.Qt import QCheckBox
from PyQt4.Qt import QListWidget
from PyQt4.Qt import QToolButton
from PyQt4.Qt import QPalette
from PyQt4.Qt import QFrame
from PyQt4.Qt import QHBoxLayout
from PyQt4.Qt import QSpacerItem
from PyQt4.Qt import QLabel
from PyQt4.Qt import QWidget
from PyQt4.Qt import QVBoxLayout
from PyQt4.Qt import QGridLayout
from PyQt4.Qt import SIGNAL
from PyQt4.Qt import QTextOption
from PyQt4.Qt import QSizePolicy
from PyQt4.Qt import QSize
from PyQt4.Qt import QRadioButton
from PyQt4.Qt import QTextCursor

from utilities import debug_flags

from utilities.icon_utilities import geticon, getpixmap
from utilities.debug import print_compact_traceback

from PM.PropMgr_Constants import pmColor
from PM.PropMgr_Constants import pmTitleFrameColor
from PM.PropMgr_Constants import pmTitleLabelColor
from PM.PropMgr_Constants import pmMinWidth
from PM.PropMgr_Constants import pmDefaultWidth
from PM.PropMgr_Constants import pmLabelRightAlignment
from PM.PropMgr_Constants import pmLabelLeftAlignment
from PM.PropMgr_Constants import pmMainVboxLayoutMargin
from PM.PropMgr_Constants import pmMainVboxLayoutSpacing
from PM.PropMgr_Constants import pmHeaderFrameMargin
from PM.PropMgr_Constants import pmHeaderFrameSpacing
from PM.PropMgr_Constants import getHeaderFont
from PM.PropMgr_Constants import pmSponsorFrameMargin
from PM.PropMgr_Constants import pmSponsorFrameSpacing
from PM.PropMgr_Constants import pmTopRowBtnsMargin
from PM.PropMgr_Constants import pmTopRowBtnsSpacing
from PM.PropMgr_Constants import pmGroupBoxSpacing
from PM.PropMgr_Constants import pmGrpBoxVboxLayoutMargin
from PM.PropMgr_Constants import pmGrpBoxVboxLayoutSpacing
from PM.PropMgr_Constants import pmGridLayoutMargin
from PM.PropMgr_Constants import pmGridLayoutSpacing
from PM.PropMgr_Constants import pmGrpBoxButtonColor
from PM.PropMgr_Constants import pmGrpBoxButtonBorderColor
from PM.PropMgr_Constants import pmGrpBoxButtonTextColor
from PM.PropMgr_Constants import pmGrpBoxExpandedImage
from PM.PropMgr_Constants import pmGrpBoxCollapsedImage
from PM.PropMgr_Constants import pmGrpBoxGridLayoutMargin
from PM.PropMgr_Constants import pmGrpBoxGridLayoutSpacing
from PM.PropMgr_Constants import pmGrpBoxColor
from PM.PropMgr_Constants import pmGrpBoxBorderColor
from PM.PropMgr_Constants import pmMsgGrpBoxMargin
from PM.PropMgr_Constants import pmMsgGrpBoxSpacing
from PM.PropMgr_Constants import pmMessageTextEditColor
from PM.PropMgr_Constants import pmDoneButton
from PM.PropMgr_Constants import pmCancelButton
from PM.PropMgr_Constants import pmRestoreDefaultsButton
from PM.PropMgr_Constants import pmPreviewButton
from PM.PropMgr_Constants import pmWhatsThisButton

# Special Qt debugging functions written by Mark. 2007-05-24 ############

def getSizePolicyName(sizepolicy):
    """Returns the formal name of <sizepolicy>, a QSizePolicy.
    FYI:
    QSizePolicy.GrowFlag   = 1
    QSizePolicy.ExpandFlag = 2
    QSizePolicy.ShrinkFlag = 4
    QSizePolicy.IgnoreFlag = 8
    """
    assert isinstance(sizepolicy, QSizePolicy)

    if sizepolicy == QSizePolicy.Fixed:
        name = "SizePolicy.Fixed"
    if sizepolicy == QSizePolicy.GrowFlag:
        name = "SizePolicy.Minimum"
    if sizepolicy == QSizePolicy.ShrinkFlag:
        name = "SizePolicy.Maximum"
    if sizepolicy == (QSizePolicy.GrowFlag | QSizePolicy.ShrinkFlag):
        name = "SizePolicy.Preferred"
    if sizepolicy == (QSizePolicy.GrowFlag | QSizePolicy.ShrinkFlag |QSizePolicy.ExpandFlag):
        name = "SizePolicy.Expanding"
    if sizepolicy == (QSizePolicy.GrowFlag | QSizePolicy.ExpandFlag):
        name = "SizePolicy.MinimumExpanding"
    if sizepolicy == (QSizePolicy.ShrinkFlag | QSizePolicy.GrowFlag | QSizePolicy.IgnoreFlag):
        name = "SizePolicy.Ignored"
    return name

def printSizePolicy(widget):
    """Special method for debugging Qt sizePolicies.
    Prints the horizontal and vertical policy of <widget>.
    """
    sizePolicy = widget.sizePolicy()
    print "-----------------------------------"
    print "Widget name =", widget.objectName()
    print "Horizontal SizePolicy =", getSizePolicyName(sizePolicy.horizontalPolicy())
    print "Vertical SizePolicy =",   getSizePolicyName(sizePolicy.verticalPolicy()
    )
def printSizeHints(widget):
    """Special method for debugging Qt size hints.
    Prints the minimumSizeHint (width and height)
    and the sizeHint (width and height) of <widget>.
    """
    print "-----------------------------------"
    print "Widget name =", widget.objectName()
    print "Current Width, Height =", widget.width(), widget.height()
    minSize = widget.minimumSizeHint()
    print "Min Width, Height =", minSize.width(), minSize.height()
    sizeHint = widget.sizeHint()
    print "SizeHint Width, Height =", sizeHint.width(), sizeHint.height()

# PropMgr helper functions ##########################################

def getPalette(palette, obj, color):
    """ Given a palette, Qt object and a color, return a new palette.
    If palette is None, create and return a new palette.
    """
    if palette:
        pass # Make sure palette is QPalette.
    else:
        palette = QPalette()

    palette.setColor(QPalette.Active, obj, color)
    palette.setColor(QPalette.Inactive, obj, color)
    palette.setColor(QPalette.Disabled, obj, color)

    return palette

def getWidgetGridLayoutParms(label, row, spanWidth):
    """PropMgr widget GridLayout helper function.
    Given <label>, <row> and <spanWitdth>, this function returns
    all the parameters needed to place the widget (and its label)
    in the caller's groupbox GridLayout.
    """

    if not spanWidth:
        # This widget and its label are on the same row
        labelRow = row
        labelColumn = 0
        labelSpanCols = 1
        labelAlignment = pmLabelRightAlignment

        widgetRow = row
        widgetColumn = 1
        widgetSpanCols = 1
        incRows = 1

    else: # This widget spans the full width of the groupbox
        if label: # The label and widget are on separate rows.

            # Set the label's row, column and alignment.
            labelRow = row
            labelColumn = 0
            labelSpanCols = 2
            labelAlignment = pmLabelLeftAlignment

            # Set this widget's row and column attrs.
            widgetRow = row + 1 # Widget is below the label.
            widgetColumn = 0
            widgetSpanCols = 2
            incRows = 2
        else:  # No label. Just the widget.
            labelRow = labelColumn = labelSpanCols = labelAlignment = 0
            # Set this widget's row and column attrs.
            widgetRow = row
            widgetColumn = 0
            widgetSpanCols = 2
            incRows = 1

    return widgetRow, widgetColumn, widgetSpanCols, incRows, \
           labelRow, labelColumn, labelSpanCols, labelAlignment

# End of getWidgetGridLayoutParms ####################################

class PropertyManager_common: #bruce 070615 split this out of class PropertyManagerMixin
    """Methods common to PropertyManagerMixin and PropMgrBaseClass.
    """
    def getPropertyManagerPalette(self):
        """ Return a palette for the property manager.
        """
        # in future we might want to set different palette colors for prop managers.
        return self.getPalette(None,
                               QPalette.ColorRole(10),
                               pmColor)

    def getPropMgrTitleFramePalette(self): # note: used only by PropMgrBaseClass as of 070615
        """ Return a palette for Property Manager title frame.
        """
        #bgrole(10) is 'Windows'
        return self.getPalette(None,
                               QPalette.ColorRole(10),
                               pmTitleFrameColor)

    def getPropMgrTitleLabelPalette(self): # note: used only by PropMgrBaseClass as of 070615
        """ Return a palette for Property Manager title label.
        """
        return self.getPalette(None,
                               QPalette.WindowText,
                               pmTitleLabelColor)

    def getPropMgrGroupBoxPalette(self): # note: used only by MessageGroupBox as of 070621
        """ Return a palette for Property Manager groupbox.
        """
        return self.getPalette(None,
                               QPalette.ColorRole(10),
                               pmGrpBoxColor)

    def getTitleButtonPalette(self): # note: used only by MessageGroupBox as of 070621
        """ Return a palette for a GroupBox title button.
        """
        return self.getPalette(None,
                               QPalette.Button,
                               pmGrpBoxButtonColor)


    def getMessageTextEditPalette(self): # note: used only by MessageGroupBox as of 070621
        """ Return a palette for a MessageGroupBox TextEdit (yellow).
        """
        return self.getPalette(None,
                               QPalette.Base,
                               pmMessageTextEditColor)


    def getPalette(self, palette, obj, color): #k maybe only used by methods in this class (not sure) [bruce 070615]
        """ Given a palette, Qt object [actually a ColorRole] and a color, return a new palette.
        If palette is None, create and return a new palette.
        """
        #bruce 070615 replaced with call to another (preexisting) function which has same name and identical code.
        return getPalette(palette, obj, color)

    ###e move more methods here?

    pass # end of class PropertyManager_common

# ==

class PropMgrBaseClass(PropertyManager_common): #bruce 070615 inherit PropertyManager_common, so GBC doesn't need to for our sake
    """Property Manager base class.
    [To make a PM class from this mixin-superclass, subclass it to customize
    the widget set and add behavior, and inherit QDialog (before this class).
    You must also provide certain methods provided by GeneratorBaseClass
    (either by inheriting it -- not sure if superclass order matters for that --
     or by defining them yourself), including ok_btn_clicked and several others,
     including at least some defined by SponsorableMixin (open_sponsor_homepage, setSponsor).
     This set of requirements may be cleaned up.]
    [Note: Technically, this is not a "base class" but a "mixin class".]
    """

    widgets = [] # All widgets in the PropMgr dialog
    num_groupboxes = 0 # Number of groupboxes in PropMgr.
    lastGroupBox = None # The last groupbox in this PropMgr
                        # (i.e. the most recent GroupBox added).
    pmHeightComputed = False # See show() for explaination.

    def __init__(self, name):

        self.setObjectName(name)
        self.widgets = [] # All widgets in the groupbox (except the title button).

        # Main pallete for PropMgr.
        propmgr_palette = self.getPropertyManagerPalette()
        self.setPalette(propmgr_palette)

        # Main vertical layout for PropMgr.
        self.VBoxLayout = QVBoxLayout(self)
        self.VBoxLayout.setMargin(pmMainVboxLayoutMargin)
        self.VBoxLayout.setSpacing(pmMainVboxLayoutSpacing)

        # PropMgr's Header.
        self.addHeader()
        self.addSponsorButton()
        self.addTopRowBtns() # Create top buttons row
        self.MessageGroupBox = PropMgrMessageGroupBox(self, "Message")

        if 0: # For me. Mark 2007-05-17.
            self.debugSizePolicy()

        # Keep this around; it might be useful.
        # I may want to use it now that I understand it.
        # Mark 2007-05-17.
        #QMetaObject.connectSlotsByName(self)

    # On the verge of insanity, then I wrote this.... Mark 2007-05-22
    def debugSizePolicy(self):
        """Special method for debugging sizePolicy.
        Without this, I couldn't figure out how to make groupboxes
        (and their widgets) behave themselves when collapsing and
        expanding them. I needed to experiment with different sizePolicies,
        especially TextEdits and GroupBoxes, to get everything working
        just right. Their layouts can be slippery. Mark 2007-05-22
        """

        if 0: # Override PropMgr sizePolicy.
            self.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                            QSizePolicy.Policy(QSizePolicy.Minimum)))

        if 0: # Override MessageGroupBox sizePolicy.
            self.MessageGroupBox.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))

        if 0: # Override MessageTextEdit sizePolicy.
            self.MessageTextEdit.setSizePolicy(
                QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                            QSizePolicy.Policy(QSizePolicy.Fixed)))

        if 1: # Print the current sizePolicies.
            printSizePolicy(self)
            printSizePolicy(self.MessageGroupBox)
            printSizePolicy(self.MessageTextEdit)

    def show(self):
        """Show the Property Manager.
        """
        self.setSponsor()
        if not self.pw or self:
            self.pw = self.win.activePartWindow()       #@@@ ninad061206
            self.pw.updatePropertyManagerTab(self) # Redundant code. Fix after A9. Mark 2007-06-01
            self.pw.featureManager.setCurrentIndex(self.pw.featureManager.indexOf(self))
        else:
            self.pw.updatePropertyManagerTab(self)
            self.pw.featureManager.setCurrentIndex(self.pw.featureManager.indexOf(self))

        if not self.pmHeightComputed:
            # This fixes a bug (discovered by Ninad) in which the
            # user *reenters* the PropMgr, and the PropMgr has a
            # collapsed groupbox. When the user expands the collapsed groupbox,
            # widgets are "crushed" in all expanded groupboxes. Mark 2007-05-24
            self.fitContents() # pmHeightComputed set to True in fitContents().

        # Show the default message whenever we open the Property Manager.
        self.MessageGroupBox.MessageTextEdit.restoreDefault()

    def fitContents(self):
        """Sets the final width and height of the PropMgr based on the
        current contents. It should be called after all the widgets
        have been added to this PropMgr.

        The important thing this does is set the height of the PropMgr
        after it is loaded with all its GroupBoxes (and their widgets).
        Since this PropMgr dialog is sitting in a ScrollArea, we want
        the scrollbar to appear only when it should. This is determined
        by our height, so we must make sure it is always accurate.

        Note: This should be called anytime the height changes.
        Examples:
        - hiding/showing a widget
        - expanding/collapsing a groupbox
        - resizing a widget based on contents (i.e. a TextEdit).

        To do: I need to try deleting the bottom spacer, compute
        pmHeight and adding the spacer back to address expanding/
        collapsing groupboxes.

        Ask Bruce how to do this. Mark 2007-05-23
        """
        if 0: # Let's see what minimumSizeHint and sizeHint say.
            printSizeHints(self)

        pmWidth = pmDefaultWidth - (4 * 2)
        pmHeight = self.sizeHint().height()
        self.pmHeightComputed = True # See show() for explanation.

        self.resize(pmWidth, pmHeight)

        # Save this. May need it when we support resizing via splitter.
        #self.resize(QSize(
        #    QRect(0,0,pmWidth,pmHeight).size()).
        #    expandedTo(self.minimumSizeHint()))

        if 0:
            print "PropMgr.fitContents(): Width, Height =", self.width(), self.height()

    def addHeader(self):
        """Creates the Property Manager header, which contains
        a pixmap and white text label.
        """

        # Heading frame (dark gray), which contains
        # a pixmap and (white) heading text.
        self.header_frame = QFrame(self)
        self.header_frame.setFrameShape(QFrame.NoFrame)
        self.header_frame.setFrameShadow(QFrame.Plain)

        header_frame_palette = self.getPropMgrTitleFramePalette()
        self.header_frame.setPalette(header_frame_palette)
        self.header_frame.setAutoFillBackground(True)

        # HBox layout for heading frame, containing the pixmap
        # and label (title).
        HeaderFrameHLayout = QHBoxLayout(self.header_frame)
        HeaderFrameHLayout.setMargin(pmHeaderFrameMargin) # 2 pixels around edges.
        HeaderFrameHLayout.setSpacing(pmHeaderFrameSpacing) # 5 pixel between pixmap and label.

        # PropMgr icon. Set image by calling setPropMgrIcon() at any time.
        self.header_pixmap = QLabel(self.header_frame)
        self.header_pixmap.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Fixed),
                              QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.header_pixmap.setScaledContents(True)

        HeaderFrameHLayout.addWidget(self.header_pixmap)

        # PropMgr title label
        self.header_label = QLabel(self.header_frame)
        header_label_palette = self.getPropMgrTitleLabelPalette()
        self.header_label.setPalette(header_label_palette)
        self.header_label.setAlignment(pmLabelLeftAlignment)

        # PropMgr heading font (for label).
        self.header_label.setFont(getHeaderFont())
        HeaderFrameHLayout.addWidget(self.header_label)

        self.VBoxLayout.addWidget(self.header_frame)

    def setPropMgrTitle(self, title):
        """Set the Property Manager header title to string <title>.
        """
        self.header_label.setText(title)

    def setPropMgrIcon(self, png_path):
        """Set the Property Manager icon in the header.
        <png_path> is the relative path to the PNG file.
        """
        self.header_pixmap.setPixmap(getpixmap(png_path))


    def addSponsorButton(self):
        """Creates the Property Manager sponsor button, which contains
        a QPushButton inside of a QGridLayout inside of a QFrame.
        The sponsor logo image is not loaded here.
        """

        # Sponsor button (inside a frame)
        self.sponsor_frame = QFrame(self)
        self.sponsor_frame.setFrameShape(QFrame.NoFrame)
        self.sponsor_frame.setFrameShadow(QFrame.Plain)

        SponsorFrameGrid = QGridLayout(self.sponsor_frame)
        SponsorFrameGrid.setMargin(pmSponsorFrameMargin)
        SponsorFrameGrid.setSpacing(pmSponsorFrameSpacing) # Has no effect.

        self.sponsor_btn = QPushButton(self.sponsor_frame)
        self.sponsor_btn.setAutoDefault(False)
        self.sponsor_btn.setFlat(True)
        self.connect(self.sponsor_btn,SIGNAL("clicked()"),
                     self.open_sponsor_homepage)

        SponsorFrameGrid.addWidget(self.sponsor_btn,0,0,1,1)

        self.VBoxLayout.addWidget(self.sponsor_frame)

        button_whatsthis_widget = self.sponsor_btn
            #bruce 070615 bugfix -- put tooltip & whatsthis on self.sponsor_btn, not self.
            # [self.sponsor_frame might be another possible place to put them.]

        button_whatsthis_widget.setWhatsThis("""<b>Sponsor Button</b>
            <p>When clicked, this sponsor logo will display a short
            description about a NanoEngineer-1 sponsor. This can
            be an official sponsor or credit given to a contributor
            that has helped code part or all of this command.
            A link is provided in the description to learn more
            about this sponsor.</p>""")

        button_whatsthis_widget.setToolTip("NanoEngineer-1 Sponsor Button")

        return

    def addTopRowBtns(self, showFlags=None):
        """Creates the OK, Cancel, Preview, and What's This
        buttons row at the top of the Pmgr.
        """
        # The Top Buttons Row includes the following widgets:
        #
        # - self.pmTopRowBtns (Hbox Layout containing everything:)
        #
        #   - frame
        #     - hbox layout "frameHboxLO" (margin=2, spacing=2)
        #     - Done (OK) button
        #     - Abort (Cancel) button
        #     - Restore Defaults button
        #     - Preview button
        #     - What's This button
        #   - right spacer (10x10)


        # Main "button group" widget (but it is not a QButtonGroup).
        self.pmTopRowBtns = QHBoxLayout()
        # This QHBoxLayout is (probably) not necessary. Try using just the frame for
        # the foundation. I think it should work. Mark 2007-05-30

        # Horizontal spacer
        HSpacer = QSpacerItem(1, 1,
                                QSizePolicy.Expanding,
                                QSizePolicy.Minimum)

        # Frame containing all the buttons.
        self.TopRowBtnsFrame = QFrame()

        self.TopRowBtnsFrame.setFrameShape(QFrame.NoFrame)
        self.TopRowBtnsFrame.setFrameShadow(QFrame.Plain)

        # Create Hbox layout for main frame.
        TopRowBtnsHLayout = QHBoxLayout(self.TopRowBtnsFrame)
        TopRowBtnsHLayout.setMargin(pmTopRowBtnsMargin)
        TopRowBtnsHLayout.setSpacing(pmTopRowBtnsSpacing)

        TopRowBtnsHLayout.addItem(HSpacer)

        # Set button type.
        if 1: # Mark 2007-05-30
            # Needs to be QToolButton for MacOS. Fine for Windows, too.
            buttonType = QToolButton
            # May want to use QToolButton.setAutoRaise(1) below. Mark 2007-05-29
        else:
            buttonType = QPushButton # Do not use.

        # OK (Done) button.
        self.done_btn = buttonType(self.TopRowBtnsFrame)
        self.done_btn.setIcon(
            geticon("ui/actions/Properties Manager/Done.png"))
        self.done_btn.setIconSize(QSize(22,22))
        self.connect(self.done_btn,SIGNAL("clicked()"),
                     self.ok_btn_clicked)
        self.done_btn.setToolTip("Done")

        TopRowBtnsHLayout.addWidget(self.done_btn)

        # Cancel (Abort) button.
        self.abort_btn = buttonType(self.TopRowBtnsFrame)
        self.abort_btn.setIcon(
            geticon("ui/actions/Properties Manager/Abort.png"))
        self.abort_btn.setIconSize(QSize(22,22))
        self.connect(self.abort_btn,SIGNAL("clicked()"),
                     self.abort_btn_clicked)
        self.abort_btn.setToolTip("Cancel")

        TopRowBtnsHLayout.addWidget(self.abort_btn)

        # Restore Defaults button.
        self.restore_defaults_btn = buttonType(self.TopRowBtnsFrame)
        self.restore_defaults_btn.setIcon(
            geticon("ui/actions/Properties Manager/Restore.png"))
        self.restore_defaults_btn.setIconSize(QSize(22,22))
        self.connect(self.restore_defaults_btn,SIGNAL("clicked()"),
                     self.restore_defaults_btn_clicked)
        self.restore_defaults_btn.setToolTip("Restore Defaults")
        TopRowBtnsHLayout.addWidget(self.restore_defaults_btn)

        # Preview (glasses) button.
        self.preview_btn = buttonType(self.TopRowBtnsFrame)
        self.preview_btn.setIcon(
            geticon("ui/actions/Properties Manager/Preview.png"))
        self.preview_btn.setIconSize(QSize(22,22))
        self.connect(self.preview_btn,SIGNAL("clicked()"),
                     self.preview_btn_clicked)
        self.preview_btn.setToolTip("Preview")

        TopRowBtnsHLayout.addWidget(self.preview_btn)

        # What's This (?) button.
        self.whatsthis_btn = buttonType(self.TopRowBtnsFrame)
        self.whatsthis_btn.setIcon(
            geticon("ui/actions/Properties Manager/WhatsThis.png"))
        self.whatsthis_btn.setIconSize(QSize(22,22))
        self.connect(self.whatsthis_btn,SIGNAL("clicked()"),
                     self.enter_WhatsThisMode)
        self.whatsthis_btn.setToolTip("What\'s This Help")

        TopRowBtnsHLayout.addWidget(self.whatsthis_btn)

        TopRowBtnsHLayout.addItem(HSpacer)

        # Create Button Row
        self.pmTopRowBtns.addWidget(self.TopRowBtnsFrame)

        self.VBoxLayout.addLayout(self.pmTopRowBtns)

        # Add What's This for buttons.

        self.done_btn.setWhatsThis("""<b>Done</b>
            <p><img source=\"ui/actions/Properties Manager/Done.png\"><br>
            Completes and/or exits the current command.</p>""")

        self.abort_btn.setWhatsThis("""<b>Cancel</b>
            <p><img source=\"ui/actions/Properties Manager/Abort.png\"><br>
            Cancels the current command.</p>""")

        self.restore_defaults_btn.setWhatsThis("""<b>Restore Defaults</b>
            <p><img source=\"ui/actions/Properties Manager/Restore.png\"><br>
            Restores the defaut values of the Property Manager.</p>""")

        self.preview_btn.setWhatsThis("""<b>Preview</b>
            <p><img source=\"ui/actions/Properties Manager/Preview.png\"><br>
            Preview the structure based on current Property Manager settings.</p>""")

        self.whatsthis_btn.setWhatsThis("""<b>What's This</b>
            <p><img source=\"ui/actions/Properties Manager/WhatsThis.png\"><br>
            Click this option to invoke a small question mark that is attached to the mouse pointer,
            then click on an object which you would like more information about.
            A pop-up box appears with information about the object you selected.</p>""")

        return

    def hideTopRowButtons(self, pmButtonFlags=None):
        """Hide one or more top row buttons using <pmButtonFlags>.
        Button flags not set will cause the button to be shown
        if currently hidden.

        The button flags are:
            pmDoneButton = 1
            pmCancelButton = 2
            pmRestoreDefaultsButton = 4
            pmPreviewButton = 8
            pmWhatsThisButton = 16
            pmAllButtons = 31

        These flags are defined in PropMgr_Constants.py.
        """

        if pmButtonFlags & pmDoneButton: self.done_btn.hide()
        else: self.done_btn.show()

        if pmButtonFlags & pmCancelButton: self.abort_btn.hide()
        else: self.abort_btn.show()

        if pmButtonFlags & pmRestoreDefaultsButton:
            self.restore_defaults_btn.hide()
        else: self.restore_defaults_btn.show()

        if pmButtonFlags & pmPreviewButton: self.preview_btn.hide()
        else: self.preview_btn.show()

        if pmButtonFlags & pmWhatsThisButton: self.whatsthis_btn.hide()
        else: self.whatsthis_btn.show()

    def restore_defaults_btn_clicked(self):
        """Slot for "Restore Defaults" button in the Property Manager.
        """
        for widget in self.widgets:
            if isinstance(widget, PropMgrGroupBox):
                widget.restoreDefault()

# End of PropMgrBaseClass ############################

class PropMgrWidgetMixin:
    """Property Manager Widget Mixin class.
    """

    def addWidgetAndLabelToParent(self, parent, label, spanWidth):
        """Add this widget and its label to <parent>.
        <label> is the text for this widget's label. If <label> is
        empty, no label will be added.
        If <spanWidth> (boolean) is True, the widget (and its label)
        will span the entire width of <parent> (a groupbox).
        """

        # A function that returns all the widget and label layout params.
        widgetRow, widgetColumn, widgetSpanCols, incRows, \
        labelRow, labelColumn, labelSpanCols, labelAlignment = \
        getWidgetGridLayoutParms(label, parent.num_rows, spanWidth)

        if label:
            # Create QLabel widget.
            self.labelWidget = QLabel()
            self.labelWidget.setAlignment(labelAlignment)
            self.labelWidget.setText(label)
            parent.GridLayout.addWidget(self.labelWidget,
                                        labelRow, 0,
                                        1, labelSpanCols)
        else:
            self.labelWidget = None

        parent.GridLayout.addWidget(self,
                                    widgetRow, widgetColumn,
                                    1, widgetSpanCols)
        parent.widgets.append(self)

        parent.num_rows += incRows


    def hide(self):
        """Hide this widget and its label. If hidden, the widget
        will not be displayed when its groupbox is expanded.
        Call show() to unhide this widget (and its label).
        """
        self.hidden = True
        QWidget.hide(self) # Hide self.
        if self.labelWidget:# Hide self's label if it has one.
            self.labelWidget.hide()

        if isinstance(self, PropMgrGroupBox):
            # Change the spacer height to zero to "hide" it unless
            # self is the last GroupBox in the Property Manager.
            # if not self.parent.lastGroupBox is self and \
            if self.VSpacerWidget:
                self.VSpacerWidget.changeSize(10, 0)

    def show(self):
        """Show this widget and its label.
        """
        self.hidden = False
        QWidget.show(self) # Show self.
        if self.labelWidget:# Show self's label if it has one.
            self.labelWidget.show()

        if isinstance(self, PropMgrGroupBox):
            # Reset the height of the VSpacerWidget, if this groupbox has one.
            if self.VSpacerWidget:
                self.VSpacerWidget.changeSize(10, self.VSpacerWidget.defaultHeight)

    def collapse(self):
        """Hides this widget (and its label) when the groupbox is collapsed.
        """
        QWidget.hide(self) # Hide self.
        if self.labelWidget:# Hide self's label if it has one.
            self.labelWidget.hide()

    def expand(self):
        """Shows this widget (and its label) when the groupbox is expanded,
        unless this widget is hidden (via its hidden attr).
        """
        if self.hidden: return
        QWidget.show(self) # Show self.
        if self.labelWidget:# Show self's label if it has one.
            self.labelWidget.show()

    def restoreDefault(self):
        """Restores the default value this widget.

        Note: Need to disconnect and reconnect wigdets to slots.
        """

        if 0: # Debugging. Mark 2007-05-25
            if self.setAsDefault:
                print "Restoring default for ", self.objectName()

        if isinstance(self, PropMgrGroupBox):
            for widget in self.widgets:
                widget.restoreDefault()

        if isinstance(self, PropMgrTextEdit):
            if self.setAsDefault:
                self.insertHtml(self.defaultText,
                                setAsDefault = True,
                                replace = True)

        if isinstance(self, PropMgrDoubleSpinBox):
            if self.setAsDefault:
                self.setValue(self.defaultValue)

        if isinstance(self, PropMgrSpinBox):
            if self.setAsDefault:
                self.setValue(self.defaultValue)

        if isinstance(self, PropMgrComboBox):
            if self.setAsDefault:
                self.clear()
                for choice in self.defaultChoices:
                    self.addItem(choice)
                self.setCurrentIndex(self.defaultIdx)

        if isinstance(self, PropMgrPushButton):
            if self.setAsDefault:
                self.setText(self.defaultText)

        if isinstance(self, PropMgrLineEdit):
            if self.setAsDefault:
                self.setText(self.defaultText)

        if isinstance(self, PropMgrCheckBox):
            if self.setAsDefault:
                self.setCheckState(self.defaultState)

        if isinstance(self, PropMgrListWidget):
            if self.setAsDefault:
                self.insertItems(0, self.defaultItems)
                self.setCurrentRow(self.defaultRow)
                '''
                self.clear()
                for choice in self.defaultChoices:
                    self.addItem(choice)
                self.setCurrentRow(self.defaultRow)
                '''

# End of PropMgrWidgetMixin ############################

class PropMgrGroupBox(QGroupBox, PropMgrWidgetMixin):
    """Group Box class for Property Manager group boxes.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    labelWidget = None # Needed for PropMgrWidgetMixin class (might use to hold title).
    expanded = True # Set to False when groupbox is collapsed.
    widgets = [] # All widgets in the groupbox (except the title button).
    num_rows = 0 # Number of rows in this groupbox.
    num_groupboxes = 0 # Number of groupboxes in this groupbox.
    lastGroupBox = None # The last groupbox in this GroupBox
                        # (i.e. the most recent GroupBox added).
    setAsDefault = True # If set to False, no widgets in this groupbox will be
                        # reset to their default value when the Restore Defaults
                        # button is clicked, regardless thier own <setAsDefault> value.

    def __init__(self, parent, title='', titleButton=False, setAsDefault=True):
        """
        Appends a QGroupBox widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - the main property manager dialog (PropMgrBaseClass).
        <title> - the GroupBox title text.
        <titleButton> - if True, a titleButton is added to the top of the
                        GroupBox with the label <title>. The titleButton is
                        used to collapse and expand the GroupBox.
                        if False, no titleButton is added. <title> will be
                        used as the GroupBox title and the GroupBox will
                        not be collapsable/expandable.
        <setAsDefault> - if False, no widgets in this groupbox will have thier
                        default values restored when the Restore Defaults
                        button is clicked, regardless thier own <setAsDefault> value.
        """

        QGroupBox.__init__(self)

        self.parent = parent
        parent.num_groupboxes += 1
        num_groupboxes = 0

        self.setObjectName(parent.objectName() +
                           "/pmGroupBox" +
                           str(parent.num_groupboxes))

        self.setAsDefault = setAsDefault

        # Calling addWidget() here is important. If done at the end,
        # the title button does not get assigned its palette for some
        # unknown reason. Mark 2007-05-20.
        parent.VBoxLayout.addWidget(self) # Add self to PropMgr's VBoxLayout

        self.widgets = [] # All widgets in the groupbox (except the title button).
        parent.widgets.append(self)

        self.setAutoFillBackground(True)
        self.setPalette(self.getPalette())
        self.setStyleSheet(self.getStyleSheet())

        # Create vertical box layout
        self.VBoxLayout = QVBoxLayout(self)
        self.VBoxLayout.setMargin(pmGrpBoxVboxLayoutMargin)
        self.VBoxLayout.setSpacing(pmGrpBoxVboxLayoutSpacing)

        # Create grid layout
        self.GridLayout = QGridLayout()
        self.GridLayout.setMargin(pmGridLayoutMargin)
        self.GridLayout.setSpacing(pmGridLayoutSpacing)

        # Insert grid layout in its own VBoxLayout
        self.VBoxLayout.addLayout(self.GridLayout)

        if titleButton: # Add title button to GroupBox
            self.titleButton = self.getTitleButton(title, self)
            self.VBoxLayout.insertWidget(0, self.titleButton)
            self.connect(self.titleButton,SIGNAL("clicked()"),
                     self.toggleExpandCollapse)
        else:
            self.setTitle(title)

        # Fixes the height of the groupbox. Very important. Mark 2007-05-29
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.addBottomSpacer()

    def addBottomSpacer(self):
        """Add a vertical spacer below this groupbox <self>.
        Assume <self> is going to be the last groupbox in this PropMgr, so set
        its spacer's vertical sizePolicy to MinimumExpanding. We then set the
        vertical sizePolicy of the last groupbox's spacer to Fixed and set its
        height to pmGroupBoxSpacing.
        """
        # Spacers are only added to groupboxes in the PropMgr, not
        # nested groupboxes.
        if not isinstance(self.parent, PropMgrBaseClass):
            self.VSpacerWidget = None
            return

        if self.parent.lastGroupBox:
            # lastGroupBox is no longer the last one. <self> will be the
            # lastGroupBox, so we must change the VSpacerWidget height
            # and sizePolicy of lastGroupBox to be a fixed
            # spacer between it and <self>.
            defaultHeight = pmGroupBoxSpacing
            self.parent.lastGroupBox.VSpacerWidget.changeSize(
                10, defaultHeight,
                QSizePolicy.Fixed,
                QSizePolicy.Fixed)
            self.parent.lastGroupBox.VSpacerWidget.defaultHeight = defaultHeight

        # Add a 1 pixel high, MinimumExpanding VSpacer below this GroupBox.
        # This keeps the PropMgr layout squeezed together as groupboxes
        # are expanded, collapsed, hidden and shown again.
        defaultHeight = 1
        self.VSpacerWidget = QSpacerItem(10, defaultHeight,
                                        QSizePolicy.Fixed,
                                        QSizePolicy.MinimumExpanding)

        self.VSpacerWidget.defaultHeight = defaultHeight

        self.parent.VBoxLayout.addItem(self.VSpacerWidget)

        # This groupbox is now the last one in the PropMgr.
        self.parent.lastGroupBox = self

    def setTitle(self, title):
        """Sets the groupbox title to <title>.
        This overrides QGroupBox's setTitle() method.
        """
        # Create QLabel widget.
        self.labelWidget = QLabel()
        labelAlignment = pmLabelLeftAlignment
        self.labelWidget.setAlignment(labelAlignment)
        self.labelWidget.setText(title)
        self.VBoxLayout.insertWidget(0, self.labelWidget)

    # Title Button Methods #####################################

    def getTitleButton(self, title, parent=None, showExpanded=True): #Ninad 070206
        """ Return the groupbox title pushbutton. The pushbutton is customized
        such that  it appears as a title bar to the user. If the user clicks on
        this 'titlebar' it sends appropriate signals to open or close the
        groupboxes   'name = string -- title of the groupbox
        'showExpanded' = boolean .. NE1 uses a different background
        image in the button's  Style Sheet depending on the bool.
        (i.e. if showExpanded = True it uses a opened group image  '^')
        See also: getGroupBoxTitleCheckBox , getGroupBoxButtonStyleSheet  methods
        """

        button  = QPushButton(title, parent)
        button.setFlat(False)
        button.setAutoFillBackground(True)

        button.setStyleSheet(self.getTitleButtonStyleSheet(showExpanded))

        self.titleButtonPalette = self.getTitleButtonPalette()
        button.setPalette(self.titleButtonPalette)

        #ninad 070221 set a non existant 'Ghost Icon' for this button
        #By setting such an icon, the button text left aligns!
        #(which what we want :-) )
        #So this might be a bug in Qt4.2.  If we don't use the following kludge,
        #there is no way to left align the push button text but to subclass it.
        #(could mean a lot of work for such a minor thing)  So OK for now

        button.setIcon(geticon("ui/actions/Properties Manager/GHOST_ICON"))

        return button

    def getTitleButtonPalette(self):
        """ Return a palette for a GroupBox title button.
        """
        return getPalette(None, QPalette.Button, pmGrpBoxButtonColor)


    def getTitleButtonStyleSheet(self, showExpanded=True):
        """Returns the style sheet for a groupbox title button (or checkbox).
        If <showExpanded> is True, the style sheet includes an expanded icon.
        If <showExpanded> is False, the style sheet includes a collapsed icon.
        """

        # Need to move border color and text color to top (make global constants).
        if showExpanded:
            styleSheet = "QPushButton {border-style:outset;\
            border-width: 2px;\
            border-color: " + pmGrpBoxButtonBorderColor + ";\
            border-radius:2px;\
            font:bold 12px 'Arial'; \
            color: " + pmGrpBoxButtonTextColor + ";\
            min-width:10em;\
            background-image: url(" + pmGrpBoxExpandedImage + ");\
            background-position: right;\
            background-repeat: no-repeat;\
            }"
        else:

            styleSheet = "QPushButton {border-style:outset;\
            border-width: 2px;\
            border-color: " + pmGrpBoxButtonBorderColor + ";\
            border-radius:2px;\
            font: bold 12px 'Arial'; \
            color: " + pmGrpBoxButtonTextColor + ";\
            min-width:10em;\
            background-image: url(" + pmGrpBoxCollapsedImage + ");\
            background-position: right;\
            background-repeat: no-repeat;\
            }"

        return styleSheet

    def toggleExpandCollapse(self):
        """Slot method for the title button to expand/collapse the groupbox.
        """
        if self.widgets:
            if self.expanded: # Collapse groupbox by hiding all widgets in groupbox.
                self.GridLayout.setMargin(0)
                self.GridLayout.setSpacing(0)
                # The styleSheet contains the expand/collapse.
                styleSheet = self.getTitleButtonStyleSheet(showExpanded = False)
                self.titleButton.setStyleSheet(styleSheet)
                # Why do we have to keep resetting the palette?
                # Does assigning a new styleSheet reset the button's palette?
                # If yes, we should add the button's color to the styleSheet.
                # Mark 2007-05-20
                self.titleButton.setPalette(self.getTitleButtonPalette())
                self.titleButton.setIcon(
                    geticon("ui/actions/Properties Manager/GHOST_ICON"))
                for widget in self.widgets:
                    widget.collapse()
                self.expanded = False
            else: # Expand groupbox by showing all widgets in groupbox.
                if isinstance(self, PropMgrMessageGroupBox):
                    # If we don't do this, we get a small space b/w the
                    # title button and the MessageTextEdit widget.
                    # Extra code unnecessary, but more readable.
                    # Mark 2007-05-21
                    self.GridLayout.setMargin(0)
                    self.GridLayout.setSpacing(0)
                else:
                    self.GridLayout.setMargin(pmGrpBoxGridLayoutMargin)
                    self.GridLayout.setSpacing(pmGrpBoxGridLayoutSpacing)
                # The styleSheet contains the expand/collapse.
                styleSheet = self.getTitleButtonStyleSheet(showExpanded = True)
                self.titleButton.setStyleSheet(styleSheet)
                # Why do we have to keep resetting the palette?
                # Does assigning a new styleSheet reset the button's palette?
                # If yes, we should add the button's color to the styleSheet.
                # Mark 2007-05-20
                self.titleButton.setPalette(self.getTitleButtonPalette())
                self.titleButton.setIcon(
                    geticon("ui/actions/Properties Manager/GHOST_ICON"))
                for widget in self.widgets:
                    widget.expand()
                self.expanded = True
        else:
            print "Groupbox has no widgets. Clicking on groupbox button has no effect"

    # GroupBox palette and stylesheet methods. ##############################3

    def getPalette(self):
        """ Return a palette for this groupbox.
        The color should be slightly darker (or lighter) than the property manager background.
        """
        #bgrole(10) is 'Windows'
        return getPalette(None, QPalette.ColorRole(10), pmGrpBoxColor)

    def getStyleSheet(self):
        """Return the style sheet for a groupbox. This sets the following
        properties only:
         - border style
         - border width
         - border color
         - border radius (on corners)
        The background color for a groupbox is set using getPalette()."""

        styleSheet = "QGroupBox {border-style:solid;\
        border-width: 1px;\
        border-color: " + pmGrpBoxBorderColor + ";\
        border-radius: 0px;\
        min-width: 10em; }"

        ## For Groupboxs' Pushbutton :
        ##Other options not used : font:bold 10px;

        return styleSheet

# End of PropMgrGroupBox ############################

class PropMgrMessageGroupBox(PropMgrGroupBox):
    '''Message GroupBox class'''

    expanded = True # Set to False when groupbox is collapsed.
    widgets = [] # All widgets in the groupbox (except the title button).
    num_rows = 0 # Number of rows in the groupbox.
    num_grouboxes = 0 # Number of groupboxes in this msg groupbox.

    def __init__(self, parent, title):
        """Constructor for PropMgr group box.
        <parent> is the PropMgr dialog (of type PropMgrBaseClass)
        <title> is the label used on the the title button
        """
        PropMgrGroupBox.__init__(self, parent, title, titleButton=True)

        parent.num_groupboxes += 1
        num_groupboxes = 0

        self.setObjectName(parent.objectName() +
                           "/pmMessageGroupBox" +
                           str(parent.num_groupboxes))

        self.widgets = [] # All widgets in the groupbox (except the title button).

        self.VBoxLayout.setMargin(pmMsgGrpBoxMargin)
        self.VBoxLayout.setSpacing(pmMsgGrpBoxSpacing)

        self.GridLayout.setMargin(0)
        self.GridLayout.setSpacing(0)

        self.MessageTextEdit = PropMgrTextEdit(self, label='', spanWidth=True)

        # wrapWrapMode seems to be set to QTextOption.WrapAnywhere on MacOS,
        # so let's force it here. Mark 2007-05-22.
        self.MessageTextEdit.setWordWrapMode(QTextOption.WordWrap)

        parent.MessageTextEdit = self.MessageTextEdit

        # These two policies very important. Mark 2007-05-22
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.MessageTextEdit.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.setWhatsThis("""<b>Messages</b>
            <p>This prompts the user for a requisite operation and/or displays
            helpful messages to the user.</p>""")

        # Hide until insertHtmlMessage() loads a message.
        self.hide()

    def insertHtmlMessage(self, text, setAsDefault=False,
                          minLines=4, maxLines=10,
                          replace=True):
        """Insert <text> (HTML) into the Prop Mgr's message groupbox.
        <minLines> - The minimum number of lines (of text) to display in the TextEdit.
        if <minLines>=0 the TextEdit will fit its own height to fit <text>. The
        default height is 4 (lines of text).
        <maxLines> - The maximum number of lines to display in the TextEdit widget.
        <replace> should be set to False if you do not wish
        to replace the current text. It will append <text> instead.

        Shows the message groupbox if it is hidden.
        """
        self.MessageTextEdit.insertHtml(text, setAsDefault,
                                        minLines=minLines, maxLines=maxLines,
                                        replace=True)
        self.show()

# End of PropMgrMessageGroupBox ############################

class PropMgrTextEdit(QTextEdit, PropMgrWidgetMixin, PropertyManager_common):
    """PropMgr TextEdit class, for groupboxes (PropMgrGroupBox) only.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to True if "Restore Defaults" should
    # reset this widget's text to defaultText.
    setAsDefault = False
    defaultText = '' # Default text

    def __init__(self, parent, label='', spanWidth=False):
        """
        Appends a QTextEdit widget to <parent>, a property manager groupbox.
        The QTextEdit is empty (has no text) by default. Use insertHtml()
        to insert HTML text into the TextEdit.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left (or above) of the TextEdit.
        <spanWidth> - if True, the TextEdit and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the TextEdit (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "QTextEdit.__init__():"
            print "  label=", label
            print "  spanWidth=",spanWidth

        if not parent:
            return

        QTextEdit.__init__(self)
        self.setObjectName(parent.objectName() +
                           "/pmTextEdit" +
                           str(parent.num_rows))

        self._setHeight() # Default height is 4 lines high.

        # Needed for Intel MacOS. Otherwise, the horizontal scrollbar
        # is displayed in the MessageGroupBox. Mark 2007-05-24.
        # Shouldn't be needed with _setHeight().
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        if isinstance(parent, PropMgrMessageGroupBox):
            # Add to parent's VBoxLayout if <parent> is a MessageGroupBox.
            parent.VBoxLayout.addWidget(self)
            # We should be calling the propmgr's getMessageTextEditPalette() method,
            # but that will take some extra work which I will do soon. Mark 2007-06-21
            self.setPalette(self.getMessageTextEditPalette())
            self.setReadOnly(True)
            self.setObjectName("MessageTextEdit")
            self.labelWidget = None # Never has one. Mark 2007-05-31
            parent.widgets.append(self)
            parent.num_rows += 1
        else:
            self.addWidgetAndLabelToParent(parent, label, spanWidth)

    def insertHtml(self, text, setAsDefault=False,
                   minLines=4, maxLines=6,
                   replace=True):
        """Insert <text> (HTML) into the Prop Mgr's message groupbox.
        <minLines> is the minimum number of lines to
        display, even if the text takes up fewer lines. The default
        number of lines is 4.
        <maxLines> is the maximum number of lines to
        diplay before adding a vertical scrollbar.
        <replace> should be set to False if you do not wish
        to replace the current text. It will append <text> instead.
        """
        if setAsDefault:
            self.defaultText = text
            self.setAsDefault = True

        if replace:
            # Replace the text by selecting effectively all text and
            # insert the new text 'over' it (overwrite). :jbirac: 20070629
            cursor  =  self.textCursor()
            cursor.setPosition( 0,
                                QTextCursor.MoveAnchor )
            cursor.setPosition( len(self.toPlainText()),
                                QTextCursor.KeepAnchor )
            self.setTextCursor( cursor )

        QTextEdit.insertHtml(self, text)

        if replace:
            # Restore the previous cursor position/selection and mode.
            cursor.setPosition( len(self.toPlainText()),
                                QTextCursor.MoveAnchor )
            self.setTextCursor( cursor )

        self._setHeight(minLines, maxLines)


    def _setHeight(self, minLines=4, maxLines=8):
        """Set the height just high enough to display
        the current text without a vertical scrollbar.
        <minLines> is the minimum number of lines to
        display, even if the text takes up fewer lines.
        <maxLines> is the maximum number of lines to
        diplay before adding a vertical scrollbar.
        """

        if minLines == 0:
            fitToHeight=True
        else:
            fitToHeight=False

        # Current width of PropMgrTextEdit widget.
        current_width = self.sizeHint().width()

        # Probably including Html tags.
        text = self.toPlainText()
        text_width = self.fontMetrics().width(text)

        num_lines = text_width/current_width + 1
            # + 1 may create an extra (empty) line on rare occasions.

        if fitToHeight:
            num_lines = min(num_lines, maxLines)

        else:
            num_lines = max(num_lines, minLines)

        #margin = self.fontMetrics().leading() * 2 # leading() returned 0. Mark 2007-05-28
        margin = 10 # Based on trial and error. Maybe it is pm?Spacing=5 (*2)? Mark 2007-05-28
        new_height = num_lines * self.fontMetrics().lineSpacing() + margin

        if 0: # Debugging code for me. Mark 2007-05-24
            print "--------------------------------"
            print "Widget name =", self.objectName()
            print "minLines =", minLines
            print "maxLines =", maxLines
            print "num_lines=", num_lines
            print "New height=", new_height
            print "text =", text
            print "Text width=", text_width
            print "current_width (of PropMgrTextEdit)=", current_width

        # Reset height of PropMgrTextEdit.
        self.setMinimumSize(QSize(pmMinWidth * 0.5, new_height))
        self.setMaximumHeight(new_height)

# End of PropMgrTextEdit ############################

class PropMgrDoubleSpinBox(QDoubleSpinBox, PropMgrWidgetMixin):
    """PropMgr SpinBox class, for groupboxes (PropMgrGroupBox) only.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's value to val.
    setAsDefault = True
    defaultValue = 0 # Default value of spinbox

    def __init__(self, parent, label='',
                 val=0, setAsDefault=True,
                 min=0, max=99,
                 singleStep=1.0, decimals=1,
                 suffix='',
                 spanWidth=False):
        """
        Appends a QDoubleSpinBox widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left (or above) of the SpinBox.
        <val> - initial value of SpinBox.
        <setAsDefault> - if True, will restore <val>
                         when the "Restore Defaults" button is clicked.
        <min> - minimum value in the SpinBox.
        <max> - maximum value in the SpinBox.
        <decimals> - precision of SpinBox.
        <singleStep> - increment/decrement value when user uses arrows.
        <suffix> - suffix.
        <spanWidth> - if True, the SpinBox and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the SpinBox (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "PropMgrSpinBox.__init__():"
            print "  label=", label
            print "  val =", val
            print "  setAsDefault =", setAsDefault
            print "  min =", min
            print "  max =", max
            print "  singleStep =", singleStep
            print "  decimals =", decimals
            print "  suffix =", suffix
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QDoubleSpinBox.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmDoubleSpinBox" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Set QDoubleSpinBox min, max, singleStep, decimals, then val
        self.setRange(min, max)
        self.setSingleStep(singleStep)
        self.setDecimals(decimals)

        self.setValue(val) # This must come after setDecimals().

        # Add suffix if supplied.
        if suffix:
            self.setSuffix(suffix)

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

    def setValue(self, val, setAsDefault=True):
        """Set value of widget to <val>. If <setAsDefault> is True,
        <val> becomes the default value for this widget so that
        "Restore Defaults" will reset this widget to <val>.
        """
        if setAsDefault:
            self.setAsDefault=True
            self.defaultValue=val
        QDoubleSpinBox.setValue(self, val)

# End of PropMgrDoubleSpinBox ############################

class PropMgrSpinBox(QSpinBox, PropMgrWidgetMixin):
    """PropMgr SpinBox class, for groupboxes (PropMgrGroupBox) only.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's value to val.
    setAsDefault = True
    defaultValue = 0 # Default value of spinbox

    def __init__(self, parent, label='',
                 val=0, setAsDefault=True,
                 min=0, max=99,
                 suffix='',
                 spanWidth=False):
        """
        Appends a QSpinBox widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left of (or above) the SpinBox.
        <val> - initial value of SpinBox.
        <setAsDefault> - if True, will restore <val>
                         when the "Restore Defaults" button is clicked.
        <min> - minimum value in the SpinBox.
        <max> - maximum value in the SpinBox.
        <suffix> - suffix.
        <spanWidth> - if True, the SpinBox and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the SpinBox (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "PropMgrSpinBox.__init__():"
            print "  label=", label
            print "  val =", val
            print "  setAsDefault =", setAsDefault
            print "  min =", min
            print "  max =", max
            print "  suffix =", suffix
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QSpinBox.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmSpinBox" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Set QSpinBox min, max and initial value
        self.setRange(min, max)
        self.setValue(val)

        # Set default value
        self.defaultValue=val
        self.setAsDefault = setAsDefault

        # Add suffix if supplied.
        if suffix:
            self.setSuffix(suffix)

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

    def setValue(self, val, setAsDefault=True):
        """Set value of widget to <val>. If <setAsDefault> is True,
        <val> becomes the default value for this widget so that
        "Restore Defaults" will reset this widget to <val>.
        """
        if setAsDefault:
            self.setAsDefault=True
            self.defaultValue=val
        QSpinBox.setValue(self, val)

# End of PropMgrSpinBox ############################

class PropMgrComboBox(QComboBox, PropMgrWidgetMixin):
    """PropMgr ComboBox class.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's choice index to idx.
    setAsDefault = True
    # <defaultIdx> - default index when "Restore Defaults" is clicked
    defaultIdx = 0
    # <defaultChoices> - default choices when "Restore Defaults" is clicked.
    defaultChoices = []

    def __init__(self, parent, label='',
                 choices=[], idx=0, setAsDefault=True,
                 spanWidth=False):
        """
        Appends a QComboBox widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left of (or above) the ComboBox.
        <choices> - list of choices (strings) in the ComboBox.
        <idx> - initial index (choice) of combobox. (0=first item)
        <setAsDefault> - if True, will restore <idx> as the current index
                         when the "Restore Defaults" button is clicked.
        <spanWidth> - if True, the ComboBox and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the ComboBox (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "PropMgrComboBox.__init__():"
            print "  label=",label
            print "  choices =", choices
            print "  idx =", idx
            print "  setAsDefault =", setAsDefault
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QComboBox.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmComboBox" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Load QComboBox widget choices and set initial choice (index).
        for choice in choices:
            self.addItem(choice)
        self.setCurrentIndex(idx)

        # Set default index
        self.defaultIdx=idx
        self.defaultChoices=choices
        self.setAsDefault = setAsDefault

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

# End of PropMgrComboBox ############################

class PropMgrPushButton(QPushButton, PropMgrWidgetMixin):
    """PropMgr PushButton class.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's text.
    setAsDefault = True
    # <defaultText> - default text when "Restore Default" is clicked
    defaultText = ""

    def __init__(self, parent, label='',
                 text='', setAsDefault=True,
                 spanWidth=False):
        """
        Appends a QPushButton widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left of (or above) the PushButton.
        <text> - text displayed on the PushButton.
        <setAsDefault> - if True, will restore <text> as the PushButton's text
                         when the "Restore Defaults" button is clicked.
        <spanWidth> - if True, the PushButton and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the ComboBox (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "PropMgrPushButton.__init__():"
            print "  label=",label
            print "  text =", text
            print "  setAsDefault =", setAsDefault
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QPushButton.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmPushButton" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Set text
        self.setText(text)

        # Set default text
        self.defaultText=text
        self.setAsDefault = setAsDefault

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

# End of PropMgrPushButton ############################

class PropMgrLineEdit(QLineEdit, PropMgrWidgetMixin):
    """PropMgr LineEdit class, for groupboxes (PropMgrGroupBox) only.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's value to val.
    setAsDefault = True
    defaultText = "" # Default value of lineedit

    def __init__(self, parent, label='',
                 text='', setAsDefault=True,
                 spanWidth=False):
        """
        Appends a QLineEdit widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left of (or above) the widget.
        <text> - initial value of LineEdit widget.
        <setAsDefault> - if True, will restore <val>
                         when the "Restore Defaults" button is clicked.
        <spanWidth> - if True, the widget and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the SpinBox (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "PropMgrLineEdit.__init__():"
            print "  label=", label
            print "  text =", text
            print "  setAsDefault =", setAsDefaultfix
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QLineEdit.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmLineEdit" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Set QLineEdit text
        self.setText(text)

        # Set default value
        self.defaultText=text
        self.setAsDefault = setAsDefault

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

# End of PropMgrLineEdit ############################

class PropMgrCheckBox(QCheckBox, PropMgrWidgetMixin):
    """PropMgr CheckBox class, for groupboxes (PropMgrGroupBox) only.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's value to val.
    setAsDefault = True
    defaultState = QtCore.Qt.Unchecked  # Default state of CheckBox. Qt.Checked is checked.

    def __init__(self, parent, label='',
                 isChecked=False, setAsDefault=True,
                 spanWidth=False, checkBoxText =''):
        """
        Appends a QCheckBox widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left of (or above) the widget.
        <isChecked> - can be True, False, or one of the Qt ToggleStates
                      True = checked
                      False = unchecked
        <setAsDefault> - if True, will restore <val>
                         when the "Restore Defaults" button is clicked.
        <spanWidth> - if True, the widget and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the widget (unless the label is empty) left justified.
        <checkBoxText> - Text for the checkbox itself. It always appears on the
                         right hand side of the checkbox
        """

        if 0: # Debugging code
            print "PropMgrCheckBox.__init__():"
            print "  label=", label
            print "  state =", state
            print "  setAsDefault =", setAsDefaultfix
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QCheckBox.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmCheckBox" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        self.setCheckState(isChecked, setAsDefault)
        self.setText(checkBoxText)
        self.addWidgetAndLabelToParent(parent, label, spanWidth)

    def setCheckState(self, state, setAsDefault=True):
        """Set state of widget to <state>. If <setAsDefault> is True,
        <state> becomes the default state for this widget so that
        "Restore Defaults" will reset this widget to <state>.
        <state> can be True, False, or one of the Qt ToggleStates
        True = checked
        False = unchecked
        """

        # Set <state> to the appropriate Qt.ToggleState if True or False.
        if state is True:  state = Qt.Checked
        if state is False: state = Qt.Unchecked

        if setAsDefault:
            self.setAsDefault=setAsDefault
            self.defaultState=state

        QCheckBox.setCheckState(self, state)

# End of PropMgrCheckBox ############################

class PropMgrListWidget(QListWidget, PropMgrWidgetMixin):
    """PropMgr ListWidget class, for groupboxes (PropMgrGroupBox) only.
    """
        # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's choice index to idx.
    setAsDefault = True
    # <defaultRow> - default row when "Restore Defaults" is clicked
    defaultRow = 0
    # <defaultItems> - list of items when "Restore Defaults" is clicked.
    defaultItems = []

    def __init__(self, parent, label='',
                 items=[], row=0, setAsDefault=True,
                 numRows=6, spanWidth=False):
        """
        Appends a QListWidget widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears to the left of (or above) the ComboBox.
        <items> - list of items (strings) to be inserted in the widget.
        <row> - current row. (0=first item)
        <setAsDefault> - if True, will restore <idx> as the current index
                         when the "Restore Defaults" button is clicked.
        <numRows> - the number of rows to display. If <items> is
                 greater than <numRows>, a scrollbar will be displayed.
        <spanWidth> - if True, the widget and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the widget (unless the label is empty) left justified.
        """

        if 0: # Debugging code
            print "PropMgrListWidget.__init__():"
            print "  label=",label
            print "  items =", items
            print "  row =",row
            print "  setAsDefault =", setAsDefault
            print "  numRows =",numRows
            print "  spanWidth =", spanWidth

        if not parent:
            return

        QListWidget.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmListWidget" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Load QComboBox widget choices and set initial choice (index).
        #for choice in choices:
        #    self.addItem(choice)

        self.insertItems(0, items, setAsDefault)
        self.setCurrentRow(row, setAsDefault)

        # Need setChoices() method
        #self.defaultChoices=choices

        # Set height
        margin = self.fontMetrics().leading() * 2 # Mark 2007-05-28
        height = numRows * self.fontMetrics().lineSpacing() + margin
        self.setMaximumHeight(height)

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

    def insertItems(self, row, items, setAsDefault=True):
        """Insert items of widget starting at <row>.
        If <setAsDefault> is True, <items> become the default list of
        items for this widget. "Restore Defaults" will reset
        the list of items to <items>.

        Note: <items> will always replace the list of current items
        in the widget. <row> is ignored. This is considered a bug. Mark 2007-06-04
        """

        if row <> 0:
            msg = "PropMgrListWidget.insertItems(): <row> must be zero. See docstring for details:"
            print_compact_traceback(msg)
            return

        if setAsDefault:
            self.setAsDefault = setAsDefault
            self.defaultItems=items

        self.clear()
        QListWidget.insertItems(self, row, items)

    def setCurrentRow(self, row, setAsDefault=True):
        """Set current row of widget to <row>. If <setAsDefault> is True,
        <row> becomes the default row for this widget so that
        "Restore Defaults" will reset this widget to <row>.
        """
        if setAsDefault:
            self.setAsDefault = setAsDefault
            self.defaultRow=row
        QListWidget.setCurrentRow(self, row)

# End of PropMgrListWidget ############################

class PropMgrToolButton(QToolButton, PropMgrWidgetMixin):
    """PropMgr ToolButton class.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's text.
    setAsDefault = True
    # <defaultText> - default text when "Restore Default" is clicked
    defaultText = ""

    def __init__(self, parent, label='',
                 labelPlacement = 'left',
                 icon = None,
                 toolButtonStyle= 'iconOnly',
                 text='',
                 setAsDefault=True,
                 spanWidth=False):
        """
        Appends a QToolButton widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <label> - label that appears at a position specified by <labelPlacement>
        relative to the Toolbutton
        <labelPlacement> - Placement of the <label> relative to ToolButton.
        (left, right, top, bottom) --- NOT IMPLEMENTED YET
        <icon> icon for the toolbutton
        <toolButtonStyle> NOT IMPLEMENTED YET
        <text> - text displayed on the ToolButton.
        <setAsDefault> - if True, will restore <text> as the ToolButton's text
                         when the "Restore Defaults" button is clicked.
        <spanWidth> - if True, the ToolButton and its label will span the width
                      of its groupbox. Its label will appear directly above
                      the ComboBox (unless the label is empty) left justified.
        """

        if not parent:
            return

        QToolButton.__init__(self)

        self.setObjectName(parent.objectName() +
                           "/pmToolButton" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        # Set text
        self.setText(text)

        # Set default text
        self.defaultText=text
        self.setAsDefault = setAsDefault

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

# End of PropMgrToolButton ############################

class PropMgrRadioButton(QRadioButton, PropMgrWidgetMixin):
    """PropMgr RadioButton class.
    """
    # Set to True to always hide this widget, even when groupbox is expanded.
    hidden = False
    # Set setAsDefault to False if "Restore Defaults" should not
    # reset this widget's text.
    setAsDefault = True
    # <defaultText> - default text when "Restore Default" is clicked
    defaultText = ""

    def __init__(self, parent,
                 text='',
                 setAsDefault=True,
                 spanWidth=True):
        """
        Appends a QRadioButton widget to <parent>, a property manager groupbox.

        Arguments:

        <parent> - a property manager groupbox (PropMgrGroupBox).
        <text> - text displayed on the RadioButton.
        <setAsDefault> - if True, will restore <text> as the RadioButton's text
                         when the "Restore Defaults" button is clicked.
        <spanWidth> - if True, the RadioButton will span the width
                      of its groupbox.
        """

        if not parent:
            return

        QRadioButton.__init__(self)

        label = ''
        self.setObjectName(parent.objectName()+
                           "/pmRadioButton" +
                           str(parent.num_groupboxes) +
                           "/'" + label + "'")

        self.setText(text)
        # Set default text
        self.defaultText=text
        self.setAsDefault = setAsDefault

        self.setCheckable(True)

        self.addWidgetAndLabelToParent(parent, label, spanWidth)

# End of PropMgrRadioButton ############################