summaryrefslogtreecommitdiff
path: root/cad/src/ne1_ui/MWsemantics.py
blob: f2d999360cff9f74566404c19a44307aa248a948 (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
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details.
"""
MWsemantics.py provides the main window class, MWsemantics.

@version: $Id$
@copyright: 2004-2009 Nanorex, Inc.  See LICENSE file for details.

History: too much to mention, except for breakups of the file.

[maybe some of those are not listed here?]
bruce 050413 split out movieDashboardSlotsMixin
bruce 050907 split out fileSlotsMixin
mark 060120 split out viewSlotsMixin
mark 2008-02-02 split out displaySlotsMixin

[Much more splitup of this file is needed. Ideally we would
split up the class MWsemantics (as for BuildCrystal_Command), not just the file.]

[some of that splitup has been done, now, by Ninad in the Qt4 branch]
"""

from utilities.qt4transition import qt4warning

from PyQt4 import QtGui, QtCore

from PyQt4.Qt import Qt
from PyQt4.Qt import QFont
from PyQt4.Qt import QMenu
from PyQt4.Qt import QSettings
from PyQt4.Qt import QVariant

from PyQt4.Qt import QMainWindow, SIGNAL
from PyQt4.Qt import QMessageBox
from PyQt4.Qt import QToolBar
from PyQt4.Qt import QStatusBar

from model.elements import PeriodicTable
from model.assembly import Assembly
from graphics.drawing.graphics_card_info import get_gl_info_string ## grantham 20051201
import os, sys
import time

from utilities import debug_flags

from platform_dependent.PlatformDependent import find_or_make_Nanorex_directory
from platform_dependent.PlatformDependent import open_file_in_editor
from platform_dependent.PlatformDependent import find_or_make_Nanorex_subdir

from ne1_ui.ViewOrientationWindow import ViewOrientationWindow # Ninad 061121

from utilities.debug import print_compact_traceback
from utilities.debug_prefs import debug_pref, Choice_boolean_False
from utilities.constants import str_or_unicode
from utilities.constants import RECENTFILES_QSETTINGS_KEY

from ne1_ui.Ui_MainWindow import Ui_MainWindow
from ne1_ui.Ui_PartWindow import Ui_PartWindow

from utilities.Log import greenmsg, redmsg, orangemsg

from operations.ops_files import fileSlotsMixin
from operations.ops_view import viewSlotsMixin
from operations.ops_display import displaySlotsMixin
from operations.ops_modify import modifySlotsMixin
from operations.ops_select import renameableLeafNode

from foundation.changes import register_postinit_object
import foundation.preferences as preferences
import foundation.env as env
import foundation.undo_internals as undo_internals

from commandSequencer.CommandSequencer import CommandSequencer

from operations.ops_select import objectSelected
from operations.ops_select import ATOMS

from widgets.widget_helpers import TextMessageBox
from widgets.simple_dialogs import grab_text_line_using_dialog

from utilities.prefs_constants import qutemol_enabled_prefs_key
from utilities.prefs_constants import nanohive_enabled_prefs_key
from utilities.prefs_constants import povray_enabled_prefs_key
from utilities.prefs_constants import megapov_enabled_prefs_key
from utilities.prefs_constants import povdir_enabled_prefs_key
from utilities.prefs_constants import gamess_enabled_prefs_key
from utilities.prefs_constants import gromacs_enabled_prefs_key
from utilities.prefs_constants import cpp_enabled_prefs_key
from utilities.prefs_constants import rosetta_enabled_prefs_key
from utilities.prefs_constants import rosetta_database_enabled_prefs_key
from utilities.prefs_constants import nv1_enabled_prefs_key
from utilities.prefs_constants import workingDirectory_prefs_key
from utilities.prefs_constants import getDefaultWorkingDirectory
from utilities.prefs_constants import rememberWinPosSize_prefs_key
from utilities.prefs_constants import captionPrefix_prefs_key
from utilities.prefs_constants import captionSuffix_prefs_key
from utilities.prefs_constants import captionFullPath_prefs_key
from utilities.prefs_constants import displayRulers_prefs_key
from utilities.prefs_constants import mouseWheelDirection_prefs_key
from utilities.prefs_constants import zoomInAboutScreenCenter_prefs_key
from utilities.prefs_constants import zoomOutAboutScreenCenter_prefs_key

eCCBtab1 = [1,2, 5,6,7,8,9,10, 13,14,15,16,17,18, 32,33,34,35,36, 51,52,53,54]

eCCBtab2 = {}
for i, elno in zip(range(len(eCCBtab1)), eCCBtab1):
    eCCBtab2[elno] = i

# Debugging for "Open Recent Files" menu. Mark 2007-12-28
debug_recent_files = False  # Do not commit with True
recentfiles_use_QSettings = True # bruce 050919 debug flag

if debug_recent_files:
    def debug_fileList(fileList):
        print "BEGIN fileList"
        for x in fileList:
            print x
        print "END fileList"
else:
    def debug_fileList(fileList):
        pass


# #######################################################################

class MWsemantics(QMainWindow,
                  fileSlotsMixin,
                  viewSlotsMixin,
                  displaySlotsMixin,
                  modifySlotsMixin,
                  Ui_MainWindow,
                  object):
    """
    The single Main Window object.
    """
    #bruce 071008 added object superclass

    # bruce 050413 fileSlotsMixin needs to come before MainWindow in the list
    # of superclasses, since MainWindow overrides its methods with "NIM stubs".
    # mark 060120: same for viewSlotsMixin.

    initialised = 0 #bruce 041222
    _ok_to_autosave_geometry_changes = False #bruce 051218

    # The default font for the main window. If I try to set defaultFont using QApplition.font() here,
    # it returns Helvetica pt12 (?), so setting it below in the constructor is a workaround.
    # Mark 2007-05-27.
    defaultFont = None

    def __init__(self, parent = None, name = None):

        assert isinstance(self, object) #bruce 071008

        self._init_part_two_done = False
        self._activepw = None

        self.commandToolbar = None

        self.orientationWindow = None


        self._dnaSequenceEditor = None  #see self.createSequenceEditrIfNeeded
                                    #for details

        self._proteinSequenceEditor = None

        # These boolean flags, if True, stop the execution of slot
        # methods that are called because the state of 'self.viewFullScreenAction
        # or self.viewSemiFullScreenAction is changed. Maybe there is a way to
        # do this using QActionGroup (which make the actions mutually exclusive)
        #.. tried that but it didn't work. After doing this when I tried to
        #  toggle the checked action in the action group, it didn't work
        #..will try it again sometime in future. The following flags are good
        # enough for now. See methods self.showFullScreen and
        # self.showSemiFullScreen where they are used. -- Ninad 2007-12-06
        self._block_viewFullScreenAction_event = False
        self._block_viewSemiFullScreenAction_event = False

        # The following maintains a list of all widgets that are hidden during
        # the FullScreen or semiFullScreen mode. This list is then used in
        # self.showNormal to show the hidden widgets if any. The list is cleared
        # at the end of self.showNormal
        self._widgetToHideDuringFullScreenMode = []

        undo_internals.just_before_mainwindow_super_init()

        qt4warning('MainWindow.__init__(self, parent, name, Qt.WDestructiveClose) - what is destructive close?')
        QMainWindow.__init__(self, parent)

        self.defaultFont = QFont(self.font()) # Makes copy of app's default font.

        # Setup the NE1 graphical user interface.
        self.setupUi() # Ui_MainWindow.setupUi()

        undo_internals.just_after_mainwindow_super_init()

        # bruce 050104 moved this here so it can be used earlier
        # (it might need to be moved into main.py at some point)
        self.tmpFilePath = find_or_make_Nanorex_directory()


        # Load all NE1 custom cursors.
        from ne1_ui.cursors import loadCursors
        loadCursors(self)

        # Set the main window environment variable. This sets a single
        # global variable to self. All uses of it need review (and revision)
        # to add support for MDI. Mark 2008-01-02.
        env.setMainWindow(self)

        # Start NE1 with an empty document called "Untitled".
        # See also the _make_and_init_assy method in our mixin class in
        # ops_files.py, which creates and inits an assy using the same method.
        #
        # Note: It is very desirable to change this startup behavior so that
        # the user must select "File > New" to open an empty document after
        # NE1 starts. Mark 2007-12-30.
        self.assy = self._make_a_main_assy()

        #bruce 050429: as part of fixing bug 413, it's now required to call
        # self.assy.reset_changed() sometime in this method; it's called below.

        pw = Ui_PartWindow(self.assy, self) # note: calls glpane.setAssy inside GLPane.__init__; that calls _reinit_modes
        self.assy.set_glpane(pw.glpane) # sets assy.o and assy.glpane
        self.assy.set_modelTree(pw.modelTree) # sets assy.mt
        self._activepw = pw
        # Note: nothing in this class can set self._activepw (except to None),
        # which one might guess means that no code yet switches between partwindows,
        # but GLPane.makeCurrent *does* set self._activepw to its .partWindow
        # (initialized to its parent arg when it's created), so that conclusion is not clear.
        # [bruce 070503 comment]

        # Set the caption to the name of the current (default) part - Mark [2004-10-11]
        self.update_mainwindow_caption()

        # This is only used by the Atom Color preference dialog, not the
        # molecular modeling kit in Build Atom (deposit mode), etc.
        start_element = 6 # Carbon
        self.Element = start_element

        # Attr/list for Atom Selection Filter. mark 060401
        # These should become attrs of the assy. mark 2008-01-02.
        self.filtered_elements = [] # Holds list of elements to be selected when the Atom Selection Filter is enabled.
        self.filtered_elements.append(PeriodicTable.getElement(start_element)) # Carbon
        self.selection_filter_enabled = False # Set to True to enable the Atom Selection Filter.

        # Enables the QWorkspace widget which provides Multiple Document
        # Interface (MDI) support for NE1 and adds the Part Window to it.
        # If not enabled, just add the Part Window widget to the
        # centralAreaVBoxLayout only (the default).
        if debug_pref("Enable QWorkspace for MDI support? (next session)",
                      Choice_boolean_False,
                      ## non_debug = True,
                      #bruce 080416 hid this, since MDI is not yet implemented
                      # (this change didn't make it into .rc2)
                      prefs_key = "A10/Use QWorkspace"):

            print "QWorkspace for MDI support is enabled (experimental)"

            from PyQt4.Qt import QWorkspace
            self.workspace = QWorkspace()
            # Note: The QWorkspace class is deprecated in Qt 4.3 and instructs
            # developers to use the new QMdiArea class instead.
            # See: http://doc.trolltech.com/4.3/qmdiarea.html
            # Uncomment the two lines below when we've upgraded to Qt 4.3.
            # from PyQt4.Qt import QMdiArea
            # self.workspace = QMdiArea()
            self.centralAreaVBoxLayout.addWidget(self.workspace)
            self.workspace.addWindow(pw)
            pw.showMaximized()
        else:
            self.centralAreaVBoxLayout.addWidget(pw)

        if not self._init_part_two_done:
            # I bet there are pieces of _init_part_two that should be done EVERY time we bring up a
            # new partwindow.
            # [I guess that comment is by Will... for now, this code doesn't do those things
            #  more than once, it appears. [bruce 070503 comment]]
            MWsemantics._init_part_two(self)

        self.commandSequencer.start_using_initial_mode('$STARTUP_MODE')

        env.register_post_event_ui_updater( self.post_event_ui_updater) #bruce 070925
        #Urmi 20080716: initiliaze the Rosetta simulation parameters
        self.rosettaArgs = []
        return

    def _make_a_main_assy(self): #bruce 080813 split this out, revised
        """
        [private]

        Make a new main assy, meant for caller to store as self.assy.

        Called during __init__, and by _make_and_init_assy (in a mixin class)
        for fileClose and fileOpen.
        """
        res = Assembly(self,
                       "Untitled",
                       own_window_UI = True,
                       run_updaters = True,
                       commandSequencerClass = CommandSequencer
                      )
            #bruce 060127 added own_window_UI flag to help fix bug 1403;
            # it's required for this assy to support Undo.
        return res

    def _init_part_two(self):
        """
        #@ NEED DOCSTRING
        """
        # Create the NE1 Progress Dialog. mark 2007-12-06
        self.createProgressDialog()

        # Create the Preferences dialog widget.
        from ne1_ui.prefs.Preferences import Preferences
        self.userPrefs = Preferences(self.assy)

        # Enable/disable plugins.  These should be moved to a central method
        # where all plug-ins get added and enabled during invocation.  Mark 050921.
        self.userPrefs.enable_qutemol(env.prefs[qutemol_enabled_prefs_key])
        self.userPrefs.enable_nanohive(env.prefs[nanohive_enabled_prefs_key])
        self.userPrefs.enable_povray(env.prefs[povray_enabled_prefs_key])
        self.userPrefs.enable_megapov(env.prefs[megapov_enabled_prefs_key])
        self.userPrefs.enable_povdir(env.prefs[povdir_enabled_prefs_key])
        self.userPrefs.enable_gamess(env.prefs[gamess_enabled_prefs_key])
        self.userPrefs.enable_gromacs(env.prefs[gromacs_enabled_prefs_key])
        self.userPrefs.enable_cpp(env.prefs[cpp_enabled_prefs_key])
        self.userPrefs.enable_rosetta(env.prefs[rosetta_enabled_prefs_key])
        self.userPrefs.enable_rosetta_db(env.prefs[rosetta_database_enabled_prefs_key])
        self.userPrefs.enable_nv1(env.prefs[nv1_enabled_prefs_key])

        #Mouse wheel behavior settings.
        self.updateMouseWheelSettings()

        # Create the Help dialog. Mark 050812
        from ne1_ui.help.help import Ne1HelpDialog
        self.help = Ne1HelpDialog()


        from commands.PovraySceneProperties.PovraySceneProp import PovraySceneProp
        self.povrayscenecntl = PovraySceneProp(self)

        from commands.CommentProperties.CommentProp import CommentProp
        self.commentcntl = CommentProp(self)

        # Minimize Energy dialog. Mark 060705.
        from commands.MinimizeEnergy.MinimizeEnergyProp import MinimizeEnergyProp
        self.minimize_energy = MinimizeEnergyProp(self)

        # Atom Generator example for developers. Mark and Jeff. 2007-06-13
        from commands.BuildAtom.AtomGenerator import AtomGenerator
        self.atomcntl = AtomGenerator(self)

        # We must enable keyboard focus for a widget if it processes
        # keyboard events. [Note added by bruce 041223: I don't know if this is
        # needed for this window; it's needed for some subwidgets, incl. glpane,
        # and done in their own code. This window forwards its own key events to
        # the glpane. This doesn't prevent other subwidgets from having focus.]
        self.setFocusPolicy(QtCore.Qt.StrongFocus)

        # 'depositState' is used by BuildAtoms_Command
        #to determine what type of object (atom, clipboard chunk or library part)
        # to deposit when pressing the left mouse button in Build mode.
        #
        # depositState can be either:
        #   'Atoms' - deposit an atom based on the current atom type selected in the MMKit 'Atoms'
        #           page or dashboard atom type combobox(es).
        #   'Clipboard' - deposit a chunk from the clipboard based on what is currently selected in
        #           the MMKit 'Clipboard' page or dashboard clipboard/paste combobox.
        #   'Library' - deposit a part from the library based on what is currently selected in the
        #           MMKit 'Library' page.  There is no dashboard option for this.
        self.depositState = 'Atoms'

        self.assy.reset_changed() #bruce 050429, part of fixing bug 413

        # 'movie_is_playing' is a flag that indicates a movie is playing. It is
        # used by other code to speed up rendering times by optionally disabling
        # the (re)building of display lists for each frame of the movie.
        self.movie_is_playing = False

        # Current Working Directory (CWD).
        # When NE1 starts, the CWD is set to the Working Directory (WD)
        # preference from the user prefs db. Every time the user opens or
        # inserts a file during a session, the CWD changes to the directory
        # containing that file. When the user closes the current file and then
        # attempts to open a new file, the CWD will still be the directory of
        # the last file opened or inserted.
        # If the user changes the WD via 'File > Set Working Directory' when
        # a file is open, the CWD will not be changed to the new WD. (This
        # rule may change. Need to discuss with Ninad).
        # On the other hand, if there is no part open, the CWD will be
        # changed to the new WD.  Mark 060729.
        self.currentWorkingDirectory = ''

        # Make sure the working directory from the user prefs db exists since
        # it might have been deleted.
        if os.path.isdir(env.prefs[workingDirectory_prefs_key]):
            self.currentWorkingDirectory = env.prefs[workingDirectory_prefs_key]
        else:
            # The CWD does not exist, so set it to the default working dir.
            self.currentWorkingDirectory = getDefaultWorkingDirectory()

        # bruce 050810 replaced user preference initialization with this,
        # and revised update_mainwindow_caption to match
        from foundation.changes import Formula
        self._caption_formula = Formula(
            # this should depend on whatever update_mainwindow_caption_properly depends on;
            # but it can't yet depend on assy.has_changed(),
            # so that calls update_mainwindow_caption_properly (or the equiv) directly.
            lambda: (env.prefs[captionPrefix_prefs_key],
                     env.prefs[captionSuffix_prefs_key],
                     env.prefs[captionFullPath_prefs_key]),
            self.update_mainwindow_caption_properly
        )

        # Setting 'initialized' to 1 enables win_update().
        # [should this be moved into _init_after_geometry_is_set??
        # bruce 060104 question]
        self.initialised = 1

        # be told to add new Jigs menu items, now or as they become available [bruce 050504]
        register_postinit_object( "Jigs menu items", self )

        # Anything which depends on this window's geometry (which is not yet set at this point)
        # should be done in the _init_after_geometry_is_set method below, not here. [bruce guess 060104]

        self._init_part_two_done = True
        return # from _init_part_two

    def updateMouseWheelSettings(self):
        """
        Updates important mouse wheel attrs kept in self, including:
        - Mouse direction
        - Zoom in point
        - Zoom out point
        @note: These are typically set from the Preferences dialog.
        """
        if env.prefs[mouseWheelDirection_prefs_key] == 0:
            self.mouseWheelDirection = 1
        else:
            self.mouseWheelDirection = -1

        self.mouseWheelZoomInPoint  = env.prefs[zoomInAboutScreenCenter_prefs_key]
        self.mouseWheelZoomOutPoint = env.prefs[zoomOutAboutScreenCenter_prefs_key]

    def _get_commandSequencer(self): #bruce 080813 revised
        res = self.assy.commandSequencer
        assert res
        return res

    commandSequencer = property(_get_commandSequencer)

    def _get_currentCommand(self):
        return self.commandSequencer.currentCommand

    currentCommand = property(_get_currentCommand)

    def post_event_ui_updater(self): #bruce 070925
        self.currentCommand.command_post_event_ui_updater()
        return

    def createPopupMenu(self): # Ninad 070328
        """
        Returns a popup menu containing checkable entries for the toolbars
        and dock widgets present in the main window.

        This function is called by the main window every time the user
        activates a context menu, typically by right-clicking on a toolbar or
        a dock widget.

        This reimplements QMainWindow's createPopupMenu() method.

        @return: The popup menu.
        @rtype: U{B{QMenu}<http://doc.trolltech.com/4/qmenu.html>}

        @note: All main window toolbars must be created before calling
        createPopupMenu().

        @see: U{B{QMainWindow.createPopupMenu}
        <http://doc.trolltech.com/4.3/qmainwindow.html#createPopupMenu>}
        """
        menu = QMenu(self)

        contextMenuToolBars = \
                            [self.standardToolBar, self.viewToolBar,
                             self.standardViewsToolBar, self.displayStylesToolBar,
                             self.simulationToolBar, self.buildToolsToolBar,
                             self.selectToolBar, self.buildStructuresToolBar,
                             self.renderingToolBar]

        for toolbar in contextMenuToolBars:
            menu.addAction(toolbar.toggleViewAction())

        return menu


    def showFullScreen(self):
        """
        Full screen mode. (maximize the glpane real estate by hiding/ collapsing
        other widgets. (only Menu bar and the glpane are shown)
        The widgets hidden or collapsed include:
         - MainWindow Title bar
         - Command Manager,
         - All toolbars,
         - ModelTree/PM area,
         - History Widget,
         - Statusbar

        @param val: The state of the QAction (checked or uncheced) If True, it
                    will show the main window full screen , otherwise show it
                    with its regular size
        @type val: boolean
        @see: self.showSemiFullScreen, self.showNormal
        @see: ops_view.viewSlotsMixin.setViewFullScreen
        """

        if self._block_viewFullScreenAction_event:
            #see self.__init__ for a detailed comment about this instance
            #variable
            return

        self._block_viewFullScreenAction_event = False

        if self.viewSemiFullScreenAction.isChecked():
            self._block_viewSemiFullScreenAction_event = True
            self.viewSemiFullScreenAction.setChecked(False)
            self._block_viewSemiFullScreenAction_event = False

        self._showFullScreenCommonCode()
        for  widget in self.children():
            if isinstance(widget, QToolBar):
                if widget.isVisible():
                    widget.hide()
                    self._widgetToHideDuringFullScreenMode.append(widget)

        self.commandToolbar.hide()

    def _showFullScreenCommonCode(self, hideLeftArea = True):
        """
        The common code for making the Mainwindow full screen (maximimzing the
        3D workspace area) This is used by both, View > Full Screen and
        View > Semi-Full Screen
        @see: self.showFullScreen
        @see: self._showSemiFullScreen
        """
        #see self.__init__ for a detailed comment about this list
        self._widgetToHideDuringFullScreenMode = []
        QMainWindow.showFullScreen(self)
        for  widget in self.children():
            if isinstance(widget, QStatusBar):
                if widget.isVisible():
                    widget.hide()
                    self._widgetToHideDuringFullScreenMode.append(widget)

        self.activePartWindow().collapseLeftArea(hideLeftArea)
        self.reportsDockWidget.hide()

    def showSemiFullScreen(self):
        """
        Semi-Full Screen mode. (maximize the glpane real estate by hiding/ collapsing
        other widgets. This is different than the 'Full Screen mode' as it hides
        or collapses only the following widgets --
         - MainWindow Title bar and border
         - Report Widget
         - Statusbar

        @param val: The state of the QAction (checked or uncheced) If True, it
                    will show the main window full screen , otherwise show it
                    with its regular size
        @type val: boolean
        @see: self.showFullScreen, self.showNormal
        @see: ops_view.viewSlotsMixin.setViewSemiFullScreen
        """
        if self._block_viewSemiFullScreenAction_event:
            #see self.__init__ for a detailed comment about this instance
            #variable
            return

        self._block_viewSemiFullScreenAction_event = False

        if self.viewFullScreenAction.isChecked():
            self._block_viewFullScreenAction_event = True
            self.viewFullScreenAction.setChecked(False)
            self._block_viewFullScreenAction_event = False

        self._showFullScreenCommonCode(hideLeftArea = False)

    def showNormal(self):
        QMainWindow.showNormal(self)
        self.activePartWindow().expandLeftArea()

        # Note: This will show the reports dock widget even if the user
        # dismissed it earlier in his session. This is OK, they can just
        # dismiss it again if they don't want it. If users complain, this
        # will be easy to fix by overriding its hide() and show() methods
        # (I suggest adding a keyword arg to hide() called "breifly", set
        # to False by default, which sets a flag attr that show() can check.
        # Mark 2008-01-05.
        self.reportsDockWidget.show()

        for  widget in self._widgetToHideDuringFullScreenMode:
            widget.show()

        self.commandToolbar.show()
        #Clear the list of hidden widgets (those are no more hidden)
        self._widgetToHideDuringFullScreenMode = []

    def activePartWindow(self): # WARNING: this is inlined in a few methods of self
        return self._activepw

    def get_glpane(self): #bruce 071008; inlines self.activePartWindow
        return self._activepw.glpane

    glpane = property(get_glpane) #bruce 071008 to replace __getattr__

    def get_mt(self): #bruce 071008; inlines self.activePartWindow # TODO: rename .mt to .modelTree
        return self._activepw.modelTree

    mt = property(get_mt) #bruce 071008 to replace __getattr__

    def closeEvent(self, ce):
        fileSlotsMixin.closeEvent(self, ce)

    def sponsoredList(self):
        return (
                self.povrayscenecntl,
                self.minimize_energy)

    def _init_after_geometry_is_set(self): #bruce 060104 renamed this from startRun and replaced its docstring.
        """
        Do whatever initialization of self needs to wait until its geometry has been set.
        [Should be called only once, after geometry is set; can be called before self is shown.
         As of 070531, this is called directly from main.py, after our __init__ but before we're first shown.]
        """
        # older docstring:
        # After the main window(its size and location) has been setup, begin to run the program from this method.
        # [Huaicai 11/1/05: try to fix the initial MMKitWin off screen problem by splitting from the __init__() method]

        self.win_update() # bruce 041222
        undo_internals.just_before_mainwindow_init_returns() # (this is now misnamed, now that it's not part of __init__)
        return

    __did_cleanUpBeforeExiting = False #bruce 070618

    def cleanUpBeforeExiting(self): #bruce 060127 added this re bug 1412 (Python crashes on exit, newly common)
        """
        NE1 is going to exit. (The user has already been given the chance to save current files
        if they are modified, and (whether or not they were saved) has approved the exit.)

        Perform whatever internal side effects are desirable to make the exit safe and efficient,
        and/or to implement features which save other info (e.g. preferences) upon exiting.

        This should be safe to call more than once, even though doing so is a bug.
        """

        # We do most things in their own try/except clauses, so if they fail,
        # we'll still do the other actions [bruce 070618 change].
        # But we always print something if they fail.

        if self.__did_cleanUpBeforeExiting:
            # This makes sure it's safe to call this method more than once.
            # (By itself, this fixes the exception in bug 2444 but not the double dialogs from it.
            #  The real fix for bug 2444 is elsewhere, and means this is no longer called more than once,
            #  but I'll leave this in for robustness.) [bruce 070618]
            return

        self.__did_cleanUpBeforeExiting = True

        msg = "exception (ignored) in cleanUpBeforeExiting: "

        try:
            # wware 060406 bug 1263 - signal the simulator that we are exiting
            # (bruce 070618 moved this here from 3 places in prepareToCloseAndExit.)
            from simulation.runSim import SimRunner
            SimRunner.PREPARE_TO_CLOSE = True
        except:
            print_compact_traceback( msg )

        try:
            env.history.message(greenmsg("Exiting program."))
        except:
            print_compact_traceback( msg )

        try:
            if env.prefs[rememberWinPosSize_prefs_key]: # Fixes bug 1249-2. Mark 060518.
                self.userPrefs.save_current_win_pos_and_size()
        except:
            print_compact_traceback( msg )

        ## self._make_and_init_assy() # (this seems to take too long, and is probably not needed)

        try:
            self.deleteOrientationWindow() # ninad 061121- perhaps it's unnecessary
        except:
            print_compact_traceback( msg )

        try:
            if self.assy:
                self.assy.close_assy()
                    # note: we won't also call
                    # self.assy.commandSequencer.exit_all_commands
                    # (with or without warn_about_abandoned_changes = False).
                    # Ideally we might sometimes like that warning here,
                    # but it's better to never have it than to risk exit bugs
                    # (in case it's too late to give it),
                    # or to bother users with duplicate warnings (if they
                    # already said to discard ordinary unsaved changes,
                    # different than the ones that warns about) which we
                    # don't presently have enough info to avoid giving.
                    # As for the exits themselves, they are not needed,
                    # and might be too slow and/or risk exit bugs.
                    # [bruce 080909 comment]
                self.assy.deinit()
                    # in particular, stop trying to update Undo/Redo actions
                    # all the time (which might cause crashes once their
                    # associated widgets are deallocated)
        except:
            print_compact_traceback( msg )

        return

    def postinit_item(self, item): #bruce 050504
        try:
            item(self)
        except:
            # blame item
            print_compact_traceback( "exception (ignored) in postinit_item(%r): " % item )
        return

    def win_update(self):
        """
        Update most state which directly affects the GUI display,
        in some cases repainting it directly.
        (Someday this should update all of it, but only what's needed,
        and perhaps also call QWidget.update. #e)
        [no longer named update, since that conflicts with QWidget.update]
        """
        if not self.initialised:
            return

        pw = self.activePartWindow()
        pw.glpane.gl_update()
        pw.modelTree.mt_update()
        self.reportsDockWidget.history_object.h_update()
            # this is self.reportsDockWidget.history_object, not env.history,
            # since it's really about this window's widget-owner,
            # not about the place to print history messages [bruce 050913]
        return

    ###################################
    # File Toolbar Slots
    ###################################

    # file toolbar slots are inherited from fileSlotsMixin (in ops_files.py) as of bruce 050907.
    # Notes:
    #   #e closeEvent method (moved to fileSlotsMixin) should be split in two
    # and the outer part moved back into this file.
    #   _make_and_init_assy method was moved to fileSlotsMixin (as it should be)


    ###################################
    # Edit Toolbar Slots
    ###################################

    def editMakeCheckpoint(self):
        """
        Slot for making a checkpoint (only available when Automatic
        Checkpointing is disabled).
        """
        import operations.undo_UI as undo_UI
        undo_UI.editMakeCheckpoint(self)
        return

    def editUndo(self):
        self.assy.editUndo()

    def editRedo(self):
        self.assy.editRedo()

    def editAutoCheckpointing(self, enabled):
        """
        Slot for enabling/disabling automatic checkpointing.
        """
        import foundation.undo_manager as undo_manager
        undo_manager.editAutoCheckpointing(self, enabled)
            # note: see code comment there, for why that's not in undo_UI.
            # note: that will probably do this (among other things):
            #   self.editMakeCheckpointAction.setVisible(not enabled)
        return

    def editClearUndoStack(self):
        """
        Slot for clearing the Undo Stack. Requires the user to confirm.
        """
        import operations.undo_UI as undo_UI
        undo_UI.editClearUndoStack(self)
        return

    # bruce 050131 moved some history messages from the following methods
    # into the assy methods they call, so the menu command versions also
    # have them

    def editCut(self):
        self.assy.cut_sel()
        self.win_update()

    def editCopy(self):
        self.assy.copy_sel()
        self.win_update()

    def editPaste(self):
        """
        Single shot paste operation accessible using 'Ctrl + V' or Edit > Paste.
        Implementation notes for the single shot paste operation:
          - The object (chunk or group) is pasted with a slight offset.
            Example:
            Create a graphene sheet, select it , do Ctrl + C and then Ctrl + V
            ... the pasted object is offset to original one.
          - It deselects others, selects the pasted item and then does a zoom to
            selection so that the selected item is in the center of the screen.
          - Bugs/ Unsupported feature: If you paste multiple copies of an object
            they are pasted at the same location. (i.e. the offset is constant)

        @see: L{ops_copy_Mixin.paste}
        """
        if self.assy.shelf.members:
            pastables = self.assy.shelf.getPastables()
            if not pastables:
                msg = orangemsg("Nothing to paste.")
                env.history.message(msg)
                return

            recentPastable = pastables[-1]
            self.assy.paste(recentPastable)
        else:
            msg = orangemsg("Nothing to paste.")
            env.history.message(msg)
        return

    def editPasteFromClipboard(self):
        """
        Invokes the L{PasteFromClipboard_Command}, a temporary command to paste items in the
        clipboard, into the 3D workspace. It also stores the command NE1 should
        return to after exiting this temporary command.
        """
        if self.assy.shelf.members:
            pastables = self.assy.shelf.getPastables()
            if not pastables:
                msg = orangemsg("Nothing to paste. Paste Command cancelled.")
                env.history.message(msg)
                return

            #pre-commandstack refactoring/cleanup comment:
            #Make 'paste' as a general command to fix this bug: Enter Dna command
            #, invoke paste command, exit paste, enter Dna again -- the flyout
            #toolbar for dna is not visible . This whole thing will get revised
            #after the command stack cleanup (to be coded soon)
            # -- Ninad 2008-07-29
            self.commandSequencer.userEnterCommand('PASTE')
        else:
            msg = orangemsg("Clipboard is empty. Paste Command cancelled.")
            env.history.message(msg)
        return

    def insertPartFromPartLib(self):
        """
        Sets the current command to L{PartLibrary_Command}, for inserting (pasting)
        a part from the partlib into the 3D workspace. It also stores the command
        NE1 should return to after exiting this temporary command.
        """
        self.commandSequencer.userEnterCommand('PARTLIB') #bruce 071011 guess ### REVIEW
        return

    # TODO: rename killDo to editDelete
    def killDo(self):
        """
        Deletes selected atoms, chunks, jigs and groups.
        """
        self.assy.delete_sel()
        ##bruce 050427 moved win_update into delete_sel as part of fixing bug 566
        ##self.win_update()

    def resizeSelectedDnaSegments(self):
        """
        Invokes the MultipleDnaSegmentResize_EditCommand to resize the
        selected segments.
        @see: chunk.make_glpane_cmenu_items (which makes a context menu
              which has an item which can call this method)
        """
        #TODO: need more ui options to invoke this command.
        selectedSegments = self.assy.getSelectedDnaSegments()
        if len(selectedSegments) > 0:
            commandSequencer = self.commandSequencer
            commandSequencer.userEnterCommand('MULTIPLE_DNA_SEGMENT_RESIZE')
            assert commandSequencer.currentCommand.commandName == 'MULTIPLE_DNA_SEGMENT_RESIZE'
            commandSequencer.currentCommand.editStructure(list(selectedSegments))
        return

    def editAddSuffix(self):
        """
        Adds a suffix to the name(s) of the selected objects.
        """
        # Don't allow renaming while animating (b/w views).
        if self.glpane.is_animating:
            return

        _cmd = greenmsg("Add Suffix: ")

        if not objectSelected(self.assy):
            if objectSelected(self.assy, objectFlags = ATOMS):
                _msg = redmsg("Cannot rename atoms.")
            else:
                _msg = redmsg("Nothing selected.")
            env.history.message(_cmd + _msg)
            return

        _renameList = self.assy.getSelectedRenameables()

        ok, new_name = grab_text_line_using_dialog(
            title = "Add Suffixes",
            label = "Suffix to add to selected nodes:",
            iconPath = "ui/actions/Edit/Add_Suffixes.png")

        if not ok:
            return

        _number_renamed = 0
        for _object in _renameList:
            if _object.rename_enabled():
                _new_name = _object.name + new_name
                print "new name = ", _new_name
                ok, info = _object.try_rename(_new_name)
                if ok:
                    _number_renamed += 1

        _msg = "%d of %d selected objects renamed." \
             % (_number_renamed, len(_renameList))
        env.history.message(_cmd + _msg)

    def editRenameSelection(self): # probably by Mark
        """
        Renames multiple selected objects (chunks or jigs).
        """
        # Don't allow renaming while animating (b/w views).
        if self.glpane.is_animating:
            return

        _cmd = greenmsg("Rename: ")

        if not objectSelected(self.assy):
            if objectSelected(self.assy, objectFlags = ATOMS):
                _msg = redmsg("Cannot rename atoms.")
            else:
                _msg = redmsg("Nothing selected.")
            env.history.message(_cmd + _msg)
            return

        _renameList = self.assy.getSelectedRenameables()

        ok, new_name = grab_text_line_using_dialog(
            title = "Rename",
            label = "New name:",
            iconPath = "ui/actions/Edit/Rename.png")

        if not ok:
            # No msg. Ok for now. --Mark
            return

        # Renumber the selected objects if the last character is "#"
        # i.e. the # character will be replaced by a number, resulting in
        # uniquely named (numbered) nodes in the model tree.
        # IIRC, the numbering is not guaranteed to be in any specific order,
        # but testing has shown that leaf nodes are numbered in the order
        # they appear in the model tree. --Mark 2008-11-12

        # REVIEW: I would guess that getSelectedRenameables is guaranteed
        # to return nodes in model tree order. If analysis of its code
        # confirms that, then its docstring should be made to say that.
        # [bruce 090115 comment]

        if new_name[-1] == "#":
            _renumber = True
            new_name = new_name[:-1]
        else:
            _renumber = False

        _number_renamed = 0
        for _object in _renameList:
            if renameableLeafNode(_object):
                # REVIEW: should renameableLeafNode be tested by getSelectedRenameables?
                # [bruce 081124 question]
                if _renumber:
                    ok, info = _object.try_rename(new_name + str(_number_renamed + 1))
                else:
                    ok, info = _object.try_rename(new_name)
                if ok:
                    _number_renamed += 1

        _msg = "%d of %d selected objects renamed." \
             % (_number_renamed, len(_renameList))
        env.history.message(_cmd + _msg)
        return

    def renameObject(self, object):
        """
        Prompts the user to rename I{object}, which can be any renameable node.

        @param object: The object to be renamed.
        @type  object: Node

        @return: A descriptive message about what happened.
        @rtype:  string
        """
        # Don't allow renaming while animating (b/w views).
        if self.glpane.is_animating:
            return

        # Note: see similar code in rename_node_using_dialog in another class.
        oldname = object.name
        ok = object.rename_enabled()
        # Various things below can set ok to False (if it's not already)
        # and set text to the reason renaming is not ok (for use in error messages).
        # Or they can put the new name in text, leave ok True, and do the renaming.
        if not ok:
            text = "Renaming this object is not permitted."
                #e someday we might want to call try_rename on fake text
                # to get a more specific error message... for now it doesn't have one.
        else:
            ok, text = grab_text_line_using_dialog(
                title = "Rename",
                label = "new name for [%s]:" % oldname,
                iconPath = "ui/actions/Edit/Rename.png",
                default = oldname )
        if ok:
            ok, text = object.try_rename(text)
        if ok:
            msg = "Renamed [%s] to [%s]" % (oldname, text)
            self.mt.mt_update()
        else:
            msg = "Can't rename [%s]: %s" % (oldname, text) # text is reason why not
        return msg

    def editRename(self):
        """
        Renames the selected node/object.

        @note: Does not work for DnaStrands or DnaSegments.
        @deprecated: Use editRenameSelection instead.
        """
        _cmd = greenmsg("Rename: ")

        if not objectSelected(self.assy):
            if objectSelected(self.assy, objectFlags = ATOMS):
                _msg = redmsg("Cannot rename atoms.")
            else:
                _msg = redmsg("Nothing selected.")
            env.history.message(_cmd + _msg)
            return

        _renameableList = self.assy.getSelectedRenameables()
        _numSelectedObjects = len(_renameableList)
        # _numSelectedObjects is > 1 if the user selected a single DnaStrand
        # or DnaSegment, since they contain chunk nodes. This is a bug
        # that I will discuss with Bruce. --Mark 2008-03-14

        if _numSelectedObjects == 0:
            _msg = "Renaming this object is not permitted."
        elif _numSelectedObjects == 1:
            _msg = self.renameObject(_renameableList[0])
        else:
            _msg = redmsg("Only one object can be selected.")
        env.history.message(_cmd + _msg)

    def editPrefs(self):
        """
        Edit Preferences
        """
        self.userPrefs.show()

    ###################################
    # View Toolbar Slots
    ###################################

    # View toolbar slots are inherited from viewSlotsMixin
    # (in ops_view.py) as of 2006-01-20. Mark

    ###################################
    # Display Toolbar Slots
    ###################################

    # Display toolbar slots are inherited from displaySlotsMixin
    # (in ops_display.py) as of 2008-020-02. Mark

    ###############################################################
    # Select Toolbar Slots
    ###############################################################

    def selectAll(self):
        """
        Select all parts if nothing selected.
        If some parts are selected, select all atoms in those parts.
        If some atoms are selected, select all atoms in the parts
        in which some atoms are selected.
        """
        env.history.message(greenmsg("Select All:"))
        self.assy.selectAll()

    def selectNone(self):
        env.history.message(greenmsg("Select None:"))
        self.assy.selectNone()

    def selectInvert(self):
        """
        If some parts are selected, select the other parts instead.
        If some atoms are selected, select the other atoms instead
        (even in chunks with no atoms selected, which end up with
        all atoms selected). (And unselect all currently selected
        parts or atoms.)
        """
        #env.history.message(greenmsg("Invert Selection:"))
        # assy method revised by bruce 041217 after discussion with Josh
        self.assy.selectInvert()

    def selectConnected(self):
        """
        Select any atom that can be reached from any currently
        selected atom through a sequence of bonds.
        """
        self.assy.selectConnected()

    def selectDoubly(self):
        """
        Select any atom that can be reached from any currently
        selected atom through two or more non-overlapping sequences of
        bonds. Also select atoms that are connected to this group by
        one bond and have no other bonds.
        """
        self.assy.selectDoubly()

    def selectExpand(self):
        """
        Slot for Expand Selection, which selects any atom that is bonded
        to any currently selected atom.
        """
        self.assy.selectExpand()

    def selectContract(self):
        """
        Slot for Contract Selection, which unselects any atom which has
        a bond to an unselected atom, or which has any open bonds.
        """
        self.assy.selectContract()

    def selectLock(self, lockState):
        """
        Slot for Lock Selection, which locks/unlocks selection.
        @param lockState: The new selection lock state, either locked (True)
                          or unlocked (False).
        @type  lockState: boolean
        """
        self.assy.lockSelection(lockState)



    ###################################
    # Jig Toolbar Slots
    ###################################

    def makeGamess(self):
        self.assy.makegamess()

    def makeAnchor(self): # Changed name from makeGround. Mark 051104.
        self.assy.makeAnchor()

    def makeStat(self):
        self.assy.makestat()

    def makeThermo(self):
        self.assy.makethermo()

    def makeRotaryMotor(self):
        self.assy.makeRotaryMotor()

    def makeLinearMotor(self):
        self.assy.makeLinearMotor()

    def createPlane(self):
        commandSequencer = self.commandSequencer
        commandSequencer.userEnterCommand('REFERENCE_PLANE')
        commandSequencer.currentCommand.runCommand()

    def makeGridPlane(self):
        self.assy.makeGridPlane()

    def createPolyLine(self):
        pass
        if 0: #NIY
            self.assy.createPolyLine()

    def makeESPImage(self):
        self.assy.makeESPImage()

    def makeAtomSet(self):
        self.assy.makeAtomSet()

    def makeMeasureDistance(self):
        self.assy.makeMeasureDistance()

    def makeMeasureAngle(self):
        self.assy.makeMeasureAngle()

    def makeMeasureDihedral(self):
        self.assy.makeMeasureDihedral()


    ###################################
    # Modify Toolbar Slots
    ###################################

    # Modify toolbar slots are inherited from modifySlotsMixin
    # (in ops_display.py) as of 2008-020-02. Mark

    ###################################
    # Help Toolbar Slots
    ###################################

    def helpTutorials(self):
        from foundation.wiki_help import open_wiki_help_URL
        url = "http://www.nanoengineer-1.net/mediawiki/index.php?title=Tutorials"
        worked = open_wiki_help_URL(url)
        return

    def helpMouseControls(self):
        self.help.showDialog(0)
        return

    def helpKeyboardShortcuts(self):
        self.help.showDialog(1)
        return

    def helpSelectionShortcuts(self):
        self.help.showDialog(2)
        return

    def helpGraphicsCard(self):
        """
        Display details about the system\'s graphics card in a dialog.
        """
        ginfo = get_gl_info_string( self.glpane) #bruce 070308 added glpane arg

        msgbox = TextMessageBox(self)
        msgbox.setWindowTitle("Graphics Card Info")
        msgbox.setText(ginfo)
        msgbox.show()
        return

# I modified a copy of cpuinfo.py from
# http://cvs.sourceforge.net/viewcvs.py/numpy/Numeric3/scipy/distutils/
# thinking it might help us support users better if we had a built-in utility
# for interrogating the CPU.  I do not plan to commit cpuinfo.py until I speak
# to Bruce about this. Mark 051209.
#
#    def helpCpuInfo(self):
#        """
#        Displays this system's CPU information.
#        """
#        from cpuinfo import get_cpuinfo
#        cpuinfo = get_cpuinfo()
#
#        from widgets import TextMessageBox
#        msgbox = TextMessageBox(self)
#        msgbox.setCaption("CPU Info")
#        msgbox.setText(cpuinfo)
#        msgbox.show()

    def helpAbout(self):
        """
        Displays information about this version of NanoEngineer-1.
        """
        from utilities.version import Version
        v = Version()
        product = v.product
        versionString = "Version " + repr(v)
        if v.releaseCandidate:
            versionString += ("_RC%d" % v.releaseCandidate)
        if v.releaseType:
            versionString += (" (%s)" % v.releaseType)
        date = "Release Date: " + v.releaseDate
        filePath = os.path.dirname(os.path.abspath(sys.argv[0]))
        if filePath.endswith('/Contents/Resources'):
            filePath = filePath[:-19]
        installdir = "Running from: " + filePath
        techsupport = "For technical support, send email to support@nanorex.com"
        website = "Website: www.nanoengineer-1.com"
        wiki = "Wiki and Tutorials: www.nanoengineer-1.net"
        aboutstr = product + " " + versionString \
                 + "\n\n" \
                 + date \
                 + "\n\n" \
                 + installdir \
                 + "\n\n" \
                 + v.copyright \
                 + "\n\n" \
                 + techsupport \
                 + "\n" \
                 + website \
                 + "\n" \
                 + wiki

        QMessageBox.about ( self, "About NanoEngineer-1", aboutstr)
        return

    def helpWhatsThis(self):
        from PyQt4.Qt import QWhatsThis ##bruce 050408
        QWhatsThis.enterWhatsThisMode()
        return

    ###################################
    # Modes Toolbar Slots
    ###################################

    # get into Select Atoms mode
    def toolsSelectAtoms(self): # note: this can NO LONGER be called from update_select_mode [as of bruce 060403]
        self.commandSequencer.userEnterCommand('SELECTATOMS', always_update = True)

    # get into Select Chunks mode
    def toolsSelectMolecules(self):# note: this can also be called from update_select_mode [bruce 060403 comment]
        self.commandSequencer.userEnterCommand('SELECTMOLS', always_update = True)

    def update_select_mode(self):
        """
        change currentCommand or assy.selwhat or selection to make them consistent
        """
        #bruce 081216 moved this here, from a method on self.mt
        # (where it made no sense)
        from operations.update_select_mode import update_select_mode # todo: move this to toplevel
        update_select_mode(self)

    # get into Move Chunks (or Translate Components) command
    def toolsMoveMolecule(self):
        self.ensureInCommand('MODIFY')
        self.commandSequencer.currentCommand.propMgr.activate_translateGroupBox()
        return

    # Rotate Components command
    def toolsRotateComponents(self):
        self.ensureInCommand('MODIFY')
        self.commandSequencer.currentCommand.propMgr.activate_rotateGroupBox()
        return

    # get into Build mode
    def toolsBuildAtoms(self): # note: this can now be called from update_select_mode [as of bruce 060403]
        self.depositState = 'Atoms'
        self.commandSequencer.userEnterCommand('DEPOSIT', always_update = True)

    # get into cookiecutter mode
    def enterBuildCrystalCommand(self):
        self.commandSequencer.userEnterCommand('CRYSTAL', always_update = True)

    # get into Extrude mode
    def toolsExtrude(self):
        self.commandSequencer.userEnterCommand('EXTRUDE', always_update = True)

    # get into Fuse Chunks mode
    def toolsFuseChunks(self):
        self.commandSequencer.userEnterCommand('FUSECHUNKS', always_update = True)

    ###################################
    # Simulator Toolbar Slots
    ###################################

    def simMinimizeEnergy(self):
        """
        Opens the Minimize Energy dialog.
        """
        self.minimize_energy.setup()

    def simSetup(self):
        """
        Creates a movie of a molecular dynamics simulation.
        """
        if debug_flags.atom_debug: #bruce 060106 added this (fixing trivial bug 1260)
            print "atom_debug: reloading sim_commandruns on each use, for development"
            import simulation.sim_commandruns as sim_commandruns
            reload(sim_commandruns)
        from simulation.sim_commandruns import simSetup_CommandRun
        cmdrun = simSetup_CommandRun( self)
        cmdrun.run()
        return

    #Urmi 20080725: Methods for running Rosetta Simulation
    def rosettaSetup(self):
        """
        Setup rosetta simulation.
        """
        from simulation.ROSETTA.RosettaSimulationPopUpDialog import RosettaSimulationPopUpDialog
        form = RosettaSimulationPopUpDialog(self)
        self.connect(form, SIGNAL('editingFinished()'), self.runRosetta)

        return

    def runRosetta(self):
        """
        Run a Rosetta simulation.
        """
        from simulation.ROSETTA.rosetta_commandruns import rosettaSetup_CommandRun
        if self.rosettaArgs[0] > 0:
            cmdrun = rosettaSetup_CommandRun(self, self.rosettaArgs, "ROSETTA_FIXED_BACKBONE_SEQUENCE_DESIGN")
            cmdrun.run()

        return

    def setRosettaParameters(self, numRuns, otherOptionsText):
        """
        Set parameters for a Rosetta .
        @param numRuns: number of Rosetta simulations.
        @type numRuns: int
        @param otherOptionsText: string of all the other options, including the
                                ones in Rosett pop up dialog.
        @type otherOptionsText: str
        """
        protein = ""
        if self.commandSequencer.currentCommand.commandName == 'BUILD_PROTEIN' or \
           self.commandSequencer.currentCommand.commandName == 'EDIT_PROTEIN' or \
           self.commandSequencer.currentCommand.commandName == 'EDIT_RESIDUES':
            protein = self.commandSequencer.currentCommand.propMgr.current_protein

        #run Rosetta for the first selected protein
        if protein == "" and len(self.assy.selmols) >= 1:
            for chunk in self.assy.selmols:
                if chunk.isProteinChunk():
                    protein = chunk.name
                    break

        argList = [numRuns, otherOptionsText, protein]
        self.rosettaArgs = []
        self.rosettaArgs.extend(argList)
        return

    #end of Rosetta simulation methods

    def simNanoHive(self):
        """
        Opens the Nano-Hive dialog... for details see subroutine's docstring.
        """
        # This should be probably be modeled after the simSetup_CommandRun class
        # I'll do this if Bruce agrees.  For now, I want to get this working ASAP.
        # Mark 050915.
        self.nanohive.showDialog(self.assy)

    def simPlot(self):
        """
        Opens the "Make Graphs" dialog if there is a movie file
        (i.e. a movie file has been opened in the Movie Player).
        For details see subroutine's docstring.
        """
        from commands.Plot.PlotTool import simPlot
        dialog = simPlot(self.assy) # Returns "None" if there is no current movie file. [mark 2007-05-03]
        if dialog:
            self.plotcntl = dialog #probably useless, but done since old code did it;
                # conceivably, keeping it matters due to its refcount. [bruce 050327]
                # matters now, since dialog can be None. [mark 2007-05-03]
        return

    def simMoviePlayer(self):
        """
        Plays a DPB movie file created by the simulator.
        """
        from commands.PlayMovie.movieMode import simMoviePlayer
        simMoviePlayer(self.assy)
        return

    def JobManager(self):
        """
        Opens the Job Manager dialog... for details see subroutine's docstring.

        @note: This is not implemented.
        """
        from analysis.GAMESS.JobManager import JobManager
        dialog = JobManager(self)
        if dialog:
            self.jobmgrcntl = dialog
                # probably useless, but done since old code did it;
                # conceivably, keeping it matters due to its refcount.
                # See Bruce's note in simPlot().
        return

    def serverManager(self):
        """
        Opens the server manager dialog.

        @note: This is not implemented.
        """
        from processes.ServerManager import ServerManager
        ServerManager().showDialog()

    ###################################
    # Insert Menu/Toolbar Slots
    ###################################

    def ensureInCommand(self, commandName): #bruce 071009
        """
        If the current command's .commandName differs from the one given, change
        to that command.

        @note: As of 080730, userEnterCommand has the same special case
               of doing nothing if we're already in the named command.
               So we just call it. (Even before, it had almost that
               special case; see its docstring for details.)

        @note: all uses of this method are causes for suspicion, about
        whether some sort of refactoring or generalization is called for,
        unless they are called from a user command whose purpose is solely
        to switch to the named command. (In other words, switching to it
        for some reason other than the user asking for that is suspicious.)
        (That happens in current code [071011], and ought to be cleared up somehow,
         but maybe not using this method in particular.)
        """
        self.commandSequencer.userEnterCommand(commandName)
            # note: this changes the value of .currentCommand
        return

    def insertAtom(self):
        self.ensureInCommand('SELECTMOLS')
        self.atomcntl.show()

    def insertGraphene(self):
        """
        Invokes the graphene command ('BUILD_GRAPHENE')
        """
        self.commandSequencer.userEnterCommand('BUILD_GRAPHENE')
        self.commandSequencer.currentCommand.runCommand()

    # Build > CNT related slots and methods. ######################

    def activateNanotubeTool(self):
        """
        Activates the Nanotube toolbar.
        """
        commandSequencer = self.commandSequencer
        commandSequencer.userEnterCommand('BUILD_NANOTUBE')
        return

    def insertNanotube(self, isChecked = False):
        """
        @param isChecked: If Nanotube button in the Nanotube Flyout toolbar is
                          checked, enter NanotubeLineMode. (provided you are
                          using the new InsertNanotube_EditCommand command.
        @type  isChecked: boolean
        @see: B{Ui_NanotubeFlyout.activateInsertNanotubeLine_EditCommand}
        """

        self.enterOrExitTemporaryCommand('INSERT_NANOTUBE')

        currentCommand = self.commandSequencer.currentCommand
        if currentCommand.commandName == "INSERT_NANOTUBE":
            currentCommand.runCommand()
        return

    def activateDnaTool(self):
        """
        Enter the InsertDna_EditCommand command.
        @see:B{self.insertDna}
        @see: B{ops_select_Mixin.getSelectedDnaGroups}
        @see: B{dna_model.DnaGroup.edit}
        """
        selectedDnaGroupList = self.assy.getSelectedDnaGroups()

        #If exactly one DnaGroup is selected then when user invokes Build > Dna
        #command, edit the selected Dnagroup instead of creating a new one
        #For all other cases, invoking Build > Dna  wikk create a new DnaGroup
        if len(selectedDnaGroupList) == 1:
            selDnaGroup = selectedDnaGroupList[0]
            selDnaGroup.edit()
        else:
            commandSequencer = self.commandSequencer
            commandSequencer.userEnterCommand('BUILD_DNA')

            assert self.commandSequencer.currentCommand.commandName == 'BUILD_DNA'
            self.commandSequencer.currentCommand.runCommand()

    def enterOrExitTemporaryCommand(self, commandName): #bruce 080730 split this out of several methods
        commandSequencer = self.commandSequencer
        currentCommand = commandSequencer.currentCommand
        if currentCommand.commandName != commandName:
            # enter command, if not already in it
            commandSequencer.userEnterCommand( commandName)
        else:
            # exit command, if already in it
            currentCommand.command_Done()
        return

    def enterBreakStrandCommand(self, isChecked = False):
        """
        """
        #REVIEW- arg isChecked is unused. Need to revise this this in several
        #methods-- Ninad 2008-07-31
        self.enterOrExitTemporaryCommand( 'BREAK_STRANDS' )

    def enterJoinStrandsCommand(self, isChecked = False):
        """
        """
        self.enterOrExitTemporaryCommand( 'JOIN_STRANDS' )

    def enterMakeCrossoversCommand(self, isChecked = False):
        """
        Enter make crossovers command.
        """
        self.enterOrExitTemporaryCommand( 'MAKE_CROSSOVERS' )

    def enterOrderDnaCommand(self, isChecked = False):
        """
        """
        self.enterOrExitTemporaryCommand('ORDER_DNA')

    def enterDnaDisplayStyleCommand(self, isChecked = False):
        """
        """
        self.enterOrExitTemporaryCommand('EDIT_DNA_DISPLAY_STYLE')

    #UM 063008: protein flyout toolbar commands
    def activateProteinTool(self):
        """
        Activates the Protein toolbar.
        """
        commandSequencer = self.commandSequencer
        commandSequencer.userEnterCommand('BUILD_PROTEIN')
        return

    def insertPeptide(self, isChecked = False):
        """
        Invokes the peptide command (INSERT_PEPTIDE)
        @param isChecked: If insertPeptide button in the
                          Protein Flyout toolbar is
                          checked, enter insertPeptideMode.
        @type isChecked: bool
        """

        self.enterOrExitTemporaryCommand('INSERT_PEPTIDE')

        currentCommand = self.commandSequencer.currentCommand
        if currentCommand.commandName == "INSERT_PEPTIDE":
            currentCommand.runCommand()

    def enterProteinDisplayStyleCommand(self, isChecked = False):
        """
        Enter protein display style command
        @param isChecked: If enterProteinDisplayStyleCommand button in the
                          Protein Flyout toolbar is
                          checked, enter ProteinDisplayStyleMode.
        @type isChecked: bool
        """
        self.enterOrExitTemporaryCommand('EDIT_PROTEIN_DISPLAY_STYLE')


    def enterEditProteinCommand(self, isChecked = False):
        """
        Enter edit rotamers command
        @param isChecked: If enterEditProteinCommand button in the
                          Protein Flyout toolbar is
                          checked, enter enterEditProteinMode.
        @type isChecked: bool
        """
        self.enterOrExitTemporaryCommand('EDIT_PROTEIN')


    def enterEditResiduesCommand(self, isChecked = False):
        """
        Enter edit residues command
        @param isChecked: If enterEditResiduesCommand button in the
                          Protein Flyout toolbar is
                          checked, enter enterEditResiduesMode.
        @type isChecked: bool
        """
        self.enterOrExitTemporaryCommand('EDIT_RESIDUES')

    def enterCompareProteinsCommand(self, isChecked = False):
        """
        Enter compare proteins command
        @param isChecked: If enterCompareProteinsCommand button in the
                          Protein Flyout toolbar is
                          checked, enter enterCompareProteinsMode.
        @type isChecked: bool
        """
        self.enterOrExitTemporaryCommand('COMPARE_PROTEINS')


    def enterStereoPropertiesCommand(self):
        """
        Enter Stereo Properties Command
        """
        self.enterOrExitTemporaryCommand('STEREO_PROPERTIES')


    def enterQuteMolCommand(self):
        """
        Show the QuteMol property manager.
        """
        commandSequencer = self.commandSequencer
        commandSequencer.userEnterCommand('QUTEMOL')
        # note: if we make the Qutemol action a 'ckeckable action'
        # (so when unchecked by the user, it should exit the QuteMol command),
        # then replace the above by a call to self.enterOrExitTemporaryCommand.

    def insertDna(self, isChecked = False):
        """
        @param isChecked: If Dna Duplex button in the Dna Flyout toolbar is
                          checked, enter DnaLineMode. (provided you are
                          using the new DNADuplexEditCommand command.
        @type  isChecked: boolean
        @see: B{Ui_DnaFlyout.activateInsertDna_EditCommand}
        """
        self.enterOrExitTemporaryCommand('INSERT_DNA')

        currentCommand = self.commandSequencer.currentCommand
        if currentCommand.commandName == 'INSERT_DNA':
            currentCommand.runCommand()

    def orderDna(self, dnaGroupList = ()):
        """
        open a text editor and load a temporary text file containing all the
        DNA strand names and their sequences in the current DNA object. It will
        look something like this: (comma separated values. To be revised)

        Strand1,ATCAGCTACGCATCGCT
        Strand2,TAGTCGATGCGTAGCGA

        The user can then save the file to a permanent location.

        @see: Ui_DnaFlyout.orderDnaCommand
        @see: self._writeDnaSequence
        @TODO: This works only for a single DNA group So dnaGroupList always
                contain a single item.
        """

        dnaGroupNameString = ''

        fileBaseName = 'DnaSequence'

        dnaSequence = ''


        if dnaGroupList:
            dnaSequence = ''
            for dnaGroup in dnaGroupList:
                dnaSequence = dnaSequence + dnaGroup.getDnaSequence(format = 'CSV')
        else:
            currentCommand = self.commandSequencer.currentCommand
            if currentCommand.commandName == 'BUILD_DNA':
                if currentCommand.struct is not None:
                    dnaSequence = currentCommand.struct.getDnaSequence()
                    dnaGroupNameString = currentCommand.struct.name
                    fileBaseName = dnaGroupNameString


        if dnaSequence:
            tmpdir = find_or_make_Nanorex_subdir('temp')
            temporaryFile = os.path.join(tmpdir, "%s.csv" % fileBaseName)
            self._writeDnaSequence(temporaryFile,
                                   dnaGroupNameString,
                                   dnaSequence)

            open_file_in_editor(temporaryFile)


    def _writeDnaSequence(self, fileName, dnaGroupNameString, dnaSequence):
        """
        Open a temporary file and write the specified dna sequence to it
        @param fileName: the full path of the temporary file to be opened
        @param  dnaSequence: The dnaSequence string to be written to the file.
        @see: self.orderDna
        """

        #Create Header
        headerString = '#NanoEngineer-1 DNA Order Form created on: '
        timestr = "%s\n" % time.strftime("%Y-%m-%d at %H:%M:%S")

        if self.assy.filename:
            mmpFileName = "[" + os.path.normpath(self.assy.filename) + "]"
        else:
            mmpFileName = "[" + self.assy.name + "]" + \
                        " ( The mmp file was probably not saved when the "\
                        " sequence was written)"

        if dnaGroupNameString:
            fileNameInfo_header = "#This sequence is created for node "\
                                "[%s] of file %s\n\n"%(dnaGroupNameString,
                                                       mmpFileName)
        else:
            fileNameInfo_header = "#This sequence is created for file '%s\n\n'"%(
                mmpFileName)

        headerString = headerString + timestr + fileNameInfo_header

        f = open(fileName,'w')
        # Write header
        f.write(headerString)
        f.write("Name,Sequence,Notes\n") # Per IDT's Excel format.
        f.write(dnaSequence)

    def createDnaSequenceEditorIfNeeded(self):
        """
        Returns a Sequence editor object (a dockwidget).
        If one doesn't already exists, it creates one .
        (created only once and only when its first requested and then the
        object is reused)
        @return: The sequence editor object (self._dnaSequenceEditor
        @rtype: B{DnaSequenceEditor}
        @see: InsertDna_PropertyManager._loadSequenceEditor
        @WARNING: QMainwindow.restoreState prints a warning message because its
        unable to find this object in the next session. (as this object is
        created only when requested) This warning message is harmless, but
        if we want to get rid of it, easiest way is to always  create this
        object when MainWindow is created. (This is a small object so may
        be thats the best way)
        """
        if self._dnaSequenceEditor is None:
            from dna.DnaSequenceEditor.DnaSequenceEditor import DnaSequenceEditor
            self._dnaSequenceEditor = DnaSequenceEditor(self)
            self._dnaSequenceEditor.setObjectName("dna_sequence_editor")
            #Should changes.keep_forevenr be called here?
            #Answer : No because python references to these objects are kept in
            #the MainWindow attrs

        return self._dnaSequenceEditor


    def createProteinSequenceEditorIfNeeded(self):
        """
        Returns a Sequence editor object (a dockwidget).
        If one doesn't already exists, it creates one .
        (created only once and only when its first requested and then the
        object is reused)
        @return: The sequence editor object (self._proteinSequenceEditor
        @rtype: B{ProteinSequenceEditor}

        """
        if self._proteinSequenceEditor is None:
            from protein.ProteinSequenceEditor.ProteinSequenceEditor import ProteinSequenceEditor
            self._proteinSequenceEditor = ProteinSequenceEditor(self)
            self._proteinSequenceEditor.setObjectName("protein_sequence_editor")

        return self._proteinSequenceEditor

    def toggle_selectByNameDockWidget(self, bool_toggle):
        pw = self._activepw
        leftChannelDockWidget = pw.getLeftChannelDockWidget()
        if bool_toggle:
            leftChannelDockWidget.show()
        else:
            leftChannelDockWidget.close()


    def insertPovrayScene(self):
        self.povrayscenecntl.setup()

    def insertComment(self):
        """
        Insert a new comment into the model tree.
        """
        self.commentcntl.setup()

    ###################################
    # Slots for future tools
    ###################################

    # Mirror Tool
    def toolsMirror(self):
        env.history.message(redmsg("Mirror Tool: Not implemented yet."))

    # Mirror Circular Boundary Tool
    def toolsMirrorCircularBoundary(self):
        env.history.message(redmsg("Mirror Circular Boundary Tool: Not implemented yet."))

    ###################################
    # Slots for Done and Cancel actions for current command
    ###################################

    def toolsDone(self):
        """
        @note: called from several places, including ok_btn_clicked
        (and in some cases, cancel_btn_clicked) of PM_Dialog and its
        subclasses

        The calls from ok_btn_clicked and cancel_btn_clicked methods are
        probablycorrect, but are deprecated, and should be replaced by calls of
        self.command.command_Done (where self is the calling PM).
        """
        #bruce 080815/080827 docstring
        command_to_exit  = self.currentCommand.command_that_supplies_PM()
        command_to_exit.command_Done()
        return

    def toolsCancel(self):
        """
        Cancel the command which is supplying the currently visible
        Property Manager.

        @note: called only from cancel_btn_clicked methods in PM_Dialog or its
        subclasses, but some of those call toolsDone instead.

        (where self is the calling PM).
        """
        #bruce 080815/080827 docstring
        command_to_exit = self.currentCommand.command_that_supplies_PM()
        command_to_exit.command_Cancel()
        return

    ######################################
    # Show View > Orientation Window
    #######################################

    def showOrientationWindow(self, isChecked = False): #Ninad 061121

        if isChecked:
            if not self.orientationWindow:
                self.orientationWindow  = ViewOrientationWindow(self)
                #self.orientationWindow.createOrientationViewList(namedViewList)
                self.orientationWindow.createOrientationViewList()
                self.orientationWindow.setVisible(True)
            else:
                if not self.orientationWindow.isVisible():
                    self.orientationWindow.setVisible(True)
        else:
            if self.orientationWindow and self.orientationWindow.isVisible():
                self.orientationWindow.setVisible(False)

        return self.orientationWindow

    def deleteOrientationWindow(self):
        """
        Delete the orientation window when the main window closes.
        """
        #ninad 061121 - this is probably unnecessary
        if self.orientationWindow:
            self.orientationWindow.close()
            self.orientationWindow = None

        return self.orientationWindow

    # key event handling revised by bruce 041220 to fix some bugs;
    # see comments in the GLPane methods.

    def keyPressEvent(self, e):
        self.glpane.keyPressEvent(e)

    def keyReleaseEvent(self, e):
        self.glpane.keyReleaseEvent(e)

    def wheelEvent(self, event): #bruce 070607 fix bug xxx [just reported, has no bug number yet]
        ## print "mwsem ignoring wheelEvent",event
        # Note: this gets called by wheel events with mouse inside history widget,
        # whenever it has reached its scrolling limit. Defining it here prevents the bug
        # of Qt passing it on to GLPane (maybe only happens if GLPane was last-clicked widget),
        # causing unintended mousewheel zoom. Apparently just catching this and returning is
        # enough -- it's not necessary to also call event.ignore(). Guess: this method's default
        # implem passes it either to "central widget" (just guessing that's the GLPane) or to
        # the last widget we clicked on (or more likely, the one with the keyfocus).
        return

    # Methods for temporarily disabling QActions in toolbars/menus ##########

    def enableViews(self, enableFlag = True):
        """
        Disables/enables view actions on toolbar and menu.

        This is typically used to momentarily disable some
        of the view actions while animating between views.

        @param enableFlag: Flag to enable/disable the View actions in this
                           method.
        @type  enableFlag: boolean
        """
        self.viewNormalToAction.setEnabled(enableFlag)
        self.viewParallelToAction.setEnabled(enableFlag)

        self.viewFrontAction.setEnabled(enableFlag)
        self.viewBackAction.setEnabled(enableFlag)
        self.viewTopAction.setEnabled(enableFlag)
        self.viewBottomAction.setEnabled(enableFlag)
        self.viewLeftAction.setEnabled(enableFlag)
        self.viewRightAction.setEnabled(enableFlag)
        self.viewIsometricAction.setEnabled(enableFlag)

        self.setViewHomeAction.setEnabled(enableFlag)
        self.setViewFitToWindowAction.setEnabled(enableFlag)
        self.setViewRecenterAction.setEnabled(enableFlag)

        self.viewFlipViewVertAction.setEnabled(enableFlag)
        self.viewFlipViewHorzAction.setEnabled(enableFlag)
        self.viewRotatePlus90Action.setEnabled(enableFlag)
        self.viewRotateMinus90Action.setEnabled(enableFlag)
        return

    def disable_QActions_for_extrudeMode(self, disableFlag = True):
        """
        Disables action items in the main window for extrudeMode.
        """
        self.disable_QActions_for_movieMode(disableFlag)
        self.modifyHydrogenateAction.setEnabled(not disableFlag)
        self.modifyDehydrogenateAction.setEnabled(not disableFlag)
        self.modifyPassivateAction.setEnabled(not disableFlag)
        self.modifyDeleteBondsAction.setEnabled(not disableFlag)
        self.modifyStretchAction.setEnabled(not disableFlag)
        self.modifySeparateAction.setEnabled(not disableFlag)
        self.modifyMergeAction.setEnabled(not disableFlag)
        self.modifyInvertAction.setEnabled(not disableFlag)
        self.modifyMirrorAction.setEnabled(not disableFlag)
        self.modifyAlignCommonAxisAction.setEnabled(not disableFlag)
        # All QActions in the Modify menu/toolbar should be disabled,
        # too. mark 060323
        return

    def disable_QActions_for_sim(self, disableFlag = True):
        """
        Disables actions items in the main window during simulations
        (and minimize).
        """
        self.disable_QActions_for_movieMode(disableFlag)
        self.simMoviePlayerAction.setEnabled(not disableFlag)
        return

    def disable_QActions_for_movieMode(self, disableFlag = True):
        """
        Disables action items in the main window for movieMode;
        also called by disable_QActions_for_extrudeMode
        and by disable_QActions_for_sim.
        """
        enable = not disableFlag
        self.modifyAdjustSelAction.setEnabled(enable) # "Adjust Selection"
        self.modifyAdjustAllAction.setEnabled(enable) # "Adjust All"
        self.simMinimizeEnergyAction.setEnabled(enable) # Minimize Energy
        self.checkAtomTypesAction.setEnabled(enable) # Check AMBER AtomTypes
        self.rosettaSetupAction.setEnabled(enable)
        self.simSetupAction.setEnabled(enable) # "Simulator"
        self.fileSaveAction.setEnabled(enable) # "File Save"
        self.fileSaveAsAction.setEnabled(enable) # "File Save As"
        self.fileOpenAction.setEnabled(enable) # "File Open"
        self.fileCloseAction.setEnabled(enable) # "File Close"
        self.fileInsertMmpAction.setEnabled(enable) # "Insert MMP"
        self.fileInsertPdbAction.setEnabled(enable) # "Insert PDB"
        self.fileInsertInAction.setEnabled(enable) # "Insert IN"
        self.editDeleteAction.setEnabled(enable) # "Delete"

        # [bruce 050426 comment: I'm skeptical of disabling zoom/pan/rotate,
        #  and suggest for some others (especially "simulator") that they
        #  auto-exit the mode rather than be disabled,
        #  but I won't revise these for now.]
        #
        # [update, bruce 070813/070820]
        # Zoom/pan/rotate are now rewritten to suspend rather than exit
        # the current mode, so they no longer need disabling in Extrude or
        # Movie modes. (There is one known minor bug (2517) -- Movie mode
        # asks whether to rewind (via popup dialog), which is only appropriate
        # to ask if it's being exited. Fixing this is relevant to the
        # upcoming "command sequencer".)
        # This is also called by disable_QActions_for_sim, and whether this
        # change is safe in that case is not carefully reviewed or tested,
        # but it seems likely to be ok.

##        self.zoomToAreaAction.setEnabled(enable) # "Zoom Tool"
##        self.panToolAction.setEnabled(enable) # "Pan Tool"
##        self.rotateToolAction.setEnabled(enable) # "Rotate Tool"

        return

# == Caption methods

    def update_mainwindow_caption_properly(self, junk = None): #bruce 050810 added this
        self.update_mainwindow_caption(self.assy.has_changed())
        # The call to updateWindowTitle() is harmless, even when MDI support
        # isn't enabled.
        self.activePartWindow().updateWindowTitle(self.assy.has_changed())

    def update_mainwindow_caption(self, changed = False): #by mark; bruce 050810 revised this in several ways, fixed bug 785
        """
        Update the window title (caption) at the top of the of the main window.
        Example:  "partname.mmp"

        @param changed: If True, the caption will include the prefix and
                        suffix (via user prefs settings in the
                        "Preferences | Window" dialog) to denote the part
                        has been modified.
        @type  changed: boolean

        @attention: I intend to remove the prefix and suffix user prefs and
        use the standard way applications indicate that a document has unsaved
        changes. On Mac OS X the close button will have a modified look;
        on other platforms the window title will have an '*' (asterisk).
        BTW, this has already been done in PartWindow.updateWindowTitle().
        Mark 2008-01-02.

        @see: U{B{windowTitle}<http://doc.trolltech.com/4/qwidget.html#windowTitle-prop>},
              U{B{windowModified}<http://doc.trolltech.com/4/qwidget.html#windowModified-prop>}
        """
        # WARNING: there is mostly-duplicated code in this method and in
        # class Ui_PartWindow.updateWindowTitle. I guess they are both
        # always called, but only one of them matters depending on whether
        # experimental (and unfinished) MDI support is enabled.
        # Ideally a single helper function, or single method in a new
        # common superclass, would be used. [bruce 081227 comment]
        caption_prefix = env.prefs[captionPrefix_prefs_key]
        caption_suffix = env.prefs[captionSuffix_prefs_key]
        caption_fullpath = env.prefs[captionFullPath_prefs_key]

        if changed:
            prefix = caption_prefix
            suffix = caption_suffix
        else:
            prefix = ''
            suffix = ''

        # this is not needed here since it's already done in the prefs values
        # themselves when we set them:
        # if prefix and not prefix.endswith(" "):
        #     prefix = prefix + " "
        # if suffix and not suffix.startswith(" "):
        #     suffix = " " + suffix

        partname = "Untitled" # fallback value if no file yet
        if self.assy.filename: #bruce 081227 cleanup: try -> if, etc
            junk, basename = os.path.split(self.assy.filename)
            if basename:
                if caption_fullpath:
                    partname = os.path.normpath(self.assy.filename)
                        #fixed bug 453-1 ninad060721
                else:
                    partname = basename

        # WARNING: the following code differs in the two versions
        # of this routine.
        ##e [bruce 050811 comment:]
        # perhaps we should move prefix to the beginning,
        # rather than just before "[";
        # and in any case the other stuff here,
        # self.name() + " - " + "[" + "]", should also be
        # user-changeable, IMHO.
        #print "****self.accessibleName *****=", self.accessibleName()
        self.setWindowTitle(self.trUtf8("NanoEngineer-1" + " - " + prefix +
                                        "[" + partname.encode("utf_8") + "]" +
                                        suffix ))
        # review: also call self.setWindowModified(changed)?
        return

    def createProgressDialog(self):
        """
        Creates the main window's Progress Dialog, which can be used to
        display a progress dialog at any time. It is modal by default.

        @see: _readmmp() for an example of use.
        @see: U{B{QProgressDialog}<http://doc.trolltech.com/4/qprogressdialog.html>}

        """
        from PyQt4.Qt import QProgressDialog
        self.progressDialog = QProgressDialog(self)
        self.progressDialog.setWindowModality(Qt.WindowModal)
        self.progressDialog.setWindowTitle("NanoEngineer-1")

        # setMinimumDuration() doesn't work. Qt bug?
        self.progressDialog.setMinimumDuration(500) # 500 ms = 0.5 seconds


    #= Methods for "Open Recent Files" menu, a submenu of the "Files" menu.

    def getRecentFilesListAndPrefsSetting(self):
        """
        Returns the list of recent files that appears in the "Open Recent Files"
        menu and the preference settings object.

        @return: List of recent files, preference settings.
        @rtype:  list, QSettings

        @see: U{B{QSettings}<http://doc.trolltech.com/4/qsettings.html>}
        """
        if recentfiles_use_QSettings:
            prefsSetting = QSettings("Nanorex", "NanoEngineer-1")
            fileList = prefsSetting.value(RECENTFILES_QSETTINGS_KEY).toStringList()
        else:
            prefsSetting = preferences.prefs_context()
            fileList = prefsSetting.get(RECENTFILES_QSETTINGS_KEY, [])

        return fileList, prefsSetting

    def updateRecentFileList(self, fileName):
        """
        Add I{filename} into the recent file list.

        @param filename: The filename to add to the recently opened files list.
        @type  filename: string
        """

        # LIST_CAPACITY could be set by user preference (NIY).
        LIST_CAPACITY = 9
            # Warning: Potential bug if number of recent files >= 10
            # (i.e. LIST_CAPACITY >= 10). See fileSlotsMixin.openRecentFile().

        fileName = os.path.normpath(str_or_unicode(fileName))

        fileList, prefsSetting = self.getRecentFilesListAndPrefsSetting()

        if len(fileList) > 0:
            # If filename is already in fileList, delete it from the list.
            # filename will be added to the top of the list later.
            for ii in range(len(fileList)):
                if str_or_unicode(fileName) == str_or_unicode(fileList[ii]):
                    del fileList[ii]
                    break

        if recentfiles_use_QSettings:
            fileList.prepend(fileName)
        else:
            fileList.insert(0, fileName)

        fileList = fileList[:LIST_CAPACITY]

        if recentfiles_use_QSettings:
            assert isinstance(prefsSetting, QSettings)
            prefsSetting.setValue(RECENTFILES_QSETTINGS_KEY, QVariant(fileList))

            if 0: #debug_recent_files:
                # confirm that the information really made it into the QSetting.
                fileListTest = prefsSetting.value(RECENTFILES_QSETTINGS_KEY).toStringList()
                fileListTest = map(str, list(fileListTest))
                assert len(fileListTest) == len(fileList)
                for i in range(len(fileList)):
                    assert str_or_unicode(fileList[i]) == str_or_unicode(fileListTest[i])
        else:
            prefsSetting[RECENTFILES_QSETTINGS_KEY] = fileList

        del prefsSetting

        self.createOpenRecentFilesMenu()
        return

    def createOpenRecentFilesMenu(self):
        """
        Creates the "Open Recent Files" menu, a submenu of the "File" menu.

        This is called whenever a new file is opened or the current file
        is renamed.
        """
        if hasattr(self, "openRecentFilesMenu"):
            # Remove the current "Open Recent Files" menu.
            # It will be recreated below.
            self.fileMenu.removeAction(self.openRecentFilesMenuAction)

        # Create a new "Open Recent Files" menu from the current list.
        self.openRecentFilesMenu = QMenu("Open Recent Files", self)

        fileList, prefsSetting = self.getRecentFilesListAndPrefsSetting()

        self.openRecentFilesMenu.clear()
        for ii in range(len(fileList)):
            _recent_filename = os.path.normpath(str_or_unicode(fileList[ii]).encode("utf_8")) # Fixes bug 2193. Mark 060808.
            self.openRecentFilesMenu.addAction(
                QtGui.QApplication.translate(
                    "Main Window",
                    "&" + str(ii + 1) + "  " + _recent_filename, None, QtGui.QApplication.UnicodeUTF8))

        # Insert the "Open Recent Files" menu above "File > Close".
        self.openRecentFilesMenuAction = \
            self.fileMenu.insertMenu(self.fileCloseAction,
                                     self.openRecentFilesMenu)

        self.connect(self.openRecentFilesMenu,
                     SIGNAL('triggered(QAction*)'),
                     self.openRecentFile)
        return

    def colorSchemeCommand(self):
        """
        This is a slot method for invoking the B{Color Scheme} command.
        """
        self.enterOrExitTemporaryCommand('COLOR_SCHEME')

    def lightingSchemeCommand(self):
        """
        This is a slot method for invoking the B{Lighting Scheme} command.
        """
        self.enterOrExitTemporaryCommand('LIGHTING_SCHEME')


    def toggleRulers(self, isChecked):
        """
        Displays/hides the rulers in the 3D graphics area (glpane).

        @param isChecked: Checked state of the B{View > Rulers} menu item
        @type  isChecked: boolean
        """
        if isChecked:
            env.prefs[displayRulers_prefs_key] = True
        else:
            env.prefs[displayRulers_prefs_key] = False

    pass # end of class MWsemantics

# end