nltk 3.2__zip → 3.3__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
- {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
- {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
- {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
- nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
- nltk-3.3/nltk/VERSION +1 -0
- {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
- {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
- {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
- {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
- {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
- {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
- {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
- {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
- {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
- {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
- {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
- {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
- {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
- {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
- {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
- {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
- {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
- nltk-3.3/nltk/ccg/logic.py +46 -0
- {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
- {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
- {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
- {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
- {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
- {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
- {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
- {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
- {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
- {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
- {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
- {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
- {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
- {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
- {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
- {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
- {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
- {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
- {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
- {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
- {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
- {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
- {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
- {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
- {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
- {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
- {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
- {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
- {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
- {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
- {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
- {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
- {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
- nltk-3.3/nltk/collections.py +640 -0
- {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
- nltk-3.3/nltk/compat.py +365 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
- {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
- nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
- nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
- nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
- {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
- {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
- {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
- {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
- {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
- {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
- {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
- {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
- {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
- {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
- {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
- {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
- {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
- {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
- {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
- {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
- {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
- {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
- {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
- {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
- {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
- {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
- {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
- {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
- {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
- {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
- nltk-3.3/nltk/metrics/aline.py +607 -0
- {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
- {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
- {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
- {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
- {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
- {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
- {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
- {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
- {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
- {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
- {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
- {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
- {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
- {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
- {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
- {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
- {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
- nltk-3.3/nltk/parse/corenlp.py +780 -0
- {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
- {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
- {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
- {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
- {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
- {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
- {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
- {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
- {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
- {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
- {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
- {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
- {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
- {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
- {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
- {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
- {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
- {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
- {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
- {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
- {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
- {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
- {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
- {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
- {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
- {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
- {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
- {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
- {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
- {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
- {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
- {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
- nltk-3.3/nltk/stem/arlstem.py +369 -0
- {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
- {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
- nltk-3.3/nltk/stem/porter.py +710 -0
- {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
- {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
- {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
- {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
- {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
- {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
- {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
- {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
- {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
- {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
- {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
- {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
- {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
- {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
- {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
- {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
- {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
- {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
- {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
- {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
- {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
- {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
- {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
- {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
- {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
- {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
- {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
- {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
- {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
- {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
- nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
- {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
- {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
- {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
- {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
- {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
- nltk-3.3/nltk/test/collections.doctest +20 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
- {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
- {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
- nltk-3.3/nltk/test/concordance.doctest +55 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
- {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
- {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
- {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
- {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
- {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
- {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
- {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
- {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
- {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
- {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
- {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
- {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
- {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
- {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
- {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
- {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
- {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
- {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
- {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
- {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
- {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
- {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
- {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
- {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
- {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
- {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
- {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
- {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
- {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
- {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
- {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
- {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
- {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
- {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
- {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
- {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
- {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
- {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
- {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
- {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
- {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
- {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
- {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
- {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
- {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
- {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
- nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
- nltk-3.3/nltk/test/unit/test_aline.py +52 -0
- nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
- nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
- nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
- nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
- nltk-3.3/nltk/test/unit/test_senna.py +74 -0
- nltk-3.3/nltk/test/unit/test_stem.py +121 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
- nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
- nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
- nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
- nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
- nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
- {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
- {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
- {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
- nltk-3.3/nltk/tokenize/nist.py +173 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
- nltk-3.3/nltk/tokenize/repp.py +151 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
- nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
- nltk-3.3/nltk/tokenize/toktok.py +155 -0
- nltk-3.3/nltk/tokenize/treebank.py +363 -0
- nltk-3.3/nltk/tokenize/util.py +261 -0
- {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
- {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
- {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
- {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
- nltk-3.3/nltk/translate/chrf_score.py +137 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
- {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
- nltk-3.3/nltk/translate/gleu_score.py +193 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
- {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
- nltk-3.3/nltk/translate/nist_score.py +178 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
- {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
- {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
- {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
- {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
- {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
- {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
- {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
- {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
- {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
- {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
- {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
- nltk-3.3/nltk/util.py +751 -0
- {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
- {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
- nltk-3.3/nltk.egg-info/requires.txt +31 -0
- {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
- {nltk-3.2 → nltk-3.3}/setup.py +102 -75
- nltk-3.2/nltk/VERSION +0 -1
- nltk-3.2/nltk/compat.py +0 -680
- nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
- nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
- nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
- nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
- nltk-3.2/nltk/six.py +0 -868
- nltk-3.2/nltk/stem/porter.py +0 -692
- nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
- nltk-3.2/nltk/test/unit/test_stem.py +0 -36
- nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
- nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
- nltk-3.2/nltk/tokenize/treebank.py +0 -124
- nltk-3.2/nltk/tokenize/util.py +0 -90
- nltk-3.2/nltk/util.py +0 -1364
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
|
@@ -1,1098 +1,1098 @@
|
|
|
1
|
-
# Natural Language Toolkit: Table widget
|
|
2
|
-
#
|
|
3
|
-
# Copyright (C) 2001-
|
|
4
|
-
# Author: Edward Loper <edloper@gmail.com>
|
|
5
|
-
# URL: <http://nltk.org/>
|
|
6
|
-
# For license information, see LICENSE.TXT
|
|
7
|
-
|
|
8
|
-
"""
|
|
9
|
-
Tkinter widgets for displaying multi-column listboxes and tables.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import division
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import operator
|
|
16
|
-
|
|
17
|
-
from tkinter import (Frame, Label, Listbox, Scrollbar, Tk)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
######################################################################
|
|
21
|
-
# Multi-Column Listbox
|
|
22
|
-
######################################################################
|
|
23
|
-
|
|
24
|
-
class MultiListbox(Frame):
|
|
25
|
-
"""
|
|
26
|
-
A multi-column listbox, where the current selection applies to an
|
|
27
|
-
entire row. Based on the MultiListbox Tkinter widget
|
|
28
|
-
recipe from the Python Cookbook (http://code.activestate.com/recipes/52266/)
|
|
29
|
-
|
|
30
|
-
For the most part, ``MultiListbox`` methods delegate to its
|
|
31
|
-
contained listboxes. For any methods that do not have docstrings,
|
|
32
|
-
see ``Tkinter.Listbox`` for a description of what that method does.
|
|
33
|
-
"""
|
|
34
|
-
#/////////////////////////////////////////////////////////////////
|
|
35
|
-
# Configuration
|
|
36
|
-
#/////////////////////////////////////////////////////////////////
|
|
37
|
-
|
|
38
|
-
#: Default configuration values for the frame.
|
|
39
|
-
FRAME_CONFIG = dict(background='#888',
|
|
40
|
-
takefocus=True,
|
|
41
|
-
highlightthickness=1)
|
|
42
|
-
|
|
43
|
-
#: Default configurations for the column labels.
|
|
44
|
-
LABEL_CONFIG = dict(borderwidth=1, relief='raised',
|
|
45
|
-
font='helvetica -16 bold',
|
|
46
|
-
background='#444', foreground='white')
|
|
47
|
-
|
|
48
|
-
#: Default configuration for the column listboxes.
|
|
49
|
-
LISTBOX_CONFIG = dict(borderwidth=1,
|
|
50
|
-
selectborderwidth=0,
|
|
51
|
-
highlightthickness=0,
|
|
52
|
-
exportselection=False,
|
|
53
|
-
selectbackground='#888',
|
|
54
|
-
activestyle='none',
|
|
55
|
-
takefocus=False)
|
|
56
|
-
|
|
57
|
-
#/////////////////////////////////////////////////////////////////
|
|
58
|
-
# Constructor
|
|
59
|
-
#/////////////////////////////////////////////////////////////////
|
|
60
|
-
|
|
61
|
-
def __init__(self, master, columns, column_weights=None, cnf={}, **kw):
|
|
62
|
-
"""
|
|
63
|
-
Construct a new multi-column listbox widget.
|
|
64
|
-
|
|
65
|
-
:param master: The widget that should contain the new
|
|
66
|
-
multi-column listbox.
|
|
67
|
-
|
|
68
|
-
:param columns: Specifies what columns should be included in
|
|
69
|
-
the new multi-column listbox. If ``columns`` is an integer,
|
|
70
|
-
the it is the number of columns to include. If it is
|
|
71
|
-
a list, then its length indicates the number of columns
|
|
72
|
-
to include; and each element of the list will be used as
|
|
73
|
-
a label for the corresponding column.
|
|
74
|
-
|
|
75
|
-
:param cnf, kw: Configuration parameters for this widget.
|
|
76
|
-
Use ``label_*`` to configure all labels; and ``listbox_*``
|
|
77
|
-
to configure all listboxes. E.g.:
|
|
78
|
-
|
|
79
|
-
>>> mlb = MultiListbox(master, 5, label_foreground='red')
|
|
80
|
-
"""
|
|
81
|
-
# If columns was specified as an int, convert it to a list.
|
|
82
|
-
if isinstance(columns, int):
|
|
83
|
-
columns = list(range(columns))
|
|
84
|
-
include_labels = False
|
|
85
|
-
else:
|
|
86
|
-
include_labels = True
|
|
87
|
-
|
|
88
|
-
if len(columns) == 0:
|
|
89
|
-
raise ValueError("Expected at least one column")
|
|
90
|
-
|
|
91
|
-
# Instance variables
|
|
92
|
-
self._column_names = tuple(columns)
|
|
93
|
-
self._listboxes = []
|
|
94
|
-
self._labels = []
|
|
95
|
-
|
|
96
|
-
# Pick a default value for column_weights, if none was specified.
|
|
97
|
-
if column_weights is None:
|
|
98
|
-
column_weights = [1] * len(columns)
|
|
99
|
-
elif len(column_weights) != len(columns):
|
|
100
|
-
raise ValueError('Expected one column_weight for each column')
|
|
101
|
-
self._column_weights = column_weights
|
|
102
|
-
|
|
103
|
-
# Configure our widgets.
|
|
104
|
-
Frame.__init__(self, master, **self.FRAME_CONFIG)
|
|
105
|
-
self.grid_rowconfigure(1, weight=1)
|
|
106
|
-
for i, label in enumerate(self._column_names):
|
|
107
|
-
self.grid_columnconfigure(i, weight=column_weights[i])
|
|
108
|
-
|
|
109
|
-
# Create a label for the column
|
|
110
|
-
if include_labels:
|
|
111
|
-
l = Label(self, text=label, **self.LABEL_CONFIG)
|
|
112
|
-
self._labels.append(l)
|
|
113
|
-
l.grid(column=i, row=0, sticky='news', padx=0, pady=0)
|
|
114
|
-
l.column_index = i
|
|
115
|
-
|
|
116
|
-
# Create a listbox for the column
|
|
117
|
-
lb = Listbox(self, **self.LISTBOX_CONFIG)
|
|
118
|
-
self._listboxes.append(lb)
|
|
119
|
-
lb.grid(column=i, row=1, sticky='news', padx=0, pady=0)
|
|
120
|
-
lb.column_index = i
|
|
121
|
-
|
|
122
|
-
# Clicking or dragging selects:
|
|
123
|
-
lb.bind('<Button-1>', self._select)
|
|
124
|
-
lb.bind('<B1-Motion>', self._select)
|
|
125
|
-
# Scroll whell scrolls:
|
|
126
|
-
lb.bind('<Button-4>', lambda e: self._scroll(-1))
|
|
127
|
-
lb.bind('<Button-5>', lambda e: self._scroll(+1))
|
|
128
|
-
lb.bind('<MouseWheel>', lambda e: self._scroll(e.delta))
|
|
129
|
-
# Button 2 can be used to scan:
|
|
130
|
-
lb.bind('<Button-2>', lambda e: self.scan_mark(e.x, e.y))
|
|
131
|
-
lb.bind('<B2-Motion>', lambda e: self.scan_dragto(e.x, e.y))
|
|
132
|
-
# Dragging outside the window has no effect (diable
|
|
133
|
-
# the default listbox behavior, which scrolls):
|
|
134
|
-
lb.bind('<B1-Leave>', lambda e: 'break')
|
|
135
|
-
# Columns can be resized by dragging them:
|
|
136
|
-
l.bind('<Button-1>', self._resize_column)
|
|
137
|
-
|
|
138
|
-
# Columns can be resized by dragging them. (This binding is
|
|
139
|
-
# used if they click on the grid between columns:)
|
|
140
|
-
self.bind('<Button-1>', self._resize_column)
|
|
141
|
-
|
|
142
|
-
# Set up key bindings for the widget:
|
|
143
|
-
self.bind('<Up>', lambda e: self.select(delta=-1))
|
|
144
|
-
self.bind('<Down>', lambda e: self.select(delta=1))
|
|
145
|
-
self.bind('<Prior>', lambda e: self.select(delta=-self._pagesize()))
|
|
146
|
-
self.bind('<Next>', lambda e: self.select(delta=self._pagesize()))
|
|
147
|
-
|
|
148
|
-
# Configuration customizations
|
|
149
|
-
self.configure(cnf, **kw)
|
|
150
|
-
|
|
151
|
-
#/////////////////////////////////////////////////////////////////
|
|
152
|
-
# Column Resizing
|
|
153
|
-
#/////////////////////////////////////////////////////////////////
|
|
154
|
-
|
|
155
|
-
def _resize_column(self, event):
|
|
156
|
-
"""
|
|
157
|
-
Callback used to resize a column of the table. Return ``True``
|
|
158
|
-
if the column is actually getting resized (if the user clicked
|
|
159
|
-
on the far left or far right 5 pixels of a label); and
|
|
160
|
-
``False`` otherwies.
|
|
161
|
-
"""
|
|
162
|
-
# If we're already waiting for a button release, then ignore
|
|
163
|
-
# the new button press.
|
|
164
|
-
if event.widget.bind('<ButtonRelease>'):
|
|
165
|
-
return False
|
|
166
|
-
|
|
167
|
-
# Decide which column (if any) to resize.
|
|
168
|
-
self._resize_column_index = None
|
|
169
|
-
if event.widget is self:
|
|
170
|
-
for i, lb in enumerate(self._listboxes):
|
|
171
|
-
if abs(event.x-(lb.winfo_x()+lb.winfo_width())) < 10:
|
|
172
|
-
self._resize_column_index = i
|
|
173
|
-
elif event.x > (event.widget.winfo_width()-5):
|
|
174
|
-
self._resize_column_index = event.widget.column_index
|
|
175
|
-
elif event.x < 5 and event.widget.column_index != 0:
|
|
176
|
-
self._resize_column_index = event.widget.column_index-1
|
|
177
|
-
|
|
178
|
-
# Bind callbacks that are used to resize it.
|
|
179
|
-
if self._resize_column_index is not None:
|
|
180
|
-
event.widget.bind('<Motion>', self._resize_column_motion_cb)
|
|
181
|
-
event.widget.bind('<ButtonRelease-%d>' % event.num,
|
|
182
|
-
self._resize_column_buttonrelease_cb)
|
|
183
|
-
return True
|
|
184
|
-
else:
|
|
185
|
-
return False
|
|
186
|
-
|
|
187
|
-
def _resize_column_motion_cb(self, event):
|
|
188
|
-
lb = self._listboxes[self._resize_column_index]
|
|
189
|
-
charwidth = lb.winfo_width() / lb['width']
|
|
190
|
-
|
|
191
|
-
x1 = event.x + event.widget.winfo_x()
|
|
192
|
-
x2 = lb.winfo_x() + lb.winfo_width()
|
|
193
|
-
|
|
194
|
-
lb['width'] = max(3, lb['width'] + (x1-x2) // charwidth)
|
|
195
|
-
|
|
196
|
-
def _resize_column_buttonrelease_cb(self, event):
|
|
197
|
-
event.widget.unbind('<ButtonRelease-%d>' % event.num)
|
|
198
|
-
event.widget.unbind('<Motion>')
|
|
199
|
-
|
|
200
|
-
#/////////////////////////////////////////////////////////////////
|
|
201
|
-
# Properties
|
|
202
|
-
#/////////////////////////////////////////////////////////////////
|
|
203
|
-
|
|
204
|
-
@property
|
|
205
|
-
def column_names(self):
|
|
206
|
-
"""
|
|
207
|
-
A tuple containing the names of the columns used by this
|
|
208
|
-
multi-column listbox.
|
|
209
|
-
"""
|
|
210
|
-
return self._column_names
|
|
211
|
-
|
|
212
|
-
@property
|
|
213
|
-
def column_labels(self):
|
|
214
|
-
"""
|
|
215
|
-
A tuple containing the ``Tkinter.Label`` widgets used to
|
|
216
|
-
display the label of each column. If this multi-column
|
|
217
|
-
listbox was created without labels, then this will be an empty
|
|
218
|
-
tuple. These widgets will all be augmented with a
|
|
219
|
-
``column_index`` attribute, which can be used to determine
|
|
220
|
-
which column they correspond to. This can be convenient,
|
|
221
|
-
e.g., when defining callbacks for bound events.
|
|
222
|
-
"""
|
|
223
|
-
return tuple(self._labels)
|
|
224
|
-
|
|
225
|
-
@property
|
|
226
|
-
def listboxes(self):
|
|
227
|
-
"""
|
|
228
|
-
A tuple containing the ``Tkinter.Listbox`` widgets used to
|
|
229
|
-
display individual columns. These widgets will all be
|
|
230
|
-
augmented with a ``column_index`` attribute, which can be used
|
|
231
|
-
to determine which column they correspond to. This can be
|
|
232
|
-
convenient, e.g., when defining callbacks for bound events.
|
|
233
|
-
"""
|
|
234
|
-
return tuple(self._listboxes)
|
|
235
|
-
|
|
236
|
-
#/////////////////////////////////////////////////////////////////
|
|
237
|
-
# Mouse & Keyboard Callback Functions
|
|
238
|
-
#/////////////////////////////////////////////////////////////////
|
|
239
|
-
|
|
240
|
-
def _select(self, e):
|
|
241
|
-
i = e.widget.nearest(e.y)
|
|
242
|
-
self.selection_clear(0, 'end')
|
|
243
|
-
self.selection_set(i)
|
|
244
|
-
self.activate(i)
|
|
245
|
-
self.focus()
|
|
246
|
-
|
|
247
|
-
def _scroll(self, delta):
|
|
248
|
-
for lb in self._listboxes:
|
|
249
|
-
lb.yview_scroll(delta, 'unit')
|
|
250
|
-
return 'break'
|
|
251
|
-
|
|
252
|
-
def _pagesize(self):
|
|
253
|
-
""":return: The number of rows that makes up one page"""
|
|
254
|
-
return int(self.index('@0,1000000')) - int(self.index('@0,0'))
|
|
255
|
-
|
|
256
|
-
#/////////////////////////////////////////////////////////////////
|
|
257
|
-
# Row selection
|
|
258
|
-
#/////////////////////////////////////////////////////////////////
|
|
259
|
-
|
|
260
|
-
def select(self, index=None, delta=None, see=True):
|
|
261
|
-
"""
|
|
262
|
-
Set the selected row. If ``index`` is specified, then select
|
|
263
|
-
row ``index``. Otherwise, if ``delta`` is specified, then move
|
|
264
|
-
the current selection by ``delta`` (negative numbers for up,
|
|
265
|
-
positive numbers for down). This will not move the selection
|
|
266
|
-
past the top or the bottom of the list.
|
|
267
|
-
|
|
268
|
-
:param see: If true, then call ``self.see()`` with the newly
|
|
269
|
-
selected index, to ensure that it is visible.
|
|
270
|
-
"""
|
|
271
|
-
if (index is not None) and (delta is not None):
|
|
272
|
-
raise ValueError('specify index or delta, but not both')
|
|
273
|
-
|
|
274
|
-
# If delta was given, then calculate index.
|
|
275
|
-
if delta is not None:
|
|
276
|
-
if len(self.curselection()) == 0:
|
|
277
|
-
index = -1 + delta
|
|
278
|
-
else:
|
|
279
|
-
index = int(self.curselection()[0]) + delta
|
|
280
|
-
|
|
281
|
-
# Clear all selected rows.
|
|
282
|
-
self.selection_clear(0, 'end')
|
|
283
|
-
|
|
284
|
-
# Select the specified index
|
|
285
|
-
if index is not None:
|
|
286
|
-
index = min(max(index, 0), self.size()-1)
|
|
287
|
-
#self.activate(index)
|
|
288
|
-
self.selection_set(index)
|
|
289
|
-
if see: self.see(index)
|
|
290
|
-
|
|
291
|
-
#/////////////////////////////////////////////////////////////////
|
|
292
|
-
# Configuration
|
|
293
|
-
#/////////////////////////////////////////////////////////////////
|
|
294
|
-
|
|
295
|
-
def configure(self, cnf={}, **kw):
|
|
296
|
-
"""
|
|
297
|
-
Configure this widget. Use ``label_*`` to configure all
|
|
298
|
-
labels; and ``listbox_*`` to configure all listboxes. E.g.:
|
|
299
|
-
|
|
300
|
-
>>> mlb = MultiListbox(master, 5)
|
|
301
|
-
>>> mlb.configure(label_foreground='red')
|
|
302
|
-
>>> mlb.configure(listbox_foreground='red')
|
|
303
|
-
"""
|
|
304
|
-
cnf = dict(list(cnf.items()) + list(kw.items()))
|
|
305
|
-
for (key, val) in list(cnf.items()):
|
|
306
|
-
if key.startswith('label_') or key.startswith('label-'):
|
|
307
|
-
for label in self._labels:
|
|
308
|
-
label.configure({key[6:]: val})
|
|
309
|
-
elif key.startswith('listbox_') or key.startswith('listbox-'):
|
|
310
|
-
for listbox in self._listboxes:
|
|
311
|
-
listbox.configure({key[8:]: val})
|
|
312
|
-
else:
|
|
313
|
-
Frame.configure(self, {key:val})
|
|
314
|
-
|
|
315
|
-
def __setitem__(self, key, val):
|
|
316
|
-
"""
|
|
317
|
-
Configure this widget. This is equivalent to
|
|
318
|
-
``self.configure({key,val``)}. See ``configure()``.
|
|
319
|
-
"""
|
|
320
|
-
self.configure({key:val})
|
|
321
|
-
|
|
322
|
-
def rowconfigure(self, row_index, cnf={}, **kw):
|
|
323
|
-
"""
|
|
324
|
-
Configure all table cells in the given row. Valid keyword
|
|
325
|
-
arguments are: ``background``, ``bg``, ``foreground``, ``fg``,
|
|
326
|
-
``selectbackground``, ``selectforeground``.
|
|
327
|
-
"""
|
|
328
|
-
for lb in self._listboxes: lb.itemconfigure(row_index, cnf, **kw)
|
|
329
|
-
|
|
330
|
-
def columnconfigure(self, col_index, cnf={}, **kw):
|
|
331
|
-
"""
|
|
332
|
-
Configure all table cells in the given column. Valid keyword
|
|
333
|
-
arguments are: ``background``, ``bg``, ``foreground``, ``fg``,
|
|
334
|
-
``selectbackground``, ``selectforeground``.
|
|
335
|
-
"""
|
|
336
|
-
lb = self._listboxes[col_index]
|
|
337
|
-
|
|
338
|
-
cnf = dict(list(cnf.items()) + list(kw.items()))
|
|
339
|
-
for (key, val) in list(cnf.items()):
|
|
340
|
-
if key in ('background', 'bg', 'foreground', 'fg',
|
|
341
|
-
'selectbackground', 'selectforeground'):
|
|
342
|
-
for i in range(lb.size()): lb.itemconfigure(i, {key:val})
|
|
343
|
-
else:
|
|
344
|
-
lb.configure({key:val})
|
|
345
|
-
|
|
346
|
-
def itemconfigure(self, row_index, col_index, cnf=None, **kw):
|
|
347
|
-
"""
|
|
348
|
-
Configure the table cell at the given row and column. Valid
|
|
349
|
-
keyword arguments are: ``background``, ``bg``, ``foreground``,
|
|
350
|
-
``fg``, ``selectbackground``, ``selectforeground``.
|
|
351
|
-
"""
|
|
352
|
-
lb = self._listboxes[col_index]
|
|
353
|
-
return lb.itemconfigure(row_index, cnf, **kw)
|
|
354
|
-
|
|
355
|
-
#/////////////////////////////////////////////////////////////////
|
|
356
|
-
# Value Access
|
|
357
|
-
#/////////////////////////////////////////////////////////////////
|
|
358
|
-
|
|
359
|
-
def insert(self, index, *rows):
|
|
360
|
-
"""
|
|
361
|
-
Insert the given row or rows into the table, at the given
|
|
362
|
-
index. Each row value should be a tuple of cell values, one
|
|
363
|
-
for each column in the row. Index may be an integer or any of
|
|
364
|
-
the special strings (such as ``'end'``) accepted by
|
|
365
|
-
``Tkinter.Listbox``.
|
|
366
|
-
"""
|
|
367
|
-
for elt in rows:
|
|
368
|
-
if len(elt) != len(self._column_names):
|
|
369
|
-
raise ValueError('rows should be tuples whose length '
|
|
370
|
-
'is equal to the number of columns')
|
|
371
|
-
for (lb,elts) in zip(self._listboxes, list(zip(*rows))):
|
|
372
|
-
lb.insert(index, *elts)
|
|
373
|
-
|
|
374
|
-
def get(self, first, last=None):
|
|
375
|
-
"""
|
|
376
|
-
Return the value(s) of the specified row(s). If ``last`` is
|
|
377
|
-
not specified, then return a single row value; otherwise,
|
|
378
|
-
return a list of row values. Each row value is a tuple of
|
|
379
|
-
cell values, one for each column in the row.
|
|
380
|
-
"""
|
|
381
|
-
values = [lb.get(first, last) for lb in self._listboxes]
|
|
382
|
-
if last:
|
|
383
|
-
return [tuple(row) for row in zip(*values)]
|
|
384
|
-
else:
|
|
385
|
-
return tuple(values)
|
|
386
|
-
|
|
387
|
-
def bbox(self, row, col):
|
|
388
|
-
"""
|
|
389
|
-
Return the bounding box for the given table cell, relative to
|
|
390
|
-
this widget's top-left corner. The bounding box is a tuple
|
|
391
|
-
of integers ``(left, top, width, height)``.
|
|
392
|
-
"""
|
|
393
|
-
dx, dy, _, _ = self.grid_bbox(row=0, column=col)
|
|
394
|
-
x, y, w, h = self._listboxes[col].bbox(row)
|
|
395
|
-
return int(x)+int(dx), int(y)+int(dy), int(w), int(h)
|
|
396
|
-
|
|
397
|
-
#/////////////////////////////////////////////////////////////////
|
|
398
|
-
# Hide/Show Columns
|
|
399
|
-
#/////////////////////////////////////////////////////////////////
|
|
400
|
-
|
|
401
|
-
def hide_column(self, col_index):
|
|
402
|
-
"""
|
|
403
|
-
Hide the given column. The column's state is still
|
|
404
|
-
maintained: its values will still be returned by ``get()``, and
|
|
405
|
-
you must supply its values when calling ``insert()``. It is
|
|
406
|
-
safe to call this on a column that is already hidden.
|
|
407
|
-
|
|
408
|
-
:see: ``show_column()``
|
|
409
|
-
"""
|
|
410
|
-
if self._labels:
|
|
411
|
-
self._labels[col_index].grid_forget()
|
|
412
|
-
self.listboxes[col_index].grid_forget()
|
|
413
|
-
self.grid_columnconfigure(col_index, weight=0)
|
|
414
|
-
|
|
415
|
-
def show_column(self, col_index):
|
|
416
|
-
"""
|
|
417
|
-
Display a column that has been hidden using ``hide_column()``.
|
|
418
|
-
It is safe to call this on a column that is not hidden.
|
|
419
|
-
"""
|
|
420
|
-
weight = self._column_weights[col_index]
|
|
421
|
-
if self._labels:
|
|
422
|
-
self._labels[col_index].grid(column=col_index, row=0,
|
|
423
|
-
sticky='news', padx=0, pady=0)
|
|
424
|
-
self._listboxes[col_index].grid(column=col_index, row=1,
|
|
425
|
-
sticky='news', padx=0, pady=0)
|
|
426
|
-
self.grid_columnconfigure(col_index, weight=weight)
|
|
427
|
-
|
|
428
|
-
#/////////////////////////////////////////////////////////////////
|
|
429
|
-
# Binding Methods
|
|
430
|
-
#/////////////////////////////////////////////////////////////////
|
|
431
|
-
|
|
432
|
-
def bind_to_labels(self, sequence=None, func=None, add=None):
|
|
433
|
-
"""
|
|
434
|
-
Add a binding to each ``Tkinter.Label`` widget in this
|
|
435
|
-
mult-column listbox that will call ``func`` in response to the
|
|
436
|
-
event sequence.
|
|
437
|
-
|
|
438
|
-
:return: A list of the identifiers of replaced binding
|
|
439
|
-
functions (if any), allowing for their deletion (to
|
|
440
|
-
prevent a memory leak).
|
|
441
|
-
"""
|
|
442
|
-
return [label.bind(sequence, func, add)
|
|
443
|
-
for label in self.column_labels]
|
|
444
|
-
|
|
445
|
-
def bind_to_listboxes(self, sequence=None, func=None, add=None):
|
|
446
|
-
"""
|
|
447
|
-
Add a binding to each ``Tkinter.Listbox`` widget in this
|
|
448
|
-
mult-column listbox that will call ``func`` in response to the
|
|
449
|
-
event sequence.
|
|
450
|
-
|
|
451
|
-
:return: A list of the identifiers of replaced binding
|
|
452
|
-
functions (if any), allowing for their deletion (to
|
|
453
|
-
prevent a memory leak).
|
|
454
|
-
"""
|
|
455
|
-
for listbox in self.listboxes:
|
|
456
|
-
listbox.bind(sequence, func, add)
|
|
457
|
-
|
|
458
|
-
def bind_to_columns(self, sequence=None, func=None, add=None):
|
|
459
|
-
"""
|
|
460
|
-
Add a binding to each ``Tkinter.Label`` and ``Tkinter.Listbox``
|
|
461
|
-
widget in this mult-column listbox that will call ``func`` in
|
|
462
|
-
response to the event sequence.
|
|
463
|
-
|
|
464
|
-
:return: A list of the identifiers of replaced binding
|
|
465
|
-
functions (if any), allowing for their deletion (to
|
|
466
|
-
prevent a memory leak).
|
|
467
|
-
"""
|
|
468
|
-
return (self.bind_to_labels(sequence, func, add) +
|
|
469
|
-
self.bind_to_listboxes(sequence, func, add))
|
|
470
|
-
|
|
471
|
-
#/////////////////////////////////////////////////////////////////
|
|
472
|
-
# Simple Delegation
|
|
473
|
-
#/////////////////////////////////////////////////////////////////
|
|
474
|
-
|
|
475
|
-
# These methods delegate to the first listbox:
|
|
476
|
-
def curselection(self, *args, **kwargs):
|
|
477
|
-
return self._listboxes[0].curselection(*args, **kwargs)
|
|
478
|
-
def selection_includes(self, *args, **kwargs):
|
|
479
|
-
return self._listboxes[0].selection_includes(*args, **kwargs)
|
|
480
|
-
def itemcget(self, *args, **kwargs):
|
|
481
|
-
return self._listboxes[0].itemcget(*args, **kwargs)
|
|
482
|
-
def size(self, *args, **kwargs):
|
|
483
|
-
return self._listboxes[0].size(*args, **kwargs)
|
|
484
|
-
def index(self, *args, **kwargs):
|
|
485
|
-
return self._listboxes[0].index(*args, **kwargs)
|
|
486
|
-
def nearest(self, *args, **kwargs):
|
|
487
|
-
return self._listboxes[0].nearest(*args, **kwargs)
|
|
488
|
-
|
|
489
|
-
# These methods delegate to each listbox (and return None):
|
|
490
|
-
def activate(self, *args, **kwargs):
|
|
491
|
-
for lb in self._listboxes: lb.activate(*args, **kwargs)
|
|
492
|
-
def delete(self, *args, **kwargs):
|
|
493
|
-
for lb in self._listboxes: lb.delete(*args, **kwargs)
|
|
494
|
-
def scan_mark(self, *args, **kwargs):
|
|
495
|
-
for lb in self._listboxes: lb.scan_mark(*args, **kwargs)
|
|
496
|
-
def scan_dragto(self, *args, **kwargs):
|
|
497
|
-
for lb in self._listboxes: lb.scan_dragto(*args, **kwargs)
|
|
498
|
-
def see(self, *args, **kwargs):
|
|
499
|
-
for lb in self._listboxes: lb.see(*args, **kwargs)
|
|
500
|
-
def selection_anchor(self, *args, **kwargs):
|
|
501
|
-
for lb in self._listboxes: lb.selection_anchor(*args, **kwargs)
|
|
502
|
-
def selection_clear(self, *args, **kwargs):
|
|
503
|
-
for lb in self._listboxes: lb.selection_clear(*args, **kwargs)
|
|
504
|
-
def selection_set(self, *args, **kwargs):
|
|
505
|
-
for lb in self._listboxes: lb.selection_set(*args, **kwargs)
|
|
506
|
-
def yview(self, *args, **kwargs):
|
|
507
|
-
for lb in self._listboxes: v = lb.yview(*args, **kwargs)
|
|
508
|
-
return v # if called with no arguments
|
|
509
|
-
def yview_moveto(self, *args, **kwargs):
|
|
510
|
-
for lb in self._listboxes: lb.yview_moveto(*args, **kwargs)
|
|
511
|
-
def yview_scroll(self, *args, **kwargs):
|
|
512
|
-
for lb in self._listboxes: lb.yview_scroll(*args, **kwargs)
|
|
513
|
-
|
|
514
|
-
#/////////////////////////////////////////////////////////////////
|
|
515
|
-
# Aliases
|
|
516
|
-
#/////////////////////////////////////////////////////////////////
|
|
517
|
-
|
|
518
|
-
itemconfig = itemconfigure
|
|
519
|
-
rowconfig = rowconfigure
|
|
520
|
-
columnconfig = columnconfigure
|
|
521
|
-
select_anchor = selection_anchor
|
|
522
|
-
select_clear = selection_clear
|
|
523
|
-
select_includes = selection_includes
|
|
524
|
-
select_set = selection_set
|
|
525
|
-
|
|
526
|
-
#/////////////////////////////////////////////////////////////////
|
|
527
|
-
# These listbox methods are not defined for multi-listbox
|
|
528
|
-
#/////////////////////////////////////////////////////////////////
|
|
529
|
-
# def xview(self, *what): pass
|
|
530
|
-
# def xview_moveto(self, fraction): pass
|
|
531
|
-
# def xview_scroll(self, number, what): pass
|
|
532
|
-
|
|
533
|
-
######################################################################
|
|
534
|
-
# Table
|
|
535
|
-
######################################################################
|
|
536
|
-
|
|
537
|
-
class Table(object):
|
|
538
|
-
"""
|
|
539
|
-
A display widget for a table of values, based on a ``MultiListbox``
|
|
540
|
-
widget. For many purposes, ``Table`` can be treated as a
|
|
541
|
-
list-of-lists. E.g., table[i] is a list of the values for row i;
|
|
542
|
-
and table.append(row) adds a new row with the given lits of
|
|
543
|
-
values. Individual cells can be accessed using table[i,j], which
|
|
544
|
-
refers to the j-th column of the i-th row. This can be used to
|
|
545
|
-
both read and write values from the table. E.g.:
|
|
546
|
-
|
|
547
|
-
>>> table[i,j] = 'hello'
|
|
548
|
-
|
|
549
|
-
The column (j) can be given either as an index number, or as a
|
|
550
|
-
column name. E.g., the following prints the value in the 3rd row
|
|
551
|
-
for the 'First Name' column:
|
|
552
|
-
|
|
553
|
-
>>> print(table[3, 'First Name'])
|
|
554
|
-
John
|
|
555
|
-
|
|
556
|
-
You can configure the colors for individual rows, columns, or
|
|
557
|
-
cells using ``rowconfig()``, ``columnconfig()``, and ``itemconfig()``.
|
|
558
|
-
The color configuration for each row will be preserved if the
|
|
559
|
-
table is modified; however, when new rows are added, any color
|
|
560
|
-
configurations that have been made for *columns* will not be
|
|
561
|
-
applied to the new row.
|
|
562
|
-
|
|
563
|
-
Note: Although ``Table`` acts like a widget in some ways (e.g., it
|
|
564
|
-
defines ``grid()``, ``pack()``, and ``bind()``), it is not itself a
|
|
565
|
-
widget; it just contains one. This is because widgets need to
|
|
566
|
-
define ``__getitem__()``, ``__setitem__()``, and ``__nonzero__()`` in
|
|
567
|
-
a way that's incompatible with the fact that ``Table`` behaves as a
|
|
568
|
-
list-of-lists.
|
|
569
|
-
|
|
570
|
-
:ivar _mlb: The multi-column listbox used to display this table's data.
|
|
571
|
-
:ivar _rows: A list-of-lists used to hold the cell values of this
|
|
572
|
-
table. Each element of _rows is a row value, i.e., a list of
|
|
573
|
-
cell values, one for each column in the row.
|
|
574
|
-
"""
|
|
575
|
-
def __init__(self, master, column_names, rows=None,
|
|
576
|
-
column_weights=None,
|
|
577
|
-
scrollbar=True, click_to_sort=True,
|
|
578
|
-
reprfunc=None, cnf={}, **kw):
|
|
579
|
-
"""
|
|
580
|
-
Construct a new Table widget.
|
|
581
|
-
|
|
582
|
-
:type master: Tkinter.Widget
|
|
583
|
-
:param master: The widget that should contain the new table.
|
|
584
|
-
:type column_names: list(str)
|
|
585
|
-
:param column_names: A list of names for the columns; these
|
|
586
|
-
names will be used to create labels for each column;
|
|
587
|
-
and can be used as an index when reading or writing
|
|
588
|
-
cell values from the table.
|
|
589
|
-
:type rows: list(list)
|
|
590
|
-
:param rows: A list of row values used to initialze the table.
|
|
591
|
-
Each row value should be a tuple of cell values, one for
|
|
592
|
-
each column in the row.
|
|
593
|
-
:type scrollbar: bool
|
|
594
|
-
:param scrollbar: If true, then create a scrollbar for the
|
|
595
|
-
new table widget.
|
|
596
|
-
:type click_to_sort: bool
|
|
597
|
-
:param click_to_sort: If true, then create bindings that will
|
|
598
|
-
sort the table's rows by a given column's values if the
|
|
599
|
-
user clicks on that colum's label.
|
|
600
|
-
:type reprfunc: function
|
|
601
|
-
:param reprfunc: If specified, then use this function to
|
|
602
|
-
convert each table cell value to a string suitable for
|
|
603
|
-
display. ``reprfunc`` has the following signature:
|
|
604
|
-
reprfunc(row_index, col_index, cell_value) -> str
|
|
605
|
-
(Note that the column is specified by index, not by name.)
|
|
606
|
-
:param cnf, kw: Configuration parameters for this widget's
|
|
607
|
-
contained ``MultiListbox``. See ``MultiListbox.__init__()``
|
|
608
|
-
for details.
|
|
609
|
-
"""
|
|
610
|
-
self._num_columns = len(column_names)
|
|
611
|
-
self._reprfunc = reprfunc
|
|
612
|
-
self._frame = Frame(master)
|
|
613
|
-
|
|
614
|
-
self._column_name_to_index = dict((c,i) for (i,c) in
|
|
615
|
-
enumerate(column_names))
|
|
616
|
-
|
|
617
|
-
# Make a copy of the rows & check that it's valid.
|
|
618
|
-
if rows is None: self._rows = []
|
|
619
|
-
else: self._rows = [[v for v in row] for row in rows]
|
|
620
|
-
for row in self._rows: self._checkrow(row)
|
|
621
|
-
|
|
622
|
-
# Create our multi-list box.
|
|
623
|
-
self._mlb = MultiListbox(self._frame, column_names,
|
|
624
|
-
column_weights, cnf, **kw)
|
|
625
|
-
self._mlb.pack(side='left', expand=True, fill='both')
|
|
626
|
-
|
|
627
|
-
# Optional scrollbar
|
|
628
|
-
if scrollbar:
|
|
629
|
-
sb = Scrollbar(self._frame, orient='vertical',
|
|
630
|
-
command=self._mlb.yview)
|
|
631
|
-
self._mlb.listboxes[0]['yscrollcommand'] = sb.set
|
|
632
|
-
#for listbox in self._mlb.listboxes:
|
|
633
|
-
# listbox['yscrollcommand'] = sb.set
|
|
634
|
-
sb.pack(side='right', fill='y')
|
|
635
|
-
self._scrollbar = sb
|
|
636
|
-
|
|
637
|
-
# Set up sorting
|
|
638
|
-
self._sortkey = None
|
|
639
|
-
if click_to_sort:
|
|
640
|
-
for i, l in enumerate(self._mlb.column_labels):
|
|
641
|
-
l.bind('<Button-1>', self._sort)
|
|
642
|
-
|
|
643
|
-
# Fill in our multi-list box.
|
|
644
|
-
self._fill_table()
|
|
645
|
-
|
|
646
|
-
#/////////////////////////////////////////////////////////////////
|
|
647
|
-
#{ Widget-like Methods
|
|
648
|
-
#/////////////////////////////////////////////////////////////////
|
|
649
|
-
# These all just delegate to either our frame or our MLB.
|
|
650
|
-
|
|
651
|
-
def pack(self, *args, **kwargs):
|
|
652
|
-
"""Position this table's main frame widget in its parent
|
|
653
|
-
widget. See ``Tkinter.Frame.pack()`` for more info."""
|
|
654
|
-
self._frame.pack(*args, **kwargs)
|
|
655
|
-
|
|
656
|
-
def grid(self, *args, **kwargs):
|
|
657
|
-
"""Position this table's main frame widget in its parent
|
|
658
|
-
widget. See ``Tkinter.Frame.grid()`` for more info."""
|
|
659
|
-
self._frame.grid(*args, **kwargs)
|
|
660
|
-
|
|
661
|
-
def focus(self):
|
|
662
|
-
"""Direct (keyboard) input foxus to this widget."""
|
|
663
|
-
self._mlb.focus()
|
|
664
|
-
|
|
665
|
-
def bind(self, sequence=None, func=None, add=None):
|
|
666
|
-
"""Add a binding to this table's main frame that will call
|
|
667
|
-
``func`` in response to the event sequence."""
|
|
668
|
-
self._mlb.bind(sequence, func, add)
|
|
669
|
-
|
|
670
|
-
def rowconfigure(self, row_index, cnf={}, **kw):
|
|
671
|
-
""":see: ``MultiListbox.rowconfigure()``"""
|
|
672
|
-
self._mlb.rowconfigure(row_index, cnf, **kw)
|
|
673
|
-
|
|
674
|
-
def columnconfigure(self, col_index, cnf={}, **kw):
|
|
675
|
-
""":see: ``MultiListbox.columnconfigure()``"""
|
|
676
|
-
col_index = self.column_index(col_index)
|
|
677
|
-
self._mlb.columnconfigure(col_index, cnf, **kw)
|
|
678
|
-
|
|
679
|
-
def itemconfigure(self, row_index, col_index, cnf=None, **kw):
|
|
680
|
-
""":see: ``MultiListbox.itemconfigure()``"""
|
|
681
|
-
col_index = self.column_index(col_index)
|
|
682
|
-
return self._mlb.itemconfigure(row_index, col_index, cnf, **kw)
|
|
683
|
-
|
|
684
|
-
def bind_to_labels(self, sequence=None, func=None, add=None):
|
|
685
|
-
""":see: ``MultiListbox.bind_to_labels()``"""
|
|
686
|
-
return self._mlb.bind_to_labels(sequence, func, add)
|
|
687
|
-
|
|
688
|
-
def bind_to_listboxes(self, sequence=None, func=None, add=None):
|
|
689
|
-
""":see: ``MultiListbox.bind_to_listboxes()``"""
|
|
690
|
-
return self._mlb.bind_to_listboxes(sequence, func, add)
|
|
691
|
-
|
|
692
|
-
def bind_to_columns(self, sequence=None, func=None, add=None):
|
|
693
|
-
""":see: ``MultiListbox.bind_to_columns()``"""
|
|
694
|
-
return self._mlb.bind_to_columns(sequence, func, add)
|
|
695
|
-
|
|
696
|
-
rowconfig = rowconfigure
|
|
697
|
-
columnconfig = columnconfigure
|
|
698
|
-
itemconfig = itemconfigure
|
|
699
|
-
|
|
700
|
-
#/////////////////////////////////////////////////////////////////
|
|
701
|
-
#{ Table as list-of-lists
|
|
702
|
-
#/////////////////////////////////////////////////////////////////
|
|
703
|
-
|
|
704
|
-
def insert(self, row_index, rowvalue):
|
|
705
|
-
"""
|
|
706
|
-
Insert a new row into the table, so that its row index will be
|
|
707
|
-
``row_index``. If the table contains any rows whose row index
|
|
708
|
-
is greater than or equal to ``row_index``, then they will be
|
|
709
|
-
shifted down.
|
|
710
|
-
|
|
711
|
-
:param rowvalue: A tuple of cell values, one for each column
|
|
712
|
-
in the new row.
|
|
713
|
-
"""
|
|
714
|
-
self._checkrow(rowvalue)
|
|
715
|
-
self._rows.insert(row_index, rowvalue)
|
|
716
|
-
if self._reprfunc is not None:
|
|
717
|
-
rowvalue = [self._reprfunc(row_index,j,v)
|
|
718
|
-
for (j,v) in enumerate(rowvalue)]
|
|
719
|
-
self._mlb.insert(row_index, rowvalue)
|
|
720
|
-
if self._DEBUG: self._check_table_vs_mlb()
|
|
721
|
-
|
|
722
|
-
def extend(self, rowvalues):
|
|
723
|
-
"""
|
|
724
|
-
Add new rows at the end of the table.
|
|
725
|
-
|
|
726
|
-
:param rowvalues: A list of row values used to initialze the
|
|
727
|
-
table. Each row value should be a tuple of cell values,
|
|
728
|
-
one for each column in the row.
|
|
729
|
-
"""
|
|
730
|
-
for rowvalue in rowvalues: self.append(rowvalue)
|
|
731
|
-
if self._DEBUG: self._check_table_vs_mlb()
|
|
732
|
-
|
|
733
|
-
def append(self, rowvalue):
|
|
734
|
-
"""
|
|
735
|
-
Add a new row to the end of the table.
|
|
736
|
-
|
|
737
|
-
:param rowvalue: A tuple of cell values, one for each column
|
|
738
|
-
in the new row.
|
|
739
|
-
"""
|
|
740
|
-
self.insert(len(self._rows), rowvalue)
|
|
741
|
-
if self._DEBUG: self._check_table_vs_mlb()
|
|
742
|
-
|
|
743
|
-
def clear(self):
|
|
744
|
-
"""
|
|
745
|
-
Delete all rows in this table.
|
|
746
|
-
"""
|
|
747
|
-
self._rows = []
|
|
748
|
-
self._mlb.delete(0, 'end')
|
|
749
|
-
if self._DEBUG: self._check_table_vs_mlb()
|
|
750
|
-
|
|
751
|
-
def __getitem__(self, index):
|
|
752
|
-
"""
|
|
753
|
-
Return the value of a row or a cell in this table. If
|
|
754
|
-
``index`` is an integer, then the row value for the ``index``th
|
|
755
|
-
row. This row value consists of a tuple of cell values, one
|
|
756
|
-
for each column in the row. If ``index`` is a tuple of two
|
|
757
|
-
integers, ``(i,j)``, then return the value of the cell in the
|
|
758
|
-
``i``th row and the ``j``th column.
|
|
759
|
-
"""
|
|
760
|
-
if isinstance(index, slice):
|
|
761
|
-
raise ValueError('Slicing not supported')
|
|
762
|
-
elif isinstance(index, tuple) and len(index)==2:
|
|
763
|
-
return self._rows[index[0]][self.column_index(index[1])]
|
|
764
|
-
else:
|
|
765
|
-
return tuple(self._rows[index])
|
|
766
|
-
|
|
767
|
-
def __setitem__(self, index, val):
|
|
768
|
-
"""
|
|
769
|
-
Replace the value of a row or a cell in this table with
|
|
770
|
-
``val``.
|
|
771
|
-
|
|
772
|
-
If ``index`` is an integer, then ``val`` should be a row value
|
|
773
|
-
(i.e., a tuple of cell values, one for each column). In this
|
|
774
|
-
case, the values of the ``index``th row of the table will be
|
|
775
|
-
replaced with the values in ``val``.
|
|
776
|
-
|
|
777
|
-
If ``index`` is a tuple of integers, ``(i,j)``, then replace the
|
|
778
|
-
value of the cell in the ``i``th row and ``j``th column with
|
|
779
|
-
``val``.
|
|
780
|
-
"""
|
|
781
|
-
if isinstance(index, slice):
|
|
782
|
-
raise ValueError('Slicing not supported')
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
# table[i,j] = val
|
|
786
|
-
elif isinstance(index, tuple) and len(index)==2:
|
|
787
|
-
i, j = index[0], self.column_index(index[1])
|
|
788
|
-
config_cookie = self._save_config_info([i])
|
|
789
|
-
self._rows[i][j] = val
|
|
790
|
-
if self._reprfunc is not None:
|
|
791
|
-
val = self._reprfunc(i, j, val)
|
|
792
|
-
self._mlb.listboxes[j].insert(i, val)
|
|
793
|
-
self._mlb.listboxes[j].delete(i+1)
|
|
794
|
-
self._restore_config_info(config_cookie)
|
|
795
|
-
|
|
796
|
-
# table[i] = val
|
|
797
|
-
else:
|
|
798
|
-
config_cookie = self._save_config_info([index])
|
|
799
|
-
self._checkrow(val)
|
|
800
|
-
self._rows[index] = list(val)
|
|
801
|
-
if self._reprfunc is not None:
|
|
802
|
-
val = [self._reprfunc(index,j,v) for (j,v) in enumerate(val)]
|
|
803
|
-
self._mlb.insert(index, val)
|
|
804
|
-
self._mlb.delete(index+1)
|
|
805
|
-
self._restore_config_info(config_cookie)
|
|
806
|
-
|
|
807
|
-
def __delitem__(self, row_index):
|
|
808
|
-
"""
|
|
809
|
-
Delete the ``row_index``th row from this table.
|
|
810
|
-
"""
|
|
811
|
-
if isinstance(
|
|
812
|
-
raise ValueError('Slicing not supported')
|
|
813
|
-
if isinstance(row_index, tuple) and len(row_index)==2:
|
|
814
|
-
raise ValueError('Cannot delete a single cell!')
|
|
815
|
-
del self._rows[row_index]
|
|
816
|
-
self._mlb.delete(row_index)
|
|
817
|
-
if self._DEBUG: self._check_table_vs_mlb()
|
|
818
|
-
|
|
819
|
-
def __len__(self):
|
|
820
|
-
"""
|
|
821
|
-
:return: the number of rows in this table.
|
|
822
|
-
"""
|
|
823
|
-
return len(self._rows)
|
|
824
|
-
|
|
825
|
-
def _checkrow(self, rowvalue):
|
|
826
|
-
"""
|
|
827
|
-
Helper function: check that a given row value has the correct
|
|
828
|
-
number of elements; and if not, raise an exception.
|
|
829
|
-
"""
|
|
830
|
-
if len(rowvalue) != self._num_columns:
|
|
831
|
-
raise ValueError('Row %r has %d columns; expected %d' %
|
|
832
|
-
(rowvalue, len(rowvalue), self._num_columns))
|
|
833
|
-
|
|
834
|
-
#/////////////////////////////////////////////////////////////////
|
|
835
|
-
# Columns
|
|
836
|
-
#/////////////////////////////////////////////////////////////////
|
|
837
|
-
|
|
838
|
-
@property
|
|
839
|
-
def column_names(self):
|
|
840
|
-
"""A list of the names of the columns in this table."""
|
|
841
|
-
return self._mlb.column_names
|
|
842
|
-
|
|
843
|
-
def column_index(self, i):
|
|
844
|
-
"""
|
|
845
|
-
If ``i`` is a valid column index integer, then return it as is.
|
|
846
|
-
Otherwise, check if ``i`` is used as the name for any column;
|
|
847
|
-
if so, return that column's index. Otherwise, raise a
|
|
848
|
-
``KeyError`` exception.
|
|
849
|
-
"""
|
|
850
|
-
if isinstance(i, int) and 0 <= i < self._num_columns:
|
|
851
|
-
return i
|
|
852
|
-
else:
|
|
853
|
-
# This raises a key error if the column is not found.
|
|
854
|
-
return self._column_name_to_index[i]
|
|
855
|
-
|
|
856
|
-
def hide_column(self, column_index):
|
|
857
|
-
""":see: ``MultiListbox.hide_column()``"""
|
|
858
|
-
self._mlb.hide_column(self.column_index(column_index))
|
|
859
|
-
|
|
860
|
-
def show_column(self, column_index):
|
|
861
|
-
""":see: ``MultiListbox.show_column()``"""
|
|
862
|
-
self._mlb.show_column(self.column_index(column_index))
|
|
863
|
-
|
|
864
|
-
#/////////////////////////////////////////////////////////////////
|
|
865
|
-
# Selection
|
|
866
|
-
#/////////////////////////////////////////////////////////////////
|
|
867
|
-
|
|
868
|
-
def selected_row(self):
|
|
869
|
-
"""
|
|
870
|
-
Return the index of the currently selected row, or None if
|
|
871
|
-
no row is selected. To get the row value itself, use
|
|
872
|
-
``table[table.selected_row()]``.
|
|
873
|
-
"""
|
|
874
|
-
sel = self._mlb.curselection()
|
|
875
|
-
if sel: return int(sel[0])
|
|
876
|
-
else: return None
|
|
877
|
-
|
|
878
|
-
def select(self, index=None, delta=None, see=True):
|
|
879
|
-
""":see: ``MultiListbox.select()``"""
|
|
880
|
-
self._mlb.select(index, delta, see)
|
|
881
|
-
|
|
882
|
-
#/////////////////////////////////////////////////////////////////
|
|
883
|
-
# Sorting
|
|
884
|
-
#/////////////////////////////////////////////////////////////////
|
|
885
|
-
|
|
886
|
-
def sort_by(self, column_index, order='toggle'):
|
|
887
|
-
"""
|
|
888
|
-
Sort the rows in this table, using the specified column's
|
|
889
|
-
values as a sort key.
|
|
890
|
-
|
|
891
|
-
:param column_index: Specifies which column to sort, using
|
|
892
|
-
either a column index (int) or a column's label name
|
|
893
|
-
(str).
|
|
894
|
-
|
|
895
|
-
:param order: Specifies whether to sort the values in
|
|
896
|
-
ascending or descending order:
|
|
897
|
-
|
|
898
|
-
- ``'ascending'``: Sort from least to greatest.
|
|
899
|
-
- ``'descending'``: Sort from greatest to least.
|
|
900
|
-
- ``'toggle'``: If the most recent call to ``sort_by()``
|
|
901
|
-
sorted the table by the same column (``column_index``),
|
|
902
|
-
then reverse the rows; otherwise sort in ascending
|
|
903
|
-
order.
|
|
904
|
-
"""
|
|
905
|
-
if order not in ('ascending', 'descending', 'toggle'):
|
|
906
|
-
raise ValueError('sort_by(): order should be "ascending", '
|
|
907
|
-
'"descending", or "toggle".')
|
|
908
|
-
column_index = self.column_index(column_index)
|
|
909
|
-
config_cookie = self._save_config_info(index_by_id=True)
|
|
910
|
-
|
|
911
|
-
# Sort the rows.
|
|
912
|
-
if order == 'toggle' and column_index == self._sortkey:
|
|
913
|
-
self._rows.reverse()
|
|
914
|
-
else:
|
|
915
|
-
self._rows.sort(key=operator.itemgetter(column_index),
|
|
916
|
-
reverse=(order=='descending'))
|
|
917
|
-
self._sortkey = column_index
|
|
918
|
-
|
|
919
|
-
# Redraw the table.
|
|
920
|
-
self._fill_table()
|
|
921
|
-
self._restore_config_info(config_cookie, index_by_id=True, see=True)
|
|
922
|
-
if self._DEBUG: self._check_table_vs_mlb()
|
|
923
|
-
|
|
924
|
-
def _sort(self, event):
|
|
925
|
-
"""Event handler for clicking on a column label -- sort by
|
|
926
|
-
that column."""
|
|
927
|
-
column_index = event.widget.column_index
|
|
928
|
-
|
|
929
|
-
# If they click on the far-left of far-right of a column's
|
|
930
|
-
# label, then resize rather than sorting.
|
|
931
|
-
if self._mlb._resize_column(event):
|
|
932
|
-
return 'continue'
|
|
933
|
-
|
|
934
|
-
# Otherwise, sort.
|
|
935
|
-
else:
|
|
936
|
-
self.sort_by(column_index)
|
|
937
|
-
return 'continue'
|
|
938
|
-
|
|
939
|
-
#/////////////////////////////////////////////////////////////////
|
|
940
|
-
#{ Table Drawing Helpers
|
|
941
|
-
#/////////////////////////////////////////////////////////////////
|
|
942
|
-
|
|
943
|
-
def _fill_table(self, save_config=True):
|
|
944
|
-
"""
|
|
945
|
-
Re-draw the table from scratch, by clearing out the table's
|
|
946
|
-
multi-column listbox; and then filling it in with values from
|
|
947
|
-
``self._rows``. Note that any cell-, row-, or column-specific
|
|
948
|
-
color configuration that has been done will be lost. The
|
|
949
|
-
selection will also be lost -- i.e., no row will be selected
|
|
950
|
-
after this call completes.
|
|
951
|
-
"""
|
|
952
|
-
self._mlb.delete(0, 'end')
|
|
953
|
-
for i, row in enumerate(self._rows):
|
|
954
|
-
if self._reprfunc is not None:
|
|
955
|
-
row = [self._reprfunc(i,j,v) for (j,v) in enumerate(row)]
|
|
956
|
-
self._mlb.insert('end', row)
|
|
957
|
-
|
|
958
|
-
def _get_itemconfig(self, r, c):
|
|
959
|
-
return dict( (k, self._mlb.itemconfig(r, c, k)[-1])
|
|
960
|
-
for k in ('foreground', 'selectforeground',
|
|
961
|
-
'background', 'selectbackground') )
|
|
962
|
-
|
|
963
|
-
def _save_config_info(self, row_indices=None, index_by_id=False):
|
|
964
|
-
"""
|
|
965
|
-
Return a 'cookie' containing information about which row is
|
|
966
|
-
selected, and what color configurations have been applied.
|
|
967
|
-
this information can the be re-applied to the table (after
|
|
968
|
-
making modifications) using ``_restore_config_info()``. Color
|
|
969
|
-
configuration information will be saved for any rows in
|
|
970
|
-
``row_indices``, or in the entire table, if
|
|
971
|
-
``row_indices=None``. If ``index_by_id=True``, the the cookie
|
|
972
|
-
will associate rows with their configuration information based
|
|
973
|
-
on the rows' python id. This is useful when performing
|
|
974
|
-
operations that re-arrange the rows (e.g. ``sort``). If
|
|
975
|
-
``index_by_id=False``, then it is assumed that all rows will be
|
|
976
|
-
in the same order when ``_restore_config_info()`` is called.
|
|
977
|
-
"""
|
|
978
|
-
# Default value for row_indices is all rows.
|
|
979
|
-
if row_indices is None:
|
|
980
|
-
row_indices = list(range(len(self._rows)))
|
|
981
|
-
|
|
982
|
-
# Look up our current selection.
|
|
983
|
-
selection = self.selected_row()
|
|
984
|
-
if index_by_id and selection is not None:
|
|
985
|
-
selection = id(self._rows[selection])
|
|
986
|
-
|
|
987
|
-
# Look up the color configuration info for each row.
|
|
988
|
-
if index_by_id:
|
|
989
|
-
config = dict((id(self._rows[r]), [self._get_itemconfig(r, c)
|
|
990
|
-
for c in range(self._num_columns)])
|
|
991
|
-
for r in row_indices)
|
|
992
|
-
else:
|
|
993
|
-
config = dict((r, [self._get_itemconfig(r, c)
|
|
994
|
-
for c in range(self._num_columns)])
|
|
995
|
-
for r in row_indices)
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
return selection, config
|
|
999
|
-
|
|
1000
|
-
def _restore_config_info(self, cookie, index_by_id=False, see=False):
|
|
1001
|
-
"""
|
|
1002
|
-
Restore selection & color configuration information that was
|
|
1003
|
-
saved using ``_save_config_info``.
|
|
1004
|
-
"""
|
|
1005
|
-
selection, config = cookie
|
|
1006
|
-
|
|
1007
|
-
# Clear the selection.
|
|
1008
|
-
if selection is None:
|
|
1009
|
-
self._mlb.selection_clear(0, 'end')
|
|
1010
|
-
|
|
1011
|
-
# Restore selection & color config
|
|
1012
|
-
if index_by_id:
|
|
1013
|
-
for r, row in enumerate(self._rows):
|
|
1014
|
-
if id(row) in config:
|
|
1015
|
-
for c in range(self._num_columns):
|
|
1016
|
-
self._mlb.itemconfigure(r, c, config[id(row)][c])
|
|
1017
|
-
if id(row) == selection:
|
|
1018
|
-
self._mlb.select(r, see=see)
|
|
1019
|
-
else:
|
|
1020
|
-
if selection is not None:
|
|
1021
|
-
self._mlb.select(selection, see=see)
|
|
1022
|
-
for r in config:
|
|
1023
|
-
for c in range(self._num_columns):
|
|
1024
|
-
self._mlb.itemconfigure(r, c, config[r][c])
|
|
1025
|
-
|
|
1026
|
-
#/////////////////////////////////////////////////////////////////
|
|
1027
|
-
# Debugging (Invariant Checker)
|
|
1028
|
-
#/////////////////////////////////////////////////////////////////
|
|
1029
|
-
|
|
1030
|
-
_DEBUG = False
|
|
1031
|
-
"""If true, then run ``_check_table_vs_mlb()`` after any operation
|
|
1032
|
-
that modifies the table."""
|
|
1033
|
-
|
|
1034
|
-
def _check_table_vs_mlb(self):
|
|
1035
|
-
"""
|
|
1036
|
-
Verify that the contents of the table's ``_rows`` variable match
|
|
1037
|
-
the contents of its multi-listbox (``_mlb``). This is just
|
|
1038
|
-
included for debugging purposes, to make sure that the
|
|
1039
|
-
list-modifying operations are working correctly.
|
|
1040
|
-
"""
|
|
1041
|
-
for col in self._mlb.listboxes:
|
|
1042
|
-
assert len(self) == col.size()
|
|
1043
|
-
for row in self:
|
|
1044
|
-
assert len(row) == self._num_columns
|
|
1045
|
-
assert self._num_columns == len(self._mlb.column_names)
|
|
1046
|
-
#assert self._column_names == self._mlb.column_names
|
|
1047
|
-
for i, row in enumerate(self):
|
|
1048
|
-
for j, cell in enumerate(row):
|
|
1049
|
-
if self._reprfunc is not None:
|
|
1050
|
-
cell = self._reprfunc(i, j, cell)
|
|
1051
|
-
assert self._mlb.get(i)[j] == cell
|
|
1052
|
-
|
|
1053
|
-
######################################################################
|
|
1054
|
-
# Demo/Test Function
|
|
1055
|
-
######################################################################
|
|
1056
|
-
|
|
1057
|
-
# update this to use new WordNet API
|
|
1058
|
-
def demo():
|
|
1059
|
-
root = Tk()
|
|
1060
|
-
root.bind('<Control-q>', lambda e: root.destroy())
|
|
1061
|
-
|
|
1062
|
-
table = Table(root, 'Word Synset Hypernym Hyponym'.split(),
|
|
1063
|
-
column_weights=[0, 1, 1, 1],
|
|
1064
|
-
reprfunc=(lambda i,j,s: ' %s' % s))
|
|
1065
|
-
table.pack(expand=True, fill='both')
|
|
1066
|
-
|
|
1067
|
-
from nltk.corpus import wordnet
|
|
1068
|
-
from nltk.corpus import brown
|
|
1069
|
-
for word, pos in sorted(set(brown.tagged_words()[:500])):
|
|
1070
|
-
if pos[0] != 'N': continue
|
|
1071
|
-
word = word.lower()
|
|
1072
|
-
for synset in wordnet.synsets(word):
|
|
1073
|
-
try:
|
|
1074
|
-
hyper_def = synset.hypernyms()[0].definition()
|
|
1075
|
-
except:
|
|
1076
|
-
hyper_def = '*none*'
|
|
1077
|
-
try:
|
|
1078
|
-
hypo_def = synset.hypernyms()[0].definition()
|
|
1079
|
-
except:
|
|
1080
|
-
hypo_def = '*none*'
|
|
1081
|
-
table.append([word,
|
|
1082
|
-
synset.definition(),
|
|
1083
|
-
hyper_def,
|
|
1084
|
-
hypo_def])
|
|
1085
|
-
|
|
1086
|
-
table.columnconfig('Word', background='#afa')
|
|
1087
|
-
table.columnconfig('Synset', background='#efe')
|
|
1088
|
-
table.columnconfig('Hypernym', background='#fee')
|
|
1089
|
-
table.columnconfig('Hyponym', background='#ffe')
|
|
1090
|
-
for row in range(len(table)):
|
|
1091
|
-
for column in ('Hypernym', 'Hyponym'):
|
|
1092
|
-
if table[row, column] == '*none*':
|
|
1093
|
-
table.itemconfig(row, column, foreground='#666',
|
|
1094
|
-
selectforeground='#666')
|
|
1095
|
-
root.mainloop()
|
|
1096
|
-
|
|
1097
|
-
if __name__ == '__main__':
|
|
1098
|
-
demo()
|
|
1
|
+
# Natural Language Toolkit: Table widget
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2001-2018 NLTK Project
|
|
4
|
+
# Author: Edward Loper <edloper@gmail.com>
|
|
5
|
+
# URL: <http://nltk.org/>
|
|
6
|
+
# For license information, see LICENSE.TXT
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Tkinter widgets for displaying multi-column listboxes and tables.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import division
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import operator
|
|
16
|
+
|
|
17
|
+
from six.moves.tkinter import (Frame, Label, Listbox, Scrollbar, Tk)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
######################################################################
|
|
21
|
+
# Multi-Column Listbox
|
|
22
|
+
######################################################################
|
|
23
|
+
|
|
24
|
+
class MultiListbox(Frame):
|
|
25
|
+
"""
|
|
26
|
+
A multi-column listbox, where the current selection applies to an
|
|
27
|
+
entire row. Based on the MultiListbox Tkinter widget
|
|
28
|
+
recipe from the Python Cookbook (http://code.activestate.com/recipes/52266/)
|
|
29
|
+
|
|
30
|
+
For the most part, ``MultiListbox`` methods delegate to its
|
|
31
|
+
contained listboxes. For any methods that do not have docstrings,
|
|
32
|
+
see ``Tkinter.Listbox`` for a description of what that method does.
|
|
33
|
+
"""
|
|
34
|
+
#/////////////////////////////////////////////////////////////////
|
|
35
|
+
# Configuration
|
|
36
|
+
#/////////////////////////////////////////////////////////////////
|
|
37
|
+
|
|
38
|
+
#: Default configuration values for the frame.
|
|
39
|
+
FRAME_CONFIG = dict(background='#888',
|
|
40
|
+
takefocus=True,
|
|
41
|
+
highlightthickness=1)
|
|
42
|
+
|
|
43
|
+
#: Default configurations for the column labels.
|
|
44
|
+
LABEL_CONFIG = dict(borderwidth=1, relief='raised',
|
|
45
|
+
font='helvetica -16 bold',
|
|
46
|
+
background='#444', foreground='white')
|
|
47
|
+
|
|
48
|
+
#: Default configuration for the column listboxes.
|
|
49
|
+
LISTBOX_CONFIG = dict(borderwidth=1,
|
|
50
|
+
selectborderwidth=0,
|
|
51
|
+
highlightthickness=0,
|
|
52
|
+
exportselection=False,
|
|
53
|
+
selectbackground='#888',
|
|
54
|
+
activestyle='none',
|
|
55
|
+
takefocus=False)
|
|
56
|
+
|
|
57
|
+
#/////////////////////////////////////////////////////////////////
|
|
58
|
+
# Constructor
|
|
59
|
+
#/////////////////////////////////////////////////////////////////
|
|
60
|
+
|
|
61
|
+
def __init__(self, master, columns, column_weights=None, cnf={}, **kw):
|
|
62
|
+
"""
|
|
63
|
+
Construct a new multi-column listbox widget.
|
|
64
|
+
|
|
65
|
+
:param master: The widget that should contain the new
|
|
66
|
+
multi-column listbox.
|
|
67
|
+
|
|
68
|
+
:param columns: Specifies what columns should be included in
|
|
69
|
+
the new multi-column listbox. If ``columns`` is an integer,
|
|
70
|
+
the it is the number of columns to include. If it is
|
|
71
|
+
a list, then its length indicates the number of columns
|
|
72
|
+
to include; and each element of the list will be used as
|
|
73
|
+
a label for the corresponding column.
|
|
74
|
+
|
|
75
|
+
:param cnf, kw: Configuration parameters for this widget.
|
|
76
|
+
Use ``label_*`` to configure all labels; and ``listbox_*``
|
|
77
|
+
to configure all listboxes. E.g.:
|
|
78
|
+
|
|
79
|
+
>>> mlb = MultiListbox(master, 5, label_foreground='red')
|
|
80
|
+
"""
|
|
81
|
+
# If columns was specified as an int, convert it to a list.
|
|
82
|
+
if isinstance(columns, int):
|
|
83
|
+
columns = list(range(columns))
|
|
84
|
+
include_labels = False
|
|
85
|
+
else:
|
|
86
|
+
include_labels = True
|
|
87
|
+
|
|
88
|
+
if len(columns) == 0:
|
|
89
|
+
raise ValueError("Expected at least one column")
|
|
90
|
+
|
|
91
|
+
# Instance variables
|
|
92
|
+
self._column_names = tuple(columns)
|
|
93
|
+
self._listboxes = []
|
|
94
|
+
self._labels = []
|
|
95
|
+
|
|
96
|
+
# Pick a default value for column_weights, if none was specified.
|
|
97
|
+
if column_weights is None:
|
|
98
|
+
column_weights = [1] * len(columns)
|
|
99
|
+
elif len(column_weights) != len(columns):
|
|
100
|
+
raise ValueError('Expected one column_weight for each column')
|
|
101
|
+
self._column_weights = column_weights
|
|
102
|
+
|
|
103
|
+
# Configure our widgets.
|
|
104
|
+
Frame.__init__(self, master, **self.FRAME_CONFIG)
|
|
105
|
+
self.grid_rowconfigure(1, weight=1)
|
|
106
|
+
for i, label in enumerate(self._column_names):
|
|
107
|
+
self.grid_columnconfigure(i, weight=column_weights[i])
|
|
108
|
+
|
|
109
|
+
# Create a label for the column
|
|
110
|
+
if include_labels:
|
|
111
|
+
l = Label(self, text=label, **self.LABEL_CONFIG)
|
|
112
|
+
self._labels.append(l)
|
|
113
|
+
l.grid(column=i, row=0, sticky='news', padx=0, pady=0)
|
|
114
|
+
l.column_index = i
|
|
115
|
+
|
|
116
|
+
# Create a listbox for the column
|
|
117
|
+
lb = Listbox(self, **self.LISTBOX_CONFIG)
|
|
118
|
+
self._listboxes.append(lb)
|
|
119
|
+
lb.grid(column=i, row=1, sticky='news', padx=0, pady=0)
|
|
120
|
+
lb.column_index = i
|
|
121
|
+
|
|
122
|
+
# Clicking or dragging selects:
|
|
123
|
+
lb.bind('<Button-1>', self._select)
|
|
124
|
+
lb.bind('<B1-Motion>', self._select)
|
|
125
|
+
# Scroll whell scrolls:
|
|
126
|
+
lb.bind('<Button-4>', lambda e: self._scroll(-1))
|
|
127
|
+
lb.bind('<Button-5>', lambda e: self._scroll(+1))
|
|
128
|
+
lb.bind('<MouseWheel>', lambda e: self._scroll(e.delta))
|
|
129
|
+
# Button 2 can be used to scan:
|
|
130
|
+
lb.bind('<Button-2>', lambda e: self.scan_mark(e.x, e.y))
|
|
131
|
+
lb.bind('<B2-Motion>', lambda e: self.scan_dragto(e.x, e.y))
|
|
132
|
+
# Dragging outside the window has no effect (diable
|
|
133
|
+
# the default listbox behavior, which scrolls):
|
|
134
|
+
lb.bind('<B1-Leave>', lambda e: 'break')
|
|
135
|
+
# Columns can be resized by dragging them:
|
|
136
|
+
l.bind('<Button-1>', self._resize_column)
|
|
137
|
+
|
|
138
|
+
# Columns can be resized by dragging them. (This binding is
|
|
139
|
+
# used if they click on the grid between columns:)
|
|
140
|
+
self.bind('<Button-1>', self._resize_column)
|
|
141
|
+
|
|
142
|
+
# Set up key bindings for the widget:
|
|
143
|
+
self.bind('<Up>', lambda e: self.select(delta=-1))
|
|
144
|
+
self.bind('<Down>', lambda e: self.select(delta=1))
|
|
145
|
+
self.bind('<Prior>', lambda e: self.select(delta=-self._pagesize()))
|
|
146
|
+
self.bind('<Next>', lambda e: self.select(delta=self._pagesize()))
|
|
147
|
+
|
|
148
|
+
# Configuration customizations
|
|
149
|
+
self.configure(cnf, **kw)
|
|
150
|
+
|
|
151
|
+
#/////////////////////////////////////////////////////////////////
|
|
152
|
+
# Column Resizing
|
|
153
|
+
#/////////////////////////////////////////////////////////////////
|
|
154
|
+
|
|
155
|
+
def _resize_column(self, event):
|
|
156
|
+
"""
|
|
157
|
+
Callback used to resize a column of the table. Return ``True``
|
|
158
|
+
if the column is actually getting resized (if the user clicked
|
|
159
|
+
on the far left or far right 5 pixels of a label); and
|
|
160
|
+
``False`` otherwies.
|
|
161
|
+
"""
|
|
162
|
+
# If we're already waiting for a button release, then ignore
|
|
163
|
+
# the new button press.
|
|
164
|
+
if event.widget.bind('<ButtonRelease>'):
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
# Decide which column (if any) to resize.
|
|
168
|
+
self._resize_column_index = None
|
|
169
|
+
if event.widget is self:
|
|
170
|
+
for i, lb in enumerate(self._listboxes):
|
|
171
|
+
if abs(event.x-(lb.winfo_x()+lb.winfo_width())) < 10:
|
|
172
|
+
self._resize_column_index = i
|
|
173
|
+
elif event.x > (event.widget.winfo_width()-5):
|
|
174
|
+
self._resize_column_index = event.widget.column_index
|
|
175
|
+
elif event.x < 5 and event.widget.column_index != 0:
|
|
176
|
+
self._resize_column_index = event.widget.column_index-1
|
|
177
|
+
|
|
178
|
+
# Bind callbacks that are used to resize it.
|
|
179
|
+
if self._resize_column_index is not None:
|
|
180
|
+
event.widget.bind('<Motion>', self._resize_column_motion_cb)
|
|
181
|
+
event.widget.bind('<ButtonRelease-%d>' % event.num,
|
|
182
|
+
self._resize_column_buttonrelease_cb)
|
|
183
|
+
return True
|
|
184
|
+
else:
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
def _resize_column_motion_cb(self, event):
|
|
188
|
+
lb = self._listboxes[self._resize_column_index]
|
|
189
|
+
charwidth = lb.winfo_width() / lb['width']
|
|
190
|
+
|
|
191
|
+
x1 = event.x + event.widget.winfo_x()
|
|
192
|
+
x2 = lb.winfo_x() + lb.winfo_width()
|
|
193
|
+
|
|
194
|
+
lb['width'] = max(3, lb['width'] + (x1-x2) // charwidth)
|
|
195
|
+
|
|
196
|
+
def _resize_column_buttonrelease_cb(self, event):
|
|
197
|
+
event.widget.unbind('<ButtonRelease-%d>' % event.num)
|
|
198
|
+
event.widget.unbind('<Motion>')
|
|
199
|
+
|
|
200
|
+
#/////////////////////////////////////////////////////////////////
|
|
201
|
+
# Properties
|
|
202
|
+
#/////////////////////////////////////////////////////////////////
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def column_names(self):
|
|
206
|
+
"""
|
|
207
|
+
A tuple containing the names of the columns used by this
|
|
208
|
+
multi-column listbox.
|
|
209
|
+
"""
|
|
210
|
+
return self._column_names
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def column_labels(self):
|
|
214
|
+
"""
|
|
215
|
+
A tuple containing the ``Tkinter.Label`` widgets used to
|
|
216
|
+
display the label of each column. If this multi-column
|
|
217
|
+
listbox was created without labels, then this will be an empty
|
|
218
|
+
tuple. These widgets will all be augmented with a
|
|
219
|
+
``column_index`` attribute, which can be used to determine
|
|
220
|
+
which column they correspond to. This can be convenient,
|
|
221
|
+
e.g., when defining callbacks for bound events.
|
|
222
|
+
"""
|
|
223
|
+
return tuple(self._labels)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def listboxes(self):
|
|
227
|
+
"""
|
|
228
|
+
A tuple containing the ``Tkinter.Listbox`` widgets used to
|
|
229
|
+
display individual columns. These widgets will all be
|
|
230
|
+
augmented with a ``column_index`` attribute, which can be used
|
|
231
|
+
to determine which column they correspond to. This can be
|
|
232
|
+
convenient, e.g., when defining callbacks for bound events.
|
|
233
|
+
"""
|
|
234
|
+
return tuple(self._listboxes)
|
|
235
|
+
|
|
236
|
+
#/////////////////////////////////////////////////////////////////
|
|
237
|
+
# Mouse & Keyboard Callback Functions
|
|
238
|
+
#/////////////////////////////////////////////////////////////////
|
|
239
|
+
|
|
240
|
+
def _select(self, e):
|
|
241
|
+
i = e.widget.nearest(e.y)
|
|
242
|
+
self.selection_clear(0, 'end')
|
|
243
|
+
self.selection_set(i)
|
|
244
|
+
self.activate(i)
|
|
245
|
+
self.focus()
|
|
246
|
+
|
|
247
|
+
def _scroll(self, delta):
|
|
248
|
+
for lb in self._listboxes:
|
|
249
|
+
lb.yview_scroll(delta, 'unit')
|
|
250
|
+
return 'break'
|
|
251
|
+
|
|
252
|
+
def _pagesize(self):
|
|
253
|
+
""":return: The number of rows that makes up one page"""
|
|
254
|
+
return int(self.index('@0,1000000')) - int(self.index('@0,0'))
|
|
255
|
+
|
|
256
|
+
#/////////////////////////////////////////////////////////////////
|
|
257
|
+
# Row selection
|
|
258
|
+
#/////////////////////////////////////////////////////////////////
|
|
259
|
+
|
|
260
|
+
def select(self, index=None, delta=None, see=True):
|
|
261
|
+
"""
|
|
262
|
+
Set the selected row. If ``index`` is specified, then select
|
|
263
|
+
row ``index``. Otherwise, if ``delta`` is specified, then move
|
|
264
|
+
the current selection by ``delta`` (negative numbers for up,
|
|
265
|
+
positive numbers for down). This will not move the selection
|
|
266
|
+
past the top or the bottom of the list.
|
|
267
|
+
|
|
268
|
+
:param see: If true, then call ``self.see()`` with the newly
|
|
269
|
+
selected index, to ensure that it is visible.
|
|
270
|
+
"""
|
|
271
|
+
if (index is not None) and (delta is not None):
|
|
272
|
+
raise ValueError('specify index or delta, but not both')
|
|
273
|
+
|
|
274
|
+
# If delta was given, then calculate index.
|
|
275
|
+
if delta is not None:
|
|
276
|
+
if len(self.curselection()) == 0:
|
|
277
|
+
index = -1 + delta
|
|
278
|
+
else:
|
|
279
|
+
index = int(self.curselection()[0]) + delta
|
|
280
|
+
|
|
281
|
+
# Clear all selected rows.
|
|
282
|
+
self.selection_clear(0, 'end')
|
|
283
|
+
|
|
284
|
+
# Select the specified index
|
|
285
|
+
if index is not None:
|
|
286
|
+
index = min(max(index, 0), self.size()-1)
|
|
287
|
+
#self.activate(index)
|
|
288
|
+
self.selection_set(index)
|
|
289
|
+
if see: self.see(index)
|
|
290
|
+
|
|
291
|
+
#/////////////////////////////////////////////////////////////////
|
|
292
|
+
# Configuration
|
|
293
|
+
#/////////////////////////////////////////////////////////////////
|
|
294
|
+
|
|
295
|
+
def configure(self, cnf={}, **kw):
|
|
296
|
+
"""
|
|
297
|
+
Configure this widget. Use ``label_*`` to configure all
|
|
298
|
+
labels; and ``listbox_*`` to configure all listboxes. E.g.:
|
|
299
|
+
|
|
300
|
+
>>> mlb = MultiListbox(master, 5)
|
|
301
|
+
>>> mlb.configure(label_foreground='red')
|
|
302
|
+
>>> mlb.configure(listbox_foreground='red')
|
|
303
|
+
"""
|
|
304
|
+
cnf = dict(list(cnf.items()) + list(kw.items()))
|
|
305
|
+
for (key, val) in list(cnf.items()):
|
|
306
|
+
if key.startswith('label_') or key.startswith('label-'):
|
|
307
|
+
for label in self._labels:
|
|
308
|
+
label.configure({key[6:]: val})
|
|
309
|
+
elif key.startswith('listbox_') or key.startswith('listbox-'):
|
|
310
|
+
for listbox in self._listboxes:
|
|
311
|
+
listbox.configure({key[8:]: val})
|
|
312
|
+
else:
|
|
313
|
+
Frame.configure(self, {key:val})
|
|
314
|
+
|
|
315
|
+
def __setitem__(self, key, val):
|
|
316
|
+
"""
|
|
317
|
+
Configure this widget. This is equivalent to
|
|
318
|
+
``self.configure({key,val``)}. See ``configure()``.
|
|
319
|
+
"""
|
|
320
|
+
self.configure({key:val})
|
|
321
|
+
|
|
322
|
+
def rowconfigure(self, row_index, cnf={}, **kw):
|
|
323
|
+
"""
|
|
324
|
+
Configure all table cells in the given row. Valid keyword
|
|
325
|
+
arguments are: ``background``, ``bg``, ``foreground``, ``fg``,
|
|
326
|
+
``selectbackground``, ``selectforeground``.
|
|
327
|
+
"""
|
|
328
|
+
for lb in self._listboxes: lb.itemconfigure(row_index, cnf, **kw)
|
|
329
|
+
|
|
330
|
+
def columnconfigure(self, col_index, cnf={}, **kw):
|
|
331
|
+
"""
|
|
332
|
+
Configure all table cells in the given column. Valid keyword
|
|
333
|
+
arguments are: ``background``, ``bg``, ``foreground``, ``fg``,
|
|
334
|
+
``selectbackground``, ``selectforeground``.
|
|
335
|
+
"""
|
|
336
|
+
lb = self._listboxes[col_index]
|
|
337
|
+
|
|
338
|
+
cnf = dict(list(cnf.items()) + list(kw.items()))
|
|
339
|
+
for (key, val) in list(cnf.items()):
|
|
340
|
+
if key in ('background', 'bg', 'foreground', 'fg',
|
|
341
|
+
'selectbackground', 'selectforeground'):
|
|
342
|
+
for i in range(lb.size()): lb.itemconfigure(i, {key:val})
|
|
343
|
+
else:
|
|
344
|
+
lb.configure({key:val})
|
|
345
|
+
|
|
346
|
+
def itemconfigure(self, row_index, col_index, cnf=None, **kw):
|
|
347
|
+
"""
|
|
348
|
+
Configure the table cell at the given row and column. Valid
|
|
349
|
+
keyword arguments are: ``background``, ``bg``, ``foreground``,
|
|
350
|
+
``fg``, ``selectbackground``, ``selectforeground``.
|
|
351
|
+
"""
|
|
352
|
+
lb = self._listboxes[col_index]
|
|
353
|
+
return lb.itemconfigure(row_index, cnf, **kw)
|
|
354
|
+
|
|
355
|
+
#/////////////////////////////////////////////////////////////////
|
|
356
|
+
# Value Access
|
|
357
|
+
#/////////////////////////////////////////////////////////////////
|
|
358
|
+
|
|
359
|
+
def insert(self, index, *rows):
|
|
360
|
+
"""
|
|
361
|
+
Insert the given row or rows into the table, at the given
|
|
362
|
+
index. Each row value should be a tuple of cell values, one
|
|
363
|
+
for each column in the row. Index may be an integer or any of
|
|
364
|
+
the special strings (such as ``'end'``) accepted by
|
|
365
|
+
``Tkinter.Listbox``.
|
|
366
|
+
"""
|
|
367
|
+
for elt in rows:
|
|
368
|
+
if len(elt) != len(self._column_names):
|
|
369
|
+
raise ValueError('rows should be tuples whose length '
|
|
370
|
+
'is equal to the number of columns')
|
|
371
|
+
for (lb,elts) in zip(self._listboxes, list(zip(*rows))):
|
|
372
|
+
lb.insert(index, *elts)
|
|
373
|
+
|
|
374
|
+
def get(self, first, last=None):
|
|
375
|
+
"""
|
|
376
|
+
Return the value(s) of the specified row(s). If ``last`` is
|
|
377
|
+
not specified, then return a single row value; otherwise,
|
|
378
|
+
return a list of row values. Each row value is a tuple of
|
|
379
|
+
cell values, one for each column in the row.
|
|
380
|
+
"""
|
|
381
|
+
values = [lb.get(first, last) for lb in self._listboxes]
|
|
382
|
+
if last:
|
|
383
|
+
return [tuple(row) for row in zip(*values)]
|
|
384
|
+
else:
|
|
385
|
+
return tuple(values)
|
|
386
|
+
|
|
387
|
+
def bbox(self, row, col):
|
|
388
|
+
"""
|
|
389
|
+
Return the bounding box for the given table cell, relative to
|
|
390
|
+
this widget's top-left corner. The bounding box is a tuple
|
|
391
|
+
of integers ``(left, top, width, height)``.
|
|
392
|
+
"""
|
|
393
|
+
dx, dy, _, _ = self.grid_bbox(row=0, column=col)
|
|
394
|
+
x, y, w, h = self._listboxes[col].bbox(row)
|
|
395
|
+
return int(x)+int(dx), int(y)+int(dy), int(w), int(h)
|
|
396
|
+
|
|
397
|
+
#/////////////////////////////////////////////////////////////////
|
|
398
|
+
# Hide/Show Columns
|
|
399
|
+
#/////////////////////////////////////////////////////////////////
|
|
400
|
+
|
|
401
|
+
def hide_column(self, col_index):
|
|
402
|
+
"""
|
|
403
|
+
Hide the given column. The column's state is still
|
|
404
|
+
maintained: its values will still be returned by ``get()``, and
|
|
405
|
+
you must supply its values when calling ``insert()``. It is
|
|
406
|
+
safe to call this on a column that is already hidden.
|
|
407
|
+
|
|
408
|
+
:see: ``show_column()``
|
|
409
|
+
"""
|
|
410
|
+
if self._labels:
|
|
411
|
+
self._labels[col_index].grid_forget()
|
|
412
|
+
self.listboxes[col_index].grid_forget()
|
|
413
|
+
self.grid_columnconfigure(col_index, weight=0)
|
|
414
|
+
|
|
415
|
+
def show_column(self, col_index):
|
|
416
|
+
"""
|
|
417
|
+
Display a column that has been hidden using ``hide_column()``.
|
|
418
|
+
It is safe to call this on a column that is not hidden.
|
|
419
|
+
"""
|
|
420
|
+
weight = self._column_weights[col_index]
|
|
421
|
+
if self._labels:
|
|
422
|
+
self._labels[col_index].grid(column=col_index, row=0,
|
|
423
|
+
sticky='news', padx=0, pady=0)
|
|
424
|
+
self._listboxes[col_index].grid(column=col_index, row=1,
|
|
425
|
+
sticky='news', padx=0, pady=0)
|
|
426
|
+
self.grid_columnconfigure(col_index, weight=weight)
|
|
427
|
+
|
|
428
|
+
#/////////////////////////////////////////////////////////////////
|
|
429
|
+
# Binding Methods
|
|
430
|
+
#/////////////////////////////////////////////////////////////////
|
|
431
|
+
|
|
432
|
+
def bind_to_labels(self, sequence=None, func=None, add=None):
|
|
433
|
+
"""
|
|
434
|
+
Add a binding to each ``Tkinter.Label`` widget in this
|
|
435
|
+
mult-column listbox that will call ``func`` in response to the
|
|
436
|
+
event sequence.
|
|
437
|
+
|
|
438
|
+
:return: A list of the identifiers of replaced binding
|
|
439
|
+
functions (if any), allowing for their deletion (to
|
|
440
|
+
prevent a memory leak).
|
|
441
|
+
"""
|
|
442
|
+
return [label.bind(sequence, func, add)
|
|
443
|
+
for label in self.column_labels]
|
|
444
|
+
|
|
445
|
+
def bind_to_listboxes(self, sequence=None, func=None, add=None):
|
|
446
|
+
"""
|
|
447
|
+
Add a binding to each ``Tkinter.Listbox`` widget in this
|
|
448
|
+
mult-column listbox that will call ``func`` in response to the
|
|
449
|
+
event sequence.
|
|
450
|
+
|
|
451
|
+
:return: A list of the identifiers of replaced binding
|
|
452
|
+
functions (if any), allowing for their deletion (to
|
|
453
|
+
prevent a memory leak).
|
|
454
|
+
"""
|
|
455
|
+
for listbox in self.listboxes:
|
|
456
|
+
listbox.bind(sequence, func, add)
|
|
457
|
+
|
|
458
|
+
def bind_to_columns(self, sequence=None, func=None, add=None):
|
|
459
|
+
"""
|
|
460
|
+
Add a binding to each ``Tkinter.Label`` and ``Tkinter.Listbox``
|
|
461
|
+
widget in this mult-column listbox that will call ``func`` in
|
|
462
|
+
response to the event sequence.
|
|
463
|
+
|
|
464
|
+
:return: A list of the identifiers of replaced binding
|
|
465
|
+
functions (if any), allowing for their deletion (to
|
|
466
|
+
prevent a memory leak).
|
|
467
|
+
"""
|
|
468
|
+
return (self.bind_to_labels(sequence, func, add) +
|
|
469
|
+
self.bind_to_listboxes(sequence, func, add))
|
|
470
|
+
|
|
471
|
+
#/////////////////////////////////////////////////////////////////
|
|
472
|
+
# Simple Delegation
|
|
473
|
+
#/////////////////////////////////////////////////////////////////
|
|
474
|
+
|
|
475
|
+
# These methods delegate to the first listbox:
|
|
476
|
+
def curselection(self, *args, **kwargs):
|
|
477
|
+
return self._listboxes[0].curselection(*args, **kwargs)
|
|
478
|
+
def selection_includes(self, *args, **kwargs):
|
|
479
|
+
return self._listboxes[0].selection_includes(*args, **kwargs)
|
|
480
|
+
def itemcget(self, *args, **kwargs):
|
|
481
|
+
return self._listboxes[0].itemcget(*args, **kwargs)
|
|
482
|
+
def size(self, *args, **kwargs):
|
|
483
|
+
return self._listboxes[0].size(*args, **kwargs)
|
|
484
|
+
def index(self, *args, **kwargs):
|
|
485
|
+
return self._listboxes[0].index(*args, **kwargs)
|
|
486
|
+
def nearest(self, *args, **kwargs):
|
|
487
|
+
return self._listboxes[0].nearest(*args, **kwargs)
|
|
488
|
+
|
|
489
|
+
# These methods delegate to each listbox (and return None):
|
|
490
|
+
def activate(self, *args, **kwargs):
|
|
491
|
+
for lb in self._listboxes: lb.activate(*args, **kwargs)
|
|
492
|
+
def delete(self, *args, **kwargs):
|
|
493
|
+
for lb in self._listboxes: lb.delete(*args, **kwargs)
|
|
494
|
+
def scan_mark(self, *args, **kwargs):
|
|
495
|
+
for lb in self._listboxes: lb.scan_mark(*args, **kwargs)
|
|
496
|
+
def scan_dragto(self, *args, **kwargs):
|
|
497
|
+
for lb in self._listboxes: lb.scan_dragto(*args, **kwargs)
|
|
498
|
+
def see(self, *args, **kwargs):
|
|
499
|
+
for lb in self._listboxes: lb.see(*args, **kwargs)
|
|
500
|
+
def selection_anchor(self, *args, **kwargs):
|
|
501
|
+
for lb in self._listboxes: lb.selection_anchor(*args, **kwargs)
|
|
502
|
+
def selection_clear(self, *args, **kwargs):
|
|
503
|
+
for lb in self._listboxes: lb.selection_clear(*args, **kwargs)
|
|
504
|
+
def selection_set(self, *args, **kwargs):
|
|
505
|
+
for lb in self._listboxes: lb.selection_set(*args, **kwargs)
|
|
506
|
+
def yview(self, *args, **kwargs):
|
|
507
|
+
for lb in self._listboxes: v = lb.yview(*args, **kwargs)
|
|
508
|
+
return v # if called with no arguments
|
|
509
|
+
def yview_moveto(self, *args, **kwargs):
|
|
510
|
+
for lb in self._listboxes: lb.yview_moveto(*args, **kwargs)
|
|
511
|
+
def yview_scroll(self, *args, **kwargs):
|
|
512
|
+
for lb in self._listboxes: lb.yview_scroll(*args, **kwargs)
|
|
513
|
+
|
|
514
|
+
#/////////////////////////////////////////////////////////////////
|
|
515
|
+
# Aliases
|
|
516
|
+
#/////////////////////////////////////////////////////////////////
|
|
517
|
+
|
|
518
|
+
itemconfig = itemconfigure
|
|
519
|
+
rowconfig = rowconfigure
|
|
520
|
+
columnconfig = columnconfigure
|
|
521
|
+
select_anchor = selection_anchor
|
|
522
|
+
select_clear = selection_clear
|
|
523
|
+
select_includes = selection_includes
|
|
524
|
+
select_set = selection_set
|
|
525
|
+
|
|
526
|
+
#/////////////////////////////////////////////////////////////////
|
|
527
|
+
# These listbox methods are not defined for multi-listbox
|
|
528
|
+
#/////////////////////////////////////////////////////////////////
|
|
529
|
+
# def xview(self, *what): pass
|
|
530
|
+
# def xview_moveto(self, fraction): pass
|
|
531
|
+
# def xview_scroll(self, number, what): pass
|
|
532
|
+
|
|
533
|
+
######################################################################
|
|
534
|
+
# Table
|
|
535
|
+
######################################################################
|
|
536
|
+
|
|
537
|
+
class Table(object):
|
|
538
|
+
"""
|
|
539
|
+
A display widget for a table of values, based on a ``MultiListbox``
|
|
540
|
+
widget. For many purposes, ``Table`` can be treated as a
|
|
541
|
+
list-of-lists. E.g., table[i] is a list of the values for row i;
|
|
542
|
+
and table.append(row) adds a new row with the given lits of
|
|
543
|
+
values. Individual cells can be accessed using table[i,j], which
|
|
544
|
+
refers to the j-th column of the i-th row. This can be used to
|
|
545
|
+
both read and write values from the table. E.g.:
|
|
546
|
+
|
|
547
|
+
>>> table[i,j] = 'hello'
|
|
548
|
+
|
|
549
|
+
The column (j) can be given either as an index number, or as a
|
|
550
|
+
column name. E.g., the following prints the value in the 3rd row
|
|
551
|
+
for the 'First Name' column:
|
|
552
|
+
|
|
553
|
+
>>> print(table[3, 'First Name'])
|
|
554
|
+
John
|
|
555
|
+
|
|
556
|
+
You can configure the colors for individual rows, columns, or
|
|
557
|
+
cells using ``rowconfig()``, ``columnconfig()``, and ``itemconfig()``.
|
|
558
|
+
The color configuration for each row will be preserved if the
|
|
559
|
+
table is modified; however, when new rows are added, any color
|
|
560
|
+
configurations that have been made for *columns* will not be
|
|
561
|
+
applied to the new row.
|
|
562
|
+
|
|
563
|
+
Note: Although ``Table`` acts like a widget in some ways (e.g., it
|
|
564
|
+
defines ``grid()``, ``pack()``, and ``bind()``), it is not itself a
|
|
565
|
+
widget; it just contains one. This is because widgets need to
|
|
566
|
+
define ``__getitem__()``, ``__setitem__()``, and ``__nonzero__()`` in
|
|
567
|
+
a way that's incompatible with the fact that ``Table`` behaves as a
|
|
568
|
+
list-of-lists.
|
|
569
|
+
|
|
570
|
+
:ivar _mlb: The multi-column listbox used to display this table's data.
|
|
571
|
+
:ivar _rows: A list-of-lists used to hold the cell values of this
|
|
572
|
+
table. Each element of _rows is a row value, i.e., a list of
|
|
573
|
+
cell values, one for each column in the row.
|
|
574
|
+
"""
|
|
575
|
+
def __init__(self, master, column_names, rows=None,
|
|
576
|
+
column_weights=None,
|
|
577
|
+
scrollbar=True, click_to_sort=True,
|
|
578
|
+
reprfunc=None, cnf={}, **kw):
|
|
579
|
+
"""
|
|
580
|
+
Construct a new Table widget.
|
|
581
|
+
|
|
582
|
+
:type master: Tkinter.Widget
|
|
583
|
+
:param master: The widget that should contain the new table.
|
|
584
|
+
:type column_names: list(str)
|
|
585
|
+
:param column_names: A list of names for the columns; these
|
|
586
|
+
names will be used to create labels for each column;
|
|
587
|
+
and can be used as an index when reading or writing
|
|
588
|
+
cell values from the table.
|
|
589
|
+
:type rows: list(list)
|
|
590
|
+
:param rows: A list of row values used to initialze the table.
|
|
591
|
+
Each row value should be a tuple of cell values, one for
|
|
592
|
+
each column in the row.
|
|
593
|
+
:type scrollbar: bool
|
|
594
|
+
:param scrollbar: If true, then create a scrollbar for the
|
|
595
|
+
new table widget.
|
|
596
|
+
:type click_to_sort: bool
|
|
597
|
+
:param click_to_sort: If true, then create bindings that will
|
|
598
|
+
sort the table's rows by a given column's values if the
|
|
599
|
+
user clicks on that colum's label.
|
|
600
|
+
:type reprfunc: function
|
|
601
|
+
:param reprfunc: If specified, then use this function to
|
|
602
|
+
convert each table cell value to a string suitable for
|
|
603
|
+
display. ``reprfunc`` has the following signature:
|
|
604
|
+
reprfunc(row_index, col_index, cell_value) -> str
|
|
605
|
+
(Note that the column is specified by index, not by name.)
|
|
606
|
+
:param cnf, kw: Configuration parameters for this widget's
|
|
607
|
+
contained ``MultiListbox``. See ``MultiListbox.__init__()``
|
|
608
|
+
for details.
|
|
609
|
+
"""
|
|
610
|
+
self._num_columns = len(column_names)
|
|
611
|
+
self._reprfunc = reprfunc
|
|
612
|
+
self._frame = Frame(master)
|
|
613
|
+
|
|
614
|
+
self._column_name_to_index = dict((c,i) for (i,c) in
|
|
615
|
+
enumerate(column_names))
|
|
616
|
+
|
|
617
|
+
# Make a copy of the rows & check that it's valid.
|
|
618
|
+
if rows is None: self._rows = []
|
|
619
|
+
else: self._rows = [[v for v in row] for row in rows]
|
|
620
|
+
for row in self._rows: self._checkrow(row)
|
|
621
|
+
|
|
622
|
+
# Create our multi-list box.
|
|
623
|
+
self._mlb = MultiListbox(self._frame, column_names,
|
|
624
|
+
column_weights, cnf, **kw)
|
|
625
|
+
self._mlb.pack(side='left', expand=True, fill='both')
|
|
626
|
+
|
|
627
|
+
# Optional scrollbar
|
|
628
|
+
if scrollbar:
|
|
629
|
+
sb = Scrollbar(self._frame, orient='vertical',
|
|
630
|
+
command=self._mlb.yview)
|
|
631
|
+
self._mlb.listboxes[0]['yscrollcommand'] = sb.set
|
|
632
|
+
#for listbox in self._mlb.listboxes:
|
|
633
|
+
# listbox['yscrollcommand'] = sb.set
|
|
634
|
+
sb.pack(side='right', fill='y')
|
|
635
|
+
self._scrollbar = sb
|
|
636
|
+
|
|
637
|
+
# Set up sorting
|
|
638
|
+
self._sortkey = None
|
|
639
|
+
if click_to_sort:
|
|
640
|
+
for i, l in enumerate(self._mlb.column_labels):
|
|
641
|
+
l.bind('<Button-1>', self._sort)
|
|
642
|
+
|
|
643
|
+
# Fill in our multi-list box.
|
|
644
|
+
self._fill_table()
|
|
645
|
+
|
|
646
|
+
#/////////////////////////////////////////////////////////////////
|
|
647
|
+
#{ Widget-like Methods
|
|
648
|
+
#/////////////////////////////////////////////////////////////////
|
|
649
|
+
# These all just delegate to either our frame or our MLB.
|
|
650
|
+
|
|
651
|
+
def pack(self, *args, **kwargs):
|
|
652
|
+
"""Position this table's main frame widget in its parent
|
|
653
|
+
widget. See ``Tkinter.Frame.pack()`` for more info."""
|
|
654
|
+
self._frame.pack(*args, **kwargs)
|
|
655
|
+
|
|
656
|
+
def grid(self, *args, **kwargs):
|
|
657
|
+
"""Position this table's main frame widget in its parent
|
|
658
|
+
widget. See ``Tkinter.Frame.grid()`` for more info."""
|
|
659
|
+
self._frame.grid(*args, **kwargs)
|
|
660
|
+
|
|
661
|
+
def focus(self):
|
|
662
|
+
"""Direct (keyboard) input foxus to this widget."""
|
|
663
|
+
self._mlb.focus()
|
|
664
|
+
|
|
665
|
+
def bind(self, sequence=None, func=None, add=None):
|
|
666
|
+
"""Add a binding to this table's main frame that will call
|
|
667
|
+
``func`` in response to the event sequence."""
|
|
668
|
+
self._mlb.bind(sequence, func, add)
|
|
669
|
+
|
|
670
|
+
def rowconfigure(self, row_index, cnf={}, **kw):
|
|
671
|
+
""":see: ``MultiListbox.rowconfigure()``"""
|
|
672
|
+
self._mlb.rowconfigure(row_index, cnf, **kw)
|
|
673
|
+
|
|
674
|
+
def columnconfigure(self, col_index, cnf={}, **kw):
|
|
675
|
+
""":see: ``MultiListbox.columnconfigure()``"""
|
|
676
|
+
col_index = self.column_index(col_index)
|
|
677
|
+
self._mlb.columnconfigure(col_index, cnf, **kw)
|
|
678
|
+
|
|
679
|
+
def itemconfigure(self, row_index, col_index, cnf=None, **kw):
|
|
680
|
+
""":see: ``MultiListbox.itemconfigure()``"""
|
|
681
|
+
col_index = self.column_index(col_index)
|
|
682
|
+
return self._mlb.itemconfigure(row_index, col_index, cnf, **kw)
|
|
683
|
+
|
|
684
|
+
def bind_to_labels(self, sequence=None, func=None, add=None):
|
|
685
|
+
""":see: ``MultiListbox.bind_to_labels()``"""
|
|
686
|
+
return self._mlb.bind_to_labels(sequence, func, add)
|
|
687
|
+
|
|
688
|
+
def bind_to_listboxes(self, sequence=None, func=None, add=None):
|
|
689
|
+
""":see: ``MultiListbox.bind_to_listboxes()``"""
|
|
690
|
+
return self._mlb.bind_to_listboxes(sequence, func, add)
|
|
691
|
+
|
|
692
|
+
def bind_to_columns(self, sequence=None, func=None, add=None):
|
|
693
|
+
""":see: ``MultiListbox.bind_to_columns()``"""
|
|
694
|
+
return self._mlb.bind_to_columns(sequence, func, add)
|
|
695
|
+
|
|
696
|
+
rowconfig = rowconfigure
|
|
697
|
+
columnconfig = columnconfigure
|
|
698
|
+
itemconfig = itemconfigure
|
|
699
|
+
|
|
700
|
+
#/////////////////////////////////////////////////////////////////
|
|
701
|
+
#{ Table as list-of-lists
|
|
702
|
+
#/////////////////////////////////////////////////////////////////
|
|
703
|
+
|
|
704
|
+
def insert(self, row_index, rowvalue):
|
|
705
|
+
"""
|
|
706
|
+
Insert a new row into the table, so that its row index will be
|
|
707
|
+
``row_index``. If the table contains any rows whose row index
|
|
708
|
+
is greater than or equal to ``row_index``, then they will be
|
|
709
|
+
shifted down.
|
|
710
|
+
|
|
711
|
+
:param rowvalue: A tuple of cell values, one for each column
|
|
712
|
+
in the new row.
|
|
713
|
+
"""
|
|
714
|
+
self._checkrow(rowvalue)
|
|
715
|
+
self._rows.insert(row_index, rowvalue)
|
|
716
|
+
if self._reprfunc is not None:
|
|
717
|
+
rowvalue = [self._reprfunc(row_index,j,v)
|
|
718
|
+
for (j,v) in enumerate(rowvalue)]
|
|
719
|
+
self._mlb.insert(row_index, rowvalue)
|
|
720
|
+
if self._DEBUG: self._check_table_vs_mlb()
|
|
721
|
+
|
|
722
|
+
def extend(self, rowvalues):
|
|
723
|
+
"""
|
|
724
|
+
Add new rows at the end of the table.
|
|
725
|
+
|
|
726
|
+
:param rowvalues: A list of row values used to initialze the
|
|
727
|
+
table. Each row value should be a tuple of cell values,
|
|
728
|
+
one for each column in the row.
|
|
729
|
+
"""
|
|
730
|
+
for rowvalue in rowvalues: self.append(rowvalue)
|
|
731
|
+
if self._DEBUG: self._check_table_vs_mlb()
|
|
732
|
+
|
|
733
|
+
def append(self, rowvalue):
|
|
734
|
+
"""
|
|
735
|
+
Add a new row to the end of the table.
|
|
736
|
+
|
|
737
|
+
:param rowvalue: A tuple of cell values, one for each column
|
|
738
|
+
in the new row.
|
|
739
|
+
"""
|
|
740
|
+
self.insert(len(self._rows), rowvalue)
|
|
741
|
+
if self._DEBUG: self._check_table_vs_mlb()
|
|
742
|
+
|
|
743
|
+
def clear(self):
|
|
744
|
+
"""
|
|
745
|
+
Delete all rows in this table.
|
|
746
|
+
"""
|
|
747
|
+
self._rows = []
|
|
748
|
+
self._mlb.delete(0, 'end')
|
|
749
|
+
if self._DEBUG: self._check_table_vs_mlb()
|
|
750
|
+
|
|
751
|
+
def __getitem__(self, index):
|
|
752
|
+
"""
|
|
753
|
+
Return the value of a row or a cell in this table. If
|
|
754
|
+
``index`` is an integer, then the row value for the ``index``th
|
|
755
|
+
row. This row value consists of a tuple of cell values, one
|
|
756
|
+
for each column in the row. If ``index`` is a tuple of two
|
|
757
|
+
integers, ``(i,j)``, then return the value of the cell in the
|
|
758
|
+
``i``th row and the ``j``th column.
|
|
759
|
+
"""
|
|
760
|
+
if isinstance(index, slice):
|
|
761
|
+
raise ValueError('Slicing not supported')
|
|
762
|
+
elif isinstance(index, tuple) and len(index)==2:
|
|
763
|
+
return self._rows[index[0]][self.column_index(index[1])]
|
|
764
|
+
else:
|
|
765
|
+
return tuple(self._rows[index])
|
|
766
|
+
|
|
767
|
+
def __setitem__(self, index, val):
|
|
768
|
+
"""
|
|
769
|
+
Replace the value of a row or a cell in this table with
|
|
770
|
+
``val``.
|
|
771
|
+
|
|
772
|
+
If ``index`` is an integer, then ``val`` should be a row value
|
|
773
|
+
(i.e., a tuple of cell values, one for each column). In this
|
|
774
|
+
case, the values of the ``index``th row of the table will be
|
|
775
|
+
replaced with the values in ``val``.
|
|
776
|
+
|
|
777
|
+
If ``index`` is a tuple of integers, ``(i,j)``, then replace the
|
|
778
|
+
value of the cell in the ``i``th row and ``j``th column with
|
|
779
|
+
``val``.
|
|
780
|
+
"""
|
|
781
|
+
if isinstance(index, slice):
|
|
782
|
+
raise ValueError('Slicing not supported')
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
# table[i,j] = val
|
|
786
|
+
elif isinstance(index, tuple) and len(index)==2:
|
|
787
|
+
i, j = index[0], self.column_index(index[1])
|
|
788
|
+
config_cookie = self._save_config_info([i])
|
|
789
|
+
self._rows[i][j] = val
|
|
790
|
+
if self._reprfunc is not None:
|
|
791
|
+
val = self._reprfunc(i, j, val)
|
|
792
|
+
self._mlb.listboxes[j].insert(i, val)
|
|
793
|
+
self._mlb.listboxes[j].delete(i+1)
|
|
794
|
+
self._restore_config_info(config_cookie)
|
|
795
|
+
|
|
796
|
+
# table[i] = val
|
|
797
|
+
else:
|
|
798
|
+
config_cookie = self._save_config_info([index])
|
|
799
|
+
self._checkrow(val)
|
|
800
|
+
self._rows[index] = list(val)
|
|
801
|
+
if self._reprfunc is not None:
|
|
802
|
+
val = [self._reprfunc(index,j,v) for (j,v) in enumerate(val)]
|
|
803
|
+
self._mlb.insert(index, val)
|
|
804
|
+
self._mlb.delete(index+1)
|
|
805
|
+
self._restore_config_info(config_cookie)
|
|
806
|
+
|
|
807
|
+
def __delitem__(self, row_index):
|
|
808
|
+
"""
|
|
809
|
+
Delete the ``row_index``th row from this table.
|
|
810
|
+
"""
|
|
811
|
+
if isinstance(row_index, slice):
|
|
812
|
+
raise ValueError('Slicing not supported')
|
|
813
|
+
if isinstance(row_index, tuple) and len(row_index)==2:
|
|
814
|
+
raise ValueError('Cannot delete a single cell!')
|
|
815
|
+
del self._rows[row_index]
|
|
816
|
+
self._mlb.delete(row_index)
|
|
817
|
+
if self._DEBUG: self._check_table_vs_mlb()
|
|
818
|
+
|
|
819
|
+
def __len__(self):
|
|
820
|
+
"""
|
|
821
|
+
:return: the number of rows in this table.
|
|
822
|
+
"""
|
|
823
|
+
return len(self._rows)
|
|
824
|
+
|
|
825
|
+
def _checkrow(self, rowvalue):
|
|
826
|
+
"""
|
|
827
|
+
Helper function: check that a given row value has the correct
|
|
828
|
+
number of elements; and if not, raise an exception.
|
|
829
|
+
"""
|
|
830
|
+
if len(rowvalue) != self._num_columns:
|
|
831
|
+
raise ValueError('Row %r has %d columns; expected %d' %
|
|
832
|
+
(rowvalue, len(rowvalue), self._num_columns))
|
|
833
|
+
|
|
834
|
+
#/////////////////////////////////////////////////////////////////
|
|
835
|
+
# Columns
|
|
836
|
+
#/////////////////////////////////////////////////////////////////
|
|
837
|
+
|
|
838
|
+
@property
|
|
839
|
+
def column_names(self):
|
|
840
|
+
"""A list of the names of the columns in this table."""
|
|
841
|
+
return self._mlb.column_names
|
|
842
|
+
|
|
843
|
+
def column_index(self, i):
|
|
844
|
+
"""
|
|
845
|
+
If ``i`` is a valid column index integer, then return it as is.
|
|
846
|
+
Otherwise, check if ``i`` is used as the name for any column;
|
|
847
|
+
if so, return that column's index. Otherwise, raise a
|
|
848
|
+
``KeyError`` exception.
|
|
849
|
+
"""
|
|
850
|
+
if isinstance(i, int) and 0 <= i < self._num_columns:
|
|
851
|
+
return i
|
|
852
|
+
else:
|
|
853
|
+
# This raises a key error if the column is not found.
|
|
854
|
+
return self._column_name_to_index[i]
|
|
855
|
+
|
|
856
|
+
def hide_column(self, column_index):
|
|
857
|
+
""":see: ``MultiListbox.hide_column()``"""
|
|
858
|
+
self._mlb.hide_column(self.column_index(column_index))
|
|
859
|
+
|
|
860
|
+
def show_column(self, column_index):
|
|
861
|
+
""":see: ``MultiListbox.show_column()``"""
|
|
862
|
+
self._mlb.show_column(self.column_index(column_index))
|
|
863
|
+
|
|
864
|
+
#/////////////////////////////////////////////////////////////////
|
|
865
|
+
# Selection
|
|
866
|
+
#/////////////////////////////////////////////////////////////////
|
|
867
|
+
|
|
868
|
+
def selected_row(self):
|
|
869
|
+
"""
|
|
870
|
+
Return the index of the currently selected row, or None if
|
|
871
|
+
no row is selected. To get the row value itself, use
|
|
872
|
+
``table[table.selected_row()]``.
|
|
873
|
+
"""
|
|
874
|
+
sel = self._mlb.curselection()
|
|
875
|
+
if sel: return int(sel[0])
|
|
876
|
+
else: return None
|
|
877
|
+
|
|
878
|
+
def select(self, index=None, delta=None, see=True):
|
|
879
|
+
""":see: ``MultiListbox.select()``"""
|
|
880
|
+
self._mlb.select(index, delta, see)
|
|
881
|
+
|
|
882
|
+
#/////////////////////////////////////////////////////////////////
|
|
883
|
+
# Sorting
|
|
884
|
+
#/////////////////////////////////////////////////////////////////
|
|
885
|
+
|
|
886
|
+
def sort_by(self, column_index, order='toggle'):
|
|
887
|
+
"""
|
|
888
|
+
Sort the rows in this table, using the specified column's
|
|
889
|
+
values as a sort key.
|
|
890
|
+
|
|
891
|
+
:param column_index: Specifies which column to sort, using
|
|
892
|
+
either a column index (int) or a column's label name
|
|
893
|
+
(str).
|
|
894
|
+
|
|
895
|
+
:param order: Specifies whether to sort the values in
|
|
896
|
+
ascending or descending order:
|
|
897
|
+
|
|
898
|
+
- ``'ascending'``: Sort from least to greatest.
|
|
899
|
+
- ``'descending'``: Sort from greatest to least.
|
|
900
|
+
- ``'toggle'``: If the most recent call to ``sort_by()``
|
|
901
|
+
sorted the table by the same column (``column_index``),
|
|
902
|
+
then reverse the rows; otherwise sort in ascending
|
|
903
|
+
order.
|
|
904
|
+
"""
|
|
905
|
+
if order not in ('ascending', 'descending', 'toggle'):
|
|
906
|
+
raise ValueError('sort_by(): order should be "ascending", '
|
|
907
|
+
'"descending", or "toggle".')
|
|
908
|
+
column_index = self.column_index(column_index)
|
|
909
|
+
config_cookie = self._save_config_info(index_by_id=True)
|
|
910
|
+
|
|
911
|
+
# Sort the rows.
|
|
912
|
+
if order == 'toggle' and column_index == self._sortkey:
|
|
913
|
+
self._rows.reverse()
|
|
914
|
+
else:
|
|
915
|
+
self._rows.sort(key=operator.itemgetter(column_index),
|
|
916
|
+
reverse=(order=='descending'))
|
|
917
|
+
self._sortkey = column_index
|
|
918
|
+
|
|
919
|
+
# Redraw the table.
|
|
920
|
+
self._fill_table()
|
|
921
|
+
self._restore_config_info(config_cookie, index_by_id=True, see=True)
|
|
922
|
+
if self._DEBUG: self._check_table_vs_mlb()
|
|
923
|
+
|
|
924
|
+
def _sort(self, event):
|
|
925
|
+
"""Event handler for clicking on a column label -- sort by
|
|
926
|
+
that column."""
|
|
927
|
+
column_index = event.widget.column_index
|
|
928
|
+
|
|
929
|
+
# If they click on the far-left of far-right of a column's
|
|
930
|
+
# label, then resize rather than sorting.
|
|
931
|
+
if self._mlb._resize_column(event):
|
|
932
|
+
return 'continue'
|
|
933
|
+
|
|
934
|
+
# Otherwise, sort.
|
|
935
|
+
else:
|
|
936
|
+
self.sort_by(column_index)
|
|
937
|
+
return 'continue'
|
|
938
|
+
|
|
939
|
+
#/////////////////////////////////////////////////////////////////
|
|
940
|
+
#{ Table Drawing Helpers
|
|
941
|
+
#/////////////////////////////////////////////////////////////////
|
|
942
|
+
|
|
943
|
+
def _fill_table(self, save_config=True):
|
|
944
|
+
"""
|
|
945
|
+
Re-draw the table from scratch, by clearing out the table's
|
|
946
|
+
multi-column listbox; and then filling it in with values from
|
|
947
|
+
``self._rows``. Note that any cell-, row-, or column-specific
|
|
948
|
+
color configuration that has been done will be lost. The
|
|
949
|
+
selection will also be lost -- i.e., no row will be selected
|
|
950
|
+
after this call completes.
|
|
951
|
+
"""
|
|
952
|
+
self._mlb.delete(0, 'end')
|
|
953
|
+
for i, row in enumerate(self._rows):
|
|
954
|
+
if self._reprfunc is not None:
|
|
955
|
+
row = [self._reprfunc(i,j,v) for (j,v) in enumerate(row)]
|
|
956
|
+
self._mlb.insert('end', row)
|
|
957
|
+
|
|
958
|
+
def _get_itemconfig(self, r, c):
|
|
959
|
+
return dict( (k, self._mlb.itemconfig(r, c, k)[-1])
|
|
960
|
+
for k in ('foreground', 'selectforeground',
|
|
961
|
+
'background', 'selectbackground') )
|
|
962
|
+
|
|
963
|
+
def _save_config_info(self, row_indices=None, index_by_id=False):
|
|
964
|
+
"""
|
|
965
|
+
Return a 'cookie' containing information about which row is
|
|
966
|
+
selected, and what color configurations have been applied.
|
|
967
|
+
this information can the be re-applied to the table (after
|
|
968
|
+
making modifications) using ``_restore_config_info()``. Color
|
|
969
|
+
configuration information will be saved for any rows in
|
|
970
|
+
``row_indices``, or in the entire table, if
|
|
971
|
+
``row_indices=None``. If ``index_by_id=True``, the the cookie
|
|
972
|
+
will associate rows with their configuration information based
|
|
973
|
+
on the rows' python id. This is useful when performing
|
|
974
|
+
operations that re-arrange the rows (e.g. ``sort``). If
|
|
975
|
+
``index_by_id=False``, then it is assumed that all rows will be
|
|
976
|
+
in the same order when ``_restore_config_info()`` is called.
|
|
977
|
+
"""
|
|
978
|
+
# Default value for row_indices is all rows.
|
|
979
|
+
if row_indices is None:
|
|
980
|
+
row_indices = list(range(len(self._rows)))
|
|
981
|
+
|
|
982
|
+
# Look up our current selection.
|
|
983
|
+
selection = self.selected_row()
|
|
984
|
+
if index_by_id and selection is not None:
|
|
985
|
+
selection = id(self._rows[selection])
|
|
986
|
+
|
|
987
|
+
# Look up the color configuration info for each row.
|
|
988
|
+
if index_by_id:
|
|
989
|
+
config = dict((id(self._rows[r]), [self._get_itemconfig(r, c)
|
|
990
|
+
for c in range(self._num_columns)])
|
|
991
|
+
for r in row_indices)
|
|
992
|
+
else:
|
|
993
|
+
config = dict((r, [self._get_itemconfig(r, c)
|
|
994
|
+
for c in range(self._num_columns)])
|
|
995
|
+
for r in row_indices)
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
return selection, config
|
|
999
|
+
|
|
1000
|
+
def _restore_config_info(self, cookie, index_by_id=False, see=False):
|
|
1001
|
+
"""
|
|
1002
|
+
Restore selection & color configuration information that was
|
|
1003
|
+
saved using ``_save_config_info``.
|
|
1004
|
+
"""
|
|
1005
|
+
selection, config = cookie
|
|
1006
|
+
|
|
1007
|
+
# Clear the selection.
|
|
1008
|
+
if selection is None:
|
|
1009
|
+
self._mlb.selection_clear(0, 'end')
|
|
1010
|
+
|
|
1011
|
+
# Restore selection & color config
|
|
1012
|
+
if index_by_id:
|
|
1013
|
+
for r, row in enumerate(self._rows):
|
|
1014
|
+
if id(row) in config:
|
|
1015
|
+
for c in range(self._num_columns):
|
|
1016
|
+
self._mlb.itemconfigure(r, c, config[id(row)][c])
|
|
1017
|
+
if id(row) == selection:
|
|
1018
|
+
self._mlb.select(r, see=see)
|
|
1019
|
+
else:
|
|
1020
|
+
if selection is not None:
|
|
1021
|
+
self._mlb.select(selection, see=see)
|
|
1022
|
+
for r in config:
|
|
1023
|
+
for c in range(self._num_columns):
|
|
1024
|
+
self._mlb.itemconfigure(r, c, config[r][c])
|
|
1025
|
+
|
|
1026
|
+
#/////////////////////////////////////////////////////////////////
|
|
1027
|
+
# Debugging (Invariant Checker)
|
|
1028
|
+
#/////////////////////////////////////////////////////////////////
|
|
1029
|
+
|
|
1030
|
+
_DEBUG = False
|
|
1031
|
+
"""If true, then run ``_check_table_vs_mlb()`` after any operation
|
|
1032
|
+
that modifies the table."""
|
|
1033
|
+
|
|
1034
|
+
def _check_table_vs_mlb(self):
|
|
1035
|
+
"""
|
|
1036
|
+
Verify that the contents of the table's ``_rows`` variable match
|
|
1037
|
+
the contents of its multi-listbox (``_mlb``). This is just
|
|
1038
|
+
included for debugging purposes, to make sure that the
|
|
1039
|
+
list-modifying operations are working correctly.
|
|
1040
|
+
"""
|
|
1041
|
+
for col in self._mlb.listboxes:
|
|
1042
|
+
assert len(self) == col.size()
|
|
1043
|
+
for row in self:
|
|
1044
|
+
assert len(row) == self._num_columns
|
|
1045
|
+
assert self._num_columns == len(self._mlb.column_names)
|
|
1046
|
+
#assert self._column_names == self._mlb.column_names
|
|
1047
|
+
for i, row in enumerate(self):
|
|
1048
|
+
for j, cell in enumerate(row):
|
|
1049
|
+
if self._reprfunc is not None:
|
|
1050
|
+
cell = self._reprfunc(i, j, cell)
|
|
1051
|
+
assert self._mlb.get(i)[j] == cell
|
|
1052
|
+
|
|
1053
|
+
######################################################################
|
|
1054
|
+
# Demo/Test Function
|
|
1055
|
+
######################################################################
|
|
1056
|
+
|
|
1057
|
+
# update this to use new WordNet API
|
|
1058
|
+
def demo():
|
|
1059
|
+
root = Tk()
|
|
1060
|
+
root.bind('<Control-q>', lambda e: root.destroy())
|
|
1061
|
+
|
|
1062
|
+
table = Table(root, 'Word Synset Hypernym Hyponym'.split(),
|
|
1063
|
+
column_weights=[0, 1, 1, 1],
|
|
1064
|
+
reprfunc=(lambda i,j,s: ' %s' % s))
|
|
1065
|
+
table.pack(expand=True, fill='both')
|
|
1066
|
+
|
|
1067
|
+
from nltk.corpus import wordnet
|
|
1068
|
+
from nltk.corpus import brown
|
|
1069
|
+
for word, pos in sorted(set(brown.tagged_words()[:500])):
|
|
1070
|
+
if pos[0] != 'N': continue
|
|
1071
|
+
word = word.lower()
|
|
1072
|
+
for synset in wordnet.synsets(word):
|
|
1073
|
+
try:
|
|
1074
|
+
hyper_def = synset.hypernyms()[0].definition()
|
|
1075
|
+
except:
|
|
1076
|
+
hyper_def = '*none*'
|
|
1077
|
+
try:
|
|
1078
|
+
hypo_def = synset.hypernyms()[0].definition()
|
|
1079
|
+
except:
|
|
1080
|
+
hypo_def = '*none*'
|
|
1081
|
+
table.append([word,
|
|
1082
|
+
synset.definition(),
|
|
1083
|
+
hyper_def,
|
|
1084
|
+
hypo_def])
|
|
1085
|
+
|
|
1086
|
+
table.columnconfig('Word', background='#afa')
|
|
1087
|
+
table.columnconfig('Synset', background='#efe')
|
|
1088
|
+
table.columnconfig('Hypernym', background='#fee')
|
|
1089
|
+
table.columnconfig('Hyponym', background='#ffe')
|
|
1090
|
+
for row in range(len(table)):
|
|
1091
|
+
for column in ('Hypernym', 'Hyponym'):
|
|
1092
|
+
if table[row, column] == '*none*':
|
|
1093
|
+
table.itemconfig(row, column, foreground='#666',
|
|
1094
|
+
selectforeground='#666')
|
|
1095
|
+
root.mainloop()
|
|
1096
|
+
|
|
1097
|
+
if __name__ == '__main__':
|
|
1098
|
+
demo()
|