tutorials.de Buch-Aktion 05/2012
ERLEDIGT
JA
ANTWORTEN
1
ZUGRIFFE
1336
EMPFEHLEN
  • An Twitter übertragen
  • An Facebook übertragen
AUF DIESES THEMA
ANTWORTEN
  1. #1
    Avatar von Carron
    Carron Carron ist offline Mitglied Silber
    Registriert seit
    May 2010
    Ort
    Berlin
    Beiträge
    61
    Hallo auch

    Im Rahmen privater und beruflicher Projekte habe ich vor knapp anderthalb Jahren mit der Java-Programmierung begonnen.
    Innerhalb eines solchen Erwuchs der Wunsch eine normale SWT-Table mit zusätzlichen Funktionen auszustatten.

    Da ich für dieses und eine Vielzahl anderer Probleme in diesem Forum stets eine gute Anlaufstelle gefunden habe, möchte ich natürlich auch etwas zurückgeben und hier quasi die bisherigen Ergebnisse meiner Bemühungen (in Sachen Tabelle) auch anderen zur Verfügung stellen.


    Die SorTable selbst bietet neben den grundsätzlichen Funktionalitäten folgende Ergänzungen (zu einem Großteil zusammengesetzt aus Snippets anderer fleißiger Helfer):
    • Sortieren
      • standardmäßig als Zeichenketten,
      • aber auch als einfache Integer-Werte,
      • Double-Werte (auch mit Einheiten)
      • oder Datums-Werte (wobei diese in der aktuellen Implementierung nur deutsche Schreibweisen beachten)
    • Editieren
      (wobei hier sowohl einzelne Spalten als editierbar ausgewählt werden können als auch die ganze Tabelle)
    • Filtern
      (in einem extra Feld eingegebene Begriffe werden als Suchfilter auf die Tabelleninhalte angewandt)
    • Drucken
      (dafür wird der separate SWTTablePrinter benutzt, welcher sich auch für jede andere SWT-Table nutzen lässt)
    • ein Hintergrundbild auf die volle Größe der Tabelle skalieren
    • ausgewählte Einträge in die Zwischenablage kopieren

    Der angesprochene separate SWTTablePrinter dient zum Ausdrucken einer beliebigen SWT-Table und kann damit auch unabhängig von der SorTable genutzt werden.
    Es kann eine Spalte festgelegt werden (meist eine Beschreibungsspalte oder eine andere mit möglicherweise großem Umfang), deren Inhalte - sofern durch das gewählte Papierformat und die zu druckenden Inhalte erforderlich - mit Zeilenumbrüchen ausgestattet werden, bzw. die bereits enthaltenen übernommen werden.
    Darüber hinaus kann eine Überschrift auf der ersten Seite ausgegeben werden und sowohl für diese Überschrift als auch den Tabellenkopf und die Tabelleninhalte benutzerdefinierte Schriftarten verwendet werden.


    Anbei noch eine kleine Test-Klasse, die - bis auf das Hintergrundbild - alle enthaltenen Funktionen vorstellen und deren Nutzung verdeutlichen soll.


    Über Kommentare/Fragen/Anregungen würde ich mich freuen. Vor allem für Möglichkeiten an der Performance zu schrauben bin ich immer zu begeistern. Sei es hinsichtlich Rechenzeit oder Speichernutzung.

    SorTable
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    589
    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    827
    828
    829
    830
    831
    832
    833
    834
    835
    836
    837
    838
    839
    840
    841
    842
    843
    844
    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    870
    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886
    887
    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901
    902
    903
    904
    905
    906
    907
    908
    909
    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    922
    923
    924
    925
    926
    927
    928
    929
    930
    931
    932
    933
    934
    935
    936
    937
    938
    939
    940
    941
    942
    943
    944
    945
    946
    947
    948
    949
    950
    951
    952
    953
    954
    955
    956
    957
    958
    959
    960
    961
    962
    963
    964
    965
    966
    967
    968
    969
    970
    971
    972
    973
    974
    975
    976
    977
    978
    979
    980
    981
    982
    983
    984
    985
    986
    987
    988
    989
    990
    991
    992
    993
    994
    995
    996
    997
    998
    999
    1000
    1001
    1002
    1003
    1004
    1005
    1006
    1007
    1008
    1009
    1010
    1011
    1012
    1013
    1014
    1015
    1016
    1017
    1018
    1019
    1020
    1021
    1022
    1023
    1024
    1025
    1026
    1027
    1028
    1029
    1030
    1031
    1032
    1033
    1034
    1035
    1036
    1037
    1038
    1039
    1040
    1041
    1042
    1043
    1044
    1045
    1046
    1047
    1048
    1049
    1050
    1051
    1052
    1053
    1054
    1055
    1056
    1057
    1058
    1059
    1060
    1061
    1062
    1063
    1064
    1065
    1066
    1067
    1068
    1069
    1070
    1071
    1072
    1073
    1074
    1075
    1076
    1077
    1078
    1079
    1080
    1081
    1082
    1083
    1084
    1085
    1086
    1087
    1088
    1089
    1090
    1091
    1092
    1093
    1094
    1095
    1096
    1097
    1098
    1099
    1100
    1101
    1102
    1103
    1104
    1105
    1106
    1107
    1108
    1109
    1110
    1111
    1112
    1113
    1114
    1115
    1116
    1117
    1118
    1119
    1120
    1121
    1122
    1123
    1124
    1125
    1126
    1127
    1128
    1129
    1130
    1131
    1132
    1133
    1134
    1135
    1136
    1137
    1138
    1139
    1140
    1141
    1142
    1143
    1144
    1145
    1146
    1147
    1148
    1149
    1150
    1151
    1152
    1153
    1154
    1155
    1156
    1157
    1158
    1159
    1160
    1161
    1162
    1163
    1164
    1165
    1166
    1167
    1168
    1169
    1170
    1171
    1172
    1173
    1174
    1175
    1176
    1177
    1178
    1179
    1180
    1181
    1182
    1183
    1184
    1185
    1186
    1187
    1188
    1189
    1190
    1191
    1192
    1193
    1194
    1195
    1196
    1197
    1198
    1199
    1200
    1201
    1202
    1203
    1204
    1205
    1206
    1207
    1208
    1209
    1210
    1211
    1212
    1213
    1214
    1215
    1216
    1217
    1218
    1219
    1220
    1221
    1222
    1223
    1224
    1225
    1226
    1227
    1228
    1229
    1230
    1231
    1232
    1233
    1234
    1235
    1236
    1237
    1238
    1239
    1240
    1241
    1242
    1243
    1244
    1245
    1246
    1247
    1248
    1249
    1250
    1251
    1252
    1253
    1254
    1255
    1256
    1257
    1258
    1259
    1260
    1261
    1262
    1263
    1264
    1265
    1266
    1267
    1268
    1269
    1270
    1271
    1272
    1273
    1274
    1275
    1276
    1277
    1278
    1279
    1280
    1281
    1282
    1283
    1284
    1285
    1286
    1287
    1288
    1289
    1290
    1291
    1292
    1293
    1294
    1295
    1296
    1297
    1298
    1299
    1300
    1301
    1302
    1303
    1304
    1305
    1306
    1307
    1308
    1309
    1310
    1311
    1312
    1313
    1314
    1315
    1316
    1317
    1318
    1319
    1320
    1321
    1322
    1323
    1324
    1325
    1326
    1327
    1328
    1329
    1330
    1331
    1332
    1333
    1334
    1335
    1336
    1337
    1338
    1339
    1340
    1341
    1342
    1343
    1344
    1345
    1346
    1347
    1348
    1349
    1350
    1351
    1352
    1353
    1354
    1355
    1356
    1357
    1358
    1359
    1360
    1361
    1362
    1363
    1364
    1365
    1366
    1367
    1368
    1369
    1370
    1371
    1372
    1373
    1374
    1375
    1376
    1377
    1378
    1379
    1380
    1381
    1382
    1383
    1384
    1385
    1386
    1387
    1388
    1389
    1390
    1391
    1392
    1393
    1394
    1395
    1396
    1397
    1398
    1399
    1400
    1401
    1402
    1403
    1404
    1405
    1406
    1407
    1408
    1409
    1410
    1411
    1412
    1413
    1414
    1415
    1416
    1417
    1418
    1419
    1420
    1421
    1422
    1423
    1424
    1425
    1426
    1427
    1428
    1429
    1430
    1431
    1432
    1433
    1434
    1435
    1436
    1437
    1438
    1439
    1440
    1441
    1442
    1443
    1444
    1445
    1446
    1447
    1448
    1449
    1450
    1451
    1452
    1453
    1454
    1455
    1456
    1457
    1458
    1459
    1460
    1461
    1462
    1463
    1464
    1465
    1466
    1467
    1468
    1469
    1470
    1471
    1472
    1473
    1474
    1475
    1476
    1477
    1478
    1479
    1480
    1481
    1482
    1483
    1484
    1485
    1486
    1487
    1488
    1489
    1490
    1491
    1492
    1493
    1494
    1495
    1496
    1497
    1498
    1499
    1500
    1501
    1502
    1503
    1504
    1505
    1506
    1507
    1508
    1509
    1510
    1511
    1512
    1513
    1514
    1515
    1516
    1517
    1518
    1519
    1520
    1521
    1522
    1523
    1524
    1525
    1526
    1527
    1528
    1529
    1530
    1531
    1532
    1533
    1534
    1535
    1536
    1537
    1538
    1539
    1540
    1541
    1542
    1543
    1544
    1545
    1546
    1547
    1548
    1549
    1550
    1551
    1552
    1553
    1554
    1555
    1556
    1557
    1558
    1559
    1560
    1561
    1562
    1563
    1564
    1565
    1566
    1567
    1568
    1569
    1570
    1571
    1572
    1573
    1574
    1575
    1576
    1577
    1578
    1579
    1580
    1581
    1582
    1583
    1584
    1585
    1586
    1587
    1588
    1589
    1590
    1591
    1592
    1593
    1594
    1595
    1596
    1597
    1598
    1599
    1600
    1601
    1602
    1603
    1604
    1605
    1606
    1607
    1608
    1609
    1610
    1611
    1612
    1613
    1614
    1615
    1616
    1617
    1618
    1619
    1620
    1621
    1622
    1623
    1624
    1625
    1626
    1627
    1628
    1629
    1630
    1631
    1632
    1633
    1634
    1635
    1636
    1637
    1638
    1639
    1640
    1641
    1642
    1643
    1644
    1645
    1646
    1647
    1648
    1649
    1650
    1651
    1652
    1653
    1654
    1655
    1656
    1657
    1658
    1659
    1660
    1661
    1662
    1663
    1664
    1665
    1666
    1667
    1668
    1669
    1670
    1671
    1672
    1673
    1674
    1675
    1676
    1677
    1678
    1679
    1680
    1681
    1682
    1683
    1684
    1685
    1686
    1687
    1688
    1689
    1690
    1691
    1692
    1693
    1694
    1695
    1696
    1697
    1698
    1699
    1700
    1701
    1702
    1703
    1704
    1705
    1706
    1707
    1708
    1709
    1710
    1711
    1712
    1713
    1714
    1715
    1716
    1717
    1718
    1719
    1720
    1721
    1722
    1723
    1724
    1725
    1726
    1727
    1728
    1729
    1730
    1731
    1732
    1733
    1734
    1735
    1736
    1737
    1738
    1739
    
    package org.hermeneutix.utilities.swt;
     
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.regex.Pattern;
     
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.custom.TableEditor;
    import org.eclipse.swt.dnd.Clipboard;
    import org.eclipse.swt.dnd.TextTransfer;
    import org.eclipse.swt.dnd.Transfer;
    import org.eclipse.swt.events.ControlAdapter;
    import org.eclipse.swt.events.ControlEvent;
    import org.eclipse.swt.events.DisposeEvent;
    import org.eclipse.swt.events.DisposeListener;
    import org.eclipse.swt.events.ModifyEvent;
    import org.eclipse.swt.events.ModifyListener;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.graphics.Color;
    import org.eclipse.swt.graphics.Font;
    import org.eclipse.swt.graphics.FontData;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.graphics.ImageData;
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.swt.graphics.Rectangle;
    import org.eclipse.swt.printing.PrintDialog;
    import org.eclipse.swt.printing.PrinterData;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.swt.widgets.Event;
    import org.eclipse.swt.widgets.Listener;
    import org.eclipse.swt.widgets.Menu;
    import org.eclipse.swt.widgets.MenuItem;
    import org.eclipse.swt.widgets.Table;
    import org.eclipse.swt.widgets.TableColumn;
    import org.eclipse.swt.widgets.TableItem;
    import org.eclipse.swt.widgets.Text;
     
    /**
     * Capsuled SWT-{@link Table} with a few additional features.
     * <p>
     * <ul>
     * <li>Sorting</li>
     * <ul>
     * <li>available sorting algorithms can be set for each column by using
     * <code>setSortingMethod()</code> in combination with one of the preset
     * compare modes (<code>COMPARE_TEXT</code>, <code>COMPARE_INT</code>,
     * <code>COMPARE_DOUBLE</code>, <code>COMPARE_DATETIME</code>) </li>
     * <li><code>COMPARE_DATETIME</code> is set for the german date format.</li>
     * <li>after inserting all column the sortability needs to be activated with
     * <code>enableSorting(true)</code>. Columns added after this activation are
     * not available for sorting till a new <code>enableSorting(true)</code> call
     * occurs.</li>
     * </ul>
     * <li>Filtering</li>
     * <ul>
     * <li>after initializing the table itself you can generate a {@link Text} with
     * <code>createSearchField()</code> which serves as a filter input for the
     * table values</li>
     * <li>the background colors of the single table items are lost each time the
     * <code>resetvalues()</code> call (which occurs on each change of the
     * filter). By using <code>addColorListener()</code> you can implement a
     * method for recoloring all items after that happens.</li>
     * </ul>
     * <li>Editing</li>
     * <ul>
     * <li><code>setEditable()</code> enables the possibilty to change the values
     * in the selected columns</li>
     * </ul>
     * <li>Scalable Background</li>
     * <li>Copy to Clipboard</li>
     * <li>Printing</li>
     * </ul>
     * 
     * @author Carsten Englert
     */
    public final class SorTable extends Composite {
     
        /**
         * Text of the popup menu offering to copy the selected items to clipboard.
         */
        private static final String COPY_TEXT = "Copy Selection";
        /**
         * Sorting method regarding the compared values as <code>String</code>s.
         */
        public static final int COMPARE_TEXT = 1;
        /**
         * Sorting method for <code>Integer</code> values.
         */
        public static final int COMPARE_INT = 2;
        /**
         * Sorting method for <code>Double</code> values.<br>
         * Every character which is neither a number, a dot or a comma is ignored.
         */
        public static final int COMPARE_DOUBLE = 4;
        /**
         * Sorting method for date strings in one of the following forms:
         * <code>DD</code>, <code>DD:MM</code>, <code>DD:MM:YYYY</code>,
         * <code>DD:MM:YYYY_hh:mm</code>, <code>DD:MM_hh:mm</code><br>
         * Every character that is no number is considered to be a separator.<br>
         * The single values are interpreted as <code>Integer</code>s.
         */
        public static final int COMPARE_DATETIME = 8;
        /**
         * Default sorting method to set use if no custom mode is selected for the
         * column to sort.<br>
         * Current setting: <code>COMPARE_TEXT</code>
         */
        public static final int COMPARE_DEFAULT = COMPARE_TEXT;
     
        private static final String REGEX_DATETIME_DD = "[\\D]*[\\d]{2}[\\D]*";
        private static final SimpleDateFormat FORMAT_DATETIME_DD = new SimpleDateFormat(
                "dd");
        private static final String REGEX_DATETIME_DDMM = "[\\D]*[\\d]{2}"
                + "[\\D]+[\\d]{2}[\\D]*";
        private static final SimpleDateFormat FORMAT_DATETIME_DDMM = new SimpleDateFormat(
                "dd MM");
        private static final String REGEX_DATETIME_DDMMYYYY = "[\\D]*[\\d]{2}"
                + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{4}[\\D]*";
        private static final SimpleDateFormat FORMAT_DATETIME_DDMMYYYY = new SimpleDateFormat(
                "dd MM yyyy");
        private static final String REGEX_DATETIME_DDMM_HHMM = "[\\D]*[\\d]{2}"
                + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{2}[\\D]*";
        private static final SimpleDateFormat FORMAT_DATETIME_DDMM_HHMM = new SimpleDateFormat(
                "dd MM HH mm");
        private static final String REGEX_DATETIME_DDMMYYYY_HHMM = "[\\D]*[\\d]{2}"
                + "[\\D]+[\\d]{2}" + "[\\D]+[\\d]{4}" + "[\\D]+[\\d]{2}"
                + "[\\D]+[\\d]{2}[\\D]*";
        private static final SimpleDateFormat FORMAT_DATETIME_DDMMYYYY_HHMM = new SimpleDateFormat(
                "dd MM yyyy HH mm");
     
        /**
         * Regular expression representing all characters to be treated as
         * separators in <code>COMPARE_DATETIME</code> mode.<br>
         * Current setting: all non-numeric characters
         */
        private static final String REGEX_DATETIME_SEPARATOR = "[\\D]+";
     
        /**
         * Regular expression representing all characters to be ignored in
         * <code>COMPARE_DOUBLE</code> mode.<br>
         * Current setting: all non-numeric characters except "-" and "."
         */
        private static final String REGEX_DOUBLE_IGNORE = "[^\\d\\.\\-]+";
     
        /**
         * Contained {@link Table}, because it is impossible to extend it directly.
         */
        final Table contentTable;
     
        /**
         * Stored sorting methods for each column
         */
        private final Map<TableColumn, Comparator<TableItem>> comparators;
     
        /**
         * Registered listeners, which are activated when the colors of the table
         * entries are lost once again. (each time the method
         * <code>resetValues()</code> is invoked)
         */
        private final List<Listener> colorListeners = new ArrayList<Listener>();
     
        /**
         * Because it is impossible to set single {@link TableItem}s invisible they
         * need to be disposed in order to apply the search filter.<br>
         * To be able to restore the previous values they have to be stored
         * separatly in this list.
         */
        List<String[]> values = null;
        /**
         * Created {@link Text} for filtering the table values.
         */
        Text searchField = null;
     
        /**
         * Last descending sorted column to change to an ascending order on the next
         * selection of the same column.
         */
        TableColumn lastDescendingColumn = null;
        /**
         * image to scale on background (not created here -> not disposed here)
         */
        private Image backgroundImage = null;
        /**
         * Scale the background image to the whole table size?
         */
        boolean backgroundScaled = false;
        /**
         * {@link TableEditor} responsible for the manipulating of single values.
         */
        TableEditor editor = null;
     
        /**
         * Constructor generates a new {@link SorTable} with visible headers and
         * lines, but without any columns or entries.
         * 
         * @param parent
         *            parent {@link Composite}
         * @param style
         *            SWT style constants to apply
         */
        public SorTable(final Composite parent, final int style) {
            super(parent, SWT.NONE);
            // initialize contained table
            this.contentTable = new Table(this, style);
            this.comparators = new HashMap<TableColumn, Comparator<TableItem>>();
            // set default visibility settings
            setHeaderVisible(true);
            setLinesVisible(true);
            // create popup menu for clipboard usage
            final Menu popup = new Menu(getShell(), SWT.POP_UP);
            final MenuItem copyItem = new MenuItem(popup, SWT.PUSH);
            copyItem.setText(COPY_TEXT);
            copyItem.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent event) {
                    // copy selected items to clipboard
                    copySelectionToClipboard();
                }
            });
            this.contentTable.setMenu(popup);
            addControlListener(new ControlAdapter() {
                @Override
                public void controlResized(final ControlEvent event) {
                    // manage correct width of columns
                    controlColumnSize();
                    if (SorTable.this.backgroundScaled) {
                        // recalculate background for new size
                        scaleBackground();
                    }
                }
            });
        }
     
        /**
         * Generates a new {@link SorTable} with visible headers and lines, creates
         * columns according to the specified headers, inserts the given contents
         * and enables the sortability regarding the <code>COMPARE_DEFAULT</code>
         * value. Other sorting methods (<code>COMPARE_TEXT</code>,
         * <code>COMPARE_INT</code>, <code>COMPARE_DOUBLE</code>,
         * <code>COMPARE_DATETIME</code>) need to be set separatly by calling
         * <code>setSortingMethod()</code> for each targeted column.
         * 
         * @param parent
         *            parent {@link Composite}
         * @param style
         *            SWT style constants to apply
         * @param header
         *            column topics to set
         * @param initialValues
         *            contents to insert after initialization
         * @return created {@link SorTable} instance
         */
        public static SorTable createSorTable(final Composite parent,
                final int style, final String[] header,
                final String[][] initialValues) {
            // invoke constructor
            final SorTable created = new SorTable(parent, style);
            // disable redraw to increase performance
            created.setRedraw(false);
            // generate all columns
            for (String singleColumn : header) {
                created.addColumn(SWT.NONE).setText(singleColumn);
            }
            // activate sortability
            created.enableSorting(true);
            if (initialValues != null) {
                // insert initial values
                created.resetValues(initialValues);
            }
            // manage width of columns
            created.controlColumnSize();
            // reactivate redraw
            created.setRedraw(true);
            return created;
        }
     
        /**
         * Adds a new column without header.
         * 
         * @param style
         *            SWT style constants to apply
         * @return created column
         */
        public TableColumn addColumn(final int style) {
            return new TableColumn(this.contentTable, style);
        }
     
        /**
         * Adds a new column with the specified header.
         * 
         * @param style
         *            SWT style constants to apply
         * @param topic
         *            column header to set
         * @return created column
         */
        public TableColumn addColumn(final int style, final String topic) {
            final TableColumn column = addColumn(style);
            column.setText(topic);
            return column;
        }
     
        /**
         * Adds a new column on the specified position without header.
         * 
         * @param style
         *            SWT style constants to apply
         * @param index
         *            where to insert the generated column
         * @return created column
         */
        public TableColumn addColumn(final int style, final int index) {
            return new TableColumn(this.contentTable, style, index);
        }
     
        /**
         * Adds a new column on the specified position with the given header.
         * 
         * @param style
         *            SWT style constants to apply
         * @param index
         *            where to insert the generated column
         * @param topic
         *            column header to set
         * @return created column
         */
        public TableColumn addColumn(final int style, final int index,
                final String topic) {
            final TableColumn column = addColumn(style, index);
            column.setText(topic);
            return column;
        }
     
        /**
         * Inserts a new entry with the given values on the end of the table.
         * 
         * @param style
         *            SWT style constants to apply
         * @param itemValues
         *            values to set
         * @return created item
         */
        public TableItem addItem(final int style, final String[] itemValues) {
            return (addItem(style, this.contentTable.getItemCount(), itemValues));
        }
     
        /**
         * Inserts a new entry with the given values on the specified position.
         * 
         * @param style
         *            SWT style constants to apply
         * @param index
         *            position where to insert the new table item
         * @param itemValues
         *            values to set
         * @return created item
         */
        public TableItem addItem(final int style, final int index,
                final String[] itemValues) {
            final TableItem created = new TableItem(this.contentTable, style, index);
            // copy values to display
            final String[] text = new String[itemValues.length];
            System.arraycopy(itemValues, 0, text, 0, itemValues.length);
            created.setText(text);
            if (!this.contentTable.getHeaderVisible()) {
                /*
                 * user is unable to set the column width on its own: do it
                 * automatically
                 */
                for (int i = 1; i < this.contentTable.getColumnCount(); i++) {
                    this.contentTable.getColumn(i - 1).pack();
                }
                controlColumnSize();
            }
            if (this.values != null) {
                // filtering is in use
                synchronized (this.values) {
                    // store values for restoration
                    this.values.add(index, text);
                }
                /*
                 * if the item is disposed by anyone except the filter, the
                 * restoration should not bring this entry back
                 */
                created.addDisposeListener(new DisposeListener() {
                    public void widgetDisposed(DisposeEvent ev) {
                        if (SorTable.this.values != null) {
                            synchronized (SorTable.this.values) {
                                // remove associated values from restoration list
                                SorTable.this.values.remove(text);
                            }
                        }
                    }
                });
            }
            return (created);
        }
     
        /**
         * Inserts a new entry with the given values on the specified position by
         * regarding the current sortation.
         * 
         * @param style
         *            SWT style constants to apply
         * @param itemValues
         *            values to set
         * @return created item
         */
        public TableItem insertItem(final int style, final String[] itemValues) {
            // disable redraw to increase performance
            this.contentTable.setRedraw(false);
            // insert an item on the end of the table
            final TableItem checkItem = addItem(style, itemValues);
            // which column is currently selected for the sorting?
            final TableColumn sortColumn = this.contentTable.getSortColumn();
            TableItem insertedItem = checkItem;
            if (sortColumn != null) {
                // get items with the (temporary) entry
                final TableItem[] items = this.contentTable.getItems();
                // get the responsible comparator
                final Comparator<TableItem> comparator = this.comparators
                        .get(sortColumn);
                // sort items including the recently added one
                Arrays.sort(items, comparator);
                // where is the correct position of the item?
                int index = Arrays.binarySearch(items, checkItem, comparator);
                // what is the current sort direction?
                final boolean descending = (this.lastDescendingColumn == sortColumn);
                // only bother if it should not be at the end
                if (descending || (index < (items.length - 1))) {
                    /*
                     * the inserted item should be treated as the largest of
                     * (possible) surrounding equals
                     */
                    if (descending) {
                        // descending sort direction: larger = up
                        while (index > 0) {
                            if (comparator.compare(items[index - 1], items[index]) != 0) {
                                // compared item is larger: not further
                                break;
                            }
                            index--;
                        }
                    } else {
                        // ascending sort direction: larger = down
                        while (index < (items.length - 1)) {
                            if (comparator.compare(items[index], items[index + 1]) != 0) {
                                // compared item is larger: not further
                                break;
                            }
                            index++;
                        }
                    }
                    // remove the temporary item at the end
                    checkItem.dispose();
                    // insert new item on the correct position
                    insertedItem = addItem(style, index, itemValues);
                }
            }
            // handle width of columns
            controlColumnSize();
            // reactivate redraw
            this.contentTable.setRedraw(true);
            return insertedItem;
        }
     
        /**
         * Select the item at the specified position.
         * 
         * @param index
         *            position of the item to select
         */
        public void select(final int index) {
            this.contentTable.select(index);
        }
     
        /**
         * Select the items at the specified indizes
         * 
         * @param indizes
         *            positions of the items to select
         */
        public void select(final int[] indizes) {
            this.contentTable.select(indizes);
        }
     
        /**
         * Select all current entries of the table.
         */
        public void selectAll() {
            this.contentTable.selectAll();
        }
     
        /**
         * Select the item at the specified position.
         * 
         * @param index
         *            position of the item to select
         */
        public void setSelection(final int index) {
            this.contentTable.setSelection(index);
        }
     
        /**
         * Select the items at the specified indizes
         * 
         * @param indizes
         *            positions of the items to select
         */
        public void setSelection(final int[] indizes) {
            this.contentTable.setSelection(indizes);
        }
     
        /**
         * Select the specified item
         * 
         * @param target
         *            item to select
         */
        public void setSelection(final TableItem target) {
            this.contentTable.setSelection(target);
        }
     
        /**
         * Select the specified items
         * 
         * @param targets
         *            items to select
         */
        public void setSelection(final TableItem[] targets) {
            this.contentTable.setSelection(targets);
        }
     
        /**
         * Set the visibility of the column headers.
         * 
         * @param flag
         *            visible or not?
         */
        public void setHeaderVisible(final boolean flag) {
            this.contentTable.setHeaderVisible(flag);
        }
     
        /**
         * Set the line visiblity.
         * 
         * @param flag
         *            visible or not?
         */
        public void setLinesVisible(final boolean flag) {
            this.contentTable.setLinesVisible(flag);
        }
     
        /**
         * Scroll visible part of the table to the specified item index.
         * 
         * @param index
         *            position of the targeted table item
         */
        public void setTopIndex(final int index) {
            this.contentTable.setTopIndex(index);
        }
     
        /**
         * Scroll visible part of the table to the specified item.
         * 
         * @param target
         *            targeted table item
         */
        public void setItemOnTop(final TableItem target) {
            setTopIndex(Math.max(0, Arrays.asList(this.contentTable.getItems())
                    .indexOf(target)));
        }
     
        @Override
        public void setBackground(final Color background) {
            // avoid setting the background color of the composite behind the table
            this.contentTable.setBackground(background);
        }
     
        /**
         * @param index
         *            position of the requested column
         * @return column on the specified position
         */
        public TableColumn getColumn(final int index) {
            return this.contentTable.getColumn(index);
        }
     
        /**
         * @return all contained columns
         */
        public TableColumn[] getColumns() {
            return this.contentTable.getColumns();
        }
     
        /**
         * @return number of contained columns
         */
        public int getColumnCount() {
            return this.contentTable.getColumnCount();
        }
     
        /**
         * @param targetColumn
         *            targeted column to request
         * @return position of the column
         */
        public int getColumnIndex(final TableColumn targetColumn) {
            return Arrays.asList(getColumns()).indexOf(targetColumn);
        }
     
        /**
         * @return all currently displayed entries
         */
        public TableItem[] getItems() {
            return this.contentTable.getItems();
        }
     
        /**
         * @param index
         *            position of the requested entry
         * @return table entry on the specified position
         */
        public TableItem getItem(final int index) {
            return this.contentTable.getItem(index);
        }
     
        /**
         * @return all currently selected entries
         */
        public TableItem[] getSelection() {
            return this.contentTable.getSelection();
        }
     
        /**
         * @return position of the (first) selected entry
         */
        public int getSelectionIndex() {
            return this.contentTable.getSelectionIndex();
        }
     
        @Override
        public void setFont(final Font value) {
            // avoid setting the font of the composite behind the table
            this.contentTable.setFont(value);
        }
     
        @Override
        public void setBackgroundImage(final Image image) {
            // avoid setting the background image of the composite behind the table
            if (!this.backgroundScaled) {
                this.backgroundImage = null;
                this.contentTable.setBackgroundImage(image);
            } else {
                // scaling was enabled earlier
                this.backgroundImage = image;
                scaleBackground();
            }
        }
     
        /**
         * Sets the background image to the given value and enables/disables the
         * scaling to the full size of the table.
         * 
         * @param image
         *            background image to set
         * @param scaled
         *            scale image to fill the whole background at once
         */
        public void setBackgroundImage(final Image image, final boolean scaled) {
            this.backgroundScaled = scaled;
            setBackgroundImage(image);
        }
     
        /**
         * Scale stored background image to fill the whole background of the table
         * at once
         */
        void scaleBackground() {
            if (this.backgroundImage == null) {
                // you are wasting my time...
                return;
            }
            // what size are we talking about?
            final Rectangle clientArea = this.contentTable.getClientArea();
            if ((clientArea.width == 0) || (clientArea.height == 0)) {
                // no need to try
                return;
            }
            final Image oldImage = this.contentTable.getBackgroundImage();
            if ((oldImage != null) && (oldImage != this.backgroundImage)
                    && (!oldImage.isDisposed())) {
                // clean up old scaled background image
                oldImage.dispose();
            }
            // what image sould be scaled here?
            final ImageData sourceData = this.backgroundImage.getImageData();
            // calculate scaling factor
            final double scale = Math.max(((double) clientArea.width)
                    / sourceData.width, ((double) clientArea.height)
                    / sourceData.height);
            // execute scaling
            final ImageData fittingData = sourceData.scaledTo(
                    (int) (sourceData.width * scale),
                    (int) (sourceData.height * scale));
            // display the fitting image
            this.contentTable.setBackgroundImage(new Image(getDisplay(),
                    fittingData));
        }
     
        /**
         * Reset the displayed contents to the given values.
         * 
         * @param newValues
         *            entries to display
         */
        public void resetValues(final String[][] newValues) {
            // deactivate redraw to increase performance
            setRedraw(false);
            final boolean filterActive = (this.values != null);
            if (filterActive) {
                // avoid multiple operations of the registered dispose listeners
                this.values = null;
            }
            // remove all old entries
            final TableItem[] oldItems = this.contentTable.getItems();
            for (TableItem singleItem : oldItems) {
                singleItem.dispose();
            }
            if (filterActive) {
                // reset filter functionality
                this.values = new ArrayList<String[]>();
            }
            if (newValues == null) {
                // no new entries to display
                this.contentTable.setItemCount(0);
            } else {
                // insert new entries
                for (String[] singleEntry : newValues) {
                    addItem(SWT.NONE, singleEntry);
                }
                // manage width of columns
                controlColumnSize();
            }
            if (!this.colorListeners.isEmpty()) {
                final Event event = new Event();
                event.widget = this;
                event.doit = true;
                // tell them to reapply the cell colors
                for (Listener singleColorListener : this.colorListeners) {
                    singleColorListener.handleEvent(event);
                }
            }
            setRedraw(true);
        }
     
        /**
         * Register a listener, which is activated each time the colors of the table
         * entries are lost. (each time the method <code>resetValues()</code> is
         * invoked)
         * 
         * @param target
         *            listener to register
         */
        public void addColorListener(final Listener target) {
            this.colorListeners.add(target);
        }
     
        /**
         * Remove a listener, waiting for the loosage of cell colors.
         * 
         * @param target
         *            listener to remove from registration
         */
        public boolean removeColorListener(final Listener target) {
            return this.colorListeners.remove(target);
        }
     
        /**
         * Set the compare mode used when the specified column is selected to sort
         * the table contents.<br>
         * Available sorting methods are accessible by the representing constants (<code>COMPARE_TEXT</code>,
         * <code>COMPARE_INT</code>, <code>COMPARE_DOUBLE</code>,
         * <code>COMPARE_DATETIME</code>).
         * 
         * @param targetColumn
         *            targeted column to be treated differently in sorting
         * @param sortMethod
         *            how to compare the values of the targeted column in two
         *            different entries?
         */
        public void setSortingMethod(final TableColumn targetColumn,
                final int sortMethod) {
            Comparator<TableItem> comparator;
            // create the choosen comparator for the targeted column
            switch (sortMethod) {
            case (COMPARE_TEXT):
                comparator = new Comparator<TableItem>() {
                    public int compare(final TableItem itemOne,
                            final TableItem itemTwo) {
                        return compareText(targetColumn, itemOne, itemTwo);
                    }
                };
                break;
            case (COMPARE_INT):
                comparator = new Comparator<TableItem>() {
                    public int compare(final TableItem itemOne,
                            final TableItem itemTwo) {
                        return compareInt(targetColumn, itemOne, itemTwo);
                    }
                };
                break;
            case (COMPARE_DOUBLE):
                comparator = new Comparator<TableItem>() {
                    public int compare(final TableItem itemOne,
                            final TableItem itemTwo) {
                        return compareDouble(targetColumn, itemOne, itemTwo);
                    }
                };
                break;
            case (COMPARE_DATETIME):
                comparator = new Comparator<TableItem>() {
                    public int compare(final TableItem itemOne,
                            final TableItem itemTwo) {
                        return compareDateTime(targetColumn, itemOne, itemTwo);
                    }
                };
                break;
            default:
                // invalid compare mode
                throw new IllegalArgumentException();
            }
            // store created comparator for the targeted column
            this.comparators.put(targetColumn, comparator);
        }
     
        /**
         * Compares the values of the given column in the two specified entries as
         * <code>String</code>s.
         * 
         * @param targetColumn
         *            selected column
         * @param itemOne
         *            first entry to compare
         * @param itemTwo
         *            second entry to compare
         * @return first entry is smaller, bigger or equals second entry
         */
        int compareText(final TableColumn targetColumn, final TableItem itemOne,
                final TableItem itemTwo) {
            // get current position of the target column
            final int columnIndex = getColumnIndex(targetColumn);
            // compare values of the specified column in both entries
            int result = itemOne.getText(columnIndex).compareToIgnoreCase(
                    itemTwo.getText(columnIndex));
            if (SorTable.this.lastDescendingColumn == targetColumn) {
                /*
                 * column was already sorted in descending order: invert sort
                 * direction
                 */
                result *= -1;
            }
            return result;
        }
     
        /**
         * Compares the values of the given column in the two specified entries as
         * <code>Integer</code>s.
         * 
         * @param targetColumn
         *            selected column
         * @param itemOne
         *            first entry to compare
         * @param itemTwo
         *            second entry to compare
         * @return first entry is smaller, bigger or equals second entry
         */
        int compareInt(final TableColumn targetColumn, final TableItem itemOne,
                final TableItem itemTwo) {
            int result;
            // get current position of the target column
            final int columnIndex = getColumnIndex(targetColumn);
            // compare values of the specified column in both entries
            try {
                // parse integer values
                final Integer valueOne = Integer.valueOf(itemOne
                        .getText(columnIndex));
                final Integer valueTwo = Integer.valueOf(itemTwo
                        .getText(columnIndex));
                // compare successfully parsed values
                result = valueOne.compareTo(valueTwo);
                if (SorTable.this.lastDescendingColumn == targetColumn) {
                    /*
                     * column was already sorted in descending order: invert sort
                     * direction
                     */
                    result *= -1;
                }
            } catch (NumberFormatException ex) {
                // at least one value cannot be interpreted as an integer
                result = compareText(targetColumn, itemOne, itemTwo);
            }
            return result;
        }
     
        /**
         * Compares the values of the given column in the two specified entries as
         * <code>Double</code>s.
         * 
         * @param targetColumn
         *            selected column
         * @param itemOne
         *            first entry to compare
         * @param itemTwo
         *            second entry to compare
         * @return first entry is smaller, bigger or equals second entry
         */
        int compareDouble(final TableColumn targetColumn, final TableItem itemOne,
                final TableItem itemTwo) {
            int result;
            // get current position of the target column
            final int columnIndex = getColumnIndex(targetColumn);
            // compare values of the specified column in both entries
            try {
                final String nothing = "";
                // parse double values
                final Double valueOne = Double.valueOf(itemOne.getText(columnIndex)
                        .replaceAll(REGEX_DOUBLE_IGNORE, nothing));
                final Double valueTwo = Double.valueOf(itemTwo.getText(columnIndex)
                        .replaceAll(REGEX_DOUBLE_IGNORE, nothing));
                // compare successfully parsed values
                result = valueOne.compareTo(valueTwo);
                if (SorTable.this.lastDescendingColumn == targetColumn) {
                    /*
                     * column was already sorted in descending order: invert sort
                     * direction
                     */
                    result *= -1;
                }
            } catch (NumberFormatException ex) {
                // at least one value cannot be interpreted as a double
                result = compareText(targetColumn, itemOne, itemTwo);
            }
            return result;
        }
     
        /**
         * Compares the values of the given column in the two specified entries as
         * date strings in one of the following forms: <code>DD.</code>,
         * <code>DD.MM.</code>, <code>DD.MM.YYYY</code>,
         * <code>DD.MM.YYYY_hh:mm</code>, <code>DD.MM_hh:mm</code><br>
         * Every character that is no number is considered to be a separator.<br>
         * The single values are interpreted as <code>Integer</code>s.
         * 
         * @param targetColumn
         *            selected column
         * @param itemOne
         *            first entry to compare
         * @param itemTwo
         *            second entry to compare
         * @return first entry is smaller, bigger or equals second entry
         */
        int compareDateTime(TableColumn targetColumn, TableItem itemOne,
                TableItem itemTwo) {
            int result;
            // get current position of the target column
            final int columnIndex = getColumnIndex(targetColumn);
            try {
                // read contained dates and compare them
                result = parseDate(itemOne.getText(columnIndex)).compareTo(
                        parseDate(itemTwo.getText(columnIndex)));
                if (SorTable.this.lastDescendingColumn == targetColumn) {
                    /*
                     * column was already sorted in descending order: invert sort
                     * direction
                     */
                    result *= -1;
                }
            } catch (final ParseException ex) {
                /*
                 * at least one of the contained strings do not fit on any of the
                 * given date formats
                 */
                result = compareText(targetColumn, itemOne, itemTwo);
            }
            return result;
        }
     
        /**
         * Reads the given <code>String</code> expecting one of the given date
         * formats:<br>
         * <code>DD.</code>, <code>DD.MM.</code>, <code>DD.MM.YYYY</code>,
         * <code>DD.MM.YYYY_hh:mm</code> or <code>DD.MM._hh:mm</code></br> All
         * non-numeric characters are treated to be separators. Empty segments are
         * ignored.
         * 
         * @param dateString
         *            date <code>String</code> to parse
         * @return contained date value
         * @throws ParseException
         *             failed to parse given format or <code>String</code> did not
         *             match any allowed pattern
         */
        private Date parseDate(final String dateString) throws ParseException {
            // replace separators with whitespaces to use trim()
            final String cleanValue = dateString.replaceAll(
                    REGEX_DATETIME_SEPARATOR, " ").trim();
            // get the Formatter for the matching pattern
            SimpleDateFormat formatter;
            if (Pattern.matches(REGEX_DATETIME_DD, cleanValue)) {
                formatter = FORMAT_DATETIME_DD;
            } else if (Pattern.matches(REGEX_DATETIME_DDMM, cleanValue)) {
                formatter = FORMAT_DATETIME_DDMM;
            } else if (Pattern.matches(REGEX_DATETIME_DDMMYYYY, cleanValue)) {
                formatter = FORMAT_DATETIME_DDMMYYYY;
            } else if (Pattern.matches(REGEX_DATETIME_DDMM_HHMM, cleanValue)) {
                formatter = FORMAT_DATETIME_DDMM_HHMM;
            } else if (Pattern.matches(REGEX_DATETIME_DDMMYYYY_HHMM, cleanValue)) {
                formatter = FORMAT_DATETIME_DDMMYYYY_HHMM;
            } else {
                // does not fit on any given pattern
                throw new ParseException(dateString, 0);
            }
            synchronized (formatter) {
                // parse the contained date
                return formatter.parse(cleanValue);
            }
        }
     
        /**
         * Activates/Deactivates the sortability of the currently contained columns.
         * 
         * @param aFlag
         *            activate or deactivate?
         */
        public void enableSorting(final boolean aFlag) {
            // remove sort column highlighting
            this.contentTable.setSortColumn(null);
            for (final TableColumn singleColumn : this.contentTable.getColumns()) {
                if (aFlag && (!singleColumn.isListening(SWT.Selection))) {
                    // activate sortability if it is not already sortable
                    singleColumn.addListener(SWT.Selection, new Listener() {
                        public void handleEvent(final Event ev) {
                            // execute sorting by the selected column
                            sortTable(singleColumn);
                        }
                    });
                }
                if (!aFlag) {
                    // remove all listeners to deactivate sortability
                    for (Listener singleListener : singleColumn
                            .getListeners(SWT.Selection)) {
                        singleColumn.removeListener(SWT.Selection, singleListener);
                    }
                }
            }
            controlColumnSize();
        }
     
        /**
         * Sort the currently contained table values by the specified column<br>
         * In case this column was the last sorted one, the sort direction is
         * inverted.
         * 
         * @param targetColumn
         *            column to sort the values by
         */
        public void sortTable(final TableColumn targetColumn) {
            // executing sorting
            sortByColumn(targetColumn);
            // show which column was selected for sorting
            this.contentTable.setSortColumn(targetColumn);
            // determine sort direction
            int sortDirection;
            if (this.lastDescendingColumn == targetColumn) {
                /*
                 * column was already sorted (descending); sort direction is
                 * inverted (ascending)
                 */
                sortDirection = SWT.UP;
                // next time the column should be sorted descending again
                this.lastDescendingColumn = null;
            } else {
                // sort direction by default: descending
                sortDirection = SWT.DOWN;
                // remember descending sorted column
                this.lastDescendingColumn = targetColumn;
            }
            // show sort direction
            this.contentTable.setSortDirection(sortDirection);
        }
     
        /**
         * Sort the table values referring to the entries in the given column.
         * 
         * @param targetColumn
         *            column to regard the entries in
         */
        private void sortByColumn(final TableColumn targetColumn) {
            // ensure a sorting method is selected for the given column
            if (!this.comparators.containsKey(targetColumn)) {
                // no sorting method stored: use default
                setSortingMethod(targetColumn, COMPARE_DEFAULT);
            }
            // is a search filter in use?
            final boolean filterInUse = (this.searchField != null)
                    && (this.searchField.getText() != null)
                    && (this.searchField.getText().length() > 0);
            // deactivate redraw to increase performance
            setRedraw(false);
            if (filterInUse && (this.values != null)) {
                // sort all entries (not only the filtered values)
                resetValues(this.values.toArray(new String[this.values.size()][]));
            }
            final TableItem[] items = getItems();
            // sort talbe entries in the array
            Arrays.sort(items, this.comparators.get(targetColumn));
     
            /*
             * display the changed entry order
             */
            final int columnCount = getColumnCount();
            final String[] itemValues = new String[columnCount];
            final Color[] backgrounds = new Color[columnCount];
            final Color[] foregrounds = new Color[columnCount];
            for (int i = 0; i < items.length; i++) {
                final TableItem targetItem = items[items.length - 1 - i];
                // collect values of the single entry
                for (int j = 0; j < columnCount; j++) {
                    itemValues[j] = targetItem.getText(j);
                }
                // remember cell colors
                for (int j = 0; !filterInUse && j < columnCount; j++) {
                    backgrounds[j] = targetItem.getBackground(j);
                    foregrounds[j] = targetItem.getForeground(j);
                }
                // remove old entry from view
                targetItem.dispose();
                // create new entry with the collected values on the right position
                final TableItem newItem = addItem(SWT.NONE, i, itemValues);
                // restore cell colors
                for (int j = 0; !filterInUse && j < columnCount; j++) {
                    newItem.setBackground(j, backgrounds[j]);
                    newItem.setForeground(j, foregrounds[j]);
                }
            }
            if (filterInUse) {
                // reapply the previously used filter on the sorted table
                applyFilter();
            }
            // reactivate visual representation
            setRedraw(true);
        }
     
        /**
         * Generate a {@link Text} which serves as a filter input for the table
         * values.<br>
         * The background colors of the single table items are lost each time the
         * filter changes.
         * <p>
         * It is impossible to create a second search field for the same table.
         * 
         * @param parent
         *            parent {@link Composite}
         * @param style
         *            SWT style constants to apply
         * @return created search field
         */
        public Text createSearchField(Composite parent, int style) {
            if (this.searchField != null) {
                // another search field already exists
                throw new IllegalStateException();
            }
            final int columnCount = this.contentTable.getColumnCount();
            // initialize fallback structure
            this.values = new ArrayList<String[]>();
            synchronized (this.values) {
                // copy current state
                for (TableItem singleItem : this.contentTable.getItems()) {
                    final String[] itemValues = new String[columnCount];
                    for (int i = 0; i < columnCount; i++) {
                        itemValues[i] = singleItem.getText(i);
                    }
                    this.values.add(itemValues);
                    /*
                     * an explicite dispose()-call on an entry should remove the
                     * entry permanently and not only until the filter changes
                     */
                    singleItem.addDisposeListener(new DisposeListener() {
                        public void widgetDisposed(final DisposeEvent ev) {
                            if (SorTable.this.values != null) {
                                // filter still active
                                synchronized (SorTable.this.values) {
                                    // permanently remove the entry
                                    SorTable.this.values.remove(itemValues);
                                }
                            }
                        }
                    });
                }
            }
            // create the search filter
            this.searchField = new Text(parent, style);
            this.searchField.addModifyListener(new ModifyListener() {
                public void modifyText(final ModifyEvent ev) {
                    synchronized (this) {
                        // apply the currently inserted filter
                        applyFilter();
                    }
                }
            });
            this.searchField.addDisposeListener(new DisposeListener() {
                public void widgetDisposed(final DisposeEvent ev) {
                    // deactivate sortability
                    SorTable.this.values = null;
                    SorTable.this.searchField = null;
                }
            });
            return this.searchField;
        }
     
        /**
         * Apply the currently inserted filter on all table entries (not only the
         * currently visible ones).
         */
        void applyFilter() {
            // deactivate redraw to increase performance
            setRedraw(false);
            // restore all entries
            resetValues(this.values.toArray(new String[this.values.size()][]));
            final String filter = this.searchField.getText();
            if ((filter != null) && (filter.length() > 0)) {
                // a filter is set: apply on all entries
                for (TableItem singleItem : getItems()) {
                    if (!filterMatchesItem(singleItem, filter)) {
                        /*
                         * single entry does not match the specified filter; its
                         * removal should not affect the stored list of entries
                         */
                        for (Listener singleListener : singleItem
                                .getListeners(SWT.Dispose)) {
                            singleItem.removeListener(SWT.Dispose, singleListener);
                        }
                        // remove unfitting entry from view
                        singleItem.dispose();
                    }
                }
            }
            // reactivate redraw
            setRedraw(true);
            layout();
        }
     
        /**
         * Check if the given table entry matches the specified filter. A
         * <code>-</code> in front of a single term inverts its result.
         * 
         * @param item
         *            table entry to check
         * @param searchFilter
         *            search filter to apply (whitespace separated terms)
         * @return given entry matches the specified filter
         */
        boolean filterMatchesItem(final TableItem item, final String searchFilter) {
            // check aech of the whitespace separated items
            for (String singleTerm : searchFilter.split("[\\s]")) {
                if (!termMatchesItem(item, singleTerm)) {
                    // entry does not match this term
                    return false;
                }
            }
            // entry matches the whole filter
            return true;
        }
     
        /**
         * Check if the given table entry matches the specified filter term. A
         * <code>-</code> in front inverts its result.
         * 
         * @param item
         *            table entry to check
         * @param searchTerm
         *            filter term to apply
         * @return <code>TRUE</code> : term contained OR term not contained and a
         *         <code>-</code> in front<br>
         *         <code>FALSE</code> : term not contained OR term contained and a
         *         <code>-</code> in front
         */
        private boolean termMatchesItem(final TableItem item,
                final String searchTerm) {
            final boolean noMatch = searchTerm.startsWith("-");
            String term = searchTerm.toLowerCase();
            if (noMatch) {
                // ignore invert character (-)
                term = term.substring(1);
            }
            if (term.length() == 0) {
                // no term to apply
                return true;
            }
            // look for the term in all columns
            for (int i = 0; i < this.contentTable.getColumnCount(); i++) {
                if (item.getText(i).toLowerCase().contains(term)) {
                    // term found in this column
                    return (!noMatch);
                }
            }
            // term is not contained in the given entry
            return noMatch;
        }
     
        /**
         * Enables/Disables the possibility to manipulate single table entries.
         * 
         * @param flag
         *            activate or deactivate editability?
         * @param editableColumns
         *            which columns should be available for manipulation? (no
         *            selection equals all)<br>
         *            if the flag is <code>FALSE</code> the editability is
         *            deactivated for alle columns.
         */
        public void setEditable(final boolean flag, final int... editableColumns) {
            if (this.editor != null) {
                // remove old editor and all MouseDown listeners
                final Listener[] listeners = this.contentTable
                        .getListeners(SWT.MouseDown);
                for (Listener singleMouseDownListener : listeners) {
                    this.contentTable.removeListener(SWT.MouseDown,
                            singleMouseDownListener);
                }
                this.editor.dispose();
            }
            if (!flag) {
                // deactivation finished
                this.editor = null;
                return;
            }
            // create a new TableEditor
            this.editor = new TableEditor(this.contentTable);
            this.editor.horizontalAlignment = SWT.LEFT;
            this.editor.grabHorizontal = true;
            // handle mouse clicks over table
            this.contentTable.addListener(SWT.MouseDown, new Listener() {
                public void handleEvent(final Event mouseDownEvent) {
                    // where was it?
                    final Point clickPosition = new Point(mouseDownEvent.x,
                            mouseDownEvent.y);
                    final TableItem item = SorTable.this.contentTable
                            .getItem(clickPosition);
                    if (item != null) {
                        // click occured over an entry
                        editItem(item, clickPosition, editableColumns);
                    }
                }
     
                private void editItem(final TableItem item,
                        final Point clickPosition, final int[] editableColumns) {
                    // check if the click was on an editable column
                    for (int i = 0; i < getColumnCount(); i++) {
                        // where is the single cell?
                        final Rectangle cellPosition = item.getBounds(i);
                        if (cellPosition.contains(clickPosition)) {
                            // selected cell found
                            editCell(item, i, editableColumns);
                            break;
                        }
                    }
                }
     
                void editCell(final TableItem item, final int columnIndex,
                        final int[] editableColumns) {
                    if ((editableColumns != null) && (editableColumns.length > 0)) {
                        for (int j = 0; j < editableColumns.length; j++) {
                            if (editableColumns[j] == columnIndex) {
                                /*
                                 * selected column is enabled for manipulation
                                 */
                                break;
                            }
                            if ((j + 1) == editableColumns.length) {
                                /*
                                 * selected column is not enabled for manipulation
                                 */
                                return;
                            }
                        }
                    }
                    /*
                     * create manipulation input
                     */
                    final int column = columnIndex;
                    final Text text = new Text(SorTable.this.contentTable, SWT.NONE);
                    text.setFont(SorTable.this.contentTable.getFont());
                    text.addListener(SWT.FocusOut, new Listener() {
                        // manipulation field lost the focus
                        public void handleEvent(final Event ev) {
                            final String newValue = text.getText();
                            if (!newValue.equals(item.getText(column))) {
                                // store changed value
                                item.setText(column, newValue);
                                reinsertChangedItem(item);
                            }
                            // remove maniulation field
                            text.dispose();
                        }
                    });
                    text.addListener(SWT.Traverse, new Listener() {
                        // manipulation field was traversed (e.g. TAB)
                        public void handleEvent(final Event ev) {
                            TableItem newItem = null;
                            if (ev.detail != SWT.TRAVERSE_ESCAPE) {
                                final String newValue = text.getText();
                                if (newValue.equals(item.getText(column))) {
                                    newItem = item;
                                } else {
                                    // store changed value
                                    item.setText(column, newValue);
                                    newItem = reinsertChangedItem(item);
                                }
                            }
                            // remove maniulation field
                            text.dispose();
                            if (newItem != null) {
                                final int index = SorTable.this.contentTable
                                        .indexOf(newItem);
                                switch (ev.detail) {
                                case SWT.TRAVERSE_ARROW_NEXT:
                                    if (ev.keyCode == SWT.ARROW_DOWN) {
                                        ev.doit = (index + 2) > SorTable.this.contentTable
                                                .getItemCount();
                                        if (!ev.doit) {
                                            editCell(getItem(index + 1),
                                                    columnIndex, editableColumns);
                                        }
                                        break;
                                    } // else { FALL THROUGH }
                                case SWT.TRAVERSE_TAB_NEXT:
                                    ev.doit = !traverseNext(newItem, columnIndex,
                                            editableColumns);
                                    break;
                                case SWT.TRAVERSE_ARROW_PREVIOUS:
                                    if (ev.keyCode == SWT.ARROW_UP) {
                                        ev.doit = (index < 1);
                                        if (!ev.doit) {
                                            editCell(getItem(index - 1),
                                                    columnIndex, editableColumns);
                                        }
                                        break;
                                    } // else { FALL THROUGH }
                                case SWT.TRAVERSE_TAB_PREVIOUS:
                                    ev.doit = !traversePrevious(newItem,
                                            columnIndex, editableColumns);
                                    break;
                                }
                            }
                        }
                    });
                    // register manipulation field
                    SorTable.this.editor.setEditor(text, item, column);
                    // preset field with current value
                    text.setText(item.getText(column));
                    // select value and request focus
                    text.selectAll();
                    text.setFocus();
                }
     
                boolean traverseNext(final TableItem item, final int columnIndex,
                        final int[] editableColumns) {
                    int nextColumn;
                    if ((editableColumns == null) || (editableColumns.length == 0)) {
                        nextColumn = columnIndex + 1;
                    } else {
                        nextColumn = getColumnCount();
                        for (int singleColumn : editableColumns) {
                            if (singleColumn > columnIndex) {
                                nextColumn = Math.min(nextColumn, singleColumn);
                            }
                        }
                    }
                    if (nextColumn < getColumnCount()) {
                        editCell(item, nextColumn, editableColumns);
                    } else {
                        final TableItem[] items = getItems();
                        final int itemIndex = Arrays.asList(items).indexOf(item);
                        if (itemIndex < (items.length - 1)) {
                            int firstColumn;
                            if ((editableColumns == null)
                                    || (editableColumns.length == 0)) {
                                firstColumn = 0;
                            } else {
                                firstColumn = editableColumns[0];
                                for (int singleColumn : editableColumns) {
                                    firstColumn = Math.min(firstColumn,
                                            singleColumn);
                                }
                            }
                            editCell(items[itemIndex + 1], firstColumn,
                                    editableColumns);
                        } else {
                            return false;
                        }
                    }
                    return true;
                }
     
                boolean traversePrevious(final TableItem item,
                        final int columnIndex, final int[] editableColumns) {
                    int previousColumn;
                    if ((editableColumns == null) || (editableColumns.length == 0)) {
                        previousColumn = columnIndex - 1;
                    } else {
                        previousColumn = -1;
                        for (int singleColumn : editableColumns) {
                            if (singleColumn < columnIndex) {
                                previousColumn = Math.max(previousColumn,
                                        singleColumn);
                            }
                        }
                    }
                    if (previousColumn > -1) {
                        editCell(item, previousColumn, editableColumns);
                    } else {
                        final TableItem[] items = getItems();
                        final int itemIndex = Arrays.asList(items).indexOf(item);
                        if (itemIndex > 0) {
                            int lastColumn;
                            if ((editableColumns == null)
                                    || (editableColumns.length == 0)) {
                                lastColumn = getColumnCount() - 1;
                            } else {
                                lastColumn = 0;
                                for (int singleColumn : editableColumns) {
                                    lastColumn = Math.max(lastColumn, singleColumn);
                                }
                            }
                            editCell(items[itemIndex - 1], lastColumn,
                                    editableColumns);
                        } else {
                            return false;
                        }
                    }
                    return true;
                }
            });
        }
     
        /**
         * Removes the given entry from the table and inserts a new one with the
         * same values at the same position in the same colors, to ensure the change
         * of an entrys values is stored properly for a possible filter usage.
         * <p>
         * The current filter is reapplied on the table (including the new entry)
         * and might remove the created entry from view.
         * </p>
         * 
         * @param item
         *            entry to remove and recreate
         * @return reinserted entry<br>
         *         returns <code>NULL</code> if the created entry does not match
         *         the current filter</br>
         */
        TableItem reinsertChangedItem(final TableItem item) {
            setRedraw(false);
            // get current entry position
            final int index = this.contentTable.indexOf(item);
            // collect changed values
            final String[] values = new String[getColumnCount()];
            // remember colors
            final Color[] backgrounds = new Color[values.length];
            final Color[] foregrounds = new Color[values.length];
            for (int i = 0; i < values.length; i++) {
                values[i] = item.getText(i);
                backgrounds[i] = item.getBackground(i);
                foregrounds[i] = item.getForeground(i);
            }
            // remove old entry from view
            item.dispose();
            TableItem changedItem = addItem(SWT.NONE, index, values);
            // restore cell colors
            for (int i = 0; i < values.length; i++) {
                changedItem.setBackground(i, backgrounds[i]);
                changedItem.setForeground(i, foregrounds[i]);
            }
            if ((SorTable.this.searchField != null)
                    && (SorTable.this.searchField.getText() != null)
                    && (SorTable.this.searchField.getText().length() > 0)) {
                /*
                 * reapply the previously used filter (might not fit anymore on the
                 * changed item)
                 */
                final int itemCount = this.contentTable.getItemCount();
                applyFilter();
                if (itemCount == this.contentTable.getItemCount()) {
                    // filter did not remove changed item
                    changedItem = this.contentTable.getItem(index);
                } else {
                    // filter removed changed item
                    changedItem = null;
                }
            }
            // reactivate visual representation
            setRedraw(true);
            return changedItem;
        }
     
        /**
         * Manage the column widths and expand the last one to fill the available
         * space
         */
        void controlColumnSize() {
            // deactivate redraw to increase performance
            this.contentTable.setRedraw(false);
            final Rectangle area = getClientArea();
            // calculate the width available for the table entries
            int width = area.width
                    - SorTable.this.contentTable.computeTrim(0, 0, 0, 0).width;
            final Point oldSize = this.contentTable.getSize();
            final TableColumn lastColumn = SorTable.this.contentTable
                    .getColumn(getColumnCount() - 1);
            // calculate how much space is left for the last column
            for (TableColumn singleColumn : this.contentTable.getColumns()) {
                if (singleColumn != lastColumn) {
                    width -= singleColumn.getWidth();
                }
            }
            if (oldSize.x > area.width) {
                /*
                 * table is getting smaller: first decrease column width to fit
                 */
                lastColumn.pack();
                if (lastColumn.getWidth() < width) {
                    lastColumn.setWidth(width);
                }
                this.contentTable.setSize(area.width, area.height);
            } else {
                /*
                 * table is getting bigger: first increase table width to fit
                 */
                this.contentTable.setSize(area.width, area.height);
                lastColumn.pack();
                if (lastColumn.getWidth() < width) {
                    lastColumn.setWidth(width);
                }
            }
            this.contentTable.setRedraw(true);
        }
     
        /**
         * All currently displayed table entries in a two-dimensional
         * <code>String</code> array. (regarding active filter and sorting)
         * 
         * @return current table entries
         */
        public String[][] getValues() {
            final TableItem[] items = getItems();
            final int columnCount = getColumnCount();
            final String[][] values = new String[items.length][];
            for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
                // copy all entries
                final String[] singleItem = new String[columnCount];
                for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    // get each cell value
                    singleItem[columnIndex] = items[itemIndex].getText(columnIndex);
                }
                values[itemIndex] = singleItem;
            }
            return values;
        }
     
        /**
         * Copy the currently selected table entries to the clipboard.<br>
         * Each entry takes up one line and the single column values are separated
         * by tabs.
         */
        public void copySelectionToClipboard() {
            final TableItem[] selection = this.contentTable.getSelection();
            if ((selection == null) || (selection.length == 0)) {
                // no selection: nothing to do
                return;
            }
            final String newLine = System.getProperty("line.separator");
            final char tab = '\t';
            final int columnCount = getColumnCount();
            final StringBuffer valueCollection = new StringBuffer();
            for (int i = 0; i < selection.length; i++) {
                for (int j = 0; j < columnCount; j++) {
                    valueCollection.append(selection[i].getText(j));
                    // separate columns by tabs
                    if ((j + 1) < columnCount) {
                        valueCollection.append(tab);
                    }
                }
                // insert line separator
                if ((i + 1) < selection.length) {
                    valueCollection.append(newLine);
                }
            }
            // copy collected values to clipboard
            new Clipboard(getDisplay()).setContents(new Object[] { valueCollection
                    .toString() }, new Transfer[] { TextTransfer.getInstance() });
        }
     
        /**
         * Print the current table state (regarding filter and sorting).<br>
         * The user selects the general print setting in a {@link PrintDialog}.
         * <p>
         * All but the printer parameter are optional and do not need to be set.
         * 
         * @param printData
         *            {@link PrintData} to use
         * @param topic
         *            topic to display on top of the first page (optional)
         * @param jobTitle
         *            print job description (optional)
         * @param wrapColumn
         *            index of the column to be able to wrap its content on multiple
         *            lines if neccessary (optional)
         * @param tableFont
         *            {@link Font} to use for all table entries (optional)
         * @param topicFont
         *            {@link Font} to use for the topic (optional)
         */
        public void print(final PrinterData printData, final String topic,
                final String jobTitle, final int wrapColumn, final FontData[] tableFont,
                final FontData[] headerFont, final FontData[] topicFont) {
            new SWTTablePrinter(this.contentTable).print(topic, jobTitle,
                    printData, wrapColumn, tableFont, headerFont, topicFont);
        }
    }

    SWTTablePrinter
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    589
    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    827
    828
    829
    830
    831
    832
    833
    834
    835
    836
    837
    838
    839
    840
    841
    842
    843
    844
    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    870
    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886
    887
    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901
    902
    903
    904
    905
    906
    907
    908
    909
    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    
    package org.hermeneutix.utilities.swt;
     
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.graphics.Font;
    import org.eclipse.swt.graphics.FontData;
    import org.eclipse.swt.graphics.GC;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.graphics.ImageData;
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.swt.graphics.Rectangle;
    import org.eclipse.swt.printing.Printer;
    import org.eclipse.swt.printing.PrinterData;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.swt.widgets.MessageBox;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.swt.widgets.Table;
    import org.eclipse.swt.widgets.TableColumn;
    import org.eclipse.swt.widgets.TableItem;
     
    /**
     * Offers the ability to print a single SWT-{@link Table} by regarding custom
     * {@link Font}s and {@link Printer} settings as well as the current header and
     * line visiblity.<br>
     * The page number is displayed on the bottom and a topic can be added on the
     * top of the first page.<br>
     * The width of each column equals the maximum width of the contained values.
     * One column can be selected as the <code>wrapColumn</code>. The values of
     * this column are wrapped in multiple lines if the needed width is bigger than
     * the space that is left.
     * <p>
     * The printing fails if the minimum width of all columns exceeds the available
     * space depending on the chosen page format or the <code>wrapColumn</code>
     * generates an entry longer than one page.
     * 
     * @author Carsten Englert
     */
    public final class SWTTablePrinter {
     
        /**
         * topic of the message box displaying an occured error
         */
        private static final String ERROR_TOPIC = "Error";
        /**
         * first part of the displayed error message if the <code>wrapColumn</code>
         * generated an entry longer than one page
         */
        private static final String ERROR_ENTRYLENGTH1 = "Entry in the ";
        /**
         * second part of the displayed error message if the <code>wrapColumn</code>
         * generated an entry longer than one page
         */
        private static final String ERROR_ENTRYLENGTH2 = ". column "
                + "exceeds the height of one page in the chosen format.\n"
                + "Printing Aborted.";
        /**
         * displayed error message if the minimum width of all columns exceeds the
         * available space depending on the chosen page format
         */
        private static final String ERROR_TABLEWIDTH = "Calculated "
                + "table width exceeds the chosen page format.\n"
                + "Print Job Canceled.";
        /**
         * {@link Font} to use for the topic if no other one is selected
         */
        private static final String DEFAULT_TOPIC_FONT_NAME = "Times New Roman";
        /**
         * font size to use for the topic if no other {@link Font} is selected
         */
        private static final int DEFAULT_TOPIC_FONT_SIZE = 16;
        /**
         * font style to set for the topic if no other {@link Font} is selected
         */
        private static final int DEFAULT_TOPIC_FONT_STYLE = SWT.BOLD;
        /**
         * {@link Font} to use for the table headers if no other one is selected
         */
        private static final String DEFAULT_HEADER_FONT_NAME = "MS Times New Roman";
        /**
         * font size to use for the table headers if no other {@link Font} is
         * selected
         */
        private static final int DEFAULT_HEADER_FONT_SIZE = 11;
        /**
         * font style to set for the table headers of no other {@link Font} is
         * selected
         */
        private static final int DEFAULT_HEADER_FONT_STYLE = SWT.BOLD;
        /**
         * {@link Font} to use for the table contents if no other one is selected
         */
        private static final String DEFAULT_TABLE_FONT_NAME = "MS Times New Roman";
        /**
         * font size to use for the table contents if no other {@link Font} is
         * selected
         */
        private static final int DEFAULT_TABLE_FONT_SIZE = 10;
        /**
         * font style to set for the table contents of no other {@link Font} is
         * selected
         */
        private static final int DEFAULT_TABLE_FONT_STYLE = SWT.NONE;
     
        final Table printSource;
     
        private Printer printer;
        GC gc;
        Font tableFont, headerFont;
        private int headerOffset, leftMargin, rightMargin, topMargin, bottomMargin,
                bottomEnd, endX, lineHeight, horizontalSpacing, verticalSpacing,
                posY, tableTop, wrapXstart, wrapXend, pageCount;
        private int[] columnPosX;
        private boolean headersVisible, linesVisible;
        private String[] headTitles;
        private Image headerImage, footerImage;
     
        /**
         * Constructor stores the targeted SWT-{@link Table}.
         * 
         * @param printSource
         *            SWT-{@link Table} to print on <code>print()</code> call
         */
        public SWTTablePrinter(final Table printSource) {
            this.printSource = printSource;
        }
     
        /**
         * Calculates the print area in the chosen page format.<br>
         * Might be used to determine the target size for the header and/or footer.</br>
         * 
         * @param printData
         *            selected printer and page format
         * @return calculated print area
         */
        public static Rectangle calculatePrintArea(final PrinterData printData) {
            final SWTTablePrinter instance = new SWTTablePrinter(null);
            // initialize needed printer and GC
            instance.printer = new Printer(printData);
            instance.gc = new GC(instance.printer);
            // calculate page format and print area
            instance.calculatePageFormat();
            // clean up resources
            instance.gc.dispose();
            instance.printer.dispose();
            // return the calculated print area
            return new Rectangle(instance.leftMargin, instance.topMargin,
                    instance.rightMargin - instance.leftMargin,
                    instance.bottomMargin - instance.topMargin);
        }
     
        /**
         * Prints the SWT-{@link Table} on the specified {@link Printer} by
         * wrapping the values in the column on the specified
         * <code>wrapColumn</code>-Index if neccessary. The width of each column
         * equals the maximum width of the contained values. If the calculated width
         * exceeds the available space, the <code>wrapColumn</code> is set to the
         * available space and enabled to fill multiple lines where needed.
         * <p>
         * In this case the given topic-<code>String</code> is transfered to an
         * image and printed (in the specified topicFont) on top of the first page.
         * </p>
         * 
         * @param topic
         *            topic to print on the top of the first page (optional)
         * @param jobTitle
         *            name of the print job (optional)
         * @param printData
         *            targeted printer regarding the selected options and page
         *            format
         * @param wrapColumn
         *            index of the column to be wrapped in case of need
         * @param tableEntryFont
         *            {@link Font} to use for the table contents (optional)
         * @param tableHeaderFont
         *            {@link Font} to use for the table headers (optional)
         * @param topicFont
         *            {@link Font} to use for the table headers (optional)
         */
        public void print(final String topic, final String jobTitle,
                final PrinterData printData, final int wrapColumn,
                final FontData[] tableEntryFont, final FontData[] tableHeaderFont,
                final FontData[] topicFont) {
            print(topic, null, 0, null, jobTitle, printData, wrapColumn,
                    tableEntryFont, tableHeaderFont, topicFont);
        }
     
        /**
         * Prints the SWT-{@link Table} on the specified {@link Printer} by
         * wrapping the values in the column on the specified
         * <code>wrapColumn</code>-Index if neccessary. The width of each column
         * equals the maximum width of the contained values. If the calculated width
         * exceeds the available space, the <code>wrapColumn</code> is set to the
         * available space and enabled to fill multiple lines where needed.
         * <p>
         * In this case the given topic-<code>String</code> is transfered to an
         * image and printed (in the specified topicFont) on top of the first page.
         * The given footer image is applied to the bottom of the last page.
         * </p>
         * 
         * @param topic
         *            topic to print on the top of the first page (optional)
         * @param topicOffset
         *            vertical position correction for the topic (optional)<br>
         *            case = 0 : top edge of the topic is the top edge of the print
         *            area (regarding top margin)</br> case < 0 : top edge of the
         *            topic is over the top edge of the print area (in the top
         *            margin)<br>
         *            case > 0 : top edge of the topic is below the top edge of the
         *            print area (additional top margin on first site)</br>
         * @param footer
         *            footer to print on the bottom of the last page (optional)
         * @param jobTitle
         *            name of the print job (optional)
         * @param printData
         *            targeted printer regarding the selected options and page
         *            format
         * @param wrapColumn
         *            index of the column to be wrapped in case of need
         * @param tableEntryFont
         *            {@link Font} to use for the table contents (optional)
         * @param tableHeaderFont
         *            {@link Font} to use for the table headers (optional)
         */
        public void print(final String topic, final int topicOffset,
                final ImageData footer, final String jobTitle,
                final PrinterData printData, final int wrapColumn,
                final FontData[] tableEntryFont, final FontData[] tableHeaderFont) {
            print(topic, null, topicOffset, footer, jobTitle, printData,
                    wrapColumn, tableEntryFont, tableHeaderFont, null);
        }
     
        /**
         * Prints the SWT-{@link Table} on the specified {@link Printer} by
         * wrapping the values in the column on the specified
         * <code>wrapColumn</code>-Index if neccessary. The width of each column
         * equals the maximum width of the contained values. If the calculated width
         * exceeds the available space, the <code>wrapColumn</code> is set to the
         * available space and enabled to fill multiple lines where needed.
         * <p>
         * In this case the given images for header and footer are applied to the
         * first (header) and the last (footer) page.
         * </p>
         * 
         * @param header
         *            header to print on the top of the first page (optional)
         * @param headerOffset
         *            vertical position correction for the header (optional)<br>
         *            case = 0 : top edge of the header is the top edge of the print
         *            area (regarding top margin)</br> case < 0 : top edge of the
         *            header is over the top edge of the print area (in the top
         *            margin)<br>
         *            case > 0 : top edge of the header is below the top edge of the
         *            print area (additional top margin on first site)</br>
         * @param footer
         *            footer to print on the bottom of the last page (optional)
         * @param jobTitle
         *            name of the print job (optional)
         * @param printData
         *            targeted printer regarding the selected options and page
         *            format
         * @param wrapColumn
         *            index of the column to be wrapped in case of need
         * @param tableEntryFont
         *            {@link Font} to use for the table contents (optional)
         * @param tableHeaderFont
         *            {@link Font} to use for the table headers (optional)
         */
        public void print(final ImageData header, final int headerOffset,
                final ImageData footer, final String jobTitle,
                final PrinterData printData, final int wrapColumn,
                final FontData[] tableEntryFont, final FontData[] tableHeaderFont) {
            print(null, header, headerOffset, footer, jobTitle, printData,
                    wrapColumn, tableEntryFont, tableHeaderFont, null);
        }
     
        /**
         * Prints the SWT-{@link Table} on the specified {@link Printer} by
         * wrapping the values in the column on the specified
         * <code>wrapColumn</code>-Index if neccessary. The width of each column
         * equals the maximum width of the contained values. If the calculated width
         * exceeds the available space, the <code>wrapColumn</code> is set to the
         * available space and enabled to fill multiple lines where needed.
         * <p>
         * Combines the three possible <code>print()</code> calls:
         * <ul>
         * <li>if the topic <code>String</code> is set, it's transfered to an
         * image and stored as the header. That's why the given header image is
         * ignored, when the topic parameter is not <code>null</code>.</li>
         * <li>the header (either the transfered topic or the given image) is
         * printed on top of the first page</li>
         * <li>the footer is printed on the bottom of the last page</li>
         * </ul>
         * </p>
         * 
         * @param topic
         *            topic to print on the top of the first page (optional)<br>
         *            if a topic is set, the header is ignored</br>
         * @param header
         *            header to print on the top of the first page (optional)
         * @param headerOffset
         *            vertical position correction for the header (optional)<br>
         *            case = 0 : top edge of the header is the top edge of the print
         *            area (regarding top margin)</br> case < 0 : top edge of the
         *            header is over the top edge of the print area (in the top
         *            margin)<br>
         *            case > 0 : top edge of the header is below the top edge of the
         *            print area (additional top margin on first site)</br>
         * @param footer
         *            footer to print on the bottom of the last page (optional)
         * @param jobTitle
         *            name of the print job (optional)
         * @param printData
         *            targeted printer regarding the selected options and page
         *            format
         * @param wrapColumn
         *            index of the column to be wrapped in case of need
         * @param tableEntryFont
         *            {@link Font} to use for the table contents (optional)
         * @param tableHeaderFont
         *            {@link Font} to use for the table headers (optional)
         * @param topicFont
         *            {@link Font} to use for the table headers (optional) <br>
         *            if a header is set, this {@link Font} is ignored</br>
         */
        private void print(final String topic, final ImageData header,
                final int headerOffset, final ImageData footer,
                final String jobTitle, final PrinterData printData,
                final int wrapColumn, final FontData[] tableEntryFont,
                final FontData[] tableHeaderFont, final FontData[] topicFont) {
            // store selected offset
            this.headerOffset = headerOffset;
            this.printer = new Printer(printData);
            if (tableEntryFont == null) {
                // no font selected, use the default
                this.tableFont = new Font(this.printer, DEFAULT_TABLE_FONT_NAME,
                        DEFAULT_TABLE_FONT_SIZE, DEFAULT_TABLE_FONT_STYLE);
            } else {
                // transfer selected font to new context
                this.tableFont = new Font(this.printer, tableEntryFont);
            }
            if (tableHeaderFont == null) {
                // no font selected, use the default
                this.headerFont = new Font(this.printer, DEFAULT_HEADER_FONT_NAME,
                        DEFAULT_HEADER_FONT_SIZE, DEFAULT_HEADER_FONT_STYLE);
            } else {
                // transfer selected font to new context
                this.headerFont = new Font(this.printer, tableHeaderFont);
            }
            this.gc = new GC(this.printer);
            // regard chosen font for the table contents
            this.gc.setFont(this.tableFont);
            // prepare printing
            calculatePageFormat();
     
            if (topic != null) {
                // transfer topic String to header Image
                this.headerImage = createTopicHeader(topic, topicFont);
            } else if (header == null) {
                this.headerImage = null;
            } else {
                this.headerImage = new Image(this.printer, header);
            }
            if (footer == null) {
                this.footerImage = null;
            } else {
                this.footerImage = new Image(this.printer, footer);
            }
            // regard header visiblity of the targeted SWT-Table
            this.headersVisible = this.printSource.getHeaderVisible();
            // regard line visiblity of the targeted SWT-Table
            this.linesVisible = this.printSource.getLinesVisible();
     
            final TableColumn[] columns = this.printSource.getColumns();
            // store table headers
            this.headTitles = new String[columns.length];
            for (int i = 0; i < columns.length; i++) {
                this.headTitles[i] = columns[i].getText();
                if (this.headTitles[i] == null) {
                    // replace invalid topic value
                    this.headTitles[i] = "";
                }
            }
            // collect all values to print
            final TableItem[] items = this.printSource.getItems();
            final String[][] allLines = new String[this.printSource.getItemCount()][];
            for (int i = 0; i < allLines.length; i++) {
                final String[] line = new String[columns.length];
                for (int j = 0; j < columns.length; j++) {
                    line[j] = items[i].getText(j);
                    if (line[j] == null) {
                        // replace invalid item value
                        line[j] = "";
                    }
                }
                allLines[i] = line;
            }
            // execute the printing in an extra thread to release the GUI
            new Thread() {
                @Override
                public void run() {
                    printWithoutSourceAccess(allLines, jobTitle, wrapColumn);
                }
            }.start();
        }
     
        /**
         * Executes the printing without referring to the SWT-Table
         * 
         * @param allLines
         *            table entries to print
         * @param jobTitle
         *            print job title to set
         * @param wrapColumn
         *            index of the column to wrapped on multiple lines
         */
        void printWithoutSourceAccess(final String[][] allLines,
                final String jobTitle, final int wrapColumn) {
            if (!calculateColumnPositions(allLines, wrapColumn)) {
                // error occured, abort
                return;
            }
            // finished initial calculations; start printing
            this.printer.startJob(jobTitle);
            this.pageCount = 0;
            newPage(this.headersVisible);
            // print all entries
            for (String[] singleLine : allLines) {
                if (!printLine(singleLine, wrapColumn)) {
                    // error occured: cancel printing
                    break;
                }
            }
            endPage(true);
            this.printer.endJob();
            // clean system resources
            disposeResources();
        }
     
        /**
         * Calculates margins and the line height regarding the chosen table
         * {@link Font}.
         */
        private void calculatePageFormat() {
            // get page format
            final Rectangle clientArea = this.printer.getClientArea();
            // calculate margins
            final Rectangle trim = this.printer.computeTrim(0, 0, 0, 0);
            // regard resolution
            final Point dpi = this.printer.getDPI();
            // left border
            this.leftMargin = dpi.x + trim.x;
            // maximum expansion to the right
            this.rightMargin = clientArea.width - dpi.x + trim.x + trim.width;
            // top border
            this.topMargin = dpi.y + trim.y;
            // height of one page
            this.bottomEnd = clientArea.height;
            // maximum expansion to the bottom
            this.bottomMargin = this.bottomEnd - dpi.y + trim.y + trim.height;
            // calculate height of one line
            this.lineHeight = (int) (1.2 * this.gc.getFontMetrics().getHeight());
            // calculate spacing between single columns
            this.verticalSpacing = 3 * this.gc.getFontMetrics()
                    .getAverageCharWidth();
            // calculate spacing between single lines
            this.horizontalSpacing = this.lineHeight / 5;
        }
     
        /**
         * Transfers the specified text with the given font in an image, which can
         * be used as the header image on top of the first page.
         * 
         * @param text
         *            text to transfer into image
         * @param topicFont
         *            font to use for the transfered text
         * @return created image
         */
        private Image createTopicHeader(final String text,
                final FontData[] topicFont) {
            // remember wrap settings
            final int tempWrapStart = this.wrapXstart;
            final int tempWrapEnd = this.wrapXend;
            // set topic wrap space
            this.wrapXstart = this.leftMargin;
            this.wrapXend = this.rightMargin;
            Font font;
            if (topicFont == null) {
                // no font selected, use the default
                font = new Font(this.printer, DEFAULT_TOPIC_FONT_NAME,
                        DEFAULT_TOPIC_FONT_SIZE, DEFAULT_TOPIC_FONT_STYLE);
            } else {
                // transfer selected font to new context
                font = new Font(this.printer, topicFont);
            }
            // use printer GC for calculations
            this.gc.setFont(font);
            // calculate height of one topic line
            final int topicHeight = this.gc.getFontMetrics().getHeight();
            // fit topic in print area (width)
            final String[] wrappedTopic = handleWrap(text).split("[\n]");
            // reset wrap column settings
            this.wrapXstart = tempWrapStart;
            this.wrapXend = tempWrapEnd;
     
            this.gc.setFont(this.tableFont);
     
            // calculate height including spacing underneath
            final int height = wrappedTopic.length * topicHeight + topicHeight / 2;
            // create Image and generate GC for draw operations
            final Image topicHeader = new Image(this.printer, this.rightMargin
                    - this.leftMargin, height);
            final GC headerGC = new GC(topicHeader);
            headerGC.setFont(font);
            // print fitting topic
            for (int i = 0; i < wrappedTopic.length; i++) {
                headerGC.drawString(wrappedTopic[i], 0, i * topicHeight);
            }
            // clean up
            headerGC.dispose();
            font.dispose();
            // finished
            return topicHeader;
        }
     
        /**
         * Calculates the width and position of the columns regarding all entries to
         * print. Stores the area for the wrap column.
         * 
         * @param allLines
         *            table entries to print
         * @param wrapColumn
         *            index of the column to be wrapped into multiple lines
         * @return all columns fit on the chosen page format (width)
         */
        private boolean calculateColumnPositions(final String[][] allLines,
                final int wrapColumn) {
            // calculate preferred width of all columns
            final int[] maxColumnWidth = new int[this.headTitles.length];
            if (this.headersVisible) {
                this.gc.setFont(this.headerFont);
                // regard table headers
                for (int i = 0; i < maxColumnWidth.length; i++) {
                    maxColumnWidth[i] = this.gc.stringExtent(this.headTitles[i]).x;
                }
                this.gc.setFont(this.tableFont);
            } else {
                // initializie minimum width of 0
                for (int i = 0; i < maxColumnWidth.length; i++) {
                    maxColumnWidth[i] = 0;
                }
            }
            // regard all entries
            for (String[] singleLine : allLines) {
                // regard minimum width of each cell
                for (int i = 0; i < maxColumnWidth.length; i++) {
                    maxColumnWidth[i] = Math.max(maxColumnWidth[i], this.gc
                            .stringExtent(singleLine[i]).x);
                }
            }
            // do not forget spacing between columns
            for (int i = 0; i < maxColumnWidth.length; i++) {
                maxColumnWidth[i] += this.verticalSpacing;
            }
            // summarize the width of all columns without wrap
            int minWidth = 0;
            for (int i = 0; i < maxColumnWidth.length; i++) {
                // ignore wrapColumn at this point
                if (wrapColumn != i) {
                    minWidth += maxColumnWidth[i];
                }
            }
            // how much space is left for the wrapColumn?
            final int wrapWidth = this.rightMargin - this.leftMargin
                    - this.verticalSpacing - minWidth;
            // wrap column need more space then available
            boolean wrapEnabled = (wrapColumn > -1)
                    && (wrapColumn < maxColumnWidth.length);
            if ((wrapWidth < 0)
                    || (wrapEnabled && (wrapWidth <= maxColumnWidth[wrapColumn]) && (wrapWidth < this.verticalSpacing))) {
                // the other columns need too much space
                showError(ERROR_TABLEWIDTH);
                return false;
            }
            // calculat X-coordinates for the beginning of each column
            this.columnPosX = new int[maxColumnWidth.length];
            // first column starts at the left margin
            this.columnPosX[0] = this.leftMargin;
            if (wrapEnabled) {
                // all columns before and the wrapColumn itself
                for (int i = 0; i < wrapColumn; i++) {
                    this.columnPosX[i + 1] = this.columnPosX[i] + maxColumnWidth[i];
                }
                // all columns behind the wrapColumn
                if ((this.columnPosX.length - 1) > wrapColumn) {
                    // last columns ends at the right margin
                    this.columnPosX[this.columnPosX.length - 1] = this.rightMargin
                            - maxColumnWidth[maxColumnWidth.length - 1];
                    for (int i = (maxColumnWidth.length - 2); i > wrapColumn; i--) {
                        // previous column in front of its follower
                        this.columnPosX[i] = this.columnPosX[i + 1]
                                - maxColumnWidth[i];
                    }
                }
            } else {
                // calculate position regarding column sizes
                for (int i = 0; i < (this.columnPosX.length - 1); i++) {
                    this.columnPosX[i + 1] = this.columnPosX[i] + maxColumnWidth[i];
                }
            }
            this.wrapXend -= this.verticalSpacing;
            if (wrapEnabled) {
                // determine the wrap area
                this.wrapXstart = this.columnPosX[wrapColumn];
                if ((wrapColumn + 1) < this.columnPosX.length) {
                    this.wrapXend = this.columnPosX[wrapColumn + 1]
                            - (2 * this.verticalSpacing / 3);
                } else {
                    // wrapColumn is the last column
                    this.wrapXend = this.rightMargin;
                }
                // last column ends at right margin
                this.endX = this.rightMargin;
            } else {
                this.wrapXstart = 0;
                this.wrapXend = 0;
                // calculate end of the last column
                this.endX = this.columnPosX[this.columnPosX.length - 1]
                        + maxColumnWidth[maxColumnWidth.length - 1];
            }
            return true;
        }
     
        /**
         * Prints one line containing the specified values.
         * 
         * @param entries
         *            values of the line to print
         * @param wrapColumn
         *            index of the column to wrap
         * 
         * @return printing successful
         */
        private boolean printLine(final String[] entries, final int wrapColumn) {
            String[] wrapEntry = null;
            int entryHeight = this.lineHeight;
            if ((wrapColumn > -1) && (wrapColumn < entries.length)) {
                // insert line separators where needed
                wrapEntry = handleWrap(entries[wrapColumn]).split("[\n]");
                entryHeight *= wrapEntry.length;
            }
            // calculate where the entry would end
            int newY = this.posY + entryHeight;
            if (newY > this.bottomMargin) {
                // the line to print exceeds the page format: end this page
                endPage(false);
                // start a new page
                newPage(this.headersVisible);
                // calculate where the entry would end
                newY = this.posY + entryHeight;
                if (newY > this.bottomMargin) {
                    // the line to print exceeds the whole page: abort
                    showError(ERROR_ENTRYLENGTH1 + (wrapColumn + 1)
                            + ERROR_ENTRYLENGTH2);
                    return false;
                }
            }
            // entry fits on the current page: print it
            for (int i = 0; i < entries.length; i++) {
                if ((wrapEntry != null) && (i == wrapColumn)) {
                    for (int j = 0; j < wrapEntry.length; j++) {
                        // draw on multiple lines
                        this.gc.drawString(wrapEntry[j], this.columnPosX[i],
                                this.posY + (j * this.lineHeight));
                    }
                } else {
                    // ignore possible line separators in non-wrap-columns
                    this.gc.drawString(entries[i], this.columnPosX[i], this.posY);
                }
            }
            if (this.linesVisible) {
                // only if this is not the first line in a headless table
                if (this.posY != this.tableTop) {
                    // draw the horizontal line over the printed table entry
                    this.gc.drawLine(this.leftMargin, this.posY
                            - this.horizontalSpacing - 1, this.endX, this.posY
                            - this.horizontalSpacing - 1);
                }
                // add an additional spacing below the printed entry
                newY += this.horizontalSpacing;
            }
            // store new vertical position for the next entry
            this.posY = newY;
            return true;
        }
     
        /**
         * Inserts - if neccessary - line separators in the given entry to ensure
         * that the entry fits in the column to wrap.
         * 
         * @param entry
         *            single entry in the column to wrap
         * 
         * @return fitting entry
         */
        private String handleWrap(final String entry) {
            if (this.gc.stringExtent(entry).x <= (this.wrapXend - this.wrapXstart)) {
                // entry fits in one line
                return entry;
            }
            int wrapIndex = this.wrapXstart;
            final StringBuffer textBuffer = new StringBuffer();
            StringBuffer wordBuffer = new StringBuffer();
            char singleChar;
            for (int i = 0; i < (entry.length() + 1); i++) {
                if (i == entry.length()) {
                    singleChar = ' ';
                } else {
                    singleChar = entry.charAt(i);
                }
                if (Character.isWhitespace(singleChar)) {
                    // end of word reached
                    final String word = wordBuffer.toString();
                    // calculate needed space for this word
                    final int wordWidth = this.gc.stringExtent(word).x;
                    if (((wrapIndex + wordWidth) > this.wrapXend)
                            && (wrapIndex != this.wrapXstart)) {
                        /*
                         * required width exceeds the space that is left: new line
                         */
                        textBuffer.append('\n');
                        wrapIndex = this.wrapXstart;
                    }
                    if ((wrapIndex + wordWidth) < this.wrapXend) {
                        // add the fitting word
                        wrapIndex += wordWidth;
                        textBuffer.append(word);
                    } else {
                        char wordChar;
                        // whole wrapColumn is not big enough for this word
                        for (int j = 0; j < word.length(); j++) {
                            if (wrapIndex > this.wrapXend) {
                                // character does not fit: new line
                                textBuffer.append('\n');
                                wrapIndex = this.wrapXstart;
                            }
                            // add each single character
                            wordChar = word.charAt(j);
                            textBuffer.append(wordChar);
                            wrapIndex += this.gc.getAdvanceWidth(wordChar);
                        }
                    }
                    if ((singleChar == 0x0a) || (singleChar == 0x0d)) {
                        textBuffer.append('\n');
                    } else if ((singleChar == ' ') && (i < entry.length())) {
                        textBuffer.append(singleChar);
                    }
                    // reset word buffer
                    wordBuffer = new StringBuffer();
                } else {
                    wordBuffer.append(singleChar);
                }
            }
            return textBuffer.toString();
        }
     
        /**
         * Request new page on printer, increase page counter by one and add topic -
         * if available - on top and table headers - according to their visibility -
         * underneath.
         * 
         * @param printTableHeader
         *            print the column topics
         */
        private void newPage(final boolean printTableHeader) {
            // request new page on printer
            this.printer.startPage();
            // increase page counter
            this.pageCount++;
            // begin on top
            this.posY = this.topMargin;
            // insert header
            if ((this.pageCount == 1) && (this.headerImage != null)) {
                this.posY += this.headerOffset;
                this.gc.drawImage(this.headerImage, this.leftMargin, this.posY);
                // calculate top position for the table header
                this.posY = Math.max(this.topMargin, this.posY
                        + this.headerImage.getImageData().height);
            }
            // store horizontal begin of the table for the vertical lines
            this.tableTop = this.posY;
            if (printTableHeader) {
                this.gc.setFont(this.headerFont);
                // add additional spacing under the headers
                this.posY += this.gc.getFontMetrics().getHeight()
                        + this.horizontalSpacing;
                if (this.posY > this.bottomMargin) {
                    /*
                     * header is so big, there is not even enough space for the
                     * column topics
                     */
                    this.gc.setFont(this.tableFont);
                    // end this page and start a new one without header image
                    endPage(false);
                    newPage(false);
                    // set the table header font again
                    this.gc.setFont(this.headerFont);
                    // recalculate the vertical position after the table header
                    this.posY += this.gc.getFontMetrics().getHeight()
                            + this.horizontalSpacing;
                }
                for (int i = 0; i < this.headTitles.length; i++) {
                    // print each table header
                    this.gc.drawString(this.headTitles[i], this.columnPosX[i],
                            this.tableTop);
                }
                // draw vertical line underneath
                this.gc.drawLine(this.leftMargin, this.posY, this.endX, this.posY);
                // add more spacing
                this.posY += (3 * this.horizontalSpacing) / 2;
                // reset table entry font
                this.gc.setFont(this.tableFont);
            }
        }
     
        /**
         * Finishes the current page by drawing the vertical column lines, inserting
         * the page count on the bottom and telling the printer to end this page.
         * 
         * @param printFooter
         *            print the footer image (if available) before closing this page
         */
        private void endPage(final boolean printFooter) {
            if (printFooter && (this.footerImage != null)) {
                if ((this.posY + this.footerImage.getImageData().height) > this.bottomMargin) {
                    /*
                     * footer does not fit on the bottom of this page, end it and
                     * start a new one, just for the footer
                     */
                    endPage(false);
                    newPage(false);
                }
                this.gc.drawImage(this.footerImage, this.leftMargin,
                        this.bottomMargin - this.footerImage.getImageData().height);
            }
            if (this.tableTop != this.posY) {
                int posX;
                // draw each vertical line between columns
                for (int i = 1; i < this.columnPosX.length; i++) {
                    posX = this.columnPosX[i] - (this.verticalSpacing / 3);
                    this.gc.drawLine(posX, this.tableTop, posX, this.posY);
                }
            }
            final String pageNumber = Integer.toString(this.pageCount);
            // calculate size of the page count string
            final int width = this.gc.stringExtent(pageNumber).x;
            // get horizontal mid for the page count
            final int pageX = (this.rightMargin - ((this.rightMargin - this.leftMargin) / 2))
                    - (width / 2);
            // get the vertical mid in the bottom margin for the page count
            final int pageY = this.bottomEnd
                    - ((this.bottomEnd - this.bottomMargin + this.lineHeight) / 2);
            // draw the page count
            this.gc.drawString(pageNumber, pageX, pageY);
            // tell printer to end current page
            this.printer.endPage();
        }
     
        /**
         * Disposes all system resources like {@link Image}s and {@link Font}s as
         * well as the used {@link GC} and {@link Printer}.
         * <p>
         * Does not affect given parameter values. Only self-allocated resources are
         * disposed.
         * </p>
         */
        private void disposeResources() {
            if (this.headerImage != null) {
                this.headerImage.dispose();
            }
            if (this.footerImage != null) {
                this.footerImage.dispose();
            }
            this.tableFont.dispose();
            this.headerFont.dispose();
            this.gc.dispose();
            this.printer.dispose();
        }
     
        /**
         * Generates a {@link MessageBox} to display the occured error by refering
         * to the specified error message.
         * 
         * @param message
         *            error message to display
         */
        private void showError(final String message) {
            // clean up
            disposeResources();
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    // get super ordinated shell for message box
                    final Shell parentShell;
                    if (SWTTablePrinter.this.printSource.isDisposed()) {
                        // no access to origin shell, create a new one
                        parentShell = new Shell();
                    } else {
                        // get current shell
                        parentShell = SWTTablePrinter.this.printSource.getShell();
                    }
                    // generate message box
                    final MessageBox box = new MessageBox(parentShell, SWT.OK
                            | SWT.APPLICATION_MODAL | SWT.ICON_ERROR);
                    // set message box topic and message
                    box.setText(ERROR_TOPIC);
                    box.setMessage(message);
                    // display message box
                    box.open();
                }
            });
        }
    }

    SorTableTester
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    
    package org.hermeneutix.utilities.swt;
     
    import java.util.Calendar;
     
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.layout.GridData;
    import org.eclipse.swt.layout.GridLayout;
    import org.eclipse.swt.printing.PrintDialog;
    import org.eclipse.swt.printing.PrinterData;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.swt.widgets.Event;
    import org.eclipse.swt.widgets.Label;
    import org.eclipse.swt.widgets.Listener;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.swt.widgets.TableColumn;
    import org.eclipse.swt.widgets.TableItem;
     
    public class SorTableTester {
     
        // date format: dd.
        private static final String DATE_FORMAT1 = "%1$td.";
        // date format: dd.mm.
        private static final String DATE_FORMAT2 = "%1$td.%1$tm.";
        // date format: dd.mm.YYYY
        private static final String DATE_FORMAT3 = "%1$td.%1$tm.%1$tY";
        // date format: dd.mm. HH:MM
        private static final String DATETIME_FORMAT1 = "%1$td.%1$tm. %1$tH:%1$tM";
        // date format: dd.mm.YYYY HH:MM
        private static final String DATETIME_FORMAT2 = "%1$td.%1$tm.%1$tY %1$tH:%1$tM";
     
        // number of items to display (try higher count to check out performance)
        private static final int ITEM_COUNT = 10;
     
        // calendar instance for the date-time columns
        private final static Calendar date = Calendar.getInstance();
     
        public static void main(String[] args) {
            // create the shell to test the SorTable in
            final Shell shell = new Shell();
            shell.setLayout(new GridLayout(1, true));
            // prepare line for the print button and the search field
            final Composite topLine = new Composite(shell, SWT.NONE);
            topLine.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            topLine.setLayout(new GridLayout(4, true));
            // create the SorTable itself
            final SorTable table = createSorTable(shell);
            // insert a button in the top line to print the table contents
            final Button printButton = new Button(topLine, SWT.PUSH);
            printButton.setText("Print");
            printButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent ev) {
                    // offer possibility to set general print settings
                    final PrinterData data = new PrintDialog(shell, SWT.BORDER)
                            .open();
                    // was the dialog canceled?
                    if (data != null) {
                        // dialog finished successfully
                        table.print(data, "Printing Example",
                                "Printing Example - SorTableTester", 3,
                                // start printing with default fonts
                                null, null, null);
                    }
                }
            });
            new Label(topLine, SWT.NONE).setText("Filter: ");
            // create a text field for filtering the table contents
            table.createSearchField(topLine, SWT.BORDER).setLayoutData(
                    new GridData(GridData.HORIZONTAL_ALIGN_FILL));
            final Button insertButton = new Button(topLine, SWT.PUSH);
            insertButton.setText("Insert an item regarding sorting");
            insertButton.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(final SelectionEvent ev) {
                    shell.getDisplay().asyncExec(new Runnable() {
                        public void run() {
                            final TableItem created = table.insertItem(SWT.NONE,
                                    createRandomEntry(table.getItems().length));
                            applyColors(created);
                            table.setSelection(created);
                        }
                    });
                }
            });
            // display shell
            shell.setSize(800, 600);
            shell.open();
            // SWT-Shell-Loop
            final Display display = shell.getDisplay();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }
        }
     
        private static SorTable createSorTable(final Composite parent) {
            // create an array of table headers
            final String[] header = new String[9];
            for (int column = 0; column < 9; column++) {
                header[column] = "Column" + (1 + column);
            }
            // create an array of sample values
            final String[][] values = new String[ITEM_COUNT][];
            for (int i = 0; i < ITEM_COUNT; i++) {
                values[i] = createRandomEntry(i);
            }
            // create the table itself
            final SorTable table = SorTable.createSorTable(parent,
                    SWT.FULL_SELECTION | SWT.MULTI, header, values);
            table.setLayoutData(new GridData(GridData.FILL_BOTH));
            /*
             * after creation of a table with visible headers, the columns need
             * separate setting of width
             */
            for (TableColumn singleColumn : table.getColumns()) {
                singleColumn.pack();
            }
            // enable compare mode for simple numbers
            table.setSortingMethod(table.getColumn(0), SorTable.COMPARE_INT);
            // enable compare mode for decimal numbers
            table.setSortingMethod(table.getColumn(1), SorTable.COMPARE_DOUBLE);
            // enable compare mode for decimal numbers
            table.setSortingMethod(table.getColumn(2), SorTable.COMPARE_DOUBLE);
            // by default the contained values are compared as texts
            // table.setSortingMethod(table.getColumn(3), SorTable.COMPARE_TEXT);
            // enable compare mode for date formats
            table.setSortingMethod(table.getColumn(4), SorTable.COMPARE_DATETIME);
            table.setSortingMethod(table.getColumn(5), SorTable.COMPARE_DATETIME);
            table.setSortingMethod(table.getColumn(6), SorTable.COMPARE_DATETIME);
            table.setSortingMethod(table.getColumn(7), SorTable.COMPARE_DATETIME);
            table.setSortingMethod(table.getColumn(8), SorTable.COMPARE_DATETIME);
            /*
             * enabled editing of three columns (equivalent to
             * table.setEditable(true, new int[] {1,2,3}))
             */
            table.setEditable(true, 1, 2, 3);
            /*
             * highlight first column items
             */
            table.addColorListener(new Listener() {
                public void handleEvent(final Event event) {
                    for (TableItem singleItem : table.getItems()) {
                        applyColors(singleItem);
                    }
                }
            });
            // initial coloring
            for (TableItem singleItem : table.getItems()) {
                applyColors(singleItem);
            }
            return table;
        }
        
        static void applyColors(final TableItem target) {
            if (Integer.parseInt(target.getText(0)) < 500) {
                target.setBackground(0, target.getDisplay()
                        .getSystemColor(SWT.COLOR_CYAN));
                target.setForeground(target.getDisplay()
                        .getSystemColor(SWT.COLOR_BLUE));
            }
        }
     
        static String[] createRandomEntry(final int index) {
            String[] entry = new String[9];
            // first column: simple int values
            entry[0] = Integer.toString((int) (Math.random() * 1000));
            // second column: simple decimal number
            entry[1] = Double.toString(Math.random() * 10);
            /*
             * third column: (possible negative) decimal number with additional unit
             * text
             */
            entry[2] = Math.round((Math.random() - 0.5) * 100) + "."
                    + ((int) (Math.random() * 1000)) + " °C";
            // fourth column: text column
            entry[3] = "Entry" + (1 + index) + " long enough to force "
                    + "a wrap in this column when printed on an A3 format\n" +
                    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            // change the displayed date
            date.add(Calendar.MINUTE, (int) ((Math.random() - 0.5) * 1000000));
            // fifth column: date in format dd.
            entry[4] = String.format(DATE_FORMAT1, date);
            // sixth column: date in format dd.mm.
            entry[5] = String.format(DATE_FORMAT2, date);
            // seventh column: date in format dd.mm.YYYY
            entry[6] = String.format(DATE_FORMAT3, date);
            // eigth column: date in format dd.mm. HH:MM
            entry[7] = String.format(DATETIME_FORMAT1, date);
            // nineth column: date in format dd.mm.YYYY HH:MM
            entry[8] = String.format(DATETIME_FORMAT2, date);
     
            return entry;
        }
    }
    Geändert von Carron (11.06.10 um 21:46 Uhr) Grund: überarbeitet
     

  2. #2
    Avatar von Carron
    Carron Carron ist offline Mitglied Silber
    Registriert seit
    May 2010
    Ort
    Berlin
    Beiträge
    61
    Nach einer Runde Bugfixing und ein paar kleinen Erweiterungen, habe ich mir erlaubt den Inhalt dieses Threads wieder auf den neusten Stand zu bringen.

    In der SorTable gibt es jetzt die Möglichkeit via addColorListener() auf das Verwerfen der Eintragsfärbung zu reagieren und quasi seine eigenen Farben wieder anzuwenden.
    Darüber hinaus hat der SWTTablePrinter jetzt noch die Möglichkeit statt des Überschriften-Strings eine Kopfzeile (für die erste Seite) und eine Fußzeile (für die letzte Seite) in Image-Form entgegen zu nehmen.
    So ist es möglich einfache Protokolle aus der Tabellenform mit einem (zum Beispiel firmeneigenen) Header zu versehen und bei Bedarf eine/mehrere Unterschriftenzeilen in die Fußzeile der letzten Seite zu legen.
    Diese neuen Druckoptionen reicht die SorTable jetzt auch weiter, so dass es zwei neue print()-Methoden gibt.

    Ansonsten sind durch den Dialog hier im Forum und ein wenig eigenes Testen noch hier und da kleine Fehler verschwunden.


    Vielleicht hilft es ja mal jemandem
    Bis dahin gibt es ja vielleicht noch Hinweise für Performance-Verbesserungen oder mögliche sinnvolle Erweiterungen, die ich einbauen könnte


    Viele Grüße
    Carron


    PS: ich hab das Ganze auch nochmal mit deutschen Kommentaren, falls das jemand haben wollen würde, müsste ich nur die firmeninternen Dinge entfernen und könnte das quasi auch liefern (
    Geändert von Carron (16.06.10 um 23:40 Uhr)
     

Ähnliche Themen

  1. SWT Table sortieren mit TableEditor(Combobox)
    Von draig im Forum Swing, Java2D/3D, SWT, JFace
    Antworten: 0
    Letzter Beitrag: 17.08.08, 17:24
  2. SWT Table sortieren
    Von elsausb im Forum Swing, Java2D/3D, SWT, JFace
    Antworten: 16
    Letzter Beitrag: 29.07.08, 12:04
  3. SWT/JFace-Table(Viewer) sortieren Problem
    Von Luxor im Forum Swing, Java2D/3D, SWT, JFace
    Antworten: 0
    Letzter Beitrag: 11.12.07, 13:40
  4. Sortieren einer SWT table
    Von stefangraf im Forum Swing, Java2D/3D, SWT, JFace
    Antworten: 0
    Letzter Beitrag: 28.02.07, 17:44
  5. swt Table sortieren
    Von Drizztd im Forum Swing, Java2D/3D, SWT, JFace
    Antworten: 3
    Letzter Beitrag: 08.08.06, 19:01

Stichworte