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
|
# Pmw megawidget base classes.
# This module provides a foundation for building megawidgets. It
# contains the MegaArchetype class which manages component widgets and
# configuration options. Also provided are the MegaToplevel and
# MegaWidget classes, derived from the MegaArchetype class. The
# MegaToplevel class contains a Tkinter Toplevel widget to act as the
# container of the megawidget. This is used as the base class of all
# megawidgets that are contained in their own top level window, such
# as a Dialog window. The MegaWidget class contains a Tkinter Frame
# to act as the container of the megawidget. This is used as the base
# class of all other megawidgets, such as a ComboBox or ButtonBox.
#
# Megawidgets are built by creating a class that inherits from either
# the MegaToplevel or MegaWidget class.
import os
import string
import sys
import traceback
import types
import Tkinter
# Special values used in index() methods of several megawidgets.
END = ['end']
SELECT = ['select']
DEFAULT = ['default']
# Constant used to indicate that an option can only be set by a call
# to the constructor.
INITOPT = ['initopt']
_DEFAULT_OPTION_VALUE = ['default_option_value']
_useTkOptionDb = 0
# Symbolic constants for the indexes into an optionInfo list.
_OPT_DEFAULT = 0
_OPT_VALUE = 1
_OPT_FUNCTION = 2
# Stacks
_busyStack = []
# Stack which tracks nested calls to show/hidebusycursor (called
# either directly or from activate()/deactivate()). Each element
# is a dictionary containing:
# 'newBusyWindows' : List of windows which had busy_hold called
# on them during a call to showbusycursor().
# The corresponding call to hidebusycursor()
# will call busy_release on these windows.
# 'busyFocus' : The blt _Busy window which showbusycursor()
# set the focus to.
# 'previousFocus' : The focus as it was when showbusycursor()
# was called. The corresponding call to
# hidebusycursor() will restore this focus if
# the focus has not been changed from busyFocus.
_grabStack = []
# Stack of grabbed windows. It tracks calls to push/popgrab()
# (called either directly or from activate()/deactivate()). The
# window on the top of the stack is the window currently with the
# grab. Each element is a dictionary containing:
# 'grabWindow' : The window grabbed by pushgrab(). The
# corresponding call to popgrab() will release
# the grab on this window and restore the grab
# on the next window in the stack (if there is one).
# 'globalMode' : True if the grabWindow was grabbed with a
# global grab, false if the grab was local
# and 'nograb' if no grab was performed.
# 'previousFocus' : The focus as it was when pushgrab()
# was called. The corresponding call to
# popgrab() will restore this focus.
# 'deactivateFunction' :
# The function to call (usually grabWindow.deactivate) if
# popgrab() is called (usually from a deactivate() method)
# on a window which is not at the top of the stack (that is,
# does not have the grab or focus). For example, if a modal
# dialog is deleted by the window manager or deactivated by
# a timer. In this case, all dialogs above and including
# this one are deactivated, starting at the top of the
# stack.
# Note that when dealing with focus windows, the name of the Tk
# widget is used, since it may be the '_Busy' window, which has no
# python instance associated with it.
#=============================================================================
# Functions used to forward methods from a class to a component.
# Fill in a flattened method resolution dictionary for a class (attributes are
# filtered out). Flattening honours the MI method resolution rules
# (depth-first search of bases in order). The dictionary has method names
# for keys and functions for values.
def __methodDict(cls, dict):
# the strategy is to traverse the class in the _reverse_ of the normal
# order, and overwrite any duplicates.
baseList = list(cls.__bases__)
baseList.reverse()
# do bases in reverse order, so first base overrides last base
for super in baseList:
__methodDict(super, dict)
# do my methods last to override base classes
for key, value in cls.__dict__.items():
# ignore class attributes
if type(value) == types.FunctionType:
dict[key] = value
def __methods(cls):
# Return all method names for a class.
# Return all method names for a class (attributes are filtered
# out). Base classes are searched recursively.
dict = {}
__methodDict(cls, dict)
return dict.keys()
# Function body to resolve a forwarding given the target method name and the
# attribute name. The resulting lambda requires only self, but will forward
# any other parameters.
__stringBody = (
'def %(method)s(this, *args, **kw): return ' +
'apply(this.%(attribute)s.%(method)s, args, kw)')
# Get a unique id
__counter = 0
def __unique():
global __counter
__counter = __counter + 1
return str(__counter)
# Function body to resolve a forwarding given the target method name and the
# index of the resolution function. The resulting lambda requires only self,
# but will forward any other parameters. The target instance is identified
# by invoking the resolution function.
__funcBody = (
'def %(method)s(this, *args, **kw): return ' +
'apply(this.%(forwardFunc)s().%(method)s, args, kw)')
def forwardmethods(fromClass, toClass, toPart, exclude = ()):
# Forward all methods from one class to another.
# Forwarders will be created in fromClass to forward method
# invocations to toClass. The methods to be forwarded are
# identified by flattening the interface of toClass, and excluding
# methods identified in the exclude list. Methods already defined
# in fromClass, or special methods with one or more leading or
# trailing underscores will not be forwarded.
# For a given object of class fromClass, the corresponding toClass
# object is identified using toPart. This can either be a String
# denoting an attribute of fromClass objects, or a function taking
# a fromClass object and returning a toClass object.
# Example:
# class MyClass:
# ...
# def __init__(self):
# ...
# self.__target = TargetClass()
# ...
# def findtarget(self):
# return self.__target
# forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
# # ...or...
# forwardmethods(MyClass, TargetClass, MyClass.findtarget,
# ['dangerous1', 'dangerous2'])
# In both cases, all TargetClass methods will be forwarded from
# MyClass except for dangerous1, dangerous2, special methods like
# __str__, and pre-existing methods like findtarget.
# Allow an attribute name (String) or a function to determine the instance
if type(toPart) != types.StringType:
# check that it is something like a function
if callable(toPart):
# If a method is passed, use the function within it
if hasattr(toPart, 'im_func'):
toPart = toPart.im_func
# After this is set up, forwarders in this class will use
# the forwarding function. The forwarding function name is
# guaranteed to be unique, so that it can't be hidden by subclasses
forwardName = '__fwdfunc__' + __unique()
fromClass.__dict__[forwardName] = toPart
# It's not a valid type
else:
raise TypeError, 'toPart must be attribute name, function or method'
# get the full set of candidate methods
dict = {}
__methodDict(toClass, dict)
# discard special methods
for ex in dict.keys():
if ex[:1] == '_' or ex[-1:] == '_':
del dict[ex]
# discard dangerous methods supplied by the caller
for ex in exclude:
if dict.has_key(ex):
del dict[ex]
# discard methods already defined in fromClass
for ex in __methods(fromClass):
if dict.has_key(ex):
del dict[ex]
for method, func in dict.items():
d = {'method': method, 'func': func}
if type(toPart) == types.StringType:
execString = \
__stringBody % {'method' : method, 'attribute' : toPart}
else:
execString = \
__funcBody % {'forwardFunc' : forwardName, 'method' : method}
exec execString in d
# this creates a method
fromClass.__dict__[method] = d[method]
#=============================================================================
def setgeometryanddeiconify(window, geom):
# To avoid flashes on X and to position the window correctly on NT
# (caused by Tk bugs).
if os.name == 'nt' or \
(os.name == 'posix' and sys.platform[:6] == 'cygwin'):
# Require overrideredirect trick to stop window frame
# appearing momentarily.
redirect = window.overrideredirect()
if not redirect:
window.overrideredirect(1)
window.deiconify()
if geom is not None:
window.geometry(geom)
# Call update_idletasks to ensure NT moves the window to the
# correct position it is raised.
window.update_idletasks()
window.tkraise()
if not redirect:
window.overrideredirect(0)
else:
if geom is not None:
window.geometry(geom)
# Problem!? Which way around should the following two calls
# go? If deiconify() is called first then I get complaints
# from people using the enlightenment or sawfish window
# managers that when a dialog is activated it takes about 2
# seconds for the contents of the window to appear. But if
# tkraise() is called first then I get complaints from people
# using the twm window manager that when a dialog is activated
# it appears in the top right corner of the screen and also
# takes about 2 seconds to appear.
#window.tkraise()
# Call update_idletasks to ensure certain window managers (eg:
# enlightenment and sawfish) do not cause Tk to delay for
# about two seconds before displaying window.
#window.update_idletasks()
#window.deiconify()
window.deiconify()
if window.overrideredirect():
# The window is not under the control of the window manager
# and so we need to raise it ourselves.
window.tkraise()
#=============================================================================
class MegaArchetype:
# Megawidget abstract root class.
# This class provides methods which are inherited by classes
# implementing useful bases (this class doesn't provide a
# container widget inside which the megawidget can be built).
def __init__(self, parent = None, hullClass = None):
# Mapping from each megawidget option to a list of information
# about the option
# - default value
# - current value
# - function to call when the option is initialised in the
# call to initialiseoptions() in the constructor or
# modified via configure(). If this is INITOPT, the
# option is an initialisation option (an option that can
# be set by the call to the constructor but can not be
# used with configure).
# This mapping is not initialised here, but in the call to
# defineoptions() which precedes construction of this base class.
#
# self._optionInfo = {}
# Mapping from each component name to a tuple of information
# about the component.
# - component widget instance
# - configure function of widget instance
# - the class of the widget (Frame, EntryField, etc)
# - cget function of widget instance
# - the name of the component group of this component, if any
self.__componentInfo = {}
# Mapping from alias names to the names of components or
# sub-components.
self.__componentAliases = {}
# Contains information about the keywords provided to the
# constructor. It is a mapping from the keyword to a tuple
# containing:
# - value of keyword
# - a boolean indicating if the keyword has been used.
# A keyword is used if, during the construction of a megawidget,
# - it is defined in a call to defineoptions() or addoptions(), or
# - it references, by name, a component of the megawidget, or
# - it references, by group, at least one component
# At the end of megawidget construction, a call is made to
# initialiseoptions() which reports an error if there are
# unused options given to the constructor.
#
# After megawidget construction, the dictionary contains
# keywords which refer to a dynamic component group, so that
# these components can be created after megawidget
# construction and still use the group options given to the
# constructor.
#
# self._constructorKeywords = {}
# List of dynamic component groups. If a group is included in
# this list, then it not an error if a keyword argument for
# the group is given to the constructor or to configure(), but
# no components with this group have been created.
# self._dynamicGroups = ()
if hullClass is None:
self._hull = None
else:
if parent is None:
parent = Tkinter._default_root
# Create the hull.
self._hull = self.createcomponent('hull',
(), None,
hullClass, (parent,))
_hullToMegaWidget[self._hull] = self
if _useTkOptionDb:
# Now that a widget has been created, query the Tk
# option database to get the default values for the
# options which have not been set in the call to the
# constructor. This assumes that defineoptions() is
# called before the __init__().
option_get = self.option_get
_VALUE = _OPT_VALUE
_DEFAULT = _OPT_DEFAULT
for name, info in self._optionInfo.items():
value = info[_VALUE]
if value is _DEFAULT_OPTION_VALUE:
resourceClass = string.upper(name[0]) + name[1:]
value = option_get(name, resourceClass)
if value != '':
try:
# Convert the string to int/float/tuple, etc
value = eval(value, {'__builtins__': {}})
except:
pass
info[_VALUE] = value
else:
info[_VALUE] = info[_DEFAULT]
def destroy(self):
# Clean up optionInfo in case it contains circular references
# in the function field, such as self._settitle in class
# MegaToplevel.
self._optionInfo = {}
if self._hull is not None:
del _hullToMegaWidget[self._hull]
self._hull.destroy()
#======================================================================
# Methods used (mainly) during the construction of the megawidget.
def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
# Create options, providing the default value and the method
# to call when the value is changed. If any option created by
# base classes has the same name as one in <optionDefs>, the
# base class's value and function will be overriden.
# This should be called before the constructor of the base
# class, so that default values defined in the derived class
# override those in the base class.
if not hasattr(self, '_constructorKeywords'):
# First time defineoptions has been called.
tmp = {}
for option, value in keywords.items():
tmp[option] = [value, 0]
self._constructorKeywords = tmp
self._optionInfo = {}
self._initialiseoptions_counter = 0
self._initialiseoptions_counter = self._initialiseoptions_counter + 1
if not hasattr(self, '_dynamicGroups'):
self._dynamicGroups = ()
self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
self.addoptions(optionDefs)
def addoptions(self, optionDefs):
# Add additional options, providing the default value and the
# method to call when the value is changed. See
# "defineoptions" for more details
# optimisations:
optionInfo = self._optionInfo
optionInfo_has_key = optionInfo.has_key
keywords = self._constructorKeywords
keywords_has_key = keywords.has_key
FUNCTION = _OPT_FUNCTION
for name, default, function in optionDefs:
if '_' not in name:
# The option will already exist if it has been defined
# in a derived class. In this case, do not override the
# default value of the option or the callback function
# if it is not None.
if not optionInfo_has_key(name):
if keywords_has_key(name):
value = keywords[name][0]
optionInfo[name] = [default, value, function]
del keywords[name]
else:
if _useTkOptionDb:
optionInfo[name] = \
[default, _DEFAULT_OPTION_VALUE, function]
else:
optionInfo[name] = [default, default, function]
elif optionInfo[name][FUNCTION] is None:
optionInfo[name][FUNCTION] = function
else:
# This option is of the form "component_option". If this is
# not already defined in self._constructorKeywords add it.
# This allows a derived class to override the default value
# of an option of a component of a base class.
if not keywords_has_key(name):
keywords[name] = [default, 0]
def createcomponent(self, componentName, componentAliases,
componentGroup, widgetClass, *widgetArgs, **kw):
# Create a component (during construction or later).
if self.__componentInfo.has_key(componentName):
raise ValueError, 'Component "%s" already exists' % componentName
if '_' in componentName:
raise ValueError, \
'Component name "%s" must not contain "_"' % componentName
if hasattr(self, '_constructorKeywords'):
keywords = self._constructorKeywords
else:
keywords = {}
for alias, component in componentAliases:
# Create aliases to the component and its sub-components.
index = string.find(component, '_')
if index < 0:
self.__componentAliases[alias] = (component, None)
else:
mainComponent = component[:index]
subComponent = component[(index + 1):]
self.__componentAliases[alias] = (mainComponent, subComponent)
# Remove aliases from the constructor keyword arguments by
# replacing any keyword arguments that begin with *alias*
# with corresponding keys beginning with *component*.
alias = alias + '_'
aliasLen = len(alias)
for option in keywords.keys():
if len(option) > aliasLen and option[:aliasLen] == alias:
newkey = component + '_' + option[aliasLen:]
keywords[newkey] = keywords[option]
del keywords[option]
componentPrefix = componentName + '_'
nameLen = len(componentPrefix)
for option in keywords.keys():
if len(option) > nameLen and option[:nameLen] == componentPrefix:
# The keyword argument refers to this component, so add
# this to the options to use when constructing the widget.
kw[option[nameLen:]] = keywords[option][0]
del keywords[option]
else:
# Check if this keyword argument refers to the group
# of this component. If so, add this to the options
# to use when constructing the widget. Mark the
# keyword argument as being used, but do not remove it
# since it may be required when creating another
# component.
index = string.find(option, '_')
if index >= 0 and componentGroup == option[:index]:
rest = option[(index + 1):]
kw[rest] = keywords[option][0]
keywords[option][1] = 1
if kw.has_key('pyclass'):
widgetClass = kw['pyclass']
del kw['pyclass']
if widgetClass is None:
return None
if len(widgetArgs) == 1 and type(widgetArgs[0]) == types.TupleType:
# Arguments to the constructor can be specified as either
# multiple trailing arguments to createcomponent() or as a
# single tuple argument.
widgetArgs = widgetArgs[0]
widget = apply(widgetClass, widgetArgs, kw)
componentClass = widget.__class__.__name__
self.__componentInfo[componentName] = (widget, widget.configure,
componentClass, widget.cget, componentGroup)
return widget
def destroycomponent(self, name):
# Remove a megawidget component.
# This command is for use by megawidget designers to destroy a
# megawidget component.
self.__componentInfo[name][0].destroy()
del self.__componentInfo[name]
def createlabel(self, parent, childCols = 1, childRows = 1):
labelpos = self['labelpos']
labelmargin = self['labelmargin']
if labelpos is None:
return
label = self.createcomponent('label',
(), None,
Tkinter.Label, (parent,))
if labelpos[0] in 'ns':
# vertical layout
if labelpos[0] == 'n':
row = 0
margin = 1
else:
row = childRows + 3
margin = row - 1
label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
parent.grid_rowconfigure(margin, minsize=labelmargin)
else:
# horizontal layout
if labelpos[0] == 'w':
col = 0
margin = 1
else:
col = childCols + 3
margin = col - 1
label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
parent.grid_columnconfigure(margin, minsize=labelmargin)
def initialiseoptions(self, dummy = None):
self._initialiseoptions_counter = self._initialiseoptions_counter - 1
if self._initialiseoptions_counter == 0:
unusedOptions = []
keywords = self._constructorKeywords
for name in keywords.keys():
used = keywords[name][1]
if not used:
# This keyword argument has not been used. If it
# does not refer to a dynamic group, mark it as
# unused.
index = string.find(name, '_')
if index < 0 or name[:index] not in self._dynamicGroups:
unusedOptions.append(name)
if len(unusedOptions) > 0:
if len(unusedOptions) == 1:
text = 'Unknown option "'
else:
text = 'Unknown options "'
raise KeyError, text + string.join(unusedOptions, ', ') + \
'" for ' + self.__class__.__name__
# Call the configuration callback function for every option.
FUNCTION = _OPT_FUNCTION
for info in self._optionInfo.values():
func = info[FUNCTION]
if func is not None and func is not INITOPT:
func()
#======================================================================
# Method used to configure the megawidget.
def configure(self, option=None, **kw):
# Query or configure the megawidget options.
#
# If not empty, *kw* is a dictionary giving new
# values for some of the options of this megawidget or its
# components. For options defined for this megawidget, set
# the value of the option to the new value and call the
# configuration callback function, if any. For options of the
# form <component>_<option>, where <component> is a component
# of this megawidget, call the configure method of the
# component giving it the new value of the option. The
# <component> part may be an alias or a component group name.
#
# If *option* is None, return all megawidget configuration
# options and settings. Options are returned as standard 5
# element tuples
#
# If *option* is a string, return the 5 element tuple for the
# given configuration option.
# First, deal with the option queries.
if len(kw) == 0:
# This configure call is querying the values of one or all options.
# Return 5-tuples:
# (optionName, resourceName, resourceClass, default, value)
if option is None:
rtn = {}
for option, config in self._optionInfo.items():
resourceClass = string.upper(option[0]) + option[1:]
rtn[option] = (option, option, resourceClass,
config[_OPT_DEFAULT], config[_OPT_VALUE])
return rtn
else:
config = self._optionInfo[option]
resourceClass = string.upper(option[0]) + option[1:]
return (option, option, resourceClass, config[_OPT_DEFAULT],
config[_OPT_VALUE])
# optimisations:
optionInfo = self._optionInfo
optionInfo_has_key = optionInfo.has_key
componentInfo = self.__componentInfo
componentInfo_has_key = componentInfo.has_key
componentAliases = self.__componentAliases
componentAliases_has_key = componentAliases.has_key
VALUE = _OPT_VALUE
FUNCTION = _OPT_FUNCTION
# This will contain a list of options in *kw* which
# are known to this megawidget.
directOptions = []
# This will contain information about the options in
# *kw* of the form <component>_<option>, where
# <component> is a component of this megawidget. It is a
# dictionary whose keys are the configure method of each
# component and whose values are a dictionary of options and
# values for the component.
indirectOptions = {}
indirectOptions_has_key = indirectOptions.has_key
for option, value in kw.items():
if optionInfo_has_key(option):
# This is one of the options of this megawidget.
# Make sure it is not an initialisation option.
if optionInfo[option][FUNCTION] is INITOPT:
raise KeyError, \
'Cannot configure initialisation option "' \
+ option + '" for ' + self.__class__.__name__
optionInfo[option][VALUE] = value
directOptions.append(option)
else:
index = string.find(option, '_')
if index >= 0:
# This option may be of the form <component>_<option>.
component = option[:index]
componentOption = option[(index + 1):]
# Expand component alias
if componentAliases_has_key(component):
component, subComponent = componentAliases[component]
if subComponent is not None:
componentOption = subComponent + '_' \
+ componentOption
# Expand option string to write on error
option = component + '_' + componentOption
if componentInfo_has_key(component):
# Configure the named component
componentConfigFuncs = [componentInfo[component][1]]
else:
# Check if this is a group name and configure all
# components in the group.
componentConfigFuncs = []
for info in componentInfo.values():
if info[4] == component:
componentConfigFuncs.append(info[1])
if len(componentConfigFuncs) == 0 and \
component not in self._dynamicGroups:
raise KeyError, 'Unknown option "' + option + \
'" for ' + self.__class__.__name__
# Add the configure method(s) (may be more than
# one if this is configuring a component group)
# and option/value to dictionary.
for componentConfigFunc in componentConfigFuncs:
if not indirectOptions_has_key(componentConfigFunc):
indirectOptions[componentConfigFunc] = {}
indirectOptions[componentConfigFunc][componentOption] \
= value
else:
raise KeyError, 'Unknown option "' + option + \
'" for ' + self.__class__.__name__
# Call the configure methods for any components.
map(apply, indirectOptions.keys(),
((),) * len(indirectOptions), indirectOptions.values())
# Call the configuration callback function for each option.
for option in directOptions:
info = optionInfo[option]
func = info[_OPT_FUNCTION]
if func is not None:
func()
def __setitem__(self, key, value):
apply(self.configure, (), {key: value})
#======================================================================
# Methods used to query the megawidget.
def component(self, name):
# Return a component widget of the megawidget given the
# component's name
# This allows the user of a megawidget to access and configure
# widget components directly.
# Find the main component and any subcomponents
index = string.find(name, '_')
if index < 0:
component = name
remainingComponents = None
else:
component = name[:index]
remainingComponents = name[(index + 1):]
# Expand component alias
if self.__componentAliases.has_key(component):
component, subComponent = self.__componentAliases[component]
if subComponent is not None:
if remainingComponents is None:
remainingComponents = subComponent
else:
remainingComponents = subComponent + '_' \
+ remainingComponents
widget = self.__componentInfo[component][0]
if remainingComponents is None:
return widget
else:
return widget.component(remainingComponents)
def interior(self):
return self._hull
def hulldestroyed(self):
return not _hullToMegaWidget.has_key(self._hull)
def __str__(self):
return str(self._hull)
def cget(self, option):
# Get current configuration setting.
# Return the value of an option, for example myWidget['font'].
if self._optionInfo.has_key(option):
return self._optionInfo[option][_OPT_VALUE]
else:
index = string.find(option, '_')
if index >= 0:
component = option[:index]
componentOption = option[(index + 1):]
# Expand component alias
if self.__componentAliases.has_key(component):
component, subComponent = self.__componentAliases[component]
if subComponent is not None:
componentOption = subComponent + '_' + componentOption
# Expand option string to write on error
option = component + '_' + componentOption
if self.__componentInfo.has_key(component):
# Call cget on the component.
componentCget = self.__componentInfo[component][3]
return componentCget(componentOption)
else:
# If this is a group name, call cget for one of
# the components in the group.
for info in self.__componentInfo.values():
if info[4] == component:
componentCget = info[3]
return componentCget(componentOption)
raise KeyError, 'Unknown option "' + option + \
'" for ' + self.__class__.__name__
__getitem__ = cget
def isinitoption(self, option):
return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
def options(self):
options = []
if hasattr(self, '_optionInfo'):
for option, info in self._optionInfo.items():
isinit = info[_OPT_FUNCTION] is INITOPT
default = info[_OPT_DEFAULT]
options.append((option, default, isinit))
options.sort()
return options
def components(self):
# Return a list of all components.
# This list includes the 'hull' component and all widget subcomponents
names = self.__componentInfo.keys()
names.sort()
return names
def componentaliases(self):
# Return a list of all component aliases.
componentAliases = self.__componentAliases
names = componentAliases.keys()
names.sort()
rtn = []
for alias in names:
(mainComponent, subComponent) = componentAliases[alias]
if subComponent is None:
rtn.append((alias, mainComponent))
else:
rtn.append((alias, mainComponent + '_' + subComponent))
return rtn
def componentgroup(self, name):
return self.__componentInfo[name][4]
#=============================================================================
# The grab functions are mainly called by the activate() and
# deactivate() methods.
#
# Use pushgrab() to add a new window to the grab stack. This
# releases the grab by the window currently on top of the stack (if
# there is one) and gives the grab and focus to the new widget.
#
# To remove the grab from the window on top of the grab stack, call
# popgrab().
#
# Use releasegrabs() to release the grab and clear the grab stack.
def pushgrab(grabWindow, globalMode, deactivateFunction):
prevFocus = grabWindow.tk.call('focus')
grabInfo = {
'grabWindow' : grabWindow,
'globalMode' : globalMode,
'previousFocus' : prevFocus,
'deactivateFunction' : deactivateFunction,
}
_grabStack.append(grabInfo)
_grabtop()
grabWindow.focus_set()
def popgrab(window):
# Return the grab to the next window in the grab stack, if any.
# If this window is not at the top of the grab stack, then it has
# just been deleted by the window manager or deactivated by a
# timer. Call the deactivate method for the modal dialog above
# this one on the stack.
if _grabStack[-1]['grabWindow'] != window:
for index in range(len(_grabStack)):
if _grabStack[index]['grabWindow'] == window:
_grabStack[index + 1]['deactivateFunction']()
break
grabInfo = _grabStack[-1]
del _grabStack[-1]
topWidget = grabInfo['grabWindow']
prevFocus = grabInfo['previousFocus']
globalMode = grabInfo['globalMode']
if globalMode != 'nograb':
topWidget.grab_release()
if len(_grabStack) > 0:
_grabtop()
if prevFocus != '':
try:
topWidget.tk.call('focus', prevFocus)
except Tkinter.TclError:
# Previous focus widget has been deleted. Set focus
# to root window.
Tkinter._default_root.focus_set()
else:
# Make sure that focus does not remain on the released widget.
if len(_grabStack) > 0:
topWidget = _grabStack[-1]['grabWindow']
topWidget.focus_set()
else:
Tkinter._default_root.focus_set()
def grabstacktopwindow():
if len(_grabStack) == 0:
return None
else:
return _grabStack[-1]['grabWindow']
def releasegrabs():
# Release grab and clear the grab stack.
current = Tkinter._default_root.grab_current()
if current is not None:
current.grab_release()
_grabStack[:] = []
def _grabtop():
grabInfo = _grabStack[-1]
topWidget = grabInfo['grabWindow']
globalMode = grabInfo['globalMode']
if globalMode == 'nograb':
return
while 1:
try:
if globalMode:
topWidget.grab_set_global()
else:
topWidget.grab_set()
break
except Tkinter.TclError:
# Another application has grab. Keep trying until
# grab can succeed.
topWidget.after(100)
#=============================================================================
class MegaToplevel(MegaArchetype):
def __init__(self, parent = None, **kw):
# Define the options for this megawidget.
optiondefs = (
('activatecommand', None, None),
('deactivatecommand', None, None),
('master', None, None),
('title', None, self._settitle),
('hull_class', self.__class__.__name__, None),
)
self.defineoptions(kw, optiondefs)
# Initialise the base class (after defining the options).
MegaArchetype.__init__(self, parent, Tkinter.Toplevel)
# Initialise instance.
# Set WM_DELETE_WINDOW protocol, deleting any old callback, so
# memory does not leak.
if hasattr(self._hull, '_Pmw_WM_DELETE_name'):
self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name)
self._hull._Pmw_WM_DELETE_name = \
self.register(self._userDeleteWindow, needcleanup = 0)
self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name)
# Initialise instance variables.
self._firstShowing = 1
# Used by show() to ensure window retains previous position on screen.
# The IntVar() variable to wait on during a modal dialog.
self._wait = None
self._active = 0
self._userDeleteFunc = self.destroy
self._userModalDeleteFunc = self.deactivate
# Check keywords and initialise options.
self.initialiseoptions()
def _settitle(self):
title = self['title']
if title is not None:
self.title(title)
def userdeletefunc(self, func=None):
if func:
self._userDeleteFunc = func
else:
return self._userDeleteFunc
def usermodaldeletefunc(self, func=None):
if func:
self._userModalDeleteFunc = func
else:
return self._userModalDeleteFunc
def _userDeleteWindow(self):
if self.active():
self._userModalDeleteFunc()
else:
self._userDeleteFunc()
def destroy(self):
# Allow this to be called more than once.
if _hullToMegaWidget.has_key(self._hull):
self.deactivate()
# Remove circular references, so that object can get cleaned up.
del self._userDeleteFunc
del self._userModalDeleteFunc
MegaArchetype.destroy(self)
def show(self, master = None):
if self.state() != 'normal':
if self._firstShowing:
# Just let the window manager determine the window
# position for the first time.
geom = None
else:
# Position the window at the same place it was last time.
geom = self._sameposition()
setgeometryanddeiconify(self, geom)
if self._firstShowing:
self._firstShowing = 0
else:
if self.transient() == '':
self.tkraise()
# Do this last, otherwise get flashing on NT:
if master is not None:
if master == 'parent':
parent = self.winfo_parent()
# winfo_parent() should return the parent widget, but the
# the current version of Tkinter returns a string.
if type(parent) == types.StringType:
parent = self._hull._nametowidget(parent)
master = parent.winfo_toplevel()
self.transient(master)
self.focus()
def _centreonscreen(self):
# Centre the window on the screen. (Actually halfway across
# and one third down.)
parent = self.winfo_parent()
if type(parent) == types.StringType:
parent = self._hull._nametowidget(parent)
# Find size of window.
self.update_idletasks()
width = self.winfo_width()
height = self.winfo_height()
if width == 1 and height == 1:
# If the window has not yet been displayed, its size is
# reported as 1x1, so use requested size.
width = self.winfo_reqwidth()
height = self.winfo_reqheight()
# Place in centre of screen:
x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx()
y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty()
if x < 0:
x = 0
if y < 0:
y = 0
return '+%d+%d' % (x, y)
def _sameposition(self):
# Position the window at the same place it was last time.
geometry = self.geometry()
index = string.find(geometry, '+')
if index >= 0:
return geometry[index:]
else:
return None
def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
if self._active:
raise ValueError, 'Window is already active'
if self.state() == 'normal':
self.withdraw()
self._active = 1
showbusycursor()
if self._wait is None:
self._wait = Tkinter.IntVar()
self._wait.set(0)
if geometry == 'centerscreenalways':
geom = self._centreonscreen()
elif geometry == 'centerscreenfirst':
if self._firstShowing:
# Centre the window the first time it is displayed.
geom = self._centreonscreen()
else:
# Position the window at the same place it was last time.
geom = self._sameposition()
elif geometry[:5] == 'first':
if self._firstShowing:
geom = geometry[5:]
else:
# Position the window at the same place it was last time.
geom = self._sameposition()
else:
geom = geometry
self._firstShowing = 0
setgeometryanddeiconify(self, geom)
# Do this last, otherwise get flashing on NT:
master = self['master']
if master is not None:
if master == 'parent':
parent = self.winfo_parent()
# winfo_parent() should return the parent widget, but the
# the current version of Tkinter returns a string.
if type(parent) == types.StringType:
parent = self._hull._nametowidget(parent)
master = parent.winfo_toplevel()
self.transient(master)
pushgrab(self._hull, globalMode, self.deactivate)
command = self['activatecommand']
if callable(command):
command()
self.wait_variable(self._wait)
return self._result
def deactivate(self, result=None):
if not self._active:
return
self._active = 0
# Restore the focus before withdrawing the window, since
# otherwise the window manager may take the focus away so we
# can't redirect it. Also, return the grab to the next active
# window in the stack, if any.
popgrab(self._hull)
command = self['deactivatecommand']
if callable(command):
command()
self.withdraw()
hidebusycursor(forceFocusRestore = 1)
self._result = result
self._wait.set(1)
def active(self):
return self._active
forwardmethods(MegaToplevel, Tkinter.Toplevel, '_hull')
#=============================================================================
class MegaWidget(MegaArchetype):
def __init__(self, parent = None, **kw):
# Define the options for this megawidget.
optiondefs = (
('hull_class', self.__class__.__name__, None),
)
self.defineoptions(kw, optiondefs)
# Initialise the base class (after defining the options).
MegaArchetype.__init__(self, parent, Tkinter.Frame)
# Check keywords and initialise options.
self.initialiseoptions()
forwardmethods(MegaWidget, Tkinter.Frame, '_hull')
#=============================================================================
# Public functions
#-----------------
_traceTk = 0
def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
global _withStackTrace
global _traceTkFile
global _traceTk
if root is None:
root = Tkinter._default_root
_withStackTrace = withStackTrace
_traceTk = on
if on:
if hasattr(root.tk, '__class__'):
# Tracing already on
return
if file is None:
_traceTkFile = sys.stderr
else:
_traceTkFile = file
tk = _TraceTk(root.tk)
else:
if not hasattr(root.tk, '__class__'):
# Tracing already off
return
tk = root.tk.getTclInterp()
_setTkInterps(root, tk)
def showbusycursor():
_addRootToToplevelBusyInfo()
root = Tkinter._default_root
busyInfo = {
'newBusyWindows' : [],
'previousFocus' : None,
'busyFocus' : None,
}
_busyStack.append(busyInfo)
if _disableKeyboardWhileBusy:
# Remember the focus as it is now, before it is changed.
busyInfo['previousFocus'] = root.tk.call('focus')
if not _havebltbusy(root):
# No busy command, so don't call busy hold on any windows.
return
for (window, winInfo) in _toplevelBusyInfo.items():
if (window.state() != 'withdrawn' and not winInfo['isBusy']
and not winInfo['excludeFromBusy']):
busyInfo['newBusyWindows'].append(window)
winInfo['isBusy'] = 1
_busy_hold(window, winInfo['busyCursorName'])
# Make sure that no events for the busy window get
# through to Tkinter, otherwise it will crash in
# _nametowidget with a 'KeyError: _Busy' if there is
# a binding on the toplevel window.
window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag')
if _disableKeyboardWhileBusy:
# Remember previous focus widget for this toplevel window
# and set focus to the busy window, which will ignore all
# keyboard events.
winInfo['windowFocus'] = \
window.tk.call('focus', '-lastfor', window._w)
window.tk.call('focus', winInfo['busyWindow'])
busyInfo['busyFocus'] = winInfo['busyWindow']
if len(busyInfo['newBusyWindows']) > 0:
if os.name == 'nt':
# NT needs an "update" before it will change the cursor.
window.update()
else:
window.update_idletasks()
def hidebusycursor(forceFocusRestore = 0):
# Remember the focus as it is now, before it is changed.
root = Tkinter._default_root
if _disableKeyboardWhileBusy:
currentFocus = root.tk.call('focus')
# Pop the busy info off the stack.
busyInfo = _busyStack[-1]
del _busyStack[-1]
for window in busyInfo['newBusyWindows']:
# If this window has not been deleted, release the busy cursor.
if _toplevelBusyInfo.has_key(window):
winInfo = _toplevelBusyInfo[window]
winInfo['isBusy'] = 0
_busy_release(window)
if _disableKeyboardWhileBusy:
# Restore previous focus window for this toplevel window,
# but only if is still set to the busy window (it may have
# been changed).
windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
if windowFocusNow == winInfo['busyWindow']:
try:
window.tk.call('focus', winInfo['windowFocus'])
except Tkinter.TclError:
# Previous focus widget has been deleted. Set focus
# to toplevel window instead (can't leave focus on
# busy window).
window.focus_set()
if _disableKeyboardWhileBusy:
# Restore the focus, depending on whether the focus had changed
# between the calls to showbusycursor and hidebusycursor.
if forceFocusRestore or busyInfo['busyFocus'] == currentFocus:
# The focus had not changed, so restore it to as it was before
# the call to showbusycursor,
previousFocus = busyInfo['previousFocus']
if previousFocus is not None:
try:
root.tk.call('focus', previousFocus)
except Tkinter.TclError:
# Previous focus widget has been deleted; forget it.
pass
else:
# The focus had changed, so restore it to what it had been
# changed to before the call to hidebusycursor.
root.tk.call('focus', currentFocus)
def clearbusycursor():
while len(_busyStack) > 0:
hidebusycursor()
def setbusycursorattributes(window, **kw):
_addRootToToplevelBusyInfo()
for name, value in kw.items():
if name == 'exclude':
_toplevelBusyInfo[window]['excludeFromBusy'] = value
elif name == 'cursorName':
_toplevelBusyInfo[window]['busyCursorName'] = value
else:
raise KeyError, 'Unknown busycursor attribute "' + name + '"'
def _addRootToToplevelBusyInfo():
# Include the Tk root window in the list of toplevels. This must
# not be called before Tkinter has had a chance to be initialised by
# the application.
root = Tkinter._default_root
if root == None:
root = Tkinter.Tk()
if not _toplevelBusyInfo.has_key(root):
_addToplevelBusyInfo(root)
def busycallback(command, updateFunction = None):
if not callable(command):
raise ValueError, \
'cannot register non-command busy callback %s %s' % \
(repr(command), type(command))
wrapper = _BusyWrapper(command, updateFunction)
return wrapper.callback
_errorReportFile = None
_errorWindow = None
def reporterrorstofile(file = None):
global _errorReportFile
_errorReportFile = file
def displayerror(text):
global _errorWindow
if _errorReportFile is not None:
_errorReportFile.write(text + '\n')
else:
# Print error on standard error as well as to error window.
# Useful if error window fails to be displayed, for example
# when exception is triggered in a <Destroy> binding for root
# window.
sys.stderr.write(text + '\n')
if _errorWindow is None:
# The error window has not yet been created.
_errorWindow = _ErrorWindow()
_errorWindow.showerror(text)
_root = None
_disableKeyboardWhileBusy = 1
def initialise(
root = None,
size = None,
fontScheme = None,
useTkOptionDb = 0,
noBltBusy = 0,
disableKeyboardWhileBusy = None,
):
# Remember if show/hidebusycursor should ignore keyboard events.
global _disableKeyboardWhileBusy
if disableKeyboardWhileBusy is not None:
_disableKeyboardWhileBusy = disableKeyboardWhileBusy
# Do not use blt busy command if noBltBusy is set. Otherwise,
# use blt busy if it is available.
global _haveBltBusy
if noBltBusy:
_haveBltBusy = 0
# Save flag specifying whether the Tk option database should be
# queried when setting megawidget option default values.
global _useTkOptionDb
_useTkOptionDb = useTkOptionDb
# If we haven't been given a root window, use the default or
# create one.
if root is None:
if Tkinter._default_root is None:
root = Tkinter.Tk()
else:
root = Tkinter._default_root
# If this call is initialising a different Tk interpreter than the
# last call, then re-initialise all global variables. Assume the
# last interpreter has been destroyed - ie: Pmw does not (yet)
# support multiple simultaneous interpreters.
global _root
if _root is not None and _root != root:
global _busyStack
global _errorWindow
global _grabStack
global _hullToMegaWidget
global _toplevelBusyInfo
_busyStack = []
_errorWindow = None
_grabStack = []
_hullToMegaWidget = {}
_toplevelBusyInfo = {}
_root = root
# Trap Tkinter Toplevel constructors so that a list of Toplevels
# can be maintained.
Tkinter.Toplevel.title = __TkinterToplevelTitle
# Trap Tkinter widget destruction so that megawidgets can be
# destroyed when their hull widget is destoyed and the list of
# Toplevels can be pruned.
Tkinter.Toplevel.destroy = __TkinterToplevelDestroy
Tkinter.Widget.destroy = __TkinterWidgetDestroy
# Modify Tkinter's CallWrapper class to improve the display of
# errors which occur in callbacks.
Tkinter.CallWrapper = __TkinterCallWrapper
# Make sure we get to know when the window manager deletes the
# root window. Only do this if the protocol has not yet been set.
# This is required if there is a modal dialog displayed and the
# window manager deletes the root window. Otherwise the
# application will not exit, even though there are no windows.
if root.protocol('WM_DELETE_WINDOW') == '':
root.protocol('WM_DELETE_WINDOW', root.destroy)
# Set the base font size for the application and set the
# Tk option database font resources.
import PmwLogicalFont
PmwLogicalFont._font_initialise(root, size, fontScheme)
return root
def alignlabels(widgets, sticky = None):
if len(widgets) == 0:
return
widgets[0].update_idletasks()
# Determine the size of the maximum length label string.
maxLabelWidth = 0
for iwid in widgets:
labelWidth = iwid.grid_bbox(0, 1)[2]
if labelWidth > maxLabelWidth:
maxLabelWidth = labelWidth
# Adjust the margins for the labels such that the child sites and
# labels line up.
for iwid in widgets:
if sticky is not None:
iwid.component('label').grid(sticky=sticky)
iwid.grid_columnconfigure(0, minsize = maxLabelWidth)
#=============================================================================
# Private routines
#-----------------
_callToTkReturned = 1
_recursionCounter = 1
class _TraceTk:
def __init__(self, tclInterp):
self.tclInterp = tclInterp
def getTclInterp(self):
return self.tclInterp
# Calling from python into Tk.
def call(self, *args, **kw):
global _callToTkReturned
global _recursionCounter
_callToTkReturned = 0
if len(args) == 1 and type(args[0]) == types.TupleType:
argStr = str(args[0])
else:
argStr = str(args)
_traceTkFile.write('CALL TK> %d:%s%s' %
(_recursionCounter, ' ' * _recursionCounter, argStr))
_recursionCounter = _recursionCounter + 1
try:
result = apply(self.tclInterp.call, args, kw)
except Tkinter.TclError, errorString:
_callToTkReturned = 1
_recursionCounter = _recursionCounter - 1
_traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' %
(_recursionCounter, ' ' * _recursionCounter,
repr(errorString)))
if _withStackTrace:
_traceTkFile.write('CALL TK> stack:\n')
traceback.print_stack()
raise Tkinter.TclError, errorString
_recursionCounter = _recursionCounter - 1
if _callToTkReturned:
_traceTkFile.write('CALL RTN> %d:%s-> %s' %
(_recursionCounter, ' ' * _recursionCounter, repr(result)))
else:
_callToTkReturned = 1
if result:
_traceTkFile.write(' -> %s' % repr(result))
_traceTkFile.write('\n')
if _withStackTrace:
_traceTkFile.write('CALL TK> stack:\n')
traceback.print_stack()
_traceTkFile.flush()
return result
def __getattr__(self, key):
return getattr(self.tclInterp, key)
def _setTkInterps(window, tk):
window.tk = tk
for child in window.children.values():
_setTkInterps(child, tk)
#=============================================================================
# Functions to display a busy cursor. Keep a list of all toplevels
# and display the busy cursor over them. The list will contain the Tk
# root toplevel window as well as all other toplevel windows.
# Also keep a list of the widget which last had focus for each
# toplevel.
# Map from toplevel windows to
# {'isBusy', 'windowFocus', 'busyWindow',
# 'excludeFromBusy', 'busyCursorName'}
_toplevelBusyInfo = {}
# Pmw needs to know all toplevel windows, so that it can call blt busy
# on them. This is a hack so we get notified when a Tk topevel is
# created. Ideally, the __init__ 'method' should be overridden, but
# it is a 'read-only special attribute'. Luckily, title() is always
# called from the Tkinter Toplevel constructor.
def _addToplevelBusyInfo(window):
if window._w == '.':
busyWindow = '._Busy'
else:
busyWindow = window._w + '._Busy'
_toplevelBusyInfo[window] = {
'isBusy' : 0,
'windowFocus' : None,
'busyWindow' : busyWindow,
'excludeFromBusy' : 0,
'busyCursorName' : None,
}
def __TkinterToplevelTitle(self, *args):
# If this is being called from the constructor, include this
# Toplevel in the list of toplevels and set the initial
# WM_DELETE_WINDOW protocol to destroy() so that we get to know
# about it.
if not _toplevelBusyInfo.has_key(self):
_addToplevelBusyInfo(self)
self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0)
self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name)
return apply(Tkinter.Wm.title, (self,) + args)
_haveBltBusy = None
def _havebltbusy(window):
global _busy_hold, _busy_release, _haveBltBusy
if _haveBltBusy is None:
import PmwBlt
_haveBltBusy = PmwBlt.havebltbusy(window)
_busy_hold = PmwBlt.busy_hold
if os.name == 'nt':
# There is a bug in Blt 2.4i on NT where the busy window
# does not follow changes in the children of a window.
# Using forget works around the problem.
_busy_release = PmwBlt.busy_forget
else:
_busy_release = PmwBlt.busy_release
return _haveBltBusy
class _BusyWrapper:
def __init__(self, command, updateFunction):
self._command = command
self._updateFunction = updateFunction
def callback(self, *args):
showbusycursor()
rtn = apply(self._command, args)
# Call update before hiding the busy windows to clear any
# events that may have occurred over the busy windows.
if callable(self._updateFunction):
self._updateFunction()
hidebusycursor()
return rtn
#=============================================================================
def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
canvas.delete(tag)
bw = (string.atoi(canvas['borderwidth']) +
string.atoi(canvas['highlightthickness']))
width = string.atoi(canvas['width'])
height = string.atoi(canvas['height'])
if direction in ('up', 'down'):
majorDimension = height
minorDimension = width
else:
majorDimension = width
minorDimension = height
offset = round(baseOffset * majorDimension)
if direction in ('down', 'right'):
base = bw + offset
apex = bw + majorDimension - offset
else:
base = bw + majorDimension - offset
apex = bw + offset
if minorDimension > 3 and minorDimension % 2 == 0:
minorDimension = minorDimension - 1
half = int(minorDimension * (1 - 2 * edgeOffset)) / 2
low = round(bw + edgeOffset * minorDimension)
middle = low + half
high = low + 2 * half
if direction in ('up', 'down'):
coords = (low, base, high, base, middle, apex)
else:
coords = (base, low, base, high, apex, middle)
kw = {'fill' : color, 'outline' : color, 'tag' : tag}
apply(canvas.create_polygon, coords, kw)
#=============================================================================
# Modify the Tkinter destroy methods so that it notifies us when a Tk
# toplevel or frame is destroyed.
# A map from the 'hull' component of a megawidget to the megawidget.
# This is used to clean up a megawidget when its hull is destroyed.
_hullToMegaWidget = {}
def __TkinterToplevelDestroy(tkWidget):
if _hullToMegaWidget.has_key(tkWidget):
mega = _hullToMegaWidget[tkWidget]
try:
mega.destroy()
except:
_reporterror(mega.destroy, ())
else:
# Delete the busy info structure for this toplevel (if the
# window was created before Pmw.initialise() was called, it
# will not have any.
if _toplevelBusyInfo.has_key(tkWidget):
del _toplevelBusyInfo[tkWidget]
if hasattr(tkWidget, '_Pmw_WM_DELETE_name'):
tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name)
del tkWidget._Pmw_WM_DELETE_name
Tkinter.BaseWidget.destroy(tkWidget)
def __TkinterWidgetDestroy(tkWidget):
if _hullToMegaWidget.has_key(tkWidget):
mega = _hullToMegaWidget[tkWidget]
try:
mega.destroy()
except:
_reporterror(mega.destroy, ())
else:
Tkinter.BaseWidget.destroy(tkWidget)
#=============================================================================
# Add code to Tkinter to improve the display of errors which occur in
# callbacks.
class __TkinterCallWrapper:
def __init__(self, func, subst, widget):
self.func = func
self.subst = subst
self.widget = widget
# Calling back from Tk into python.
def __call__(self, *args):
try:
if self.subst:
args = apply(self.subst, args)
if _traceTk:
if not _callToTkReturned:
_traceTkFile.write('\n')
if hasattr(self.func, 'im_class'):
name = self.func.im_class.__name__ + '.' + \
self.func.__name__
else:
name = self.func.__name__
if len(args) == 1 and hasattr(args[0], 'type'):
# The argument to the callback is an event.
eventName = _eventTypeToName[string.atoi(args[0].type)]
if eventName in ('KeyPress', 'KeyRelease',):
argStr = '(%s %s Event: %s)' % \
(eventName, args[0].keysym, args[0].widget)
else:
argStr = '(%s Event, %s)' % (eventName, args[0].widget)
else:
argStr = str(args)
_traceTkFile.write('CALLBACK> %d:%s%s%s\n' %
(_recursionCounter, ' ' * _recursionCounter, name, argStr))
_traceTkFile.flush()
return apply(self.func, args)
except SystemExit, msg:
raise SystemExit, msg
except:
_reporterror(self.func, args)
_eventTypeToName = {
2 : 'KeyPress', 15 : 'VisibilityNotify', 28 : 'PropertyNotify',
3 : 'KeyRelease', 16 : 'CreateNotify', 29 : 'SelectionClear',
4 : 'ButtonPress', 17 : 'DestroyNotify', 30 : 'SelectionRequest',
5 : 'ButtonRelease', 18 : 'UnmapNotify', 31 : 'SelectionNotify',
6 : 'MotionNotify', 19 : 'MapNotify', 32 : 'ColormapNotify',
7 : 'EnterNotify', 20 : 'MapRequest', 33 : 'ClientMessage',
8 : 'LeaveNotify', 21 : 'ReparentNotify', 34 : 'MappingNotify',
9 : 'FocusIn', 22 : 'ConfigureNotify', 35 : 'VirtualEvents',
10 : 'FocusOut', 23 : 'ConfigureRequest', 36 : 'ActivateNotify',
11 : 'KeymapNotify', 24 : 'GravityNotify', 37 : 'DeactivateNotify',
12 : 'Expose', 25 : 'ResizeRequest', 38 : 'MouseWheelEvent',
13 : 'GraphicsExpose', 26 : 'CirculateNotify',
14 : 'NoExpose', 27 : 'CirculateRequest',
}
def _reporterror(func, args):
# Fetch current exception values.
exc_type, exc_value, exc_traceback = sys.exc_info()
# Give basic information about the callback exception.
if type(exc_type) == types.ClassType:
# Handle python 1.5 class exceptions.
exc_type = exc_type.__name__
msg = exc_type + ' Exception in Tk callback\n'
msg = msg + ' Function: %s (type: %s)\n' % (repr(func), type(func))
msg = msg + ' Args: %s\n' % str(args)
if type(args) == types.TupleType and len(args) > 0 and \
hasattr(args[0], 'type'):
eventArg = 1
else:
eventArg = 0
# If the argument to the callback is an event, add the event type.
if eventArg:
eventNum = string.atoi(args[0].type)
if eventNum in _eventTypeToName.keys():
msg = msg + ' Event type: %s (type num: %d)\n' % \
(_eventTypeToName[eventNum], eventNum)
else:
msg = msg + ' Unknown event type (type num: %d)\n' % eventNum
# Add the traceback.
msg = msg + 'Traceback (innermost last):\n'
for tr in traceback.extract_tb(exc_traceback):
msg = msg + ' File "%s", line %s, in %s\n' % (tr[0], tr[1], tr[2])
msg = msg + ' %s\n' % tr[3]
msg = msg + '%s: %s\n' % (exc_type, exc_value)
# If the argument to the callback is an event, add the event contents.
if eventArg:
msg = msg + '\n================================================\n'
msg = msg + ' Event contents:\n'
keys = args[0].__dict__.keys()
keys.sort()
for key in keys:
msg = msg + ' %s: %s\n' % (key, args[0].__dict__[key])
clearbusycursor()
try:
displayerror(msg)
except:
pass
class _ErrorWindow:
def __init__(self):
self._errorQueue = []
self._errorCount = 0
self._open = 0
self._firstShowing = 1
# Create the toplevel window
self._top = Tkinter.Toplevel()
self._top.protocol('WM_DELETE_WINDOW', self._hide)
self._top.title('Error in background function')
self._top.iconname('Background error')
# Create the text widget and scrollbar in a frame
upperframe = Tkinter.Frame(self._top)
scrollbar = Tkinter.Scrollbar(upperframe, orient='vertical')
scrollbar.pack(side = 'right', fill = 'y')
self._text = Tkinter.Text(upperframe, yscrollcommand=scrollbar.set)
self._text.pack(fill = 'both', expand = 1)
scrollbar.configure(command=self._text.yview)
# Create the buttons and label in a frame
lowerframe = Tkinter.Frame(self._top)
ignore = Tkinter.Button(lowerframe,
text = 'Ignore remaining errors', command = self._hide)
ignore.pack(side='left')
self._nextError = Tkinter.Button(lowerframe,
text = 'Show next error', command = self._next)
self._nextError.pack(side='left')
self._label = Tkinter.Label(lowerframe, relief='ridge')
self._label.pack(side='left', fill='x', expand=1)
# Pack the lower frame first so that it does not disappear
# when the window is resized.
lowerframe.pack(side = 'bottom', fill = 'x')
upperframe.pack(side = 'bottom', fill = 'both', expand = 1)
def showerror(self, text):
if self._open:
self._errorQueue.append(text)
else:
self._display(text)
self._open = 1
# Display the error window in the same place it was before.
if self._top.state() == 'normal':
# If update_idletasks is not called here, the window may
# be placed partially off the screen. Also, if it is not
# called and many errors are generated quickly in
# succession, the error window may not display errors
# until the last one is generated and the interpreter
# becomes idle.
# XXX: remove this, since it causes omppython to go into an
# infinite loop if an error occurs in an omp callback.
# self._top.update_idletasks()
pass
else:
if self._firstShowing:
geom = None
else:
geometry = self._top.geometry()
index = string.find(geometry, '+')
if index >= 0:
geom = geometry[index:]
else:
geom = None
setgeometryanddeiconify(self._top, geom)
if self._firstShowing:
self._firstShowing = 0
else:
self._top.tkraise()
self._top.focus()
self._updateButtons()
# Release any grab, so that buttons in the error window work.
releasegrabs()
def _hide(self):
self._errorCount = self._errorCount + len(self._errorQueue)
self._errorQueue = []
self._top.withdraw()
self._open = 0
def _next(self):
# Display the next error in the queue.
text = self._errorQueue[0]
del self._errorQueue[0]
self._display(text)
self._updateButtons()
def _display(self, text):
self._errorCount = self._errorCount + 1
text = 'Error: %d\n%s' % (self._errorCount, text)
self._text.delete('1.0', 'end')
self._text.insert('end', text)
def _updateButtons(self):
numQueued = len(self._errorQueue)
if numQueued > 0:
self._label.configure(text='%d more errors' % numQueued)
self._nextError.configure(state='normal')
else:
self._label.configure(text='No more errors')
self._nextError.configure(state='disabled')
|